@ai-ecoverse/slicc-browsers 1.7.11 → 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
@@ -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
@@ -115,6 +116,7 @@ class Execution {
115
116
  this.expectWhere = [...(vector.setup ?? []), ...(vector.trigger ?? [])].some(
116
117
  (step) => step.children && step.where
117
118
  );
119
+ this.scale = options.settleScale ?? 1;
118
120
  this.childRuns = new Map();
119
121
  this.named = new Map();
120
122
  this.processed = new Set();
@@ -167,8 +169,12 @@ class Execution {
167
169
  return out;
168
170
  }
169
171
 
172
+ bound(ms) {
173
+ return Math.round(ms * this.scale);
174
+ }
175
+
170
176
  async poll(predicate, ms = 1500) {
171
- const deadline = Date.now() + ms;
177
+ const deadline = Date.now() + this.bound(ms);
172
178
  do {
173
179
  if (await predicate()) return true;
174
180
  await sleep(25);
@@ -513,7 +519,7 @@ class Execution {
513
519
  this.rule(event)?.url === url
514
520
  );
515
521
  if (!(await this.poll(seen, ms)) && this.side === 'control')
516
- 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`);
517
523
  }
518
524
 
519
525
  async answer(event) {
@@ -651,7 +657,7 @@ class Execution {
651
657
  if (refused() && !alt) return skipped();
652
658
  if (settle.variant) await sleep(this.delay);
653
659
  if (settle.title && !this.reader) {
654
- await sleep(settle.ms ?? 1500);
660
+ await sleep(this.bound(settle.ms ?? 1500));
655
661
  return;
656
662
  }
657
663
  const met = await this.poll(async () => {
@@ -677,7 +683,7 @@ class Execution {
677
683
  if (refused() && !alt) return skipped();
678
684
  if (settle.required && !met)
679
685
  this.errors.push(
680
- `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`
681
687
  );
682
688
  }
683
689
 
@@ -795,7 +801,10 @@ class Execution {
795
801
  if (!waits.length) return true;
796
802
  let timer;
797
803
  const expired = new Promise((resolve) => {
798
- timer = setTimeout(() => resolve(false), this.options.namedResultMs ?? namedResultMs);
804
+ timer = setTimeout(
805
+ () => resolve(false),
806
+ this.bound(this.options.namedResultMs ?? namedResultMs)
807
+ );
799
808
  });
800
809
  const done = await Promise.race([Promise.all(waits).then(() => true), expired]);
801
810
  clearTimeout(timer);
@@ -814,11 +823,24 @@ class Execution {
814
823
  .filter((name) => typeof name === 'string' && this.named.has(name));
815
824
  if (!names.length || (await this.namedSettled(names))) return true;
816
825
  this.errors.push(
817
- `${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`
818
827
  );
819
828
  return false;
820
829
  }
821
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
+
822
844
  async check(check) {
823
845
  const c = expand(check, this.vars);
824
846
  if (!(await this.ready(c))) return false;
@@ -1022,7 +1044,7 @@ class Execution {
1022
1044
  for (const [name, observation] of Object.entries(this.vector.observe ?? {})) {
1023
1045
  if (!supported(observation.requires, this.options.capabilities)) continue;
1024
1046
  if ((observation.dependsOn ?? []).some((dep) => this.refused[dep])) continue;
1025
- observed[name] = await this.check(observation.check);
1047
+ observed[name] = await this.observe(name, observation.check);
1026
1048
  if (observation.eitherWhen && this.side === 'guarded')
1027
1049
  observed[`${name}.either`] = await this.check(observation.eitherWhen);
1028
1050
  }
@@ -1391,6 +1413,11 @@ function validateBlocked(vectors) {
1391
1413
 
1392
1414
  function validate(set, opts) {
1393
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');
1394
1421
  for (const vector of set.vectors)
1395
1422
  for (const [name, observation] of Object.entries(vector.observe ?? {}))
1396
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.11",
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.11",
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",