mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
fix(esm): show codeframe when errors get reported (#15262)
This commit is contained in:
parent
4de14e7d2c
commit
3d1d723c56
@ -15,11 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { StackUtils } from '../utilsBundle';
|
import { parseStackTraceLine } from '../utilsBundle';
|
||||||
import { isUnderTest } from './';
|
import { isUnderTest } from './';
|
||||||
|
|
||||||
const stackUtils = new StackUtils();
|
|
||||||
|
|
||||||
export function rewriteErrorMessage<E extends Error>(e: E, newMessage: string): E {
|
export function rewriteErrorMessage<E extends Error>(e: E, newMessage: string): E {
|
||||||
const lines: string[] = (e.stack?.split('\n') || []).filter(l => l.startsWith(' at '));
|
const lines: string[] = (e.stack?.split('\n') || []).filter(l => l.startsWith(' at '));
|
||||||
e.message = newMessage;
|
e.message = newMessage;
|
||||||
@ -82,17 +80,11 @@ export function captureStackTrace(rawStack?: string): ParsedStackTrace {
|
|||||||
inCore: boolean;
|
inCore: boolean;
|
||||||
};
|
};
|
||||||
let parsedFrames = stack.split('\n').map(line => {
|
let parsedFrames = stack.split('\n').map(line => {
|
||||||
const frame = stackUtils.parseLine(line);
|
const { frame, fileName } = parseStackTraceLine(line);
|
||||||
if (!frame || !frame.file)
|
if (!frame || !frame.file || !fileName)
|
||||||
return null;
|
return null;
|
||||||
if (isInternalFileName(frame.file, frame.function))
|
if (isInternalFileName(frame.file, frame.function))
|
||||||
return null;
|
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))
|
if (isTesting && fileName.includes(COVERAGE_PATH))
|
||||||
return null;
|
return null;
|
||||||
const inCore = fileName.startsWith(CORE_LIB) || fileName.startsWith(CORE_SRC);
|
const inCore = fileName.startsWith(CORE_LIB) || fileName.startsWith(CORE_SRC);
|
||||||
|
@ -14,6 +14,9 @@
|
|||||||
* limitations under the License.
|
* 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 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 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;
|
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 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 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 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 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 wsServer: typeof import('../bundles/utils/node_modules/@types/ws').WebSocketServer = require('./utilsBundleImpl').wsServer;
|
||||||
export const wsReceiver = require('./utilsBundleImpl').wsReceiver;
|
export const wsReceiver = require('./utilsBundleImpl').wsReceiver;
|
||||||
export const wsSender = require('./utilsBundleImpl').wsSender;
|
export const wsSender = require('./utilsBundleImpl').wsSender;
|
||||||
export type { Command } from '../bundles/utils/node_modules/commander';
|
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';
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -14,14 +14,12 @@
|
|||||||
* limitations under the License.
|
* 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 fs from 'fs';
|
||||||
import path from 'path';
|
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 { FullConfig, TestCase, Suite, TestResult, TestError, Reporter, FullResult, TestStep, Location } from '../../types/testReporter';
|
||||||
import type { FullConfigInternal } from '../types';
|
import type { FullConfigInternal } from '../types';
|
||||||
import { codeFrameColumns } from '../babelBundle';
|
import { codeFrameColumns } from '../babelBundle';
|
||||||
const stackUtils = new StackUtils();
|
|
||||||
|
|
||||||
export type TestResultOutput = { chunk: string | Buffer, type: 'stdout' | 'stderr' };
|
export type TestResultOutput = { chunk: string | Buffer, type: 'stdout' | 'stderr' };
|
||||||
export const kOutputSymbol = Symbol('output');
|
export const kOutputSymbol = Symbol('output');
|
||||||
@ -411,10 +409,9 @@ export function prepareErrorStack(stack: string, file?: string): {
|
|||||||
const stackLines = lines.slice(firstStackLine);
|
const stackLines = lines.slice(firstStackLine);
|
||||||
let location: Location | undefined;
|
let location: Location | undefined;
|
||||||
for (const line of stackLines) {
|
for (const line of stackLines) {
|
||||||
const parsed = stackUtils.parseLine(line);
|
const { frame: parsed, fileName: resolvedFile } = parseStackTraceLine(line);
|
||||||
if (!parsed || !parsed.file)
|
if (!parsed || !resolvedFile)
|
||||||
continue;
|
continue;
|
||||||
const resolvedFile = path.join(process.cwd(), parsed.file);
|
|
||||||
if (!file || resolvedFile === file) {
|
if (!file || resolvedFile === file) {
|
||||||
location = { file: resolvedFile, column: parsed.column || 0, line: parsed.line || 0 };
|
location = { file: resolvedFile, column: parsed.column || 0, line: parsed.line || 0 };
|
||||||
break;
|
break;
|
||||||
|
@ -148,7 +148,6 @@ test('should use source maps', async ({ runInlineTest, nodeVersion }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should show the codeframe in errors', async ({ runInlineTest, nodeVersion }) => {
|
test('should show the codeframe in errors', async ({ runInlineTest, nodeVersion }) => {
|
||||||
test.fixme();
|
|
||||||
// We only support experimental esm mode on Node 16+
|
// We only support experimental esm mode on Node 16+
|
||||||
test.skip(nodeVersion.major < 16);
|
test.skip(nodeVersion.major < 16);
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
@ -163,17 +162,31 @@ test('should show the codeframe in errors', async ({ runInlineTest, nodeVersion
|
|||||||
expect(1).toBe(2);
|
expect(1).toBe(2);
|
||||||
expect(testInfo.project.name).toBe('foo');
|
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);
|
const output = stripAnsi(result.output);
|
||||||
expect(result.exitCode).toBe(1);
|
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(
|
expect(output, 'error carrot—via source maps—is positioned appropriately').toContain(
|
||||||
[
|
[
|
||||||
` > 8 | expect(1).toBe(2);`,
|
` > 8 | expect(1).toBe(2);`,
|
||||||
` | ^`
|
` | ^`
|
||||||
].join('\n'));
|
].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 }) => {
|
test('should filter by line', async ({ runInlineTest, nodeVersion }) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user