kaloko 0.4.10 → 0.5.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 (52) hide show
  1. package/README.md +64 -4
  2. package/package.json +1 -1
  3. package/schemas/config.v1.json +70 -2
  4. package/schemas/run.v1.json +33 -2
  5. package/schemas/scenario.v1.json +130 -17
  6. package/skills/kaloko/SKILL.md +18 -5
  7. package/skills/kaloko/reference/acceptance.md +70 -1
  8. package/skills/kaloko/reference/apps.md +103 -8
  9. package/skills/kaloko/reference/ci.md +3 -1
  10. package/skills/kaloko/reference/design.md +39 -0
  11. package/skills/kaloko/reference/devices.md +109 -0
  12. package/skills/kaloko/reference/environments.md +6 -0
  13. package/skills/kaloko/reference/execution.md +6 -0
  14. package/skills/kaloko/reference/mcp.md +1 -0
  15. package/skills/kaloko/reference/recording.md +95 -0
  16. package/src/cli.mjs +381 -167
  17. package/src/lib/access.mjs +5 -2
  18. package/src/lib/anim.mjs +125 -0
  19. package/src/lib/apps/devices.mjs +187 -0
  20. package/src/lib/apps/drivers.mjs +11 -3
  21. package/src/lib/apps/ios-device.mjs +78 -0
  22. package/src/lib/apps/recording-native.mjs +212 -0
  23. package/src/lib/apps/tree.mjs +9 -1
  24. package/src/lib/apps/webview.mjs +54 -0
  25. package/src/lib/apps/windows.mjs +108 -0
  26. package/src/lib/apps/windows.ps1 +78 -0
  27. package/src/lib/assertions.mjs +300 -0
  28. package/src/lib/build.mjs +63 -4
  29. package/src/lib/capture.mjs +146 -119
  30. package/src/lib/ci.mjs +8 -4
  31. package/src/lib/design-cli.mjs +35 -6
  32. package/src/lib/design.mjs +3 -1
  33. package/src/lib/devices.mjs +85 -0
  34. package/src/lib/devspec.mjs +3 -2
  35. package/src/lib/dimensions.mjs +326 -0
  36. package/src/lib/doctor.mjs +35 -6
  37. package/src/lib/drift.mjs +8 -4
  38. package/src/lib/evaluate.mjs +25 -7
  39. package/src/lib/import.mjs +3 -2
  40. package/src/lib/jev.mjs +3 -1
  41. package/src/lib/manifest-core.mjs +12 -3
  42. package/src/lib/probe.mjs +60 -0
  43. package/src/lib/recheck.mjs +6 -1
  44. package/src/lib/recording.mjs +373 -0
  45. package/src/lib/scenario.mjs +3 -0
  46. package/src/lib/stability-core.mjs +4 -2
  47. package/src/lib/trace.mjs +109 -0
  48. package/src/lib/video-cli.mjs +209 -0
  49. package/src/lib/video.mjs +427 -0
  50. package/src/lib/walk-dimensions.mjs +83 -0
  51. package/src/lib/webm.mjs +144 -0
  52. package/viewer/index.html +467 -25
@@ -182,7 +182,8 @@ returning visitor). The plan is the source of truth; this is only the default.
182
182
  Each criterion gets exactly one check:
183
183
 
184
184
  - `deterministic` for anything countable or exact: presence, counts, text,
185
- prices, attributes, overflow, console errors, title/meta/canonical.
185
+ prices, attributes, the URL, computed styles, values in the page,
186
+ overflow, console errors, title/meta/canonical.
186
187
  - `semantic` for judgement: clarity, separation of consents, whether copy
187
188
  fits the purpose. Write the question in English, yes = met, one condition
188
189
  per question. Add `true`/`false` for edge cases and `scope` (CSS selector)
@@ -216,6 +217,74 @@ button styled uppercase reads `VYTVOŘIT`. Add `flags: i` to ignore case
216
217
  white-label check for `vyvolej` also matches the Czech verb "Vyvolej si";
217
218
  anchor it (`vyvolej\.to`).
218
219
 
