@specific.dev/spectest 0.32.0 → 0.32.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/dist/browser.js CHANGED
@@ -32,7 +32,7 @@ import { readFileSync } from "node:fs";
32
32
  import path from "node:path";
33
33
  import { fileURLToPath } from "node:url";
34
34
  import { generateId } from "./ids.js";
35
- import { recordBrowser, reserveEvent, truncateUtf8 } from "./recorder.js";
35
+ import { recordBrowser, reserveBackdated, reserveEvent, truncateUtf8 } from "./recorder.js";
36
36
  import { wrap } from "./inspect.js";
37
37
  import { describeUrlPattern, matchesUrl } from "./url-match.js";
38
38
  import { attachBrowserProbe, DEFAULT_ACTION_TIMEOUT_MS, desktopStrategy, makeLocator, mobileStrategy, } from "./locator.js";
@@ -1407,7 +1407,9 @@ function buildBackend(holder, recorder, buildOpts) {
1407
1407
  // No page work — the matcher already read the value via silentRead. We
1408
1408
  // only mint the timeline anchor: the seq the assertion nests under, plus
1409
1409
  // `sessionTimestamp` (post-settle wall clock) so the dashboard seeks the
1410
- // replay to the frame the assertion observed.
1410
+ // replay to the frame the assertion observed. The offset is backdated to
1411
+ // where the matcher started polling, since that's what `tOffsetMs` means
1412
+ // everywhere else (the ops that reserve up front stamp their start).
1411
1413
  const endT = Date.now();
1412
1414
  const seq = recordBrowser({
1413
1415
  action,
@@ -1417,7 +1419,7 @@ function buildBackend(holder, recorder, buildOpts) {
1417
1419
  : {}),
1418
1420
  durationMs: waitedMs,
1419
1421
  ...(error ? { error } : {}),
1420
- });
1422
+ }, reserveBackdated(waitedMs));
1421
1423
  // Drain the rrweb the page buffered while the matcher waited into this
1422
1424
  // step's chunk, so `settledTarget` has bounds to seek into.
1423
1425
  await drain(action);
@@ -5,8 +5,11 @@ interface BaseEvent {
5
5
  * before it does still sorts ahead of them. Ops that don't reserve get
6
6
  * their seq at record (= finish) time. */
7
7
  seq: number;
8
- /** Milliseconds since the recorder started, captured when the op began
9
- * (reserved) or, absent a reservation, when it was recorded. */
8
+ /** Milliseconds since the recorder started, captured when the op *began* —
9
+ * either reserved up front, or backdated at record time by an op that could
10
+ * only reserve at finish (`reserveBackdated`). Consumers read the step's
11
+ * completion time as `tOffsetMs + durationMs`, so an op that stamps a bare
12
+ * finish offset while also reporting a duration will render late. */
10
13
  tOffsetMs: number;
11
14
  /**
12
15
  * Optional grouping pointer. When set, this event was emitted inside
@@ -475,6 +478,14 @@ export declare function isRecording(): boolean;
475
478
  * when nothing is recording, in which case `record*` falls back to allocating
476
479
  * the seq at record time. */
477
480
  export declare function reserveEvent(): EventReservation | undefined;
481
+ /** Reserve at op *finish* but backdate the offset by `elapsedMs` — for ops that
482
+ * can't reserve up front because they only know they were an op once they're
483
+ * done (the locator matchers: they poll silently and emit one settled step at
484
+ * the end). Keeps `tOffsetMs` meaning "when the op started" for every kind,
485
+ * which is what lets the dashboard render `tOffsetMs + durationMs` as the
486
+ * step's completion time. Ordering is unaffected — the caller records
487
+ * immediately, so the seq is the one it would have got anyway. */
488
+ export declare function reserveBackdated(elapsedMs: number): EventReservation | undefined;
478
489
  export declare function recordExec(ev: Omit<ExecEvent, "seq" | "tOffsetMs" | "kind">, reservation?: EventReservation): number | undefined;
479
490
  export declare function recordAssertion(ev: Omit<AssertionEvent, "seq" | "tOffsetMs" | "kind">): number | undefined;
480
491
  export declare function recordHttp(ev: Omit<HttpEvent, "seq" | "tOffsetMs" | "kind">, reservation?: EventReservation): number | undefined;
package/dist/recorder.js CHANGED
@@ -132,6 +132,19 @@ export function isRecording() {
132
132
  export function reserveEvent() {
133
133
  return active() ? current.reserve() : undefined;
134
134
  }
135
+ /** Reserve at op *finish* but backdate the offset by `elapsedMs` — for ops that
136
+ * can't reserve up front because they only know they were an op once they're
137
+ * done (the locator matchers: they poll silently and emit one settled step at
138
+ * the end). Keeps `tOffsetMs` meaning "when the op started" for every kind,
139
+ * which is what lets the dashboard render `tOffsetMs + durationMs` as the
140
+ * step's completion time. Ordering is unaffected — the caller records
141
+ * immediately, so the seq is the one it would have got anyway. */
142
+ export function reserveBackdated(elapsedMs) {
143
+ const resv = reserveEvent();
144
+ if (!resv)
145
+ return undefined;
146
+ return { seq: resv.seq, tOffsetMs: Math.max(0, resv.tOffsetMs - Math.max(0, elapsedMs)) };
147
+ }
135
148
  export function recordExec(ev, reservation) {
136
149
  return active() ? current.push({ kind: "exec", ...ev }, reservation) : undefined;
137
150
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specific.dev/spectest",
3
- "version": "0.32.0",
3
+ "version": "0.32.1",
4
4
  "description": "Spectest SDK for defining test environments in TypeScript.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",