mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-12 11:50:22 +03:00
feat(trace-viewer): render call info w/ params, result (#7438)
This commit is contained in:
parent
ec8d0629f3
commit
99d7d196c5
@ -246,7 +246,7 @@ export class DispatcherConnection {
|
||||
} case 'after': {
|
||||
const originalMetadata = this._waitOperations.get(info.waitId)!;
|
||||
originalMetadata.endTime = monotonicTime();
|
||||
originalMetadata.error = info.error;
|
||||
originalMetadata.error = info.error ? { error: { name: 'Error', message: info.error } } : undefined;
|
||||
this._waitOperations.delete(info.waitId);
|
||||
await sdkObject.instrumentation.onAfterCall(sdkObject, originalMetadata);
|
||||
return;
|
||||
@ -255,14 +255,15 @@ export class DispatcherConnection {
|
||||
}
|
||||
|
||||
|
||||
let result: any;
|
||||
let error: any;
|
||||
await sdkObject?.instrumentation.onBeforeCall(sdkObject, callMetadata);
|
||||
try {
|
||||
result = await (dispatcher as any)[method](validParams, callMetadata);
|
||||
const result = await (dispatcher as any)[method](validParams, callMetadata);
|
||||
callMetadata.result = this._replaceDispatchersWithGuids(result);
|
||||
} catch (e) {
|
||||
// Dispatching error
|
||||
callMetadata.error = e.message;
|
||||
// We want original, unmodified error in metadata.
|
||||
callMetadata.error = serializeError(e);
|
||||
if (callMetadata.log.length)
|
||||
rewriteErrorMessage(e, e.message + formatLogRecording(callMetadata.log));
|
||||
error = serializeError(e);
|
||||
@ -271,10 +272,10 @@ export class DispatcherConnection {
|
||||
await sdkObject?.instrumentation.onAfterCall(sdkObject, callMetadata);
|
||||
}
|
||||
|
||||
if (error)
|
||||
this.onmessage({ id, error });
|
||||
if (callMetadata.error)
|
||||
this.onmessage({ id, error: error });
|
||||
else
|
||||
this.onmessage({ id, result: this._replaceDispatchersWithGuids(result) });
|
||||
this.onmessage({ id, result: callMetadata.result });
|
||||
}
|
||||
|
||||
private _replaceDispatchersWithGuids(payload: any): any {
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { Point, StackFrame } from '../common/types';
|
||||
import { SerializedError } from '../protocol/channels';
|
||||
import { createGuid } from '../utils/utils';
|
||||
import type { Browser } from './browser';
|
||||
import type { BrowserContext } from './browserContext';
|
||||
@ -46,7 +47,8 @@ export type CallMetadata = {
|
||||
stack?: StackFrame[];
|
||||
log: string[];
|
||||
snapshots: { title: string, snapshotName: string }[];
|
||||
error?: string;
|
||||
error?: SerializedError;
|
||||
result?: any;
|
||||
point?: Point;
|
||||
objectId?: string;
|
||||
pageId?: string;
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { Point } from '../../../common/types';
|
||||
import { SerializedError } from '../../../protocol/channels';
|
||||
|
||||
export type Mode = 'inspecting' | 'recording' | 'none';
|
||||
|
||||
@ -36,7 +37,7 @@ export type CallLog = {
|
||||
title: string;
|
||||
messages: string[];
|
||||
status: CallLogStatus;
|
||||
error?: string;
|
||||
error?: SerializedError;
|
||||
reveal?: boolean;
|
||||
duration?: number;
|
||||
params: {
|
||||
|
@ -19,6 +19,7 @@ import { FrameSnapshot, ResourceSnapshot } from '../../snapshot/snapshotTypes';
|
||||
import { BrowserContextOptions } from '../../types';
|
||||
|
||||
export type ContextCreatedTraceEvent = {
|
||||
version: number,
|
||||
type: 'context-options',
|
||||
browserName: string,
|
||||
options: BrowserContextOptions
|
||||
|
@ -35,6 +35,8 @@ export type TracerOptions = {
|
||||
screenshots?: boolean;
|
||||
};
|
||||
|
||||
export const VERSION = 1;
|
||||
|
||||
export class Tracing implements InstrumentationListener {
|
||||
private _appendEventChain = Promise.resolve();
|
||||
private _snapshotter: TraceSnapshotter;
|
||||
@ -63,6 +65,7 @@ export class Tracing implements InstrumentationListener {
|
||||
|
||||
this._appendEventChain = mkdirIfNeeded(this._traceFile);
|
||||
const event: trace.ContextCreatedTraceEvent = {
|
||||
version: VERSION,
|
||||
type: 'context-options',
|
||||
browserName: this._context._browser.options.name,
|
||||
options: this._context._options
|
||||
@ -90,7 +93,7 @@ export class Tracing implements InstrumentationListener {
|
||||
for (const { sdkObject, metadata, beforeSnapshot, actionSnapshot, afterSnapshot } of this._pendingCalls.values()) {
|
||||
await Promise.all([beforeSnapshot, actionSnapshot, afterSnapshot]);
|
||||
if (!afterSnapshot)
|
||||
metadata.error = 'Action was interrupted';
|
||||
metadata.error = { error: { name: 'Error', message: 'Action was interrupted' } };
|
||||
await this.onAfterCall(sdkObject, metadata);
|
||||
}
|
||||
for (const page of this._context.pages())
|
||||
@ -227,6 +230,6 @@ export class Tracing implements InstrumentationListener {
|
||||
}
|
||||
}
|
||||
|
||||
function shouldCaptureSnapshot(metadata: CallMetadata): boolean {
|
||||
export function shouldCaptureSnapshot(metadata: CallMetadata): boolean {
|
||||
return commandsWithTracingSnapshots.has(metadata.type + '.' + metadata.method);
|
||||
}
|
||||
|
@ -18,8 +18,9 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import * as trace from '../common/traceEvents';
|
||||
import { ContextResources, ResourceSnapshot } from '../../snapshot/snapshotTypes';
|
||||
import { BaseSnapshotStorage, SnapshotStorage } from '../../snapshot/snapshotStorage';
|
||||
import { BaseSnapshotStorage } from '../../snapshot/snapshotStorage';
|
||||
import { BrowserContextOptions } from '../../types';
|
||||
import { shouldCaptureSnapshot, VERSION } from '../recorder/tracing';
|
||||
export * as trace from '../common/traceEvents';
|
||||
|
||||
export class TraceModel {
|
||||
@ -27,6 +28,7 @@ export class TraceModel {
|
||||
pageEntries = new Map<string, PageEntry>();
|
||||
contextResources = new Map<string, ContextResources>();
|
||||
private _snapshotStorage: PersistentSnapshotStorage;
|
||||
private _version: number | undefined;
|
||||
|
||||
constructor(snapshotStorage: PersistentSnapshotStorage) {
|
||||
this._snapshotStorage = snapshotStorage;
|
||||
@ -40,12 +42,10 @@ export class TraceModel {
|
||||
};
|
||||
}
|
||||
|
||||
appendEvents(events: trace.TraceEvent[], snapshotStorage: SnapshotStorage) {
|
||||
for (const event of events)
|
||||
this.appendEvent(event);
|
||||
build() {
|
||||
for (const page of this.contextEntry!.pages)
|
||||
page.actions.sort((a1, a2) => a1.metadata.startTime - a2.metadata.startTime);
|
||||
this.contextEntry!.resources = snapshotStorage.resources();
|
||||
this.contextEntry!.resources = this._snapshotStorage.resources();
|
||||
}
|
||||
|
||||
private _pageEntry(pageId: string): PageEntry {
|
||||
@ -63,9 +63,11 @@ export class TraceModel {
|
||||
return pageEntry;
|
||||
}
|
||||
|
||||
appendEvent(event: trace.TraceEvent) {
|
||||
appendEvent(line: string) {
|
||||
const event = this._modernize(JSON.parse(line));
|
||||
switch (event.type) {
|
||||
case 'context-options': {
|
||||
this._version = event.version || 0;
|
||||
this.contextEntry.browserName = event.browserName;
|
||||
this.contextEntry.options = event.options;
|
||||
break;
|
||||
@ -76,7 +78,7 @@ export class TraceModel {
|
||||
}
|
||||
case 'action': {
|
||||
const metadata = event.metadata;
|
||||
const include = typeof event.hasSnapshot !== 'boolean' || event.hasSnapshot;
|
||||
const include = event.hasSnapshot;
|
||||
if (include && metadata.pageId)
|
||||
this._pageEntry(metadata.pageId).actions.push(event);
|
||||
break;
|
||||
@ -103,6 +105,24 @@ export class TraceModel {
|
||||
this.contextEntry!.endTime = Math.max(this.contextEntry!.endTime, event.metadata.endTime);
|
||||
}
|
||||
}
|
||||
|
||||
private _modernize(event: any): trace.TraceEvent {
|
||||
if (this._version === undefined)
|
||||
return event;
|
||||
for (let version = this._version; version < VERSION; ++version)
|
||||
event = (this as any)[`_modernize_${version}_to_${version + 1}`].call(this, event);
|
||||
return event;
|
||||
}
|
||||
|
||||
_modernize_0_to_1(event: any): any {
|
||||
if (event.type === 'action') {
|
||||
if (typeof event.metadata.error === 'string')
|
||||
event.metadata.error = { error: { name: 'Error', message: event.metadata.error } };
|
||||
if (event.metadata && typeof event.hasSnapshot !== 'boolean')
|
||||
event.hasSnapshot = shouldCaptureSnapshot(event.metadata);
|
||||
}
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
||||
export type ContextEntry = {
|
||||
|
@ -16,12 +16,12 @@
|
||||
|
||||
import extract from 'extract-zip';
|
||||
import fs from 'fs';
|
||||
import readline from 'readline';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import rimraf from 'rimraf';
|
||||
import { createPlaywright } from '../../playwright';
|
||||
import { PersistentSnapshotStorage, TraceModel } from './traceModel';
|
||||
import { TraceEvent } from '../common/traceEvents';
|
||||
import { ServerRouteHandler, HttpServer } from '../../../utils/httpServer';
|
||||
import { SnapshotServer } from '../../snapshot/snapshotServer';
|
||||
import * as consoleApiSource from '../../../generated/consoleApiSource';
|
||||
@ -80,10 +80,15 @@ export class TraceViewer {
|
||||
response.statusCode = 200;
|
||||
response.setHeader('Content-Type', 'application/json');
|
||||
(async () => {
|
||||
const traceContent = await fs.promises.readFile(tracePrefix + '.trace', 'utf8');
|
||||
const events = traceContent.split('\n').map(line => line.trim()).filter(line => !!line).map(line => JSON.parse(line)) as TraceEvent[];
|
||||
const fileStream = fs.createReadStream(tracePrefix + '.trace', 'utf8');
|
||||
const rl = readline.createInterface({
|
||||
input: fileStream,
|
||||
crlfDelay: Infinity
|
||||
});
|
||||
const model = new TraceModel(snapshotStorage);
|
||||
model.appendEvents(events, snapshotStorage);
|
||||
for await (const line of rl as any)
|
||||
model.appendEvent(line);
|
||||
model.build();
|
||||
response.end(JSON.stringify(model.contextEntry));
|
||||
})().catch(e => console.error(e));
|
||||
return true;
|
||||
|
@ -32,7 +32,6 @@ export const CallLogView: React.FC<CallLogProps> = ({
|
||||
if (log.find(callLog => callLog.reveal))
|
||||
messagesEndRef.current?.scrollIntoView({ block: 'center', inline: 'nearest' });
|
||||
}, [messagesEndRef]);
|
||||
|
||||
return <div className='call-log' style={{flex: 'auto'}}>
|
||||
{log.map(callLog => {
|
||||
const expandOverride = expandOverrides.get(callLog.id);
|
||||
@ -55,9 +54,7 @@ export const CallLogView: React.FC<CallLogProps> = ({
|
||||
{ message.trim() }
|
||||
</div>;
|
||||
})}
|
||||
{ callLog.error ? <div className='call-log-message error' hidden={!isExpanded}>
|
||||
{ callLog.error }
|
||||
</div> : undefined }
|
||||
{ !!callLog.error && <div className='call-log-message error' hidden={!isExpanded}>{ callLog.error.error?.message }</div> }
|
||||
</div>
|
||||
})}
|
||||
<div ref={messagesEndRef}></div>
|
||||
|
@ -14,30 +14,52 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.logs-tab {
|
||||
.call-tab {
|
||||
flex: auto;
|
||||
line-height: 20px;
|
||||
line-height: 24px;
|
||||
white-space: pre;
|
||||
overflow: auto;
|
||||
padding-top: 3px;
|
||||
padding: 6px 0;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.logs-error {
|
||||
.call-error {
|
||||
border-bottom: 1px solid var(--background);
|
||||
padding: 3px 0 3px 12px;
|
||||
}
|
||||
|
||||
.logs-error .codicon {
|
||||
.call-error .codicon {
|
||||
color: red;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.log-line {
|
||||
flex: none;
|
||||
padding: 3px 0 3px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.call-section {
|
||||
padding-left: 6px;
|
||||
border-top: 1px solid #ddd;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
font-size: 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
background-color: #efefef;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.call-line {
|
||||
padding: 0 0 2px 6px;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.call-line .string {
|
||||
color: var(--orange);
|
||||
}
|
||||
|
||||
.call-line .number,
|
||||
.call-line .boolean,
|
||||
.call-line .object {
|
||||
color: var(--blue);
|
||||
}
|
66
src/web/traceViewer/ui/callTab.tsx
Normal file
66
src/web/traceViewer/ui/callTab.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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 React from 'react';
|
||||
import './callTab.css';
|
||||
import { ActionTraceEvent } from '../../../server/trace/common/traceEvents';
|
||||
|
||||
export const CallTab: React.FunctionComponent<{
|
||||
action: ActionTraceEvent | undefined,
|
||||
}> = ({ action }) => {
|
||||
if (!action)
|
||||
return null;
|
||||
const logs = action.metadata.log;
|
||||
const error = action.metadata.error?.error?.message;
|
||||
const params = { ...action.metadata.params };
|
||||
delete params.info;
|
||||
const paramKeys = Object.keys(params);
|
||||
return <div className='call-tab'>
|
||||
<div className='call-error' key='error' hidden={!error}>
|
||||
<div className='codicon codicon-issues'/>
|
||||
{error}
|
||||
</div>
|
||||
<div className='call-line'>{action.metadata.apiName}</div>
|
||||
{ !!paramKeys.length && <div className='call-section'>Parameters</div> }
|
||||
{
|
||||
!!paramKeys.length && paramKeys.map(name =>
|
||||
<div className='call-line'>{name}: <span className={typeof params[name]}>{renderValue(params[name])}</span></div>
|
||||
)
|
||||
}
|
||||
{ !!action.metadata.result && <div className='call-section'>Return value</div> }
|
||||
{
|
||||
!!action.metadata.result && Object.keys(action.metadata.result).map(name =>
|
||||
<div className='call-line'>{name}: <span className={typeof action.metadata.result[name]}>{renderValue(action.metadata.result[name])}</span></div>
|
||||
)
|
||||
}
|
||||
<div className='call-section'>Log</div>
|
||||
{
|
||||
logs.map((logLine, index) => {
|
||||
return <div key={index} className='call-line'>
|
||||
{logLine}
|
||||
</div>;
|
||||
})
|
||||
}
|
||||
</div>;
|
||||
};
|
||||
|
||||
function renderValue(value: any) {
|
||||
const type = typeof value;
|
||||
if (type !== 'object')
|
||||
return String(value);
|
||||
if (value.guid)
|
||||
return '<handle>';
|
||||
}
|
@ -1,37 +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 React from 'react';
|
||||
import './logsTab.css';
|
||||
import { ActionTraceEvent } from '../../../server/trace/common/traceEvents';
|
||||
|
||||
export const LogsTab: React.FunctionComponent<{
|
||||
action: ActionTraceEvent | undefined,
|
||||
}> = ({ action }) => {
|
||||
const logs = action?.metadata.log || [];
|
||||
const error = action?.metadata.error;
|
||||
return <div className='logs-tab'>{
|
||||
<div className='logs-error' key='error' hidden={!error}>
|
||||
<div className='codicon codicon-issues'/>
|
||||
{error}
|
||||
</div>}{
|
||||
logs.map((logLine, index) => {
|
||||
return <div key={index} className='log-line'>
|
||||
{logLine}
|
||||
</div>;
|
||||
})
|
||||
}</div>;
|
||||
};
|
@ -22,6 +22,7 @@ const contextSymbol = Symbol('context');
|
||||
const pageSymbol = Symbol('context');
|
||||
const nextSymbol = Symbol('next');
|
||||
const eventsSymbol = Symbol('events');
|
||||
const resourcesSymbol = Symbol('resources');
|
||||
|
||||
export function indexModel(context: ContextEntry) {
|
||||
for (const page of context.pages) {
|
||||
@ -84,8 +85,14 @@ export function eventsForAction(action: ActionTraceEvent): ActionTraceEvent[] {
|
||||
}
|
||||
|
||||
export function resourcesForAction(action: ActionTraceEvent): ResourceSnapshot[] {
|
||||
let result: ResourceSnapshot[] = (action as any)[resourcesSymbol];
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
const nextAction = next(action);
|
||||
return context(action).resources.filter(resource => {
|
||||
result = context(action).resources.filter(resource => {
|
||||
return resource.timestamp > action.metadata.startTime && (!nextAction || resource.timestamp < nextAction.metadata.startTime);
|
||||
});
|
||||
(action as any)[resourcesSymbol] = result;
|
||||
return result;
|
||||
}
|
||||
|
@ -44,7 +44,7 @@
|
||||
}
|
||||
|
||||
.tab-element {
|
||||
padding: 2px 12px 0 12px;
|
||||
padding: 2px 10px 0 10px;
|
||||
margin-right: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
@ -65,6 +65,13 @@
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tab-count {
|
||||
font-size: 10px;
|
||||
display: flex;
|
||||
align-self: flex-start;
|
||||
width: 0px;
|
||||
}
|
||||
|
||||
.tab-element.selected {
|
||||
border-bottom-color: #666;
|
||||
}
|
||||
|
@ -16,10 +16,12 @@
|
||||
|
||||
import './tabbedPane.css';
|
||||
import * as React from 'react';
|
||||
import { count } from 'console';
|
||||
|
||||
export interface TabbedPaneTab {
|
||||
id: string;
|
||||
title: string;
|
||||
count: number;
|
||||
render: () => React.ReactElement;
|
||||
}
|
||||
|
||||
@ -37,6 +39,7 @@ export const TabbedPane: React.FunctionComponent<{
|
||||
onClick={() => setSelectedTab(tab.id)}
|
||||
key={tab.id}>
|
||||
<div className='tab-label'>{tab.title}</div>
|
||||
<div className='tab-count'>{tab.count || ''}</div>
|
||||
</div>
|
||||
})
|
||||
}</div>
|
||||
|
@ -25,7 +25,7 @@ import { ContextSelector } from './contextSelector';
|
||||
import { NetworkTab } from './networkTab';
|
||||
import { SourceTab } from './sourceTab';
|
||||
import { SnapshotTab } from './snapshotTab';
|
||||
import { LogsTab } from './logsTab';
|
||||
import { CallTab } from './callTab';
|
||||
import { SplitView } from '../../components/splitView';
|
||||
import { useAsyncMemo } from './helpers';
|
||||
import { ConsoleTab } from './consoleTab';
|
||||
@ -59,7 +59,9 @@ export const Workbench: React.FunctionComponent<{
|
||||
|
||||
// Leave some nice free space on the right hand side.
|
||||
boundaries.maximum += (boundaries.maximum - boundaries.minimum) / 20;
|
||||
|
||||
const { errors, warnings } = selectedAction ? modelUtil.stats(selectedAction) : { errors: 0, warnings: 0 };
|
||||
const consoleCount = errors + warnings;
|
||||
const networkCount = selectedAction ? modelUtil.resourcesForAction(selectedAction).length : 0;
|
||||
|
||||
return <div className='vbox workbench'>
|
||||
<div className='hbox header'>
|
||||
@ -89,10 +91,10 @@ export const Workbench: React.FunctionComponent<{
|
||||
<SplitView sidebarSize={300} orientation='horizontal'>
|
||||
<SnapshotTab action={selectedAction} snapshotSize={snapshotSize} />
|
||||
<TabbedPane tabs={[
|
||||
{ id: 'logs', title: 'Log', render: () => <LogsTab action={selectedAction} /> },
|
||||
{ id: 'console', title: 'Console', render: () => <ConsoleTab action={selectedAction} /> },
|
||||
{ id: 'source', title: 'Source', render: () => <SourceTab action={selectedAction} /> },
|
||||
{ id: 'network', title: 'Network', render: () => <NetworkTab action={selectedAction} /> },
|
||||
{ id: 'logs', title: 'Call', count: 0, render: () => <CallTab action={selectedAction} /> },
|
||||
{ id: 'console', title: 'Console', count: consoleCount, render: () => <ConsoleTab action={selectedAction} /> },
|
||||
{ id: 'network', title: 'Network', count: networkCount, render: () => <NetworkTab action={selectedAction} /> },
|
||||
{ id: 'source', title: 'Source', count: 0, render: () => <SourceTab action={selectedAction} /> },
|
||||
]} selectedTab={selectedTab} setSelectedTab={setSelectedTab}/>
|
||||
</SplitView>
|
||||
<ActionList
|
||||
|
@ -43,8 +43,8 @@ class TraceViewerPage {
|
||||
}
|
||||
|
||||
async logLines() {
|
||||
await this.page.waitForSelector('.log-line:visible');
|
||||
return await this.page.$$eval('.log-line:visible', ee => ee.map(e => e.textContent));
|
||||
await this.page.waitForSelector('.call-line:visible');
|
||||
return await this.page.$$eval('.call-line:visible', ee => ee.map(e => e.textContent));
|
||||
}
|
||||
|
||||
async eventBars() {
|
||||
@ -130,7 +130,7 @@ test('should open simple trace viewer', async ({ showTraceViewer }) => {
|
||||
]);
|
||||
});
|
||||
|
||||
test('should contain action log', async ({ showTraceViewer }) => {
|
||||
test('should contain action info', async ({ showTraceViewer }) => {
|
||||
const traceViewer = await showTraceViewer(traceFile);
|
||||
await traceViewer.selectAction('page.click');
|
||||
const logLines = await traceViewer.logLines();
|
||||
|
@ -192,7 +192,7 @@ test('should include interrupted actions', async ({ context, page, server }, tes
|
||||
const { events } = await parseTrace(testInfo.outputPath('trace.zip'));
|
||||
const clickEvent = events.find(e => e.metadata?.apiName === 'page.click');
|
||||
expect(clickEvent).toBeTruthy();
|
||||
expect(clickEvent.metadata.error).toBe('Action was interrupted');
|
||||
expect(clickEvent.metadata.error.error.message).toBe('Action was interrupted');
|
||||
});
|
||||
|
||||
|
||||
|
@ -161,8 +161,7 @@ DEPS['src/utils/'] = ['src/common/'];
|
||||
// Trace viewer
|
||||
DEPS['src/server/trace/common/'] = ['src/server/snapshot/', ...DEPS['src/server/']];
|
||||
DEPS['src/server/trace/recorder/'] = ['src/server/trace/common/', ...DEPS['src/server/trace/common/']];
|
||||
DEPS['src/server/trace/viewer/'] = ['src/server/trace/common/', 'src/server/chromium/', ...DEPS['src/server/trace/common/']];
|
||||
|
||||
DEPS['src/server/trace/viewer/'] = ['src/server/trace/common/', 'src/server/trace/recorder/', 'src/server/chromium/', ...DEPS['src/server/trace/common/']];
|
||||
DEPS['src/test/'] = ['src/test/**', 'src/utils/utils.ts'];
|
||||
|
||||
checkDeps().catch(e => {
|
||||
|
Loading…
Reference in New Issue
Block a user