Playwright captures a screenshot the moment a test fails — genuinely useful evidence. The problem is where it ends up. In GitHub Actions the standard move is actions/upload-artifact, which bundles everything into a zip. To actually see the failure you download the zip, unzip it, and hunt for the right PNG. Nobody does that at 2 AM during an incident, and you can't drop a zip into a PR comment.
What you want is a plain URL — one you can click from the CI log, paste into a review, or post to Slack. That's a one-call upload.
Upload a screenshot, get a URL
From a shell step in any CI system:
# get back a URL you can paste into a PR or Slack.
curl -s -X POST https://api.pixelvault.dev/v1/images \
-H "Authorization: Bearer $PIXELVAULT_API_KEY" \
-F "file=@test-results/failure.png" | jq -r '.url'
# → https://img.pixelvault.dev/proj_abc/img_xyz.png
Do it automatically on test failure
Better still, host the screenshot from inside the test and attach the URL to the report, so every failure comes with a link — no extra CI step, no artifact spelunking:
test("checkout flow", async ({ page }, testInfo) => {
// ... your test steps ...
// On failure, host the screenshot and attach the URL to the report
if (testInfo.status !== testInfo.expectedStatus) {
const shot = await page.screenshot();
const form = new FormData();
form.append("file", new Blob([shot], { type: "image/png" }), "failure.png");
const res = await fetch("https://api.pixelvault.dev/v1/images", {
method: "POST",
headers: { "Authorization": `Bearer ${process.env.PIXELVAULT_API_KEY}` },
body: form,
});
const { url } = await res.json();
// Now clickable straight from the test report or CI log
testInfo.annotations.push({ type: "screenshot", description: url });
}
});
Set the API key once as a CI secret and the tests pick it up from the environment:
- name: Run Playwright tests
run: npx playwright test
env:
PIXELVAULT_API_KEY: ${{ secrets.PIXELVAULT_API_KEY }}
Why a hosting API beats artifacts here
- Clickable, not zipped. A URL renders inline in a PR, an issue, or a chat message. A zip renders nowhere.
- Test keys that clean themselves up. Use a
pv_test_key and uploads auto-expire after 24h and don't count against your limits — perfect for ephemeral CI noise. - Zero egress fees. Screenshots get viewed by teammates and bots alike; bandwidth doesn't bill you by surprise.
- Same API everywhere. GitHub Actions, GitLab CI, CircleCI, or a local run — it's just an HTTP POST.
- Keep the ones that matter. Promote a flaky-test screenshot to a permanent live upload when you want a durable record.
For agents running the tests
When a coding agent runs your E2E suite, it hits the same wall — a screenshot it can't easily surface to a human. Because registration and upload are both plain API calls, an agent can host the failure screenshot itself and hand you back a link in its summary. Same two calls, no browser required.
Free to start
PixelVault's free tier includes 200 MB storage, 500 uploads/month, and 1 GB bandwidth — no credit card, no trial expiry. Paid plans start at $9/month.