Skip to content

Commit 42c06ec

Browse files
authored
[py][bidi]: implement bidi permissions module (#15830)
* implement bidi permissions module * add tests Thank you @navin772
1 parent 4b7f476 commit 42c06ec

File tree

3 files changed

+254
-0
lines changed

3 files changed

+254
-0
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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+
from typing import Optional, Union
19+
20+
from selenium.webdriver.common.bidi.common import command_builder
21+
22+
23+
class PermissionState:
24+
"""Represents the possible permission states."""
25+
26+
GRANTED = "granted"
27+
DENIED = "denied"
28+
PROMPT = "prompt"
29+
30+
31+
class PermissionDescriptor:
32+
"""Represents a permission descriptor."""
33+
34+
def __init__(self, name: str):
35+
self.name = name
36+
37+
def to_dict(self) -> dict:
38+
return {"name": self.name}
39+
40+
41+
class Permissions:
42+
"""
43+
BiDi implementation of the permissions module.
44+
"""
45+
46+
def __init__(self, conn):
47+
self.conn = conn
48+
49+
def set_permission(
50+
self,
51+
descriptor: Union[str, PermissionDescriptor],
52+
state: str,
53+
origin: str,
54+
user_context: Optional[str] = None,
55+
) -> None:
56+
"""Sets a permission state for a given permission descriptor.
57+
58+
Parameters:
59+
-----------
60+
descriptor: The permission name (str) or PermissionDescriptor object.
61+
Examples: "geolocation", "camera", "microphone"
62+
state: The permission state (granted, denied, prompt).
63+
origin: The origin for which the permission is set.
64+
user_context: The user context id (optional).
65+
66+
Raises:
67+
------
68+
ValueError: If the permission state is invalid.
69+
"""
70+
if state not in [PermissionState.GRANTED, PermissionState.DENIED, PermissionState.PROMPT]:
71+
valid_states = f"{PermissionState.GRANTED}, {PermissionState.DENIED}, {PermissionState.PROMPT}"
72+
raise ValueError(f"Invalid permission state. Must be one of: {valid_states}")
73+
74+
if isinstance(descriptor, str):
75+
permission_descriptor = PermissionDescriptor(descriptor)
76+
else:
77+
permission_descriptor = descriptor
78+
79+
params = {
80+
"descriptor": permission_descriptor.to_dict(),
81+
"state": state,
82+
"origin": origin,
83+
}
84+
85+
if user_context is not None:
86+
params["userContext"] = user_context
87+
88+
self.conn.execute(command_builder("permissions.setPermission", params))

py/selenium/webdriver/remote/webdriver.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from selenium.webdriver.common.bidi.browser import Browser
4343
from selenium.webdriver.common.bidi.browsing_context import BrowsingContext
4444
from selenium.webdriver.common.bidi.network import Network
45+
from selenium.webdriver.common.bidi.permissions import Permissions
4546
from selenium.webdriver.common.bidi.script import Script
4647
from selenium.webdriver.common.bidi.session import Session
4748
from selenium.webdriver.common.bidi.storage import Storage
@@ -265,6 +266,7 @@ def __init__(
265266
self._browsing_context = None
266267
self._storage = None
267268
self._webextension = None
269+
self._permissions = None
268270

269271
def __repr__(self):
270272
return f'<{type(self).__module__}.{type(self).__name__} (session="{self.session_id}")>'
@@ -1339,6 +1341,28 @@ def storage(self):
13391341

13401342
return self._storage
13411343

1344+
@property
1345+
def permissions(self):
1346+
"""Returns a permissions module object for BiDi permissions commands.
1347+
1348+
Returns:
1349+
--------
1350+
Permissions: an object containing access to BiDi permissions commands.
1351+
1352+
Examples:
1353+
---------
1354+
>>> from selenium.webdriver.common.bidi.permissions import PermissionDescriptor, PermissionState
1355+
>>> descriptor = PermissionDescriptor("geolocation")
1356+
>>> driver.permissions.set_permission(descriptor, PermissionState.GRANTED, "https://example.com")
1357+
"""
1358+
if not self._websocket_connection:
1359+
self._start_bidi()
1360+
1361+
if self._permissions is None:
1362+
self._permissions = Permissions(self._websocket_connection)
1363+
1364+
return self._permissions
1365+
13421366
@property
13431367
def webextension(self):
13441368
"""Returns a webextension module object for BiDi webextension commands.
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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+
import pytest
19+
20+
from selenium.webdriver.common.bidi.permissions import PermissionDescriptor, PermissionState
21+
from selenium.webdriver.common.window import WindowTypes
22+
23+
24+
def get_origin(driver):
25+
"""Get the current window origin."""
26+
return driver.execute_script("return window.location.origin;")
27+
28+
29+
def get_geolocation_permission(driver):
30+
"""Get the geolocation permission state."""
31+
script = """
32+
const callback = arguments[arguments.length - 1];
33+
navigator.permissions.query({ name: 'geolocation' }).then(permission => {
34+
callback(permission.state);
35+
}).catch(error => {
36+
callback(null);
37+
});
38+
"""
39+
return driver.execute_async_script(script)
40+
41+
42+
def test_permissions_initialized(driver):
43+
"""Test that the permissions module is initialized properly."""
44+
assert driver.permissions is not None
45+
46+
47+
def test_can_set_permission_to_granted(driver, pages):
48+
"""Test setting permission to granted state."""
49+
pages.load("blank.html")
50+
51+
origin = get_origin(driver)
52+
53+
# Set geolocation permission to granted
54+
driver.permissions.set_permission("geolocation", PermissionState.GRANTED, origin)
55+
56+
result = get_geolocation_permission(driver)
57+
assert result == PermissionState.GRANTED
58+
59+
60+
def test_can_set_permission_to_denied(driver, pages):
61+
"""Test setting permission to denied state."""
62+
pages.load("blank.html")
63+
64+
origin = get_origin(driver)
65+
66+
# Set geolocation permission to denied
67+
driver.permissions.set_permission("geolocation", PermissionState.DENIED, origin)
68+
69+
result = get_geolocation_permission(driver)
70+
assert result == PermissionState.DENIED
71+
72+
73+
def test_can_set_permission_to_prompt(driver, pages):
74+
"""Test setting permission to prompt state."""
75+
pages.load("blank.html")
76+
77+
origin = get_origin(driver)
78+
79+
# First set to denied, then to prompt since most of the time the default state is prompt
80+
driver.permissions.set_permission("geolocation", PermissionState.DENIED, origin)
81+
driver.permissions.set_permission("geolocation", PermissionState.PROMPT, origin)
82+
83+
result = get_geolocation_permission(driver)
84+
assert result == PermissionState.PROMPT
85+
86+
87+
def test_can_set_permission_for_user_context(driver, pages):
88+
"""Test setting permission for a specific user context."""
89+
# Create a user context
90+
user_context = driver.browser.create_user_context()
91+
92+
context_id = driver.browsing_context.create(type=WindowTypes.TAB, user_context=user_context)
93+
94+
# Navigate both contexts to the same page
95+
pages.load("blank.html")
96+
original_window = driver.current_window_handle
97+
driver.switch_to.window(context_id)
98+
pages.load("blank.html")
99+
100+
origin = get_origin(driver)
101+
102+
# Get original permission states
103+
driver.switch_to.window(original_window)
104+
original_permission = get_geolocation_permission(driver)
105+
106+
driver.switch_to.window(context_id)
107+
108+
# Set permission only for the user context using PermissionDescriptor
109+
descriptor = PermissionDescriptor("geolocation")
110+
driver.permissions.set_permission(descriptor, PermissionState.GRANTED, origin, user_context)
111+
112+
# Check that the original window's permission hasn't changed
113+
driver.switch_to.window(original_window)
114+
updated_original_permission = get_geolocation_permission(driver)
115+
assert updated_original_permission == original_permission
116+
117+
# Check that the new context's permission was updated
118+
driver.switch_to.window(context_id)
119+
updated_new_context_permission = get_geolocation_permission(driver)
120+
assert updated_new_context_permission == PermissionState.GRANTED
121+
122+
driver.browsing_context.close(context_id)
123+
driver.browser.remove_user_context(user_context)
124+
125+
126+
def test_invalid_permission_state_raises_error(driver, pages):
127+
"""Test that invalid permission state raises ValueError."""
128+
pages.load("blank.html")
129+
origin = get_origin(driver)
130+
131+
# set permission using PermissionDescriptor
132+
descriptor = PermissionDescriptor("geolocation")
133+
134+
with pytest.raises(ValueError, match="Invalid permission state"):
135+
driver.permissions.set_permission(descriptor, "invalid_state", origin)
136+
137+
138+
def test_permission_states_constants():
139+
"""Test that permission state constants are correctly defined."""
140+
assert PermissionState.GRANTED == "granted"
141+
assert PermissionState.DENIED == "denied"
142+
assert PermissionState.PROMPT == "prompt"

0 commit comments

Comments
 (0)