Skip to content

feat: upload attachments to test cases within a test run - #461

Open
kk-marriott-blc wants to merge 2 commits into
kimatata:developfrom
kk-marriott-blc:upload-test-run-attachments
Open

feat: upload attachments to test cases within a test run#461
kk-marriott-blc wants to merge 2 commits into
kimatata:developfrom
kk-marriott-blc:upload-test-run-attachments

Conversation

@kk-marriott-blc

@kk-marriott-blc kk-marriott-blc commented Aug 24, 2026

Copy link
Copy Markdown

Adds the ability to attach files — typically failure screenshots — to a test case within a test run. Attachments were previously only possible on the test case itself, so there was no way to record evidence against a specific run.

Backend

New runCaseAttachments join table (migration 20260824000000), mirroring the existing caseAttachments structure with a unique index on (runCaseId, attachmentId) and cascade deletes from both parents. The attachments table is unchanged — it was already parent-agnostic.

Three endpoints under /runcaseattachments:

  • GET /?runCaseId= — list, requires project visibility
  • POST /?runCaseId= — upload, requires reporter
  • DELETE /:attachmentId?runCaseId= — delete, requires reporter, and refuses attachments that aren't linked to that run case

Reporter is the minimum role, matching what commenting on a run case already requires. Two middleware helpers do the run case → run → project lookup: verifyProjectReporterFromRunCaseId and verifyProjectVisibleFromRunCaseId. Downloads reuse the existing GET /attachments/download/:attachmentId, which is already parent-agnostic.

Frontend

A new "Attachments" tab in the run's test case detail pane, alongside Comments and History, deep-linkable with ?tab=attachments. Clicking a thumbnail opens a full-screen preview with a download button.

CaseAttachmentsEditor and isImage move to frontend/components/AttachmentsEditor.tsx and frontend/utils/isImage.ts so the test case and run views share one uploader.

Screenshot 2026-08-24 at 15 44 43

Changes to existing test case attachments

The multer setup moves out of routes/attachments/new.js into backend/config/upload.js so both upload routes share it. Two consequences for the existing test case upload path:

  • The 50 MB limit shown in the UI is now enforced server-side. Previously no limit was set; an oversized upload now returns 413 instead of being accepted.
  • Uploading two files with the same name in one request used to overwrite each other. Multer resolves every filename before writing any bytes, so the fs.existsSync check saw nothing and both files claimed the same name. Names claimed within a request are now tracked, so they are suffixed as intended.

Docs

docs/docs/dev/er.md gains runCaseAttachments. I corrected two pre-existing gaps while there: runCases was missing from the diagram entirely, and attachments.path was renamed to filename by migration 20250712000000.

Tests

25 new Vitest tests — 14 across the three routes (permissions, run case scoping on delete, transaction rollback with cleanup of files already written to disk), 5 for the shared upload config (including the collision fix and the size limit), and 6 for the frontend control.

Note

backend/public is served statically, so uploaded files stay readable by URL without a token. That's pre-existing behaviour for test case attachments and unchanged here, but it does mean run attachments aren't private. Happy to address that separately if you'd like it in scope.

@kk-marriott-blc kk-marriott-blc changed the title Upload test case attachments feat(ui): upload test case attachments Aug 24, 2026
@kk-marriott-blc kk-marriott-blc changed the title feat(ui): upload test case attachments feat(ui): upload attachments within a test run Aug 24, 2026
@kk-marriott-blc kk-marriott-blc changed the title feat(ui): upload attachments within a test run feat: upload attachments to test cases within a test run Aug 24, 2026
@kk-marriott-blc
kk-marriott-blc marked this pull request as ready for review August 24, 2026 15:56
@kimatata
kimatata requested a lite review from Copilot August 29, 2026 03:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

There are confirmed frontend correctness/performance issues (stale state updates in RunCaseAttachments and unreleased object URLs on download) that should be fixed before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds first-class support for uploading and managing attachments (e.g., screenshots) on a test case within a specific test run, by introducing a new backend join table + routes and a new frontend “Attachments” tab that reuses a shared uploader component.

Changes:

  • Backend: adds runCaseAttachments join table + /runcaseattachments GET/POST/DELETE routes with run-case-to-project permission middleware.
  • Backend: factors shared multer config into backend/config/upload.js (enforces 50MB limit server-side; fixes same-request filename collisions).
  • Frontend: introduces run-case attachments UI tab and shares AttachmentsEditor + isImage across case and run-case views, with new i18n message group.
