From 77533e8abb2c2e9820926dfef667d9bdeb8629d1 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 12 Jun 2025 14:43:09 +0200 Subject: [PATCH 1/4] test: use role based selectors in trace-viewer tests --- packages/trace-viewer/src/ui/logTab.tsx | 1 + packages/trace-viewer/src/ui/metadataView.tsx | 2 +- packages/trace-viewer/src/ui/networkTab.tsx | 1 + packages/trace-viewer/src/ui/stackTrace.tsx | 1 + packages/web/src/components/gridView.tsx | 1 + packages/web/src/components/listView.tsx | 5 ++- tests/config/traceViewerFixtures.ts | 45 +++++++++---------- tests/library/trace-viewer.spec.ts | 23 +++++----- tests/playwright-test/reporter-html.spec.ts | 6 +-- .../ui-mode-test-network-tab.spec.ts | 6 +-- 10 files changed, 48 insertions(+), 43 deletions(-) diff --git a/packages/trace-viewer/src/ui/logTab.tsx b/packages/trace-viewer/src/ui/logTab.tsx index 4fcb8a6f43bfd..89bf177461474 100644 --- a/packages/trace-viewer/src/ui/logTab.tsx +++ b/packages/trace-viewer/src/ui/logTab.tsx @@ -58,6 +58,7 @@ export const LogTab: React.FunctionComponent<{ return
{entry.time} diff --git a/packages/trace-viewer/src/ui/metadataView.tsx b/packages/trace-viewer/src/ui/metadataView.tsx index 88c2e2bf933fd..9d2e1c207cda4 100644 --- a/packages/trace-viewer/src/ui/metadataView.tsx +++ b/packages/trace-viewer/src/ui/metadataView.tsx @@ -27,7 +27,7 @@ export const MetadataView: React.FunctionComponent<{ const wallTime = model.wallTime !== undefined ? new Date(model.wallTime).toLocaleString(undefined, { timeZoneName: 'short' }) : undefined; - return
+ return
Time
{!!wallTime &&
start time:{wallTime}
}
duration:{msToString(model.endTime - model.startTime)}
diff --git a/packages/trace-viewer/src/ui/networkTab.tsx b/packages/trace-viewer/src/ui/networkTab.tsx index 65faffd01b4a5..29624df2feb82 100644 --- a/packages/trace-viewer/src/ui/networkTab.tsx +++ b/packages/trace-viewer/src/ui/networkTab.tsx @@ -94,6 +94,7 @@ export const NetworkTab: React.FunctionComponent<{ const grid = setSelectedEntry(item)} diff --git a/packages/trace-viewer/src/ui/stackTrace.tsx b/packages/trace-viewer/src/ui/stackTrace.tsx index 6a0bad1672742..f0e79cd673b53 100644 --- a/packages/trace-viewer/src/ui/stackTrace.tsx +++ b/packages/trace-viewer/src/ui/stackTrace.tsx @@ -29,6 +29,7 @@ export const StackTraceView: React.FunctionComponent<{ const frames = stack || []; return { diff --git a/packages/web/src/components/gridView.tsx b/packages/web/src/components/gridView.tsx index 303de4b8d3600..58942e5527be7 100644 --- a/packages/web/src/components/gridView.tsx +++ b/packages/web/src/components/gridView.tsx @@ -92,6 +92,7 @@ export function GridView(model: GridViewProps) { { return <> diff --git a/packages/web/src/components/listView.tsx b/packages/web/src/components/listView.tsx index 079936c4a12c6..fcf0a461e5ab6 100644 --- a/packages/web/src/components/listView.tsx +++ b/packages/web/src/components/listView.tsx @@ -27,6 +27,7 @@ export type ListViewProps = { isError?: (item: T, index: number) => boolean, isWarning?: (item: T, index: number) => boolean, isInfo?: (item: T, index: number) => boolean, + ariaLabel?: string, selectedItem?: T, onAccepted?: (item: T, index: number) => void, onSelected?: (item: T, index: number) => void, @@ -56,6 +57,7 @@ export function ListView({ noItemsMessage, dataTestId, notSelectable, + ariaLabel, }: ListViewProps) { const itemListRef = React.useRef(null); const [highlightedItem, setHighlightedItem] = React.useState(); @@ -80,7 +82,7 @@ export function ListView({ itemListRef.current.scrollTop = scrollPositions.get(name) || 0; }, [name]); - return
0 ? 'list' : undefined} data-testid={dataTestId || (name + '-list')}> + return
0 ? 'list' : undefined} aria-label={ariaLabel}>
({ isError?.(item, index) && 'error', isWarning?.(item, index) && 'warning', isInfo?.(item, index) && 'info')} + aria-selected={selectedItem === item} onClick={() => onSelected?.(item, index)} onMouseEnter={() => setHighlightedItem(item)} onMouseLeave={() => setHighlightedItem(undefined)} diff --git a/tests/config/traceViewerFixtures.ts b/tests/config/traceViewerFixtures.ts index eb025c97dcc04..8273fc62f08aa 100644 --- a/tests/config/traceViewerFixtures.ts +++ b/tests/config/traceViewerFixtures.ts @@ -57,74 +57,73 @@ class TraceViewerPage { this.actionTitles = page.locator('.action-title'); this.actionsTree = page.getByTestId('actions-tree'); this.callLines = page.locator('.call-tab .call-line'); - this.logLines = page.getByTestId('log-list').locator('.list-view-entry'); - this.consoleLines = page.locator('.console-line'); + this.logLines = page.getByRole('list', { name: 'Log entries' }).getByRole('listitem'); + this.consoleLines = page.getByRole('tabpanel', { name: 'Console' }).getByRole('listitem'); this.consoleLineMessages = page.locator('.console-line-message'); this.errorMessages = page.locator('.error-message'); this.consoleStacks = page.locator('.console-stack'); - this.stackFrames = page.getByTestId('stack-trace-list').locator('.list-view-entry'); - this.networkRequests = page.getByTestId('network-list').locator('.list-view-entry'); + this.stackFrames = page.getByRole('list', { name: 'Stack trace' }).getByRole('listitem'); + this.networkRequests = page.getByRole('list', { name: 'Network requests' }).getByRole('listitem'); this.snapshotContainer = page.locator('.snapshot-container iframe.snapshot-visible[name=snapshot]'); - this.metadataTab = page.getByTestId('metadata-view'); - this.sourceCodeTab = page.getByTestId('source-code'); + this.metadataTab = page.getByRole('tabpanel', { name: 'Metadata' }); + this.sourceCodeTab = page.getByRole('tabpanel', { name: 'Source' }); this.settingsDialog = page.getByTestId('settings-toolbar-dialog'); this.darkModeSetting = page.locator('.setting').getByText('Dark mode'); this.displayCanvasContentSetting = page.locator('.setting').getByText('Display canvas content'); } - async actionIconsText(action: string) { - const entry = await this.page.waitForSelector(`.tree-view-entry:has-text("${action}")`); - await entry.waitForSelector('.action-icon-value:visible'); - return await entry.$$eval('.action-icon-value:visible', ee => ee.map(e => e.textContent)); + actionIconsText(action: string) { + const entry = this.actionsTree.getByRole('treeitem', { name: action }); + return entry.locator('.action-icon-value').filter({ visible: true }); } - async actionIcons(action: string) { - return await this.page.waitForSelector(`.tree-view-entry:has-text("${action}") .action-icons`); + actionIcons(action: string) { + return this.actionsTree.getByRole('treeitem', { name: action }).locator('.action-icons').filter({ visible: true }); } @step - async expandAction(title: string, ordinal: number = 0) { - await this.actionsTree.locator('.tree-view-entry', { hasText: title }).nth(ordinal).locator('.codicon-chevron-right').click(); + async expandAction(title: string) { + await this.actionsTree.getByRole('treeitem', { name: title }).locator('.codicon-chevron-right').click(); } @step async selectAction(title: string, ordinal: number = 0) { - await this.page.locator(`.action-title:has-text("${title}")`).nth(ordinal).click(); + await this.actionsTree.getByTitle(title).nth(ordinal).click(); } @step async hoverAction(title: string, ordinal: number = 0) { - await this.page.locator(`.action-title:has-text("${title}")`).nth(ordinal).hover(); + await this.actionTitles.filter({ hasText: title }).nth(ordinal).hover(); } @step async selectSnapshot(name: string) { - await this.page.click(`.snapshot-tab .tabbed-pane-tab-label:has-text("${name}")`); + await this.page.getByRole('tab', { name }).click(); } async showErrorsTab() { - await this.page.click('text="Errors"'); + await this.page.getByRole('tab', { name: 'Errors' }).click(); } async showConsoleTab() { - await this.page.click('text="Console"'); + await this.page.getByRole('tab', { name: 'Console' }).click(); } async showSourceTab() { - await this.page.click('text="Source"'); + await this.page.getByRole('tab', { name: 'Source' }).click(); } async showNetworkTab() { - await this.page.click('text="Network"'); + await this.page.getByRole('tab', { name: 'Network' }).click(); } async showMetadataTab() { - await this.page.click('text="Metadata"'); + await this.page.getByRole('tab', { name: 'Metadata' }).click(); } async showSettings() { - await this.page.locator('.settings-gear').click(); + await this.page.getByRole('button', { name: 'Settings' }).click(); } @step diff --git a/tests/library/trace-viewer.spec.ts b/tests/library/trace-viewer.spec.ts index ff50a454a5e16..855d065a1a80d 100644 --- a/tests/library/trace-viewer.spec.ts +++ b/tests/library/trace-viewer.spec.ts @@ -282,12 +282,12 @@ test('should render console', async ({ showTraceViewer, browserName }) => { await expect(listViews.filter({ hasText: 'Cheers!' })).toHaveClass('list-view-entry'); }); -test('should open console errors on click', async ({ showTraceViewer, browserName }) => { +test('should open console errors on click', async ({ showTraceViewer }) => { const traceViewer = await showTraceViewer([traceFile]); - expect(await traceViewer.actionIconsText('Evaluate')).toEqual(['2', '1']); - expect(await traceViewer.page.isHidden('.console-tab')).toBeTruthy(); - await (await traceViewer.actionIcons('Evaluate')).click(); - expect(await traceViewer.page.waitForSelector('.console-tab')).toBeTruthy(); + await expect(traceViewer.actionIconsText('Evaluate')).toHaveText(['2', '1']); + await expect(traceViewer.page.getByRole('tabpanel', { name: 'Console' })).toBeHidden(); + await traceViewer.actionIcons('Evaluate').click(); + await traceViewer.page.getByRole('tabpanel', { name: 'Console' }).waitFor(); }); test('should show params and return value', async ({ showTraceViewer }) => { @@ -977,14 +977,13 @@ test('should highlight expect failure', async ({ page, server, runAndTrace }) => test('should show action source', async ({ showTraceViewer }) => { const traceViewer = await showTraceViewer([traceFile]); await traceViewer.selectAction('Click'); - const page = traceViewer.page; - await page.click('text=Source'); - await expect(page.locator('.source-line-running')).toContainText('await page.getByText(\'Click\').click()'); - await expect(page.getByTestId('stack-trace-list').locator('.list-view-entry.selected')).toHaveText(/doClick.*trace-viewer\.spec\.ts:[\d]+/); + await traceViewer.showSourceTab(); + await expect(traceViewer.page.locator('.source-line-running')).toContainText('await page.getByText(\'Click\').click()'); + await expect(traceViewer.stackFrames.and(traceViewer.page.locator('.selected'))).toHaveText(/doClick.*trace-viewer\.spec\.ts:[\d]+/); await traceViewer.hoverAction('Wait for navigation'); - await expect(page.locator('.source-line-running')).toContainText('page.waitForNavigation()'); + await expect(traceViewer.page.locator('.source-line-running')).toContainText('page.waitForNavigation()'); }); test('should follow redirects', async ({ page, runAndTrace, server, asset }) => { @@ -1015,7 +1014,7 @@ test('should follow redirects', async ({ page, runAndTrace, server, asset }) => test('should include metainfo', async ({ showTraceViewer }) => { const traceViewer = await showTraceViewer([traceFile]); - await traceViewer.page.locator('text=Metadata').click(); + await traceViewer.page.getByRole('tab', { name: 'Metadata' }).click(); const callLine = traceViewer.metadataTab.locator('.call-line'); await expect(callLine.getByText('start time')).toHaveText(/start time:[\d/,: ]+/); await expect(callLine.getByText('duration')).toHaveText(/duration:[\dms]+/); @@ -1065,7 +1064,7 @@ test('should open two trace files', async ({ context, page, request, server, sho /Fetch "\/one-style\.css"/, ]); - await traceViewer.page.locator('text=Metadata').click(); + await traceViewer.page.getByRole('tab', { name: 'Metadata' }).click(); const callLine = traceViewer.page.locator('.call-line'); // Should get metadata from the context trace await expect(callLine.getByText('start time')).toHaveText(/start time:[\d/,: ]+/); diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index b939327f492af..7a2df14ef33f5 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -518,7 +518,7 @@ for (const useIntermediateMergeReport of [true, false] as const) { await page.getByRole('link', { name: 'passes' }).click(); await page.click('img'); await page.click('.action-title >> text=EVALUATE'); - await page.click('text=Source'); + await page.getByRole('tab', { name: 'Source' }).click(); await expect(page.locator('.CodeMirror-line')).toContainText([ /import.*test/, @@ -526,10 +526,10 @@ for (const useIntermediateMergeReport of [true, false] as const) { ]); await expect(page.locator('.source-line-running')).toContainText('page.evaluate'); - await expect(page.getByTestId('stack-trace-list')).toContainText([ + await expect(page.getByRole('list', { name: 'Stack trace' }).getByRole('listitem')).toContainText([ /a.test.js:[\d]+/, ]); - await expect(page.getByTestId('stack-trace-list').locator('.list-view-entry.selected')).toContainText('a.test.js'); + await expect(page.getByRole('list', { name: 'Stack trace' }).getByRole('listitem').and(page.locator('.selected'))).toContainText('a.test.js'); }); test('should not show stack trace', async ({ runInlineTest, page, showReport }) => { diff --git a/tests/playwright-test/ui-mode-test-network-tab.spec.ts b/tests/playwright-test/ui-mode-test-network-tab.spec.ts index 30c63494c1f2a..10810d1616184 100644 --- a/tests/playwright-test/ui-mode-test-network-tab.spec.ts +++ b/tests/playwright-test/ui-mode-test-network-tab.spec.ts @@ -32,7 +32,7 @@ test('should filter network requests by resource type', async ({ runUITest, serv await page.getByText('network tab test').dblclick(); await page.getByText('Network', { exact: true }).click(); - const networkItems = page.getByTestId('network-list').getByRole('listitem'); + const networkItems = page.getByRole('list', { name: 'Network requests' }).getByRole('listitem'); await page.getByText('JS', { exact: true }).click(); await expect(networkItems).toHaveCount(1); @@ -73,7 +73,7 @@ test('should filter network requests by url', async ({ runUITest, server }) => { await page.getByText('network tab test').dblclick(); await page.getByText('Network', { exact: true }).click(); - const networkItems = page.getByTestId('network-list').getByRole('listitem'); + const networkItems = page.getByRole('list', { name: 'Network requests' }).getByRole('listitem'); await page.getByPlaceholder('Filter network').fill('script.'); await expect(networkItems).toHaveCount(1); @@ -198,5 +198,5 @@ test('should not duplicate network entries from beforeAll', { await page.getByText('first test').dblclick(); await page.getByText('Network', { exact: true }).click(); - await expect(page.getByTestId('network-list').getByText('empty.html')).toHaveCount(1); + await expect(page.getByRole('list', { name: 'Network requests' }).getByText('empty.html')).toHaveCount(1); }); From b8228cbae4de9eb6c6f75fd51767dcb1d2a124e1 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 13 Jun 2025 13:24:56 +0200 Subject: [PATCH 2/4] review feedback --- tests/config/traceViewerFixtures.ts | 11 ++++++++--- tests/library/trace-viewer.spec.ts | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/config/traceViewerFixtures.ts b/tests/config/traceViewerFixtures.ts index 8273fc62f08aa..5712ef16bd2b8 100644 --- a/tests/config/traceViewerFixtures.ts +++ b/tests/config/traceViewerFixtures.ts @@ -43,7 +43,6 @@ class TraceViewerPage { errorMessages: Locator; consoleLineMessages: Locator; consoleStacks: Locator; - stackFrames: Locator; networkRequests: Locator; metadataTab: Locator; snapshotContainer: Locator; @@ -62,7 +61,6 @@ class TraceViewerPage { this.consoleLineMessages = page.locator('.console-line-message'); this.errorMessages = page.locator('.error-message'); this.consoleStacks = page.locator('.console-stack'); - this.stackFrames = page.getByRole('list', { name: 'Stack trace' }).getByRole('listitem'); this.networkRequests = page.getByRole('list', { name: 'Network requests' }).getByRole('listitem'); this.snapshotContainer = page.locator('.snapshot-container iframe.snapshot-visible[name=snapshot]'); this.metadataTab = page.getByRole('tabpanel', { name: 'Metadata' }); @@ -73,6 +71,13 @@ class TraceViewerPage { this.displayCanvasContentSetting = page.locator('.setting').getByText('Display canvas content'); } + stackFrames(selected?: boolean) { + const entry = this.page.getByRole('list', { name: 'Stack trace' }).getByRole('listitem'); + if (selected) + return entry.and(this.page.locator('.selected')); + return entry; + } + actionIconsText(action: string) { const entry = this.actionsTree.getByRole('treeitem', { name: action }); return entry.locator('.action-icon-value').filter({ visible: true }); @@ -94,7 +99,7 @@ class TraceViewerPage { @step async hoverAction(title: string, ordinal: number = 0) { - await this.actionTitles.filter({ hasText: title }).nth(ordinal).hover(); + await this.actionsTree.getByRole('treeitem', { name: title }).nth(ordinal).hover(); } @step diff --git a/tests/library/trace-viewer.spec.ts b/tests/library/trace-viewer.spec.ts index 855d065a1a80d..268be55ad5866 100644 --- a/tests/library/trace-viewer.spec.ts +++ b/tests/library/trace-viewer.spec.ts @@ -346,7 +346,7 @@ test('should have correct stack trace', async ({ showTraceViewer }) => { await traceViewer.selectAction('Click'); await traceViewer.showSourceTab(); - await expect(traceViewer.stackFrames).toContainText([ + await expect(traceViewer.stackFrames()).toContainText([ /doClick\s+trace-viewer.spec.ts\s+:\d+/, /recordTrace\s+trace-viewer.spec.ts\s+:\d+/, ], { useInnerText: true }); @@ -980,7 +980,7 @@ test('should show action source', async ({ showTraceViewer }) => { await traceViewer.showSourceTab(); await expect(traceViewer.page.locator('.source-line-running')).toContainText('await page.getByText(\'Click\').click()'); - await expect(traceViewer.stackFrames.and(traceViewer.page.locator('.selected'))).toHaveText(/doClick.*trace-viewer\.spec\.ts:[\d]+/); + await expect(traceViewer.stackFrames(true)).toHaveText(/doClick.*trace-viewer\.spec\.ts:[\d]+/); await traceViewer.hoverAction('Wait for navigation'); await expect(traceViewer.page.locator('.source-line-running')).toContainText('page.waitForNavigation()'); From 22aeb0865b264ad18bafa4fc9cfbc377b80444ee Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 16 Jun 2025 16:22:24 +0200 Subject: [PATCH 3/4] Update tests/config/traceViewerFixtures.ts Co-authored-by: Dmitry Gozman Signed-off-by: Max Schmitt --- tests/config/traceViewerFixtures.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/config/traceViewerFixtures.ts b/tests/config/traceViewerFixtures.ts index 5712ef16bd2b8..bacea482f9532 100644 --- a/tests/config/traceViewerFixtures.ts +++ b/tests/config/traceViewerFixtures.ts @@ -74,7 +74,7 @@ class TraceViewerPage { stackFrames(selected?: boolean) { const entry = this.page.getByRole('list', { name: 'Stack trace' }).getByRole('listitem'); if (selected) - return entry.and(this.page.locator('.selected')); + return entry.locator(':scope.selected'); return entry; } From 7388f78bc0ccc5e2c360715b53ed83dc68152d42 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 16 Jun 2025 16:25:30 +0200 Subject: [PATCH 4/4] review feedback --- tests/config/traceViewerFixtures.ts | 4 ++-- tests/library/trace-viewer.spec.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/config/traceViewerFixtures.ts b/tests/config/traceViewerFixtures.ts index bacea482f9532..d4d6284c8b0a4 100644 --- a/tests/config/traceViewerFixtures.ts +++ b/tests/config/traceViewerFixtures.ts @@ -71,9 +71,9 @@ class TraceViewerPage { this.displayCanvasContentSetting = page.locator('.setting').getByText('Display canvas content'); } - stackFrames(selected?: boolean) { + stackFrames(options: { selected?: boolean } = {}) { const entry = this.page.getByRole('list', { name: 'Stack trace' }).getByRole('listitem'); - if (selected) + if (options.selected) return entry.locator(':scope.selected'); return entry; } diff --git a/tests/library/trace-viewer.spec.ts b/tests/library/trace-viewer.spec.ts index 268be55ad5866..16a36e5d59067 100644 --- a/tests/library/trace-viewer.spec.ts +++ b/tests/library/trace-viewer.spec.ts @@ -980,7 +980,7 @@ test('should show action source', async ({ showTraceViewer }) => { await traceViewer.showSourceTab(); await expect(traceViewer.page.locator('.source-line-running')).toContainText('await page.getByText(\'Click\').click()'); - await expect(traceViewer.stackFrames(true)).toHaveText(/doClick.*trace-viewer\.spec\.ts:[\d]+/); + await expect(traceViewer.stackFrames({ selected: true })).toHaveText(/doClick.*trace-viewer\.spec\.ts:[\d]+/); await traceViewer.hoverAction('Wait for navigation'); await expect(traceViewer.page.locator('.source-line-running')).toContainText('page.waitForNavigation()');