diff --git a/packages/trace-viewer/src/ui/modelUtil.ts b/packages/trace-viewer/src/ui/modelUtil.ts
index c82cbc99a6..5793536da5 100644
--- a/packages/trace-viewer/src/ui/modelUtil.ts
+++ b/packages/trace-viewer/src/ui/modelUtil.ts
@@ -24,6 +24,7 @@ import type { StackFrame } from '@protocol/channels';
const contextSymbol = Symbol('context');
const nextInContextSymbol = Symbol('next');
const prevInListSymbol = Symbol('prev');
+const parentActionSymbol = Symbol('parent');
const eventsSymbol = Symbol('events');
export type SourceLocation = {
@@ -195,8 +196,11 @@ function mergeActionsAndUpdateTiming(contexts: ContextEntry[]) {
return a1.startTime - a2.startTime;
});
- for (let i = 1; i < result.length; ++i)
+ for (let i = 1; i < result.length; ++i) {
(result[i] as any)[prevInListSymbol] = result[i - 1];
+ if (result[i].parentId)
+ (result[i] as any)[parentActionSymbol] = result.find(a => a.callId === result[i].parentId);
+ }
return result;
}
@@ -356,6 +360,10 @@ export function prevInList(action: ActionTraceEvent): ActionTraceEvent {
return (action as any)[prevInListSymbol];
}
+export function parentAction(action: ActionTraceEvent): ActionTraceEvent {
+ return (action as any)[parentActionSymbol];
+}
+
export function stats(action: ActionTraceEvent): { errors: number, warnings: number } {
let errors = 0;
let warnings = 0;
diff --git a/packages/trace-viewer/src/ui/snapshotTab.tsx b/packages/trace-viewer/src/ui/snapshotTab.tsx
index 798025cbbd..149e554d5b 100644
--- a/packages/trace-viewer/src/ui/snapshotTab.tsx
+++ b/packages/trace-viewer/src/ui/snapshotTab.tsx
@@ -17,7 +17,7 @@
import './snapshotTab.css';
import * as React from 'react';
import type { ActionTraceEvent } from '@trace/trace';
-import { context, prevInList } from './modelUtil';
+import { context, parentAction, prevInList } from './modelUtil';
import { Toolbar } from '@web/components/toolbar';
import { ToolbarButton } from '@web/components/toolbarButton';
import { clsx, useMeasure } from '@web/uiUtils';
@@ -50,7 +50,7 @@ export const SnapshotTab: React.FunctionComponent<{
// if the action has no beforeSnapshot, use the last available afterSnapshot.
let beforeSnapshot: Snapshot | undefined = action.beforeSnapshot ? { action, snapshotName: action.beforeSnapshot } : undefined;
- let a = action;
+ let a: ActionTraceEvent | undefined = action;
while (!beforeSnapshot && a) {
a = prevInList(a);
beforeSnapshot = a?.afterSnapshot ? { action: a, snapshotName: a?.afterSnapshot } : undefined;
@@ -180,27 +180,51 @@ export const SnapshotTab: React.FunctionComponent<{
setHighlightedLocator={setHighlightedLocator}
iframe={iframeRef1.current}
iteration={loadingRef.current.iteration} />
-
- setIsInspecting(!isInspecting)} />
- {['action', 'before', 'after'].map(tab => {
- return setSnapshotTab(tab as 'action' | 'before' | 'after')}
- >;
- })}
-
- {
- if (!openPage)
- openPage = window.open;
- const win = openPage(popoutUrl || '', '_blank');
- win?.addEventListener('DOMContentLoaded', () => {
- const injectedScript = new InjectedScript(win as any, false, sdkLanguage, testIdAttributeName, 1, 'chromium', []);
- new ConsoleAPI(injectedScript);
- });
- }}>
-
+
+
+ setIsInspecting(!isInspecting)} />
+ {['action', 'before', 'after'].map(tab => {
+ return setSnapshotTab(tab as 'action' | 'before' | 'after')}
+ >;
+ })}
+
+
+ {actionToBreadcrumb(action)}
+
+
+ {
+ if (!openPage)
+ openPage = window.open;
+ const win = openPage(popoutUrl || '', '_blank');
+ win?.addEventListener('DOMContentLoaded', () => {
+ const injectedScript = new InjectedScript(win as any, false, sdkLanguage, testIdAttributeName, 1, 'chromium', []);
+ new ConsoleAPI(injectedScript);
+ });
+ }}>
+
+
;
};
+function actionToBreadcrumb(action: ActionTraceEvent | undefined): React.ReactNode {
+ if (!action)
+ return '';
+ const parts = [];
+ while (action) {
+ parts.push(action.apiName);
+ action = parentAction(action);
+ }
+ return parts.reverse().join(' › ');
+}
+
function renderTitle(snapshotTitle: string): string {
if (snapshotTitle === 'before')
return 'Before';
diff --git a/packages/web/src/components/toolbar.tsx b/packages/web/src/components/toolbar.tsx
index c7dd6843d9..97212f70a5 100644
--- a/packages/web/src/components/toolbar.tsx
+++ b/packages/web/src/components/toolbar.tsx
@@ -22,15 +22,14 @@ type ToolbarProps = {
noShadow?: boolean;
noMinHeight?: boolean;
className?: string;
- onClick?: (e: React.MouseEvent) => void;
-};
+} & React.HTMLAttributes
;
export const Toolbar: React.FC> = ({
noShadow,
children,
noMinHeight,
className,
- onClick,
+ ...props
}) => {
- return {children}
;
+ return {children}
;
};
diff --git a/tests/library/trace-viewer.spec.ts b/tests/library/trace-viewer.spec.ts
index 43a42512fd..7e2add53bb 100644
--- a/tests/library/trace-viewer.spec.ts
+++ b/tests/library/trace-viewer.spec.ts
@@ -289,6 +289,14 @@ test('should show snapshot URL', async ({ page, runAndTrace, server }) => {
});
test('should popup snapshot', async ({ page, runAndTrace, server }) => {
+ const traceViewer = await runAndTrace(async () => {
+ await page.goto(server.EMPTY_PAGE);
+ });
+ await traceViewer.snapshotFrame('page.goto');
+ await expect(traceViewer.page.getByTestId('snapshot-breadcrumb')).toHaveText('page.goto');
+});
+
+test('should show active action title', async ({ page, runAndTrace, server }) => {
const traceViewer = await runAndTrace(async () => {
await page.goto(server.EMPTY_PAGE);
await page.setContent('hello');
diff --git a/tests/playwright-test/ui-mode-test-attachments.spec.ts b/tests/playwright-test/ui-mode-test-attachments.spec.ts
index 7016a36115..0234004228 100644
--- a/tests/playwright-test/ui-mode-test-attachments.spec.ts
+++ b/tests/playwright-test/ui-mode-test-attachments.spec.ts
@@ -91,7 +91,7 @@ test('should contain string attachment', async ({ runUITest }) => {
await page.getByTitle('Run all').click();
await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)');
await page.getByText('Attachments').click();
- await page.getByText('attach "note"', { exact: true }).click();
+ await page.getByTestId('actions-tree').getByRole('listitem').filter({ hasText: 'attach "note"' }).click();
const downloadPromise = page.waitForEvent('download');
await page.locator('.expandable-title', { hasText: 'note' }).getByRole('link').click();
const download = await downloadPromise;
diff --git a/tests/playwright-test/ui-mode-trace.spec.ts b/tests/playwright-test/ui-mode-trace.spec.ts
index 9f0749893e..d44b1f595a 100644
--- a/tests/playwright-test/ui-mode-trace.spec.ts
+++ b/tests/playwright-test/ui-mode-trace.spec.ts
@@ -112,7 +112,7 @@ test('should locate sync assertions in source', async ({ runUITest }) => {
});
await page.getByText('trace test').dblclick();
- await page.getByText('expect.toBe').click();
+ await page.getByTestId('actions-tree').getByRole('listitem').filter({ hasText: 'expect.toBe' }).click();
await expect(
page.locator('.CodeMirror .source-line-running'),