From eb73ac8e15ce70db224212c7fcd89da006ab76b7 Mon Sep 17 00:00:00 2001 From: Puja Jagani Date: Wed, 13 Aug 2025 18:24:02 +0530 Subject: [PATCH 1/5] [js][bidi] Add Emulation module --- javascript/selenium-webdriver/BUILD.bazel | 1 + .../bidi/emulation/emulation.js | 85 +++++++++ .../bidi/emulation/geolocationCoordinates.js | 78 ++++++++ .../test/bidi/emulation_test.js | 167 ++++++++++++++++++ 4 files changed, 331 insertions(+) create mode 100644 javascript/selenium-webdriver/bidi/emulation/emulation.js create mode 100644 javascript/selenium-webdriver/bidi/emulation/geolocationCoordinates.js create mode 100644 javascript/selenium-webdriver/test/bidi/emulation_test.js diff --git a/javascript/selenium-webdriver/BUILD.bazel b/javascript/selenium-webdriver/BUILD.bazel index 292370e429251..0fe8ce60d7369 100644 --- a/javascript/selenium-webdriver/BUILD.bazel +++ b/javascript/selenium-webdriver/BUILD.bazel @@ -39,6 +39,7 @@ js_library( "common/*.js", "bidi/*.js", "bidi/external/*.js", + "bidi/emulation/*.js", ]), deps = [ ":node_modules/@bazel/runfiles", diff --git a/javascript/selenium-webdriver/bidi/emulation/emulation.js b/javascript/selenium-webdriver/bidi/emulation/emulation.js new file mode 100644 index 0000000000000..1f4bb76fe41d1 --- /dev/null +++ b/javascript/selenium-webdriver/bidi/emulation/emulation.js @@ -0,0 +1,85 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you 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. + +const GeolocationCoordinates = require("./geolocationCoordinates"); + +const GeolocationPositionError = Object.freeze({ + type: 'positionUnavailable' +}) + +class Emulation { + constructor(driver) { + this._driver = driver + } + + async init() { + if (!(await this._driver.getCapabilities()).get('webSocketUrl')) { + throw Error('WebDriver instance must support BiDi protocol') + } + + this.bidi = await this._driver.getBidi() + } + + async setGeolocationOverride(value, contexts= undefined, userContexts = undefined) { + const map = new Map() + + if (value instanceof GeolocationCoordinates) { + map.set('coordinates', Object.fromEntries(value.asMap())) + } else if (value === GeolocationPositionError) { + map.set('error', value) + } else { + throw new Error( + 'First argument must be a GeoCoordinates instance or GeolocationPositionError constant' + ) + } + + if (contexts !== undefined && typeof contexts === 'string') { + contexts = [contexts] + } else if (contexts !== undefined && !Array.isArray(contexts)) { + throw new Error('contexts must be a string or an array of strings') + } + + map.set('contexts', contexts) + + if (userContexts !== undefined && typeof userContexts === 'string') { + userContexts = [userContexts] + } else if (userContexts !== undefined && !Array.isArray(userContexts)) { + throw new Error('userContexts must be a string or an array of strings') + } + + map.set('userContexts', userContexts) + + const command = { + method: 'emulation.setGeolocationOverride', + params: Object.fromEntries(map) + } + + const response = await this.bidi.send(command) + + if (response.type === 'error') { + throw new Error(`${response.error}: ${response.message}`) + } + } +} + +async function getEmulationInstance(driver) { + let instance = new Emulation(driver) + await instance.init() + return instance +} + +module.exports = { getEmulationInstance, GeolocationPositionError} diff --git a/javascript/selenium-webdriver/bidi/emulation/geolocationCoordinates.js b/javascript/selenium-webdriver/bidi/emulation/geolocationCoordinates.js new file mode 100644 index 0000000000000..3d393a1c51d33 --- /dev/null +++ b/javascript/selenium-webdriver/bidi/emulation/geolocationCoordinates.js @@ -0,0 +1,78 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you 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. + +class GeolocationCoordinates { + #map = new Map() + + constructor(latitude, longitude) { + if (typeof latitude !== 'number' || latitude < -90.0 || latitude > 90.0) { + throw new Error(`Latitude must be a number between -90.0 and 90.0. Received: '${latitude}'`) + } + this.#map.set('latitude', latitude) + + if (typeof longitude !== 'number' || longitude < -180.0 || longitude > 180.0) { + throw new Error(`Longitude must be a number between -180.0 and 180.0. Received: '${longitude}'`) + } + this.#map.set('longitude', longitude) + } + + accuracy(value) { + if (typeof value !== 'number' || value < 0.0) { + throw new Error(`Accuracy must be a number >= 0.0. Received: '${value}'`) + } + this.#map.set('accuracy', value) + return this + } + + altitude(value) { + if (value !== null && typeof value !== 'number') { + throw new Error(`Altitude must be a number. Received: '${value}'`) + } + this.#map.set('altitude', value) + return this + } + + altitudeAccuracy(value) { + if (value !== null && (typeof value !== 'number' || value < 0.0)) { + throw new Error(`AltitudeAccuracy must be a number >= 0.0. Received: '${value}'`) + } + this.#map.set('altitudeAccuracy', value) + return this + } + + heading(value) { + if (value !== null && (typeof value !== 'number' || value < 0.0 || value > 360.0)) { + throw new Error(`Heading must be a number between 0.0 and 360.0. Received: '${value}'`) + } + this.#map.set('heading', value) + return this + } + + speed(value) { + if (value !== null && (typeof value !== 'number' || value < 0.0)) { + throw new Error(`Speed must be a number >= 0.0. Received: '${value}'`) + } + this.#map.set('speed', value) + return this + } + + asMap() { + return this.#map + } +} + +module.exports = GeolocationCoordinates diff --git a/javascript/selenium-webdriver/test/bidi/emulation_test.js b/javascript/selenium-webdriver/test/bidi/emulation_test.js new file mode 100644 index 0000000000000..644eb9bd1f611 --- /dev/null +++ b/javascript/selenium-webdriver/test/bidi/emulation_test.js @@ -0,0 +1,167 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you 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. + +'use strict' + +const assert = require('node:assert') +const {Pages, suite} = require('../../lib/test') +const {Browser} = require('selenium-webdriver') +const BrowserBiDi = require('selenium-webdriver/bidi/browser') +const getScriptManager = require('selenium-webdriver/bidi/scriptManager') +const {GeolocationPositionError, getEmulationInstance } = require('selenium-webdriver/bidi/emulation/emulation') +const GeolocationCoordinates = require('selenium-webdriver/bidi/emulation/geolocationCoordinates') +const BrowsingContext = require('selenium-webdriver/bidi/browsingContext') +const {getPermissionInstance, PermissionState} = require('selenium-webdriver/bidi/external/permissions') +const {CreateContextParameters} = require('selenium-webdriver/bidi/createContextParameters') + +suite( + function (env) { + describe('BiDi Emulation', function () { + let driver, emulation, permission, script, browser + + const GET_ORIGIN = '() => {return window.location.origin;}' + + const GET_CURRENT_GEOLOCATION = ` + new Promise((resolve) => { + navigator.geolocation.getCurrentPosition( + position => { + const coords = position.coords; + resolve({ + latitude: coords.latitude, + longitude: coords.longitude, + accuracy: coords.accuracy, + altitude: coords.altitude, + altitudeAccuracy: coords.altitudeAccuracy, + heading: coords.heading, + speed: coords.speed, + timestamp: position.timestamp + }); + }, + error => resolve({ error: error.message }), + { enableHighAccuracy: false, timeout: 10000, maximumAge: 0 } + ); + }) +` + + beforeEach(async function () { + driver = await env.builder().build() + emulation = await getEmulationInstance(driver) + permission = await getPermissionInstance(driver) + script = await getScriptManager([], driver) + browser = await BrowserBiDi(driver) + }) + + afterEach(function () { + return driver.quit() + }) + + it('can override geolocation for browsing context', async function () { + const windowHandle = await driver.getWindowHandle() + const context = await BrowsingContext(driver, {browsingContextId: windowHandle}) + await context.navigate(Pages.blankPage, 'complete') + + const origin = await script.callFunctionInBrowsingContext(context.id, GET_ORIGIN, true, []) + const originValue = origin.result.value + await permission.setPermission({name: 'geolocation'}, PermissionState.GRANTED, originValue) + + const coords = new GeolocationCoordinates(37.7749, -122.4194) + + await emulation.setGeolocationOverride(coords, windowHandle) + + const result = await script.evaluateFunctionInBrowsingContext(context.id, GET_CURRENT_GEOLOCATION, true) + + const geolocation = result.result.value + const currentLatitude = geolocation.latitude.value + const currentLongitude = geolocation.longitude.value + + assert.strictEqual(currentLatitude, 37.7749) + assert.strictEqual(currentLongitude, -122.4194) + }) + + it('can override geolocation for user context', async function () { + const userContext1 = await browser.createUserContext() + const userContext2 = await browser.createUserContext() + + const createParams1 = new CreateContextParameters().userContext(userContext1) + const createParams2 = new CreateContextParameters().userContext(userContext2) + + const context1 = await BrowsingContext(driver, {type: 'tab', createParameters: createParams1}) + + const context2 = await BrowsingContext(driver, {type: 'tab', createParameters: createParams2}) + + const coords = new GeolocationCoordinates(45.5, -122.4194) + + await emulation.setGeolocationOverride(coords, undefined, [userContext1, userContext2]) + + await driver.switchTo().window(context1.id) + + await context1.navigate(Pages.blankPage, 'complete') + const origin1 = (await script.callFunctionInBrowsingContext(context1.id, GET_ORIGIN, true, [])).result.value + await permission.setPermission({name: 'geolocation'}, PermissionState.GRANTED, origin1, userContext1) + + const result1 = await script.evaluateFunctionInBrowsingContext(context1.id, GET_CURRENT_GEOLOCATION, true) + const geolocation1 = result1.result.value + const currentLatitude1 = geolocation1.latitude.value + const currentLongitude1 = geolocation1.longitude.value + + assert.strictEqual(currentLatitude1, 45.5) + assert.strictEqual(currentLongitude1, -122.4194) + + await driver.switchTo().window(context2.id) + + await context2.navigate(Pages.blankPage, 'complete') + const origin2 = (await script.callFunctionInBrowsingContext(context1.id, GET_ORIGIN, true, [])).result.value + await permission.setPermission({name: 'geolocation'}, PermissionState.GRANTED, origin2, userContext2) + + const result2 = await script.evaluateFunctionInBrowsingContext(context2.id, GET_CURRENT_GEOLOCATION, true) + const geolocation2 = result2.result.value + const currentLatitude2 = geolocation2.latitude.value + const currentLongitude2 = geolocation2.longitude.value + + assert.strictEqual(currentLatitude2, 45.5) + assert.strictEqual(currentLongitude2, -122.4194) + }) + + it('can override geolocation with error', async function () { + const windowHandle = await driver.getWindowHandle() + const context = await BrowsingContext(driver, { browsingContextId: windowHandle }) + await context.navigate(Pages.blankPage, 'complete') + + const origin = await script.callFunctionInBrowsingContext(context.id, GET_ORIGIN, true, []) + const originValue = origin.result.value + + await permission.setPermission({name: 'geolocation'}, PermissionState.GRANTED, originValue) + + await emulation.setGeolocationOverride(GeolocationPositionError, windowHandle) + + const result = await script.evaluateFunctionInBrowsingContext( + context.id, + GET_CURRENT_GEOLOCATION, + true + ) + + const geolocation = result.result.value + + assert.ok( + Object.hasOwn(geolocation, 'error'), + `Expected geolocation to have 'error' key, but got: ${JSON.stringify(geolocation)}` + ) + }) + }) + }, + {browsers: [Browser.CHROME]}, +) From 0b722abe0fdf364c001319c355887205d13921f1 Mon Sep 17 00:00:00 2001 From: Puja Jagani Date: Wed, 13 Aug 2025 18:33:50 +0530 Subject: [PATCH 2/5] Fix formatting --- .../bidi/emulation/emulation.js | 20 +++++------ .../test/bidi/emulation_test.js | 36 +++++++++---------- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/javascript/selenium-webdriver/bidi/emulation/emulation.js b/javascript/selenium-webdriver/bidi/emulation/emulation.js index 1f4bb76fe41d1..ddfdd6c6bdada 100644 --- a/javascript/selenium-webdriver/bidi/emulation/emulation.js +++ b/javascript/selenium-webdriver/bidi/emulation/emulation.js @@ -15,10 +15,10 @@ // specific language governing permissions and limitations // under the License. -const GeolocationCoordinates = require("./geolocationCoordinates"); +const GeolocationCoordinates = require('./geolocationCoordinates') const GeolocationPositionError = Object.freeze({ - type: 'positionUnavailable' + type: 'positionUnavailable', }) class Emulation { @@ -34,7 +34,7 @@ class Emulation { this.bidi = await this._driver.getBidi() } - async setGeolocationOverride(value, contexts= undefined, userContexts = undefined) { + async setGeolocationOverride(value, contexts = undefined, userContexts = undefined) { const map = new Map() if (value instanceof GeolocationCoordinates) { @@ -42,20 +42,18 @@ class Emulation { } else if (value === GeolocationPositionError) { map.set('error', value) } else { - throw new Error( - 'First argument must be a GeoCoordinates instance or GeolocationPositionError constant' - ) + throw new Error('First argument must be a GeoCoordinates instance or GeolocationPositionError constant') } - if (contexts !== undefined && typeof contexts === 'string') { + if (contexts !== undefined && typeof contexts === 'string') { contexts = [contexts] - } else if (contexts !== undefined && !Array.isArray(contexts)) { + } else if (contexts !== undefined && !Array.isArray(contexts)) { throw new Error('contexts must be a string or an array of strings') } map.set('contexts', contexts) - if (userContexts !== undefined && typeof userContexts === 'string') { + if (userContexts !== undefined && typeof userContexts === 'string') { userContexts = [userContexts] } else if (userContexts !== undefined && !Array.isArray(userContexts)) { throw new Error('userContexts must be a string or an array of strings') @@ -65,7 +63,7 @@ class Emulation { const command = { method: 'emulation.setGeolocationOverride', - params: Object.fromEntries(map) + params: Object.fromEntries(map), } const response = await this.bidi.send(command) @@ -82,4 +80,4 @@ async function getEmulationInstance(driver) { return instance } -module.exports = { getEmulationInstance, GeolocationPositionError} +module.exports = { getEmulationInstance, GeolocationPositionError } diff --git a/javascript/selenium-webdriver/test/bidi/emulation_test.js b/javascript/selenium-webdriver/test/bidi/emulation_test.js index 644eb9bd1f611..9d11f33b30d78 100644 --- a/javascript/selenium-webdriver/test/bidi/emulation_test.js +++ b/javascript/selenium-webdriver/test/bidi/emulation_test.js @@ -18,15 +18,15 @@ 'use strict' const assert = require('node:assert') -const {Pages, suite} = require('../../lib/test') -const {Browser} = require('selenium-webdriver') +const { Pages, suite, ignore } = require('../../lib/test') +const { Browser } = require('selenium-webdriver') const BrowserBiDi = require('selenium-webdriver/bidi/browser') const getScriptManager = require('selenium-webdriver/bidi/scriptManager') -const {GeolocationPositionError, getEmulationInstance } = require('selenium-webdriver/bidi/emulation/emulation') +const { GeolocationPositionError, getEmulationInstance } = require('selenium-webdriver/bidi/emulation/emulation') const GeolocationCoordinates = require('selenium-webdriver/bidi/emulation/geolocationCoordinates') const BrowsingContext = require('selenium-webdriver/bidi/browsingContext') -const {getPermissionInstance, PermissionState} = require('selenium-webdriver/bidi/external/permissions') -const {CreateContextParameters} = require('selenium-webdriver/bidi/createContextParameters') +const { getPermissionInstance, PermissionState } = require('selenium-webdriver/bidi/external/permissions') +const { CreateContextParameters } = require('selenium-webdriver/bidi/createContextParameters') suite( function (env) { @@ -71,12 +71,12 @@ suite( it('can override geolocation for browsing context', async function () { const windowHandle = await driver.getWindowHandle() - const context = await BrowsingContext(driver, {browsingContextId: windowHandle}) + const context = await BrowsingContext(driver, { browsingContextId: windowHandle }) await context.navigate(Pages.blankPage, 'complete') const origin = await script.callFunctionInBrowsingContext(context.id, GET_ORIGIN, true, []) const originValue = origin.result.value - await permission.setPermission({name: 'geolocation'}, PermissionState.GRANTED, originValue) + await permission.setPermission({ name: 'geolocation' }, PermissionState.GRANTED, originValue) const coords = new GeolocationCoordinates(37.7749, -122.4194) @@ -99,9 +99,9 @@ suite( const createParams1 = new CreateContextParameters().userContext(userContext1) const createParams2 = new CreateContextParameters().userContext(userContext2) - const context1 = await BrowsingContext(driver, {type: 'tab', createParameters: createParams1}) + const context1 = await BrowsingContext(driver, { type: 'tab', createParameters: createParams1 }) - const context2 = await BrowsingContext(driver, {type: 'tab', createParameters: createParams2}) + const context2 = await BrowsingContext(driver, { type: 'tab', createParameters: createParams2 }) const coords = new GeolocationCoordinates(45.5, -122.4194) @@ -111,7 +111,7 @@ suite( await context1.navigate(Pages.blankPage, 'complete') const origin1 = (await script.callFunctionInBrowsingContext(context1.id, GET_ORIGIN, true, [])).result.value - await permission.setPermission({name: 'geolocation'}, PermissionState.GRANTED, origin1, userContext1) + await permission.setPermission({ name: 'geolocation' }, PermissionState.GRANTED, origin1, userContext1) const result1 = await script.evaluateFunctionInBrowsingContext(context1.id, GET_CURRENT_GEOLOCATION, true) const geolocation1 = result1.result.value @@ -125,7 +125,7 @@ suite( await context2.navigate(Pages.blankPage, 'complete') const origin2 = (await script.callFunctionInBrowsingContext(context1.id, GET_ORIGIN, true, [])).result.value - await permission.setPermission({name: 'geolocation'}, PermissionState.GRANTED, origin2, userContext2) + await permission.setPermission({ name: 'geolocation' }, PermissionState.GRANTED, origin2, userContext2) const result2 = await script.evaluateFunctionInBrowsingContext(context2.id, GET_CURRENT_GEOLOCATION, true) const geolocation2 = result2.result.value @@ -136,7 +136,7 @@ suite( assert.strictEqual(currentLongitude2, -122.4194) }) - it('can override geolocation with error', async function () { + ignore(env.browsers(Browser.FIREFOX)).it('can override geolocation with error', async function () { const windowHandle = await driver.getWindowHandle() const context = await BrowsingContext(driver, { browsingContextId: windowHandle }) await context.navigate(Pages.blankPage, 'complete') @@ -144,24 +144,20 @@ suite( const origin = await script.callFunctionInBrowsingContext(context.id, GET_ORIGIN, true, []) const originValue = origin.result.value - await permission.setPermission({name: 'geolocation'}, PermissionState.GRANTED, originValue) + await permission.setPermission({ name: 'geolocation' }, PermissionState.GRANTED, originValue) await emulation.setGeolocationOverride(GeolocationPositionError, windowHandle) - const result = await script.evaluateFunctionInBrowsingContext( - context.id, - GET_CURRENT_GEOLOCATION, - true - ) + const result = await script.evaluateFunctionInBrowsingContext(context.id, GET_CURRENT_GEOLOCATION, true) const geolocation = result.result.value assert.ok( Object.hasOwn(geolocation, 'error'), - `Expected geolocation to have 'error' key, but got: ${JSON.stringify(geolocation)}` + `Expected geolocation to have 'error' key, but got: ${JSON.stringify(geolocation)}`, ) }) }) }, - {browsers: [Browser.CHROME]}, + { browsers: [Browser.FIREFOX, Browser.CHROME, Browser.EDGE] }, ) From 9f804f8c9919d9da9e2f7e9bdfc0ae7684f64dc6 Mon Sep 17 00:00:00 2001 From: Puja Jagani Date: Thu, 14 Aug 2025 10:41:19 +0530 Subject: [PATCH 3/5] Fix formatting --- .../test/bidi/emulation_test.js | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/javascript/selenium-webdriver/test/bidi/emulation_test.js b/javascript/selenium-webdriver/test/bidi/emulation_test.js index 9d11f33b30d78..e021d8d1a2ab0 100644 --- a/javascript/selenium-webdriver/test/bidi/emulation_test.js +++ b/javascript/selenium-webdriver/test/bidi/emulation_test.js @@ -31,31 +31,30 @@ const { CreateContextParameters } = require('selenium-webdriver/bidi/createConte suite( function (env) { describe('BiDi Emulation', function () { - let driver, emulation, permission, script, browser + let driver, emulation, permission, script, browser, windowHandle const GET_ORIGIN = '() => {return window.location.origin;}' const GET_CURRENT_GEOLOCATION = ` - new Promise((resolve) => { - navigator.geolocation.getCurrentPosition( - position => { - const coords = position.coords; - resolve({ - latitude: coords.latitude, - longitude: coords.longitude, - accuracy: coords.accuracy, - altitude: coords.altitude, - altitudeAccuracy: coords.altitudeAccuracy, - heading: coords.heading, - speed: coords.speed, - timestamp: position.timestamp - }); - }, - error => resolve({ error: error.message }), - { enableHighAccuracy: false, timeout: 10000, maximumAge: 0 } - ); - }) -` + new Promise((resolve) => { + navigator.geolocation.getCurrentPosition( + position => { + const coords = position.coords; + resolve({ + latitude: coords.latitude, + longitude: coords.longitude, + accuracy: coords.accuracy, + altitude: coords.altitude, + altitudeAccuracy: coords.altitudeAccuracy, + heading: coords.heading, + speed: coords.speed, + timestamp: position.timestamp + }); + }, + error => resolve({ error: error.message }), + { enableHighAccuracy: false, timeout: 10000, maximumAge: 0 } + ); + })` beforeEach(async function () { driver = await env.builder().build() @@ -63,6 +62,7 @@ suite( permission = await getPermissionInstance(driver) script = await getScriptManager([], driver) browser = await BrowserBiDi(driver) + windowHandle = await driver.getWindowHandle() }) afterEach(function () { @@ -70,7 +70,6 @@ suite( }) it('can override geolocation for browsing context', async function () { - const windowHandle = await driver.getWindowHandle() const context = await BrowsingContext(driver, { browsingContextId: windowHandle }) await context.navigate(Pages.blankPage, 'complete') @@ -137,7 +136,6 @@ suite( }) ignore(env.browsers(Browser.FIREFOX)).it('can override geolocation with error', async function () { - const windowHandle = await driver.getWindowHandle() const context = await BrowsingContext(driver, { browsingContextId: windowHandle }) await context.navigate(Pages.blankPage, 'complete') From 81a74231a0122d5a1c3f73745c99d378152f9ab0 Mon Sep 17 00:00:00 2001 From: Puja Jagani Date: Thu, 14 Aug 2025 10:51:55 +0530 Subject: [PATCH 4/5] Add docs --- .../bidi/emulation/emulation.js | 12 ++++ .../bidi/emulation/geolocationCoordinates.js | 59 +++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/javascript/selenium-webdriver/bidi/emulation/emulation.js b/javascript/selenium-webdriver/bidi/emulation/emulation.js index ddfdd6c6bdada..ccd6f0f047bf9 100644 --- a/javascript/selenium-webdriver/bidi/emulation/emulation.js +++ b/javascript/selenium-webdriver/bidi/emulation/emulation.js @@ -21,7 +21,12 @@ const GeolocationPositionError = Object.freeze({ type: 'positionUnavailable', }) +/** + * Emulation class provides methods to interact with browser emulation features + * via the BiDi protocol. + */ class Emulation { + constructor(driver) { this._driver = driver } @@ -34,6 +39,13 @@ class Emulation { this.bidi = await this._driver.getBidi() } + /** + * Overrides the browser's geolocation. + * @param {GeolocationCoordinates|GeolocationPositionError} value - Geolocation coordinates or error constant. + * @param {string|string[]|undefined} contexts - Optional browsing context(s) to apply the override. + * @param {string|string[]|undefined} userContexts - Optional user context(s) to apply the override. + * @throws {Error} If arguments are invalid or the BiDi command fails. + */ async setGeolocationOverride(value, contexts = undefined, userContexts = undefined) { const map = new Map() diff --git a/javascript/selenium-webdriver/bidi/emulation/geolocationCoordinates.js b/javascript/selenium-webdriver/bidi/emulation/geolocationCoordinates.js index 3d393a1c51d33..042958b629695 100644 --- a/javascript/selenium-webdriver/bidi/emulation/geolocationCoordinates.js +++ b/javascript/selenium-webdriver/bidi/emulation/geolocationCoordinates.js @@ -15,9 +15,34 @@ // specific language governing permissions and limitations // under the License. +/** + * Represents geolocation coordinates with optional accuracy, altitude, heading, and speed. + * + * Example usage: + * const coords = new GeolocationCoordinates(37.7749, -122.4194) + * .accuracy(10) + * .altitude(30) + * .heading(90) + * .speed(5); + * + * Properties: + * - latitude: number (-90.0 to 90.0) + * - longitude: number (-180.0 to 180.0) + * - accuracy: number >= 0.0 (optional) + * - altitude: number or null (optional) + * - altitudeAccuracy: number >= 0.0 or null (optional) + * - heading: number 0.0 to 360.0 or null (optional) + * - speed: number >= 0.0 or null (optional) + */ class GeolocationCoordinates { #map = new Map() + /** + * Constructs a GeolocationCoordinates instance. + * @param {number} latitude - Latitude (-90.0 to 90.0). + * @param {number} longitude - Longitude (-180.0 to 180.0). + * @throws {Error} If latitude or longitude are out of bounds. + */ constructor(latitude, longitude) { if (typeof latitude !== 'number' || latitude < -90.0 || latitude > 90.0) { throw new Error(`Latitude must be a number between -90.0 and 90.0. Received: '${latitude}'`) @@ -30,6 +55,12 @@ class GeolocationCoordinates { this.#map.set('longitude', longitude) } + /** + * Sets the accuracy in meters. + * @param {number} value - Accuracy (>= 0.0). + * @returns {GeolocationCoordinates} This instance. + * @throws {Error} If value is invalid. + */ accuracy(value) { if (typeof value !== 'number' || value < 0.0) { throw new Error(`Accuracy must be a number >= 0.0. Received: '${value}'`) @@ -38,6 +69,12 @@ class GeolocationCoordinates { return this } + /** + * Sets the altitude in meters. + * @param {number|null} value - Altitude or null. + * @returns {GeolocationCoordinates} This instance. + * @throws {Error} If value is invalid. + */ altitude(value) { if (value !== null && typeof value !== 'number') { throw new Error(`Altitude must be a number. Received: '${value}'`) @@ -46,6 +83,12 @@ class GeolocationCoordinates { return this } + /** + * Sets the altitude accuracy in meters. + * @param {number|null} value - Altitude accuracy (>= 0.0) or null. + * @returns {GeolocationCoordinates} This instance. + * @throws {Error} If value is invalid. + */ altitudeAccuracy(value) { if (value !== null && (typeof value !== 'number' || value < 0.0)) { throw new Error(`AltitudeAccuracy must be a number >= 0.0. Received: '${value}'`) @@ -54,6 +97,12 @@ class GeolocationCoordinates { return this } + /** + * Sets the heading in degrees. + * @param {number|null} value - Heading (0.0 to 360.0) or null. + * @returns {GeolocationCoordinates} This instance. + * @throws {Error} If value is invalid. + */ heading(value) { if (value !== null && (typeof value !== 'number' || value < 0.0 || value > 360.0)) { throw new Error(`Heading must be a number between 0.0 and 360.0. Received: '${value}'`) @@ -62,6 +111,12 @@ class GeolocationCoordinates { return this } + /** + * Sets the speed in meters per second. + * @param {number|null} value - Speed (>= 0.0) or null. + * @returns {GeolocationCoordinates} This instance. + * @throws {Error} If value is invalid. + */ speed(value) { if (value !== null && (typeof value !== 'number' || value < 0.0)) { throw new Error(`Speed must be a number >= 0.0. Received: '${value}'`) @@ -70,6 +125,10 @@ class GeolocationCoordinates { return this } + /** + * Returns the internal map of coordinate properties. + * @returns {Map} Map of properties. + */ asMap() { return this.#map } From 3e420f5735bf6cf35ad1951d6d3ccb4d74373a93 Mon Sep 17 00:00:00 2001 From: Puja Jagani Date: Thu, 14 Aug 2025 16:20:01 +0530 Subject: [PATCH 5/5] Fix formatting --- javascript/selenium-webdriver/bidi/emulation/emulation.js | 1 - .../bidi/emulation/geolocationCoordinates.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/javascript/selenium-webdriver/bidi/emulation/emulation.js b/javascript/selenium-webdriver/bidi/emulation/emulation.js index ccd6f0f047bf9..5fcbbda39eb14 100644 --- a/javascript/selenium-webdriver/bidi/emulation/emulation.js +++ b/javascript/selenium-webdriver/bidi/emulation/emulation.js @@ -26,7 +26,6 @@ const GeolocationPositionError = Object.freeze({ * via the BiDi protocol. */ class Emulation { - constructor(driver) { this._driver = driver } diff --git a/javascript/selenium-webdriver/bidi/emulation/geolocationCoordinates.js b/javascript/selenium-webdriver/bidi/emulation/geolocationCoordinates.js index 042958b629695..d5fe04c11f9a1 100644 --- a/javascript/selenium-webdriver/bidi/emulation/geolocationCoordinates.js +++ b/javascript/selenium-webdriver/bidi/emulation/geolocationCoordinates.js @@ -17,14 +17,14 @@ /** * Represents geolocation coordinates with optional accuracy, altitude, heading, and speed. - * + * * Example usage: * const coords = new GeolocationCoordinates(37.7749, -122.4194) * .accuracy(10) * .altitude(30) * .heading(90) * .speed(5); - * + * * Properties: * - latitude: number (-90.0 to 90.0) * - longitude: number (-180.0 to 180.0)