kaloko 0.4.11 → 0.5.1

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 (48) hide show
  1. package/README.md +45 -4
  2. package/package.json +1 -1
  3. package/schemas/config.v1.json +42 -2
  4. package/schemas/run.v1.json +33 -2
  5. package/schemas/scenario.v1.json +107 -16
  6. package/skills/kaloko/SKILL.md +12 -4
  7. package/skills/kaloko/reference/acceptance.md +74 -3
  8. package/skills/kaloko/reference/apps.md +103 -8
  9. package/skills/kaloko/reference/ci.md +3 -1
  10. package/skills/kaloko/reference/collaboration.md +35 -6
  11. package/skills/kaloko/reference/design.md +39 -0
  12. package/skills/kaloko/reference/devices.md +109 -0
  13. package/skills/kaloko/reference/environments.md +6 -0
  14. package/skills/kaloko/reference/execution.md +15 -0
  15. package/skills/kaloko/reference/mcp.md +5 -2
  16. package/src/cli.mjs +366 -192
  17. package/src/lib/access.mjs +5 -2
  18. package/src/lib/apps/devices.mjs +187 -0
  19. package/src/lib/apps/drivers.mjs +11 -3
  20. package/src/lib/apps/ios-device.mjs +78 -0
  21. package/src/lib/apps/tree.mjs +9 -1
  22. package/src/lib/apps/webview.mjs +54 -0
  23. package/src/lib/apps/windows.mjs +108 -0
  24. package/src/lib/apps/windows.ps1 +78 -0
  25. package/src/lib/assertions.mjs +300 -0
  26. package/src/lib/build.mjs +4 -1
  27. package/src/lib/capture.mjs +146 -119
  28. package/src/lib/ci.mjs +8 -4
  29. package/src/lib/design-cli.mjs +35 -6
  30. package/src/lib/design.mjs +3 -1
  31. package/src/lib/devices.mjs +85 -0
  32. package/src/lib/devspec.mjs +3 -2
  33. package/src/lib/dimensions.mjs +326 -0
  34. package/src/lib/doctor.mjs +31 -6
  35. package/src/lib/drift.mjs +8 -4
  36. package/src/lib/evaluate.mjs +25 -7
  37. package/src/lib/import.mjs +2 -1
  38. package/src/lib/jev.mjs +3 -1
  39. package/src/lib/manifest-core.mjs +12 -3
  40. package/src/lib/probe.mjs +60 -0
  41. package/src/lib/recheck.mjs +6 -1
  42. package/src/lib/scenario.mjs +3 -0
  43. package/src/lib/signoff-core.mjs +138 -0
  44. package/src/lib/signoff.mjs +83 -0
  45. package/src/lib/stability-core.mjs +4 -2
  46. package/src/lib/stability.mjs +82 -0
  47. package/src/lib/walk-dimensions.mjs +83 -0
  48. package/viewer/index.html +739 -48
@@ -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
@@ -72,8 +72,11 @@ comments; at the end they accept or return the run.
72
72
  Comments are threads in three places:
73
73
 
74
74
  - **A step:** optionally pinned to a position on the screenshot
75
- (`pin: {x, y}` as fractions, per viewport) or to an element of a live
76
- design step (`anchor`).
75
+ (`pin: {x, y}` as fractions, per viewport), drawn on it (a box, an arrow or
76
+ a line: `shape`, with `region` giving the area as fractions and in CSS
77
+ pixels of the page, and for an arrow the point it aims at), or attached to
78
+ an element of a live design step (`anchor`). Comments may carry images
79
+ (`attachments`, each with a download URL that needs the token).
77
80
  - **The whole run:** `node_id` is `''`.
78
81
  - **The whole flow:** these stay across runs, e.g. "add the empty-cart
79
82
  state".
@@ -85,8 +88,12 @@ said what.
85
88
 
86
89
  The loop, for every open thread:
87
90
 
88
- 1. **Understand it.** Open the canvas link when a pin or an element
89
- matters; the position is on the screenshot of that viewport and locale.
91
+ 1. **Understand it.** A `region` says exactly where to look: `px` is
92
+ `[x, y, width, height]` in CSS pixels of the captured page for that
93
+ viewport (`region.viewport`) and locale; find the element there in the
94
+ step's HTML or screenshot. Download attached images (a corrected copy, a
95
+ reference) before you change anything. Open the canvas link when a pin or
96
+ an element matters.
90
97
  2. **Fix it** in the code or the scenario. When the comment asks for a
91
98
  change of intent (criteria, scope), ask the person first.
92
99
  3. **Answer and close.** Use
@@ -181,14 +188,25 @@ e-mail works too). Use it when a step needs a decision from a specific person
181
188
  Put the relevant lines in the issue comment. On the canvas the same
182
189
  comparison is the **Compare** selector in the top bar (or `#cmp=<run>` in
183
190
  the URL): pixel differences per step, changed criteria, and a side-by-side
184
- or diff view in the lightbox. Pixel diffs are computed in the browser;
185
- nothing is uploaded.
191
+ or diff view in the lightbox, plus *Swipe* (a divider dragged across both
192
+ screenshots) and *Onion skin* (this run faded over the other). Pixel diffs
193
+ are computed in the browser; nothing is uploaded. **G** opens the grid: one
194
+ step in every locale and viewport at once, failing cells marked.
186
195
  - **Manual criteria** are decided on the canvas by a reviewer; treat a
187
196
  reviewer's `fail` like a failing deterministic check.
188
197
  - Admins may name **required approvers** per scenario (Settings). Such a
189
198
  run stays `pending` until every one of them approved; `kaloko feedback`
190
199
  prints `awaiting approval from: …`, so do not treat a single approval as
191
200
  acceptance there.
