-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbrowser.py
266 lines (222 loc) · 9.15 KB
/
browser.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
from selenium import webdriver
import selenium.common.exceptions as selenium_exceptions
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.firefox.options import Options
from selenium.webdriver import ActionChains
import os
import globals
import logging
import sys
import commands
import time
driver = None # type:webdriver.Firefox
# define the Selenium browser configuration options
# each browser needs to be instantiated differently, so this dictionary allows this to happen
BROWSERS = {
"firefox": {
"class": webdriver.Firefox,
"options": webdriver.firefox.options.Options,
},
"chrome": {
"class": webdriver.Chrome,
"options": webdriver.chrome.options.Options,
},
"edge": {
"class": webdriver.Edge,
},
"ie": {
"class": webdriver.Ie
},
"opera": {
"class": webdriver.Opera
}
}
def initialize_browser(browser: str, headless: bool = False):
"""
Initializes the specified web browser with options
:param browser: the name of the browser to use (case insensitive) (ie. Firefox, Chrome...)
:param headless: if the browser should be run in headless mode. WARNING: Experamental
"""
global driver
browser_data = BROWSERS[browser.lower()]
# create the specific browser options (if necessary)
options = None
if "options" in browser_data:
options = browser_data["options"]()
options.headless = headless
# access the driver's path if specified
driver_path = None
if "driver" in browser_data:
driver_path = browser_data["driver"]
driver_path = os.path.join(os.path.dirname(__file__), driver_path)
# instantiate the browser with necessary configurations
os.environ['PATH'] += ";" + os.path.join(os.path.dirname(__file__), "webDrivers")
if driver_path is None:
driver = browser_data["class"]()
elif options is None:
driver = browser_data["class"](driver_path)
else:
driver = browser_data["class"](driver_path, options=options)
# set the implicit wait time (maximum time to wait before giving up on finding elements)
# and create the global action chain
driver.set_page_load_timeout(10)
driver.implicitly_wait(10)
globals.action_chain = ActionChains(driver)
globals.original_window = driver.window_handles[0]
def get_element_selector(selector: str, index=0, raise_exception_on_failure=False, get_mode=False):
"""
Returns the element described by the parameters with the dynamic timeout
:param selector: The CSS selector
(use <selector>%<text> to search all elements that match "selector" with the inner text of "text")
:param index: if multiple selectors exist, which selector to return (default=0)
:param raise_exception_on_failure: the Python exception to raise if the element could not be found
(default=False - raise AWT SelectorNotFoundException)
:param get_mode: if all matching elements should be returned (ignore index param) (Default=False)
:return: matching Selenium element(s)
"""
start = time.time()
while True:
try:
return locate_element(selector, index, RecursionError, get_mode)
except RecursionError:
if time.time() - start > globals.maximum_delay:
if raise_exception_on_failure is False:
raise_error(
"SelectorNotFoundException",
"Could not find {}th occurrence of selector {}".format(index, selector)
)
else:
raise raise_exception_on_failure
time.sleep(globals.current_delay)
globals.current_delay *= 2
def locate_element(selector: str, index=0, raise_exception_on_failure=False, get_mode=False):
"""
Returns the element described by the parameters
NOTE: call 'get_element_selector', not this funciton!
:param selector: The CSS selector
(use <selector>%<text> to search all elements that match "selector" with the inner text of "text")
:param index: if multiple selectors exist, which selector to return (default=0)
:param raise_exception_on_failure: the Python exception to raise if the element could not be found
(default=False - raise AWT SelectorNotFoundException)
:param get_mode: if all matching elements should be returned (ignore index param) (Default=False)
:return: matching Selenium element(s)
"""
index = int(index)
inner_text_search = False
text = None
elements = []
# check if the search involves a inner text search
if "%" in selector:
# if a text search is being executed, split into the CSS selector, and inner text
s = selector.split("%")
element, text = s[0], "%".join(s[1:])
inner_text_search = True
# raise an error if no selector has been specified
if element == "":
raise_error(
"InvalidSelectorException", "A selector must precede the % (Search by tag text) selector ({})".format(
selector
)
)
else:
# if not in text search mode, copy the selector string by value
element = selector[:]
try:
# get all elements with the specified CSS selector
elements = driver.find_elements_by_css_selector(element)
except selenium_exceptions.InvalidSelectorException:
# wrapper Invalid Selector exception
raise_error(
"InvalidSelectorException", "The provided selector ({}) is invalid.".format(
selector
)
)
inner_text_index = 0
if inner_text_search:
# if an inner text search is done, filter out all of the tags which do not match the specified inner text
# a second array is used as you can not modify the size of an array as you are iterating through it
matches = []
for e in elements:
if e.text == text:
if not get_mode:
if inner_text_index == index:
if globals.highlight_mode:
highlight_element(e)
return e
inner_text_index += 1
matches.append(e)
elements = matches
if get_mode:
# if get mode is enabled, highlight all matches (if running in highlight mode)
if globals.highlight_mode:
for e in elements:
highlight_element(e)
# and return all elements
return elements
else:
try:
# if we return a single item (get mode disabled), highlight the match (if running in highlight mode)
if globals.highlight_mode:
highlight_element(elements[index])
# and return the match
return elements[index]
except IndexError:
# if no matches exist, raise the proper error
if raise_exception_on_failure is False:
raise_error(
"SelectorNotFoundException", "Could not find {}th occurrence of selector {}".format(index, selector)
)
else:
raise raise_exception_on_failure
"""
SOME of the functions which are bound to commands are below this comment. See commands.py for the rest
"""
def highlight_element(selector, index=0, color="red", border=2):
"""
Sets the element's border to a 2px solid red border
:param selector: the item to highlight (either Selenium element, or string CSS selector)
:param index: the index of the item to highlight
:param color: the color of the border (Default=red)
:param border: the line thickness of the border in px (Default=2)
"""
# get the element if the selector is a CSS selector
if type(selector) is str:
selector = get_element_selector(selector, int(index))
# apply the style to the element
driver.execute_script(
"arguments[0].setAttribute('style', arguments[1]);",
selector,
"border: {0}px solid {1};".format(border, color)
)
def kill(status=0):
"""
Kills execution of the script
"""
if globals.final_screenshot is not False:
commands.screenshot(globals.final_screenshot)
# if pause mode is enabled, pause execution
if globals.terminate_pause:
commands.pause()
# close the driver and quit the application
driver.quit()
commands.log(
"--------[ Finished in {}s with exit code {} ]--------".format(
round(time.time() - globals.start_time, 2), status
),
"info" if status == 0 else "error"
)
sys.exit(status)
def raise_error(error_type, message):
"""
Raises an error and terminates the application
:param error_type: the error type. Must end with the text "Exception" (Not enforced, just best practice)
:param message: the message to attach to the error
"""
# log the error message as fatal (CRITICAL)
logging.fatal("{} - {} @ File: '{}' - Block: '{}' - Line: {}".format(
error_type, message, globals.current_code_block.filename, globals.current_code_block.block_name,
globals.current_code_block.current_line
))
# terminate and quit
kill(2)
sys.exit(2)