mirror of
https://github.com/microsoft/playwright.git
synced 2024-11-24 06:49:04 +03:00
feat(pwt): serialize and expose Error.cause from Worker process (#32833)
This commit is contained in:
parent
5c0fdfed50
commit
9eb4fe5546
@ -4,6 +4,12 @@
|
||||
|
||||
Information about an error thrown during test execution.
|
||||
|
||||
## property: TestInfoError.cause
|
||||
* since: v1.49
|
||||
- type: ?<[TestInfoError]>
|
||||
|
||||
Error cause. Set when there is a [cause](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) for the error. Will be `undefined` if there is no cause or if the cause is not an instance of [Error].
|
||||
|
||||
## property: TestInfoError.message
|
||||
* since: v1.10
|
||||
- type: ?<[string]>
|
||||
|
@ -4,6 +4,12 @@
|
||||
|
||||
Information about an error thrown during test execution.
|
||||
|
||||
## property: TestError.cause
|
||||
* since: v1.49
|
||||
- type: ?<[TestError]>
|
||||
|
||||
Error cause. Set when there is a [cause](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) for the error. Will be `undefined` if there is no cause or if the cause is not an instance of [Error].
|
||||
|
||||
## property: TestError.message
|
||||
* since: v1.10
|
||||
- type: ?<[string]>
|
||||
|
@ -434,15 +434,16 @@ export function formatError(error: TestError, highlightCode: boolean): ErrorDeta
|
||||
tokens.push(snippet);
|
||||
}
|
||||
|
||||
if (parsedStack && parsedStack.stackLines.length) {
|
||||
tokens.push('');
|
||||
if (parsedStack && parsedStack.stackLines.length)
|
||||
tokens.push(colors.dim(parsedStack.stackLines.join('\n')));
|
||||
}
|
||||
|
||||
let location = error.location;
|
||||
if (parsedStack && !location)
|
||||
location = parsedStack.location;
|
||||
|
||||
if (error.cause)
|
||||
tokens.push(colors.dim('[cause]: ') + formatError(error.cause, highlightCode).message);
|
||||
|
||||
return {
|
||||
location,
|
||||
message: tokens.join('\n'),
|
||||
|
@ -29,15 +29,17 @@ import type { TestInfoErrorImpl } from './common/ipc';
|
||||
const PLAYWRIGHT_TEST_PATH = path.join(__dirname, '..');
|
||||
const PLAYWRIGHT_CORE_PATH = path.dirname(require.resolve('playwright-core/package.json'));
|
||||
|
||||
export function filterStackTrace(e: Error): { message: string, stack: string } {
|
||||
export function filterStackTrace(e: Error): { message: string, stack: string, cause?: ReturnType<typeof filterStackTrace> } {
|
||||
const name = e.name ? e.name + ': ' : '';
|
||||
const cause = e.cause instanceof Error ? filterStackTrace(e.cause) : undefined;
|
||||
if (process.env.PWDEBUGIMPL)
|
||||
return { message: name + e.message, stack: e.stack || '' };
|
||||
return { message: name + e.message, stack: e.stack || '', cause };
|
||||
|
||||
const stackLines = stringifyStackFrames(filteredStackTrace(e.stack?.split('\n') || []));
|
||||
return {
|
||||
message: name + e.message,
|
||||
stack: `${name}${e.message}${stackLines.map(line => '\n' + line).join('')}`
|
||||
stack: `${name}${e.message}${stackLines.map(line => '\n' + line).join('')}`,
|
||||
cause,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -224,11 +224,18 @@ export class TestTracing {
|
||||
const stack = rawStack ? filteredStackTrace(rawStack) : [];
|
||||
this._appendTraceEvent({
|
||||
type: 'error',
|
||||
message: error.message || String(error.value),
|
||||
message: this._formatError(error),
|
||||
stack,
|
||||
});
|
||||
}
|
||||
|
||||
_formatError(error: TestInfoErrorImpl) {
|
||||
const parts: string[] = [error.message || String(error.value)];
|
||||
if (error.cause)
|
||||
parts.push('[cause]: ' + this._formatError(error.cause));
|
||||
return parts.join('\n');
|
||||
}
|
||||
|
||||
appendStdioToTrace(type: 'stdout' | 'stderr', chunk: string | Buffer) {
|
||||
this._appendTraceEvent({
|
||||
type,
|
||||
|
7
packages/playwright/types/test.d.ts
vendored
7
packages/playwright/types/test.d.ts
vendored
@ -9152,6 +9152,13 @@ export interface TestInfo {
|
||||
* Information about an error thrown during test execution.
|
||||
*/
|
||||
export interface TestInfoError {
|
||||
/**
|
||||
* Error cause. Set when there is a
|
||||
* [cause](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) for the
|
||||
* error. Will be `undefined` if there is no cause or if the cause is not an instance of [Error].
|
||||
*/
|
||||
cause?: TestInfoError;
|
||||
|
||||
/**
|
||||
* Error message. Set when [Error] (or its subclass) has been thrown.
|
||||
*/
|
||||
|
7
packages/playwright/types/testReporter.d.ts
vendored
7
packages/playwright/types/testReporter.d.ts
vendored
@ -554,6 +554,13 @@ export interface TestCase {
|
||||
* Information about an error thrown during test execution.
|
||||
*/
|
||||
export interface TestError {
|
||||
/**
|
||||
* Error cause. Set when there is a
|
||||
* [cause](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) for the
|
||||
* error. Will be `undefined` if there is no cause or if the cause is not an instance of [Error].
|
||||
*/
|
||||
cause?: TestError;
|
||||
|
||||
/**
|
||||
* Error location in the source code.
|
||||
*/
|
||||
|
@ -118,6 +118,53 @@ 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 {
|
||||
const error = new Error('my-message');
|
||||
error.name = 'SpecialError';
|
||||
throw error;
|
||||
} 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 });
|
||||
}
|
||||
});
|
||||
test.afterAll(() => {
|
||||
expect(test.info().errors.length).toBe(1);
|
||||
expect(test.info().errors[0]).toBe(test.info().error);
|
||||
expect(test.info().error.message).toBe('Error: wrapper-message');
|
||||
expect(test.info().error.cause.message).toBe('Error: outer-message');
|
||||
expect(test.info().error.cause.cause.message).toBe('Error: inner-message');
|
||||
expect(test.info().error.cause.cause.cause.message).toBe('SpecialError: my-message');
|
||||
expect(test.info().error.cause.cause.cause.cause).toBe(undefined);
|
||||
console.log('afterAll executed successfully');
|
||||
})
|
||||
`
|
||||
});
|
||||
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(`${testFile}:18:21`);
|
||||
expect(result.output).toContain(`[cause]: Error: outer-message`);
|
||||
expect(result.output).toContain(`${testFile}:14:25`);
|
||||
expect(result.output).toContain(`[cause]: Error: inner-message`);
|
||||
expect(result.output).toContain(`${testFile}:12:25`);
|
||||
expect(result.output).toContain(`[cause]: SpecialError: my-message`);
|
||||
expect(result.output).toContain(`${testFile}:7:31`);
|
||||
expect(result.output).toContain('afterAll executed successfully');
|
||||
});
|
||||
|
||||
test('should print codeframe from a helper', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'helper.ts': `
|
||||
|
Loading…
Reference in New Issue
Block a user