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.

Files changed (83) hide show
  1. cucu/__init__.py +38 -0
  2. cucu/ansi_parser.py +58 -0
  3. cucu/behave_tweaks.py +196 -0
  4. cucu/browser/__init__.py +0 -0
  5. cucu/browser/core.py +80 -0
  6. cucu/browser/frames.py +106 -0
  7. cucu/browser/selenium.py +323 -0
  8. cucu/browser/selenium_tweaks.py +27 -0
  9. cucu/cli/__init__.py +3 -0
  10. cucu/cli/core.py +788 -0
  11. cucu/cli/run.py +207 -0
  12. cucu/cli/steps.py +137 -0
  13. cucu/cli/thread_dumper.py +55 -0
  14. cucu/config.py +440 -0
  15. cucu/edgedriver_autoinstaller/README.md +1 -0
  16. cucu/edgedriver_autoinstaller/__init__.py +37 -0
  17. cucu/edgedriver_autoinstaller/utils.py +231 -0
  18. cucu/environment.py +283 -0
  19. cucu/external/jquery/jquery-3.5.1.min.js +2 -0
  20. cucu/formatter/__init__.py +0 -0
  21. cucu/formatter/cucu.py +261 -0
  22. cucu/formatter/json.py +321 -0
  23. cucu/formatter/junit.py +289 -0
  24. cucu/fuzzy/__init__.py +3 -0
  25. cucu/fuzzy/core.py +107 -0
  26. cucu/fuzzy/fuzzy.js +253 -0
  27. cucu/helpers.py +875 -0
  28. cucu/hooks.py +205 -0
  29. cucu/language_server/__init__.py +3 -0
  30. cucu/language_server/core.py +114 -0
  31. cucu/lint/__init__.py +0 -0
  32. cucu/lint/linter.py +397 -0
  33. cucu/lint/rules/format.yaml +125 -0
  34. cucu/logger.py +113 -0
  35. cucu/matcher/__init__.py +0 -0
  36. cucu/matcher/core.py +30 -0
  37. cucu/page_checks.py +63 -0
  38. cucu/reporter/__init__.py +3 -0
  39. cucu/reporter/external/bootstrap.min.css +7 -0
  40. cucu/reporter/external/bootstrap.min.js +7 -0
  41. cucu/reporter/external/dataTables.bootstrap.min.css +1 -0
  42. cucu/reporter/external/dataTables.bootstrap.min.js +14 -0
  43. cucu/reporter/external/jquery-3.5.1.min.js +2 -0
  44. cucu/reporter/external/jquery.dataTables.min.js +192 -0
  45. cucu/reporter/external/popper.min.js +5 -0
  46. cucu/reporter/favicon.png +0 -0
  47. cucu/reporter/html.py +452 -0
  48. cucu/reporter/templates/feature.html +72 -0
  49. cucu/reporter/templates/flat.html +48 -0
  50. cucu/reporter/templates/index.html +49 -0
  51. cucu/reporter/templates/layout.html +109 -0
  52. cucu/reporter/templates/scenario.html +200 -0
  53. cucu/steps/__init__.py +27 -0
  54. cucu/steps/base_steps.py +88 -0
  55. cucu/steps/browser_steps.py +337 -0
  56. cucu/steps/button_steps.py +91 -0
  57. cucu/steps/checkbox_steps.py +111 -0
  58. cucu/steps/command_steps.py +181 -0
  59. cucu/steps/comment_steps.py +17 -0
  60. cucu/steps/draggable_steps.py +168 -0
  61. cucu/steps/dropdown_steps.py +467 -0
  62. cucu/steps/file_input_steps.py +80 -0
  63. cucu/steps/filesystem_steps.py +144 -0
  64. cucu/steps/flow_control_steps.py +198 -0
  65. cucu/steps/image_steps.py +37 -0
  66. cucu/steps/input_steps.py +301 -0
  67. cucu/steps/link_steps.py +63 -0
  68. cucu/steps/menuitem_steps.py +39 -0
  69. cucu/steps/platform_steps.py +29 -0
  70. cucu/steps/radio_steps.py +187 -0
  71. cucu/steps/step_utils.py +55 -0
  72. cucu/steps/tab_steps.py +68 -0
  73. cucu/steps/table_steps.py +437 -0
  74. cucu/steps/tables.js +28 -0
  75. cucu/steps/text_steps.py +78 -0
  76. cucu/steps/variable_steps.py +100 -0
  77. cucu/steps/webserver_steps.py +40 -0
  78. cucu/utils.py +269 -0
  79. cucu-1.0.0.dist-info/METADATA +424 -0
  80. cucu-1.0.0.dist-info/RECORD +83 -0
  81. cucu-1.0.0.dist-info/WHEEL +4 -0
  82. cucu-1.0.0.dist-info/entry_points.txt +2 -0
  83. cucu-1.0.0.dist-info/licenses/LICENSE +32 -0
