From 9117562377b8122b4db16f599bda54d809679021 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 20 Sep 2024 11:49:44 +0200 Subject: [PATCH] chore: throw on nested toPass calls --- packages/playwright/src/matchers/matchers.ts | 6 +++++- tests/playwright-test/expect-to-pass.spec.ts | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/playwright/src/matchers/matchers.ts b/packages/playwright/src/matchers/matchers.ts index 3ca9180ae2..b6b93576ce 100644 --- a/packages/playwright/src/matchers/matchers.ts +++ b/packages/playwright/src/matchers/matchers.ts @@ -26,6 +26,7 @@ import { currentTestInfo } from '../common/globals'; import { TestInfoImpl } from '../worker/testInfo'; import type { ExpectMatcherState } from '../../types/test'; import { takeFirst } from '../common/config'; +import { AsyncLocalStorage } from 'async_hooks'; interface LocatorEx extends Locator { _expect(expression: string, options: Omit & { expectedValue?: any }): Promise<{ matches: boolean, received?: any, log?: string[], timedOut?: boolean }>; @@ -397,6 +398,7 @@ export async function toBeOK( return { message, pass }; } +const toPassContextStorage = new AsyncLocalStorage(); export async function toPass( this: ExpectMatcherState, callback: () => any, @@ -405,6 +407,8 @@ export async function toPass( timeout?: number, } = {}, ) { + if (toPassContextStorage.getStore() !== undefined) + throw new Error(`Error: Nested toPass calls detected. toPass should not be used inside another toPass.`); const testInfo = currentTestInfo(); const timeout = takeFirst(options.timeout, testInfo?._projectInternal.expect?.toPass?.timeout, 0); const intervals = takeFirst(options.intervals, testInfo?._projectInternal.expect?.toPass?.intervals, [100, 250, 500, 1000]); @@ -414,7 +418,7 @@ export async function toPass( if (testInfo && currentTestInfo() !== testInfo) return { continuePolling: false, result: undefined }; try { - await callback(); + await toPassContextStorage.run('inside', () => callback()); return { continuePolling: !!this.isNot, result: undefined }; } catch (e) { return { continuePolling: !this.isNot, result: e }; diff --git a/tests/playwright-test/expect-to-pass.spec.ts b/tests/playwright-test/expect-to-pass.spec.ts index c3d16639db..3140654596 100644 --- a/tests/playwright-test/expect-to-pass.spec.ts +++ b/tests/playwright-test/expect-to-pass.spec.ts @@ -298,3 +298,20 @@ test('should give priority to intervals parameter over intervals in config file' expect(result.output).toContain('Expected: -1'); expect(result.output).toContain('Received: 3'); }); + +test('should throw if you have nested toPass', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('nested toPass', async () => { + await test.expect(async () => { + await test.expect(() => { + expect(1).toBe(2); + }).toPass({ timeout: 50 }); + }).toPass({ timeout: 100 }); + }); + ` + }); + expect(result.exitCode).toBe(1); + expect(result.output).toContain('Error: Nested toPass calls detected. toPass should not be used inside another toPass.'); +});