@keepsnappy/agent 0.1.0-alpha.24 → 0.1.0-alpha.25

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
@@ -37,7 +37,7 @@ keepsnappy run -- maestro test --include-tags perf e2e/flows/ # wrap your own
37
37
  keepsnappy run --upload --wait # send the results; the server judges them and the CLI prints its verdict
38
38
  ```
39
39
 
40
- 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`.
40
+ 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 UDID` (the simulator or iPhone to run on; default `device:` in keepsnappy.yml, else the only booted simulator; with several booted and none named the run refuses and lists them; every Maestro command gets it as `maestro --device UDID`), `--device-model` / `--device-os` (default: read from that 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`.
41
41
 
42
42
  Commit details: when a run set opens (`keepsnappy run --upload`, `keepsnappy farm open`), the CLI reads the subject, author
43
43
  name and committer date of `--commit` and `--base-commit` with `git log` in the checkout (argv only, 5 s timeout) and
@@ -60,7 +60,7 @@ Annotated example: `examples/keepsnappy.yml`. Every setting:
60
60
  | `project.key_env` | Name of the env var with the project key (default `KEEPSNAPPY_KEY`). Only read with `--upload`. |
61
61
  | `project.ingest_url` | KeepSnappy API base URL for `--upload` (or `--ingest-url`, or env `KEEPSNAPPY_API`). Defaults to the hosted KeepSnappy API. |
62
62
  | `app.bundle_id` | Required. App whose keepsnappy log is read after each run. |
63
- | `platform`, `device` | `ios-sim` (default; `device` = UDID or `booted`) or `ios-device` (UDID required). One device per invocation in v1. |
63
+ | `platform`, `device` | `ios-sim` (default; `device` = UDID, or `booted` for the only booted simulator: with several booted, name one) or `ios-device` (UDID required). One device per invocation in v1. |
64
64
  | `repetitions` | Runs of the whole flow set (default 5). |
65
65
  | `flows.runner` | `maestro` (build `maestro test ...`) or `command` (run `flows.command`). |
66
66
  | `flows.tags`, `flows.exclude_tags` | `--include-tags=` / `--exclude-tags=`. |
@@ -546,7 +546,6 @@ class Driver {
546
546
  }
547
547
  ]
548
548
  });
549
- await this.cmd("DELETE", "/actions").catch(() => null);
550
549
  }
