chore: remove log from htmlreport (#8898)

This commit is contained in:
Pavel Feldman 2021-09-13 18:07:15 -07:00 committed by GitHub
parent d9d2d809a2
commit b76e993951
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 24 additions and 117 deletions

View file

@ -21,7 +21,7 @@ import { debugLogger } from '../utils/debugLogger';
import { captureStackTrace, ParsedStackTrace } from '../utils/stackTrace';
import { isUnderTest } from '../utils/utils';
import type { Connection } from './connection';
import type { ClientSideInstrumentation, LogContainer, Logger } from './types';
import type { ClientSideInstrumentation, Logger } from './types';
export abstract class ChannelOwner<T extends channels.Channel = channels.Channel, Initializer = {}> extends EventEmitter {
protected _connection: Connection;
@ -72,15 +72,15 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
};
}
private _createChannel(base: Object, stackTrace: ParsedStackTrace | null, logContainer?: LogContainer): T {
private _createChannel(base: Object, stackTrace: ParsedStackTrace | null): T {
const channel = new Proxy(base, {
get: (obj: any, prop) => {
if (prop === 'debugScopeState')
return (params: any) => this._connection.sendMessageToServer(this, prop, params, stackTrace, logContainer);
return (params: any) => this._connection.sendMessageToServer(this, prop, params, stackTrace);
if (typeof prop === 'string') {
const validator = scheme[paramsName(this._type, prop)];
if (validator)
return (params: any) => this._connection.sendMessageToServer(this, prop, validator(params, ''), stackTrace, logContainer);
return (params: any) => this._connection.sendMessageToServer(this, prop, validator(params, ''), stackTrace);
}
return obj[prop];
},
@ -97,22 +97,21 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
let ancestorWithCSI: ChannelOwner<any> = this;
while (!ancestorWithCSI._csi && ancestorWithCSI._parent)
ancestorWithCSI = ancestorWithCSI._parent;
let csiCallback: ((log: string[], e?: Error) => void) | undefined;
const logContainer = { log: [] };
let csiCallback: ((e?: Error) => void) | undefined;
try {
logApiCall(logger, `=> ${apiName} started`);
csiCallback = ancestorWithCSI._csi?.onApiCall(stackTrace);
const channel = this._createChannel({}, stackTrace, csiCallback ? logContainer : undefined);
const channel = this._createChannel({}, stackTrace);
const result = await func(channel as any, stackTrace);
csiCallback?.(logContainer.log);
csiCallback?.();
logApiCall(logger, `<= ${apiName} succeeded`);
return result;
} catch (e) {
const innerError = ((process.env.PWDEBUGIMPL || isUnderTest()) && e.stack) ? '\n<inner error>\n' + e.stack : '';
e.message = apiName + ': ' + e.message;
e.stack = e.message + '\n' + frameTexts.join('\n') + innerError;
csiCallback?.(logContainer.log, e);
csiCallback?.(e);
logApiCall(logger, `<= ${apiName} failed`);
throw e;
}

View file

@ -39,7 +39,6 @@ import { ParsedStackTrace } from '../utils/stackTrace';
import { Artifact } from './artifact';
import { EventEmitter } from 'events';
import { JsonPipe } from './jsonPipe';
import type { LogContainer } from './types';
class Root extends ChannelOwner<channels.RootChannel, {}> {
constructor(connection: Connection) {
@ -58,7 +57,7 @@ export class Connection extends EventEmitter {
private _waitingForObject = new Map<string, any>();
onmessage = (message: object): void => {};
private _lastId = 0;
private _callbacks = new Map<number, { resolve: (a: any) => void, reject: (a: Error) => void, stackTrace: ParsedStackTrace, logContainer: LogContainer | undefined }>();
private _callbacks = new Map<number, { resolve: (a: any) => void, reject: (a: Error) => void, stackTrace: ParsedStackTrace }>();
private _rootObject: Root;
private _disconnectedErrorMessage: string | undefined;
private _onClose?: () => void;
@ -81,7 +80,7 @@ export class Connection extends EventEmitter {
return this._objects.get(guid)!;
}
async sendMessageToServer(object: ChannelOwner, method: string, params: any, maybeStackTrace: ParsedStackTrace | null, logContainer?: LogContainer): Promise<any> {
async sendMessageToServer(object: ChannelOwner, method: string, params: any, maybeStackTrace: ParsedStackTrace | null): Promise<any> {
const guid = object._guid;
const stackTrace = maybeStackTrace || { frameTexts: [], frames: [], apiName: '' };
const { frames, apiName } = stackTrace;
@ -90,12 +89,12 @@ export class Connection extends EventEmitter {
const converted = { id, guid, method, params };
// Do not include metadata in debug logs to avoid noise.
debugLogger.log('channel:command', converted);
const metadata: channels.Metadata = { stack: frames, apiName, collectLogs: logContainer ? true : undefined };
const metadata: channels.Metadata = { stack: frames, apiName };
this.onmessage({ ...converted, metadata });
if (this._disconnectedErrorMessage)
throw new Error(this._disconnectedErrorMessage);
return await new Promise((resolve, reject) => this._callbacks.set(id, { resolve, reject, stackTrace, logContainer }));
return await new Promise((resolve, reject) => this._callbacks.set(id, { resolve, reject, stackTrace }));
}
_debugScopeState(): any {
@ -103,15 +102,13 @@ export class Connection extends EventEmitter {
}
dispatch(message: object) {
const { id, guid, method, params, result, error, log } = message as any;
const { id, guid, method, params, result, error } = message as any;
if (id) {
debugLogger.log('channel:response', message);
const callback = this._callbacks.get(id);
if (!callback)
throw new Error(`Cannot find command to respond: ${id}`);
this._callbacks.delete(id);
if (log && callback.logContainer)
callback.logContainer.log.push(...log);
if (error)
callback.reject(parseError(error));
else

View file

@ -27,11 +27,8 @@ export interface Logger {
}
export interface ClientSideInstrumentation {
onApiCall(stackTrace: ParsedStackTrace): ((log: string[], error?: Error) => void) | undefined;
onApiCall(stackTrace: ParsedStackTrace): ((error?: Error) => void) | undefined;
}
export type LogContainer = {
log: string[];
};
export type StrictOptions = { strict?: boolean };
export type Headers = { [key: string]: string };

View file

@ -331,7 +331,6 @@ export class Dispatcher {
step.duration = params.wallTime - step.startTime.getTime();
if (params.error)
step.error = params.error;
step.data = params.data;
stepStack.delete(step);
steps.delete(params.stepId);
this._reporter.onStepEnd?.(test, result, step);

View file

@ -41,8 +41,6 @@ import type { Expect, TestError } from './types';
import matchers from 'expect/build/matchers';
import { currentTestInfo } from './globals';
import { serializeError } from './util';
import StackUtils from 'stack-utils';
import path from 'path';
export const expect: Expect = expectLibrary as any;
expectLibrary.setState({ expand: false });
@ -77,7 +75,7 @@ function wrap(matcherName: string, matcher: any) {
const INTERNAL_STACK_LENGTH = 3;
const stackLines = new Error().stack!.split('\n').slice(INTERNAL_STACK_LENGTH + 1);
const step = testInfo._addStep('expect', `expect${this.isNot ? '.not' : ''}.${matcherName}`, prepareExpectStepData(stackLines));
const step = testInfo._addStep('expect', `expect${this.isNot ? '.not' : ''}.${matcherName}`);
const reportStepEnd = (result: any) => {
const success = result.pass !== this.isNot;
@ -106,22 +104,6 @@ function wrap(matcherName: string, matcher: any) {
};
}
const stackUtils = new StackUtils();
function prepareExpectStepData(lines: string[]) {
const frames = lines.map(line => {
const parsed = stackUtils.parseLine(line);
if (!parsed)
return;
return {
file: parsed.file ? path.resolve(process.cwd(), parsed.file) : undefined,
line: parsed.line,
column: parsed.column
};
}).filter(frame => !!frame);
return { stack: frames, log: [] };
}
const wrappedMatchers: any = {};
for (const matcherName in matchers)
wrappedMatchers[matcherName] = wrap(matcherName, matchers[matcherName]);

View file

@ -206,9 +206,8 @@ export const test = _baseTest.extend<TestFixtures, WorkerAndFileFixtures>({
onApiCall: (stackTrace: ParsedStackTrace) => {
const testInfoImpl = testInfo as TestInfoImpl;
const existingStep = testInfoImpl._currentSteps().find(step => step.category === 'pw:api' || step.category === 'expect');
const newStep = existingStep ? undefined : testInfoImpl._addStep('pw:api', stackTrace.apiName, { stack: stackTrace.frames, log: [] });
return (log: string[], error?: Error) => {
(existingStep || newStep)?.data.log?.push(...log);
const newStep = existingStep ? undefined : testInfoImpl._addStep('pw:api', stackTrace.apiName);
return (error?: Error) => {
newStep?.complete(error);
};
},

View file

@ -59,7 +59,6 @@ export type StepEndPayload = {
stepId: string;
wallTime: number; // milliseconds since unix epoch
error?: TestError;
data: { [key: string]: any };
};
export type TestEntry = {

View file

@ -28,11 +28,10 @@ export type Annotations = { type: string, description?: string }[];
export interface TestStepInternal {
complete(error?: Error | TestError): void;
category: string;
data: { [key: string]: any };
}
export interface TestInfoImpl extends TestInfo {
_testFinished: Promise<void>;
_addStep: (category: string, title: string, data?: { [key: string]: any }) => TestStepInternal;
_addStep: (category: string, title: string) => TestStepInternal;
_currentSteps(): TestStepInternal[];
}

View file

@ -268,11 +268,10 @@ export class WorkerRunner extends EventEmitter {
deadlineRunner.updateDeadline(deadline());
},
_testFinished: new Promise(f => testFinishedCallback = f),
_addStep: (category: string, title: string, data: { [key: string]: any } = {}) => {
_addStep: (category: string, title: string) => {
const stepId = `${category}@${title}@${++lastStepId}`;
let callbackHandled = false;
const step: TestStepInternal = {
data,
category,
complete: (error?: Error | TestError) => {
if (callbackHandled)
@ -286,7 +285,6 @@ export class WorkerRunner extends EventEmitter {
stepId,
wallTime: Date.now(),
error,
data,
};
if (reportEvents)
this.emit('stepEnd', payload);

View file

@ -272,14 +272,9 @@ const TestStepDetails: React.FC<{
}
})();
}, [step]);
const [selectedTab, setSelectedTab] = React.useState('log');
const [selectedTab, setSelectedTab] = React.useState('errors');
return <div className='vbox'>
<TabbedPane selectedTab={selectedTab} setSelectedTab={setSelectedTab} tabs={[
{
id: 'log',
title: 'Log',
render: () => <div className='step-log'>{step?.log ? step.log.join('\n') : ''}</div>
},
{
id: 'errors',
title: 'Errors',

View file

@ -181,23 +181,14 @@ const StepTreeItem: React.FC<{
<span style={{ whiteSpace: 'pre' }}>{step.title}</span>
<div style={{ flex: 'auto' }}></div>
<div>{msToString(step.duration)}</div>
</div>} loadChildren={step.steps.length + (step.log || []).length + (step.error ? 1 : 0) ? () => {
const stepChildren = step.steps.map((s, i) => <StepTreeItem key={i} step={s} depth={depth + 1}></StepTreeItem>);
const logChildren = (step.log || []).map((l, i) => <LogTreeItem key={step.steps.length + i} log={l} depth={depth + 1}></LogTreeItem>);
const children = [...stepChildren, ...logChildren];
</div>} loadChildren={step.steps.length + (step.error ? 1 : 0) ? () => {
const children = step.steps.map((s, i) => <StepTreeItem key={i} step={s} depth={depth + 1}></StepTreeItem>);
if (step.error)
children.unshift(<ErrorMessage error={step.error}></ErrorMessage>);
return children;
} : undefined} depth={depth}></TreeItem>;
};
const LogTreeItem: React.FC<{
log: string;
depth: number,
}> = ({ log, depth }) => {
return <TreeItem title={<div style={{ display: 'flex', alignItems: 'center', flex: 'auto' }}>{ log }</div>} depth={depth}></TreeItem>;
};
function statusIconForFailedTests(failedTests: number) {
return failedTests ? statusIcon('failed') : statusIcon('passed');
}

View file

@ -142,11 +142,8 @@ export const playwrightFixtures: Fixtures<PlaywrightTestOptions & PlaywrightTest
onApiCall: (stackTrace: any) => {
const testInfoImpl = testInfo as any;
const existingStep = testInfoImpl._currentSteps().find(step => step.category === 'pw:api' || step.category === 'expect');
const newStep = existingStep ? undefined : testInfoImpl._addStep('pw:api', stackTrace.apiName, { stack: stackTrace.frames, log: [] });
return (log: string[], error?: Error) => {
(existingStep || newStep)?.data.log?.push(...log);
newStep?.complete(error);
};
const newStep = existingStep ? undefined : testInfoImpl._addStep('pw:api', stackTrace.apiName);
return (error?: Error) => newStep?.complete(error);
},
};
contexts.push(context);

View file

@ -359,51 +359,6 @@ test('should not have internal error when steps are finished after timeout', asy
expect(result.output).not.toContain('Internal error');
});
test('should report expect and pw:api stacks and logs', async ({ runInlineTest }, testInfo) => {
const expectReporterJS = `
class Reporter {
stepStack(step) {
if (!step.data.stack || !step.data.stack[0])
return step.title + ' <no stack>';
const frame = step.data.stack[0]
return step.title + ' stack: ' + frame.file + ':' + frame.line + ':' + frame.column;
}
onStepEnd(test, result, step) {
console.log('%%%% ' + this.stepStack(step));
console.log('%%%% ' + step.title + ' log: ' + (step.data.log || []).join(''));
}
}
module.exports = Reporter;
`;
const result = await runInlineTest({
'reporter.ts': expectReporterJS,
'playwright.config.ts': `
module.exports = {
reporter: './reporter',
};
`,
'a.test.ts': `
const { test } = pwt;
test('pass', async ({ page }) => {
await page.setContent('<title>hello</title><body><div>Click me</div></body>');
await page.click('text=Click me');
expect(1).toBe(1);
await expect(page.locator('div')).toHaveText('Click me');
});
`
}, { reporter: '', workers: 1 });
expect(result.exitCode).toBe(0);
expect(result.output).toContain(`%% page.setContent stack: ${testInfo.outputPath('a.test.ts:7:20')}`);
expect(result.output).toContain(`%% page.setContent log: setting frame content, waiting until "load"`);
expect(result.output).toContain(`%% page.click stack: ${testInfo.outputPath('a.test.ts:8:20')}`);
expect(result.output).toContain(`%% page.click log: waiting for selector "text=Click me"`);
expect(result.output).toContain(`%% expect.toBe stack: ${testInfo.outputPath('a.test.ts:9:19')}`);
expect(result.output).toContain(`%% expect.toHaveText stack: ${testInfo.outputPath('a.test.ts:10:43')}`);
expect(result.output).toContain(`%% expect.toHaveText log: retrieving textContent from "div"`);
});
function stripEscapedAscii(str: string) {
return str.replace(/\\u00[a-z0-9][a-z0-9]\[[^m]+m/g, '');
}