cucu 1.2.0__tar.gz → 1.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of cucu might be problematic. Click here for more details.

Files changed (94) hide show
  1. {cucu-1.2.0 → cucu-1.2.2}/CHANGELOG.md +9 -0
  2. {cucu-1.2.0 → cucu-1.2.2}/PKG-INFO +1 -1
  3. {cucu-1.2.0 → cucu-1.2.2}/pyproject.toml +2 -2
  4. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/browser/core.py +1 -1
  5. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/browser/selenium.py +0 -2
  6. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/cli/core.py +54 -0
  7. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/environment.py +17 -2
  8. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/formatter/cucu.py +3 -3
  9. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/formatter/json.py +1 -1
  10. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/reporter/html.py +3 -3
  11. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/button_steps.py +9 -1
  12. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/checkbox_steps.py +13 -1
  13. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/radio_steps.py +13 -1
  14. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/table_steps.py +5 -5
  15. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/utils.py +39 -0
  16. {cucu-1.2.0 → cucu-1.2.2}/.gitignore +0 -0
  17. {cucu-1.2.0 → cucu-1.2.2}/LICENSE +0 -0
  18. {cucu-1.2.0 → cucu-1.2.2}/README.md +0 -0
  19. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/__init__.py +0 -0
  20. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/ansi_parser.py +0 -0
  21. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/behave_tweaks.py +0 -0
  22. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/browser/__init__.py +0 -0
  23. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/browser/frames.py +0 -0
  24. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/browser/selenium_tweaks.py +0 -0
  25. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/cli/__init__.py +0 -0
  26. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/cli/run.py +0 -0
  27. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/cli/steps.py +0 -0
  28. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/cli/thread_dumper.py +0 -0
  29. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/config.py +0 -0
  30. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/edgedriver_autoinstaller/README.md +0 -0
  31. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/edgedriver_autoinstaller/__init__.py +0 -0
  32. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/edgedriver_autoinstaller/utils.py +0 -0
  33. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/external/jquery/jquery-3.5.1.min.js +0 -0
  34. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/formatter/__init__.py +0 -0
  35. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/formatter/junit.py +0 -0
  36. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/fuzzy/__init__.py +0 -0
  37. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/fuzzy/core.py +0 -0
  38. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/fuzzy/fuzzy.js +0 -0
  39. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/helpers.py +0 -0
  40. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/hooks.py +0 -0
  41. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/init_data/.gitignore +0 -0
  42. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/init_data/README.md +0 -0
  43. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/init_data/cucurc.yml +0 -0
  44. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/init_data/data/www/example.html +0 -0
  45. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/init_data/features/cucurc.yml +0 -0
  46. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/init_data/features/environment.py +0 -0
  47. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/init_data/features/example.feature +0 -0
  48. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/init_data/features/lint_rules/sid.yaml +0 -0
  49. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/init_data/features/steps/__init__.py +0 -0
  50. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/init_data/features/steps/my_steps.py +0 -0
  51. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/language_server/__init__.py +0 -0
  52. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/language_server/core.py +0 -0
  53. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/lint/__init__.py +0 -0
  54. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/lint/linter.py +0 -0
  55. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/lint/rules/format.yaml +0 -0
  56. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/logger.py +0 -0
  57. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/matcher/__init__.py +0 -0
  58. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/matcher/core.py +0 -0
  59. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/page_checks.py +0 -0
  60. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/reporter/__init__.py +0 -0
  61. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/reporter/external/bootstrap.min.css +0 -0
  62. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/reporter/external/bootstrap.min.js +0 -0
  63. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/reporter/external/dataTables.bootstrap.min.css +0 -0
  64. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/reporter/external/dataTables.bootstrap.min.js +0 -0
  65. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/reporter/external/jquery-3.5.1.min.js +0 -0
  66. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/reporter/external/jquery.dataTables.min.js +0 -0
  67. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/reporter/external/popper.min.js +0 -0
  68. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/reporter/favicon.png +0 -0
  69. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/reporter/templates/feature.html +0 -0
  70. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/reporter/templates/flat.html +0 -0
  71. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/reporter/templates/index.html +0 -0
  72. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/reporter/templates/layout.html +0 -0
  73. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/reporter/templates/scenario.html +0 -0
  74. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/__init__.py +0 -0
  75. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/base_steps.py +0 -0
  76. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/browser_steps.py +0 -0
  77. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/command_steps.py +0 -0
  78. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/draggable_steps.py +0 -0
  79. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/dropdown_steps.py +0 -0
  80. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/file_input_steps.py +0 -0
  81. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/filesystem_steps.py +0 -0
  82. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/flow_control_steps.py +0 -0
  83. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/image_steps.py +0 -0
  84. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/input_steps.py +0 -0
  85. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/link_steps.py +0 -0
  86. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/menuitem_steps.py +0 -0
  87. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/platform_steps.py +0 -0
  88. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/section_steps.py +0 -0
  89. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/step_utils.py +0 -0
  90. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/tab_steps.py +0 -0
  91. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/tables.js +0 -0
  92. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/text_steps.py +0 -0
  93. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/variable_steps.py +0 -0
  94. {cucu-1.2.0 → cucu-1.2.2}/src/cucu/steps/webserver_steps.py +0 -0
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project closely adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+
9
+ ## 1.2.2
10
+ - Chore - make lint also run ruff format --check
11
+ - Add - tags command to list tags and affected scenario counts
12
+ - Add - click parent label of an input element when the size of the input element is zero
13
+
14
+ ## 1.2.1
15
+ - Add - tab information to html report and cucu debug console log
16
+
8
17
  ## 1.2.0
