Skip to content

Commit fe6bb19

Browse files
authored
Merge pull request #28 from levi-rs/add-find-one-method
Add find_one method to waiter
2 parents f2db2b5 + 01fd85b commit fe6bb19

File tree

4 files changed

+99
-16
lines changed

4 files changed

+99
-16
lines changed

explicit/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
__version__ = get_versions()['version']
55
del get_versions
66

7-
ID = By.ID
7+
CLASS_NAME = By.CLASS_NAME
88
CSS = By.CSS_SELECTOR
9+
ID = By.ID
10+
LINK = By.LINK_TEXT
11+
NAME = By.NAME
12+
PARTIAL_LINK = By.PARTIAL_LINK_TEXT
13+
TAG = By.TAG_NAME
914
XPATH = By.XPATH

explicit/waiter.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
from selenium.webdriver.support.ui import WebDriverWait
1111
from selenium.webdriver.support import expected_conditions as EC
1212

13-
from explicit import CSS
13+
from explicit import (
14+
CLASS_NAME, CSS, ID, LINK, NAME, PARTIAL_LINK, TAG, XPATH)
1415

1516
TIMEOUT = 30
1617
""" int: Default timeout value, in seconds"""
@@ -64,6 +65,48 @@ def find_elements(driver, elem_path, by=CSS, timeout=TIMEOUT, poll_frequency=0.5
6465
return wait.until(EC.presence_of_all_elements_located((by, elem_path)))
6566

6667

68+
def find_one(driver, locator_list, elem_type=CSS, timeout=TIMEOUT):
69+
"""
70+
Args:
71+
driver (selenium webdriver): Selenium webdriver object
72+
locator_list (:obj: `list` of :obj: `str`): List of CSS selector strings
73+
elem_type (Selenium By types): Selenium By type (i.e. By.CSS_SELECTOR)
74+
timeout (int): Number of seconds to wait before timing out
75+
76+
Returns:
77+
Selenium Element
78+
79+
Raises:
80+
TimeoutException: Raised if no elements are found within the TIMEOUT
81+
"""
82+
def _find_one(driver):
83+
""" Expected Condition to find and return first located element """
84+
finders = {
85+
CLASS_NAME: driver.find_elements_by_class_name,
86+
CSS: driver.find_elements_by_css_selector,
87+
ID: driver.find_elements_by_id,
88+
LINK: driver.find_elements_by_link_text,
89+
NAME: driver.find_elements_by_name,
90+
PARTIAL_LINK: driver.find_elements_by_partial_link_text,
91+
TAG: driver.find_elements_by_tag_name,
92+
XPATH: driver.find_elements_by_xpath
93+
}
94+
95+
elems = [finders[elem_type](loc) for loc in locator_list]
96+
97+
if any([len(elem_list) > 0 for elem_list in elems]):
98+
return elems
99+
else:
100+
return False
101+
102+
raw_results = WebDriverWait(driver, timeout).until(_find_one)
103+
104+
# Pull out any found elements from lists
105+
results = [elem for elem_list in raw_results for elem in elem_list]
106+
107+
return results.pop() if len(results) == 1 else results
108+
109+
67110
def find_write(driver, elem_path, write_str, clear_first=True, send_enter=False,
68111
by=CSS, timeout=TIMEOUT, poll_frequency=0.5):
69112
""" Find a writable element and write to it

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
'pytest',
3333
],
3434
classifiers=[
35-
'Development Status :: 3 - Alpha',
35+
'Development Status :: 4 - Beta',
3636
'Intended Audience :: Developers',
3737
'Natural Language :: English',
3838
'License :: OSI Approved :: MIT License',

tests/test_waiter.py

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,9 @@
33
except ImportError:
44
from unittest import mock
55

6-
from selenium.webdriver.common.by import By
76
from selenium.webdriver.common.keys import Keys
87

9-
from explicit import waiter
10-
11-
CSS = By.CSS_SELECTOR
8+
from explicit import waiter, CSS, ID, XPATH
129

1310

1411
def test_find_element_with_defaults(driver, element):
@@ -35,13 +32,13 @@ def test_find_element_with_non_defaults(driver, element):
3532

3633
driver.find_element.side_effect = [None, element]
3734

38-
elem = waiter.find_element(driver, mock_xpath_path, by=By.XPATH)
35+
elem = waiter.find_element(driver, mock_xpath_path, by=XPATH)
3936

4037
assert driver.find_element.called
4138
assert elem is element
4239
assert len(driver.mock_calls) == 2
43-
assert driver.find_element.call_args_list == [mock.call(By.XPATH, mock_xpath_path),
44-
mock.call(By.XPATH, mock_xpath_path)]
40+
assert driver.find_element.call_args_list == [mock.call(XPATH, mock_xpath_path),
41+
mock.call(XPATH, mock_xpath_path)]
4542

4643

4744
def test_find_elements_with_defaults(driver, element):
@@ -69,14 +66,14 @@ def test_find_elements_with_non_defaults(driver, element):
6966

7067
driver.find_elements.side_effect = [None, [element, element]]
7168

72-
elem_list = waiter.find_elements(driver, mock_xpath_path, by=By.XPATH)
69+
elem_list = waiter.find_elements(driver, mock_xpath_path, by=XPATH)
7370

7471
assert driver.find_elements.called
7572
assert isinstance(elem_list, list)
7673
assert all(map(lambda x: x is element, elem_list))
7774
assert len(driver.mock_calls) == 2
78-
assert driver.find_elements.call_args_list == [mock.call(By.XPATH, mock_xpath_path),
79-
mock.call(By.XPATH, mock_xpath_path)]
75+
assert driver.find_elements.call_args_list == [mock.call(XPATH, mock_xpath_path),
76+
mock.call(XPATH, mock_xpath_path)]
8077

8178

8279
def test_find_write_with_defaults(driver, element):
@@ -113,17 +110,55 @@ def test_find_write_with_non_defaults(driver, element):
113110
driver.find_element.side_effect = [None, element]
114111

115112
elem = waiter.find_write(driver, mock_id_path, mock_write_string,
116-
by=By.ID, clear_first=False, send_enter=True)
113+
by=ID, clear_first=False, send_enter=True)
117114

118115
# Element locating asserts
119116
assert driver.find_element.called
120117
assert elem is element
121118
assert len(driver.mock_calls) == 2
122-
assert driver.find_element.call_args_list == [mock.call(By.ID, mock_id_path),
123-
mock.call(By.ID, mock_id_path)]
119+
assert driver.find_element.call_args_list == [mock.call(ID, mock_id_path),
120+
mock.call(ID, mock_id_path)]
124121

125122
# Element writing asserts
126123
assert not elem.clear.called
127124
assert len(elem.send_keys.call_args_list) == 2
128125
assert elem.send_keys.call_args_list == [mock.call(mock_write_string),
129126
mock.call(Keys.ENTER)]
127+
128+
129+
def test_find_one_with_defaults(driver, element):
130+
""" Verify the waiter can locate one of several possible elements """
131+
side_effects = [[], [], [element, ], []]
132+
driver.find_elements_by_css_selector.side_effect = side_effects
133+
134+
div1 = 'div.mock_1'
135+
div2 = 'div.mock_2'
136+
137+
locators = [div1, div2]
138+
elem = waiter.find_one(driver, locators)
139+
140+
assert elem is element
141+
assert driver.method_calls == [mock.call.find_elements_by_css_selector(div1),
142+
mock.call.find_elements_by_css_selector(div2),
143+
mock.call.find_elements_by_css_selector(div1),
144+
mock.call.find_elements_by_css_selector(div2)]
145+
146+
147+
def test_find_one_with_non_defaults(driver, element):
148+
""" Verify the waiter can locate one of several possible elements,
149+
using non-default function parameters
150+
"""
151+
side_effects = [[], [], [element, ], []]
152+
driver.find_elements_by_id.side_effect = side_effects
153+
154+
id_1 = 'mock_1'
155+
id_2 = 'mock_2'
156+
157+
locators = [id_1, id_2]
158+
elem = waiter.find_one(driver, locators, elem_type=ID, timeout=20)
159+
160+
assert elem is element
161+
assert driver.method_calls == [mock.call.find_elements_by_id(id_1),
162+
mock.call.find_elements_by_id(id_2),
163+
mock.call.find_elements_by_id(id_1),
164+
mock.call.find_elements_by_id(id_2)]

0 commit comments

Comments
 (0)