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,437 @@
1
+ import pkgutil
2
+ import re
3
+ from io import StringIO
4
+
5
+ from selenium.webdriver.common.by import By
6
+
7
+ from cucu import (
8
+ config,
9
+ format_gherkin_table,
10
+ fuzzy,
11
+ helpers,
12
+ logger,
13
+ retry,
14
+ step,
15
+ )
16
+ from cucu.browser.frames import run_in_all_frames
17
+
18
+
19
+ def find_tables(ctx):
20
+ """
21
+ find all the tables currently present on the page
22
+
23
+ parameters:
24
+ ctx(object): behave context object used to share data between steps
25
+
26
+ returns:
27
+ an array of arrays containing the HTML tables currently displayed
28
+ """
29
+ ctx.check_browser_initialized()
30
+ tables_lib = pkgutil.get_data("cucu", "steps/tables.js")
31
+ tables_lib = tables_lib.decode("utf8")
32
+
33
+ def search_for_tables():
34
+ ctx.browser.execute(tables_lib)
35
+ return ctx.browser.execute("return findAllTables();")
36
+
37
+ return run_in_all_frames(ctx.browser, search_for_tables)
38
+
39
+
40
+ def behave_table_to_array(table):
41
+ """
42
+ given a behave.model.Table object convert it to an array of rows
43
+
44
+ parameters:
45
+ table(behave.model.Table): the behave table to convert into an array
46
+
47
+ returns:
48
+ array of rows representing the behave table provided.
49
+ """
50
+ result = [table.headings]
51
+
52
+ for row in table.rows:
53
+ values = []
54
+ for value in row:
55
+ values.append(value)
56
+ result.append(values)
57
+
58
+ return result
59
+
60
+
61
+ def check_table_equals_table(table, expected_table):
62
+ """
63
+ check that table is equal to expected_table
64
+ """
65
+ return table == expected_table
66
+
67
+
68
+ def check_table_matches_table(table, expected_table):
69
+ """
70
+ check if table matches the regex patterns in expected table
71
+ """
72
+
73
+ if len(table) == len(expected_table):
74
+ table_matched = True
75
+
76
+ for expected_row, row in zip(expected_table, table):
77
+ for expected_value, value in zip(expected_row, row):
78
+ if not re.match(expected_value, value):
79
+ table_matched = False
80
+
81
+ if table_matched:
82
+ return True
83
+
84
+ return False
85
+
86
+
87
+ def check_table_contains_table(table, expected_table):
88
+ """
89
+ check that table contains the rows in expected_table
90
+ """
91
+ return all(row in table for row in expected_table)
92
+
93
+
94
+ def check_table_contains_matching_rows_in_table(table, expected_table):
95
+ """
96
+ check that table contains the matching rows in expected_table
97
+ """
98
+ table_matched = True
99
+
100
+ for expected_row in expected_table:
101
+ for row in table:
102
+ found_row = True
103
+ for expected_value, value in zip(expected_row, row):
104
+ if not re.match(expected_value, value):
105
+ found_row = False
106
+ if found_row:
107
+ break
108
+
109
+ if not found_row:
110
+ table_matched = False
111
+ break
112
+
113
+ if table_matched:
114
+ return True
115
+
116
+ return False
117
+
118
+
119
+ def report_unable_to_find_table(expected_table, found_tables):
120
+ stream = StringIO()
121
+ stream.write("\n")
122
+ for index, table in enumerate(found_tables):
123
+ print_index = helpers.nth_to_ordinal(index) or '"1st" '
124
+ stream.write(
125
+ f"{print_index}table:\n{format_gherkin_table(table, [], ' ')}\n"
126
+ )
127
+
128
+ stream.seek(0)
129
+ raise RuntimeError(
130
+ f"unable to find desired table\nexpected:\n{format_gherkin_table(expected_table, [], ' ')}\n\nfound:{stream.read()}"
131
+ )
132
+
133
+
134
+ def report_found_undesired_table(unexpected_tables, found_tables):
135
+ stream = StringIO()
136
+ stream.write("\n")
137
+ for index, table in enumerate(found_tables):
138
+ print_index = helpers.nth_to_ordinal(index) or '"1st" '
139
+ stream.write(
140
+ f"{print_index}table:\n{format_gherkin_table(table, [], ' ')}\n"
141
+ )
142
+
143
+ stream.seek(0)
144
+ error_message = ""
145
+ for table in unexpected_tables:
146
+ error_message += (
147
+ f"found undesired table\n\nundesired table:\n{format_gherkin_table(table, [], ' ')}\n\n"
148
+ f"all tables found:{stream.read()}\n"
149
+ )
150
+ raise RuntimeError(error_message)
151
+
152
+
153
+ def find_table(ctx, assert_func, nth=None):
154
+ """
155
+ validate we can find the table passed in the ctx object and assert it
156
+ matches anyone of the tables on the current web page. If `nth` is set to
157
+ something then we only check against the nth table of the available tables.
158
+
159
+ paramters:
160
+ ctx(object): behave context object
161
+ assert_func(function): function used to assert two tables "match"
162
+ nth(int): when set to an int specifies the exact table within the list
163
+ of available tables to match against.
164
+
165
+ raises:
166
+ RuntimeError when the desired table was not found
167
+ """
168
+ expected = behave_table_to_array(ctx.table)
169
+ found_tables = find_tables(ctx)
170
+
171
+ if nth is not None:
172
+ if assert_func(found_tables[nth], expected):
173
+ return
174
+
175
+ else:
176
+ for table in found_tables:
177
+ if assert_func(table, expected):
178
+ return
179
+
180
+ report_unable_to_find_table(expected, found_tables)
181
+
182
+
183
+ def do_not_find_table(ctx, assert_func, nth=None):
184
+ """
185
+ validate we can not find the table passed in the ctx object and assert it
186
+ matches anyone of the tables on the current web page. If `nth` is set to
187
+ something then we only check against the nth table of the available tables.
188
+
189
+ paramters:
190
+ ctx(object): behave context object
191
+ assert_func(function): function used to assert two tables "match"
192
+ nth(int): when set to an int specifies the exact table within the list
193
+ of available tables to match against.
194
+
195
+ raises:
196
+ RuntimeError when the desired table was not found
197
+ """
198
+ expected = behave_table_to_array(ctx.table)
199
+ tables = find_tables(ctx)
200
+ matching_tables = []
201
+
202
+ if nth is not None:
203
+ if assert_func(tables[nth], expected):
204
+ matching_tables.append(tables[nth])
205
+
206
+ else:
207
+ for table in tables:
208
+ if assert_func(table, expected):
209
+ matching_tables.append(table)
210
+
211
+ # If none of the tables match the pattern, then return
212
+ if len(matching_tables) == 0:
213
+ return
214
+
215
+ report_found_undesired_table(matching_tables, tables)
216
+
217
+
218
+ for thing, check_func in {
219
+ "is": check_table_equals_table,
220
+ "matches": check_table_matches_table,
221
+ "contains": check_table_contains_table,
222
+ "contains rows matching": check_table_contains_matching_rows_in_table,
223
+ }.items():
224
+
225
+ @step(f"I should see a table that {thing} the following")
226
+ def should_see_the_table(ctx, check_func=check_func):
227
+ find_table(ctx, check_func)
228
+
229
+ @step(f"I should not see a table that {thing} the following")
230
+ def should_not_see_the_table(ctx, check_func=check_func):
231
+ do_not_find_table(ctx, check_func)
232
+
233
+ @step(f"I wait to see a table that {thing} the following")
234
+ def wait_to_see_the_table(ctx, check_func=check_func):
235
+ retry(find_table)(ctx, check_func)
236
+
237
+ @step(f"I wait to not see a table that {thing} the following")
238
+ def wait_to_not_see_the_table(ctx, check_func=check_func):
239
+ retry(do_not_find_table)(ctx, check_func)
240
+
241
+ @step(
242
+ f'I wait up to "{{seconds}}" seconds to see a table that {thing} the following'
243
+ )
244
+ def wait_up_to_seconds_to_see_the_table(
245
+ ctx, seconds, check_func=check_func
246
+ ):
247
+ seconds = float(seconds)
248
+ retry(find_table, wait_up_to_s=seconds)(ctx, check_func)
249
+
250
+ @step(
251
+ f'I wait up to "{{seconds}}" seconds to not see a table that {thing} the following'
252
+ )
253
+ def wait_up_to_seconds_to_not_see_the_table(
254
+ ctx, seconds, check_func=check_func
255
+ ):
256
+ seconds = float(seconds)
257
+ retry(do_not_find_table, wait_up_to_s=seconds)(ctx, check_func)
258
+
259
+ @step(f'I should see the "{{nth}}" table {thing} the following')
260
+ def should_see_the_nth_table(ctx, nth, check_func=check_func):
261
+ find_table(ctx, check_func, nth=nth)
262
+
263
+ @step(f'I wait to see the "{{nth}}" table {thing} the following')
264
+ def wait_to_see_the_nth_table(ctx, nth, check_func=check_func):
265
+ retry(find_table)(ctx, check_func, nth=nth)
266
+
267
+ @step(
268
+ f'I wait up to "{{seconds}}" seconds to see the "{{nth}}" table {thing} the following'
269
+ )
270
+ def wait_up_to_seconds_to_see_the_nth_table(
271
+ ctx, seconds, nth, check_func=check_func
272
+ ):
273
+ seconds = float(seconds)
274
+ retry(find_table, wait_up_to_s=seconds)(ctx, check_func, nth=nth)
275
+
276
+
277
+ def find_table_header(ctx, name, index=0):
278
+ """
279
+ find a table header with the provided name
280
+ """
281
+ ctx.check_browser_initialized()
282
+ return fuzzy.find(ctx.browser, name, ["th"], index=index)
283
+
284
+
285
+ def click_table_header(ctx, header):
286
+ """
287
+ internal method used to simply click a table header element
288
+ """
289
+ ctx.check_browser_initialized()
290
+ ctx.browser.click(header)
291
+
292
+
293
+ helpers.define_action_on_thing_with_name_steps(
294
+ "table header",
295
+ "click",
296
+ find_table_header,
297
+ click_table_header,
298
+ with_nth=True,
299
+ )
300
+
301
+
302
+ def get_table_cell_value(ctx, table, row, column, variable_name):
303
+ tables = find_tables(ctx)
304
+
305
+ try:
306
+ cell_value = tables[table][row][column]
307
+ except IndexError:
308
+ raise RuntimeError(
309
+ f"Cannot find table:{table+1},row:{row+1},column:{column+1}. Please check your table data."
310
+ )
311
+ config.CONFIG[variable_name] = cell_value
312
+
313
+
314
+ @step(
315
+ 'I save "{table:nth}" table, "{row:nth}" row, "{column:nth}" column value to a variable "{variable_name}"'
316
+ )
317
+ def step_get_table_cell_value(ctx, table, row, column, variable_name):
318
+ get_table_cell_value(ctx, table, row, column, variable_name)
319
+
320
+
321
+ @step(
322
+ 'I wait to save "{table:nth}" table, "{row:nth}" row, "{column:nth}" column value to a variable "{variable_name}"'
323
+ )
324
+ def wait_to_get_table_cell_value(ctx, table, row, column, variable_name):
325
+ retry(get_table_cell_value)(ctx, table, row, column, variable_name)
326
+
327
+
328
+ def find_table_element(ctx, nth=1):
329
+ """
330
+ Return the nth table as a WebElement
331
+
332
+ parameters:
333
+ ctx(object): behave context object used to share data between steps
334
+ nth(int): specifies the exact table within the list of available tables to match against.
335
+ Defaults to 1st table.
336
+
337
+ returns:
338
+ A selenium WebElement associated with the table that was specified
339
+ """
340
+ ctx.check_browser_initialized()
341
+
342
+ try:
343
+ return ctx.browser.css_find_elements("table")[nth]
344
+ except IndexError:
345
+ raise RuntimeError(
346
+ f"Cannot find table:{nth+1}. Please check your table data."
347
+ )
348
+
349
+
350
+ def click_table_cell(ctx, row, column, table):
351
+ """
352
+ Clicks the cell corresponding to the given row and column
353
+
354
+ parameters:
355
+ ctx(object): behave context object used to share data between steps
356
+ row(int): the row of the table to click
357
+ column(int): the column of the table to click
358
+ table(int): specifies the exact table within the list of available tables to match against.
359
+ """
360
+ table_element = find_table_element(ctx, table)
361
+
362
+ try:
363
+ row = table_element.find_elements(By.CSS_SELECTOR, "tbody tr")[row]
364
+ cell = row.find_elements(By.CSS_SELECTOR, "td")[column]
365
+ except IndexError:
366
+ raise RuntimeError(
367
+ f"Cannot find table:{table+1},row:{row+1},column:{column+1}. Please check your table data."
368
+ )
369
+ ctx.browser.click(cell)
370
+
371
+
372
+ @step('I wait to click the "{row:nth}" row in the "{table:nth}" table')
373
+ def wait_click_table_row(ctx, row, table):
374
+ """
375
+ Add 1 to the row number if the table has a header row.
376
+
377
+ Note: Firefox is unable to click directly on a row <tr> if it has child columns <td>.
378
+ In order to workaround this, the step just clicks the first column <td> of the row <tr>.
379
+ Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1448825
380
+ """
381
+ retry(click_table_cell)(ctx, row, 1, table)
382
+
383
+
384
+ @step(
385
+ 'I wait to click the cell corresponding to the "{row:nth}" row and "{column:nth}" column in the "{table:nth}" table'
386
+ )
387
+ def wait_click_table_cell(ctx, row, column, table):
388
+ """
389
+ Add 1 to the row number if the table has a header row.
390
+ """
391
+ retry(click_table_cell)(ctx, row, column, table)
392
+
393
+
394
+ @step(
395
+ 'I wait to click the "{column:nth}" column within a row that contains the text "{match_text}" in the "{table:nth}" table'
396
+ )
397
+ def wait_click_table_cell_matching_text(ctx, column, match_text, table):
398
+ def click_table_cell_matching_text(ctx, column, match_text, table):
399
+ table_element = find_table_element(ctx, table)
400
+
401
+ try:
402
+ row = table_element.find_elements(
403
+ By.XPATH, f'//td[.="{match_text}"]/parent::tr'
404
+ )
405
+ if len(row) > 1:
406
+ logger.warn(
407
+ f'Found {len(row)} rows with matching text "{match_text}", using the first row.'
408
+ )
409
+ cell = row[0].find_elements(By.CSS_SELECTOR, "td")[column]
410
+ except IndexError:
411
+ raise RuntimeError(
412
+ f"Cannot find table:{table+1},column:{column+1},text:{match_text}. Please check your table data."
413
+ )
414
+
415
+ ctx.browser.click(cell)
416
+
417
+ retry(click_table_cell_matching_text)(ctx, column, match_text, table)
418
+
419
+
420
+ @step('I wait to see there are "{row_count}" rows in the "{table:nth}" table')
421
+ def wait_table_row_count(ctx, row_count, table):
422
+ """
423
+ Add 1 to the row number if the table has a header row.
424
+ """
425
+
426
+ def find_table_row_count(ctx, row_count, table):
427
+ table_element = find_table_element(ctx, table)
428
+ table_rows = len(table_element.find_elements(By.CSS_SELECTOR, "tr"))
429
+
430
+ if int(row_count) == table_rows:
431
+ return
432
+ else:
433
+ raise RuntimeError(
434
+ f"Unable to find {row_count} rows in table {table+1}. Please check your table data."
435
+ )
436
+
437
+ retry(find_table_row_count)(ctx, row_count, table)
cucu/steps/tables.js ADDED
@@ -0,0 +1,28 @@
1
+ (function(){
2
+ window.findAllTables = function() {
3
+ var tables = [];
4
+ function tableToJSON(table) {
5
+ var data = [];
6
+ for (var rIndex=0; rIndex < table.rows.length; rIndex++) {
7
+ var row = table.rows[rIndex];
8
+ var values = [];
9
+ for (var vIndex=0; vIndex < row.cells.length; vIndex++) {
10
+ var value = row.cells[vIndex].innerText.trim();
11
+ value = value.replace(/[\r\n\s]+/g, " ");
12
+ values.push(value);
13
+ }
14
+ if (values.length != 0) {
15
+ data.push(values);
16
+ }
17
+ }
18
+ return data;
19
+ }
20
+
21
+ var table_elements = document.querySelectorAll('table');
22
+ for(var index=0; index < table_elements.length; index++) {
23
+ tables.push(tableToJSON(table_elements[index]));
24
+ };
25
+
26
+ return tables;
27
+ };
28
+ })();
@@ -0,0 +1,78 @@
1
+ from cucu import fuzzy, helpers, step
2
+ from cucu.browser.frames import try_in_frames_until_success
3
+ from cucu.steps import step_utils
4
+ from cucu.utils import take_saw_element_screenshot, text_in_current_frame
5
+
6
+
7
+ def find_text(ctx, name, index=0):
8
+ """
9
+ find any element containing the text provide.
10
+
11
+ parameters:
12
+ ctx(object): behave context object used to share data between steps
13
+ name(str): name that identifies the desired radio text on screen
14
+ index(str): the index of the radio text if there are duplicates
15
+
16
+ returns:
17
+ the WebElement that matches the provided arguments or None if none found
18
+ """
19
+ ctx.check_browser_initialized()
20
+ element = fuzzy.find(
21
+ ctx.browser,
22
+ name,
23
+ ["*"],
24
+ index=index,
25
+ direction=fuzzy.Direction.LEFT_TO_RIGHT,
26
+ )
27
+
28
+ take_saw_element_screenshot(ctx, "text", name, index, element)
29
+
30
+ return element
31
+
32
+
33
+ # Also update the line number in the scenario: `User gets the right stacktrace for steps using step helpers` when changing the code below.
34
+ helpers.define_should_see_thing_with_name_steps("text", find_text)
35
+ helpers.define_run_steps_if_I_can_see_element_with_name_steps(
36
+ "text", find_text
37
+ )
38
+
39
+
40
+ @step(
41
+ 'I search for the regex "{regex}" on the current page and save the group "{name}" to the variable "{variable}"'
42
+ )
43
+ def search_for_regex_to_page_and_save(ctx, regex, name, variable):
44
+ ctx.check_browser_initialized()
45
+
46
+ def search_for_regex_in_frame():
47
+ text = text_in_current_frame(ctx.browser)
48
+ step_utils.search_and_save(
49
+ regex=regex, value=text, name=name, variable=variable
50
+ )
51
+
52
+ try_in_frames_until_success(ctx.browser, search_for_regex_in_frame)
53
+
54
+
55
+ @step(
56
+ 'I match the regex "{regex}" on the current page and save the group "{name}" to the variable "{variable}"'
57
+ )
58
+ def match_for_regex_to_page_and_save(ctx, regex, name, variable):
59
+ ctx.check_browser_initialized()
60
+
61
+ def match_for_regex_in_frame():
62
+ text = text_in_current_frame(ctx.browser)
63
+ step_utils.match_and_save(
64
+ regex=regex, value=text, name=name, variable=variable
65
+ )
66
+
67
+ try_in_frames_until_success(ctx.browser, match_for_regex_in_frame)
68
+
69
+
70
+ @step('I should see text matching the regex "{regex}" on the current page')
71
+ def search_for_regex_on_page(ctx, regex):
72
+ ctx.check_browser_initialized()
73
+
74
+ def search_for_regex_in_frame():
75
+ text = text_in_current_frame(ctx.browser)
76
+ step_utils.search(regex=regex, value=text)
77
+
78
+ try_in_frames_until_success(ctx.browser, search_for_regex_in_frame)
@@ -0,0 +1,100 @@
1
+ import re
2
+
3
+ from cucu import config, step
4
+ from cucu.steps import step_utils
5
+
6
+
7
+ @step('I set the variable "{variable}" to "{value}"')
8
+ def set_variable_to(_, variable, value):
9
+ config.CONFIG[variable] = value
10
+
11
+
12
+ @step('I set the variable "{variable}" to the following')
13
+ def set_variable_to_the_following(ctx, variable):
14
+ config.CONFIG[variable] = ctx.text
15
+
16
+
17
+ @step('I should see "{this}" is empty')
18
+ def should_see_is_empty(_, this):
19
+ if this or len(this) != 0:
20
+ raise RuntimeError(f"{this} is not empty")
21
+
22
+
23
+ @step('I should see "{this}" is equal to "{that}"')
24
+ def should_see_is_equal(_, this, that):
25
+ if this != that:
26
+ raise RuntimeError(f"{this} is not equal to {that}")
27
+
28
+
29
+ @step('I should see "{this}" is not equal to "{that}"')
30
+ def should_see_is_not_equal(_, this, that):
31
+ if this == that:
32
+ raise RuntimeError(f"{this} is equal to {that}")
33
+
34
+
35
+ @step('I should see "{this}" contains "{that}"')
36
+ def should_see_it_contains(_, this, that):
37
+ if that not in this:
38
+ raise RuntimeError(f"{this} does not contain {that}")
39
+
40
+
41
+ @step('I should see "{this}" contains the following')
42
+ def should_see_it_contains_the_following(ctx, this):
43
+ if ctx.text not in this:
44
+ raise RuntimeError(f"{this} does not contain {ctx.text}")
45
+
46
+
47
+ @step('I should see "{this}" does not contain "{that}"')
48
+ def should_see_it_doest_not_contain(_, this, that):
49
+ if that in this:
50
+ raise RuntimeError(f"{this} contains {that}")
51
+
52
+
53
+ @step('I should see "{this}" does not contain the following')
54
+ def should_see_it_does_not_contain(ctx, this):
55
+ if ctx.text in this:
56
+ raise RuntimeError(f"{this} contain {ctx.text}")
57
+
58
+
59
+ @step('I should see "{this}" is equal to the following')
60
+ def should_see_is_equal_to_the_following(ctx, this):
61
+ that = ctx.text
62
+
63
+ if this != that:
64
+ raise RuntimeError(f"{this} is not equal to {that}")
65
+
66
+
67
+ @step('I should see "{this}" matches "{that}"')
68
+ def should_see_matches(_, this, that):
69
+ if re.match(that, this) is None:
70
+ raise RuntimeError(f"{this} does not match {that}")
71
+
72
+
73
+ @step('I should see "{this}" matches the following')
74
+ def should_see_matches_the_following(ctx, this):
75
+ that = ctx.text
76
+
77
+ if re.match(that, this) is None:
78
+ raise RuntimeError(f"{this}\ndoes not match:\n{that}")
79
+
80
+
81
+ @step('I should see "{this}" does not match the following')
82
+ def should_does_not_see_matches_the_following(ctx, this):
83
+ that = ctx.text
84
+
85
+ if re.match(that, this) is not None:
86
+ raise RuntimeError(f"{this}\nmatches:\n{that}")
87
+
88
+
89
+ @step(
90
+ 'I search for the regex "{regex}" in "{value}" and save the group "{name}" to the variable "{variable}"'
91
+ )
92
+ def search_and_save(ctx, regex, value, name, variable):
93
+ step_utils.search_and_save(regex, value, name, variable)
94
+
95
+
96
+ @step(
97
+ 'I match the regex "{regex}" in "{value}" and save the group "{name}" to the variable "{variable}"'
98
+ )
99
+ def match_and_save(ctx, regex, value, name, variable):
100
+ step_utils.match_and_save(regex, value, name, variable)
@@ -0,0 +1,40 @@
1
+ from functools import partial
2
+ from http.server import HTTPServer, SimpleHTTPRequestHandler
3
+ from threading import Thread
4
+
5
+ from behave import step
6
+
7
+ from cucu import register_after_this_scenario_hook
8
+ from cucu.config import CONFIG
9
+
10
+
11
+ class QuietHTTPRequestHandler(SimpleHTTPRequestHandler):
12
+ def log_message(self, format, *args):
13
+ return
14
+
15
+
16
+ @step(
17
+ 'I start a webserver at directory "{directory}" and save the port to the variable "{variable}"'
18
+ )
19
+ def run_webserver_for_scenario(ctx, directory, variable):
20
+ """
21
+ start a webserver with the root at the directory provided and save the
22
+ port that the server is listening at to the variable name provided
23
+
24
+ examples:
25
+ Given I start webserver at directory "/some/path" and save the port to the variable "PORT"
26
+ And I open a browser at the url "http://{HOST_ADDRESS}:{PORT}/somefile.html"
27
+ """
28
+ handler = partial(QuietHTTPRequestHandler, directory=directory)
29
+ httpd = HTTPServer(("", 0), handler)
30
+ thread = Thread(target=httpd.serve_forever)
31
+ thread.start()
32
+
33
+ _, port = httpd.server_address
34
+ CONFIG[variable] = str(port)
35
+
36
+ def shutdown_webserver(_):
37
+ httpd.shutdown()
38
+ thread.join()
39
+
40
+ register_after_this_scenario_hook(shutdown_webserver)