From 6771089b3f66f885307d675cdda2ecfabe6da9d8 Mon Sep 17 00:00:00 2001
From: Cedric <95940265+ddl-cedricyoung@users.noreply.github.com>
Date: Wed, 26 Apr 2023 14:17:06 -0700
Subject: [PATCH] QE-11002 better reports part 2 (#325)
HTML scenario report
1. add variable resolution to output
![image](https://user-images.githubusercontent.com/95940265/234357886-bf34daeb-545c-4d51-bf72-731215545d46.png)
2. add comment "step" to report
![image](https://user-images.githubusercontent.com/95940265/234358389-d75630c8-b451-4abb-ab14-beb655586450.png)
3. add Flat link
![image](https://user-images.githubusercontent.com/95940265/234357808-2eed3a4d-2a0a-402f-8127-094c8a7d041c.png)
5. disable recording env vars by default in run_details.json
![image](https://user-images.githubusercontent.com/95940265/234701469-1dccdd4c-64a6-426b-b2cf-81895b48a3f7.png)
6. use dark theme for log colors since we are using Behave's builtin
colors
![image](https://user-images.githubusercontent.com/95940265/234358520-88fc5e9c-ba17-4920-bc51-e219dff973f7.png)
7. tighten up vertical spacing
---
.gitignore | 1 -
data/features/with_secret/cucurc.yml | 1 +
.../scenario_with_comments.feature | 17 ++++++++
data/unit/ansi.log.html | 4 +-
features/cli/report_basics.feature | 21 +++++++++-
pyproject.toml | 2 +-
src/cucu/behave_tweaks.py | 23 +++++------
src/cucu/cli/core.py | 10 +++++
src/cucu/cli/run.py | 7 +++-
src/cucu/config.py | 1 +
src/cucu/formatter/json.py | 39 ++++++++++++++-----
src/cucu/reporter/html.py | 5 ++-
src/cucu/reporter/parser.py | 7 +++-
src/cucu/reporter/templates/scenario.html | 32 ++++++++++++---
src/cucu/steps/__init__.py | 1 +
src/cucu/steps/comment_steps.py | 17 ++++++++
16 files changed, 153 insertions(+), 35 deletions(-)
create mode 100644 data/features/with_secret/cucurc.yml
create mode 100644 data/features/with_secret/scenario_with_comments.feature
create mode 100644 src/cucu/steps/comment_steps.py
diff --git a/.gitignore b/.gitignore
index 7f72e977..1a93ea16 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,7 +3,6 @@ dist
.coverage.*
.DS_Store
*.egg-info
-cucurc.yml
htmlcov
.nox
terraform.tfstate
diff --git a/data/features/with_secret/cucurc.yml b/data/features/with_secret/cucurc.yml
new file mode 100644
index 00000000..d32eda46
--- /dev/null
+++ b/data/features/with_secret/cucurc.yml
@@ -0,0 +1 @@
+CUCU_SECRETS: MY_SECRET
diff --git a/data/features/with_secret/scenario_with_comments.feature b/data/features/with_secret/scenario_with_comments.feature
new file mode 100644
index 00000000..596b66aa
--- /dev/null
+++ b/data/features/with_secret/scenario_with_comments.feature
@@ -0,0 +1,17 @@
+Feature: Feature with comments
+
+ Scenario: Scenario with comments
+ * # First comment
+ Given I set the variable "FOO" to "bar"
+ And I echo "{FOO}"
+ Then I echo the following
+ """
+ This is a multiline text that
+ can go on for a few lines
+ and print variables like FOO={FOO}
+ """
+
+ * # Second comment about
+ When I set the variable "MY_SECRET" to "buzz"
+ * # Comment about {MY_SECRET}
+ Then I echo "{MY_SECRET}"
diff --git a/data/unit/ansi.log.html b/data/unit/ansi.log.html
index 3719dca4..85fc3667 100644
--- a/data/unit/ansi.log.html
+++ b/data/unit/ansi.log.html
@@ -1,4 +1,4 @@
-
+
Scenario: Just a scenario that opens a web page
Given I start a webserver at directory "data/www" and save the port to the variable "PORT"
@@ -39,4 +39,4 @@
Took 0m4.773s
Error: test run failed, see above for details
-
+
diff --git a/features/cli/report_basics.feature b/features/cli/report_basics.feature
index 7084b33b..1fa6ebe5 100644
--- a/features/cli/report_basics.feature
+++ b/features/cli/report_basics.feature
@@ -30,6 +30,25 @@ Feature: Report basics
[\s\S]*
"""
+ Scenario: User can run a test and see extended output
+ Given I run the command "cucu run data/features/with_secret/scenario_with_comments.feature --results {CUCU_RESULTS_DIR}/browser-results --env CUCU_BROKEN_IMAGES_PAGE_CHECK=disabled" and expect exit code "0"
+ And I run the command "cucu report {CUCU_RESULTS_DIR}/browser-results --output {CUCU_RESULTS_DIR}/browser-report" and expect exit code "0"
+ And I start a webserver at directory "{CUCU_RESULTS_DIR}/browser-report/" and save the port to the variable "PORT"
+ And I open a browser at the url "http://{HOST_ADDRESS}:{PORT}/flat.html"
+ And I wait to click the link "Scenario with comments"
+ And I wait to click the button "show images"
+
+ * # Can see inline comments
+ Then I wait to see the text "# First comment"
+ And I should see the text "# Second comment"
+ And I should see the text "# Comment about \{MY_SECRET\}"
+
+ * # Can see variable interpolation
+ Then I wait to see the text "# FOO=\"bar\""
+
+ * # Cannot see secrets in variable interpolation
+ Then I wait to see the text "# MY_SECRET=\"****\""
+
@QE-6852
Scenario: User can run a multi scenario test with web steps and generate report with a shareable url
Given I run the command "cucu run data/features/multiple_scenarios_with_browser_steps.feature --env CUCU_BROKEN_IMAGES_PAGE_CHECK=disabled --results {CUCU_RESULTS_DIR}/multi-scenario-browser-results" and expect exit code "0"
@@ -265,7 +284,7 @@ Feature: Report basics
When I click the button "Feature with failing scenario with web"
Then I should see a table that matches the following:
| Offset | Scenario | Steps | Status | Duration |
- | .* | Just a scenario that opens a web page | 3 | failed | .* |
+ | .* | Just a scenario that opens a web page | 3 | failed | .* |
When I click the button "Just a scenario that opens a web page"
And I wait to click the button "show images"
And I should see the image with the alt text "And I should see the text \"inexistent\""
diff --git a/pyproject.toml b/pyproject.toml
index afefec39..a1536a5c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "cucu"
-version = "0.126.0"
+version = "0.127.0"
license = "MIT"
description = ""
authors = ["Rodney Gomes "]
diff --git a/src/cucu/behave_tweaks.py b/src/cucu/behave_tweaks.py
index c192cec6..42d63db7 100644
--- a/src/cucu/behave_tweaks.py
+++ b/src/cucu/behave_tweaks.py
@@ -126,22 +126,23 @@ def inner_step(*args, **kwargs):
def hide_secrets(line):
- secrets = CONFIG["CUCU_SECRETS"]
+ secret_keys = [
+ x.strip()
+ for x in CONFIG.get("CUCU_SECRETS", "").split(",")
+ if x.strip()
+ ]
+ secret_values = [CONFIG.get(x) for x in secret_keys if CONFIG.get(x)]
# here's where we can hide secrets
- for secret in secrets.split(","):
- if secret is not None:
- secret = secret.strip()
- value = CONFIG.get(secret)
+ for value in secret_values:
- if value is not None:
- replacement = "*" * len(value)
+ replacement = "*" * len(value)
- if isinstance(line, bytes):
- value = bytes(value, "utf8")
- replacement = bytes(replacement, "utf8")
+ if isinstance(line, bytes):
+ value = bytes(value, "utf8")
+ replacement = bytes(replacement, "utf8")
- line = line.replace(value, replacement)
+ line = line.replace(value, replacement)
return line
diff --git a/src/cucu/cli/core.py b/src/cucu/cli/core.py
index 42615d82..496f7a0b 100644
--- a/src/cucu/cli/core.py
+++ b/src/cucu/cli/core.py
@@ -141,6 +141,12 @@ def main():
default=False,
help="when set we will not remove any existing results directory",
)
+@click.option(
+ "--record-env-vars",
+ default=False,
+ is_flag=True,
+ help="when set will record shell environment variables to debug file: run_details.json",
+)
@click.option(
"--report",
default="report",
@@ -211,6 +217,7 @@ def run(
logging_level,
periodic_thread_dumper,
preserve_results,
+ record_env_vars,
report,
report_only_failures,
results,
@@ -280,6 +287,9 @@ def run(
if report_only_failures:
os.environ["CUCU_REPORT_ONLY_FAILURES"] = "true"
+ if record_env_vars:
+ os.environ["CUCU_RECORD_ENV_VARS"] = "true"
+
if not dry_run:
write_run_details(results, filepath)
diff --git a/src/cucu/cli/run.py b/src/cucu/cli/run.py
index ffb7e59d..9d9677b5 100644
--- a/src/cucu/cli/run.py
+++ b/src/cucu/cli/run.py
@@ -181,10 +181,15 @@ def write_run_details(results, filepath):
if os.path.exists(run_details_filepath):
return
+ if CONFIG["CUCU_RECORD_ENV_VARS"]:
+ env_values = dict(os.environ)
+ else:
+ env_values = "To enable use the --record-env-vars flag"
+
run_details = {
"filepath": filepath,
"full_arguments": sys.argv,
- "env": dict(os.environ),
+ "env": env_values,
"date": datetime.now().isoformat(),
}
diff --git a/src/cucu/config.py b/src/cucu/config.py
index e2fe9eaf..16dda0a3 100644
--- a/src/cucu/config.py
+++ b/src/cucu/config.py
@@ -174,6 +174,7 @@ def resolve(self, string):
if value is None:
value = ""
+ # print directly to the output stream, which was taken over in behave_tweaks
print(f'WARNING variable "{var_name}" is undefined')
string = string.replace("{" + var_name + "}", str(value))
diff --git a/src/cucu/formatter/json.py b/src/cucu/formatter/json.py
index 95bb1bd2..a579bb60 100644
--- a/src/cucu/formatter/json.py
+++ b/src/cucu/formatter/json.py
@@ -145,15 +145,6 @@ def match(self, match):
def result(self, step):
steps = self.current_feature_element["steps"]
-
- stdout = None
- if "stdout" in step.__dict__ and step.stdout != []:
- stdout = ["".join(step.stdout)]
-
- stderr = None
- if "stderr" in step.__dict__ and step.stderr != []:
- stderr = ["".join(step.stderr)]
-
step_index = 0
for other_step in self.steps:
if other_step.unique_id == step.unique_id:
@@ -167,6 +158,36 @@ def result(self, step):
if step.status.name in ["passed", "failed"]:
timestamp = step.start_timestamp
+ step_variables = CONFIG.expand(step.name)
+
+ if step.text:
+ step_variables.update(CONFIG.expand(step.text))
+
+ if step.table:
+ for row in step.table.original.rows:
+ for value in row:
+ step_variables.update(CONFIG.expand(value))
+
+ if step_variables:
+ expanded = " ".join(
+ [
+ f'{key}="{value}"'
+ for (key, value) in step_variables.items()
+ ]
+ )
+ padding = f" {' '*(len('Given')-len(step.keyword))}"
+ step.stdout.insert(
+ 0, f"{padding}# {behave_tweaks.hide_secrets(expanded)}\n"
+ )
+
+ stdout = None
+ if "stdout" in step.__dict__ and step.stdout != []:
+ stdout = ["".join(step.stdout).rstrip()]
+
+ stderr = None
+ if "stderr" in step.__dict__ and step.stderr != []:
+ stderr = ["".join(step.stderr).rstrip()]
+
steps[step_index]["result"] = {
"stdout": stdout,
"stderr": stderr,
diff --git a/src/cucu/reporter/html.py b/src/cucu/reporter/html.py
index 96dab7e9..d47f06b7 100644
--- a/src/cucu/reporter/html.py
+++ b/src/cucu/reporter/html.py
@@ -20,7 +20,7 @@ def escape(data):
if data is None:
return None
- return escape_(data, {'"': """})
+ return escape_(data, {'"': """}).rstrip()
def process_tags(element):
@@ -147,6 +147,9 @@ def generate(results, basepath, only_failures=False):
)
image_filepath = os.path.join(scenario_filepath, image_filename)
+ if step["name"].startswith("#"):
+ step["heading_level"] = "h4"
+
if os.path.exists(image_filepath):
step["image"] = urllib.parse.quote(image_filename)
diff --git a/src/cucu/reporter/parser.py b/src/cucu/reporter/parser.py
index 9605b8f5..3ca1cfe3 100644
--- a/src/cucu/reporter/parser.py
+++ b/src/cucu/reporter/parser.py
@@ -24,10 +24,13 @@
def parse_log_to_html(input: str) -> str:
- """ "
+ """
Parse an ansi color log to html
"""
- result = f"\n{REGEX.sub(lambda match: TRANSLATION[match.group(0)], html.escape(input, quote=False))}\n
\n"
+
+ body_start = '' # use dark bg since colors are from behave
+ body_end = "\n"
+ result = f"{body_start}\n{REGEX.sub(lambda match: TRANSLATION[match.group(0)], html.escape(input, quote=False))}\n
{body_end}"
if ESC_SEQ in result:
lines = "\n".join([x for x in result.split("\n") if ESC_SEQ in x])
logger.info(f"Detected unmapped ansi escape code!:\n{lines}")
diff --git a/src/cucu/reporter/templates/scenario.html b/src/cucu/reporter/templates/scenario.html
index 85b4abff..5fcb77b9 100644
--- a/src/cucu/reporter/templates/scenario.html
+++ b/src/cucu/reporter/templates/scenario.html
@@ -7,7 +7,10 @@
-
- Index
+ Flat
+
+ -
+ Index
-
Feature
@@ -88,6 +91,16 @@
{% set step_timing = "" %}
{% endif %}
{% set step_keyword = step_prefix + step['keyword'].rjust(6, ' ') %}
+
+ {% if step['heading_level'] %}
+
+
+ 🔗
+ <{{ step['heading_level'] }} style="display: contents;" title="{{ escape(step['name']) }}"> {{ escape(step['name']) }}{{ step['heading_level'] }}>
+ |
+
+
+ {% else %}
🔗
@@ -95,14 +108,15 @@
|
{{ step_timing }} |
+ {% endif %}
{% if step['text'] or step['table'] %}
{% if step['text'] %}
- {{ escape(step['text']) }}
+ {{ escape(step['text']) }}
{% endif %}
{% if step['table'] %}
- {{ escape(step['table']) }}
+ {{ escape(step['table']) }}
{% endif %}
|
{% endif %}
@@ -111,15 +125,21 @@
{% if step['result']['stdout'] %}
- {{ escape("\n".join(step['result']['stdout'])) }}
+ {{ escape("\n".join(step['result']['stdout'])) }}
{% endif %}
{% if step['image'] is defined %}
- ![{{ step_name }} {{ step_name }}]({{ step["image"] }})
+ {% if step['result']['stdout'] %}
+
+ {% endif %}
+
{% endif %}
{% if step['result']['error_message'] is defined %}
- {{ escape("\n".join(step['result']['error_message'])) }}
+ {% if step['image'] is defined %}
+
+ {% endif %}
+ {{ escape("\n".join(step['result']['error_message'])) }}
{% endif %}
|
{% endif %}
diff --git a/src/cucu/steps/__init__.py b/src/cucu/steps/__init__.py
index f6afa21b..970a15f4 100644
--- a/src/cucu/steps/__init__.py
+++ b/src/cucu/steps/__init__.py
@@ -2,6 +2,7 @@
# nopycln: file
import cucu.hooks
+import cucu.steps.comment_steps
import cucu.steps.base_steps
import cucu.steps.browser_steps
diff --git a/src/cucu/steps/comment_steps.py b/src/cucu/steps/comment_steps.py
new file mode 100644
index 00000000..f2db69ab
--- /dev/null
+++ b/src/cucu/steps/comment_steps.py
@@ -0,0 +1,17 @@
+from behave import use_step_matcher
+
+from cucu import step
+
+use_step_matcher("re") # use regex to match comments
+
+
+@step("#.*")
+def comment_step(ctx):
+ """
+ A no-op step so that we can see "comments" in results and report.
+ Usage: add `* #` to the line you want to show up.
+ """
+ pass
+
+
+use_step_matcher("parse") # set this back to cucu's default matcher parser