@ai-ecoverse/slicc-browsers 1.8.0 → 1.8.1

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
@@ -300,7 +300,9 @@ every alternative of an `any`, or a required (`all`, `not` or plain) check, need
300
300
  selects one variant or an array; omission selects all. `parallel` defaults to 1.
301
301
  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.
302
302
 
303
- `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.
303
+ `settleScale` (default 1) multiplies the bounds of waits for events the leg expects to happen: settles (beacon, served, title and load) on the control leg and `required` settles on either leg, `paused` on the control leg, 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 the leg expects to be `true`. 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.
304
+
305
+ `refusalScale` (default 1) multiplies the bounds of waits for what the guarded leg expects to be absent, the beacon, request, title or navigation that the guard should block: settles that are not `required`, and `paused`, on the guarded leg. Such a wait runs to its full bound whenever the guard works, so `settleScale` leaves it alone. The trade-off: a larger `refusalScale` gives more sensitivity to a leak that arrives late on a slow host, because the observations after the wait see it, at the cost of that much more time per guarded row. Adopters should keep `refusalScale` at 1 or higher, never below 1; the runner refuses a `refusalScale` below 1 with `refusalScale must be a number of at least 1`.
304
306
 
305
307
  `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.
306
308
 
package/guard-runner.mjs CHANGED
@@ -117,6 +117,7 @@ class Execution {
117
117
  (step) => step.children && step.where
118
118
  );
119
119
  this.scale = options.settleScale ?? 1;
120
+ this.refusalScale = options.refusalScale ?? 1;
120
121
  this.childRuns = new Map();
121
122
  this.named = new Map();
122
123
  this.processed = new Set();
@@ -169,12 +170,12 @@ class Execution {
169
170
  return out;
170
171
  }
171
172
 
172
- bound(ms) {
173
- return Math.round(ms * this.scale);
173
+ bound(ms, absent = false) {
174
+ return Math.round(ms * (absent ? this.refusalScale : this.scale));
174
175
  }
175
176
 
176
- async poll(predicate, ms = 1500) {
177
- const deadline = Date.now() + this.bound(ms);
177
+ async poll(predicate, ms = 1500, absent = false) {
178
+ const deadline = Date.now() + this.bound(ms, absent);
178
179
  do {
179
180
  if (await predicate()) return true;
180
181
  await sleep(25);
@@ -524,7 +525,7 @@ class Execution {
524
525
  event.params?.request?.url === url &&
525
526
  this.rule(event)?.url === url
526
527
  );
527
- if (!(await this.poll(seen, ms)) && this.side === 'control')
528
+ if (!(await this.poll(seen, ms, this.side === 'guarded')) && this.side === 'control')
528
529
  this.errors.push(`paused: no pause for ${url} within ${this.bound(ms)}ms`);
529
530
  }
530
531
 
@@ -660,13 +661,14 @@ class Execution {
660
661
  );
661
662
  };
662
663
  const alt = settle.or;
664
+ const absent = this.side === 'guarded' && !settle.required;
663
665
  if (refused() && !alt) return skipped();
664
666
  if (settle.variant) await sleep(this.delay);
665
667
  if (settle.title && !this.reader) {
666
- await sleep(this.bound(settle.ms ?? 1500));
668
+ await sleep(this.bound(settle.ms ?? 1500, absent));
667
669
  return;
668
670
  }
669
- const met = await this.poll(async () => {
671
+ const arrived = async () => {
670
672
  if (refused()) return !alt || (await this.check(alt));
671
673
  if (settle.served) return this.requestCheck({ served: settle.served, tagged: false });
672
674
  if (settle.beacon)
@@ -685,7 +687,8 @@ class Execution {
685
687
  .slice(index + 1)
686
688
  .some((e) => e.method === 'Page.loadEventFired' && e.sessionId === event.sessionId)
687
689
  );
688
- }, settle.ms ?? 1500);
690
+ };
691
+ const met = await this.poll(arrived, settle.ms ?? 1500, absent);
689
692
  if (refused() && !alt) return skipped();
690
693
  if (settle.required && !met)
691
694
  this.errors.push(
@@ -1419,13 +1422,22 @@ function validateBlocked(vectors) {
1419
1422
  throw new TypeError(`${vector.id}: blocked needs exactly one url or nonempty anyOf`);
1420
1423
  }
1421
1424
 
1422
- function validate(set, opts) {
1423
- if (!opts.fixture?.log) throw new TypeError('fixture.log is required');
1425
+ function validateScales(opts) {
1424
1426
  if (
1425
1427
  opts.settleScale !== undefined &&
1426
1428
  !(Number.isFinite(opts.settleScale) && opts.settleScale > 0)
1427
1429
  )
1428
1430
  throw new TypeError('settleScale must be a positive number');
1431
+ if (
1432
+ opts.refusalScale !== undefined &&
1433
+ !(Number.isFinite(opts.refusalScale) && opts.refusalScale >= 1)
1434
+ )
1435
+ throw new TypeError('refusalScale must be a number of at least 1');
1436
+ }
1437
+
1438
+ function validate(set, opts) {
1439
+ if (!opts.fixture?.log) throw new TypeError('fixture.log is required');
1440
+ validateScales(opts);
1429
1441
  for (const vector of set.vectors)
1430
1442
  for (const [name, observation] of Object.entries(vector.observe ?? {}))
1431
1443
  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.8.0",
3
+ "setVersion": "1.8.1",
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.8.0",
3
+ "version": "1.8.1",
4
4
  "description": "SLICC's shared browser catalog, JSON schemas and discovery conformance corpus",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -65,7 +65,7 @@
65
65
  "trust:dry": "npx fledgling sync --dry-run -y --skip-publish",
66
66
  "release": "semantic-release",
67
67
  "test:guard": "npm run test:guard-integration && npm run test:guard-sweep",
68
- "test:guard-integration": "node --test test/integration/guard-fit.test.mjs test/integration/guard-chain.test.mjs test/integration/guard-validation.test.mjs test/integration/guard-liveness.test.mjs test/integration/guard-observations.test.mjs test/integration/guard-child-latency.test.mjs test/integration/guard-adoption.test.mjs test/integration/guard-scoping.test.mjs test/integration/guard-late-pause.test.mjs test/integration/guard-legs.test.mjs test/integration/guard-diagnostics.test.mjs test/integration/guard-chrome.test.mjs",
68
+ "test:guard-integration": "node --test test/integration/guard-fit.test.mjs test/integration/guard-chain.test.mjs test/integration/guard-validation.test.mjs test/integration/guard-liveness.test.mjs test/integration/guard-observations.test.mjs test/integration/guard-child-latency.test.mjs test/integration/guard-adoption.test.mjs test/integration/guard-scoping.test.mjs test/integration/guard-late-pause.test.mjs test/integration/guard-legs.test.mjs test/integration/guard-diagnostics.test.mjs test/integration/guard-chrome.test.mjs test/integration/guard-scales.test.mjs",
69
69
  "test:guard-sweep": "node scripts/test-guards.mjs",
70
70
  "test:gates": "node --test test/integration/gates.test.mjs",
71
71
  "test:guard-target": "node --test test/integration/guard-runner.test.mjs",