201
+ - An accepted run has a **signed acceptance record** (Business and up): the
202
+ run, scenario, commit and environment, who approved what and when (people
203
+ apart from tokens and agents, approvals carried over unchanged steps with
204
+ where they came from), criteria results, capture hashes and the hash of
205
+ `run.json`, signed with the service's Ed25519 key. `kaloko signoff <run>`
206
+ downloads the JSON and the PDF and checks them; `kaloko signoff --verify
207
+ <file>` checks a record someone handed over (public key at
208
+ `/.well-known/kaloko-signoff.json`). Give it to the person when they ask
209
+ for proof of acceptance; never create one for a run that is not accepted.
192
210
 
193
211
  ## Cleaning up runs
194
212
 
@@ -245,6 +263,17 @@ Everything above is available over the API with an organization token:
245
263
  (the whole flow), and per comment `PATCH`, `DELETE`, `POST …/<cid>/resolve`
246
264
  (with `note`) and `…/<cid>/reopen`; `GET /api/v1/orgs/<org>/comments?state=open|all&scenario=&run=&mine=1`
247
265
  across the organization. Lists of runs and scenarios carry `open_comments`.
266
+ A comment body may carry `shape` (`{kind: box, box: [x, y, w, h]}`,
267
+ `{kind: arrow, from, to}` or `{kind: pen, points}` in fractions, with
268
+ `viewport` and `size`) and `attachments` (ids from
269
+ `POST …/comments/attachments?name=<file>` with the image as the body: PNG,
270
+ JPEG, WebP or GIF up to 5 MB, counted in the plan's storage).
271
+ - `GET|POST /api/v1/orgs/<org>/runs/<id>/ignore-regions`,
272
+ `DELETE …/ignore-regions/<rid>`, `GET …/scenarios/<id>/ignore-regions[?state=all]`
273
+ and `POST …/scenarios/<id>/ignore-regions/applied` (`{ids}`): regions drawn
274
+ to leave out of pixel diffs (drawing needs Developer / Tester).
275
+ - `GET /api/v1/orgs/<org>/runs/<id>/signoff` (JSON), `…/signoff.pdf`,
276
+ `…/signoff.html`: the signed acceptance record of an accepted run.
248
277
  - `GET /api/v1/orgs/<org>/notifications[?unread=1]`, `POST` with
249
278
  `{"read": "all"}` or `{"read": [ids]}`
250
279
  - `GET /api/v1/orgs/<org>/runs[?project=&scenario=&kind=&verdict=&author=&environment=&branch=&q=][&latest=1]`
@@ -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
@@ -82,6 +88,7 @@ When pixel diffs between runs keep flagging the same clock, ad or carousel:
82
88
  kaloko walk --stability 3 # 3 screenshots per capture, a moment apart; what moved is noted
83
89
  kaloko stability # this run against the 3 previous local runs of the scenario
84
90
  kaloko stability --apply # adds the suggested ignore: to each step (block-style YAML only)
91
+ kaloko stability --pull # regions people drew on screenshots on kaloko.app go into ignore:
85
92
  ```
86
93
 
87
94
  `kaloko stability` writes `stability.json` and `stability.yml` into the run
@@ -92,6 +99,14 @@ changed is not noise. `--same-commit` compares only runs of the same commit,
92
99
  `--runs a,b` names them, `--last N` sets how many runs are compared. Capture
93
100
  the steps again after applying: the capture records the ignored regions.
94
101
 
102
+ People mark noise by hand too: a Developer / Tester draws a rectangle on a
103
+ screenshot (step detail → *Mark a changing region*). It is kept per flow, shows
104
+ dashed on every run and the canvas pixel diff already skips it.
105
+ `kaloko stability --pull` writes those regions into the scenario's `ignore:`
106
+ (appended to an existing block list, never twice) and tells the service they
107
+ are in the file; walk those steps again afterwards. MCP `list_ignore_regions`
108
+ lists them, `add_ignore_region` suggests one.
109
+
95
110
  ## Only what changed
96
111
 
97
112
  After a fix, walk the steps the change reaches instead of the whole flow:
@@ -42,8 +42,11 @@ file.
42
42
 
43
43
  | Tool | Use |
44
44
  |---|---|
45
- | `list_comments` | open threads (or `state: all`) across the organization, filtered by `scenario` or `run_id`, with replies, pins, anchors and who wrote them |
46
- | `add_comment` | comment on a step (`run_id` + `step`), the whole run (`run_id`) or the whole flow (`scenario`); `parent_id` replies; optional `pin` `{ x, y, viewport }` |
45
+ | `list_comments` | open threads (or `state: all`) across the organization, filtered by `scenario` or `run_id`, with replies, pins, anchors, drawn regions (`region.px`: CSS pixels of the page in `region.viewport`), attached images with URLs, and who wrote them |
46
+ | `add_comment` | comment on a step (`run_id` + `step`), the whole run (`run_id`) or the whole flow (`scenario`); `parent_id` replies; optional `pin` `{ x, y, viewport }` or `shape` (box or arrow in fractions, with `viewport` and `size`) |
47
+ | `list_ignore_regions` | regions people drew to leave out of pixel diffs, per step, with the `ignore:` entry each becomes (`kaloko stability --pull` writes them) |
48
+ | `add_ignore_region` | suggest such a region (`run_id`, `step`, `viewport`, `rect` in CSS px); Developer / Tester, scope upload or full |
49
+ | `get_signoff_record` | the signed acceptance record of an accepted run (Business): approvals with provenance, criteria, capture hashes, signature |
47
50
  | `resolve_comment` | close a thread with a `note` that says what changed (it becomes the last reply) |
48
51
  | `reopen_comment`, `edit_comment`, `delete_comment` | reopen; edit or delete your own comment |
49
52