diff --git a/packages/playwright-test/src/util.ts b/packages/playwright-test/src/util.ts index 5f7a33cf00..d868c4d08d 100644 --- a/packages/playwright-test/src/util.ts +++ b/packages/playwright-test/src/util.ts @@ -173,11 +173,17 @@ export function getContainedPath(parentPath: string, subPath: string = ''): stri export const debugTest = debug('pw:test'); -export function prependToTestError(testError: TestError | undefined, message: string | undefined) { +export function prependToTestError(testError: TestError | undefined, message: string | undefined, location?: Location) { if (!message) return testError; - if (!testError) - return { value: message }; + if (!testError) { + if (!location) + return { value: message }; + let stack = ` at ${location.file}:${location.line}:${location.column}`; + if (!message.endsWith('\n')) + stack = '\n' + stack; + return { message: message, stack: message + stack }; + } if (testError.message) { const stack = testError.stack ? message + testError.stack : testError.stack; message = message + testError.message; diff --git a/packages/playwright-test/src/workerRunner.ts b/packages/playwright-test/src/workerRunner.ts index dbf50be082..71b83e5fa4 100644 --- a/packages/playwright-test/src/workerRunner.ts +++ b/packages/playwright-test/src/workerRunner.ts @@ -21,7 +21,7 @@ import * as mime from 'mime'; import util from 'util'; import colors from 'colors/safe'; import { EventEmitter } from 'events'; -import { monotonicTime, serializeError, sanitizeForFilePath, getContainedPath, addSuffixToFilePath, prependToTestError, trimLongString } from './util'; +import { monotonicTime, serializeError, sanitizeForFilePath, getContainedPath, addSuffixToFilePath, prependToTestError, trimLongString, formatLocation } from './util'; import { TestBeginPayload, TestEndPayload, RunPayload, TestEntry, DonePayload, WorkerInitParams, StepBeginPayload, StepEndPayload } from './ipc'; import { setCurrentTestInfo } from './globals'; import { Loader } from './loader'; @@ -192,7 +192,7 @@ export class WorkerRunner extends EventEmitter { const result = await raceAgainstDeadline(this._fixtureRunner.resolveParametersAndRunHookOrTest(beforeAllModifier.fn, this._workerInfo, undefined), this._deadline()); if (result.timedOut) { if (!this._fatalError) - this._fatalError = serializeError(new Error(`Timeout of ${this._project.config.timeout}ms exceeded while running ${beforeAllModifier.type} modifier`)); + this._fatalError = serializeError(new Error(`Timeout of ${this._project.config.timeout}ms exceeded while running ${beforeAllModifier.type} modifier\n at ${formatLocation(beforeAllModifier.location)}`)); this.stop(); } if (!!result.result) @@ -451,7 +451,7 @@ export class WorkerRunner extends EventEmitter { this._fatalError = testInfo.error; // Keep any error we have, and add "timeout" message. if (testInfo.status === 'timedOut') - this._fatalError = prependToTestError(this._fatalError, colors.red(`Timeout of ${testInfo.timeout}ms exceeded in ${test._type} hook.\n`)); + this._fatalError = prependToTestError(this._fatalError, colors.red(`Timeout of ${testInfo.timeout}ms exceeded in ${test._type} hook.\n`), test.location); } this.stop(); } else { diff --git a/tests/playwright-test/hooks.spec.ts b/tests/playwright-test/hooks.spec.ts index 39a0d5fe5f..c8b70d7c3d 100644 --- a/tests/playwright-test/hooks.spec.ts +++ b/tests/playwright-test/hooks.spec.ts @@ -450,7 +450,7 @@ test('beforeAll timeout should be reported', async ({ runInlineTest }) => { expect(result.output).toContain('Timeout of 1000ms exceeded in beforeAll hook.'); }); -test('afterAll timeout should be reported', async ({ runInlineTest }) => { +test('afterAll timeout should be reported', async ({ runInlineTest }, testInfo) => { const result = await runInlineTest({ 'a.test.js': ` const { test } = pwt; @@ -470,6 +470,7 @@ test('afterAll timeout should be reported', async ({ runInlineTest }) => { '%%afterAll', ]); expect(result.output).toContain('Timeout of 1000ms exceeded in afterAll hook.'); + expect(result.output).toContain(`at ${testInfo.outputPath('a.test.js')}:6:12`); }); test('beforeAll and afterAll timeouts at the same time should be reported', async ({ runInlineTest }) => { diff --git a/tests/playwright-test/test-modifiers.spec.ts b/tests/playwright-test/test-modifiers.spec.ts index 25ab552d38..4133b597a6 100644 --- a/tests/playwright-test/test-modifiers.spec.ts +++ b/tests/playwright-test/test-modifiers.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { test, expect } from './playwright-test-fixtures'; +import { test, expect, stripAscii } from './playwright-test-fixtures'; test('test modifiers should work', async ({ runInlineTest }) => { const result = await runInlineTest({ @@ -318,3 +318,18 @@ test('test.skip should not define a skipped test inside another test', async ({ expect(result.failed).toBe(1); expect(result.output).toContain('It looks like you are calling test.skip() inside the test and pass a callback'); }); + +test('modifier timeout should be reported', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'a.test.ts': ` + const { test } = pwt; + test.skip(async () => new Promise(() => {})); + test('fails', () => { + }); + `, + }, { timeout: 2000 }); + expect(result.exitCode).toBe(1); + expect(result.failed).toBe(1); + expect(result.output).toContain('Error: Timeout of 2000ms exceeded while running skip modifier'); + expect(stripAscii(result.output)).toContain('6 | test.skip(async () => new Promise(() => {}));'); +});