chore: refactor error context (#35613)

This commit is contained in:
Simon Knott 2025-04-15 17:29:07 +02:00 committed by GitHub
parent 78600c60f8
commit cb2d94e467
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 89 additions and 72 deletions

View File

@ -20,17 +20,18 @@ import './testErrorView.css';
import type { ImageDiff } from '@web/shared/imageDiffView'; import type { ImageDiff } from '@web/shared/imageDiffView';
import { ImageDiffView } from '@web/shared/imageDiffView'; import { ImageDiffView } from '@web/shared/imageDiffView';
import { TestAttachment } from './types'; import { TestAttachment } from './types';
import { fixTestInstructions } from '@web/prompts';
export const TestErrorView: React.FC<{ export const TestErrorView: React.FC<{
error: string; error: string;
testId?: string; testId?: string;
prompt?: TestAttachment; context?: TestAttachment;
}> = ({ error, testId, prompt }) => { }> = ({ error, testId, context }) => {
return ( return (
<CodeSnippet code={error} testId={testId}> <CodeSnippet code={error} testId={testId}>
{prompt && ( {context && (
<div style={{ position: 'absolute', right: 0, padding: '10px' }}> <div style={{ position: 'absolute', right: 0, padding: '10px' }}>
<PromptButton prompt={prompt} /> <PromptButton context={context} />
</div> </div>
)} )}
</CodeSnippet> </CodeSnippet>
@ -47,14 +48,14 @@ export const CodeSnippet = ({ code, children, testId }: React.PropsWithChildren<
); );
}; };
const PromptButton: React.FC<{ prompt: TestAttachment }> = ({ prompt }) => { const PromptButton: React.FC<{ context: TestAttachment }> = ({ context }) => {
const [copied, setCopied] = React.useState(false); const [copied, setCopied] = React.useState(false);
return <button return <button
className='button' className='button'
style={{ minWidth: 100 }} style={{ minWidth: 100 }}
onClick={async () => { onClick={async () => {
const text = prompt.body ? prompt.body : await fetch(prompt.path!).then(r => r.text()); const text = context.body ? context.body : await fetch(context.path!).then(r => r.text());
await navigator.clipboard.writeText(text); await navigator.clipboard.writeText(fixTestInstructions + text);
setCopied(true); setCopied(true);
setTimeout(() => { setTimeout(() => {
setCopied(false); setCopied(false);

View File

@ -90,7 +90,7 @@ export const TestResultView: React.FC<{
{errors.map((error, index) => { {errors.map((error, index) => {
if (error.type === 'screenshot') if (error.type === 'screenshot')
return <TestScreenshotErrorView key={'test-result-error-message-' + index} errorPrefix={error.errorPrefix} diff={error.diff!} errorSuffix={error.errorSuffix}></TestScreenshotErrorView>; return <TestScreenshotErrorView key={'test-result-error-message-' + index} errorPrefix={error.errorPrefix} diff={error.diff!} errorSuffix={error.errorSuffix}></TestScreenshotErrorView>;
return <TestErrorView key={'test-result-error-message-' + index} error={error.error!} prompt={error.prompt}></TestErrorView>; return <TestErrorView key={'test-result-error-message-' + index} error={error.error!} context={error.context}></TestErrorView>;
})} })}
</AutoChip>} </AutoChip>}
{!!result.steps.length && <AutoChip header='Test Steps'> {!!result.steps.length && <AutoChip header='Test Steps'>
@ -165,8 +165,8 @@ function classifyErrors(testErrors: string[], diffs: ImageDiff[], attachments: T
} }
} }
const prompt = attachments.find(a => a.name === `_prompt-${i}`); const context = attachments.find(a => a.name === `_error-context-${i}`);
return { type: 'regular', error, prompt }; return { type: 'regular', error, context };
}); });
} }

View File

@ -14,5 +14,5 @@ common/
[internalsForTest.ts] [internalsForTest.ts]
** **
[prompt.ts] [errorContext.ts]
./transform/babelBundle.ts ./transform/babelBundle.ts

View File

@ -22,14 +22,25 @@ import { parseErrorStack } from 'playwright-core/lib/utils';
import { stripAnsiEscapes } from './util'; import { stripAnsiEscapes } from './util';
import { codeFrameColumns } from './transform/babelBundle'; import { codeFrameColumns } from './transform/babelBundle';
import type { TestInfo } from '../types/test';
import type { MetadataWithCommitInfo } from './isomorphic/types'; import type { MetadataWithCommitInfo } from './isomorphic/types';
import type { TestInfoImpl } from './worker/testInfo'; import type { TestInfoImpl } from './worker/testInfo';
export async function attachErrorPrompts(testInfo: TestInfo, sourceCache: Map<string, string>, ariaSnapshot: string | undefined) { export async function attachErrorContext(testInfo: TestInfoImpl, format: 'markdown' | 'json', sourceCache: Map<string, string>, ariaSnapshot: string | undefined) {
if (process.env.PLAYWRIGHT_NO_COPY_PROMPT) if (format === 'json') {
if (!ariaSnapshot)
return; return;
testInfo._attach({
name: `_error-context`,
contentType: 'application/json',
body: Buffer.from(JSON.stringify({
pageSnapshot: ariaSnapshot,
})),
}, undefined);
return;
}
const meaningfulSingleLineErrors = new Set(testInfo.errors.filter(e => e.message && !e.message.includes('\n')).map(e => e.message!)); const meaningfulSingleLineErrors = new Set(testInfo.errors.filter(e => e.message && !e.message.includes('\n')).map(e => e.message!));
for (const error of testInfo.errors) { for (const error of testInfo.errors) {
for (const singleLineError of meaningfulSingleLineErrors.keys()) { for (const singleLineError of meaningfulSingleLineErrors.keys()) {
@ -51,16 +62,10 @@ export async function attachErrorPrompts(testInfo: TestInfo, sourceCache: Map<st
for (const [index, error] of errors) { for (const [index, error] of errors) {
const metadata = testInfo.config.metadata as MetadataWithCommitInfo; const metadata = testInfo.config.metadata as MetadataWithCommitInfo;
if (testInfo.attachments.find(a => a.name === `_prompt-${index}`)) if (testInfo.attachments.find(a => a.name === `_error-context-${index}`))
continue; continue;
const promptParts = [ const lines = [
`# Instructions`,
'',
`- Following Playwright test failed.`,
`- Explain why, be concise, respect Playwright best practices.`,
`- Provide a snippet of code with the fix, if possible.`,
'',
`# Test info`, `# Test info`,
'', '',
`- Name: ${testInfo.titlePath.slice(1).join(' >> ')}`, `- Name: ${testInfo.titlePath.slice(1).join(' >> ')}`,
@ -74,7 +79,7 @@ export async function attachErrorPrompts(testInfo: TestInfo, sourceCache: Map<st
]; ];
if (ariaSnapshot) { if (ariaSnapshot) {
promptParts.push( lines.push(
'', '',
'# Page snapshot', '# Page snapshot',
'', '',
@ -103,7 +108,7 @@ export async function attachErrorPrompts(testInfo: TestInfo, sourceCache: Map<st
message: inlineMessage || undefined, message: inlineMessage || undefined,
} }
); );
promptParts.push( lines.push(
'', '',
'# Test source', '# Test source',
'', '',
@ -113,7 +118,7 @@ export async function attachErrorPrompts(testInfo: TestInfo, sourceCache: Map<st
); );
if (metadata.gitDiff) { if (metadata.gitDiff) {
promptParts.push( lines.push(
'', '',
'# Local changes', '# Local changes',
'', '',
@ -123,30 +128,17 @@ export async function attachErrorPrompts(testInfo: TestInfo, sourceCache: Map<st
); );
} }
const promptPath = testInfo.outputPath(errors.length === 1 ? `prompt.md` : `prompt-${index}.md`); const filePath = testInfo.outputPath(errors.length === 1 ? `error-context.md` : `error-context-${index}.md`);
await fs.writeFile(promptPath, promptParts.join('\n'), 'utf8'); await fs.writeFile(filePath, lines.join('\n'), 'utf8');
(testInfo as TestInfoImpl)._attach({ (testInfo as TestInfoImpl)._attach({
name: `_prompt-${index}`, name: `_error-context-${index}`,
contentType: 'text/markdown', contentType: 'text/markdown',
path: promptPath, path: filePath,
}, undefined); }, undefined);
} }
} }
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) {

View File

@ -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 { attachErrorContext, attachErrorPrompts } from './prompt'; import { attachErrorContext } from './errorContext';
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';
@ -55,13 +55,15 @@ type TestFixtures = PlaywrightTestArgs & PlaywrightTestOptions & {
_contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>; _contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>;
}; };
type ErrorContextOption = { format: 'json' | 'markdown' } | undefined;
type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & { type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & {
playwright: PlaywrightImpl; playwright: PlaywrightImpl;
_browserOptions: LaunchOptions; _browserOptions: LaunchOptions;
_optionContextReuseMode: ContextReuseMode, _optionContextReuseMode: ContextReuseMode,
_optionConnectOptions: PlaywrightWorkerOptions['connectOptions'], _optionConnectOptions: PlaywrightWorkerOptions['connectOptions'],
_reuseContext: boolean, _reuseContext: boolean,
_optionAttachErrorContext: boolean, _optionErrorContext: ErrorContextOption,
}; };
const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
@ -245,13 +247,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, _optionAttachErrorContext }, use, testInfo) => { _setupArtifacts: [async ({ playwright, screenshot, _optionErrorContext }, 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, _optionAttachErrorContext); const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot, _optionErrorContext);
await artifactsRecorder.willStartTest(testInfo as TestInfoImpl); await artifactsRecorder.willStartTest(testInfo as TestInfoImpl);
const tracingGroupSteps: TestStepInternal[] = []; const tracingGroupSteps: TestStepInternal[] = [];
@ -393,7 +395,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 }], _optionErrorContext: [process.env.PLAYWRIGHT_NO_COPY_PROMPT ? undefined : { format: 'markdown' }, { scope: 'worker', option: true }],
_reuseContext: [async ({ video, _optionContextReuseMode }, use) => { _reuseContext: [async ({ video, _optionContextReuseMode }, use) => {
let mode = _optionContextReuseMode; let mode = _optionContextReuseMode;
@ -622,12 +624,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; private _errorContext: ErrorContextOption;
constructor(playwright: PlaywrightImpl, artifactsDir: string, screenshot: ScreenshotOption, attachErrorContext: boolean) { constructor(playwright: PlaywrightImpl, artifactsDir: string, screenshot: ScreenshotOption, errorContext: ErrorContextOption) {
this._playwright = playwright; this._playwright = playwright;
this._artifactsDir = artifactsDir; this._artifactsDir = artifactsDir;
this._attachErrorContext = attachErrorContext; this._errorContext = errorContext;
const screenshotOptions = typeof screenshot === 'string' ? undefined : screenshot; const screenshotOptions = typeof screenshot === 'string' ? undefined : screenshot;
this._startedCollectingArtifacts = Symbol('startedCollectingArtifacts'); this._startedCollectingArtifacts = Symbol('startedCollectingArtifacts');
@ -671,7 +673,7 @@ class ArtifactsRecorder {
} }
private async _takePageSnapshot(context: BrowserContext) { private async _takePageSnapshot(context: BrowserContext) {
if (process.env.PLAYWRIGHT_NO_COPY_PROMPT) if (!this._errorContext)
return; return;
if (this._testInfo.errors.length === 0) if (this._testInfo.errors.length === 0)
return; return;
@ -719,10 +721,8 @@ class ArtifactsRecorder {
if (context) if (context)
await this._takePageSnapshot(context); await this._takePageSnapshot(context);
if (this._attachErrorContext) if (this._errorContext)
await attachErrorContext(this._testInfo, this._pageSnapshot); await attachErrorContext(this._testInfo, this._errorContext.format, this._sourceCache, this._pageSnapshot);
else
await attachErrorPrompts(this._testInfo, this._sourceCache, this._pageSnapshot);
} }
private async _startTraceChunkOnContextCreation(tracing: Tracing) { private async _startTraceChunkOnContextCreation(tracing: Tracing) {

View File

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

View File

@ -337,9 +337,9 @@ export function formatFailure(screen: Screen, config: FullConfig, test: TestCase
// } // }
for (let i = 0; i < result.attachments.length; ++i) { for (let i = 0; i < result.attachments.length; ++i) {
const attachment = result.attachments[i]; const attachment = result.attachments[i];
if (attachment.name.startsWith('_prompt') && attachment.path) { if (attachment.name.startsWith('_error-context') && attachment.path) {
resultLines.push(''); resultLines.push('');
resultLines.push(screen.colors.dim(` Error Prompt: ${relativeFilePath(screen, config, attachment.path)}`)); resultLines.push(screen.colors.dim(` Error Context: ${relativeFilePath(screen, config, attachment.path)}`));
continue; continue;
} }
if (attachment.name.startsWith('_')) if (attachment.name.startsWith('_'))

View File

@ -314,7 +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, _optionErrorContext: params.errorContext,
}, },
...(params.updateSnapshots ? { updateSnapshots: params.updateSnapshots } : {}), ...(params.updateSnapshots ? { updateSnapshots: params.updateSnapshots } : {}),
...(params.updateSourceMethod ? { updateSourceMethod: params.updateSourceMethod } : {}), ...(params.updateSourceMethod ? { updateSourceMethod: params.updateSourceMethod } : {}),

View File

@ -26,6 +26,7 @@ import { ToolbarButton } from '@web/components/toolbarButton';
import { useIsLLMAvailable, useLLMChat } from './llm'; import { useIsLLMAvailable, useLLMChat } from './llm';
import { useAsyncMemo } from '@web/uiUtils'; import { useAsyncMemo } from '@web/uiUtils';
import { attachmentURL } from './attachmentsTab'; import { attachmentURL } from './attachmentsTab';
import { fixTestInstructions } from '@web/prompts';
const CopyPromptButton: React.FC<{ prompt: string }> = ({ prompt }) => { const CopyPromptButton: React.FC<{ prompt: string }> = ({ prompt }) => {
return ( return (
@ -67,10 +68,10 @@ function Error({ message, error, errorId, sdkLanguage, revealInSource }: { messa
} }
const prompt = useAsyncMemo(async () => { const prompt = useAsyncMemo(async () => {
if (!error.prompt) if (!error.context)
return; return;
const response = await fetch(attachmentURL(error.prompt)); const response = await fetch(attachmentURL(error.context));
return await response.text(); return fixTestInstructions + await response.text();
}, [error], undefined); }, [error], undefined);
return <div style={{ display: 'flex', flexDirection: 'column', overflowX: 'clip' }}> return <div style={{ display: 'flex', flexDirection: 'column', overflowX: 'clip' }}>

View File

@ -56,7 +56,7 @@ export type ErrorDescription = {
action?: ActionTraceEventInContext; action?: ActionTraceEventInContext;
stack?: StackFrame[]; stack?: StackFrame[];
message: string; message: string;
prompt?: trace.AfterActionTraceEventAttachment & { traceUrl: string }; context?: trace.AfterActionTraceEventAttachment & { traceUrl: string };
}; };
export type Attachment = trace.AfterActionTraceEventAttachment & { traceUrl: string }; export type Attachment = trace.AfterActionTraceEventAttachment & { traceUrl: string };
@ -141,7 +141,7 @@ export class MultiTraceModel {
return this.errors.filter(e => !!e.message).map((error, i) => ({ return this.errors.filter(e => !!e.message).map((error, i) => ({
stack: error.stack, stack: error.stack,
message: error.message, message: error.message,
prompt: this.attachments.find(a => a.name === `_prompt-${i}`), context: this.attachments.find(a => a.name === `_error-context-${i}`),
})); }));
} }
} }

View File

@ -0,0 +1,23 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const fixTestInstructions = `
# Instructions
- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.
`.trimStart();

View File

@ -167,7 +167,7 @@ test('should print debug log when failed to connect', async ({ runInlineTest })
expect(result.exitCode).toBe(1); expect(result.exitCode).toBe(1);
expect(result.failed).toBe(1); expect(result.failed).toBe(1);
expect(result.output).toContain('b-debug-log-string'); expect(result.output).toContain('b-debug-log-string');
expect(result.results[0].attachments).toEqual([expect.objectContaining({ name: '_prompt-0' })]); expect(result.results[0].attachments).toEqual([expect.objectContaining({ name: '_error-context-0' })]);
}); });
test('should record trace', async ({ runInlineTest }) => { test('should record trace', async ({ runInlineTest }) => {
@ -223,7 +223,7 @@ test('should record trace', async ({ runInlineTest }) => {
'After Hooks', 'After Hooks',
'fixture: page', 'fixture: page',
'fixture: context', 'fixture: context',
'_attach "_prompt-0"', '_attach "_error-context-0"',
'Worker Cleanup', 'Worker Cleanup',
'fixture: browser', 'fixture: browser',
]); ]);

View File

@ -510,13 +510,13 @@ test('should work with video: on-first-retry', async ({ runInlineTest }) => {
expect(fs.existsSync(dirPass)).toBeFalsy(); expect(fs.existsSync(dirPass)).toBeFalsy();
const dirFail = test.info().outputPath('test-results', 'a-fail-chromium'); const dirFail = test.info().outputPath('test-results', 'a-fail-chromium');
expect(fs.readdirSync(dirFail)).toEqual(['prompt.md']); expect(fs.readdirSync(dirFail)).toEqual(['error-context.md']);
const dirRetry = test.info().outputPath('test-results', 'a-fail-chromium-retry1'); const dirRetry = test.info().outputPath('test-results', 'a-fail-chromium-retry1');
const videoFailRetry = fs.readdirSync(dirRetry).find(file => file.endsWith('webm')); const videoFailRetry = fs.readdirSync(dirRetry).find(file => file.endsWith('webm'));
expect(videoFailRetry).toBeTruthy(); expect(videoFailRetry).toBeTruthy();
const errorPrompt = expect.objectContaining({ name: '_prompt-0' }); const errorPrompt = expect.objectContaining({ name: '_error-context-0' });
expect(result.report.suites[0].specs[1].tests[0].results[0].attachments).toEqual([errorPrompt]); expect(result.report.suites[0].specs[1].tests[0].results[0].attachments).toEqual([errorPrompt]);
expect(result.report.suites[0].specs[1].tests[0].results[1].attachments).toEqual([{ expect(result.report.suites[0].specs[1].tests[0].results[1].attachments).toEqual([{
name: 'video', name: 'video',

View File

@ -359,7 +359,7 @@ test('should report parallelIndex', async ({ runInlineTest }, testInfo) => {
test('attaches error context', async ({ runInlineTest }) => { test('attaches error context', async ({ runInlineTest }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'playwright.config.ts': ` 'playwright.config.ts': `
export default { use: { _optionAttachErrorContext: true } }; export default { use: { _optionErrorContext: { format: 'json' } } };
`, `,
'a.test.js': ` 'a.test.js': `
const { test, expect } = require('@playwright/test'); const { test, expect } = require('@playwright/test');

View File

@ -190,7 +190,7 @@ for (const useIntermediateMergeReport of [false, true] as const) {
expect(result.exitCode).toBe(1); expect(result.exitCode).toBe(1);
}); });
test('should show error prompt with relative path', async ({ runInlineTest, useIntermediateMergeReport }) => { test('should show error context with relative path', async ({ runInlineTest, useIntermediateMergeReport }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'a.test.js': ` 'a.test.js': `
const { test, expect } = require('@playwright/test'); const { test, expect } = require('@playwright/test');
@ -201,9 +201,9 @@ for (const useIntermediateMergeReport of [false, true] as const) {
}, { reporter: 'line' }); }, { reporter: 'line' });
const text = result.output; const text = result.output;
if (useIntermediateMergeReport) if (useIntermediateMergeReport)
expect(text).toContain(`Error Prompt: ${path.join('blob-report', 'resources')}`); expect(text).toContain(`Error Context: ${path.join('blob-report', 'resources')}`);
else else
expect(text).toContain(`Error Prompt: ${path.join('test-results', 'a-one', 'prompt.md')}`); expect(text).toContain(`Error Context: ${path.join('test-results', 'a-one', 'error-context.md')}`);
expect(result.exitCode).toBe(1); expect(result.exitCode).toBe(1);
}); });
}); });