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
1865 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
driver.browsing_context.set_viewport(context=context_id, viewport={"width": 250, "height": 300})
352
353
viewport_size = driver.execute_script("return [window.innerWidth, window.innerHeight];")
354
355
assert viewport_size[0] == 250
356
assert viewport_size[1] == 300
357
358
359
def test_set_viewport_with_device_pixel_ratio(driver, pages):
360
"""Test setting the viewport with device pixel ratio."""
361
context_id = driver.current_window_handle
362
driver.get(pages.url("formPage.html"))
363
364
driver.browsing_context.set_viewport(
365
context=context_id, viewport={"width": 250, "height": 300}, device_pixel_ratio=5
366
)
367
368
viewport_size = driver.execute_script("return [window.innerWidth, window.innerHeight];")
369
370
assert viewport_size[0] == 250
371
assert viewport_size[1] == 300
372
373
device_pixel_ratio = driver.execute_script("return window.devicePixelRatio")
374
375
assert device_pixel_ratio == 5
376
377
378
def test_print_page(driver, pages):
379
"""Test printing a page."""
380
context_id = driver.current_window_handle
381
382
driver.get(pages.url("formPage.html"))
383
384
print_result = driver.browsing_context.print(context=context_id)
385
386
assert len(print_result) > 0
387
# Check if it's a valid PDF (starts with JVBERi which is the base64 encoding of %PDF)
388
assert "JVBERi" in print_result
389
390
391
def test_navigate_back_in_browser_history(driver, pages):
392
"""Test navigating back in the browser history."""
393
context_id = driver.current_window_handle
394
driver.browsing_context.navigate(context=context_id, url=pages.url("formPage.html"), wait=ReadinessState.COMPLETE)
395
396
# Navigate to another page by submitting a form
397
driver.find_element(By.ID, "imageButton").submit()
398
WebDriverWait(driver, 5).until(EC.title_is("We Arrive Here"))
399
400
driver.browsing_context.traverse_history(context=context_id, delta=-1)
401
WebDriverWait(driver, 5).until(EC.title_is("We Leave From Here"))
402
403
404
def test_navigate_forward_in_browser_history(driver, pages):
405
"""Test navigating forward in the browser history."""
406
context_id = driver.current_window_handle
407
driver.browsing_context.navigate(context=context_id, url=pages.url("formPage.html"), wait=ReadinessState.COMPLETE)
408
409
# Navigate to another page by submitting a form
410
driver.find_element(By.ID, "imageButton").submit()
411
WebDriverWait(driver, 5).until(EC.title_is("We Arrive Here"))
412
413
# Go back
414
driver.browsing_context.traverse_history(context=context_id, delta=-1)
415
WebDriverWait(driver, 5).until(EC.title_is("We Leave From Here"))
416
417
# Go forward
418
driver.browsing_context.traverse_history(context=context_id, delta=1)
419
WebDriverWait(driver, 5).until(EC.title_is("We Arrive Here"))
420
421
422
# Tests for locate nodes
423
def test_locate_nodes(driver, pages):
424
"""Test locating nodes with CSS selector."""
425
context_id = driver.current_window_handle
426
427
driver.get(pages.url("xhtmlTest.html"))
428
429
elements = driver.browsing_context.locate_nodes(context=context_id, locator={"type": "css", "value": "div"})
430
431
assert len(elements) > 0
432
433
434
def test_locate_nodes_with_css_locator(driver, pages):
435
"""Test locating nodes with specific CSS selector."""
436
context_id = driver.current_window_handle
437
438
driver.get(pages.url("xhtmlTest.html"))
439
440
elements = driver.browsing_context.locate_nodes(
441
context=context_id, locator={"type": "css", "value": "div.extraDiv, div.content"}, max_node_count=1
442
)
443
444
assert len(elements) >= 1
445
446
value = elements[0]
447
assert value["type"] == "node"
448
assert "value" in value
449
assert "localName" in value["value"]
450
assert value["value"]["localName"] == "div"
451
assert "attributes" in value["value"]
452
assert "class" in value["value"]["attributes"]
453
assert value["value"]["attributes"]["class"] == "content"
454
455
456
def test_locate_nodes_with_xpath_locator(driver, pages):
457
"""Test locating nodes with XPath selector."""
458
context_id = driver.current_window_handle
459
460
driver.get(pages.url("xhtmlTest.html"))
461
462
elements = driver.browsing_context.locate_nodes(
463
context=context_id, locator={"type": "xpath", "value": "/html/body/div[2]"}, max_node_count=1
464
)
465
466
assert len(elements) >= 1
467
468
value = elements[0]
469
assert value["type"] == "node"
470
assert "value" in value
471
assert "localName" in value["value"]
472
assert value["value"]["localName"] == "div"
473
assert "attributes" in value["value"]
474
assert "class" in value["value"]["attributes"]
475
assert value["value"]["attributes"]["class"] == "content"
476
477
478
@pytest.mark.xfail_firefox
479
def test_locate_nodes_with_inner_text(driver, pages):
480
"""Test locating nodes with innerText selector."""
481
context_id = driver.current_window_handle
482
483
driver.get(pages.url("xhtmlTest.html"))
484
485
elements = driver.browsing_context.locate_nodes(
486
context=context_id, locator={"type": "innerText", "value": "Spaced out"}, max_node_count=1
487
)
488
489
assert len(elements) >= 1
490
491
value = elements[0]
492
assert value["type"] == "node"
493
assert "value" in value
494
495
496
def test_locate_nodes_with_max_node_count(driver, pages):
497
"""Test locating nodes with maximum node count."""
498
context_id = driver.current_window_handle
499
500
driver.get(pages.url("xhtmlTest.html"))
501
502
elements = driver.browsing_context.locate_nodes(
503
context=context_id, locator={"type": "css", "value": "div"}, max_node_count=4
504
)
505
506
assert len(elements) == 4
507
508
509
def test_locate_nodes_given_start_nodes(driver, pages):
510
"""Test locating nodes with start nodes."""
511
context_id = driver.current_window_handle
512
513
driver.get(pages.url("formPage.html"))
514
515
form_elements = driver.browsing_context.locate_nodes(
516
context=context_id, locator={"type": "css", "value": "form[name='login']"}
517
)
518
519
assert len(form_elements) == 1
520
521
form_shared_id = form_elements[0]["sharedId"]
522
523
elements = driver.browsing_context.locate_nodes(
524
context=context_id,
525
locator={"type": "css", "value": "input"},
526
start_nodes=[{"sharedId": form_shared_id}],
527
max_node_count=50,
528
)
529
# The login form should have 3 input elements (email, age, and submit button)
530
assert len(elements) == 3
531
532
533
# Tests for event handlers
534
535
536
def test_add_event_handler_context_created(driver):
537
"""Test adding event handler for context_created event."""
538
events_received = []
539
540
def on_context_created(info):
541
events_received.append(info)
542
543
callback_id = driver.browsing_context.add_event_handler("context_created", on_context_created)
544
assert callback_id is not None
545
546
# Create a new context to trigger the event
547
context_id = driver.browsing_context.create(type=WindowTypes.TAB)
548
549
# Verify the event was received (might be > 1 since default context is also included)
550
assert len(events_received) >= 1
551
assert events_received[0].context == context_id or events_received[1].context == context_id
552
553
driver.browsing_context.close(context_id)
554
driver.browsing_context.remove_event_handler("context_created", callback_id)
555
556
557
def test_add_event_handler_context_destroyed(driver):
558
"""Test adding event handler for context_destroyed event."""
559
events_received = []
560
561
def on_context_destroyed(info):
562
events_received.append(info)
563
564
callback_id = driver.browsing_context.add_event_handler("context_destroyed", on_context_destroyed)
565
assert callback_id is not None
566
567
# Create and then close a context to trigger the event
568
context_id = driver.browsing_context.create(type=WindowTypes.TAB)
569
driver.browsing_context.close(context_id)
570
571
assert len(events_received) == 1
572
assert events_received[0].context == context_id
573
574
driver.browsing_context.remove_event_handler("context_destroyed", callback_id)
575
576
577
def test_add_event_handler_navigation_committed(driver, pages):
578
"""Test adding event handler for navigation_committed event."""
579
events_received = []
580
581
def on_navigation_committed(info):
582
events_received.append(info)
583
584
callback_id = driver.browsing_context.add_event_handler("navigation_committed", on_navigation_committed)
585
assert callback_id is not None
586
587
# Navigate to trigger the event
588
context_id = driver.current_window_handle
589
url = pages.url("simpleTest.html")
590
driver.browsing_context.navigate(context=context_id, url=url, wait=ReadinessState.COMPLETE)
591
592
assert len(events_received) >= 1
593
assert any(url in event.url for event in events_received)
594
595
driver.browsing_context.remove_event_handler("navigation_committed", callback_id)
596
597
598
def test_add_event_handler_dom_content_loaded(driver, pages):
599
"""Test adding event handler for dom_content_loaded event."""
600
events_received = []
601
602
def on_dom_content_loaded(info):
603
events_received.append(info)
604
605
callback_id = driver.browsing_context.add_event_handler("dom_content_loaded", on_dom_content_loaded)
606
assert callback_id is not None
607
608
# Navigate to trigger the event
609
context_id = driver.current_window_handle
610
url = pages.url("simpleTest.html")
611
driver.browsing_context.navigate(context=context_id, url=url, wait=ReadinessState.COMPLETE)
612
613
assert len(events_received) == 1
614
assert any("simpleTest" in event.url for event in events_received)
615
616
driver.browsing_context.remove_event_handler("dom_content_loaded", callback_id)
617
618
619
def test_add_event_handler_load(driver, pages):
620
"""Test adding event handler for load event."""
621
events_received = []
622
623
def on_load(info):
624
events_received.append(info)
625
626
callback_id = driver.browsing_context.add_event_handler("load", on_load)
627
assert callback_id is not None
628
629
context_id = driver.current_window_handle
630
url = pages.url("simpleTest.html")
631
driver.browsing_context.navigate(context=context_id, url=url, wait=ReadinessState.COMPLETE)
632
633
assert len(events_received) == 1
634
assert any("simpleTest" in event.url for event in events_received)
635
636
driver.browsing_context.remove_event_handler("load", callback_id)
637
638
639
def test_add_event_handler_navigation_started(driver, pages):
640
"""Test adding event handler for navigation_started event."""
641
events_received = []
642
643
def on_navigation_started(info):
644
events_received.append(info)
645
646
callback_id = driver.browsing_context.add_event_handler("navigation_started", on_navigation_started)
647
assert callback_id is not None
648
649
context_id = driver.current_window_handle
650
url = pages.url("simpleTest.html")
651
driver.browsing_context.navigate(context=context_id, url=url, wait=ReadinessState.COMPLETE)
652
653
assert len(events_received) == 1
654
assert any("simpleTest" in event.url for event in events_received)
655
656
driver.browsing_context.remove_event_handler("navigation_started", callback_id)
657
658
659
def test_add_event_handler_fragment_navigated(driver, pages):
660
"""Test adding event handler for fragment_navigated event."""
661
events_received = []
662
663
def on_fragment_navigated(info):
664
events_received.append(info)
665
666
callback_id = driver.browsing_context.add_event_handler("fragment_navigated", on_fragment_navigated)
667
assert callback_id is not None
668
669
# First navigate to a page
670
context_id = driver.current_window_handle
671
url = pages.url("linked_image.html")
672
driver.browsing_context.navigate(context=context_id, url=url, wait=ReadinessState.COMPLETE)
673
674
# Then navigate to the same page with a fragment to trigger the event
675
fragment_url = url + "#link"
676
driver.browsing_context.navigate(context=context_id, url=fragment_url, wait=ReadinessState.COMPLETE)
677
678
assert len(events_received) == 1
679
assert any("link" in event.url for event in events_received)
680
681
driver.browsing_context.remove_event_handler("fragment_navigated", callback_id)
682
683
684
@pytest.mark.xfail_firefox
685
def test_add_event_handler_navigation_failed(driver):
686
"""Test adding event handler for navigation_failed event."""
687
events_received = []
688
689
def on_navigation_failed(info):
690
events_received.append(info)
691
692
callback_id = driver.browsing_context.add_event_handler("navigation_failed", on_navigation_failed)
693
assert callback_id is not None
694
695
# Navigate to an invalid URL to trigger the event
696
context_id = driver.current_window_handle
697
try:
698
driver.browsing_context.navigate(context=context_id, url="http://invalid-domain-that-does-not-exist.test/")
699
except Exception:
700
# Expect an exception due to navigation failure
701
pass
702
703
assert len(events_received) == 1
704
assert events_received[0].url == "http://invalid-domain-that-does-not-exist.test/"
705
assert events_received[0].context == context_id
706
707
driver.browsing_context.remove_event_handler("navigation_failed", callback_id)
708
709
710
def test_add_event_handler_user_prompt_opened(driver, pages):
711
"""Test adding event handler for user_prompt_opened event."""
712
events_received = []
713
714
def on_user_prompt_opened(info):
715
events_received.append(info)
716
717
callback_id = driver.browsing_context.add_event_handler("user_prompt_opened", on_user_prompt_opened)
718
assert callback_id is not None
719
720
# Create an alert to trigger the event
721
create_alert_page(driver, pages)
722
driver.find_element(By.ID, "alert").click()
723
WebDriverWait(driver, 5).until(EC.alert_is_present())
724
725
assert len(events_received) == 1
726
assert events_received[0].type == "alert"
727
assert events_received[0].message == "cheese"
728
729
# Clean up the alert
730
driver.browsing_context.handle_user_prompt(context=driver.current_window_handle)
731
driver.browsing_context.remove_event_handler("user_prompt_opened", callback_id)
732
733
734
def test_add_event_handler_user_prompt_closed(driver, pages):
735
"""Test adding event handler for user_prompt_closed event."""
736
events_received = []
737
738
def on_user_prompt_closed(info):
739
events_received.append(info)
740
741
callback_id = driver.browsing_context.add_event_handler("user_prompt_closed", on_user_prompt_closed)
742
assert callback_id is not None
743
744
create_prompt_page(driver, pages)
745
driver.execute_script("prompt('Enter something')")
746
WebDriverWait(driver, 5).until(EC.alert_is_present())
747
748
driver.browsing_context.handle_user_prompt(
749
context=driver.current_window_handle, accept=True, user_text="test input"
750
)
751
752
assert len(events_received) == 1
753
assert events_received[0].accepted is True
754
assert events_received[0].user_text == "test input"
755
756
driver.browsing_context.remove_event_handler("user_prompt_closed", callback_id)
757
758
759
def test_add_event_handler_history_updated(driver, pages):
760
"""Test adding event handler for history_updated event."""
761
events_received = []
762
763
def on_history_updated(info):
764
events_received.append(info)
765
766
callback_id = driver.browsing_context.add_event_handler("history_updated", on_history_updated)
767
assert callback_id is not None
768
769
context_id = driver.current_window_handle
770
url = pages.url("simpleTest.html")
771
driver.browsing_context.navigate(context=context_id, url=url, wait=ReadinessState.COMPLETE)
772
773
# Use history.pushState to trigger history updated event
774
driver.script.execute("() => { history.pushState({}, '', '/new-path'); }")
775
WebDriverWait(driver, 5).until(lambda d: len(events_received) > 0)
776
777
assert len(events_received) >= 1
778
assert "/new-path" in events_received[0].url
779
assert events_received[0].context == context_id
780
781
driver.browsing_context.remove_event_handler("history_updated", callback_id)
782
783
784
@pytest.mark.xfail_firefox
785
def test_add_event_handler_download_will_begin(driver, pages):
786
"""Test adding event handler for download_will_begin event."""
787
events_received = []
788
789
def on_download_will_begin(info):
790
events_received.append(info)
791
792
callback_id = driver.browsing_context.add_event_handler("download_will_begin", on_download_will_begin)
793
assert callback_id is not None
794
795
# click on a download link to trigger the event
796
context_id = driver.current_window_handle
797
url = pages.url("downloads/download.html")
798
driver.browsing_context.navigate(context=context_id, url=url, wait=ReadinessState.COMPLETE)
799
800
download_xpath_file_1_txt = '//*[@id="file-1"]'
801
driver.find_element(By.XPATH, download_xpath_file_1_txt).click()
802
WebDriverWait(driver, 5).until(lambda d: len(events_received) > 0)
803
804
assert len(events_received) == 1
805
assert events_received[0].suggested_filename == "file_1.txt"
806
807
driver.browsing_context.remove_event_handler("download_will_begin", callback_id)
808
809
810
def test_add_event_handler_with_specific_contexts(driver):
811
"""Test adding event handler with specific browsing contexts."""
812
events_received = []
813
814
def on_context_created(info):
815
events_received.append(info)
816
817
context_id = driver.browsing_context.create(type=WindowTypes.TAB)
818
819
# Add event handler for specific context
820
callback_id = driver.browsing_context.add_event_handler(
821
"context_created", on_context_created, contexts=[context_id]
822
)
823
assert callback_id is not None
824
825
# Create another context (should trigger event)
826
new_context_id = driver.browsing_context.create(type=WindowTypes.TAB)
827
828
assert len(events_received) >= 1
829
830
driver.browsing_context.close(context_id)
831
driver.browsing_context.close(new_context_id)
832
driver.browsing_context.remove_event_handler("context_created", callback_id)
833
834
835
def test_remove_event_handler(driver):
836
"""Test removing event handler."""
837
events_received = []
838
839
def on_context_created(info):
840
events_received.append(info)
841
842
callback_id = driver.browsing_context.add_event_handler("context_created", on_context_created)
843
844
# Create a context to trigger the event
845
context_id_1 = driver.browsing_context.create(type=WindowTypes.TAB)
846
847
initial_events = len(events_received)
848
849
# Remove the event handler
850
driver.browsing_context.remove_event_handler("context_created", callback_id)
851
852
# Create another context (should not trigger event after removal)
853
context_id_2 = driver.browsing_context.create(type=WindowTypes.TAB)
854
855
# Verify no new events were received after removal
856
assert len(events_received) == initial_events
857
858
driver.browsing_context.close(context_id_1)
859
driver.browsing_context.close(context_id_2)
860
861
862
def test_multiple_event_handlers_same_event(driver):
863
"""Test adding multiple event handlers for the same event."""
864
events_received_1 = []
865
events_received_2 = []
866
867
def on_context_created_1(info):
868
events_received_1.append(info)
869
870
def on_context_created_2(info):
871
events_received_2.append(info)
872
873
# Add multiple event handlers for the same event
874
callback_id_1 = driver.browsing_context.add_event_handler("context_created", on_context_created_1)
875
callback_id_2 = driver.browsing_context.add_event_handler("context_created", on_context_created_2)
876
877
# Create a context to trigger both handlers
878
context_id = driver.browsing_context.create(type=WindowTypes.TAB)
879
880
# Verify both handlers received the event
881
assert len(events_received_1) >= 1
882
assert len(events_received_2) >= 1
883
# Check any of the events has the required context ID
884
assert any(event.context == context_id for event in events_received_1)
885
assert any(event.context == context_id for event in events_received_2)
886
887
driver.browsing_context.close(context_id)
888
driver.browsing_context.remove_event_handler("context_created", callback_id_1)
889
driver.browsing_context.remove_event_handler("context_created", callback_id_2)
890
891
892
def test_remove_specific_event_handler_multiple_handlers(driver):
893
"""Test removing a specific event handler when multiple handlers exist."""
894
events_received_1 = []
895
events_received_2 = []
896
897
def on_context_created_1(info):
898
events_received_1.append(info)
899
900
def on_context_created_2(info):
901
events_received_2.append(info)
902
903
# Add multiple event handlers
904
callback_id_1 = driver.browsing_context.add_event_handler("context_created", on_context_created_1)
905
callback_id_2 = driver.browsing_context.add_event_handler("context_created", on_context_created_2)
906
907
# Create a context to trigger both handlers
908
context_id_1 = driver.browsing_context.create(type=WindowTypes.TAB)
909
910
# Verify both handlers received the event
911
assert len(events_received_1) >= 1
912
assert len(events_received_2) >= 1
913
914
# store the initial event counts
915
initial_count_1 = len(events_received_1)
916
initial_count_2 = len(events_received_2)
917
918
# Remove only the first handler
919
driver.browsing_context.remove_event_handler("context_created", callback_id_1)
920
921
# Create another context
922
context_id_2 = driver.browsing_context.create(type=WindowTypes.TAB)
923
924
# Verify only the second handler received the new event
925
assert len(events_received_1) == initial_count_1 # No new events
926
assert len(events_received_2) == initial_count_2 + 1 # 1 new event
927
928
driver.browsing_context.close(context_id_1)
929
driver.browsing_context.close(context_id_2)
930
driver.browsing_context.remove_event_handler("context_created", callback_id_2)
931
932
933
class _EventHandlerTestHelper:
934
def __init__(self, driver):
935
self.driver = driver
936
self.events_received = []
937
self.context_counts = {}
938
self.event_type_counts = {}
939
self.processing_times = []
940
self.consistency_errors = []
941
self.thread_errors = []
942
self.callback_ids = []
943
self.data_lock = threading.Lock()
944
self.registration_complete = threading.Event()
945
946
def make_callback(self):
947
def callback(info):
948
start_time = time.time()
949
time.sleep(0.02) # Simulate race window
950
951
with self.data_lock:
952
initial_event_count = len(self.events_received)
953
954
self.events_received.append(info)
955
956
context_id = info.context
957
self.context_counts.setdefault(context_id, 0)
958
self.context_counts[context_id] += 1
959
960
event_type = info.__class__.__name__
961
self.event_type_counts.setdefault(event_type, 0)
962
self.event_type_counts[event_type] += 1
963
964
processing_time = time.time() - start_time
965
self.processing_times.append(processing_time)
966
967
final_event_count = len(self.events_received)
968
final_context_total = sum(self.context_counts.values())
969
final_type_total = sum(self.event_type_counts.values())
970
final_processing_count = len(self.processing_times)
971
972
expected_count = initial_event_count + 1
973
if not (
974
final_event_count
975
== final_context_total
976
== final_type_total
977
== final_processing_count
978
== expected_count
979
):
980
self.consistency_errors.append("Data consistency error")
981
982
return callback
983
984
def register_handler(self, thread_id):
985
try:
986
callback = self.make_callback()
987
callback_id = self.driver.browsing_context.add_event_handler("context_created", callback)
988
with self.data_lock:
989
self.callback_ids.append(callback_id)
990
if len(self.callback_ids) == 5:
991
self.registration_complete.set()
992
return callback_id
993
except Exception as e:
994
with self.data_lock:
995
self.thread_errors.append(f"Thread {thread_id}: Registration failed: {e}")
996
return None
997
998
def remove_handler(self, callback_id, thread_id):
999
try:
1000
self.driver.browsing_context.remove_event_handler("context_created", callback_id)
1001
except Exception as e:
1002
with self.data_lock:
1003
self.thread_errors.append(f"Thread {thread_id}: Removal failed: {e}")
1004
1005
1006
def test_concurrent_event_handler_registration(driver):
1007
helper = _EventHandlerTestHelper(driver)
1008
1009
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
1010
futures = [executor.submit(helper.register_handler, f"reg-{i}") for i in range(5)]
1011
for future in futures:
1012
future.result(timeout=15)
1013
1014
helper.registration_complete.wait(timeout=5)
1015
assert len(helper.callback_ids) == 5, f"Expected 5 handlers, got {len(helper.callback_ids)}"
1016
assert not helper.thread_errors, "Errors during registration: \n" + "\n".join(helper.thread_errors)
1017
1018
1019
def test_event_callback_data_consistency(driver):
1020
helper = _EventHandlerTestHelper(driver)
1021
1022
for i in range(5):
1023
helper.register_handler(f"reg-{i}")
1024
1025
test_contexts = []
1026
for _ in range(3):
1027
context = driver.browsing_context.create(type=WindowTypes.TAB)
1028
test_contexts.append(context)
1029
1030
for ctx in test_contexts:
1031
driver.browsing_context.close(ctx)
1032
1033
with helper.data_lock:
1034
assert not helper.consistency_errors, "Consistency errors: " + str(helper.consistency_errors)
1035
assert len(helper.events_received) > 0, "No events received"
1036
assert len(helper.events_received) == sum(helper.context_counts.values())
1037
assert len(helper.events_received) == sum(helper.event_type_counts.values())
1038
assert len(helper.events_received) == len(helper.processing_times)
1039
1040
1041
def test_concurrent_event_handler_removal(driver):
1042
helper = _EventHandlerTestHelper(driver)
1043
1044
for i in range(5):
1045
helper.register_handler(f"reg-{i}")
1046
1047
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
1048
futures = [
1049
executor.submit(helper.remove_handler, callback_id, f"rem-{i}")
1050
for i, callback_id in enumerate(helper.callback_ids)
1051
]
1052
for future in futures:
1053
future.result(timeout=15)
1054
1055
assert not helper.thread_errors, "Errors during removal: \n" + "\n".join(helper.thread_errors)
1056
1057
1058
def test_no_event_after_handler_removal(driver):
1059
helper = _EventHandlerTestHelper(driver)
1060
1061
for i in range(5):
1062
helper.register_handler(f"reg-{i}")
1063
1064
context = driver.browsing_context.create(type=WindowTypes.TAB)
1065
driver.browsing_context.close(context)
1066
1067
events_before = len(helper.events_received)
1068
1069
for i, callback_id in enumerate(helper.callback_ids):
1070
helper.remove_handler(callback_id, f"rem-{i}")
1071
1072
post_context = driver.browsing_context.create(type=WindowTypes.TAB)
1073
driver.browsing_context.close(post_context)
1074
1075
with helper.data_lock:
1076
new_events = len(helper.events_received) - events_before
1077
1078
assert new_events == 0, f"Expected 0 new events after removal, got {new_events}"
1079
1080