feat(pwt): serialize Error.cause as multiple errors from Worker process
This commit is contained in:
parent
d07f6cfc5c
commit
55a0a7d892
|
|
@ -62,12 +62,18 @@ export function filteredStackTrace(rawStack: RawStack): StackFrame[] {
|
|||
return frames;
|
||||
}
|
||||
|
||||
export function serializeError(error: Error | any): TestInfoError {
|
||||
if (error instanceof Error)
|
||||
return filterStackTrace(error);
|
||||
return {
|
||||
export function serializeError(error: Error | any): TestInfoError[] {
|
||||
if (error instanceof Error) {
|
||||
const errors = [filterStackTrace(error)];
|
||||
while (error.cause) {
|
||||
error = error.cause;
|
||||
errors.push(filterStackTrace(error));
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
return [{
|
||||
value: util.inspect(error)
|
||||
};
|
||||
}];
|
||||
}
|
||||
|
||||
export type Matcher = (value: string) => boolean;
|
||||
|
|
|
|||
|
|
@ -272,7 +272,7 @@ export class TestInfoImpl implements TestInfo {
|
|||
if (result.error) {
|
||||
if (typeof result.error === 'object' && !(result.error as any)?.[stepSymbol])
|
||||
(result.error as any)[stepSymbol] = step;
|
||||
const error = serializeError(result.error);
|
||||
const error = serializeError(result.error)[0];
|
||||
if (data.boxedStack)
|
||||
error.stack = `${error.message}\n${stringifyStackFrames(data.boxedStack).join('\n')}`;
|
||||
step.error = error;
|
||||
|
|
@ -333,9 +333,9 @@ export class TestInfoImpl implements TestInfo {
|
|||
const serialized = serializeError(error);
|
||||
const step: TestStepInternal | undefined = typeof error === 'object' ? (error as any)?.[stepSymbol] : undefined;
|
||||
if (step && step.boxedStack)
|
||||
serialized.stack = `${(error as Error).name}: ${(error as Error).message}\n${stringifyStackFrames(step.boxedStack).join('\n')}`;
|
||||
this.errors.push(serialized);
|
||||
this._tracing.appendForError(serialized);
|
||||
serialized[0].stack = `${(error as Error).name}: ${(error as Error).message}\n${stringifyStackFrames(step.boxedStack).join('\n')}`;
|
||||
this.errors.push(...serialized);
|
||||
this._tracing.appendForError(...serialized);
|
||||
}
|
||||
|
||||
async _runAsStage(stage: TestStage, cb: () => Promise<any>) {
|
||||
|
|
|
|||
|
|
@ -219,14 +219,16 @@ export class TestTracing {
|
|||
this._testInfo.attachments.push({ name: 'trace', path: tracePath, contentType: 'application/zip' });
|
||||
}
|
||||
|
||||
appendForError(error: TestInfoError) {
|
||||
const rawStack = error.stack?.split('\n') || [];
|
||||
const stack = rawStack ? filteredStackTrace(rawStack) : [];
|
||||
this._appendTraceEvent({
|
||||
type: 'error',
|
||||
message: error.message || String(error.value),
|
||||
stack,
|
||||
});
|
||||
appendForError(...errors: TestInfoError[]) {
|
||||
for (const error of errors) {
|
||||
const rawStack = error.stack?.split('\n') || [];
|
||||
const stack = rawStack ? filteredStackTrace(rawStack) : [];
|
||||
this._appendTraceEvent({
|
||||
type: 'error',
|
||||
message: error.message || String(error.value),
|
||||
stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
appendStdioToTrace(type: 'stdout' | 'stderr', chunk: string | Buffer) {
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ export class WorkerMain extends ProcessRunner {
|
|||
await fakeTestInfo._runAsStage({ title: 'worker cleanup', runnable }, () => gracefullyCloseAll()).catch(() => {});
|
||||
this._fatalErrors.push(...fakeTestInfo.errors);
|
||||
} catch (e) {
|
||||
this._fatalErrors.push(serializeError(e));
|
||||
this._fatalErrors.push(...serializeError(e));
|
||||
}
|
||||
|
||||
if (this._fatalErrors.length) {
|
||||
|
|
@ -153,7 +153,7 @@ export class WorkerMain extends ProcessRunner {
|
|||
// No current test - fatal error.
|
||||
if (!this._currentTest) {
|
||||
if (!this._fatalErrors.length)
|
||||
this._fatalErrors.push(serializeError(error));
|
||||
this._fatalErrors.push(...serializeError(error));
|
||||
void this._stop();
|
||||
return;
|
||||
}
|
||||
|
|
@ -224,7 +224,7 @@ export class WorkerMain extends ProcessRunner {
|
|||
// In theory, we should run above code without any errors.
|
||||
// However, in the case we screwed up, or loadTestFile failed in the worker
|
||||
// but not in the runner, let's do a fatal error.
|
||||
this._fatalErrors.push(serializeError(e));
|
||||
this._fatalErrors.push(...serializeError(e));
|
||||
void this._stop();
|
||||
} finally {
|
||||
const donePayload: DonePayload = {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
import { test, expect } from './playwright-test-fixtures';
|
||||
import * as path from 'path';
|
||||
|
||||
for (const useIntermediateMergeReport of [false, true] as const) {
|
||||
for (const useIntermediateMergeReport of [false] as const) {
|
||||
test.describe(`${useIntermediateMergeReport ? 'merged' : 'created'}`, () => {
|
||||
test.use({ useIntermediateMergeReport });
|
||||
|
||||
|
|
@ -118,6 +118,39 @@ for (const useIntermediateMergeReport of [false, true] as const) {
|
|||
expect(output).toContain(`a.spec.ts:5:13`);
|
||||
});
|
||||
|
||||
test('should print error with a nested cause', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.spec.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('foobar', async ({}) => {
|
||||
try {
|
||||
try {
|
||||
throw new Error('my-message');
|
||||
} catch (e) {
|
||||
try {
|
||||
throw new Error('inner-message', { cause: e });
|
||||
} catch (e) {
|
||||
throw new Error('outer-message', { cause: e });
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error('wrapper-message', { cause: e });
|
||||
}
|
||||
});
|
||||
`
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.failed).toBe(1);
|
||||
const testFile = path.join(result.report.config.rootDir, result.report.suites[0].specs[0].file);
|
||||
expect(result.output).toContain(` at fn (${testFile}:15:21)`);
|
||||
expect(result.output).toContain(` Error: outer-message`);
|
||||
expect(result.output).toContain(` at fn (${testFile}:11:25)`);
|
||||
expect(result.output).toContain(` Error: inner-message`);
|
||||
expect(result.output).toContain(` at fn (${testFile}:9:25)`);
|
||||
expect(result.output).toContain(` Error: my-message`);
|
||||
expect(result.output).toContain(` at fn (${testFile}:6:23)`);
|
||||
});
|
||||
|
||||
test('should print codeframe from a helper', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'helper.ts': `
|
||||
|
|
|
|||
Loading…
Reference in a new issue