diff --git a/.browser b/.browser index 3486beaf41..03c32ff200 100644 --- a/.browser +++ b/.browser @@ -1 +1 @@ -chrome@133.0.6857.0 \ No newline at end of file +chrome@133.0.6901.0 \ No newline at end of file diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 582db2fa7f..78e7f271d7 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.10.2" + ".": "0.11.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 9636d4470d..ec973eed78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ All notable changes to this project will be documented in this file. +## [0.11.0](https://github.com/GoogleChromeLabs/chromium-bidi/compare/chromium-bidi-v0.10.2...chromium-bidi-v0.11.0) (2024-12-17) + + +### ⚠ BREAKING CHANGES + +* align navigation started with the spec + +### Features + +* abort navigation only after frame navigated ([#2898](https://github.com/GoogleChromeLabs/chromium-bidi/issues/2898)) ([6c3d406](https://github.com/GoogleChromeLabs/chromium-bidi/commit/6c3d406296ad70ca51eca5dd7ec92b8e62843c95)) + + +### Bug Fixes + +* align navigation started with the spec ([960531f](https://github.com/GoogleChromeLabs/chromium-bidi/commit/960531f4663a74de0dc5623889357d5c80172dbf)) + ## [0.10.2](https://github.com/GoogleChromeLabs/chromium-bidi/compare/chromium-bidi-v0.10.1...chromium-bidi-v0.10.2) (2024-12-11) diff --git a/package-lock.json b/package-lock.json index df3b8068fd..d581c11836 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "chromium-bidi", - "version": "0.10.2", + "version": "0.11.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "chromium-bidi", - "version": "0.10.2", + "version": "0.11.0", "license": "Apache-2.0", "dependencies": { "mitt": "3.0.1", @@ -34,7 +34,7 @@ "chai": "4.5.0", "chai-as-promised": "7.1.2", "debug": "4.4.0", - "devtools-protocol": "0.0.1387316", + "devtools-protocol": "0.0.1396320", "eslint": "9.12.0", "eslint-config-prettier": "9.1.0", "eslint-import-resolver-typescript": "3.7.0", @@ -2979,9 +2979,9 @@ } }, "node_modules/devtools-protocol": { - "version": "0.0.1387316", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1387316.tgz", - "integrity": "sha512-W4OoKnrGj0Vpa26C41xZr+PkGTFnJS1s8vuIuuvMPTV6zi0vWWyvM8jDAs6X+a4ig311oHpdaTNlJjWAl0WZTA==", + "version": "0.0.1396320", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1396320.tgz", + "integrity": "sha512-QQoDyeDxHmexf/ZwBw3Q/rUUZ7ntvBElUr7S/72lD+rWnvr25fjOdvSSmgyVEdTg6kXwUYc+UJvboO/qHPiJHA==", "dev": true, "license": "BSD-3-Clause" }, diff --git a/package.json b/package.json index febd3162a6..5cf771e05f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "chromium-bidi", - "version": "0.10.2", + "version": "0.11.0", "description": "An implementation of the WebDriver BiDi protocol for Chromium implemented as a JavaScript layer translating between BiDi and CDP, running inside a Chrome tab.", "scripts": { "build": "wireit", @@ -181,7 +181,7 @@ "chai": "4.5.0", "chai-as-promised": "7.1.2", "debug": "4.4.0", - "devtools-protocol": "0.0.1387316", + "devtools-protocol": "0.0.1396320", "eslint": "9.12.0", "eslint-config-prettier": "9.1.0", "eslint-import-resolver-typescript": "3.7.0", diff --git a/src/bidiMapper/modules/context/BrowsingContextImpl.ts b/src/bidiMapper/modules/context/BrowsingContextImpl.ts index 34629255b6..9620df1b6a 100644 --- a/src/bidiMapper/modules/context/BrowsingContextImpl.ts +++ b/src/bidiMapper/modules/context/BrowsingContextImpl.ts @@ -43,7 +43,11 @@ import {WindowRealm} from '../script/WindowRealm.js'; import type {EventManager} from '../session/EventManager.js'; import type {BrowsingContextStorage} from './BrowsingContextStorage.js'; -import {NavigationTracker} from './NavigationTracker.js'; +import { + NavigationEventName, + NavigationResult, + NavigationTracker, +} from './NavigationTracker.js'; export class BrowsingContextImpl { static readonly LOGGER_PREFIX = `${LogType.debug}:browsingContext` as const; @@ -106,7 +110,12 @@ export class BrowsingContextImpl { this.#logger = logger; this.#originalOpener = originalOpener; - this.#navigationTracker = new NavigationTracker(url, id, eventManager); + this.#navigationTracker = new NavigationTracker( + url, + id, + eventManager, + logger, + ); } static create( @@ -377,18 +386,32 @@ export class BrowsingContextImpl { } #initListeners() { + this.#cdpTarget.cdpClient.on('Network.loadingFailed', (params) => { + // Detect navigation errors like `net::ERR_BLOCKED_BY_RESPONSE`. + // Network related to navigation has request id equals to navigation's loader id. + this.#navigationTracker.networkLoadingFailed( + params.requestId, + params.errorText, + ); + }); + this.#cdpTarget.cdpClient.on('Page.frameNavigated', (params) => { if (this.id !== params.frame.id) { return; } this.#navigationTracker.frameNavigated( params.frame.url + (params.frame.urlFragment ?? ''), + params.frame.loaderId, + // `unreachableUrl` indicates if the navigation failed. + params.frame.unreachableUrl, ); // At the point the page is initialized, all the nested iframes from the // previous page are detached and realms are destroyed. // Delete children from context. this.#deleteAllChildren(); + + this.#documentChanged(params.frame.loaderId); }); this.#cdpTarget.on(TargetEvents.FrameStartedNavigating, (params) => { @@ -397,6 +420,21 @@ export class BrowsingContextImpl { `Received ${TargetEvents.FrameStartedNavigating} event`, params, ); + + // The frame ID can be either a browsing context id, or not set in case of the frame + // is the top-level in the current CDP target. + const possibleFrameIds = [ + this.id, + ...(this.cdpTarget.id === this.id ? [undefined] : []), + ]; + if (!possibleFrameIds.includes(params.frameId)) { + return; + } + + this.#navigationTracker.frameStartedNavigating( + params.url, + params.loaderId, + ); }); this.#cdpTarget.cdpClient.on('Page.navigatedWithinDocument', (params) => { @@ -423,22 +461,6 @@ export class BrowsingContextImpl { } }); - this.#cdpTarget.cdpClient.on('Page.frameStartedLoading', (params) => { - if (this.id !== params.frameId) { - return; - } - - this.#navigationTracker.frameStartedLoading(); - }); - - // TODO: don't use deprecated `Page.frameScheduledNavigation` event. - this.#cdpTarget.cdpClient.on('Page.frameScheduledNavigation', (params) => { - if (this.id !== params.frameId) { - return; - } - this.#navigationTracker.frameScheduledNavigation(params.url); - }); - this.#cdpTarget.cdpClient.on('Page.frameRequestedNavigation', (params) => { if (this.id !== params.frameId) { return; @@ -476,7 +498,7 @@ export class BrowsingContextImpl { switch (params.name) { case 'DOMContentLoaded': - if (!this.#navigationTracker.initialNavigation) { + if (!this.#navigationTracker.isInitialNavigation) { // Do not emit for the initial navigation. this.#eventManager.registerEvent( { @@ -497,7 +519,7 @@ export class BrowsingContextImpl { break; case 'load': - if (!this.#navigationTracker.initialNavigation) { + if (!this.#navigationTracker.isInitialNavigation) { // Do not emit for the initial navigation. this.#eventManager.registerEvent( { @@ -514,7 +536,7 @@ export class BrowsingContextImpl { ); } // The initial navigation is finished. - this.#navigationTracker.lifecycleEventLoad(); + this.#navigationTracker.loadPageEvent(params.loaderId); this.#lifecycle.load.resolve(); break; } @@ -646,6 +668,9 @@ export class BrowsingContextImpl { this.#cdpTarget.cdpClient.on('Page.javascriptDialogOpening', (params) => { const promptType = BrowsingContextImpl.#getPromptType(params.type); + if (params.type === 'beforeunload') { + this.#navigationTracker.beforeunload(); + } // Set the last prompt type to provide it in closing event. this.#lastUserPromptType = promptType; const promptHandler = this.#getPromptHandler(promptType); @@ -735,8 +760,6 @@ export class BrowsingContextImpl { #documentChanged(loaderId?: Protocol.Network.LoaderId) { if (loaderId === undefined || this.#loaderId === loaderId) { - // Same document navigation. Document didn't change. - this.#navigationTracker.navigationFinishedWithinSameDocument(); return; } @@ -792,7 +815,7 @@ export class BrowsingContextImpl { } const commandNavigation = - this.#navigationTracker.createCommandNavigation(url); + this.#navigationTracker.createPendingNavigation(url); // Navigate and wait for the result. If the navigation fails, the error event is // emitted and the promise is rejected. @@ -807,64 +830,67 @@ export class BrowsingContextImpl { if (cdpNavigateResult.errorText) { // If navigation failed, no pending navigation is left. - this.#navigationTracker.failCommandNavigation(commandNavigation); + this.#navigationTracker.failNavigation( + commandNavigation, + cdpNavigateResult.errorText, + ); throw new UnknownErrorException(cdpNavigateResult.errorText); } + this.#navigationTracker.navigationCommandFinished( + commandNavigation, + cdpNavigateResult.loaderId, + ); + this.#documentChanged(cdpNavigateResult.loaderId); - return cdpNavigateResult; })(); if (wait === BrowsingContext.ReadinessState.None) { - // Do not wait for the result of the navigation promise. - this.#navigationTracker.finishCommandNavigation(commandNavigation, true); - return { navigation: commandNavigation.navigationId, url, }; } - const cdpNavigateResult = await cdpNavigatePromise; - // Wait for either the navigation is finished or canceled by another navigation. - await Promise.race([ + const result = await Promise.race([ // No `loaderId` means same-document navigation. - this.#waitNavigation(wait, cdpNavigateResult.loaderId === undefined), + this.#waitNavigation(wait, cdpNavigatePromise), // Throw an error if the navigation is canceled. - this.#navigationTracker.pendingCommandNavigation, - ]).catch((e) => { - // Aborting navigation should not fail the original navigation command for now. - // https://github.com/w3c/webdriver-bidi/issues/799#issue-2605618955 - if (e.message !== 'navigation aborted') { - throw e; + commandNavigation.finished, + ]); + + if (result instanceof NavigationResult) { + if ( + // TODO: check after decision on the spec is done: + // https://github.com/w3c/webdriver-bidi/issues/799. + result.eventName === NavigationEventName.NavigationAborted || + result.eventName === NavigationEventName.NavigationFailed + ) { + throw new UnknownErrorException(result.message ?? 'unknown exception'); } - }); + } - // `#pendingCommandNavigation` can be already rejected and set to undefined. - this.#navigationTracker.finishCommandNavigation(commandNavigation, false); return { navigation: commandNavigation.navigationId, - // Url can change due to redirect. Get the latest one. - url: this.#navigationTracker.url, + // Url can change due to redirects. Get the one from commandNavigation. + url: commandNavigation.url, }; } async #waitNavigation( wait: BrowsingContext.ReadinessState, - withinDocument: boolean, + cdpCommandPromise: Promise, ) { - if (withinDocument) { - await this.#navigationTracker.navigation.withinDocument; - return; - } switch (wait) { case BrowsingContext.ReadinessState.None: return; case BrowsingContext.ReadinessState.Interactive: + await cdpCommandPromise; await this.#lifecycle.DOMContentLoaded; return; case BrowsingContext.ReadinessState.Complete: + await cdpCommandPromise; await this.#lifecycle.load; return; } @@ -879,40 +905,38 @@ export class BrowsingContextImpl { this.#resetLifecycleIfFinished(); - const commandNavigation = this.#navigationTracker.createCommandNavigation( + const commandNavigation = this.#navigationTracker.createPendingNavigation( this.#navigationTracker.url, ); - await this.#cdpTarget.cdpClient.sendCommand('Page.reload', { - ignoreCache, - }); + const cdpReloadPromise = this.#cdpTarget.cdpClient.sendCommand( + 'Page.reload', + { + ignoreCache, + }, + ); - switch (wait) { - case BrowsingContext.ReadinessState.None: - this.#navigationTracker.finishCommandNavigation( - commandNavigation, - true, - ); - break; - case BrowsingContext.ReadinessState.Interactive: - await this.#lifecycle.DOMContentLoaded; - this.#navigationTracker.finishCommandNavigation( - commandNavigation, - false, - ); - break; - case BrowsingContext.ReadinessState.Complete: - await this.#lifecycle.load; - this.#navigationTracker.finishCommandNavigation( - commandNavigation, - false, - ); - break; + // Wait for either the navigation is finished or canceled by another navigation. + const result = await Promise.race([ + // No `loaderId` means same-document navigation. + this.#waitNavigation(wait, cdpReloadPromise), + // Throw an error if the navigation is canceled. + commandNavigation.finished, + ]); + + if (result instanceof NavigationResult) { + if ( + result.eventName === NavigationEventName.NavigationAborted || + result.eventName === NavigationEventName.NavigationFailed + ) { + throw new UnknownErrorException(result.message ?? 'unknown exception'); + } } return { - navigation: this.#navigationTracker.currentNavigationId, - url: this.url, + navigation: commandNavigation.navigationId, + // Url can change due to redirects. Get the one from commandNavigation. + url: commandNavigation.url, }; } diff --git a/src/bidiMapper/modules/context/NavigationTracker.spec.ts b/src/bidiMapper/modules/context/NavigationTracker.spec.ts new file mode 100644 index 0000000000..86b6f217d8 --- /dev/null +++ b/src/bidiMapper/modules/context/NavigationTracker.spec.ts @@ -0,0 +1,402 @@ +/* + * Copyright 2024 Google LLC. + * Copyright (c) Microsoft Corporation. + * + * 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 {assert} from 'chai'; +import sinon, {type SinonStubbedInstance} from 'sinon'; + +import {ChromiumBidi} from '../../../protocol/protocol.js'; +import {EventManager} from '../session/EventManager.js'; + +import {NavigationEventName, NavigationTracker} from './NavigationTracker.js'; + +// keep-sorted start block=yes +const ANOTHER_LOADER_ID = 'ANOTHER_LOADER_ID'; +const ANOTHER_URL = 'ANOTHER_URL'; +const BROWSING_CONTEXT_ID = 'browsingContextId'; +const ERROR_MESSAGE = 'ERROR_MESSAGE'; +const INITIAL_URL = 'about:blank'; +const LOADER_ID = 'LOADER_ID'; +const SOME_URL = 'SOME_URL'; +const YET_ANOTHER_URL = 'YET_ANOTHER_URL'; +// keep-sorted end + +describe('NavigationTracker', () => { + let navigationTracker: NavigationTracker; + let eventManager: SinonStubbedInstance; + let initialNavigationId: string; + + function assertNoNavigationEvents() { + // `eventManager.registerEvent` is safe do be used unbound. + // eslint-disable-next-line @typescript-eslint/unbound-method + sinon.assert.notCalled(eventManager.registerEvent); + } + + function assertNavigationEvent( + this: void, + eventName: string, + navigationId: string | sinon.SinonMatcher, + url: string, + ) { + sinon.assert.calledWith( + // `eventManager.registerEvent` is safe do be used unbound. + // eslint-disable-next-line @typescript-eslint/unbound-method + eventManager.registerEvent, + sinon.match({ + type: 'event', + method: eventName, + params: { + context: BROWSING_CONTEXT_ID, + navigation: navigationId, + timestamp: sinon.match.any, + url, + }, + }), + sinon.match(BROWSING_CONTEXT_ID), + ); + eventManager.registerEvent.reset(); + } + + beforeEach(() => { + eventManager = sinon.createStubInstance(EventManager); + navigationTracker = new NavigationTracker( + INITIAL_URL, + BROWSING_CONTEXT_ID, + eventManager, + ); + initialNavigationId = navigationTracker.currentNavigationId; + }); + + describe('CDP command initiated navigation', () => { + it('should process fragment navigation', async () => { + const navigation = navigationTracker.createPendingNavigation(SOME_URL); + assert.equal(navigation.url, SOME_URL); + assert.equal(navigationTracker.url, INITIAL_URL); + navigationTracker.navigationCommandFinished(navigation, undefined); + + // Assert navigation is not finished. + assertNoNavigationEvents(); + + // Fragment navigation should not update the current navigation. + assert.equal(navigationTracker.currentNavigationId, initialNavigationId); + + navigationTracker.navigatedWithinDocument(SOME_URL, 'fragment'); + + assertNavigationEvent( + ChromiumBidi.BrowsingContext.EventNames.FragmentNavigated, + navigation.navigationId, + SOME_URL, + ); + assert.equal( + (await navigation.finished).eventName, + NavigationEventName.FragmentNavigated, + ); + // Fragment navigation should not update the current navigation. + assert.equal(navigationTracker.currentNavigationId, initialNavigationId); + assert.equal(navigationTracker.url, SOME_URL); + }); + + describe('cross-document navigation', () => { + it('started', async () => { + const navigation = navigationTracker.createPendingNavigation(SOME_URL); + + assertNoNavigationEvents(); + assert.equal(navigation.url, SOME_URL); + assert.equal(navigationTracker.url, INITIAL_URL); + assert.equal( + navigationTracker.currentNavigationId, + initialNavigationId, + ); + + navigationTracker.frameStartedNavigating(ANOTHER_URL, LOADER_ID); + + assertNavigationEvent( + ChromiumBidi.BrowsingContext.EventNames.NavigationStarted, + navigation.navigationId, + ANOTHER_URL, + ); + assert.equal(navigation.url, ANOTHER_URL); + assert.equal(navigationTracker.url, INITIAL_URL); + assert.equal( + navigationTracker.currentNavigationId, + navigation.navigationId, + ); + + navigationTracker.navigationCommandFinished(navigation, LOADER_ID); + + assertNoNavigationEvents(); + assert.equal(navigation.url, ANOTHER_URL); + assert.equal(navigationTracker.url, ANOTHER_URL); + assert.equal( + navigationTracker.currentNavigationId, + navigation.navigationId, + ); + + navigationTracker.loadPageEvent(ANOTHER_LOADER_ID); + + assertNoNavigationEvents(); + + navigationTracker.loadPageEvent(LOADER_ID); + + assertNoNavigationEvents(); + assert.equal( + (await navigation.finished).eventName, + NavigationEventName.Load, + ); + }); + }); + + it('canceled by script-initiated navigation', async () => { + const navigation = navigationTracker.createPendingNavigation(SOME_URL); + navigationTracker.frameStartedNavigating(ANOTHER_URL, LOADER_ID); + + assertNavigationEvent( + ChromiumBidi.BrowsingContext.EventNames.NavigationStarted, + navigation.navigationId, + ANOTHER_URL, + ); + + navigationTracker.frameRequestedNavigation(YET_ANOTHER_URL); + + assertNavigationEvent( + ChromiumBidi.BrowsingContext.EventNames.NavigationFailed, + navigation.navigationId, + ANOTHER_URL, + ); + + assert.equal( + (await navigation.finished).eventName, + NavigationEventName.NavigationFailed, + ); + assert.equal(navigationTracker.currentNavigationId, initialNavigationId); + assert.equal(navigationTracker.url, INITIAL_URL); + }); + + it('aborted by script-initiated navigation', async () => { + const navigation = navigationTracker.createPendingNavigation(SOME_URL); + navigationTracker.frameStartedNavigating(ANOTHER_URL, LOADER_ID); + navigationTracker.frameNavigated(ANOTHER_URL, LOADER_ID); + + eventManager.registerEvent.reset(); + + navigationTracker.frameRequestedNavigation(YET_ANOTHER_URL); + navigationTracker.frameNavigated(YET_ANOTHER_URL, ANOTHER_LOADER_ID); + + assertNavigationEvent( + ChromiumBidi.BrowsingContext.EventNames.NavigationAborted, + navigation.navigationId, + ANOTHER_URL, + ); + + assert.equal( + (await navigation.finished).eventName, + NavigationEventName.NavigationAborted, + ); + }); + + it('failed command', async () => { + const navigation = navigationTracker.createPendingNavigation(SOME_URL); + navigationTracker.frameStartedNavigating(ANOTHER_URL, LOADER_ID); + + assertNavigationEvent( + ChromiumBidi.BrowsingContext.EventNames.NavigationStarted, + navigation.navigationId, + ANOTHER_URL, + ); + + navigationTracker.failNavigation(navigation, ERROR_MESSAGE); + + assertNavigationEvent( + ChromiumBidi.BrowsingContext.EventNames.NavigationFailed, + navigation.navigationId, + ANOTHER_URL, + ); + + assert.equal( + (await navigation.finished).eventName, + NavigationEventName.NavigationFailed, + ); + assert.equal((await navigation.finished).message, ERROR_MESSAGE); + assert.equal( + navigationTracker.currentNavigationId, + navigation.navigationId, + ); + assert.equal(navigationTracker.url, INITIAL_URL); + }); + + it('failed network', async () => { + const navigation = navigationTracker.createPendingNavigation(SOME_URL); + navigationTracker.frameStartedNavigating(ANOTHER_URL, LOADER_ID); + + assertNavigationEvent( + ChromiumBidi.BrowsingContext.EventNames.NavigationStarted, + navigation.navigationId, + ANOTHER_URL, + ); + + navigationTracker.networkLoadingFailed(LOADER_ID, ERROR_MESSAGE); + + assertNavigationEvent( + ChromiumBidi.BrowsingContext.EventNames.NavigationFailed, + navigation.navigationId, + ANOTHER_URL, + ); + + assert.equal( + (await navigation.finished).eventName, + NavigationEventName.NavigationFailed, + ); + assert.equal((await navigation.finished).message, ERROR_MESSAGE); + assert.equal( + navigationTracker.currentNavigationId, + navigation.navigationId, + ); + assert.equal(navigationTracker.url, INITIAL_URL); + }); + }); + + describe('Renderer initiated navigation', () => { + it('should process fragment navigation', () => { + navigationTracker.navigatedWithinDocument(SOME_URL, 'fragment'); + + assertNavigationEvent( + ChromiumBidi.BrowsingContext.EventNames.FragmentNavigated, + sinon.match.any, + SOME_URL, + ); + + assert.equal(navigationTracker.currentNavigationId, initialNavigationId); + assert.equal(navigationTracker.url, SOME_URL); + }); + + describe('cross-document navigation', () => { + it('started', () => { + navigationTracker.frameRequestedNavigation(SOME_URL); + + assertNoNavigationEvents(); + assert.equal( + navigationTracker.currentNavigationId, + initialNavigationId, + ); + assert.equal(navigationTracker.url, INITIAL_URL); + + navigationTracker.frameStartedNavigating(ANOTHER_URL, LOADER_ID); + + assertNavigationEvent( + ChromiumBidi.BrowsingContext.EventNames.NavigationStarted, + sinon.match.any, + ANOTHER_URL, + ); + + assert.notEqual( + navigationTracker.currentNavigationId, + initialNavigationId, + ); + assert.equal(navigationTracker.url, INITIAL_URL); + + navigationTracker.frameNavigated(YET_ANOTHER_URL, LOADER_ID); + + assertNoNavigationEvents(); + assert.equal(navigationTracker.url, YET_ANOTHER_URL); + + navigationTracker.loadPageEvent(LOADER_ID); + + assertNoNavigationEvents(); + }); + + it('canceled by script-initiated navigation', async () => { + navigationTracker.frameRequestedNavigation(SOME_URL); + + assertNoNavigationEvents(); + assert.equal( + navigationTracker.currentNavigationId, + initialNavigationId, + ); + assert.equal(navigationTracker.url, INITIAL_URL); + + navigationTracker.frameStartedNavigating(ANOTHER_URL, LOADER_ID); + + assertNavigationEvent( + ChromiumBidi.BrowsingContext.EventNames.NavigationStarted, + sinon.match.any, + ANOTHER_URL, + ); + + navigationTracker.frameRequestedNavigation(YET_ANOTHER_URL); + + assertNavigationEvent( + ChromiumBidi.BrowsingContext.EventNames.NavigationFailed, + sinon.match.any, + ANOTHER_URL, + ); + assert.equal( + navigationTracker.currentNavigationId, + initialNavigationId, + ); + assert.equal(navigationTracker.url, INITIAL_URL); + }); + + it('canceled by command navigation', async () => { + navigationTracker.frameRequestedNavigation(SOME_URL); + navigationTracker.frameStartedNavigating(ANOTHER_URL, LOADER_ID); + + assertNavigationEvent( + ChromiumBidi.BrowsingContext.EventNames.NavigationStarted, + sinon.match.any, + ANOTHER_URL, + ); + + navigationTracker.createPendingNavigation(YET_ANOTHER_URL); + + assertNavigationEvent( + ChromiumBidi.BrowsingContext.EventNames.NavigationFailed, + sinon.match.any, + ANOTHER_URL, + ); + assert.equal( + navigationTracker.currentNavigationId, + initialNavigationId, + ); + assert.equal(navigationTracker.url, INITIAL_URL); + }); + + it('failed network', () => { + navigationTracker.frameRequestedNavigation(SOME_URL); + navigationTracker.frameStartedNavigating(ANOTHER_URL, LOADER_ID); + + assertNavigationEvent( + ChromiumBidi.BrowsingContext.EventNames.NavigationStarted, + sinon.match.any, + ANOTHER_URL, + ); + + navigationTracker.networkLoadingFailed(LOADER_ID, ERROR_MESSAGE); + + assertNavigationEvent( + ChromiumBidi.BrowsingContext.EventNames.NavigationFailed, + sinon.match.any, + ANOTHER_URL, + ); + assert.notEqual( + navigationTracker.currentNavigationId, + initialNavigationId, + ); + assert.equal(navigationTracker.url, INITIAL_URL); + }); + }); + }); +}); diff --git a/src/bidiMapper/modules/context/NavigationTracker.ts b/src/bidiMapper/modules/context/NavigationTracker.ts index d6fb337a0b..ed32579ba7 100644 --- a/src/bidiMapper/modules/context/NavigationTracker.ts +++ b/src/bidiMapper/modules/context/NavigationTracker.ts @@ -19,50 +19,147 @@ import type {Protocol} from 'devtools-protocol'; import { + type BrowsingContext, ChromiumBidi, - UnknownErrorException, } from '../../../protocol/protocol.js'; import {Deferred} from '../../../utils/Deferred.js'; +import {type LoggerFn, LogType} from '../../../utils/log.js'; import {getTimestamp} from '../../../utils/time.js'; import {urlMatchesAboutBlank} from '../../../utils/UrlHelpers.js'; import {uuidv4} from '../../../utils/uuid.js'; import type {EventManager} from '../session/EventManager.js'; +export const enum NavigationEventName { + FragmentNavigated = ChromiumBidi.BrowsingContext.EventNames.FragmentNavigated, + NavigationAborted = ChromiumBidi.BrowsingContext.EventNames.NavigationAborted, + NavigationFailed = ChromiumBidi.BrowsingContext.EventNames.NavigationFailed, + Load = ChromiumBidi.BrowsingContext.EventNames.Load, +} + +export class NavigationResult { + readonly eventName: NavigationEventName; + readonly message?: string; + + constructor(eventName: NavigationEventName, message?: string) { + this.eventName = eventName; + this.message = message; + } +} + class NavigationState { readonly navigationId = uuidv4(); - url?: string; + readonly #browsingContextId: string; + + #started = false; + #finished = new Deferred(); + url: string; + loaderId?: string; + #isInitial: boolean; + #eventManager: EventManager; + #navigated = false; + + get finished(): Promise { + return this.#finished; + } - constructor(url?: string) { + constructor( + url: string, + browsingContextId: string, + isInitial: boolean, + eventManager: EventManager, + ) { + this.#browsingContextId = browsingContextId; this.url = url; + this.#isInitial = isInitial; + this.#eventManager = eventManager; + } + + navigationInfo(): BrowsingContext.NavigationInfo { + return { + context: this.#browsingContextId, + navigation: this.navigationId, + timestamp: getTimestamp(), + url: this.url, + }; + } + + start() { + if (!this.#isInitial && !this.#started) { + this.#eventManager.registerEvent( + { + type: 'event', + method: ChromiumBidi.BrowsingContext.EventNames.NavigationStarted, + params: this.navigationInfo(), + }, + this.#browsingContextId, + ); + } + + this.#started = true; + } + + #finish(navigationResult: NavigationResult) { + this.#started = true; + + if ( + !this.#isInitial && + !this.#finished.isFinished && + navigationResult.eventName !== NavigationEventName.Load + ) { + this.#eventManager.registerEvent( + { + type: 'event', + method: navigationResult.eventName, + params: this.navigationInfo(), + }, + this.#browsingContextId, + ); + } + this.#finished.resolve(navigationResult); + } + + frameNavigated() { + this.#navigated = true; + } + + fragmentNavigated() { + this.#navigated = true; + this.#finish(new NavigationResult(NavigationEventName.FragmentNavigated)); + } + + load() { + this.#finish(new NavigationResult(NavigationEventName.Load)); + } + + fail(message: string) { + this.#finish( + new NavigationResult( + this.#navigated + ? NavigationEventName.NavigationAborted + : NavigationEventName.NavigationFailed, + message, + ), + ); } } +/** + * Keeps track of navigations. Details: http://go/webdriver:bidi-navigation + */ export class NavigationTracker { readonly #eventManager: EventManager; + readonly #logger?: LoggerFn; + readonly #loaderIdToNavigationsMap = new Map(); + readonly #browsingContextId: string; - #currentNavigation = new NavigationState(); + #currentNavigation: NavigationState; // When a new navigation is started via `BrowsingContext.navigate` with `wait` set to // `None`, the command result should have `navigation` value, but mapper does not have // it yet. This value will be set to `navigationId` after next . #pendingNavigation?: NavigationState; - #url: string; - // The URL of the navigation that is currently in progress. A workaround of the CDP - // lacking URL for the pending navigation events, e.g. `Page.frameStartedLoading`. - // Set on `Page.navigate`, `Page.reload` commands, on `Page.frameRequestedNavigation` or - // on a deprecated `Page.frameScheduledNavigation` event. The latest is required as the - // `Page.frameRequestedNavigation` event is not emitted for same-document navigations. - #pendingNavigationUrl: string | undefined; - // Flags if the initial navigation to `about:blank` is in progress. - #initialNavigation = true; - // Flags if the navigation is initiated by `browsingContext.navigate` or - // `browsingContext.reload` command. - #navigationInitiatedByCommand = false; - - // Set if there is a pending navigation initiated by `BrowsingContext.navigate` command. - // The promise is resolved when the navigation is finished or rejected when canceled. - #pendingCommandNavigation: Deferred | undefined; + #isInitialNavigation = true; navigation = { withinDocument: new Deferred(), @@ -72,210 +169,293 @@ export class NavigationTracker { url: string, browsingContextId: string, eventManager: EventManager, + logger?: LoggerFn, ) { this.#browsingContextId = browsingContextId; - this.#url = url; this.#eventManager = eventManager; + this.#logger = logger; + + this.#isInitialNavigation = true; + this.#currentNavigation = new NavigationState( + url, + browsingContextId, + urlMatchesAboutBlank(url), + this.#eventManager, + ); } + /** + * Returns current started ongoing navigation. It can be either a started pending + * navigation, or one is already navigated. + */ get currentNavigationId() { + if (this.#pendingNavigation?.loaderId !== undefined) { + return this.#pendingNavigation.navigationId; + } + return this.#currentNavigation.navigationId; } - get initialNavigation(): boolean { - return this.#initialNavigation; + /** + * Flags if the current navigation relates to the initial to `about:blank` navigation. + */ + get isInitialNavigation(): boolean { + return this.#isInitialNavigation; } - get pendingCommandNavigation(): Deferred | undefined { - return this.#pendingCommandNavigation; + /** + * Url of the last navigated navigation. + */ + get url(): string { + return this.#currentNavigation.url; } - get url(): string { - return this.#url; + /** + * Creates a pending navigation e.g. when navigation command is called. Required to + * provide navigation id before the actual navigation is started. It will be used when + * navigation started. Can be aborted, failed, fragment navigated, or became a current + * navigation. + */ + createPendingNavigation( + url: string, + canBeInitialNavigation: boolean = false, + ): NavigationState { + this.#logger?.(LogType.debug, 'createCommandNavigation'); + this.#isInitialNavigation = + canBeInitialNavigation && + this.#isInitialNavigation && + urlMatchesAboutBlank(url); + + this.#pendingNavigation?.fail( + 'navigation canceled by concurrent navigation', + ); + const navigation = new NavigationState( + url, + this.#browsingContextId, + this.#isInitialNavigation, + this.#eventManager, + ); + this.#pendingNavigation = navigation; + return navigation; } dispose() { - this.#pendingCommandNavigation?.reject( - new UnknownErrorException('navigation canceled by context disposal'), - ); + this.#pendingNavigation?.fail('navigation canceled by context disposal'); + this.#currentNavigation.fail('navigation canceled by context disposal'); } + // Update the current url. onTargetInfoChanged(url: string) { - this.#url = url; + this.#logger?.(LogType.debug, `onTargetInfoChanged ${url}`); + this.#currentNavigation.url = url; + } + + #getNavigationForFrameNavigated( + url: string, + loaderId: string, + ): NavigationState { + if (this.#loaderIdToNavigationsMap.has(loaderId)) { + return this.#loaderIdToNavigationsMap.get(loaderId)!; + } + + if ( + this.#pendingNavigation !== undefined && + this.#pendingNavigation?.loaderId === undefined + ) { + // This can be a pending navigation to `about:blank` created by a command. Use the + // pending navigation in this case. + return this.#pendingNavigation; + } + // Create a new pending navigation. + return this.createPendingNavigation(url, true); } - frameNavigated(url: string) { - this.#url = url; - this.#pendingNavigationUrl = undefined; + /** + * @param {string} unreachableUrl indicated the navigation is actually failed. + */ + frameNavigated(url: string, loaderId: string, unreachableUrl?: string) { + this.#logger?.(LogType.debug, `frameNavigated ${url}`); + + if ( + unreachableUrl !== undefined && + !this.#loaderIdToNavigationsMap.has(loaderId) + ) { + // The navigation failed before started. Get or create pending navigation and fail + // it. + const navigation = + this.#pendingNavigation ?? + this.createPendingNavigation(unreachableUrl, true); + navigation.url = unreachableUrl; + navigation.start(); + navigation.fail('the requested url is unreachable'); + return; + } + + const navigation = this.#getNavigationForFrameNavigated(url, loaderId); + navigation.frameNavigated(); + + if (navigation !== this.#currentNavigation) { + this.#currentNavigation.fail( + 'navigation canceled by concurrent navigation', + ); + } + + navigation.url = url; + navigation.loaderId = loaderId; + this.#loaderIdToNavigationsMap.set(loaderId, navigation); + navigation.start(); + + this.#currentNavigation = navigation; + if (this.#pendingNavigation === navigation) { + this.#pendingNavigation = undefined; + } } navigatedWithinDocument( url: string, navigationType: Protocol.Page.NavigatedWithinDocumentEvent['navigationType'], ) { - this.#pendingNavigationUrl = undefined; - const timestamp = getTimestamp(); - this.#url = url; - this.navigation.withinDocument.resolve(); + this.#logger?.( + LogType.debug, + `navigatedWithinDocument ${url}, ${navigationType}`, + ); - if (navigationType === 'fragment') { - this.#eventManager.registerEvent( - { - type: 'event', - method: ChromiumBidi.BrowsingContext.EventNames.FragmentNavigated, - params: { - context: this.#browsingContextId, - navigation: this.#currentNavigation.navigationId, - timestamp, - url: this.#url, - }, - }, - this.#browsingContextId, - ); + // Current navigation URL should be updated. + this.#currentNavigation.url = url; + + if (navigationType !== 'fragment') { + // TODO: check for other navigation types, like `javascript`. + return; } - } - frameStartedLoading() { - if (this.#navigationInitiatedByCommand) { - // In case of the navigation is initiated by `browsingContext.navigate` or - // `browsingContext.reload` commands, the `Page.frameRequestedNavigation` is not - // emitted, which means the `NavigationStarted` is not emitted. - // TODO: consider emit it right after the CDP command `navigate` or `reload` is finished. - - // The URL of the navigation that is currently in progress. Although the URL - // is not yet known in case of user-initiated navigations, it is possible to - // provide the URL in case of BiDi-initiated navigations. - // TODO: provide proper URL in case of user-initiated navigations. - const url = this.#pendingNavigationUrl ?? 'UNKNOWN'; - this.#currentNavigation = - this.#pendingNavigation ?? new NavigationState(); - this.#pendingNavigation = undefined; - this.#eventManager.registerEvent( - { - type: 'event', - method: ChromiumBidi.BrowsingContext.EventNames.NavigationStarted, - params: { - context: this.#browsingContextId, - navigation: this.#currentNavigation.navigationId, - timestamp: getTimestamp(), + // There is no way to guaranteed match pending navigation with finished fragment + // navigations. So assume any pending navigation without loader id is the fragment + // one. + const fragmentNavigation = + this.#pendingNavigation !== undefined && + this.#pendingNavigation.loaderId === undefined + ? this.#pendingNavigation + : new NavigationState( url, - }, - }, - this.#browsingContextId, - ); + this.#browsingContextId, + false, + this.#eventManager, + ); + + // Finish ongoing navigation. + fragmentNavigation.fragmentNavigated(); + + if (fragmentNavigation === this.#pendingNavigation) { + this.#pendingNavigation = undefined; } } - frameScheduledNavigation(url: string) { - this.#pendingNavigationUrl = url; + frameRequestedNavigation(url: string) { + this.#logger?.(LogType.debug, `Page.frameRequestedNavigation ${url}`); + // The page is about to navigate to the url. + this.createPendingNavigation(url, true); } - frameRequestedNavigation(url: string) { - if (this.#pendingCommandNavigation !== undefined) { - // The pending navigation was aborted by the new one. - this.#eventManager.registerEvent( - { - type: 'event', - method: ChromiumBidi.BrowsingContext.EventNames.NavigationAborted, - params: { - context: this.#browsingContextId, - navigation: this.#currentNavigation.navigationId, - timestamp: getTimestamp(), - url: this.#url, - }, - }, - this.#browsingContextId, - ); - this.#pendingCommandNavigation.reject( - new UnknownErrorException('navigation aborted'), - ); - this.#pendingCommandNavigation = undefined; - this.#navigationInitiatedByCommand = false; + /** + * Required to mark navigation as fully complete. + * TODO: navigation should be complete when it became the current one on + * `Page.frameNavigated` or on navigating command finished with a new loader Id. + */ + loadPageEvent(loaderId: string) { + this.#logger?.(LogType.debug, 'loadPageEvent'); + // Even if it was an initial navigation, it is finished. + this.#isInitialNavigation = false; + + this.#loaderIdToNavigationsMap.get(loaderId)?.load(); + } + + /** + * Fail navigation due to navigation command failed. + */ + failNavigation(navigation: NavigationState, errorText: string) { + this.#logger?.(LogType.debug, 'failCommandNavigation'); + navigation.fail(errorText); + } + + /** + * Updates the navigation's `loaderId` and sets it as current one, if it is a + * cross-document navigation. + */ + navigationCommandFinished(navigation: NavigationState, loaderId?: string) { + this.#logger?.( + LogType.debug, + `finishCommandNavigation ${navigation.navigationId}, ${loaderId}`, + ); + + if (loaderId !== undefined) { + navigation.loaderId = loaderId; + this.#loaderIdToNavigationsMap.set(loaderId, navigation); } - if (!urlMatchesAboutBlank(url)) { - // If the url does not match about:blank, do not consider it is an initial - // navigation and emit all the required events. - // https://github.com/GoogleChromeLabs/chromium-bidi/issues/2793. - this.#initialNavigation = false; + + if (loaderId === undefined || this.#currentNavigation === navigation) { + // If the command's navigation is same-document or is already the current one, + // nothing to do. + return; } - if (!this.#initialNavigation) { - // Do not emit the event for the initial navigation to `about:blank`. - this.#currentNavigation = - this.#pendingNavigation ?? new NavigationState(); + this.#currentNavigation.fail( + 'navigation canceled by concurrent navigation', + ); + + navigation.start(); + this.#currentNavigation = navigation; + + if (this.#pendingNavigation === navigation) { this.#pendingNavigation = undefined; - this.#eventManager.registerEvent( - { - type: 'event', - method: ChromiumBidi.BrowsingContext.EventNames.NavigationStarted, - params: { - context: this.#browsingContextId, - navigation: this.#currentNavigation.navigationId, - timestamp: getTimestamp(), - url, - }, - }, - this.#browsingContextId, - ); } - - this.#pendingNavigationUrl = url; } - navigationFinishedWithinSameDocument() { - if (this.navigation.withinDocument.isFinished) { - this.navigation.withinDocument = new Deferred(); + /** + * Emulated event, tight to `Network.requestWillBeSent`. + */ + frameStartedNavigating(url: string, loaderId: string) { + this.#logger?.(LogType.debug, `frameStartedNavigating ${url}, ${loaderId}`); + + if (this.#loaderIdToNavigationsMap.has(loaderId)) { + // The `frameStartedNavigating` is tight to the `Network.requestWillBeSent` event + // which can be emitted several times, e.g. in case of redirection. Nothing to do in + // such a case. + return; } - } - lifecycleEventLoad() { - this.#initialNavigation = false; - } + const pendingNavigation = + this.#pendingNavigation ?? this.createPendingNavigation(url, true); - createCommandNavigation(url: string): NavigationState { - this.#pendingCommandNavigation?.reject( - new UnknownErrorException('navigation canceled by concurrent navigation'), - ); - // Set the pending navigation URL to provide it in `browsingContext.navigationStarted` - // event. - // TODO: detect navigation start not from CDP. Check if - // `Page.frameRequestedNavigation` can be used for this purpose. - this.#pendingNavigationUrl = url; - const navigation = new NavigationState(url); - this.#pendingNavigation = navigation; - this.#pendingCommandNavigation = new Deferred(); - this.#navigationInitiatedByCommand = true; + pendingNavigation.url = url; + pendingNavigation.start(); - return navigation; + pendingNavigation.loaderId = loaderId; + this.#loaderIdToNavigationsMap.set(loaderId, pendingNavigation); } - failCommandNavigation(navigation: NavigationState) { - // If navigation failed, no pending navigation is left. - this.#pendingNavigationUrl = undefined; - this.#eventManager.registerEvent( - { - type: 'event', - method: ChromiumBidi.BrowsingContext.EventNames.NavigationFailed, - params: { - context: this.#browsingContextId, - navigation: this.#currentNavigation.navigationId, - timestamp: getTimestamp(), - url: navigation.url ?? 'UNKNOWN', - }, - }, - this.#browsingContextId, - ); - } + /** + * In case of `beforeunload` handler, the pending navigation should be marked as started + * for consistency, as the `browsingContext.navigationStarted` should be emitted before + * user prompt. + */ + beforeunload() { + this.#logger?.(LogType.debug, `beforeunload`); - finishCommandNavigation( - navigation: NavigationState, - finishedByWaitNone: boolean, - ) { - // `#pendingCommandNavigation` can be already rejected and set to undefined. - this.#pendingCommandNavigation?.resolve(); - if (!finishedByWaitNone) { - this.#navigationInitiatedByCommand = false; + if (this.#pendingNavigation === undefined) { + this.#logger?.( + LogType.debugError, + `Unexpectedly no pending navigation on beforeunload`, + ); + return; } - this.#pendingCommandNavigation = undefined; + this.#pendingNavigation.start(); + } + + /** + * If there is a navigation with the loaderId equals to the network request id, it means + * that the navigation failed. + */ + networkLoadingFailed(loaderId: string, errorText: string) { + this.#loaderIdToNavigationsMap.get(loaderId)?.fail(errorText); } } diff --git a/tests/browsing_context/__snapshots__/test_navigate_events.ambr b/tests/browsing_context/__snapshots__/test_navigate_events.ambr index 22b145b8a8..4ca2f4db7d 100644 --- a/tests/browsing_context/__snapshots__/test_navigate_events.ambr +++ b/tests/browsing_context/__snapshots__/test_navigate_events.ambr @@ -2,17 +2,32 @@ # name: test_navigate_aboutBlank_checkEvents list([ dict({ - 'id': 'stable_0', - 'result': dict({ + 'method': 'script.message', + 'params': dict({ + 'channel': 'beforeunload_channel', + 'data': dict({ + 'type': 'string', + 'value': 'beforeunload', + }), + 'source': dict({ + 'context': 'stable_0', + }), + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.navigationStarted', + 'params': dict({ + 'context': 'stable_0', 'navigation': 'stable_1', 'url': 'stable_2', }), - 'type': 'success', + 'type': 'event', }), dict({ 'method': 'browsingContext.domContentLoaded', 'params': dict({ - 'context': 'stable_3', + 'context': 'stable_0', 'navigation': 'stable_1', 'url': 'stable_2', }), @@ -21,37 +36,269 @@ dict({ 'method': 'browsingContext.load', 'params': dict({ - 'context': 'stable_3', + 'context': 'stable_0', 'navigation': 'stable_1', 'url': 'stable_2', }), 'type': 'event', }), + dict({ + 'id': 'stable_3', + 'result': dict({ + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'success', + }), + ]) +# --- +# name: test_navigate_checkEvents + list([ + dict({ + 'method': 'script.message', + 'params': dict({ + 'channel': 'beforeunload_channel', + 'data': dict({ + 'type': 'string', + 'value': 'beforeunload', + }), + 'source': dict({ + 'context': 'stable_0', + }), + }), + 'type': 'event', + }), dict({ 'method': 'browsingContext.navigationStarted', 'params': dict({ - 'context': 'stable_3', + 'context': 'stable_0', 'navigation': 'stable_1', 'url': 'stable_2', }), 'type': 'event', }), + dict({ + 'method': 'network.beforeRequestSent', + 'params': dict({ + 'context': 'stable_0', + 'isBlocked': False, + 'navigation': 'stable_1', + 'redirectCount': 0, + 'request': 'stable_4', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.domContentLoaded', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.load', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'event', + }), + dict({ + 'id': 'stable_5', + 'result': dict({ + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'success', + }), ]) # --- # name: test_navigate_dataUrl_checkEvents list([ dict({ - 'id': 'stable_0', + 'method': 'script.message', + 'params': dict({ + 'channel': 'beforeunload_channel', + 'data': dict({ + 'type': 'string', + 'value': 'beforeunload', + }), + 'source': dict({ + 'context': 'stable_0', + }), + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.navigationStarted', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'event', + }), + dict({ + 'method': 'network.beforeRequestSent', + 'params': dict({ + 'context': 'stable_0', + 'isBlocked': False, + 'navigation': 'stable_1', + 'redirectCount': 0, + 'request': 'stable_4', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.domContentLoaded', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.load', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'event', + }), + dict({ + 'id': 'stable_5', 'result': dict({ 'navigation': 'stable_1', 'url': 'stable_2', }), 'type': 'success', }), + ]) +# --- +# name: test_navigate_hang_navigate_again_checkEvents + list([ + dict({ + 'method': 'network.beforeRequestSent', + 'params': dict({ + 'context': 'stable_0', + 'isBlocked': False, + 'navigation': 'stable_1', + 'redirectCount': 0, + 'request': 'stable_4', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.navigationFailed', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_3', + }), + 'type': 'event', + }), + dict({ + 'error': 'unknown error', + 'id': 'stable_5', + 'message': 'navigation canceled by concurrent navigation', + 'type': 'error', + }), + dict({ + 'method': 'script.message', + 'params': dict({ + 'channel': 'beforeunload_channel', + 'data': dict({ + 'type': 'string', + 'value': 'beforeunload', + }), + 'source': dict({ + 'context': 'stable_0', + }), + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.navigationStarted', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_6', + 'url': 'stable_7', + }), + 'type': 'event', + }), + dict({ + 'method': 'network.beforeRequestSent', + 'params': dict({ + 'context': 'stable_0', + 'isBlocked': False, + 'navigation': 'stable_6', + 'redirectCount': 0, + 'request': 'stable_9', + }), + 'type': 'event', + }), dict({ 'method': 'browsingContext.domContentLoaded', 'params': dict({ - 'context': 'stable_3', + 'context': 'stable_0', + 'navigation': 'stable_6', + 'url': 'stable_7', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.load', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_6', + 'url': 'stable_7', + }), + 'type': 'event', + }), + dict({ + 'id': 'stable_10', + 'result': dict({ + 'navigation': 'stable_6', + 'url': 'stable_7', + }), + 'type': 'success', + }), + ]) +# --- +# name: test_reload_aboutBlank_checkEvents + list([ + dict({ + 'method': 'script.message', + 'params': dict({ + 'channel': 'beforeunload_channel', + 'data': dict({ + 'type': 'string', + 'value': 'beforeunload', + }), + 'source': dict({ + 'context': 'stable_0', + }), + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.navigationStarted', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.domContentLoaded', + 'params': dict({ + 'context': 'stable_0', 'navigation': 'stable_1', 'url': 'stable_2', }), @@ -60,16 +307,42 @@ dict({ 'method': 'browsingContext.load', 'params': dict({ - 'context': 'stable_3', + 'context': 'stable_0', 'navigation': 'stable_1', 'url': 'stable_2', }), 'type': 'event', }), + dict({ + 'id': 'stable_3', + 'result': dict({ + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'success', + }), + ]) +# --- +# name: test_reload_checkEvents + list([ + dict({ + 'method': 'script.message', + 'params': dict({ + 'channel': 'beforeunload_channel', + 'data': dict({ + 'type': 'string', + 'value': 'beforeunload', + }), + 'source': dict({ + 'context': 'stable_0', + }), + }), + 'type': 'event', + }), dict({ 'method': 'browsingContext.navigationStarted', 'params': dict({ - 'context': 'stable_3', + 'context': 'stable_0', 'navigation': 'stable_1', 'url': 'stable_2', }), @@ -78,36 +351,292 @@ dict({ 'method': 'network.beforeRequestSent', 'params': dict({ - 'context': 'stable_3', - 'initiator': dict({ - 'type': 'other', - }), + 'context': 'stable_0', 'isBlocked': False, 'navigation': 'stable_1', 'redirectCount': 0, - 'request': 'stable_5', + 'request': 'stable_4', }), 'type': 'event', }), dict({ - 'method': 'network.responseCompleted', + 'method': 'browsingContext.domContentLoaded', 'params': dict({ - 'context': 'stable_3', - 'isBlocked': False, + 'context': 'stable_0', 'navigation': 'stable_1', - 'redirectCount': 0, - 'request': 'stable_5', + 'url': 'stable_2', }), 'type': 'event', }), dict({ - 'method': 'network.responseStarted', + 'method': 'browsingContext.load', 'params': dict({ - 'context': 'stable_3', + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'event', + }), + dict({ + 'id': 'stable_5', + 'result': dict({ + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'success', + }), + ]) +# --- +# name: test_reload_dataUrl_checkEvents + list([ + dict({ + 'method': 'script.message', + 'params': dict({ + 'channel': 'beforeunload_channel', + 'data': dict({ + 'type': 'string', + 'value': 'beforeunload', + }), + 'source': dict({ + 'context': 'stable_0', + }), + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.navigationStarted', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'event', + }), + dict({ + 'method': 'network.beforeRequestSent', + 'params': dict({ + 'context': 'stable_0', 'isBlocked': False, 'navigation': 'stable_1', 'redirectCount': 0, - 'request': 'stable_5', + 'request': 'stable_4', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.domContentLoaded', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.load', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'event', + }), + dict({ + 'id': 'stable_5', + 'result': dict({ + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'success', + }), + ]) +# --- +# name: test_scriptNavigate_aboutBlank_checkEvents + list([ + dict({ + 'method': 'browsingContext.navigationStarted', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.navigationAborted', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.navigationStarted', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_3', + 'url': 'stable_4', + }), + 'type': 'event', + }), + ]) +# --- +# name: test_scriptNavigate_checkEvents + list([ + dict({ + 'method': 'browsingContext.navigationStarted', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.navigationStarted', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_3', + 'url': 'stable_4', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.navigationAborted', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.domContentLoaded', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_3', + 'url': 'stable_4', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.load', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_3', + 'url': 'stable_4', + }), + 'type': 'event', + }), + ]) +# --- +# name: test_scriptNavigate_dataUrl_checkEvents + list([ + dict({ + 'method': 'browsingContext.navigationStarted', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.domContentLoaded', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.load', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'event', + }), + ]) +# --- +# name: test_scriptNavigate_fragment_checkEvents + list([ + dict({ + 'method': 'browsingContext.navigationStarted', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.fragmentNavigated', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_3', + 'url': 'stable_4', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.domContentLoaded', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_4', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.load', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_4', + }), + 'type': 'event', + }), + ]) +# --- +# name: test_scriptNavigate_fragment_nested_checkEvents + list([ + dict({ + 'method': 'browsingContext.navigationStarted', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_2', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.fragmentNavigated', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_3', + 'url': 'stable_4', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.domContentLoaded', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_4', + }), + 'type': 'event', + }), + dict({ + 'method': 'browsingContext.load', + 'params': dict({ + 'context': 'stable_0', + 'navigation': 'stable_1', + 'url': 'stable_4', }), 'type': 'event', }), diff --git a/tests/browsing_context/test_create.py b/tests/browsing_context/test_create.py index ba4149ecd8..dc20f9aa32 100644 --- a/tests/browsing_context/test_create.py +++ b/tests/browsing_context/test_create.py @@ -13,15 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License. import pytest -from anys import ANY_STR, Not +from anys import ANY_STR from test_helpers import (ANY_TIMESTAMP, AnyExtending, execute_command, get_tree, goto_url, read_JSON_message, send_JSON_command, subscribe) @pytest.mark.asyncio -async def test_browsingContext_create_eventsEmitted(websocket, - read_sorted_messages): +async def test_browsingContext_create_eventsEmitted(websocket, read_messages): await subscribe(websocket, "browsingContext") command_id = await send_JSON_command(websocket, { @@ -31,9 +30,10 @@ async def test_browsingContext_create_eventsEmitted(websocket, } }) - messages = await read_sorted_messages(2, - keys_to_stabilize=['context'], - check_no_other_messages=True) + messages = await read_messages(2, + keys_to_stabilize=['context'], + check_no_other_messages=True, + sort=True) assert messages == [{ "type": "success", "id": command_id, @@ -57,7 +57,7 @@ async def test_browsingContext_create_eventsEmitted(websocket, @pytest.mark.asyncio async def test_browsingContext_windowOpen_blank_eventsEmitted( - websocket, context_id, read_sorted_messages): + websocket, context_id, read_messages): await subscribe(websocket, "browsingContext") command_id = await send_JSON_command( @@ -73,33 +73,32 @@ async def test_browsingContext_windowOpen_blank_eventsEmitted( } }) - messages = await read_sorted_messages(2, check_no_other_messages=True) - assert messages == [ - AnyExtending({ - "type": "success", - "id": command_id, - }), { - "type": "event", - "method": "browsingContext.contextCreated", - "params": { - "context": ANY_STR, - "url": "about:blank", - "children": None, - "parent": None, - "userContext": "default", - "originalOpener": ANY_STR, - 'clientWindow': ANY_STR, - } + messages = await read_messages(2, check_no_other_messages=True, sort=False) + assert messages == [{ + "type": "event", + "method": "browsingContext.contextCreated", + "params": { + "context": ANY_STR, + "url": "about:blank", + "children": None, + "parent": None, + "userContext": "default", + "originalOpener": ANY_STR, + 'clientWindow': ANY_STR, } - ] + }, + AnyExtending({ + "type": "success", + "id": command_id, + })] @pytest.mark.asyncio async def test_browsingContext_windowOpen_nonBlank_eventsEmitted( - websocket, context_id, read_sorted_messages, url_example): + websocket, context_id, read_messages, url_example): await subscribe(websocket, "browsingContext") - command_id = await send_JSON_command( + await send_JSON_command( websocket, { "method": "script.evaluate", "params": { @@ -112,67 +111,54 @@ async def test_browsingContext_windowOpen_nonBlank_eventsEmitted( } }) - events = await read_sorted_messages( - 5, + events = await read_messages( + 4, + # Filter out the command result, as it's order is not defined. + filter_lambda=lambda m: 'id' not in m, keys_to_stabilize=['context', 'navigation'], - check_no_other_messages=True) - - assert events == [ - AnyExtending({ - "type": "success", - "id": command_id, - "result": { - "result": { - "value": { - "context": "stable_0", - } - } - } - }), - { - "type": "event", - "method": "browsingContext.contextCreated", - "params": { - 'context': 'stable_0', - "url": "about:blank", - "children": None, - "parent": None, - "userContext": "default", - "originalOpener": ANY_STR, - 'clientWindow': ANY_STR, - } - }, - { - 'method': 'browsingContext.domContentLoaded', - 'params': { - 'context': 'stable_0', - 'navigation': 'stable_1', - 'timestamp': ANY_TIMESTAMP, - 'url': url_example, - }, - 'type': 'event', + check_no_other_messages=True, + sort=False) + + assert events == [{ + "type": "event", + "method": "browsingContext.contextCreated", + "params": { + 'context': 'stable_0', + "url": "about:blank", + "children": None, + "parent": None, + "userContext": "default", + "originalOpener": ANY_STR, + 'clientWindow': ANY_STR, + } + }, { + 'method': 'browsingContext.navigationStarted', + 'params': { + 'context': 'stable_0', + 'navigation': 'stable_1', + 'timestamp': ANY_TIMESTAMP, + 'url': url_example, }, - { - 'method': 'browsingContext.load', - 'params': { - 'context': 'stable_0', - 'navigation': 'stable_1', - 'timestamp': ANY_TIMESTAMP, - 'url': url_example, - }, - 'type': 'event', + 'type': 'event', + }, { + 'method': 'browsingContext.domContentLoaded', + 'params': { + 'context': 'stable_0', + 'navigation': 'stable_1', + 'timestamp': ANY_TIMESTAMP, + 'url': url_example, }, - { - 'method': 'browsingContext.navigationStarted', - 'params': { - 'context': 'stable_0', - 'navigation': 'stable_1', - 'timestamp': ANY_TIMESTAMP, - 'url': url_example, - }, - 'type': 'event', + 'type': 'event', + }, { + 'method': 'browsingContext.load', + 'params': { + 'context': 'stable_0', + 'navigation': 'stable_1', + 'timestamp': ANY_TIMESTAMP, + 'url': url_example, }, - ] + 'type': 'event', + }] @pytest.mark.asyncio @@ -205,7 +191,7 @@ async def test_browsingContext_createWithNestedSameOriginContexts_eventContextCr tree = await get_tree(websocket) - assert { + assert tree == { "contexts": [{ "context": ANY_STR, "parent": None, @@ -233,7 +219,7 @@ async def test_browsingContext_createWithNestedSameOriginContexts_eventContextCr }, ] }] - } == tree + } intermediate_page_context_id = tree["contexts"][0]["children"][0][ "context"] @@ -266,7 +252,7 @@ async def test_browsingContext_createWithNestedSameOriginContexts_eventContextCr @pytest.mark.asyncio async def test_browsingContext_create_withUserGesture_eventsEmitted( - websocket, context_id, html, url_example, read_sorted_messages): + websocket, context_id, html, url_example, read_messages): LINK_WITH_BLANK_TARGET = html( f'''new tab''') @@ -274,7 +260,7 @@ async def test_browsingContext_create_withUserGesture_eventsEmitted( await subscribe(websocket, 'browsingContext') - command_id = await send_JSON_command( + await send_JSON_command( websocket, { 'method': 'script.evaluate', 'params': { @@ -287,32 +273,60 @@ async def test_browsingContext_create_withUserGesture_eventsEmitted( } }) - messages = await read_sorted_messages(2, check_no_other_messages=True) + messages = await read_messages( + 4, + # Filter out the command result, as it's order is not defined. + filter_lambda=lambda m: 'id' not in m, + check_no_other_messages=True, + keys_to_stabilize=['context', 'navigation'], + sort=False) - assert messages == [ - AnyExtending({ - 'id': command_id, - 'type': 'success', - }), { - 'type': 'event', - 'method': 'browsingContext.contextCreated', - 'params': { - 'context': ANY_STR & Not(context_id), - 'url': 'about:blank', - 'clientWindow': ANY_STR, - 'children': None, - 'parent': None, - 'userContext': 'default', - 'originalOpener': ANY_STR, - } + assert messages == [{ + 'type': 'event', + 'method': 'browsingContext.contextCreated', + 'params': { + 'context': 'stable_0', + 'url': 'about:blank', + 'clientWindow': ANY_STR, + 'children': None, + 'parent': None, + 'userContext': 'default', + 'originalOpener': ANY_STR, } - ] + }, { + 'method': 'browsingContext.navigationStarted', + 'params': { + 'context': 'stable_0', + 'navigation': 'stable_1', + 'timestamp': ANY_TIMESTAMP, + 'url': url_example, + }, + 'type': 'event', + }, { + 'type': 'event', + 'method': 'browsingContext.domContentLoaded', + 'params': { + 'context': 'stable_0', + 'navigation': 'stable_1', + 'timestamp': ANY_TIMESTAMP, + 'url': url_example, + }, + }, { + 'type': 'event', + 'method': 'browsingContext.load', + 'params': { + 'context': 'stable_0', + 'navigation': 'stable_1', + 'timestamp': ANY_TIMESTAMP, + 'url': url_example, + }, + }] @pytest.mark.asyncio @pytest.mark.parametrize("type", ["window", "tab"]) async def test_browsingContext_create_withUserContext(websocket, type, - read_sorted_messages): + read_messages): result = await execute_command(websocket, { "method": "browser.createUserContext", "params": {} @@ -330,9 +344,10 @@ async def test_browsingContext_create_withUserContext(websocket, type, } }) - messages = await read_sorted_messages(2, - keys_to_stabilize=['context'], - check_no_other_messages=True) + messages = await read_messages(2, + keys_to_stabilize=['context'], + check_no_other_messages=True, + sort=True) assert messages == [{ 'id': command_id, diff --git a/tests/browsing_context/test_dom_content_loaded.py b/tests/browsing_context/test_dom_content_loaded.py index 6dd64cdf1f..9c51546f6d 100644 --- a/tests/browsing_context/test_dom_content_loaded.py +++ b/tests/browsing_context/test_dom_content_loaded.py @@ -44,7 +44,7 @@ async def test_browsingContext_domContentLoaded_create_notReceived( @pytest.mark.asyncio async def test_browsingContext_domContentLoaded_navigate_received( - websocket, context_id, url_example, read_sorted_messages): + websocket, context_id, url_example, read_messages): await subscribe(websocket, ["browsingContext.domContentLoaded"]) command_id = await send_JSON_command( @@ -57,9 +57,10 @@ async def test_browsingContext_domContentLoaded_navigate_received( } }) - messages = await read_sorted_messages(2, - keys_to_stabilize=['navigation'], - check_no_other_messages=True) + messages = await read_messages(2, + keys_to_stabilize=['navigation'], + check_no_other_messages=True, + sort=True) assert messages == [ { diff --git a/tests/browsing_context/test_load.py b/tests/browsing_context/test_load.py index faef19e8cd..20b443c42a 100644 --- a/tests/browsing_context/test_load.py +++ b/tests/browsing_context/test_load.py @@ -87,7 +87,7 @@ async def test_browsingContext_noInitialLoadEvents(websocket, html, @pytest.mark.asyncio async def test_browsingContext_load_properNavigation(websocket, context_id, url_example, - read_sorted_messages): + read_messages): await subscribe(websocket, "browsingContext.load") command_id = await send_JSON_command( @@ -100,9 +100,10 @@ async def test_browsingContext_load_properNavigation(websocket, context_id, } }) - messages = await read_sorted_messages(2, - keys_to_stabilize=['navigation'], - check_no_other_messages=True) + messages = await read_messages(2, + keys_to_stabilize=['navigation'], + check_no_other_messages=True, + sort=True) assert messages == [ { diff --git a/tests/browsing_context/test_navigate.py b/tests/browsing_context/test_navigate.py index 4ecb9ea096..5f51f8a81f 100644 --- a/tests/browsing_context/test_navigate.py +++ b/tests/browsing_context/test_navigate.py @@ -21,7 +21,7 @@ @pytest.mark.asyncio async def test_browsingContext_navigateWaitInteractive_redirect( - websocket, context_id, html, url_example, read_sorted_messages): + websocket, context_id, html, url_example, read_messages): await subscribe(websocket, ["browsingContext.navigationAborted"]) initial_url = html(f"") @@ -36,19 +36,18 @@ async def test_browsingContext_navigateWaitInteractive_redirect( } }) - messages = await read_sorted_messages(2, - keys_to_stabilize=['navigation'], - check_no_other_messages=True) + messages = await read_messages(2, + keys_to_stabilize=['navigation'], + check_no_other_messages=True, + sort=True) assert messages == [ - { + AnyExtending({ 'id': command_id, - 'result': { - 'navigation': 'stable_0', - 'url': initial_url, - }, - 'type': 'success', - }, + 'error': 'unknown error', + 'message': 'navigation canceled by concurrent navigation', + 'type': 'error', + }), { 'method': 'browsingContext.navigationAborted', 'params': { @@ -377,11 +376,11 @@ async def test_navigateToPageWithHash_contextInfoUpdated( @pytest.mark.asyncio async def test_browsingContext_navigationStartedEvent_viaScript( - websocket, context_id, url_base): + websocket, context_id, url_base, read_messages): serialized_url = {"type": "string", "value": url_base} await subscribe(websocket, ["browsingContext.navigationStarted"]) - await send_JSON_command( + command_id = await send_JSON_command( websocket, { "method": "script.callFunction", "params": { @@ -396,23 +395,27 @@ async def test_browsingContext_navigationStartedEvent_viaScript( } }) - response = await read_JSON_message(websocket) - assert response == { - 'type': 'event', - "method": "browsingContext.navigationStarted", - "params": { - "context": context_id, - "navigation": ANY_UUID, - "timestamp": ANY_TIMESTAMP, - # TODO: Should report correct string - "url": ANY_STR, + messages = await read_messages(2, check_no_other_messages=True, sort=True) + assert messages == [ + AnyExtending({ + 'id': command_id, + 'type': 'success', + }), { + 'type': 'event', + "method": "browsingContext.navigationStarted", + "params": { + "context": context_id, + "navigation": ANY_UUID, + "timestamp": ANY_TIMESTAMP, + "url": url_base, + } } - } + ] @pytest.mark.asyncio async def test_browsingContext_navigationStartedEvent_iframe_viaCommand( - websocket, context_id, url_base, html, iframe, read_sorted_messages): + websocket, context_id, url_base, html, iframe, read_messages): await subscribe(websocket, ["browsingContext.navigationStarted"]) iframe_url = html("

