diff --git a/packages/puppeteer-core/src/common/bidi/Browser.ts b/packages/puppeteer-core/src/common/bidi/Browser.ts index f6e02f0bb92..76eb1ed214a 100644 --- a/packages/puppeteer-core/src/common/bidi/Browser.ts +++ b/packages/puppeteer-core/src/common/bidi/Browser.ts @@ -99,8 +99,9 @@ export class Browser extends BrowserBase { this.#browserName = opts.browserName; this.#browserVersion = opts.browserVersion; - this.#process?.on('close', () => { - return this.emit(BrowserEmittedEvents.Disconnected); + this.#process?.once('close', () => { + this.#connection.dispose(); + this.emit(BrowserEmittedEvents.Disconnected); }); } @@ -109,6 +110,9 @@ export class Browser extends BrowserBase { } override async close(): Promise { + if (this.#connection.closed) { + return; + } this.#connection.dispose(); await this.#closeCallback?.call(null); } diff --git a/test/src/ariaqueryhandler.spec.ts b/test/src/ariaqueryhandler.spec.ts index e09aeeef781..8197e0bec6e 100644 --- a/test/src/ariaqueryhandler.spec.ts +++ b/test/src/ariaqueryhandler.spec.ts @@ -427,6 +427,9 @@ describe('AriaQueryHandler', () => { .waitForSelector('aria/inner', {visible: true}) .then(() => { return (divVisible = true); + }) + .catch(() => { + return (divVisible = false); }); await page.setContent( `
hi
` @@ -456,6 +459,9 @@ describe('AriaQueryHandler', () => { .waitForSelector('aria/[role="button"]', {hidden: true}) .then(() => { return (divHidden = true); + }) + .catch(() => { + return (divHidden = false); }); await page.waitForSelector('aria/[role="button"]'); // do a round trip expect(divHidden).toBe(false); @@ -479,6 +485,9 @@ describe('AriaQueryHandler', () => { .waitForSelector('aria/[role="main"]', {hidden: true}) .then(() => { return (divHidden = true); + }) + .catch(() => { + return (divHidden = false); }); await page.waitForSelector('aria/[role="main"]'); // do a round trip expect(divHidden).toBe(false); @@ -500,6 +509,9 @@ describe('AriaQueryHandler', () => { .waitForSelector('aria/[role="main"]', {hidden: true}) .then(() => { return (divRemoved = true); + }) + .catch(() => { + return (divRemoved = false); }); await page.waitForSelector('aria/[role="main"]'); // do a round trip expect(divRemoved).toBe(false); @@ -553,9 +565,14 @@ describe('AriaQueryHandler', () => { const {page} = getTestState(); let divFound = false; - const waitForSelector = page.waitForSelector('aria/zombo').then(() => { - return (divFound = true); - }); + const waitForSelector = page + .waitForSelector('aria/zombo') + .then(() => { + return (divFound = true); + }) + .catch(() => { + return (divFound = false); + }); await page.setContent(`
`); expect(divFound).toBe(false); await page.evaluate(() => { @@ -569,7 +586,9 @@ describe('AriaQueryHandler', () => { it('should return the element handle', async () => { const {page} = getTestState(); - const waitForSelector = page.waitForSelector('aria/zombo'); + const waitForSelector = page.waitForSelector('aria/zombo').catch(err => { + return err; + }); await page.setContent(`
anything
`); expect( await page.evaluate(x => { diff --git a/test/src/elementhandle.spec.ts b/test/src/elementhandle.spec.ts index aea0a9be889..e9291327ba9 100644 --- a/test/src/elementhandle.spec.ts +++ b/test/src/elementhandle.spec.ts @@ -389,23 +389,29 @@ describe('ElementHandle specs', function () { describe('Element.waitForSelector', () => { it('should wait correctly with waitForSelector on an element', async () => { const {page} = getTestState(); - const waitFor = page.waitForSelector('.foo') as Promise< - ElementHandle - >; + const waitFor = page.waitForSelector('.foo').catch(err => { + return err; + }) as Promise>; // Set the page content after the waitFor has been started. await page.setContent( '
bar2
Foo1
' ); let element = (await waitFor)!; + if (element instanceof Error) { + throw element; + } expect(element).toBeDefined(); - const innerWaitFor = element.waitForSelector('.bar') as Promise< - ElementHandle - >; + const innerWaitFor = element.waitForSelector('.bar').catch(err => { + return err; + }) as Promise>; await element.evaluate(el => { el.innerHTML = '
bar1
'; }); element = (await innerWaitFor)!; + if (element instanceof Error) { + throw element; + } expect(element).toBeDefined(); expect( await element.evaluate(el => { @@ -683,7 +689,9 @@ describe('ElementHandle specs', function () { return (element as Element).querySelector(`.${selector}`); }, }); - const waitFor = page.waitForSelector('getByClass/foo'); + const waitFor = page.waitForSelector('getByClass/foo').catch(err => { + return err; + }); // Set the page content after the waitFor has been started. await page.setContent( @@ -691,6 +699,10 @@ describe('ElementHandle specs', function () { ); const element = await waitFor; + if (element instanceof Error) { + throw element; + } + expect(element).toBeDefined(); }); @@ -701,26 +713,34 @@ describe('ElementHandle specs', function () { return (element as Element).querySelector(`.${selector}`); }, }); - const waitFor = page.waitForSelector('getByClass/foo') as Promise< - ElementHandle - >; + const waitFor = page.waitForSelector('getByClass/foo').catch(err => { + return err; + }) as Promise>; // Set the page content after the waitFor has been started. await page.setContent( '
bar2
Foo1
' ); let element = (await waitFor)!; + if (element instanceof Error) { + throw element; + } expect(element).toBeDefined(); - const innerWaitFor = element.waitForSelector('getByClass/bar') as Promise< - ElementHandle - >; + const innerWaitFor = element + .waitForSelector('getByClass/bar') + .catch(err => { + return err; + }) as Promise>; await element.evaluate(el => { el.innerHTML = '
bar1
'; }); element = (await innerWaitFor)!; + if (element instanceof Error) { + throw element; + } expect(element).toBeDefined(); expect( await element.evaluate(el => { @@ -738,7 +758,9 @@ describe('ElementHandle specs', function () { return (element as Element).querySelector(`.${selector}`); }, }); - const waitFor = page.waitForSelector('getByClass/foo'); + const waitFor = page.waitForSelector('getByClass/foo').catch(err => { + return err; + }); // Set the page content after the waitFor has been started. await page.setContent( @@ -746,6 +768,10 @@ describe('ElementHandle specs', function () { ); const element = await waitFor; + if (element instanceof Error) { + throw element; + } + expect(element).toBeDefined(); }); it('should work when both queryOne and queryAll are registered', async () => { diff --git a/test/src/locator.spec.ts b/test/src/locator.spec.ts index 698339156f0..a13df428ffc 100644 --- a/test/src/locator.spec.ts +++ b/test/src/locator.spec.ts @@ -150,7 +150,12 @@ describe('Locator', function () { `); const button = await page.$('button'); - const result = page.locator('button').click(); + const result = page + .locator('button') + .click() + .catch(err => { + return err; + }); expect( await button?.evaluate(el => { return el.innerText; @@ -159,7 +164,10 @@ describe('Locator', function () { await button?.evaluate(el => { el.style.display = 'block'; }); - await result; + const maybeError = await result; + if (maybeError instanceof Error) { + throw maybeError; + } expect( await button?.evaluate(el => { return el.innerText; diff --git a/test/src/utils.ts b/test/src/utils.ts index 1f1bf918181..e5d3014750c 100644 --- a/test/src/utils.ts +++ b/test/src/utils.ts @@ -20,6 +20,7 @@ import expect from 'expect'; import {Frame} from 'puppeteer-core/internal/api/Frame.js'; import {Page} from 'puppeteer-core/internal/api/Page.js'; import {EventEmitter} from 'puppeteer-core/internal/common/EventEmitter.js'; +import {Deferred} from 'puppeteer-core/internal/util/Deferred.js'; import {compare} from './golden-utils.js'; @@ -132,19 +133,27 @@ export const dumpFrames = (frame: Frame, indentation?: string): string[] => { return result; }; -export const waitEvent = ( +export const waitEvent = async ( emitter: EventEmitter, eventName: string, predicate: (event: T) => boolean = () => { return true; } ): Promise => { - return new Promise(fulfill => { - emitter.on(eventName, (event: T) => { - if (!predicate(event)) { - return; - } - fulfill(event); - }); + const deferred = Deferred.create({ + timeout: 5000, + message: 'Waiting for test event timed out.', }); + const handler = (event: T) => { + if (!predicate(event)) { + return; + } + deferred.resolve(event); + }; + emitter.on(eventName, handler); + try { + return await deferred.valueOrThrow(); + } finally { + emitter.off(eventName, handler); + } };