diff --git a/packages/playwright/src/matchers/expect.ts b/packages/playwright/src/matchers/expect.ts index 3e49360eb7..ea796bfc72 100644 --- a/packages/playwright/src/matchers/expect.ts +++ b/packages/playwright/src/matchers/expect.ts @@ -60,7 +60,7 @@ import { } from '../common/expectBundle'; import { zones } from 'playwright-core/lib/utils'; import { TestInfoImpl } from '../worker/testInfo'; -import { ExpectError } from './matcherHint'; +import { ExpectError, isExpectError } from './matcherHint'; // #region // Mirrored from https://github.com/facebook/jest/blob/f13abff8df9a0e1148baf3584bcde6d1b479edc7/packages/expect/src/print.ts @@ -289,8 +289,8 @@ class ExpectMetaInfoProxyHandler implements ProxyHandler { const step = testInfo._addStep(stepInfo); - const reportStepError = (jestError: ExpectError) => { - const error = new ExpectError(jestError, customMessage, stackFrames); + const reportStepError = (jestError: Error | unknown) => { + const error = isExpectError(jestError) ? new ExpectError(jestError, customMessage, stackFrames) : jestError; step.complete({ error }); if (this._info.isSoft) testInfo._failWithError(error); diff --git a/packages/playwright/src/matchers/matcherHint.ts b/packages/playwright/src/matchers/matcherHint.ts index e8aba2bbff..8a78932c68 100644 --- a/packages/playwright/src/matchers/matcherHint.ts +++ b/packages/playwright/src/matchers/matcherHint.ts @@ -64,3 +64,7 @@ export class ExpectError extends Error { this.stack = this.name + ': ' + this.message + '\n' + stringifyStackFrames(stackFrames).join('\n'); } } + +export function isExpectError(e: unknown): e is ExpectError { + return e instanceof Error && 'matcherResult' in e; +} diff --git a/packages/playwright/src/worker/testInfo.ts b/packages/playwright/src/worker/testInfo.ts index e6b07be7c0..378b32524f 100644 --- a/packages/playwright/src/worker/testInfo.ts +++ b/packages/playwright/src/worker/testInfo.ts @@ -30,7 +30,7 @@ import type { Attachment } from './testTracing'; import type { StackFrame } from '@protocol/channels'; export interface TestStepInternal { - complete(result: { error?: Error, attachments?: Attachment[] }): void; + complete(result: { error?: Error | unknown, attachments?: Attachment[] }): void; stepId: string; title: string; category: 'hook' | 'fixture' | 'test.step' | 'expect' | 'attach' | string; @@ -270,7 +270,7 @@ export class TestInfoImpl implements TestInfo { step.endWallTime = Date.now(); if (result.error) { - if (!(result.error as any)[stepSymbol]) + if (typeof result.error === 'object' && !(result.error as any)?.[stepSymbol]) (result.error as any)[stepSymbol] = step; const error = serializeError(result.error); if (data.boxedStack) @@ -327,13 +327,13 @@ export class TestInfoImpl implements TestInfo { this.status = 'interrupted'; } - _failWithError(error: Error) { + _failWithError(error: Error | unknown) { if (this.status === 'passed' || this.status === 'skipped') this.status = error instanceof TimeoutManagerError ? 'timedOut' : 'failed'; const serialized = serializeError(error); - const step = (error as any)[stepSymbol] as TestStepInternal | undefined; + const step: TestStepInternal | undefined = typeof error === 'object' ? (error as any)?.[stepSymbol] : undefined; if (step && step.boxedStack) - serialized.stack = `${error.name}: ${error.message}\n${stringifyStackFrames(step.boxedStack).join('\n')}`; + serialized.stack = `${(error as Error).name}: ${(error as Error).message}\n${stringifyStackFrames(step.boxedStack).join('\n')}`; this.errors.push(serialized); this._tracing.appendForError(serialized); } diff --git a/tests/playwright-test/expect-poll.spec.ts b/tests/playwright-test/expect-poll.spec.ts index e740fd5abe..aa8bbde79d 100644 --- a/tests/playwright-test/expect-poll.spec.ts +++ b/tests/playwright-test/expect-poll.spec.ts @@ -232,3 +232,29 @@ test('should show intermediate result for poll that spills over test time', asyn expect(result.output).toContain('Expected: 2'); expect(result.output).toContain('Received: 3'); }); + +test('should propagate promise rejections', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/32256' } }, async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('should fail', async () => { + await expect.poll(() => Promise.reject('some error')).toBe({ foo: 'baz' }); + }); + ` + }); + + expect(result.output).toContain('some error'); +}); + +test('should propagate string exception from async arrow function', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/32256' } }, async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('should fail', async () => { + await expect.poll(async () => { throw 'some error' }).toBe({ foo: 'baz' }); + }); + ` + }); + + expect(result.output).toContain('some error'); +}); \ No newline at end of file