======================================================================== SRC PORTAL — QA TEST REPORT ======================================================================== Run timestamp: 2026-05-18_12-17-47 Commit under test: a9d9504 Commit message: fix(clinical-notes): exclude transitioned-out participants from notes portal Total duration: 307s HTML report: http://192.168.2.174:8082/2026-05-18_12-17-47/ History index: http://192.168.2.174:8082/ ------------------------------------------------------------------------ OVERALL ------------------------------------------------------------------------ Passed: 16 Failed: 6 Skipped: 14 Flaky: 0 ------------------------------------------------------------------------ BY SUITE ------------------------------------------------------------------------ suites/00_smoke.spec.js 12 passed / 0 failed / 0 skipped suites/01_admissions.spec.js 3 passed / 0 failed / 0 skipped suites/02_hub_intake.spec.js 0 passed / 2 failed / 1 skipped suites/03_transport.spec.js 1 passed / 1 failed / 0 skipped suites/04_facility_ops.spec.js 0 passed / 1 failed / 4 skipped suites/05_housing_nav.spec.js 0 passed / 1 failed / 4 skipped suites/06_lifecycle_e2e.spec.js 0 passed / 1 failed / 5 skipped ======================================================================== FAILURES (6) ======================================================================== [1] @critical HI-09 — full 12-step intake → Active in Program Suite: suites/02_hub_intake.spec.js > @intake File: suites/02_hub_intake.spec.js:86 Status: timedOut Duration: 184s Error: Test timeout of 180000ms exceeded. Error: locator.click: Test timeout of 180000ms exceeded. Call log:  - waiting for getByRole('button', { name: /active in program/i }).first() 171 | // STEP 12 — Disposition (required). 172 | await ui.selectOption(page, 'Disposition', 'Active in Program').catch(async () => { > 173 | await page.getByRole('button', { name: /active in program/i }).first().click(); | ^ 174 | }); 175 | const finish = page.getByRole('button', { name: /complete|finish|submit/i }).first(); 176 | await finish.click(); Artifacts: - screenshot: /opt/qa-playwright/test-results/suites-02_hub_intake--inta-6b101--intake-→-Active-in-Program-chromium/test-failed-1.png - video: /opt/qa-playwright/test-results/suites-02_hub_intake--inta-6b101--intake-→-Active-in-Program-chromium/video.webm - error-context: /opt/qa-playwright/test-results/suites-02_hub_intake--inta-6b101--intake-→-Active-in-Program-chromium/error-context.md - trace: /opt/qa-playwright/test-results/suites-02_hub_intake--inta-6b101--intake-→-Active-in-Program-chromium/trace.zip [2] @critical HI-11 — Withdrew / AMA disposition (no HN case, no transport) Suite: suites/02_hub_intake.spec.js > @intake File: suites/02_hub_intake.spec.js:186 Status: failed Duration: 43s Error: TimeoutError: locator.click: Timeout 10000ms exceeded. Call log:  - waiting for getByRole('button', { name: /withdrew/i }).first() 217 | // Step 12: Withdrew / AMA 218 | await ui.selectOption(page, 'Disposition', 'Withdrew / AMA').catch(async () => { > 219 | await page.getByRole('button', { name: /withdrew/i }).first().click(); | ^ 220 | }); 221 | await page.getByRole('button', { name: /complete|finish|submit/i }).first().click(); 222 | Artifacts: - screenshot: /opt/qa-playwright/test-results/suites-02_hub_intake--inta-56c27-on-no-HN-case-no-transport--chromium/test-failed-1.png - video: /opt/qa-playwright/test-results/suites-02_hub_intake--inta-56c27-on-no-HN-case-no-transport--chromium/video.webm - error-context: /opt/qa-playwright/test-results/suites-02_hub_intake--inta-56c27-on-no-HN-case-no-transport--chromium/error-context.md - trace: /opt/qa-playwright/test-results/suites-02_hub_intake--inta-56c27-on-no-HN-case-no-transport--chromium/trace.zip [3] @critical TP-02 + TP-03 + TP-04 + TP-05 — build run → in transit → deliver → complete Suite: suites/03_transport.spec.js > @transport File: suites/03_transport.spec.js:28 Status: failed Duration: 11s Error: TimeoutError: locator.click: Timeout 10000ms exceeded. Call log:  - waiting for getByRole('button', { name: /save|create|build run/i }).first()  - locator resolved to   - attempting click action  2 × waiting for element to be visible, enabled and stable  - element is not enabled  - retrying click action  - waiting 20ms  2 × waiting for element to be visible, enabled and stable  - element is not enabled  - retrying click action Artifacts: - screenshot: /opt/qa-playwright/test-results/suites-03_transport--trans-768da-ransit-→-deliver-→-complete-chromium/test-failed-1.png - video: /opt/qa-playwright/test-results/suites-03_transport--trans-768da-ransit-→-deliver-→-complete-chromium/video.webm - error-context: /opt/qa-playwright/test-results/suites-03_transport--trans-768da-ransit-→-deliver-→-complete-chromium/error-context.md - trace: /opt/qa-playwright/test-results/suites-03_transport--trans-768da-ransit-→-deliver-→-complete-chromium/trace.zip [4] @critical FO-01 — arrivals queue surfaces delivered participants Suite: suites/04_facility_ops.spec.js > @facility File: suites/04_facility_ops.spec.js:23 Status: failed Duration: 16s Error: Error: expect(locator).toContainText(expected) failed Locator: locator('body') Timeout: 5000ms Expected pattern: /arrival|new arrivals/i Received string: "  ← Departments🏢Facility OperationsQA Sup St. AndrewsSign out🏢 Select FacilityChoose the facility you are working at today— Select facility —CarmelFigueroaRaywoodSt. AndrewsContinue →···· " Call log:  - Expect "toContainText" with timeout 5000ms  - waiting for locator('body') Artifacts: - screenshot: /opt/qa-playwright/test-results/suites-04_facility_ops--fa-aa583-aces-delivered-participants-chromium/test-failed-1.png - video: /opt/qa-playwright/test-results/suites-04_facility_ops--fa-aa583-aces-delivered-participants-chromium/video.webm - error-context: /opt/qa-playwright/test-results/suites-04_facility_ops--fa-aa583-aces-delivered-participants-chromium/error-context.md - trace: /opt/qa-playwright/test-results/suites-04_facility_ops--fa-aa583-aces-delivered-participants-chromium/trace.zip [5] @critical HN-03 — tier labels use T3/T2/T1, not DB values Suite: suites/05_housing_nav.spec.js > @hn File: suites/05_housing_nav.spec.js:31 Status: failed Duration: 0s Error: Error: expect(received).toMatch(expected) Expected pattern: /T3.*(make contact|caller)|T2.*operator|T1.*action items/i Received string: "← Departments 🗺️ Housing Navigation QA Housing Nav Sign out Housing Navigation· Case management, touchpoint tracking, and housing placement· Who are you? Select your name to see your assigned cases Artifacts: - screenshot: /opt/qa-playwright/test-results/suites-05_housing_nav--hn--44645--use-T3-T2-T1-not-DB-values-chromium/test-failed-1.png - video: /opt/qa-playwright/test-results/suites-05_housing_nav--hn--44645--use-T3-T2-T1-not-DB-values-chromium/video.webm - error-context: /opt/qa-playwright/test-results/suites-05_housing_nav--hn--44645--use-T3-T2-T1-not-DB-values-chromium/error-context.md - trace: /opt/qa-playwright/test-results/suites-05_housing_nav--hn--44645--use-T3-T2-T1-not-DB-values-chromium/trace.zip [6] Admissions — create + Eligible (HN=Yes, ECM=Yes) Suite: suites/06_lifecycle_e2e.spec.js > @e2e @critical LC-01 — full lifecycle File: suites/06_lifecycle_e2e.spec.js:19 Status: failed Duration: 11s Error: TimeoutError: locator.fill: Timeout 10000ms exceeded. Call log:  - waiting for locator('//label[contains(translate(., \'ABCDEFGHIJKLMNOPQRSTUVWXYZ\',\'abcdefghijklmnopqrstuvwxyz\'), \'first name\')]/following::input[1]').first() at helpers/ui.js:48 46 | `xpath=//label[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'), '${label.toLowerCase()}')]/following::input[1]` 47 | ).first(); > 48 | await fallback.fill(String(value)); | ^ 49 | } Artifacts: - screenshot: /opt/qa-playwright/test-results/suites-06_lifecycle_e2e--e-f35a3-te-Eligible-HN-Yes-ECM-Yes--chromium/test-failed-1.png - video: /opt/qa-playwright/test-results/suites-06_lifecycle_e2e--e-f35a3-te-Eligible-HN-Yes-ECM-Yes--chromium/video.webm - error-context: /opt/qa-playwright/test-results/suites-06_lifecycle_e2e--e-f35a3-te-Eligible-HN-Yes-ECM-Yes--chromium/error-context.md - trace: /opt/qa-playwright/test-results/suites-06_lifecycle_e2e--e-f35a3-te-Eligible-HN-Yes-ECM-Yes--chromium/trace.zip ======================================================================== KNOWN DEFECTS (cumulative — from DEFECTS.md) ======================================================================== # QA Defect Log Format: [test that revealed it] — description — evidence --- ## DEFECT-001 — portal_users stores passwords as plain text **Revealed by:** Phase 4 seed investigation (2026-05-15) **Category:** B — Real portal bug **Severity:** CRITICAL (security) **Affects:** Test stack (:8090) AND production mirror (:8080) — same codebase, same DB pattern `userService.js:findUserByCredentials` compares `u.password === password` — a plain-text equality check. The `portal_users.password` column contains unencrypted passwords (confirmed in prod-mirror: `Demo@2026!`, `Admin@2026!`, etc.). **Evidence:** - `src/layers/service/userService.js:130`: `u.password === password` - `src/layers/auth/AuthContext.jsx:76`: `found.password === password` - Prod-mirror `portal_users` table: passwords visible as plain text **Impact:** Any database read (compromised Supabase key, RLS misconfiguration, or direct DB access) exposes all staff credentials immediately. **HIPAA implications:** This portal handles Protected Health Information (PHI) for shelter residents. Staff accounts authenticate into screens containing participant medical history, medication records, housing status, and case notes. Plain-text credential storage violates HIPAA Security Rule §164.312(d) (person authentication) and §164.312(a)(2)(i) (unique user identification protections). A single DB credential leak or misconfigured RLS policy would simultaneously expose both staff credentials and the PHI they guard. This is reportable if exploited. **Action required:** Escalate to portal team immediately. Do NOT modify portal source in this repo (upstream read-only rule). This is a portal-team fix: migrate `portal_users.password` to bcrypt hashes and update `findUserByCredentials` to use `bcrypt.compare()`. **Not fixed in tests:** The test seed stores `Test@2026!` in plain text to match the portal's expectation. If the portal migrates to server-side bcrypt, update `\set test_hash` in `scripts/seed-test-data.sql` and remove this note. --- ======================================================================== End of report ========================================================================