@a1knowhow/demo-maker 0.1.0

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.
Files changed (44) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +104 -0
  3. package/SECURITY.md +33 -0
  4. package/bin/demo-maker.mjs +14 -0
  5. package/docs/README.md +35 -0
  6. package/docs/actions.md +160 -0
  7. package/docs/cli.md +73 -0
  8. package/docs/getting-started.md +220 -0
  9. package/docs/imports-and-fragments.md +78 -0
  10. package/docs/library-api.md +64 -0
  11. package/docs/locators.md +119 -0
  12. package/docs/recording.md +59 -0
  13. package/docs/repairing-scenarios.md +73 -0
  14. package/docs/scenario-schema.md +80 -0
  15. package/docs/url-capture.md +75 -0
  16. package/docs/variables.md +48 -0
  17. package/docs/video-and-pacing.md +109 -0
  18. package/examples/demo1-introduction.yaml +166 -0
  19. package/examples/demo2-github-actions.yaml +109 -0
  20. package/examples/files/demo1-introduction-run.log +50 -0
  21. package/examples/fragments/open-repository.yaml +15 -0
  22. package/examples/smoke.yaml +18 -0
  23. package/llms.txt +32 -0
  24. package/package.json +66 -0
  25. package/src/cli.ts +226 -0
  26. package/src/env.ts +112 -0
  27. package/src/export-chapters.ts +99 -0
  28. package/src/ffmpeg.ts +168 -0
  29. package/src/index.ts +54 -0
  30. package/src/load.ts +143 -0
  31. package/src/logger.ts +126 -0
  32. package/src/main.ts +68 -0
  33. package/src/parse-errors.ts +99 -0
  34. package/src/parse.ts +622 -0
  35. package/src/playwright-selector-to-locator.ts +328 -0
  36. package/src/record-cli.ts +179 -0
  37. package/src/recorder.ts +442 -0
  38. package/src/recording-to-yaml.ts +435 -0
  39. package/src/runner.ts +1582 -0
  40. package/src/selector-to-locator.ts +69 -0
  41. package/src/templates.ts +48 -0
  42. package/src/types.ts +319 -0
  43. package/src/url-capture.ts +105 -0
  44. package/src/variables.ts +64 -0
@@ -0,0 +1,220 @@
1
+ # Getting started
2
+
3
+ Purpose: Step-by-step from install to a first recorded MP4.
4
+
5
+ ## Step 1 — Install prerequisites
6
+
7
+ **Goal:** Node, Chromium, and ffmpeg available.
8
+
9
+ ```bash
10
+ node -v # need 20+
11
+ brew install ffmpeg # macOS; or apt/winget on Linux/Windows
12
+ ```
13
+
14
+ **Clone and install** (from the repo root):
15
+
16
+ ```bash
17
+ npm install
18
+ npx playwright install chromium
19
+ ```
20
+
21
+ Or as a dependency in your project:
22
+
23
+ ```bash
24
+ npm install -D @a1knowhow/demo-maker
25
+ npx playwright install chromium
26
+ ```
27
+
28
+ **Expected:** `npx demo-maker --help` prints usage.
29
+
30
+ **Common failure:** `ffmpeg: command not found` — install ffmpeg before using `.mp4`, `cut_video`, or background audio.
31
+
32
+ ---
33
+
34
+ ## Step 2 — Run the smoke example
35
+
36
+ **Goal:** Prove the CLI drives a browser.
37
+
38
+ ```bash
39
+ npx demo-maker run examples/smoke.yaml --headless --fast
40
+ ```
41
+
42
+ **Expected:** Scenario completes successfully (opens example.com, asserts “Example Domain”).
43
+
44
+ **Common failure:** Playwright browser missing → re-run `npx playwright install chromium`.
45
+
46
+ For a public docs tour (getting-started → actions → locators):
47
+
48
+ ```bash
49
+ npx demo-maker run examples/demo1-introduction.yaml --headless --fast
50
+ ```
51
+
52
+ For a richer GitHub Actions UI walkthrough:
53
+
54
+ ```bash
55
+ npx demo-maker run examples/demo2-github-actions.yaml --headless --fast
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Step 3 — Point at your app
61
+
62
+ **Goal:** A minimal scenario against your product.
63
+
64
+ 1. Copy `examples/smoke.yaml` to e.g. `scenarios/my-app.yaml`.
65
+ 2. Set `base_url` to your app (e.g. `http://localhost:3000`).
66
+ 3. Replace steps with `navigate` / `click` / `type` / `expect_visible` for a short happy path.
67
+
68
+ ```yaml
69
+ name: my-app-smoke
70
+ description: First walkthrough
71
+ base_url: http://localhost:3000/
72
+ steps:
73
+ - action: navigate
74
+ url: "/"
75
+ - action: expect_visible
76
+ locator: { role: heading, name: /Welcome/i }
77
+ - action: wait
78
+ timeout_ms: 1500
79
+ ```
80
+
81
+ ```bash
82
+ npx demo-maker run scenarios/my-app.yaml --headless --fast
83
+ ```
84
+
85
+ **Expected:** Steps pass against your running app.
86
+
87
+ **Common failure:** Locator not found → see [Locators](./locators.md) and [Repairing scenarios](./repairing-scenarios.md). Prefer `role`/`label`/`text` over CSS.
88
+
89
+ ---
90
+
91
+ ## Step 4 — Env file and auth (when needed)
92
+
93
+ **Goal:** Credentials and `{{ VAR }}` without hardcoding secrets in YAML.
94
+
95
+ Create `scenarios/u2.env` (do not commit secrets):
96
+
97
+ ```dotenv
98
+ E2E_TEST_USER_EMAIL=you@example.com
99
+ E2E_TEST_USER_PASSWORD=secret
100
+ BASE_URL=http://localhost:3000/
101
+ DEMO_WORKSPACE1=Acme Corp
102
+ ```
103
+
104
+ If the scenario includes an `auth:` block, those env vars are required. Otherwise empty auth is allowed (as in smoke).
105
+
106
+ ```yaml
107
+ auth:
108
+ email_env: E2E_TEST_USER_EMAIL
109
+ password_env: E2E_TEST_USER_PASSWORD
110
+ steps:
111
+ - action: type
112
+ locator: { role: textbox, name: /email/i }
113
+ value: "{{ auth.email }}"
114
+ ```
115
+
116
+ ```bash
117
+ npx demo-maker run scenarios/login.yaml --env-file scenarios/u2.env --fast
118
+ ```
119
+
120
+ **Save session** after login for later runs:
121
+
122
+ ```bash
123
+ npx demo-maker run scenarios/login.yaml --env-file scenarios/u2.env \
124
+ --save-storage-state=scenarios/session.json
125
+
126
+ npx demo-maker run scenarios/app.yaml --env-file scenarios/u2.env \
127
+ --storage-state=scenarios/session.json --fast
128
+ ```
129
+
130
+ ---
131
+
132
+ ## Step 5 — Record a video (MP4)
133
+
134
+ **Goal:** Polished output for demos.
135
+
136
+ ```yaml
137
+ video:
138
+ enabled: true
139
+ dir: output/videos
140
+ filename: demo-[date:"YYYY-MM-DD-HHmm"].mp4
141
+ size: { width: 1920, height: 1080 }
142
+ viewport: { width: 1440, height: 810 } # optional; fit laptop screen when headed
143
+ ```
144
+
145
+ ```bash
146
+ npx demo-maker run scenarios/my-app.yaml --env-file scenarios/u2.env
147
+ ```
148
+
149
+ **Expected:** MP4 under `output/videos/` plus a sibling `*.timing.json`.
150
+
151
+ **Common failure:** `.mp4` without ffmpeg → runner keeps WebM and warns. Install ffmpeg and re-run.
152
+
153
+ Details: [Video and pacing](./video-and-pacing.md).
154
+
155
+ ---
156
+
157
+ ## Step 6 — Record interactions instead of writing YAML by hand
158
+
159
+ **Goal:** Capture real clicks as steps.
160
+
161
+ ```bash
162
+ demo-maker record \
163
+ --save-to scenarios/recorded.yaml \
164
+ --url http://localhost:3000/ \
165
+ --env-file scenarios/u2.env
166
+ ```
167
+
168
+ Interact in the browser, then **Ctrl+C** to save. Edit captions and locators, then `npx demo-maker run`.
169
+
170
+ Continue from an existing prefix (e.g. login):
171
+
172
+ ```bash
173
+ demo-maker record \
174
+ --continue-from scenarios/01.login.yaml \
175
+ --save-to scenarios/recorded-segment.yaml \
176
+ --fast --env-file scenarios/u2.env
177
+ ```
178
+
179
+ Details: [Recording](./recording.md).
180
+
181
+ ---
182
+
183
+ ## Step 7 — Reuse fragments with imports
184
+
185
+ **Goal:** Don’t copy-paste login into every scenario.
186
+
187
+ ```yaml
188
+ # scenarios/full-demo.yaml
189
+ name: full-demo
190
+ base_url: http://localhost:3000/
191
+ steps:
192
+ - import: 01.login.yaml
193
+ - action: click
194
+ locator: { role: link, name: Dashboard }
195
+ - import: fragments/create-item.yaml
196
+ ```
197
+
198
+ Details: [Imports and fragments](./imports-and-fragments.md).
199
+
200
+ ---
201
+
202
+ ## Step 8 — YouTube chapters
203
+
204
+ **Goal:** Paste chapter timestamps into a video description.
205
+
206
+ ```bash
207
+ demo-maker export-chapters output/videos
208
+ # or: demo-maker export-chapters output/videos/demo-….timing.json
209
+ ```
210
+
211
+ Use `action: log` with `── Scene N: …` markers and/or step `caption:` for chapter labels.
212
+
213
+ ---
214
+
215
+ ## Next reading
216
+
217
+ - Full action list: [Actions](./actions.md)
218
+ - Locator guide: [Locators](./locators.md)
219
+ - After UI breaks demos: [Repairing scenarios](./repairing-scenarios.md)
220
+ - All flags: [CLI](./cli.md)
@@ -0,0 +1,78 @@
1
+ # Imports and fragments
2
+
3
+ Purpose: Compose reusable YAML without duplicating steps.
4
+
5
+ ## Two forms
6
+
7
+ 1. **Top-level `imports:`** — paths relative to the entry file; their steps run **first**, in order, then local `steps`.
8
+ 2. **Step-level `- import: path.yaml`** — expanded **in place** so you can interleave fragments.
9
+
10
+ Nested imports are allowed. Circular imports error.
11
+
12
+ **Metadata:** Only the **entry** file’s `name`, `base_url`, `video`, pacing, `auth`, etc. apply. Imported files contribute steps only.
13
+
14
+ Paths resolve relative to the YAML file that contains the reference.
15
+
16
+ ## Fragment example
17
+
18
+ ```yaml
19
+ # fragments/login.yaml
20
+ name: login
21
+ base_url: http://localhost:3000/
22
+ steps:
23
+ - action: navigate
24
+ url: /login
25
+ - action: type
26
+ locator: { role: textbox, name: /email/i }
27
+ value: "{{ auth.email }}"
28
+ - action: type
29
+ locator: { role: textbox, name: /password/i }
30
+ value: "{{ auth.password }}"
31
+ - action: click
32
+ locator: { role: button, name: /Sign in/i }
33
+ ```
34
+
35
+ ## Step-level composition
36
+
37
+ ```yaml
38
+ name: full-demo
39
+ base_url: http://localhost:3000/
40
+ steps:
41
+ - import: fragments/login.yaml
42
+ - action: click
43
+ locator: { role: link, name: Dashboard }
44
+ - import: fragments/create-item.yaml
45
+ - action: wait
46
+ timeout_ms: 2000
47
+ ```
48
+
49
+ Order: login steps → click Dashboard → create-item steps → wait.
50
+
51
+ ## Top-level imports only
52
+
53
+ ```yaml
54
+ name: full-demo
55
+ base_url: http://localhost:3000/
56
+ imports:
57
+ - fragments/login.yaml
58
+ - fragments/create-item.yaml
59
+ steps:
60
+ - action: click
61
+ locator: { role: link, name: folder }
62
+ ```
63
+
64
+ Order: login → create-item → click folder.
65
+
66
+ ## Continue-from recordings
67
+
68
+ When you `demo-maker record --continue-from 01.login.yaml --save-to recorded.yaml`, the saved file typically has:
69
+
70
+ ```yaml
71
+ name: recorded-…
72
+ imports:
73
+ - 01.login.yaml
74
+ steps:
75
+ # only newly recorded steps
76
+ ```
77
+
78
+ See [Recording](./recording.md).
@@ -0,0 +1,64 @@
1
+ # Library API
2
+
3
+ Purpose: Use demo-maker from TypeScript instead of the CLI.
4
+
5
+ Package entry: `@a1knowhow/demo-maker` (see `src/index.ts`).
6
+
7
+ ```ts
8
+ import {
9
+ loadScenario,
10
+ runScenario,
11
+ createDemoLogger,
12
+ createRegexUrlCapture,
13
+ type RunContext,
14
+ } from "@a1knowhow/demo-maker";
15
+
16
+ const scenario = loadScenario("./scenarios/demo.yaml", { cwd: process.cwd() });
17
+
18
+ const context: RunContext = {
19
+ auth: {
20
+ email: process.env.E2E_TEST_USER_EMAIL ?? "",
21
+ password: process.env.E2E_TEST_USER_PASSWORD ?? "",
22
+ },
23
+ // Prefer explicit keys or an env file — avoid spreading all of process.env
24
+ variables: {
25
+ BASE_URL: scenario.base_url,
26
+ ...(process.env.DEMO_WORKSPACE1
27
+ ? { DEMO_WORKSPACE1: process.env.DEMO_WORKSPACE1 }
28
+ : {}),
29
+ },
30
+ };
31
+
32
+ const result = await runScenario(scenario, context, {
33
+ logger: createDemoLogger("info"),
34
+ headless: true,
35
+ cwd: process.cwd(),
36
+ urlCaptureHooks: [
37
+ createRegexUrlCapture([
38
+ { variable: "workspace_id", pattern: String.raw`/workspace/([^/]+)` },
39
+ ]),
40
+ ],
41
+ });
42
+
43
+ if (!result.success) {
44
+ throw new Error(result.error);
45
+ }
46
+ ```
47
+
48
+ ## Useful exports
49
+
50
+ | Export | Role |
51
+ |--------|------|
52
+ | `loadScenario` / `parseScenario` | YAML → scenario |
53
+ | `runScenario` / `runScenarioOnExistingPage` | Execute |
54
+ | `sliceScenarioByEndStep` | Truncate steps |
55
+ | `resolveVariables` | `{{ }}` helper |
56
+ | `formatYouTubeChapters` | Timing → chapter text |
57
+ | `createRegexUrlCapture` / `buildUrlCaptureHooks` | URL → variable capture |
58
+ | Types | `DemoScenario`, `DemoStep`, `Locator`, `RunContext`, `UrlCaptureRule`, … |
59
+
60
+ ## Options highlights
61
+
62
+ `RunScenarioOptions`: `logger`, `headless`, `cwd`, `storageStatePath`, `saveStorageStatePath`, `failFast`, `endStep`, `pauseAtEnd`, `debugDir`, `urlCaptureHooks`.
63
+
64
+ Relative paths in YAML (`video.dir`, file uploads) resolve against `cwd` (default `process.cwd()`).
@@ -0,0 +1,119 @@
1
+ # Locators
2
+
3
+ Purpose: How to target UI elements in YAML (`locator:`). Prefer accessibility over CSS.
4
+
5
+ ## Locator kinds
6
+
7
+ Each `locator:` object uses **exactly one** shape:
8
+
9
+ | Kind | YAML shape | Playwright | When to use |
10
+ |------|------------|------------|-------------|
11
+ | **role** | `{ role: button, name: Submit }` | `getByRole` | Buttons, links, headings, dialogs, comboboxes |
12
+ | **label** | `{ label: Email address }` | `getByLabel` | Fields with `<label>` or `aria-label` |
13
+ | **text** | `{ text: Acme Corp }` | `getByText` | Visible copy (substring); plain string only |
14
+ | **placeholder** | `{ placeholder: Search… }` | `getByPlaceholder` | Inputs by placeholder |
15
+ | **title** | `{ title: Close dialog }` | `getByTitle` | `title` attribute |
16
+ | **selector** | `{ selector: "#id" }` | `page.locator` | **Last resort** CSS |
17
+
18
+ Common roles: `button`, `textbox`, `link`, `heading`, `main`, `dialog`, `combobox`, `menuitem`, `checkbox`, `option`.
19
+
20
+ ## Matching rules
21
+
22
+ - **Role + string `name`** — **exact** accessible name (`exact: true`)
23
+ - **Role + regex `name`** — `/Sign in/i` for case-insensitive / partial
24
+ - **`label`** — string or regex
25
+ - **`text`** — substring match; **no** regex on `text` (use `role`+regex or `selector: 'text=/…/i'` as last resort)
26
+ - **`settle_until: { text: "…" }`** — **exact** text (unlike step `locator: { text }`)
27
+
28
+ ## Regex in YAML
29
+
30
+ Write JS-style regex as strings the parser understands:
31
+
32
+ ```yaml
33
+ locator: { role: button, name: /Sign in/i }
34
+ locator: { label: /email address/i }
35
+ ```
36
+
37
+ Supported flags: `g`, `i`, `m`, `s`, `u`, `y`.
38
+
39
+ **`{{ variables }}` do not work inside regex.** `/{{ DEMO_NAME }}/i` is literal. Use string fields:
40
+
41
+ ```yaml
42
+ locator: { text: "{{ DEMO_NAME }}" }
43
+ locator: { role: link, name: "{{ DEMO_NAME }}" }
44
+ ```
45
+
46
+ ## Variables in locators
47
+
48
+ `{{ VAR }}` resolves at **run time** for string fields only (`text`, string `name`/`label`/`placeholder`/`title`/`selector`).
49
+
50
+ ## Prefer stable kinds
51
+
52
+ `label` / `role`+`name` > `text` > `placeholder` > `selector`
53
+
54
+ ## Examples
55
+
56
+ ```yaml
57
+ # Login button
58
+ - action: click
59
+ locator: { role: button, name: /Sign in/i }
60
+
61
+ # Form field
62
+ - action: type
63
+ locator: { label: Email }
64
+ value: "{{ auth.email }}"
65
+
66
+ # List row by visible name
67
+ - action: click
68
+ locator: { text: "{{ DEMO_WORKSPACE1 }}" }
69
+
70
+ # Search then pick option
71
+ - action: type
72
+ locator: { placeholder: Search… }
73
+ value: Acme
74
+ - action: click
75
+ locator: { role: option, name: /Acme/i }
76
+
77
+ # File upload (often no a11y hook)
78
+ - action: type
79
+ locator: { selector: 'input[type="file"]' }
80
+ value: /absolute/path/to/file.pdf
81
+ ```
82
+
83
+ ## Anti-patterns
84
+
85
+ ```yaml
86
+ # wrong: "text" is not an ARIA role; {{ }} inside /…/i is literal
87
+ locator:
88
+ role: text
89
+ name: /{{ DEMO_NAME }}/i
90
+
91
+ # right
92
+ locator:
93
+ text: "{{ DEMO_NAME }}"
94
+ ```
95
+
96
+ ## When a step fails
97
+
98
+ 1. Note step number and Playwright error (`not found`, `strict mode violation`)
99
+ 2. Isolate: `--end-step=N-1 --pause-at-end` and inspect Accessibility tree
100
+ 3. Fix locator or add unique `aria-label` in the app
101
+ 4. Or re-record a segment (`demo-maker record --continue-from …`) and transplant locators only
102
+
103
+ | Problem | Fix |
104
+ |---------|-----|
105
+ | Button renamed | Update `name` or `/partial/i` |
106
+ | Strict mode (2+ matches) | Exact name, unique `aria-label`, tighter regex |
107
+ | Custom editor (no role) | Add `aria-label` → `{ label: … }` or `{ role: textbox, name: … }` |
108
+ | Dynamic row | `{ text: "{{ VAR }}" }` |
109
+ | Recorder wrote `(recorded)` | Fix a11y; re-record or hand-write |
110
+
111
+ ## Accessibility tip (for app authors)
112
+
113
+ Add unique `aria-label` on inputs, editors, and buttons. In loops, suffix with index or entity name so each instance is uniquely addressable. Dialogs need `aria-label` or `aria-labelledby` on the root.
114
+
115
+ ## Recorder mapping
116
+
117
+ When recording/importing, priority is roughly: `aria-label`/`label=` → Playwright `getBy*` code → internal selector parse → fallback `text` or `(recorded)`.
118
+
119
+ See [Recording](./recording.md) and [Repairing scenarios](./repairing-scenarios.md).
@@ -0,0 +1,59 @@
1
+ # Recording
2
+
3
+ Purpose: Capture browser actions into YAML (or import from JSON).
4
+
5
+ ## Live record
6
+
7
+ Uses Playwright’s recorder API (codegen without the Inspector UI). Actions stream to the CLI; **Ctrl+C** writes YAML.
8
+
9
+ ```bash
10
+ demo-maker record \
11
+ --save-to scenarios/recorded.yaml \
12
+ --save-json scenarios/recording.json \
13
+ --url http://localhost:3000/ \
14
+ --base-url http://localhost:3000/
15
+ ```
16
+
17
+ - `--save-to` — required output YAML
18
+ - `--save-json` — optional raw JSON for later `--from` / `import`
19
+ - Refuses to overwrite an existing `--save-to` on a fresh live record (delete/rename first)
20
+
21
+ Edit captions, variables, and locators after saving, then `npx demo-maker run`.
22
+
23
+ ## Continue from existing YAML
24
+
25
+ Replay a prefix scenario, then record additional steps (e.g. login once, then capture a new flow).
26
+
27
+ ```bash
28
+ demo-maker record \
29
+ --continue-from scenarios/01.login.yaml \
30
+ --save-to scenarios/recorded-segment.yaml \
31
+ --fast \
32
+ --env-file scenarios/u2.env
33
+ ```
34
+
35
+ - On Ctrl+C, saved YAML contains **only newly recorded steps** plus top-level `imports` pointing at the continue-from file (relative path).
36
+ - Overwrite of `--save-to` is allowed with `--continue-from`.
37
+ - `--end-step=N` limits replay to steps 1..N before recording starts.
38
+ - If replay fails, the process exits and does **not** start recording.
39
+
40
+ ## Import from JSON
41
+
42
+ Convert demo-maker recorder JSON or Chrome DevTools Recorder (Puppeteer Replay) JSON:
43
+
44
+ ```bash
45
+ demo-maker record --from recording.json --save-to scenario.yaml --base-url http://localhost:3000/
46
+ # or
47
+ demo-maker import recording.json -o scenario.yaml --base-url http://localhost:3000/
48
+ ```
49
+
50
+ ## After recording
51
+
52
+ 1. Replace brittle selectors with `role` / `label` / `text` where possible ([Locators](./locators.md))
53
+ 2. Add `caption:` sparingly (scene titles, not every click)
54
+ 3. Wire `{{ auth.* }}` / env vars ([Variables](./variables.md))
55
+ 4. Enable `video:` when ready ([Video and pacing](./video-and-pacing.md))
56
+
57
+ ## Repair loop
58
+
59
+ See [Repairing scenarios](./repairing-scenarios.md) for detect → isolate → continue-from → transplant locators → verify.
@@ -0,0 +1,73 @@
1
+ # Repairing scenarios
2
+
3
+ Purpose: YAML is valid but fails at runtime after UI changes. This is a **runtime** smoke loop, not schema validation.
4
+
5
+ ## 1. Detect drift
6
+
7
+ ```bash
8
+ demo-maker check scenarios/02.create.yaml \
9
+ --env-file scenarios/u2.env \
10
+ --storage-state=scenarios/session.json
11
+ ```
12
+
13
+ `check` = `run --fail-fast --fast`. Note the failing **step number** and Playwright message. Treat the browser + error as source of truth.
14
+
15
+ Optional failure screenshot:
16
+
17
+ ```bash
18
+ demo-maker check scenarios/02.create.yaml \
19
+ --env-file scenarios/u2.env \
20
+ --debug-dir=output/demo-debug
21
+ ```
22
+
23
+ ## 2. Isolate
24
+
25
+ Replay only the prefix, then pause headed:
26
+
27
+ ```bash
28
+ npx demo-maker run scenarios/02.create.yaml \
29
+ --env-file scenarios/u2.env \
30
+ --storage-state=scenarios/session.json \
31
+ --fast --end-step=7 --pause-at-end
32
+ ```
33
+
34
+ Inspect the Accessibility tree (role, name, label) for the next control.
35
+
36
+ ## 3. Re-capture
37
+
38
+ ```bash
39
+ demo-maker record \
40
+ --continue-from scenarios/02.create.yaml \
41
+ --end-step=7 \
42
+ --save-to _repair/segment.yaml \
43
+ --save-json _repair/segment.json \
44
+ --fast --env-file scenarios/u2.env
45
+ ```
46
+
47
+ Perform the corrected path, **Ctrl+C**. Do **not** blindly overwrite the canonical YAML — transplant changed `locator` values and steps; keep `caption`, `video`, pacing, and `metadata`.
48
+
49
+ ## 4. Verify
50
+
51
+ ```bash
52
+ demo-maker check scenarios/02.create.yaml \
53
+ --env-file scenarios/u2.env \
54
+ --storage-state=scenarios/session.json
55
+ ```
56
+
57
+ Then re-run your full demo / project smoke targets if you have them.
58
+
59
+ ## Flags used in the loop
60
+
61
+ | Flag | Purpose |
62
+ |------|---------|
63
+ | `--fail-fast` / `check` | Stop on first failure |
64
+ | `--end-step=N` | Run or replay only 1..N |
65
+ | `--pause-at-end` | Keep browser open |
66
+ | `--debug-dir=PATH` | PNG on failure |
67
+ | `--continue-from` | Replay then record |
68
+
69
+ See [CLI](./cli.md), [Locators](./locators.md), [Recording](./recording.md).
70
+
71
+ ## Prefer fixing a11y
72
+
73
+ When locators break, prefer adding unique `aria-label` (and dialog names) in the app over brittle CSS. Then update YAML to `{ role, name }` / `{ label }`.
@@ -0,0 +1,80 @@
1
+ # Scenario schema
2
+
3
+ Purpose: Top-level YAML fields for an entry scenario. Typed definitions: [`src/types.ts`](../src/types.ts).
4
+
5
+ **Metadata rule:** Only the **entry** file (the path you pass to `npx demo-maker run`) supplies `name`, `base_url`, `video`, pacing, `auth`, etc. Imported files contribute **steps only**.
6
+
7
+ ## Required / common fields
8
+
9
+ ```yaml
10
+ name: string # required
11
+ description: string # optional (may be empty)
12
+ base_url: string # default http://localhost:2000/
13
+ imports: [path, ...] # optional; prepended steps from other files
14
+ metadata: { ... } # optional; arbitrary (e.g. YouTube paste); ignored by runner
15
+ auth:
16
+ email_env: ENV_VAR_NAME
17
+ password_env: ENV_VAR_NAME
18
+ steps: [] # required array of steps or { import: path }
19
+ ```
20
+
21
+ ## Pacing (optional, milliseconds)
22
+
23
+ | Field | Role |
24
+ |-------|------|
25
+ | `step_delay_ms` | Delay after each step |
26
+ | `slow_mo_ms` | Playwright slowMo after every browser action |
27
+ | `typing_delay_ms` | Delay between keystrokes for `type` (per-step override allowed) |
28
+ | `post_click_delay_ms` | Hold after click (after settle) |
29
+ | `navigate_hold_ms` | Hold after navigate (after DOM settle) |
30
+ | `pre_action_delay_ms` | Delay before navigate/click |
31
+ | `action_timeout_ms` | Default Playwright action timeout (default 10000) |
32
+
33
+ `--fast` overrides pacing to near-zero for smoke runs.
34
+
35
+ ## Video / audio / screenshots
36
+
37
+ See [Video and pacing](./video-and-pacing.md).
38
+
39
+ ```yaml
40
+ video:
41
+ enabled: true
42
+ dir: output/videos
43
+ filename: demo-[date:"YYYY-MM-DD-HHmm"].mp4
44
+ size: { width: 1920, height: 1080 }
45
+ viewport: { width: 1440, height: 810 }
46
+ start_step: 3
47
+ end_step: 20
48
+ caption_parallel: true
49
+ playback_speed: 1.0
50
+
51
+ audio:
52
+ background: path/to/track.mp3 # mixed into final MP4 via ffmpeg
53
+
54
+ screenshots:
55
+ dir: output/screenshots
56
+
57
+ device_scale_factor: 2 # optional; crisp PNG screenshots
58
+ ```
59
+
60
+ ## Storage state
61
+
62
+ ```yaml
63
+ storage_state: session.json # load
64
+ save_storage_state: session.json # save after run
65
+
66
+ url_capture: # optional; see docs/url-capture.md
67
+ - variable: workspace_id
68
+ pattern: "/workspace/([^/]+)"
69
+ ```
70
+
71
+ CLI `--storage-state` / `--save-storage-state` override these.
72
+
73
+ ## Steps
74
+
75
+ Each item is either:
76
+
77
+ - A normal step with `action: …` (see [Actions](./actions.md)), or
78
+ - `{ import: "relative/path.yaml" }` (see [Imports](./imports-and-fragments.md))
79
+
80
+ Any step may include optional `caption:`, `timeout_ms:`, `video_exclude: true`, and action-specific fields.