220
+ `text_equals` compares the whole text of an element (whitespace collapsed,
221
+ `flags: i` ignores case) with `value`: a total, a status, a label.
222
+ `attr_matches` runs `pattern` on an attribute (`selector`, `attr`). Both pass
223
+ when one match passes; `all: true` asks every match.
224
+
225
+ ### URL, computed styles and values in the page
226
+
227
+ Never make a step script compute a check and leave a `data-qa-*` marker for
228
+ a criterion to find. The marker proves only that the script ran. Check the
229
+ URL, the style or the value directly:
230
+
231
+ ```yaml
232
+ # the URL after the step (part: href (default), path, query, hash, host, origin)
233
+ - { id: AC1, text: Lands on the thank-you page, check: { type: deterministic, assert: url_matches, part: path, pattern: '^/(cs|en)/thanks$' } }
234
+ # exact path; ignore_query drops ?… and #…; a value with https:// compares the whole URL
235
+ - { id: AC2, text: Stays on the cart, check: { type: deterministic, assert: url_equals, value: /cart, ignore_query: true } }
236
+ # computed CSS (getComputedStyle) of the matched elements
237
+ - { id: AC3, text: Primary button in brand colour, check: { type: deterministic, assert: style_equals, selector: button.primary, property: background-color, token: color.brand.primary, all: true } }
238
+ - { id: AC4, text: Body text at least 16 px, check: { type: deterministic, assert: style_equals, selector: main p, property: font-size, op: '>=', value: 16px, all: true } }
239
+ - { id: AC5, text: Error text red, check: { type: deterministic, assert: style_equals, selector: .field-error, property: color, value: '#d92d20' } }
240
+ - { id: AC6, text: Promo banner hidden, check: { type: deterministic, assert: style_matches, selector: .promo, property: display, pattern: '^none$' } }
241
+ # a read-only expression in the page
242
+ - { id: AC7, text: Three items in the cart, check: { type: deterministic, assert: js_equals, expression: "document.querySelectorAll('.cart-item').length", value: 3 } }
243
+ - { id: AC8, text: Cart kept for the next visit, check: { type: deterministic, assert: js_equals, expression: "JSON.parse(localStorage.getItem('cart') || '[]').length", op: '>=', value: 1 } }
244
+ - { id: AC9, text: Purchase sent to analytics, check: { type: deterministic, assert: js_equals, expression: "window.dataLayer?.some((e) => e.event === 'purchase')", value: true } }
245
+ ```
246
+
247
+ - `url_matches` and `url_equals` read the URL the browser shows after the
248
+ step, redirects included. The evidence names the URL.
249
+ - `style_equals` compares `property` (a longhand: `background-color`, not
250
+ `background`) with `value` or `token`. Colours compare in one form, so
251
+ `red`, `#ff0000` and `rgb(255, 0, 0)` are equal; transparency counts.
252
+ Lengths compare in px (`rem` uses the page's root size), with
253
+ `tolerance: 1` for rounding; `op: '<' | '<=' | '>' | '>=' | '=='` compares
254
+ numbers. Other values (`display`, `font-weight`, `font-family`) compare as
255
+ text without quotes or case. Visible matches count; when none is visible
256
+ (a check for `display: none`), every match does. `style_matches` runs
257
+ `pattern` on the computed value.
258
+ - `token: color.brand.primary` takes the expected value from the project's
259
+ DTCG token set (`kaloko tokens push`, or `tokens.json` of the design
260
+ folder) when `kaloko evaluate` runs, or from the page's
261
+ `--color-brand-primary` custom property during the walk. The evidence
262
+ reads `color is rgb(46, 107, 255) = color.brand.primary (#2e6bff)`.
263
+ - `:hover` and `:focus` states are not supported: the check reads the page
264
+ as the step left it. Hover or focus the element in the step script when
265
+ that state is what you accept, and say so in the criterion text.
266
+ - `js_equals` evaluates one `expression` in the page (2 s limit; promises are
267
+ awaited) and compares the result with `value` (a number value accepts
268
+ `"3"`; `null` matches a missing `localStorage` key), with `op` and a
269
+ number, or with `pattern` (objects as JSON). Without `value` and
270
+ `pattern` the result must be truthy. Only reading is allowed: no
271
+ assignment, no `++`/`--`, no `new` except `new URL(…)`, and calls only
272
+ from a list of reading methods (`querySelector(All)`, `getAttribute`,
273
+ `getItem`, `getComputedStyle`, `filter`, `some`, `JSON.parse`, …).
274
+ `kaloko validate` refuses anything else (`setItem`, `click`, `fetch`).
275
+ The guard is a convention, not a sandbox: a getter in the page can still
276
+ run code, so read data the page exposes (storage, `dataLayer`, the DOM)
277
+ and nothing that acts. Prefer `style_*`, `text_*` and `count` when they
278
+ can say the same.
279
+
280
+ On `kaloko evaluate --recheck` the URL checks run again from the stored URL.
281
+ The computed styles and expression results are stored with the capture, so a
282
+ recheck decides again from them when the criterion still reads the same
283
+ selector and property (or the same expression), for example after you fix
284
+ the expected value. A new selector, property or expression needs the live
285
+ page: the criterion is "not evaluated" with that reason until the step is
286
+ captured again.
287
+
219
288
  Two checks need the network and run next to the capture, once per page:
220
289
 
221
290
  - `links_ok`: every same-origin link (`selector`, default `a[href]`) answers
@@ -6,13 +6,19 @@ driver differs, and it is chosen by the scenario's `platform`.
6
6
 
7
7
  | platform | Driven by | UI tree | Interactions |
8
8
  |---|---|---|---|
9
- | `android` | `adb` (emulator or USB device) | uiautomator, in dp | deep links, tap by selector or point, type, back |
9
+ | `android` | `adb`: emulator, USB phone or a phone over Wi-Fi | uiautomator, in dp | deep links, tap by selector or point, type, back |
10
10
  | `ios` | `xcrun simctl` (iOS Simulator) | `idb` when installed, otherwise text by OCR (macOS Vision) | deep links; taps and typing need `idb` |
11
+ | `ios` + `real: true` | `xcrun devicectl` and `idevicescreenshot` or `pymobiledevice3` (a real iPhone) | text by OCR | install, launch, deep links; no taps |
11
12
  | `electron` | Playwright Electron | the full DOM: every web check applies | everything Playwright does (`page`) |
12
13
  | `desktop` | macOS window of a named app | macOS accessibility tree, otherwise OCR | none built in: use your tool, AppleScript or a person |
14
+ | `windows` | the app's window through Windows PowerShell and UI Automation (nothing to install) | UI Automation tree | tap by selector or point, type; walk on Windows |
13
15
 
14
- Anything else, including a real iPhone, Windows apps, Flutter/React Native
15
- under Detox, Maestro flows or XCUITest, is captured by import:
16
+ An installed web app (PWA) is a web scenario with `display_modes: [standalone]`; see
17
+ [devices.md](devices.md). A WebView inside an Android app is captured as a web page; see
18
+ [WebViews](#webviews-inside-apps).
19
+
20
+ Anything else, including Flutter or React Native under Detox, Maestro flows,
21
+ XCUITest or Appium runs, is captured by import:
16
22
 
17
23
  ```bash
18
24
  npx kaloko capture --step checkout --image shot.png --tree hierarchy.xml --device "Pixel 8" --density 2.625
@@ -24,6 +30,7 @@ npx kaloko capture --step checkout --image shot.png --tree hierarchy.xml --devic
24
30
  - Appium iOS XML (`XCUIElementType…`);
25
31
  - `idb ui describe-all --json`;
26
32
  - Maestro `hierarchy` JSON;
33
+ - Windows UI Automation JSON from Kaloko's `windows.ps1 tree` (`--platform windows`);
27
34
  - Kaloko's own `qawalk.tree.v1`.
28
35
 
29
36
  `--ocr` reads the text from the image instead (macOS). `--density` or
@@ -46,6 +53,7 @@ environments:
46
53
  app:
47
54
  electron: { main: dist/main.js } # or executable: path to the packaged app
48
55
  # desktop: { name: Notes } # a macOS app by its window owner name
56
+ # windows: { name: contoso, path: dist/Contoso.exe } # or aumid: a Store/MSIX app
49
57
  ```
50
58
 
51
59
  ```yaml
@@ -81,9 +89,50 @@ nodes:
81
89
  - The app language is set per locale: Android 13+ app locales, iOS
82
90
  `-AppleLanguages`, Electron `--lang`.
83
91
 
92
+ ## Devices, dark mode and motion
93
+
94
+ `devices:` walks the same scenario on several emulators, simulators or phones; each device is its own capture key
95
+ next to the colour schemes and motion of the run:
96
+
97
+ ```yaml
98
+ # qa/flows/shop-android.yml
99
+ platform: android
100
+ devices: [pixel-8-api-34, pixel-7-api-33, galaxy]
101
+ color_schemes: [light, dark]
102
+ ```
103
+
104
+ ```yaml
105
+ # kaloko.config.yml
106
+ environments:
107
+ staging:
108
+ app:
109
+ android:
110
+ package: com.acme.shop.staging
111
+ devices:
112
+ galaxy: { connect: 192.168.1.20:5555, title: Galaxy S24 } # a phone over Wi-Fi
113
+ ios:
114
+ bundle: com.acme.shop
115
+ devices:
116
+ my-iphone: { udid: 00008110-000A…, real: true, title: Test iPhone }
117
+ ```
118
+
119
+ - A name without an entry says what it is. Android: an adb serial (`emulator-5554`, `R58M…`, `host:port`) or an
120
+ AVD name (`pixel-8-api-34` finds `Pixel_8_API_34`; Kaloko starts it headless when it is not running). iOS: a
121
+ simulator by name and runtime (`iphone-15-ios-18` is "iPhone 15" on iOS 18; `iphone-se-3` is "iPhone SE (3rd
122
+ generation)").
123
+ - `kaloko start --devices pixel-8-api-34` overrides the scenario; `kaloko walk --devices galaxy` walks one of them.
124
+ - Real Android phones are woken up and unlocked (no PIN) and get a clean status bar (demo mode, 9:41, full battery).
125
+ `wake: false` and `status_bar: false` turn that off.
126
+ - Dark mode and reduced motion are set on the device per capture: `cmd uimode night` and the animator scales on
127
+ Android (the app redraws; some apps recreate the screen), `simctl ui appearance` on the iOS Simulator, emulated
128
+ media in Electron. The iOS Simulator has no command for reduced motion, and real devices keep their own setting;
129
+ the capture notes it.
130
+ - The canvas draws the device frame of the closest preset (`pixel-8-api-34` → Pixel 8) and offers the safe-area
131
+ overlay; app captures are the whole screen.
132
+
84
133
  **Step scripts**
85
134
 
86
- A script gets `{ app, page, locale, env, run, capture, link }`:
135
+ A script gets `{ app, page, webview, device, locale, env, run, capture, link }`:
87
136
 
88
137
  ```js
89
138
  // steps/add-to-cart.mjs
@@ -97,9 +146,52 @@ export default async ({ app, link, capture }) => {
97
146
  ```
98
147
 
99
148
  - `app.tap`, `app.wait` and `app.type` take app selectors. `app.tap({ x, y })`
100
- taps by coordinates, in dp or points.
149
+ taps by coordinates, in dp or points (logical pixels on Windows).
101
150
  - On Electron, selectors are CSS and `page` is the Playwright page, so
102
151
  any Playwright code works there.
152
+ - `capture()` takes every key of the current device (light and dark);
153
+ `capture({ viewport: 'pixel-8-api-34.dark' })` one of them.
154
+
155
+ ## WebViews inside apps
156
+
157
+ An Android WebView with debugging on (`WebView.setWebContentsDebuggingEnabled(true)` in debug builds) is a Chromium
158
+ page. `await webview()` in a step script returns it as a Playwright page (Kaloko forwards its DevTools socket with
159
+ adb), and `capture({ webview: true })`, or `webview: true` in `app.android`, captures it as a web page: the DOM, the
160
+ web checks, the accessibility basics. The native screen around it is not in that capture; capture both when both
161
+ matter.
162
+
163
+ iOS WKWebViews (`isInspectable`) speak Safari's Web Inspector protocol, which Playwright cannot attach to. Capture
164
+ those screens as app screens (tree or OCR), and test the web content itself as a web scenario in WebKit
165
+ (`browsers: [webkit]`) with an iPhone preset.
166
+
167
+ ## Windows apps
168
+
169
+ `platform: windows` with `app.windows: { name, path | aumid, args }` walks a desktop app on Windows through the
170
+ PowerShell every Windows has: the window as a screenshot (at its display scale), the UI Automation tree (control
171
+ types, AutomationId, names, values), clicks by selector and typing (SendKeys). It needs a signed-in desktop session
172
+ (a person's machine, or a `windows-latest` CI runner); a locked screen or a service account cannot see windows.
173
+ Elsewhere, take the screenshots on Windows and import them with
174
+ `kaloko capture --image shot.png --tree uia.json --platform windows`, or run `kaloko import` on a folder of
175
+ screenshots.
176
+
177
+ ## Real iPhones
178
+
179
+ `real: true` with the `udid` uses Xcode's `devicectl` to install (`app:` a `.app` or `.ipa` signed for the device),
180
+ launch and open deep links, and `idevicescreenshot` (`brew install libimobiledevice`) or `pymobiledevice3` for the
181
+ screenshot. On iOS 17 and newer, `pymobiledevice3` needs its tunnel running (`sudo pymobiledevice3 remote tunneld`).
182
+ There is no UI tree or tapping on a real device: text comes from OCR on macOS, and flows that need taps run in
183
+ XCUITest, Maestro or Appium, with each screen imported by `kaloko capture --image`.
184
+
185
+ ## React Native and Flutter
186
+
187
+ - **React Native** renders native views: `testID` becomes the Android resource-id and the iOS accessibility id, so
188
+ `[id$=checkout]` selectors work; `accessibilityLabel` becomes the name `app_labels` checks. Deep links through
189
+ React Navigation linking work as step paths.
190
+ - **Flutter** draws its own widgets. The UI tree only has what the semantics tree exposes: wrap controls in
191
+ `Semantics(label: …)` or give them `Key`s that reach the accessibility layer, otherwise checks see one big view.
192
+ Flutter web builds are a web scenario. For taps, Maestro handles Flutter well; import its screens.
193
+ - Both: keep a debug or staging flavour with a stable package name per environment, and deep links into every screen
194
+ a scenario needs. That makes steps a deep link instead of a script.
103
195
 
104
196
  ## Selectors and checks on the UI tree
105
197
 
@@ -127,8 +219,8 @@ dialog, webview and group.
127
219
  - **Pack `app` (AC7xx):** those four checks, plus a semantic question about
128
220
  the screen's purpose and next action.
129
221
  - **Web-only checks** (`no_overflow`, `http_status`, meta, `a11y_contrast`,
130
- `perf_*`): these are "not evaluated" on app captures. Do not put them on
131
- app steps.
222
+ `perf_*`, `url_*`, `style_*`, `js_equals`): these are "not evaluated" on
223
+ app captures. Do not put them on app steps.
132
224
  - **OCR trees:** these have text only, so role checks (labels, touch
133
225
  targets) are not evaluated there. Say so in the report and install `idb`
134
226
  when those checks matter.
@@ -150,7 +242,10 @@ questions about what is on the screen, not about the markup.
150
242
 
151
243
  - **Devices:** never install on a person's own phone or sign in with
152
244
  real accounts without asking. Emulators and simulators are yours to
153
- start, reset and install builds on.
245
+ start, reset and install builds on. Dark mode and the animation scales
246
+ are restored after a walk, and so is the status bar of a real phone.
247
+ - **Windows:** clicks and typing go to the real desktop. Walk on a machine
248
+ nobody is using at that moment, or a CI runner.
154
249
  - **Production apps:** production apps (store builds) are read-only in
155
250
  spirit. Walk them only with deep links and screens that change no data,
156
251
  unless the person agrees.
@@ -16,7 +16,9 @@ deployments; `--trigger pull_request` runs on every push instead and waits for
16
16
  the deployment):
17
17
 
18
18
  1. installs the project (`npm ci`, or pnpm, yarn, bun by the lockfile) and the
19
- Chromium of kaloko's Playwright (`kaloko browser install --with-deps`);
19
+ Chromium of kaloko's Playwright (`kaloko browser install --with-deps`), plus
20
+ WebKit and Firefox when a scenario or `walk.browsers` names them
21
+ (`--browsers chromium,webkit`; see [devices.md](devices.md));
20
22
  `KALOKO_BROWSER_CHANNEL=chromium` makes the run use it, so no separate CI
21
23
  config is needed;
22
24
  2. `kaloko ci preview-url --wait 600 --github-env` finds the preview URL, the
@@ -48,6 +48,45 @@ design/
48
48
  `kaloko design lint` checks the folder; `kaloko sync` runs it first and stops on errors (things that break on
49
49
  the live step), warnings are printed.
50
50
 
51
+ ## Devices, dark mode and app screens
52
+
53
+ Design steps can target device presets and colour schemes like any run ([devices.md](devices.md)):
54
+
55
+ ```yaml
56
+ # the design scenario (the same one development uses)
57
+ viewports: [iphone-15, pixel-8, ipad-landscape, laptop]
58
+ color_schemes: [light, dark]
59
+ ```
60
+
61
+ - On a design environment a phone or tablet preset is the whole screen (393 × 852 on an iPhone 15), rendered with
62
+ the preset's touch, pixel ratio and user agent, so `(pointer: coarse)` and `(hover: none)` apply. The step detail
63
+ draws the device frame; *Safe areas* shows the notch, the home indicator and where the first screen ends.
64
+ - Dark mode: write `@media (prefers-color-scheme: dark)` in the step's CSS, and the dark captures show it. A step
65
+ whose dark screen is a different page gets `<step>/dark/index.html` (or `<step>/<locale>/dark/index.html`); dark
66
+ captures render that page, and it travels with the revision.
67
+ - Token modes follow the scheme: with a `theme` modifier (or `light`/`dark` groups) in the token set, Dev mode, the
68
+ step spec and the token check of a dark capture name the dark tokens, and drift compares dark with dark.
69
+ - Browsers: design steps render in Chromium. Check the implementation in WebKit and Firefox instead.
70
+
71
+ **Native app screens as portable HTML.** A mockup of an iOS or Android screen is a full-screen page:
72
+
73
+ - Size it to the screen, not to a document: `html, body { height: 100% }` and the main area `min-height: 100dvh`.
74
+ Do not draw the status bar or the home indicator; the frame does.
75
+ - Keep content out of the safe areas with
76
+ `padding-top: max(env(safe-area-inset-top), var(--kaloko-safe-top, 0px))` (and `-bottom`, `-left`, `-right`).
77
+ On the phone `env()` gives the inset; in Kaloko `--kaloko-safe-*` does, because desktop browsers report 0. Add
78
+ `viewport-fit=cover` to the viewport meta.
79
+ - Use the roles and names the app will have: `<nav aria-label="Tabs">` with buttons for a tab bar, `<header>` with
80
+ an `<h1>` for the navigation bar, `<button>` and labelled inputs. The step spec and the checks match the
81
+ implementation by role and name, and React Native `accessibilityLabel` or Android `contentDescription` are those
82
+ names.
83
+ - Touch targets of at least 44 × 44 pt (iOS) or 48 × 48 dp (Android); the app pack checks them on the real app.
84
+ - One design serves both platforms unless they really differ. When they do, give the platform its own scenario
85
+ step or its own viewport set (`viewports: [iphone-15]` in one scenario, `[pixel-8]` in another), not user-agent
86
+ sniffing in the page.
87
+ - System fonts are not portable (`-apple-system` renders differently on each machine): bundle the font the app
88
+ uses, as the portable HTML rules say.
89
+
51
90
  ## Sync and publish
52
91
 
53
92
  - `kaloko walk` renders only revisions whose captures are missing (same files, same capture).
@@ -0,0 +1,109 @@
1
+ # Browsers, devices, colour schemes and motion
2
+
3
+ A run captures every step in each locale and viewport. Five more dimensions can be added to that matrix: the
4
+ browser engine, the colour scheme, reduced motion, forced colours (Windows high contrast) and the display mode of an
5
+ installed web app. Viewports can name device presets instead of sizes.
6
+
7
+ ## Asking for them
8
+
9
+ In the scenario, with defaults for the whole project in `walk:` of `kaloko.config.yml`, and per run on the
10
+ command line (the command line wins, then the scenario, then the config):
11
+
12
+ ```yaml
13
+ # qa/flows/checkout.yml
14
+ viewports: [iphone-se, iphone-15, pixel-8, ipad-landscape, laptop] # presets, or { desktop: [1280, 800] } as before
15
+ browsers: [chromium, webkit, firefox]
16
+ color_schemes: [light, dark]
17
+ reduced_motion: [no-preference, reduce] # optional
18
+ forced_colors: [none, active] # optional, Windows high contrast
19
+ display_modes: [browser, standalone] # optional, the page as an installed PWA
20
+ ```
21
+
22
+ ```bash
23
+ npx kaloko start --scenario qa/flows/checkout.yml --env staging --browsers chromium,webkit --color-schemes light,dark
24
+ npx kaloko start … --viewports iphone-se,fold-open,desktop-hd # presets the scenario does not list
25
+ npx kaloko walk # one pass per engine and device profile
26
+ npx kaloko walk --browsers webkit # only the WebKit captures of the run
27
+ npx kaloko browser install --browsers webkit,firefox # once per machine or CI job
28
+ npx kaloko devices # the preset catalogue
29
+ ```
30
+
31
+ Every combination is one capture: `checkout × 3 viewports × 2 browsers × 2 schemes` is 12 screenshots per step and
32
+ locale. Keep the matrix to what the task is about. A Safari bug report needs `browsers: [chromium, webkit]` on the
33
+ affected steps, not every dimension on every step. A run refuses more than 400 combinations.
34
+
35
+ ## Capture keys
36
+
37
+ A capture is keyed by the viewport name plus the values that differ from the default, joined by dots:
38
+ `mobile`, `mobile.webkit`, `iphone-15.webkit.dark`, `desktop.reduce`, `phone.standalone`. Chromium, light, no
39
+ motion preference, no forced colours and the browser display mode are the defaults. Runs from before these
40
+ dimensions have plain keys and read as Chromium. Use the keys wherever a viewport name goes: `--viewport
41
+ iphone-15.dark`, `capture({ viewports: ['mobile'] })` (a viewport name takes all its keys in the current pass),
42
+ `kaloko spec home --viewport desktop.dark`.
43
+
44
+ ## Device presets
45
+
46
+ | Kind | Presets |
47
+ |---|---|
48
+ | Phones | `iphone-se` (small), `iphone-15`, `iphone-16`, `iphone-16-pro-max` (large), `pixel-8`, `pixel-8-pro`, `galaxy-s24` |
49
+ | Foldables | `fold-folded`, `fold-open` |
50
+ | Tablets | `ipad-portrait`, `ipad-landscape`, `ipad-pro-portrait`, `ipad-pro-landscape`, `android-tablet-portrait`, `android-tablet-landscape` |
51
+ | Computers and TV | `laptop` (1440), `desktop-hd` (1920), `desktop-wide` (2560), `tv-1080p` |
52
+
53
+ - `<preset>-landscape` turns any phone, foldable or tablet sideways (`iphone-15-landscape`).
54
+ - A preset sets the CSS viewport, the pixel ratio, mobile mode, touch and the device's user agent. Web walks get
55
+ the screen minus the browser's bars (393 × 659 on an iPhone 15 in Safari); Design environments and installed
56
+ PWAs get the whole screen (393 × 852).
57
+ - Overrides: `{ phone: { device: iphone-15, height: 700 } }`, or `full_screen: true` for a kiosk or an app mockup.
58
+ - Screenshots stay in CSS pixels whatever the pixel ratio, so boxes, diffs and comments line up across devices.
59
+ - The values are those of the device class, not of a lab. They catch layout bugs; a real device catches the rest.
60
+
61
+ ## How each dimension is produced
62
+
63
+ | Dimension | How | Engines |
64
+ |---|---|---|
65
+ | Browser | Playwright's Chromium, WebKit (Safari's engine) and Firefox, each in its own pass | all |
66
+ | Device preset | its own browser context with the pixel ratio, mobile mode, touch and user agent | all; Firefox has no mobile mode, so presets there set size, ratio, touch and agent |
67
+ | Colour scheme, reduced motion, forced colours | `prefers-color-scheme`, `prefers-reduced-motion` and `forced-colors` emulated per capture | all |
68
+ | Display mode | `display-mode` media rules of same-origin style sheets and `matchMedia` answered as installed; full-screen size | all |
69
+
70
+ - Scripts that read `matchMedia('(display-mode: standalone)')` once at load still see the browser mode; CSS and
71
+ later calls see the installed mode. Check `navigator.standalone`-style logic on a real install.
72
+ - The shared browser (Chromium over CDP) walks the default pass; WebKit, Firefox and device profiles always get
73
+ their own context, signed in through the scenario's sessions as usual.
74
+ - E-mail steps are captured once, in the first pass.
75
+
76
+ ## Checks that only Chromium can run
77
+
78
+ These are "not evaluated" on WebKit and Firefox captures, with the reason, and do not make a step partial when
79
+ Chromium evaluated them:
80
+
81
+ - `a11y_ax_names` (the accessibility tree is read over the DevTools protocol);
82
+ - `a11y_focus_visible` (focus rings are compared on Chromium screenshots);
83
+ - `perf_cls` (only Chromium reports layout shifts);
84
+ - on WebKit also the Tab walk (`a11y_focus_order`, `a11y_keyboard_trap`, `a11y_skip_link`): WebKit moves Tab
85
+ between form controls only, like Safari by default, so the walk would report false gaps.
86
+
87
+ Everything else (DOM checks, contrast, overflow, links, LCP, TTFB, weight, semantic and vision criteria) runs on
88
+ every engine. Contrast is checked per colour scheme, so a dark-mode contrast bug fails on the `.dark` capture.
89
+
90
+ Semantic questions read the page text: the same text in another engine or scheme is the same question, answered
91
+ once and then from the cache. Vision questions (`vision: true`) look at each screenshot, so every combination is
92
+ asked separately; keep `vision: true` to the steps where the look is the point.
93
+
94
+ ## On the canvas
95
+
96
+ - Switchers for the browser, colour scheme, motion, forced colours and display mode sit next to the locale in the
97
+ top bar and in review mode. The map, the index, the step detail and review show the chosen combination.
98
+ With more than five values (20 locales, say) a switcher is a button that opens a searchable list with the
99
+ status of each value; `L` opens the locale list, `[` and `]` move to the previous and next locale.
100
+ - The step detail of a device preset draws its frame: bezel, status bar, the browser's bars and the island, notch
101
+ or camera. *Safe areas* marks the insets and where the first screen ends. Both can be switched off.
102
+ - *Beside* puts the same step in another browser or scheme next to it (WebKit beside Chromium, dark beside light).
103
+ - Links keep the key (`#step=cart&vp=iphone-15.webkit.dark`) and switch the canvas to it.
104
+
105
+ ## Doctor and CI
106
+
107
+ `kaloko doctor` lists the engines the scenarios and `walk.browsers` name and says how to install a missing one.
108
+ In CI install them in the job: `npx kaloko browser install --browsers chromium,webkit --with-deps` (Linux needs
109
+ the system libraries WebKit and Firefox bring).
@@ -18,6 +18,12 @@ environment whose URL comes from CI: `preview: { base_url: ${KALOKO_PREVIEW_URL}
18
18
  which `kaloko ci github|gitlab` adds and `kaloko ci preview-url` fills; see
19
19
  [ci.md](ci.md).
20
20
 
21
+ An environment says where the product runs. In which browsers, on which devices and in which colour scheme it is
22
+ walked is the run's matrix: `browsers`, `color_schemes`, `reduced_motion` and device presets in `viewports` of the
23
+ scenario, defaults in `walk:` of the config, `--browsers` and friends on `kaloko start`; see
24
+ [devices.md](devices.md). App devices (emulators, simulators, phones) are named per environment under
25
+ `app.<platform>.devices`; see [apps.md](apps.md).
26
+
21
27
  ## Who does what
22
28
 
23
29
  Kaloko needs evidence in its format: captures of screens and e-mails in a run.
@@ -41,6 +41,12 @@ call `kaloko session reset` themselves when the plan starts as a new visitor.
41
41
  Prefer turning an agent step into a playwright script once it has worked
42
42
  twice the same way.
43
43
 
44
+ A step script brings the page into the state the step accepts; it does not
45
+ check it. Do not compute a result in the script and write a `data-qa-*`
46
+ marker into the page for a criterion to find. The URL, computed styles and
47
+ values in the page have their own checks (`url_matches`, `style_equals`,
48
+ `js_equals`; see [acceptance.md](acceptance.md#url-computed-styles-and-values-in-the-page)).
49
+
44
50
  ## Driving the shared browser with your capabilities
45
51
 
46
52
  - Browser tool over MCP (e.g. Playwright MCP): connect to the CDP endpoint
@@ -28,6 +28,7 @@ file.
28
28
  | `list_notifications` | the token owner's inbox (`unread_only`, `limit`) |
29
29
  | `list_projects` | projects of the organization with counts; the slug is the `project` filter of `list_runs` and the value for `project:` in a scenario |
30
30
  | `get_criteria_verdicts` | reviewer verdicts on manual or overridden criteria of a run |
31
+ | `get_step_recording` | a step's recording and trace (`run_id`, `step`, `locale`, `viewport`, `trace: true` for the full trace): video URLs, duration, its chapter of a flow recording, failed attempts, the trace summary (failed requests, console errors, slowest request); see [recording.md](recording.md). `get_run_summary` lists trace summaries per step too |
31
32
  | `get_review_queue` | what a person still has to decide in a run (`run_id`, or `scenario` / nothing for the newest run): steps in review order with reasons, decisions, approvals carried over unchanged steps with their origin, counts and `review_url` to give the person |
32
33
  | `decide_step` | records your (the agent's) decision on a step: `approve`, or `return` with a `note`; marked as an agent's and not counted as a person's. Never use it to approve for a person |
33
34
  | `revoke_carried_approval` | revokes an approval carried over to a step that looks unchanged but should be looked at again (`run_id`, `step`) |
@@ -0,0 +1,95 @@
1
+ # Recordings, traces and the walkthrough video
2
+
3
+ A screenshot shows where a step ended. A recording shows how it got there:
4
+ the clicks, the spinner that never stopped, the menu that opened twice. A trace
5
+ lists what happened meanwhile: actions, network requests with status, size and
6
+ timing, and console messages.
7
+
8
+ ## When to record
9
+
10
+ Record when a screenshot cannot show the problem or the behaviour a reviewer
11
+ has to judge:
12
+
13
+ - a flaky step (passes, then fails on the same commit): `--record failed` keeps
14
+ only the attempts that fail;
15
+ - animations, transitions, loading states, drag and drop, menus and dialogs
16
+ that open and close;
17
+ - a bug report or a PR where the person needs to see the interaction once;
18
+ - a demo for a client or a release note (`kaloko video export`).
19
+
20
+ Do not record every walk by default. Recordings are much larger than
21
+ screenshots, count in the organization's storage and expire after 30 days on
22
+ Business. Traces are small and are kept for every walked web step anyway.
23
+
24
+ ## Commands
25
+
26
+ ```bash
27
+ kaloko walk --record # each step: from the previous screen to its capture
28
+ kaloko walk --record flow # the whole walk per locale, a chapter per step
29
+ kaloko walk --record failed # only steps whose script fails or whose checks fail
30
+ kaloko walk --record --record-quality low --steps checkout
31
+ kaloko walk --no-trace # no trace either
32
+ kaloko video list # what was recorded and traced, with sizes
33
+ kaloko video trace --step checkout # the step's trace as a timeline in the terminal
34
+ kaloko video export --format mp4 # the run's recordings with a title card per step
35
+ kaloko video tools # ffmpeg, adb, simctl on this machine
36
+ ```
37
+
38
+ In a scenario, or in `kaloko.config.yml` for the whole project:
39
+
40
+ ```yaml
41
+ capture:
42
+ record: { mode: failed, quality: low, max_seconds: 45 } # or true, step, flow, failed, false
43
+ trace: true
44
+ nodes:
45
+ - id: checkout
46
+ record: true # this step always; record: false never
47
+ ```
48
+
49
+ Quality presets: `low` 8 fps up to 960 px, `medium` (default) 12 fps up to
50
+ 1280 px, `high` 24 fps up to 1920 px. A step keeps its last 60 seconds and a
51
+ flow its last 15 minutes unless `max_seconds` says otherwise.
52
+
53
+ ## What you get
54
+
55
+ Per recorded step under `steps/<step>/<locale>/<viewport>.`: `rec.webm` (VP9
56
+ from Chrome, no extra tools), `rec.mp4` (H.264, when ffmpeg is installed; Safari
57
+ before iOS 17.4 needs it), `rec.poster.webp`, `rec.webp` (a few-second animated
58
+ preview; `rec.gif` where the browser cannot encode WebP) and `trace.json`. The
59
+ pointer and clicks are drawn into web recordings. A flow recording is
60
+ `attachments/flow/<locale>.rec.*`; the walkthrough is
61
+ `attachments/walkthrough.rec.<format>`.
62
+
63
+ Web and Electron record through Chrome's screencast; walks in Firefox or WebKit
64
+ are neither recorded nor traced. Apps: Android with `adb shell screenrecord`
65
+ (segments, touches shown), the iOS Simulator with `simctl io recordVideo`,
66
+ macOS desktop apps with `screencapture` (Screen Recording permission). App
67
+ recordings need ffmpeg for the poster and preview; `kaloko doctor` lists what is
68
+ missing with the install command for the OS.
69
+
70
+ ## On the canvas
71
+
72
+ The step detail gets Video next to Screenshot: the poster first, nothing loads
73
+ until played, markers for the start, the capture, actions and failed requests.
74
+ Technical shows the trace; a click on a row moves the video there. Cards play
75
+ the preview on hover. Replay plays the flow recording with its chapters. The
76
+ walkthrough video is in the top bar.
77
+
78
+ ## Plans and sharing
79
+
80
+ Recording is local and works on every plan. `kaloko share` uploads recordings
81
+ on Business and Enterprise; on Free and Team it says they stay in the local
82
+ preview and shares screenshots and traces. A recording over the service's file
83
+ limit (25 MB) stays local too: lower the quality or `max_seconds`. Recordings
84
+ expire 30 days after the run is shared (Enterprise chooses in Settings →
85
+ Storage); the screenshots stay as long as the run. Uploading is still "ask
86
+ first", and so is turning recordings on for a whole project.
87
+
88
+ ## Reading them as an agent
89
+
90
+ `kaloko video trace --step <id>` locally, or MCP `get_step_recording`
91
+ (`run_id`, `step`; `trace: true` adds the full trace) on a shared run. It
92
+ answers the video URLs, duration, the step's chapter of a flow recording,
93
+ failed attempts and the trace summary: failed requests with status, console
94
+ errors, the slowest request. Fetch the URLs with the same token. Start there
95
+ when a step failed and the evidence in the criteria is not enough.