steppy-dev 0.3.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.
- steppy/CHANGELOG.md +862 -0
- steppy/__init__.py +9 -0
- steppy/__main__.py +13 -0
- steppy/_cli_common.py +183 -0
- steppy/_docker/Dockerfile.installed +53 -0
- steppy/_parallel/__init__.py +7 -0
- steppy/_parallel/compose.py +349 -0
- steppy/_parallel/event_bus.py +418 -0
- steppy/_parallel/failures.py +267 -0
- steppy/_parallel/fingerprint.py +141 -0
- steppy/_parallel/image.py +380 -0
- steppy/_parallel/lastfailed.py +125 -0
- steppy/_parallel/lifecycle.py +90 -0
- steppy/_parallel/preauth.py +109 -0
- steppy/_parallel/progress.py +101 -0
- steppy/_parallel/queue.py +385 -0
- steppy/_parallel/renderer.py +489 -0
- steppy/_parallel/retry.py +316 -0
- steppy/_player_bundle/THIRD_PARTY_LICENSES.txt +158 -0
- steppy/_player_bundle/__init__.py +100 -0
- steppy/_player_bundle/steppy-cursor-legacy.js +2 -0
- steppy/_player_bundle/steppy-cursor-legacy.js.map +1 -0
- steppy/_player_bundle/steppy-player.js +5419 -0
- steppy/_player_bundle/steppy-player.js.map +1 -0
- steppy/_player_bundle/steppy-video.js +472 -0
- steppy/_player_bundle/steppy-video.js.map +1 -0
- steppy/_templating.py +117 -0
- steppy/assertions.py +251 -0
- steppy/auth.py +493 -0
- steppy/build.py +2798 -0
- steppy/build_models.py +669 -0
- steppy/buildid.py +134 -0
- steppy/bundle/__init__.py +47 -0
- steppy/bundle/archive.py +239 -0
- steppy/bundle/classify.py +329 -0
- steppy/bundle/create.py +490 -0
- steppy/bundle/hashing.py +28 -0
- steppy/bundle/manifest.py +334 -0
- steppy/bundle/results.py +546 -0
- steppy/bundle_cli.py +455 -0
- steppy/captures.py +207 -0
- steppy/cli.py +2489 -0
- steppy/cloud/__init__.py +58 -0
- steppy/cloud/client.py +267 -0
- steppy/cloud/upload.py +497 -0
- steppy/compat.py +109 -0
- steppy/config.py +231 -0
- steppy/config_schema.py +87 -0
- steppy/contracts/__init__.py +54 -0
- steppy/contracts/bundle.py +215 -0
- steppy/contracts/report.py +346 -0
- steppy/contracts/tours.py +505 -0
- steppy/coordinator.py +1352 -0
- steppy/coverage/__init__.py +9 -0
- steppy/coverage/gaps.py +190 -0
- steppy/coverage/manifest.py +193 -0
- steppy/coverage/merge.py +363 -0
- steppy/coverage/normalize.py +174 -0
- steppy/coverage/report.py +178 -0
- steppy/coverage/sourcemap.py +219 -0
- steppy/coverage/store.py +473 -0
- steppy/coverage/triage.py +151 -0
- steppy/coverage_cli.py +265 -0
- steppy/durations.py +48 -0
- steppy/exclusive.py +178 -0
- steppy/flaky_cli.py +169 -0
- steppy/generator.py +633 -0
- steppy/guide.py +1467 -0
- steppy/history.py +352 -0
- steppy/init_cli.py +330 -0
- steppy/locales.py +282 -0
- steppy/migrate/__init__.py +35 -0
- steppy/migrate/apply.py +194 -0
- steppy/migrate/plan.py +199 -0
- steppy/migrate/rules.py +465 -0
- steppy/migrate/verify.py +163 -0
- steppy/migrate_cli.py +152 -0
- steppy/models.py +2539 -0
- steppy/naming.py +37 -0
- steppy/parser.py +475 -0
- steppy/pdf.py +193 -0
- steppy/presets.py +284 -0
- steppy/record_cli.py +796 -0
- steppy/report/__init__.py +37 -0
- steppy/report/assets.py +197 -0
- steppy/report/build.py +261 -0
- steppy/report/coverage_source.py +277 -0
- steppy/report/model.py +423 -0
- steppy/report/render.py +303 -0
- steppy/report/source.py +238 -0
- steppy/report_cli.py +173 -0
- steppy/runmeta.py +114 -0
- steppy/selection.py +183 -0
- steppy/sitemap.py +205 -0
- steppy/skills/steppy-app-tours/SKILL.md +448 -0
- steppy/skills/steppy-ci-report/SKILL.md +567 -0
- steppy/skills/steppy-scenario-author/SKILL.md +2176 -0
- steppy/skills/steppy-template/SKILL.md +676 -0
- steppy/templates/_steppy_events.py +291 -0
- steppy/templates/_steppy_tours.py +404 -0
- steppy/templates/angular/component-interactive.ts.j2 +109 -0
- steppy/templates/angular/component-video.ts.j2 +112 -0
- steppy/templates/angular/component.ts.j2 +229 -0
- steppy/templates/angular/content.ts.j2 +31 -0
- steppy/templates/angular/gitignore.j2 +3 -0
- steppy/templates/angular/group.component.ts.j2 +39 -0
- steppy/templates/angular/onboarding.component.ts.j2 +43 -0
- steppy/templates/angular/routes.ts.j2 +34 -0
- steppy/templates/ci/github-actions.yml +284 -0
- steppy/templates/ci/gitlab-ci.yml +178 -0
- steppy/templates/conftest.py.j2 +2405 -0
- steppy/templates/guide.book.html.j2 +145 -0
- steppy/templates/guide.fragment.html.j2 +257 -0
- steppy/templates/guide.html.j2 +301 -0
- steppy/templates/guide.md.j2 +18 -0
- steppy/templates/init/config.yaml +124 -0
- steppy/templates/init/credentials.yaml.example +30 -0
- steppy/templates/init/scenarios/example--first-flow.md +53 -0
- steppy/templates/report/_head.html.j2 +14 -0
- steppy/templates/report/_mark.html.j2 +3 -0
- steppy/templates/report/coverage.html.j2 +124 -0
- steppy/templates/report/index.html.j2 +203 -0
- steppy/templates/report/report.css +401 -0
- steppy/templates/report/scenario.html.j2 +137 -0
- steppy/templates/static/_headers.j2 +30 -0
- steppy/templates/static/_mode_links.html.j2 +22 -0
- steppy/templates/static/_seo_head.html.j2 +37 -0
- steppy/templates/static/cursor.js.j2 +10 -0
- steppy/templates/static/group.html.j2 +78 -0
- steppy/templates/static/guides.css +678 -0
- steppy/templates/static/index.html.j2 +79 -0
- steppy/templates/static/interactive.html.j2 +100 -0
- steppy/templates/static/onboarding.html.j2 +64 -0
- steppy/templates/static/page.html.j2 +145 -0
- steppy/templates/static/robots.txt.j2 +7 -0
- steppy/templates/static/video.html.j2 +135 -0
- steppy/templates/test_scenario.py.j2 +767 -0
- steppy/testids.py +166 -0
- steppy/totp.py +26 -0
- steppy/tour_spec.py +163 -0
- steppy/tours/__init__.py +22 -0
- steppy/tours/actions.py +402 -0
- steppy/tours/compiler.py +144 -0
- steppy/tours/diagnostics.py +137 -0
- steppy/tours/emit.py +239 -0
- steppy/tours/ids.py +40 -0
- steppy/tours/inference.py +399 -0
- steppy/tours/model.py +120 -0
- steppy/tours/paths.py +126 -0
- steppy/tours/pipeline.py +277 -0
- steppy/tours/publish.py +124 -0
- steppy/tours/replay.py +234 -0
- steppy/tours/routes.py +175 -0
- steppy/tours/settings.py +164 -0
- steppy/tours/text.py +237 -0
- steppy/tours_cli.py +219 -0
- steppy/tts/__init__.py +280 -0
- steppy/tts/cache.py +185 -0
- steppy/tts/presets.py +125 -0
- steppy/tts/providers/__init__.py +6 -0
- steppy/tts/providers/http.py +184 -0
- steppy/tts/providers/local.py +207 -0
- steppy/tts/providers/null.py +28 -0
- steppy/tts/registry.py +201 -0
- steppy/tts/track.py +158 -0
- steppy/tts/types.py +94 -0
- steppy/update_cli.py +460 -0
- steppy/video.py +730 -0
- steppy/video_cache.py +239 -0
- steppy/video_cli.py +271 -0
- steppy/worker.py +301 -0
- steppy_dev-0.3.0.dist-info/METADATA +1335 -0
- steppy_dev-0.3.0.dist-info/RECORD +177 -0
- steppy_dev-0.3.0.dist-info/WHEEL +4 -0
- steppy_dev-0.3.0.dist-info/entry_points.txt +2 -0
- steppy_dev-0.3.0.dist-info/licenses/LICENSE +202 -0
- steppy_dev-0.3.0.dist-info/licenses/NOTICE +13 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# {{ title }}
|
|
2
|
+
|
|
3
|
+
> Guide pour l'application **{{ role }}**
|
|
4
|
+
|
|
5
|
+
{% for step in steps %}
|
|
6
|
+
## Étape {{ step.index }} : {{ step.title }}
|
|
7
|
+
|
|
8
|
+
{% if step.subtitle %}
|
|
9
|
+
*{{ step.subtitle }}*
|
|
10
|
+
|
|
11
|
+
{% endif %}
|
|
12
|
+
{% if step.text %}
|
|
13
|
+
{{ step.text }}
|
|
14
|
+
|
|
15
|
+
{% endif %}
|
|
16
|
+

