cucu 1.0.2__py3-none-any.whl → 1.0.7__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/cli/core.py CHANGED
@@ -4,6 +4,7 @@ import json
4
4
  import os
5
5
  import shutil
6
6
  import signal
7
+ import sys
7
8
  import time
8
9
  import xml.etree.ElementTree as ET
9
10
  from importlib.metadata import version
@@ -189,6 +190,7 @@ def main():
189
190
  "--workers",
190
191
  default=None,
191
192
  help="Specifies the number of workers to use to run tests in parallel",
193
+ type=int,
192
194
  )
193
195
  @click.option(
194
196
  "--verbose/--no-verbose",
@@ -338,11 +340,20 @@ def run(
338
340
  if os.path.isdir(filepath):
339
341
  basepath = os.path.join(filepath, "**/*.feature")
340
342
  feature_filepaths = list(glob.iglob(basepath, recursive=True))
341
-
342
343
  else:
343
344
  feature_filepaths = [filepath]
344
345
 
345
- with WorkerPool(n_jobs=int(workers), start_method="spawn") as pool:
346
+ if sys.platform == "darwin":
347
+ logger.info(
348
+ "MAC OS detected, using 'forkserver' start method since 'fork' is unstable"
349
+ )
350
+ start_method = "forkserver"
351
+ else:
352
+ start_method = "fork"
353
+
354
+ with WorkerPool(
355
+ n_jobs=int(workers), start_method=start_method
356
+ ) as pool:
346
357
  # Each feature file is applied to the pool as an async task.
347
358
  # It then polls the async result of each task. It the result
348
359
  # is ready, it removes the result from the list of results that
@@ -364,6 +375,34 @@ def run(
364
375
  timer = Timer(runtime_timeout, runtime_exit)
365
376
  timer.start()
366
377
 
378
+ def kill_workers():
379
+ for worker in pool._workers:
380
+ try:
381
+ worker_proc = psutil.Process(worker.pid)
382
+ for child in worker_proc.children():
383
+ child.kill()
384
+
385
+ worker_proc.kill()
386
+ except psutil.NoSuchProcess:
387
+ pass
388
+
389
+ def handle_kill_signal(signum, frame):
390
+ signal.signal(
391
+ signum, signal.SIG_IGN
392
+ ) # ignore additional signals
393
+ logger.warn(
394
+ f"received signal {signum}, sending kill signal to workers"
395
+ )
396
+ kill_workers()
397
+ if timer:
398
+ timer.cancel()
399
+
400
+ os.kill(os.getpid(), signal.SIGINT)
401
+
402
+ # This is for local runs where you want to cancel the run with a ctrl+c or SIGTERM
403
+ signal.signal(signal.SIGINT, handle_kill_signal)
404
+ signal.signal(signal.SIGTERM, handle_kill_signal)
405
+
367
406
  async_results = {}
368
407
  for feature_filepath in feature_filepaths:
369
408
  async_results[feature_filepath] = pool.apply_async(
@@ -430,15 +469,7 @@ def run(
430
469
 
431
470
  if timeout_reached:
432
471
  logger.warn("Timeout reached, send kill signal to workers")
433
- for worker in pool._workers:
434
- try:
435
- worker_proc = psutil.Process(worker.pid)
436
- for child in worker_proc.children():
437
- child.kill()
438
-
439
- worker_proc.kill()
440
- except psutil.NoSuchProcess:
441
- pass
472
+ kill_workers()
442
473
 
443
474
  task_failed.update(async_results)
444
475
 
@@ -2,7 +2,7 @@ import os
2
2
 
3
3
  import humanize
4
4
 
5
- from cucu import fuzzy, logger, step
5
+ from cucu import fuzzy, logger, retry, step
6
6
  from cucu.utils import take_saw_element_screenshot
7
7
 
8
8
 
@@ -41,35 +41,129 @@ def upload_file_to_input(ctx, filepath, name):
41
41
 
42
42
 
43
43
  JS_DROP_FILE = """
44
- var target = arguments[0],
45
- offsetX = arguments[1],
46
- offsetY = arguments[2],
47
- document = target.ownerDocument || document,
48
- window = document.defaultView || window;
49
-
50
- var input = document.createElement('INPUT');
51
- input.type = 'file';
52
- input.onchange = function () {
53
- var rect = target.getBoundingClientRect(),
54
- x = rect.left + (offsetX || (rect.width >> 1)),
55
- y = rect.top + (offsetY || (rect.height >> 1)),
56
- dataTransfer = { files: this.files };
57
-
58
- ['dragenter', 'dragover', 'drop'].forEach(function (name) {
59
- var evt = document.createEvent('MouseEvent');
60
- evt.initMouseEvent(name, !0, !0, window, 0, 0, 0, x, y, !1, !1, !1, !1, 0, null);
61
- evt.dataTransfer = dataTransfer;
62
- target.dispatchEvent(evt);
63
- });
64
-
65
- setTimeout(function () { document.body.removeChild(input); }, 25);
44
+ var args = arguments,
45
+ element = args[0],
46
+ offsetX = args[1],
47
+ offsetY = args[2],
48
+ doc = element.ownerDocument || document;
49
+
50
+ var box = element.getBoundingClientRect(),
51
+ clientX = box.left + (offsetX || box.width / 2),
52
+ clientY = box.top + (offsetY || box.height / 2),
53
+ target = doc.elementFromPoint(clientX, clientY);
54
+ if (!(target && element.contains(target))) {
55
+ element.scrollIntoView({
56
+ behavior: "instant",
57
+ block: "center",
58
+ inline: "center",
59
+ });
60
+ var box = element.getBoundingClientRect(),
61
+ clientX = box.left + (offsetX || box.width / 2),
62
+ clientY = box.top + (offsetY || box.height / 2),
63
+ target = doc.elementFromPoint(clientX, clientY);
64
+
65
+ if (!(target && element.contains(target))) {
66
+ // Scrolling didn't do the trick, so give up
67
+ var ex = new Error("Element not interactable");
68
+ ex.code = 15;
69
+ throw ex;
70
+ }
71
+ }
72
+
73
+ var input = doc.createElement("INPUT");
74
+ input.setAttribute("type", "file");
75
+ input.setAttribute("multiple", "");
76
+ input.setAttribute("style", "position:fixed;z-index:2147483647;left:0;top:0;");
77
+ input.onchange = function (ev) {
78
+ input.parentElement.removeChild(input);
79
+ ev.stopPropagation();
80
+
81
+ var dataTransfer = {
82
+ constructor: DataTransfer,
83
+ effectAllowed: "all",
84
+ dropEffect: "none",
85
+ types: ["Files"],
86
+ files: input.files,
87
+ setData: function setData() {},
88
+ getData: function getData() {},
89
+ clearData: function clearData() {},
90
+ setDragImage: function setDragImage() {},
66
91
  };
67
- document.body.appendChild(input);
92
+
93
+ if (window.DataTransferItemList) {
94
+ dataTransfer.items = Object.setPrototypeOf(
95
+ Array.prototype.map.call(input.files, function (f) {
96
+ return {
97
+ constructor: DataTransferItem,
98
+ kind: "file",
99
+ type: f.type,
100
+ getAsFile: function getAsFile() {
101
+ return f;
102
+ },
103
+ getAsString: function getAsString(callback) {
104
+ var reader = new FileReader();
105
+ reader.onload = function (ev) {
106
+ callback(ev.target.result);
107
+ };
108
+ reader.readAsText(f);
109
+ },
110
+ webkitGetAsEntry: function webkitGetAsEntry() {
111
+ return {
112
+ constructor: window.FileSystemEntry || window.Entry,
113
+ name: f.name,
114
+ fullPath: "/" + f.name,
115
+ isFile: true,
116
+ isDirectory: false,
117
+ file: function file(callback) {
118
+ callback(f);
119
+ },
120
+ };
121
+ },
122
+ };
123
+ }),
124
+ {
125
+ constructor: DataTransferItemList,
126
+ add: function add() {},
127
+ clear: function clear() {},
128
+ remove: function remove() {},
129
+ }
130
+ );
131
+ }
132
+
133
+ ["dragenter", "dragover", "drop"].forEach(function (type) {
134
+ var event = doc.createEvent("DragEvent");
135
+ event.initMouseEvent(
136
+ type,
137
+ true,
138
+ true,
139
+ doc.defaultView,
140
+ 0,
141
+ 0,
142
+ 0,
143
+ clientX,
144
+ clientY,
145
+ false,
146
+ false,
147
+ false,
148
+ false,
149
+ 0,
150
+ null
151
+ );
152
+
153
+ Object.setPrototypeOf(event, null);
154
+ event.dataTransfer = dataTransfer;
155
+ Object.setPrototypeOf(event, DragEvent.prototype);
156
+
157
+ target.dispatchEvent(event);
158
+ });
159
+ };
160
+
161
+ doc.documentElement.appendChild(input);
162
+ input.getBoundingClientRect(); /* force reflow for Firefox */
68
163
  return input;
69
164
  """
70
165
 
71
166
 
72
- @step('I drag and drop the file "{filepath}" to "{name}"')
73
167
  def drag_and_drop_file(ctx, name, filepath):
74
168
  drop_target = fuzzy.find(ctx.browser, name, ["*"])
75
169
  drop_target_html = drop_target.get_attribute("outerHTML")
@@ -78,3 +172,13 @@ def drag_and_drop_file(ctx, name, filepath):
78
172
  )
79
173
  file_input = ctx.browser.execute(JS_DROP_FILE, drop_target, 0, 0)
80
174
  file_input.send_keys(os.path.abspath(filepath))
175
+
176
+
177
+ @step('I drag and drop the file "{filepath}" to "{name}"')
178
+ def should_drag_and_drop_file(ctx, filepath, name):
179
+ drag_and_drop_file(ctx, name, filepath)
180
+
181
+
182
+ @step('I wait to drag and drop the file "{filepath}" to "{name}"')
183
+ def wait_to_drag_and_drop_file(ctx, filepath, name):
184
+ retry(drag_and_drop_file)(ctx, name, filepath)
@@ -1,10 +1,11 @@
1
+ import socket
1
2
  from functools import partial
2
3
  from http.server import HTTPServer, SimpleHTTPRequestHandler
3
4
  from threading import Thread
4
5
 
5
6
  from behave import step
6
7
 
7
- from cucu import register_after_this_scenario_hook
8
+ from cucu import logger, register_after_this_scenario_hook
8
9
  from cucu.config import CONFIG
9
10
 
10
11
 
@@ -33,6 +34,9 @@ def run_webserver_for_scenario(ctx, directory, variable):
33
34
  _, port = httpd.server_address
34
35
  CONFIG[variable] = str(port)
35
36
 
37
+ with socket.create_connection(("localhost", port), timeout=5):
38
+ logger.debug(f"Webserver is running at {port=}port")
39
+
36
40
  def shutdown_webserver(_):
37
41
  httpd.shutdown()
38
42
  thread.join()
cucu/utils.py CHANGED
@@ -11,6 +11,8 @@ import shutil
11
11
  import humanize
12
12
  from tabulate import DataRow, TableFormat, tabulate
13
13
  from tenacity import (
14
+ after_log,
15
+ before_log,
14
16
  before_sleep_log,
15
17
  retry_if_not_exception_type,
16
18
  stop_after_delay,
@@ -119,6 +121,8 @@ def retry(func, wait_up_to_s=None, retry_after_s=None):
119
121
  stop=stop_after_delay(wait_up_to_s),
120
122
  wait=wait_fixed(retry_after_s),
121
123
  retry=retry_if_not_exception_type(StopRetryException),
124
+ before=before_log(logger, logging.DEBUG),
125
+ after=after_log(logger, logging.DEBUG),
122
126
  before_sleep=before_sleep_log(logger, logging.DEBUG),
123
127
  )
124
128
  def new_decorator(*args, **kwargs):
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: cucu
3
- Version: 1.0.2
3
+ Version: 1.0.7
4
4
  Summary: Easy BDD web testing
5
5
  Project-URL: Homepage, https://github.com/dominodatalab/cucu
6
6
  Project-URL: Download, https://pypi.org/project/cucu/
@@ -34,14 +34,14 @@ Requires-Dist: geckodriver-autoinstaller~=0.1.0
34
34
  Requires-Dist: humanize~=4.8.0
35
35
  Requires-Dist: importlib-metadata~=8.0.0
36
36
  Requires-Dist: ipdb~=0.13.13
37
- Requires-Dist: jellyfish~=1.0.1
37
+ Requires-Dist: jellyfish~=1.1.3
38
38
  Requires-Dist: jinja2~=3.1.3
39
39
  Requires-Dist: lsprotocol~=2023.0.1
40
40
  Requires-Dist: mpire~=2.10.2
41
41
  Requires-Dist: psutil~=6.0.0
42
42
  Requires-Dist: pygls~=1.3.1
43
43
  Requires-Dist: pyyaml~=6.0.1
44
- Requires-Dist: requests~=2.31.0
44
+ Requires-Dist: requests<3.0.0,>=2.31.0
45
45
  Requires-Dist: selenium~=4.15
46
46
  Requires-Dist: tabulate~=0.9.0
47
47
  Requires-Dist: tenacity~=9.0.0
@@ -7,14 +7,14 @@ cucu/helpers.py,sha256=l_YMmbuXjtBRo-MER-qe6soUIyjt0ey2BoSgWs4zYwA,36285
7
7
  cucu/hooks.py,sha256=3Z1mavU42XMQ0DZ7lVWwTB-BJYHRyYUOzzOtmkdIsow,7117
8
8
  cucu/logger.py,sha256=LjJEzAKySlEiSmarpoeiMN4c29RIfrrERIk1swbdU4s,3343
9
9
  cucu/page_checks.py,sha256=vkkZ9EyLCNBAwriED8ur3zS3flOYchXx-mMcrZF5dgY,2176
10
- cucu/utils.py,sha256=eaGbrqVSdi4b59U2BjqRl-Yp7DsVTAlkN8DrFxQsX_o,8482
10
+ cucu/utils.py,sha256=608b28WVWgzmFMAMLXpFWeXe3NjRZEhpEfcTnYy2JRM,8611
11
11
  cucu/browser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  cucu/browser/core.py,sha256=YAHhj6AMf7kpZR57thQaPHVHVHwnMBzCI0yofIYtfaY,2355
13
13
  cucu/browser/frames.py,sha256=IW7kzRJn5PkbMaovIelAeCWO-T-2sOTwqaYBw-0-LKU,3545
14
14
  cucu/browser/selenium.py,sha256=c3B6IShD8Es-TGW-dxxNibGItJ4WEQI_xJpgc6uL6-E,11613
15
15
  cucu/browser/selenium_tweaks.py,sha256=oUIhWVhBZbc9qsmQUJMpIr9uUWKxtgZBcnySWU6Yttk,879
16
16
  cucu/cli/__init__.py,sha256=uXX5yVG1konJ_INdlrcfMg-Tt_5_cSx29Ed8R8v908A,62
17
- cucu/cli/core.py,sha256=OAdjT0_4I4sPCOgEssYW0ihKLu-z2vuses-H5MUQRgU,23089
17
+ cucu/cli/core.py,sha256=qFEEmTrj2avKoyYtPqQjd-tgvHp8R21NCtP_pknwEos,24225
18
18
  cucu/cli/run.py,sha256=e2JR77YF-7YSC4nCjogPcIsfoH7T43dAz5x_eeeue6k,5906
19
19
  cucu/cli/steps.py,sha256=hxsLymlYvF0uqUkDVq3s6heABkYnRo_SdQCpBdpb0e0,4009
20
20
  cucu/cli/thread_dumper.py,sha256=Z3XnYSxidx6pqjlQ7zu-TKMIYZWk4z9c5YLdPkcemiU,1593
@@ -60,7 +60,7 @@ cucu/steps/command_steps.py,sha256=nVCc8-TEitetk-zhk4z1wa0owqLQyHeQVH5THioUw-k,5
60
60
  cucu/steps/comment_steps.py,sha256=KcU0Ya8XSjYEh8gdUGh0qsAxgB_Ru977E84yJaXrvz0,379
61
61
  cucu/steps/draggable_steps.py,sha256=lnQLicp0GZJaxD_Qm2P13ruUZAsl3mptwaI5-SQ6XJ0,4655
62
62
  cucu/steps/dropdown_steps.py,sha256=abykG--m79kDQ4LU1tm73fNLFPmrKDavyFzJb2MYCu0,15601
63
- cucu/steps/file_input_steps.py,sha256=ng6cnLVKO0yAtd5HWy4AWomRbmYfxSIhhGDbOVZyRIA,2534
63
+ cucu/steps/file_input_steps.py,sha256=LLMAozVpceLMD-kJOE-auKHAdWLbNprH8eCfVQuNoGg,5523
64
64
  cucu/steps/filesystem_steps.py,sha256=8l37A-yPxT4Mzdi1JNSTShZkwyFxgSwnh6C0V-hM_RA,4741
65
65
  cucu/steps/flow_control_steps.py,sha256=vlW0CsphVS9NvrOnpT8wSS2ngHmO3Z87H9siKIQwsAw,6365
66
66
  cucu/steps/image_steps.py,sha256=4X6bdumsIybcJBuao83TURxWAIshZyCvKi1uTJEoy1k,941
@@ -75,9 +75,9 @@ cucu/steps/table_steps.py,sha256=XB-NTwuue5LCXGlLZLex0QcRKOSrc4UA_F4Kj6l-J78,137
75
75
  cucu/steps/tables.js,sha256=Os2a7Fo-cg03XVli7USvcnBVad4N7idXr-HBuzdLvVQ,945
76
76
  cucu/steps/text_steps.py,sha256=Jj_GHoHeemNwVdUOdqcehArNp7WM-WMjljA4w0pLXuw,2576
77
77
  cucu/steps/variable_steps.py,sha256=WSctH3_xcxjijGPYZlxp-foC_SIAAKtF__saNtgZJbk,2966
78
- cucu/steps/webserver_steps.py,sha256=_bvqJxF8BSIIufNItZMSyjijEJEEPJbEvwnqPHoM9Tg,1259
79
- cucu-1.0.2.dist-info/METADATA,sha256=N7sYUoJnlT2VqkESiGX_pB4KhFHD7Ojvz9utNSQrwYg,16238
80
- cucu-1.0.2.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
81
- cucu-1.0.2.dist-info/entry_points.txt,sha256=YEXTyEfIZbcV0GJ9R3Gfu3j6DcOJJK7_XHkJqE3Yiao,39
82
- cucu-1.0.2.dist-info/licenses/LICENSE,sha256=WfgJYF9EaQoL_OeWr2Qd0MxhhFegDfzWSUmvDTwFxis,1721
83
- cucu-1.0.2.dist-info/RECORD,,
78
+ cucu/steps/webserver_steps.py,sha256=wWkpSvcSMdiskPkh4cqlepWx1nkvEpTU2tRXQmPDbyo,1410
79
+ cucu-1.0.7.dist-info/METADATA,sha256=EGuVc5vJmj6BjOYIEwPQByWkoXhr1ip91MbnPcVzAZk,16245
80
+ cucu-1.0.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
81
+ cucu-1.0.7.dist-info/entry_points.txt,sha256=YEXTyEfIZbcV0GJ9R3Gfu3j6DcOJJK7_XHkJqE3Yiao,39
82
+ cucu-1.0.7.dist-info/licenses/LICENSE,sha256=WfgJYF9EaQoL_OeWr2Qd0MxhhFegDfzWSUmvDTwFxis,1721
83
+ cucu-1.0.7.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.25.0
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any