cucu 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cucu might be problematic. Click here for more details.
- cucu/__init__.py +38 -0
- cucu/ansi_parser.py +58 -0
- cucu/behave_tweaks.py +196 -0
- cucu/browser/__init__.py +0 -0
- cucu/browser/core.py +80 -0
- cucu/browser/frames.py +106 -0
- cucu/browser/selenium.py +323 -0
- cucu/browser/selenium_tweaks.py +27 -0
- cucu/cli/__init__.py +3 -0
- cucu/cli/core.py +788 -0
- cucu/cli/run.py +207 -0
- cucu/cli/steps.py +137 -0
- cucu/cli/thread_dumper.py +55 -0
- cucu/config.py +440 -0
- cucu/edgedriver_autoinstaller/README.md +1 -0
- cucu/edgedriver_autoinstaller/__init__.py +37 -0
- cucu/edgedriver_autoinstaller/utils.py +231 -0
- cucu/environment.py +283 -0
- cucu/external/jquery/jquery-3.5.1.min.js +2 -0
- cucu/formatter/__init__.py +0 -0
- cucu/formatter/cucu.py +261 -0
- cucu/formatter/json.py +321 -0
- cucu/formatter/junit.py +289 -0
- cucu/fuzzy/__init__.py +3 -0
- cucu/fuzzy/core.py +107 -0
- cucu/fuzzy/fuzzy.js +253 -0
- cucu/helpers.py +875 -0
- cucu/hooks.py +205 -0
- cucu/language_server/__init__.py +3 -0
- cucu/language_server/core.py +114 -0
- cucu/lint/__init__.py +0 -0
- cucu/lint/linter.py +397 -0
- cucu/lint/rules/format.yaml +125 -0
- cucu/logger.py +113 -0
- cucu/matcher/__init__.py +0 -0
- cucu/matcher/core.py +30 -0
- cucu/page_checks.py +63 -0
- cucu/reporter/__init__.py +3 -0
- cucu/reporter/external/bootstrap.min.css +7 -0
- cucu/reporter/external/bootstrap.min.js +7 -0
- cucu/reporter/external/dataTables.bootstrap.min.css +1 -0
- cucu/reporter/external/dataTables.bootstrap.min.js +14 -0
- cucu/reporter/external/jquery-3.5.1.min.js +2 -0
- cucu/reporter/external/jquery.dataTables.min.js +192 -0
- cucu/reporter/external/popper.min.js +5 -0
- cucu/reporter/favicon.png +0 -0
- cucu/reporter/html.py +452 -0
- cucu/reporter/templates/feature.html +72 -0
- cucu/reporter/templates/flat.html +48 -0
- cucu/reporter/templates/index.html +49 -0
- cucu/reporter/templates/layout.html +109 -0
- cucu/reporter/templates/scenario.html +200 -0
- cucu/steps/__init__.py +27 -0
- cucu/steps/base_steps.py +88 -0
- cucu/steps/browser_steps.py +337 -0
- cucu/steps/button_steps.py +91 -0
- cucu/steps/checkbox_steps.py +111 -0
- cucu/steps/command_steps.py +181 -0
- cucu/steps/comment_steps.py +17 -0
- cucu/steps/draggable_steps.py +168 -0
- cucu/steps/dropdown_steps.py +467 -0
- cucu/steps/file_input_steps.py +80 -0
- cucu/steps/filesystem_steps.py +144 -0
- cucu/steps/flow_control_steps.py +198 -0
- cucu/steps/image_steps.py +37 -0
- cucu/steps/input_steps.py +301 -0
- cucu/steps/link_steps.py +63 -0
- cucu/steps/menuitem_steps.py +39 -0
- cucu/steps/platform_steps.py +29 -0
- cucu/steps/radio_steps.py +187 -0
- cucu/steps/step_utils.py +55 -0
- cucu/steps/tab_steps.py +68 -0
- cucu/steps/table_steps.py +437 -0
- cucu/steps/tables.js +28 -0
- cucu/steps/text_steps.py +78 -0
- cucu/steps/variable_steps.py +100 -0
- cucu/steps/webserver_steps.py +40 -0
- cucu/utils.py +269 -0
- cucu-1.0.0.dist-info/METADATA +424 -0
- cucu-1.0.0.dist-info/RECORD +83 -0
- cucu-1.0.0.dist-info/WHEEL +4 -0
- cucu-1.0.0.dist-info/entry_points.txt +2 -0
- cucu-1.0.0.dist-info/licenses/LICENSE +32 -0
cucu/browser/selenium.py
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import chromedriver_autoinstaller
|
|
4
|
+
import geckodriver_autoinstaller
|
|
5
|
+
import urllib3
|
|
6
|
+
from selenium import webdriver
|
|
7
|
+
from selenium.webdriver.chrome.options import Options as ChromeOptions
|
|
8
|
+
from selenium.webdriver.common.by import By
|
|
9
|
+
from selenium.webdriver.edge.options import Options as EdgeOptions
|
|
10
|
+
from selenium.webdriver.firefox.options import Options as FirefoxOptions
|
|
11
|
+
from selenium.webdriver.remote.remote_connection import RemoteConnection
|
|
12
|
+
|
|
13
|
+
from cucu import config, edgedriver_autoinstaller, logger
|
|
14
|
+
from cucu.browser.core import Browser
|
|
15
|
+
from cucu.browser.frames import search_in_all_frames
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DisableLogger:
|
|
19
|
+
def __enter__(self):
|
|
20
|
+
logging.disable(logging.CRITICAL)
|
|
21
|
+
|
|
22
|
+
def __exit__(self, exit_type, exit_value, exit_traceback):
|
|
23
|
+
logging.disable(logging.NOTSET)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def init():
|
|
27
|
+
"""
|
|
28
|
+
initialize any selenium specific needs
|
|
29
|
+
"""
|
|
30
|
+
if config.CONFIG["CUCU_BROWSER"] == "chrome":
|
|
31
|
+
try:
|
|
32
|
+
with DisableLogger():
|
|
33
|
+
# auto install chromedriver if not present
|
|
34
|
+
chromedriver_autoinstaller.install()
|
|
35
|
+
except: # noqa: E722
|
|
36
|
+
logging.debug("unable to auto install chromedriver")
|
|
37
|
+
|
|
38
|
+
if config.CONFIG["CUCU_BROWSER"] == "firefox":
|
|
39
|
+
# https://github.com/mozilla/geckodriver/issues/330
|
|
40
|
+
logger.warn("browser console logs not available on firefox")
|
|
41
|
+
geckodriver_autoinstaller.install()
|
|
42
|
+
|
|
43
|
+
if config.CONFIG["CUCU_BROWSER"] == "edge":
|
|
44
|
+
edgedriver_autoinstaller.install()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Selenium(Browser):
|
|
48
|
+
def __init__(self):
|
|
49
|
+
self.driver = None
|
|
50
|
+
|
|
51
|
+
def open(
|
|
52
|
+
self, browser, headless=False, selenium_remote_url=None, detach=False
|
|
53
|
+
):
|
|
54
|
+
if selenium_remote_url is None:
|
|
55
|
+
init()
|
|
56
|
+
|
|
57
|
+
timeout = float(config.CONFIG["CUCU_SELENIUM_DEFAULT_TIMEOUT_S"])
|
|
58
|
+
RemoteConnection.set_timeout(timeout)
|
|
59
|
+
|
|
60
|
+
height = config.CONFIG["CUCU_BROWSER_WINDOW_HEIGHT"]
|
|
61
|
+
width = config.CONFIG["CUCU_BROWSER_WINDOW_WIDTH"]
|
|
62
|
+
cucu_downloads_dir = config.CONFIG["CUCU_BROWSER_DOWNLOADS_DIR"]
|
|
63
|
+
ignore_ssl_certificate_errors = config.CONFIG[
|
|
64
|
+
"CUCU_IGNORE_SSL_CERTIFICATE_ERRORS"
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
if browser.startswith("chrome"):
|
|
68
|
+
options = ChromeOptions()
|
|
69
|
+
options.add_experimental_option("detach", detach)
|
|
70
|
+
|
|
71
|
+
prefs = {"download.default_directory": cucu_downloads_dir}
|
|
72
|
+
options.add_experimental_option("prefs", prefs)
|
|
73
|
+
|
|
74
|
+
options.add_argument(f"--window-size={width},{height}")
|
|
75
|
+
options.add_argument("--disable-dev-shm-usage")
|
|
76
|
+
|
|
77
|
+
if headless:
|
|
78
|
+
options.add_argument("--headless")
|
|
79
|
+
|
|
80
|
+
if ignore_ssl_certificate_errors:
|
|
81
|
+
options.add_argument("ignore-certificate-errors")
|
|
82
|
+
options.set_capability("goog:loggingPrefs", {"browser": "ALL"})
|
|
83
|
+
|
|
84
|
+
if selenium_remote_url is not None:
|
|
85
|
+
logger.debug(f"webdriver.Remote init: {selenium_remote_url}")
|
|
86
|
+
try:
|
|
87
|
+
self.driver = webdriver.Remote(
|
|
88
|
+
command_executor=selenium_remote_url,
|
|
89
|
+
options=options,
|
|
90
|
+
)
|
|
91
|
+
except urllib3.exceptions.ReadTimeoutError:
|
|
92
|
+
print("*" * 80)
|
|
93
|
+
print(
|
|
94
|
+
"* unable to connect to the remote selenium setup,"
|
|
95
|
+
" you may need to restart it"
|
|
96
|
+
)
|
|
97
|
+
print("*" * 80)
|
|
98
|
+
print("")
|
|
99
|
+
raise
|
|
100
|
+
else:
|
|
101
|
+
logger.debug("webdriver.Chrome init")
|
|
102
|
+
self.driver = webdriver.Chrome(
|
|
103
|
+
options=options,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
elif browser.startswith("firefox"):
|
|
107
|
+
options = FirefoxOptions()
|
|
108
|
+
|
|
109
|
+
options.set_preference("browser.download.folderList", 2)
|
|
110
|
+
options.set_preference(
|
|
111
|
+
"browser.download.manager.showWhenStarting", False
|
|
112
|
+
)
|
|
113
|
+
options.set_preference("browser.download.dir", cucu_downloads_dir)
|
|
114
|
+
options.set_preference(
|
|
115
|
+
"browser.helperApps.neverAsk.saveToDisk",
|
|
116
|
+
"images/jpeg, application/pdf, application/octet-stream, text/plain",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if ignore_ssl_certificate_errors:
|
|
120
|
+
options.accept_insecure_certs = True
|
|
121
|
+
|
|
122
|
+
options.add_argument(f"--width={width}")
|
|
123
|
+
options.add_argument(f"--height={height}")
|
|
124
|
+
|
|
125
|
+
# TODO: re-enable once Firefox supports this
|
|
126
|
+
# options.set_capability("loggingPrefs", {"browser": "ALL"})
|
|
127
|
+
|
|
128
|
+
if headless:
|
|
129
|
+
options.add_argument("--headless")
|
|
130
|
+
|
|
131
|
+
if selenium_remote_url is not None:
|
|
132
|
+
logger.debug(f"webdriver.Remote init: {selenium_remote_url}")
|
|
133
|
+
try:
|
|
134
|
+
self.driver = webdriver.Remote(
|
|
135
|
+
command_executor=selenium_remote_url,
|
|
136
|
+
options=options,
|
|
137
|
+
)
|
|
138
|
+
except urllib3.exceptions.ReadTimeoutError:
|
|
139
|
+
print("*" * 80)
|
|
140
|
+
print(
|
|
141
|
+
"* unable to connect to the remote selenium setup,"
|
|
142
|
+
" you may need to restart it"
|
|
143
|
+
)
|
|
144
|
+
print("*" * 80)
|
|
145
|
+
print("")
|
|
146
|
+
raise
|
|
147
|
+
else:
|
|
148
|
+
logger.debug("webdriver.Firefox init")
|
|
149
|
+
self.driver = webdriver.Firefox(
|
|
150
|
+
options=options,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
elif browser.startswith("edge"):
|
|
154
|
+
options = EdgeOptions()
|
|
155
|
+
|
|
156
|
+
options.add_experimental_option(
|
|
157
|
+
"prefs", {"download.default_directory": cucu_downloads_dir}
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
if headless:
|
|
161
|
+
options.use_chromium = True
|
|
162
|
+
options.add_argument("--headless")
|
|
163
|
+
|
|
164
|
+
if ignore_ssl_certificate_errors:
|
|
165
|
+
options.set_capability("acceptSslCerts", True)
|
|
166
|
+
|
|
167
|
+
if selenium_remote_url is not None:
|
|
168
|
+
logger.debug(f"webdriver.Remote init: {selenium_remote_url}")
|
|
169
|
+
try:
|
|
170
|
+
self.driver = webdriver.Remote(
|
|
171
|
+
command_executor=selenium_remote_url,
|
|
172
|
+
options=options,
|
|
173
|
+
)
|
|
174
|
+
except urllib3.exceptions.ReadTimeoutError:
|
|
175
|
+
print("*" * 80)
|
|
176
|
+
print(
|
|
177
|
+
"* unable to connect to the remote selenium setup,"
|
|
178
|
+
" you may need to restart it"
|
|
179
|
+
)
|
|
180
|
+
print("*" * 80)
|
|
181
|
+
print("")
|
|
182
|
+
raise
|
|
183
|
+
else:
|
|
184
|
+
logger.debug("webdriver.Edge init")
|
|
185
|
+
edgedriver_filepath = (
|
|
186
|
+
edgedriver_autoinstaller.utils.download_edgedriver()
|
|
187
|
+
)
|
|
188
|
+
self.driver = webdriver.Edge(
|
|
189
|
+
executable_path=edgedriver_filepath,
|
|
190
|
+
options=options,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
else:
|
|
194
|
+
raise Exception(f"unknown browser {browser}")
|
|
195
|
+
|
|
196
|
+
self.driver.set_window_size(width, height)
|
|
197
|
+
|
|
198
|
+
def get_log(self):
|
|
199
|
+
if config.CONFIG["CUCU_BROWSER"] == "firefox":
|
|
200
|
+
return []
|
|
201
|
+
|
|
202
|
+
return self.driver.get_log("browser")
|
|
203
|
+
|
|
204
|
+
def get_current_url(self):
|
|
205
|
+
return self.driver.current_url
|
|
206
|
+
|
|
207
|
+
def navigate(self, url):
|
|
208
|
+
self.driver.get(url)
|
|
209
|
+
self.wait_for_page_to_load()
|
|
210
|
+
|
|
211
|
+
def switch_to_next_tab(self):
|
|
212
|
+
window_handles = self.driver.window_handles
|
|
213
|
+
window_handle = self.driver.current_window_handle
|
|
214
|
+
window_handle_index = window_handles.index(window_handle)
|
|
215
|
+
|
|
216
|
+
if window_handle_index == len(window_handles) - 1:
|
|
217
|
+
raise RuntimeError("no next browser tab available")
|
|
218
|
+
|
|
219
|
+
self.driver.switch_to.window(window_handles[window_handle_index + 1])
|
|
220
|
+
|
|
221
|
+
def switch_to_previous_tab(self):
|
|
222
|
+
window_handles = self.driver.window_handles
|
|
223
|
+
window_handle = self.driver.current_window_handle
|
|
224
|
+
window_handle_index = window_handles.index(window_handle)
|
|
225
|
+
|
|
226
|
+
if window_handle_index == 0:
|
|
227
|
+
raise RuntimeError("no previous browser tab available")
|
|
228
|
+
|
|
229
|
+
self.driver.switch_to.window(window_handles[window_handle_index - 1])
|
|
230
|
+
|
|
231
|
+
def back(self):
|
|
232
|
+
self.driver.back()
|
|
233
|
+
self.wait_for_page_to_load()
|
|
234
|
+
|
|
235
|
+
def refresh(self):
|
|
236
|
+
self.driver.refresh()
|
|
237
|
+
self.wait_for_page_to_load()
|
|
238
|
+
|
|
239
|
+
def title(self):
|
|
240
|
+
return self.driver.title
|
|
241
|
+
|
|
242
|
+
def css_find_elements(self, selector):
|
|
243
|
+
def find_elements_in_frame():
|
|
244
|
+
elements = self.driver.find_elements(By.CSS_SELECTOR, selector)
|
|
245
|
+
|
|
246
|
+
def visible(element):
|
|
247
|
+
return element.is_displayed()
|
|
248
|
+
|
|
249
|
+
return list(filter(visible, elements))
|
|
250
|
+
|
|
251
|
+
return search_in_all_frames(self, find_elements_in_frame)
|
|
252
|
+
|
|
253
|
+
def execute(self, javascript, *args):
|
|
254
|
+
return self.driver.execute_script(javascript, *args)
|
|
255
|
+
|
|
256
|
+
def click(self, element):
|
|
257
|
+
element.click()
|
|
258
|
+
# let cucu's own wait for page to load checks run
|
|
259
|
+
self.wait_for_page_to_load()
|
|
260
|
+
|
|
261
|
+
def switch_to_default_frame(self):
|
|
262
|
+
self.driver.switch_to.default_content()
|
|
263
|
+
|
|
264
|
+
def switch_to_frame(self, frame):
|
|
265
|
+
self.driver.switch_to.frame(frame)
|
|
266
|
+
|
|
267
|
+
def screenshot(self, filepath):
|
|
268
|
+
self.driver.get_screenshot_as_file(filepath)
|
|
269
|
+
|
|
270
|
+
def close_window(self):
|
|
271
|
+
window_handles = self.driver.window_handles
|
|
272
|
+
window_handle = self.driver.current_window_handle
|
|
273
|
+
window_handle_index = window_handles.index(window_handle)
|
|
274
|
+
|
|
275
|
+
self.driver.close()
|
|
276
|
+
self.driver.switch_to.window(window_handles[window_handle_index - 1])
|
|
277
|
+
|
|
278
|
+
def download_mht(self, target_filepath):
|
|
279
|
+
if self.driver is None:
|
|
280
|
+
logger.warn(
|
|
281
|
+
"No active browser; will not attempt to download .mht file."
|
|
282
|
+
)
|
|
283
|
+
return
|
|
284
|
+
|
|
285
|
+
browser_name = self.driver.name.lower()
|
|
286
|
+
if "chrome" not in browser_name:
|
|
287
|
+
logger.warn(
|
|
288
|
+
"The web driver is not using Chrome as a web browser"
|
|
289
|
+
f", but {browser_name}. This browser does not support"
|
|
290
|
+
"dowloading .mht files; will not attempt to download one."
|
|
291
|
+
)
|
|
292
|
+
return
|
|
293
|
+
|
|
294
|
+
mht_data = None
|
|
295
|
+
if self.driver._is_remote:
|
|
296
|
+
cdp_url = f"{self.driver.command_executor._url}/session/{self.driver.session_id}/chromium/send_command_and_get_result"
|
|
297
|
+
cdp_request_body = '{"cmd": "Page.captureSnapshot", "params": {}}'
|
|
298
|
+
cdp_response = self.driver.command_executor._request(
|
|
299
|
+
"POST", cdp_url, cdp_request_body
|
|
300
|
+
)
|
|
301
|
+
# Flaky: Adding try catch block to handle the situation where we do not get a dictionary object.
|
|
302
|
+
# Could not reproduce this in local. So far we have seen this issue only when running on a remote web driver.
|
|
303
|
+
try:
|
|
304
|
+
mht_data = cdp_response.get("value").get("data")
|
|
305
|
+
except Exception as e:
|
|
306
|
+
logger.error(f'object "{cdp_response.get("value")}" : {e}')
|
|
307
|
+
else:
|
|
308
|
+
mht_response = self.driver.execute_cdp_cmd(
|
|
309
|
+
"Page.captureSnapshot", {}
|
|
310
|
+
)
|
|
311
|
+
mht_data = mht_response.get("data")
|
|
312
|
+
|
|
313
|
+
if mht_data is None:
|
|
314
|
+
logger.warn(
|
|
315
|
+
"Something unexpected has happened: fetched MHT data, but that data was empty. Not writing MHT file."
|
|
316
|
+
)
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
with open(target_filepath, "w") as mht_file:
|
|
320
|
+
mht_file.write(mht_data)
|
|
321
|
+
|
|
322
|
+
def quit(self):
|
|
323
|
+
self.driver.quit()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from selenium.webdriver.remote import webelement
|
|
2
|
+
|
|
3
|
+
from cucu.browser.frames import search_in_all_frames
|
|
4
|
+
from cucu.config import CONFIG
|
|
5
|
+
|
|
6
|
+
# monkey patch some methods at the WebElement level
|
|
7
|
+
__ORIGINAL_FIND_ELEMENTS = webelement.WebElement.find_elements
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def init():
|
|
11
|
+
"""
|
|
12
|
+
initialize various selenium tweaks to change the behavior of the underlying
|
|
13
|
+
selenium classes.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
# intercept the find_elements calls and use the nifty frames method to allow
|
|
17
|
+
# the expression to be executed against every visible frame to find the
|
|
18
|
+
# desired element.
|
|
19
|
+
def find_elements(self, by="id", value=None):
|
|
20
|
+
ctx = CONFIG["__CUCU_CTX"]
|
|
21
|
+
|
|
22
|
+
def find_elements_in_frame():
|
|
23
|
+
return __ORIGINAL_FIND_ELEMENTS(self, by, value)
|
|
24
|
+
|
|
25
|
+
return search_in_all_frames(ctx.browser, find_elements_in_frame)
|
|
26
|
+
|
|
27
|
+
webelement.WebElement.find_elements = find_elements
|
cucu/cli/__init__.py
ADDED