mountable.io
By mountable.io · Cookbooks
  • playwright
  • testing
  • filesystem
  • cookbook

Upload a Failed Playwright Test’s Trace Without Turning the Job Green

Keep the runner’s failing exit code while uploading its finished trace and JSON report. Read the same evidence from a fresh process.

A Playwright assertion fails. Your script uploads its trace, the upload succeeds, and the job turns green. You have the evidence, but the job now says the test passed.

A shell script can lose either side of that result. With set -e or test && upload, a failing test stops the script before the upload. With test; upload, the last command can supply the script’s successful exit code. You need to keep the trace and the failed job together.

mountable.io is a persistent filesystem for AI agents. A worker can store the failed test’s trace and JSON report there before it exits. Inside CI, an artifact step can keep files after the job ends. Sandbox and queue workers can use this filesystem handoff instead. Our earlier trace recipe covers the general capture workflow.

Playwright Test fails with exit 1. The worker waits for its trace and report to finish, uploads both to mountable.io, and still returns exit 1 to the job. A fresh process reads the same files through a read-only session.
The evidence survives. The failed job stays failed.

Save the runner’s code before uploading

Wait for Playwright to exit so it finishes writing the zip. Then save its exit code before doing the upload. This is the core pattern in the companion:

const code = await runTest(dir);
process.exitCode = code;
try {
  await fs.write(tracePath, trace, { ifNoneMatch: true });
  await fs.write(reportPath, report, { ifNoneMatch: true });
  console.log(`retained runner_exit=${code}`);
} catch {
  console.error(`retention_failed runner_exit=${code}`);
}

For the failed run, exit 1 stays exit 1 after either upload outcome. The upload never replaces an earlier file. The companion checks both files before writing either one, and prints retained only after both writes succeed.

The demo runs one intentional assertion failure on a local synthetic page, with retain-on-failure and no retries. It uploads only trace.zip and report.json.

node failure.mjs run --filesystem "$FS_ID" --run "$RUN" --dir ./failed-out

Read the evidence from a fresh process

A successful upload prints retained runner_exit=1. An upload error prints retention_failed runner_exit=1. Both commands fail the job. If an upload fails, inspect both paths before retrying because one file may already have arrived.

We ran this against production. The wrapper uploaded 21,338 bytes, and the job still exited 1. A fresh process opened the filesystem read-only and matched both downloaded files byte for byte. It also saved the downloads, ready to open locally:

node failure.mjs check --filesystem "$FS_ID" --run "$RUN" --dir ./failed-out
npx playwright show-trace ./failed-out/downloaded/trace.zip

Keep each run small

The example refuses to upload more than 4 MiB combined. Each run writes to a new folder. At the listed prices, 4 MiB of logical file content kept for a full month costs $0.00078125, and one full download adds $0.000078125. Four new file and directory entries add $0.000002 per full month. Uploads and individual operations have no separate fee.

Actual retained bytes and storage duration determine the bill, before taxes and invoice rounding. Use failure.mjs cleanup with the same filesystem and run id to remove the remote files and empty directories. Deleted content can remain billed until reclamation.

Follow the failed-test cookbook for setup, expected output and cleanup. The runnable example includes the failure checks. mountable.io maintains it, separately from Playwright.