Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 51 additions & 28 deletions packages/playwright/src/errorContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { codeFrameColumns } from './transform/babelBundle';

import type { MetadataWithCommitInfo } from './isomorphic/types';
import type { TestInfoImpl } from './worker/testInfo';
import type { Location } from '../types/test';

export async function attachErrorContext(testInfo: TestInfoImpl, format: 'markdown' | 'json', sourceCache: Map<string, string>, ariaSnapshot: string | undefined) {
if (format === 'json') {
Expand Down Expand Up @@ -91,31 +92,32 @@ export async function attachErrorContext(testInfo: TestInfoImpl, format: 'markdo

const parsedError = error.stack ? parseErrorStack(error.stack, path.sep) : undefined;
const inlineMessage = stripAnsiEscapes(parsedError?.message || error.message || '').split('\n')[0];
const location = parsedError?.location || { file: testInfo.file, line: testInfo.line, column: testInfo.column };
const source = await loadSource(location.file, sourceCache);
const codeFrame = codeFrameColumns(
source,
{
start: {
line: location.line,
column: location.column
const loadedSource = await loadSource(parsedError?.location, testInfo, sourceCache);
if (loadedSource) {
const codeFrame = codeFrameColumns(
loadedSource.source,
{
start: {
line: loadedSource.location.line,
column: loadedSource.location.column
},
},
},
{
highlightCode: false,
linesAbove: 100,
linesBelow: 100,
message: inlineMessage || undefined,
}
);
lines.push(
'',
'# Test source',
'',
'```ts',
codeFrame,
'```',
);
{
highlightCode: false,
linesAbove: 100,
linesBelow: 100,
message: inlineMessage || undefined,
}
);
lines.push(
'',
'# Test source',
'',
'```ts',
codeFrame,
'```',
);
}

if (metadata.gitDiff) {
lines.push(
Expand All @@ -139,12 +141,33 @@ export async function attachErrorContext(testInfo: TestInfoImpl, format: 'markdo
}
}

async function loadSource(file: string, sourceCache: Map<string, string>) {
async function loadSource(
errorLocation: Location | undefined,
testLocation: Location,
sourceCache: Map<string, string>
): Promise<{ location: Location, source: string } | undefined> {
if (errorLocation) {
const source = await loadSourceCached(errorLocation.file, sourceCache);
if (source)
return { location: errorLocation, source };
}
// If the error location is not available on the disk (e.g. fake page.evaluate in-browser error), then fallback to the test file.
const source = await loadSourceCached(testLocation.file, sourceCache);
if (source)
return { location: testLocation, source };
return undefined;
}

async function loadSourceCached(file: string, sourceCache: Map<string, string>): Promise<string | undefined> {
let source = sourceCache.get(file);
if (!source) {
// A mild race is Ok here.
source = await fs.readFile(file, 'utf8');
sourceCache.set(file, source);
try {
// A mild race is Ok here.
source = await fs.readFile(file, 'utf8');
sourceCache.set(file, source);
} catch (e) {
// Ignore errors.
}
}
return source;
}
22 changes: 22 additions & 0 deletions tests/playwright-test/reporter-line.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import path from 'path';
import fs from 'fs';
import { test, expect } from './playwright-test-fixtures';

for (const useIntermediateMergeReport of [false, true] as const) {
Expand Down Expand Up @@ -206,5 +207,26 @@ for (const useIntermediateMergeReport of [false, true] as const) {
expect(text).toContain(`Error Context: ${path.join('test-results', 'a-one', 'error-context.md')}`);
expect(result.exitCode).toBe(1);
});

test('should show error context if exception contains non-existent file', async ({ runInlineTest, useIntermediateMergeReport }) => {
const result = await runInlineTest({
'a.test.js': `
const { test, expect } = require('@playwright/test');
test('one', async ({ page }) => {
await page.evaluate(() => {
throw new Error('error');
});
});
`,
}, { reporter: 'line' });
if (useIntermediateMergeReport)
expect(result.output).toContain(`Error Context: ${path.join('blob-report', 'resources')}`);
else
expect(result.output).toContain(`Error Context: ${path.join('test-results', 'a-one', 'error-context.md')}`);
const file = /Error Context: (.*)/.exec(result.output)?.[1];
const content = await fs.promises.readFile(path.join(result.report.config.rootDir, file), 'utf8');
expect(content).toContain('^ Error: page.evaluate: Error: error');
expect(result.exitCode).toBe(1);
});
});
}
Loading