@ai-ecoverse/slicc-browsers 1.7.10 → 1.7.12

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
@@ -170,7 +170,7 @@ Any `cdp`, `page` or `children` step can take `requires`, the capabilities it ne
170
170
  - `eval`: evaluate `expression`.
171
171
  `hops` routes `url` through `/hop` on each `origin`, with its `code`.
172
172
  - `{ "answer": { "url", "stage"?, "cdp"?, "params"?, "hold"? } }`: answer each paused request or auth challenge for `url` with that call; `hold` leaves it paused. Every other paused request continues unchanged.
173
- - `{ "children": steps, "where"? }`: run these steps on each target auto-attached from then on, then resume it. `where` scopes them to the row's own target: `{ "openerId": "page" }` (opened by the row's page) or `{ "urlPrefix": url }`; other attached targets are resumed and otherwise left alone.
173
+ - `{ "children": steps, "where"? }`: run these steps on each target auto-attached from then on, then resume it. `where` scopes them to the row's own target: `{ "openerId": "page" }` (opened by the row's page) or `{ "urlPrefix": url }`; other attached targets are resumed and otherwise left alone. When a vector has a `children` step with `where`, a target that attaches before that step runs waits until it does, and is then matched against `where`. A target is only resumed when it attached waiting for the debugger, and a `browser_ui` target (Chrome's own UI, such as `chrome://omnibox-popup.top-chrome/`) is never adopted or sent anything.
174
174
  - `{ "settle": { "beacon"?, "served"?, "title"?, "ms"?, "variant"?, "session"?, "dependsOn"?, "or"? } }`: wait for a beacon from `beacon`, for the fixture to log a request for the URL `served`, for the page `title`, or for the session to detach. When a refusal in `dependsOn` was recorded, the beacon can't come: a settle with `or` (a check) then waits until `or` holds, and one without it is skipped. Without such a refusal, `or` plays no part. Each beacon settle after an init script depends on that script's registration, and its `or` is what the row reads: the hops and the destination for the redirect chains, or the served page otherwise. It waits up to `ms` (10000 by default), then another 600 ms. `variant` waits the run's detach variant instead.
175
175
  - `{ "wait": ms }`.
176
176
  - `{ "paused": { "url", "ms" } }`: wait until the client has seen the pause that an `answer` rule for `url` matches (`Fetch.requestPaused` at the rule's stage, or `Fetch.authRequired` for a `Fetch.continueWithAuth` rule). It waits up to `ms`. On the control leg, a pause that never comes fails the row with `paused: no pause for <url> within <ms>ms`. On the guarded leg the guard may hide that pause, so the step waits the full `ms` and doesn't fail. The C5 guessed-id vectors and `C5.auth` use it before their second request, so the probe's interception id falls between the two ids the guess spans.
@@ -287,6 +287,8 @@ every alternative of an `any`, or a required (`all`, `not` or plain) check, need
287
287
  selects one variant or an array; omission selects all. `parallel` defaults to 1.
288
288
  With the `browserContexts` capability each leg runs in its own browser context. A `Target.createBrowserContext` that fails with a timeout is retried once, and the row's `notes` records it; any other failure fails the row.
289
289
 
290
+ `settleScale` (default 1) multiplies every bound the runner waits on: settles (beacon, served, title and load), `paused`, page and child loads, the wait for a background step's `as` result (`<as> did not complete within <ms>ms`), and the wait for an observation. Adopters on slow hosts can set 3 or 4. Every wait polls and returns as soon as its event arrives, so a larger bound only costs time when something is really missing, and each timeout error names the bound it used. An observation read from the fixture (`observer: "fixture"`, such as a beacon or a served request) that the leg expects to be `true` is polled for up to 5000 ms (scaled) instead of read once. One that the leg expects to be `false` is still read once.
291
+
290
292
  `onStart(row)` receives each row's `id`, `target`, `variant`, `transport` and `via` as the row starts. `run()` returns a promise that also has `inFlight()`, and its result has `inFlight` too. `inFlight()` lists the rows that are running now, each as `{ id, target, variant, leg, phase, step, clients }`, where `step` is the index of the current step in its `phase` (`setup`, `trigger` or `child`), and `clients` lists each client's pending CDP calls as `{ method, session, ageMs }` (`client`, plus `unguarded` when it is a separate client). With `rowTimeoutMs`, a row still running after that many milliseconds fails on its own. Its `errors` hold `row timed out after <ms>ms: <the in-flight view>`, `inFlight` holds the view, and the clients that `clientFactory` made for it are closed. The run then goes on to the next row. It is off by default, and a client passed as `client` is never closed.
291
293
 
292
294
  `log(row)` receives completed rows. Besides `control`, `guarded`, `live`, `why` and `errors`, a row may carry `ran` (steps that ran on one leg only) and `notes` (settles skipped because their registration was refused). Both are informational: they explain a row's verdict, and adopters (bios, slicc-cli, slicc-cdp) must not count them as failures, or as passes. The verdict is `control`, `guarded` and `live`. A variant supplies the wait before an
package/guard-runner.mjs CHANGED
@@ -13,6 +13,7 @@ import {
13
13
 
14
14
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
15
15
  const namedResultMs = 10000;
16
+ const observeMs = 5000;
16
17
  const q = JSON.stringify;
17
18
  const at = (value, path = '') =>
18
19
  path
@@ -111,6 +112,11 @@ class Execution {
111
112
  this.answers = [];
112
113
  this.attachSettings = new Map();
113
114
  this.childSteps = [];
115
+ this.deferred = [];
116
+ this.expectWhere = [...(vector.setup ?? []), ...(vector.trigger ?? [])].some(
117
+ (step) => step.children && step.where
118
+ );
119
+ this.scale = options.settleScale ?? 1;
114
120
  this.childRuns = new Map();
115
121
  this.named = new Map();
116
122
  this.processed = new Set();
@@ -163,8 +169,12 @@ class Execution {
163
169
  return out;
164
170
  }
165
171
 
172
+ bound(ms) {
173
+ return Math.round(ms * this.scale);
174
+ }
175
+
166
176
  async poll(predicate, ms = 1500) {
167
- const deadline = Date.now() + ms;
177
+ const deadline = Date.now() + this.bound(ms);
168
178
  do {
169
179
  if (await predicate()) return true;
170
180
  await sleep(25);
@@ -447,6 +457,11 @@ class Execution {
447
457
  adopt(event) {
448
458
  const info = event.params.targetInfo;
449
459
  const child = event.params.sessionId;
460
+ if (info?.type === 'browser_ui') return;
461
+ if (this.expectWhere && !this.childWhere) {
462
+ this.deferred.push(event);
463
+ return;
464
+ }
450
465
  if (info?.browserContextId && this.vars.context && info.browserContextId !== this.vars.context)
451
466
  return;
452
467
  if (this.childWhere && !this.childMatches(info)) {
@@ -462,7 +477,8 @@ class Execution {
462
477
  else {
463
478
  await this.execute(this.childSteps.slice(0, reload), child);
464
479
  const start = this.client.events.length;
465
- await outcome(this.client.send('Runtime.runIfWaitingForDebugger', {}, child));
480
+ if (event.params.waitingForDebugger)
481
+ await outcome(this.client.send('Runtime.runIfWaitingForDebugger', {}, child));
466
482
  await this.poll(
467
483
  () =>
468
484
  this.client.events
@@ -473,7 +489,8 @@ class Execution {
473
489
  await this.execute(this.childSteps.slice(reload), child);
474
490
  }
475
491
  const resumed = this.client.events.length;
476
- await outcome(this.client.send('Runtime.runIfWaitingForDebugger', {}, child));
492
+ if (event.params.waitingForDebugger)
493
+ await outcome(this.client.send('Runtime.runIfWaitingForDebugger', {}, child));
477
494
  if (this.childSteps.some((step) => step.cdp === 'Page.enable'))
478
495
  await this.poll(() => this.firstLoad(child, resumed), 5000);
479
496
  })().catch((error) => this.errors.push(String(error.message ?? error)));
@@ -502,7 +519,7 @@ class Execution {
502
519
  this.rule(event)?.url === url
503
520
  );
504
521
  if (!(await this.poll(seen, ms)) && this.side === 'control')
505
- this.errors.push(`paused: no pause for ${url} within ${ms}ms`);
522
+ this.errors.push(`paused: no pause for ${url} within ${this.bound(ms)}ms`);
506
523
  }
507
524
 
508
525
  async answer(event) {
@@ -604,6 +621,7 @@ class Execution {
604
621
  if (step.children) {
605
622
  this.childSteps = step.children;
606
623
  this.childWhere = step.where;
624
+ for (const event of this.deferred.splice(0)) this.adopt(event);
607
625
  return;
608
626
  }
609
627
  if (step.answer) {
@@ -639,7 +657,7 @@ class Execution {
639
657
  if (refused() && !alt) return skipped();
640
658
  if (settle.variant) await sleep(this.delay);
641
659
  if (settle.title && !this.reader) {
642
- await sleep(settle.ms ?? 1500);
660
+ await sleep(this.bound(settle.ms ?? 1500));
643
661
  return;
644
662
  }
645
663
  const met = await this.poll(async () => {
@@ -665,7 +683,7 @@ class Execution {
665
683
  if (refused() && !alt) return skipped();
666
684
  if (settle.required && !met)
667
685
  this.errors.push(
668
- `settle: no ${settle.beacon ? `beacon from ${settle.beacon}` : settle.served ? `request for ${settle.served}` : settle.title ? `title ${settle.title}` : 'load'} within ${settle.ms ?? 1500}ms`
686
+ `settle: no ${settle.beacon ? `beacon from ${settle.beacon}` : settle.served ? `request for ${settle.served}` : settle.title ? `title ${settle.title}` : 'load'} within ${this.bound(settle.ms ?? 1500)}ms`
669
687
  );
670
688
  }
671
689
 
@@ -783,7 +801,10 @@ class Execution {
783
801
  if (!waits.length) return true;
784
802
  let timer;
785
803
  const expired = new Promise((resolve) => {
786
- timer = setTimeout(() => resolve(false), this.options.namedResultMs ?? namedResultMs);
804
+ timer = setTimeout(
805
+ () => resolve(false),
806
+ this.bound(this.options.namedResultMs ?? namedResultMs)
807
+ );
787
808
  });
788
809
  const done = await Promise.race([Promise.all(waits).then(() => true), expired]);
789
810
  clearTimeout(timer);
@@ -802,11 +823,24 @@ class Execution {
802
823
  .filter((name) => typeof name === 'string' && this.named.has(name));
803
824
  if (!names.length || (await this.namedSettled(names))) return true;
804
825
  this.errors.push(
805
- `${names.join(', ')} did not complete within ${this.options.namedResultMs ?? namedResultMs}ms`
826
+ `${names.join(', ')} did not complete within ${this.bound(this.options.namedResultMs ?? namedResultMs)}ms`
806
827
  );
807
828
  return false;
808
829
  }
809
830
 
831
+ async observe(name, check) {
832
+ const want = (this.side === 'control' ? this.vector.control.expected : this.vector.expected)?.[
833
+ name
834
+ ];
835
+ if (want !== true || check?.observer !== 'fixture') return this.check(check);
836
+ let seen = false;
837
+ await this.poll(async () => {
838
+ seen = await this.check(check);
839
+ return seen === true;
840
+ }, observeMs);
841
+ return seen;
842
+ }
843
+
810
844
  async check(check) {
811
845
  const c = expand(check, this.vars);
812
846
  if (!(await this.ready(c))) return false;
@@ -1010,7 +1044,7 @@ class Execution {
1010
1044
  for (const [name, observation] of Object.entries(this.vector.observe ?? {})) {
1011
1045
  if (!supported(observation.requires, this.options.capabilities)) continue;
1012
1046
  if ((observation.dependsOn ?? []).some((dep) => this.refused[dep])) continue;
1013
- observed[name] = await this.check(observation.check);
1047
+ observed[name] = await this.observe(name, observation.check);
1014
1048
  if (observation.eitherWhen && this.side === 'guarded')
1015
1049
  observed[`${name}.either`] = await this.check(observation.eitherWhen);
1016
1050
  }
@@ -1379,6 +1413,11 @@ function validateBlocked(vectors) {
1379
1413
 
1380
1414
  function validate(set, opts) {
1381
1415
  if (!opts.fixture?.log) throw new TypeError('fixture.log is required');
1416
+ if (
1417
+ opts.settleScale !== undefined &&
1418
+ !(Number.isFinite(opts.settleScale) && opts.settleScale > 0)
1419
+ )
1420
+ throw new TypeError('settleScale must be a positive number');
1382
1421
  for (const vector of set.vectors)
1383
1422
  for (const [name, observation] of Object.entries(vector.observe ?? {}))
1384
1423
  if (!observation.check) throw new TypeError(`${vector.id}: ${name} lacks a check`);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "setVersion": "1.7.10",
3
+ "setVersion": "1.7.12",
4
4
  "sources": [
5
5
  {
6
6
  "repo": "ai-ecoverse/slicc-cli",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/slicc-browsers",
3
- "version": "1.7.10",
3
+ "version": "1.7.12",
4
4
  "description": "SLICC's shared browser catalog, JSON schemas and discovery conformance corpus",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",