mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(trace viewer): render selectors as locators (#19907)
Drive-by: fix more places with SerializedValue rendering. Fixes #19085.
This commit is contained in:
parent
31a63b5c2a
commit
cd698a2258
@ -77,12 +77,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.call-line .datetime,
|
.call-line .datetime,
|
||||||
.call-line .string {
|
.call-line .string,
|
||||||
|
.call-line .locator {
|
||||||
color: var(--orange);
|
color: var(--orange);
|
||||||
}
|
}
|
||||||
|
|
||||||
.call-line .number,
|
.call-line .number,
|
||||||
|
.call-line .bigint,
|
||||||
.call-line .boolean,
|
.call-line .boolean,
|
||||||
|
.call-line .symbol,
|
||||||
|
.call-line .undefined,
|
||||||
|
.call-line .function,
|
||||||
.call-line .object {
|
.call-line .object {
|
||||||
color: var(--blue);
|
color: var(--blue);
|
||||||
}
|
}
|
||||||
|
@ -21,10 +21,13 @@ import { msToString } from '@web/uiUtils';
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import './callTab.css';
|
import './callTab.css';
|
||||||
import { CopyToClipboard } from './copyToClipboard';
|
import { CopyToClipboard } from './copyToClipboard';
|
||||||
|
import { asLocator } from '@isomorphic/locatorGenerators';
|
||||||
|
import type { Language } from '@isomorphic/locatorGenerators';
|
||||||
|
|
||||||
export const CallTab: React.FunctionComponent<{
|
export const CallTab: React.FunctionComponent<{
|
||||||
action: ActionTraceEvent | undefined,
|
action: ActionTraceEvent | undefined,
|
||||||
}> = ({ action }) => {
|
sdkLanguage: Language | undefined,
|
||||||
|
}> = ({ action, sdkLanguage }) => {
|
||||||
if (!action)
|
if (!action)
|
||||||
return null;
|
return null;
|
||||||
const logs = action.metadata.log;
|
const logs = action.metadata.log;
|
||||||
@ -48,12 +51,12 @@ export const CallTab: React.FunctionComponent<{
|
|||||||
</>}
|
</>}
|
||||||
{ !!paramKeys.length && <div className='call-section'>Parameters</div> }
|
{ !!paramKeys.length && <div className='call-section'>Parameters</div> }
|
||||||
{
|
{
|
||||||
!!paramKeys.length && paramKeys.map((name, index) => renderLine(action.metadata, name, params[name], 'param-' + index))
|
!!paramKeys.length && paramKeys.map((name, index) => renderProperty(propertyToString(action.metadata, name, params[name], sdkLanguage), 'param-' + index))
|
||||||
}
|
}
|
||||||
{ !!action.metadata.result && <div className='call-section'>Return value</div> }
|
{ !!action.metadata.result && <div className='call-section'>Return value</div> }
|
||||||
{
|
{
|
||||||
!!action.metadata.result && Object.keys(action.metadata.result).map((name, index) =>
|
!!action.metadata.result && Object.keys(action.metadata.result).map((name, index) =>
|
||||||
renderLine(action.metadata, name, action.metadata.result[name], 'result-' + index)
|
renderProperty(propertyToString(action.metadata, name, action.metadata.result[name], sdkLanguage), 'result-' + index)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<div className='call-section'>Log</div>
|
<div className='call-section'>Log</div>
|
||||||
@ -67,44 +70,42 @@ export const CallTab: React.FunctionComponent<{
|
|||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function shouldCopy(type: string): boolean {
|
type Property = {
|
||||||
return !!({
|
name: string;
|
||||||
'string': true,
|
type: 'string' | 'number' | 'object' | 'locator' | 'handle' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'function';
|
||||||
'number': true,
|
text: string;
|
||||||
'object': true,
|
};
|
||||||
}[type]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderLine(metadata: CallMetadata, name: string, value: any, key: string) {
|
function renderProperty(property: Property, key: string) {
|
||||||
const { title, type } = toString(metadata, name, value);
|
let text = property.text.replace(/\n/g, '↵');
|
||||||
let text = title.replace(/\n/g, '↵');
|
if (property.type === 'string')
|
||||||
if (type === 'string')
|
|
||||||
text = `"${text}"`;
|
text = `"${text}"`;
|
||||||
return (
|
return (
|
||||||
<div key={key} className='call-line'>
|
<div key={key} className='call-line'>
|
||||||
{name}: <span className={`call-value ${type}`} title={title}>{text}</span>
|
{property.name}: <span className={`call-value ${property.type}`} title={property.text}>{text}</span>
|
||||||
{ shouldCopy(type) && (
|
{ ['string', 'number', 'object', 'locator'].includes(property.type) &&
|
||||||
<span className='call-line__copy-icon'>
|
<span className='call-line__copy-icon'>
|
||||||
<CopyToClipboard value={title} />
|
<CopyToClipboard value={property.text} />
|
||||||
</span>
|
</span>
|
||||||
)}
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toString(metadata: CallMetadata, name: string, value: any): { title: string, type: string } {
|
function propertyToString(metadata: CallMetadata, name: string, value: any, sdkLanguage: Language | undefined): Property {
|
||||||
if (metadata.method.includes('eval')) {
|
const isEval = metadata.method.includes('eval') || metadata.method === 'waitForFunction';
|
||||||
if (name === 'arg')
|
if (name === 'eventInit' || name === 'expectedValue' || (name === 'arg' && isEval))
|
||||||
value = parseSerializedValue(value.value, new Array(10).fill({ handle: '<handle>' }));
|
value = parseSerializedValue(value.value, new Array(10).fill({ handle: '<handle>' }));
|
||||||
if (name === 'value')
|
if ((name === 'value' && isEval) || (name === 'received' && metadata.method === 'expect'))
|
||||||
value = parseSerializedValue(value, new Array(10).fill({ handle: '<handle>' }));
|
value = parseSerializedValue(value, new Array(10).fill({ handle: '<handle>' }));
|
||||||
}
|
if (name === 'selector')
|
||||||
|
return { text: asLocator(sdkLanguage || 'javascript', metadata.params.selector), type: 'locator', name: 'locator' };
|
||||||
const type = typeof value;
|
const type = typeof value;
|
||||||
if (type !== 'object' || value === null)
|
if (type !== 'object' || value === null)
|
||||||
return { title: String(value), type };
|
return { text: String(value), type, name };
|
||||||
if (value.guid)
|
if (value.guid)
|
||||||
return { title: '<handle>', type: 'handle' };
|
return { text: '<handle>', type: 'handle', name };
|
||||||
return { title: JSON.stringify(value), type: 'object' };
|
return { text: JSON.stringify(value), type: 'object', name };
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseSerializedValue(value: SerializedValue, handles: any[] | undefined): any {
|
function parseSerializedValue(value: SerializedValue, handles: any[] | undefined): any {
|
||||||
|
@ -144,7 +144,7 @@ export const Workbench: React.FunctionComponent<{
|
|||||||
const networkCount = selectedAction ? modelUtil.resourcesForAction(selectedAction).length : 0;
|
const networkCount = selectedAction ? modelUtil.resourcesForAction(selectedAction).length : 0;
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ id: 'logs', title: 'Call', count: 0, render: () => <CallTab action={selectedAction} /> },
|
{ id: 'logs', title: 'Call', count: 0, render: () => <CallTab action={selectedAction} sdkLanguage={model.sdkLanguage} /> },
|
||||||
{ id: 'console', title: 'Console', count: consoleCount, render: () => <ConsoleTab action={selectedAction} /> },
|
{ id: 'console', title: 'Console', count: consoleCount, render: () => <ConsoleTab action={selectedAction} /> },
|
||||||
{ id: 'network', title: 'Network', count: networkCount, render: () => <NetworkTab action={selectedAction} /> },
|
{ id: 'network', title: 'Network', count: networkCount, render: () => <NetworkTab action={selectedAction} /> },
|
||||||
];
|
];
|
||||||
|
@ -151,6 +151,18 @@ test('should show params and return value', async ({ showTraceViewer, browserNam
|
|||||||
'arg: {"a":"paramA","b":4}',
|
'arg: {"a":"paramA","b":4}',
|
||||||
'value: "return paramA"'
|
'value: "return paramA"'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
await traceViewer.selectAction(`locator('button')`);
|
||||||
|
await expect(traceViewer.callLines).toContainText([
|
||||||
|
/expect.toHaveText/,
|
||||||
|
/wall time: [0-9/:,APM ]+/,
|
||||||
|
/duration: [\d]+ms/,
|
||||||
|
/locator: locator\('button'\)/,
|
||||||
|
/expression: "to.have.text"/,
|
||||||
|
/timeout: 10000/,
|
||||||
|
/matches: true/,
|
||||||
|
/received: "Click"/,
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should show null as a param', async ({ showTraceViewer, browserName }) => {
|
test('should show null as a param', async ({ showTraceViewer, browserName }) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user