cucu 1.0.13__py3-none-any.whl → 1.1.2__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.
- cucu/behave_tweaks.py +6 -2
- cucu/cli/core.py +43 -7
- cucu/cli/run.py +3 -3
- cucu/cli/steps.py +4 -0
- cucu/environment.py +3 -3
- cucu/formatter/cucu.py +4 -0
- cucu/init_data/.gitignore +5 -0
- cucu/init_data/README.md +63 -0
- cucu/init_data/cucurc.yml +10 -0
- cucu/init_data/data/www/example.html +27 -0
- cucu/init_data/features/cucurc.yml +9 -0
- cucu/init_data/features/environment.py +90 -0
- cucu/init_data/features/example.feature +18 -0
- cucu/init_data/features/lint_rules/sid.yaml +29 -0
- cucu/init_data/features/steps/__init__.py +7 -0
- cucu/init_data/features/steps/my_steps.py +26 -0
- {cucu-1.0.13.dist-info → cucu-1.1.2.dist-info}/METADATA +57 -41
- {cucu-1.0.13.dist-info → cucu-1.1.2.dist-info}/RECORD +21 -11
- {cucu-1.0.13.dist-info → cucu-1.1.2.dist-info}/WHEEL +0 -0
- {cucu-1.0.13.dist-info → cucu-1.1.2.dist-info}/entry_points.txt +0 -0
- {cucu-1.0.13.dist-info → cucu-1.1.2.dist-info}/licenses/LICENSE +0 -0
cucu/behave_tweaks.py
CHANGED
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
# writing while not interfering with the way behave does its own log
|
|
4
4
|
# capturing
|
|
5
5
|
#
|
|
6
|
+
import os
|
|
6
7
|
import sys
|
|
7
8
|
import warnings
|
|
8
9
|
from functools import wraps
|
|
9
10
|
|
|
11
|
+
# set env var BEHAVE_STRIP_STEPS_WITH_TRAILING_COLON=yes before importing behave
|
|
12
|
+
os.environ["BEHAVE_STRIP_STEPS_WITH_TRAILING_COLON"] = "yes"
|
|
13
|
+
|
|
10
14
|
import behave
|
|
11
15
|
from behave.__main__ import main as original_behave_main
|
|
12
16
|
from behave.model import Table
|
|
@@ -62,7 +66,7 @@ def init_step_hooks(stdout, stderr):
|
|
|
62
66
|
# resolve variables in the table values
|
|
63
67
|
if ctx.table is not None:
|
|
64
68
|
ctx.table.original = Table(
|
|
65
|
-
ctx.table.headings, rows=ctx.table.rows
|
|
69
|
+
ctx.table.headings, rows=ctx.table.rows, line=ctx.table.line
|
|
66
70
|
)
|
|
67
71
|
new_rows = []
|
|
68
72
|
for row in ctx.table.rows:
|
|
@@ -119,7 +123,7 @@ def init_step_hooks(stdout, stderr):
|
|
|
119
123
|
|
|
120
124
|
return wrapper
|
|
121
125
|
|
|
122
|
-
behave.__dict__[decorator_name] = new_decorator
|
|
126
|
+
behave.__dict__[decorator_name.title()] = behave.__dict__[decorator_name] = new_decorator
|
|
123
127
|
|
|
124
128
|
|
|
125
129
|
_stdout = sys.stdout
|
cucu/cli/core.py
CHANGED
|
@@ -8,6 +8,7 @@ import sys
|
|
|
8
8
|
import time
|
|
9
9
|
import xml.etree.ElementTree as ET
|
|
10
10
|
from importlib.metadata import version
|
|
11
|
+
from pathlib import Path
|
|
11
12
|
from threading import Timer
|
|
12
13
|
|
|
13
14
|
import click
|
|
@@ -90,10 +91,10 @@ def main():
|
|
|
90
91
|
)
|
|
91
92
|
@click.option("-n", "--name", help="used to specify the exact scenario to run")
|
|
92
93
|
@click.option(
|
|
93
|
-
"-
|
|
94
|
-
"--
|
|
94
|
+
"-d",
|
|
95
|
+
"--debug-on-failure/--no-debug-on-failure",
|
|
95
96
|
default=False,
|
|
96
|
-
help="on failure drop into the
|
|
97
|
+
help="on failure drop into the debug shell",
|
|
97
98
|
)
|
|
98
99
|
@click.option(
|
|
99
100
|
"-j",
|
|
@@ -213,7 +214,7 @@ def run(
|
|
|
213
214
|
fail_fast,
|
|
214
215
|
headless,
|
|
215
216
|
name,
|
|
216
|
-
|
|
217
|
+
debug_on_failure,
|
|
217
218
|
junit,
|
|
218
219
|
junit_with_stacktrace,
|
|
219
220
|
logging_level,
|
|
@@ -323,7 +324,7 @@ def run(
|
|
|
323
324
|
fail_fast,
|
|
324
325
|
headless,
|
|
325
326
|
name,
|
|
326
|
-
|
|
327
|
+
debug_on_failure,
|
|
327
328
|
junit,
|
|
328
329
|
results,
|
|
329
330
|
secrets,
|
|
@@ -415,7 +416,7 @@ def run(
|
|
|
415
416
|
fail_fast,
|
|
416
417
|
headless,
|
|
417
418
|
name,
|
|
418
|
-
|
|
419
|
+
debug_on_failure,
|
|
419
420
|
junit,
|
|
420
421
|
results,
|
|
421
422
|
secrets,
|
|
@@ -468,7 +469,9 @@ def run(
|
|
|
468
469
|
time.sleep(1)
|
|
469
470
|
|
|
470
471
|
if timeout_reached:
|
|
471
|
-
logger.warning(
|
|
472
|
+
logger.warning(
|
|
473
|
+
"Timeout reached, send kill signal to workers"
|
|
474
|
+
)
|
|
472
475
|
kill_workers()
|
|
473
476
|
|
|
474
477
|
task_failed.update(async_results)
|
|
@@ -765,6 +768,39 @@ def vars(filepath):
|
|
|
765
768
|
print(tabulate(variables, tablefmt="fancy_grid"))
|
|
766
769
|
|
|
767
770
|
|
|
771
|
+
@main.command()
|
|
772
|
+
@click.argument("filepath", default="")
|
|
773
|
+
@click.option(
|
|
774
|
+
"-l",
|
|
775
|
+
"--logging-level",
|
|
776
|
+
default="INFO",
|
|
777
|
+
help="set logging level to one of debug, warn or info (default)",
|
|
778
|
+
)
|
|
779
|
+
def init(filepath, logging_level):
|
|
780
|
+
"""
|
|
781
|
+
initialize cucu in the current directory
|
|
782
|
+
|
|
783
|
+
Copies the `init_data` directory to the current directory
|
|
784
|
+
"""
|
|
785
|
+
os.environ["CUCU_LOGGING_LEVEL"] = logging_level.upper()
|
|
786
|
+
logger.init_logging(logging_level.upper())
|
|
787
|
+
|
|
788
|
+
init_data_dir = Path(__file__).parent.parent / "init_data"
|
|
789
|
+
|
|
790
|
+
logger.debug(f"cucu init: copy example directory from {init_data_dir=}")
|
|
791
|
+
repo_dir = filepath if filepath.strip() else os.path.join(os.getcwd())
|
|
792
|
+
|
|
793
|
+
features_dir = os.path.join(repo_dir, "features")
|
|
794
|
+
if os.path.exists(features_dir):
|
|
795
|
+
answer = input("Overwrite existing files? [y/N]:")
|
|
796
|
+
if answer.lower() != "y":
|
|
797
|
+
print("Aborted!")
|
|
798
|
+
return
|
|
799
|
+
|
|
800
|
+
shutil.copytree(init_data_dir, repo_dir, dirs_exist_ok=True)
|
|
801
|
+
print("You can now start writing your tests")
|
|
802
|
+
|
|
803
|
+
|
|
768
804
|
@main.command()
|
|
769
805
|
@click.option(
|
|
770
806
|
"-b",
|
cucu/cli/run.py
CHANGED
|
@@ -46,7 +46,7 @@ def behave(
|
|
|
46
46
|
fail_fast,
|
|
47
47
|
headless,
|
|
48
48
|
name,
|
|
49
|
-
|
|
49
|
+
debug_on_failure,
|
|
50
50
|
junit,
|
|
51
51
|
results,
|
|
52
52
|
secrets,
|
|
@@ -81,8 +81,8 @@ def behave(
|
|
|
81
81
|
key, value = variable.split("=")
|
|
82
82
|
os.environ[key] = value
|
|
83
83
|
|
|
84
|
-
if
|
|
85
|
-
os.environ["
|
|
84
|
+
if debug_on_failure:
|
|
85
|
+
os.environ["CUCU_DEBUG_ON_FAILURE"] = "true"
|
|
86
86
|
|
|
87
87
|
os.environ["CUCU_RESULTS_DIR"] = results
|
|
88
88
|
os.environ["CUCU_JUNIT_DIR"] = junit
|
cucu/cli/steps.py
CHANGED
|
@@ -47,6 +47,10 @@ def load_cucu_steps(filepath=None):
|
|
|
47
47
|
stdout = stdout.getvalue()
|
|
48
48
|
stderr = stderr.getvalue()
|
|
49
49
|
|
|
50
|
+
if stdout.startswith("USING RUNNER:"):
|
|
51
|
+
# remove the first line of the output so that linter won't error out trying to parse it as a step
|
|
52
|
+
stdout = "\n".join(stdout.split("\n")[1:])
|
|
53
|
+
|
|
50
54
|
if stdout.startswith("ParserError"):
|
|
51
55
|
print(stdout)
|
|
52
56
|
raise RuntimeError(
|
cucu/environment.py
CHANGED
|
@@ -270,11 +270,11 @@ def after_step(ctx, step):
|
|
|
270
270
|
ctx.step_index += 1
|
|
271
271
|
CONFIG["__STEP_SCREENSHOT_COUNT"] = 0
|
|
272
272
|
|
|
273
|
-
if CONFIG.bool("
|
|
273
|
+
if CONFIG.bool("CUCU_DEBUG_ON_FAILURE") and step.status == "failed":
|
|
274
274
|
ctx._runner.stop_capture()
|
|
275
|
-
import
|
|
275
|
+
import pdb
|
|
276
276
|
|
|
277
|
-
|
|
277
|
+
pdb.post_mortem(step.exc_traceback)
|
|
278
278
|
|
|
279
279
|
CONFIG["__CUCU_BEFORE_THIS_SCENARIO_HOOKS"] = []
|
|
280
280
|
|
cucu/formatter/cucu.py
CHANGED
|
@@ -171,6 +171,10 @@ class CucuFormatter(Formatter):
|
|
|
171
171
|
text = self.colorize(
|
|
172
172
|
f"{indent}{prefix}{keyword} {step.name}\n", "cyan"
|
|
173
173
|
)
|
|
174
|
+
else:
|
|
175
|
+
text = self.colorize(
|
|
176
|
+
f"{indent}{prefix}{keyword} {step.name}\n", "yellow"
|
|
177
|
+
)
|
|
174
178
|
|
|
175
179
|
if self.monochrome:
|
|
176
180
|
self.stream.write(f"{text}")
|
cucu/init_data/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<!-- your project readme here -->
|
|
2
|
+
|
|
3
|
+
# Development
|
|
4
|
+
## E2E tests
|
|
5
|
+
Use [cucu](https://pypi.org/project/cucu/) to run e2e-tests with the following:
|
|
6
|
+
1. make sure you're in the **repo root**
|
|
7
|
+
2. install the --dev dependecies
|
|
8
|
+
```sh
|
|
9
|
+
uv sync --dev
|
|
10
|
+
```
|
|
11
|
+
3. activate it
|
|
12
|
+
```sh
|
|
13
|
+
source .venv/bin/activate
|
|
14
|
+
```
|
|
15
|
+
4. run scenario
|
|
16
|
+
```sh
|
|
17
|
+
cucu run features
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Other helpful commands
|
|
21
|
+
|
|
22
|
+
- Show all steps
|
|
23
|
+
```sh
|
|
24
|
+
cucu steps
|
|
25
|
+
```
|
|
26
|
+
- Run scenario by tag
|
|
27
|
+
```sh
|
|
28
|
+
cucu run features --tag my_tag
|
|
29
|
+
```
|
|
30
|
+
- Run scenario by line number
|
|
31
|
+
```sh
|
|
32
|
+
cucu run features/example.feature:8
|
|
33
|
+
```
|
|
34
|
+
- Generate an html report
|
|
35
|
+
```sh
|
|
36
|
+
cucu report
|
|
37
|
+
```
|
|
38
|
+
- Run scenarios and generate report
|
|
39
|
+
```sh
|
|
40
|
+
cucu run features -g
|
|
41
|
+
```
|
|
42
|
+
- Lint your feature files!
|
|
43
|
+
_see `/features/lint_rules` for custom rules_
|
|
44
|
+
```sh
|
|
45
|
+
cucu lint
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Tagging Tests
|
|
49
|
+
|
|
50
|
+
> [!Note] This is just an example of how use tags
|
|
51
|
+
|
|
52
|
+
1. Tags are very important as they provide:
|
|
53
|
+
|
|
54
|
+
- Critical metadata for reporting and tracking
|
|
55
|
+
- Control where it runs in CI
|
|
56
|
+
- Filtering on what tests should run
|
|
57
|
+
|
|
58
|
+
2. They can be at the **Feature** file level or at the **Scenario** level.
|
|
59
|
+
|
|
60
|
+
3. Here's a list of some notable tags
|
|
61
|
+
| Tag | Gist | Example |
|
|
62
|
+
| --------- | ----------------------------------------------------------------------------------------------- | --------- |
|
|
63
|
+
| @sid-#### | **[Required]** The scenario (i.e. test case) id (numbers only) that is used to track and record | @sid-1234 |
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Don't check in this file! (I mean you could, but you probably don't want to)
|
|
2
|
+
|
|
3
|
+
# The config heirarchy starts with ~/.cucurc.yml, then this file, and down the folder tree.
|
|
4
|
+
# Use this file for local configuration per user
|
|
5
|
+
|
|
6
|
+
# This is just an example, normally you could have it as environment variables or pass it in the command line
|
|
7
|
+
MY_SECRET_USERNAME: "Bob"
|
|
8
|
+
MY_SECRET_PASSWORD: "123456"
|
|
9
|
+
|
|
10
|
+
SOME_OTHER_SECRET: "another secret"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<html>
|
|
2
|
+
<head>
|
|
3
|
+
<title>Some Example</title>
|
|
4
|
+
<script>
|
|
5
|
+
function update() {
|
|
6
|
+
document.querySelector("#my_status").textContent = "Welcome " + document.querySelector("#my_name").value + " !";
|
|
7
|
+
document.querySelector("#my_name").value = "";
|
|
8
|
+
document.querySelector("#my_key").value = "";
|
|
9
|
+
}
|
|
10
|
+
</script>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
|
|
14
|
+
<h1>Some Example</h1>
|
|
15
|
+
<div id="my_status"></div>
|
|
16
|
+
<br/>
|
|
17
|
+
|
|
18
|
+
<div style="background: aliceblue;">Fake login box
|
|
19
|
+
<br/>
|
|
20
|
+
<label>username:</label><input id="my_name" type="text"></input>
|
|
21
|
+
<br/>
|
|
22
|
+
<label>The password here but use aria label to find</label><input aria-label="password" id="my_key" type="password"></input>
|
|
23
|
+
<br/>
|
|
24
|
+
<button onclick="update();">Login</button>
|
|
25
|
+
</div>
|
|
26
|
+
</body>
|
|
27
|
+
</html>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Checkin this file for the repo configuration
|
|
2
|
+
|
|
3
|
+
# CUCU_SECRETS is a comma-separated list of the names of the secrets (not the values)
|
|
4
|
+
CUCU_SECRETS: "MY_SECRET_PASSWORD,SOME_OTHER_SECRET"
|
|
5
|
+
|
|
6
|
+
CUCU_BROWSER_WINDOW_HEIGHT: 1080
|
|
7
|
+
CUCU_BROWSER_WINDOW_WIDTH: 1920
|
|
8
|
+
|
|
9
|
+
CUCU_BROKEN_IMAGES_PAGE_CHECK: false
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Define custom before/after hooks here
|
|
2
|
+
# Warn: Don't import this file into other python files to avoid duplicating hook registers
|
|
3
|
+
|
|
4
|
+
import datetime
|
|
5
|
+
|
|
6
|
+
from cucu import (
|
|
7
|
+
register_custom_scenario_subheader_in_report_handling,
|
|
8
|
+
register_custom_tags_in_report_handling,
|
|
9
|
+
)
|
|
10
|
+
from cucu.config import CONFIG
|
|
11
|
+
from cucu.environment import * # noqa
|
|
12
|
+
|
|
13
|
+
# point the `cucu lint` command to our own custom linting rules
|
|
14
|
+
CONFIG["CUCU_LINT_RULES_PATH"] = "features/lint_rules"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def make_scenario_id_clickable(tag):
|
|
18
|
+
"""Example of processing a tag to open up a link to some url"""
|
|
19
|
+
scenario_id = tag.replace(r"@sid-", "")
|
|
20
|
+
return f'<a href="https://localhost/index.php?/cases/view/{scenario_id}" target="_blank">{tag}</a>'
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
register_custom_tags_in_report_handling(r"@sid-.+", make_scenario_id_clickable)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def my_example_subheader(scenario):
|
|
27
|
+
"""Example of a custom subheader for the report
|
|
28
|
+
|
|
29
|
+
This may be useful if you want to link results to your log monitor system
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
query_time = ""
|
|
33
|
+
if (
|
|
34
|
+
"steps" in scenario
|
|
35
|
+
and len(scenario["steps"]) > 0
|
|
36
|
+
and "result" in scenario["steps"][0]
|
|
37
|
+
):
|
|
38
|
+
one_minute = datetime.timedelta(minutes=1)
|
|
39
|
+
# get start time from first step iso timestamp
|
|
40
|
+
start_datetime = (
|
|
41
|
+
datetime.datetime.fromisoformat(
|
|
42
|
+
scenario["steps"][0]["result"]["timestamp"]
|
|
43
|
+
)
|
|
44
|
+
- one_minute
|
|
45
|
+
)
|
|
46
|
+
epoc_start_time_ms = int(start_datetime.timestamp()) * 1000
|
|
47
|
+
|
|
48
|
+
end_datetime = start_datetime + (
|
|
49
|
+
one_minute * 30
|
|
50
|
+
) # default to start + 30 minutes
|
|
51
|
+
for step in reversed(scenario["steps"]):
|
|
52
|
+
if step.get("result", []) and step["result"].get(
|
|
53
|
+
"timestamp", None
|
|
54
|
+
):
|
|
55
|
+
end_duration = (
|
|
56
|
+
datetime.timedelta(
|
|
57
|
+
seconds=step["result"].get("duration", 0)
|
|
58
|
+
)
|
|
59
|
+
+ one_minute
|
|
60
|
+
)
|
|
61
|
+
end_datetime = (
|
|
62
|
+
datetime.datetime.fromisoformat(
|
|
63
|
+
step["result"]["timestamp"]
|
|
64
|
+
)
|
|
65
|
+
+ end_duration
|
|
66
|
+
)
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
epoc_end_time_ms = int(end_datetime.timestamp()) * 1000
|
|
70
|
+
query_time = f"&begin={epoc_start_time_ms}&end={epoc_end_time_ms}"
|
|
71
|
+
|
|
72
|
+
account_id = "123456"
|
|
73
|
+
result = ""
|
|
74
|
+
if account_id and query_time:
|
|
75
|
+
html = "<span>Log Monitoring System: "
|
|
76
|
+
html += f'<a href="https://localhost/logger?account={account_id}{query_time}" target="_blank">Link 🔗</a> '
|
|
77
|
+
result = html
|
|
78
|
+
|
|
79
|
+
return result
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
register_custom_scenario_subheader_in_report_handling(my_example_subheader)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# TODO:
|
|
86
|
+
# - add a register_page_check_hook # to ensure the page is loaded)
|
|
87
|
+
# - add a register_before_retry_hook # when using the retry decorator on steps)
|
|
88
|
+
# - add a register_custom_tags_in_report_handling
|
|
89
|
+
# - add a register_custom_scenario_subheader_in_report_handling
|
|
90
|
+
# - add a register_custom_variable_handling
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Feature: Cucu init feature
|
|
2
|
+
We want new projects to have a least one feature file with a scenario
|
|
3
|
+
Note: This is a comment, you can continue writing more comments here.
|
|
4
|
+
Avoid starting a line with keywords like Given, When, Then, And, Scenario, Background, etc.
|
|
5
|
+
|
|
6
|
+
You can also have blank lines between comments.
|
|
7
|
+
|
|
8
|
+
@example @another-tag
|
|
9
|
+
# The @sid- is just an example of a tag to mark this test with an id.
|
|
10
|
+
@sid-314159
|
|
11
|
+
Scenario: Example scenario
|
|
12
|
+
This is a sample scenario using a fake page
|
|
13
|
+
Normally you'd have your website somewhere and not use a static files like this.
|
|
14
|
+
Note: The login step is custom, you'll need to implement it in your project.
|
|
15
|
+
Given I start a webserver at directory "data/www" and save the port to the variable "PORT"
|
|
16
|
+
And I open a browser at the url "http://localhost:{PORT}/example.html"
|
|
17
|
+
When I login in with the username "{MY_SECRET_USERNAME}" and password as var "MY_SECRET_PASSWORD"
|
|
18
|
+
Then I should see the text "Welcome {MY_SECRET_USERNAME} !"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# This is an example of a lint rule file to ensure every scenario has a unique id
|
|
2
|
+
|
|
3
|
+
scenario_must_have_one_scenario_tag:
|
|
4
|
+
message: each scenario must have one sid tag
|
|
5
|
+
type: error
|
|
6
|
+
exclude: .*/features/(setup|teardown)/.*
|
|
7
|
+
current_line:
|
|
8
|
+
match: '\s+Scenario:.+'
|
|
9
|
+
previous_line:
|
|
10
|
+
match: '(?!\s+@sid-\d+)'
|
|
11
|
+
|
|
12
|
+
feature_must_not_have_scenario_tags:
|
|
13
|
+
message: features should not have a scenario tag, add tag to scenarios instead
|
|
14
|
+
type: error
|
|
15
|
+
exclude: .*/features/(setup|teardown)/.*
|
|
16
|
+
current_line:
|
|
17
|
+
match: "Feature:.+"
|
|
18
|
+
previous_line:
|
|
19
|
+
match: '^(@sid-.*)'
|
|
20
|
+
|
|
21
|
+
scenario_id_must_be_unique_across_all_scenarios:
|
|
22
|
+
message: you must use unique scenario id per scenario across all features
|
|
23
|
+
exclude: .*/features/(setup|teardown)/.*
|
|
24
|
+
type: error
|
|
25
|
+
current_line:
|
|
26
|
+
match: '\s+Scenario:.+'
|
|
27
|
+
previous_line:
|
|
28
|
+
match: '\s+(@sid-\d+)'
|
|
29
|
+
unique_per_all_scenarios: true
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# import all of the steps from cucu
|
|
2
|
+
from cucu.steps import * # noqa: F403, F401
|
|
3
|
+
|
|
4
|
+
# import individual sub-modules here (i.e. module names of your custom step py files)
|
|
5
|
+
# Example: For file features/steps/ui/login.py
|
|
6
|
+
# import steps.ui.login_steps
|
|
7
|
+
import steps.my_steps
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from cucu import run_steps, step
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@step(
|
|
5
|
+
'I login in with the username "{username}" and password as var "{password_var}"'
|
|
6
|
+
)
|
|
7
|
+
def step_with_substeps_that_log(ctx, username, password_var):
|
|
8
|
+
"""This is an example of a step that uses substeps.
|
|
9
|
+
|
|
10
|
+
You probably want write a login step anyways"""
|
|
11
|
+
run_steps(
|
|
12
|
+
ctx,
|
|
13
|
+
f"""
|
|
14
|
+
When I write "{username}" into the input "username"
|
|
15
|
+
And I write "{{{password_var}}}" into the input "password"
|
|
16
|
+
And I click the button "Login"
|
|
17
|
+
Then I wait to see the text "Welcome {username} !"
|
|
18
|
+
""",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# TODO:
|
|
23
|
+
# - add a step that takes a table and prints it
|
|
24
|
+
# - add a step that takes a multiline string and prints it
|
|
25
|
+
# - add a step that looks up a html element by css selector
|
|
26
|
+
# - add a step that waits with retries
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cucu
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.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/
|
|
@@ -23,18 +23,18 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.10
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.11
|
|
25
25
|
Classifier: Programming Language :: Python :: 3.12
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
26
27
|
Classifier: Topic :: Software Development :: Testing :: BDD
|
|
27
28
|
Requires-Python: >=3.9
|
|
28
29
|
Requires-Dist: beautifulsoup4~=4.13.3
|
|
29
|
-
Requires-Dist: behave
|
|
30
|
+
Requires-Dist: behave==1.2.6
|
|
30
31
|
Requires-Dist: chromedriver-autoinstaller~=0.6.2
|
|
31
32
|
Requires-Dist: click~=8.1.7
|
|
32
|
-
Requires-Dist: coverage[toml]~=7.
|
|
33
|
+
Requires-Dist: coverage[toml]~=7.8
|
|
33
34
|
Requires-Dist: geckodriver-autoinstaller~=0.1.0
|
|
34
35
|
Requires-Dist: humanize~=4.12.1
|
|
35
|
-
Requires-Dist: importlib-metadata~=8.
|
|
36
|
-
Requires-Dist:
|
|
37
|
-
Requires-Dist: jellyfish~=1.1.3
|
|
36
|
+
Requires-Dist: importlib-metadata~=8.7.0
|
|
37
|
+
Requires-Dist: jellyfish>=1.1
|
|
38
38
|
Requires-Dist: jinja2~=3.1.3
|
|
39
39
|
Requires-Dist: lsprotocol~=2023.0.1
|
|
40
40
|
Requires-Dist: mpire~=2.10.2
|
|
@@ -42,9 +42,9 @@ Requires-Dist: psutil>=6.0
|
|
|
42
42
|
Requires-Dist: pygls~=1.3.1
|
|
43
43
|
Requires-Dist: pyyaml~=6.0.1
|
|
44
44
|
Requires-Dist: requests<3.0.0,>=2.31.0
|
|
45
|
-
Requires-Dist: selenium~=4.
|
|
45
|
+
Requires-Dist: selenium~=4.31
|
|
46
46
|
Requires-Dist: tabulate~=0.9.0
|
|
47
|
-
Requires-Dist: tenacity
|
|
47
|
+
Requires-Dist: tenacity>=9.0
|
|
48
48
|
Description-Content-Type: text/markdown
|
|
49
49
|
|
|
50
50
|
[](https://pypi.org/project/cucu/)
|
|
@@ -90,8 +90,9 @@ to drive various underlying tools/frameworks to create real world testing scenar
|
|
|
90
90
|
|
|
91
91
|
- [Installation](#installation)
|
|
92
92
|
- [Requirements](#requirements)
|
|
93
|
-
- [
|
|
94
|
-
- [Usage](#usage)
|
|
93
|
+
- [Setup](#setup)
|
|
94
|
+
- [Usage](#usage)
|
|
95
|
+
- [Usage](#usage-1)
|
|
95
96
|
- [Cucu Run](#cucu-run)
|
|
96
97
|
- [Run specific browser version with docker](#run-specific-browser-version-with-docker)
|
|
97
98
|
- [Extending Cucu](#extending-cucu)
|
|
@@ -103,40 +104,55 @@ to drive various underlying tools/frameworks to create real world testing scenar
|
|
|
103
104
|
- [Install From Build](#install-from-build)
|
|
104
105
|
|
|
105
106
|
# Installation
|
|
107
|
+
Let's get your repo to start using the cucu framework!
|
|
108
|
+
> [!NOTE] If you're not using uv, then just
|
|
109
|
+
> `pip install cucu` for your repo
|
|
110
|
+
|
|
106
111
|
## Requirements
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
112
|
+
1. Docker (for UI testing)
|
|
113
|
+
2. The [uv](https://docs.astral.sh/uv/) tool
|
|
114
|
+
3. A repo in a clean state
|
|
110
115
|
|
|
111
|
-
##
|
|
112
|
-
|
|
116
|
+
## Setup
|
|
117
|
+
> [!NOTE] Always run cucu from your repo root folder
|
|
113
118
|
|
|
114
|
-
1.
|
|
115
|
-
2.
|
|
119
|
+
1. Have docker running (don't need it right now, but why not get ready for testing)
|
|
120
|
+
2. Make sure you have no files to commit and a clean working tree
|
|
116
121
|
```
|
|
117
|
-
|
|
122
|
+
git status
|
|
118
123
|
```
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
124
|
+
Should report: `nothing to commit, working tree clean`
|
|
125
|
+
|
|
126
|
+
3. Add [cucu](https://pypi.org/project/cucu/) to your project and activate your venv
|
|
127
|
+
```
|
|
128
|
+
uv add cucu --dev
|
|
129
|
+
source .venv/bin/activate
|
|
130
|
+
```
|
|
131
|
+
4. Initialize cucu (copies the `init_data` folder to your repo)
|
|
132
|
+
```
|
|
133
|
+
cucu init
|
|
134
|
+
```
|
|
135
|
+
5. Manually resolve any file conflicts
|
|
136
|
+
```
|
|
137
|
+
git status
|
|
138
|
+
```
|
|
139
|
+
6. Run the example tests
|
|
140
|
+
```
|
|
141
|
+
cucu run features
|
|
142
|
+
```
|
|
143
|
+
7. Done! But there is more optional stuff you can do, like:
|
|
144
|
+
1. Run with `--no-headless` mode to see the browser interaction
|
|
145
|
+
2. Run with `--generate-report` (or `-g`) to generate the html **report/** folder
|
|
146
|
+
3. Reference the exact test (i.e. scenario) with `features/example.feature:8` instead of using the `features` folder
|
|
147
|
+
```
|
|
148
|
+
cucu run features/example.feature:8 -g --no-headless
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Usage
|
|
152
|
+
> [!Note] Cucu needs to be run from your **repo root** (i.e. the parent of the `features` folder)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
1. list available cucu steps
|
|
140
156
|
```bash
|
|
141
157
|
cucu steps
|
|
142
158
|
```
|
|
@@ -145,7 +161,7 @@ _Get your repo setup using cucu as a test framework_
|
|
|
145
161
|
cucu steps | fzf
|
|
146
162
|
# start typing for search
|
|
147
163
|
```
|
|
148
|
-
|
|
164
|
+
2. **create your first cucu test**
|
|
149
165
|
- features/my_first_test.feature
|
|
150
166
|
```gherkin
|
|
151
167
|
Feature: My First Cucu Test
|
|
@@ -157,7 +173,7 @@ _Get your repo setup using cucu as a test framework_
|
|
|
157
173
|
And I click the button "Google Search"
|
|
158
174
|
Then I wait to see the text "results"
|
|
159
175
|
```
|
|
160
|
-
|
|
176
|
+
3. **run it**
|
|
161
177
|
```bash
|
|
162
178
|
cucu run features/my_first_test.feature
|
|
163
179
|
```
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
cucu/__init__.py,sha256=YtuajsJBj3_DgNoygHen9gKojeQF523Oc27kyCUzoG0,1013
|
|
2
2
|
cucu/ansi_parser.py,sha256=_yTlqr6KruLsqgWR6BkpJUC3bmlQy_9JbkuxFx6Jrbo,2213
|
|
3
|
-
cucu/behave_tweaks.py,sha256=
|
|
3
|
+
cucu/behave_tweaks.py,sha256=SfI1GVefiihdcJSZI4WLawYToTgTS_bv-TlXuw8Q4rw,6794
|
|
4
4
|
cucu/config.py,sha256=12SXNtBSnD3N6K9DnCDYHZDA4_Wrh4g7whdgHDKSuPw,14022
|
|
5
|
-
cucu/environment.py,sha256=
|
|
5
|
+
cucu/environment.py,sha256=n5FyAq2T8t_dl1YdM38Wdp6yx9h0bL0pHyP5mAlhl00,9429
|
|
6
6
|
cucu/helpers.py,sha256=l_YMmbuXjtBRo-MER-qe6soUIyjt0ey2BoSgWs4zYwA,36285
|
|
7
7
|
cucu/hooks.py,sha256=3Z1mavU42XMQ0DZ7lVWwTB-BJYHRyYUOzzOtmkdIsow,7117
|
|
8
8
|
cucu/logger.py,sha256=Y4eHmNFCphqXEzUQD-DJ8dsaqTNtqxmaKSCdo66Oc-g,3349
|
|
@@ -14,21 +14,31 @@ cucu/browser/frames.py,sha256=IW7kzRJn5PkbMaovIelAeCWO-T-2sOTwqaYBw-0-LKU,3545
|
|
|
14
14
|
cucu/browser/selenium.py,sha256=IOLzSyxNDUxEccc10qXV8hf817hNgUBRCHz1PbCEKhs,11605
|
|
15
15
|
cucu/browser/selenium_tweaks.py,sha256=oUIhWVhBZbc9qsmQUJMpIr9uUWKxtgZBcnySWU6Yttk,879
|
|
16
16
|
cucu/cli/__init__.py,sha256=uXX5yVG1konJ_INdlrcfMg-Tt_5_cSx29Ed8R8v908A,62
|
|
17
|
-
cucu/cli/core.py,sha256=
|
|
18
|
-
cucu/cli/run.py,sha256=
|
|
19
|
-
cucu/cli/steps.py,sha256=
|
|
17
|
+
cucu/cli/core.py,sha256=Lh6kRFuzQkuPo01oYfZNedn6PM4F4p1qtUKgDwh4K1I,25334
|
|
18
|
+
cucu/cli/run.py,sha256=uZOf1c5zAXT0ThaQvrb5kgXGwl80lAtHaTm5dDwolng,5909
|
|
19
|
+
cucu/cli/steps.py,sha256=lg5itVH_C-0_3RelWXv9X2qQUHggdxuxLCGwH5l1bf4,4210
|
|
20
20
|
cucu/cli/thread_dumper.py,sha256=Z3XnYSxidx6pqjlQ7zu-TKMIYZWk4z9c5YLdPkcemiU,1593
|
|
21
21
|
cucu/edgedriver_autoinstaller/README.md,sha256=tDkAWIqgRdCjt-oX1nYqikIC_FfiOEM2-pc5S5VbRLo,84
|
|
22
22
|
cucu/edgedriver_autoinstaller/__init__.py,sha256=fo6xJJPvcc5Xvni8epXfxDoPxJH5_b6Vk2jD9JTwfRs,969
|
|
23
23
|
cucu/edgedriver_autoinstaller/utils.py,sha256=iRKTww77CGaTAntt_QDvxlKPxpMU4otx95OeD97khcM,6802
|
|
24
24
|
cucu/external/jquery/jquery-3.5.1.min.js,sha256=9_aliU8dGd2tb6OSsuzixeV4y_faTqgFtohetphbbj0,89476
|
|
25
25
|
cucu/formatter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
-
cucu/formatter/cucu.py,sha256=
|
|
26
|
+
cucu/formatter/cucu.py,sha256=ekyxcirvo44YYGu41OHVjsgMEin0zD6snwJOuZeeb-4,9294
|
|
27
27
|
cucu/formatter/json.py,sha256=BPmXkSpKFZo5b_6MCWva6WAQaQrr0NL5BTZ2SmOGBr8,10592
|
|
28
28
|
cucu/formatter/junit.py,sha256=aJ9dGLbamMH-wUi_msF66_-_c_YUq07-8_wCNEjUju4,10129
|
|
29
29
|
cucu/fuzzy/__init__.py,sha256=ce4JRmaBF6oab6U99Qbpt7DrD3elhH32__-ND6fw5xc,104
|
|
30
30
|
cucu/fuzzy/core.py,sha256=tmQKX_Ni-2ohoxzctRUg2x7zMeEW8MlJJmpU3PfTmvQ,3153
|
|
31
31
|
cucu/fuzzy/fuzzy.js,sha256=ee-TytISLyUo7cMAkuVI5qbLXdt0eoFWczTsoU4zYhg,11618
|
|
32
|
+
cucu/init_data/.gitignore,sha256=FgMovmORdGgSvRH6EoFK6ch_EyCMyQof61yamXG0P50,137
|
|
33
|
+
cucu/init_data/README.md,sha256=M9nx3JTJxGkExHuy7-JJGcPglRqMFEGI1rX0x36QinQ,1596
|
|
34
|
+
cucu/init_data/cucurc.yml,sha256=3guzj3cu1n0EDaMszvFDGsAghGfemquhXBgf6twYRGY,424
|
|
35
|
+
cucu/init_data/data/www/example.html,sha256=SB0U1Fn1L2BS19Ta3QJKQAf9OlMh4mcbeEn23rE63DA,788
|
|
36
|
+
cucu/init_data/features/cucurc.yml,sha256=OIdK6fbD8aKXm3Sd_roe5_lEb8OpipcZB81T86YK1E0,291
|
|
37
|
+
cucu/init_data/features/environment.py,sha256=uHu-IiPy4Yofk7R8YAAHDTmNKI0kno6In_A80GlANlw,3021
|
|
38
|
+
cucu/init_data/features/example.feature,sha256=f99YDdawnizs4cmUJYKNhfpcghZopVrf6XQtCMeVjf4,1010
|
|
39
|
+
cucu/init_data/features/lint_rules/sid.yaml,sha256=uNYb3f58VJiRKzz7aQnXO6gkHwTU1Ymc1ei3pZoza9U,877
|
|
40
|
+
cucu/init_data/features/steps/__init__.py,sha256=rM252Kn5nESojPltNcpyY9gCxgewmE9UkcRU7UGz5nc,267
|
|
41
|
+
cucu/init_data/features/steps/my_steps.py,sha256=1Rciu2N0iakp9cAAFcRmGVA4bOVqIZJIzxCJj4ylZEA,804
|
|
32
42
|
cucu/language_server/__init__.py,sha256=IUO7FXpRDgvEmljSuB_WpT_BoBRdgG7lhTBFWUwmMR8,75
|
|
33
43
|
cucu/language_server/core.py,sha256=mUCdkk484ldlXRC7hQmLKXGnQ37NumwuERqNZEyCUN4,3532
|
|
34
44
|
cucu/lint/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -76,8 +86,8 @@ cucu/steps/tables.js,sha256=Os2a7Fo-cg03XVli7USvcnBVad4N7idXr-HBuzdLvVQ,945
|
|
|
76
86
|
cucu/steps/text_steps.py,sha256=Jj_GHoHeemNwVdUOdqcehArNp7WM-WMjljA4w0pLXuw,2576
|
|
77
87
|
cucu/steps/variable_steps.py,sha256=WSctH3_xcxjijGPYZlxp-foC_SIAAKtF__saNtgZJbk,2966
|
|
78
88
|
cucu/steps/webserver_steps.py,sha256=wWkpSvcSMdiskPkh4cqlepWx1nkvEpTU2tRXQmPDbyo,1410
|
|
79
|
-
cucu-1.
|
|
80
|
-
cucu-1.
|
|
81
|
-
cucu-1.
|
|
82
|
-
cucu-1.
|
|
83
|
-
cucu-1.
|
|
89
|
+
cucu-1.1.2.dist-info/METADATA,sha256=dUcLBWY-bMgZQBL79R-zDUhUOGlfLQi1780LUll8a1c,16935
|
|
90
|
+
cucu-1.1.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
91
|
+
cucu-1.1.2.dist-info/entry_points.txt,sha256=YEXTyEfIZbcV0GJ9R3Gfu3j6DcOJJK7_XHkJqE3Yiao,39
|
|
92
|
+
cucu-1.1.2.dist-info/licenses/LICENSE,sha256=WfgJYF9EaQoL_OeWr2Qd0MxhhFegDfzWSUmvDTwFxis,1721
|
|
93
|
+
cucu-1.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|