# SRC Portal — QA Test Report

| | |
|---|---|
| Run timestamp | `2026-05-18_12-17-47` |
| Commit | `a9d9504` |
| Commit message | fix(clinical-notes): exclude transitioned-out participants from notes portal |
| Duration | 307s |
| HTML report | http://192.168.2.174:8082/2026-05-18_12-17-47/ |
| History | http://192.168.2.174:8082/ |

## Overall

- ✅ **Passed:** 16
- ❌ **Failed:** 6
- ⏭️ **Skipped:** 14
- ⚠️ **Flaky:** 0

## By suite

| Suite | Passed | Failed | Skipped |
|---|---:|---:|---:|
| suites/00_smoke.spec.js | 12 | 0 | 0 |
| suites/01_admissions.spec.js | 3 | 0 | 0 |
| suites/02_hub_intake.spec.js | 0 | 2 | 1 |
| suites/03_transport.spec.js | 1 | 1 | 0 |
| suites/04_facility_ops.spec.js | 0 | 1 | 4 |
| suites/05_housing_nav.spec.js | 0 | 1 | 4 |
| suites/06_lifecycle_e2e.spec.js | 0 | 1 | 5 |

## 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:**
```
[31mTest timeout of 180000ms exceeded.[39m
Error: locator.click: Test timeout of 180000ms exceeded.
Call log:
[2m  - waiting for getByRole('button', { name: /active in program/i }).first()[22m


  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();
    at /opt/qa-playwright/tests/suites/02_hub_intake.spec.js:173:78
    at /opt/qa-playwright/tests/suites/02_hub_intake.spec.js:172:5
```

**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:
[2m  - waiting for getByRole('button', { name: /withdrew/i }).first()[22m


  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 |
    at /opt/qa-playwright/tests/suites/02_hub_intake.spec.js:219:69
    at /opt/qa-playwright/tests/suites/02_hub_intake.spec.js:218:5
```

**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:
[2m  - waiting for getByRole('button', { name: /save|create|build run/i }).first()[22m
[2m    - locator resolved to <button disabled class="tp-btn-primary">Create Run →</button>[22m
[2m  - attempting click action[22m
[2m    2 × waiting for element to be visible, enabled and stable[22m
[2m      - element is not enabled[22m
[2m    - retrying click action[22m
[2m    - waiting 20ms[22m
[2m    2 × waiting for element to be visible, enabled and stable[22m
[2m      - element is not enabled[22m
[2m    - retrying click action[22m
[2m      - waiting 100ms[22m
[2m    19 × waiting for element to be visible, enabled and stable[22m
[2m       - element is not enabled[22m
```

**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: [2mexpect([22m[31mlocator[39m[2m).[22mtoContainText[2m([22m[32mexpected[39m[2m)[22m failed

Locator: locator('body')
Timeout: 5000ms
Expected pattern: [32m/arrival|new arrivals/i[39m
Received string:  [31m"[39m
[31m    ← Departments🏢Facility OperationsQA Sup St. AndrewsSign out🏢 Select FacilityChoose the facility you are working at today— Select facility —CarmelFigueroaRaywoodSt. AndrewsContinue →····[39m
[31m"[39m

Call log:
[2m  - Expect "toContainText" with timeout 5000ms[22m
[2m  - waiting for locator('body')[22m
[2m    14 × locator resolved to <body>…</body>[22m
[2m       - unexpected value "[22m
[2m    ← Departments🏢Facility OperationsQA Sup St. AndrewsSign out🏢 Select FacilityChoose the facility you are working at today— Select facility —CarmelFigueroaRaywoodSt. AndrewsContinue →[22m
```

**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: [2mexpect([22m[31mreceived[39m[2m).[22mtoMatch[2m([22m[32mexpected[39m[2m)[22m

Expected pattern: [32m/T3.*(make contact|caller)|T2.*operator|T1.*action items/i[39m
Received string:  [31m"← Departments[39m
[31m🗺️[39m
[31mHousing Navigation[39m
[31mQA Housing Nav[39m
[31mSign out[39m
[31mHousing Navigation·[39m
[31mCase management, touchpoint tracking, and housing placement·[39m
[31mWho are you?[39m
[31mSelect your name to see your assigned cases[39m
[31m— Select your name —[39m
[31mMy name isn't listed…"[39m

```

**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:
[2m  - waiting for locator('//label[contains(translate(., \'ABCDEFGHIJKLMNOPQRSTUVWXYZ\',\'abcdefghijklmnopqrstuvwxyz\'), \'first name\')]/following::input[1]').first()[22m


   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 | }
  50 |
  51 | /**
    at Object.fillField (/opt/qa-playwright/tests/helpers/ui.js:48:18)
```

**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)

# 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.

---

---

*Generated automatically by run-report.sh. No Claude involvement.*