|
| 1 | +"""Translate-only FirefoxBrowser methods against an in-memory FakeBiDiConnection. |
| 2 | +
|
| 3 | +The BiDi counterpart of tests/cdp/unit/test_browser_commands.py: browser-level |
| 4 | +methods that turn a Python call into a WebDriver BiDi command and return simple |
| 5 | +state — download behaviour, cookies, user contexts, windows, permissions and |
| 6 | +request interception. Assertions check the command shape and resulting state, not |
| 7 | +that some command was merely emitted. Event-driven flows run against real Firefox |
| 8 | +in the integration suite. |
| 9 | +""" |
| 10 | + |
| 11 | +from __future__ import annotations |
| 12 | + |
| 13 | +import pytest |
| 14 | + |
| 15 | +from pydoll.browser.firefox.tab import BiDiTab |
| 16 | +from pydoll.protocol.types import DownloadBehavior, Permission |
| 17 | + |
| 18 | + |
| 19 | +@pytest.mark.asyncio |
| 20 | +async def test_set_download_behavior_allow_carries_destination(fake_bidi_browser, fake_bidi_conn): |
| 21 | + await fake_bidi_browser.set_download_behavior(DownloadBehavior.ALLOW, '/tmp/dl') |
| 22 | + behavior = fake_bidi_conn.last_command('browser.setDownloadBehavior')['params']['downloadBehavior'] |
| 23 | + assert behavior['type'] == 'allowed' |
| 24 | + assert behavior['destinationFolder'] == '/tmp/dl' |
| 25 | + |
| 26 | + |
| 27 | +@pytest.mark.asyncio |
| 28 | +async def test_set_download_behavior_deny(fake_bidi_browser, fake_bidi_conn): |
| 29 | + await fake_bidi_browser.set_download_behavior(DownloadBehavior.DENY) |
| 30 | + behavior = fake_bidi_conn.last_command('browser.setDownloadBehavior')['params']['downloadBehavior'] |
| 31 | + assert behavior['type'] == 'denied' |
| 32 | + |
| 33 | + |
| 34 | +@pytest.mark.asyncio |
| 35 | +async def test_set_download_path_uses_allow_with_path(fake_bidi_browser, fake_bidi_conn): |
| 36 | + await fake_bidi_browser.set_download_path('/tmp/here') |
| 37 | + behavior = fake_bidi_conn.last_command('browser.setDownloadBehavior')['params']['downloadBehavior'] |
| 38 | + assert behavior == {'type': 'allowed', 'destinationFolder': '/tmp/here'} |
| 39 | + |
| 40 | + |
| 41 | +@pytest.mark.asyncio |
| 42 | +async def test_set_cookies_sends_set_cookie_per_cookie(fake_bidi_browser, fake_bidi_conn): |
| 43 | + await fake_bidi_browser.set_cookies([{'name': 'a', 'value': '1', 'domain': 'example.com'}]) |
| 44 | + cookie = fake_bidi_conn.last_command('storage.setCookie')['params']['cookie'] |
| 45 | + assert cookie['name'] == 'a' |
| 46 | + assert cookie['value'] == {'type': 'string', 'value': '1'} |
| 47 | + assert cookie['domain'] == 'example.com' |
| 48 | + |
| 49 | + |
| 50 | +@pytest.mark.asyncio |
| 51 | +async def test_get_cookies_returns_generic_cookies(fake_bidi_browser, fake_bidi_conn): |
| 52 | + fake_bidi_conn.set_result( |
| 53 | + 'storage.getCookies', |
| 54 | + {'cookies': [{ |
| 55 | + 'name': 'sid', 'value': {'type': 'string', 'value': 'xyz'}, |
| 56 | + 'domain': 'example.com', 'path': '/', 'size': 6, |
| 57 | + 'httpOnly': True, 'secure': True, 'sameSite': 'lax', |
| 58 | + }]}, |
| 59 | + ) |
| 60 | + cookies = await fake_bidi_browser.get_cookies() |
| 61 | + assert cookies[0]['name'] == 'sid' |
| 62 | + assert cookies[0]['value'] == 'xyz' |
| 63 | + assert cookies[0]['httpOnly'] is True |
| 64 | + |
| 65 | + |
| 66 | +@pytest.mark.asyncio |
| 67 | +async def test_delete_all_cookies_sends_command(fake_bidi_browser, fake_bidi_conn): |
| 68 | + await fake_bidi_browser.delete_all_cookies() |
| 69 | + assert fake_bidi_conn.commands_for('storage.deleteCookies') |
| 70 | + |
| 71 | + |
| 72 | +@pytest.mark.asyncio |
| 73 | +async def test_create_browser_context_returns_user_context_id(fake_bidi_browser, fake_bidi_conn): |
| 74 | + fake_bidi_conn.set_result('browser.createUserContext', {'userContext': 'ctx-1'}) |
| 75 | + context_id = await fake_bidi_browser.create_browser_context() |
| 76 | + assert context_id == 'ctx-1' |
| 77 | + |
| 78 | + |
| 79 | +@pytest.mark.asyncio |
| 80 | +async def test_create_browser_context_with_proxy_carries_manual_config( |
| 81 | + fake_bidi_browser, fake_bidi_conn |
| 82 | +): |
| 83 | + fake_bidi_conn.set_result('browser.createUserContext', {'userContext': 'ctx-2'}) |
| 84 | + await fake_bidi_browser.create_browser_context(proxy_server='http://127.0.0.1:8080') |
| 85 | + proxy = fake_bidi_conn.last_command('browser.createUserContext')['params']['proxy'] |
| 86 | + assert proxy['proxyType'] == 'manual' |
| 87 | + assert proxy['httpProxy'] == 'http://127.0.0.1:8080' |
| 88 | + |
| 89 | + |
| 90 | +@pytest.mark.asyncio |
| 91 | +async def test_delete_browser_context_sends_command(fake_bidi_browser, fake_bidi_conn): |
| 92 | + await fake_bidi_browser.delete_browser_context('ctx-1') |
| 93 | + assert fake_bidi_conn.last_command('browser.removeUserContext')['params']['userContext'] == 'ctx-1' |
| 94 | + |
| 95 | + |
| 96 | +@pytest.mark.asyncio |
| 97 | +async def test_get_browser_contexts_lists_ids(fake_bidi_browser, fake_bidi_conn): |
| 98 | + fake_bidi_conn.set_result( |
| 99 | + 'browser.getUserContexts', |
| 100 | + {'userContexts': [{'userContext': 'default'}, {'userContext': 'ctx-1'}]}, |
| 101 | + ) |
| 102 | + assert await fake_bidi_browser.get_browser_contexts() == ['default', 'ctx-1'] |
| 103 | + |
| 104 | + |
| 105 | +@pytest.mark.asyncio |
| 106 | +async def test_get_opened_tabs_builds_tabs_from_tree(fake_bidi_browser, fake_bidi_conn): |
| 107 | + fake_bidi_conn.set_result( |
| 108 | + 'browsingContext.getTree', |
| 109 | + {'contexts': [{'context': 'c1'}, {'context': 'c2'}]}, |
| 110 | + ) |
| 111 | + tabs = await fake_bidi_browser.get_opened_tabs() |
| 112 | + assert all(isinstance(tab, BiDiTab) for tab in tabs) |
| 113 | + assert [tab._context_id for tab in tabs] == ['c1', 'c2'] |
| 114 | + |
| 115 | + |
| 116 | +@pytest.mark.asyncio |
| 117 | +async def test_new_tab_creates_context_and_returns_tab(fake_bidi_browser, fake_bidi_conn): |
| 118 | + fake_bidi_conn.set_result('browsingContext.create', {'context': 'new-ctx'}) |
| 119 | + tab = await fake_bidi_browser.new_tab() |
| 120 | + assert isinstance(tab, BiDiTab) |
| 121 | + assert tab._context_id == 'new-ctx' |
| 122 | + |
| 123 | + |
| 124 | +@pytest.mark.asyncio |
| 125 | +async def test_get_version_reads_cached_capabilities(fake_bidi_browser): |
| 126 | + fake_bidi_browser._capabilities = { |
| 127 | + 'browserName': 'Firefox', 'browserVersion': '142.0', 'userAgent': 'UA/1', |
| 128 | + } |
| 129 | + version = await fake_bidi_browser.get_version() |
| 130 | + assert version['browserName'] == 'Firefox' |
| 131 | + assert version['browserVersion'] == '142.0' |
| 132 | + assert version['userAgent'] == 'UA/1' |
| 133 | + |
| 134 | + |
| 135 | +@pytest.mark.asyncio |
| 136 | +async def test_set_window_maximized_sets_state(fake_bidi_browser, fake_bidi_conn): |
| 137 | + fake_bidi_conn.set_result( |
| 138 | + 'browser.getClientWindows', {'clientWindows': [{'clientWindow': 'w1'}]} |
| 139 | + ) |
| 140 | + await fake_bidi_browser.set_window_maximized() |
| 141 | + command = fake_bidi_conn.last_command('browser.setClientWindowState') |
| 142 | + assert command['params']['clientWindow'] == 'w1' |
| 143 | + assert command['params']['state'] == 'maximized' |
| 144 | + |
| 145 | + |
| 146 | +@pytest.mark.asyncio |
| 147 | +async def test_set_window_bounds_carries_dimensions(fake_bidi_browser, fake_bidi_conn): |
| 148 | + fake_bidi_conn.set_result( |
| 149 | + 'browser.getClientWindows', {'clientWindows': [{'clientWindow': 'w1'}]} |
| 150 | + ) |
| 151 | + await fake_bidi_browser.set_window_bounds({'width': 800, 'height': 600}) |
| 152 | + params = fake_bidi_conn.last_command('browser.setClientWindowState')['params'] |
| 153 | + assert params['width'] == 800 |
| 154 | + assert params['height'] == 600 |
| 155 | + |
| 156 | + |
| 157 | +@pytest.mark.asyncio |
| 158 | +async def test_grant_permissions_maps_name_and_requires_origin(fake_bidi_browser, fake_bidi_conn): |
| 159 | + await fake_bidi_browser.grant_permissions( |
| 160 | + [Permission.GEOLOCATION], origin='https://example.com' |
| 161 | + ) |
| 162 | + params = fake_bidi_conn.last_command('permissions.setPermission')['params'] |
| 163 | + assert params['descriptor']['name'] == 'geolocation' |
| 164 | + assert params['origin'] == 'https://example.com' |
| 165 | + assert params['state'] == 'granted' |
| 166 | + |
| 167 | + |
| 168 | +@pytest.mark.asyncio |
| 169 | +async def test_grant_permissions_without_origin_raises(fake_bidi_browser): |
| 170 | + with pytest.raises(ValueError): |
| 171 | + await fake_bidi_browser.grant_permissions([Permission.GEOLOCATION]) |
| 172 | + |
| 173 | + |
| 174 | +@pytest.mark.asyncio |
| 175 | +async def test_grant_permissions_warns_on_unsupported(fake_bidi_browser): |
| 176 | + with pytest.warns(UserWarning): |
| 177 | + await fake_bidi_browser.grant_permissions( |
| 178 | + [Permission.PROTECTED_MEDIA_IDENTIFIER], origin='https://example.com' |
| 179 | + ) |
| 180 | + |
| 181 | + |
| 182 | +@pytest.mark.asyncio |
| 183 | +async def test_reset_permissions_resets_previously_granted(fake_bidi_browser, fake_bidi_conn): |
| 184 | + await fake_bidi_browser.grant_permissions( |
| 185 | + [Permission.GEOLOCATION], origin='https://example.com' |
| 186 | + ) |
| 187 | + await fake_bidi_browser.reset_permissions() |
| 188 | + states = [c['params']['state'] for c in fake_bidi_conn.commands_for('permissions.setPermission')] |
| 189 | + assert 'prompt' in states |
| 190 | + |
| 191 | + |
| 192 | +@pytest.mark.asyncio |
| 193 | +async def test_on_subscribes_and_registers_callback(fake_bidi_browser, fake_bidi_conn): |
| 194 | + async def cb(_event): |
| 195 | + return None |
| 196 | + |
| 197 | + await fake_bidi_browser.on('browsingContext.load', cb) |
| 198 | + assert fake_bidi_conn.commands_for('session.subscribe') |
| 199 | + |
| 200 | + |
| 201 | +@pytest.mark.asyncio |
| 202 | +async def test_intercept_requests_adds_intercept_and_subscribes(fake_bidi_browser, fake_bidi_conn): |
| 203 | + fake_bidi_conn.set_result('network.addIntercept', {'intercept': 'i1'}) |
| 204 | + |
| 205 | + async def cb(_req): |
| 206 | + return None |
| 207 | + |
| 208 | + intercept_id = await fake_bidi_browser.intercept_requests(cb) |
| 209 | + assert intercept_id == 'i1' |
| 210 | + subscribe = fake_bidi_conn.last_command('session.subscribe') |
| 211 | + assert 'network.beforeRequestSent' in subscribe['params']['events'] |
| 212 | + |
| 213 | + |
| 214 | +@pytest.mark.asyncio |
| 215 | +async def test_intercept_callback_receives_blocked_request(fake_bidi_browser, fake_bidi_conn): |
| 216 | + fake_bidi_conn.set_result('network.addIntercept', {'intercept': 'i1'}) |
| 217 | + received = [] |
| 218 | + |
| 219 | + async def cb(req): |
| 220 | + received.append(req) |
| 221 | + await req.continue_() |
| 222 | + |
| 223 | + await fake_bidi_browser.intercept_requests(cb) |
| 224 | + handler = next(iter(fake_bidi_conn._callbacks.values()))['callback'] |
| 225 | + await handler({'params': { |
| 226 | + 'isBlocked': True, |
| 227 | + 'request': {'request': 'r1', 'url': 'http://x/a', 'method': 'GET', 'headers': []}, |
| 228 | + }}) |
| 229 | + |
| 230 | + assert received and received[0].url == 'http://x/a' |
| 231 | + assert fake_bidi_conn.commands_for('network.continueRequest') |
| 232 | + |
| 233 | + |
| 234 | +@pytest.mark.asyncio |
| 235 | +async def test_intercept_callback_ignores_unblocked_request(fake_bidi_browser, fake_bidi_conn): |
| 236 | + fake_bidi_conn.set_result('network.addIntercept', {'intercept': 'i1'}) |
| 237 | + received = [] |
| 238 | + |
| 239 | + async def cb(req): |
| 240 | + received.append(req) |
| 241 | + |
| 242 | + await fake_bidi_browser.intercept_requests(cb) |
| 243 | + handler = next(iter(fake_bidi_conn._callbacks.values()))['callback'] |
| 244 | + await handler({'params': {'isBlocked': False, 'request': {'request': 'r1', 'url': 'http://x'}}}) |
| 245 | + assert received == [] |
| 246 | + |
| 247 | + |
| 248 | +@pytest.mark.asyncio |
| 249 | +async def test_remove_intercept_unsubscribes_on_last(fake_bidi_browser, fake_bidi_conn): |
| 250 | + fake_bidi_conn.set_result('network.addIntercept', {'intercept': 'i1'}) |
| 251 | + |
| 252 | + async def cb(_req): |
| 253 | + return None |
| 254 | + |
| 255 | + intercept_id = await fake_bidi_browser.intercept_requests(cb) |
| 256 | + await fake_bidi_browser.remove_intercept(intercept_id) |
| 257 | + assert fake_bidi_conn.last_command('network.removeIntercept')['params']['intercept'] == 'i1' |
| 258 | + assert fake_bidi_conn.commands_for('session.unsubscribe') |
| 259 | + |
| 260 | + |
| 261 | +@pytest.mark.asyncio |
| 262 | +async def test_browser_set_cookies_carries_optional_attributes(fake_bidi_browser, fake_bidi_conn): |
| 263 | + await fake_bidi_browser.set_cookies([{ |
| 264 | + 'name': 'a', 'value': '1', 'domain': 'example.com', |
| 265 | + 'path': '/p', 'httpOnly': True, 'secure': True, 'expiry': 99, 'sameSite': 'Lax', |
| 266 | + }]) |
| 267 | + cookie = fake_bidi_conn.last_command('storage.setCookie')['params']['cookie'] |
| 268 | + assert cookie['path'] == '/p' |
| 269 | + assert cookie['httpOnly'] is True |
| 270 | + assert cookie['secure'] is True |
| 271 | + assert cookie['expiry'] == 99 |
| 272 | + assert cookie['sameSite'] == 'lax' |
| 273 | + |
| 274 | + |
| 275 | +@pytest.mark.asyncio |
| 276 | +async def test_set_download_path_delegates_to_allow(fake_bidi_browser, fake_bidi_conn): |
| 277 | + await fake_bidi_browser.set_download_path('/tmp/dl') |
| 278 | + behavior = fake_bidi_conn.last_command('browser.setDownloadBehavior')['params']['downloadBehavior'] |
| 279 | + assert behavior['type'] == 'allowed' |
| 280 | + assert behavior['destinationFolder'] == '/tmp/dl' |
| 281 | + |
| 282 | + |
| 283 | +@pytest.mark.asyncio |
| 284 | +async def test_delete_all_cookies_with_context_uses_partition(fake_bidi_browser, fake_bidi_conn): |
| 285 | + await fake_bidi_browser.delete_all_cookies(browser_context_id='ctx-1') |
| 286 | + params = fake_bidi_conn.last_command('storage.deleteCookies')['params'] |
| 287 | + assert params['partition']['userContext'] == 'ctx-1' |
| 288 | + |
| 289 | + |
| 290 | +@pytest.mark.asyncio |
| 291 | +async def test_set_window_minimized_sets_state(fake_bidi_browser, fake_bidi_conn): |
| 292 | + fake_bidi_conn.set_result( |
| 293 | + 'browser.getClientWindows', {'clientWindows': [{'clientWindow': 'w1'}]} |
| 294 | + ) |
| 295 | + await fake_bidi_browser.set_window_minimized() |
| 296 | + assert fake_bidi_conn.last_command('browser.setClientWindowState')['params']['state'] == 'minimized' |
| 297 | + |
| 298 | + |
| 299 | +@pytest.mark.asyncio |
| 300 | +async def test_create_browser_context_warns_on_proxy_bypass(fake_bidi_browser, fake_bidi_conn): |
| 301 | + fake_bidi_conn.set_result('browser.createUserContext', {'userContext': 'c'}) |
| 302 | + with pytest.warns(UserWarning): |
| 303 | + await fake_bidi_browser.create_browser_context(proxy_bypass_list='localhost') |
| 304 | + |
| 305 | + |
| 306 | +@pytest.mark.asyncio |
| 307 | +async def test_reset_permissions_filters_by_context(fake_bidi_browser, fake_bidi_conn): |
| 308 | + await fake_bidi_browser.grant_permissions( |
| 309 | + [Permission.GEOLOCATION], origin='https://a.com', browser_context_id='ctx-1' |
| 310 | + ) |
| 311 | + await fake_bidi_browser.grant_permissions( |
| 312 | + [Permission.NOTIFICATIONS], origin='https://b.com', browser_context_id='ctx-2' |
| 313 | + ) |
| 314 | + fake_bidi_conn.commands.clear() |
| 315 | + await fake_bidi_browser.reset_permissions(browser_context_id='ctx-1') |
| 316 | + reset = fake_bidi_conn.commands_for('permissions.setPermission') |
| 317 | + assert len(reset) == 1 |
| 318 | + assert reset[0]['params']['origin'] == 'https://a.com' |
| 319 | + |
| 320 | + |
| 321 | +@pytest.mark.asyncio |
| 322 | +async def test_connect_establishes_session_and_returns_first_tab(fake_bidi_browser, fake_bidi_conn): |
| 323 | + fake_bidi_conn.set_result('session.new', {'sessionId': 's1', 'capabilities': {}}) |
| 324 | + fake_bidi_conn.set_result('browsingContext.getTree', {'contexts': [{'context': 'c1'}]}) |
| 325 | + tab = await fake_bidi_browser.connect('ws://127.0.0.1/session/abc') |
| 326 | + assert tab._context_id == 'c1' |
| 327 | + assert fake_bidi_browser._session_id == 's1' |
| 328 | + |
| 329 | + |
| 330 | +@pytest.mark.asyncio |
| 331 | +async def test_connect_opens_new_tab_when_no_contexts(fake_bidi_browser, fake_bidi_conn): |
| 332 | + fake_bidi_conn.set_result('session.new', {'sessionId': 's1', 'capabilities': {}}) |
| 333 | + fake_bidi_conn.set_result('browsingContext.getTree', {'contexts': []}) |
| 334 | + fake_bidi_conn.set_result('browsingContext.create', {'context': 'fresh'}) |
| 335 | + tab = await fake_bidi_browser.connect('ws://127.0.0.1/session/abc') |
| 336 | + assert tab._context_id == 'fresh' |
0 commit comments