cucu 1.3.14__tar.gz → 1.3.16__tar.gz

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.

Files changed (94) hide show
  1. {cucu-1.3.14 → cucu-1.3.16}/PKG-INFO +1 -1
  2. {cucu-1.3.14 → cucu-1.3.16}/pyproject.toml +1 -1
  3. cucu-1.3.16/src/cucu/reporter/external/manage_scenario_highlights.js +69 -0
  4. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/reporter/html.py +50 -27
  5. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/reporter/templates/scenario.html +5 -1
  6. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/browser_steps.py +1 -1
  7. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/utils.py +28 -42
  8. {cucu-1.3.14 → cucu-1.3.16}/README.md +0 -0
  9. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/__init__.py +0 -0
  10. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/ansi_parser.py +0 -0
  11. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/behave_tweaks.py +0 -0
  12. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/browser/__init__.py +0 -0
  13. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/browser/core.py +0 -0
  14. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/browser/frames.py +0 -0
  15. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/browser/selenium.py +0 -0
  16. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/browser/selenium_tweaks.py +0 -0
  17. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/cli/__init__.py +0 -0
  18. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/cli/core.py +0 -0
  19. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/cli/run.py +0 -0
  20. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/cli/steps.py +0 -0
  21. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/cli/thread_dumper.py +0 -0
  22. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/config.py +0 -0
  23. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/db.py +0 -0
  24. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/edgedriver_autoinstaller/README.md +0 -0
  25. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/edgedriver_autoinstaller/__init__.py +0 -0
  26. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/edgedriver_autoinstaller/utils.py +0 -0
  27. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/environment.py +0 -0
  28. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/external/jquery/jquery-3.5.1.min.js +0 -0
  29. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/formatter/__init__.py +0 -0
  30. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/formatter/cucu.py +0 -0
  31. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/formatter/json.py +0 -0
  32. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/formatter/junit.py +0 -0
  33. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/formatter/rundb.py +0 -0
  34. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/fuzzy/__init__.py +0 -0
  35. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/fuzzy/core.py +0 -0
  36. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/fuzzy/fuzzy.js +0 -0
  37. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/helpers.py +0 -0
  38. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/hooks.py +0 -0
  39. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/init_data/.gitignore +0 -0
  40. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/init_data/README.md +0 -0
  41. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/init_data/cucurc.yml +0 -0
  42. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/init_data/data/www/example.html +0 -0
  43. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/init_data/features/cucurc.yml +0 -0
  44. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/init_data/features/environment.py +0 -0
  45. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/init_data/features/example.feature +0 -0
  46. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/init_data/features/lint_rules/sid.yaml +0 -0
  47. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/init_data/features/steps/__init__.py +0 -0
  48. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/init_data/features/steps/my_steps.py +0 -0
  49. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/language_server/__init__.py +0 -0
  50. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/language_server/core.py +0 -0
  51. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/lint/__init__.py +0 -0
  52. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/lint/linter.py +0 -0
  53. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/lint/rules/format.yaml +0 -0
  54. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/logger.py +0 -0
  55. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/matcher/__init__.py +0 -0
  56. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/matcher/core.py +0 -0
  57. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/page_checks.py +0 -0
  58. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/reporter/__init__.py +0 -0
  59. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/reporter/external/bootstrap.min.css +0 -0
  60. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/reporter/external/bootstrap.min.js +0 -0
  61. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/reporter/external/dataTables.bootstrap.min.css +0 -0
  62. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/reporter/external/dataTables.bootstrap.min.js +0 -0
  63. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/reporter/external/jquery-3.5.1.min.js +0 -0
  64. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/reporter/external/jquery.dataTables.min.js +0 -0
  65. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/reporter/external/popper.min.js +0 -0
  66. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/reporter/favicon.png +0 -0
  67. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/reporter/templates/feature.html +0 -0
  68. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/reporter/templates/flat.html +0 -0
  69. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/reporter/templates/index.html +0 -0
  70. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/reporter/templates/layout.html +0 -0
  71. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/__init__.py +0 -0
  72. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/base_steps.py +0 -0
  73. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/button_steps.py +0 -0
  74. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/checkbox_steps.py +0 -0
  75. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/command_steps.py +0 -0
  76. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/draggable_steps.py +0 -0
  77. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/dropdown_steps.py +0 -0
  78. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/file_input_steps.py +0 -0
  79. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/filesystem_steps.py +0 -0
  80. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/flow_control_steps.py +0 -0
  81. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/image_steps.py +0 -0
  82. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/input_steps.py +0 -0
  83. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/link_steps.py +0 -0
  84. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/menuitem_steps.py +0 -0
  85. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/platform_steps.py +0 -0
  86. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/radio_steps.py +0 -0
  87. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/section_steps.py +0 -0
  88. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/step_utils.py +0 -0
  89. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/tab_steps.py +0 -0
  90. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/table_steps.py +0 -0
  91. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/tables.js +0 -0
  92. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/text_steps.py +0 -0
  93. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/variable_steps.py +0 -0
  94. {cucu-1.3.14 → cucu-1.3.16}/src/cucu/steps/webserver_steps.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: cucu