551
550
  async stroke(from, points, note) {
552
551
  const safe = await this.safeBottomY();
@@ -662,7 +661,7 @@ async function iosCommand(driver, link, cmd, n, state, waitMs) {
662
661
  }
663
662
  var ANDROID = {
664
663
  name: "android",
665
- defaultDevice: "Google Pixel 7-13.0",
664
+ defaultDevice: "Google Pixel 9 Pro-15.0",
666
665
  caps: (device) => ({
667
666
  platformName: "Android",
668
667
  "appium:automationName": "UiAutomator2",
@@ -937,7 +936,7 @@ async function rowsInList(t, spec, c) {
937
936
  const android = t.driver.platform.name === "android";
938
937
  const call = spec.field === "description" ? /descriptionMatches/ : /textMatches/;
939
938
  const own = android && spec.field ? all.filter((l) => call.test(l.value)) : all;
940
- const attr = sel.text !== undefined ? android ? spec.field === "description" ? "content-desc" : "text" : "label" : android ? "resource-id" : "name";
939
+ const attr = rowAttr(t, sel, spec.field);
941
940
  const frames = [];
942
941
  const inside = [];
943
942
  const parent = spec.scoped ?? true ? c.at.el : undefined;
@@ -963,6 +962,10 @@ async function rowsInList(t, spec, c) {
963
962
  ids.push(String(await t.driver.attribute(el, attr).catch(() => null) ?? ""));
964
963
  return { ids, frames, list };
965
964
  }
965
+ var rowAttr = (t, sel, field) => {
966
+ const android = t.driver.platform.name === "android";
967
+ return sel.text !== undefined ? android ? field === "description" ? "content-desc" : "text" : "label" : android ? "resource-id" : "name";
968
+ };
966
969
  function room(t, { frames, list }, toward) {
967
970
  if (frames.length === 0)
968
971
  return false;
@@ -1140,7 +1143,7 @@ import { dirname, join, relative, resolve } from "node:path";
1140
1143
  import { fileURLToPath } from "node:url";
1141
1144
  var DRIVER_NAME = "appium";
1142
1145
  function driverVersion() {
1143
- const built = "0.1.0-alpha.24";
1146
+ const built = "0.1.0-alpha.25";
1144
1147
  if (built)
1145
1148
  return built;
1146
1149
  let dir = dirname(fileURLToPath(import.meta.url));
@@ -1334,6 +1337,7 @@ class Session {
1334
1337
  capture;
1335
1338
  gcN = 0;
1336
1339
  stepDepth = 0;
1340
+ beforeLaunchOpen = false;
1337
1341
  stepLog = [];
1338
1342
  defects = { gestureRetry: 0, navStuck: 0, navCatchUp: 0, items: [] };
1339
1343
  navigating = false;
@@ -1352,6 +1356,7 @@ class Session {
1352
1356
  this.capture = o.capture;
1353
1357
  }
1354
1358
  async gc(atEnd = false) {
1359
+ this.notInBeforeLaunch("GC command");
1355
1360
  if (this.actionGc !== "step" && !(atEnd && this.endGc && this.actionGc === "off"))
1356
1361
  return null;
1357
1362
  if (this.linkState.linkMode === "safari") {
@@ -1369,6 +1374,7 @@ class Session {
1369
1374
  }
1370
1375
  memReadOff = false;
1371
1376
  async memRead(n) {
1377
+ this.notInBeforeLaunch("memory read");
1372
1378
  if (n === null || this.memReadOff || this.linkState.linkMode === "safari")
1373
1379
  return false;
1374
1380
  const start = this.now();
@@ -1398,6 +1404,7 @@ class Session {
1398
1404
  return took;
1399
1405
  }
1400
1406
  async navigation(fn) {
1407
+ this.notInBeforeLaunch("navigation pass");
1401
1408
  if (this.stepDepth > 0)
1402
1409
  throw new Error("a navigation pass cannot run inside a measured step");
1403
1410
  if (this.navigating)
@@ -1419,10 +1426,16 @@ class Session {
1419
1426
  declaresSteps() {
1420
1427
  return this.markers.some((m) => m.phase === "start" && m.step !== NAV_STEP);
1421
1428
  }
1429
+ notInBeforeLaunch(what) {
1430
+ if (this.beforeLaunchOpen)
1431
+ throw new Error(`no step or marker in beforeLaunch (${what}): it runs before the measured launch`);
1432
+ }
1422
1433
  marker(step, phase, gcN = null) {
1434
+ this.notInBeforeLaunch(`marker ${step}:${phase}`);
1423
1435
  this.markers.push({ token: this.token, step, phase, t_host: this.now(), ...gcN !== null ? { gc_n: gcN } : {} });
1424
1436
  }
1425
1437
  async step(name, action, checks = noop, once = noop) {
1438
+ this.notInBeforeLaunch(`step ${name}`);
1426
1439
  if (this.navigating)
1427
1440
  throw new Error("a measured step cannot start inside a navigation pass");
1428
1441
  await this.ready();
@@ -1743,7 +1756,7 @@ class Session {
1743
1756
  activate() {
1744
1757
  return this.driver.activate();
1745
1758
  }
1746
- async beginRun({ token, args = "", home }) {
1759
+ async beginRun({ token, args = "", home, beforeLaunch }) {
1747
1760
  this.token = token;
1748
1761
  if (token)
1749
1762
  this.opened.add(token);
@@ -1753,6 +1766,14 @@ class Session {
1753
1766
  await this.waitVisible(home, HOME_READY_MS);
1754
1767
  const alert = await this.openLink(activationLink(this.scheme, token, args));
1755
1768
  await this.driver.sleep(ACTIVATION_SETTLE_MS);
1769
+ if (beforeLaunch) {
1770
+ this.beforeLaunchOpen = true;
1771
+ try {
1772
+ await beforeLaunch();
1773
+ } finally {
1774
+ this.beforeLaunchOpen = false;
1775
+ }
1776
+ }
1756
1777
  await this.relaunch();
1757
1778
  await this.waitVisible(home, HOME_READY_MS);
1758
1779
  if (token && this.linkState.linkMode !== "safari")
@@ -2160,11 +2181,22 @@ async function runIterations(t, module, ctx, iterations, progress) {
2160
2181
  progress();
2161
2182
  driver.context = { journey: it.journey, iteration: it.iteration, phase: "begin" };
2162
2183
  const t0 = t.now();
2163
- rec.alert = await t.beginRun({ token: it.token, home: module.home, ...module.sdkArgs ? { args: module.sdkArgs } : {} });
2184
+ const itCtx = { ...ctx, journey: it.journey, iteration: it.iteration };
2185
+ const hook = module.beforeLaunch;
2186
+ const beforeLaunch = hook && (async () => {
2187
+ driver.context = { ...driver.context, phase: "before-launch" };
2188
+ try {
2189
+ await hook(t, itCtx);
2190
+ } catch (e) {
2191
+ throw new Error(`beforeLaunch failed (${it.journey} #${it.iteration}): ${e instanceof Error ? e.message : String(e)}`, { cause: e });
2192
+ }
2193
+ driver.context = { ...driver.context, phase: "begin" };
2194
+ });
2195
+ rec.alert = await t.beginRun({ token: it.token, home: module.home, ...module.sdkArgs ? { args: module.sdkArgs } : {}, ...beforeLaunch ? { beforeLaunch } : {} });
2164
2196
  const t1 = t.now();
2165
2197
  driver.context = { ...driver.context, phase: "journey" };
2166
2198
  const waitsBefore = t.readyWaits.length;
2167
- await module.journeys[it.journey](t, { ...ctx, journey: it.journey, iteration: it.iteration });
2199
+ await module.journeys[it.journey](t, itCtx);
2168
2200
  await t.ready();
2169
2201
  const t2 = t.now();
2170
2202
  t.marker("run:complete", "end");