|
|
17
|
+
|
|
18
|
+
{% endfor %}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# yaml-language-server: $schema=./steppy-config.schema.json
|
|
2
|
+
#
|
|
3
|
+
# Steppy configuration — created by `steppy init`.
|
|
4
|
+
#
|
|
5
|
+
# A "role" is one front-end app / persona you test. This starter uses
|
|
6
|
+
# admin / editor / viewer; rename them (and their URLs) to fit your product,
|
|
7
|
+
# then add/remove roles freely.
|
|
8
|
+
#
|
|
9
|
+
# Credentials use ${ENV_VAR} references so no secrets live in this committed
|
|
10
|
+
# file. Set the vars in your environment/CI, or copy credentials.yaml.example to
|
|
11
|
+
# credentials.yaml (gitignored) and fill in real values. ${VAR:-default}
|
|
12
|
+
# supplies a fallback. See SECURITY.md in the Steppy repo for details.
|
|
13
|
+
#
|
|
14
|
+
# The `# yaml-language-server` line above wires up autocomplete + inline typo
|
|
15
|
+
# checking in editors with the YAML extension (VS Code, etc.).
|
|
16
|
+
apps:
|
|
17
|
+
admin:
|
|
18
|
+
name: "My App — Admin"
|
|
19
|
+
url: "http://localhost:3000"
|
|
20
|
+
auth:
|
|
21
|
+
# `form` = generic hosted-login form-fill (works with any IdP that has a
|
|
22
|
+
# fillable login form: Auth0, Keycloak, …). Other modes: storage-state,
|
|
23
|
+
# bypass, token. See STEPPY-REFERENCE.md.
|
|
24
|
+
mode: form
|
|
25
|
+
credentials:
|
|
26
|
+
username: "${STEPPY_ADMIN_USERNAME}"
|
|
27
|
+
password: "${STEPPY_ADMIN_PASSWORD}"
|
|
28
|
+
# Per-locale localStorage values, set before screenshots so the app renders
|
|
29
|
+
# in the right language during `steppy run --guide`.
|
|
30
|
+
i18n:
|
|
31
|
+
localstorage:
|
|
32
|
+
key: "app-language"
|
|
33
|
+
values:
|
|
34
|
+
en: "en"
|
|
35
|
+
fr: "fr"
|
|
36
|
+
editor:
|
|
37
|
+
name: "My App — Editor"
|
|
38
|
+
url: "http://localhost:3001"
|
|
39
|
+
auth:
|
|
40
|
+
mode: form
|
|
41
|
+
credentials:
|
|
42
|
+
username: "${STEPPY_EDITOR_USERNAME}"
|
|
43
|
+
password: "${STEPPY_EDITOR_PASSWORD}"
|
|
44
|
+
viewer:
|
|
45
|
+
name: "My App — Viewer"
|
|
46
|
+
url: "http://localhost:3002"
|
|
47
|
+
auth:
|
|
48
|
+
mode: form
|
|
49
|
+
credentials:
|
|
50
|
+
username: "${STEPPY_VIEWER_USERNAME}"
|
|
51
|
+
password: "${STEPPY_VIEWER_PASSWORD}"
|
|
52
|
+
|
|
53
|
+
# Auth settings. The `form:` block holds the three login-form selectors; name an
|
|
54
|
+
# IdP `preset:` (auth0 | keycloak) to fill them at once.
|
|
55
|
+
auth:
|
|
56
|
+
storage_state_dir: "./.auth"
|
|
57
|
+
storage_state_ttl: 3600 # seconds (1 hour)
|
|
58
|
+
form:
|
|
59
|
+
# preset: keycloak # -> #username / #password / #kc-login
|
|
60
|
+
username_selector: "input[name='username'], input[name='email'], #username"
|
|
61
|
+
password_selector: "input[name='password'], #password"
|
|
62
|
+
submit_selector: "button[type='submit'], button[name='action'][value='default']"
|
|
63
|
+
post_login_url_pattern: "**/callback**"
|
|
64
|
+
consent_selector: null
|
|
65
|
+
|
|
66
|
+
# Supported locales for guide generation. Add any locale code here.
|
|
67
|
+
locales:
|
|
68
|
+
- en
|
|
69
|
+
- fr
|
|
70
|
+
|
|
71
|
+
default_locale: en
|
|
72
|
+
|
|
73
|
+
screenshot:
|
|
74
|
+
viewport:
|
|
75
|
+
width: 1280
|
|
76
|
+
height: 720
|
|
77
|
+
format: png
|
|
78
|
+
full_page: false
|
|
79
|
+
highlight: true
|
|
80
|
+
highlight_color: "rgba(255, 0, 0, 0.3)"
|
|
81
|
+
highlight_border: "2px solid red"
|
|
82
|
+
|
|
83
|
+
timing:
|
|
84
|
+
navigation_wait: 2000
|
|
85
|
+
action_wait: 500
|
|
86
|
+
sqs_wait: 5000
|
|
87
|
+
step_timeout: 30000
|
|
88
|
+
|
|
89
|
+
# Where artifacts are written and read back, resolved against the project root.
|
|
90
|
+
# `screenshots_dir` is honoured by the capture side and by every reader
|
|
91
|
+
# (`steppy guide`, `steppy build`, `steppy bundle`); `guides_dir` is where
|
|
92
|
+
# `steppy guide` writes. Pointing either outside `output/` works, but only
|
|
93
|
+
# `output/` is bundled, uploaded by the CI template, gitignored, and mounted
|
|
94
|
+
# back to the host by `steppy run --workers N`.
|
|
95
|
+
output:
|
|
96
|
+
screenshots_dir: "./output/screenshots"
|
|
97
|
+
guides_dir: "./output/guides"
|
|
98
|
+
|
|
99
|
+
# Optional blocks not shown here (all off / absent by default):
|
|
100
|
+
# report: JUnit XML, HTML report, Playwright trace + video on failure, retries
|
|
101
|
+
# parallel: `steppy run --workers N` — Docker-backed parallel execution, plus the
|
|
102
|
+
# retry pass (flaky vs. real failures), circuit breaker and auth isolation
|
|
103
|
+
# coverage: `steppy run --coverage` + the `steppy coverage` gap report. Set
|
|
104
|
+
# `coverage.non_ui` there (health checks, webhooks, cron callbacks)
|
|
105
|
+
# or CI counts them against you — triage flags live in the
|
|
106
|
+
# gitignored .steppy/coverage.db and a fresh checkout has none.
|
|
107
|
+
# security: shell-capture gate; see also `screenshot.mask` for redaction
|
|
108
|
+
# pdf: paper settings for `--pdf` / `--handbook`
|
|
109
|
+
# video: `--video` narrated MP4 export, incl. the `video.tts` provider
|
|
110
|
+
# (none | piper | espeak | elevenlabs | openai | azure | google | http).
|
|
111
|
+
# Run `steppy video check` to verify the toolchain.
|
|
112
|
+
# tours: in-app tours from `tour:`-enabled scenarios — `steppy generate`
|
|
113
|
+
# writes tour JSON into your app's assets plus a CI replay per tour.
|
|
114
|
+
# Minimal: `tours: {enabled: true, targets: [{type: app-assets,
|
|
115
|
+
# path: ../your-app/public/tours}]}`; CI gate: `steppy tours
|
|
116
|
+
# --check`. The overlay's buttons ship in en/fr/nl/de (other tour
|
|
117
|
+
# languages need the host's `strings`). See docs/tours.md in the
|
|
118
|
+
# Steppy repository.
|
|
119
|
+
# cloud: `url:` of a Steppy control plane, for `steppy bundle upload`.
|
|
120
|
+
# The URL only — the token is read from STEPPY_CLOUD_TOKEN (or
|
|
121
|
+
# --token) and has no config key on purpose, because this file is
|
|
122
|
+
# committed. Everything else here works with no plane at all.
|
|
123
|
+
# Each is documented with commented examples in the Steppy repo's own config.yaml,
|
|
124
|
+
# and your editor will autocomplete them from the $schema line at the top.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# credentials.yaml — LOCAL, GIT-IGNORED credential overlay.
|
|
2
|
+
#
|
|
3
|
+
# Copy this file to `credentials.yaml` (same directory as config.yaml) and fill
|
|
4
|
+
# in real values. It is deep-merged ON TOP of config.yaml at load time, so it
|
|
5
|
+
# only needs the `apps.<role>.auth.credentials` blocks you want to override —
|
|
6
|
+
# everything else is inherited from config.yaml.
|
|
7
|
+
#
|
|
8
|
+
# `credentials.yaml` is listed in .gitignore and must never be committed. Prefer
|
|
9
|
+
# this file (or ${ENV_VAR} references in config.yaml) over inline plaintext
|
|
10
|
+
# secrets. See SECURITY.md for the full credential-handling guidance.
|
|
11
|
+
#
|
|
12
|
+
# You can ALSO keep credentials.yaml env-referenced if you like — the same
|
|
13
|
+
# ${VAR} / ${VAR:-default} interpolation applies to this overlay.
|
|
14
|
+
|
|
15
|
+
apps:
|
|
16
|
+
admin:
|
|
17
|
+
auth:
|
|
18
|
+
credentials:
|
|
19
|
+
username: "test-admin@example.com"
|
|
20
|
+
password: "change-me"
|
|
21
|
+
editor:
|
|
22
|
+
auth:
|
|
23
|
+
credentials:
|
|
24
|
+
username: "test-editor@example.com"
|
|
25
|
+
password: "change-me"
|
|
26
|
+
viewer:
|
|
27
|
+
auth:
|
|
28
|
+
credentials:
|
|
29
|
+
username: "test-viewer@example.com"
|
|
30
|
+
password: "change-me"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Create a project
|
|
3
|
+
description: A starter scenario — an admin creates a new project
|
|
4
|
+
tags:
|
|
5
|
+
- e2e
|
|
6
|
+
- user-guide-admin
|
|
7
|
+
priority: high
|
|
8
|
+
guide:
|
|
9
|
+
enabled: true
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# This is a starter scenario written by `steppy init`. Each `###` heading is a
|
|
13
|
+
# step; each `- key: value` bullet is a field. Replace the selectors, URLs, and
|
|
14
|
+
# text with your app's. Run `steppy validate` to check it, then `steppy generate`
|
|
15
|
+
# to produce the Playwright test. See STEPPY-REFERENCE.md for the full grammar.
|
|
16
|
+
|
|
17
|
+
### 1. Admin opens the projects list
|
|
18
|
+
- role: admin
|
|
19
|
+
- action: navigate
|
|
20
|
+
- target: /projects
|
|
21
|
+
- screenshot: admin
|
|
22
|
+
- guide-text:
|
|
23
|
+
en: "From the main menu, open 'Projects' to see all projects."
|
|
24
|
+
fr: "Depuis le menu principal, ouvrez 'Projets' pour voir tous les projets."
|
|
25
|
+
|
|
26
|
+
### 2. Admin starts a new project
|
|
27
|
+
- role: admin
|
|
28
|
+
- action: click
|
|
29
|
+
- selector: [data-testid="new-project-btn"]
|
|
30
|
+
- screenshot: admin
|
|
31
|
+
- guide-text:
|
|
32
|
+
en: "Click 'New project' in the top-right corner."
|
|
33
|
+
fr: "Cliquez sur 'Nouveau projet' en haut à droite."
|
|
34
|
+
|
|
35
|
+
### 3. Admin names the project
|
|
36
|
+
- role: admin
|
|
37
|
+
- action: fill
|
|
38
|
+
- selector: [data-testid="project-name"]
|
|
39
|
+
- value: "Website redesign"
|
|
40
|
+
- screenshot: admin
|
|
41
|
+
- guide-text:
|
|
42
|
+
en: "Give the project a clear, descriptive name."
|
|
43
|
+
fr: "Donnez au projet un nom clair et descriptif."
|
|
44
|
+
|
|
45
|
+
### 4. Admin saves the project
|
|
46
|
+
- role: admin
|
|
47
|
+
- action: click
|
|
48
|
+
- selector: [data-testid="save-project-btn"]
|
|
49
|
+
- expect: contains([data-testid="toast"], "Project created")
|
|
50
|
+
- screenshot: admin
|
|
51
|
+
- guide-text:
|
|
52
|
+
en: "Save to create the project — it now appears in the list."
|
|
53
|
+
fr: "Enregistrez pour créer le projet — il apparaît maintenant dans la liste."
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<meta charset="utf-8">
|
|
2
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
3
|
+
{# Always private. A run report has no SEO value, and ADR 0013 is emphatic
|
|
4
|
+
that a published Pages site means permanently indexed screenshots — so
|
|
5
|
+
there is deliberately no config knob to turn this off. The guides site
|
|
6
|
+
keeps its own SeoConfig for the case where publishing IS the point.
|
|
7
|
+
|
|
8
|
+
This meta is the real control: robots.txt only stops the fetch, and a
|
|
9
|
+
crawler that never fetches the page never learns it is noindex. #}
|
|
10
|
+
<meta name="robots" content="noindex,nofollow,noarchive">
|
|
11
|
+
<title>{{ page_title }}</title>
|
|
12
|
+
<link rel="stylesheet" href="{{ root }}assets/report.css">
|
|
13
|
+
{# The app icon from docs/brand/steppy-icon.svg, inlined so the report stays one self-contained tree. #}
|
|
14
|
+
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220.00 0.00 200.00 200.00%22 width=%22200%22 height=%22200%22 role=%22img%22 aria-label=%22steppy%22%3E%3Ctitle%3Esteppy%3C/title%3E%3Crect width=%22200%22 height=%22200%22 rx=%2250%22 fill=%22%2315332A%22/%3E%3Cg transform=%22translate%2815 15%29 scale%280.85%29%22%3E%3Cpath d=%22M68 40H42V160H68M132 40H158V160H132%22 fill=%22none%22 stroke=%22%23E4E9E1%22 stroke-width=%2218%22 stroke-linejoin=%22miter%22/%3E%3Cpath d=%22M74 102L95 123L130 80%22 fill=%22none%22 stroke=%22%23D2561A%22 stroke-width=%2220%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22/%3E%3C/g%3E%3C/svg%3E">
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
{# The steppy mark (logo track C), inline so the brackets follow the theme
|
|
2
|
+
via report.css; aria-hidden because the h1 beside it carries the text. #}
|
|
3
|
+
<svg class="steppy-mark" viewBox="0 0 200 200" aria-hidden="true" focusable="false"><path class="steppy-mark-brackets" d="M68 40H42V160H68M132 40H158V160H132" fill="none" stroke-width="18" stroke-linejoin="miter"/><path class="steppy-mark-check" d="M74 102L95 123L130 80" fill="none" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
{# The gap buckets. Not line coverage: this answers "which of my declared routes
|
|
2
|
+
and API endpoints has any e2e scenario actually exercised". Autoescape is ON. #}
|
|
3
|
+
<!doctype html>
|
|
4
|
+
<html lang="en">
|
|
5
|
+
<head>
|
|
6
|
+
{% include "report/_head.html.j2" %}
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<header class="steppy-report-header">
|
|
10
|
+
<h1>{% include "report/_mark.html.j2" %}E2E surface coverage</h1>
|
|
11
|
+
<p class="steppy-report-meta steppy-small">
|
|
12
|
+
<span><a href="{{ root }}index.html">← Run report</a></span>
|
|
13
|
+
<span>source: {{ coverage.tier }}</span>
|
|
14
|
+
</p>
|
|
15
|
+
</header>
|
|
16
|
+
|
|
17
|
+
<main class="steppy-report-main">
|
|
18
|
+
|
|
19
|
+
<section class="steppy-report-section">
|
|
20
|
+
<div class="steppy-badge-line">
|
|
21
|
+
{% if coverage.badge.percent is not none %}
|
|
22
|
+
<span class="steppy-pct">{{ coverage.badge.percent }}%</span>
|
|
23
|
+
<span class="steppy-muted">
|
|
24
|
+
{{ coverage.badge.tested }} of {{ coverage.badge.declared }} declared
|
|
25
|
+
{{ coverage.badge.surface if coverage.badge.surface != 'all' else 'endpoints and routes' }}
|
|
26
|
+
a UI test could reach
|
|
27
|
+
</span>
|
|
28
|
+
{% else %}
|
|
29
|
+
<span class="steppy-pct steppy-muted">n/a</span>
|
|
30
|
+
<span class="steppy-muted">
|
|
31
|
+
nothing declared. Configure <code>coverage.apis</code> (and optionally
|
|
32
|
+
<code>coverage.sitemap</code>), then run <code>steppy coverage refresh</code>.
|
|
33
|
+
</span>
|
|
34
|
+
{% endif %}
|
|
35
|
+
</div>
|
|
36
|
+
<p class="steppy-small steppy-muted">
|
|
37
|
+
This is not line coverage. It answers which of the routes and API endpoints you
|
|
38
|
+
<em>declared</em> any scenario actually exercised — the denominator is the declared
|
|
39
|
+
surface minus what is marked non-UI-testable.
|
|
40
|
+
</p>
|
|
41
|
+
{% if coverage.non_ui.declared %}
|
|
42
|
+
<p class="steppy-small">
|
|
43
|
+
{{ coverage.non_ui.declared }} declared item{{ '' if coverage.non_ui.declared == 1 else 's' }}
|
|
44
|
+
discounted as <code>non_ui</code>
|
|
45
|
+
({% for source, count in coverage.non_ui.by_source.items() %}{{ count }} from {{ source }}{% if not loop.last %}, {% endif %}{% endfor %}).
|
|
46
|
+
Committed rules live in <code>coverage.non_ui</code>; a <code>store</code> source is
|
|
47
|
+
a flag set locally with <code>steppy coverage triage set</code>.
|
|
48
|
+
</p>
|
|
49
|
+
{% endif %}
|
|
50
|
+
</section>
|
|
51
|
+
|
|
52
|
+
{% if not coverage.capabilities_strength %}
|
|
53
|
+
<div class="steppy-notice">
|
|
54
|
+
<strong>Assert strength is not shown.</strong> These numbers were rebuilt from the
|
|
55
|
+
run's raw records, and a bundle carries no <code>scenarios/*.md</code> to count assert
|
|
56
|
+
steps in — so every tested endpoint would read as weakly covered, which would be a
|
|
57
|
+
plausible-looking lie. Run <code>steppy coverage report</code> in the project for the
|
|
58
|
+
strong/weak split.
|
|
59
|
+
</div>
|
|
60
|
+
{% endif %}
|
|
61
|
+
|
|
62
|
+
{% for kind in ('endpoints', 'routes') %}
|
|
63
|
+
{% set counts = coverage.counts.get(kind) %}
|
|
64
|
+
{% if counts %}
|
|
65
|
+
<section class="steppy-report-section">
|
|
66
|
+
<h2>{{ kind | capitalize }}</h2>
|
|
67
|
+
<p class="steppy-report-meta">
|
|
68
|
+
<span><strong>{{ counts.declared }}</strong> declared</span>
|
|
69
|
+
<span><strong>{{ counts.tested }}</strong> tested{% if coverage.capabilities_strength %} ({{ counts.tested_strong }} strongly asserted){% endif %}</span>
|
|
70
|
+
<span><strong>{{ counts.declared_unreached }}</strong> never reached ({{ counts.declared_unreached_actionable }} actionable)</span>
|
|
71
|
+
<span><strong>{{ counts.discovered_uncovered }}</strong> seen but weakly covered</span>
|
|
72
|
+
{% if counts.discovered_tested %}<span><strong>{{ counts.discovered_tested }}</strong> tested but undeclared</span>{% endif %}
|
|
73
|
+
</p>
|
|
74
|
+
|
|
75
|
+
{% set unreached = bucket(kind, 'declared_unreached') | rejectattr('triage', 'equalto', 'non_ui') | list %}
|
|
76
|
+
{% if unreached %}
|
|
77
|
+
<h3 class="steppy-small">Declared but never reached — where a new scenario pays off</h3>
|
|
78
|
+
<div class="steppy-scroll">
|
|
79
|
+
<table class="steppy-report-table">
|
|
80
|
+
<thead><tr>{% if kind == 'endpoints' %}<th>API</th><th>Method</th>{% endif %}<th>Path</th></tr></thead>
|
|
81
|
+
<tbody>
|
|
82
|
+
{% for row in unreached[:200] %}
|
|
83
|
+
<tr>{% if kind == 'endpoints' %}<td>{{ row.api }}</td><td>{{ row.method }}</td>{% endif %}<td><code>{{ row.key }}</code></td></tr>
|
|
84
|
+
{% endfor %}
|
|
85
|
+
</tbody>
|
|
86
|
+
</table>
|
|
87
|
+
</div>
|
|
88
|
+
{% if unreached | length > 200 %}
|
|
89
|
+
<p class="steppy-small steppy-muted">… and {{ unreached | length - 200 }} more; see <a href="{{ root }}report.json">report.json</a>.</p>
|
|
90
|
+
{% endif %}
|
|
91
|
+
{% endif %}
|
|
92
|
+
|
|
93
|
+
{% set weak = bucket(kind, 'discovered_uncovered') %}
|
|
94
|
+
{% if weak %}
|
|
95
|
+
<h3 class="steppy-small">Exercised but not asserted</h3>
|
|
96
|
+
<div class="steppy-scroll">
|
|
97
|
+
<table class="steppy-report-table">
|
|
98
|
+
<thead><tr>{% if kind == 'endpoints' %}<th>API</th><th>Method</th>{% endif %}<th>Path</th><th>Scenarios</th></tr></thead>
|
|
99
|
+
<tbody>
|
|
100
|
+
{% for row in weak[:200] %}
|
|
101
|
+
<tr>{% if kind == 'endpoints' %}<td>{{ row.api }}</td><td>{{ row.method }}</td>{% endif %}<td><code>{{ row.key }}</code></td><td class="steppy-small">{{ row.scenarios | join(', ') }}</td></tr>
|
|
102
|
+
{% endfor %}
|
|
103
|
+
</tbody>
|
|
104
|
+
</table>
|
|
105
|
+
</div>
|
|
106
|
+
{% endif %}
|
|
107
|
+
</section>
|
|
108
|
+
{% endif %}
|
|
109
|
+
{% endfor %}
|
|
110
|
+
|
|
111
|
+
{% if coverage.warnings %}
|
|
112
|
+
<section class="steppy-report-section">
|
|
113
|
+
<h2>Warnings</h2>
|
|
114
|
+
<ul class="steppy-small">{% for warning in coverage.warnings %}<li>{{ warning }}</li>{% endfor %}</ul>
|
|
115
|
+
</section>
|
|
116
|
+
{% endif %}
|
|
117
|
+
|
|
118
|
+
</main>
|
|
119
|
+
|
|
120
|
+
<footer class="steppy-report-footer">
|
|
121
|
+
<p><a href="{{ root }}index.html">← Run report</a></p>
|
|
122
|
+
</footer>
|
|
123
|
+
</body>
|
|
124
|
+
</html>
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
{# Run report landing page. Rendered with autoescape ON — every value below comes
|
|
2
|
+
from a bundle, and a failure tail is arbitrary page content. #}
|
|
3
|
+
<!doctype html>
|
|
4
|
+
<html lang="en">
|
|
5
|
+
<head>
|
|
6
|
+
{% include "report/_head.html.j2" %}
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<header class="steppy-report-header">
|
|
10
|
+
<h1>{% include "report/_mark.html.j2" %}{{ title }}</h1>
|
|
11
|
+
<p class="steppy-report-meta steppy-small">
|
|
12
|
+
<span>{{ report.run.run_id }}</span>
|
|
13
|
+
<span>{{ report.run.created_at }}</span>
|
|
14
|
+
<span>steppy {{ report.run.steppy_version }}</span>
|
|
15
|
+
<span>{{ report.run.executor }}</span>
|
|
16
|
+
</p>
|
|
17
|
+
</header>
|
|
18
|
+
|
|
19
|
+
<main class="steppy-report-main">
|
|
20
|
+
|
|
21
|
+
<section class="steppy-report-section">
|
|
22
|
+
<div class="steppy-counts">
|
|
23
|
+
<span class="steppy-verdict" data-outcome="{{ report.totals.verdict }}">{{ report.totals.verdict | upper }}</span>
|
|
24
|
+
<span class="steppy-muted">
|
|
25
|
+
{{ report.totals.scenarios }} scenario{{ '' if report.totals.scenarios == 1 else 's' }},
|
|
26
|
+
{{ report.totals.tests }} test{{ '' if report.totals.tests == 1 else 's' }}
|
|
27
|
+
</span>
|
|
28
|
+
{% for outcome, count in report.totals.outcomes.items() %}
|
|
29
|
+
<span class="steppy-chip" data-outcome="{{ outcome }}">{{ count }} {{ outcome }}</span>
|
|
30
|
+
{% endfor %}
|
|
31
|
+
</div>
|
|
32
|
+
<dl class="steppy-report-meta">
|
|
33
|
+
{% if report.run.git.branch or report.run.git.sha %}
|
|
34
|
+
<div><dt>Commit</dt><dd>{{ report.run.git.branch or '(detached)' }}{% if report.run.git.sha %} @ {{ report.run.git.sha[:8] }}{% endif %}{% if report.run.git.pr %} (PR #{{ report.run.git.pr }}){% endif %}</dd></div>
|
|
35
|
+
{% endif %}
|
|
36
|
+
{% if report.dimensions.locales %}
|
|
37
|
+
<div><dt>Locales</dt><dd>{{ report.dimensions.locales | join(', ') }}</dd></div>
|
|
38
|
+
{% endif %}
|
|
39
|
+
{% if report.dimensions.layouts %}
|
|
40
|
+
<div><dt>Layouts</dt><dd>{{ report.dimensions.layouts | join(', ') }}</dd></div>
|
|
41
|
+
{% endif %}
|
|
42
|
+
{% if report.run.spec.workers and report.run.spec.workers > 1 %}
|
|
43
|
+
<div><dt>Workers</dt><dd>{{ report.run.spec.workers }}</dd></div>
|
|
44
|
+
{% endif %}
|
|
45
|
+
{% if report.run.spec.guide %}<div><dt>Mode</dt><dd>guide capture</dd></div>{% endif %}
|
|
46
|
+
{# Named for what it is: under --workers N this is not wall clock,
|
|
47
|
+
and a bundle carries no finished_at to compute that from. #}
|
|
48
|
+
<div><dt>Test time</dt><dd>{{ report.totals.duration_ms_sum | duration }}</dd></div>
|
|
49
|
+
</dl>
|
|
50
|
+
</section>
|
|
51
|
+
|
|
52
|
+
{% if report.truncated %}
|
|
53
|
+
<div class="steppy-notice">
|
|
54
|
+
<strong>Truncated.</strong> The size budget dropped artifacts that belonged in this
|
|
55
|
+
bundle, so something below may be missing. Screenshots and their sidecars are never
|
|
56
|
+
dropped — see the warnings for what was.
|
|
57
|
+
</div>
|
|
58
|
+
{% endif %}
|
|
59
|
+
|
|
60
|
+
{% if not report.capabilities.step_timeline and report.totals.tests %}
|
|
61
|
+
<div class="steppy-notice">
|
|
62
|
+
No per-step timeline was recorded for this run, so the pages below show outcomes
|
|
63
|
+
without step detail. Either the run did not pass <code>--bundle</code> (which sets
|
|
64
|
+
<code>STEPPY_STEP_EVENTS=1</code>), it set <code>STEPPY_STEP_EVENTS=0</code>, or the
|
|
65
|
+
tests were generated before per-step markers existed — re-run
|
|
66
|
+
<code>steppy generate</code> in that case.
|
|
67
|
+
</div>
|
|
68
|
+
{% endif %}
|
|
69
|
+
|
|
70
|
+
{% if not report.totals.tests %}
|
|
71
|
+
<div class="steppy-notice">
|
|
72
|
+
This run recorded <strong>no test outcomes</strong>. The scenarios below were
|
|
73
|
+
reconstructed from the captures on disk. Add <code>--bundle</code> to the run
|
|
74
|
+
(<code>steppy run --bundle</code>) so outcomes, failure tails and step boundaries are
|
|
75
|
+
written down.
|
|
76
|
+
</div>
|
|
77
|
+
{% endif %}
|
|
78
|
+
|
|
79
|
+
{% if report.failure_causes %}
|
|
80
|
+
<section class="steppy-report-section">
|
|
81
|
+
<h2>Failure causes ({{ report.failure_causes | length }} distinct)</h2>
|
|
82
|
+
<p class="steppy-small steppy-muted">
|
|
83
|
+
Grouped by normalised failure text, biggest first. One broken selector across
|
|
84
|
+
forty scenarios is one row here, not forty tracebacks. The tail shown is the
|
|
85
|
+
first occurrence's — the rest are the same failure.
|
|
86
|
+
</p>
|
|
87
|
+
{% for cause in report.failure_causes %}
|
|
88
|
+
<div class="steppy-cause">
|
|
89
|
+
<div class="steppy-cause-head">
|
|
90
|
+
<span class="steppy-cause-count">×{{ cause.count }}</span>
|
|
91
|
+
<span class="steppy-cause-headline">{{ cause.headline }}</span>
|
|
92
|
+
<span class="steppy-fingerprint">{{ cause.fingerprint }}</span>
|
|
93
|
+
</div>
|
|
94
|
+
<p class="steppy-small steppy-muted">
|
|
95
|
+
{% for slug in cause.scenarios[:8] %}<a href="{{ scenario_href(slug) }}">{{ slug }}</a>{% if not loop.last %}, {% endif %}{% endfor %}
|
|
96
|
+
{% if cause.scenarios | length > 8 %} and {{ cause.scenarios | length - 8 }} more{% endif %}
|
|
97
|
+
</p>
|
|
98
|
+
{% if cause.exemplar_tail %}
|
|
99
|
+
<pre class="steppy-tail">{{ cause.exemplar_tail }}</pre>
|
|
100
|
+
{% endif %}
|
|
101
|
+
</div>
|
|
102
|
+
{% endfor %}
|
|
103
|
+
</section>
|
|
104
|
+
{% endif %}
|
|
105
|
+
|
|
106
|
+
<section class="steppy-report-section">
|
|
107
|
+
<h2>Scenarios</h2>
|
|
108
|
+
<div class="steppy-scroll">
|
|
109
|
+
<table class="steppy-report-table">
|
|
110
|
+
<thead>
|
|
111
|
+
<tr>
|
|
112
|
+
<th>Scenario</th>
|
|
113
|
+
{% if matrix_columns %}{% for column in matrix_columns %}<th>{{ column.label }}</th>{% endfor %}
|
|
114
|
+
{% else %}<th>Outcome</th>{% endif %}
|
|
115
|
+
<th>Test time</th>
|
|
116
|
+
</tr>
|
|
117
|
+
</thead>
|
|
118
|
+
<tbody>
|
|
119
|
+
{% for scenario in report.scenarios %}
|
|
120
|
+
<tr>
|
|
121
|
+
<td><a href="{{ scenario.href }}">{{ scenario.slug }}</a></td>
|
|
122
|
+
{% if matrix_columns %}
|
|
123
|
+
{% for column in matrix_columns %}
|
|
124
|
+
{% set cell = matrix_cell(scenario, column) %}
|
|
125
|
+
<td>{% if cell %}<span class="steppy-chip" data-outcome="{{ cell }}">{{ cell }}</span>{% else %}<span class="steppy-muted">—</span>{% endif %}</td>
|
|
126
|
+
{% endfor %}
|
|
127
|
+
{% else %}
|
|
128
|
+
<td><span class="steppy-chip" data-outcome="{{ scenario.outcome }}">{{ scenario.outcome }}</span></td>
|
|
129
|
+
{% endif %}
|
|
130
|
+
<td class="steppy-muted">{{ scenario.duration_ms_sum | duration }}</td>
|
|
131
|
+
</tr>
|
|
132
|
+
{% endfor %}
|
|
133
|
+
</tbody>
|
|
134
|
+
</table>
|
|
135
|
+
</div>
|
|
136
|
+
</section>
|
|
137
|
+
|
|
138
|
+
{% if report.coverage.tier != 'none' %}
|
|
139
|
+
<section class="steppy-report-section">
|
|
140
|
+
<h2>E2E surface coverage</h2>
|
|
141
|
+
<div class="steppy-badge-line">
|
|
142
|
+
{% if report.coverage.badge.percent is not none %}
|
|
143
|
+
<span class="steppy-pct">{{ report.coverage.badge.percent }}%</span>
|
|
144
|
+
<span class="steppy-muted">
|
|
145
|
+
{{ report.coverage.badge.tested }} of {{ report.coverage.badge.declared }}
|
|
146
|
+
declared endpoints and routes a UI test could reach
|
|
147
|
+
</span>
|
|
148
|
+
{% else %}
|
|
149
|
+
<span class="steppy-pct steppy-muted">n/a</span>
|
|
150
|
+
<span class="steppy-muted">nothing declared — configure <code>coverage.apis</code> and run <code>steppy coverage refresh</code></span>
|
|
151
|
+
{% endif %}
|
|
152
|
+
</div>
|
|
153
|
+
<p class="steppy-small"><a href="coverage.html">Full gap report</a></p>
|
|
154
|
+
</section>
|
|
155
|
+
{% endif %}
|
|
156
|
+
|
|
157
|
+
{% if report.artifacts.by_kind %}
|
|
158
|
+
<section class="steppy-report-section">
|
|
159
|
+
<h2>Artifacts in this run</h2>
|
|
160
|
+
<p class="steppy-small steppy-muted">
|
|
161
|
+
What the run produced, by kind. Traces and videos are listed rather than copied
|
|
162
|
+
here — a Playwright trace needs <code>playwright show-trace</code> to open, so a
|
|
163
|
+
link would be a dead end.
|
|
164
|
+
</p>
|
|
165
|
+
<div class="steppy-scroll">
|
|
166
|
+
<table class="steppy-report-table">
|
|
167
|
+
<thead><tr><th>Kind</th><th>Files</th><th>Size</th></tr></thead>
|
|
168
|
+
<tbody>
|
|
169
|
+
{% for kind, group in report.artifacts.by_kind.items() %}
|
|
170
|
+
<tr><td>{{ kind }}</td><td>{{ group.count }}</td><td>{{ group.bytes | filesize }}</td></tr>
|
|
171
|
+
{% endfor %}
|
|
172
|
+
</tbody>
|
|
173
|
+
</table>
|
|
174
|
+
</div>
|
|
175
|
+
</section>
|
|
176
|
+
{% endif %}
|
|
177
|
+
|
|
178
|
+
{% if report.warnings or report.coverage.warnings %}
|
|
179
|
+
<section class="steppy-report-section">
|
|
180
|
+
<h2>Warnings</h2>
|
|
181
|
+
<ul class="steppy-small">
|
|
182
|
+
{% for warning in report.warnings %}<li>{{ warning }}</li>{% endfor %}
|
|
183
|
+
{% for warning in report.coverage.warnings %}<li>{{ warning }}</li>{% endfor %}
|
|
184
|
+
</ul>
|
|
185
|
+
</section>
|
|
186
|
+
{% endif %}
|
|
187
|
+
|
|
188
|
+
</main>
|
|
189
|
+
|
|
190
|
+
<footer class="steppy-report-footer">
|
|
191
|
+
<p>
|
|
192
|
+
Generated by Steppy {{ report.generated_by }} from {{ report.source.kind }}
|
|
193
|
+
<code>{{ report.source.label }}</code>.
|
|
194
|
+
Machine-readable: <a href="report.json">report.json</a>{% if report.coverage.tier != 'none' %},
|
|
195
|
+
<a href="badge.json">badge.json</a>{% endif %}.
|
|
196
|
+
</p>
|
|
197
|
+
<p>
|
|
198
|
+
This page is marked <code>noindex</code>. It is a run report, not documentation —
|
|
199
|
+
and a published Pages site means published screenshots.
|
|
200
|
+
</p>
|
|
201
|
+
</footer>
|
|
202
|
+
</body>
|
|
203
|
+
</html>
|