3
- Version: 1.3.14
3
+ Version: 1.3.16
4
4
  Summary: Easy BDD web testing
5
5
  Keywords: cucumber,selenium,behave
6
6
  Author: Domino Data Lab, Rodney Gomes, Cedric Young, Xin Dong, Kavya Yakkati, Kevin Garton, Joy Liao
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cucu"
3
- version = "1.3.14"
3
+ version = "1.3.16"
4
4
  description = "Easy BDD web testing"
5
5
  readme = "README.md"
6
6
  license = { text = "The Clear BSD License" }
@@ -0,0 +1,69 @@
1
+ ;
2
+ function fitHighlightToImage(highlightDiv) {
3
+ console.log(highlightDiv);
4
+
5
+ // Every highlight element is expected to have an ID of the form
6
+ // <image ID>-highlight . Get the image ID here.
7
+ const sliceEndIndex = highlightDiv.id.length - "-highlight".length;
8
+
9
+ const targetImgId = highlightDiv.id.slice(0, sliceEndIndex);
10
+ const targetImg = document.querySelector(`img#${targetImgId}`);
11
+ const imgViewportRect = targetImg.getBoundingClientRect();
12
+ // Compute absolute coordinates relative to the whole document
13
+ const targetDimensions = {
14
+ height: imgViewportRect["height"],
15
+ width: imgViewportRect["width"],
16
+ top: imgViewportRect["top"] + window.pageYOffset,
17
+ left: imgViewportRect["left"] + window.pageXOffset,
18
+ };
19
+
20
+ const updatedHighlightStyle = [
21
+ "position:absolute",
22
+ `height:${targetDimensions["height"] * highlightDiv.getAttribute("height-ratio")}px`,
23
+ `width:${targetDimensions["width"] * highlightDiv.getAttribute("width-ratio")}px`,
24
+ `top:${(targetDimensions["height"] * highlightDiv.getAttribute("top-ratio")) + targetDimensions["top"]}px`,
25
+ `left:${(targetDimensions["width"] * highlightDiv.getAttribute("left-ratio")) + targetDimensions["left"]}px`,
26
+ "border:2px solid magenta",
27
+ "border-radius:2px",
28
+ ].join(";");
29
+ highlightDiv.style = updatedHighlightStyle;
30
+ };
31
+
32
+ function resizeAllHighlights(){
33
+ const highlights = document.querySelectorAll(".step-image-highlight");
34
+ highlights.forEach(h => fitHighlightToImage(h));
35
+ };
36
+
37
+ // The <summary> elements make the DOM jump around.
38
+ // The delayed resizeAllHighlights helps make sure all the highlights
39
+ // from lower down the page get into the right place after things settle.
40
+ document.querySelectorAll("summary").forEach(
41
+ e => {
42
+ e.addEventListener("click", resizeAllHighlights);
43
+ e.addEventListener(
44
+ "click",
45
+ () => setTimeout(resizeAllHighlights, 10)
46
+ );
47
+ }
48
+ );
49
+ // The td.data-toggles cause an animation that takes a lot longer than
50
+ // the one from the <summary> elements. As before, the fast
51
+ // resizeAllHighlights makes the ones the user can see first jump into
52
+ // place, then the delayed one helps put the ones lower down into place.
53
+ document.querySelectorAll("td[data-toggle=collapse").forEach(
54
+ e => {
55
+ e.addEventListener(
56
+ "click",
57
+ () => setTimeout(resizeAllHighlights, 10)
58
+ );
59
+ e.addEventListener(
60
+ "click",
61
+ // 400ms value was determined experimentally.
62
+ // It's approximately the duration of the animation triggered
63
+ // by clicking on these elements
64
+ () => setTimeout(resizeAllHighlights, 400)
65
+ );
66
+ }
67
+ );
68
+ window.onload = resizeAllHighlights;
69
+ window.onresize = resizeAllHighlights;
@@ -14,7 +14,11 @@ import cucu.db as db
14
14
  from cucu import format_gherkin_table, logger
15
15
  from cucu.ansi_parser import parse_log_to_html
16
16
  from cucu.config import CONFIG
