Skip to content

Commit 39b221d

Browse files
navin772pujaganiharsha509
authored andcommitted
[js][bidi]: implement permissions module commands in JS (SeleniumHQ#15304)
* implement BiDi permission module for JS * add tests for permission module * ignore chrome browser tests * modify test as per chrome default permissions * run `format.sh` * remove initial permission assertions as it is different for browsers/envvironments * move `permissions.js` to `external/` dir --------- Co-authored-by: Puja Jagani <[email protected]> Co-authored-by: Sri Harsha <[email protected]>
1 parent 71d3926 commit 39b221d

File tree

3 files changed

+221
-0
lines changed

3 files changed

+221
-0
lines changed

javascript/node/selenium-webdriver/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ js_library(
3838
"devtools/*.js",
3939
"common/*.js",
4040
"bidi/*.js",
41+
"bidi/external/*.js",
4142
]),
4243
deps = [
4344
":node_modules/@bazel/runfiles",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
const PermissionState = Object.freeze({
19+
GRANTED: 'granted',
20+
DENIED: 'denied',
21+
PROMPT: 'prompt',
22+
})
23+
24+
class Permission {
25+
constructor(driver) {
26+
this._driver = driver
27+
}
28+
29+
async init() {
30+
if (!(await this._driver.getCapabilities()).get('webSocketUrl')) {
31+
throw Error('WebDriver instance must support BiDi protocol')
32+
}
33+
34+
this.bidi = await this._driver.getBidi()
35+
}
36+
37+
/**
38+
* Sets a permission state for a given permission descriptor.
39+
* @param {Object} permissionDescriptor The permission descriptor.
40+
* @param {string} state The permission state (granted, denied, prompt).
41+
* @param {string} origin The origin for which the permission is set.
42+
* @param {string} [userContext] The user context id (optional).
43+
* @returns {Promise<void>}
44+
*/
45+
async setPermission(permissionDescriptor, state, origin, userContext = null) {
46+
if (!Object.values(PermissionState).includes(state)) {
47+
throw new Error(`Invalid permission state. Must be one of: ${Object.values(PermissionState).join(', ')}`)
48+
}
49+
50+
const command = {
51+
method: 'permissions.setPermission',
52+
params: {
53+
descriptor: permissionDescriptor,
54+
state: state,
55+
origin: origin,
56+
},
57+
}
58+
59+
if (userContext) {
60+
command.params.userContext = userContext
61+
}
62+
63+
await this.bidi.send(command)
64+
}
65+
}
66+
67+
async function getPermissionInstance(driver) {
68+
let instance = new Permission(driver)
69+
await instance.init()
70+
return instance
71+
}
72+
73+
module.exports = { getPermissionInstance, PermissionState }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
'use strict'
19+
20+
const assert = require('node:assert')
21+
const { Pages, suite } = require('../../lib/test')
22+
const { Browser } = require('selenium-webdriver')
23+
const BrowserBiDi = require('selenium-webdriver/bidi/browser')
24+
const getScriptManager = require('selenium-webdriver/bidi/scriptManager')
25+
const { getPermissionInstance, PermissionState } = require('selenium-webdriver/bidi/external/permissions')
26+
const BrowsingContext = require('selenium-webdriver/bidi/browsingContext')
27+
const { CreateContextParameters } = require('selenium-webdriver/bidi/createContextParameters')
28+
29+
suite(
30+
function (env) {
31+
describe('BiDi Permissions', function () {
32+
let driver, permission, browser, script
33+
34+
const GET_GEOLOCATION_PERMISSION =
35+
"async () => { const perm = await navigator.permissions.query({ name: 'geolocation' }); return perm.state; }"
36+
const GET_ORIGIN = '() => {return window.location.origin;}'
37+
38+
beforeEach(async function () {
39+
driver = await env.builder().build()
40+
permission = await getPermissionInstance(driver)
41+
browser = await BrowserBiDi(driver)
42+
script = await getScriptManager([], driver)
43+
})
44+
45+
afterEach(function () {
46+
return driver.quit()
47+
})
48+
49+
it('can set permission to granted', async function () {
50+
const context = await BrowsingContext(driver, { type: 'tab' })
51+
await context.navigate(Pages.blankPage, 'complete')
52+
53+
const origin = await script.callFunctionInBrowsingContext(context.id, GET_ORIGIN, true, [])
54+
const originValue = origin.result.value
55+
56+
await permission.setPermission({ name: 'geolocation' }, PermissionState.GRANTED, originValue)
57+
58+
const result = await script.callFunctionInBrowsingContext(context.id, GET_GEOLOCATION_PERMISSION, true, [])
59+
assert.strictEqual(result.result.value, PermissionState.GRANTED)
60+
})
61+
62+
it('can set permission to denied', async function () {
63+
const context = await BrowsingContext(driver, { type: 'tab' })
64+
await context.navigate(Pages.blankPage, 'complete')
65+
66+
const origin = await script.callFunctionInBrowsingContext(context.id, GET_ORIGIN, true, [])
67+
68+
const originValue = origin.result.value
69+
await permission.setPermission({ name: 'geolocation' }, PermissionState.DENIED, originValue)
70+
71+
const result = await script.callFunctionInBrowsingContext(context.id, GET_GEOLOCATION_PERMISSION, true, [])
72+
assert.strictEqual(result.result.value, PermissionState.DENIED)
73+
})
74+
75+
it('can set permission to prompt', async function () {
76+
const context = await BrowsingContext(driver, { type: 'tab' })
77+
await context.navigate(Pages.blankPage, 'complete')
78+
79+
const origin = await script.callFunctionInBrowsingContext(context.id, GET_ORIGIN, true, [])
80+
81+
const originValue = origin.result.value
82+
await permission.setPermission({ name: 'geolocation' }, PermissionState.DENIED, originValue)
83+
84+
await permission.setPermission({ name: 'geolocation' }, PermissionState.PROMPT, originValue)
85+
86+
const result = await script.callFunctionInBrowsingContext(context.id, GET_GEOLOCATION_PERMISSION, true, [])
87+
assert.strictEqual(result.result.value, PermissionState.PROMPT)
88+
})
89+
90+
it('can set permission for a user context', async function () {
91+
const userContext = await browser.createUserContext()
92+
93+
const context1 = await BrowsingContext(driver, { type: 'tab' })
94+
const context2 = await BrowsingContext(driver, {
95+
type: 'tab',
96+
createParameters: new CreateContextParameters().userContext(userContext),
97+
})
98+
99+
await context1.navigate(Pages.blankPage, 'complete')
100+
await context2.navigate(Pages.blankPage, 'complete')
101+
102+
const origin = await script.callFunctionInBrowsingContext(context1.id, GET_ORIGIN, true, [])
103+
const originValue = origin.result.value
104+
105+
// Get the actual permission states from each context
106+
const originalTabPermission = await script.callFunctionInBrowsingContext(
107+
context1.id,
108+
GET_GEOLOCATION_PERMISSION,
109+
true,
110+
[],
111+
)
112+
113+
const newTabPermission = await script.callFunctionInBrowsingContext(
114+
context2.id,
115+
GET_GEOLOCATION_PERMISSION,
116+
true,
117+
[],
118+
)
119+
120+
const originalTabState = originalTabPermission.result.value
121+
const newTabState = newTabPermission.result.value
122+
123+
// Set permission only for the user context
124+
await permission.setPermission({ name: 'geolocation' }, PermissionState.GRANTED, originValue, userContext)
125+
126+
// Check that the original tab's permission state hasn't changed
127+
const originalTabUpdatedPermission = await script.callFunctionInBrowsingContext(
128+
context1.id,
129+
GET_GEOLOCATION_PERMISSION,
130+
true,
131+
[],
132+
)
133+
assert.strictEqual(originalTabUpdatedPermission.result.value, originalTabState)
134+
135+
// Check that the new tab's permission state has been updated to GRANTED
136+
const newTabUpdatedPermission = await script.callFunctionInBrowsingContext(
137+
context2.id,
138+
GET_GEOLOCATION_PERMISSION,
139+
true,
140+
[],
141+
)
142+
assert.strictEqual(newTabUpdatedPermission.result.value, PermissionState.GRANTED)
143+
})
144+
})
145+
},
146+
{ browsers: [Browser.FIREFOX, Browser.CHROME, Browser.EDGE] },
147+
)

0 commit comments

Comments
 (0)