fix(steps): only propagate soft errors up the hierarchy (#24054)
Fixes https://github.com/microsoft/playwright/issues/23979
This commit is contained in:
parent
ea3a29eacd
commit
566b277ce8
|
|
@ -255,7 +255,8 @@ class ExpectMetaInfoProxyHandler implements ProxyHandler<any> {
|
||||||
category: 'expect',
|
category: 'expect',
|
||||||
title: trimLongString(customMessage || defaultTitle, 1024),
|
title: trimLongString(customMessage || defaultTitle, 1024),
|
||||||
params: args[0] ? { expected: args[0] } : undefined,
|
params: args[0] ? { expected: args[0] } : undefined,
|
||||||
wallTime
|
wallTime,
|
||||||
|
infectParentStepsWithError: this._info.isSoft,
|
||||||
}) : undefined;
|
}) : undefined;
|
||||||
|
|
||||||
const reportStepError = (jestError: Error) => {
|
const reportStepError = (jestError: Error) => {
|
||||||
|
|
|
||||||
|
|
@ -352,7 +352,6 @@ export async function toPass(
|
||||||
title: 'expect.toPass',
|
title: 'expect.toPass',
|
||||||
category: 'expect',
|
category: 'expect',
|
||||||
location: stackFrames[0],
|
location: stackFrames[0],
|
||||||
insulateChildErrors: true,
|
|
||||||
}, callback);
|
}, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export interface TestStepInternal {
|
||||||
apiName?: string;
|
apiName?: string;
|
||||||
params?: Record<string, any>;
|
params?: Record<string, any>;
|
||||||
error?: TestInfoError;
|
error?: TestInfoError;
|
||||||
insulateChildErrors?: boolean;
|
infectParentStepsWithError?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TestInfoImpl implements TestInfo {
|
export class TestInfoImpl implements TestInfo {
|
||||||
|
|
@ -265,12 +265,23 @@ export class TestInfoImpl implements TestInfo {
|
||||||
} else if (result.error) {
|
} else if (result.error) {
|
||||||
// Internal API step reported an error.
|
// Internal API step reported an error.
|
||||||
error = result.error;
|
error = result.error;
|
||||||
} else if (!data.insulateChildErrors) {
|
|
||||||
// One of the child steps failed (probably soft expect).
|
|
||||||
// Report this step as failed to make it easier to spot.
|
|
||||||
error = step.steps.map(s => s.error).find(e => !!e);
|
|
||||||
}
|
}
|
||||||
step.error = error;
|
step.error = error;
|
||||||
|
|
||||||
|
if (!error) {
|
||||||
|
// Soft errors inside try/catch will make the test fail.
|
||||||
|
// In order to locate the failing step, we are marking all the parent
|
||||||
|
// steps as failing unconditionally.
|
||||||
|
for (const childStep of step.steps) {
|
||||||
|
if (childStep.error && childStep.infectParentStepsWithError) {
|
||||||
|
step.error = childStep.error;
|
||||||
|
step.infectParentStepsWithError = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error = step.error;
|
||||||
|
}
|
||||||
|
|
||||||
const payload: StepEndPayload = {
|
const payload: StepEndPayload = {
|
||||||
testId: this._test.id,
|
testId: this._test.id,
|
||||||
stepId,
|
stepId,
|
||||||
|
|
|
||||||
|
|
@ -1128,3 +1128,164 @@ test('should show final toPass error', async ({ runInlineTest }) => {
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should propagate nested soft errors', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'reporter.ts': stepHierarchyReporter,
|
||||||
|
'playwright.config.ts': `module.exports = { reporter: './reporter', };`,
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('fail', async () => {
|
||||||
|
await test.step('first outer', async () => {
|
||||||
|
await test.step('first inner', async () => {
|
||||||
|
expect.soft(1).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('second outer', async () => {
|
||||||
|
await test.step('second inner', async () => {
|
||||||
|
expect(1).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
`
|
||||||
|
}, { reporter: '' });
|
||||||
|
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
const objects = result.outputLines.map(line => JSON.parse(line));
|
||||||
|
expect(objects).toEqual([
|
||||||
|
{
|
||||||
|
category: 'hook',
|
||||||
|
title: 'Before Hooks',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: 'test.step',
|
||||||
|
title: 'first outer',
|
||||||
|
error: '<error>',
|
||||||
|
location: { column: 'number', file: 'a.test.ts', line: 'number' },
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
category: 'test.step',
|
||||||
|
title: 'first inner',
|
||||||
|
error: '<error>',
|
||||||
|
location: { column: 'number', file: 'a.test.ts', line: 'number' },
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
category: 'expect',
|
||||||
|
title: 'expect.soft.toBe',
|
||||||
|
error: '<error>',
|
||||||
|
location: { column: 'number', file: 'a.test.ts', line: 'number' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: 'test.step',
|
||||||
|
title: 'second outer',
|
||||||
|
error: '<error>',
|
||||||
|
location: { column: 'number', file: 'a.test.ts', line: 'number' },
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
category: 'test.step',
|
||||||
|
title: 'second inner',
|
||||||
|
error: '<error>',
|
||||||
|
location: { column: 'number', file: 'a.test.ts', line: 'number' },
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
category: 'expect',
|
||||||
|
title: 'expect.toBe',
|
||||||
|
error: '<error>',
|
||||||
|
location: { column: 'number', file: 'a.test.ts', line: 'number' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: 'hook',
|
||||||
|
title: 'After Hooks',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not propagate nested hard errors', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'reporter.ts': stepHierarchyReporter,
|
||||||
|
'playwright.config.ts': `module.exports = { reporter: './reporter', };`,
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('fail', async () => {
|
||||||
|
await test.step('first outer', async () => {
|
||||||
|
await test.step('first inner', async () => {
|
||||||
|
try {
|
||||||
|
expect(1).toBe(2);
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('second outer', async () => {
|
||||||
|
await test.step('second inner', async () => {
|
||||||
|
expect(1).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
`
|
||||||
|
}, { reporter: '' });
|
||||||
|
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
const objects = result.outputLines.map(line => JSON.parse(line));
|
||||||
|
expect(objects).toEqual([
|
||||||
|
{
|
||||||
|
category: 'hook',
|
||||||
|
title: 'Before Hooks',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: 'test.step',
|
||||||
|
title: 'first outer',
|
||||||
|
location: { column: 'number', file: 'a.test.ts', line: 'number' },
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
category: 'test.step',
|
||||||
|
title: 'first inner',
|
||||||
|
location: { column: 'number', file: 'a.test.ts', line: 'number' },
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
category: 'expect',
|
||||||
|
title: 'expect.toBe',
|
||||||
|
error: '<error>',
|
||||||
|
location: { column: 'number', file: 'a.test.ts', line: 'number' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: 'test.step',
|
||||||
|
title: 'second outer',
|
||||||
|
error: '<error>',
|
||||||
|
location: { column: 'number', file: 'a.test.ts', line: 'number' },
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
category: 'test.step',
|
||||||
|
title: 'second inner',
|
||||||
|
error: '<error>',
|
||||||
|
location: { column: 'number', file: 'a.test.ts', line: 'number' },
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
category: 'expect',
|
||||||
|
title: 'expect.toBe',
|
||||||
|
error: '<error>',
|
||||||
|
location: { column: 'number', file: 'a.test.ts', line: 'number' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: 'hook',
|
||||||
|
title: 'After Hooks',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue