doc-detective 4.32.0 → 4.34.0

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.
Files changed (39) hide show
  1. package/dist/common/src/schemas/schemas.json +2688 -126
  2. package/dist/common/src/types/generated/record_v3.d.ts +48 -2
  3. package/dist/common/src/types/generated/record_v3.d.ts.map +1 -1
  4. package/dist/common/src/types/generated/step_v3.d.ts +48 -2
  5. package/dist/common/src/types/generated/step_v3.d.ts.map +1 -1
  6. package/dist/common/src/types/generated/test_v3.d.ts +96 -4
  7. package/dist/common/src/types/generated/test_v3.d.ts.map +1 -1
  8. package/dist/core/tests/ffmpegRecorder.d.ts +12 -1
  9. package/dist/core/tests/ffmpegRecorder.d.ts.map +1 -1
  10. package/dist/core/tests/ffmpegRecorder.js +141 -3
  11. package/dist/core/tests/ffmpegRecorder.js.map +1 -1
  12. package/dist/core/tests/recordingCheckpoints.d.ts +76 -0
  13. package/dist/core/tests/recordingCheckpoints.d.ts.map +1 -0
  14. package/dist/core/tests/recordingCheckpoints.js +335 -0
  15. package/dist/core/tests/recordingCheckpoints.js.map +1 -0
  16. package/dist/core/tests/saveScreenshot.d.ts +5 -1
  17. package/dist/core/tests/saveScreenshot.d.ts.map +1 -1
  18. package/dist/core/tests/saveScreenshot.js +46 -12
  19. package/dist/core/tests/saveScreenshot.js.map +1 -1
  20. package/dist/core/tests/startRecording.d.ts.map +1 -1
  21. package/dist/core/tests/startRecording.js +26 -10
  22. package/dist/core/tests/startRecording.js.map +1 -1
  23. package/dist/core/tests/stopRecording.d.ts.map +1 -1
  24. package/dist/core/tests/stopRecording.js +381 -15
  25. package/dist/core/tests/stopRecording.js.map +1 -1
  26. package/dist/core/tests.d.ts.map +1 -1
  27. package/dist/core/tests.js +54 -37
  28. package/dist/core/tests.js.map +1 -1
  29. package/dist/hints/context.d.ts +1 -0
  30. package/dist/hints/context.d.ts.map +1 -1
  31. package/dist/hints/context.js +9 -0
  32. package/dist/hints/context.js.map +1 -1
  33. package/dist/hints/hints.d.ts.map +1 -1
  34. package/dist/hints/hints.js +15 -0
  35. package/dist/hints/hints.js.map +1 -1
  36. package/dist/hints/types.d.ts +8 -0
  37. package/dist/hints/types.d.ts.map +1 -1
  38. package/dist/index.cjs +3464 -325
  39. package/package.json +1 -1
@@ -24,6 +24,7 @@ import { typeKeys } from "./tests/typeKeys.js";
24
24
  import { swipeSurface } from "./tests/swipe.js";
25
25
  import { wait } from "./tests/wait.js";
26
26
  import { saveScreenshot } from "./tests/saveScreenshot.js";
27
+ import { capPathSegment, stepArtifactFileName, resolveCheckpointsConfig, captureRecordingCheckpoints, } from "./tests/recordingCheckpoints.js";
27
28
  import { startRecording } from "./tests/startRecording.js";
28
29
  import { stopRecording } from "./tests/stopRecording.js";
29
30
  import { browserCaptureTitle, browserDownloadDir, coerceRecordContextBrowser, jobIsFfmpegRecording, computeEffectiveConcurrency, jobExclusiveResources, checkSystemBinary, xvfbDisplay, startXvfb, XVFB_SCREEN_SIZE, isRecordingActive, recordStepName, detectRecordingNameConflict, getFfmpegPath, ffmpegPathEnv, } from "./tests/ffmpegRecorder.js";
@@ -50,7 +51,7 @@ import { dragAndDropElement } from "./tests/dragAndDrop.js";
50
51
  import { createSessionRegistry, registerSession, activeDriver, sweepSessions, } from "./tests/browserSessions.js";
51
52
  import path from "node:path";
52
53
  import { spawn } from "node:child_process";
53
- import { randomUUID, createHash } from "node:crypto";
54
+ import { randomUUID } from "node:crypto";
54
55
  import { setAppiumHome } from "./appium.js";
55
56
  import { contentHash } from "../common/src/detectTests.js";
56
57
  import { resolveExpression } from "./expressions.js";
@@ -2908,28 +2909,6 @@ function buildAutoRecordStep({ config, spec, test, context, }) {
2908
2909
  __autoRecord: true,
2909
2910
  };
2910
2911
  }
2911
- // Directory/file segments built from IDs are capped so deeply nested doc
2912
- // trees can't push the full path past Windows' MAX_PATH. The default cap is
2913
- // 32: the REST artifact tree nests several id segments
2914
- // (specs/<id>/tests/<id>/contexts/<id>/…), so a larger default could exceed
2915
- // MAX_PATH on Windows.
2916
- //
2917
- // Plain tail truncation alone is unsafe: two distinct ids that share the same
2918
- // trailing `max` characters (e.g. mirror directory trees that differ only in a
2919
- // long prefix) would collapse into the same path segment, so one context's
2920
- // screenshots/recording could overwrite another's and the reported relative
2921
- // path would resolve to the wrong artifact. When a segment exceeds the cap,
2922
- // prepend a short deterministic hash of the *full* segment so distinct ids stay
2923
- // distinct, and keep the trailing chars (where generated ids carry their
2924
- // content hash) for human correlation. Deterministic — the same id maps to the
2925
- // same segment every run, preserving run-over-run comparison.
2926
- function capPathSegment(segment, max = 32) {
2927
- if (segment.length <= max)
2928
- return segment;
2929
- const hash = createHash("sha1").update(segment).digest("hex").slice(0, 8);
2930
- const tail = segment.slice(segment.length - (max - hash.length - 1));
2931
- return `${hash}-${tail}`;
2932
- }
2933
2912
  // Capture a post-step screenshot for `autoScreenshot` runs. The relative
2934
2913
  // path follows the REST resource tree — stable IDs (spec/test/context) as
2935
2914
  // nested collections plus the step's order, action, and ID (e.g.
@@ -2940,21 +2919,14 @@ function capPathSegment(segment, max = 32) {
2940
2919
  // missed capture must not fail the step it documents.
2941
2920
  async function captureAutoScreenshot({ config, driver, spec, test, context, step, stepIndex, stepCount, }) {
2942
2921
  try {
2943
- const action = driverActions.find((key) => typeof step[key] !== "undefined") || "step";
2944
- const sanitizedTestId = sanitizeFilesystemName(String(test.testId ?? ""), "test");
2945
2922
  const runDir = getRunOutputDir(config);
2946
- const dir = path.join(runDir, "specs", capPathSegment(sanitizeFilesystemName(String(spec.specId ?? ""), "spec")), "tests", capPathSegment(sanitizedTestId), "contexts", capPathSegment(sanitizeFilesystemName(String(context.contextId ?? ""), "context")), "screenshots");
2947
- // The stepId usually embeds the testId (its parent folder) — strip that
2948
- // prefix so filenames stay short while still carrying the step's ID.
2949
- const stepIdString = sanitizeFilesystemName(String(step.stepId ?? ""), "step");
2950
- const stepRef = capPathSegment(stepIdString.startsWith(`${sanitizedTestId}~`)
2951
- ? stepIdString.slice(sanitizedTestId.length + 1)
2952
- : stepIdString);
2953
- // Zero-pad the step ordinal to the width of the context's step count
2954
- // (min 2), so file listings sort naturally even past 99 steps (100 would
2955
- // otherwise sort before 11).
2956
- const pad = Math.max(2, String(stepCount).length);
2957
- const fileName = `${String(stepIndex + 1).padStart(pad, "0")}-${action}-${stepRef}.png`;
2923
+ const dir = path.join(runDir, "specs", capPathSegment(sanitizeFilesystemName(String(spec.specId ?? ""), "spec")), "tests", capPathSegment(sanitizeFilesystemName(String(test.testId ?? ""), "test")), "contexts", capPathSegment(sanitizeFilesystemName(String(context.contextId ?? ""), "context")), "screenshots");
2924
+ const fileName = stepArtifactFileName({
2925
+ step,
2926
+ stepIndex,
2927
+ stepCount,
2928
+ testId: test.testId,
2929
+ });
2958
2930
  const screenshotStep = {
2959
2931
  stepId: `${step.stepId}_auto`,
2960
2932
  description: "Automatic post-step screenshot",
@@ -3951,6 +3923,35 @@ async function runContext({ config, spec, test, context, runnerDetails, appiumPo
3951
3923
  if (capturedPath)
3952
3924
  stepReport.autoScreenshot = capturedPath;
3953
3925
  }
3926
+ // Recording checkpoints (ADR 01075): while a checkpoint-enabled
3927
+ // recording is active, capture a compare-only screenshot per handle
3928
+ // after every step (final attempt only, same placement rationale as
3929
+ // autoScreenshot — retry frames would poison the staged captures).
3930
+ // The record step's own post-step capture is the opening bookend.
3931
+ // The host is the ACTIVE session's driver — the same driver runStep
3932
+ // pushed the handle onto — falling back to the app session's host for
3933
+ // app-only contexts (which skip capture inside the helper: no browser
3934
+ // driver to capture with).
3935
+ // Recordings live per SESSION, so sweep every live session driver the
3936
+ // way stopAllRecordings does — not just the active one. A span started
3937
+ // on a second browser surface keeps its handle on that session's
3938
+ // driver; checking only the active session would silently capture
3939
+ // nothing for it. Each driver is both the host (whose recordings we
3940
+ // read) and the capture source, so a checkpoint always photographs the
3941
+ // surface its own recording is filming.
3942
+ for (const checkpointDriver of sessionDrivers(browserSessions, driver)) {
3943
+ await captureRecordingCheckpoints({
3944
+ config,
3945
+ driver: checkpointDriver,
3946
+ recordingHost: checkpointDriver,
3947
+ step,
3948
+ stepStatus: stepReport.result,
3949
+ stepIndex,
3950
+ stepCount: context.steps.length,
3951
+ testId: test.testId,
3952
+ appSession,
3953
+ });
3954
+ }
3954
3955
  pushStepReport(stepReport);
3955
3956
  // Apply the terminal routing decision. `continue` runs the next step; a
3956
3957
  // `stop` halts the remaining steps in this context (`spec`/`run` scope —
@@ -4262,6 +4263,22 @@ async function runStep({ config = {}, context = {}, step, driver, metaValues = {
4262
4263
  const handle = actionResult.recording;
4263
4264
  handle.id = handle.id ?? randomUUID();
4264
4265
  handle.name = handle.name ?? recordStepName(step.record);
4266
+ // Recording checkpoints (ADR 01075): resolve the step's `checkpoints`
4267
+ // field once, here, where the handle and its target path are both at
4268
+ // hand — the post-step hook and stopRecord read the resolved config
4269
+ // off the handle. resolveCheckpointsConfig returns null for every
4270
+ // record form without a checkpoints field (string/boolean included),
4271
+ // so `null` is the single "disabled" encoding. `overwrite` rides along
4272
+ // for stopRecord's aboveVariation staging/promote decision (ADR 01078).
4273
+ handle.overwrite = step.record?.overwrite;
4274
+ handle.verify = step.record?.verify;
4275
+ if (handle.targetPath) {
4276
+ handle.checkpoints = resolveCheckpointsConfig({
4277
+ record: step.record,
4278
+ targetPath: handle.targetPath,
4279
+ handleId: handle.id,
4280
+ });
4281
+ }
4265
4282
  if (step.__autoRecord) {
4266
4283
  handle.synthetic = true;
4267
4284
  // Desktop app-only context: no window exists yet to crop to. Mark the