fix(esm): show codeframe when errors get reported (#15262)

This commit is contained in:
Max Schmitt 2022-06-30 21:17:08 +02:00 committed by GitHub
parent 4de14e7d2c
commit 3d1d723c56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 43 additions and 21 deletions

View File

@ -15,11 +15,9 @@
*/
import path from 'path';
import { StackUtils } from '../utilsBundle';
import { parseStackTraceLine } from '../utilsBundle';
import { isUnderTest } from './';
const stackUtils = new StackUtils();
export function rewriteErrorMessage<E extends Error>(e: E, newMessage: string): E {
const lines: string[] = (e.stack?.split('\n') || []).filter(l => l.startsWith(' at '));
e.message = newMessage;
@ -82,17 +80,11 @@ export function captureStackTrace(rawStack?: string): ParsedStackTrace {
inCore: boolean;
};
let parsedFrames = stack.split('\n').map(line => {
const frame = stackUtils.parseLine(line);
if (!frame || !frame.file)
const { frame, fileName } = parseStackTraceLine(line);
if (!frame || !frame.file || !fileName)
return null;
if (isInternalFileName(frame.file, frame.function))
return null;
// Workaround for https://github.com/tapjs/stack-utils/issues/60
let fileName: string;
if (frame.file.startsWith('file://'))
fileName = new URL(frame.file).pathname;
else
fileName = path.resolve(process.cwd(), frame.file);
if (isTesting && fileName.includes(COVERAGE_PATH))
return null;
const inCore = fileName.startsWith(CORE_LIB) || fileName.startsWith(CORE_SRC);

View File

@ -14,6 +14,9 @@
* limitations under the License.
*/
import url from 'url';
import path from 'path';
export const colors: typeof import('../bundles/utils/node_modules/colors/safe') = require('./utilsBundleImpl').colors;
export const debug: typeof import('../bundles/utils/node_modules/@types/debug') = require('./utilsBundleImpl').debug;
export const getProxyForUrl: typeof import('../bundles/utils/node_modules/@types/proxy-from-env').getProxyForUrl = require('./utilsBundleImpl').getProxyForUrl;
@ -28,10 +31,27 @@ export const program: typeof import('../bundles/utils/node_modules/commander').p
export const progress: typeof import('../bundles/utils/node_modules/@types/progress') = require('./utilsBundleImpl').progress;
export const rimraf: typeof import('../bundles/utils/node_modules/@types/rimraf') = require('./utilsBundleImpl').rimraf;
export const SocksProxyAgent: typeof import('../bundles/utils/node_modules/socks-proxy-agent').SocksProxyAgent = require('./utilsBundleImpl').SocksProxyAgent;
export const StackUtils: typeof import('../bundles/utils/node_modules/@types/stack-utils') = require('./utilsBundleImpl').StackUtils;
export const ws: typeof import('../bundles/utils/node_modules/@types/ws') = require('./utilsBundleImpl').ws;
export const wsServer: typeof import('../bundles/utils/node_modules/@types/ws').WebSocketServer = require('./utilsBundleImpl').wsServer;
export const wsReceiver = require('./utilsBundleImpl').wsReceiver;
export const wsSender = require('./utilsBundleImpl').wsSender;
export type { Command } from '../bundles/utils/node_modules/commander';
export type { WebSocket, WebSocketServer, RawData as WebSocketRawData, EventEmitter as WebSocketEventEmitter } from '../bundles/utils/node_modules/@types/ws';
const StackUtils: typeof import('../bundles/utils/node_modules/@types/stack-utils') = require('./utilsBundleImpl').StackUtils;
const stackUtils = new StackUtils();
export function parseStackTraceLine(line: string): { frame: import('../bundles/utils/node_modules/@types/stack-utils').StackLineData | null, fileName: string | null } {
const frame = stackUtils.parseLine(line);
if (!frame)
return { frame: null, fileName: null };
let fileName = null;
if (frame.file) {
// ESM files return file:// URLs, see here: https://github.com/tapjs/stack-utils/issues/60
fileName = frame.file.startsWith('file://') ? url.fileURLToPath(frame.file) : path.resolve(process.cwd(), frame.file);
}
return {
frame,
fileName,
};
}

View File

@ -14,14 +14,12 @@
* limitations under the License.
*/
import { colors, ms as milliseconds } from 'playwright-core/lib/utilsBundle';
import { colors, ms as milliseconds, parseStackTraceLine } from 'playwright-core/lib/utilsBundle';
import fs from 'fs';
import path from 'path';
import { StackUtils } from 'playwright-core/lib/utilsBundle';
import type { FullConfig, TestCase, Suite, TestResult, TestError, Reporter, FullResult, TestStep, Location } from '../../types/testReporter';
import type { FullConfigInternal } from '../types';
import { codeFrameColumns } from '../babelBundle';
const stackUtils = new StackUtils();
export type TestResultOutput = { chunk: string | Buffer, type: 'stdout' | 'stderr' };
export const kOutputSymbol = Symbol('output');
@ -411,10 +409,9 @@ export function prepareErrorStack(stack: string, file?: string): {
const stackLines = lines.slice(firstStackLine);
let location: Location | undefined;
for (const line of stackLines) {
const parsed = stackUtils.parseLine(line);
if (!parsed || !parsed.file)
const { frame: parsed, fileName: resolvedFile } = parseStackTraceLine(line);
if (!parsed || !resolvedFile)
continue;
const resolvedFile = path.join(process.cwd(), parsed.file);
if (!file || resolvedFile === file) {
location = { file: resolvedFile, column: parsed.column || 0, line: parsed.line || 0 };
break;

View File

@ -148,7 +148,6 @@ test('should use source maps', async ({ runInlineTest, nodeVersion }) => {
});
test('should show the codeframe in errors', async ({ runInlineTest, nodeVersion }) => {
test.fixme();
// We only support experimental esm mode on Node 16+
test.skip(nodeVersion.major < 16);
const result = await runInlineTest({
@ -163,17 +162,31 @@ test('should show the codeframe in errors', async ({ runInlineTest, nodeVersion
expect(1).toBe(2);
expect(testInfo.project.name).toBe('foo');
});
test('foobar', async ({}) => {
const error = new Error('my-message');
error.name = 'FooBarError';
throw error;
});
`
}, { reporter: 'list' });
}, { reporter: 'list' }, {
FORCE_COLOR: '0',
});
const output = stripAnsi(result.output);
expect(result.exitCode).toBe(1);
expect(result.failed).toBe(1);
expect(result.failed).toBe(2);
expect(output, 'error carrot—via source maps—is positioned appropriately').toContain(
[
` > 8 | expect(1).toBe(2);`,
` | ^`
].join('\n'));
expect(result.output).toContain('FooBarError: my-message');
expect(result.output).not.toContain('at a.test.ts');
expect(result.output).toContain(` 12 | test('foobar', async ({}) => {`);
expect(result.output).toContain(`> 13 | const error = new Error('my-message');`);
expect(result.output).toContain(' | ^');
expect(result.output).toContain(' 14 | error.name = \'FooBarError\';');
});
test('should filter by line', async ({ runInlineTest, nodeVersion }) => {