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,198 @@
1
+ import os
2
+ import re
3
+ import subprocess
4
+ import time
5
+
6
+ from cucu import logger, retry, run_steps, step
7
+ from cucu.config import CONFIG
8
+ from cucu.hooks import register_after_this_scenario_hook
9
+
10
+
11
+ @step('I skip this scenario if the file at "{filepath}" exists')
12
+ def skip_scenario_if_file_exists(ctx, filepath):
13
+ if os.path.exists(filepath):
14
+ ctx.scenario.skip(
15
+ reason='skipping scenario since file at "{filepath}" exists'
16
+ )
17
+
18
+
19
+ @step('I expect the following step to fail with "{message}"')
20
+ def expect_the_following_step_to_fail(ctx, message):
21
+ try:
22
+ run_steps(ctx, ctx.text)
23
+ except Exception as exception:
24
+ if str(exception).find(message) == -1:
25
+ raise RuntimeError(
26
+ f'expected failure message was "{str(exception)}" not "{message}"'
27
+ )
28
+ return
29
+
30
+ raise RuntimeError("previous steps did not fail!")
31
+
32
+
33
+ @step('I should see the previous step took less than "{seconds}" seconds')
34
+ def should_see_previous_step_took_less_than(ctx, seconds):
35
+ if ctx.previous_step_duration > float(seconds):
36
+ raise RuntimeError(
37
+ f"previous step took {ctx.previous_step_duration}, which is more than {seconds}"
38
+ )
39
+
40
+
41
+ @step('I should see the previous step took more than "{seconds}" seconds')
42
+ def should_see_previous_step_took_more_than(ctx, seconds):
43
+ if ctx.previous_step_duration < float(seconds):
44
+ raise RuntimeError(
45
+ f"previous step took {ctx.previous_step_duration}, which is less than {seconds}"
46
+ )
47
+
48
+
49
+ @step("I wait to see the following steps succeed")
50
+ def wait_to_see_the_following_steps_suceed(ctx):
51
+ retry(run_steps)(ctx, ctx.text)
52
+
53
+
54
+ @step('I wait up to "{seconds}" seconds to see the following steps succeed')
55
+ def wait_up_to_seconds_to_see_the_following_steps_suceed(ctx, seconds):
56
+ retry(run_steps, wait_up_to_s=float(seconds))(ctx, ctx.text)
57
+
58
+
59
+ def wait_for_steps_to_fail(ctx, steps, timeout=None):
60
+ def steps_should_fail():
61
+ try:
62
+ run_steps(ctx, steps)
63
+ except: # noqa: E722
64
+ return
65
+
66
+ raise RuntimeError("underlying steps did not fail")
67
+
68
+ retry(steps_should_fail, wait_up_to_s=timeout)()
69
+
70
+
71
+ @step("I wait to see the following steps fail")
72
+ def wait_to_see_the_following_steps_fail(ctx):
73
+ wait_for_steps_to_fail(ctx, ctx.text)
74
+
75
+
76
+ @step('I wait up to "{seconds}" seconds to see the following steps fail')
77
+ def wait_up_to_seconds_to_see_the_following_steps_fail(ctx, seconds):
78
+ wait_for_steps_to_fail(ctx, ctx.text, timeout=float(seconds))
79
+
80
+
81
+ def repeat_steps(ctx, steps, repeat, variable=None):
82
+ for index in range(0, repeat):
83
+ if variable is not None:
84
+ CONFIG[variable] = index + 1
85
+
86
+ run_steps(ctx, steps)
87
+
88
+
89
+ @step('I repeat "{repeat}" times the following steps')
90
+ def repeat_n_times_the_following_steps(ctx, repeat):
91
+ repeat_steps(ctx, ctx.text, int(repeat))
92
+
93
+
94
+ @step(
95
+ 'I repeat "{repeat}" times the following steps with iteration variable "{variable}"'
96
+ )
97
+ def repeat_n_times_the_following_steps_with_variable(ctx, repeat, variable):
98
+ repeat_steps(ctx, ctx.text, int(repeat), variable=variable)
99
+
100
+
101
+ @step('I run the following steps if the file at "{filepath}" does not exist')
102
+ def run_steps_if_file_does_not_exist(ctx, filepath):
103
+ if not os.path.exists(filepath):
104
+ run_steps(ctx, ctx.text)
105
+
106
+
107
+ @step('I run the following steps if the file at "{filepath}" exists')
108
+ def run_steps_if_file_exists(ctx, filepath):
109
+ if os.path.exists(filepath):
110
+ run_steps(ctx, ctx.text)
111
+
112
+
113
+ @step('I run the following timed steps as "{name}"')
114
+ def run_and_measure_the_following_steps(ctx, name):
115
+ start = time.time()
116
+ run_steps(ctx, ctx.text)
117
+ duration = round(time.time() - start, 3)
118
+ logger.info(f'"{name}" timer took {duration}s')
119
+
120
+
121
+ @step('I start the timer "{name}"')
122
+ def start_the_timer(ctx, name):
123
+ ctx.step_timers[name] = time.time()
124
+
125
+
126
+ @step('I stop the timer "{name}"')
127
+ def stop_the_timer(ctx, name):
128
+ if name not in ctx.step_timers:
129
+ raise RuntimeError(f'no previously started timer with name "{name}"')
130
+ start = ctx.step_timers[name]
131
+ del ctx.step_timers[name]
132
+
133
+ duration = round(time.time() - start, 3)
134
+ logger.info(f'"{name}" timer took {duration}s')
135
+
136
+
137
+ def run_feature(ctx, filepath, results):
138
+ command = f"cucu run {filepath} --results {results}"
139
+ process = subprocess.run(command, shell=True) # nosec
140
+
141
+ return_code = process.returncode
142
+ if return_code != 0:
143
+ raise RuntimeError(
144
+ f'"{command}" exited with {return_code}, see above for details'
145
+ )
146
+
147
+
148
+ @step(
149
+ 'I run the feature at "{feature_filepath}" with results at "{results_filepath}"'
150
+ )
151
+ def run_feature_at(ctx, feature_filepath, results_filepath):
152
+ run_feature(ctx, feature_filepath, results_filepath)
153
+
154
+
155
+ @step(
156
+ 'I run the feature at "{feature_filepath}" with results at "{results_filepath}" if the file at "{filepath}" does not exist'
157
+ )
158
+ def run_feature_if_file_does_not_exist(
159
+ ctx, feature_filepath, results_filepath, filepath
160
+ ):
161
+ if not os.path.exists(filepath):
162
+ run_feature(ctx, feature_filepath, results_filepath)
163
+
164
+
165
+ @step(
166
+ 'I run the feature at "{feature_filepath}" with results at "{results_filepath}" if the file at "{filepath}" exists'
167
+ )
168
+ def run_feature_if_file_exists(
169
+ ctx, feature_filepath, results_filepath, filepath
170
+ ):
171
+ if os.path.exists(filepath):
172
+ run_feature(ctx, feature_filepath, results_filepath)
173
+
174
+
175
+ @step("I run the following steps at the end of the current scenario")
176
+ def run_the_following_steps_at_end_of_scenario(ctx):
177
+ steps = ctx.text
178
+
179
+ def run_final_steps(ctx):
180
+ run_steps(ctx, steps)
181
+
182
+ register_after_this_scenario_hook(run_final_steps)
183
+
184
+
185
+ for operation, operator in {
186
+ "is equal to": lambda x, y: x == y,
187
+ "is not equal to": lambda x, y: x != y,
188
+ "contains": lambda x, y: y in x,
189
+ "does not contain": lambda x, y: y not in x,
190
+ "matches": lambda x, y: re.match(y, x),
191
+ "does not match": lambda x, y: not (re.match(y, x)),
192
+ }.items():
193
+ # the operator keyword below is used to scope the value of the operator
194
+ # value to the correct value at runtime when creating the various steps
195
+ @step('I run the following steps if "{this}" ' + operation + ' "{that}"')
196
+ def run_steps_if_this_operator_that(ctx, this, that, operator=operator):
197
+ if operator(this, that):
198
+ run_steps(ctx, ctx.text)
@@ -0,0 +1,37 @@
1
+ from cucu import helpers
2
+ from cucu.utils import take_saw_element_screenshot
3
+
4
+
5
+ def find_image(ctx, name, index=0):
6
+ """
7
+ find an input on screen by fuzzy matching on the name provided and the
8
+ target element:
9
+
10
+ * <img>
11
+
12
+ parameters:
13
+ ctx(object): behave context object used to share data between steps
14
+ name(str): name that identifies the desired image on screen
15
+ index(str): the index of the image if there are duplicates
16
+
17
+ returns:
18
+ the WebElement that matches the provided arguments.
19
+ """
20
+ ctx.check_browser_initialized()
21
+
22
+ name = name.replace('"', '\\"')
23
+ images = ctx.browser.css_find_elements(f'img[alt="{name}"')
24
+
25
+ if index >= len(images):
26
+ return None
27
+
28
+ element = images[index]
29
+
30
+ take_saw_element_screenshot(ctx, "image", name, index, element)
31
+
32
+ return element
33
+
34
+
35
+ helpers.define_should_see_thing_with_name_steps(
36
+ "image with the alt text", find_image
37
+ )
@@ -0,0 +1,301 @@
1
+ import sys
2
+
3
+ import humanize
4
+
5
+ # XXX: this would have to be generalized to other browser abstractions
6
+ from selenium.webdriver.common.keys import Keys
7
+
8
+ from cucu import fuzzy, helpers, retry, step
9
+ from cucu.utils import take_saw_element_screenshot
10
+
11
+ from . import base_steps
12
+
13
+
14
+ def find_input(ctx, name, index=0):
15
+ """
16
+ find an input on screen by fuzzy matching on the name and index provided.
17
+
18
+ * <input>
19
+ * <textarea>
20
+
21
+ parameters:
22
+ ctx(object): behave context object used to share data between steps
23
+ name(str): name that identifies the desired input on screen
24
+ index(str): the index of the input if there are a few with the same name.
25
+
26
+ returns:
27
+ the WebElement that matches the provided arguments.
28
+
29
+ raises:
30
+ a RuntimeError if the input isn't found
31
+ """
32
+ ctx.check_browser_initialized()
33
+
34
+ element = fuzzy.find(
35
+ ctx.browser,
36
+ name,
37
+ [
38
+ # we can only write into things that do not have the type:
39
+ # button, checkbox, radio, color, hidden, range, reset
40
+ "input[type!=button][type!=checkbox][type!=radio][button!=color][button!=hidden][button!=range][button!=reset]",
41
+ "textarea",
42
+ ],
43
+ index=index,
44
+ )
45
+
46
+ prefix = "" if index == 0 else f"{humanize.ordinal(index)} "
47
+
48
+ take_saw_element_screenshot(ctx, "input", name, index, element)
49
+
50
+ if element is None:
51
+ raise RuntimeError(f'unable to find the {prefix}input "{name}"')
52
+
53
+ return element
54
+
55
+
56
+ def click_input(ctx, input_):
57
+ """
58
+ internal method used to simply click a input element
59
+ """
60
+ ctx.check_browser_initialized()
61
+
62
+ if base_steps.is_disabled(input_):
63
+ raise RuntimeError("unable to click the input, as it is disabled")
64
+
65
+ ctx.browser.click(input_)
66
+
67
+
68
+ def clear_input(input_):
69
+ """
70
+ internal method used to clear inputs and make sure they clear correctly
71
+ """
72
+ # Keys.CONTROL works on the Selenium grid (when running in CI)
73
+ # - it stopped working on laptop after the upgrade in antd version (Feb 2023)
74
+ # Keys.COMMAND works on laptop, but not on Selenium grid,
75
+ # and actually causes an active session on the grid to hang.
76
+ if "darwin" in sys.platform:
77
+ input_.send_keys(Keys.COMMAND, "a")
78
+ else:
79
+ input_.send_keys(Keys.CONTROL, "a")
80
+ input_.send_keys(Keys.BACKSPACE)
81
+
82
+
83
+ def find_n_write(ctx, name, value, index=0, clear_existing=True):
84
+ """
85
+ find the input with the name provided and write the value provided into it.
86
+
87
+ parameters:
88
+ ctx(object): behave context object used to share data between steps
89
+ name(str): name that identifies the desired input on screen
90
+ index(str): the index of the input if there are a few with the same name.
91
+ clear_existing(bool): if clearing the existing content before writing
92
+
93
+ raises:
94
+ an error if the desired input is not found
95
+ """
96
+ ctx.check_browser_initialized()
97
+
98
+ input_ = find_input(ctx, name, index=index)
99
+
100
+ if base_steps.is_disabled(input_):
101
+ raise RuntimeError("unable to write into the input, as it is disabled")
102
+
103
+ if clear_existing:
104
+ clear_input(input_)
105
+
106
+ if len(value) > 512:
107
+ #
108
+ # to avoid various bugs with writing large chunks of text into
109
+ # inputs/textareas using send_keys we can just put the text into the
110
+ # @value attribute and simulate a keystroke so other UI related events
111
+ # fire, list of some bugs:
112
+ #
113
+ # * https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/4469
114
+ # * various stackoverflow articles on this matter
115
+ #
116
+ ctx.browser.execute(
117
+ "arguments[0].value = arguments[1];", input_, value
118
+ )
119
+ input_.send_keys(" ")
120
+ input_.send_keys(Keys.BACKSPACE)
121
+
122
+ else:
123
+ input_.send_keys(value)
124
+
125
+
126
+ def find_n_clear(ctx, name, index=0):
127
+ """
128
+ find the input with the name provided and clear whatever value it has.
129
+
130
+ parameters:
131
+ ctx(object): behave context object used to share data between steps
132
+ name(str): name that identifies the desired input on screen
133
+ index(str): the index of the input if there are a few with the same name.
134
+
135
+ raises:
136
+ an error if the desired input is not found
137
+ """
138
+ ctx.check_browser_initialized()
139
+
140
+ input_ = find_input(ctx, name, index=index)
141
+
142
+ if base_steps.is_disabled(input_):
143
+ raise RuntimeError("unable to clear the input, as it is disabled")
144
+
145
+ clear_input(input_)
146
+
147
+
148
+ def assert_input_value(ctx, name, value, index=0):
149
+ """
150
+ assert the input with the name provided has the value specified, unless the
151
+ value is None which then means to verify there is not value set.
152
+
153
+ Params:
154
+ ctx(object): behave context used to share information between steps
155
+ name(string): name of the input to find and assert is visible
156
+ value(string): value to assert the input has, when set to None we verify
157
+ the input currently has no value.
158
+ index(int): index of the input to assert when there are multiple
159
+ inputs have the same name.
160
+ """
161
+ input_ = find_input(ctx, name, index=index)
162
+ actual = input_.get_attribute("value")
163
+ prefix = "" if index == 0 else f"{humanize.ordinal(index)} "
164
+
165
+ if value is None:
166
+ if actual != "":
167
+ raise RuntimeError(
168
+ f'the {prefix}input "{name}" has value "{actual}"'
169
+ )
170
+
171
+ elif actual != value:
172
+ raise RuntimeError(f'the {prefix}input "{name}" has value "{actual}"')
173
+
174
+
175
+ helpers.define_should_see_thing_with_name_steps("input", find_input)
176
+ helpers.define_thing_with_name_in_state_steps(
177
+ "input", "disabled", find_input, base_steps.is_disabled
178
+ )
179
+ helpers.define_thing_with_name_in_state_steps(
180
+ "input", "not disabled", find_input, base_steps.is_not_disabled
181
+ )
182
+ helpers.define_run_steps_if_I_can_see_element_with_name_steps(
183
+ "input", find_input
184
+ )
185
+ helpers.define_action_on_thing_with_name_steps(
186
+ "input", "click", find_input, click_input, with_nth=True
187
+ )
188
+
189
+
190
+ @step('I write "{value}" into the input "{name}"')
191
+ def writes_into_input(ctx, value, name):
192
+ find_n_write(ctx, name, value)
193
+
194
+
195
+ @step('I wait to write "{value}" into the input "{name}"')
196
+ def wait_to_write_into_input(ctx, value, name):
197
+ retry(find_n_write)(ctx, name, value)
198
+
199
+
200
+ @step(
201
+ 'I wait up to "{seconds}" seconds to write "{value}" into the input "{name}"'
202
+ )
203
+ def wait_up_to_write_into_input(ctx, seconds, value, name):
204
+ retry(find_n_write, wait_up_to_s=float(seconds))(ctx, name, value)
205
+
206
+
207
+ @step('I write the following into the input "{name}"')
208
+ def writes_multi_lines_into_input(ctx, name):
209
+ find_n_write(ctx, name, ctx.text)
210
+
211
+
212
+ @step('I wait to write the following into the input "{name}"')
213
+ def wait_to_write_multi_lines_into_input(ctx, name):
214
+ retry(find_n_write)(ctx, name, ctx.text)
215
+
216
+
217
+ @step(
218
+ 'I wait up to "{seconds}" to write the following into the input "{name}"'
219
+ )
220
+ def wait_up_to_write_multi_lines_into_input(ctx, seconds, name):
221
+ retry(find_n_write, wait_up_to_s=float(seconds))(ctx, name, ctx.text)
222
+
223
+
224
+ @step('I send the "{key}" key to the input "{name}"')
225
+ def send_keys_to_input(ctx, key, name):
226
+ find_n_write(ctx, name, Keys.__dict__[key.upper()], clear_existing=False)
227
+
228
+
229
+ @step('I clear the input "{name}"')
230
+ def clear_the_input(ctx, name):
231
+ find_n_clear(ctx, name)
232
+
233
+
234
+ @step('I wait to clear the input "{name}"')
235
+ def wait_to_clear_input(ctx, name):
236
+ retry(find_n_clear)(ctx, name)
237
+
238
+
239
+ @step('I wait up to "{seconds}" seconds to clear the input "{name}"')
240
+ def wait_up_to_clear_input(ctx, seconds, name):
241
+ retry(find_n_clear, wait_up_to_s=float(seconds))(ctx, name)
242
+
243
+
244
+ @step('I should see the input "{name}" is empty')
245
+ def should_see_the_input_is_empty(ctx, name):
246
+ assert_input_value(ctx, name, None)
247
+
248
+
249
+ @step('I should see "{value}" in the input "{name}"')
250
+ def should_see_the_input_with_value(ctx, value, name):
251
+ assert_input_value(ctx, name, value)
252
+
253
+
254
+ @step('I wait to see the value "{value}" in the input "{name}"')
255
+ def wait_to_see_the_input_with_value(ctx, value, name):
256
+ retry(assert_input_value)(ctx, name, value)
257
+
258
+
259
+ @step('I should see the following in the input "{name}"')
260
+ def should_see_the_following_input_with_value(ctx, name):
261
+ assert_input_value(ctx, name, ctx.text)
262
+
263
+
264
+ @step('I wait to see the following in the input "{name}"')
265
+ def wait_to_see_the_following_input_with_value(ctx, name):
266
+ retry(assert_input_value)(ctx, name, ctx.text)
267
+
268
+
269
+ @step('I should see no value in the input "{name}"')
270
+ def should_to_see_the_input_with_no_value(ctx, name):
271
+ assert_input_value(ctx, name, None)
272
+
273
+
274
+ @step('I write "{value}" into the "{nth:nth}" input "{name}"')
275
+ def write_into_the_nth_input(ctx, value, nth, name):
276
+ find_n_write(ctx, name, value, index=nth)
277
+
278
+
279
+ @step('I wait to write "{value}" into the "{nth:nth}" input "{name}"')
280
+ def wait_to_write_into_the_nth_input(ctx, value, nth, name):
281
+ retry(find_n_write)(ctx, name, value, index=nth)
282
+
283
+
284
+ @step('I wait to see the "{nth:nth}" input "{name}"')
285
+ def wait_to_see_the_nth_input(ctx, nth, name):
286
+ retry(find_input)(ctx, name, index=nth)
287
+
288
+
289
+ @step('I should see "{value}" in the "{nth:nth}" input "{name}"')
290
+ def should_see_the_nth_input_with_value(ctx, value, nth, name):
291
+ assert_input_value(ctx, name, value, index=nth)
292
+
293
+
294
+ @step('I wait to see the value "{value}" in the "{nth:nth}" input "{name}"')
295
+ def wait_to_see_the_nth_input_with_value(ctx, value, nth, name):
296
+ retry(assert_input_value)(ctx, name, value, index=nth)
297
+
298
+
299
+ @step('I should see no value in the "{nth:nth}: input "{name}"')
300
+ def should_see_the_nth_input_with_no_value(ctx, nth, name):
301
+ assert_input_value(ctx, name, None, index=nth)
@@ -0,0 +1,63 @@
1
+ from cucu import fuzzy, helpers
2
+ from cucu.utils import take_saw_element_screenshot
3
+
4
+ from . import base_steps
5
+
6
+
7
+ def find_link(ctx, name, index=0):
8
+ """
9
+ find a link on screen by fuzzy matching on the name provided and the target
10
+ element:
11
+
12
+ * <a>
13
+ * <* role="link">
14
+
15
+ parameters:
16
+ ctx(object): behave context object used to share data between steps
17
+ name(str): name that identifies the desired link on screen
18
+ index(str): the index of the link if there are duplicates
19
+
20
+ returns:
21
+ the WebElement that matches the provided arguments.
22
+ """
23
+ ctx.check_browser_initialized()
24
+ element = fuzzy.find(
25
+ ctx.browser, name, ["a", '*[role="link"]'], index=index
26
+ )
27
+
28
+ take_saw_element_screenshot(ctx, "link", name, index, element)
29
+
30
+ return element
31
+
32
+
33
+ def click_link(ctx, link):
34
+ """
35
+ internal method to click a link
36
+ """
37
+ ctx.check_browser_initialized()
38
+
39
+ if base_steps.is_disabled(link):
40
+ raise RuntimeError("unable to click the link, as it is disabled")
41
+
42
+ ctx.browser.click(link)
43
+
44
+
45
+ helpers.define_should_see_thing_with_name_steps(
46
+ "link", find_link, with_nth=True
47
+ )
48
+ helpers.define_action_on_thing_with_name_steps(
49
+ "link", "click", find_link, click_link, with_nth=True
50
+ )
51
+ helpers.define_thing_with_name_in_state_steps(
52
+ "link", "disabled", find_link, base_steps.is_disabled, with_nth=True
53
+ )
54
+ helpers.define_thing_with_name_in_state_steps(
55
+ "link",
56
+ "not disabled",
57
+ find_link,
58
+ base_steps.is_not_disabled,
59
+ with_nth=True,
60
+ )
61
+ helpers.define_run_steps_if_I_can_see_element_with_name_steps(
62
+ "link", find_link
63
+ )
@@ -0,0 +1,39 @@
1
+ #
2
+ # menuitem steps
3
+ # https://www.w3.org/TR/wai-aria-1.1/#menuitem
4
+ #
5
+ from cucu import fuzzy, helpers
6
+ from cucu.utils import take_saw_element_screenshot
7
+
8
+ from . import base_steps
9
+
10
+
11
+ def find_menuitem(ctx, name, index=0):
12
+ """
13
+ find a menuitem containing the menuitem provide.
14
+
15
+ parameters:
16
+ ctx(object): behave context object used to share data between steps
17
+ name(str): name that identifies the desired menuitem on screen
18
+ index(str): the index of the menuitem if there are duplicates
19
+
20
+ returns:
21
+ the WebElement that matches the provided arguments.
22
+ """
23
+ ctx.check_browser_initialized()
24
+ element = fuzzy.find(
25
+ ctx.browser, name, ['*[role="menuitem"]'], index=index
26
+ )
27
+
28
+ take_saw_element_screenshot(ctx, "menuitem", name, index, element)
29
+
30
+ return element
31
+
32
+
33
+ helpers.define_should_see_thing_with_name_steps("menu item", find_menuitem)
34
+ helpers.define_thing_with_name_in_state_steps(
35
+ "menu item", "disabled", find_menuitem, base_steps.is_disabled
36
+ )
37
+ helpers.define_thing_with_name_in_state_steps(
38
+ "menu item", "not disabled", find_menuitem, base_steps.is_not_disabled
39
+ )
@@ -0,0 +1,29 @@
1
+ import sys
2
+
3
+ from cucu import run_steps, step
4
+
5
+
6
+ @step("I skip this scenario if not on mac")
7
+ def skip_scenario_if_on_mac(ctx):
8
+ if "darwin" not in sys.platform:
9
+ ctx.scenario.skip(reason="skipping scenario since we're not on a mac")
10
+
11
+
12
+ @step("I skip this scenario if not on linux")
13
+ def skip_scenario_if_on_linuxc(ctx):
14
+ if "linux" not in sys.platform:
15
+ ctx.scenario.skip(
16
+ reason="skipping scenario since we're not on a linux"
17
+ )
18
+
19
+
20
+ @step("I run the following steps if on mac")
21
+ def run_steps_if_on_mac(ctx):
22
+ if "darwin" in sys.platform:
23
+ run_steps(ctx, ctx.text)
24
+
25
+
26
+ @step("I run the following steps if on linux")
27
+ def run_steps_if_on_linux(ctx):
28
+ if "linux" in sys.platform:
29
+ run_steps(ctx, ctx.text)