9
18
  - Add - levels 2-4 to section step
10
19
  - Change - rename comment step to section step
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cucu
3
- Version: 1.2.0
3
+ Version: 1.2.2
4
4
  Summary: Easy BDD web testing
5
5
  Project-URL: Homepage, https://github.com/dominodatalab/cucu/wiki
6
6
  Project-URL: Download, https://pypi.org/project/cucu/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cucu"
3
- version = "1.2.0"
3
+ version = "1.2.2"
4
4
  description = "Easy BDD web testing"
5
5
  readme = "README.md"
6
6
  license = { text = "The Clear BSD License" }
@@ -82,7 +82,7 @@ exclude = [
82
82
  [tool.uv]
83
83
  dev-dependencies = [
84
84
  "pre-commit>=3.8.0",
85
- "pytest~=8.3.5",
85
+ "pytest~=8.4.0",
86
86
  "ruff>=0.6.4",
87
87
  ]
88
88
 
@@ -76,5 +76,5 @@ class Browser:
76
76
  start = time.time()
77
77
  hook(self)
78
78
  logger.debug(
79
- f'executed page check "{name}" in {round(time.time()-start, 3)}s'
79
+ f'executed page check "{name}" in {round(time.time() - start, 3)}s'
80
80
  )
@@ -215,7 +215,6 @@ class Selenium(Browser):
215
215
 
216
216
  if window_handle_index == len(window_handles) - 1:
217
217
  raise RuntimeError("no next browser tab available")
218
-
219
218
  self.driver.switch_to.window(window_handles[window_handle_index + 1])
220
219
 
221
220
  def switch_to_previous_tab(self):
@@ -225,7 +224,6 @@ class Selenium(Browser):
225
224
 
226
225
  if window_handle_index == 0:
227
226
  raise RuntimeError("no previous browser tab available")
228
-
229
227
  self.driver.switch_to.window(window_handles[window_handle_index - 1])
230
228
 
231
229
  def back(self):
@@ -7,6 +7,7 @@ import signal
7
7
  import sys
8
8
  import time
9
9
  import xml.etree.ElementTree as ET
10
+ from collections import Counter
10
11
  from importlib.metadata import version
11
12
  from pathlib import Path
12
13
  from threading import Timer
@@ -14,6 +15,8 @@ from threading import Timer
14
15
  import click
15
16
  import coverage
16
17
  import psutil
18
+ from behave.model_core import FileLocation
19
+ from behave.runner_util import parse_features
17
20
  from click import ClickException
18
21
  from mpire import WorkerPool
19
22
  from tabulate import tabulate
@@ -851,5 +854,56 @@ def debug(browser, url, detach, logging_level):
851
854
  time.sleep(5)
852
855
 
853
856
 
857
+ @main.command()
858
+ @click.option(
859
+ "-l",
860
+ "--logging-level",
861
+ default="INFO",
862
+ help="set logging level to one of debug, warn or info (default)",
863
+ )
864
+ @click.argument(
865
+ "filepath", default="features", type=click.Path(path_type=Path)
866
+ )
867
+ def tags(filepath, logging_level):
868
+ """
869
+ print a table of tags and affected scenario counts
870
+ """
871
+ init_global_hook_variables()
872
+ os.environ["CUCU_LOGGING_LEVEL"] = logging_level.upper()
873
+ logger.init_logging(logging_level.upper())
874
+
875
+ if filepath.is_file():
876
+ feature_files = [filepath]
877
+ else:
878
+ feature_files = list(filepath.rglob("*.feature"))
879
+
880
+ if not filepath.exists() or not feature_files:
881
+ raise ClickException("No feature files found.")
882
+
883
+ file_locations = [
884
+ FileLocation(os.path.abspath(str(f))) for f in feature_files
885
+ ]
886
+ features = parse_features(file_locations)
887
+ tag_scenarios = Counter()
888
+
889
+ for feature in features:
890
+ for scenario in feature.scenarios:
891
+ affecting_tags = set(feature.tags + scenario.tags)
892
+ tag_scenarios.update(affecting_tags)
893
+
894
+ if not tag_scenarios:
895
+ print("No tags found in feature files.")
896
+ return
897
+
898
+ table_data = [["Tag", "Scenarios Affected"]] + [
899
+ [tag_name, str(count)]
900
+ for tag_name, count in sorted(
901
+ tag_scenarios.items(), key=lambda x: x[0].lower()
902
+ )
903
+ ]
904
+
905
+ print(tabulate(table_data, headers="firstrow", tablefmt="fancy_grid"))
906
+
907
+
854
908
  if __name__ == "__main__":
855
909
  main()
@@ -10,7 +10,7 @@ from functools import partial
10
10
  from cucu import config, init_scenario_hook_variables, logger
11
11
  from cucu.config import CONFIG
12
12
  from cucu.page_checks import init_page_checks
13
- from cucu.utils import ellipsize_filename, take_screenshot
13
+ from cucu.utils import ellipsize_filename, get_tab_information, take_screenshot
14
14
 
15
15
  CONFIG.define(
16
16
  "FEATURE_RESULTS_DIR",
@@ -24,7 +24,7 @@ CONFIG.define(
24
24
  )
25
25
  CONFIG.define(
26
26
  "SCENARIO_DOWNLOADS_DIR",
27
- "the browser downloads directory for the currently " "executing scenario",
27
+ "the browser downloads directory for the currently executing scenario",
28
28
  default=None,
29
29
  )
30
30
 
@@ -264,6 +264,21 @@ def after_step(ctx, step):
264
264
  if ctx.browser is not None and ctx.current_step.has_substeps is False:
265
265
  take_screenshot(ctx, step.name, label=f"After {step.name}")
266
266
 
267
+ tab_info = get_tab_information(ctx)
268
+ total_tabs = tab_info["window_count"]
269
+ current_tab = tab_info["current_index"] + 1
270
+ title = tab_info["current_title"]
271
+ url = tab_info["current_url"]
272
+ log_message = (
273
+ f"\ntab({current_tab} of {total_tabs}): {title}\nurl: {url}\n"
274
+ )
275
+ logger.debug(log_message)
276
+
277
+ # Add tab info to step.stdout so it shows up in the HTML report
278
+ step.stdout += (
279
+ f"\ntab({current_tab} of {total_tabs}): {title}\nurl: {url}\n"
280
+ )
281
+
267
282
  # if the step has substeps from using `run_steps` then we already moved
268
283
  # the step index in the run_steps method and shouldn't do it here
269
284
  if not step.has_substeps:
@@ -66,7 +66,7 @@ class CucuFormatter(Formatter):
66
66
  # -- IMPLEMENT-INTERFACE FOR: Formatter
67
67
  def feature(self, feature):
68
68
  self.write_tags(feature.tags, for_feature=True)
69
- text = f'{self.colorize(feature.keyword, "magenta")}: {feature.name}\n'
69
+ text = f"{self.colorize(feature.keyword, 'magenta')}: {feature.name}\n"
70
70
  self.stream.write(text)
71
71
 
72
72
  def colorize(self, text, color):
@@ -193,7 +193,7 @@ class CucuFormatter(Formatter):
193
193
  status_text_padding = (
194
194
  max_line_length - len(current_step_text) - len(prefix)
195
195
  )
196
- status_text = f'{" " * status_text_padding}{status_text}'
196
+ status_text = f"{' ' * status_text_padding}{status_text}"
197
197
  status_text = self.colorize(status_text, "yellow")
198
198
 
199
199
  self.stream.write(f"{status_text}\n")
@@ -227,7 +227,7 @@ class CucuFormatter(Formatter):
227
227
  ]
228
228
  )
