chore: two-line trace view (3) (#36058)

This commit is contained in:
Pavel Feldman 2025-05-22 19:00:33 -07:00 committed by GitHub
parent 3a8592910f
commit a15e94aa3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 95 additions and 82 deletions

View File

@ -16,7 +16,7 @@
import { EventEmitter } from './eventEmitter';
import { ValidationError, maybeFindValidator } from '../protocol/validator';
import { methodMetainfo } from '../protocol/debug';
import { methodMetainfo } from '../utils/isomorphic/protocolMetainfo';
import { captureLibraryStackTrace } from './clientStackTrace';
import { stringifyStackFrames } from '../utils/isomorphic/stackTrace';

View File

@ -18,7 +18,7 @@ import { EventEmitter } from 'events';
import { debugMode, isUnderTest, monotonicTime } from '../utils';
import { BrowserContext } from './browserContext';
import { methodMetainfo } from '../protocol/debug';
import { methodMetainfo } from '../utils/isomorphic/protocolMetainfo';
import type { CallMetadata, InstrumentationListener, SdkObject } from './instrumentation';

View File

@ -18,13 +18,12 @@ import { EventEmitter } from 'events';
import { eventsHelper } from '../utils/eventsHelper';
import { ValidationError, createMetadataValidator, findValidator } from '../../protocol/validator';
import { LongStandingScope, assert, formatProtocolParam, monotonicTime, rewriteErrorMessage } from '../../utils';
import { LongStandingScope, assert, monotonicTime, rewriteErrorMessage } from '../../utils';
import { isUnderTest } from '../utils/debug';
import { TargetClosedError, isTargetClosedError, serializeError } from '../errors';
import { SdkObject } from '../instrumentation';
import { isProtocolError } from '../protocolError';
import { compressCallLog } from '../callLog';
import { methodMetainfo } from '../../protocol/debug';
import type { CallMetadata } from '../instrumentation';
import type { PlaywrightDispatcher } from './playwrightDispatcher';
@ -309,7 +308,7 @@ export class DispatcherConnection {
const callMetadata: CallMetadata = {
id: `call@${id}`,
location: validMetadata.location,
title: renderTitle(dispatcher._type, method, params, validMetadata.title),
title: validMetadata.title,
internal: validMetadata.internal,
stepId: validMetadata.stepId,
objectId: sdkObject?.guid,
@ -389,10 +388,3 @@ function closeReason(sdkObject: SdkObject): string | undefined {
sdkObject.attribution.context?._closeReason ||
sdkObject.attribution.browser?._closeReason;
}
function renderTitle(type: string, method: string, params: Record<string, string> | undefined, title?: string) {
const titleFormat = title ?? methodMetainfo.get(type + '.' + method)?.title ?? method;
return titleFormat.replace(/\{([^}]+)\}/g, (_, p1) => {
return formatProtocolParam(params, p1);
});
}

View File

@ -27,7 +27,7 @@ import * as network from './network';
import { Page } from './page';
import { ProgressController } from './progress';
import * as types from './types';
import { LongStandingScope, asLocator, assert, constructURLBasedOnBaseURL, makeWaitForNextTask, monotonicTime } from '../utils';
import { LongStandingScope, asLocator, assert, constructURLBasedOnBaseURL, makeWaitForNextTask, monotonicTime, renderTitleForCall } from '../utils';
import { isSessionClosedError } from './protocolError';
import { debugLogger } from './utils/debugLogger';
import { eventsHelper } from './utils/eventsHelper';
@ -1432,7 +1432,7 @@ export class Frame extends SdkObject {
// Step 1: perform locator handlers checkpoint with a specified timeout.
await (new ProgressController(metadata, this)).run(async progress => {
progress.log(`${metadata.title}${timeout ? ` with timeout ${timeout}ms` : ''}`);
progress.log(`${renderTitleForCall(metadata)}${timeout ? ` with timeout ${timeout}ms` : ''}`);
progress.log(`waiting for ${this._asLocator(selector)}`);
await this._page.performActionPreChecks(progress);
}, timeout);

View File

@ -27,7 +27,7 @@ import { SdkObject } from './instrumentation';
import * as js from './javascript';
import { ProgressController } from './progress';
import { Screenshotter, validateScreenshotOptions } from './screenshotter';
import { LongStandingScope, assert, trimStringWithEllipsis } from '../utils';
import { LongStandingScope, assert, renderTitleForCall, trimStringWithEllipsis } from '../utils';
import { asLocator } from '../utils';
import { getComparator } from './utils/comparators';
import { debugLogger } from './utils/debugLogger';
@ -624,7 +624,7 @@ export class Page extends SdkObject {
let actual: Buffer | undefined;
let previous: Buffer | undefined;
const pollIntervals = [0, 100, 250, 500];
progress.log(`${metadata.title}${callTimeout ? ` with timeout ${callTimeout}ms` : ''}`);
progress.log(`${renderTitleForCall(metadata)}${callTimeout ? ` with timeout ${callTimeout}ms` : ''}`);
if (options.expected)
progress.log(` verifying given screenshot expectation`);
else

View File

@ -14,6 +14,8 @@
* limitations under the License.
*/
import { renderTitleForCall } from '../../utils/isomorphic/protocolFormatter';
import type { Frame } from '../frames';
import type { CallMetadata } from '../instrumentation';
import type { Page } from '../page';
@ -25,7 +27,7 @@ export function buildFullSelector(framePath: string[], selector: string) {
}
export function metadataToCallLog(metadata: CallMetadata, status: CallLogStatus): CallLog {
const title = metadata.title;
const title = renderTitleForCall(metadata);
if (metadata.error)
status = 'error';
const params = {

View File

@ -19,7 +19,7 @@ import os from 'os';
import path from 'path';
import { Snapshotter } from './snapshotter';
import { methodMetainfo } from '../../../protocol/debug';
import { methodMetainfo } from '../../../utils/isomorphic/protocolMetainfo';
import { assert } from '../../../utils/isomorphic/assert';
import { monotonicTime } from '../../../utils/isomorphic/time';
import { eventsHelper } from '../../utils/eventsHelper';

View File

@ -21,6 +21,8 @@ export * from './utils/isomorphic/locatorGenerators';
export * from './utils/isomorphic/manualPromise';
export * from './utils/isomorphic/mimeType';
export * from './utils/isomorphic/multimap';
export * from './utils/isomorphic/protocolFormatter';
export * from './utils/isomorphic/protocolMetainfo';
export * from './utils/isomorphic/rtti';
export * from './utils/isomorphic/semaphore';
export * from './utils/isomorphic/stackTrace';
@ -52,7 +54,4 @@ export * from './server/utils/wsServer';
export * from './server/utils/zipFile';
export * from './server/utils/zones';
export * from './protocol/debug';
export * from './protocol/formatter';
export { colors } from './utilsBundle';

View File

@ -14,6 +14,8 @@
* limitations under the License.
*/
import { methodMetainfo } from './protocolMetainfo';
export function formatProtocolParam(params: Record<string, string> | undefined, name: string): string {
if (!params)
return '';
@ -29,8 +31,10 @@ export function formatProtocolParam(params: Record<string, string> | undefined,
return params[name];
}
}
if (name === 'timeNumber')
if (name === 'timeNumber') {
// eslint-disable-next-line no-restricted-globals
return new Date(params[name]).toString();
}
return deepParam(params, name);
}
@ -46,3 +50,10 @@ function deepParam(params: Record<string, any>, name: string): string {
return '';
return String(current);
}
export function renderTitleForCall(metadata: { title?: string, type: string, method: string, params: Record<string, string> | undefined }) {
const titleFormat = metadata.title ?? methodMetainfo.get(metadata.type + '.' + metadata.method)?.title ?? metadata.method;
return titleFormat.replace(/\{([^}]+)\}/g, (_, p1) => {
return formatProtocolParam(metadata.params, p1);
});
}

View File

@ -18,7 +18,7 @@ import fs from 'fs';
import path from 'path';
import * as playwrightLibrary from 'playwright-core';
import { setBoxedStackPrefixes, createGuid, currentZone, debugMode, jsonStringifyForceASCII, methodMetainfo, asLocatorDescription, formatProtocolParam } from 'playwright-core/lib/utils';
import { setBoxedStackPrefixes, createGuid, currentZone, debugMode, jsonStringifyForceASCII, asLocatorDescription, renderTitleForCall } from 'playwright-core/lib/utils';
import { currentTestInfo } from './common/globals';
import { rootTestType } from './common/testType';
@ -758,8 +758,7 @@ class ArtifactsRecorder {
}
function renderTitle(type: string, method: string, params: Record<string, string> | undefined, title?: string) {
const titleFormat = title ?? methodMetainfo.get(type + '.' + method)?.title ?? method;
const prefix = titleFormat.replace(/\{([^}]+)\}/g, (_, p1) => formatProtocolParam(params, p1));
const prefix = renderTitleForCall({ title, type, method, params });
let selector;
if (params?.['selector'])
selector = asLocatorDescription('javascript', params.selector);

View File

@ -26,6 +26,8 @@ import type { ActionTraceEventInContext, ActionTreeItem } from './modelUtil';
import type { Boundaries } from './geometry';
import { ToolbarButton } from '@web/components/toolbarButton';
import { testStatusIcon } from './testUtils';
import { methodMetainfo } from '@isomorphic/protocolMetainfo';
import { formatProtocolParam } from '@isomorphic/protocolFormatter';
export interface ActionListProps {
actions: ActionTraceEventInContext[],
@ -128,10 +130,10 @@ export const renderAction = (
time = 'Timed out';
else if (!isLive)
time = '-';
const renderedTitle = highlightQuotedText(action.title || action.method);
const { elements, title } = renderTitleForCall(action);
return <div className='action-title vbox'>
<div className='hbox'>
<span className='action-title-method' title={action.title || action.method}>{renderedTitle}</span>
<span className='action-title-method' title={title}>{elements}</span>
{(showDuration || showBadges || showAttachments || isSkipped) && <div className='spacer'></div>}
{showAttachments && <ToolbarButton icon='attach' title='Open Attachment' onClick={() => revealAttachment(action.attachments![0])} />}
{showDuration && !isSkipped && <div className='action-duration'>{time || <span className='codicon codicon-loading'></span>}</div>}
@ -145,19 +147,33 @@ export const renderAction = (
</div>;
};
function highlightQuotedText(text: string): React.ReactNode[] {
const result: React.ReactNode[] = [];
export function renderTitleForCall(action: ActionTraceEvent): { elements: React.ReactNode[], title: string } {
const titleFormat = action.title ?? methodMetainfo.get(action.class + '.' + action.method)?.title ?? action.method;
const elements: React.ReactNode[] = [];
const title: string[] = [];
let currentIndex = 0;
const regex = /("[^"]*")/g;
const regex = /\{([^}]+)\}/g;
let match;
while ((match = regex.exec(text)) !== null) {
while ((match = regex.exec(titleFormat)) !== null) {
const [fullMatch, quotedText] = match;
result.push(text.slice(currentIndex, match.index));
result.push(<span className='action-title-param'>{quotedText}</span>);
const chunk = titleFormat.slice(currentIndex, match.index);
elements.push(chunk);
title.push(chunk);
const param = formatProtocolParam(action.params, quotedText);
elements.push(<span className='action-title-param'>{param}</span>);
title.push(param);
currentIndex = match.index + fullMatch.length;
}
if (currentIndex < text.length)
result.push(text.slice(currentIndex));
return result;
if (currentIndex < titleFormat.length) {
const chunk = titleFormat.slice(currentIndex);
elements.push(chunk);
title.push(chunk);
}
return { elements, title: title.join('') };
}

View File

@ -24,6 +24,7 @@ import type { ActionTreeItem } from '../../packages/trace-viewer/src/ui/modelUti
import { buildActionTree, MultiTraceModel } from '../../packages/trace-viewer/src/ui/modelUtil';
import type { ActionTraceEvent, ConsoleMessageTraceEvent, EventTraceEvent, TraceEvent } from '@trace/trace';
import style from 'ansi-styles';
import { renderTitleForCall } from '../../packages/playwright-core/lib/utils/isomorphic/protocolFormatter';
export async function attachFrame(page: Page, frameId: string, url: string): Promise<Frame> {
const handle = await page.evaluateHandle(async ({ frameId, url }) => {
@ -151,7 +152,7 @@ export async function parseTraceRaw(file: string): Promise<{ events: any[], reso
return {
events,
resources,
actions: actionObjects.map(a => a.title ?? a.class.toLowerCase() + '.' + a.method),
actions: actionObjects.map(a => renderTitleForCall({ ...a, type: a.class })),
actionObjects,
stacks,
};
@ -165,13 +166,14 @@ export async function parseTrace(file: string): Promise<{ resources: Map<string,
const { rootItem } = buildActionTree(model.actions);
const actionTree: string[] = [];
const visit = (actionItem: ActionTreeItem, indent: string) => {
actionTree.push(`${indent}${actionItem.action?.title || actionItem.id}`);
const title = renderTitleForCall({ ...actionItem.action, type: actionItem.action.class });
actionTree.push(`${indent}${title || actionItem.id}`);
for (const child of actionItem.children)
visit(child, indent + ' ');
};
rootItem.children.forEach(a => visit(a, ''));
return {
titles: model.actions.map(a => a.title ?? a.class.toLowerCase() + '.' + a.method),
titles: model.actions.map(a => renderTitleForCall({ ...a, type: a.class })),
resources: backend.entries,
actions: model.actions,
events: model.events,

View File

@ -271,7 +271,7 @@ test('should render console', async ({ showTraceViewer, browserName }) => {
await expect.soft(traceViewer.consoleLines.filter({ hasText: 'Cheers!' }).locator('.codicon')).toHaveClass('codicon codicon-browser status-none');
await expect(traceViewer.consoleStacks.first()).toContainText('Error: Unhandled exception');
await traceViewer.selectAction('EVALUATE');
await traceViewer.selectAction('Evaluate');
const listViews = traceViewer.page.locator('.console-tab').locator('.list-view-entry');
await expect(listViews.nth(0)).toHaveClass('list-view-entry');
@ -284,17 +284,17 @@ test('should render console', async ({ showTraceViewer, browserName }) => {
test('should open console errors on click', async ({ showTraceViewer, browserName }) => {
const traceViewer = await showTraceViewer([traceFile]);
expect(await traceViewer.actionIconsText('EVALUATE')).toEqual(['2', '1']);
expect(await traceViewer.actionIconsText('Evaluate')).toEqual(['2', '1']);
expect(await traceViewer.page.isHidden('.console-tab')).toBeTruthy();
await (await traceViewer.actionIcons('EVALUATE')).click();
await (await traceViewer.actionIcons('Evaluate')).click();
expect(await traceViewer.page.waitForSelector('.console-tab')).toBeTruthy();
});
test('should show params and return value', async ({ showTraceViewer }) => {
const traceViewer = await showTraceViewer([traceFile]);
await traceViewer.selectAction('EVALUATE');
await traceViewer.selectAction('Evaluate');
await expect(traceViewer.callLines).toHaveText([
/Evaluate/,
'',
/start:[\d\.]+m?s/,
/duration:[\d]+ms/,
/expression:"\({↵ a↵ }\) => {↵ console\.log\(\'Info\'\);↵ console\.warn\(\'Warning\'\);↵ console/,
@ -318,9 +318,9 @@ test('should show params and return value', async ({ showTraceViewer }) => {
test('should show null as a param', async ({ showTraceViewer, browserName }) => {
const traceViewer = await showTraceViewer([traceFile]);
await traceViewer.selectAction('EVALUATE', 1);
await traceViewer.selectAction('Evaluate', 1);
await expect(traceViewer.callLines).toHaveText([
/Evaluate/,
'',
/start:[\d\.]+m?s/,
/duration:[\d]+ms/,
'expression:"() => 1 + 1"',
@ -354,7 +354,7 @@ test('should have correct stack trace', async ({ showTraceViewer }) => {
test('should have network requests', async ({ showTraceViewer }) => {
const traceViewer = await showTraceViewer([traceFile]);
await traceViewer.selectAction('NAVIGATE');
await traceViewer.selectAction('Navigate');
await traceViewer.showNetworkTab();
await expect(traceViewer.networkRequests).toContainText([/frame.htmlGET200text\/html/]);
await expect(traceViewer.networkRequests).toContainText([/style.cssGET200text\/css/]);
@ -369,7 +369,7 @@ test('should filter network requests by resource type', async ({ page, runAndTra
await page.goto(`${server.PREFIX}/network-tab/network.html`);
await page.evaluate(() => (window as any).donePromise);
});
await traceViewer.selectAction('NAVIGATE');
await traceViewer.selectAction('Navigate');
await traceViewer.showNetworkTab();
await traceViewer.page.getByText('JS', { exact: true }).click();
@ -402,7 +402,7 @@ test('should show font preview', async ({ page, runAndTrace, server }) => {
await page.goto(`${server.PREFIX}/network-tab/network.html`);
await page.evaluate(() => (window as any).donePromise);
});
await traceViewer.selectAction('NAVIGATE');
await traceViewer.selectAction('Navigate');
await traceViewer.showNetworkTab();
await traceViewer.page.getByText('Font', { exact: true }).click();
@ -417,7 +417,7 @@ test('should filter network requests by url', async ({ page, runAndTrace, server
await page.goto(`${server.PREFIX}/network-tab/network.html`);
await page.evaluate(() => (window as any).donePromise);
});
await traceViewer.selectAction('NAVIGATE');
await traceViewer.selectAction('Navigate');
await traceViewer.showNetworkTab();
await traceViewer.page.getByPlaceholder('Filter network').fill('script.');
@ -446,7 +446,7 @@ test('should have network request overrides', async ({ page, server, runAndTrace
await page.route('**/style.css', route => route.abort());
await page.goto(server.PREFIX + '/frames/frame.html');
});
await traceViewer.selectAction('NAVIGATE');
await traceViewer.selectAction('Navigate');
await traceViewer.showNetworkTab();
await expect(traceViewer.networkRequests).toContainText([/frame.htmlGET200text\/html/]);
await expect(traceViewer.networkRequests).toContainText([/style.cssGETx-unknown.*aborted/]);
@ -458,7 +458,7 @@ test('should have network request overrides 2', async ({ page, server, runAndTra
await page.route('**/script.js', route => route.continue());
await page.goto(server.PREFIX + '/frames/frame.html');
});
await traceViewer.selectAction('NAVIGATE');
await traceViewer.selectAction('Navigate');
await traceViewer.showNetworkTab();
await expect.soft(traceViewer.networkRequests).toContainText([/frame.htmlGET200text\/html.*/]);
await expect.soft(traceViewer.networkRequests).toContainText([/script.jsGET200application\/javascript.*continued/]);
@ -1506,7 +1506,7 @@ test('should show correct request start time', {
return fetch('/api').then(r => r.text());
});
});
await traceViewer.selectAction('EVALUATE');
await traceViewer.selectAction('Evaluate');
await traceViewer.showNetworkTab();
await expect(traceViewer.networkRequests).toContainText([/apiGET200text/]);
const line = traceViewer.networkRequests.getByText(/apiGET200text/);
@ -1553,7 +1553,7 @@ test('should show baseURL in metadata pane', {
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/31847' },
}, async ({ showTraceViewer }) => {
const traceViewer = await showTraceViewer([traceFile]);
await traceViewer.selectAction('EVALUATE');
await traceViewer.selectAction('Evaluate');
await traceViewer.showMetadataTab();
await expect(traceViewer.metadataTab).toContainText('baseURL:https://example.com');
});
@ -1595,22 +1595,22 @@ test('should not leak recorders', {
await expect(traceViewer.snapshotContainer.contentFrame().locator('body')).toContainText(`Hi, I'm frame`);
const frame1 = await forceRecorder('NAVIGATE');
const frame1 = await forceRecorder('Navigate');
await expect(frame1.locator('body')).toContainText('Hello world');
const frame2 = await forceRecorder('EVALUATE');
const frame2 = await forceRecorder('Evaluate');
await expect(frame2.locator('button')).toBeVisible();
await traceViewer.page.requestGC();
await expect.poll(() => aliveCount()).toBeLessThanOrEqual(2); // two snapshot iframes
const frame3 = await forceRecorder('SET VIEWPORT SIZE');
const frame3 = await forceRecorder('Set viewport size');
await expect(frame3.locator('body')).toContainText(`Hi, I'm frame`);
const frame4 = await forceRecorder('NAVIGATE');
const frame4 = await forceRecorder('Navigate');
await expect(frame4.locator('body')).toContainText('Hello world');
const frame5 = await forceRecorder('EVALUATE');
const frame5 = await forceRecorder('Evaluate');
await expect(frame5.locator('button')).toBeVisible();
await traceViewer.page.requestGC();
@ -1751,14 +1751,14 @@ test('should show a modal dialog', async ({ runAndTrace, page, platform, browser
test('should open settings dialog', async ({ showTraceViewer }) => {
const traceViewer = await showTraceViewer([traceFile]);
await traceViewer.selectAction('NAVIGATE');
await traceViewer.selectAction('Navigate');
await traceViewer.showSettings();
await expect(traceViewer.settingsDialog).toBeVisible();
});
test('should toggle theme color', async ({ showTraceViewer, page }) => {
const traceViewer = await showTraceViewer([traceFile]);
await traceViewer.selectAction('NAVIGATE');
await traceViewer.selectAction('Navigate');
await traceViewer.showSettings();
await expect(traceViewer.darkModeSetting).toBeChecked({ checked: false });
@ -1781,7 +1781,7 @@ test('should toggle canvas rendering', async ({ runAndTrace, page }) => {
let snapshotRequestPromise = traceViewer.page.waitForRequest(request => request.url().includes('/snapshot/'));
// Click on the action with a canvas snapshot
await traceViewer.selectAction('NAVIGATE', 0);
await traceViewer.selectAction('Navigate', 0);
let snapshotRequest = await snapshotRequestPromise;
@ -1794,12 +1794,12 @@ test('should toggle canvas rendering', async ({ runAndTrace, page }) => {
await expect(traceViewer.displayCanvasContentSetting).toBeChecked({ checked: true });
// Deselect canvas
await traceViewer.selectAction('NAVIGATE', 1);
await traceViewer.selectAction('Navigate', 1);
snapshotRequestPromise = traceViewer.page.waitForRequest(request => request.url().includes('/snapshot/'));
// Select canvas again
await traceViewer.selectAction('NAVIGATE', 0);
await traceViewer.selectAction('Navigate', 0);
snapshotRequest = await snapshotRequestPromise;

View File

@ -134,7 +134,7 @@ test('should not include buffers in the trace', async ({ context, page, server }
await page.screenshot();
await context.tracing.stop({ path: testInfo.outputPath('trace.zip') });
const { actionObjects } = await parseTraceRaw(testInfo.outputPath('trace.zip'));
const screenshotEvent = actionObjects.find(a => a.title === 'Screenshot');
const screenshotEvent = actionObjects.find(a => a.method === 'screenshot');
expect(screenshotEvent.beforeSnapshot).toBeTruthy();
expect(screenshotEvent.afterSnapshot).toBeTruthy();
expect(screenshotEvent.result).toEqual({
@ -160,13 +160,12 @@ test('should exclude internal pages', async ({ browserName, context, page, serve
expect(pageIds.size).toBe(1);
});
test('should include context API requests', async ({ browserName, context, page, server }, testInfo) => {
test('should include context API requests', async ({ context, page, server }, testInfo) => {
await context.tracing.start({ snapshots: true });
await page.request.post(server.PREFIX + '/simple.json', { data: { foo: 'bar' } });
await context.tracing.stop({ path: testInfo.outputPath('trace.zip') });
const { events } = await parseTraceRaw(testInfo.outputPath('trace.zip'));
const postEvent = events.find(e => e.title === 'Fetch "/simple.json"');
expect(postEvent).toBeTruthy();
const { events, actions } = await parseTraceRaw(testInfo.outputPath('trace.zip'));
expect(actions).toContain('Fetch "/simple.json"');
const harEntry = events.find(e => e.type === 'resource-snapshot');
expect(harEntry).toBeTruthy();
expect(harEntry.snapshot.request.url).toBe(server.PREFIX + '/simple.json');
@ -482,9 +481,8 @@ test('should include interrupted actions', async ({ context, page, server }, tes
await context.tracing.stop({ path: testInfo.outputPath('trace.zip') });
await context.close();
const { events } = await parseTraceRaw(testInfo.outputPath('trace.zip'));
const clickEvent = events.find(e => e.title === 'Click');
expect(clickEvent).toBeTruthy();
const { actions } = await parseTraceRaw(testInfo.outputPath('trace.zip'));
expect(actions).toContain('Click');
});
test('should throw when starting with different options', async ({ context }) => {
@ -741,7 +739,6 @@ test('should not flush console events', async ({ context, page, mode }, testInfo
await expect(async () => {
const traceName = fs.readdirSync(dir).find(name => name.endsWith(testId + '.trace'));
content = await fs.promises.readFile(path.join(dir, traceName), 'utf8');
expect(content).toContain('Evaluate');
expect(content).toContain('31415926');
}).toPass();
expect(content).not.toContain('hello 0');
@ -823,17 +820,14 @@ test('should not emit after w/o before', async ({ browserType, mode }, testInfo)
{
type: 'before',
callId: expect.any(Number),
title: 'Evaluate'
},
{
type: 'before',
callId: expect.any(Number),
title: 'Wait for event "console"'
},
{
type: 'after',
callId: expect.any(Number),
title: undefined,
},
]);
call1 = sanitized[0].callId;
@ -849,12 +843,10 @@ test('should not emit after w/o before', async ({ browserType, mode }, testInfo)
{
type: 'before',
callId: expect.any(Number),
title: 'Evaluate'
},
{
type: 'after',
callId: expect.any(Number),
title: undefined
}
]);
call2before = sanitized[0].callId;

View File

@ -1346,7 +1346,7 @@ test('should record trace snapshot for more obscure commands', async ({ runInlin
const snapshots = trace.traceModel.storage();
const snapshotFrameOrPageId = snapshots.snapshotsForTest()[0];
const countAction = trace.actions.find(a => a.title === 'Query count');
const countAction = trace.actions.find(a => a.method === 'queryCount');
expect(countAction.beforeSnapshot).toBeTruthy();
expect(countAction.afterSnapshot).toBeTruthy();
expect(snapshots.snapshotByName(snapshotFrameOrPageId, countAction.beforeSnapshot)).toBeTruthy();

View File

@ -157,7 +157,7 @@ export type { Validator, ValidatorContext } from './validatorPrimitives';
export { ValidationError, findValidator, maybeFindValidator, createMetadataValidator } from './validatorPrimitives';
`];
const debug_ts = [
const metainfo_ts = [
`/**
* Copyright (c) Microsoft Corporation.
*
@ -328,7 +328,7 @@ for (const [name, item] of Object.entries(protocol)) {
}
}
debug_ts.push(`export const methodMetainfo = new Map<string, { internal?: boolean, title?: string, slowMo?: boolean, snapshot?: boolean, pausesBeforeInput?: boolean }>([
metainfo_ts.push(`export const methodMetainfo = new Map<string, { internal?: boolean, title?: string, slowMo?: boolean, snapshot?: boolean, pausesBeforeInput?: boolean }>([
${methodMetainfo.join(`,\n `)}
]);`);
@ -348,6 +348,6 @@ function writeFile(filePath, content) {
}
writeFile(path.join(__dirname, '..', 'packages', 'protocol', 'src', 'channels.d.ts'), channels_ts.join('\n') + '\n');
writeFile(path.join(__dirname, '..', 'packages', 'playwright-core', 'src', 'protocol', 'debug.ts'), debug_ts.join('\n') + '\n');
writeFile(path.join(__dirname, '..', 'packages', 'playwright-core', 'src', 'utils', 'isomorphic', 'protocolMetainfo.ts'), metainfo_ts.join('\n') + '\n');
writeFile(path.join(__dirname, '..', 'packages', 'playwright-core', 'src', 'protocol', 'validator.ts'), validator_ts.join('\n') + '\n');
process.exit(hasChanges ? 1 : 0);