feat(ui-mode): show step breadcrumbs in Trace Viewer
This commit is contained in:
parent
d0c840f639
commit
8663a6dade
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
<Toolbar>
|
||||
<ToolbarButton className='pick-locator' title='Pick locator' icon='target' toggled={isInspecting} onClick={() => setIsInspecting(!isInspecting)} />
|
||||
{['action', 'before', 'after'].map(tab => {
|
||||
return <TabbedPaneTab
|
||||
id={tab}
|
||||
title={renderTitle(tab)}
|
||||
selected={snapshotTab === tab}
|
||||
onSelect={() => setSnapshotTab(tab as 'action' | 'before' | 'after')}
|
||||
></TabbedPaneTab>;
|
||||
})}
|
||||
<div style={{ flex: 'auto' }}></div>
|
||||
<ToolbarButton icon='link-external' title='Open snapshot in a new tab' disabled={!popoutUrl} onClick={() => {
|
||||
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);
|
||||
});
|
||||
}}></ToolbarButton>
|
||||
</Toolbar>
|
||||
<div className='hbox' style={{ flex: '0 0 auto' }}>
|
||||
<Toolbar style={{
|
||||
flex: '0 1 0',
|
||||
minWidth: 'min-content',
|
||||
flexGrow: 1
|
||||
}}>
|
||||
<ToolbarButton className='pick-locator' title='Pick locator' icon='target' toggled={isInspecting} onClick={() => setIsInspecting(!isInspecting)} />
|
||||
{['action', 'before', 'after'].map(tab => {
|
||||
return <TabbedPaneTab
|
||||
id={tab}
|
||||
title={renderTitle(tab)}
|
||||
selected={snapshotTab === tab}
|
||||
onSelect={() => setSnapshotTab(tab as 'action' | 'before' | 'after')}
|
||||
></TabbedPaneTab>;
|
||||
})}
|
||||
</Toolbar>
|
||||
<Toolbar
|
||||
style={{
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
direction: 'rtl',
|
||||
flexGrow: 0,
|
||||
flexShrink: 1,
|
||||
}}
|
||||
data-testid='snapshot-breadcrumb'>
|
||||
{actionToBreadcrumb(action)}
|
||||
</Toolbar>
|
||||
<Toolbar style={{
|
||||
flex: '0 1 0',
|
||||
minWidth: 'min-content',
|
||||
flexGrow: 1,
|
||||
justifyContent: 'flex-end',
|
||||
}}>
|
||||
<ToolbarButton icon='link-external' title='Open snapshot in a new tab' disabled={!popoutUrl} onClick={() => {
|
||||
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);
|
||||
});
|
||||
}}></ToolbarButton>
|
||||
</Toolbar>
|
||||
</div>
|
||||
<div ref={ref} className='snapshot-wrapper'>
|
||||
<div className='snapshot-container' style={{
|
||||
width: snapshotContainerSize.width + 'px',
|
||||
|
|
@ -217,6 +241,17 @@ export const SnapshotTab: React.FunctionComponent<{
|
|||
</div>;
|
||||
};
|
||||
|
||||
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';
|
||||
|
|
|
|||
|
|
@ -22,15 +22,14 @@ type ToolbarProps = {
|
|||
noShadow?: boolean;
|
||||
noMinHeight?: boolean;
|
||||
className?: string;
|
||||
onClick?: (e: React.MouseEvent) => void;
|
||||
};
|
||||
} & React.HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
export const Toolbar: React.FC<React.PropsWithChildren<ToolbarProps>> = ({
|
||||
noShadow,
|
||||
children,
|
||||
noMinHeight,
|
||||
className,
|
||||
onClick,
|
||||
...props
|
||||
}) => {
|
||||
return <div className={clsx('toolbar', noShadow && 'no-shadow', noMinHeight && 'no-min-height', className)} onClick={onClick}>{children}</div>;
|
||||
return <div className={clsx('toolbar', noShadow && 'no-shadow', noMinHeight && 'no-min-height', className)} {...props}>{children}</div>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
Loading…
Reference in a new issue