@keepsnappy/agent 0.1.0-alpha.17 → 0.1.0-alpha.19

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 CHANGED
@@ -70,7 +70,7 @@ Annotated example: `examples/keepsnappy.yml`. Every setting:
70
70
 
71
71
  Unknown settings and wrong types are errors, all listed together.
72
72
 
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`) fails the job (exit `3`) only when the verdict computed on that run set is Worse and blocking. Closing a run set never computes its verdict (decision 0031: the operator computes verdicts on demand), so with no computed verdict `--fail-on-worse` checks nothing: the CLI prints `Verdict not computed yet (computed on demand): --fail-on-worse checked nothing` and exits `0`. The CLI exits `2` for a configuration problem (bad or missing `keepsnappy.yml`, unknown or misspelt 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, an API URL that does not answer as the KeepSnappy API, `--upload` that sent no run: "Blocked"; 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:
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`) fails the job (exit `3`) only when the verdict computed on that run set is Worse and blocking. Closing a run set never computes its verdict (decision 0031: the operator computes verdicts on demand), so with no computed verdict `--fail-on-worse` checks nothing: the CLI prints `Verdict not computed yet (computed on demand): --fail-on-worse checked nothing` and exits `0`. The CLI exits `2` for a configuration problem (bad or missing `keepsnappy.yml`, unknown or misspelt 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, an API URL that does not answer as the KeepSnappy API, `--upload` that sent no run: "Blocked", or a local run with no valid run; results stay in the out dir). For a local run with no valid run, it prints `No valid run was recorded` with the invalid reasons when known. Failed runs are marked invalid in `runs.ndjson` / `metrics.csv` and ignored by the statistics. The ending summary reads like:
74
74
 
