0
# WebDriverWait and Expected Conditions
1
2
This document covers WebDriverWait and Expected Conditions in Python Selenium WebDriver, which provide mechanisms for handling dynamic web content by waiting for specific conditions to be met before proceeding with test execution.
3
4
## WebDriverWait Class
5
6
{ .api }
7
```python
8
from selenium.webdriver.support.ui import WebDriverWait
9
10
class WebDriverWait(Generic[D]):
11
def __init__(
12
self,
13
driver: Union[WebDriver, WebElement],
14
timeout: float,
15
poll_frequency: float = 0.5,
16
ignored_exceptions: Optional[WaitExcTypes] = None,
17
)
18
```
19
20
**Description**: WebDriverWait implements explicit waits that pause execution until a certain condition is met or a timeout occurs.
21
22
**Parameters**:
23
- `driver`: Instance of WebDriver (Chrome, Firefox, etc.) or a WebElement
24
- `timeout`: Number of seconds before timing out
25
- `poll_frequency`: Sleep interval between calls (default: 0.5 seconds)
26
- `ignored_exceptions`: Iterable of exception classes to ignore during calls (default: NoSuchElementException)
27
28
**Default Values**:
29
- `POLL_FREQUENCY`: 0.5 seconds
30
- `IGNORED_EXCEPTIONS`: (NoSuchElementException,)
31
32
**Example**:
33
```python
34
from selenium.webdriver.support.ui import WebDriverWait
35
from selenium.webdriver.support import expected_conditions as EC
36
from selenium.webdriver.common.by import By
37
38
# Basic wait setup
39
wait = WebDriverWait(driver, 10)
40
41
# Wait with custom poll frequency
42
fast_wait = WebDriverWait(driver, 5, poll_frequency=0.1)
43
44
# Wait with custom ignored exceptions
45
from selenium.common.exceptions import ElementNotVisibleException
46
custom_wait = WebDriverWait(
47
driver,
48
10,
49
ignored_exceptions=(ElementNotVisibleException, NoSuchElementException)
50
)
51
```
52
53
## WebDriverWait Methods
54
55
### until() Method
56
57
{ .api }
58
```python
59
def until(
60
self,
61
method: Callable[[D], Union[Literal[False], T]],
62
message: str = ""
63
) -> T
64
```
65
66
**Description**: Waits until the method returns a value that is not False.
67
68
**Parameters**:
69
- `method`: A callable that takes a WebDriver instance and returns a truthy value when the condition is met
70
- `message`: Optional message for TimeoutException
71
72
**Returns**: The result of the last call to `method`
73
74
**Raises**:
75
- `TimeoutException`: If 'method' does not return a truthy value within the timeout
76
77
**Example**:
78
```python
79
from selenium.webdriver.support.ui import WebDriverWait
80
from selenium.webdriver.support import expected_conditions as EC
81
82
# Wait for element to be visible
83
wait = WebDriverWait(driver, 10)
84
element = wait.until(
85
EC.visibility_of_element_located((By.ID, "myElement"))
86
)
87
88
# Wait with custom message
89
element = wait.until(
90
EC.element_to_be_clickable((By.ID, "submit-btn")),
91
"Submit button did not become clickable"
92
)
93
```
94
95
### until_not() Method
96
97
{ .api }
98
```python
99
def until_not(
100
self,
101
method: Callable[[D], T],
102
message: str = ""
103
) -> Union[T, Literal[True]]
104
```
105
106
**Description**: Waits until the method returns a value that evaluates to False.
107
108
**Parameters**:
109
- `method`: A callable that takes a WebDriver instance as an argument
110
- `message`: Optional message for TimeoutException
111
112
**Returns**: The result of the last call to `method` or True if an ignored exception occurred
113
114
**Raises**:
115
- `TimeoutException`: If 'method' does not return False within the timeout
116
117
**Example**:
118
```python
119
# Wait for element to disappear
120
wait = WebDriverWait(driver, 10)
121
is_disappeared = wait.until_not(
122
EC.visibility_of_element_located((By.ID, "loading-spinner"))
123
)
124
125
# Wait for element to become stale
126
old_element = driver.find_element(By.ID, "dynamic-content")
127
# Trigger page refresh or DOM change
128
refresh_button.click()
129
wait.until_not(EC.staleness_of(old_element))
130
```
131
132
## Expected Conditions Module
133
134
Expected Conditions provide pre-built condition functions for common waiting scenarios.
135
136
```python
137
from selenium.webdriver.support import expected_conditions as EC
138
```
139
140
## Page and URL Conditions
141
142
### Title Conditions
143
144
{ .api }
145
```python
146
def title_is(title: str) -> Callable[[WebDriver], bool]
147
```
148
149
**Description**: An expectation for checking the title of a page.
150
151
**Parameters**:
152
- `title`: The expected title (exact match)
153
154
**Returns**: True if the title matches, False otherwise
155
156
{ .api }
157
```python
158
def title_contains(title: str) -> Callable[[WebDriver], bool]
159
```
160
161
**Description**: An expectation for checking that the title contains a case-sensitive substring.
162
163
**Parameters**:
164
- `title`: The fragment of title expected
165
166
**Returns**: True when the title contains the substring, False otherwise
167
168
**Example**:
169
```python
170
# Wait for exact title
171
wait.until(EC.title_is("Welcome to My Site"))
172
173
# Wait for title to contain text
174
wait.until(EC.title_contains("Dashboard"))
175
```
176
177
### URL Conditions
178
179
{ .api }
180
```python
181
def url_contains(url: str) -> Callable[[WebDriver], bool]
182
```
183
184
**Description**: An expectation for checking that the current URL contains a case-sensitive substring.
185
186
**Parameters**:
187
- `url`: The fragment of URL expected
188
189
**Returns**: True when the URL contains the substring, False otherwise
190
191
{ .api }
192
```python
193
def url_matches(pattern: str) -> Callable[[WebDriver], bool]
194
```
195
196
**Description**: An expectation for checking that the current URL matches a regular expression pattern.
197
198
**Parameters**:
199
- `pattern`: The regex pattern to match
200
201
**Returns**: True when the URL matches the pattern, False otherwise
202
203
{ .api }
204
```python
205
def url_to_be(url: str) -> Callable[[WebDriver], bool]
206
```
207
208
**Description**: An expectation for checking the current URL.
209
210
**Parameters**:
211
- `url`: The expected URL (exact match)
212
213
**Returns**: True if the URL matches, False otherwise
214
215
{ .api }
216
```python
217
def url_changes(url: str) -> Callable[[WebDriver], bool]
218
```
219
220
**Description**: An expectation for checking that the current URL does not match the given URL.
221
222
**Parameters**:
223
- `url`: The URL to compare against
224
225
**Returns**: True if current URL is different, False otherwise
226
227
**Example**:
228
```python
229
# Wait for URL to contain specific path
230
wait.until(EC.url_contains("/dashboard"))
231
232
# Wait for URL to match pattern
233
wait.until(EC.url_matches(r"https://.*\.example\.com/user/\d+"))
234
235
# Wait for navigation to complete
236
current_url = driver.current_url
237
login_button.click()
238
wait.until(EC.url_changes(current_url))
239
```
240
241
## Element Presence and Visibility
242
243
### Element Presence
244
245
{ .api }
246
```python
247
def presence_of_element_located(
248
locator: Tuple[str, str]
249
) -> Callable[[WebDriverOrWebElement], WebElement]
250
```
251
252
**Description**: An expectation for checking that an element is present on the DOM. This does not necessarily mean that the element is visible.
253
254
**Parameters**:
255
- `locator`: Used to find the element (By strategy, value)
256
257
**Returns**: The WebElement once it is located
258
259
{ .api }
260
```python
261
def presence_of_all_elements_located(
262
locator: Tuple[str, str]
263
) -> Callable[[WebDriverOrWebElement], List[WebElement]]
264
```
265
266
**Description**: An expectation for checking that there is at least one element present on a web page.
267
268
**Parameters**:
269
- `locator`: Used to find the elements
270
271
**Returns**: List of WebElements once they are located
272
273
**Example**:
274
```python
275
# Wait for single element to be present in DOM
276
element = wait.until(
277
EC.presence_of_element_located((By.ID, "content"))
278
)
279
280
# Wait for multiple elements to be present
281
elements = wait.until(
282
EC.presence_of_all_elements_located((By.CLASS_NAME, "item"))
283
)
284
```
285
286
### Element Visibility
287
288
{ .api }
289
```python
290
def visibility_of_element_located(
291
locator: Tuple[str, str]
292
) -> Callable[[WebDriverOrWebElement], WebElement]
293
```
294
295
**Description**: An expectation for checking that an element is present on the DOM and visible. Visibility means that the element is not only displayed but also has a height and width greater than 0.
296
297
**Parameters**:
298
- `locator`: Used to find the element
299
300
**Returns**: The WebElement once it is located and visible
301
302
{ .api }
303
```python
304
def visibility_of(element: WebElement) -> Callable[[Any], Union[Literal[False], WebElement]]
305
```
306
307
**Description**: An expectation for checking that an element, known to be present on the DOM, is visible.
308
309
**Parameters**:
310
- `element`: The WebElement to check
311
312
**Returns**: The WebElement if visible, False otherwise
313
314
{ .api }
315
```python
316
def visibility_of_all_elements_located(
317
locator: Tuple[str, str]
318
) -> Callable[[WebDriverOrWebElement], List[WebElement]]
319
```
320
321
**Description**: An expectation for checking that all elements are present on the DOM and visible.
322
323
**Parameters**:
324
- `locator`: Used to find the elements
325
326
**Returns**: List of WebElements once they are located and visible
327
328
{ .api }
329
```python
330
def visibility_of_any_elements_located(
331
locator: Tuple[str, str]
332
) -> Callable[[WebDriverOrWebElement], List[WebElement]]
333
```
334
335
**Description**: An expectation for checking that there is at least one element visible on a web page.
336
337
**Parameters**:
338
- `locator`: Used to find the elements
339
340
**Returns**: List of visible WebElements
341
342
**Example**:
343
```python
344
# Wait for element to be visible
345
visible_element = wait.until(
346
EC.visibility_of_element_located((By.ID, "modal"))
347
)
348
349
# Wait for existing element to become visible
350
element = driver.find_element(By.ID, "hidden-content")
351
visible_element = wait.until(EC.visibility_of(element))
352
353
# Wait for all matching elements to be visible
354
all_visible = wait.until(
355
EC.visibility_of_all_elements_located((By.CLASS_NAME, "menu-item"))
356
)
357
358
# Wait for any matching elements to be visible
359
any_visible = wait.until(
360
EC.visibility_of_any_elements_located((By.TAG_NAME, "button"))
361
)
362
```
363
364
### Element Invisibility
365
366
{ .api }
367
```python
368
def invisibility_of_element_located(
369
locator: Tuple[str, str]
370
) -> Callable[[WebDriverOrWebElement], Union[WebElement, bool]]
371
```
372
373
**Description**: An expectation for checking that an element is either invisible or not present on the DOM.
374
375
**Parameters**:
376
- `locator`: Used to find the element
377
378
**Returns**: WebElement if invisible, True if not present
379
380
{ .api }
381
```python
382
def invisibility_of_element(
383
element: WebElement
384
) -> Callable[[Any], Union[WebElement, bool]]
385
```
386
387
**Description**: An expectation for checking that an element is either invisible or not present on the DOM.
388
389
**Parameters**:
390
- `element`: The WebElement to check
391
392
**Returns**: WebElement if invisible, True if not present
393
394
**Example**:
395
```python
396
# Wait for loading spinner to disappear
397
wait.until(
398
EC.invisibility_of_element_located((By.ID, "loading-spinner"))
399
)
400
401
# Wait for specific element to become invisible
402
modal = driver.find_element(By.ID, "error-modal")
403
close_button.click()
404
wait.until(EC.invisibility_of_element(modal))
405
```
406
407
## Element Interaction Conditions
408
409
### Clickability
410
411
{ .api }
412
```python
413
def element_to_be_clickable(
414
mark: Union[WebElement, Tuple[str, str]]
415
) -> Callable[[WebDriverOrWebElement], Union[Literal[False], WebElement]]
416
```
417
418
**Description**: An expectation for checking an element is visible and enabled such that you can click it.
419
420
**Parameters**:
421
- `mark`: WebElement or locator tuple (By strategy, value)
422
423
**Returns**: The WebElement once it is clickable, False otherwise
424
425
**Example**:
426
```python
427
# Wait for button to be clickable
428
clickable_button = wait.until(
429
EC.element_to_be_clickable((By.ID, "submit-btn"))
430
)
431
clickable_button.click()
432
433
# Wait for existing element to be clickable
434
button = driver.find_element(By.ID, "action-btn")
435
wait.until(EC.element_to_be_clickable(button))
436
```
437
438
### Element Selection State
439
440
{ .api }
441
```python
442
def element_to_be_selected(element: WebElement) -> Callable[[Any], bool]
443
```
444
445
**Description**: An expectation for checking that an element is selected.
446
447
**Parameters**:
448
- `element`: The WebElement to check
449
450
**Returns**: True if the element is selected, False otherwise
451
452
{ .api }
453
```python
454
def element_located_to_be_selected(
455
locator: Tuple[str, str]
456
) -> Callable[[WebDriverOrWebElement], bool]
457
```
458
459
**Description**: An expectation for the element to be located and selected.
460
461
**Parameters**:
462
- `locator`: Used to find the element
463
464
**Returns**: True if the element is selected, False otherwise
465
466
{ .api }
467
```python
468
def element_selection_state_to_be(
469
element: WebElement,
470
is_selected: bool
471
) -> Callable[[Any], bool]
472
```
473
474
**Description**: An expectation for checking if the given element's selection state matches the expected state.
475
476
**Parameters**:
477
- `element`: The WebElement to check
478
- `is_selected`: Expected selection state
479
480
**Returns**: True if the element's selection state matches, False otherwise
481
482
{ .api }
483
```python
484
def element_located_selection_state_to_be(
485
locator: Tuple[str, str],
486
is_selected: bool
487
) -> Callable[[WebDriverOrWebElement], bool]
488
```
489
490
**Description**: An expectation to locate an element and check if its selection state matches the expected state.
491
492
**Parameters**:
493
- `locator`: Used to find the element
494
- `is_selected`: Expected selection state
495
496
**Returns**: True if the element's selection state matches, False otherwise
497
498
**Example**:
499
```python
500
# Wait for checkbox to be selected
501
checkbox = driver.find_element(By.ID, "agree-terms")
502
wait.until(EC.element_to_be_selected(checkbox))
503
504
# Wait for element to be found and selected
505
wait.until(
506
EC.element_located_to_be_selected((By.ID, "default-option"))
507
)
508
509
# Wait for specific selection state
510
wait.until(
511
EC.element_selection_state_to_be(checkbox, True)
512
)
513
514
# Wait for located element to have specific selection state
515
wait.until(
516
EC.element_located_selection_state_to_be((By.ID, "toggle"), False)
517
)
518
```
519
520
## Text and Attribute Conditions
521
522
### Text Presence
523
524
{ .api }
525
```python
526
def text_to_be_present_in_element(
527
locator: Tuple[str, str],
528
text_: str
529
) -> Callable[[WebDriverOrWebElement], bool]
530
```
531
532
**Description**: An expectation for checking if the given text is present in the specified element.
533
534
**Parameters**:
535
- `locator`: Used to find the element
536
- `text_`: The text to check for
537
538
**Returns**: True if text is present, False otherwise
539
540
{ .api }
541
```python
542
def text_to_be_present_in_element_value(
543
locator: Tuple[str, str],
544
text_: str
545
) -> Callable[[WebDriverOrWebElement], bool]
546
```
547
548
**Description**: An expectation for checking if the given text is present in the element's value attribute.
549
550
**Parameters**:
551
- `locator`: Used to find the element
552
- `text_`: The text to check for in the value attribute
553
554
**Returns**: True if text is present in value, False otherwise
555
556
{ .api }
557
```python
558
def text_to_be_present_in_element_attribute(
559
locator: Tuple[str, str],
560
attribute_: str,
561
text_: str
562
) -> Callable[[WebDriverOrWebElement], bool]
563
```
564
565
**Description**: An expectation for checking if the given text is present in the element's specified attribute.
566
567
**Parameters**:
568
- `locator`: Used to find the element
569
- `attribute_`: The attribute to check
570
- `text_`: The text to check for in the attribute
571
572
**Returns**: True if text is present in attribute, False otherwise
573
574
**Example**:
575
```python
576
# Wait for text to appear in element
577
wait.until(
578
EC.text_to_be_present_in_element(
579
(By.ID, "status"), "Operation completed"
580
)
581
)
582
583
# Wait for text in input value
584
wait.until(
585
EC.text_to_be_present_in_element_value(
586
(By.ID, "search-box"), "selenium"
587
)
588
)
589
590
# Wait for text in custom attribute
591
wait.until(
592
EC.text_to_be_present_in_element_attribute(
593
(By.ID, "progress"), "data-status", "finished"
594
)
595
)
596
```
597
598
### Attribute Conditions
599
600
{ .api }
601
```python
602
def element_attribute_to_include(
603
locator: Tuple[str, str],
604
attribute_: str
605
) -> Callable[[WebDriverOrWebElement], bool]
606
```
607
608
**Description**: An expectation for checking if the element has a particular attribute.
609
610
**Parameters**:
611
- `locator`: Used to find the element
612
- `attribute_`: The attribute to check for
613
614
**Returns**: True if the attribute is present, False otherwise
615
616
**Example**:
617
```python
618
# Wait for element to have specific attribute
619
wait.until(
620
EC.element_attribute_to_include(
621
(By.ID, "upload-btn"), "disabled"
622
)
623
)
624
```
625
626
## Advanced Conditions
627
628
### Staleness
629
630
{ .api }
631
```python
632
def staleness_of(element: WebElement) -> Callable[[Any], bool]
633
```
634
635
**Description**: Wait until an element is no longer attached to the DOM.
636
637
**Parameters**:
638
- `element`: The element to wait for
639
640
**Returns**: False if the element is still attached, True otherwise
641
642
**Example**:
643
```python
644
# Store reference to element before DOM change
645
old_element = driver.find_element(By.ID, "dynamic-content")
646
647
# Trigger page refresh or dynamic update
648
refresh_button.click()
649
650
# Wait for old element to become stale
651
wait.until(EC.staleness_of(old_element))
652
653
# Now safe to find the new element
654
new_element = driver.find_element(By.ID, "dynamic-content")
655
```
656
657
### Frame Switching
658
659
{ .api }
660
```python
661
def frame_to_be_available_and_switch_to_it(
662
locator: Union[Tuple[str, str], str]
663
) -> Callable[[WebDriver], bool]
664
```
665
666
**Description**: An expectation for checking whether the given frame is available to switch to. If the frame is available, it switches to it.
667
668
**Parameters**:
669
- `locator`: Either a frame name/id (string) or a locator tuple
670
671
**Returns**: True if the frame was switched to, False otherwise
672
673
**Example**:
674
```python
675
# Wait for frame to be available and switch to it
676
wait.until(
677
EC.frame_to_be_available_and_switch_to_it("payment-frame")
678
)
679
680
# Or using locator
681
wait.until(
682
EC.frame_to_be_available_and_switch_to_it(
683
(By.ID, "checkout-iframe")
684
)
685
)
686
687
# Interact with frame content
688
frame_button = driver.find_element(By.ID, "pay-now")
689
frame_button.click()
690
691
# Switch back to default content
692
driver.switch_to.default_content()
693
```
694
695
### Window Management
696
697
{ .api }
698
```python
699
def number_of_windows_to_be(num_windows: int) -> Callable[[WebDriver], bool]
700
```
701
702
**Description**: An expectation for the number of windows to be a certain value.
703
704
**Parameters**:
705
- `num_windows`: The expected number of windows
706
707
**Returns**: True when the number of windows matches, False otherwise
708
709
{ .api }
710
```python
711
def new_window_is_opened(current_handles: List[str]) -> Callable[[WebDriver], bool]
712
```
713
714
**Description**: An expectation that a new window will be opened and have the number of windows handles increase.
715
716
**Parameters**:
717
- `current_handles`: List of current window handles
718
719
**Returns**: True when a new window is opened, False otherwise
720
721
**Example**:
722
```python
723
# Store current window handles
724
current_handles = driver.window_handles
725
726
# Click link that opens new window
727
external_link.click()
728
729
# Wait for new window to open
730
wait.until(EC.new_window_is_opened(current_handles))
731
732
# Switch to new window
733
new_handles = driver.window_handles
734
new_window = [h for h in new_handles if h not in current_handles][0]
735
driver.switch_to.window(new_window)
736
737
# Wait for specific number of windows
738
wait.until(EC.number_of_windows_to_be(3))
739
```
740
741
### Alert Handling
742
743
{ .api }
744
```python
745
def alert_is_present() -> Callable[[WebDriver], Union[Alert, Literal[False]]]
746
```
747
748
**Description**: An expectation for checking that an alert is present.
749
750
**Returns**: Alert object if present, False otherwise
751
752
**Example**:
753
```python
754
# Trigger alert
755
delete_button.click()
756
757
# Wait for alert and handle it
758
alert = wait.until(EC.alert_is_present())
759
alert_text = alert.text
760
alert.accept() # or alert.dismiss()
761
```
762
763
## Logical Conditions
764
765
### any_of()
766
767
{ .api }
768
```python
769
def any_of(*expected_conditions: Callable[[D], T]) -> Callable[[D], Union[Literal[False], T]]
770
```
771
772
**Description**: An expectation that any of multiple expected conditions is true. Equivalent to a logical OR.
773
774
**Parameters**:
775
- `*expected_conditions`: Variable number of expected condition functions
776
777
**Returns**: The result of the first condition that evaluates to True, False if none do
778
779
**Example**:
780
```python
781
# Wait for either success or error message
782
result = wait.until(
783
EC.any_of(
784
EC.visibility_of_element_located((By.ID, "success-message")),
785
EC.visibility_of_element_located((By.ID, "error-message"))
786
)
787
)
788
789
# Check which condition was met
790
if result.get_attribute("id") == "success-message":
791
print("Operation succeeded")
792
else:
793
print("Operation failed")
794
```
795
796
### all_of() (Custom Implementation)
797
798
While not built into Selenium, you can create an all_of condition:
799
800
```python
801
def all_of(*expected_conditions):
802
"""Wait for all conditions to be true"""
803
def _predicate(driver):
804
results = []
805
for condition in expected_conditions:
806
result = condition(driver)
807
if not result:
808
return False
809
results.append(result)
810
return results
811
return _predicate
812
813
# Usage
814
wait.until(
815
all_of(
816
EC.visibility_of_element_located((By.ID, "form")),
817
EC.element_to_be_clickable((By.ID, "submit")),
818
EC.text_to_be_present_in_element((By.ID, "status"), "Ready")
819
)
820
)
821
```
822
823
## Custom Expected Conditions
824
825
You can create custom expected conditions by following the same pattern:
826
827
```python
828
def element_has_css_class(locator, css_class):
829
"""Wait for element to have a specific CSS class"""
830
def _predicate(driver):
831
try:
832
element = driver.find_element(*locator)
833
classes = element.get_attribute("class")
834
return css_class in classes.split() if classes else False
835
except:
836
return False
837
return _predicate
838
839
def page_source_contains(text):
840
"""Wait for page source to contain specific text"""
841
def _predicate(driver):
842
return text in driver.page_source
843
return _predicate
844
845
def element_count_to_be(locator, count):
846
"""Wait for specific number of elements"""
847
def _predicate(driver):
848
elements = driver.find_elements(*locator)
849
return len(elements) == count
850
return _predicate
851
852
# Usage
853
wait.until(element_has_css_class((By.ID, "status"), "active"))
854
wait.until(page_source_contains("Welcome"))
855
wait.until(element_count_to_be((By.CLASS_NAME, "item"), 5))
856
```
857
858
## Best Practices and Examples
859
860
### 1. Combine Multiple Conditions
861
862
```python
863
def wait_for_form_ready(driver):
864
"""Wait for form to be completely ready for interaction"""
865
wait = WebDriverWait(driver, 10)
866
867
# Wait for form to be visible
868
form = wait.until(
869
EC.visibility_of_element_located((By.ID, "registration-form"))
870
)
871
872
# Wait for all required fields to be present
873
wait.until(
874
EC.presence_of_all_elements_located((By.CSS_SELECTOR, "input[required]"))
875
)
876
877
# Wait for submit button to be clickable
878
wait.until(
879
EC.element_to_be_clickable((By.ID, "submit-btn"))
880
)
881
882
return form
883
```
884
885
### 2. Handle Dynamic Content
886
887
```python
888
def wait_for_search_results(driver, search_term):
889
"""Wait for search results to load and contain expected content"""
890
wait = WebDriverWait(driver, 15)
891
892
# Wait for loading to disappear
893
wait.until(
894
EC.invisibility_of_element_located((By.ID, "search-loading"))
895
)
896
897
# Wait for results container to be visible
898
results_container = wait.until(
899
EC.visibility_of_element_located((By.ID, "search-results"))
900
)
901
902
# Wait for at least one result or no-results message
903
wait.until(
904
EC.any_of(
905
EC.presence_of_element_located((By.CLASS_NAME, "result-item")),
906
EC.text_to_be_present_in_element(
907
(By.ID, "search-results"), "No results found"
908
)
909
)
910
)
911
912
return results_container
913
```
914
915
### 3. Error Handling with Waits
916
917
```python
918
def safe_wait_and_click(driver, locator, timeout=10):
919
"""Safely wait for element and click with error handling"""
920
try:
921
wait = WebDriverWait(driver, timeout)
922
element = wait.until(EC.element_to_be_clickable(locator))
923
element.click()
924
return True
925
except TimeoutException:
926
print(f"Element {locator} not clickable within {timeout} seconds")
927
return False
928
except Exception as e:
929
print(f"Error clicking element {locator}: {e}")
930
return False
931
932
# Usage
933
if safe_wait_and_click(driver, (By.ID, "submit-btn")):
934
print("Successfully clicked submit button")
935
else:
936
print("Failed to click submit button")
937
```
938
939
### 4. Polling with Custom Logic
940
941
```python
942
def wait_for_api_response(driver, timeout=30):
943
"""Wait for API response to be displayed"""
944
wait = WebDriverWait(driver, timeout, poll_frequency=0.5)
945
946
def api_response_ready(driver):
947
try:
948
status = driver.find_element(By.ID, "api-status")
949
response = driver.find_element(By.ID, "api-response")
950
951
# Check if API call is complete
952
if status.text == "Complete":
953
# Check if response has content
954
if response.text and response.text != "Loading...":
955
return {"status": status.text, "response": response.text}
956
return False
957
except:
958
return False
959
960
return wait.until(api_response_ready)
961
962
# Usage
963
result = wait_for_api_response(driver)
964
print(f"API Status: {result['status']}")
965
print(f"Response: {result['response']}")
966
```
967
968
### 5. Fluent Wait Pattern
969
970
```python
971
from selenium.webdriver.support.ui import WebDriverWait
972
from selenium.common.exceptions import TimeoutException
973
974
class FluentWait:
975
def __init__(self, driver, timeout=10, poll_frequency=0.5):
976
self.driver = driver
977
self.timeout = timeout
978
self.poll_frequency = poll_frequency
979
self.ignored_exceptions = []
980
981
def ignoring(self, *exceptions):
982
"""Add exceptions to ignore"""
983
self.ignored_exceptions.extend(exceptions)
984
return self
985
986
def until(self, condition, message=None):
987
"""Wait until condition is met"""
988
wait = WebDriverWait(
989
self.driver,
990
self.timeout,
991
self.poll_frequency,
992
tuple(self.ignored_exceptions) if self.ignored_exceptions else None
993
)
994
return wait.until(condition, message)
995
996
# Usage
997
from selenium.common.exceptions import StaleElementReferenceException
998
999
result = FluentWait(driver, timeout=15, poll_frequency=0.2)\
1000
.ignoring(StaleElementReferenceException)\
1001
.until(EC.element_to_be_clickable((By.ID, "dynamic-btn")))
1002
```
1003
1004
This comprehensive guide covers all aspects of WebDriverWait and Expected Conditions, providing you with the tools to handle any dynamic web content scenario in your Selenium automation.