Merge pull request #6070 from usebruno/feature/collection-test-results-and-filtering-internal

Feature/collection test results and filtering internal
This commit is contained in:
Sid
2025-11-14 13:46:32 +05:30
committed by GitHub
12 changed files with 655 additions and 484 deletions

View File

@@ -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 }) => (
<button
onClick={onClick}
className={`font-medium transition-colors cursor-pointer flex items-center gap-1.5 border-b-2 pb-2 ${
active
? 'text-[#343434] dark:text-[#CCCCCC] border-[#F59E0B]'
: 'text-[#989898] dark:text-[#CCCCCC80] border-transparent'
}`}
style={{ fontFamily: 'Inter', fontSize: '14px', fontWeight: 500, lineHeight: '100%', letterSpacing: '0%' }}
>
{label}
<span
className="px-[4.5px] py-[2px] rounded-[2px] bg-[#F7F7F7] dark:bg-[#242424] border border-[#EFEFEF] dark:border-[#92929233] text-[#989898] dark:text-inherit"
style={{ borderWidth: '1px', fontFamily: 'Inter', fontSize: '10px', fontWeight: 500, lineHeight: '100%', letterSpacing: '0%' }}
>
{count}
</span>
</button>
);
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 (
<StyledWrapper className="pl-4 overflow-hidden h-full">
@@ -285,27 +351,57 @@ export default function RunnerResults({ collection }) {
return (
<StyledWrapper className="px-4 pb-4 flex flex-grow flex-col relative overflow-auto">
<div className="flex items-center my-6 flex-row">
<div className="font-medium title flex items-center">
Runner
<IconRun size={20} strokeWidth={1.5} className="ml-2" />
{/* Filter Bar and Actions */}
<div className="flex items-center justify-between mb-4 pt-[14px] gap-4">
<div className="flex items-stretch rounded-lg border border-[#EFEFEF] dark:border-[#92929233] max-h-[35px] flex-shrink-0" style={{ borderWidth: '1px' }}>
<div className="flex items-center px-3 py-2 rounded-l-lg bg-[#F3F3F3] dark:bg-[#2B2D2F]">
<span className="text-gray-600 dark:text-gray-400" style={{ fontFamily: 'Inter', fontSize: '14px', fontWeight: 400 }}>
Filter by:
</span>
</div>
<div className="flex items-center gap-5 px-3 pt-2 pb-0 rounded-r-lg bg-transparent dark:bg-transparent">
{Object.entries(FILTERS).map(([key, { label }]) => (
<FilterButton
key={key}
label={label}
count={filterCounts[key]}
active={activeFilter === key}
onClick={() => setActiveFilter(key)}
/>
))}
</div>
</div>
{runnerInfo.status !== 'ended' && runnerInfo.cancelTokenUid && (
<button className="btn btn-sm btn-danger" onClick={cancelExecution}>
Cancel Execution
</button>
)}
{runnerInfo.status !== 'ended' && runnerInfo.cancelTokenUid ? (
<div className="flex items-center flex-shrink-0">
<button className="btn btn-sm btn-danger" onClick={cancelExecution}>Cancel Execution</button>
</div>
) : runnerInfo.status === 'ended' ? (
<div className="flex items-center gap-3 flex-shrink-0">
<button
type="button"
className="px-3 py-1.5 rounded-md bg-transparent border border-[#989898] dark:border-[#444444] text-[#989898] hover:opacity-80 transition-colors"
style={{ fontFamily: 'Inter', fontSize: '12px', fontWeight: 500 }}
onClick={runAgain}
>
Run Again
</button>
<button
type="button"
className="px-3 py-1.5 rounded-md bg-transparent border border-[#989898] dark:border-[#444444] text-[#989898] hover:opacity-80 transition-colors"
style={{ fontFamily: 'Inter', fontSize: '12px', fontWeight: 500 }}
onClick={resetRunner}
>
Reset
</button>
</div>
) : null}
</div>
<div className="flex gap-4 h-[calc(100vh_-_10rem)] overflow-hidden">
<div
className={`flex flex-col overflow-y-auto ${selectedItem || (configureMode && !selectedItem && !runnerInfo.status === 'running') ? 'w-1/2' : 'w-full'}`}
ref={runnerBodyRef}
className="flex flex-col w-1/2"
>
<div className="pb-2 font-medium test-summary">
Total Requests: {items.length}, Passed: {passedRequests.length}, Failed: {failedRequests.length}, Skipped:{' '}
{skippedRequests.length}
</div>
{tagsEnabled && areTagsAdded && (
<div className="pb-2 text-xs flex flex-row gap-1">
Tags:
@@ -326,8 +422,8 @@ export default function RunnerResults({ collection }) {
: null}
{/* Items list */}
<div className="overflow-y-auto flex-1">
{items.map((item) => {
<div className="overflow-y-auto flex-1 " ref={runnerBodyRef}>
{filteredItems.map((item) => {
return (
<div key={item.uid}>
<div className="item-path mt-2">
@@ -371,7 +467,7 @@ export default function RunnerResults({ collection }) {
<ul className="pl-8">
{item.preRequestTestResults
? item.preRequestTestResults.map((result) => (
? filterTestResults(item.preRequestTestResults).map((result) => (
<li key={result.uid}>
{result.status === 'pass' ? (
<span className="test-success flex items-center">
@@ -391,7 +487,7 @@ export default function RunnerResults({ collection }) {
))
: null}
{item.postResponseTestResults
? item.postResponseTestResults.map((result) => (
? filterTestResults(item.postResponseTestResults).map((result) => (
<li key={result.uid}>
{result.status === 'pass' ? (
<span className="test-success flex items-center">
@@ -411,7 +507,7 @@ export default function RunnerResults({ collection }) {
))
: null}
{item.testResults
? item.testResults.map((result) => (
? filterTestResults(item.testResults).map((result) => (
<li key={result.uid}>
{result.status === 'pass' ? (
<span className="test-success flex items-center">
@@ -430,7 +526,7 @@ export default function RunnerResults({ collection }) {
</li>
))
: null}
{item.assertionResults?.map((result) => (
{filterTestResults(item.assertionResults).map((result) => (
<li key={result.uid}>
{result.status === 'pass' ? (
<span className="test-success flex items-center">
@@ -454,42 +550,50 @@ export default function RunnerResults({ collection }) {
);
})}
</div>
{runnerInfo.status === 'ended' ? (
<div className="mt-2 mb-4">
<button type="submit" className="submit btn btn-sm btn-secondary mt-6" onClick={runAgain}>
Run Again
</button>
<button type="submit" className="submit btn btn-sm btn-secondary mt-6 ml-3" disabled={shouldDisableCollectionRun} onClick={runCollection}>
Run Collection
</button>
<button className="btn btn-sm btn-close mt-6 ml-3" onClick={resetRunner}>
Reset
</button>
</div>
) : null}
</div>
{selectedItem ? (
<div className="flex flex-1 w-[50%] overflow-y-auto">
<div className="flex flex-col w-full overflow-hidden">
<div className="flex items-center mb-4 font-medium">
<span className="mr-2">{selectedItem.displayName}</span>
<span>
{allTestsPassed(selectedItem) ?
<IconCircleCheck className="test-success" size={20} strokeWidth={1.5} />
: null}
{anyTestFailed(selectedItem) ?
<IconCircleX className="test-failure" size={20} strokeWidth={1.5} />
: null}
{selectedItem.status === 'skipped' ?
<IconCircleOff className="skipped-request" size={20} strokeWidth={1.5} />
: null}
</span>
<div className="flex items-center justify-between mb-4 font-medium">
<div className="flex items-center">
<span className="mr-2">{selectedItem.displayName}</span>
<span>
{allTestsPassed(selectedItem)
? <IconCircleCheck className="test-success" size={20} strokeWidth={1.5} />
: null}
{anyTestFailed(selectedItem)
? <IconCircleX className="test-failure" size={20} strokeWidth={1.5} />
: null}
{selectedItem.status === 'skipped'
? <IconCircleOff className="skipped-request" size={20} strokeWidth={1.5} />
: null}
</span>
</div>
<button
onClick={() => setSelectedItem(null)}
className="p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors cursor-pointer flex items-center justify-center"
title="Close"
aria-label="Close response view"
>
<IconX size={16} strokeWidth={1.5} />
</button>
</div>
<ResponsePane item={selectedItem} collection={collection} />
</div>
</div>
) : null}
) : (
<div className="flex flex-1 w-[50%] overflow-y-auto">
<div className="flex flex-col w-full h-full items-center justify-center text-center">
<div className="mb-4 text-gray-400 dark:text-gray-500">
<IconExternalLink size={64} strokeWidth={1.5} />
</div>
<p className="text-gray-600 dark:text-gray-400 text-sm">
Click on the status code to view the response
</p>
</div>
</div>
)}
</div>
</StyledWrapper>
);

View File

@@ -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);
});
});

View File

@@ -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
});
});
});

View File

@@ -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
});
});
});

View File

@@ -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
});
});
});

View File

@@ -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
});
});
});

View File

@@ -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
});
});
});

View File

@@ -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
});
});
});

View File

@@ -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
});
});
});

View File

@@ -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
});
});
});

View File

@@ -1 +1,2 @@
export * from './actions';
export * from './runner';

191
tests/utils/page/runner.ts Normal file
View File

@@ -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);
};