Skip to content

Commit 4820105

Browse files
committed
Autowaiting
1 parent 675792a commit 4820105

File tree

5 files changed

+164
-0
lines changed

5 files changed

+164
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Welcome to the Selenium + Python Template Project! This repository provides a we
88
- **Scalability**: Easy to extend for larger projects.
99
- **Integration-Ready**: Built-in support for integrating with APIs, CI/CD pipelines, and reporting tools.
1010
- **Cross-Browser Support**: Pre-configured WebDriver factory for managing multiple browsers.
11+
- **Auto-waiting**: `WebDriver.click()` waits for an element to be clickable automatically.
1112

1213
## 🏗️ Project Structure
1314

conftest.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import pytest
44
from selenium.webdriver.remote.webdriver import WebDriver
55

6+
from utils.autowait import enable_autowait, disable_autowait
67
from webdriver_factory import get_driver
78

89

@@ -12,3 +13,10 @@ def driver() -> Generator[WebDriver, None, None]:
1213
drv = get_driver()
1314
yield drv
1415
drv.quit()
16+
17+
18+
@pytest.fixture
19+
def autowait() -> Generator[None, None, None]:
20+
enable_autowait()
21+
yield
22+
disable_autowait()

utils/autowait.html

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Title</title>
6+
</head>
7+
<body>
8+
9+
<p>
10+
<button id="first" onclick="first_button_on_click()">Enable button</button>
11+
</p>
12+
<p>
13+
<button id="second" disabled onclick="second_button_on_click()">Second button</button>
14+
</p>
15+
<p id="placeholder"></p>
16+
<p>
17+
<button id="third" onclick="third_button_on_click()">Enable input</button>
18+
</p>
19+
<p>
20+
<label for="input">Disabled input</label><input id="input" disabled />
21+
</p>
22+
<script>
23+
function first_button_on_click() {
24+
setTimeout(() => {
25+
document.getElementById("second").disabled = false;
26+
}, 1500);
27+
}
28+
29+
function second_button_on_click() {
30+
document.getElementById("placeholder").innerText = "Second button works!";
31+
}
32+
33+
function third_button_on_click() {
34+
setTimeout(() => {
35+
document.getElementById("input").disabled = false;
36+
}, 1500);
37+
}
38+
</script>
39+
</body>
40+
</html>

utils/autowait.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Monkey-patching WebElement.click to add retry logic for unclickable elements.
2+
# This patch is applied globally to all WebElement instances.
3+
from selenium.webdriver.remote.webelement import WebElement
4+
from selenium.webdriver.support import expected_conditions as EC
5+
from selenium.webdriver.support.wait import WebDriverWait
6+
7+
8+
def patched_click_factory(timeout: float):
9+
"""
10+
Args:
11+
timeout (float): Maximum time to wait for the element to become clickable.
12+
"""
13+
14+
def patched_click(self):
15+
"""
16+
Patches the WebElement's click method to wait for the element to be clickable.
17+
Args:
18+
self: WebElement object.
19+
"""
20+
driver = self.parent
21+
WebDriverWait(driver, timeout).until(EC.element_to_be_clickable(self))
22+
23+
self._original_click()
24+
25+
return patched_click
26+
27+
28+
def patched_send_keys_factory(timeout: float):
29+
"""
30+
Args:
31+
timeout (float): Maximum time to wait for the element to become clickable.
32+
"""
33+
34+
def patched_send_keys(self, *args, **kwargs):
35+
"""
36+
Patches the WebElement's click method to wait for the element to be clickable.
37+
Args:
38+
self: WebElement object.
39+
"""
40+
driver = self.parent
41+
WebDriverWait(driver, timeout).until(EC.element_to_be_clickable(self))
42+
43+
self._original_send_keys(*args, **kwargs)
44+
45+
return patched_send_keys
46+
47+
48+
def enable_autowait(timeout: float=10.0) -> None:
49+
"""
50+
Enables auto-waiting globally for all WebDriver.click() invokcations
51+
Args:
52+
self: WebElement object.
53+
timeout (float): Maximum time to wait for the element to become clickable.
54+
"""
55+
assert not hasattr(WebElement, "_original_click"), "Autowait already enabled."
56+
assert not hasattr(WebElement, "_original_send_keys"), "Autowait already enabled."
57+
WebElement._original_click = WebElement.click
58+
WebElement.click = patched_click_factory(timeout)
59+
WebElement._original_send_keys = WebElement.send_keys
60+
WebElement.send_keys = patched_send_keys_factory(timeout)
61+
62+
63+
def disable_autowait() -> None:
64+
assert hasattr(WebElement, "_original_click"), "Autowait is not enabled."
65+
assert hasattr(WebElement, "_original_send_keys"), "Autowait is not enabled."
66+
WebElement.click = WebElement._original_click
67+
del(WebElement._original_click)
68+
WebElement.send_keys = WebElement._original_send_keys
69+
del(WebElement._original_send_keys)

utils/test_autowait.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import os
2+
3+
import pytest
4+
from selenium.common import ElementNotInteractableException
5+
from selenium.webdriver.common.by import By
6+
from selenium.webdriver.remote.webdriver import WebDriver
7+
8+
9+
def test_click(driver: WebDriver):
10+
driver.get(f"file://{os.path.abspath('utils/autowait.html')}")
11+
first_button = driver.find_element(By.ID, "first")
12+
second_button = driver.find_element(By.ID, "second")
13+
first_button.click()
14+
second_button.click()
15+
assert driver.find_element(By.ID, "placeholder").text == ""
16+
17+
18+
def test_send_keys(driver: WebDriver):
19+
driver.get(f"file://{os.path.abspath('utils/autowait.html')}")
20+
21+
third_button = driver.find_element(By.ID, "third")
22+
third_button.click()
23+
input_el = driver.find_element(By.ID, "input")
24+
25+
with pytest.raises(ElementNotInteractableException) as exc:
26+
input_el.send_keys("hello")
27+
28+
29+
def test_click_auto_wait(driver: WebDriver, autowait):
30+
driver.get(f"file://{os.path.abspath('utils/autowait.html')}")
31+
first_button = driver.find_element(By.ID, "first")
32+
second_button = driver.find_element(By.ID, "second")
33+
first_button.click()
34+
second_button.click()
35+
assert driver.find_element(By.ID, "placeholder").text == "Second button works!"
36+
37+
38+
def test_send_keys_auto_wait(driver: WebDriver, autowait):
39+
driver.get(f"file://{os.path.abspath('utils/autowait.html')}")
40+
41+
third_button = driver.find_element(By.ID, "third")
42+
third_button.click()
43+
input_el = driver.find_element(By.ID, "input")
44+
input_el.send_keys("hello")
45+
46+
assert input_el.get_attribute("value") == "hello"

0 commit comments

Comments
 (0)