17
- from cucu.utils import ellipsize_filename, get_step_image_dir
17
+ from cucu.utils import (
18
+ ellipsize_filename,
19
+ generate_short_id,
20
+ get_step_image_dir,
21
+ )
18
22
 
19
23
 
20
24
  def escape(data):
@@ -130,6 +134,7 @@ def generate(results, basepath, only_failures=False):
130
134
  "timestamp": db_step.end_at or "",
131
135
  },
132
136
  "substep": db_step.is_substep,
137
+ "screenshots": db_step.screenshots,
133
138
  }
134
139
 
135
140
  if db_step.text:
@@ -281,8 +286,6 @@ def generate(results, basepath, only_failures=False):
281
286
  scenario_started_at = None
282
287
  for step in scenario["steps"]:
283
288
  total_steps += 1
284
- image_dir = get_step_image_dir(step_index, step["name"])
285
- image_dirpath = os.path.join(scenario_filepath, image_dir)
286
289
 
287
290
  # Handle section headings with different levels (# to ####)
288
291
  if step["name"].startswith("#"):
@@ -292,32 +295,52 @@ def generate(results, basepath, only_failures=False):
292
295
  f"h{step['name'][:4].count('#') + 1}"
293
296
  )
294
297
 
295
- if os.path.exists(image_dirpath):
296
- _, _, image_names = next(os.walk(image_dirpath))
297
- images = []
298
- for image_name in image_names:
299
- words = image_name.split("-", 1)
300
- index = words[0].strip()
298
+ images = []
299
+ image_dir = get_step_image_dir(step_index, step["name"])
300
+ image_dirpath = os.path.join(scenario_filepath, image_dir)
301
+ for screenshot_index, screenshot in enumerate(step["screenshots"]):
302
+ filename = os.path.split(screenshot["filepath"])[-1]
303
+ filepath = os.path.join(image_dirpath, filename)
304
+ if not os.path.exists(filepath):
305
+ continue
306
+ label = screenshot.get("label", step["name"])
307
+ highlight = None
308
+ if (
309
+ screenshot["location"]
310
+ and not CONFIG["CUCU_SKIP_HIGHLIGHT_BORDER"]
311
+ ):
312
+ window_height = screenshot["size"]["height"]
313
+ window_width = screenshot["size"]["width"]
301
314
  try:
302
- # Images with label should have a name in the form:
303
- # 0000 - This is the image label.png
304
- label, _ = os.path.splitext(words[1].strip())
305
- except IndexError:
306
- # Images with no label should instead look like:
307
- # 0000.png
308
- # so we default to the step name in this case.
309
- label = step["name"]
310
-
311
- images.append(
312
- {
313
- "src": urllib.parse.quote(
314
- os.path.join(image_dir, image_name)
315
- ),
316
- "index": index,
317
- "label": label,
315
+ highlight = {
316
+ "height_ratio": screenshot["location"][
317
+ "height"
318
+ ]
319
+ / window_height,
320
+ "width_ratio": screenshot["location"]["width"]
321
+ / window_width,
322
+ "top_ratio": screenshot["location"]["y"]
323
+ / window_height,
324
+ "left_ratio": screenshot["location"]["x"]
325
+ / window_width,
318
326
  }
319
- )
320
- step["images"] = sorted(images, key=lambda x: x["index"])
327
+ except TypeError:
328
+ # If any of the necessary properties is absent,
329
+ # then oh well, no highlight this time.
330
+ pass
331
+ screenshot_id = f"step-img-{screenshot.get("step_run_id", generate_short_id())}-{screenshot_index:0>4}"
332
+ images.append(
333
+ {
334
+ "src": urllib.parse.quote(
335
+ os.path.join(image_dir, filename)
336
+ ),
337
+ "index": screenshot_index,
338
+ "label": label,
339
+ "id": screenshot_id,
340
+ "highlight": highlight,
341
+ }
342
+ )
343
+ step["images"] = sorted(images, key=lambda x: x["index"])
321
344
 
322
345
  if "result" in step:
323
346
  if step["result"]["status"] in ["failed", "passed"]:
@@ -150,7 +150,10 @@
150
150
  <summary style="color: dimgray;">images ({{ step['images']|length }} images)</summary>
151
151
  <div style="margin: 10px 0 0 0;">
152
152
  {% for image in step['images'] %}
153
- <img class="mx-auto d-block img-fluid shadow bg-white rounded" style="margin-bottom:15px" alt='{{ image["label"] }}' title='{{ image["label"] }}' src='{{ image["src"] }}'></img>
153
+ <img class="mx-auto d-block img-fluid shadow bg-white rounded" id='{{image['id']}}' style="margin-bottom:15px" alt='{{ image["label"] }}' title='{{ image["label"] }}' src='{{ image["src"] }}'></img>
154
+ {% if image['highlight'] %}
155
+ <div class="step-image-highlight" id='{{ image["id"]}}-highlight' height-ratio='{{ image['highlight']['height_ratio'] }}' width-ratio='{{ image['highlight']['width_ratio'] }}' top-ratio='{{ image['highlight']['top_ratio'] }}' left-ratio='{{ image['highlight']['left_ratio'] }}'></div>
156
+ {% endif %}
154
157
  {% endfor %}
155
158
  </div>
156
159
  </details>
@@ -222,4 +225,5 @@
222
225
  });