229
229
 
230
- padding = f" {' '*(len('Given')-len(step.keyword))}"
230
+ padding = f" {' ' * (len('Given') - len(step.keyword))}"
231
231
  variable_line = f"{padding}# {expanded}\n"
232
232
  # hide secrets before we do anything to add color which could
233
233
  # modify the output and result in not being able to correctly
@@ -176,7 +176,7 @@ class CucuJSONFormatter(Formatter):
176
176
  for (key, value) in step_variables.items()
177
177
  ]
178
178
  )
179
- padding = f" {' '*(len('Given')-len(step.keyword))}"
179
+ padding = f" {' ' * (len('Given') - len(step.keyword))}"
180
180
  step.stdout.insert(
181
181
  0, f"{padding}# {CONFIG.hide_secrets(expanded)}\n"
182
182
  )
@@ -171,7 +171,7 @@ def generate(results, basepath, only_failures=False):
171
171
  sub_headers.append(handler(scenario))
172
172
  except Exception:
173
173
  logger.warning(
174
- f"Exception while trying to run sub_headers hook for scenario: \"{scenario['name']}\"\n{traceback.format_exc()}"
174
+ f'Exception while trying to run sub_headers hook for scenario: "{scenario["name"]}"\n{traceback.format_exc()}'
175
175
  )
