MCPcopy Create free account
hub / github.com/TestSprite/testsprite-cli / writeStepArtifacts

Function writeStepArtifacts

src/lib/bundle.ts:592–676  ·  view source on GitHub ↗

* Write per-step artifacts under ` /steps/`. Each step gets up to * three files: ` -screenshot.png` (when a screenshot URL exists — * M2 backend never sets one yet), ` -snapshot.html` (when * `htmlSnapshotUrl` exists), and ` -evidence.json` (a small JSON * file containing the non-sn

(
  step: CliTestStep,
  allEvidence: ReadonlyArray<CliFailureContext['failure']['evidence'][number]>,
  stepsTmpDir: string,
  fetchImpl: FetchImpl,
  filesWritten: string[],
)

Source from the content-addressed store, hash-verified

590 * there's nothing to put in it; it's NOT a placeholder.
591 */
592async function writeStepArtifacts(
593 step: CliTestStep,
594 allEvidence: ReadonlyArray<CliFailureContext['failure']['evidence'][number]>,
595 stepsTmpDir: string,
596 fetchImpl: FetchImpl,
597 filesWritten: string[],
598): Promise<void> {
599 const prefix = stepFilenamePrefix(step.stepIndex);
600
601 if (step.screenshotUrl) {
602 const file = `${prefix}-screenshot.png`;
603 await streamUrlToFile(step.screenshotUrl, join(stepsTmpDir, file), fetchImpl);
604 filesWritten.push(`steps/${file}`);
605 }
606
607 if (step.htmlSnapshotUrl) {
608 const file = `${prefix}-snapshot.html`;
609 await streamUrlToFile(step.htmlSnapshotUrl, join(stepsTmpDir, file), fetchImpl);
610 filesWritten.push(`steps/${file}`);
611 }
612
613 // Evidence sidecar artifacts. Per codex round-1 P2: the bundle must
614 // be self-contained — every `evidence[].url` in the failure response
615 // resolves to a local file by the time `meta.json` is written. The
616 // earlier filter dropped screenshot/snapshot kinds on the assumption
617 // they were always duplicates of `step.screenshotUrl` /
618 // `step.htmlSnapshotUrl`, but if the evidence carries a different URL
619 // (different snapshot variant, a per-evidence sidecar shot, etc.) the
620 // bundle silently lost it and `failure.json` still referenced an
621 // expiring presigned link. Rule now: include every evidence entry;
622 // remap to the existing step file when URLs match, download a fresh
623 // sidecar otherwise.
624 //
625 // Critical: leaving any URL inside `<NN>-evidence.json` means the
626 // bundle claims completeness but actually points at presigned URLs
627 // that expire after 15 min — an agent opening the bundle one hour
628 // later would see metadata referencing dead links. Streaming the
629 // bytes here makes the bundle self-contained.
630 const sidecar = allEvidence.filter(e => e.stepIndex === step.stepIndex);
631 if (sidecar.length > 0) {
632 const dereferenced = await Promise.all(
633 sidecar.map(async (entry, i) => {
634 // Reuse the already-downloaded step file when the evidence URL
635 // matches the step's primary screenshot/snapshot URL. Cheap
636 // dedupe — no extra HTTP round-trip, no duplicate bytes on disk.
637 if (entry.kind === 'screenshot' && step.screenshotUrl && step.screenshotUrl === entry.url) {
638 return {
639 kind: entry.kind,
640 stepIndex: entry.stepIndex,
641 summary: entry.summary,
642 path: `steps/${prefix}-screenshot.png`,
643 };
644 }
645 if (
646 entry.kind === 'snapshot' &&
647 step.htmlSnapshotUrl &&
648 step.htmlSnapshotUrl === entry.url
649 ) {

Callers 1

writeBundleFunction · 0.85

Calls 4

stepFilenamePrefixFunction · 0.85
streamUrlToFileFunction · 0.85
sidecarExtensionFunction · 0.85
writeFileFunction · 0.50

Tested by

no test coverage detected