chore: provide error context to test server (#35339)

This commit is contained in:
Simon Knott 2025-03-25 14:07:02 +01:00 committed by GitHub
parent 3d2aa1a5e4
commit 87bc0f48ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 50 additions and 5 deletions

View File

@ -22,7 +22,7 @@ import { setBoxedStackPrefixes, asLocator, createGuid, currentZone, debugMode, i
import { currentTestInfo } from './common/globals';
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 { ContextReuseMode } from './common/config';
@ -61,6 +61,7 @@ type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & {
_optionContextReuseMode: ContextReuseMode,
_optionConnectOptions: PlaywrightWorkerOptions['connectOptions'],
_reuseContext: boolean,
_optionAttachErrorContext: boolean,
};
const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
@ -245,13 +246,13 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
playwright._defaultContextNavigationTimeout = undefined;
}, { 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
// happens even after some fixtures or hooks time out.
// Now that default test timeout is known, we can replace zero with an actual value.
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);
const tracingGroupSteps: TestStepInternal[] = [];
@ -393,6 +394,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
_optionContextReuseMode: ['none', { scope: 'worker', option: true }],
_optionConnectOptions: [undefined, { scope: 'worker', option: true }],
_optionAttachErrorContext: [false, { scope: 'worker', option: true }],
_reuseContext: [async ({ video, _optionContextReuseMode }, use) => {
let mode = _optionContextReuseMode;
@ -621,10 +623,12 @@ class ArtifactsRecorder {
private _screenshotRecorder: SnapshotRecorder;
private _pageSnapshot: string | undefined;
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._artifactsDir = artifactsDir;
this._attachErrorContext = attachErrorContext;
const screenshotOptions = typeof screenshot === 'string' ? undefined : screenshot;
this._startedCollectingArtifacts = Symbol('startedCollectingArtifacts');
@ -703,7 +707,10 @@ class ArtifactsRecorder {
})));
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) {

View File

@ -102,6 +102,7 @@ export interface TestServerInterface {
projects?: string[];
reuseContext?: boolean;
connectWsEndpoint?: string;
attachErrorContext?: boolean;
}): Promise<{
status: reporterTypes.FullResult['status'];
}>;

View File

@ -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>) {
let source = sourceCache.get(file);
if (!source) {

View File

@ -314,6 +314,7 @@ export class TestServerDispatcher implements TestServerInterface {
...(params.headed !== undefined ? { headless: !params.headed } : {}),
_optionContextReuseMode: params.reuseContext ? 'when-possible' : undefined,
_optionConnectOptions: params.connectWsEndpoint ? { wsEndpoint: params.connectWsEndpoint } : undefined,
_optionAttachErrorContext: params.attachErrorContext,
},
...(params.updateSnapshots ? { updateSnapshots: params.updateSnapshots } : {}),
...(params.updateSourceMethod ? { updateSourceMethod: params.updateSourceMethod } : {}),

View File

@ -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[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),
});
});