176
176
  scenario["sub_headers"] = "<br/>".join(sub_headers)
177
177
 
@@ -204,7 +204,7 @@ def generate(results, basepath, only_failures=False):
204
204
  # Map the count to the appropriate HTML heading (h2-h5)
205
205
  # We use h2-h5 instead of h1-h4 so h1 can be reserved for scenario/feature titles
206
206
  step["heading_level"] = (
207
- f"h{step["name"][:4].count("#") + 1}"
207
+ f"h{step['name'][:4].count('#') + 1}"
208
208
  )
209
209
 
210
210
  if os.path.exists(image_dirpath):
@@ -422,7 +422,7 @@ def generate(results, basepath, only_failures=False):
422
422
  )
423
423
 
424
424
  feature_output_filepath = os.path.join(
425
- basepath, f'{feature["name"]}.html'
425
+ basepath, f"{feature['name']}.html"
426
426
  )
427
427
 
428
428
  with open(feature_output_filepath, "wb") as output:
@@ -1,5 +1,9 @@
1
1
  from cucu import fuzzy, helpers
2
- from cucu.utils import take_saw_element_screenshot
2
+ from cucu.utils import (
3
+ find_n_click_input_parent_label,
4
+ is_element_size_zero,
5
+ take_saw_element_screenshot,
6
+ )
3
7
 
