@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.
- package/LICENSE +201 -0
- package/README.md +104 -0
- package/SECURITY.md +33 -0
- package/bin/demo-maker.mjs +14 -0
- package/docs/README.md +35 -0
- package/docs/actions.md +160 -0
- package/docs/cli.md +73 -0
- package/docs/getting-started.md +220 -0
- package/docs/imports-and-fragments.md +78 -0
- package/docs/library-api.md +64 -0
- package/docs/locators.md +119 -0
- package/docs/recording.md +59 -0
- package/docs/repairing-scenarios.md +73 -0
- package/docs/scenario-schema.md +80 -0
- package/docs/url-capture.md +75 -0
- package/docs/variables.md +48 -0
- package/docs/video-and-pacing.md +109 -0
- package/examples/demo1-introduction.yaml +166 -0
- package/examples/demo2-github-actions.yaml +109 -0
- package/examples/files/demo1-introduction-run.log +50 -0
- package/examples/fragments/open-repository.yaml +15 -0
- package/examples/smoke.yaml +18 -0
- package/llms.txt +32 -0
- package/package.json +66 -0
- package/src/cli.ts +226 -0
- package/src/env.ts +112 -0
- package/src/export-chapters.ts +99 -0
- package/src/ffmpeg.ts +168 -0
- package/src/index.ts +54 -0
- package/src/load.ts +143 -0
- package/src/logger.ts +126 -0
- package/src/main.ts +68 -0
- package/src/parse-errors.ts +99 -0
- package/src/parse.ts +622 -0
- package/src/playwright-selector-to-locator.ts +328 -0
- package/src/record-cli.ts +179 -0
- package/src/recorder.ts +442 -0
- package/src/recording-to-yaml.ts +435 -0
- package/src/runner.ts +1582 -0
- package/src/selector-to-locator.ts +69 -0
- package/src/templates.ts +48 -0
- package/src/types.ts +319 -0
- package/src/url-capture.ts +105 -0
- 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()`).
|
package/docs/locators.md
ADDED
|
@@ -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.
|