From 7f87a4822805c8f8934c73b7cf4ca70086bbef1d Mon Sep 17 00:00:00 2001 From: Navin Chandra Date: Wed, 19 Feb 2025 12:27:39 +0530 Subject: [PATCH 1/7] implement BiDi permission module for JS --- .../selenium-webdriver/bidi/permissions.js | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 javascript/node/selenium-webdriver/bidi/permissions.js diff --git a/javascript/node/selenium-webdriver/bidi/permissions.js b/javascript/node/selenium-webdriver/bidi/permissions.js new file mode 100644 index 0000000000000..70b70c6e6500b --- /dev/null +++ b/javascript/node/selenium-webdriver/bidi/permissions.js @@ -0,0 +1,73 @@ +// 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 PermissionState = Object.freeze({ + GRANTED: 'granted', + DENIED: 'denied', + PROMPT: 'prompt', +}) + +class Permission { + 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() + } + + /** + * Sets a permission state for a given permission descriptor. + * @param {Object} permissionDescriptor The permission descriptor. + * @param {string} state The permission state (granted, denied, prompt). + * @param {string} origin The origin for which the permission is set. + * @param {string} [userContext] The user context id (optional). + * @returns {Promise} + */ + async setPermission(permissionDescriptor, state, origin, userContext = null) { + if (!Object.values(PermissionState).includes(state)) { + throw new Error(`Invalid permission state. Must be one of: ${Object.values(PermissionState).join(', ')}`) + } + + const command = { + method: 'permissions.setPermission', + params: { + descriptor: permissionDescriptor, + state: state, + origin: origin, + }, + } + + if (userContext) { + command.params.userContext = userContext + } + + await this.bidi.send(command) + } +} + +async function getPermissionInstance(driver) { + let instance = new Permission(driver) + await instance.init() + return instance +} + +module.exports = { getPermissionInstance, PermissionState } From e8662dec15ab0ecf0c88c33f6775dbe238cbc80c Mon Sep 17 00:00:00 2001 From: Navin Chandra Date: Wed, 19 Feb 2025 12:28:51 +0530 Subject: [PATCH 2/7] add tests for permission module --- .../test/bidi/permissions_test.js | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 javascript/node/selenium-webdriver/test/bidi/permissions_test.js diff --git a/javascript/node/selenium-webdriver/test/bidi/permissions_test.js b/javascript/node/selenium-webdriver/test/bidi/permissions_test.js new file mode 100644 index 0000000000000..abaf514fab9f4 --- /dev/null +++ b/javascript/node/selenium-webdriver/test/bidi/permissions_test.js @@ -0,0 +1,158 @@ +// 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 { getPermissionInstance, PermissionState } = require('selenium-webdriver/bidi/permissions') +const BrowsingContext = require('selenium-webdriver/bidi/browsingContext') +const { CreateContextParameters } = require('selenium-webdriver/bidi/createContextParameters') + +suite( + function (env) { + describe('BiDi Permissions', function () { + let driver, permission, browser, script + + const GET_GEOLOCATION_PERMISSION = + "async () => { const perm = await navigator.permissions.query({ name: 'geolocation' }); return perm.state; }" + const GET_ORIGIN = '() => {return window.location.origin;}' + + beforeEach(async function () { + driver = await env.builder().build() + permission = await getPermissionInstance(driver) + browser = await BrowserBiDi(driver) + script = await getScriptManager([], driver) + }) + + afterEach(function () { + return driver.quit() + }) + + it('can set permission to granted', async function () { + const context = await BrowsingContext(driver, { type: 'tab' }) + await context.navigate(Pages.blankPage, 'complete') + + const initialPermission = await script.callFunctionInBrowsingContext( + context.id, + GET_GEOLOCATION_PERMISSION, + true, + [], + ) + assert.strictEqual(initialPermission.result.value, 'prompt') + + const origin = await script.callFunctionInBrowsingContext(context.id, GET_ORIGIN, true, []) + const originValue = origin.result.value + + await permission.setPermission({ name: 'geolocation' }, PermissionState.GRANTED, originValue) + + const result = await script.callFunctionInBrowsingContext(context.id, GET_GEOLOCATION_PERMISSION, true, []) + assert.strictEqual(result.result.value, PermissionState.GRANTED) + }) + + it('can set permission to denied', async function () { + const context = await BrowsingContext(driver, { type: 'tab' }) + await context.navigate(Pages.blankPage, 'complete') + + const initialPermission = await script.callFunctionInBrowsingContext( + context.id, + GET_GEOLOCATION_PERMISSION, + true, + [], + ) + assert.strictEqual(initialPermission.result.value, 'prompt') + + const origin = await script.callFunctionInBrowsingContext(context.id, GET_ORIGIN, true, []) + + const originValue = origin.result.value + await permission.setPermission({ name: 'geolocation' }, PermissionState.DENIED, originValue) + + const result = await script.callFunctionInBrowsingContext(context.id, GET_GEOLOCATION_PERMISSION, true, []) + assert.strictEqual(result.result.value, PermissionState.DENIED) + }) + + it('can set permission to prompt', async function () { + const context = await BrowsingContext(driver, { type: 'tab' }) + 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.DENIED, originValue) + + await permission.setPermission({ name: 'geolocation' }, PermissionState.PROMPT, originValue) + + const result = await script.callFunctionInBrowsingContext(context.id, GET_GEOLOCATION_PERMISSION, true, []) + assert.strictEqual(result.result.value, PermissionState.PROMPT) + }) + + it('can set permission for a user context', async function () { + const userContext = await browser.createUserContext() + + const context1 = await BrowsingContext(driver, { type: 'tab' }) + const context2 = await BrowsingContext(driver, { + type: 'tab', + createParameters: new CreateContextParameters().userContext(userContext), + }) + + await context1.navigate(Pages.blankPage, 'complete') + await context2.navigate(Pages.blankPage, 'complete') + + const origin = await script.callFunctionInBrowsingContext(context1.id, GET_ORIGIN, true, []) + const originValue = origin.result.value + + const originalTabPermission = await script.callFunctionInBrowsingContext( + context1.id, + GET_GEOLOCATION_PERMISSION, + true, + [], + ) + assert.strictEqual(originalTabPermission.result.value, 'prompt') + + const newTabPermission = await script.callFunctionInBrowsingContext( + context2.id, + GET_GEOLOCATION_PERMISSION, + true, + [], + ) + assert.strictEqual(newTabPermission.result.value, 'prompt') + + await permission.setPermission({ name: 'geolocation' }, PermissionState.GRANTED, originValue, userContext) + + const originalTabUpdatedPermission = await script.callFunctionInBrowsingContext( + context1.id, + GET_GEOLOCATION_PERMISSION, + true, + [], + ) + assert.strictEqual(originalTabUpdatedPermission.result.value, 'prompt') + + const newTabUpdatedPermission = await script.callFunctionInBrowsingContext( + context2.id, + GET_GEOLOCATION_PERMISSION, + true, + [], + ) + assert.strictEqual(newTabUpdatedPermission.result.value, PermissionState.GRANTED) + }) + }) + }, + { browsers: [Browser.FIREFOX, Browser.CHROME, Browser.EDGE] }, +) From 58e6c9aa2d5c62dbc69f0a28739393e86766c629 Mon Sep 17 00:00:00 2001 From: Navin Chandra Date: Thu, 13 Mar 2025 00:35:11 +0530 Subject: [PATCH 3/7] ignore chrome browser tests --- .../node/selenium-webdriver/test/bidi/permissions_test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/javascript/node/selenium-webdriver/test/bidi/permissions_test.js b/javascript/node/selenium-webdriver/test/bidi/permissions_test.js index abaf514fab9f4..d3aa42f4cf285 100644 --- a/javascript/node/selenium-webdriver/test/bidi/permissions_test.js +++ b/javascript/node/selenium-webdriver/test/bidi/permissions_test.js @@ -22,13 +22,14 @@ 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 { ignore } = require('../../lib/test') const { getPermissionInstance, PermissionState } = require('selenium-webdriver/bidi/permissions') const BrowsingContext = require('selenium-webdriver/bidi/browsingContext') const { CreateContextParameters } = require('selenium-webdriver/bidi/createContextParameters') suite( function (env) { - describe('BiDi Permissions', function () { + ignore(env.browsers(Browser.CHROME)).describe('BiDi Permissions', function () { let driver, permission, browser, script const GET_GEOLOCATION_PERMISSION = From 2ab68ff874eb4e91bec225ddfbe09718c19d6ba0 Mon Sep 17 00:00:00 2001 From: Navin Chandra Date: Fri, 14 Mar 2025 01:06:57 +0530 Subject: [PATCH 4/7] modify test as per chrome default permissions --- .../test/bidi/permissions_test.js | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/javascript/node/selenium-webdriver/test/bidi/permissions_test.js b/javascript/node/selenium-webdriver/test/bidi/permissions_test.js index d3aa42f4cf285..d54a00ac71217 100644 --- a/javascript/node/selenium-webdriver/test/bidi/permissions_test.js +++ b/javascript/node/selenium-webdriver/test/bidi/permissions_test.js @@ -22,15 +22,14 @@ 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 { ignore } = require('../../lib/test') const { getPermissionInstance, PermissionState } = require('selenium-webdriver/bidi/permissions') const BrowsingContext = require('selenium-webdriver/bidi/browsingContext') const { CreateContextParameters } = require('selenium-webdriver/bidi/createContextParameters') suite( function (env) { - ignore(env.browsers(Browser.CHROME)).describe('BiDi Permissions', function () { - let driver, permission, browser, script + describe('BiDi Permissions', function () { + let driver, permission, browser, script, browserName const GET_GEOLOCATION_PERMISSION = "async () => { const perm = await navigator.permissions.query({ name: 'geolocation' }); return perm.state; }" @@ -41,6 +40,7 @@ suite( permission = await getPermissionInstance(driver) browser = await BrowserBiDi(driver) script = await getScriptManager([], driver) + browserName = (await driver.getCapabilities()).getBrowserName().toLowerCase() }) afterEach(function () { @@ -57,7 +57,10 @@ suite( true, [], ) - assert.strictEqual(initialPermission.result.value, 'prompt') + + // Chrome's default permission state is 'denied', while Firefox uses 'prompt' + const expectedDefaultState = ['chrome', 'microsoftedge'].includes(browserName) ? 'denied' : 'prompt' + assert.strictEqual(initialPermission.result.value, expectedDefaultState) const origin = await script.callFunctionInBrowsingContext(context.id, GET_ORIGIN, true, []) const originValue = origin.result.value @@ -78,7 +81,9 @@ suite( true, [], ) - assert.strictEqual(initialPermission.result.value, 'prompt') + // Chrome's default permission state is 'denied', while Firefox uses 'prompt' + const expectedDefaultState = ['chrome', 'microsoftedge'].includes(browserName) ? 'denied' : 'prompt' + assert.strictEqual(initialPermission.result.value, expectedDefaultState) const origin = await script.callFunctionInBrowsingContext(context.id, GET_ORIGIN, true, []) @@ -119,13 +124,13 @@ suite( const origin = await script.callFunctionInBrowsingContext(context1.id, GET_ORIGIN, true, []) const originValue = origin.result.value + // Get the actual permission states from each context const originalTabPermission = await script.callFunctionInBrowsingContext( context1.id, GET_GEOLOCATION_PERMISSION, true, [], ) - assert.strictEqual(originalTabPermission.result.value, 'prompt') const newTabPermission = await script.callFunctionInBrowsingContext( context2.id, @@ -133,18 +138,23 @@ suite( true, [], ) - assert.strictEqual(newTabPermission.result.value, 'prompt') + const originalTabState = originalTabPermission.result.value + const newTabState = newTabPermission.result.value + + // Set permission only for the user context await permission.setPermission({ name: 'geolocation' }, PermissionState.GRANTED, originValue, userContext) + // Check that the original tab's permission state hasn't changed const originalTabUpdatedPermission = await script.callFunctionInBrowsingContext( context1.id, GET_GEOLOCATION_PERMISSION, true, [], ) - assert.strictEqual(originalTabUpdatedPermission.result.value, 'prompt') + assert.strictEqual(originalTabUpdatedPermission.result.value, originalTabState) + // Check that the new tab's permission state has been updated to GRANTED const newTabUpdatedPermission = await script.callFunctionInBrowsingContext( context2.id, GET_GEOLOCATION_PERMISSION, From 30ae69072b50c4be4f124273ec937bc2247f1836 Mon Sep 17 00:00:00 2001 From: Navin Chandra Date: Fri, 14 Mar 2025 01:22:22 +0530 Subject: [PATCH 5/7] run `format.sh` --- .../selenium/webdriver/common/selenium_manager_tests.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/py/test/selenium/webdriver/common/selenium_manager_tests.py b/py/test/selenium/webdriver/common/selenium_manager_tests.py index 67239bf87a67d..9195badf0ed21 100644 --- a/py/test/selenium/webdriver/common/selenium_manager_tests.py +++ b/py/test/selenium/webdriver/common/selenium_manager_tests.py @@ -31,9 +31,10 @@ def test_gets_results(monkeypatch): expected_output = {"driver_path": "/path/to/driver"} lib_path = "selenium.webdriver.common.selenium_manager.SeleniumManager" - with mock.patch(lib_path + "._get_binary", return_value="/path/to/sm") as mock_get_binary, mock.patch( - lib_path + "._run", return_value=expected_output - ) as mock_run: + with ( + mock.patch(lib_path + "._get_binary", return_value="/path/to/sm") as mock_get_binary, + mock.patch(lib_path + "._run", return_value=expected_output) as mock_run, + ): SeleniumManager().binary_paths([]) mock_get_binary.assert_called_once() From d15c29b41eda8e7c25429167a53436e8a5fdb726 Mon Sep 17 00:00:00 2001 From: Navin Chandra Date: Fri, 14 Mar 2025 17:35:32 +0530 Subject: [PATCH 6/7] remove initial permission assertions as it is different for browsers/envvironments --- .../test/bidi/permissions_test.js | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/javascript/node/selenium-webdriver/test/bidi/permissions_test.js b/javascript/node/selenium-webdriver/test/bidi/permissions_test.js index d54a00ac71217..b20516cb65fa6 100644 --- a/javascript/node/selenium-webdriver/test/bidi/permissions_test.js +++ b/javascript/node/selenium-webdriver/test/bidi/permissions_test.js @@ -29,7 +29,7 @@ const { CreateContextParameters } = require('selenium-webdriver/bidi/createConte suite( function (env) { describe('BiDi Permissions', function () { - let driver, permission, browser, script, browserName + let driver, permission, browser, script const GET_GEOLOCATION_PERMISSION = "async () => { const perm = await navigator.permissions.query({ name: 'geolocation' }); return perm.state; }" @@ -40,7 +40,6 @@ suite( permission = await getPermissionInstance(driver) browser = await BrowserBiDi(driver) script = await getScriptManager([], driver) - browserName = (await driver.getCapabilities()).getBrowserName().toLowerCase() }) afterEach(function () { @@ -51,17 +50,6 @@ suite( const context = await BrowsingContext(driver, { type: 'tab' }) await context.navigate(Pages.blankPage, 'complete') - const initialPermission = await script.callFunctionInBrowsingContext( - context.id, - GET_GEOLOCATION_PERMISSION, - true, - [], - ) - - // Chrome's default permission state is 'denied', while Firefox uses 'prompt' - const expectedDefaultState = ['chrome', 'microsoftedge'].includes(browserName) ? 'denied' : 'prompt' - assert.strictEqual(initialPermission.result.value, expectedDefaultState) - const origin = await script.callFunctionInBrowsingContext(context.id, GET_ORIGIN, true, []) const originValue = origin.result.value @@ -75,16 +63,6 @@ suite( const context = await BrowsingContext(driver, { type: 'tab' }) await context.navigate(Pages.blankPage, 'complete') - const initialPermission = await script.callFunctionInBrowsingContext( - context.id, - GET_GEOLOCATION_PERMISSION, - true, - [], - ) - // Chrome's default permission state is 'denied', while Firefox uses 'prompt' - const expectedDefaultState = ['chrome', 'microsoftedge'].includes(browserName) ? 'denied' : 'prompt' - assert.strictEqual(initialPermission.result.value, expectedDefaultState) - const origin = await script.callFunctionInBrowsingContext(context.id, GET_ORIGIN, true, []) const originValue = origin.result.value From b40904dd8a2c8fa74d3e417ff62f6ccd228eec5a Mon Sep 17 00:00:00 2001 From: Navin Chandra Date: Mon, 17 Mar 2025 19:48:17 +0530 Subject: [PATCH 7/7] move `permissions.js` to `external/` dir --- javascript/node/selenium-webdriver/BUILD.bazel | 1 + .../node/selenium-webdriver/bidi/{ => external}/permissions.js | 0 .../node/selenium-webdriver/test/bidi/permissions_test.js | 2 +- 3 files changed, 2 insertions(+), 1 deletion(-) rename javascript/node/selenium-webdriver/bidi/{ => external}/permissions.js (100%) diff --git a/javascript/node/selenium-webdriver/BUILD.bazel b/javascript/node/selenium-webdriver/BUILD.bazel index 0e67097c8f114..81a21c716cf0e 100644 --- a/javascript/node/selenium-webdriver/BUILD.bazel +++ b/javascript/node/selenium-webdriver/BUILD.bazel @@ -39,6 +39,7 @@ js_library( "devtools/*.js", "common/*.js", "bidi/*.js", + "bidi/external/*.js", ]), deps = [ ":node_modules/@bazel/runfiles", diff --git a/javascript/node/selenium-webdriver/bidi/permissions.js b/javascript/node/selenium-webdriver/bidi/external/permissions.js similarity index 100% rename from javascript/node/selenium-webdriver/bidi/permissions.js rename to javascript/node/selenium-webdriver/bidi/external/permissions.js diff --git a/javascript/node/selenium-webdriver/test/bidi/permissions_test.js b/javascript/node/selenium-webdriver/test/bidi/permissions_test.js index b20516cb65fa6..e79b7f41346fc 100644 --- a/javascript/node/selenium-webdriver/test/bidi/permissions_test.js +++ b/javascript/node/selenium-webdriver/test/bidi/permissions_test.js @@ -22,7 +22,7 @@ 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 { getPermissionInstance, PermissionState } = require('selenium-webdriver/bidi/permissions') +const { getPermissionInstance, PermissionState } = require('selenium-webdriver/bidi/external/permissions') const BrowsingContext = require('selenium-webdriver/bidi/browsingContext') const { CreateContextParameters } = require('selenium-webdriver/bidi/createContextParameters')