4
8
  from . import base_steps
5
9
 
@@ -67,6 +71,10 @@ def click_button(ctx, button):
67
71
  if base_steps.is_disabled(button):
68
72
  raise RuntimeError("unable to click the button, as it is disabled")
69
73
 
74
+ if is_element_size_zero(button):
75
+ find_n_click_input_parent_label(ctx, button)
76
+ return
77
+
70
78
  ctx.browser.click(button)
71
79
 
72
80
 
@@ -1,5 +1,9 @@
1
1
  from cucu import fuzzy, helpers
2
- from cucu.utils import take_saw_element_screenshot
2
+ from cucu.utils import (
3
+ find_n_click_input_parent_label,
4
+ is_element_size_zero,
5
+ take_saw_element_screenshot,
6
+ )
3
7
 
4
8
  from . import base_steps
5
9
 
@@ -66,6 +70,10 @@ def check_checkbox(ctx, checkbox):
66
70
  if base_steps.is_disabled(checkbox):
67
71
  raise RuntimeError("unable to check the checkbox, as it is disabled")
68
72
 
73
+ if is_element_size_zero(checkbox):
74
+ find_n_click_input_parent_label(ctx, checkbox)
75
+ return
76
+
69
77
  ctx.browser.click(checkbox)
70
78
 
71
79
 
@@ -78,6 +86,10 @@ def uncheck_checkbox(ctx, checkbox):
78
86
  if is_not_checked(checkbox):
79
87
  raise Exception("checkbox already unchecked")
80
88
 
89
+ if is_element_size_zero(checkbox):
90
+ find_n_click_input_parent_label(ctx, checkbox)
91
+ return
92
+
81
93
  ctx.browser.click(checkbox)
82
94
 
83
95
 
@@ -1,6 +1,10 @@
1
1
  from cucu import fuzzy, helpers, retry, step
2
2
  from cucu.config import CONFIG
3
- from cucu.utils import take_saw_element_screenshot
3
+ from cucu.utils import (
4
+ find_n_click_input_parent_label,
5
+ is_element_size_zero,
6
+ take_saw_element_screenshot,
7
+ )
4
8
 
5
9
  from . import base_steps
6
10
 
@@ -90,6 +94,10 @@ def find_n_select_radio_button(ctx, name, index=0, ignore_if_selected=False):
90
94
 
91
95
  raise Exception(f'radio button "{name}" already selected')
92
96
 
97
+ if is_element_size_zero(radio):
98
+ find_n_click_input_parent_label(ctx, radio)
99
+ return
100
+
93
101
  ctx.browser.click(radio)
94
102
 
95
103
 
@@ -123,6 +131,10 @@ def select_radio_button(ctx, radiobox):
123
131
  if selected:
124
132
  raise Exception("radiobox already selected")
125
133
 
134
+ if is_element_size_zero(radiobox):
135
+ find_n_click_input_parent_label(ctx, radiobox)
136
+ return
137
+
126
138
  ctx.browser.click(radiobox)
127
139
 
128
140
 
@@ -306,7 +306,7 @@ def get_table_cell_value(ctx, table, row, column, variable_name):
306
306
  cell_value = tables[table][row][column]
307
307
  except IndexError:
308
308
  raise RuntimeError(
309
- f"Cannot find table:{table+1},row:{row+1},column:{column+1}. Please check your table data."
309
+ f"Cannot find table:{table + 1},row:{row + 1},column:{column + 1}. Please check your table data."
310
310
  )
311
311
  config.CONFIG[variable_name] = cell_value
312
312
 