223
226
  });
224
227
  </script>
228
+ <script src="../../external/manage_scenario_highlights.js"></script>
225
229
  {% endblock %}
@@ -67,7 +67,7 @@ def execute_javascript_and_save(ctx, variable):
67
67
  def assert_url_is(ctx, value):
68
68
  ctx.check_browser_initialized()
69
69
  url = ctx.browser.get_current_url()
70
- if value == url:
70
+ if value != url:
71
71
  raise RuntimeError(f"current url is {url}, not {value}")
72
72
 
73
73
 
@@ -245,56 +245,42 @@ def take_screenshot(ctx, step_name, label="", element=None):
245
245
  os.mkdir(screenshot_dir)
246
246
 
247
247
  if len(label) > 0:
248
- label = f" - {CONFIG.hide_secrets(label).replace('/', '_')}"
249
- filename = f"{CONFIG['__STEP_SCREENSHOT_COUNT']:0>4}{label}.png"
248
+ label = CONFIG.hide_secrets(label).replace("/", "_")
249
+ filename = f"{CONFIG['__STEP_SCREENSHOT_COUNT']:0>4} - {label}.png"
250
250
  filename = ellipsize_filename(filename)
251
251
  filepath = os.path.join(screenshot_dir, filename)
252
252
 
253
- if CONFIG["CUCU_SKIP_HIGHLIGHT_BORDER"] or not element:
254
- ctx.browser.screenshot(filepath)
255
- else:
256
- location = element.location
257
- border_width = 4
258
- x, y = location["x"] - border_width, location["y"] - border_width
259
- size = element.size
260
- width, height = size["width"], size["height"]
261
-
262
- position_css = f"position: absolute; top: {y}px; left: {x}px; width: {width}px; height: {height}px; z-index: 9001;"
263
- visual_css = "border-radius: 4px; border: 4px solid #ff00ff1c; background: #ff00ff05; filter: drop-shadow(magenta 0 0 10px);"
264
-
265
- script = f"""
266
- (function() {{ // double curly-brace to escape python f-string
267
- var body = document.querySelector('body');
268
- var cucu_border = document.createElement('div');
269
- cucu_border.setAttribute('id', 'cucu_border');
270
- cucu_border.setAttribute('style', '{position_css} {visual_css}');
271
- body.append(cucu_border);
272
- }})();
273
- """
274
- ctx.browser.execute(script)
275
-
276
- ctx.browser.screenshot(filepath)
277
-
278
- clear_highlight = """
279
- (function() {
280
- var body = document.querySelector('body');
281
- var cucu_border = document.getElementById('cucu_border');
282
- body.removeChild(cucu_border);
283
- })();
284
- """
285
- ctx.browser.execute(clear_highlight, element)
286
-
253
+ ctx.browser.screenshot(filepath)
254
+ element_info = {
255
+ "x": None,
256
+ "y": None,
257
+ "height": None,
258
+ "width": None,
259
+ }
260
+ if element:
261
+ element_info.update(element.rect)
262
+ # driver.get_window_size returns the size of the window the OS draws for
263
+ # the browser, not the window the browser uses to display the DOM.
264
+ # If we go through JavaScript, we ignore the height of the adress bar
265
+ # and such.
266
+ script = """
267
+ function getDimensionsOfCurrentWindow() {
268
+ const windowSize = {
269
+ width: window.innerWidth,
270
+ height: window.innerHeight,
271
+ };
272
+ return windowSize;
273
+ };
274
+ return getDimensionsOfCurrentWindow();
275
+ """
276
+ browser_window_size = ctx.browser.execute(script)
287
277
  screenshot = {
288
278
  "step_name": step_name,
289
279
  "label": label,
290
280
  "element": element,
291
- "location": f"({element.location['x']},{element.location['y']})"
292
- if element
293
- else "",
294
- "size": f"({element.size['width']},{element.size['height']})"
295
- if element
296
- else "",
281
+ "location": element_info,
297
282
  "filepath": filepath,
283
+ "size": browser_window_size,
298
284
  }
299
285
  step.screenshots.append(screenshot)
300
286
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes