mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: remove trace viewer (#3869)
This commit is contained in:
parent
dfbd1ceacc
commit
c20cbae529
1126
package-lock.json
generated
1126
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -29,8 +29,7 @@
|
|||||||
"typecheck-tests": "tsc -p ./test/",
|
"typecheck-tests": "tsc -p ./test/",
|
||||||
"roll-browser": "node utils/roll_browser.js",
|
"roll-browser": "node utils/roll_browser.js",
|
||||||
"coverage": "node test/checkCoverage.js",
|
"coverage": "node test/checkCoverage.js",
|
||||||
"check-deps": "node utils/check_deps.js",
|
"check-deps": "node utils/check_deps.js"
|
||||||
"show-trace": "node utils/showTestTraces.js"
|
|
||||||
},
|
},
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Microsoft Corporation"
|
"name": "Microsoft Corporation"
|
||||||
|
@ -46,7 +46,7 @@ export class ConsoleMessage extends ChannelOwner<channels.ConsoleMessageChannel,
|
|||||||
return this._initializer.location;
|
return this._initializer.location;
|
||||||
}
|
}
|
||||||
|
|
||||||
[util.inspect.custom]() {
|
[(util.inspect as any).custom]() {
|
||||||
return this.text();
|
return this.text();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,8 @@ import * as types from '../server/types';
|
|||||||
import { SnapshotData, takeSnapshotInFrame } from './snapshotterInjected';
|
import { SnapshotData, takeSnapshotInFrame } from './snapshotterInjected';
|
||||||
import { assert, calculateSha1, createGuid } from '../utils/utils';
|
import { assert, calculateSha1, createGuid } from '../utils/utils';
|
||||||
|
|
||||||
export type SanpshotterResource = {
|
export type SnapshotterResource = {
|
||||||
|
pageId: string,
|
||||||
frameId: string,
|
frameId: string,
|
||||||
url: string,
|
url: string,
|
||||||
contentType: string,
|
contentType: string,
|
||||||
@ -53,7 +54,8 @@ export type PageSnapshot = {
|
|||||||
|
|
||||||
export interface SnapshotterDelegate {
|
export interface SnapshotterDelegate {
|
||||||
onBlob(blob: SnapshotterBlob): void;
|
onBlob(blob: SnapshotterBlob): void;
|
||||||
onResource(resource: SanpshotterResource): void;
|
onResource(resource: SnapshotterResource): void;
|
||||||
|
pageId(page: Page): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Snapshotter {
|
export class Snapshotter {
|
||||||
@ -75,11 +77,11 @@ export class Snapshotter {
|
|||||||
|
|
||||||
private _onPage(page: Page) {
|
private _onPage(page: Page) {
|
||||||
this._eventListeners.push(helper.addEventListener(page, Page.Events.Response, (response: network.Response) => {
|
this._eventListeners.push(helper.addEventListener(page, Page.Events.Response, (response: network.Response) => {
|
||||||
this._saveResource(response).catch(e => debugLogger.log('error', e));
|
this._saveResource(page, response).catch(e => debugLogger.log('error', e));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _saveResource(response: network.Response) {
|
private async _saveResource(page: Page, response: network.Response) {
|
||||||
const isRedirect = response.status() >= 300 && response.status() <= 399;
|
const isRedirect = response.status() >= 300 && response.status() <= 399;
|
||||||
if (isRedirect)
|
if (isRedirect)
|
||||||
return;
|
return;
|
||||||
@ -98,7 +100,8 @@ export class Snapshotter {
|
|||||||
|
|
||||||
const body = await response.body().catch(e => debugLogger.log('error', e));
|
const body = await response.body().catch(e => debugLogger.log('error', e));
|
||||||
const sha1 = body ? calculateSha1(body) : 'none';
|
const sha1 = body ? calculateSha1(body) : 'none';
|
||||||
const resource: SanpshotterResource = {
|
const resource: SnapshotterResource = {
|
||||||
|
pageId: this._delegate.pageId(page),
|
||||||
frameId: response.frame()._id,
|
frameId: response.frame()._id,
|
||||||
url,
|
url,
|
||||||
contentType,
|
contentType,
|
||||||
|
@ -31,6 +31,7 @@ export type ContextDestroyedTraceEvent = {
|
|||||||
export type NetworkResourceTraceEvent = {
|
export type NetworkResourceTraceEvent = {
|
||||||
type: 'resource',
|
type: 'resource',
|
||||||
contextId: string,
|
contextId: string,
|
||||||
|
pageId: string,
|
||||||
frameId: string,
|
frameId: string,
|
||||||
url: string,
|
url: string,
|
||||||
contentType: string,
|
contentType: string,
|
||||||
|
@ -1,299 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) Microsoft Corporation.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as path from 'path';
|
|
||||||
import * as util from 'util';
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import type { NetworkResourceTraceEvent, ActionTraceEvent, ContextCreatedTraceEvent, ContextDestroyedTraceEvent, PageCreatedTraceEvent, PageDestroyedTraceEvent } from './traceTypes';
|
|
||||||
import type { FrameSnapshot, PageSnapshot } from './snapshotter';
|
|
||||||
import type { Browser, BrowserContext, Frame, Page, Route } from '../client/api';
|
|
||||||
import type { Playwright } from '../client/playwright';
|
|
||||||
|
|
||||||
const fsReadFileAsync = util.promisify(fs.readFile.bind(fs));
|
|
||||||
type TraceEvent =
|
|
||||||
ContextCreatedTraceEvent |
|
|
||||||
ContextDestroyedTraceEvent |
|
|
||||||
PageCreatedTraceEvent |
|
|
||||||
PageDestroyedTraceEvent |
|
|
||||||
NetworkResourceTraceEvent |
|
|
||||||
ActionTraceEvent;
|
|
||||||
|
|
||||||
class TraceViewer {
|
|
||||||
private _playwright: Playwright;
|
|
||||||
private _traceStorageDir: string;
|
|
||||||
private _traces: { traceFile: string, events: TraceEvent[] }[] = [];
|
|
||||||
private _browserNames = new Set<string>();
|
|
||||||
private _resourceEventsByUrl = new Map<string, NetworkResourceTraceEvent[]>();
|
|
||||||
private _contextEventById = new Map<string, ContextCreatedTraceEvent>();
|
|
||||||
private _contextById = new Map<string, BrowserContext>();
|
|
||||||
|
|
||||||
constructor(playwright: Playwright, traceStorageDir: string) {
|
|
||||||
this._playwright = playwright;
|
|
||||||
this._traceStorageDir = traceStorageDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
async load(traceFile: string) {
|
|
||||||
// TODO: validate trace?
|
|
||||||
const traceContent = await fsReadFileAsync(traceFile, 'utf8');
|
|
||||||
const events = traceContent.split('\n').map(line => line.trim()).filter(line => !!line).map(line => JSON.parse(line));
|
|
||||||
for (const event of events) {
|
|
||||||
if (event.type === 'context-created')
|
|
||||||
this._browserNames.add(event.browserName);
|
|
||||||
if (event.type === 'resource') {
|
|
||||||
let responseEvents = this._resourceEventsByUrl.get(event.url);
|
|
||||||
if (!responseEvents) {
|
|
||||||
responseEvents = [];
|
|
||||||
this._resourceEventsByUrl.set(event.url, responseEvents);
|
|
||||||
}
|
|
||||||
responseEvents.push(event);
|
|
||||||
}
|
|
||||||
if (event.type === 'context-created')
|
|
||||||
this._contextEventById.set(event.contextId, event);
|
|
||||||
}
|
|
||||||
this._traces.push({ traceFile, events });
|
|
||||||
}
|
|
||||||
|
|
||||||
browserNames(): Set<string> {
|
|
||||||
return this._browserNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
async show(browserName: string) {
|
|
||||||
const browser = await this._playwright[browserName as ('chromium' | 'firefox' | 'webkit')].launch({ headless: false });
|
|
||||||
const uiPage = await browser.newPage();
|
|
||||||
await uiPage.exposeBinding('renderSnapshot', async (source, action: ActionTraceEvent) => {
|
|
||||||
const snapshot = await fsReadFileAsync(path.join(this._traceStorageDir, action.snapshot!.sha1), 'utf8');
|
|
||||||
const context = await this._ensureContext(browser, action.contextId);
|
|
||||||
const page = await context.newPage();
|
|
||||||
await this._renderSnapshot(page, JSON.parse(snapshot), action.contextId);
|
|
||||||
});
|
|
||||||
|
|
||||||
const contextData: { [contextId: string]: { label: string, actions: ActionTraceEvent[] } } = {};
|
|
||||||
for (const trace of this._traces) {
|
|
||||||
let contextId = 0;
|
|
||||||
for (const event of trace.events) {
|
|
||||||
if (event.type !== 'action')
|
|
||||||
continue;
|
|
||||||
const contextEvent = this._contextEventById.get(event.contextId)!;
|
|
||||||
if (contextEvent.browserName !== browserName)
|
|
||||||
continue;
|
|
||||||
let data = contextData[contextEvent.contextId];
|
|
||||||
if (!data) {
|
|
||||||
data = { label: trace.traceFile + ' :: context' + (++contextId), actions: [] };
|
|
||||||
contextData[contextEvent.contextId] = data;
|
|
||||||
}
|
|
||||||
data.actions.push(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await uiPage.evaluate(traces => {
|
|
||||||
function createSection(parent: Element, title: string): HTMLDetailsElement {
|
|
||||||
const details = document.createElement('details');
|
|
||||||
details.style.paddingLeft = '10px';
|
|
||||||
const summary = document.createElement('summary');
|
|
||||||
summary.textContent = title;
|
|
||||||
details.appendChild(summary);
|
|
||||||
parent.appendChild(details);
|
|
||||||
return details;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createField(parent: Element, text: string) {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.style.whiteSpace = 'pre';
|
|
||||||
div.textContent = text;
|
|
||||||
parent.appendChild(div);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const trace of traces) {
|
|
||||||
const traceSection = createSection(document.body, trace.traceFile);
|
|
||||||
traceSection.open = true;
|
|
||||||
|
|
||||||
const contextSections = new Map<string, Element>();
|
|
||||||
const pageSections = new Map<string, Element>();
|
|
||||||
|
|
||||||
for (const event of trace.events) {
|
|
||||||
if (event.type === 'context-created') {
|
|
||||||
const contextSection = createSection(traceSection, event.contextId);
|
|
||||||
contextSection.open = true;
|
|
||||||
contextSections.set(event.contextId, contextSection);
|
|
||||||
}
|
|
||||||
if (event.type === 'page-created') {
|
|
||||||
const contextSection = contextSections.get(event.contextId)!;
|
|
||||||
const pageSection = createSection(contextSection, event.pageId);
|
|
||||||
pageSection.open = true;
|
|
||||||
pageSections.set(event.pageId, pageSection);
|
|
||||||
}
|
|
||||||
if (event.type === 'action') {
|
|
||||||
const parentSection = event.pageId ? pageSections.get(event.pageId)! : contextSections.get(event.contextId)!;
|
|
||||||
const actionSection = createSection(parentSection, event.action);
|
|
||||||
if (event.label)
|
|
||||||
createField(actionSection, `label: ${event.label}`);
|
|
||||||
if (event.target)
|
|
||||||
createField(actionSection, `target: ${event.target}`);
|
|
||||||
if (event.value)
|
|
||||||
createField(actionSection, `value: ${event.value}`);
|
|
||||||
if (event.startTime && event.endTime)
|
|
||||||
createField(actionSection, `duration: ${event.endTime - event.startTime}ms`);
|
|
||||||
if (event.error) {
|
|
||||||
const errorSection = createSection(actionSection, 'error');
|
|
||||||
createField(errorSection, event.error);
|
|
||||||
}
|
|
||||||
if (event.stack) {
|
|
||||||
const errorSection = createSection(actionSection, 'stack');
|
|
||||||
createField(errorSection, event.stack);
|
|
||||||
}
|
|
||||||
if (event.logs && event.logs.length) {
|
|
||||||
const errorSection = createSection(actionSection, 'logs');
|
|
||||||
createField(errorSection, event.logs.join('\n'));
|
|
||||||
}
|
|
||||||
if (event.snapshot) {
|
|
||||||
const button = document.createElement('button');
|
|
||||||
button.style.display = 'block';
|
|
||||||
button.textContent = `snapshot after (${event.snapshot.duration}ms)`;
|
|
||||||
button.addEventListener('click', () => (window as any).renderSnapshot(event));
|
|
||||||
actionSection.appendChild(button);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, this._traces);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _ensureContext(browser: Browser, contextId: string): Promise<BrowserContext> {
|
|
||||||
let context = this._contextById.get(contextId);
|
|
||||||
if (!context) {
|
|
||||||
const event = this._contextEventById.get(contextId)!;
|
|
||||||
context = await browser.newContext({
|
|
||||||
isMobile: event.isMobile,
|
|
||||||
viewport: event.viewportSize || null,
|
|
||||||
deviceScaleFactor: event.deviceScaleFactor,
|
|
||||||
});
|
|
||||||
this._contextById.set(contextId, context);
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _readResource(event: NetworkResourceTraceEvent, overrideSha1: string | undefined) {
|
|
||||||
try {
|
|
||||||
const body = await fsReadFileAsync(path.join(this._traceStorageDir, overrideSha1 || event.sha1));
|
|
||||||
return {
|
|
||||||
contentType: event.contentType,
|
|
||||||
body,
|
|
||||||
headers: event.responseHeaders,
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _renderSnapshot(page: Page, snapshot: PageSnapshot, contextId: string): Promise<void> {
|
|
||||||
const frameBySrc = new Map<string, FrameSnapshot>();
|
|
||||||
for (const frameSnapshot of snapshot.frames)
|
|
||||||
frameBySrc.set(frameSnapshot.url, frameSnapshot);
|
|
||||||
|
|
||||||
const intercepted: Promise<any>[] = [];
|
|
||||||
|
|
||||||
const unknownUrls = new Set<string>();
|
|
||||||
const unknown = (route: Route): void => {
|
|
||||||
const url = route.request().url();
|
|
||||||
if (!unknownUrls.has(url)) {
|
|
||||||
console.log(`Request to unknown url: ${url}`); /* eslint-disable-line no-console */
|
|
||||||
unknownUrls.add(url);
|
|
||||||
}
|
|
||||||
intercepted.push(route.abort());
|
|
||||||
};
|
|
||||||
|
|
||||||
await page.route('**', async route => {
|
|
||||||
const url = route.request().url();
|
|
||||||
if (frameBySrc.has(url)) {
|
|
||||||
const frameSnapshot = frameBySrc.get(url)!;
|
|
||||||
intercepted.push(route.fulfill({
|
|
||||||
contentType: 'text/html',
|
|
||||||
body: Buffer.from(frameSnapshot.html),
|
|
||||||
}));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const frameSrc = route.request().frame().url();
|
|
||||||
const frameSnapshot = frameBySrc.get(frameSrc);
|
|
||||||
if (!frameSnapshot)
|
|
||||||
return unknown(route);
|
|
||||||
|
|
||||||
// Find a matching resource from the same context, preferrably from the same frame.
|
|
||||||
// Note: resources are stored without hash, but page may reference them with hash.
|
|
||||||
let resource: NetworkResourceTraceEvent | null = null;
|
|
||||||
for (const resourceEvent of this._resourceEventsByUrl.get(removeHash(url)) || []) {
|
|
||||||
if (resourceEvent.contextId !== contextId)
|
|
||||||
continue;
|
|
||||||
if (resource && resourceEvent.frameId !== frameSnapshot.frameId)
|
|
||||||
continue;
|
|
||||||
resource = resourceEvent;
|
|
||||||
if (resourceEvent.frameId === frameSnapshot.frameId)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!resource)
|
|
||||||
return unknown(route);
|
|
||||||
|
|
||||||
// This particular frame might have a resource content override, for example when
|
|
||||||
// stylesheet is modified using CSSOM.
|
|
||||||
const resourceOverride = frameSnapshot.resourceOverrides.find(o => o.url === url);
|
|
||||||
const overrideSha1 = resourceOverride ? resourceOverride.sha1 : undefined;
|
|
||||||
const resourceData = await this._readResource(resource, overrideSha1);
|
|
||||||
if (!resourceData)
|
|
||||||
return unknown(route);
|
|
||||||
const headers: { [key: string]: string } = {};
|
|
||||||
for (const { name, value } of resourceData.headers)
|
|
||||||
headers[name] = value;
|
|
||||||
headers['Access-Control-Allow-Origin'] = '*';
|
|
||||||
intercepted.push(route.fulfill({
|
|
||||||
contentType: resourceData.contentType,
|
|
||||||
body: resourceData.body,
|
|
||||||
headers,
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
await page.goto(snapshot.frames[0].url);
|
|
||||||
await this._postprocessSnapshotFrame(snapshot, snapshot.frames[0], page.mainFrame());
|
|
||||||
await Promise.all(intercepted);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _postprocessSnapshotFrame(snapshot: PageSnapshot, frameSnapshot: FrameSnapshot, frame: Frame) {
|
|
||||||
for (const childFrame of frame.childFrames()) {
|
|
||||||
await childFrame.waitForLoadState();
|
|
||||||
const url = childFrame.url();
|
|
||||||
for (const childData of snapshot.frames) {
|
|
||||||
if (url.endsWith(childData.url))
|
|
||||||
await this._postprocessSnapshotFrame(snapshot, childData, childFrame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function showTraceViewer(playwright: Playwright, traceStorageDir: string, traceFiles: string[]) {
|
|
||||||
const traceViewer = new TraceViewer(playwright, traceStorageDir);
|
|
||||||
for (const traceFile of traceFiles)
|
|
||||||
await traceViewer.load(traceFile);
|
|
||||||
for (const browserName of traceViewer.browserNames())
|
|
||||||
await traceViewer.show(browserName);
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeHash(url: string) {
|
|
||||||
try {
|
|
||||||
const u = new URL(url);
|
|
||||||
u.hash = '';
|
|
||||||
return u.toString();
|
|
||||||
} catch (e) {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { BrowserContext } from '../server/browserContext';
|
import { BrowserContext } from '../server/browserContext';
|
||||||
import type { SanpshotterResource, SnapshotterBlob, SnapshotterDelegate } from './snapshotter';
|
import type { SnapshotterResource as SnapshotterResource, SnapshotterBlob, SnapshotterDelegate } from './snapshotter';
|
||||||
import { ContextCreatedTraceEvent, ContextDestroyedTraceEvent, NetworkResourceTraceEvent, ActionTraceEvent, PageCreatedTraceEvent, PageDestroyedTraceEvent } from './traceTypes';
|
import { ContextCreatedTraceEvent, ContextDestroyedTraceEvent, NetworkResourceTraceEvent, ActionTraceEvent, PageCreatedTraceEvent, PageDestroyedTraceEvent } from './traceTypes';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as util from 'util';
|
import * as util from 'util';
|
||||||
@ -118,10 +118,11 @@ class ContextTracer implements SnapshotterDelegate {
|
|||||||
this._writeArtifact(blob.sha1, blob.buffer);
|
this._writeArtifact(blob.sha1, blob.buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
onResource(resource: SanpshotterResource): void {
|
onResource(resource: SnapshotterResource): void {
|
||||||
const event: NetworkResourceTraceEvent = {
|
const event: NetworkResourceTraceEvent = {
|
||||||
type: 'resource',
|
type: 'resource',
|
||||||
contextId: this._contextId,
|
contextId: this._contextId,
|
||||||
|
pageId: resource.pageId,
|
||||||
frameId: resource.frameId,
|
frameId: resource.frameId,
|
||||||
url: resource.url,
|
url: resource.url,
|
||||||
contentType: resource.contentType,
|
contentType: resource.contentType,
|
||||||
@ -131,6 +132,10 @@ class ContextTracer implements SnapshotterDelegate {
|
|||||||
this._appendTraceEvent(event);
|
this._appendTraceEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pageId(page: Page): string {
|
||||||
|
return this._pageToId.get(page)!;
|
||||||
|
}
|
||||||
|
|
||||||
async captureSnapshot(page: Page, options: types.TimeoutOptions & { label?: string } = {}): Promise<void> {
|
async captureSnapshot(page: Page, options: types.TimeoutOptions & { label?: string } = {}): Promise<void> {
|
||||||
const snapshot = await this._takeSnapshot(page, options.timeout);
|
const snapshot = await this._takeSnapshot(page, options.timeout);
|
||||||
if (!snapshot)
|
if (!snapshot)
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright Microsoft Corporation. All rights reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const path = require('path');
|
|
||||||
const fs = require('fs');
|
|
||||||
const playwright = require('..');
|
|
||||||
const { showTraceViewer } = require('../lib/trace/traceViewer');
|
|
||||||
|
|
||||||
if (process.argv.includes('--help')) {
|
|
||||||
console.log(`Usage:`);
|
|
||||||
console.log(` - npm run show-trace`);
|
|
||||||
console.log(` Show traces from the last test run.`);
|
|
||||||
console.log(` - npm run show-trace <test-results-directory>`);
|
|
||||||
console.log(` Show traces from the downloaded test results.`);
|
|
||||||
console.log(` - npm run show-trace <trace-file> <trace-storage-directory>`);
|
|
||||||
console.log(` Show single trace file from the manual run.`);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let traceStorageDir, files;
|
|
||||||
if (process.argv[3]) {
|
|
||||||
files = [process.argv[2]];
|
|
||||||
traceStorageDir = process.argv[3];
|
|
||||||
} else {
|
|
||||||
const testResultsDir = process.argv[2] || path.join(__dirname, '..', 'test-results');
|
|
||||||
files = collectFiles(testResultsDir, '');
|
|
||||||
traceStorageDir = path.join(testResultsDir, 'trace-storage');
|
|
||||||
}
|
|
||||||
console.log(`Found ${files.length} trace files`);
|
|
||||||
showTraceViewer(playwright, traceStorageDir, files);
|
|
||||||
|
|
||||||
function collectFiles(dir) {
|
|
||||||
const files = [];
|
|
||||||
for (const name of fs.readdirSync(dir)) {
|
|
||||||
const fullName = path.join(dir, name);
|
|
||||||
if (fs.lstatSync(fullName).isDirectory())
|
|
||||||
files.push(...collectFiles(fullName));
|
|
||||||
else if (name.endsWith('.trace'))
|
|
||||||
files.push(fullName);
|
|
||||||
}
|
|
||||||
return files;
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user