@@ -343,7 +343,7 @@ def find_table_element(ctx, nth=1):
343
343
  return ctx.browser.css_find_elements("table")[nth]
344
344
  except IndexError:
345
345
  raise RuntimeError(
346
- f"Cannot find table:{nth+1}. Please check your table data."
346
+ f"Cannot find table:{nth + 1}. Please check your table data."
347
347
  )
348
348
 
349
349
 
@@ -364,7 +364,7 @@ def click_table_cell(ctx, row, column, table):
364
364
  cell = row.find_elements(By.CSS_SELECTOR, "td")[column]
365
365
  except IndexError:
366
366
  raise RuntimeError(
367
- f"Cannot find table:{table+1},row:{row+1},column:{column+1}. Please check your table data."
367
+ f"Cannot find table:{table + 1},row:{row + 1},column:{column + 1}. Please check your table data."
368
368
  )
369
369
  ctx.browser.click(cell)
370
370
 
@@ -409,7 +409,7 @@ def wait_click_table_cell_matching_text(ctx, column, match_text, table):
409
409
  cell = row[0].find_elements(By.CSS_SELECTOR, "td")[column]
410
410
  except IndexError:
411
411
  raise RuntimeError(
412
- f"Cannot find table:{table+1},column:{column+1},text:{match_text}. Please check your table data."
412
+ f"Cannot find table:{table + 1},column:{column + 1},text:{match_text}. Please check your table data."
413
413
  )
414
414
 
415
415
  ctx.browser.click(cell)
@@ -431,7 +431,7 @@ def wait_table_row_count(ctx, row_count, table):
431
431
  return
432
432
  else:
433
433
  raise RuntimeError(
434
- f"Unable to find {row_count} rows in table {table+1}. Please check your table data."
434
+ f"Unable to find {row_count} rows in table {table + 1}. Please check your table data."
435
435
  )
436
436
 
437
437
  retry(find_table_row_count)(ctx, row_count, table)
@@ -9,6 +9,7 @@ import pkgutil
9
9
  import shutil
10
10
 
11
11
  import humanize
12
+ from selenium.webdriver.common.by import By
12
13
  from tabulate import DataRow, TableFormat, tabulate
13
14
  from tenacity import (
14
15
  after_log,
@@ -271,3 +272,41 @@ def take_screenshot(ctx, step_name, label="", element=None):
271
272
  shutil.copyfile(filepath, CONFIG["CUCU_MONITOR_PNG"])
272
273
 
273
274
  CONFIG["__STEP_SCREENSHOT_COUNT"] += 1
275
+
276
+
277
+ def get_tab_information(ctx):
278
+ driver = ctx.browser.driver
279
+ window_handles = driver.window_handles
280
+ current_window = driver.current_window_handle
281
+ window_handle_index = window_handles.index(current_window)
282
+ return {
283
+ "window_count": len(window_handles),
284
+ "current_index": window_handle_index,
285
+ "current_title": driver.title,
286
+ "current_url": driver.current_url,
287
+ }
288
+
289
+
290
+ def find_n_click_input_parent_label(ctx, input_element):
291
+ """
292
+ Clicks the nearest parent <label> of an input elemnt (if input is visually hidden or size is zero).
293
+ """
294
+ try:
295
+ # Find the closest ancestor <label> element
296
+ label = input_element.find_element(By.XPATH, "ancestor::label[1]")
297
+
298
+ if label and label.is_displayed():
299
+ ctx.browser.click(label)
300
+ logger.debug("Successfully clicked the parent label.")
301
+ else:
302
+ logger.warning("Parent label is not displayed or not found.")
303
+
304
+ except Exception as e:
305
+ logger.error(
306
+ f"Click on parent label failed (possibly missing label ancestor): {e}"
307
+ )
308
+
309
+
310
+ def is_element_size_zero(element):
311
+ size = element.size
312
+ return size["width"] == 0 and size["height"] == 0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes