diff --git a/packages/puppeteer-core/src/api/Page.ts b/packages/puppeteer-core/src/api/Page.ts index 6c3e85b858c..e1193029f5b 100644 --- a/packages/puppeteer-core/src/api/Page.ts +++ b/packages/puppeteer-core/src/api/Page.ts @@ -84,7 +84,6 @@ import type {Viewport} from '../common/Viewport.js'; import type {ScreenRecorder} from '../node/ScreenRecorder.js'; import {assert} from '../util/assert.js'; import {guarded} from '../util/decorators.js'; -import type {Deferred} from '../util/Deferred.js'; import { AsyncDisposableStack, asyncDisposeSymbol, @@ -1741,36 +1740,32 @@ export abstract class Page extends EventEmitter { /** * @internal */ - protected async _waitForNetworkIdle( + _waitForNetworkIdle( networkManager: BidiNetworkManager | CdpNetworkManager, idleTime: number, - ms: number, - closedDeferred: Deferred - ): Promise { - await firstValueFrom( - merge( - fromEvent( - networkManager, - NetworkManagerEvent.Request as unknown as string - ), - fromEvent( - networkManager, - NetworkManagerEvent.Response as unknown as string - ), - fromEvent( - networkManager, - NetworkManagerEvent.RequestFailed as unknown as string - ) - ).pipe( - startWith(null), - filter(() => { - return networkManager.inFlightRequestsCount() === 0; - }), - switchMap(v => { - return of(v).pipe(delay(idleTime)); - }), - raceWith(timeout(ms), from(closedDeferred.valueOrThrow())) - ) + requestsInFlight = 0 + ): Observable { + return merge( + fromEvent( + networkManager, + NetworkManagerEvent.Request as unknown as string + ) as Observable, + fromEvent( + networkManager, + NetworkManagerEvent.Response as unknown as string + ) as Observable, + fromEvent( + networkManager, + NetworkManagerEvent.RequestFailed as unknown as string + ) as Observable + ).pipe( + startWith(undefined), + filter(() => { + return networkManager.inFlightRequestsCount() <= requestsInFlight; + }), + switchMap(v => { + return of(v).pipe(delay(idleTime)); + }) ); } diff --git a/packages/puppeteer-core/src/bidi/BrowsingContext.ts b/packages/puppeteer-core/src/bidi/BrowsingContext.ts index e27ecb92888..e4367aba58b 100644 --- a/packages/puppeteer-core/src/bidi/BrowsingContext.ts +++ b/packages/puppeteer-core/src/bidi/BrowsingContext.ts @@ -3,27 +3,14 @@ import type ProtocolMapping from 'devtools-protocol/types/protocol-mapping.js'; import {CDPSession} from '../api/CDPSession.js'; import type {Connection as CdpConnection} from '../cdp/Connection.js'; -import type {PuppeteerLifeCycleEvent} from '../cdp/LifecycleWatcher.js'; import {TargetCloseError} from '../common/Errors.js'; import type {EventType} from '../common/EventEmitter.js'; import {debugError} from '../common/util.js'; -import {assert} from '../util/assert.js'; import {Deferred} from '../util/Deferred.js'; import type {BidiConnection} from './Connection.js'; import {BidiRealm} from './Realm.js'; -/** - * @internal - */ -export const lifeCycleToSubscribedEvent = new Map< - PuppeteerLifeCycleEvent, - string ->([ - ['load', 'browsingContext.load'], - ['domcontentloaded', 'browsingContext.domContentLoaded'], -]); - /** * @internal */ @@ -198,30 +185,3 @@ export class BrowsingContext extends BidiRealm { void this.#cdpSession.detach().catch(debugError); } } - -/** - * @internal - */ -export function getWaitUntilSingle( - event: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[] -): Extract { - if (Array.isArray(event) && event.length > 1) { - throw new Error('BiDi support only single `waitUntil` argument'); - } - const waitUntilSingle = Array.isArray(event) - ? (event.find(lifecycle => { - return lifecycle === 'domcontentloaded' || lifecycle === 'load'; - }) as PuppeteerLifeCycleEvent) - : event; - - if ( - waitUntilSingle === 'networkidle0' || - waitUntilSingle === 'networkidle2' - ) { - throw new Error(`BiDi does not support 'waitUntil' ${waitUntilSingle}`); - } - - assert(waitUntilSingle, `Invalid waitUntil option ${waitUntilSingle}`); - - return waitUntilSingle; -} diff --git a/packages/puppeteer-core/src/bidi/Frame.ts b/packages/puppeteer-core/src/bidi/Frame.ts index 64085d6416d..72e0ded08af 100644 --- a/packages/puppeteer-core/src/bidi/Frame.ts +++ b/packages/puppeteer-core/src/bidi/Frame.ts @@ -16,16 +16,17 @@ import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js'; +import type {ObservableInput} from '../../third_party/rxjs/rxjs.js'; import { type Observable, - firstValueFrom, from, fromEvent, merge, - raceWith, - switchMap, + map, forkJoin, first, + firstValueFrom, + raceWith, } from '../../third_party/rxjs/rxjs.js'; import type {CDPSession} from '../api/CDPSession.js'; import { @@ -34,26 +35,26 @@ import { type WaitForOptions, throwIfDetached, } from '../api/Frame.js'; -import type {PuppeteerLifeCycleEvent} from '../cdp/LifecycleWatcher.js'; -import {ProtocolError, TimeoutError} from '../common/Errors.js'; import type {TimeoutSettings} from '../common/TimeoutSettings.js'; import type {Awaitable} from '../common/types.js'; import { + NETWORK_IDLE_TIME, UTILITY_WORLD_NAME, setPageContent, - waitWithTimeout, + timeout, } from '../common/util.js'; -import {timeout} from '../common/util.js'; import {Deferred} from '../util/Deferred.js'; import {disposeSymbol} from '../util/disposable.js'; -import { - getWaitUntilSingle, - lifeCycleToSubscribedEvent, - type BrowsingContext, -} from './BrowsingContext.js'; +import type {BrowsingContext} from './BrowsingContext.js'; import {ExposeableFunction} from './ExposedFunction.js'; import type {BidiHTTPResponse} from './HTTPResponse.js'; +import type {BiDiNetworkIdle} from './lifecycle.js'; +import { + getBiDiLifecycleEvent, + getBiDiReadinessState, + rewriteNavigationError, +} from './lifecycle.js'; import type {BidiPage} from './Page.js'; import { MAIN_SANDBOX, @@ -62,17 +63,6 @@ import { type SandboxChart, } from './Sandbox.js'; -/** - * @internal - */ -export const lifeCycleToReadinessState = new Map< - PuppeteerLifeCycleEvent, - Bidi.BrowsingContext.ReadinessState ->([ - ['load', Bidi.BrowsingContext.ReadinessState.Complete], - ['domcontentloaded', Bidi.BrowsingContext.ReadinessState.Interactive], -]); - /** * Puppeteer's Frame class could be viewed as a BiDi BrowsingContext implementation * @internal @@ -145,33 +135,25 @@ export class BidiFrame extends Frame { ): Promise { const { waitUntil = 'load', - timeout = this.#timeoutSettings.navigationTimeout(), + timeout: ms = this.#timeoutSettings.navigationTimeout(), } = options; - const readinessState = lifeCycleToReadinessState.get( - getWaitUntilSingle(waitUntil) - ) as Bidi.BrowsingContext.ReadinessState; + const [readiness, networkIdle] = getBiDiReadinessState(waitUntil); - try { - const {result} = await waitWithTimeout( + const response = await firstValueFrom( + this._waitWithNetworkIdle( this.#context.connection.send('browsingContext.navigate', { - url: url, - context: this._id, - wait: readinessState, + context: this.#context.id, + url, + wait: readiness, }), - 'Navigation', - timeout - ); + networkIdle + ) + .pipe(raceWith(timeout(ms), from(this.#abortDeferred.valueOrThrow()))) + .pipe(rewriteNavigationError(url, ms)) + ); - return this.#page.getNavigationResponse(result.navigation); - } catch (error) { - if (error instanceof ProtocolError) { - error.message += ` at ${url}`; - } else if (error instanceof TimeoutError) { - error.message = 'Navigation timeout of ' + timeout + ' ms exceeded'; - } - throw error; - } + return this.#page.getNavigationResponse(response?.result.navigation); } @throwIfDetached @@ -184,15 +166,22 @@ export class BidiFrame extends Frame { timeout: ms = this.#timeoutSettings.navigationTimeout(), } = options; - const waitUntilEvent = lifeCycleToSubscribedEvent.get( - getWaitUntilSingle(waitUntil) - ) as string; + const [waitEvent, networkIdle] = getBiDiLifecycleEvent(waitUntil); await firstValueFrom( - forkJoin([ - fromEvent(this.#context, waitUntilEvent).pipe(first()), - from(setPageContent(this, html)), - ]).pipe(raceWith(timeout(ms))) + this._waitWithNetworkIdle( + forkJoin([ + fromEvent(this.#context, waitEvent).pipe(first()), + from(setPageContent(this, html)), + ]).pipe( + map(() => { + return null; + }) + ), + networkIdle + ) + .pipe(raceWith(timeout(ms), from(this.#abortDeferred.valueOrThrow()))) + .pipe(rewriteNavigationError('setContent', ms)) ); } @@ -209,31 +198,38 @@ export class BidiFrame extends Frame { timeout: ms = this.#timeoutSettings.navigationTimeout(), } = options; - const waitUntilEvent = lifeCycleToSubscribedEvent.get( - getWaitUntilSingle(waitUntil) - ) as string; + const [waitUntilEvent, networkIdle] = getBiDiLifecycleEvent(waitUntil); - const info = await firstValueFrom( - merge( + const navigatedObservable = merge( + forkJoin([ fromEvent( this.#context, Bidi.ChromiumBidi.BrowsingContext.EventNames.NavigationStarted - ).pipe( - switchMap(() => { - return fromEvent( - this.#context, - waitUntilEvent - ) as Observable; - }) - ), - fromEvent( - this.#context, - Bidi.ChromiumBidi.BrowsingContext.EventNames.FragmentNavigated - ) as Observable - ).pipe(raceWith(timeout(ms), from(this.#abortDeferred.valueOrThrow()))) + ).pipe(first()), + fromEvent(this.#context, waitUntilEvent).pipe( + first() + ) as Observable, + ]), + fromEvent( + this.#context, + Bidi.ChromiumBidi.BrowsingContext.EventNames.FragmentNavigated + ) as Observable + ).pipe( + map(result => { + if (Array.isArray(result)) { + return {result: result[1]}; + } + return {result}; + }) ); - return this.#page.getNavigationResponse(info.navigation); + const response = await firstValueFrom( + this._waitWithNetworkIdle(navigatedObservable, networkIdle).pipe( + raceWith(timeout(ms), from(this.#abortDeferred.valueOrThrow())) + ) + ); + + return this.#page.getNavigationResponse(response?.result.navigation); } override get detached(): boolean { @@ -270,4 +266,31 @@ export class BidiFrame extends Frame { throw error; } } + + /** @internal */ + _waitWithNetworkIdle( + observableInput: ObservableInput<{ + result: Bidi.BrowsingContext.NavigateResult; + } | null>, + networkIdle: BiDiNetworkIdle + ): Observable<{ + result: Bidi.BrowsingContext.NavigateResult; + } | null> { + const delay = networkIdle + ? this.#page._waitForNetworkIdle( + this.#page._networkManager, + NETWORK_IDLE_TIME, + networkIdle === 'networkidle0' ? 0 : 2 + ) + : from(Promise.resolve()); + + return forkJoin([ + from(observableInput).pipe(first()), + delay.pipe(first()), + ]).pipe( + map(([response]) => { + return response; + }) + ); + } } diff --git a/packages/puppeteer-core/src/bidi/NetworkManager.ts b/packages/puppeteer-core/src/bidi/NetworkManager.ts index 8f5103adc7a..982657ac6f4 100644 --- a/packages/puppeteer-core/src/bidi/NetworkManager.ts +++ b/packages/puppeteer-core/src/bidi/NetworkManager.ts @@ -122,7 +122,7 @@ export class BidiNetworkManager extends EventEmitter { this.#requestMap.delete(event.request.request); } - getNavigationResponse(navigationId: string | null): BidiHTTPResponse | null { + getNavigationResponse(navigationId?: string | null): BidiHTTPResponse | null { if (!navigationId) { return null; } diff --git a/packages/puppeteer-core/src/bidi/Page.ts b/packages/puppeteer-core/src/bidi/Page.ts index 8bf1e4f2657..6b9d11fccca 100644 --- a/packages/puppeteer-core/src/bidi/Page.ts +++ b/packages/puppeteer-core/src/bidi/Page.ts @@ -39,11 +39,7 @@ import { ConsoleMessage, type ConsoleMessageLocation, } from '../common/ConsoleMessage.js'; -import { - ProtocolError, - TargetCloseError, - TimeoutError, -} from '../common/Errors.js'; +import {TargetCloseError} from '../common/Errors.js'; import type {Handler} from '../common/EventEmitter.js'; import {NetworkManagerEvent} from '../common/NetworkManagerEvents.js'; import type {PDFOptions} from '../common/PDFOptions.js'; @@ -51,10 +47,10 @@ import type {Awaitable} from '../common/types.js'; import { debugError, evaluationString, + NETWORK_IDLE_TIME, timeout, validateDialogType, waitForHTTP, - waitWithTimeout, } from '../common/util.js'; import type {Viewport} from '../common/Viewport.js'; import {assert} from '../util/assert.js'; @@ -66,7 +62,6 @@ import type {BidiBrowserContext} from './BrowserContext.js'; import { BrowsingContextEvent, CdpSessionWrapper, - getWaitUntilSingle, type BrowsingContext, } from './BrowsingContext.js'; import type {BidiConnection} from './Connection.js'; @@ -74,11 +69,12 @@ import {BidiDeserializer} from './Deserializer.js'; import {BidiDialog} from './Dialog.js'; import {BidiElementHandle} from './ElementHandle.js'; import {EmulationManager} from './EmulationManager.js'; -import {BidiFrame, lifeCycleToReadinessState} from './Frame.js'; +import {BidiFrame} from './Frame.js'; import type {BidiHTTPRequest} from './HTTPRequest.js'; import type {BidiHTTPResponse} from './HTTPResponse.js'; import {BidiKeyboard, BidiMouse, BidiTouchscreen} from './Input.js'; import type {BidiJSHandle} from './JSHandle.js'; +import {getBiDiReadinessState, rewriteNavigationError} from './lifecycle.js'; import {BidiNetworkManager} from './NetworkManager.js'; import {createBidiHandle} from './Realm.js'; @@ -89,7 +85,7 @@ export class BidiPage extends Page { #accessibility: Accessibility; #connection: BidiConnection; #frameTree = new FrameTree(); - #networkManager: BidiNetworkManager; + _networkManager: BidiNetworkManager; #viewport: Viewport | null = null; #closedDeferred = Deferred.create(); #subscribedEvents = new Map>([ @@ -169,7 +165,7 @@ export class BidiPage extends Page { this.#browsingContext.on(event, subscriber); } - this.#networkManager = new BidiNetworkManager(this.#connection, this); + this._networkManager = new BidiNetworkManager(this.#connection, this); for (const [event, subscriber] of this.#subscribedEvents) { this.#connection.on(event, subscriber); @@ -177,7 +173,7 @@ export class BidiPage extends Page { for (const [event, subscriber] of this.#networkManagerEvents) { // TODO: remove any - this.#networkManager.on(event, subscriber as any); + this._networkManager.on(event, subscriber as any); } const frame = new BidiFrame( @@ -381,7 +377,7 @@ export class BidiPage extends Page { this.#removeFramesRecursively(child); } frame[disposeSymbol](); - this.#networkManager.clearMapAfterFrameDispose(frame); + this._networkManager.clearMapAfterFrameDispose(frame); this.#frameTree.removeFrame(frame); this.emit(PageEvent.FrameDetached, frame); } @@ -460,8 +456,8 @@ export class BidiPage extends Page { this.emit(PageEvent.Dialog, dialog); } - getNavigationResponse(id: string | null): BidiHTTPResponse | null { - return this.#networkManager.getNavigationResponse(id); + getNavigationResponse(id?: string | null): BidiHTTPResponse | null { + return this._networkManager.getNavigationResponse(id); } override isClosed(): boolean { @@ -474,7 +470,7 @@ export class BidiPage extends Page { } this.#closedDeferred.reject(new TargetCloseError('Page closed!')); - this.#networkManager.dispose(); + this._networkManager.dispose(); await this.#connection.send('browsingContext.close', { context: this.mainFrame()._id, @@ -489,32 +485,25 @@ export class BidiPage extends Page { ): Promise { const { waitUntil = 'load', - timeout = this._timeoutSettings.navigationTimeout(), + timeout: ms = this._timeoutSettings.navigationTimeout(), } = options; - const readinessState = lifeCycleToReadinessState.get( - getWaitUntilSingle(waitUntil) - ) as Bidi.BrowsingContext.ReadinessState; + const [readiness, networkIdle] = getBiDiReadinessState(waitUntil); - try { - const {result} = await waitWithTimeout( - this.#connection.send('browsingContext.reload', { - context: this.mainFrame()._id, - wait: readinessState, - }), - 'Navigation', - timeout - ); + const response = await firstValueFrom( + this.mainFrame() + ._waitWithNetworkIdle( + this.#connection.send('browsingContext.reload', { + context: this.mainFrame()._id, + wait: readiness, + }), + networkIdle + ) + .pipe(raceWith(timeout(ms), from(this.#closedDeferred.valueOrThrow()))) + .pipe(rewriteNavigationError(this.url(), ms)) + ); - return this.getNavigationResponse(result.navigation); - } catch (error) { - if (error instanceof ProtocolError) { - error.message += ` at ${this.url}`; - } else if (error instanceof TimeoutError) { - error.message = 'Navigation timeout of ' + timeout + ' ms exceeded'; - } - throw error; - } + return this.getNavigationResponse(response?.result.navigation); } override setDefaultNavigationTimeout(timeout: number): void { @@ -692,7 +681,7 @@ export class BidiPage extends Page { ): Promise { const {timeout = this._timeoutSettings.timeout()} = options; return await waitForHTTP( - this.#networkManager, + this._networkManager, NetworkManagerEvent.Request, urlOrPredicate, timeout, @@ -708,7 +697,7 @@ export class BidiPage extends Page { ): Promise { const {timeout = this._timeoutSettings.timeout()} = options; return await waitForHTTP( - this.#networkManager, + this._networkManager, NetworkManagerEvent.Response, urlOrPredicate, timeout, @@ -719,13 +708,15 @@ export class BidiPage extends Page { override async waitForNetworkIdle( options: {idleTime?: number; timeout?: number} = {} ): Promise { - const {idleTime = 500, timeout = this._timeoutSettings.timeout()} = options; + const { + idleTime = NETWORK_IDLE_TIME, + timeout: ms = this._timeoutSettings.timeout(), + } = options; - await this._waitForNetworkIdle( - this.#networkManager, - idleTime, - timeout, - this.#closedDeferred + await firstValueFrom( + this._waitForNetworkIdle(this._networkManager, idleTime).pipe( + raceWith(timeout(ms), from(this.#closedDeferred.valueOrThrow())) + ) ); } diff --git a/packages/puppeteer-core/src/bidi/lifecycle.ts b/packages/puppeteer-core/src/bidi/lifecycle.ts new file mode 100644 index 00000000000..5bb52b08e7f --- /dev/null +++ b/packages/puppeteer-core/src/bidi/lifecycle.ts @@ -0,0 +1,120 @@ +/** + * Copyright 2023 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js'; + +import type { + ObservableInput, + ObservedValueOf, + OperatorFunction, +} from '../../third_party/rxjs/rxjs'; +import {catchError} from '../../third_party/rxjs/rxjs'; +import type {PuppeteerLifeCycleEvent} from '../cdp/LifecycleWatcher'; +import {ProtocolError, TimeoutError} from '../common/Errors'; + +export type BiDiNetworkIdle = Extract< + PuppeteerLifeCycleEvent, + 'networkidle0' | 'networkidle2' +> | null; + +/** + * @internal + */ +export function getBiDiLifeCycles( + event: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[] +): [ + Extract, + BiDiNetworkIdle, +] { + if (Array.isArray(event)) { + const pageLifeCycle = event.some(lifeCycle => { + return lifeCycle !== 'domcontentloaded'; + }) + ? 'load' + : 'domcontentloaded'; + + const networkLifeCycle = event.reduce((acc, lifeCycle) => { + if (lifeCycle === 'networkidle0') { + return lifeCycle; + } else if (acc !== 'networkidle0' && lifeCycle === 'networkidle2') { + return lifeCycle; + } + return acc; + }, null as BiDiNetworkIdle); + + return [pageLifeCycle, networkLifeCycle]; + } + + if (event === 'networkidle0' || event === 'networkidle2') { + return ['load', event]; + } + + return [event, null]; +} + +/** + * @internal + */ +export const lifeCycleToReadinessState = new Map< + PuppeteerLifeCycleEvent, + Bidi.BrowsingContext.ReadinessState +>([ + ['load', Bidi.BrowsingContext.ReadinessState.Complete], + ['domcontentloaded', Bidi.BrowsingContext.ReadinessState.Interactive], +]); + +export function getBiDiReadinessState( + event: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[] +): [Bidi.BrowsingContext.ReadinessState, BiDiNetworkIdle] { + const lifeCycles = getBiDiLifeCycles(event); + const readiness = lifeCycleToReadinessState.get(lifeCycles[0])!; + return [readiness, lifeCycles[1]]; +} + +/** + * @internal + */ +export const lifeCycleToSubscribedEvent = new Map< + PuppeteerLifeCycleEvent, + 'browsingContext.load' | 'browsingContext.domContentLoaded' +>([ + ['load', 'browsingContext.load'], + ['domcontentloaded', 'browsingContext.domContentLoaded'], +]); + +export function getBiDiLifecycleEvent( + event: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[] +): [ + 'browsingContext.load' | 'browsingContext.domContentLoaded', + BiDiNetworkIdle, +] { + const lifeCycles = getBiDiLifeCycles(event); + const bidiEvent = lifeCycleToSubscribedEvent.get(lifeCycles[0])!; + return [bidiEvent, lifeCycles[1]]; +} + +export function rewriteNavigationError>( + message: string, + ms: number +): OperatorFunction> { + return catchError(error => { + if (error instanceof ProtocolError) { + error.message += ` at ${message}`; + } else if (error instanceof TimeoutError) { + error.message = `Navigation timeout of ${ms} ms exceeded`; + } + throw error; + }); +} diff --git a/packages/puppeteer-core/src/cdp/Page.ts b/packages/puppeteer-core/src/cdp/Page.ts index e29f24a9bd4..8f023bb5bf8 100644 --- a/packages/puppeteer-core/src/cdp/Page.ts +++ b/packages/puppeteer-core/src/cdp/Page.ts @@ -53,6 +53,7 @@ import { evaluationString, getReadableAsBuffer, getReadableFromProtocolStream, + NETWORK_IDLE_TIME, pageBindingInitString, timeout, validateDialogType, @@ -936,13 +937,18 @@ export class CdpPage extends Page { override async waitForNetworkIdle( options: {idleTime?: number; timeout?: number} = {} ): Promise { - const {idleTime = 500, timeout = this._timeoutSettings.timeout()} = options; + const { + idleTime = NETWORK_IDLE_TIME, + timeout: ms = this._timeoutSettings.timeout(), + } = options; - await this._waitForNetworkIdle( - this.#frameManager.networkManager, - idleTime, - timeout, - this.#sessionCloseDeferred + await firstValueFrom( + this._waitForNetworkIdle( + this.#frameManager.networkManager, + idleTime + ).pipe( + raceWith(timeout(ms), from(this.#sessionCloseDeferred.valueOrThrow())) + ) ); } diff --git a/packages/puppeteer-core/src/common/util.ts b/packages/puppeteer-core/src/common/util.ts index a85286af9a5..f6cca6059f0 100644 --- a/packages/puppeteer-core/src/common/util.ts +++ b/packages/puppeteer-core/src/common/util.ts @@ -34,7 +34,7 @@ import type {CDPSession} from '../api/CDPSession.js'; import type {Page} from '../api/Page.js'; import {isNode} from '../environment.js'; import {assert} from '../util/assert.js'; -import {Deferred} from '../util/Deferred.js'; +import type {Deferred} from '../util/Deferred.js'; import {isErrorLike} from '../util/ErrorLike.js'; import {debug} from './Debug.js'; @@ -409,22 +409,6 @@ export function pageBindingInitString(type: string, name: string): string { return evaluationString(addPageBinding, type, name); } -/** - * @internal - */ -export async function waitWithTimeout( - promise: Promise, - taskName: string, - timeout: number -): Promise { - const deferred = Deferred.create({ - message: `waiting for ${taskName} failed: timeout ${timeout}ms exceeded`, - timeout, - }); - - return await Deferred.race([promise, deferred]); -} - /** * @internal */ @@ -633,3 +617,8 @@ export async function waitForHTTP( ) ); } + +/** + * @internal + */ +export const NETWORK_IDLE_TIME = 500; diff --git a/packages/puppeteer-core/third_party/rxjs/rxjs.ts b/packages/puppeteer-core/third_party/rxjs/rxjs.ts index 503f1044820..c8b693e4f65 100644 --- a/packages/puppeteer-core/third_party/rxjs/rxjs.ts +++ b/packages/puppeteer-core/third_party/rxjs/rxjs.ts @@ -24,6 +24,7 @@ export { filter, first, firstValueFrom, + forkJoin, from, fromEvent, identity, @@ -34,9 +35,7 @@ export { mergeMap, NEVER, noop, - Observable, of, - OperatorFunction, pipe, race, raceWith, @@ -47,9 +46,10 @@ export { tap, throwIfEmpty, timer, - forkJoin, } from 'rxjs'; +export type * from 'rxjs'; + import {filter, from, map, mergeMap, type Observable} from 'rxjs'; export function filterAsync( diff --git a/test/TestExpectations.json b/test/TestExpectations.json index e3114efec46..467a45413d7 100644 --- a/test/TestExpectations.json +++ b/test/TestExpectations.json @@ -155,18 +155,6 @@ "parameters": ["webDriverBiDi"], "expectations": ["PASS"] }, - { - "testIdPattern": "[navigation.spec] navigation Frame.goto *", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] - }, - { - "testIdPattern": "[navigation.spec] navigation Frame.waitForNavigation *", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["PASS"] - }, { "testIdPattern": "[navigation.spec] navigation Page.goBack *", "platforms": ["darwin", "linux", "win32"], @@ -195,7 +183,7 @@ "testIdPattern": "[network.spec] network Page.setBypassServiceWorker *", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] + "expectations": ["FAIL", "TIMEOUT"] }, { "testIdPattern": "[network.spec] network Page.setBypassServiceWorker *", @@ -792,10 +780,10 @@ "expectations": ["FAIL"] }, { - "testIdPattern": "[navigation.spec] navigation Frame.goto should reject when frame detaches", + "testIdPattern": "[navigation.spec] navigation Frame.goto should return matching responses", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], - "expectations": ["PASS"] + "expectations": ["FAIL"] }, { "testIdPattern": "[navigation.spec] navigation Page.goto should fail when navigating to bad SSL", @@ -803,42 +791,12 @@ "parameters": ["firefox"], "expectations": ["FAIL"] }, - { - "testIdPattern": "[navigation.spec] navigation Page.goto should navigate to empty page with networkidle0", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] - }, - { - "testIdPattern": "[navigation.spec] navigation Page.goto should navigate to empty page with networkidle2", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] - }, - { - "testIdPattern": "[navigation.spec] navigation Page.goto should navigate to page with iframe and networkidle0", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] - }, { "testIdPattern": "[navigation.spec] navigation Page.goto should send referer", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], "expectations": ["FAIL"] }, - { - "testIdPattern": "[navigation.spec] navigation Page.goto should wait for network idle to succeed navigation", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] - }, - { - "testIdPattern": "[navigation.spec] navigation Page.waitForNavigation *", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["PASS"] - }, { "testIdPattern": "[network.spec] network Network Events Page.Events.RequestFinished", "platforms": ["darwin", "linux", "win32"], @@ -897,7 +855,7 @@ "testIdPattern": "[network.spec] network Response.fromServiceWorker Response.fromServiceWorker", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] + "expectations": ["FAIL", "TIMEOUT"] }, { "testIdPattern": "[network.spec] network Response.timing returns timing information", @@ -2336,8 +2294,8 @@ { "testIdPattern": "[navigation.spec] navigation Frame.goto should navigate subframes", "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["PASS"] + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"] }, { "testIdPattern": "[navigation.spec] navigation Frame.goto should reject when frame detaches", diff --git a/test/src/navigation.spec.ts b/test/src/navigation.spec.ts index bbabbb5c758..d2fdaa50482 100644 --- a/test/src/navigation.spec.ts +++ b/test/src/navigation.spec.ts @@ -817,6 +817,7 @@ describe('navigation', function () { const error = await navigationPromise; expect(error.message).atLeastOneToContain([ 'Navigating frame was detached', + 'Frame detached', 'Error: NS_BINDING_ABORTED', 'net::ERR_ABORTED', ]); diff --git a/tools/mocha-runner/package.json b/tools/mocha-runner/package.json index 11a532daf21..30037868085 100644 --- a/tools/mocha-runner/package.json +++ b/tools/mocha-runner/package.json @@ -21,6 +21,9 @@ "output": [ "bin/**", "tsconfig.tsbuildinfo" + ], + "dependencies": [ + "../../packages/puppeteer-core:build" ] }, "test": {