chore: provide error context to test server (#35339)
This commit is contained in:
parent
3d2aa1a5e4
commit
87bc0f48ef
|
@ -22,7 +22,7 @@ import { setBoxedStackPrefixes, asLocator, createGuid, currentZone, debugMode, i
|
||||||
|
|
||||||
import { currentTestInfo } from './common/globals';
|
import { currentTestInfo } from './common/globals';
|
||||||
import { rootTestType } from './common/testType';
|
import { rootTestType } from './common/testType';
|
||||||
import { attachErrorPrompts } from './prompt';
|
import { attachErrorContext, attachErrorPrompts } from './prompt';
|
||||||
|
|
||||||
import type { Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, ScreenshotMode, TestInfo, TestType, VideoMode } from '../types/test';
|
import type { Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, ScreenshotMode, TestInfo, TestType, VideoMode } from '../types/test';
|
||||||
import type { ContextReuseMode } from './common/config';
|
import type { ContextReuseMode } from './common/config';
|
||||||
|
@ -61,6 +61,7 @@ type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & {
|
||||||
_optionContextReuseMode: ContextReuseMode,
|
_optionContextReuseMode: ContextReuseMode,
|
||||||
_optionConnectOptions: PlaywrightWorkerOptions['connectOptions'],
|
_optionConnectOptions: PlaywrightWorkerOptions['connectOptions'],
|
||||||
_reuseContext: boolean,
|
_reuseContext: boolean,
|
||||||
|
_optionAttachErrorContext: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||||
|
@ -245,13 +246,13 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||||
playwright._defaultContextNavigationTimeout = undefined;
|
playwright._defaultContextNavigationTimeout = undefined;
|
||||||
}, { auto: 'all-hooks-included', title: 'context configuration', box: true } as any],
|
}, { auto: 'all-hooks-included', title: 'context configuration', box: true } as any],
|
||||||
|
|
||||||
_setupArtifacts: [async ({ playwright, screenshot }, use, testInfo) => {
|
_setupArtifacts: [async ({ playwright, screenshot, _optionAttachErrorContext }, use, testInfo) => {
|
||||||
// This fixture has a separate zero-timeout slot to ensure that artifact collection
|
// This fixture has a separate zero-timeout slot to ensure that artifact collection
|
||||||
// happens even after some fixtures or hooks time out.
|
// happens even after some fixtures or hooks time out.
|
||||||
// Now that default test timeout is known, we can replace zero with an actual value.
|
// Now that default test timeout is known, we can replace zero with an actual value.
|
||||||
testInfo.setTimeout(testInfo.project.timeout);
|
testInfo.setTimeout(testInfo.project.timeout);
|
||||||
|
|
||||||
const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot);
|
const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot, _optionAttachErrorContext);
|
||||||
await artifactsRecorder.willStartTest(testInfo as TestInfoImpl);
|
await artifactsRecorder.willStartTest(testInfo as TestInfoImpl);
|
||||||
|
|
||||||
const tracingGroupSteps: TestStepInternal[] = [];
|
const tracingGroupSteps: TestStepInternal[] = [];
|
||||||
|
@ -393,6 +394,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||||
|
|
||||||
_optionContextReuseMode: ['none', { scope: 'worker', option: true }],
|
_optionContextReuseMode: ['none', { scope: 'worker', option: true }],
|
||||||
_optionConnectOptions: [undefined, { scope: 'worker', option: true }],
|
_optionConnectOptions: [undefined, { scope: 'worker', option: true }],
|
||||||
|
_optionAttachErrorContext: [false, { scope: 'worker', option: true }],
|
||||||
|
|
||||||
_reuseContext: [async ({ video, _optionContextReuseMode }, use) => {
|
_reuseContext: [async ({ video, _optionContextReuseMode }, use) => {
|
||||||
let mode = _optionContextReuseMode;
|
let mode = _optionContextReuseMode;
|
||||||
|
@ -621,10 +623,12 @@ class ArtifactsRecorder {
|
||||||
private _screenshotRecorder: SnapshotRecorder;
|
private _screenshotRecorder: SnapshotRecorder;
|
||||||
private _pageSnapshot: string | undefined;
|
private _pageSnapshot: string | undefined;
|
||||||
private _sourceCache: Map<string, string> = new Map();
|
private _sourceCache: Map<string, string> = new Map();
|
||||||
|
private _attachErrorContext: boolean;
|
||||||
|
|
||||||
constructor(playwright: PlaywrightImpl, artifactsDir: string, screenshot: ScreenshotOption) {
|
constructor(playwright: PlaywrightImpl, artifactsDir: string, screenshot: ScreenshotOption, attachErrorContext: boolean) {
|
||||||
this._playwright = playwright;
|
this._playwright = playwright;
|
||||||
this._artifactsDir = artifactsDir;
|
this._artifactsDir = artifactsDir;
|
||||||
|
this._attachErrorContext = attachErrorContext;
|
||||||
const screenshotOptions = typeof screenshot === 'string' ? undefined : screenshot;
|
const screenshotOptions = typeof screenshot === 'string' ? undefined : screenshot;
|
||||||
this._startedCollectingArtifacts = Symbol('startedCollectingArtifacts');
|
this._startedCollectingArtifacts = Symbol('startedCollectingArtifacts');
|
||||||
|
|
||||||
|
@ -703,7 +707,10 @@ class ArtifactsRecorder {
|
||||||
})));
|
})));
|
||||||
|
|
||||||
await this._screenshotRecorder.persistTemporary();
|
await this._screenshotRecorder.persistTemporary();
|
||||||
await attachErrorPrompts(this._testInfo, this._sourceCache, this._pageSnapshot);
|
if (this._attachErrorContext)
|
||||||
|
await attachErrorContext(this._testInfo, this._pageSnapshot);
|
||||||
|
else
|
||||||
|
await attachErrorPrompts(this._testInfo, this._sourceCache, this._pageSnapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _startTraceChunkOnContextCreation(tracing: Tracing) {
|
private async _startTraceChunkOnContextCreation(tracing: Tracing) {
|
||||||
|
|
|
@ -102,6 +102,7 @@ export interface TestServerInterface {
|
||||||
projects?: string[];
|
projects?: string[];
|
||||||
reuseContext?: boolean;
|
reuseContext?: boolean;
|
||||||
connectWsEndpoint?: string;
|
connectWsEndpoint?: string;
|
||||||
|
attachErrorContext?: boolean;
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
status: reporterTypes.FullResult['status'];
|
status: reporterTypes.FullResult['status'];
|
||||||
}>;
|
}>;
|
||||||
|
|
|
@ -127,6 +127,19 @@ export async function attachErrorPrompts(testInfo: TestInfo, sourceCache: Map<st
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function attachErrorContext(testInfo: TestInfo, ariaSnapshot: string | undefined) {
|
||||||
|
if (!ariaSnapshot)
|
||||||
|
return;
|
||||||
|
|
||||||
|
(testInfo as TestInfoImpl)._attach({
|
||||||
|
name: `_error-context`,
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: Buffer.from(JSON.stringify({
|
||||||
|
pageSnapshot: ariaSnapshot,
|
||||||
|
})),
|
||||||
|
}, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
async function loadSource(file: string, sourceCache: Map<string, string>) {
|
async function loadSource(file: string, sourceCache: Map<string, string>) {
|
||||||
let source = sourceCache.get(file);
|
let source = sourceCache.get(file);
|
||||||
if (!source) {
|
if (!source) {
|
||||||
|
|
|
@ -314,6 +314,7 @@ export class TestServerDispatcher implements TestServerInterface {
|
||||||
...(params.headed !== undefined ? { headless: !params.headed } : {}),
|
...(params.headed !== undefined ? { headless: !params.headed } : {}),
|
||||||
_optionContextReuseMode: params.reuseContext ? 'when-possible' : undefined,
|
_optionContextReuseMode: params.reuseContext ? 'when-possible' : undefined,
|
||||||
_optionConnectOptions: params.connectWsEndpoint ? { wsEndpoint: params.connectWsEndpoint } : undefined,
|
_optionConnectOptions: params.connectWsEndpoint ? { wsEndpoint: params.connectWsEndpoint } : undefined,
|
||||||
|
_optionAttachErrorContext: params.attachErrorContext,
|
||||||
},
|
},
|
||||||
...(params.updateSnapshots ? { updateSnapshots: params.updateSnapshots } : {}),
|
...(params.updateSnapshots ? { updateSnapshots: params.updateSnapshots } : {}),
|
||||||
...(params.updateSourceMethod ? { updateSourceMethod: params.updateSourceMethod } : {}),
|
...(params.updateSourceMethod ? { updateSourceMethod: params.updateSourceMethod } : {}),
|
||||||
|
|
|
@ -355,3 +355,26 @@ test('should report parallelIndex', async ({ runInlineTest }, testInfo) => {
|
||||||
expect(result.report.suites[0].specs[1].tests[0].results[0].parallelIndex).toBe(1);
|
expect(result.report.suites[0].specs[1].tests[0].results[0].parallelIndex).toBe(1);
|
||||||
expect(result.report.suites[0].specs[2].tests[0].results[0].parallelIndex).toBe(1);
|
expect(result.report.suites[0].specs[2].tests[0].results[0].parallelIndex).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('attaches error context', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'playwright.config.ts': `
|
||||||
|
export default { use: { _optionAttachErrorContext: true } };
|
||||||
|
`,
|
||||||
|
'a.test.js': `
|
||||||
|
const { test, expect } = require('@playwright/test');
|
||||||
|
test('one', async ({ page }, testInfo) => {
|
||||||
|
await page.setContent('<button>Click me</button>');
|
||||||
|
throw new Error('kaboom');
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { reporter: 'json' });
|
||||||
|
|
||||||
|
const errorContext = result.report.suites[0].specs[0].tests[0].results[0].attachments.find(a => a.name === '_error-context');
|
||||||
|
expect(errorContext).toBeDefined();
|
||||||
|
expect(errorContext!.contentType).toBe('application/json');
|
||||||
|
const json = JSON.parse(Buffer.from(errorContext!.body, 'base64').toString('utf-8'));
|
||||||
|
expect(json).toEqual({
|
||||||
|
pageSnapshot: expect.any(String),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue