Skip to content

Commit

Permalink
feat(trace-viewer): Render context string for most actions (#34292)
Browse files Browse the repository at this point in the history
  • Loading branch information
agg23 authored Jan 16, 2025
1 parent be6caed commit 84bbc5f
Show file tree
Hide file tree
Showing 3 changed files with 234 additions and 5 deletions.
9 changes: 8 additions & 1 deletion packages/trace-viewer/src/ui/actionList.css
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,20 @@
flex: none;
}

.action-selector {
.action-parameter {
display: inline;
flex: none;
padding-left: 5px;
}

.action-locator-parameter {
color: var(--vscode-charts-orange);
}

.action-generic-parameter {
color: var(--vscode-charts-purple);
}

.action-url {
display: inline;
flex: none;
Expand Down
175 changes: 171 additions & 4 deletions packages/trace-viewer/src/ui/actionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ import { msToString } from '@web/uiUtils';
import * as React from 'react';
import './actionList.css';
import * as modelUtil from './modelUtil';
import { asLocator } from '@isomorphic/locatorGenerators';
import type { Language } from '@isomorphic/locatorGenerators';
import { asLocator, type Language } from '@isomorphic/locatorGenerators';
import type { TreeState } from '@web/components/treeView';
import { TreeView } from '@web/components/treeView';
import type { ActionTraceEventInContext, ActionTreeItem } from './modelUtil';
Expand Down Expand Up @@ -116,9 +115,10 @@ export const renderAction = (
}) => {
const { sdkLanguage, revealConsole, revealAttachment, isLive, showDuration, showBadges } = options;
const { errors, warnings } = modelUtil.stats(action);
const locator = action.params.selector ? asLocator(sdkLanguage || 'javascript', action.params.selector) : undefined;
const showAttachments = !!action.attachments?.length && !!revealAttachment;

const parameterString = actionParameterDisplayString(action, sdkLanguage || 'javascript');

let time: string = '';
if (action.endTime)
time = msToString(action.endTime - action.startTime);
Expand All @@ -129,7 +129,23 @@ export const renderAction = (
return <>
<div className='action-title' title={action.apiName}>
<span>{action.apiName}</span>
{locator && <div className='action-selector' title={locator}>{locator}</div>}
{parameterString &&
(parameterString.type === 'locator' ? (
<>
<span className='action-parameter action-locator-parameter'>
{parameterString.value}
</span>
{parameterString.childDisplayString && (
<span className='action-parameter action-generic-parameter'>
{parameterString.childDisplayString.value}
</span>
)}
</>
) : (
<span className='action-parameter action-generic-parameter'>
{parameterString.value}
</span>
))}
{action.method === 'goto' && action.params.url && <div className='action-url' title={action.params.url}>{action.params.url}</div>}
{action.class === 'APIRequestContext' && action.params.url && <div className='action-url' title={action.params.url}>{excludeOrigin(action.params.url)}</div>}
</div>
Expand All @@ -151,3 +167,154 @@ function excludeOrigin(url: string): string {
return url;
}
}

type ActionParameterDisplayString =
| {
type: 'generic';
value: string;
}
| {
type: 'locator';
value: string;
childDisplayString?: ActionParameterDisplayString;
};

const clockDisplayString = (
action: ActionTraceEvent,
): ActionParameterDisplayString | undefined => {
switch (action.method) {
case 'clockPauseAt':
case 'clockSetFixedTime':
case 'clockSetSystemTime': {
if (
action.params.timeString === undefined &&
action.params.timeNumber === undefined
)
return undefined;
return {
type: 'generic',
value: new Date(
action.params.timeString ?? action.params.timeNumber,
).toLocaleString(undefined, { timeZone: 'UTC' }),
};
}
case 'clockFastForward':
case 'clockRunFor': {
if (
action.params.ticksNumber === undefined &&
action.params.ticksString === undefined
)
return undefined;
return {
type: 'generic',
value: action.params.ticksString ?? `${action.params.ticksNumber}ms`,
};
}
}

return undefined;
};

const keyboardDisplayString = (
action: ActionTraceEvent,
): ActionParameterDisplayString | undefined => {
switch (action.method) {
case 'press':
case 'keyboardPress':
case 'keyboardDown':
case 'keyboardUp': {
if (action.params.key === undefined)
return undefined;
return { type: 'generic', value: action.params.key };
}
case 'type':
case 'fill':
case 'keyboardType':
case 'keyboardInsertText': {
const string = action.params.text ?? action.params.value;
if (string === undefined)
return undefined;
return { type: 'generic', value: `"${string}"` };
}
}
};

const mouseDisplayString = (
action: ActionTraceEvent,
): ActionParameterDisplayString | undefined => {
switch (action.method) {
case 'click':
case 'dblclick':
case 'mouseClick':
case 'mouseMove': {
if (action.params.x === undefined || action.params.y === undefined)
return undefined;
return {
type: 'generic',
value: `(${action.params.x}, ${action.params.y})`,
};
}
case 'mouseWheel': {
if (
action.params.deltaX === undefined ||
action.params.deltaY === undefined
)
return undefined;
return {
type: 'generic',
value: `(${action.params.deltaX}, ${action.params.deltaY})`,
};
}
}
};

const touchscreenDisplayString = (
action: ActionTraceEvent,
): ActionParameterDisplayString | undefined => {
switch (action.method) {
case 'tap': {
if (action.params.x === undefined || action.params.y === undefined)
return undefined;
return {
type: 'generic',
value: `(${action.params.x}, ${action.params.y})`,
};
}
}
};

const actionParameterDisplayString = (
action: ActionTraceEvent,
sdkLanguage: Language,
ignoreLocator: boolean = false,
): ActionParameterDisplayString | undefined => {
const params = action.params;

// Locators have many possible classes, so follow existing logic and use `selector` presence
if (!ignoreLocator && params.selector !== undefined) {
return {
type: 'locator',
value: asLocator(sdkLanguage, params.selector),
childDisplayString: actionParameterDisplayString(
action,
sdkLanguage,
true,
),
};
}

switch (action.class.toLowerCase()) {
case 'browsercontext':
return clockDisplayString(action);
case 'page':
case 'frame':
case 'elementhandle':
return (
keyboardDisplayString(action) ??
mouseDisplayString(action) ??
touchscreenDisplayString(action)
);
}

return undefined;
};
55 changes: 55 additions & 0 deletions tests/library/trace-viewer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,61 @@ test('should open simple trace viewer', async ({ showTraceViewer }) => {
]);
});

test('should show action context on locators and other common actions', async ({
runAndTrace,
page,
}) => {
const traceViewer = await runAndTrace(async () => {
await page.setContent('<input type="text" />');
await page.locator('input').click({ button: 'right' });
await page.getByRole('textbox').click();
await expect(page.locator('input')).toHaveText('');
await page.locator('input').press('Enter');
await page.keyboard.type(
'Hello world this is a very long string what happens when it overflows?',
);
await page.keyboard.press('Control+c');
await page.keyboard.down('Shift');
await page.keyboard.insertText('Hello world');
await page.keyboard.up('Shift');
await page.mouse.move(0, 0);
await page.mouse.down();
await page.mouse.move(100, 200);
await page.mouse.wheel(5, 7);
await page.mouse.up();
await page.clock.fastForward(1000);
await page.clock.fastForward('30:00');
await page.clock.pauseAt(new Date('2020-02-02T00:00:00Z'));
await page.clock.runFor(10);
await page.clock.setFixedTime(new Date('2020-02-02T00:00:00Z'));
await page.clock.setSystemTime(new Date('2020-02-02T00:00:00Z'));
});

await expect(traceViewer.actionTitles).toHaveText([
/page.setContent/,
/locator.clicklocator\('input'\)/,
/locator.clickgetByRole\('textbox'\)/,
/expect.toHaveTextlocator\('input'\)/,
/locator.presslocator\('input'\)Enter/,
/keyboard.type\"Hello world this is a very long string what happens when it overflows\?\"/,
/keyboard.pressControl\+c/,
/keyboard.downShift/,
/keyboard.insertText\"Hello world\"/,
/keyboard.upShift/,
/mouse.move\(0, 0\)/,
/mouse.down/,
/mouse.move\(100, 200\)/,
/mouse.wheel\(5, 7\)/,
/mouse.up/,
/clock.fastForward1000ms/,
/clock.fastForward30:00/,
/clock.pauseAt2\/2\/2020, 12:00:00 AM/,
/clock.runFor10ms/,
/clock.setFixedTime2\/2\/2020, 12:00:00 AM/,
/clock.setSystemTime2\/2\/2020, 12:00:00 AM/,
]);
});

test('should complain about newer version of trace in old viewer', async ({ showTraceViewer, asset }, testInfo) => {
const traceViewer = await showTraceViewer([asset('trace-from-the-future.zip')]);
await expect(traceViewer.page.getByText('The trace was created by a newer version of Playwright and is not supported by this version of the viewer.')).toBeVisible();
Expand Down

0 comments on commit 84bbc5f

Please sign in to comment.