File summaries
File Description
frontend/utils/runCaseAttachmentControl.ts Adds API helpers for listing/creating/deleting run-case attachments.
frontend/utils/runCaseAttachmentControl.test.ts Unit tests for the new run-case attachment API helpers.
frontend/utils/isImage.ts Shared image-type detection helper for attachment rendering.
frontend/utils/attachmentDownload.ts Centralized attachment download helper used by both views.
frontend/types/run.ts Adds attachments label for run case detail tabs.
frontend/types/case.ts Extends attachment typing to support run-case join and shared editor labels.
frontend/types/attachment.ts Introduces shared message types for the reusable attachments editor.
frontend/src/app/[locale]/projects/[projectId]/runs/[runId]/cases/[caseId]/RunCaseAttachments.tsx New run-case attachments tab content and interaction logic.
frontend/src/app/[locale]/projects/[projectId]/runs/[runId]/cases/[caseId]/page.tsx Wires new “Attachments” messages into the run-case detail pane.
frontend/src/app/[locale]/projects/[projectId]/runs/[runId]/cases/[caseId]/DetailPane.tsx Adds the new Attachments tab and deep-linking via ?tab=attachments.
frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/page.tsx Adds close label needed by the shared AttachmentsEditor.
frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseEditor.tsx Switches case attachment UI to the shared AttachmentsEditor.
frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/attachmentControl.ts Removes duplicated download helper in favor of shared download util.
frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/attachmentControl.test.ts Updates tests to use the shared isImage helper and normalized join typing.
frontend/messages/zh-CN.json Adds new strings for Attachments tab and shared editor actions.
frontend/messages/pt-BR.json Adds new strings for Attachments tab and shared editor actions.
frontend/messages/ja.json Adds new strings for Attachments tab and shared editor actions.
frontend/messages/en.json Adds new strings for Attachments tab and shared editor actions.
frontend/messages/de.json Adds new strings for Attachments tab and shared editor actions.
frontend/components/AttachmentsEditor.tsx Refactors uploader into a shared component; adds image preview modal.
docs/docs/dev/er.md Updates ER diagram to include runCases, runCaseAttachments, and attachments.filename.
backend/server.ts Registers the new /runcaseattachments routes.
backend/routes/runcaseattachments/new.test.js Tests run-case attachment upload behavior, permission gating, and rollback cleanup.
backend/routes/runcaseattachments/new.js Implements POST upload/linking route for run-case attachments.
backend/routes/runcaseattachments/index.test.js Tests listing run-case attachments with visibility constraints.
backend/routes/runcaseattachments/index.js Implements GET list route for run-case attachments.
backend/routes/runcaseattachments/delete.test.js Tests scoped deletion and transactional behavior.
backend/routes/runcaseattachments/delete.js Implements scoped DELETE route for run-case attachments.
backend/routes/attachments/new.js Switches existing case-attachment upload route to shared upload middleware.
backend/models/runCases.js Adds association between RunCase and Attachment via runCaseAttachments.
backend/models/runCaseAttachments.js Adds new RunCaseAttachment model definition.
backend/models/attachments.js Adds association between Attachment and RunCase via runCaseAttachments.
backend/migrations/20260824000000-create-run-case-attachments.js Migration creating runCaseAttachments + unique index.
backend/middleware/verifyVisible.js Adds verifyProjectVisibleFromRunCaseId helper.
backend/middleware/verifyEditable.js Adds verifyProjectReporterFromRunCaseId helper.
backend/config/upload.test.js Tests shared upload constraints + collision handling.
backend/config/upload.js Introduces shared multer config with enforced size/count limits + collision fix.
Review details

Suppressed comments (2)

frontend/src/app/[locale]/projects/[projectId]/runs/[runId]/cases/[caseId]/RunCaseAttachments.tsx:95

  • This delete handler filters using the attachments value from the render that created the callback; if attachments change between scheduling and completion (e.g. concurrent upload/delete), the state update can resurrect removed items. Use a functional update to ensure it applies to the latest list.
      setAttachments(attachments.filter((attachment) => attachment.id !== attachmentId));

frontend/components/AttachmentsEditor.tsx:59

  • Using the array index as the React key can cause incorrect UI updates when attachments are inserted/removed/reordered (e.g. preview state or DOM reuse mismatching items). Prefer a stable identifier such as image.id.
  • Files reviewed: 36/37 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

if (!newAttachments) {
throw new Error('Failed to upload attachments');
}
setAttachments([...attachments, ...newAttachments]);
Comment on lines +25 to +33
const blob = await response.blob();
const downloadUrl = window.URL.createObjectURL(blob);

const link = document.createElement('a');
link.href = downloadUrl;
link.download = downloadFileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
@kimatata

Copy link
Copy Markdown
Owner

Thanks for the PR.

The addition of test code and the integration into the existing Attachments look perfect.
Features to improve the visibility of attached image files looks also useful.

I’d appreciate it if you could also take a look at the review comments from GitHub Copilot.
That said, it is ready to merge as-is, so just let me know.

@kimatata

Copy link
Copy Markdown
Owner

Note
backend/public is served statically, so uploaded files stay readable by URL without a token. That's pre-existing behaviour for test case attachments and unchanged here, but it does mean run attachments aren't private. Happy to address that separately if you'd like it in scope.

thanks for pointing out visibility problem. i created a issue.
#462

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants