Skip to content

QA plan: Frontend build and deploy (#30)

Story: Frontend build and deploy per env (#30)
Capability: Environments and deployment promotion (#26)
ADR: RAG001 — per-env SPA bundle on S3 + CloudFront

TypeCoverage
Buildnpm run build exits 0; dist/ contains index.html and hashed JS/CSS assets
BuildNo console.error or TypeScript errors in the Vite build output
Pipeline behaviorDeploy step syncs dist/ to the correct env S3 bucket (QA bucket on QA job, Prod bucket on Prod job)
Pipeline behaviorCloudFront invalidation runs after S3 sync; the new index.html is served within 60 s
SecurityCloudFront response-headers policy includes Content-Security-Policy header; verified by fetching index.html from the CloudFront URL after deploy
SecuritySPA bundle does not contain any hardcoded secret, API key, or credential (checked by secret scanner in CI pipeline, Story #29)
Integration (manual, QA)After deploy, loading the QA CloudFront URL renders the chat UI (no blank screen, no 404 on assets)
e2e (QA only)Playwright smoke: page loads, key input field is visible, badge renders after key entry
  • Story #17 (React chat UI) merged
  • Story #24 (Frontend Terraform module — S3 + CloudFront) deployed to QA
  • CloudFront distribution and S3 bucket exist in QA
  • npm run build exits 0 in CI
  • S3 sync and CloudFront invalidation steps exit 0
  • CSP header present on the CloudFront index.html response (verified by curl -I)
  • Manual QA: chat UI renders at QA CloudFront URL
  • Playwright smoke: all assertions pass against QA

Build: CI. Deploy: CI (QA on main merge, Prod on approval). Smoke: QA (manual + Playwright in CI).

npm/Vite, AWS CLI (s3 sync, cloudfront create-invalidation), curl, Playwright.

Terminal window
# Given the frontend has been deployed to the QA CloudFront distribution
# When the Content-Security-Policy header is checked
RESPONSE=$(curl -sI "https://$QA_CF_DOMAIN/")
echo "$RESPONSE" | grep -i "content-security-policy"
# Then the header is present in the response

Playwright smoke:

// Given the QA SPA URL is loaded
await page.goto(process.env.QA_SPA_URL);
// When the page settles
await page.waitForLoadState('networkidle');
// Then the key input field is visible
await expect(page.getByTestId('key-input')).toBeVisible();
  • CloudFront propagation delay: CI may check the URL before the invalidation completes. Add a sleep 30 or a retry loop in the smoke step to avoid a false failure.
  • Stale browser cache in Playwright: use --no-cache context option to ensure fresh asset loading.

RAG001: SPA bundle deployed per-env to S3/CloudFront. Security per-feature requirements (#18): CSP header on the CloudFront distribution. Exit criteria cover both.