mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
chore: use internal method for networkidle with BiDi (#11167)
This commit is contained in:
parent
f0af79a1d6
commit
5278de9276
@ -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<PageEvents> {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected async _waitForNetworkIdle(
|
||||
_waitForNetworkIdle(
|
||||
networkManager: BidiNetworkManager | CdpNetworkManager,
|
||||
idleTime: number,
|
||||
ms: number,
|
||||
closedDeferred: Deferred<TargetCloseError>
|
||||
): Promise<void> {
|
||||
await firstValueFrom(
|
||||
merge(
|
||||
requestsInFlight = 0
|
||||
): Observable<void> {
|
||||
return merge(
|
||||
fromEvent(
|
||||
networkManager,
|
||||
NetworkManagerEvent.Request as unknown as string
|
||||
),
|
||||
) as Observable<void>,
|
||||
fromEvent(
|
||||
networkManager,
|
||||
NetworkManagerEvent.Response as unknown as string
|
||||
),
|
||||
) as Observable<void>,
|
||||
fromEvent(
|
||||
networkManager,
|
||||
NetworkManagerEvent.RequestFailed as unknown as string
|
||||
)
|
||||
) as Observable<void>
|
||||
).pipe(
|
||||
startWith(null),
|
||||
startWith(undefined),
|
||||
filter(() => {
|
||||
return networkManager.inFlightRequestsCount() === 0;
|
||||
return networkManager.inFlightRequestsCount() <= requestsInFlight;
|
||||
}),
|
||||
switchMap(v => {
|
||||
return of(v).pipe(delay(idleTime));
|
||||
}),
|
||||
raceWith(timeout(ms), from(closedDeferred.valueOrThrow()))
|
||||
)
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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<PuppeteerLifeCycleEvent, 'load' | 'domcontentloaded'> {
|
||||
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;
|
||||
}
|
||||
|
@ -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<BidiHTTPResponse | null> {
|
||||
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(
|
||||
this._waitWithNetworkIdle(
|
||||
forkJoin([
|
||||
fromEvent(this.#context, waitUntilEvent).pipe(first()),
|
||||
fromEvent(this.#context, waitEvent).pipe(first()),
|
||||
from(setPageContent(this, html)),
|
||||
]).pipe(raceWith(timeout(ms)))
|
||||
]).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<Bidi.BrowsingContext.NavigationInfo>;
|
||||
})
|
||||
),
|
||||
).pipe(first()),
|
||||
fromEvent(this.#context, waitUntilEvent).pipe(
|
||||
first()
|
||||
) as Observable<Bidi.BrowsingContext.NavigationInfo>,
|
||||
]),
|
||||
fromEvent(
|
||||
this.#context,
|
||||
Bidi.ChromiumBidi.BrowsingContext.EventNames.FragmentNavigated
|
||||
) as Observable<Bidi.BrowsingContext.NavigationInfo>
|
||||
).pipe(raceWith(timeout(ms), from(this.#abortDeferred.valueOrThrow())))
|
||||
).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;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ export class BidiNetworkManager extends EventEmitter<NetworkManagerEvents> {
|
||||
this.#requestMap.delete(event.request.request);
|
||||
}
|
||||
|
||||
getNavigationResponse(navigationId: string | null): BidiHTTPResponse | null {
|
||||
getNavigationResponse(navigationId?: string | null): BidiHTTPResponse | null {
|
||||
if (!navigationId) {
|
||||
return null;
|
||||
}
|
||||
|
@ -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<BidiFrame>();
|
||||
#networkManager: BidiNetworkManager;
|
||||
_networkManager: BidiNetworkManager;
|
||||
#viewport: Viewport | null = null;
|
||||
#closedDeferred = Deferred.create<never, TargetCloseError>();
|
||||
#subscribedEvents = new Map<Bidi.Event['method'], Handler<any>>([
|
||||
@ -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<BidiHTTPResponse | null> {
|
||||
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.mainFrame()
|
||||
._waitWithNetworkIdle(
|
||||
this.#connection.send('browsingContext.reload', {
|
||||
context: this.mainFrame()._id,
|
||||
wait: readinessState,
|
||||
wait: readiness,
|
||||
}),
|
||||
'Navigation',
|
||||
timeout
|
||||
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<BidiHTTPRequest> {
|
||||
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<BidiHTTPResponse> {
|
||||
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<void> {
|
||||
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()))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
120
packages/puppeteer-core/src/bidi/lifecycle.ts
Normal file
120
packages/puppeteer-core/src/bidi/lifecycle.ts
Normal file
@ -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<PuppeteerLifeCycleEvent, 'load' | 'domcontentloaded'>,
|
||||
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<T, R extends ObservableInput<T>>(
|
||||
message: string,
|
||||
ms: number
|
||||
): OperatorFunction<T, T | ObservedValueOf<R>> {
|
||||
return catchError<T, R>(error => {
|
||||
if (error instanceof ProtocolError) {
|
||||
error.message += ` at ${message}`;
|
||||
} else if (error instanceof TimeoutError) {
|
||||
error.message = `Navigation timeout of ${ms} ms exceeded`;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
}
|
@ -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<void> {
|
||||
const {idleTime = 500, timeout = this._timeoutSettings.timeout()} = options;
|
||||
const {
|
||||
idleTime = NETWORK_IDLE_TIME,
|
||||
timeout: ms = this._timeoutSettings.timeout(),
|
||||
} = options;
|
||||
|
||||
await this._waitForNetworkIdle(
|
||||
await firstValueFrom(
|
||||
this._waitForNetworkIdle(
|
||||
this.#frameManager.networkManager,
|
||||
idleTime,
|
||||
timeout,
|
||||
this.#sessionCloseDeferred
|
||||
idleTime
|
||||
).pipe(
|
||||
raceWith(timeout(ms), from(this.#sessionCloseDeferred.valueOrThrow()))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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<T>(
|
||||
promise: Promise<T>,
|
||||
taskName: string,
|
||||
timeout: number
|
||||
): Promise<T> {
|
||||
const deferred = Deferred.create<never>({
|
||||
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<T extends {url(): string}>(
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const NETWORK_IDLE_TIME = 500;
|
||||
|
@ -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<T>(
|
||||
|
@ -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",
|
||||
|
@ -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',
|
||||
]);
|
||||
|
@ -21,6 +21,9 @@
|
||||
"output": [
|
||||
"bin/**",
|
||||
"tsconfig.tsbuildinfo"
|
||||
],
|
||||
"dependencies": [
|
||||
"../../packages/puppeteer-core:build"
|
||||
]
|
||||
},
|
||||
"test": {
|
||||
|
Loading…
Reference in New Issue
Block a user