Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/py/test/selenium/webdriver/common/bidi_browsing_context_tests.py
4078 views
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 base64
19
import concurrent.futures
20
import threading
21
import time
22
23
import pytest
24
25
from selenium.webdriver.common.bidi.browsing_context import ReadinessState
26
from selenium.webdriver.common.by import By
27
from selenium.webdriver.common.window import WindowTypes
28
from selenium.webdriver.support import expected_conditions as EC
29
from selenium.webdriver.support.ui import WebDriverWait
30
31
32
def create_alert_page(driver, pages):
33
"""Create a page with an alert."""
34
url = pages.url("alerts.html")
35
driver.get(url)
36
return url
37
38
39
def create_prompt_page(driver, pages):
40
"""Create a page with a prompt."""
41
url = pages.url("javascriptPage.html")
42
driver.get(url)
43
return url
44
45
46
def test_browsing_context_initialized(driver):
47
"""Test that the browsing context module is initialized properly."""
48
assert driver.browsing_context is not None
49
50
51
def test_create_window(driver):
52
"""Test creating a window."""
53
context_id = driver.browsing_context.create(type=WindowTypes.WINDOW)
54
assert context_id is not None
55
56
# Clean up
57
driver.browsing_context.close(context_id)
58
59
60
def test_create_window_with_reference_context(driver):
61
"""Test creating a window with a reference context."""
62
reference_context = driver.current_window_handle
63
context_id = driver.browsing_context.create(type=WindowTypes.WINDOW, reference_context=reference_context)
64
assert context_id is not None
65
66
# Clean up
67
driver.browsing_context.close(context_id)
68
69
70
def test_create_tab(driver):
71
"""Test creating a tab."""
72
context_id = driver.browsing_context.create(type=WindowTypes.TAB)
73
assert context_id is not None
74
75
# Clean up
76
driver.browsing_context.close(context_id)
77
78
79
def test_create_tab_with_reference_context(driver):
80
"""Test creating a tab with a reference context."""
81
reference_context = driver.current_window_handle
82
context_id = driver.browsing_context.create(type=WindowTypes.TAB, reference_context=reference_context)
83
assert context_id is not None
84
85
# Clean up
86
driver.browsing_context.close(context_id)
87
88
89
def test_create_context_with_all_parameters(driver):
90
"""Test creating a context with all parameters."""
91
reference_context = driver.current_window_handle
92
user_context = driver.browser.create_user_context()
93
94
context_id = driver.browsing_context.create(
95
type=WindowTypes.WINDOW, reference_context=reference_context, user_context=user_context, background=True
96
)
97
assert context_id is not None
98
assert context_id != reference_context
99
100
# Clean up
101
driver.browsing_context.close(context_id)
102
driver.browser.remove_user_context(user_context)
103
104
105
def test_navigate_to_url(driver, pages):
106
"""Test navigating to a URL."""
107
context_id = driver.browsing_context.create(type=WindowTypes.TAB)
108
109
url = pages.url("bidi/logEntryAdded.html")
110
result = driver.browsing_context.navigate(context=context_id, url=url)
111
112
assert context_id is not None
113
assert "/bidi/logEntryAdded.html" in result["url"]
114
115
# Clean up
116
driver.browsing_context.close(context_id)
117
118
119
def test_navigate_to_url_with_readiness_state(driver, pages):
120
"""Test navigating to a URL with readiness state."""
121
context_id = driver.browsing_context.create(type=WindowTypes.TAB)
122
123
url = pages.url("bidi/logEntryAdded.html")
124
result = driver.browsing_context.navigate(context=context_id, url=url, wait=ReadinessState.COMPLETE)
125
126
assert context_id is not None
127
assert "/bidi/logEntryAdded.html" in result["url"]
128
129
# Clean up
130
driver.browsing_context.close(context_id)
131
132
133
def test_get_tree_with_child(driver, pages):
134
"""Test getting the tree with a child."""
135
reference_context = driver.current_window_handle
136
137
url = pages.url("iframes.html")
138
driver.browsing_context.navigate(context=reference_context, url=url, wait=ReadinessState.COMPLETE)
139
140
context_info_list = driver.browsing_context.get_tree(root=reference_context)
141
142
assert len(context_info_list) == 1
143
info = context_info_list[0]
144
assert len(info.children) == 1
145
assert info.context == reference_context
146
assert "formPage.html" in info.children[0].url
147
148
149
def test_get_tree_with_depth(driver, pages):
150
"""Test getting the tree with depth."""
151
reference_context = driver.current_window_handle
152
153
url = pages.url("iframes.html")
154
driver.browsing_context.navigate(context=reference_context, url=url, wait=ReadinessState.COMPLETE)
155
156
context_info_list = driver.browsing_context.get_tree(root=reference_context, max_depth=0)
157
158
assert len(context_info_list) == 1
159
info = context_info_list[0]
160
assert info.children is None # since depth is 0
161
assert info.context == reference_context
162
163
164
def test_get_all_top_level_contexts(driver):
165
"""Test getting all top-level contexts."""
166
_ = driver.current_window_handle
167
window_handle = driver.browsing_context.create(type=WindowTypes.WINDOW)
168
169
context_info_list = driver.browsing_context.get_tree()
170
171
assert len(context_info_list) == 2
172
173
# Clean up
174
driver.browsing_context.close(window_handle)
175
176
177
def test_close_window(driver):
178
"""Test closing a window."""
179
window1 = driver.browsing_context.create(type=WindowTypes.WINDOW)
180
window2 = driver.browsing_context.create(type=WindowTypes.WINDOW)
181
182
driver.browsing_context.close(window2)
183
184
with pytest.raises(Exception):
185
driver.browsing_context.get_tree(root=window2)
186
187
# Clean up
188
driver.browsing_context.close(window1)
189
190
191
def test_close_tab(driver):
192
"""Test closing a tab."""
193
tab1 = driver.browsing_context.create(type=WindowTypes.TAB)
194
tab2 = driver.browsing_context.create(type=WindowTypes.TAB)
195
196
driver.browsing_context.close(tab2)
197
198
with pytest.raises(Exception):
199
driver.browsing_context.get_tree(root=tab2)
200
201
# Clean up
202
driver.browsing_context.close(tab1)
203
204
205
def test_activate_browsing_context(driver):
206
"""Test activating a browsing context."""
207
window1 = driver.current_window_handle
208
# 2nd window is focused
209
window2 = driver.browsing_context.create(type=WindowTypes.WINDOW)
210
211
# We did not switch the driver, so we are running the script to check focus on 1st window
212
assert not driver.execute_script("return document.hasFocus();")
213
214
driver.browsing_context.activate(window1)
215
216
assert driver.execute_script("return document.hasFocus();")
217
218
# Clean up
219
driver.browsing_context.close(window2)
220
221
222
def test_reload_browsing_context(driver, pages):
223
"""Test reloading a browsing context."""
224
context_id = driver.browsing_context.create(type=WindowTypes.TAB)
225
226
url = pages.url("bidi/logEntryAdded.html")
227
driver.browsing_context.navigate(context=context_id, url=url, wait=ReadinessState.COMPLETE)
228
229
reload_info = driver.browsing_context.reload(context=context_id)
230
231
assert "/bidi/logEntryAdded.html" in reload_info["url"]
232
233
# Clean up
234
driver.browsing_context.close(context_id)
235
236
237
def test_reload_with_readiness_state(driver, pages):
238
"""Test reloading with readiness state."""
239
context_id = driver.browsing_context.create(type=WindowTypes.TAB)
240
241
url = pages.url("bidi/logEntryAdded.html")
242
driver.browsing_context.navigate(context=context_id, url=url, wait=ReadinessState.COMPLETE)
243
244
reload_info = driver.browsing_context.reload(context=context_id, wait=ReadinessState.COMPLETE)
245
246
assert reload_info["navigation"] is not None
247
assert "/bidi/logEntryAdded.html" in reload_info["url"]
248
249
# Clean up
250
driver.browsing_context.close(context_id)
251
252
253
def test_handle_user_prompt(driver, pages):
254
"""Test handling a user prompt."""
255
context_id = driver.current_window_handle
256
257
create_alert_page(driver, pages)
258
259
driver.find_element(By.ID, "alert").click()
260
WebDriverWait(driver, 5).until(EC.alert_is_present())
261
262
driver.browsing_context.handle_user_prompt(context=context_id)
263
264
assert "Alerts" in driver.title
265
266
267
def test_accept_user_prompt(driver, pages):
268
"""Test accepting a user prompt."""
269
context_id = driver.current_window_handle
270
271
create_alert_page(driver, pages)
272
273
driver.find_element(By.ID, "alert").click()
274
WebDriverWait(driver, 5).until(EC.alert_is_present())
275
276
driver.browsing_context.handle_user_prompt(context=context_id, accept=True)
277
278
assert "Alerts" in driver.title
279
280
281
def test_dismiss_user_prompt(driver, pages):
282
"""Test dismissing a user prompt."""
283
context_id = driver.current_window_handle
284
285
create_alert_page(driver, pages)
286
287
driver.find_element(By.ID, "alert").click()
288
WebDriverWait(driver, 5).until(EC.alert_is_present())
289
290
driver.browsing_context.handle_user_prompt(context=context_id, accept=False)
291
292
assert "Alerts" in driver.title
293
294
295
def test_pass_user_text_to_prompt(driver, pages):
296
"""Test passing user text to a prompt."""
297
context_id = driver.current_window_handle
298
299
create_prompt_page(driver, pages)
300
301
driver.execute_script("prompt('Enter something')")
302
WebDriverWait(driver, 5).until(EC.alert_is_present())
303
304
user_text = "Selenium automates browsers"
305
306
driver.browsing_context.handle_user_prompt(context=context_id, user_text=user_text)
307
308
# Check if the text was entered (this is browser-dependent)
309
310
311
def test_capture_screenshot(driver, pages):
312
"""Test capturing a screenshot."""
313
context_id = driver.current_window_handle
314
315
driver.get(pages.url("simpleTest.html"))
316
317
screenshot = driver.browsing_context.capture_screenshot(context=context_id)
318
319
# Verify it's a valid base64 string
320
try:
321
base64.b64decode(screenshot)
322
is_valid = True
323
except Exception:
324
is_valid = False
325
326
assert is_valid
327
assert len(screenshot) > 0
328
329
330
def test_capture_screenshot_with_parameters(driver, pages):
331
"""Test capturing a screenshot with parameters."""
332
context_id = driver.current_window_handle
333
334
driver.get(pages.url("coordinates_tests/simple_page.html"))
335
element = driver.find_element(By.ID, "box")
336
337
rect = element.rect
338
339
clip = {"type": "box", "x": rect["x"], "y": rect["y"], "width": 5, "height": 5}
340
341
screenshot = driver.browsing_context.capture_screenshot(context=context_id, origin="document", clip=clip)
342
343
assert len(screenshot) > 0
344
345
346
def test_set_viewport(driver, pages):
347
"""Test setting the viewport."""
348
context_id = driver.current_window_handle
349
driver.get(pages.url("formPage.html"))
350
351
try:
352
driver.browsing_context.set_viewport(context=context_id, viewport={"width": 251, "height": 301})
353
354
viewport_size = driver.execute_script("return [window.innerWidth, window.innerHeight];")
355
356
assert viewport_size[0] == 251
357
assert viewport_size[1] == 301
358
finally:
359
driver.browsing_context.set_viewport(context=context_id, viewport=None, device_pixel_ratio=None)
360
361
362
def test_set_viewport_with_device_pixel_ratio(driver, pages):
363
"""Test setting the viewport with device pixel ratio."""
364
context_id = driver.current_window_handle
365
driver.get(pages.url("formPage.html"))
366
367
try:
368
driver.browsing_context.set_viewport(
369
context=context_id, viewport={"width": 252, "height": 302}, device_pixel_ratio=5
370
)
371
372
viewport_size = driver.execute_script("return [window.innerWidth, window.innerHeight];")
373
374
assert viewport_size[0] == 252
375
assert viewport_size[1] == 302
376
377
device_pixel_ratio = driver.execute_script("return window.devicePixelRatio")
378
379
assert device_pixel_ratio == 5
380
finally:
381
driver.browsing_context.set_viewport(context=context_id, viewport=None, device_pixel_ratio=None)
382
383
384
def test_set_viewport_with_no_args_doesnt_change_values(driver, pages):
385
"""Test setting the viewport with no args doesn't change viewport or device pixel ratio."""
386
context_id = driver.current_window_handle
387
driver.get(pages.url("formPage.html"))
388
389
try:
390
driver.browsing_context.set_viewport(
391
context=context_id, viewport={"width": 253, "height": 303}, device_pixel_ratio=6
392
)
393
394
driver.browsing_context.set_viewport(context=context_id)
395
396
viewport_size = driver.execute_script("return [window.innerWidth, window.innerHeight];")
397
398
assert viewport_size[0] == 253
399
assert viewport_size[1] == 303
400
401
device_pixel_ratio = driver.execute_script("return window.devicePixelRatio")
402
403
assert device_pixel_ratio == 6
404
finally:
405
driver.browsing_context.set_viewport(context=context_id, viewport=None, device_pixel_ratio=None)
406
407
408
def test_set_viewport_back_to_default(driver, pages):
409
"""Test resetting the viewport and device pixel ratio to defaults."""
410
context_id = driver.current_window_handle
411
driver.get(pages.url("formPage.html"))
412
413
default_viewport_size = driver.execute_script("return [window.innerWidth, window.innerHeight];")
414
default_device_pixel_ratio = driver.execute_script("return window.devicePixelRatio")
415
416
try:
417
driver.browsing_context.set_viewport(
418
context=context_id, viewport={"width": 254, "height": 304}, device_pixel_ratio=10
419
)
420
421
driver.browsing_context.set_viewport(context=context_id, viewport=None, device_pixel_ratio=None)
422
423
viewport_size = driver.execute_script("return [window.innerWidth, window.innerHeight];")
424
device_pixel_ratio = driver.execute_script("return window.devicePixelRatio")
425
426
assert viewport_size[0] == default_viewport_size[0]
427
assert viewport_size[1] == default_viewport_size[1]
428
assert device_pixel_ratio == default_device_pixel_ratio
429
finally:
430
driver.browsing_context.set_viewport(context=context_id, viewport=None, device_pixel_ratio=None)
431
432
433
def test_print_page(driver, pages):
434
"""Test printing a page."""
435
context_id = driver.current_window_handle
436
437
driver.get(pages.url("formPage.html"))
438
439
print_result = driver.browsing_context.print(context=context_id)
440
441
assert len(print_result) > 0
442
# Check if it's a valid PDF (starts with JVBERi which is the base64 encoding of %PDF)
443
assert "JVBERi" in print_result
444
445
446
def test_navigate_back_in_browser_history(driver, pages):
447
"""Test navigating back in the browser history."""
448
context_id = driver.current_window_handle
449
driver.browsing_context.navigate(context=context_id, url=pages.url("formPage.html"), wait=ReadinessState.COMPLETE)
450
451
# Navigate to another page by submitting a form
452
driver.find_element(By.ID, "imageButton").submit()
453
WebDriverWait(driver, 5).until(EC.title_is("We Arrive Here"))
454
455
driver.browsing_context.traverse_history(context=context_id, delta=-1)
456
WebDriverWait(driver, 5).until(EC.title_is("We Leave From Here"))
457
458
459
def test_navigate_forward_in_browser_history(driver, pages):
460
"""Test navigating forward in the browser history."""
461
context_id = driver.current_window_handle
462
driver.browsing_context.navigate(context=context_id, url=pages.url("formPage.html"), wait=ReadinessState.COMPLETE)
463
464
# Navigate to another page by submitting a form
465
driver.find_element(By.ID, "imageButton").submit()
466
WebDriverWait(driver, 5).until(EC.title_is("We Arrive Here"))
467
468
# Go back
469
driver.browsing_context.traverse_history(context=context_id, delta=-1)
470
WebDriverWait(driver, 5).until(EC.title_is("We Leave From Here"))
471
472
# Go forward
473
driver.browsing_context.traverse_history(context=context_id, delta=1)
474
WebDriverWait(driver, 5).until(EC.title_is("We Arrive Here"))
475
476
477
# Tests for locate nodes
478
def test_locate_nodes(driver, pages):
479
"""Test locating nodes with CSS selector."""
480
context_id = driver.current_window_handle
481
482
driver.get(pages.url("xhtmlTest.html"))
483
484
elements = driver.browsing_context.locate_nodes(context=context_id, locator={"type": "css", "value": "div"})
485
486
assert len(elements) > 0
487
488
489
def test_locate_nodes_with_css_locator(driver, pages):
490
"""Test locating nodes with specific CSS selector."""
491
context_id = driver.current_window_handle
492
493
driver.get(pages.url("xhtmlTest.html"))
494
495
elements = driver.browsing_context.locate_nodes(
496
context=context_id, locator={"type": "css", "value": "div.extraDiv, div.content"}, max_node_count=1
497
)
498
499
assert len(elements) >= 1
500
501
value = elements[0]
502
assert value["type"] == "node"
503
assert "value" in value
504
assert "localName" in value["value"]
505
assert value["value"]["localName"] == "div"
506
assert "attributes" in value["value"]
507
assert "class" in value["value"]["attributes"]
508
assert value["value"]["attributes"]["class"] == "content"
509
510
511
def test_locate_nodes_with_xpath_locator(driver, pages):
512
"""Test locating nodes with XPath selector."""
513
context_id = driver.current_window_handle
514
515
driver.get(pages.url("xhtmlTest.html"))
516
517
elements = driver.browsing_context.locate_nodes(
518
context=context_id, locator={"type": "xpath", "value": "/html/body/div[2]"}, max_node_count=1
519
)
520
521
assert len(elements) >= 1
522
523
value = elements[0]
524
assert value["type"] == "node"
525
assert "value" in value
526
assert "localName" in value["value"]
527
assert value["value"]["localName"] == "div"
528
assert "attributes" in value["value"]
529
assert "class" in value["value"]["attributes"]
530
assert value["value"]["attributes"]["class"] == "content"
531
532
533
@pytest.mark.xfail_firefox
534
def test_locate_nodes_with_inner_text(driver, pages):
535
"""Test locating nodes with innerText selector."""
536
context_id = driver.current_window_handle
537
538
driver.get(pages.url("xhtmlTest.html"))
539
540
elements = driver.browsing_context.locate_nodes(
541
context=context_id, locator={"type": "innerText", "value": "Spaced out"}, max_node_count=1
542
)
543
544
assert len(elements) >= 1
545
546
value = elements[0]
547
assert value["type"] == "node"
548
assert "value" in value
549
550
551
def test_locate_nodes_with_max_node_count(driver, pages):
552
"""Test locating nodes with maximum node count."""
553
context_id = driver.current_window_handle
554
555
driver.get(pages.url("xhtmlTest.html"))
556
557
elements = driver.browsing_context.locate_nodes(
558
context=context_id, locator={"type": "css", "value": "div"}, max_node_count=4
559
)
560
561
assert len(elements) == 4
562
563
564
def test_locate_nodes_given_start_nodes(driver, pages):
565
"""Test locating nodes with start nodes."""
566
context_id = driver.current_window_handle
567
568
driver.get(pages.url("formPage.html"))
569
570
form_elements = driver.browsing_context.locate_nodes(
571
context=context_id, locator={"type": "css", "value": "form[name='login']"}
572
)
573
574
assert len(form_elements) == 1
575
576
form_shared_id = form_elements[0]["sharedId"]
577
578
elements = driver.browsing_context.locate_nodes(
579
context=context_id,
580
locator={"type": "css", "value": "input"},
581
start_nodes=[{"sharedId": form_shared_id}],
582
max_node_count=50,
583
)
584
# The login form should have 3 input elements (email, age, and submit button)
585
assert len(elements) == 3
586
587
588
# Tests for event handlers
589
590
591
def test_add_event_handler_context_created(driver):
592
"""Test adding event handler for context_created event."""
593
events_received = []
594
595
def on_context_created(info):
596
events_received.append(info)
597
598
callback_id = driver.browsing_context.add_event_handler("context_created", on_context_created)
599
assert callback_id is not None
600
601
# Create a new context to trigger the event
602
context_id = driver.browsing_context.create(type=WindowTypes.TAB)
603
604
# Verify the event was received (might be > 1 since default context is also included)
605
assert len(events_received) >= 1
606
assert events_received[0].context == context_id or events_received[1].context == context_id
607
608
driver.browsing_context.close(context_id)
609
driver.browsing_context.remove_event_handler("context_created", callback_id)
610
611
612
def test_add_event_handler_context_destroyed(driver):
613
"""Test adding event handler for context_destroyed event."""
614
events_received = []
615
616
def on_context_destroyed(info):
617
events_received.append(info)
618
619
callback_id = driver.browsing_context.add_event_handler("context_destroyed", on_context_destroyed)
620
assert callback_id is not None
621
622
# Create and then close a context to trigger the event
623
context_id = driver.browsing_context.create(type=WindowTypes.TAB)
624
driver.browsing_context.close(context_id)
625
626
assert len(events_received) == 1
627
assert events_received[0].context == context_id
628
629
driver.browsing_context.remove_event_handler("context_destroyed", callback_id)
630
631
632
def test_add_event_handler_navigation_committed(driver, pages):
633
"""Test adding event handler for navigation_committed event."""
634
events_received = []
635
636
def on_navigation_committed(info):
637
events_received.append(info)
638
639
callback_id = driver.browsing_context.add_event_handler("navigation_committed", on_navigation_committed)
640
assert callback_id is not None
641
642
# Navigate to trigger the event
643
context_id = driver.current_window_handle
644
url = pages.url("simpleTest.html")
645
driver.browsing_context.navigate(context=context_id, url=url, wait=ReadinessState.COMPLETE)
646
647
assert len(events_received) >= 1
648
assert any(url in event.url for event in events_received)
649
650
driver.browsing_context.remove_event_handler("navigation_committed", callback_id)
651
652
653
def test_add_event_handler_dom_content_loaded(driver, pages):
654
"""Test adding event handler for dom_content_loaded event."""
655
events_received = []
656
657
def on_dom_content_loaded(info):
658
events_received.append(info)
659
660
callback_id = driver.browsing_context.add_event_handler("dom_content_loaded", on_dom_content_loaded)
661
assert callback_id is not None
662
663
# Navigate to trigger the event
664
context_id = driver.current_window_handle
665
url = pages.url("simpleTest.html")
666
driver.browsing_context.navigate(context=context_id, url=url, wait=ReadinessState.COMPLETE)
667
668
assert len(events_received) == 1
669
assert any("simpleTest" in event.url for event in events_received)
670
671
driver.browsing_context.remove_event_handler("dom_content_loaded", callback_id)
672
673
674
def test_add_event_handler_load(driver, pages):
675
"""Test adding event handler for load event."""
676
events_received = []
677
678
def on_load(info):
679
events_received.append(info)
680
681
callback_id = driver.browsing_context.add_event_handler("load", on_load)
682
assert callback_id is not None
683
684
context_id = driver.current_window_handle
685
url = pages.url("simpleTest.html")
686
driver.browsing_context.navigate(context=context_id, url=url, wait=ReadinessState.COMPLETE)
687
688
assert len(events_received) == 1
689
assert any("simpleTest" in event.url for event in events_received)
690
691
driver.browsing_context.remove_event_handler("load", callback_id)
692
693
694
def test_add_event_handler_navigation_started(driver, pages):
695
"""Test adding event handler for navigation_started event."""
696
events_received = []
697
698
def on_navigation_started(info):
699
events_received.append(info)
700
701
callback_id = driver.browsing_context.add_event_handler("navigation_started", on_navigation_started)
702
assert callback_id is not None
703
704
context_id = driver.current_window_handle
705
url = pages.url("simpleTest.html")
706
driver.browsing_context.navigate(context=context_id, url=url, wait=ReadinessState.COMPLETE)
707
708
assert len(events_received) == 1
709
assert any("simpleTest" in event.url for event in events_received)
710
711
driver.browsing_context.remove_event_handler("navigation_started", callback_id)
712
713
714
def test_add_event_handler_fragment_navigated(driver, pages):
715
"""Test adding event handler for fragment_navigated event."""
716
events_received = []
717
718
def on_fragment_navigated(info):
719
events_received.append(info)
720
721
callback_id = driver.browsing_context.add_event_handler("fragment_navigated", on_fragment_navigated)
722
assert callback_id is not None
723
724
# First navigate to a page
725
context_id = driver.current_window_handle
726
url = pages.url("linked_image.html")
727
driver.browsing_context.navigate(context=context_id, url=url, wait=ReadinessState.COMPLETE)
728
729
# Then navigate to the same page with a fragment to trigger the event
730
fragment_url = url + "#link"
731
driver.browsing_context.navigate(context=context_id, url=fragment_url, wait=ReadinessState.COMPLETE)
732
733
assert len(events_received) == 1
734
assert any("link" in event.url for event in events_received)
735
736
driver.browsing_context.remove_event_handler("fragment_navigated", callback_id)
737
738
739
@pytest.mark.xfail_firefox
740
def test_add_event_handler_navigation_failed(driver):
741
"""Test adding event handler for navigation_failed event."""
742
events_received = []
743
744
def on_navigation_failed(info):
745
events_received.append(info)
746
747
callback_id = driver.browsing_context.add_event_handler("navigation_failed", on_navigation_failed)
748
assert callback_id is not None
749
750
# Navigate to an invalid URL to trigger the event
751
context_id = driver.current_window_handle
752
try:
753
driver.browsing_context.navigate(context=context_id, url="http://invalid-domain-that-does-not-exist.test/")
754
except Exception:
755
# Expect an exception due to navigation failure
756
pass
757
758
assert len(events_received) == 1
759
assert events_received[0].url == "http://invalid-domain-that-does-not-exist.test/"
760
assert events_received[0].context == context_id
761
762
driver.browsing_context.remove_event_handler("navigation_failed", callback_id)
763
764
765
def test_add_event_handler_user_prompt_opened(driver, pages):
766
"""Test adding event handler for user_prompt_opened event."""
767
events_received = []
768
769
def on_user_prompt_opened(info):
770
events_received.append(info)
771
772
callback_id = driver.browsing_context.add_event_handler("user_prompt_opened", on_user_prompt_opened)
773
assert callback_id is not None
774
775
# Create an alert to trigger the event
776
create_alert_page(driver, pages)
777
driver.find_element(By.ID, "alert").click()
778
WebDriverWait(driver, 5).until(EC.alert_is_present())
779
WebDriverWait(driver, 5).until(lambda d: len(events_received) > 0)
780
781
assert len(events_received) == 1
782
assert events_received[0].type == "alert"
783
assert events_received[0].message == "cheese"
784
785
# Clean up the alert
786
driver.browsing_context.handle_user_prompt(context=driver.current_window_handle)
787
driver.browsing_context.remove_event_handler("user_prompt_opened", callback_id)
788
789
790
def test_add_event_handler_user_prompt_closed(driver, pages):
791
"""Test adding event handler for user_prompt_closed event."""
792
events_received = []
793
794
def on_user_prompt_closed(info):
795
events_received.append(info)
796
797
callback_id = driver.browsing_context.add_event_handler("user_prompt_closed", on_user_prompt_closed)
798
assert callback_id is not None
799
800
create_prompt_page(driver, pages)
801
driver.execute_script("prompt('Enter something')")
802
WebDriverWait(driver, 5).until(EC.alert_is_present())
803
804
driver.browsing_context.handle_user_prompt(
805
context=driver.current_window_handle, accept=True, user_text="test input"
806
)
807
808
assert len(events_received) == 1
809
assert events_received[0].accepted is True
810
assert events_received[0].user_text == "test input"
811
812
driver.browsing_context.remove_event_handler("user_prompt_closed", callback_id)
813
814
815
def test_add_event_handler_history_updated(driver, pages):
816
"""Test adding event handler for history_updated event."""
817
events_received = []
818
819
def on_history_updated(info):
820
events_received.append(info)
821
822
callback_id = driver.browsing_context.add_event_handler("history_updated", on_history_updated)
823
assert callback_id is not None
824
825
context_id = driver.current_window_handle
826
url = pages.url("simpleTest.html")
827
driver.browsing_context.navigate(context=context_id, url=url, wait=ReadinessState.COMPLETE)
828
829
# Use history.pushState to trigger history updated event
830
driver.script.execute("() => { history.pushState({}, '', '/new-path'); }")
831
WebDriverWait(driver, 5).until(lambda d: len(events_received) > 0)
832
833
assert len(events_received) >= 1
834
assert "/new-path" in events_received[0].url
835
assert events_received[0].context == context_id
836
837
driver.browsing_context.remove_event_handler("history_updated", callback_id)
838
839
840
def test_add_event_handler_download_will_begin(driver, pages):
841
"""Test adding event handler for download_will_begin event."""
842
events_received = []
843
844
def on_download_will_begin(info):
845
events_received.append(info)
846
847
callback_id = driver.browsing_context.add_event_handler("download_will_begin", on_download_will_begin)
848
assert callback_id is not None
849
850
# click on a download link to trigger the event
851
context_id = driver.current_window_handle
852
url = pages.url("downloads/download.html")
853
driver.browsing_context.navigate(context=context_id, url=url, wait=ReadinessState.COMPLETE)
854
855
download_xpath_file_1_txt = '//*[@id="file-1"]'
856
driver.find_element(By.XPATH, download_xpath_file_1_txt).click()
857
WebDriverWait(driver, 5).until(lambda d: len(events_received) > 0)
858
859
assert len(events_received) == 1
860
# filename maybe file_1.txt or file_1(1).txt depending on existing files in download dir
861
assert "file_1" in events_received[0].suggested_filename
862
863
driver.browsing_context.remove_event_handler("download_will_begin", callback_id)
864
865
866
def test_add_event_handler_download_end(driver, pages):
867
"""Test adding event handler for download_end event."""
868
events_received = []
869
870
def on_download_end(info):
871
events_received.append(info)
872
873
callback_id = driver.browsing_context.add_event_handler("download_end", on_download_end)
874
assert callback_id is not None
875
876
context_id = driver.current_window_handle
877
url = pages.url("downloads/download.html")
878
driver.browsing_context.navigate(context=context_id, url=url, wait=ReadinessState.COMPLETE)
879
880
driver.find_element(By.ID, "file-1").click()
881
882
driver.find_element(By.ID, "file-2").click()
883
WebDriverWait(driver, 5).until(lambda d: len(events_received) == 2)
884
885
assert len(events_received) == 2
886
887
for ev in events_received:
888
assert ev.download_params is not None
889
assert ev.download_params.status == "complete"
890
assert ev.download_params.context == context_id
891
assert ev.download_params.timestamp is not None
892
893
# we assert that atleast "file_1" is present in the downloaded file since multiple downloads
894
# will have numbered suffix like file_1 (1)
895
assert any(
896
"downloads/file_1.txt" in ev.download_params.url and "file_1" in ev.download_params.filepath
897
for ev in events_received
898
)
899
900
assert any(
901
"downloads/file_2.jpg" in ev.download_params.url and "file_2" in ev.download_params.filepath
902
for ev in events_received
903
)
904
905
driver.browsing_context.remove_event_handler("download_end", callback_id)
906
907
908
def test_add_event_handler_with_specific_contexts(driver):
909
"""Test adding event handler with specific browsing contexts."""
910
events_received = []
911
912
def on_context_created(info):
913
events_received.append(info)
914
915
context_id = driver.browsing_context.create(type=WindowTypes.TAB)
916
917
# Add event handler for specific context
918
callback_id = driver.browsing_context.add_event_handler(
919
"context_created", on_context_created, contexts=[context_id]
920
)
921
assert callback_id is not None
922
923
# Create another context (should trigger event)
924
new_context_id = driver.browsing_context.create(type=WindowTypes.TAB)
925
926
assert len(events_received) >= 1
927
928
driver.browsing_context.close(context_id)
929
driver.browsing_context.close(new_context_id)
930
driver.browsing_context.remove_event_handler("context_created", callback_id)
931
932
933
def test_remove_event_handler(driver):
934
"""Test removing event handler."""
935
events_received = []
936
937
def on_context_created(info):
938
events_received.append(info)
939
940
callback_id = driver.browsing_context.add_event_handler("context_created", on_context_created)
941
942
# Create a context to trigger the event
943
context_id_1 = driver.browsing_context.create(type=WindowTypes.TAB)
944
945
initial_events = len(events_received)
946
947
# Remove the event handler
948
driver.browsing_context.remove_event_handler("context_created", callback_id)
949
950
# Create another context (should not trigger event after removal)
951
context_id_2 = driver.browsing_context.create(type=WindowTypes.TAB)
952
953
# Verify no new events were received after removal
954
assert len(events_received) == initial_events
955
956
driver.browsing_context.close(context_id_1)
957
driver.browsing_context.close(context_id_2)
958
959
960
def test_multiple_event_handlers_same_event(driver):
961
"""Test adding multiple event handlers for the same event."""
962
events_received_1 = []
963
events_received_2 = []
964
965
def on_context_created_1(info):
966
events_received_1.append(info)
967
968
def on_context_created_2(info):
969
events_received_2.append(info)
970
971
# Add multiple event handlers for the same event
972
callback_id_1 = driver.browsing_context.add_event_handler("context_created", on_context_created_1)
973
callback_id_2 = driver.browsing_context.add_event_handler("context_created", on_context_created_2)
974
975
# Create a context to trigger both handlers
976
context_id = driver.browsing_context.create(type=WindowTypes.TAB)
977
978
# Verify both handlers received the event
979
assert len(events_received_1) >= 1
980
assert len(events_received_2) >= 1
981
# Check any of the events has the required context ID
982
assert any(event.context == context_id for event in events_received_1)
983
assert any(event.context == context_id for event in events_received_2)
984
985
driver.browsing_context.close(context_id)
986
driver.browsing_context.remove_event_handler("context_created", callback_id_1)
987
driver.browsing_context.remove_event_handler("context_created", callback_id_2)
988
989
990
def test_remove_specific_event_handler_multiple_handlers(driver):
991
"""Test removing a specific event handler when multiple handlers exist."""
992
events_received_1 = []
993
events_received_2 = []
994
995
def on_context_created_1(info):
996
events_received_1.append(info)
997
998
def on_context_created_2(info):
999
events_received_2.append(info)
1000
1001
# Add multiple event handlers
1002
callback_id_1 = driver.browsing_context.add_event_handler("context_created", on_context_created_1)
1003
callback_id_2 = driver.browsing_context.add_event_handler("context_created", on_context_created_2)
1004
1005
# Create a context to trigger both handlers
1006
context_id_1 = driver.browsing_context.create(type=WindowTypes.TAB)
1007
1008
# Verify both handlers received the event
1009
assert len(events_received_1) >= 1
1010
assert len(events_received_2) >= 1
1011
1012
# store the initial event counts
1013
initial_count_1 = len(events_received_1)
1014
initial_count_2 = len(events_received_2)
1015
1016
# Remove only the first handler
1017
driver.browsing_context.remove_event_handler("context_created", callback_id_1)
1018
1019
# Create another context
1020
context_id_2 = driver.browsing_context.create(type=WindowTypes.TAB)
1021
1022
# Verify only the second handler received the new event
1023
assert len(events_received_1) == initial_count_1 # No new events
1024
assert len(events_received_2) == initial_count_2 + 1 # 1 new event
1025
1026
driver.browsing_context.close(context_id_1)
1027
driver.browsing_context.close(context_id_2)
1028
driver.browsing_context.remove_event_handler("context_created", callback_id_2)
1029
1030
1031
class _EventHandlerTestHelper:
1032
def __init__(self, driver):
1033
self.driver = driver
1034
self.events_received = []
1035
self.context_counts = {}
1036
self.event_type_counts = {}
1037
self.processing_times = []
1038
self.consistency_errors = []
1039
self.thread_errors = []
1040
self.callback_ids = []
1041
self.data_lock = threading.Lock()
1042
self.registration_complete = threading.Event()
1043
1044
def make_callback(self):
1045
def callback(info):
1046
start_time = time.time()
1047
time.sleep(0.02) # Simulate race window
1048
1049
with self.data_lock:
1050
initial_event_count = len(self.events_received)
1051
1052
self.events_received.append(info)
1053
1054
context_id = info.context
1055
self.context_counts.setdefault(context_id, 0)
1056
self.context_counts[context_id] += 1
1057
1058
event_type = info.__class__.__name__
1059
self.event_type_counts.setdefault(event_type, 0)
1060
self.event_type_counts[event_type] += 1
1061
1062
processing_time = time.time() - start_time
1063
self.processing_times.append(processing_time)
1064
1065
final_event_count = len(self.events_received)
1066
final_context_total = sum(self.context_counts.values())
1067
final_type_total = sum(self.event_type_counts.values())
1068
final_processing_count = len(self.processing_times)
1069
1070
expected_count = initial_event_count + 1
1071
if not (
1072
final_event_count
1073
== final_context_total
1074
== final_type_total
1075
== final_processing_count
1076
== expected_count
1077
):
1078
self.consistency_errors.append("Data consistency error")
1079
1080
return callback
1081
1082
def register_handler(self, thread_id):
1083
try:
1084
callback = self.make_callback()
1085
callback_id = self.driver.browsing_context.add_event_handler("context_created", callback)
1086
with self.data_lock:
1087
self.callback_ids.append(callback_id)
1088
if len(self.callback_ids) == 5:
1089
self.registration_complete.set()
1090
return callback_id
1091
except Exception as e:
1092
with self.data_lock:
1093
self.thread_errors.append(f"Thread {thread_id}: Registration failed: {e}")
1094
return None
1095
1096
def remove_handler(self, callback_id, thread_id):
1097
try:
1098
self.driver.browsing_context.remove_event_handler("context_created", callback_id)
1099
except Exception as e:
1100
with self.data_lock:
1101
self.thread_errors.append(f"Thread {thread_id}: Removal failed: {e}")
1102
1103
1104
def test_concurrent_event_handler_registration(driver):
1105
helper = _EventHandlerTestHelper(driver)
1106
1107
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
1108
futures = [executor.submit(helper.register_handler, f"reg-{i}") for i in range(5)]
1109
for future in futures:
1110
future.result(timeout=15)
1111
1112
helper.registration_complete.wait(timeout=5)
1113
assert len(helper.callback_ids) == 5, f"Expected 5 handlers, got {len(helper.callback_ids)}"
1114
assert not helper.thread_errors, "Errors during registration: \n" + "\n".join(helper.thread_errors)
1115
1116
1117
def test_event_callback_data_consistency(driver):
1118
helper = _EventHandlerTestHelper(driver)
1119
1120
for i in range(5):
1121
helper.register_handler(f"reg-{i}")
1122
1123
test_contexts = []
1124
for _ in range(3):
1125
context = driver.browsing_context.create(type=WindowTypes.TAB)
1126
test_contexts.append(context)
1127
1128
for ctx in test_contexts:
1129
driver.browsing_context.close(ctx)
1130
1131
with helper.data_lock:
1132
assert not helper.consistency_errors, "Consistency errors: " + str(helper.consistency_errors)
1133
assert len(helper.events_received) > 0, "No events received"
1134
assert len(helper.events_received) == sum(helper.context_counts.values())
1135
assert len(helper.events_received) == sum(helper.event_type_counts.values())
1136
assert len(helper.events_received) == len(helper.processing_times)
1137
1138
1139
def test_concurrent_event_handler_removal(driver):
1140
helper = _EventHandlerTestHelper(driver)
1141
1142
for i in range(5):
1143
helper.register_handler(f"reg-{i}")
1144
1145
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
1146
futures = [
1147
executor.submit(helper.remove_handler, callback_id, f"rem-{i}")
1148
for i, callback_id in enumerate(helper.callback_ids)
1149
]
1150
for future in futures:
1151
future.result(timeout=15)
1152
1153
assert not helper.thread_errors, "Errors during removal: \n" + "\n".join(helper.thread_errors)
1154
1155
1156
def test_no_event_after_handler_removal(driver):
1157
helper = _EventHandlerTestHelper(driver)
1158
1159
for i in range(5):
1160
helper.register_handler(f"reg-{i}")
1161
1162
context = driver.browsing_context.create(type=WindowTypes.TAB)
1163
driver.browsing_context.close(context)
1164
1165
events_before = len(helper.events_received)
1166
1167
for i, callback_id in enumerate(helper.callback_ids):
1168
helper.remove_handler(callback_id, f"rem-{i}")
1169
1170
post_context = driver.browsing_context.create(type=WindowTypes.TAB)
1171
driver.browsing_context.close(post_context)
1172
1173
with helper.data_lock:
1174
new_events = len(helper.events_received) - events_before
1175
1176
assert new_events == 0, f"Expected 0 new events after removal, got {new_events}"
1177
1178