@keepsnappy/agent 0.1.0-alpha.12 → 0.1.0-alpha.14
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/README.md +53 -55
- package/dist/appium-driver.js +334 -84
- package/dist/keepsnappy.js +456 -931
- package/examples/farm/flows/{perflab → keepsnappy}/begin-run.yaml +1 -1
- package/examples/farm/flows/keepsnappy/end-run.yaml +17 -0
- package/examples/farm/flows/keepsnappy/next-token.js +7 -0
- package/examples/farm/flows/{perflab/run-perflab.yaml → keepsnappy/run-keepsnappy.yaml} +1 -1
- package/examples/farm/flows/{perflab → keepsnappy}/send-markers.js +3 -3
- package/examples/farm/flows/keepsnappy/step-markers.yaml +13 -0
- package/examples/farm/flows/keepsnappy/tokens.js +2 -0
- package/examples/farm/workflows/{perflab-measure.yml → keepsnappy-measure.yml} +17 -17
- package/examples/farm/workflows/perf-device-run.fragment.yml +5 -5
- package/examples/github-actions.yml +14 -14
- package/examples/keepsnappy.yml +2 -4
- package/examples/maestro-steps/e2e-community-and-live-chat.yaml +10 -10
- package/maestro/{perflab → keepsnappy}/send-markers.js +3 -3
- package/maestro/{perflab → keepsnappy}/step.js +6 -6
- package/maestro/keepsnappy/step.yaml +11 -0
- package/package.json +1 -1
- package/examples/farm/flows/perflab/end-run.yaml +0 -16
- package/examples/farm/flows/perflab/next-token.js +0 -7
- package/examples/farm/flows/perflab/step-markers.yaml +0 -13
- package/examples/farm/flows/perflab/tokens.js +0 -2
- package/maestro/perflab/step.yaml +0 -11
package/README.md
CHANGED
|
@@ -1,41 +1,40 @@
|
|
|
1
1
|
# @keepsnappy/agent
|
|
2
2
|
|
|
3
|
-
The `keepsnappy` host CLI. It runs the customer's own test command several times, collects the
|
|
3
|
+
The `keepsnappy` host CLI. It runs the customer's own test command several times, collects the keepsnappy data the app wrote during each run, turns it into metrics, and with `--upload` sends the runs to the server, which judges them. v1 is measure-only: no code changes in the customer app, no driver of our own (the test command is Maestro, Appium, XCUITest, anything that exits 0 on success).
|
|
4
4
|
|
|
5
5
|
## Commands
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
8
|
bun install && bunx tsc --noEmit && bunx vitest run
|
|
9
|
-
bun run build # dist/src/cli.js (bin:
|
|
9
|
+
bun run build # dist/src/cli.js (bin: keepsnappy)
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
keepsnappy measure --repeat 10 \
|
|
12
12
|
--arms A=/builds/base.app,B=/builds/candidate.app \
|
|
13
13
|
--platform ios-sim [--device <udid>] [--bundle-id com.x.app] [--out runs/s1] [--timeout 600] \
|
|
14
14
|
[--ri-capture observations.ndjson] [--mem-window chat.open] \
|
|
15
15
|
-- maestro test flows/open-chat.yaml
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
perflab doctor # xcrun, simctl, devicectl, maestro, appium, node, booted sims, devices
|
|
16
|
+
keepsnappy extract runs/s1 --ri-capture observations.ndjson # re-derive metrics.csv (adds ri.* / mem.*)
|
|
17
|
+
keepsnappy aa runs/s1 [--reps 300 --boot 400] # noise floor / MDE per metric
|
|
18
|
+
keepsnappy doctor # xcrun, simctl, devicectl, maestro, appium, node, booted sims, devices
|
|
20
19
|
```
|
|
21
20
|
|
|
22
21
|
- `--arms`: `NAME=<.app/.ipa path>` (installed with `simctl install` / `devicectl device install app`, only when the arm changes) or `NAME=<bundle id>` (already installed). Without `--arms` there is one arm `A`: plain repetition, usable for `keepsnappy aa`.
|
|
23
|
-
- `--arm-env 'A:KEY=VAL'` (repeatable): extra env for the test command on that arm's runs, also exported as `MAESTRO_KEY` (Maestro forwards only `MAESTRO_*` to flows). Used for same-binary arms that differ only in knobs, e.g. `--arms A=dev.
|
|
22
|
+
- `--arm-env 'A:KEY=VAL'` (repeatable): extra env for the test command on that arm's runs, also exported as `MAESTRO_KEY` (Maestro forwards only `MAESTRO_*` to flows). Used for same-binary arms that differ only in knobs, e.g. `--arms A=dev.keepsnappy.reference,B=dev.keepsnappy.reference --arm-env 'A:REF_KNOBS=…' --arm-env 'B:REF_KNOBS=…'`. The arms with their env are written to `<out>/arms.json`. Order stays ABBA.
|
|
24
23
|
- `--platform ios-device` needs `--device <udid>`. The simulator default target is `booted`.
|
|
25
24
|
- The test command runs as argv (no shell) with `KEEPSNAPPY_RUN_ID`, `KEEPSNAPPY_ARM`, `KEEPSNAPPY_RUN_DIR`, `KEEPSNAPPY_MARKER_URL` and `MAESTRO_KEEPSNAPPY_MARKER_URL` / `MAESTRO_KEEPSNAPPY_RUN_ID` in its environment.
|
|
26
|
-
- Declared steps (Maestro): call `maestro/
|
|
25
|
+
- Declared steps (Maestro): call `maestro/keepsnappy/step.yaml` with `STEP`/`PHASE` (start|end) from your flow; `measure` adds `step:<name>.*` rows (source=declared). `--no-markers` turns the endpoint off, `--clock-offset-ms` overrides the clock estimate. See docs/research/28-step-markers-prototype.md.
|
|
27
26
|
|
|
28
27
|
## `keepsnappy run` and `keepsnappy.yml` (customer entry point)
|
|
29
28
|
|
|
30
|
-
`keepsnappy run` is the wrapper for the e2e command you already have (mode A in docs/research/23-execution-modes-and-data-flow.md). It runs the command N times, drops runs where it failed, pulls the app's
|
|
29
|
+
`keepsnappy run` is the wrapper for the e2e command you already have (mode A in docs/research/23-execution-modes-and-data-flow.md). It runs the command N times, drops runs where it failed, pulls the app's keepsnappy data after each run, derives metrics and writes the out dir. It does not judge anything itself: `--upload` sends the runs and the server says what changed. Nothing is built or installed by it: your CI supplies the build (with `KEEPSNAPPY=1`) and the device.
|
|
31
30
|
|
|
32
31
|
```sh
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
keepsnappy run # command built from keepsnappy.yml
|
|
33
|
+
keepsnappy run -- maestro test --include-tags perf e2e/flows/ # wrap your own command (config still gives bundle id, device, repetitions)
|
|
34
|
+
keepsnappy run --upload --wait # send the results; the server judges them and the CLI prints its verdict
|
|
36
35
|
```
|
|
37
36
|
|
|
38
|
-
Flags: `--config` (default `keepsnappy.yml`), `--repeat N`, `--out DIR`, `--
|
|
37
|
+
Flags: `--config` (default `keepsnappy.yml`), `--repeat N`, `--out DIR`, `--upload [--ingest-url URL]`, `--wait`, `--fail-on-worse`, `--commit SHA` / `--branch NAME` (default `GITHUB_SHA` / `GITHUB_HEAD_REF` on a pull request, else `GITHUB_REF_NAME`), `--base-commit SHA`, `--device-model` / `--device-os` (default: read from the booted simulator), `--scenario NAME` (flow name for a command given after `--`), `--replay <out dir>[:ARM]` (repeatable: take recorded runs instead of running anything), `--no-markers`, `--clock-offset-ms`.
|
|
39
38
|
|
|
40
39
|
Commit details: when a run set opens (`keepsnappy run --upload`, `keepsnappy farm open`), the CLI reads the subject, author
|
|
41
40
|
name and committer date of `--commit` and `--base-commit` with `git log` in the checkout (argv only, 5 s timeout) and
|
|
@@ -45,6 +44,8 @@ or outside a pull request, the fields are left out and nothing fails. On a GitHu
|
|
|
45
44
|
the merge commit, so its title is "Merge … into …"; pass `--commit ${{ github.event.pull_request.head.sha }}` (with the
|
|
46
45
|
head commit fetched) to get the branch's own commit.
|
|
47
46
|
|
|
47
|
+
Base commit: without `--base-commit`, a run that is not on main sends `base_commit_sha` = `git merge-base origin/main <commit>` (then `main`), the merge-base of main with the commit recorded as `commit_sha` (`--commit`, else `GITHUB_SHA`): the two fields always name one commit. It is never taken from `HEAD`: without a recorded commit nothing is sent. After main was merged into the branch it is the last merged main commit, not where the branch was cut; on a `pull_request` checkout `commit_sha` is GitHub's merge commit, so it is main's tip (pass `--commit ${{ github.event.pull_request.head.sha }}` and both fields name the branch's own commit). `GITHUB_BASE_REF` names another target branch (`origin/<it>`) and `KEEPSNAPPY_BASE_REF` overrides both; a repo whose default branch is not `main` sets it, e.g. `KEEPSNAPPY_BASE_REF=origin/master`. It is recorded only; the verdict does not read it. Nothing is sent when the commit is already on main (including a run on main under any remote name). If git cannot tell (no commit recorded, no git, main or the commit not in the checkout), nothing is sent, one warning line is printed and the run goes on: a wrong base is worse than none. In CI, check out with `fetch-depth: 0` (or fetch main and the commit).
|
|
48
|
+
|
|
48
49
|
Journey titles: on `--upload` the run set carries `journeys[]` — per flow its key (the file stem), `source_path` (the flow file relative to the git repo root) and a title and description: from the `{path, title, description}` form of `flows.paths`, else the flow's own `name:` (when it differs from the stem) and the first paragraph of the comment that opens the file. Given values update the journey on the dashboard; absent ones keep what is stored.
|
|
49
50
|
|
|
50
51
|
Flows: with `runner: maestro` every flow file is run on its own (one `maestro test` per file per repetition, tag filters applied) and its file stem is the journey name (`meta.scenario`, plan D1). A directory in `flows.paths` means the flow files directly in it (not `config.yaml`). A command after `--` or `flows.command` is one flow, named by `--scenario`, its only `.yaml` argument, or `e2e`. With several flows, run ids get the flow as a suffix and `runs.ndjson` records `scenario`.
|
|
@@ -55,40 +56,37 @@ Annotated example: `examples/keepsnappy.yml`. Every setting:
|
|
|
55
56
|
|---|---|
|
|
56
57
|
| `project.key_env` | Name of the env var with the project key (default `KEEPSNAPPY_KEY`). Only read with `--upload`. |
|
|
57
58
|
| `project.ingest_url` | KeepSnappy API base URL for `--upload` (or `--ingest-url`, or env `KEEPSNAPPY_API`). Defaults to the hosted KeepSnappy API. |
|
|
58
|
-
| `app.bundle_id` | Required. App whose
|
|
59
|
+
| `app.bundle_id` | Required. App whose keepsnappy log is read after each run. |
|
|
59
60
|
| `platform`, `device` | `ios-sim` (default; `device` = UDID or `booted`) or `ios-device` (UDID required). One device per invocation in v1. |
|
|
60
61
|
| `repetitions` | Runs of the whole flow set (default 5). |
|
|
61
|
-
| `baseline_branch` | Branch the baseline comes from (default `main`). Recorded for CI; the CLI compares with the directory you pass to `--baseline`. |
|
|
62
62
|
| `flows.runner` | `maestro` (build `maestro test ...`) or `command` (run `flows.command`). |
|
|
63
63
|
| `flows.tags`, `flows.exclude_tags` | `--include-tags=` / `--exclude-tags=`. |
|
|
64
|
-
| `flows.paths` | Files, directories or globs (`*`, `?`, `**`); expanded by
|
|
64
|
+
| `flows.paths` | Files, directories or globs (`*`, `?`, `**`); expanded by keepsnappy (no shell). With only tags the path defaults to `.`. An entry can also be `{path, title, description}` (one flow file): how the dashboard names the journey. |
|
|
65
65
|
| `flows.command` | Any command that exits 0 on success (Appium, Detox, ...); text or list, run without a shell. |
|
|
66
66
|
| `flows.maestro_version` | Note only: printed at the start of the run. |
|
|
67
67
|
| `steps.rename` | `old: new` names applied to metrics (`old.dur_ms` becomes `new.dur_ms`, `step:old.*` too), so history keeps stable names. |
|
|
68
|
-
| `
|
|
69
|
-
| `output.dir` | Out dir (default `keepsnappy-out`): `runs.ndjson`, `metrics.csv`, `<run>/test.log`, `<run>/perf-lab.log`, `<run>/markers.ndjson`, and with a baseline `compare/metrics.csv` + `compare/verdict.json`. |
|
|
68
|
+
| `output.dir` | Out dir (default `keepsnappy-out`): `runs.ndjson`, `metrics.csv`, `<run>/test.log`, `<run>/keepsnappy.log`, `<run>/markers.ndjson`. |
|
|
70
69
|
| `timeout_seconds` | Per run of the command. |
|
|
71
70
|
|
|
72
71
|
Unknown settings and wrong types are errors, all listed together.
|
|
73
72
|
|
|
74
|
-
Results and exit codes: results never change the exit code by default (ruling D9, no blocking PR checks): `0` also for "More work". `--fail-on-worse` (needs `--wait`) makes "More work" exit `3`. The CLI exits `2` for a configuration problem (bad or missing `keepsnappy.yml`, flags, a glob matching nothing, `--wait` without a branch or key) and `1` for an infrastructure problem (cannot read the device log,
|
|
73
|
+
Results and exit codes: results never change the exit code by default (ruling D9, no blocking PR checks): `0` also for "More work". `--fail-on-worse` (needs `--wait`) makes "More work" exit `3`. The CLI exits `2` for a configuration problem (bad or missing `keepsnappy.yml`, flags, a glob matching nothing, `--wait` without a branch or key) and `1` for an infrastructure problem (cannot read the device log, an API request failing; results stay in the out dir). Failed runs are marked invalid in `runs.ndjson` / `metrics.csv` and ignored by the statistics. The ending summary reads like:
|
|
75
74
|
|
|
76
75
|
```
|
|
77
76
|
7/8 runs valid (dropped: 1 test-failed: exit 1)
|
|
78
|
-
|
|
79
|
-
Top changes:
|
|
80
|
-
More work chat.open: renders +100% (100 -> 200)
|
|
81
|
-
More work: 1 Less work: 0 No change: 38 Not enough data yet: 3
|
|
77
|
+
Results: keepsnappy-out
|
|
82
78
|
```
|
|
83
79
|
|
|
84
|
-
`--upload
|
|
80
|
+
With `--upload --wait` the server's result follows it.
|
|
81
|
+
|
|
82
|
+
`--upload` (off by default) needs a project key with `run` scope in the env var named by `project.key_env` (default `KEEPSNAPPY_KEY`); the API URL defaults to the hosted KeepSnappy API. With a branch it opens a run set (`POST /v1/run-sets`) before measuring, so a wrong key fails before device time is spent; then sends each valid run: `POST /v1/runs` (header `x-keepsnappy-key`, `meta.scenario`, `meta.run_set_id`), `POST /v1/windows` with the run's own `KEEPSNAPPY window` log lines (run token, 200 per batch, deterministic batch ids so a retry is safe), `POST /v1/markers` with its declared step markers, `POST /v1/runs/:id/finish`. A 429 is retried after `retry-after`. Then it closes the run set:
|
|
85
83
|
|
|
86
84
|
- with `--wait`: `POST /v1/run-sets/:id/complete` computes the verdicts inline and the CLI prints them in plain words (More work / Less work / No change / Not enough data yet per journey and device, the top changed checks, "Few runs — result may be noisy" when the server flags it) plus a dashboard link that opens the comparison;
|
|
87
85
|
- without `--wait`: the set is closed with `verdicts: on-read` and the CLI exits 0 at once, printing the dashboard link; the verdicts are computed when someone looks.
|
|
88
86
|
|
|
89
|
-
On the baseline branch (`main` by default, set per app on the server) the runs grow each journey's history; on any other branch they are compared with it. Without a branch the runs are uploaded as before, without a run set and without a verdict. The CLI writes `<out>/
|
|
87
|
+
On the baseline branch (`main` by default, set per app on the server) the runs grow each journey's history; on any other branch they are compared with it. Without a branch the runs are uploaded as before, without a run set and without a verdict. The CLI writes `<out>/keepsnappy-result.json` (`run_set_id`, `dashboard_url`, `state`, summary) and `<out>/keepsnappy-run.json` (device, branch), and on GitHub Actions appends the result to `$GITHUB_STEP_SUMMARY`. Ready workflow: `examples/github-actions.yml`.
|
|
90
88
|
|
|
91
|
-
`keepsnappy
|
|
89
|
+
There is no local verdict. `keepsnappy run --baseline <dir>` and `keepsnappy verdict` (any form) exit `2` with "use --upload; the server judges", and `GET /v1/baselines` answers `410`.
|
|
92
90
|
|
|
93
91
|
## `keepsnappy farm` (device farm, the app pushes)
|
|
94
92
|
|
|
@@ -97,12 +95,12 @@ app (SDK activated per iteration with a run token) pushes its windows to the API
|
|
|
97
95
|
markers there directly. Ready workflow and Maestro flows: `examples/farm/`.
|
|
98
96
|
|
|
99
97
|
```sh
|
|
100
|
-
|
|
98
|
+
keepsnappy farm open --api https://keepsnappy-api-….run.app --branch main --commit "$APP_COMMIT" \
|
|
101
99
|
--device-model 'iPhone 15 Pro' --device-os 17 --device-provider browserstack \
|
|
102
100
|
--journeys e2e-community-and-live-chat --repeat 5 \
|
|
103
|
-
--tokens-js farm-run/
|
|
104
|
-
# ... the farm session runs (browserstack-run.mjs --suite-overlay "flows/
|
|
105
|
-
|
|
101
|
+
--tokens-js farm-run/keepsnappy-tokens.js --state farm-run/keepsnappy-farm.json
|
|
102
|
+
# ... the farm session runs (browserstack-run.mjs --suite-overlay "flows/keepsnappy/tokens.js:farm-run/keepsnappy-tokens.js")
|
|
103
|
+
keepsnappy farm close --state farm-run/keepsnappy-farm.json --bs-build farm-run/browserstack.ios.json --settle-seconds 60 --wait
|
|
106
104
|
```
|
|
107
105
|
|
|
108
106
|
`keepsnappy farm run` does the whole night in one command (docs/plans/54a): resolve the app (`--app eas:<id>|file|bs://`), `farm open`, an optional BrowserStack Local tunnel (`--tunnel-for localhost:<port>`, repeatable), the device run (`--driver maestro` with `--suite-dir`/`--execute`, or `--driver appium --journey-module FILE` for the built-in Appium driver, `@keepsnappy/appium-driver`, docs/plans/54b; `--driver-script FILE` still runs your own driver), `farm close`, and cleanup (tunnel stopped, tokens file reset) whatever happened. `--env KEY=VALUE` values may use `{tunnel:<port>}`, `{run_id}`, `{out}`, `{tokens_js}`, `{run_set_id}`. Exit codes as `farm close`; a failed device run is 1. `--dry-run` prints the plan with no request. Needs `KEEPSNAPPY_KEY`, `BROWSERSTACK_USERNAME`, `BROWSERSTACK_ACCESS_KEY` (and `EXPO_TOKEN` for `eas:`).
|
|
@@ -117,14 +115,14 @@ it, not by what the phone reports) and one run per journey per iteration, ahead
|
|
|
117
115
|
runner. It writes:
|
|
118
116
|
|
|
119
117
|
- `--tokens-js`: the only file holding run tokens. BrowserStack caps env values at 100 characters, so the tokens travel
|
|
120
|
-
inside the uploaded suite; the dispatcher overlays this file at `e2e/flows/
|
|
118
|
+
inside the uploaded suite; the dispatcher overlays this file at `e2e/flows/keepsnappy/tokens.js` for one upload. ES5,
|
|
121
119
|
for Maestro's `runScript`:
|
|
122
120
|
```js
|
|
123
|
-
output.
|
|
124
|
-
output.
|
|
125
|
-
output.
|
|
121
|
+
output.keepsnappyApi = "https://keepsnappy-api-….run.app";
|
|
122
|
+
output.keepsnappyRunSet = "<run set id>";
|
|
123
|
+
output.keepsnappyTokens = {"<journey>": ["pl_rt_…", …one per iteration]};
|
|
126
124
|
```
|
|
127
|
-
`next-token.js` (examples) hands out `keepsnappyTokens[JOURNEY][i]` as `output.
|
|
125
|
+
`next-token.js` (examples) hands out `keepsnappyTokens[JOURNEY][i]` as `output.keepsnappyToken` for iteration i.
|
|
128
126
|
- `--state`: JSON for `farm close`, no token and no key (safe to upload as an artifact): `version`, `api`, `run_set_id`,
|
|
129
127
|
`dashboard_url`, `branch`, `commit`, `base_commit`, `device`, `journeys`, `repeat`, `tokens_js`, `opened_at`,
|
|
130
128
|
`expires_at`, `runs[]` (`order`, `journey`, `iteration`, `run_id`, `external_id`), then `closed_at` and `result`.
|
|
@@ -138,7 +136,7 @@ runner. It writes:
|
|
|
138
136
|
is never blamed; a Maestro failure stops the session, so later iterations have no data;
|
|
139
137
|
3. finishes every run not yet finished (a run without a single batch is reported as "no data", never judged), closes the
|
|
140
138
|
set (`--wait`: verdicts inline; else on read) and prints the iterations and the same plain-words verdict as
|
|
141
|
-
`keepsnappy run --upload --wait`, plus `$GITHUB_STEP_SUMMARY` and `<out>/
|
|
139
|
+
`keepsnappy run --upload --wait`, plus `$GITHUB_STEP_SUMMARY` and `<out>/keepsnappy-result.json` (`--out`, default the
|
|
142
140
|
state's directory);
|
|
143
141
|
4. puts the empty stub back into the tokens file, also when a step above failed. The example workflow also deletes the
|
|
144
142
|
file before uploading `$OUT_DIR`.
|
|
@@ -153,13 +151,13 @@ result: a pull request only when something got worse (a table of the 3 biggest c
|
|
|
153
151
|
something changed (`KEEPSNAPPY_SLACK_ALWAYS=1` also posts "no change"), and a measurement that sent no data always. The
|
|
154
152
|
commit's title and author come from `git log` in the checkout. A Slack failure is printed and never changes the exit code.
|
|
155
153
|
|
|
156
|
-
Declared steps on the farm: `maestro/
|
|
154
|
+
Declared steps on the farm: `maestro/keepsnappy/step.yaml` / `step.js` / `send-markers.js` (copy them into the suite).
|
|
157
155
|
With a token set, `step.js` only records the marker; `send-markers.js`, run in `end-run.yaml` after each iteration,
|
|
158
|
-
posts them to `<output.
|
|
156
|
+
posts them to `<output.keepsnappyApi or KEEPSNAPPY_API_URL>/v1/markers` with `Authorization: Bearer <run token>`, so the test
|
|
159
157
|
never waits on the network between actions (and sends the host's clock round trips, plan 51 G1.4). A failed iteration
|
|
160
|
-
never reaches `end-run.yaml`, so the example `run-
|
|
158
|
+
never reaches `end-run.yaml`, so the example `run-keepsnappy.yaml` also runs `send-markers.js` from `onFlowComplete`; such a
|
|
161
159
|
run is invalid through its missing completion marker, and its markers stay for diagnosis. The example folder
|
|
162
|
-
(`examples/farm/flows/
|
|
160
|
+
(`examples/farm/flows/keepsnappy/`) ships its own copy of `send-markers.js`. Without a token, `step.js` posts to the local
|
|
163
161
|
CLI endpoint at once; there a step started but never ended invalidates its run (`unclosed-steps`).
|
|
164
162
|
|
|
165
163
|
Detox (Jest): `packages/detox` (`@keepsnappy/detox`) gives `step(name, fn)` / `stepStart` / `stepEnd`, posting the same markers: to the local endpoint from `KEEPSNAPPY_MARKER_URL` + `KEEPSNAPPY_RUN_ID` (set by `keepsnappy run`/`measure`), or on the farm to `KEEPSNAPPY_API_URL/v1/markers` with `KEEPSNAPPY_RUN_TOKEN` as the bearer token. Optional per-test step names via `@keepsnappy/detox/auto-steps`. See its README.
|
|
@@ -177,16 +175,16 @@ same words (`packages/mcp/README.md`).
|
|
|
177
175
|
|
|
178
176
|
```sh
|
|
179
177
|
export KEEPSNAPPY_KEY=pl_rk_… # a project key with read scope (a CI key with run scope works too)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
178
|
+
keepsnappy findings --branch feat/keepsnappy-integration # what got worse or better on a PR, worst first
|
|
179
|
+
keepsnappy findings --main --journey live-ui --phone "iPhone 15"
|
|
180
|
+
keepsnappy findings --state all --json # also the resolved and not judged ones, as the API's JSON
|
|
181
|
+
keepsnappy findings --state all --offset 200 # the next page (at most 200 per answer)
|
|
182
|
+
keepsnappy finding f_0123456789abcdef # one finding with its evidence
|
|
183
|
+
keepsnappy finding f_0123456789abcdef --prompt | pbcopy # only the hand-off for a coding agent, ending with its id and page
|
|
184
|
+
keepsnappy compare <runA> <runB> # two runs (ids or external ids), window by window, no statistics
|
|
185
|
+
keepsnappy history live-ui "Tap: live chat send" # one window on main, run by run (id or name)
|
|
186
|
+
keepsnappy commits a1b2c3d c3d4e5f [--branch main] # the measured commits between two points
|
|
187
|
+
keepsnappy layers # which layer answers which question, and its certificate
|
|
190
188
|
```
|
|
191
189
|
|
|
192
190
|
Flags: `--api URL` (or `KEEPSNAPPY_API`, default the hosted API), `--key-env NAME` (default `KEEPSNAPPY_KEY`), `--json`.
|
|
@@ -205,9 +203,9 @@ branch was measured differently from main.
|
|
|
205
203
|
<out>/arms.json arms of a `measure` (name, target, per-arm env)
|
|
206
204
|
<out>/runs.ndjson one record per run: run_id, arm, block, t0, t1, exit_code, valid, invalid_reason
|
|
207
205
|
<out>/<run_id>/test.log stdout/stderr of the test command
|
|
208
|
-
<out>/<run_id>/
|
|
206
|
+
<out>/<run_id>/keepsnappy.log Documents/keepsnappy/log.txt pulled from the app
|
|
209
207
|
<out>/metrics.csv long format: run_id,arm,block,metric,value,valid,invalid_reason
|
|
210
|
-
<out>/
|
|
208
|
+
<out>/aa.json written by aa
|
|
211
209
|
```
|
|
212
210
|
|
|
213
211
|
## Architecture
|
|
@@ -219,14 +217,14 @@ branch was measured differently from main.
|
|
|
219
217
|
├─ Spawner.run(test command) t0 .. t1, exit code
|
|
220
218
|
├─ DataSource.fetchLog ── IosSimulatorDataSource | IosDeviceDataSource
|
|
221
219
|
└─ window-log (parse, slice by time) ► metrics (per-window medians) ► metrics.csv
|
|
222
|
-
cli.ts ──►
|
|
220
|
+
cli.ts ──► aa ──► @keepsnappy/stats (aaRuns) ──► table + aa.json
|
|
223
221
|
```
|
|
224
222
|
|
|
225
223
|
Pure core (`schedule`, `window-log`, `metrics`, table formatting) plus thin adapters. Process spawning, clock and filesystem come in through `Deps` (`ports.ts`), so every test runs on fakes and fixtures; nothing touches a simulator or device.
|
|
226
224
|
|
|
227
225
|
## How measure-only works
|
|
228
226
|
|
|
229
|
-
1. The customer's build already contains the
|
|
227
|
+
1. The customer's build already contains the keepsnappy SDK, which appends `<wall ms> KEEPSNAPPY window {json}` lines to `Documents/keepsnappy/log.txt` for every window it observes (screen opens, interactions).
|
|
230
228
|
2. After each run the log is pulled and the windows whose start (`wall - durMs`) lies in `[t0, t1]` of that run are kept. Anything older is leftover from earlier runs.
|
|
231
229
|
3. Per window name, the run's value is the median over its cycles of: `dur_ms`, `commits`, `renders`, `mounts`, `longtask_ms`, `gc_count`, `gc_ms`, `alloc_mb`, `touch_max_ms`; plus `n`, `timeouts` (windows ending in `timeout` are excluded from medians) and `retained_mb_per_cycle` (Theil-Sen slope of heap after GC across cycles, needs 3+ cycles). Metric names are `<window>.<field>`, kept as-is (e.g. `screen:(app)/community/[handle]/(tabs).dur_ms`) and quoted in the CSV when needed.
|
|
232
230
|
4. A failed test, an install or log-pull failure, or a run with no windows is written as invalid with a reason. Invalid runs stay in `metrics.csv` (a `run.status` row when they have no metrics) and the stats engine drops them whole.
|