75
75
  ```
76
76
  7/8 runs valid (dropped: 1 test-failed: exit 1)
@@ -137,7 +137,7 @@ runner. It writes:
137
137
  run with data. A measured iteration sends nothing until its end-of-iteration flush, so a run with data completed and
138
138
  is never blamed; a Maestro failure stops the session, so later iterations have no data;
139
139
  3. finishes every run not yet finished (a run without a single batch is reported as "no data", never judged), closes the
140
- set (`--wait`: verdicts inline; else on read) and prints the iterations and the same plain-words verdict as
140
+ set (`--wait`: verdicts inline; else on read). It then prints the iterations and the same plain-words verdict as
141
141
  `keepsnappy run --upload --wait`, plus `$GITHUB_STEP_SUMMARY` and `<out>/keepsnappy-result.json` (`--out`, default the
142
142
  state's directory);
143
143
  4. puts the empty stub back into the tokens file, also when a step above failed. The example workflow also deletes the
@@ -149,10 +149,10 @@ result, `3` with `--fail-on-worse` when the verdict computed on that run set is
149
149
  verdict it checks nothing and says so, exit `0`), `2` for configuration, `1` when the API fails or nothing could be
150
150
  judged ("Blocked": no iteration sent any data).
151
151
 
152
- Slack: set `KEEPSNAPPY_SLACK_WEBHOOK` (an incoming-webhook URL kept as a CI secret) and `farm close --wait` posts the
153
- result: a pull request only when something got worse (a table of the 3 biggest changes, main vs PR), main only when
154
- something changed (`KEEPSNAPPY_SLACK_ALWAYS=1` also posts "no change"), and a measurement that sent no data always. The
155
- commit's title and author come from `git log` in the checkout. A Slack failure is printed and never changes the exit code.
152
+ Slack: set `KEEPSNAPPY_SLACK_WEBHOOK` (an incoming-webhook URL kept as a CI secret) and `farm close` posts a measurement that sent
153
+ no data ("No result") at once. Results are posted after the verdict is computed, by the operator's recompute job (decision 0031), in
154
+ two lines and one button (docs/mockups/2026-10-26-slack, present `resultSlackPayload`). `$GITHUB_STEP_SUMMARY` gets the same two
155
+ lines: "No result", the computed result, or "Not judged yet · <branch> / Waiting for the judgement" with the run set's page. A Slack failure is printed and never changes the exit code.
156
156
 
157
157
  Declared steps on the farm: `maestro/keepsnappy/step.yaml` / `step.js` / `send-markers.js` (copy them into the suite).
158
158
  With a token set, `step.js` only records the marker; `send-markers.js`, run in `end-run.yaml` after each iteration,
@@ -507,23 +507,29 @@ class Driver {
507
507
  });
508
508
  await this.cmd("DELETE", "/actions").catch(() => null);
509
509
  }
510
- async drag(from, to, durationMs) {
510
+ async drag(from, to, durationMs, note) {
511
511
  to = { x: to.x, y: Math.min(to.y, await this.safeBottomY()) };
512
- await this.cmd("POST", "/actions", {
513
- actions: [
514
- {
515
- type: "pointer",
516
- id: "finger1",
517
- parameters: { pointerType: "touch" },
518
- actions: [
519
- { type: "pointerMove", duration: 0, x: from.x, y: from.y },
520
- { type: "pointerDown", button: 0 },
521
- { type: "pointerMove", duration: durationMs, x: to.x, y: to.y },
522
- { type: "pointerUp", button: 0 }
523
- ]
524
- }
525
- ]
526
- });
512
+ const logged = this.log.length;
513
+ try {
514
+ await this.cmd("POST", "/actions", {
515
+ actions: [
516
+ {
517
+ type: "pointer",
518
+ id: "finger1",
519
+ parameters: { pointerType: "touch" },
520
+ actions: [
521
+ { type: "pointerMove", duration: 0, x: from.x, y: from.y },
522
+ { type: "pointerDown", button: 0 },
523
+ { type: "pointerMove", duration: durationMs, x: to.x, y: to.y },
524
+ { type: "pointerUp", button: 0 }
525
+ ]
526
+ }
527
+ ]
528
+ });
529
+ } finally {
530
+ if (note && this.log[logged])
531
+ Object.assign(this.log[logged], note);
532
+ }
527
533
  await this.cmd("DELETE", "/actions").catch(() => null);
528
534
  }
529
535
  async relaunch(bundleId = this.bundleId) {
@@ -726,7 +732,7 @@ import { dirname, join, relative, resolve } from "node:path";
726
732
  import { fileURLToPath } from "node:url";
727
733
  var DRIVER_NAME = "appium";
728
734
  function driverVersion() {
729
- const built = "0.1.0-alpha.17";
735
+ const built = "0.1.0-alpha.19";
730
736
  if (built)
731
737
  return built;
732
738
  let dir = dirname(fileURLToPath(import.meta.url));
@@ -800,7 +806,7 @@ async function sendMarkers(fetchImpl, api, markers, opts = {}) {
800
806
  const url = `${base}/v1/markers`;
801
807
  const version = (opts.driverVersion ?? driverVersion)();
802
808
  const reported = opts.reported ?? new Set;
803
- const driverField = { driver: { name: DRIVER_NAME, ...version ? { version } : {}, ...opts.deviceUnit ? { unit: opts.deviceUnit.slice(0, 200) } : {} } };
809
+ const driverField = { driver: { name: DRIVER_NAME, ...version ? { version } : {}, ...opts.deviceUnit ? { unit: opts.deviceUnit.slice(0, 200) } : {}, ...opts.capture ? { capture: opts.capture } : {} } };
804
810
  let sent = 0;
805
811
  for (const [token, list] of byToken) {
806
812
  const trips = [];
@@ -902,9 +908,11 @@ class Session {
902
908
  readyWaits = [];
903
909
  actionGc;
904
910
  endGc;
911
+ capture;
905
912
  gcN = 0;
906
913
  stepDepth = 0;
907
914
  stepLog = [];
915
+ defects = { gestureRetry: 0, items: [] };
908
916
  onStepChange = null;
909
917
  refinds = 0;
910
918
  get staleRefinds() {
@@ -917,6 +925,7 @@ class Session {
917
925
  this.api = o.api ?? "";
918
926
  this.actionGc = o.actionGc ? "step" : "off";
919
927
  this.endGc = o.endGc ?? END_GC_DEFAULT;
928
+ this.capture = o.capture;
920
929
  }
921
930
  async gc(atEnd = false) {
922
931
  if (this.actionGc !== "step" && !(atEnd && this.endGc && this.actionGc === "off"))
@@ -1293,7 +1302,7 @@ class Session {
1293
1302
  async endRun() {
1294
1303
  if (this.markers.some((m) => m.phase === "start"))
1295
1304
  await this.memRead(await this.gc(true));
1296
- await sendMarkers(this.fetch, this.api, this.markers, { sleep: (ms) => this.driver.sleep(ms), now: () => this.now(), deviceUnit: this.driver.deviceUnit, tokens: [...this.opened], reported: this.told, note: (line) => this.driver.log.push({ ...this.driver.context, cmd: "driver-report", t: this.now(), ms: 0, status: 1, answer: line }) });
1305
+ await sendMarkers(this.fetch, this.api, this.markers, { sleep: (ms) => this.driver.sleep(ms), now: () => this.now(), deviceUnit: this.driver.deviceUnit, capture: this.capture, tokens: [...this.opened], reported: this.told, note: (line) => this.driver.log.push({ ...this.driver.context, cmd: "driver-report", t: this.now(), ms: 0, status: 1, answer: line }) });
1297
1306
  const evidence = { linkAt: this.now(), resentAt: null, ackAt: null, gaveUpAt: null };
1298
1307
  this.lastFlush = evidence;
1299
1308
  const acks = new Set;
@@ -1460,7 +1469,7 @@ async function runDriver(argv, module, deps = realRunDeps()) {
1460
1469
  const { api, tokens } = parseTokensJs(readFileSync2(String(a["tokens-js"]), "utf8"));
1461
1470
  const log = [];
1462
1471
  const driver = new Driver({ hub: HUB, log, headers: { Authorization: auth }, bundleId: module.app.bundleId, platform, fetch: deps.fetch, now: deps.now, sleep: deps.sleep });
1463
- const t = new Session({ driver, scheme: module.app.scheme, fetch: deps.fetch, api, actionGc: a["action-gc"] === "on", endGc: a["end-gc"] === "on" });
1472
+ const t = new Session({ driver, scheme: module.app.scheme, fetch: deps.fetch, api, actionGc: a["action-gc"] === "on", endGc: a["end-gc"] === "on", capture: capture ? "on" : "off" });
1464
1473
  const sessionName = module.sessionName ?? "keepsnappy (appium)";
1465
1474
  const device = String(a.device);
1466
1475
  const ctx = { fetch: deps.fetch, tokens, journeys, repeat, args: a };
@@ -1503,6 +1512,7 @@ async function runDriver(argv, module, deps = realRunDeps()) {
1503
1512
  flushError,
1504
1513
  flushOff,
1505
1514
  finalFlush,
1515
+ defects: t.defects,
1506
1516
  steps: t.stepLog,
1507
1517
  iterations,
1508
1518
  commands: log