Skip to content

Run result attachments

While executing a test run, a member can record an actual result per step (free text) plus optional image attachments (PNG, JPEG, or WebP). The flow mirrors step attachments: images are processed server-side to WebP (full size, longest edge ≤ 2048px, aspect ratio preserved) plus a thumbnail (longest edge ≤ 320px), then staged-and-committed.

Access requires member+ on an open run. All write operations (stage, commit, delete) are rejected with 409 on a closed run; reads are still allowed (the panel is read-only).

  1. StagePOST /api/v1/runs/{runUlid}/cases/{runCaseUlid}/steps/{stepUlid}/result-attachments:stage with multipart/form-data and one or more file parts. Writes to staging/<orgUlid>/ and returns staged refs (no DB row yet).
  2. CommitPATCH /api/v1/runs/{runUlid}/cases/{runCaseUlid}/steps/{stepUlid}/result with actualResult text and the staged refs in attachments[]. The server copies staged → permanent keys (attachments/<orgUlid>/<projectUlid>/run-steps/<runCaseStepUlid>/<attUlid>.webp), inserts rows, deletes staged objects, and persists the text.
  3. ReadGET /api/v1/runs/{runUlid}/cases/{runCaseUlid} returns each step’s actualResult and attachments[] (ordered by position). Thumbnails are served at GET /__assets/{thumbKey}.

Abandoned staged objects expire automatically via the R2 lifecycle rule (~48 h).

Terminal window
curl -sS -X POST \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
-F "file=@screenshot.png" \
"https://probara.net/api/v1/runs/$RUN/cases/$RC/steps/$ST/result-attachments:stage"

200 response shape is identical to step attachments (StagedRunResultAttachmentRef).

{
"actualResult": "Saw HTTP 500 instead of the dashboard",
"attachments": [
{
"ulid": "01J...ATT",
"position": 0,
"objectKey": "staging/01J...ORG/01J...UPLOAD.webp",
"thumbKey": "staging/01J...ORG/01J...UPLOAD_thumb.webp",
"mime": "image/webp",
"byteSize": 8420,
"width": 1200,
"height": 800,
"originalFilename": "screenshot.png"
}
]
}

Reconciliation works exactly like step attachments — new ulids are promoted, existing ulids are retained (only position may update), and any committed ulid omitted from the payload is deleted (rows + permanent R2 + storage counter).

The server does not trust client byteSize, width, or height on commit — it re-measures stored R2 objects. Staged refs whose key prefix does not match the requesting organization are rejected (cross-tenant guard).

actualResult: null clears the text; omitting it leaves the existing value unchanged. Omitting attachments leaves the committed set untouched.

Terminal window
curl -sS -X DELETE \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
"https://probara.net/api/v1/runs/$RUN/cases/$RC/steps/$ST/result-attachments/$ATT"

204 on success. 404 when the attachment is unknown or cross-org. 409 when the run is closed.

The redesigned execution board also adds two case-level operations:

  • POST /api/v1/runs/{runUlid}/cases/{runCaseUlid}/retry — resets the case (and its frozen step snapshots) to untested and recomputes the run aggregate. Emits run.case_retried.
  • DELETE /api/v1/runs/{runUlid}/cases/{runCaseUlid} — removes the case (cascades steps + result attachments) and recomputes the run aggregate. Emits run.case_removed.

Both require member+ on an open run and respond 409 when the run is closed.

Committed run-result attachment bytes (full + thumbnail) increment organization_storage_usage for category run_result_attachment. Staging bytes are not counted. The counter is the basis for future pricing tiers.