diff --git a/packages/bruno-app/src/components/RunnerResults/index.jsx b/packages/bruno-app/src/components/RunnerResults/index.jsx index 7956eb1ad..ba6521be5 100644 --- a/packages/bruno-app/src/components/RunnerResults/index.jsx +++ b/packages/bruno-app/src/components/RunnerResults/index.jsx @@ -3,16 +3,13 @@ import path from 'utils/common/path'; import { useDispatch } from 'react-redux'; import { get, cloneDeep } from 'lodash'; import { runCollectionFolder, cancelRunnerExecution, mountCollection, updateRunnerConfiguration } from 'providers/ReduxStore/slices/collections/actions'; -import { resetCollectionRunner } from 'providers/ReduxStore/slices/collections'; -import { findItemInCollection, getTotalRequestCountInCollection } from 'utils/collections'; -import { IconRefresh, IconCircleCheck, IconCircleX, IconCircleOff, IconCheck, IconX, IconRun, IconLoader2 } from '@tabler/icons'; +import { resetCollectionRunner, updateRunnerTagsDetails } from 'providers/ReduxStore/slices/collections'; +import { findItemInCollection, getTotalRequestCountInCollection, areItemsLoading, getRequestItemsForCollectionRun } from 'utils/collections'; +import { IconRefresh, IconCircleCheck, IconCircleX, IconCircleOff, IconCheck, IconX, IconRun, IconExternalLink } from '@tabler/icons'; import ResponsePane from './ResponsePane'; import StyledWrapper from './StyledWrapper'; -import { areItemsLoading } from 'utils/collections'; import RunnerTags from './RunnerTags/index'; import RunConfigurationPanel from './RunConfigurationPanel'; -import { getRequestItemsForCollectionRun } from 'utils/collections/index'; -import { updateRunnerTagsDetails } from 'providers/ReduxStore/slices/collections/index'; const getDisplayName = (fullPath, pathname, name = '') => { let relativePath = path.relative(fullPath, pathname); @@ -42,49 +39,61 @@ const anyTestFailed = (item) => { item.postResponseTestStatus === 'fail'; }; +// === Centralized filters definition === +const FILTERS = { + all: { + label: 'All', + predicate: () => true, + resultFilter: (results) => results + }, + passed: { + label: 'Passed', + predicate: (item) => allTestsPassed(item), + resultFilter: (results) => results?.filter((r) => r.status === 'pass') + }, + failed: { + label: 'Failed', + predicate: (item) => anyTestFailed(item), + resultFilter: (results) => results?.filter((r) => ['fail', 'error'].includes(r.status)) + }, + skipped: { + label: 'Skipped', + predicate: (item) => item.status === 'skipped', + resultFilter: (results) => results + } +}; + +// === Reusable filter button === +const FilterButton = ({ label, count, active, onClick }) => ( + +); + export default function RunnerResults({ collection }) { const dispatch = useDispatch(); const [selectedItem, setSelectedItem] = useState(null); const [delay, setDelay] = useState(null); + const [activeFilter, setActiveFilter] = useState('all'); const [selectedRequestItems, setSelectedRequestItems] = useState([]); const [configureMode, setConfigureMode] = useState(false); - // ref for the runner output body const runnerBodyRef = useRef(); - const autoScrollRunnerBody = () => { - if (runnerBodyRef?.current) { - // mimics the native terminal scroll style - runnerBodyRef.current.scrollTo(0, 100000); - } - }; - - useEffect(() => { - if (!collection.runnerResult) { - setSelectedItem(null); - } - autoScrollRunnerBody(); - }, [collection, setSelectedItem]); - - useEffect(() => { - const runnerInfo = get(collection, 'runnerResult.info', {}); - if (runnerInfo.status === 'running') { - setConfigureMode(false); - } - }, [collection.runnerResult]); - - useEffect(() => { - const savedConfiguration = get(collection, 'runnerConfiguration', null); - if (savedConfiguration) { - if (savedConfiguration.selectedRequestItems && configureMode) { - setSelectedRequestItems(savedConfiguration.selectedRequestItems); - } - if (savedConfiguration.delay !== undefined && delay === null) { - setDelay(savedConfiguration.delay); - } - } - }, [collection.runnerConfiguration, configureMode, delay]); - const collectionCopy = cloneDeep(collection); const runnerInfo = get(collection, 'runnerResult.info', {}); @@ -126,6 +135,63 @@ export default function RunnerResults({ collection }) { }) .filter(Boolean); + const activeFilterConfig = FILTERS[activeFilter]; + const filteredItems = items.filter(activeFilterConfig.predicate); + + const filterTestResults = (results) => { + if (!results || !Array.isArray(results)) return []; + return activeFilterConfig.resultFilter(results); + }; + + const autoScrollRunnerBody = () => { + if (runnerBodyRef?.current) { + const element = runnerBodyRef.current; + const scrollThreshold = 100; // pixels from bottom to consider "at bottom" + const isNearBottom + = element.scrollHeight - element.scrollTop - element.clientHeight < scrollThreshold; + + // Only auto-scroll if user is already near the bottom + if (isNearBottom) { + // mimics the native terminal scroll style + element.scrollTo(0, 100000); + } + } + }; + + useEffect(() => { + if (!collection.runnerResult) { + setSelectedItem(null); + } + autoScrollRunnerBody(); + }, [collection, setSelectedItem]); + + useEffect(() => { + // Auto-scroll when items are added or updated during execution + // Only scrolls if user is already at/near the bottom + if (filteredItems.length > 0) { + autoScrollRunnerBody(); + } + }, [filteredItems]); + + useEffect(() => { + const runnerInfo = get(collection, 'runnerResult.info', {}); + if (runnerInfo.status === 'running') { + setConfigureMode(false); + } + }, [collection.runnerResult]); + + useEffect(() => { + const savedConfiguration = get(collection, 'runnerConfiguration', null); + if (savedConfiguration) { + if (savedConfiguration.selectedRequestItems && configureMode) { + setSelectedRequestItems(savedConfiguration.selectedRequestItems); + } + if (savedConfiguration.delay !== undefined && delay === null) { + setDelay(savedConfiguration.delay); + } + } + }, [collection.runnerConfiguration, configureMode, delay]); + const ensureCollectionIsMounted = () => { if(collection.mountStatus === 'mounted'){ return; @@ -192,14 +258,14 @@ export default function RunnerResults({ collection }) { }, [tagsEnabled]); const totalRequestsInCollection = getTotalRequestCountInCollection(collectionCopy); - const passedRequests = items.filter(allTestsPassed); - const failedRequests = items.filter(anyTestFailed); + const filterCounts = { + all: items.length, + passed: items.filter(allTestsPassed).length, + failed: items.filter(anyTestFailed).length, + skipped: items.filter((i) => i.status === 'skipped').length + }; - const skippedRequests = items.filter((item) => { - return item.status === 'skipped'; - }); let isCollectionLoading = areItemsLoading(collection); - if (!items || !items.length) { return ( @@ -285,27 +351,57 @@ export default function RunnerResults({ collection }) { return ( -
-
- Runner - + {/* Filter Bar and Actions */} +
+
+
+ + Filter by: + +
+
+ {Object.entries(FILTERS).map(([key, { label }]) => ( + setActiveFilter(key)} + /> + ))} +
- {runnerInfo.status !== 'ended' && runnerInfo.cancelTokenUid && ( - - )} + + {runnerInfo.status !== 'ended' && runnerInfo.cancelTokenUid ? ( +
+ +
+ ) : runnerInfo.status === 'ended' ? ( +
+ + +
+ ) : null}
-
- Total Requests: {items.length}, Passed: {passedRequests.length}, Failed: {failedRequests.length}, Skipped:{' '} - {skippedRequests.length} -
{tagsEnabled && areTagsAdded && (
Tags: @@ -326,8 +422,8 @@ export default function RunnerResults({ collection }) { : null} {/* Items list */} -
- {items.map((item) => { +
+ {filteredItems.map((item) => { return (
@@ -371,7 +467,7 @@ export default function RunnerResults({ collection }) {
    {item.preRequestTestResults - ? item.preRequestTestResults.map((result) => ( + ? filterTestResults(item.preRequestTestResults).map((result) => (
  • {result.status === 'pass' ? ( @@ -391,7 +487,7 @@ export default function RunnerResults({ collection }) { )) : null} {item.postResponseTestResults - ? item.postResponseTestResults.map((result) => ( + ? filterTestResults(item.postResponseTestResults).map((result) => (
  • {result.status === 'pass' ? ( @@ -411,7 +507,7 @@ export default function RunnerResults({ collection }) { )) : null} {item.testResults - ? item.testResults.map((result) => ( + ? filterTestResults(item.testResults).map((result) => (
  • {result.status === 'pass' ? ( @@ -430,7 +526,7 @@ export default function RunnerResults({ collection }) {
  • )) : null} - {item.assertionResults?.map((result) => ( + {filterTestResults(item.assertionResults).map((result) => (
  • {result.status === 'pass' ? ( @@ -454,42 +550,50 @@ export default function RunnerResults({ collection }) { ); })}
- - {runnerInfo.status === 'ended' ? ( -
- - - -
- ) : null}
+ {selectedItem ? (
-
- {selectedItem.displayName} - - {allTestsPassed(selectedItem) ? - - : null} - {anyTestFailed(selectedItem) ? - - : null} - {selectedItem.status === 'skipped' ? - - : null} - +
+
+ {selectedItem.displayName} + + {allTestsPassed(selectedItem) + ? + : null} + {anyTestFailed(selectedItem) + ? + : null} + {selectedItem.status === 'skipped' + ? + : null} + +
+
- ) : null} + ) : ( +
+
+
+ +
+

+ Click on the status code to view the response +

+
+
+ )}
); diff --git a/tests/runner/collection-run.ts b/tests/runner/collection-run.ts index d683712de..8ac19857c 100644 --- a/tests/runner/collection-run.ts +++ b/tests/runner/collection-run.ts @@ -1,27 +1,24 @@ -import { test, expect } from '../../playwright'; +import { test } from '../../playwright'; +import { setSandboxMode, runCollection, validateRunnerResults } from '../utils/page/index'; test.describe.parallel('Collection Run', () => { test('Run bruno-testbench in Developer Mode', async ({ pageWithUserData: page }) => { test.setTimeout(2 * 60 * 1000); - await page.getByText('bruno-testbench').click(); - await page.getByLabel('Developer Mode(use only if').check(); - await page.getByRole('button', { name: 'Save' }).click(); + // Set up developer mode + await setSandboxMode(page, 'bruno-testbench', 'developer'); + + // Select environment await page.locator('.environment-selector').nth(1).click(); await page.locator('.dropdown-item').getByText('Prod').click(); - await page.locator('.collection-actions').hover(); - await page.locator('.collection-actions .icon').click(); - await page.getByText('Run', { exact: true }).click(); - await page.getByRole('button', { name: 'Run Collection' }).click(); - await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 }); - const result = await page.getByText('Total Requests: ').innerText(); - const [totalRequests, passed, failed, skipped] = result - .match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/) - .slice(1); + // Run the collection + await runCollection(page, 'bruno-testbench'); - await expect(parseInt(failed)).toBe(0); - await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed)); + // Validate test results + await validateRunnerResults(page, { + failed: 0 + }); }); test.fixme('Run bruno-testbench in Safe Mode', async ({ pageWithUserData: page }) => { @@ -32,18 +29,41 @@ test.describe.parallel('Collection Run', () => { await page.getByRole('button', { name: 'Save' }).click(); await page.locator('.environment-selector').nth(1).click(); await page.locator('.dropdown-item').getByText('Prod').click(); - await page.locator('.collection-actions').hover(); - await page.locator('.collection-actions .icon').click(); + const collectionContainer = page.locator('.collection-name').filter({ hasText: 'bruno-testbench' }); + await collectionContainer.locator('.collection-actions').hover(); + await collectionContainer.locator('.collection-actions .icon').waitFor({ state: 'visible' }); + await collectionContainer.locator('.collection-actions .icon').click(); await page.getByText('Run', { exact: true }).click(); - await page.getByRole('button', { name: 'Run Collection' }).click(); + // Wait for the runner tab to open + // If there are existing results, reset first, otherwise wait for Run Collection button + const resetButton = page.getByRole('button', { name: 'Reset' }); + const runCollectionButton = page.getByRole('button', { name: 'Run Collection' }); + + // Check if Reset button is visible (means there are existing results) + const resetVisible = await resetButton.isVisible().catch(() => false); + if (resetVisible) { + await resetButton.click(); + // Wait a bit for the reset to complete + await page.waitForTimeout(500); + } + + // Now wait for and click Run Collection button + await runCollectionButton.waitFor({ state: 'visible', timeout: 10000 }); + await runCollectionButton.click(); await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 }); - const result = await page.getByText('Total Requests: ').innerText(); - const [totalRequests, passed, failed, skipped] = result - .match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/) - .slice(1); + // Parse and validate test results from filter buttons + const allButton = page.locator('button').filter({ hasText: /^All/ }); + const passedButton = page.locator('button').filter({ hasText: /^Passed/ }); + const failedButton = page.locator('button').filter({ hasText: /^Failed/ }); + const skippedButton = page.locator('button').filter({ hasText: /^Skipped/ }); - await expect(parseInt(failed)).toBe(0); - await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed)); + const totalRequests = parseInt(await allButton.locator('span').innerText()); + const passed = parseInt(await passedButton.locator('span').innerText()); + const failed = parseInt(await failedButton.locator('span').innerText()); + const skipped = parseInt(await skippedButton.locator('span').innerText()); + + await expect(failed).toBe(0); + await expect(passed).toBe(totalRequests - skipped - failed); }); }); \ No newline at end of file diff --git a/tests/scripting/inbuilt-libraries/jsonwebtoken/jsonwebtoken.spec.ts b/tests/scripting/inbuilt-libraries/jsonwebtoken/jsonwebtoken.spec.ts index 74bb23c6d..dacdf563b 100644 --- a/tests/scripting/inbuilt-libraries/jsonwebtoken/jsonwebtoken.spec.ts +++ b/tests/scripting/inbuilt-libraries/jsonwebtoken/jsonwebtoken.spec.ts @@ -1,64 +1,40 @@ -import { test, expect } from '../../../../playwright'; +import { test } from '../../../../playwright'; +import { setSandboxMode, runCollection, validateRunnerResults } from '../../../utils/page'; test.describe.serial('jwt collection success', () => { test('developer mode', async ({ pageWithUserData: page }) => { - // init dev mode - await page.getByTitle('jsonwebtoken').click(); - await page.getByLabel('Developer Mode(use only if').check(); - await page.getByRole('button', { name: 'Save' }).click(); - test.setTimeout(2 * 60 * 1000); + // Set up developer mode + await setSandboxMode(page, 'jsonwebtoken', 'developer'); + // Run the collection - await page.locator('.collection-actions').hover(); - await page.locator('.collection-actions .icon').click(); - await page.getByText('Run', { exact: true }).click(); - await page.getByRole('button', { name: 'Run Collection' }).click(); - await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 }); + await runCollection(page, 'jsonwebtoken'); - // Parse and validate test results - const result = await page.getByText('Total Requests: ').innerText(); - const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/); - if (!matches) { - throw new Error('Could not parse test results'); - } - const [totalRequests, passed, failed, skipped] = matches.slice(1); - - await expect(parseInt(totalRequests)).toBe(7); - await expect(parseInt(passed)).toBe(7); - await expect(parseInt(failed)).toBe(0); - await expect(parseInt(skipped)).toBe(0); - await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed)); + // Validate test results + await validateRunnerResults(page, { + totalRequests: 7, + passed: 7, + failed: 0, + skipped: 0 + }); }); test('safe mode', async ({ pageWithUserData: page }) => { - // init safe mode - await page.getByTitle('jsonwebtoken').click(); - await page.getByText('Developer Mode').click(); - await page.getByLabel('Safe Mode').check(); - await page.getByRole('button', { name: 'Save' }).click(); - test.setTimeout(2 * 60 * 1000); + // Set up safe mode + await setSandboxMode(page, 'jsonwebtoken', 'safe'); + // Run the collection - await page.locator('.collection-actions').hover(); - await page.locator('.collection-actions .icon').click(); - await page.getByText('Run', { exact: true }).click(); - await page.getByRole('button', { name: 'Run Collection' }).click(); - await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 }); + await runCollection(page, 'jsonwebtoken'); - // Parse and validate test results - const result = await page.getByText('Total Requests: ').innerText(); - const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/); - if (!matches) { - throw new Error('Could not parse test results'); - } - const [totalRequests, passed, failed, skipped] = matches.slice(1); - - await expect(parseInt(totalRequests)).toBe(7); - await expect(parseInt(passed)).toBe(7); - await expect(parseInt(failed)).toBe(0); - await expect(parseInt(skipped)).toBe(0); - await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed)); + // Validate test results + await validateRunnerResults(page, { + totalRequests: 7, + passed: 7, + failed: 0, + skipped: 0 + }); }); }); diff --git a/tests/ssl/basic-ssl/tests/basic-ssl-success/basic-ssl-success.spec.ts b/tests/ssl/basic-ssl/tests/basic-ssl-success/basic-ssl-success.spec.ts index 26eb6bf2f..85131cee3 100644 --- a/tests/ssl/basic-ssl/tests/basic-ssl-success/basic-ssl-success.spec.ts +++ b/tests/ssl/basic-ssl/tests/basic-ssl-success/basic-ssl-success.spec.ts @@ -1,59 +1,40 @@ -import { test, expect } from '../../../../../playwright'; +import { test } from '../../../../../playwright'; +import { setSandboxMode, runCollection, validateRunnerResults } from '../../../../utils/page'; test.describe.serial('basic ssl success', () => { test('developer mode', async ({ pageWithUserData: page }) => { - - // init dev mode - await page.getByText('badssl').click(); - await page.getByLabel('Developer Mode(use only if').check(); - await page.getByRole('button', { name: 'Save' }).click(); - test.setTimeout(2 * 60 * 1000); - await page.locator('.collection-actions').hover(); - await page.locator('.collection-actions .icon').click(); - await page.getByText('Run', { exact: true }).click(); - await page.getByRole('button', { name: 'Run Collection' }).click(); - await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 }); - const result = await page.getByText('Total Requests: ').innerText(); - const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/); - if (!matches) { - throw new Error('Could not parse test results'); - } - const [totalRequests, passed, failed, skipped] = matches.slice(1); - - await expect(parseInt(totalRequests)).toBe(1); - await expect(parseInt(passed)).toBe(1); - await expect(parseInt(failed)).toBe(0); - await expect(parseInt(skipped)).toBe(0); - await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed)); + // Set up developer mode + await setSandboxMode(page, 'badssl', 'developer'); + + // Run the collection + await runCollection(page, 'badssl'); + + // Validate test results + await validateRunnerResults(page, { + totalRequests: 1, + passed: 1, + failed: 0, + skipped: 0 + }); }); test('safe mode', async ({ pageWithUserData: page }) => { - - // init safe mode - await page.getByText('Developer Mode').click(); - await page.getByLabel('Safe Mode').check(); - await page.getByRole('button', { name: 'Save' }).click(); - test.setTimeout(2 * 60 * 1000); - await page.locator('.collection-actions').hover(); - await page.locator('.collection-actions .icon').click(); - await page.getByText('Run', { exact: true }).click(); - await page.getByRole('button', { name: 'Run Collection' }).click(); - await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 }); - const result = await page.getByText('Total Requests: ').innerText(); - const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/); - if (!matches) { - throw new Error('Could not parse test results'); - } - const [totalRequests, passed, failed, skipped] = matches.slice(1); + // Set up safe mode + await setSandboxMode(page, 'badssl', 'safe'); - await expect(parseInt(totalRequests)).toBe(1); - await expect(parseInt(passed)).toBe(1); - await expect(parseInt(failed)).toBe(0); - await expect(parseInt(skipped)).toBe(0); - await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed)); + // Run the collection + await runCollection(page, 'badssl'); + + // Validate test results + await validateRunnerResults(page, { + totalRequests: 1, + passed: 1, + failed: 0, + skipped: 0 + }); }); }); \ No newline at end of file diff --git a/tests/ssl/basic-ssl/tests/self-signed-rejected/self-signed-rejected.spec.ts b/tests/ssl/basic-ssl/tests/self-signed-rejected/self-signed-rejected.spec.ts index 0674154a4..489177144 100644 --- a/tests/ssl/basic-ssl/tests/self-signed-rejected/self-signed-rejected.spec.ts +++ b/tests/ssl/basic-ssl/tests/self-signed-rejected/self-signed-rejected.spec.ts @@ -1,57 +1,40 @@ -import { test, expect } from '../../../../../playwright'; +import { test } from '../../../../../playwright'; +import { setSandboxMode, runCollection, validateRunnerResults } from '../../../../utils/page'; test.describe.serial('self signed rejected', () => { test('developer mode', async ({ pageWithUserData: page }) => { - // init dev mode - await page.getByText('self-signed-badssl').click(); - await page.getByLabel('Developer Mode(use only if').check(); - await page.getByRole('button', { name: 'Save' }).click(); - test.setTimeout(2 * 60 * 1000); - await page.locator('.collection-actions').hover(); - await page.locator('.collection-actions .icon').click(); - await page.getByText('Run', { exact: true }).click(); - await page.getByRole('button', { name: 'Run Collection' }).click(); - await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 }); - const result = await page.getByText('Total Requests: ').innerText(); - const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/); - if (!matches) { - throw new Error('Could not parse test results'); - } - const [totalRequests, passed, failed, skipped] = matches.slice(1); - - await expect(parseInt(totalRequests)).toBe(1); - await expect(parseInt(passed)).toBe(0); - await expect(parseInt(failed)).toBe(1); - await expect(parseInt(skipped)).toBe(0); - await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed)); + // Set up developer mode + await setSandboxMode(page, 'self-signed-badssl', 'developer'); + + // Run the collection + await runCollection(page, 'self-signed-badssl'); + + // Validate test results + await validateRunnerResults(page, { + totalRequests: 1, + passed: 0, + failed: 1, + skipped: 0 + }); }); test('safe mode', async ({ pageWithUserData: page }) => { - // init safe mode - await page.getByText('Developer Mode').click(); - await page.getByLabel('Safe Mode').check(); - await page.getByRole('button', { name: 'Save' }).click(); - test.setTimeout(2 * 60 * 1000); - await page.locator('.collection-actions').hover(); - await page.locator('.collection-actions .icon').click(); - await page.getByText('Run', { exact: true }).click(); - await page.getByRole('button', { name: 'Run Collection' }).click(); - await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 }); - const result = await page.getByText('Total Requests: ').innerText(); - const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/); - if (!matches) { - throw new Error('Could not parse test results'); - } - const [totalRequests, passed, failed, skipped] = matches.slice(1); + // Set up safe mode + await setSandboxMode(page, 'self-signed-badssl', 'safe'); - await expect(parseInt(totalRequests)).toBe(1); - await expect(parseInt(passed)).toBe(0); - await expect(parseInt(failed)).toBe(1); - await expect(parseInt(skipped)).toBe(0); - await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed)); + // Run the collection + await runCollection(page, 'self-signed-badssl'); + + // Validate test results + await validateRunnerResults(page, { + totalRequests: 1, + passed: 0, + failed: 1, + skipped: 0 + }); }); }); \ No newline at end of file diff --git a/tests/ssl/basic-ssl/tests/self-signed-success-with-validation-disabled/self-signed-success-with-validation-disabled.spec.ts b/tests/ssl/basic-ssl/tests/self-signed-success-with-validation-disabled/self-signed-success-with-validation-disabled.spec.ts index d8edf5a0a..4b2543db8 100644 --- a/tests/ssl/basic-ssl/tests/self-signed-success-with-validation-disabled/self-signed-success-with-validation-disabled.spec.ts +++ b/tests/ssl/basic-ssl/tests/self-signed-success-with-validation-disabled/self-signed-success-with-validation-disabled.spec.ts @@ -1,59 +1,40 @@ -import { test, expect } from '../../../../../playwright'; +import { test } from '../../../../../playwright'; +import { setSandboxMode, runCollection, validateRunnerResults } from '../../../../utils/page'; test.describe.serial('self signed success with validation disabled', () => { test('developer mode', async ({ pageWithUserData: page }) => { - - // init dev mode - await page.getByText('self-signed-badssl').click(); - await page.getByLabel('Developer Mode(use only if').check(); - await page.getByRole('button', { name: 'Save' }).click(); - test.setTimeout(2 * 60 * 1000); - await page.locator('.collection-actions').hover(); - await page.locator('.collection-actions .icon').click(); - await page.getByText('Run', { exact: true }).click(); - await page.getByRole('button', { name: 'Run Collection' }).click(); - await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 }); - const result = await page.getByText('Total Requests: ').innerText(); - const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/); - if (!matches) { - throw new Error('Could not parse test results'); - } - const [totalRequests, passed, failed, skipped] = matches.slice(1); - - await expect(parseInt(totalRequests)).toBe(1); - await expect(parseInt(passed)).toBe(1); - await expect(parseInt(failed)).toBe(0); - await expect(parseInt(skipped)).toBe(0); - await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed)); + // Set up developer mode + await setSandboxMode(page, 'self-signed-badssl', 'developer'); + + // Run the collection + await runCollection(page, 'self-signed-badssl'); + + // Validate test results + await validateRunnerResults(page, { + totalRequests: 1, + passed: 1, + failed: 0, + skipped: 0 + }); }); test('safe mode', async ({ pageWithUserData: page }) => { - - // init safe mode - await page.getByText('Developer Mode').click(); - await page.getByLabel('Safe Mode').check(); - await page.getByRole('button', { name: 'Save' }).click(); - test.setTimeout(2 * 60 * 1000); - await page.locator('.collection-actions').hover(); - await page.locator('.collection-actions .icon').click(); - await page.getByText('Run', { exact: true }).click(); - await page.getByRole('button', { name: 'Run Collection' }).click(); - await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 }); - const result = await page.getByText('Total Requests: ').innerText(); - const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/); - if (!matches) { - throw new Error('Could not parse test results'); - } - const [totalRequests, passed, failed, skipped] = matches.slice(1); + // Set up safe mode + await setSandboxMode(page, 'self-signed-badssl', 'safe'); - await expect(parseInt(totalRequests)).toBe(1); - await expect(parseInt(passed)).toBe(1); - await expect(parseInt(failed)).toBe(0); - await expect(parseInt(skipped)).toBe(0); - await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed)); + // Run the collection + await runCollection(page, 'self-signed-badssl'); + + // Validate test results + await validateRunnerResults(page, { + totalRequests: 1, + passed: 1, + failed: 0, + skipped: 0 + }); }); }); \ No newline at end of file diff --git a/tests/ssl/custom-ca-certs/tests/custom-invalid-ca-cert-in-config-with-defaults/custom-invalid-ca-cert-in-config-with-defaults.spec.ts b/tests/ssl/custom-ca-certs/tests/custom-invalid-ca-cert-in-config-with-defaults/custom-invalid-ca-cert-in-config-with-defaults.spec.ts index 7b43ead19..b34314214 100644 --- a/tests/ssl/custom-ca-certs/tests/custom-invalid-ca-cert-in-config-with-defaults/custom-invalid-ca-cert-in-config-with-defaults.spec.ts +++ b/tests/ssl/custom-ca-certs/tests/custom-invalid-ca-cert-in-config-with-defaults/custom-invalid-ca-cert-in-config-with-defaults.spec.ts @@ -1,55 +1,40 @@ -import { test, expect } from '../../../../../playwright'; +import { test } from '../../../../../playwright'; +import { setSandboxMode, runCollection, validateRunnerResults } from '../../../../utils/page'; -test.describe.serial('custom invalid ca cert added to the config and keep default ca certs', () => { +test.describe('custom invalid ca cert added to the config and keep default ca certs', () => { test('developer mode', async ({ pageWithUserData: page }) => { - // init dev mode - await page.getByText('custom-ca-certs').click(); - await page.getByLabel('Developer Mode(use only if').check(); - await page.getByRole('button', { name: 'Save' }).click(); - test.setTimeout(2 * 60 * 1000); - await page.locator('.collection-actions').hover(); - await page.locator('.collection-actions .icon').click(); - await page.getByText('Run', { exact: true }).click(); - await page.getByRole('button', { name: 'Run Collection' }).click(); - await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 }); - const result = await page.getByText('Total Requests: ').innerText(); - const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/); - if (!matches) { - throw new Error('Could not parse test results'); - } - const [totalRequests, passed, failed, skipped] = matches.slice(1); - await expect(parseInt(totalRequests)).toBe(1); - await expect(parseInt(passed)).toBe(1); - await expect(parseInt(failed)).toBe(0); - await expect(parseInt(skipped)).toBe(0); - await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed)); + // Set up developer mode + await setSandboxMode(page, 'custom-ca-certs', 'developer'); + + // Run the collection + await runCollection(page, 'custom-ca-certs'); + + // Validate test results + await validateRunnerResults(page, { + totalRequests: 1, + passed: 1, + failed: 0, + skipped: 0 + }); }); test('safe mode', async ({ pageWithUserData: page }) => { - // init safe mode - await page.getByText('Developer Mode').click(); - await page.getByLabel('Safe Mode').check(); - await page.getByRole('button', { name: 'Save' }).click(); - test.setTimeout(2 * 60 * 1000); - await page.locator('.collection-actions').hover(); - await page.locator('.collection-actions .icon').click(); - await page.getByText('Run', { exact: true }).click(); - await page.getByRole('button', { name: 'Run Collection' }).click(); - await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 }); - const result = await page.getByText('Total Requests: ').innerText(); - const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/); - if (!matches) { - throw new Error('Could not parse test results'); - } - const [totalRequests, passed, failed, skipped] = matches.slice(1); - await expect(parseInt(totalRequests)).toBe(1); - await expect(parseInt(passed)).toBe(1); - await expect(parseInt(failed)).toBe(0); - await expect(parseInt(skipped)).toBe(0); - await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed)); + // Set up safe mode + await setSandboxMode(page, 'custom-ca-certs', 'safe'); + + // Run the collection + await runCollection(page, 'custom-ca-certs'); + + // Validate test results + await validateRunnerResults(page, { + totalRequests: 1, + passed: 1, + failed: 0, + skipped: 0 + }); }); }); \ No newline at end of file diff --git a/tests/ssl/custom-ca-certs/tests/custom-invalid-ca-cert-in-config/custom-invalid-ca-cert-in-config.spec.ts b/tests/ssl/custom-ca-certs/tests/custom-invalid-ca-cert-in-config/custom-invalid-ca-cert-in-config.spec.ts index 9d89e0bc4..825bcc63c 100644 --- a/tests/ssl/custom-ca-certs/tests/custom-invalid-ca-cert-in-config/custom-invalid-ca-cert-in-config.spec.ts +++ b/tests/ssl/custom-ca-certs/tests/custom-invalid-ca-cert-in-config/custom-invalid-ca-cert-in-config.spec.ts @@ -1,57 +1,40 @@ -import { test, expect } from '../../../../../playwright'; +import { test } from '../../../../../playwright'; +import { setSandboxMode, runCollection, validateRunnerResults } from '../../../../utils/page'; test.describe.serial('custom invalid ca cert added to the config and NO default ca certs', () => { test('developer mode', async ({ pageWithUserData: page }) => { - - // init dev mode - await page.getByText('custom-ca-certs').click(); - await page.getByLabel('Developer Mode(use only if').check(); - await page.getByRole('button', { name: 'Save' }).click(); - test.setTimeout(2 * 60 * 1000); - await page.locator('.collection-actions').hover(); - await page.locator('.collection-actions .icon').click(); - await page.getByText('Run', { exact: true }).click(); - await page.getByRole('button', { name: 'Run Collection' }).click(); - await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 }); - const result = await page.getByText('Total Requests: ').innerText(); - const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/); - if (!matches) { - throw new Error('Could not parse test results'); - } - const [totalRequests, passed, failed, skipped] = matches.slice(1); - await expect(parseInt(totalRequests)).toBe(1); - await expect(parseInt(passed)).toBe(0); - await expect(parseInt(failed)).toBe(1); - await expect(parseInt(skipped)).toBe(0); - await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed)); + // Set up developer mode + await setSandboxMode(page, 'custom-ca-certs', 'developer'); + + // Run the collection + await runCollection(page, 'custom-ca-certs'); + + // Validate test results + await validateRunnerResults(page, { + totalRequests: 1, + passed: 0, + failed: 1, + skipped: 0 + }); }); test('safe mode', async ({ pageWithUserData: page }) => { - - // init safe mode - await page.getByText('Developer Mode').click(); - await page.getByLabel('Safe Mode').check(); - await page.getByRole('button', { name: 'Save' }).click(); - test.setTimeout(2 * 60 * 1000); - await page.locator('.collection-actions').hover(); - await page.locator('.collection-actions .icon').click(); - await page.getByText('Run', { exact: true }).click(); - await page.getByRole('button', { name: 'Run Collection' }).click(); - await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 }); - const result = await page.getByText('Total Requests: ').innerText(); - const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/); - if (!matches) { - throw new Error('Could not parse test results'); - } - const [totalRequests, passed, failed, skipped] = matches.slice(1); - await expect(parseInt(totalRequests)).toBe(1); - await expect(parseInt(passed)).toBe(0); - await expect(parseInt(failed)).toBe(1); - await expect(parseInt(skipped)).toBe(0); - await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed)); + // Set up safe mode + await setSandboxMode(page, 'custom-ca-certs', 'safe'); + + // Run the collection + await runCollection(page, 'custom-ca-certs'); + + // Validate test results + await validateRunnerResults(page, { + totalRequests: 1, + passed: 0, + failed: 1, + skipped: 0 + }); }); }); \ No newline at end of file diff --git a/tests/ssl/custom-ca-certs/tests/custom-valid-ca-cert-in-config-with-defaults/custom-valid-ca-cert-in-config-with-defaults.spec.ts b/tests/ssl/custom-ca-certs/tests/custom-valid-ca-cert-in-config-with-defaults/custom-valid-ca-cert-in-config-with-defaults.spec.ts index 97db83b44..3811355c4 100644 --- a/tests/ssl/custom-ca-certs/tests/custom-valid-ca-cert-in-config-with-defaults/custom-valid-ca-cert-in-config-with-defaults.spec.ts +++ b/tests/ssl/custom-ca-certs/tests/custom-valid-ca-cert-in-config-with-defaults/custom-valid-ca-cert-in-config-with-defaults.spec.ts @@ -1,57 +1,40 @@ -import { test, expect } from '../../../../../playwright'; +import { test } from '../../../../../playwright'; +import { setSandboxMode, runCollection, validateRunnerResults } from '../../../../utils/page'; -test.describe.serial('custom valid ca cert added to the config and keep default ca certs', () => { +test.describe('custom valid ca cert added to the config and keep default ca certs', () => { test('developer mode', async ({ pageWithUserData: page }) => { - - // init dev mode - await page.getByText('custom-ca-certs').click(); - await page.getByLabel('Developer Mode(use only if').check(); - await page.getByRole('button', { name: 'Save' }).click(); - test.setTimeout(2 * 60 * 1000); - await page.locator('.collection-actions').hover(); - await page.locator('.collection-actions .icon').click(); - await page.getByText('Run', { exact: true }).click(); - await page.getByRole('button', { name: 'Run Collection' }).click(); - await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 }); - const result = await page.getByText('Total Requests: ').innerText(); - const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/); - if (!matches) { - throw new Error('Could not parse test results'); - } - const [totalRequests, passed, failed, skipped] = matches.slice(1); - await expect(parseInt(totalRequests)).toBe(1); - await expect(parseInt(passed)).toBe(1); - await expect(parseInt(failed)).toBe(0); - await expect(parseInt(skipped)).toBe(0); - await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed)); + // Set up developer mode + await setSandboxMode(page, 'custom-ca-certs', 'developer'); + + // Run the collection + await runCollection(page, 'custom-ca-certs'); + + // Validate test results + await validateRunnerResults(page, { + totalRequests: 1, + passed: 1, + failed: 0, + skipped: 0 + }); }); test('safe mode', async ({ pageWithUserData: page }) => { - - // init safe mode - await page.getByText('Developer Mode').click(); - await page.getByLabel('Safe Mode').check(); - await page.getByRole('button', { name: 'Save' }).click(); - test.setTimeout(2 * 60 * 1000); - await page.locator('.collection-actions').hover(); - await page.locator('.collection-actions .icon').click(); - await page.getByText('Run', { exact: true }).click(); - await page.getByRole('button', { name: 'Run Collection' }).click(); - await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 }); - const result = await page.getByText('Total Requests: ').innerText(); - const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/); - if (!matches) { - throw new Error('Could not parse test results'); - } - const [totalRequests, passed, failed, skipped] = matches.slice(1); - await expect(parseInt(totalRequests)).toBe(1); - await expect(parseInt(passed)).toBe(1); - await expect(parseInt(failed)).toBe(0); - await expect(parseInt(skipped)).toBe(0); - await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed)); + // Set up safe mode + await setSandboxMode(page, 'custom-ca-certs', 'safe'); + + // Run the collection + await runCollection(page, 'custom-ca-certs'); + + // Validate test results + await validateRunnerResults(page, { + totalRequests: 1, + passed: 1, + failed: 0, + skipped: 0 + }); }); }); \ No newline at end of file diff --git a/tests/ssl/custom-ca-certs/tests/custom-valid-ca-cert-in-config/custom-valid-ca-cert-in-config.spec.ts b/tests/ssl/custom-ca-certs/tests/custom-valid-ca-cert-in-config/custom-valid-ca-cert-in-config.spec.ts index 35a7776b2..603976275 100644 --- a/tests/ssl/custom-ca-certs/tests/custom-valid-ca-cert-in-config/custom-valid-ca-cert-in-config.spec.ts +++ b/tests/ssl/custom-ca-certs/tests/custom-valid-ca-cert-in-config/custom-valid-ca-cert-in-config.spec.ts @@ -1,57 +1,40 @@ -import { test, expect } from '../../../../../playwright'; +import { test } from '../../../../../playwright'; +import { setSandboxMode, runCollection, validateRunnerResults } from '../../../../utils/page'; -test.describe.serial('custom valid ca cert added to the config and NO default ca certs', () => { +test.describe('custom valid ca cert added to the config and NO default ca certs', () => { test('developer mode', async ({ pageWithUserData: page }) => { - - // init dev mode - await page.getByText('custom-ca-certs').click(); - await page.getByLabel('Developer Mode(use only if').check(); - await page.getByRole('button', { name: 'Save' }).click(); - test.setTimeout(2 * 60 * 1000); - await page.locator('.collection-actions').hover(); - await page.locator('.collection-actions .icon').click(); - await page.getByText('Run', { exact: true }).click(); - await page.getByRole('button', { name: 'Run Collection' }).click(); - await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 }); - const result = await page.getByText('Total Requests: ').innerText(); - const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/); - if (!matches) { - throw new Error('Could not parse test results'); - } - const [totalRequests, passed, failed, skipped] = matches.slice(1); - await expect(parseInt(totalRequests)).toBe(1); - await expect(parseInt(passed)).toBe(1); - await expect(parseInt(failed)).toBe(0); - await expect(parseInt(skipped)).toBe(0); - await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed)); + // Set up developer mode + await setSandboxMode(page, 'custom-ca-certs', 'developer'); + + // Run the collection + await runCollection(page, 'custom-ca-certs'); + + // Validate test results + await validateRunnerResults(page, { + totalRequests: 1, + passed: 1, + failed: 0, + skipped: 0 + }); }); test('safe mode', async ({ pageWithUserData: page }) => { - - // init safe mode - await page.getByText('Developer Mode').click(); - await page.getByLabel('Safe Mode').check(); - await page.getByRole('button', { name: 'Save' }).click(); - test.setTimeout(2 * 60 * 1000); - await page.locator('.collection-actions').hover(); - await page.locator('.collection-actions .icon').click(); - await page.getByText('Run', { exact: true }).click(); - await page.getByRole('button', { name: 'Run Collection' }).click(); - await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 }); - const result = await page.getByText('Total Requests: ').innerText(); - const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/); - if (!matches) { - throw new Error('Could not parse test results'); - } - const [totalRequests, passed, failed, skipped] = matches.slice(1); - await expect(parseInt(totalRequests)).toBe(1); - await expect(parseInt(passed)).toBe(1); - await expect(parseInt(failed)).toBe(0); - await expect(parseInt(skipped)).toBe(0); - await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed)); + // Set up safe mode + await setSandboxMode(page, 'custom-ca-certs', 'safe'); + + // Run the collection + await runCollection(page, 'custom-ca-certs'); + + // Validate test results + await validateRunnerResults(page, { + totalRequests: 1, + passed: 1, + failed: 0, + skipped: 0 + }); }); }); \ No newline at end of file diff --git a/tests/utils/page/index.ts b/tests/utils/page/index.ts index 485f1b10a..69b3d32f1 100644 --- a/tests/utils/page/index.ts +++ b/tests/utils/page/index.ts @@ -1 +1,2 @@ export * from './actions'; +export * from './runner'; diff --git a/tests/utils/page/runner.ts b/tests/utils/page/runner.ts new file mode 100644 index 000000000..d6454b4ed --- /dev/null +++ b/tests/utils/page/runner.ts @@ -0,0 +1,191 @@ +import { Page, expect } from '../../../playwright'; + +/** + * Reads test result counts from the filter buttons in the runner results view + * @param page - The Playwright page object + * @returns An object with totalRequests, passed, failed, and skipped counts + */ +export const getRunnerResultCounts = async (page: Page) => { + const allButton = page.locator('button').filter({ hasText: /^All/ }); + const passedButton = page.locator('button').filter({ hasText: /^Passed/ }); + const failedButton = page.locator('button').filter({ hasText: /^Failed/ }); + const skippedButton = page.locator('button').filter({ hasText: /^Skipped/ }); + + const totalRequests = parseInt(await allButton.locator('span').innerText()); + const passed = parseInt(await passedButton.locator('span').innerText()); + const failed = parseInt(await failedButton.locator('span').innerText()); + const skipped = parseInt(await skippedButton.locator('span').innerText()); + + return { totalRequests, passed, failed, skipped }; +}; + +/** + * Runs a collection by clicking the Run menu item and handling the runner tab + * Includes logic to reset existing results if present + * @param page - The Playwright page object + * @param collectionName - The name of the collection to run + * @returns void + */ +export const runCollection = async (page: Page, collectionName: string) => { + // Ensure collection is visible and loaded + const collectionContainer = page.locator('.collection-name').filter({ hasText: collectionName }); + await collectionContainer.waitFor({ state: 'visible' }); + // Wait a bit for the UI to stabilize + await page.waitForTimeout(300); + + // Open collection actions menu + await collectionContainer.locator('.collection-actions').hover(); + const icon = collectionContainer.locator('.collection-actions .icon'); + await icon.waitFor({ state: 'visible', timeout: 5000 }); + await page.waitForTimeout(200); // Small delay to ensure hover state is stable + await icon.click(); + + // Click Run menu item + await page.getByText('Run', { exact: true }).click(); + + // Handle runner tab - reset if needed, then run + const resetButton = page.getByRole('button', { name: 'Reset' }); + const runCollectionButton = page.getByRole('button', { name: 'Run Collection' }); + + // Check if Reset button is visible (means there are existing results) + const resetVisible = await resetButton.isVisible().catch(() => false); + if (resetVisible) { + await resetButton.click(); + // Wait a bit for the reset to complete + await page.waitForTimeout(500); + } + + // Now wait for and click Run Collection button + await runCollectionButton.waitFor({ state: 'visible', timeout: 10000 }); + await runCollectionButton.click(); + + // Wait for the run to complete + await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 }); +}; + +/** + * Sets up the JavaScript sandbox mode for a collection + * @param page - The Playwright page object + * @param collectionName - The name of the collection (can be title or text) + * @param mode - 'developer' or 'safe' mode + * @returns void + */ +export const setSandboxMode = async (page: Page, collectionName: string, mode: 'developer' | 'safe') => { + // Click on the collection name - try sidebar first, then fall back to collection tab/name + // First try sidebar collection name (more reliable) + const sidebarCollection = page.locator('#sidebar-collection-name').filter({ hasText: collectionName }); + const sidebarExists = await sidebarCollection.count().then((count) => count > 0).catch(() => false); + + if (sidebarExists) { + await sidebarCollection.click(); + } else { + // Fall back to collection by title or text + const collectionByTitle = page.getByTitle(collectionName); + const collectionByText = page.getByText(collectionName); + const titleExists = await collectionByTitle.count().then((count) => count > 0).catch(() => false); + if (titleExists) { + await collectionByTitle.click(); + } else { + await collectionByText.click(); + } + } + + // Wait a moment for the UI to load + await page.waitForTimeout(300); + + // Check if there's already a mode selected - if so, we need to click the badge to open settings tab + // Look for the Developer Mode or Safe Mode badge/button + const developerModeBadge = page.locator('.developer-mode').filter({ hasText: 'Developer Mode' }); + const safeModeBadge = page.locator('.safe-mode').filter({ hasText: 'Safe Mode' }); + + const developerBadgeExists = await developerModeBadge.count().then((count) => count > 0).catch(() => false); + const safeBadgeExists = await safeModeBadge.count().then((count) => count > 0).catch(() => false); + + // If a badge exists, click it to open the security settings tab + if (developerBadgeExists || safeBadgeExists) { + // Click the appropriate badge to open the security settings tab + if (developerBadgeExists) { + await developerModeBadge.click(); + } else { + await safeModeBadge.click(); + } + + // Wait for the security settings tab to be active and form to be visible + // Look for the security settings content - it should have "JavaScript Sandbox" heading + await page.getByText('JavaScript Sandbox').waitFor({ state: 'visible', timeout: 10000 }); + await page.waitForTimeout(300); + } + // If no badge exists, the modal should have appeared automatically (first time selection) + + // Wait for security settings form to be visible - wait for either radio button + // These should be in the active tab (either modal or tab) + const safeModeRadio = page.getByLabel('Safe Mode'); + const developerRadio = page.getByLabel('Developer Mode(use only if'); + + // Wait for at least one of them to be visible + await Promise.race([ + safeModeRadio.waitFor({ state: 'visible', timeout: 10000 }).catch(() => {}), + developerRadio.waitFor({ state: 'visible', timeout: 10000 }).catch(() => {}) + ]); + + // Additional wait to ensure UI is stable + await page.waitForTimeout(300); + + if (mode === 'developer') { + await developerRadio.waitFor({ state: 'visible', timeout: 5000 }); + await developerRadio.check(); + } else { + // For safe mode, check if developer mode is currently selected + const developerModeChecked = await developerRadio.isChecked().catch(() => false); + + if (developerModeChecked) { + // Click the Developer Mode label text inside the security settings form + // Scope to the form container to avoid clicking the badge + // The form should have the "JavaScript Sandbox" heading, so scope to that container + const securityForm = page.locator('div').filter({ hasText: 'JavaScript Sandbox' }).locator('..').first(); + const developerLabel = securityForm.locator('label').filter({ hasText: /^Developer Mode/ }).first(); + await developerLabel.waitFor({ state: 'visible', timeout: 5000 }); + await developerLabel.click(); + // Wait for UI to update + await page.waitForTimeout(300); + } + + // Ensure Safe Mode radio is visible and check it + await safeModeRadio.waitFor({ state: 'visible', timeout: 5000 }); + await safeModeRadio.check(); + } + + await page.getByRole('button', { name: 'Save' }).click(); +}; + +/** + * Validates runner results against expected counts + * @param page - The Playwright page object + * @param expected - Expected counts + * @returns void + */ +export const validateRunnerResults = async (page: Page, + expected: { + totalRequests?: number; + passed?: number; + failed?: number; + skipped?: number; + }) => { + const { totalRequests, passed, failed, skipped } = await getRunnerResultCounts(page); + + if (expected.totalRequests !== undefined) { + await expect(totalRequests).toBe(expected.totalRequests); + } + if (expected.passed !== undefined) { + await expect(passed).toBe(expected.passed); + } + if (expected.failed !== undefined) { + await expect(failed).toBe(expected.failed); + } + if (expected.skipped !== undefined) { + await expect(skipped).toBe(expected.skipped); + } + + // Validate that passed + failed + skipped = totalRequests + await expect(passed).toBe(totalRequests - skipped - failed); +};