Files
CompanySite/QA_BUG_REPORT.md
T
QA AgentandClaude Opus 4.7 2d76e24e25 qa: comprehensive bug report after full Playwright sweep + manual smoke
Captured: 536 passed / 135 failed (chromium, 671 tests, 8.9 min) plus
9 manual smoke checks covering nav, contact + newsletter forms, blog
markdown, portfolio filters, /api/health.json, 404 page, broken
images, and mobile menu.

7 distinct root causes identified; 3 are demo blockers:

  BUG-001 (Critical) — site-wide stylesheet 404. Running SSR process
    started before dist/ was rebuilt mid-test; in-memory manifest
    references stale CSS hash (about.DJCIkvZw.css) while disk has
    only about.DDsw4wcw.css. Every page logs CSP/MIME error.

  BUG-002 (High) — contact + newsletter rate limiter increments BEFORE
    validation. 5 invalid attempts lock the form for 1 hour; corporate
    NAT will trip this immediately. Cascaded into ~40 test failures.

  BUG-004 (High) — 44/90 SVGs on home lack aria-hidden / aria-label.
    WCAG 2.1 AA violation; blocker for govt-IT clients.

Verdict: NOT READY for client demo until BUG-001, -002, -004 fixed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 15:11:00 +05:30

235 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# QA Bug Report — WorkRoot IT Solutions Site
- **Date:** 2026-05-11
- **Target:** http://localhost:10000 (Astro SSR, node-standalone adapter, mode=ssr, version=1.0.0)
- **Tooling:** Playwright 1.58.2 (chromium project) + curl smoke suite
- **Scope:** Full automated suite + 9-point manual smoke pass
---
## 1. Playwright suite — pass / fail counts
Command run: `npx playwright test --project=chromium --reporter=list` (head-500 piped output also captured per request — that early run was killed by SIGPIPE after ~496 chromium tests).
| Metric | Count |
|-----------------------|----------------|
| **Total (chromium)** | **671** |
| Passed | **536** (79.9 %) |
| Failed | **135** (20.1 %) |
| Wall time | 8.9 min |
> The full configured matrix is **4 697 tests** across 7 browser projects (Chromium, Firefox, WebKit, MS Edge, Mobile Chrome, Mobile Safari, iPad). Only chromium was executed end-to-end; cross-browser projects were not run because chromium alone already surfaced every regression worth fixing and the matrix would have taken ~60 min. Findings below should reproduce on Firefox / WebKit (verified spot-checks of error-context artifacts from a prior partial multi-browser run).
### Failures grouped by spec file
| Spec file | Failures |
|----------------------------------------|---------:|
| tests/cross-browser.spec.ts | 27 |
| tests/accessibility.spec.ts | 17 |
| tests/e2e-form-interactions.spec.ts | 16 |
| tests/destructive-chaos.spec.ts | 15 |
| tests/theme-cross-browser.spec.ts | 10 |
| tests/api-integration.spec.ts | 10 |
| tests/pages.spec.ts | 9 |
| tests/e2e-smoke-suite.spec.ts | 7 |
| tests/e2e-blog-navigation.spec.ts | 7 |
| tests/newsletter-subscription.spec.ts | 5 |
| tests/bug-fix-verification.spec.ts | 5 |
| tests/e2e-critical-paths.spec.ts | 4 |
| tests/security-headers.test.ts | 2 |
| tests/navigation.spec.ts | 1 |
---
## 2. Manual smoke checks — pass / fail
| # | Check | Result | Notes |
|----|------------------------------------------|:------:|-------|
| 1 | All nav links work (`/about`, `/services`, `/portfolio`, `/blog`, `/contact`, `/privacy`, `/terms`, `/cookies`, `/sitemap`) | ✅ | All HTTP 200; mobile menu has correct labels & overlay |
| 2 | Contact form submission | ✅ / 🐛 | `POST /api/contact` returns 200 success on valid input, 422 on invalid subject, 200 on honeypot (silent OK ✓). 429 after 5 submits/hr per IP — see BUG-002 |
| 3 | Newsletter form | ✅ / 🐛 | `POST /api/newsletter` returns 200 success, 422 on invalid/empty email. Same per-IP 5/hr limit applies — see BUG-002 |
| 4 | Blog post renders markdown | ✅ | `/blog/ai-transforming-business`: 4×h2, 8×h3, 21×p, 3×pre, 3×code, 5×ul, 47×a — all render correctly |
| 5 | Portfolio filters | ✅ | Buttons `data-filter="all|web|mobile|enterprise|government"`, 6 cards with categories present and HTML rendered |
| 6 | `/api/health.json` returns ok | ✅ | `{"status":"ok","mode":"ssr","adapter":"node-standalone","domain":"workroot.in",...}` |
| 7 | 404 page for `/nonexistent` | ✅ | HTTP 404 with proper `<title>Page Not Found | WorkRoot IT Solutions LLP</title>` |
| 8 | Broken images | 🐛 | `/favicon.ico` returns HTTP 404 (only `/favicon.svg` is shipped) — see BUG-005. All `<img>` tags on portfolio/about/home including external Unsplash images return 200 |
| 9 | Mobile viewport menu | ✅ | `#mobile-menu-toggle`, `#mobile-menu-overlay`, dialog with `aria-label="Mobile navigation menu"`, close button, theme toggle, "Get Started" CTA all present |
---
## 3. Bug list
> The Playwright failures cluster into 7 distinct root causes. The list below describes the **bugs**, not the test count — many test failures share a single underlying cause.
---
### 🔴 BUG-001 — CRITICAL — Site-wide stylesheet 404 (stale CSS hash served by SSR)
- **Severity:** Critical (visible regression — every page logs a console error and likely renders without its compiled stylesheet)
- **Pages affected:** `/`, `/about`, `/services`, `/portfolio`, `/blog`, `/blog/*`, `/contact`, `/privacy`, `/terms`, `/cookies`, `/sitemap`, `/404` — every SSR-rendered page
- **Steps to reproduce:**
1. Open `http://localhost:10000/about` (or any page).
2. Open DevTools → Console.
3. Observe the error.
4. `curl -I http://localhost:10000/_assets/about.DJCIkvZw.css` → returns `HTTP/1.1 404`.
- **Expected:** The CSS file referenced in the rendered HTML (`<link rel="stylesheet" href="/_assets/about.DJCIkvZw.css">`) loads with HTTP 200 and `content-type: text/css`.
- **Actual:** All pages reference the **stale** filename `about.DJCIkvZw.css` while disk contains only the newer `about.DDsw4wcw.css`. The 404 returns HTML, which Chromium's strict-MIME-checking refuses, producing the console error:
> `Refused to apply style from 'http://localhost:10000/_assets/about.DJCIkvZw.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.`
- **Root cause:** The running SSR process started at 14:40 with the old build's manifest in memory; `dist/` was rebuilt at 14:58 (during the test run — likely an unsupervised watcher/rebuild), and the new build emits a different content-hash for the page CSS. The old PID is now serving HTML that points at filenames the new `dist/client/_assets/` no longer contains. The `dist/server/manifest_efxKN8uX.mjs` on disk correctly lists `about.DDsw4wcw.css`.
- **Fix:** Either restart the node SSR process to pick up the new manifest (immediate), or wire up `pm2 reload ecosystem.config.cjs` whenever the build emits a new `manifest_*.mjs`. Long-term: forbid "rebuild while running" — gate the dist swap behind a graceful restart.
- **Test impact:** Caused ~12 of the 135 chromium failures: all 9 of `pages.spec.ts Console Error Checks *`, plus `cross-browser.spec.ts Console Error Monitoring`, `e2e-smoke-suite.spec.ts no JavaScript errors on homepage`, and `accessibility.spec.ts Color & Distinct background colors` (which depended on the styled output).
---
### 🟠 BUG-002 — HIGH — Rate limiter counts BEFORE validation, silently traps real users
- **Severity:** High (UX hostile — a customer who mistypes their email 5 times is blocked for 1 hour)
- **Pages affected:** `/contact` and any page hosting the newsletter widget (`/`, `/about`, `/services`, `/blog`, `/contact`)
- **Steps to reproduce:**
1. From the same IP, `POST /api/contact` 5 times with **invalid** payloads (e.g. `{"email":"oops"}`).
2. Submit a 6th request, this time **fully valid**.
3. Observe HTTP 429 `Too many requests. Please try again later.`
- **Expected:** Failed validations should not consume the rate budget — only successfully-validated submissions should.
- **Actual:** `src/pages/api/contact.ts` line 340 invokes `checkRateLimit(ip)` **before** parsing or validating the body (line 404). Same pattern in `src/pages/api/newsletter.ts`. Limit is **5 requests / hour per IP** with an in-memory `Map` that is not shared across SSR worker processes.
- **Real-world impact:** corporate / household NAT puts dozens of customers behind one IP. Any bot scan, accidental double-tap, or simple typo loop locks the contact form for an hour for every visitor on that IP.
- **Test impact:** Cascaded into ~40+ of the 135 chromium failures — `e2e-form-interactions.spec.ts` (16), `api-integration.spec.ts` (10), `destructive-chaos.spec.ts` (15) all expected 422/200 responses but received 429 because parallel workers shared the `127.0.0.1` rate-limit bucket. Manual repro with separate `X-Forwarded-For` headers passed every validation case.
- **Fix:** Move the `checkRateLimit` call to **after** `validateContactForm` succeeds (and after honeypot rejection, which is silent). Increase per-IP cap to 2030/hr to survive NAT. Optionally bypass the limit when `request.headers.get('content-length') < 16384` AND validation fails synchronously.
---
### 🟠 BUG-003 — HIGH — CORS `Access-Control-Allow-Origin` is hardcoded to production host
- **Severity:** High for staging/UAT browsers; not user-visible in production at workroot.in (same-origin)
- **Pages affected:** `POST /api/contact`, `POST /api/newsletter`, both `OPTIONS` preflights
- **Steps to reproduce:**
1. `curl -I -X OPTIONS http://localhost:10000/api/contact -H 'Origin: http://localhost:10000' -H 'Access-Control-Request-Method: POST'`
2. Read `Access-Control-Allow-Origin` header.
- **Expected:** Header echoes the request `Origin` after allow-list check (or returns the literal request origin when same-host).
- **Actual:** Always returns `access-control-allow-origin: https://workroot.in` regardless of the request `Origin`. A browser running at any other host would block the response.
- **Test impact:** Caused all 5 failures in `bug-fix-verification.spec.ts BUG-4: CORS headers on API endpoints`.
- **Fix:** In `src/pages/api/{contact,newsletter}.ts`, modify `corsHeaders(origin)` to allow-list `[https://workroot.in, http://localhost:10000, http://localhost:4321]` and echo the matching origin (with `Vary: Origin` already correctly present).
---
### 🟠 BUG-004 — HIGH — Accessibility regression: 44 SVGs without `aria-hidden` or label on homepage
- **Severity:** High (WCAG 2.1 AA violation — 1.1.1 Non-text Content); blocks AAA audit and accessibility certification
- **Pages affected:** every page with inline SVG icons, especially `/`, `/about`, `/services`, `/blog`
- **Steps to reproduce:**
1. View page source of `/`.
2. Count `<svg>` elements: 90.
3. Count those with `aria-hidden="true"`, `aria-label`, or `aria-labelledby`: only 46.
- **Expected:** Decorative SVGs declare `aria-hidden="true"`; meaningful SVGs declare `role="img"` plus `<title>` or `aria-label`.
- **Actual:** 44 SVGs (about half of all icons) lack any accessibility attribute. Sample:
```html
<svg class="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">…</svg>
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">…</svg>
```
- **Test impact:** 9 distinct failures across `accessibility.spec.ts 1.1.1 Non-text Content (Images & Icons) *: SVGs are either decorative (aria-hidden) or have accessible labels` (Home, About, Services, Portfolio, Blog, Contact, Privacy Policy, Terms, Sitemap).
- **Fix:** Audit `src/components/Header.astro`, `Footer.astro`, hero icons in `src/pages/index.astro`, `services.astro`, etc. Most are decorative chevrons/arrows — add `aria-hidden="true"` to those. Add proper `aria-label` to the few that convey meaning (e.g. social media icons in the footer).
---
### 🟡 BUG-005 — MEDIUM — Stale portfolio category in test fixtures (or stale UX scope)
- **Severity:** Medium — either test scope is wrong or product copy is wrong; ambiguity blocks demo confidence
- **Pages affected:** `/portfolio`
- **Steps to reproduce:**
1. Browse `/portfolio`. Note filter tabs: **All / Web / Mobile / Enterprise / Government** (5 tabs, 6 project cards).
2. `tests/cross-browser.spec.ts:492` asserts: `data-filter="ai"` button is visible AND `data-filter` set is exactly `{all, web, mobile, ai}`, AND `.project-card` count is exactly **8**.
- **Expected (pick one):** Either tests are stale (product owner removed the AI category and added Enterprise + Government) and need updating, OR product is missing the "AI & ML" filter and 2 portfolio cards that the tests expect.
- **Test impact:** Caused ~7 cross-browser failures (`Portfolio Filters All filter buttons are present`, `"All Projects" filter is active by default`, `"AI & ML" filter shows only AI projects`, etc.).
- **Fix:** Confirm with product whether AI/ML is a deferred portfolio category. If yes — add it back with seed projects. If no — update the tests' expected `data-filter` set and project count.
---
### 🟡 BUG-006 — MEDIUM — `/favicon.ico` returns HTTP 404 (only `/favicon.svg` is shipped)
- **Severity:** Medium (some older browsers + email clients + RSS readers still request `/favicon.ico` by default; logs noise)
- **Pages affected:** every page (browsers auto-request `/favicon.ico` on first visit unless overridden in `<link rel="icon">`)
- **Steps to reproduce:** `curl -o /dev/null -w '%{http_code}\n' http://localhost:10000/favicon.ico` → `404`.
- **Expected:** A shipped `.ico` file or a 301 redirect to `/favicon.svg`.
- **Actual:** 404 served from the SSR 404.astro template (41 KB HTML response for what should be a 0.5 KB icon — wasted bandwidth).
- **Fix:** Either drop a 16×16 / 32×32 multi-resolution `.ico` into `public/`, or add an Express handler in `server.mjs` that 301-redirects `/favicon.ico` → `/favicon.svg`. The HTML already declares `<link rel="icon" href="/favicon.svg">` so most modern browsers won't ask, but Edge legacy and some crawlers still do.
---
### 🟡 BUG-007 — MEDIUM — `security-headers.test.ts` Domain Security: canonical / OG host mismatch on local environment
- **Severity:** Medium (test asserts production domain string in OG tags, fails on localhost build)
- **Pages affected:** `/`, all SEO-tagged pages
- **Steps to reproduce:**
1. Render `/`. Inspect `<link rel="canonical">` and `<meta property="og:url">`.
2. Both report the production hostname `https://workroot.in/...`.
- **Expected:** Canonical and OG URLs reflect the **deployed** domain. On a local environment they appear "wrong" but are intentionally hard-coded for production deploys.
- **Actual:** Tests `should have canonical URL with workroot.in` and `should have correct domain in Open Graph tags` would pass on production but fail when run against local `http://localhost:10000` because the live domain in OG was something else in this test environment, OR the test asserts the literal string but the meta is dynamically replaced. This is a **test environment / config mismatch**, not a runtime bug — but it should be parameterized so QA against staging URLs doesn't false-fail.
- **Fix:** Read the expected canonical host from a `process.env.SITE_URL` in the test file, with `https://workroot.in` as default.
---
### 🟢 BUG-008 — LOW — Newsletter API rate-limit headers absent on first request, then OK
- **Severity:** Low — cosmetic test failure
- **Pages affected:** `/api/newsletter`
- **Steps to reproduce:** First post to `/api/newsletter` from a cold worker omits `X-RateLimit-*` headers under some race conditions (test failure: `newsletter API returns rate limit headers` — flaky, passed in our manual curl repro).
- **Expected:** `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` headers always set.
- **Actual:** Headers present on every manual repro; intermittent in parallel test workers when the Map is being initialised.
- **Fix:** Initialize `rateLimitStore` lazily inside a synchronous closure to avoid the race; add headers to all early-return paths.
---
### 🟢 BUG-009 — LOW — Honeypot returns success body but distinct text from real success
- **Severity:** Low — OPSEC observation, slight inconsistency
- **Pages affected:** `POST /api/contact`
- **Steps to reproduce:**
1. Real submit: `{"success":true,"message":"Your message has been received. We will get back to you soon."}`
2. Honeypot trip (`website` field non-empty): `{"success":true,"message":"Message received. Thank you!"}`
- **Expected:** Identical response body so a bot can't fingerprint the honeypot.
- **Actual:** Two distinct `message` strings — a sufficiently clever scraper could detect the silent-reject branch.
- **Fix:** Use the same JSON body in both branches.
---
## 4. Test failures that are NOT product bugs (test-suite hygiene)
These accounted for a large share of the 135 reds and should be triaged **on the test side**:
- **Stale CSS-asset 404 (BUG-001) → ~12 tests** — restart fixes them.
- **Per-IP rate-limit collision (BUG-002) → ~40 tests** — running parallel workers from `127.0.0.1` blows the 5/hr cap. Tests should: (a) pass `X-Forwarded-For` with a unique pseudo-IP, OR (b) reset the in-memory rate-limit store between tests via a debug endpoint, OR (c) lower parallelism and add a cooldown.
- **CORS allow-origin host hard-coded (BUG-003) → 5 tests** — would pass once the handler echoes the request origin.
- **`e2e-blog-navigation direct URL access to blog post`** — passes manually; failed in suite due to `networkidle` timing under parallel load.
- **`e2e-critical-paths Mobile First-Time Visitor`** — long flow that touched the contact form and was 429'd by BUG-002.
- **`destructive-chaos XSS / SQL-injection in name field`** — payloads were correctly rejected at API level (422), but the *test* expects a different success-message text in the UI; UI never renders because BUG-002 returned 429.
After BUG-001, BUG-002, BUG-003 are fixed, expected chromium pass rate is **~95 %+** (estimated 635 / 671 passing).
---
## 5. Verdict
🔴 **NOT READY for client demo.**
**Blockers (must fix before showing to client):**
1. **BUG-001 (Critical)** — every page is missing its compiled stylesheet because the running SSR process was rebuilt out from under itself. Either restart the node process now and confirm `/_assets/about.*.css` returns 200, or guarantee the dist swap is atomic. Demoing a site that logs a CSP/MIME error on every page is unacceptable.
2. **BUG-002 (High)** — the contact form is the single most important conversion path and currently locks itself for an hour after 5 attempts (including invalid ones). A live demo that shows "Too many requests" mid-flow is a deal-killer.
3. **BUG-004 (High)** — 44 unlabeled SVGs across the site means any client-side accessibility audit (Lighthouse, axe, govt RFP a11y check) will fail visibly. WorkRoot's portfolio includes government IT systems — clients in this space ask for WCAG conformance.
**Should fix before demo:**
4. BUG-003 (CORS hard-coded) — embarrassing if a partner integrates the form.
5. BUG-005 (portfolio AI filter) — confirm scope with product so demo narrative matches what's on screen.
6. BUG-006 (favicon.ico 404) — five-minute fix.
**Nice-to-have:**
7. BUG-007 (security-headers test parameterization)
8. BUG-008 (rate-limit headers race)
9. BUG-009 (honeypot fingerprinting)
After BUG-001, -002, -004 are fixed and verified, re-run the suite and re-issue this report.
---
*Generated 2026-05-11 by automated QA pass; full Playwright JSON output preserved at `test-results/` artifacts.*