diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index 950b844ce2..b6338da4fa 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -1288,7 +1288,11 @@ export class Frame extends SdkObject { const state = element ? injected.elementState(element, 'visible') : false; return state === 'error:notconnected' ? false : state; }, { info: resolved.info }); - }, this._page._timeoutSettings.timeout({})); + }, this._page._timeoutSettings.timeout({})).catch(e => { + if (js.isJavaScriptErrorInEvaluate(e) || isInvalidSelectorError(e) || isSessionClosedError(e)) + throw e; + return false; + }); } async isHidden(metadata: CallMetadata, selector: string, options: types.StrictOptions = {}): Promise { diff --git a/packages/playwright-core/src/utils/manualPromise.ts b/packages/playwright-core/src/utils/manualPromise.ts index d5eae1c274..5867840c10 100644 --- a/packages/playwright-core/src/utils/manualPromise.ts +++ b/packages/playwright-core/src/utils/manualPromise.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { rewriteErrorMessage } from './stackTrace'; + export class ManualPromise extends Promise { private _resolve!: (t: T) => void; private _reject!: (e: Error) => void; @@ -56,12 +58,14 @@ export class ManualPromise extends Promise { export class ScopedRace { private _terminateError: Error | undefined; - private _terminatePromises = new Set>(); + private _terminatePromises = new Map, Error>(); scopeClosed(error: Error) { this._terminateError = error; - for (const p of this._terminatePromises) - p.resolve(error); + for (const [p, e] of this._terminatePromises) { + rewriteErrorMessage(e, error.message); + p.resolve(e); + } } async race(promise: Promise): Promise { @@ -76,7 +80,8 @@ export class ScopedRace { const terminatePromise = new ManualPromise(); if (this._terminateError) terminatePromise.resolve(this._terminateError); - this._terminatePromises.add(terminatePromise); + const error = new Error(''); + this._terminatePromises.set(terminatePromise, error); try { return await Promise.race([ terminatePromise.then(e => safe ? defaultValue : Promise.reject(e)), diff --git a/tests/page/locator-is-visible.spec.ts b/tests/page/locator-is-visible.spec.ts index 2c607ac921..e4203a0f5f 100644 --- a/tests/page/locator-is-visible.spec.ts +++ b/tests/page/locator-is-visible.spec.ts @@ -85,3 +85,23 @@ it('isVisible inside a role=button', async ({ page }) => { await span.waitFor({ state: 'hidden' }); await page.locator('[role=button]').waitFor({ state: 'visible' }); }); + +it('isVisible during navigation should not throw', async ({ page, server }) => { + for (let i = 0; i < 20; i++) { + // Make sure previous navigation finishes, to avoid page.setContent throwing. + await page.waitForTimeout(100); + await page.setContent(` + + `); + expect(await page.locator('div').isVisible()).toBe(false); + } +}); + +it('isVisible with invalid selector should throw', async ({ page }) => { + const error = await page.locator('hey=what').isVisible().catch(e => e); + expect(error.message).toContain('Unknown engine "hey" while parsing selector hey=what'); +});