FRAME

") page_url = html(iframe(iframe_url)) @@ -426,9 +429,10 @@ async def test_browsingContext_navigationStartedEvent_iframe_viaCommand( } }) - messages = await read_sorted_messages(3, - keys_to_stabilize=['navigation'], - check_no_other_messages=True) + messages = await read_messages(3, + keys_to_stabilize=['navigation'], + check_no_other_messages=True, + sort=True) assert messages == [ { @@ -464,7 +468,7 @@ async def test_browsingContext_navigationStartedEvent_iframe_viaCommand( @pytest.mark.asyncio async def test_browsingContext_navigationStartedEvent_iframe_viaScript( - websocket, context_id, url_base, html, iframe, read_sorted_messages): + websocket, context_id, url_base, html, iframe, read_messages): await subscribe(websocket, ["browsingContext.navigationStarted"]) iframe_url = html("

FRAME

") page_url = html(iframe(iframe_url)) @@ -487,9 +491,10 @@ async def test_browsingContext_navigationStartedEvent_iframe_viaScript( } }) - messages = await read_sorted_messages(3, - keys_to_stabilize=['navigation'], - check_no_other_messages=True) + messages = await read_messages(3, + keys_to_stabilize=['navigation'], + check_no_other_messages=True, + sort=True) assert messages == [ AnyExtending({ @@ -521,7 +526,7 @@ async def test_browsingContext_navigationStartedEvent_iframe_viaScript( @pytest.mark.asyncio async def test_browsingContext_navigationStartedEvent_viaCommand( - websocket, context_id, html, read_sorted_messages): + websocket, context_id, html, read_messages): url = html() await subscribe(websocket, ["browsingContext.navigationStarted"]) @@ -536,9 +541,10 @@ async def test_browsingContext_navigationStartedEvent_viaCommand( } }) - messages = await read_sorted_messages(2, - keys_to_stabilize=['navigation'], - check_no_other_messages=True) + messages = await read_messages(2, + keys_to_stabilize=['navigation'], + check_no_other_messages=True, + sort=True) assert messages == [{ 'id': command_id, @@ -561,7 +567,7 @@ async def test_browsingContext_navigationStartedEvent_viaCommand( @pytest.mark.asyncio async def test_browsingContext_navigationStarted_browsingContextClosedBeforeNavigationEnded_navigationFailed( - websocket, context_id, read_sorted_messages, url_hang_forever): + websocket, context_id, read_messages, url_hang_forever): navigate_command_id = await send_JSON_command( websocket, { "method": "browsingContext.navigate", @@ -581,13 +587,13 @@ async def test_browsingContext_navigationStarted_browsingContextClosedBeforeNavi # Command result order is not guaranteed. [navigation_command_result, - close_command_result] = await read_sorted_messages(2) + close_command_result] = await read_messages(2, sort=True) assert navigation_command_result == AnyExtending({ 'id': navigate_command_id, 'type': 'error', 'error': 'unknown error', - 'message': 'navigation canceled by context disposal', + 'message': 'net::ERR_ABORTED', }) assert close_command_result == AnyExtending({ @@ -598,10 +604,11 @@ async def test_browsingContext_navigationStarted_browsingContextClosedBeforeNavi @pytest.mark.asyncio async def test_browsingContext_navigationStarted_sameDocumentNavigation( - websocket, context_id, url_base): - await subscribe( - websocket, - ["browsingContext.navigationStarted", "browsingContext.load"]) + websocket, context_id, url_base, assert_no_more_messages): + await subscribe(websocket, [ + "browsingContext.navigationStarted", + "browsingContext.fragmentNavigated", "browsingContext.load" + ]) # Make an initial navigation. command_id = await send_JSON_command( @@ -666,7 +673,7 @@ async def test_browsingContext_navigationStarted_sameDocumentNavigation( response = await read_JSON_message(websocket) assert response == AnyExtending({ 'type': 'event', - "method": "browsingContext.navigationStarted", + "method": "browsingContext.fragmentNavigated", "params": { "context": context_id, "navigation": ANY_UUID, @@ -688,26 +695,33 @@ async def test_browsingContext_navigationStarted_sameDocumentNavigation( indirect=True) async def test_browsingContext_acceptInsecureCertsCapability_respected( websocket, context_id, url_bad_ssl, capabilities): - async def navigate(): - await execute_command( - websocket, { - 'method': "browsingContext.navigate", - 'params': { - 'url': url_bad_ssl, - 'wait': 'complete', - 'context': context_id - } - }) + command_id = await send_JSON_command( + websocket, { + 'method': "browsingContext.navigate", + 'params': { + 'url': url_bad_ssl, + 'wait': 'complete', + 'context': context_id + } + }) + resp = await read_JSON_message(websocket) if capabilities.get('acceptInsecureCerts'): - await navigate() + assert resp == { + 'id': command_id, + 'result': { + 'navigation': ANY_UUID, + 'url': url_bad_ssl, + }, + 'type': 'success', + } else: - with pytest.raises(Exception, - match=str({ - 'error': 'unknown error', - 'message': 'net::ERR_CERT_AUTHORITY_INVALID' - })): - await navigate() + assert resp == AnyExtending({ + 'error': 'unknown error', + 'id': command_id, + 'message': 'net::ERR_CERT_AUTHORITY_INVALID', + 'type': 'error', + }) @pytest.mark.asyncio @@ -716,8 +730,7 @@ async def navigate(): }], indirect=True) async def test_speculationrules_prerender(websocket, context_id, html, - test_headless_mode, - read_sorted_messages): + test_headless_mode, read_messages): if test_headless_mode == "old": pytest.xfail("Old headless mode does not support prerendering") @@ -748,7 +761,7 @@ async def test_speculationrules_prerender(websocket, context_id, html, } }) - [command_result, context_created_event] = await read_sorted_messages(2) + [command_result, context_created_event] = await read_messages(2, sort=True) # Assert that the navigation command was finished. assert command_result == AnyExtending({ diff --git a/tests/browsing_context/test_navigate_events.py b/tests/browsing_context/test_navigate_events.py index 769b93fde8..6d8dc3833a 100644 --- a/tests/browsing_context/test_navigate_events.py +++ b/tests/browsing_context/test_navigate_events.py @@ -15,23 +15,169 @@ import pytest from syrupy.filters import props -from test_helpers import goto_url, send_JSON_command, subscribe +from test_helpers import (execute_command, goto_url, send_JSON_command, + subscribe, wait_for_event) -SNAPSHOT_EXCLUDE = props("timestamp", "message", "timings", "headers", - "stacktrace", "response") +SNAPSHOT_EXCLUDE = props("timestamp", "timings", "headers", "stacktrace", + "response", "initiator", "realm") KEYS_TO_STABILIZE = ['context', 'navigation', 'id', 'url', 'request'] +async def set_beforeunload_handler(websocket, context_id): + await execute_command( + websocket, { + "method": "script.callFunction", + "params": { + "functionDeclaration": """ + (channel) => { + window.addEventListener('beforeunload', () => { + channel("beforeunload"); + },false) + } + """, + "arguments": [{ + "type": "channel", + "value": { + "channel": "beforeunload_channel", + "ownership": "none", + }, + }], + "target": { + "context": context_id, + }, + "awaitPromise": False + } + }) + + +@pytest.mark.asyncio +async def test_navigate_checkEvents(websocket, context_id, url_base, + url_example, read_messages, snapshot): + await goto_url(websocket, context_id, url_base) + await set_beforeunload_handler(websocket, context_id) + await subscribe( + websocket, + ["browsingContext", "script.message", "network.beforeRequestSent"]) + + await send_JSON_command( + websocket, { + "method": "browsingContext.navigate", + "params": { + "url": url_example, + "wait": "complete", + "context": context_id + } + }) + + messages = await read_messages(6, + keys_to_stabilize=KEYS_TO_STABILIZE, + check_no_other_messages=True, + sort=False) + assert messages == snapshot(exclude=SNAPSHOT_EXCLUDE) + + @pytest.mark.asyncio -async def test_navigate_scriptRedirect_checkEvents(websocket, context_id, html, - url_example, - read_sorted_messages, - snapshot): - pytest.xfail( - reason= # noqa: E251. The line is too long. - "TODO: https://github.com/GoogleChromeLabs/chromium-bidi/issues/2856") +async def test_navigate_aboutBlank_checkEvents(websocket, context_id, url_base, + read_messages, snapshot): + await goto_url(websocket, context_id, url_base) + await set_beforeunload_handler(websocket, context_id) + await subscribe( + websocket, + ["browsingContext", "script.message", "network.beforeRequestSent"]) + + about_blank_url = 'about:blank' + + await send_JSON_command( + websocket, { + "method": "browsingContext.navigate", + "params": { + "url": about_blank_url, + "wait": "complete", + "context": context_id + } + }) + + messages = await read_messages(5, + keys_to_stabilize=KEYS_TO_STABILIZE, + check_no_other_messages=True, + sort=False) + assert messages == snapshot(exclude=SNAPSHOT_EXCLUDE) + + +@pytest.mark.asyncio +async def test_navigate_dataUrl_checkEvents(websocket, context_id, url_base, + read_messages, snapshot): + await goto_url(websocket, context_id, url_base) + await set_beforeunload_handler(websocket, context_id) + await subscribe( + websocket, + ["browsingContext", "script.message", "network.beforeRequestSent"]) + + data_url = "data:text/html;,

header

" + + await send_JSON_command( + websocket, { + "method": "browsingContext.navigate", + "params": { + "url": data_url, + "wait": "complete", + "context": context_id + } + }) + + messages = await read_messages(6, + keys_to_stabilize=KEYS_TO_STABILIZE, + check_no_other_messages=True, + sort=False) + assert messages == snapshot(exclude=SNAPSHOT_EXCLUDE) + + +@pytest.mark.asyncio +async def test_navigate_hang_navigate_again_checkEvents( + websocket, context_id, url_base, url_hang_forever, + url_example_another_origin, read_messages, snapshot, + assert_no_more_messages): + # Use `url_example_another_origin`, as `url_example` will hang because of + # `url_hang_forever`. + await goto_url(websocket, context_id, url_base) + await set_beforeunload_handler(websocket, context_id) + await subscribe( + websocket, + ["browsingContext", "script.message", "network.beforeRequestSent"]) + + await send_JSON_command( + websocket, { + "method": "browsingContext.navigate", + "params": { + "url": url_hang_forever, + "wait": "complete", + "context": context_id + } + }) + + await wait_for_event(websocket, "browsingContext.navigationStarted") - await subscribe(websocket, ["browsingContext", "network"]) + await send_JSON_command( + websocket, { + "method": "browsingContext.navigate", + "params": { + "url": url_example_another_origin, + "wait": "complete", + "context": context_id + } + }) + + messages = await read_messages(9, + keys_to_stabilize=KEYS_TO_STABILIZE, + check_no_other_messages=True, + sort=False) + assert messages == snapshot(exclude=SNAPSHOT_EXCLUDE) + + +@pytest.mark.asyncio +async def test_scriptNavigate_checkEvents(websocket, context_id, url_example, + html, read_messages, snapshot): + await subscribe(websocket, ["browsingContext"]) initial_url = html(f"") @@ -45,23 +191,60 @@ async def test_navigate_scriptRedirect_checkEvents(websocket, context_id, html, } }) - messages = await read_sorted_messages(12, - keys_to_stabilize=KEYS_TO_STABILIZE, - check_no_other_messages=True) + messages = await read_messages( + 5, + # Filter out command result, as it can be + # racy with other events. + filter_lambda=lambda x: 'id' not in x, + keys_to_stabilize=KEYS_TO_STABILIZE, + check_no_other_messages=True, + sort=False) assert messages == snapshot(exclude=SNAPSHOT_EXCLUDE) @pytest.mark.asyncio -async def test_navigate_scriptFragmentRedirect_checkEvents( - websocket, context_id, html, url_example, read_sorted_messages, - snapshot): - pytest.xfail( - reason= # noqa: E251. The line is too long. - "TODO: https://github.com/GoogleChromeLabs/chromium-bidi/issues/2856") +async def test_scriptNavigate_aboutBlank_checkEvents(websocket, context_id, + url_base, html, + read_messages, snapshot): + # Other events can be racy. + await subscribe(websocket, [ + "browsingContext.navigationStarted", + "browsingContext.navigationAborted" + ]) + + about_blank_url = 'about:blank' + initial_url = html( + f"") + + await send_JSON_command( + websocket, { + "method": "browsingContext.navigate", + "params": { + "url": initial_url, + "wait": "complete", + "context": context_id + } + }) - await subscribe(websocket, ["browsingContext", "network"]) + messages = await read_messages( + 3, + # Filter out command result, as it can be + # racy with other events. + filter_lambda=lambda x: 'id' not in x, + keys_to_stabilize=KEYS_TO_STABILIZE, + check_no_other_messages=True, + sort=False) + assert messages == snapshot(exclude=SNAPSHOT_EXCLUDE) - initial_url = html("") + +@pytest.mark.asyncio +async def test_scriptNavigate_dataUrl_checkEvents(websocket, context_id, + url_base, html, + read_messages, snapshot): + await subscribe(websocket, ["browsingContext"]) + + data_url = "data:text/html;,

header

" + initial_url = html(f"") await send_JSON_command( websocket, { @@ -73,21 +256,50 @@ async def test_navigate_scriptFragmentRedirect_checkEvents( } }) - messages = await read_sorted_messages(8, - keys_to_stabilize=KEYS_TO_STABILIZE, - check_no_other_messages=True) + messages = await read_messages( + 3, + # Filter out command result, as it can be + # racy with other events. + filter_lambda=lambda x: 'id' not in x, + keys_to_stabilize=KEYS_TO_STABILIZE, + check_no_other_messages=True, + sort=False) assert messages == snapshot(exclude=SNAPSHOT_EXCLUDE) @pytest.mark.asyncio -async def test_nested_navigate_scriptFragmentRedirect_checkEvents( - websocket, iframe_id, html, url_example, read_sorted_messages, - snapshot): - pytest.xfail( - reason= # noqa: E251. The line is too long. - "TODO: https://github.com/GoogleChromeLabs/chromium-bidi/issues/2856") +async def test_scriptNavigate_fragment_checkEvents(websocket, context_id, + url_base, html, + read_messages, snapshot): + await subscribe(websocket, ["browsingContext"]) + + initial_url = html("") + + await send_JSON_command( + websocket, { + "method": "browsingContext.navigate", + "params": { + "url": initial_url, + "wait": "complete", + "context": context_id + } + }) + + messages = await read_messages( + 4, + # Filter out command result, as it can be + # racy with other events. + filter_lambda=lambda x: 'id' not in x, + keys_to_stabilize=KEYS_TO_STABILIZE, + check_no_other_messages=True, + sort=False) + assert messages == snapshot(exclude=SNAPSHOT_EXCLUDE) - await subscribe(websocket, ["browsingContext", "network"]) + +@pytest.mark.asyncio +async def test_scriptNavigate_fragment_nested_checkEvents( + websocket, iframe_id, html, url_base, read_messages, snapshot): + await subscribe(websocket, ["browsingContext"]) initial_url = html("") @@ -101,86 +313,93 @@ async def test_nested_navigate_scriptFragmentRedirect_checkEvents( } }) - messages = await read_sorted_messages(8, - keys_to_stabilize=KEYS_TO_STABILIZE, - check_no_other_messages=True) + messages = await read_messages( + 4, + # Filter out command result, as it can be + # racy with other events. + filter_lambda=lambda x: 'id' not in x, + keys_to_stabilize=KEYS_TO_STABILIZE, + check_no_other_messages=True, + sort=False) assert messages == snapshot(exclude=SNAPSHOT_EXCLUDE) @pytest.mark.asyncio -async def test_navigate_aboutBlank_checkEvents(websocket, context_id, - url_example, - read_sorted_messages, snapshot): +async def test_reload_checkEvents(websocket, context_id, url_example, html, + read_messages, snapshot): await goto_url(websocket, context_id, url_example) - await subscribe(websocket, ["browsingContext", "network"]) + await set_beforeunload_handler(websocket, context_id) + await subscribe( + websocket, + ["browsingContext", "script.message", "network.beforeRequestSent"]) await send_JSON_command( websocket, { - "method": "browsingContext.navigate", + "method": "browsingContext.reload", "params": { - "url": "about:blank", "wait": "complete", "context": context_id } }) - messages = await read_sorted_messages(4, - keys_to_stabilize=KEYS_TO_STABILIZE, - check_no_other_messages=True) + messages = await read_messages(6, + keys_to_stabilize=KEYS_TO_STABILIZE, + check_no_other_messages=True, + sort=False) assert messages == snapshot(exclude=SNAPSHOT_EXCLUDE) @pytest.mark.asyncio -async def test_navigate_dataUrl_checkEvents(websocket, context_id, url_example, - read_sorted_messages, snapshot): - data_url = "data:text/html;,

header

" - await goto_url(websocket, context_id, url_example) +async def test_reload_aboutBlank_checkEvents(websocket, context_id, html, + url_base, read_messages, + snapshot): + url = 'about:blank' + await goto_url(websocket, context_id, url) - await subscribe(websocket, ["browsingContext", "network"]) + await set_beforeunload_handler(websocket, context_id) + await subscribe( + websocket, + ["browsingContext", "script.message", "network.beforeRequestSent"]) await send_JSON_command( websocket, { - "method": "browsingContext.navigate", + "method": "browsingContext.reload", "params": { - "url": data_url, "wait": "complete", "context": context_id } }) - messages = await read_sorted_messages(7, - keys_to_stabilize=KEYS_TO_STABILIZE, - check_no_other_messages=True) + messages = await read_messages(5, + keys_to_stabilize=KEYS_TO_STABILIZE, + check_no_other_messages=True, + sort=False) assert messages == snapshot(exclude=SNAPSHOT_EXCLUDE) @pytest.mark.asyncio -async def test_scriptNavigate_aboutBlank_checkEvents(websocket, context_id, - url_example, html, - read_sorted_messages, - snapshot): - pytest.xfail( - reason= # noqa: E251. The line is too long. - "TODO: https://github.com/GoogleChromeLabs/chromium-bidi/issues/2856") +async def test_reload_dataUrl_checkEvents(websocket, context_id, html, + url_base, read_messages, snapshot): + data_url = "data:text/html;,

header

" + await goto_url(websocket, context_id, data_url) - await subscribe(websocket, ["browsingContext", "network"]) - - about_blank_url = 'about:blank' - initial_url = html( - f"") + await set_beforeunload_handler(websocket, context_id) + await subscribe( + websocket, + ["browsingContext", "script.message", "network.beforeRequestSent"]) await send_JSON_command( websocket, { - "method": "browsingContext.navigate", + "method": "browsingContext.reload", "params": { - "url": initial_url, "wait": "complete", "context": context_id } }) - messages = await read_sorted_messages(9, - keys_to_stabilize=KEYS_TO_STABILIZE, - check_no_other_messages=True) + messages = await read_messages(6, + keys_to_stabilize=KEYS_TO_STABILIZE, + check_no_other_messages=True, + sort=False) assert messages == snapshot(exclude=SNAPSHOT_EXCLUDE) diff --git a/tests/browsing_context/test_nested_browsing_context.py b/tests/browsing_context/test_nested_browsing_context.py index 6ea6a20b95..bef2e267b6 100644 --- a/tests/browsing_context/test_nested_browsing_context.py +++ b/tests/browsing_context/test_nested_browsing_context.py @@ -46,7 +46,7 @@ async def test_nestedBrowsingContext_navigateToPageWithHash_contextInfoUpdated( @pytest.mark.asyncio async def test_nestedBrowsingContext_navigateWaitNone_navigated( - websocket, iframe_id, html, read_sorted_messages): + websocket, iframe_id, html, read_messages): url = html("

iframe

") await subscribe( @@ -58,7 +58,8 @@ async def test_nestedBrowsingContext_navigateWaitNone_navigated( assert result == {"navigation": navigation_id, "url": url} - [dom_content_loaded, browsing_context_load] = await read_sorted_messages(2) + [dom_content_loaded, + browsing_context_load] = await read_messages(2, sort=True) assert dom_content_loaded == { 'type': 'event', "method": "browsingContext.domContentLoaded", diff --git a/tests/browsing_context/test_print.py b/tests/browsing_context/test_print.py index 9b89c1fad9..f562bbc26c 100644 --- a/tests/browsing_context/test_print.py +++ b/tests/browsing_context/test_print.py @@ -19,7 +19,10 @@ @pytest.mark.asyncio -async def test_print_top_level_context(websocket, context_id, html): +async def test_print_top_level_context(websocket, context_id, html, + test_headless_mode): + if test_headless_mode == "old": + pytest.xfail("PDF viewer not available in headless.") await goto_url(websocket, context_id, html()) print_result = await execute_command( @@ -38,15 +41,8 @@ async def test_print_top_level_context(websocket, context_id, html): # 'data' is not deterministic, ~a dozen characters differ between runs. assert print_result["data"] == ANY_STR - try: - await goto_url(websocket, context_id, - f'data:application/pdf,base64;{print_result["data"]}') - except Exception as e: - assert e.args[0] == { - 'error': 'unknown error', - 'message': 'net::ERR_ABORTED' - } - pytest.xfail("PDF viewer not available in headless.") + await goto_url(websocket, context_id, + f'data:application/pdf,base64;{print_result["data"]}') @pytest.mark.asyncio diff --git a/tests/conftest.py b/tests/conftest.py index 37aa325ce2..7dd9946d5a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -300,7 +300,7 @@ def url_cacheable(local_server_http): @pytest.fixture -def read_sorted_messages(websocket, read_all_messages): +def read_messages(websocket, read_all_messages): """ Reads the specified number of messages from the WebSocket, returning them in a consistent order. @@ -314,11 +314,12 @@ def read_sorted_messages(websocket, read_all_messages): is determined by the key and the order in which unique values for that key are encountered. """ - async def read_sorted_messages( - message_count, - filter_lambda: Callable[[dict], bool] = lambda _: True, - keys_to_stabilize: list[str] = [], - check_no_other_messages: bool = False): + async def read_messages(message_count, + filter_lambda: Callable[[dict], + bool] = lambda _: True, + keys_to_stabilize: list[str] = [], + check_no_other_messages: bool = False, + sort=True): messages = [] for _ in range(message_count): # Get the next message matching the filter. @@ -329,10 +330,12 @@ async def read_sorted_messages( messages.append(message) if check_no_other_messages: - messages = messages + await read_all_messages() + messages = messages + await read_all_messages( + filter_lambda=filter_lambda) - messages.sort(key=lambda x: x["method"] - if "method" in x else str(x["id"]) if "id" in x else "") + if sort: + messages.sort(key=lambda x: x["method"] if "method" in x else str( + x["id"]) if "id" in x else "") # Stabilize some values through the messages. stabilize_key_values(messages, keys_to_stabilize) @@ -345,12 +348,13 @@ async def read_sorted_messages( return messages - return read_sorted_messages + return read_messages @pytest.fixture def read_all_messages(websocket): - async def read_all_messages(): + async def read_all_messages( + filter_lambda: Callable[[dict], bool] = lambda _: True): messages = [] """ Walk through all the browsing contexts and evaluate an async script""" command_id = await send_JSON_command(websocket, { @@ -359,8 +363,9 @@ async def read_all_messages(): }) message = await read_JSON_message(websocket) while message != AnyExtending({'id': command_id}): - # Unexpected message. Add to the result list. - messages.append(message) + if filter_lambda(message): + # Unexpected message. Add to the result list. + messages.append(message) message = await read_JSON_message(websocket) else: assert message == AnyExtending({ @@ -382,7 +387,9 @@ async def read_all_messages(): message = await read_JSON_message(websocket) while message != AnyExtending({'id': command_id}): # Ignore both success and failure command result. - messages.append(message) + if filter_lambda(message): + # Unexpected message. Add to the result list. + messages.append(message) message = await read_JSON_message(websocket) return messages diff --git a/tests/input/test_input.py b/tests/input/test_input.py index fb18b94e50..f684577b78 100644 --- a/tests/input/test_input.py +++ b/tests/input/test_input.py @@ -530,7 +530,7 @@ async def test_input_performActionsEmitsWheelEvents(websocket, context_id, @pytest.mark.parametrize("same_origin", [True, False]) @pytest.mark.asyncio async def test_click_iframe_context(websocket, context_id, html, same_origin, - read_sorted_messages): + read_messages): # TODO: add test for double-nested iframes. await subscribe(websocket, ["log.entryAdded"]) @@ -562,7 +562,7 @@ async def test_click_iframe_context(websocket, context_id, html, same_origin, # Wait for the iframe to load. It cannot be guaranteed by the "wait" # condition. - [_, frame_loaded_console_event] = await read_sorted_messages(2) + [_, frame_loaded_console_event] = await read_messages(2, sort=True) assert frame_loaded_console_event == AnyExtending({ "method": "log.entryAdded", "params": { @@ -608,7 +608,7 @@ async def test_click_iframe_context(websocket, context_id, html, same_origin, await wait_for_event(websocket, "log.entryAdded") - [mousedown_console_event] = await read_sorted_messages(1) + [mousedown_console_event] = await read_messages(1, sort=True) assert mousedown_console_event == AnyExtending({ "method": "log.entryAdded", "params": { diff --git a/tests/network/test_network.py b/tests/network/test_network.py index defeb56834..c66f8a8195 100644 --- a/tests/network/test_network.py +++ b/tests/network/test_network.py @@ -505,7 +505,7 @@ async def test_network_should_not_block_queue_shared_workers_with_data_url( "method": "network.beforeRequestSent", "params": { "isBlocked": False, - "context": None, + "context": context_id, "navigation": None, "redirectCount": 0, "request": { diff --git a/tests/script/test_add_preload_script.py b/tests/script/test_add_preload_script.py index 69713be537..6a8ede565c 100644 --- a/tests/script/test_add_preload_script.py +++ b/tests/script/test_add_preload_script.py @@ -221,7 +221,7 @@ async def test_preloadScript_add_sameScriptMultipleTimes( @pytest.mark.asyncio async def test_preloadScript_add_loadedInNewIframes(websocket, context_id, url_all_origins, html, - read_sorted_messages): + read_messages): await subscribe(websocket, ["log.entryAdded"]) await execute_command( @@ -276,7 +276,7 @@ async def test_preloadScript_add_loadedInNewIframes(websocket, context_id, }) # Event order is not guaranteed, so read 2 messages, sort them and assert. - [command_result, log_entry_added] = await read_sorted_messages(2) + [command_result, log_entry_added] = await read_messages(2, sort=True) assert command_result == { "type": "success", @@ -410,7 +410,7 @@ async def test_preloadScript_add_loadedInMultipleContexts( @pytest.mark.asyncio async def test_preloadScript_add_loadedInMultipleContexts_withIframes( - websocket, context_id, url_all_origins, html, read_sorted_messages): + websocket, context_id, url_all_origins, html, read_messages): await subscribe(websocket, ["script.message"]) await goto_url(websocket, context_id, html()) @@ -452,7 +452,7 @@ async def test_preloadScript_add_loadedInMultipleContexts_withIframes( # Depending on the URL, the iframe can be loaded before or after the script # is done. - [command_result, script_message_event] = await read_sorted_messages(2) + [command_result, script_message_event] = await read_messages(2, sort=True) assert [command_result, script_message_event] == [ AnyExtending({ @@ -652,7 +652,7 @@ async def test_preloadScript_add_sandbox_existing_context( @pytest.mark.asyncio async def test_preloadScript_add_withUserGesture_blankTargetLink( - websocket, context_id, html, read_sorted_messages, url_example): + websocket, context_id, html, read_messages, url_example): LINK_WITH_BLANK_TARGET = html( f'new tab') @@ -684,7 +684,7 @@ async def test_preloadScript_add_withUserGesture_blankTargetLink( } }) - [command_result, log_entry_added] = await read_sorted_messages(2) + [command_result, log_entry_added] = await read_messages(2, sort=True) assert command_result == AnyExtending({ "id": command_id, "type": "success", @@ -707,7 +707,7 @@ async def test_preloadScript_add_withUserGesture_blankTargetLink( @pytest.mark.asyncio async def test_preloadScript_channel_navigate(websocket, context_id, html, - read_sorted_messages): + read_messages): await subscribe(websocket, ["script.message"]) result = await execute_command( @@ -744,7 +744,7 @@ async def test_preloadScript_channel_navigate(websocket, context_id, html, } }) - [command_result, channel_message] = await read_sorted_messages(2) + [command_result, channel_message] = await read_messages(2, sort=True) assert command_result == { "type": "success", "id": command_id, @@ -768,8 +768,7 @@ async def test_preloadScript_channel_navigate(websocket, context_id, html, @pytest.mark.asyncio -async def test_preloadScript_channel_newContext(websocket, - read_sorted_messages): +async def test_preloadScript_channel_newContext(websocket, read_messages): await subscribe(websocket, ["script.message"]) result = await execute_command( @@ -797,7 +796,7 @@ async def test_preloadScript_channel_newContext(websocket, } }) - [command_result, channel_message] = await read_sorted_messages(2) + [command_result, channel_message] = await read_messages(2, sort=True) assert command_result == { "type": "success", "id": command_id, diff --git a/tests/session/test_subscription.py b/tests/session/test_subscription.py index cbc9989438..b1cda25554 100644 --- a/tests/session/test_subscription.py +++ b/tests/session/test_subscription.py @@ -564,7 +564,7 @@ async def test_unsubscribeIsAtomic(websocket, context_id, iframe_id): @pytest.mark.asyncio async def test_unsubscribe_from_detached_target(websocket, context_id, - read_sorted_messages): + read_messages): events = [ 'bluetooth', 'browser', 'browsingContext', 'cdp', 'input', 'log', 'network', 'script', 'session' @@ -588,7 +588,7 @@ async def test_unsubscribe_from_detached_target(websocket, context_id, # Read only command responses ignoring events previously subscribed. [close_command_response, unsubscribe_command_response - ] = await read_sorted_messages(2, lambda message: "id" in message) + ] = await read_messages(2, lambda message: "id" in message, sort=True) assert close_command_response == AnyExtending({ "id": close_command_id, "type": "success" diff --git a/wpt b/wpt index bf49dde84c..d484471acb 160000 --- a/wpt +++ b/wpt @@ -1 +1 @@ -Subproject commit bf49dde84c5f05613115d6146d109f0ec3900694 +Subproject commit d484471acba5785f41b8d657ec0dafbfc6327fc5 diff --git a/wpt-metadata/chromedriver/headful/webdriver/tests/bidi/browsing_context/close/prompt_unload.py.ini b/wpt-metadata/chromedriver/headful/webdriver/tests/bidi/browsing_context/close/prompt_unload.py.ini deleted file mode 100644 index d171df2f21..0000000000 --- a/wpt-metadata/chromedriver/headful/webdriver/tests/bidi/browsing_context/close/prompt_unload.py.ini +++ /dev/null @@ -1,7 +0,0 @@ -[prompt_unload.py] - expected: TIMEOUT - [test_prompt_unload_triggering_dialog[capabilities0-window\]] - expected: ERROR - - [test_prompt_unload_triggering_dialog[capabilities0-tab\]] - expected: ERROR diff --git a/wpt-metadata/chromedriver/headful/webdriver/tests/bidi/browsing_context/navigation_failed/navigation_failed.py.ini b/wpt-metadata/chromedriver/headful/webdriver/tests/bidi/browsing_context/navigation_failed/navigation_failed.py.ini index 5571718a39..3e677dec03 100644 --- a/wpt-metadata/chromedriver/headful/webdriver/tests/bidi/browsing_context/navigation_failed/navigation_failed.py.ini +++ b/wpt-metadata/chromedriver/headful/webdriver/tests/bidi/browsing_context/navigation_failed/navigation_failed.py.ini @@ -1,30 +1,9 @@ [navigation_failed.py] - [test_with_csp_meta_tag] - expected: FAIL - - [test_with_content_blocking_header_in_top_context[Content-Security-Policy, default-src 'self'\]] - expected: FAIL - - [test_with_content_blocking_header_in_top_context[Cross-Origin-Embedder-Policy, require-corp\]] - expected: FAIL - - [test_with_x_frame_options_header[SAMEORIGIN\]] - expected: FAIL - - [test_with_x_frame_options_header[DENY\]] - expected: FAIL - [test_with_new_navigation_inside_page] expected: FAIL - [test_close_context[tab\]] - expected: FAIL - - [test_close_context[window\]] - expected: FAIL + [test_with_new_navigation] + expected: [PASS, FAIL] [test_close_iframe] expected: FAIL - - [test_with_new_navigation] - expected: [PASS, FAIL] diff --git a/wpt-metadata/chromedriver/headless/webdriver/tests/bidi/browsing_context/close/prompt_unload.py.ini b/wpt-metadata/chromedriver/headless/webdriver/tests/bidi/browsing_context/close/prompt_unload.py.ini deleted file mode 100644 index d171df2f21..0000000000 --- a/wpt-metadata/chromedriver/headless/webdriver/tests/bidi/browsing_context/close/prompt_unload.py.ini +++ /dev/null @@ -1,7 +0,0 @@ -[prompt_unload.py] - expected: TIMEOUT - [test_prompt_unload_triggering_dialog[capabilities0-window\]] - expected: ERROR - - [test_prompt_unload_triggering_dialog[capabilities0-tab\]] - expected: ERROR diff --git a/wpt-metadata/chromedriver/headless/webdriver/tests/bidi/browsing_context/navigation_failed/navigation_failed.py.ini b/wpt-metadata/chromedriver/headless/webdriver/tests/bidi/browsing_context/navigation_failed/navigation_failed.py.ini index 5571718a39..3e677dec03 100644 --- a/wpt-metadata/chromedriver/headless/webdriver/tests/bidi/browsing_context/navigation_failed/navigation_failed.py.ini +++ b/wpt-metadata/chromedriver/headless/webdriver/tests/bidi/browsing_context/navigation_failed/navigation_failed.py.ini @@ -1,30 +1,9 @@ [navigation_failed.py] - [test_with_csp_meta_tag] - expected: FAIL - - [test_with_content_blocking_header_in_top_context[Content-Security-Policy, default-src 'self'\]] - expected: FAIL - - [test_with_content_blocking_header_in_top_context[Cross-Origin-Embedder-Policy, require-corp\]] - expected: FAIL - - [test_with_x_frame_options_header[SAMEORIGIN\]] - expected: FAIL - - [test_with_x_frame_options_header[DENY\]] - expected: FAIL - [test_with_new_navigation_inside_page] expected: FAIL - [test_close_context[tab\]] - expected: FAIL - - [test_close_context[window\]] - expected: FAIL + [test_with_new_navigation] + expected: [PASS, FAIL] [test_close_iframe] expected: FAIL - - [test_with_new_navigation] - expected: [PASS, FAIL] diff --git a/wpt-metadata/mapper/headless/webdriver/tests/bidi/browsing_context/navigation_failed/navigation_failed.py.ini b/wpt-metadata/mapper/headless/webdriver/tests/bidi/browsing_context/navigation_failed/navigation_failed.py.ini index 5571718a39..3e677dec03 100644 --- a/wpt-metadata/mapper/headless/webdriver/tests/bidi/browsing_context/navigation_failed/navigation_failed.py.ini +++ b/wpt-metadata/mapper/headless/webdriver/tests/bidi/browsing_context/navigation_failed/navigation_failed.py.ini @@ -1,30 +1,9 @@ [navigation_failed.py] - [test_with_csp_meta_tag] - expected: FAIL - - [test_with_content_blocking_header_in_top_context[Content-Security-Policy, default-src 'self'\]] - expected: FAIL - - [test_with_content_blocking_header_in_top_context[Cross-Origin-Embedder-Policy, require-corp\]] - expected: FAIL - - [test_with_x_frame_options_header[SAMEORIGIN\]] - expected: FAIL - - [test_with_x_frame_options_header[DENY\]] - expected: FAIL - [test_with_new_navigation_inside_page] expected: FAIL - [test_close_context[tab\]] - expected: FAIL - - [test_close_context[window\]] - expected: FAIL + [test_with_new_navigation] + expected: [PASS, FAIL] [test_close_iframe] expected: FAIL - - [test_with_new_navigation] - expected: [PASS, FAIL]