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
cucu/lint/linter.py ADDED
@@ -0,0 +1,397 @@
1
+ import glob
2
+ import importlib
3
+ import os
4
+ import re
5
+
6
+ import yaml
7
+
8
+ from cucu import logger
9
+ from cucu.cli.steps import load_cucu_steps
10
+ from cucu.config import CONFIG
11
+
12
+
13
+ def load_lint_rules(rules, filepath):
14
+ """
15
+ load all of the lint rules from the filepath provided
16
+
17
+ Returns:
18
+ hashmap of of lint rules
19
+ """
20
+ filepath = os.path.abspath(filepath)
21
+
22
+ if os.path.isdir(filepath):
23
+ basepath = os.path.join(filepath, "**/*.yaml")
24
+ lint_rule_filepaths = glob.iglob(basepath, recursive=True)
25
+
26
+ else:
27
+ lint_rule_filepaths = [filepath]
28
+
29
+ for lint_rule_filepath in lint_rule_filepaths:
30
+ logger.debug(f"loading lint rules from {lint_rule_filepath}")
31
+
32
+ with open(lint_rule_filepath, "r", encoding="utf8") as _input:
33
+ rules_loaded = yaml.safe_load(_input.read())
34
+
35
+ for rule_name, rule in rules_loaded.items():
36
+ if rule_name in rules:
37
+ raise RuntimeError(
38
+ f"found duplicate rule names {rule_name}, please correct one of the locations."
39
+ )
40
+
41
+ rules[rule_name] = rule
42
+
43
+ return rules
44
+
45
+
46
+ def parse_matcher(name, rule_name, rule, line, state):
47
+ """
48
+ parses the "matcher" from the rule provided and then returns the tuple:
49
+ (matched, extra_matcher_message) where matched is a boolean indicating that
50
+ the rule name matcher name and rule matched on the line provided and the
51
+ extra_matcher_message is used when reporting the linting failure upstream.
52
+
53
+ name(string): name of the line to match on, ie current_line, previous_line,
54
+ next_line are currently supported
55
+ rule_name(string): the actual name of the rule from the rules file, such as:
56
+ when_keyword_indented_correctly
57
+ rule(dict): the rule dictionary object which contains the matcher, fix, etc.
58
+ line(string): actual line to parse the matcher against
59
+ state(dict): state object passed in which contains a few things such as:
60
+ current_feature_filepath, current_feature_name, etc.
61
+
62
+ returns: a tuple where the first element is True if the matcher matches the
63
+ specified line. False if the matcher simply doesn't apply to this
64
+ line. The second part of the tuple is an string used to augment
65
+ the lint violation if the remainder of the matchers in a rule
66
+ matched their specific line (current_line, previous_line, etc).
67
+ """
68
+ if name not in rule:
69
+ # when the name provided isn't in the rule then we simply have a no-op
70
+ # where this specific matcher name wasn't even used.
71
+ return (True, "")
72
+
73
+ if "match" in rule[name]:
74
+ if line is None:
75
+ return (False, "")
76
+
77
+ match = re.match(rule[name]["match"], line)
78
+
79
+ if match is None:
80
+ # no match on the specified rule means there's not violation to
81
+ # report
82
+ return (False, "")
83
+
84
+ cwd = f"{os.getcwd()}/"
85
+
86
+ # unique across all feature files
87
+ if "unique_per_all_features" in rule[name]:
88
+ value = match.groups()[0]
89
+ feature_filepath = state["current_feature_filepath"]
90
+ # make the path relative to the current working directory
91
+ feature_filepath = feature_filepath.replace(cwd, "")
92
+
93
+ if rule_name not in state["unique_per_all_features"]:
94
+ state["unique_per_all_features"][rule_name] = {}
95
+
96
+ if value in state["unique_per_all_features"][rule_name]:
97
+ # we have another feature which already has this value in use.
98
+ other_filepath = state["unique_per_all_features"][rule_name][
99
+ value
100
+ ]
101
+ # make the path relative to the current working directory
102
+ other_filepath = other_filepath.replace(cwd, "")
103
+
104
+ if other_filepath != feature_filepath:
105
+ return (
106
+ True,
107
+ f', "{value}" also used in "{other_filepath}"',
108
+ )
109
+
110
+ state["unique_per_all_features"][rule_name][value] = (
111
+ feature_filepath
112
+ )
113
+ return (False, "")
114
+
115
+ # unique across all scenarios across all features
116
+ if "unique_per_all_scenarios" in rule[name]:
117
+ value = match.groups()[0]
118
+ feature_filepath = state["current_feature_filepath"]
119
+ # make the path relative to the current working directory
120
+ feature_filepath = feature_filepath.replace(cwd, "")
121
+ scenario_name = state["current_scenario_name"]
122
+
123
+ if rule_name not in state["unique_per_all_scenarios"]:
124
+ state["unique_per_all_scenarios"][rule_name] = {}
125
+
126
+ if value in state["unique_per_all_scenarios"][rule_name]:
127
+ # we have another scenario which already has this value in use.
128
+ other_file_path, other_line_number, other_scenario_name = (
129
+ state["unique_per_all_scenarios"][rule_name][value]
130
+ )
131
+
132
+ return (
133
+ True,
134
+ f', "{value}" also used in "{other_file_path}:{other_line_number}" Scenario: "{other_scenario_name}"',
135
+ )
136
+
137
+ state["unique_per_all_scenarios"][rule_name][value] = [
138
+ feature_filepath,
139
+ state["current_line_number"],
140
+ scenario_name,
141
+ ]
142
+
143
+ return (False, "")
144
+
145
+ return (True, "")
146
+
147
+ raise RuntimeError(f"unsupported matcher for {name}")
148
+
149
+
150
+ def lint_line(state, rules, steps, line_number, lines, filepath):
151
+ """ """
152
+ if line_number >= 1:
153
+ previous_line = lines[line_number - 1]
154
+
155
+ else:
156
+ previous_line = None
157
+
158
+ current_line = lines[line_number]
159
+
160
+ if line_number + 1 < len(lines):
161
+ next_line = lines[line_number + 1]
162
+ else:
163
+ next_line = None
164
+
165
+ logger.debug(f'linting line "{current_line}"')
166
+
167
+ violations = []
168
+ for rule_name in rules.keys():
169
+ logger.debug(f' * checking against rule "{rule_name}"')
170
+ rule = rules[rule_name]
171
+
172
+ # skip paths that match the exclude regex
173
+ if "exclude" in rule and re.match(rule["exclude"], filepath):
174
+ continue
175
+
176
+ (current_matcher, current_message) = parse_matcher(
177
+ "current_line",
178
+ rule_name,
179
+ rule,
180
+ current_line,
181
+ state,
182
+ )
183
+ (previous_matcher, previous_message) = parse_matcher(
184
+ "previous_line",
185
+ rule_name,
186
+ rule,
187
+ previous_line,
188
+ state,
189
+ )
190
+ (next_matcher, next_message) = parse_matcher(
191
+ "next_line",
192
+ rule_name,
193
+ rule,
194
+ next_line,
195
+ state,
196
+ )
197
+
198
+ logger.debug(
199
+ f"previous matcher {previous_matcher} current matcher {current_matcher} next matcher {next_matcher}"
200
+ )
201
+
202
+ if current_matcher and previous_matcher and next_matcher:
203
+ type = rule["type"][0].upper()
204
+ message = rule["message"]
205
+
206
+ if "fix" in rule:
207
+ fix = rule["fix"]
208
+ else:
209
+ fix = None
210
+
211
+ violations.append(
212
+ {
213
+ "location": {
214
+ "filepath": os.path.relpath(filepath),
215
+ "line": line_number,
216
+ },
217
+ "type": type,
218
+ "message": f"{message}{previous_message}{current_message}{next_message}",
219
+ "fix": fix,
220
+ }
221
+ )
222
+
223
+ # find any undefined steps and mark them as an unfixable violation
224
+ current_line = current_line.strip()
225
+ undefined_steps = [
226
+ {
227
+ "location": {
228
+ "filepath": os.path.relpath(filepath),
229
+ "line": line_number,
230
+ },
231
+ "type": "error",
232
+ "message": f'undefined step "{step_name}"',
233
+ "fix": None,
234
+ }
235
+ for step_name in steps
236
+ # step with no location/type/etc is an undefined step
237
+ if steps[step_name] is None and current_line.find(step_name) != -1
238
+ ]
239
+ violations.extend(undefined_steps)
240
+
241
+ return violations
242
+
243
+
244
+ def fix(violations):
245
+ """
246
+ fix the violations found in a set of violations relating to a single
247
+ feature file.
248
+ """
249
+ if not violations:
250
+ return
251
+
252
+ deletions = []
253
+ filepath = violations[0]["location"]["filepath"]
254
+ lines = open(filepath, "r").read().split("\n")
255
+
256
+ for violation in violations:
257
+ if violation["fix"] is None:
258
+ violation["fixed"] = False
259
+
260
+ else:
261
+ line_number = violation["location"]["line"]
262
+ line_to_fix = lines[line_number]
263
+
264
+ if "delete" in violation["fix"]:
265
+ # store the deletions to do at the end
266
+ deletions.append(violation)
267
+
268
+ elif "match" in violation["fix"]:
269
+ match = violation["fix"]["match"]
270
+ replace = violation["fix"]["replace"]
271
+ fixed_line = re.sub(match, replace, line_to_fix)
272
+ lines[line_number] = fixed_line
273
+ violation["fixed"] = True
274
+
275
+ else:
276
+ raise RuntimeError(f"unknown fix type in {violation}")
277
+
278
+ # sort the deletions from bottom to the top of the file and then perform
279
+ # the deletions
280
+ deletions.sort(key=lambda x: x["location"]["line"], reverse=True)
281
+ for violation in deletions:
282
+ del lines[violation["location"]["line"]]
283
+ violation["fixed"] = True
284
+
285
+ with open(filepath, "w") as output:
286
+ output.write("\n".join(lines))
287
+
288
+ return violations
289
+
290
+
291
+ def load_builtin_lint_rules(rules):
292
+ """
293
+ load internal builtin lint rules and used primarily for unit testing
294
+ """
295
+ cucu_path = os.path.dirname(importlib.util.find_spec("cucu").origin)
296
+ lint_rules_path = os.path.join(cucu_path, "lint", "rules")
297
+ load_lint_rules(rules, lint_rules_path)
298
+
299
+
300
+ def lint(filepath):
301
+ """
302
+ lint the filepath provided which could be a base directory where many
303
+ feature files exists and we must traverse recursively or a specific file.
304
+
305
+ Params:
306
+ filepath(string): path to a directory or feature file to lint
307
+
308
+ Returns:
309
+ a generator of violations per file, so each value yielded to the
310
+ generator is a list of violations within the same file
311
+ """
312
+ rules = {}
313
+
314
+ logger.debug(f"linting {filepath}")
315
+
316
+ # load the base lint rules
317
+ load_builtin_lint_rules(rules)
318
+
319
+ if CONFIG["CUCU_LINT_RULES_PATH"]:
320
+ # load any other rules paths linked via CUCU_LINT_RULES_PATH variable
321
+ lint_rule_paths = CONFIG["CUCU_LINT_RULES_PATH"].split(",")
322
+
323
+ for lint_rule_path in lint_rule_paths:
324
+ logger.debug(f"loading custom rules from: {lint_rule_path}")
325
+ load_lint_rules(rules, lint_rule_path)
326
+
327
+ steps, steps_error = load_cucu_steps(filepath=filepath)
328
+
329
+ # state object used to carry state from the top level linting function down
330
+ # to the functions handling the lint rules and reporting on lint failures
331
+ state = {
332
+ "unique_per_all_features": {},
333
+ "unique_per_all_scenarios": {},
334
+ }
335
+
336
+ if steps_error:
337
+ yield [
338
+ {
339
+ "type": "steps_error",
340
+ "message": steps_error,
341
+ }
342
+ ]
343
+
344
+ filepath = os.path.abspath(filepath)
345
+
346
+ if os.path.isdir(filepath):
347
+ basepath = os.path.join(filepath, "**/*.feature")
348
+ # XXX: for now sorted by name... we could expose some options for other
349
+ # sorting orders if it makes sense
350
+ feature_filepaths = sorted(glob.iglob(basepath, recursive=True))
351
+
352
+ else:
353
+ feature_filepaths = [filepath]
354
+
355
+ for feature_filepath in feature_filepaths:
356
+ state["current_feature_filepath"] = feature_filepath
357
+
358
+ lines = open(feature_filepath).read().split("\n")
359
+ line_number = 0
360
+
361
+ violations = []
362
+ in_docstring = {
363
+ '"""': False,
364
+ "'''": False,
365
+ }
366
+
367
+ for line in lines:
368
+ state["current_line_number"] = line_number
369
+
370
+ feature_match = re.match(".*Feature: (.*)", line)
371
+ if feature_match is not None:
372
+ state["current_feature_name"] = feature_match.group(1)
373
+
374
+ scenario_match = re.match(" Scenario: (.*)", line)
375
+
376
+ if scenario_match is not None:
377
+ state["current_scenario_name"] = scenario_match.group(1)
378
+ else:
379
+ state["current_scenario_name"] = ""
380
+
381
+ # maintain state of if we're inside a docstring and if we are then
382
+ # do not apply any linting rules as its a freeform space for text
383
+ if line.strip() == '"""':
384
+ in_docstring['"""'] = not in_docstring['"""']
385
+
386
+ if line.strip() == "'''":
387
+ in_docstring["'''"] = not in_docstring["'''"]
388
+
389
+ if not (in_docstring['"""'] or in_docstring["'''"]):
390
+ for violation in lint_line(
391
+ state, rules, steps, line_number, lines, feature_filepath
392
+ ):
393
+ violations.append(violation)
394
+
395
+ line_number += 1
396
+
397
+ yield violations
@@ -0,0 +1,125 @@
1
+ # built in cucu linting rules
2
+ ---
3
+
4
+ feature_name_must_not_contain_special_characters:
5
+ message: "feature name must not contain the characters '/\\:?'"
6
+ type: error
7
+ current_line:
8
+ match: '^\s*Feature: .*[/\:?].*'
9
+
10
+ scenario_name_must_not_contain_special_characters:
11
+ message: "scenario name must not contain the characters '/\\:?'"
12
+ type: error
13
+ current_line:
14
+ match: '^\s*Scenario: .*[/\:?].*'
15
+
16
+ feature_name_must_be_unique:
17
+ message: feature name must be unique
18
+ type: error
19
+ current_line:
20
+ match: '^\s*Feature: (.*)'
21
+ unique_per_all_features: true
22
+
23
+ scenario_name_must_be_unique:
24
+ message: scenario name must be unique
25
+ type: error
26
+ current_line:
27
+ match: '^\s*Scenario: (.*)'
28
+ unique_per_all_scenarios: true
29
+
30
+ feature_name_on_first_line:
31
+ message: feature name should not have any indentation
32
+ type: warning
33
+ current_line:
34
+ match: '^\s+Feature: .*'
35
+ fix:
36
+ match: '^\s*'
37
+ replace: ''
38
+
39
+ scenario_tags_with_appropriate_indentation:
40
+ message: scenario tags should be indented with 2 spaces
41
+ type: warning
42
+ # any tag line that has 0,1 or 3 or more spaces is a violation
43
+ current_line:
44
+ match: '^(\s{0,1}|\s{3,})@.*'
45
+ next_line:
46
+ match: '^\s*Scenario: (.*)'
47
+ fix:
48
+ match: '^\s*'
49
+ replace: ' '
50
+
51
+ feature_tags_with_appropriate_indentation:
52
+ message: feature tags should not be indented
53
+ type: warning
54
+ current_line:
55
+ match: '^(\s+)@.*'
56
+ next_line:
57
+ match: '^\s*Feature: (.*)'
58
+ fix:
59
+ match: '^\s*'
60
+ replace: ''
61
+
62
+ scenario_name_with_appropriate_indentation:
63
+ message: scenario name should be indented with 2 spaces
64
+ type: warning
65
+ # any scenario line that has 0,1 or 3 or more spaces is a violation
66
+ current_line:
67
+ match: '^(\s{0,1}|\s{3,})Scenario: .*'
68
+ fix:
69
+ match: '^\s*'
70
+ replace: ' '
71
+
72
+ given_keyword_indented_correctly:
73
+ message: given keyword should be indented with 4 spaces
74
+ type: warning
75
+ current_line:
76
+ match: '^(\s{0,3}|\s{5,})Given .*'
77
+ fix:
78
+ match: '^\s*'
79
+ replace: ' '
80
+
81
+ when_keyword_indented_correctly:
82
+ message: when keyword should be indented with 5 spaces
83
+ type: warning
84
+ current_line:
85
+ match: '^(\s{0,4}|\s{6,})When .*'
86
+ fix:
87
+ match: '^\s*'
88
+ replace: ' '
89
+
90
+ then_keyword_indented_correctly:
91
+ message: then keyword should be indented with 5 spaces
92
+ type: warning
93
+ current_line:
94
+ match: '^(\s{0,4}|\s{6,})Then .*'
95
+ fix:
96
+ match: '^\s*'
97
+ replace: ' '
98
+
99
+ and_keyword_indented_correctly:
100
+ message: and keyword should be indented with 6 spaces
101
+ type: warning
102
+ current_line:
103
+ match: '^(\s{0,5}|\s{7,})And .*'
104
+ fix:
105
+ match: '^\s*'
106
+ replace: ' '
107
+
108
+ line_with_extraneous_whitespace:
109
+ message: line has extraneous whitespace at the end
110
+ type: warning
111
+ current_line:
112
+ match: '^.*[ \t]+$'
113
+ fix:
114
+ match: '[ \t]+$'
115
+ replace: ''
116
+
117
+ too_many_blank_lines:
118
+ message: too many blank lines
119
+ type: warning
120
+ previous_line:
121
+ match: '^\s*$'
122
+ current_line:
123
+ match: '^\s*$'
124
+ fix:
125
+ delete: true
cucu/logger.py ADDED
@@ -0,0 +1,113 @@
1
+ import logging
2
+ import sys
3
+ from functools import wraps
4
+
5
+ from cucu.config import CONFIG
6
+
7
+
8
+ def init_logging(logging_level):
9
+ """
10
+ initialize the cucu logger
11
+ """
12
+ formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
13
+ handler = logging.StreamHandler(sys.stdout)
14
+ handler.setFormatter(formatter)
15
+ handler.setLevel(logging_level)
16
+
17
+ logging.getLogger().addHandler(handler)
18
+ # set the top level logger to DEBUG while the console stream handler is
19
+ # actually set to whatever you passed in using --logging-level which is
20
+ # INFO by default
21
+ logging.getLogger().setLevel(logging.DEBUG)
22
+
23
+ logging.debug("logger initialized")
24
+
25
+ logging.getLogger("parse").setLevel(logging.WARNING)
26
+ logging.getLogger("selenium").setLevel(logging.WARNING)
27
+ logging.getLogger("urllib3").setLevel(logging.WARNING)
28
+
29
+
30
+ def init_debug_logger(output_file):
31
+ """
32
+ initialize a debug logger handler that runs at debug level and pushes
33
+ all of the logs to the output file provided without affecting the logging
34
+ of the main root logger
35
+ """
36
+ # assume that there's only 2 loggers the first one is the console one and
37
+ # the second one if present is a previously set debug logger for the
38
+ # previously executed scenario
39
+ if len(logging.getLogger().handlers) > 1:
40
+ logging.getLogger().removeHandler(logging.getLogger().handlers[1])
41
+
42
+ formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
43
+ handler = logging.StreamHandler(output_file)
44
+ handler.setFormatter(formatter)
45
+ handler.setLevel(logging.DEBUG)
46
+ logging.getLogger().addHandler(handler)
47
+
48
+
49
+ @wraps(logging.log)
50
+ def log(*args, **kwargs):
51
+ console_handler = logging.getLogger().handlers[0]
52
+ logging_level = console_handler.level
53
+
54
+ msg_level = args[0]
55
+ if logging_level <= msg_level:
56
+ CONFIG["__CUCU_WROTE_TO_OUTPUT"] = True
57
+
58
+ logging.getLogger().log(*args, **kwargs)
59
+
60
+
61
+ @wraps(logging.debug)
62
+ def debug(*args, **kwargs):
63
+ console_handler = logging.getLogger().handlers[0]
64
+ logging_level = console_handler.level
65
+
66
+ if logging_level <= logging.DEBUG:
67
+ CONFIG["__CUCU_WROTE_TO_OUTPUT"] = True
68
+
69
+ logging.getLogger().debug(*args, **kwargs)
70
+
71
+
72
+ @wraps(logging.info)
73
+ def info(*args, **kwargs):
74
+ console_handler = logging.getLogger().handlers[0]
75
+ logging_level = console_handler.level
76
+
77
+ if logging_level <= logging.INFO:
78
+ CONFIG["__CUCU_WROTE_TO_OUTPUT"] = True
79
+
80
+ logging.info(*args, **kwargs)
81
+
82
+
83
+ @wraps(logging.warn)
84
+ def warn(*args, **kwargs):
85
+ console_handler = logging.getLogger().handlers[0]
86
+ logging_level = console_handler.level
87
+
88
+ if logging_level <= logging.WARN:
89
+ CONFIG["__CUCU_WROTE_TO_OUTPUT"] = True
90
+
91
+ logging.getLogger().warning(*args, **kwargs)
92
+
93
+
94
+ @wraps(logging.error)
95
+ def error(*args, **kwargs):
96
+ console_handler = logging.getLogger().handlers[0]
97
+ logging_level = console_handler.level
98
+
99
+ if logging_level <= logging.ERROR:
100
+ CONFIG["__CUCU_WROTE_TO_OUTPUT"] = True
101
+
102
+ logging.getLogger().error(*args, **kwargs)
103
+
104
+
105
+ @wraps(logging.exception)
106
+ def exception(*args, **kwargs):
107
+ console_handler = logging.getLogger().handlers[0]
108
+ logging_level = console_handler.level
109
+
110
+ if logging_level <= logging.ERROR:
111
+ CONFIG["__CUCU_WROTE_TO_OUTPUT"] = True
112
+
113
+ logging.getLogger().exception(*args, **kwargs)
File without changes
cucu/matcher/core.py ADDED
@@ -0,0 +1,30 @@
1
+ #
2
+ # cucu's matching sub system which allows for a pluggable matching architecture
3
+ #
4
+
5
+ # WIP: thinking of how to generalize the idea of finding elements on the
6
+ # currently opened browser while also being able to handle opening
7
+ # browsers through selenium, cypress or whatever.
8
+
9
+
10
+ class ElementMatcher:
11
+ def __init__(self):
12
+ pass
13
+
14
+ def find_button(self, execute_script, name, index: 0):
15
+ """
16
+ return the button labeled by the name provided and use the index to
17
+ pick a specific one if there are duplicates.
18
+
19
+ arguments:
20
+ execute_script - function to execute javascript in the currently
21
+ running browsers javascript console.
22
+ name - name of the button we want to find on screen.
23
+ index - 0-base index of which element to return if there
24
+ are duplicates. default: 0
25
+
26
+ return:
27
+ return the Element found
28
+
29
+ """
30
+ raise RuntimeError("implement me")