@@ -0,0 +1,467 @@
1
+ import logging
2
+
3
+ import humanize
4
+ from selenium.common.exceptions import (
5
+ ElementClickInterceptedException,
6
+ ElementNotInteractableException,
7
+ )
8
+ from selenium.webdriver.common.by import By
9
+ from selenium.webdriver.common.keys import Keys
10
+ from selenium.webdriver.support.ui import Select
11
+ from tenacity import (
12
+ before_sleep_log,
13
+ retry_if_exception_type,
14
+ retry_if_result,
15
+ stop_after_attempt,
16
+ wait_fixed,
17
+ )
18
+ from tenacity import retry as retrying
19
+
20
+ from cucu import fuzzy, helpers, logger, retry, step
21
+ from cucu.steps.input_steps import find_input
22
+ from cucu.utils import take_saw_element_screenshot
23
+
24
+ from . import base_steps
25
+
26
+
27
+ def find_dropdown(ctx, name, index=0):
28
+ """
29
+ find a dropdown on screen by fuzzy matching on the name provided and the
30
+ target element:
31
+
32
+ * <select>
33
+ * <* role="combobox">
34
+ * <* role="listbox">
35
+
36
+ parameters:
37
+ ctx(object): behave context object used to share data between steps
38
+ name(str): name that identifies the desired dropdown on screen
39
+ index(str): the index of the dropdown if there are duplicates
40
+
41
+ returns:
42
+ the WebElement that matches the provided arguments.
43
+ """
44
+ ctx.check_browser_initialized()
45
+
46
+ dropdown = fuzzy.find(
47
+ ctx.browser,
48
+ name,
49
+ [
50
+ "select",
51
+ '*[role="combobox"]',
52
+ '*[role="listbox"]',
53
+ ],
54
+ index=index,
55
+ direction=fuzzy.Direction.LEFT_TO_RIGHT,
56
+ )
57
+ if not dropdown:
58
+ # In case the name is on the top of the dropdown element,
59
+ # the name is after the ting in DOM. Try the other direction.
60
+ dropdown = fuzzy.find(
61
+ ctx.browser,
62
+ name,
63
+ [
64
+ "select",
65
+ '*[role="combobox"]',
66
+ '*[role="listbox"]',
67
+ ],
68
+ index=index,
69
+ direction=fuzzy.Direction.RIGHT_TO_LEFT,
70
+ )
71
+
72
+ take_saw_element_screenshot(ctx, "dropdown", name, index, dropdown)
73
+
74
+ if dropdown:
75
+ outer_html = dropdown.get_attribute("outerHTML")
76
+ logger.debug(f'looked for dropdown "{name}", and found "{outer_html}"')
77
+ else:
78
+ logger.debug(f'looked for dropdown "{name}" but found none')
79
+
80
+ return dropdown
81
+
82
+
83
+ @retrying(
84
+ retry=retry_if_result(lambda result: result is None),
85
+ stop=stop_after_attempt(10),
86
+ wait=wait_fixed(0.1),
87
+ before_sleep=before_sleep_log(logger, logging.DEBUG),
88
+ reraise=True,
89
+ retry_error_callback=lambda retry_state: retry_state.outcome.result(),
90
+ )
91
+ def find_dropdown_option(ctx, name, index=0):
92
+ """
93
+ find a dropdown option with the provided name. It only considers
94
+ the web element with the name inside the element.
95
+
96
+ * <option>
97
+ * <* role="option">
98
+
99
+ parameters:
100
+ ctx(object): behave context object used to share data between steps
101
+ name(str): name that identifies the desired dropdown on screen
102
+ index(str): the index of the dropdown if there are duplicates
103
+
104
+ returns:
105
+ the WebElement that matches the provided arguments.
106
+ """
107
+ ctx.check_browser_initialized()
108
+
109
+ option = fuzzy.find(
110
+ ctx.browser,
111
+ name,
112
+ [
113
+ "option",
114
+ '*[role="option"]',
115
+ '*[role="treeitem"]',
116
+ "*[aria-selected]",
117
+ ],
118
+ index=index,
119
+ direction=fuzzy.Direction.LEFT_TO_RIGHT,
120
+ name_within_thing=True,
121
+ )
122
+ if option:
123
+ outer_html = option.get_attribute("outerHTML")
124
+ logger.debug(
125
+ f'looked for dropdown option "{name}", and found "{outer_html}"'
126
+ )
127
+ else:
128
+ logger.debug(f'looked for dropdown option "{name}" but found none')
129
+
130
+ return option
131
+
132
+
133
+ def click_dropdown(ctx, dropdown):
134
+ """
135
+ Internal method used to simply click a dropdown element
136
+
137
+ Args:
138
+ ctx(object): behave context object used to share data between steps
139
+ dropdown(WebElement): the dropdown element
140
+ """
141
+ ctx.check_browser_initialized()
142
+
143
+ if base_steps.is_disabled(dropdown):
144
+ raise RuntimeError("unable to click the button, as it is disabled")
145
+
146
+ logger.debug("clicking dropdown")
147
+ try:
148
+ ctx.browser.click(dropdown)
149
+ except ElementClickInterceptedException:
150
+ clickable = dropdown
151
+ while True:
152
+ # In some cases, the dropdown is blocked by the selected item.
153
+ # It finds the ancestors of the dropdown that is clickable and click.
154
+ clickable = clickable.find_element(By.XPATH, "..")
155
+ try:
156
+ ctx.browser.click(clickable)
157
+ except ElementClickInterceptedException:
158
+ continue
159
+ break
160
+
161
+
162
+ @retrying(
163
+ retry=retry_if_exception_type(ElementNotInteractableException),
164
+ stop=stop_after_attempt(10),
165
+ wait=wait_fixed(0.1),
166
+ before_sleep=before_sleep_log(logger, logging.DEBUG),
167
+ reraise=True,
168
+ )
169
+ def click_dynamic_dropdown_option(ctx, option_element):
170
+ ctx.browser.execute("arguments[0].scrollIntoView();", option_element)
171
+ ctx.browser.click(option_element)
172
+
173
+
174
+ def find_n_select_dropdown_option(ctx, dropdown, option, index=0):
175
+ """
176
+ find and select dropdown option
177
+
178
+ parameters:
179
+ ctx(object): behave context object used to share data between steps
180
+ name(str): name that identifies the desired dropdown on screen
181
+ option(str): name of the option to select
182
+ index(str): the index of the dropdown if there are duplicates
183
+ """
184
+ ctx.check_browser_initialized()
185
+
186
+ dropdown_element = find_dropdown(ctx, dropdown, index)
187
+
188
+ if dropdown_element is None:
189
+ prefix = "" if index == 0 else f"{humanize.ordinal(index)} "
190
+ raise RuntimeError(f"unable to find the {prefix}dropdown {dropdown}")
191
+
192
+ if base_steps.is_disabled(dropdown_element):
193
+ raise RuntimeError(
194
+ "unable to select from the dropdown, as it is disabled"
195
+ )
196
+
197
+ if dropdown_element.tag_name == "select":
198
+ select_element = Select(dropdown_element)
199
+ select_element.select_by_visible_text(option)
200
+
201
+ else:
202
+ if dropdown_element.get_attribute("aria-expanded") != "true":
203
+ # open the dropdown
204
+ click_dropdown(ctx, dropdown_element)
205
+
206
+ option_element = find_dropdown_option(ctx, option)
207
+
208
+ if option_element is None:
209
+ raise RuntimeError(
210
+ f'unable to find option "{option}" in dropdown "{dropdown}"'
211
+ )
212
+
213
+ logger.debug("clicking dropdown option")
214
+ ctx.browser.execute("arguments[0].scrollIntoView();", option_element)
215
+ ctx.browser.click(option_element)
216
+
217
+
218
+ def find_n_select_dynamic_dropdown_option(ctx, dropdown, option, index=0):
219
+ """
220
+ find and select dynamic dropdown option
221
+
222
+ parameters:
223
+ ctx(object): behave context object used to share data between steps
224
+ name(str): name that identifies the desired dropdown on screen
225
+ option(str): name of the option to select
226
+ index(str): the index of the dropdown if there are duplicates
227
+ """
228
+ ctx.check_browser_initialized()
229
+
230
+ dropdown_element = find_dropdown(ctx, dropdown, index)
231
+
232
+ if dropdown_element is None:
233
+ prefix = "" if index == 0 else f"{humanize.ordinal(index)} "
234
+ raise RuntimeError(f"unable to find the {prefix}dropdown {dropdown}")
235
+
236
+ if base_steps.is_disabled(dropdown_element):
237
+ raise RuntimeError(
238
+ "unable to select from the dropdown, as it is disabled"
239
+ )
240
+
241
+ if dropdown_element.get_attribute("aria-expanded") != "true":
242
+ # open the dropdown
243
+ click_dropdown(ctx, dropdown_element)
244
+
245
+ option_element = find_dropdown_option(ctx, option)
246
+
247
+ # Use the search feature to make the option visible so cucu can pick it up
248
+ if option_element is None:
249
+ dropdown_input = find_input(ctx, dropdown, index)
250
+ logger.debug(
251
+ f'option "{option}" is not found, trying to send keys "{option}".'
252
+ )
253
+ dropdown_value = dropdown_input.get_attribute("value")
254
+ if dropdown_value:
255
+ logger.debug(f"clear dropdown value: {dropdown_value}")
256
+ dropdown_input.send_keys(
257
+ Keys.ARROW_RIGHT * len(dropdown_value)
258
+ ) # make sure the cursor is at the end
259
+ dropdown_input.send_keys(Keys.BACKSPACE * len(dropdown_value))
260
+ # After each key stroke there is a request and an update of the option list. To prevent stale element,
261
+ # we send keys one by one here and try to find the option after each key.
262
+ for key in option:
263
+ try:
264
+ dropdown_input = find_input(ctx, dropdown, index)
265
+ logger.debug(f'sending key "{key}"')
266
+ dropdown_input.send_keys(key)
267
+ ctx.browser.wait_for_page_to_load()
268
+ option_element = find_dropdown_option(ctx, option)
269
+ if option_element:
270
+ break
271
+ except Exception:
272
+ option_element = None
273
+
274
+ if option_element is None:
275
+ raise RuntimeError(
276
+ f'unable to find option "{option}" in dropdown "{dropdown}"'
277
+ )
278
+
279
+ logger.debug("clicking dropdown option")
280
+ click_dynamic_dropdown_option(ctx, option_element)
281
+
282
+
283
+ def assert_dropdown_option_selected(
284
+ ctx, dropdown, option, index=0, is_selected=True
285
+ ):
286
+ """
287
+ assert dropdown option is selected
288
+
289
+ parameters:
290
+ ctx(object): behave context object used to share data between steps
291
+ name(str): name that identifies the desired dropdown on screen
292
+ option(str): name of the option to select
293
+ index(str): the index of the dropdown if there are duplicates
294
+ """
295
+ ctx.check_browser_initialized()
296
+
297
+ dropdown_element = find_dropdown(ctx, dropdown, index)
298
+ if dropdown_element is None:
299
+ raise RuntimeError(f'unable to find dropdown "{dropdown}"')
300
+
301
+ selected_option = None
302
+ if dropdown_element.tag_name == "select":
303
+ select_element = Select(dropdown_element)
304
+ selected_option = select_element.first_selected_option
305
+
306
+ if selected_option is None:
307
+ raise RuntimeError(
308
+ f"unable to find selected option in dropdown {dropdown}"
309
+ )
310
+
311
+ selected_name = selected_option.get_attribute("textContent")
312
+
313
+ # XXX: we're doing contains because a lot of our existing dropdowns
314
+ # do not use aria-label/aria-describedby to make them accessible
315
+ # and easier to find for automation by their name
316
+ if is_selected:
317
+ if selected_name.find(option) == -1:
318
+ raise RuntimeError(f"{option} is not selected")
319
+ else:
320
+ if selected_name.find(option) != -1:
321
+ raise RuntimeError(f"{option} is selected")
322
+
323
+ else:
324
+ if dropdown_element.get_attribute("aria-expanded") != "true":
325
+ # open the dropdown to see its options
326
+ click_dropdown(ctx, dropdown_element)
327
+
328
+ selected_option = find_dropdown_option(ctx, option)
329
+
330
+ if selected_option is None:
331
+ raise RuntimeError(
332
+ f'unable to find option "{option}" in dropdown "{dropdown}"'
333
+ )
334
+
335
+ if is_selected:
336
+ if selected_option.get_attribute("aria-selected") != "true":
337
+ raise RuntimeError(f"{option} is not selected")
338
+ else:
339
+ if selected_option.get_attribute("aria-selected") == "true":
340
+ raise RuntimeError(f"{option} is selected")
341
+
342
+ # close the dropdown
343
+ click_dropdown(ctx, dropdown_element)
344
+
345
+
346
+ helpers.define_should_see_thing_with_name_steps("dropdown", find_dropdown)
347
+ helpers.define_thing_with_name_in_state_steps(
348
+ "dropdown", "disabled", find_dropdown, base_steps.is_disabled
349
+ )
350
+ helpers.define_thing_with_name_in_state_steps(
351
+ "dropdown", "not disabled", find_dropdown, base_steps.is_not_disabled
352
+ )
353
+ helpers.define_run_steps_if_I_can_see_element_with_name_steps(
354
+ "dropdown", find_dropdown
355
+ )
356
+ helpers.define_action_on_thing_with_name_steps(
357
+ "dropdown", "click", find_dropdown, click_dropdown, with_nth=True
358
+ )
359
+ helpers.define_thing_with_name_in_state_steps(
360
+ "dropdown option", "disabled", find_dropdown_option, base_steps.is_disabled
361
+ )
362
+
363
+
364
+ @step('I select the option "{option}" from the dropdown "{dropdown}"')
365
+ def select_option_from_dropdown(ctx, option, dropdown):
366
+ find_n_select_dropdown_option(ctx, dropdown, option)
367
+
368
+
369
+ @step(
370
+ 'I select the option "{option}" from the "{index:nth}" dropdown "{dropdown}"'
371
+ )
372
+ def select_option_from_nth_dropdown(ctx, option, dropdown, index):
373
+ find_n_select_dropdown_option(ctx, dropdown, option, index)
374
+
375
+
376
+ @step(
377
+ 'I wait to select the option "{option}" from the "{index:nth}" dropdown "{dropdown}"'
378
+ )
379
+ def wait_to_select_option_from_nth_dropdown(ctx, option, dropdown, index):
380
+ retry(find_n_select_dropdown_option)(ctx, dropdown, option, index)
381
+
382
+
383
+ @step('I wait to select the option "{option}" from the dropdown "{dropdown}"')
384
+ def wait_to_select_option_from_dropdown(ctx, option, dropdown):
385
+ retry(find_n_select_dropdown_option)(ctx, dropdown, option)
386
+
387
+
388
+ @step('I select the option "{option}" from the dynamic dropdown "{dropdown}"')
389
+ def select_option_from_dynamic_dropdown(ctx, option, dropdown):
390
+ find_n_select_dynamic_dropdown_option(ctx, dropdown, option)
391
+
392
+
393
+ @step(
394
+ 'I select the option "{option}" from the "{index:nth}" dynamic dropdown "{dropdown}"'
395
+ )
396
+ def select_option_from_nth_dynamic_dropdown(ctx, option, dropdown, index):
397
+ find_n_select_dynamic_dropdown_option(ctx, dropdown, option, index)
398
+
399
+
400
+ @step(
401
+ 'I wait to select the option "{option}" from the "{index:nth}" dynamic dropdown "{dropdown}"'
402
+ )
403
+ def wait_to_select_option_from_nth_dynamic_dropdown(
404
+ ctx, option, dropdown, index
405
+ ):
406
+ retry(find_n_select_dynamic_dropdown_option)(ctx, dropdown, option, index)
407
+
408
+
409
+ @step(
410
+ 'I wait to select the option "{option}" from the dynamic dropdown "{dropdown}"'
411
+ )
412
+ def wait_to_select_option_from_dynamic_dropdown(ctx, option, dropdown):
413
+ retry(find_n_select_dynamic_dropdown_option)(ctx, dropdown, option)
414
+
415
+
416
+ @step(
417
+ 'I should see the option "{option}" is selected on the dropdown "{dropdown}"'
418
+ )
419
+ def should_see_option_is_selected_from_dropdown(ctx, option, dropdown):
420
+ assert_dropdown_option_selected(ctx, dropdown, option, is_selected=True)
421
+
422
+
423
+ @step(
424
+ 'I should see the option "{option}" is selected on the "{index:nth}" dropdown "{dropdown}"'
425
+ )
426
+ def should_see_option_is_selected_from_nth_dropdown(
427
+ ctx, option, dropdown, index
428
+ ):
429
+ assert_dropdown_option_selected(
430
+ ctx, dropdown, option, index, is_selected=True
431
+ )
432
+
433
+
434
+ @step(
435
+ 'I wait to see the option "{option}" is selected on the dropdown "{dropdown}"'
436
+ )
437
+ def wait_to_see_option_is_selected_from_dropdown(ctx, option, dropdown):
438
+ retry(assert_dropdown_option_selected)(
439
+ ctx, dropdown, option, is_selected=True
440
+ )
441
+
442
+
443
+ @step(
444
+ 'I should see the option "{option}" is not selected on the "{index:nth}" dropdown "{dropdown}"'
445
+ )
446
+ def should_see_option_is_not_selected_from_nth_dropdown(
447
+ ctx, option, dropdown, index
448
+ ):
449
+ assert_dropdown_option_selected(
450
+ ctx, dropdown, option, index, is_selected=False
451
+ )
452
+
453
+
454
+ @step(
455
+ 'I should see the option "{option}" is not selected on the dropdown "{dropdown}"'
456
+ )
457
+ def should_see_option_is_not_selected_from_dropdown(ctx, option, dropdown):
458
+ assert_dropdown_option_selected(ctx, dropdown, option, is_selected=False)
459
+
460
+
461
+ @step(
462
+ 'I wait to see the option "{option}" is not selected on the dropdown "{dropdown}"'
463
+ )
464
+ def wait_to_see_option_is_not_selected_from_dropdown(
465
+ ctx, option, dropdown, is_selected=False
466
+ ):
467
+ retry(assert_dropdown_option_selected)(ctx, dropdown, option)
@@ -0,0 +1,80 @@
1
+ import os
2
+
3
+ import humanize
4
+
5
+ from cucu import fuzzy, logger, step
6
+ from cucu.utils import take_saw_element_screenshot
7
+
8
+
9
+ def find_file_input(ctx, name, index=0):
10
+ """
11
+
12
+ * <input type="file">
13
+
14
+ parameters:
15
+ ctx(object): behave context object used to share data between steps
16
+ name(str): name that identifies the desired button on screen
17
+ index(str): the index of the button if there are duplicates
18
+
19
+ returns:
20
+ the WebElement that matches the provided arguments.
21
+ """
22
+ ctx.check_browser_initialized()
23
+ element = fuzzy.find(
24
+ ctx.browser, name, ['input[type="file"]'], index=index
25
+ )
26
+
27
+ prefix = "" if index == 0 else f"{humanize.ordinal(index)} "
28
+
29
+ take_saw_element_screenshot(ctx, "file input", name, index, element)
30
+
31
+ if element is None:
32
+ raise RuntimeError(f'unable to find the {prefix}file input "{name}"')
33
+
34
+ return element
35
+
36
+
37
+ @step('I upload the file "{filepath}" to the file input "{name}"')
38
+ def upload_file_to_input(ctx, filepath, name):
39
+ _input = find_file_input(ctx, name)
40
+ _input.send_keys(os.path.abspath(filepath))
41
+
42
+
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);
66
+ };
67
+ document.body.appendChild(input);
68
+ return input;
69
+ """
70
+
71
+
72
+ @step('I drag and drop the file "{filepath}" to "{name}"')
73
+ def drag_and_drop_file(ctx, name, filepath):
74
+ drop_target = fuzzy.find(ctx.browser, name, ["*"])
75
+ drop_target_html = drop_target.get_attribute("outerHTML")
76
+ logger.debug(
77
+ f'looked for drag & drop target "{name}" and found "{drop_target_html}"'
78
+ )
79
+ file_input = ctx.browser.execute(JS_DROP_FILE, drop_target, 0, 0)
80
+ file_input.send_keys(os.path.abspath(filepath))
@@ -0,0 +1,144 @@
1
+ import os
2
+ import re
3
+
4
+ from cucu import retry, step
5
+ from cucu.config import CONFIG
6
+
7
+
8
+ @step('I create a file at "{filepath}" with the following')
9
+ def create_file_with_the_following(ctx, filepath):
10
+ dirname = os.path.dirname(filepath)
11
+ if dirname and not os.path.exists(dirname):
12
+ os.makedirs(dirname)
13
+
14
+ with open(filepath, "wb") as output:
15
+ output.write(bytes(ctx.text, "utf8"))
16
+
17
+
18
+ @step('I create the directory at "{filepath}"')
19
+ def create_directory_at(ctx, filepath):
20
+ os.makedirs(filepath)
21
+
22
+
23
+ @step('I delete the file at "{filepath}"')
24
+ def delete_file_at(ctx, filepath):
25
+ os.remove(filepath)
26
+
27
+
28
+ @step('I delete the file at "{filepath}" if it exists')
29
+ def delete_file_at_if_it_exists(ctx, filepath):
30
+ if os.path.exists(filepath):
31
+ os.remove(filepath)
32
+
33
+
34
+ @step(
35
+ 'I read the contents of the file at "{filepath}" and save to the variable "{variable}"'
36
+ )
37
+ def read_file_contents(ctx, filepath, variable):
38
+ with open(filepath, "r") as _input:
39
+ CONFIG[variable] = CONFIG.escape(_input.read())
40
+
41
+
42
+ @step('I append to the file at "{filepath}" the following')
43
+ def append_to_file_the_following(ctx, filepath):
44
+ with open(filepath, "ab") as output:
45
+ output.write(bytes(ctx.text, "utf8"))
46
+
47
+
48
+ def assert_file(ctx, filepath):
49
+ if not (os.path.exists(filepath) and os.path.isfile(filepath)):
50
+ raise RuntimeError(f"unable to see file at {filepath}")
51
+
52
+
53
+ @step('I should see a file at "{filepath}"')
54
+ def should_see_file(ctx, filepath):
55
+ assert_file(ctx, filepath)
56
+
57
+
58
+ @step('I should not see a file at "{filepath}"')
59
+ def should_not_see_file(ctx, filepath):
60
+ if os.path.exists(filepath) and os.path.isfile(filepath):
61
+ raise RuntimeError(f"able to see file at {filepath}")
62
+
63
+
64
+ @step('I wait to see a file at "{filepath}"')
65
+ def wait_to_see_file(ctx, filepath):
66
+ retry(assert_file)(ctx, filepath)
67
+
68
+
69
+ @step('I wait up to "{seconds}" seconds to see a file at "{filepath}"')
70
+ def wait_up_to_see_file(ctx, seconds, filepath):
71
+ seconds = float(seconds)
72
+ retry(assert_file, wait_up_to_s=seconds)(ctx, filepath)
73
+
74
+
75
+ @step('I should see the directory at "{filepath}"')
76
+ def should_see_directory(ctx, filepath):
77
+ if not (os.path.exists(filepath) and os.path.isdir(filepath)):
78
+ raise RuntimeError(f"unable to see directory at {filepath}")
79
+
80
+
81
+ @step('I should not see the directory at "{filepath}"')
82
+ def should_not_see_directory(ctx, filepath):
83
+ if os.path.exists(filepath) and os.path.isdir(filepath):
84
+ raise RuntimeError(f"able to see directory at {filepath}")
85
+
86
+
87
+ @step('I should see the file at "{filepath}" is equal to the following')
88
+ def should_see_file_is_equal_to_the_following(ctx, filepath):
89
+ with open(filepath, "rb") as input:
90
+ file_contents = input.read().decode("utf8")
91
+
92
+ if file_contents != ctx.text:
93
+ raise RuntimeError(
94
+ f"\n{file_contents}\nis not equal to\n{ctx.text}\n"
95
+ )
96
+
97
+
98
+ @step('I should see the file at "{filepath}" contains the following')
99
+ def should_see_file_contains_the_following(ctx, filepath):
100
+ with open(filepath, "rb") as input:
101
+ file_contents = input.read().decode("utf8")
102
+
103
+ if ctx.text not in file_contents:
104
+ raise RuntimeError(
105
+ f"\n{file_contents}\ndoes not contain\n{ctx.text}\n"
106
+ )
107
+
108
+
109
+ @step('I should see the file at "{filepath}" matches the following')
110
+ def should_see_file_matches_the_following(ctx, filepath):
111
+ with open(filepath, "rb") as input:
112
+ file_contents = input.read().decode("utf8")
113
+
114
+ if not re.match(ctx.text, file_contents):
115
+ raise RuntimeError(
116
+ f"\n{file_contents}\ndoes not match\n{ctx.text}\n"
117
+ )
118
+
119
+
120
+ @step('I should see the file at "{filepath}" is not equal to the following')
121
+ def should_see_file_is_not_equal_to_the_following(ctx, filepath):
122
+ with open(filepath, "rb") as input:
123
+ file_contents = input.read().decode("utf8")
124
+
125
+ if file_contents != ctx.text:
126
+ raise RuntimeError(f"\n{file_contents}\nis equal to\n{ctx.text}\n")
127
+
128
+
129
+ @step('I should see the file at "{filepath}" does not contain the following')
130
+ def should_see_file_does_not_contain_the_following(ctx, filepath):
131
+ with open(filepath, "rb") as input:
132
+ file_contents = input.read().decode("utf8")
133
+
134
+ if ctx.text in file_contents:
135
+ raise RuntimeError(f"\n{file_contents}\ncontains\n{ctx.text}\n")
136
+
137
+
138
+ @step('I should see the file at "{filepath}" does not match the following')
139
+ def should_see_file_does_not_match_the_following(ctx, filepath):
140
+ with open(filepath, "rb") as input:
141
+ file_contents = input.read().decode("utf8")
142
+
143
+ if re.match(ctx.text, file_contents):
144
+ raise RuntimeError(f"\n{file_contents}\nmatches\n{ctx.text}\n")