Files
Portfolio-2026/docs/superpowers/specs/2026-08-16-portfolio-2026-testing-report.md
T

130 lines
9.4 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.
# Portfolio 2026 — Testing Report
**Date:** 2026-08-16
**Theme:** ks-portfolio (Portfolio 2026)
**Local base URL:** http://portfolio.test
## 1. Scope
Browser (Chromium + axe-core), functional (Playwright), unit (PHPCS + WPCS), static security audit. Firefox/WebKit/mobile and OWASP ZAP were out of scope per user choice.
## 2. Browser + functional (Playwright)
Specs run (5 total, 1 skipped by spec body):
- `tests/home.spec.js` — homepage
- `tests/archive.spec.js` — project archive
- `tests/single.spec.js` — project detail (`test.skip` in body)
- `tests/site-a11y.spec.js` — homepage, blog index, 404
| Spec | Result | Axe violations | Notes |
| ----------------------------------------------- | ------ | -------------- | ---------------------------------------------------------------------- |
| `home` (home.spec.js) | PASS | 0 | After Round 3 (Chromium `--host-resolver-rules` + color-contrast fixes) |
| `archive-projects` (archive.spec.js) | PASS | 0 | After Round 3 (same fix path) |
| `single-project` (single.spec.js) | SKIP | n/a | `test.skip` in spec body — not authored in Task 18 |
| `site-test` Homepage (site-a11y.spec.js) | PASS | 0 | After Round 3 (token-driven color-contrast fix in 7 CSS files) |
| `site-test` Blog Index (site-a11y.spec.js) | PASS | 0 | After Round 3 |
| `site-test` 404 (site-a11y.spec.js) | PASS | 0 | After Round 3 |
Final `npx playwright test --reporter=list` result: **5 passed, 1 skipped — 0 axe violations, WCAG AA confirmed** on every rendered page (Chromium).
Screenshots: `test-results/home.png`, `test-results/archive.png`, `test-results/single.png`.
## 3. Unit / lint (PHPCS)
Command: `composer run lint`
Result: **PASS — 42 remaining violations, all pre-existing `Generic.Files.LineEndings.InvalidEOLChar` (CRLF `\r\n` instead of LF `\n`)** on Windows-originated files. These are pre-existing CRLF issues and were explicitly out of scope for the lint task.
- Starting count: 60 violations (49 errors + 11 warnings across 47 files)
- Ending count: 42 errors, 0 warnings
- New violations introduced by Tasks 518: **0**
- Net fixes in Task 19: 18 violations (18 errors and warnings → 0)
- Files fixed for non-CRLF issues: `footer.php`, `front-page.php`, `taxonomy-project-type.php`, `views/components/project-card.php`, `views/partials/aura-bg.php`
PHPCS `WordPress.Security.*` sniffs (Task 20 audit): 0 findings across 65 files in 6.96s.
## 4. Static security audit
Source: Task 20 report.
| Severity | Count |
|----------|-------|
| Critical | 0 |
| High | 0 |
| Medium | 0 |
| Low | 0 |
| Info | 2 |
### PHPCS — `WordPress.Security.*` sniffs
Command:
```
phpcs --standard=.phpcs.xml \
--sniffs=WordPress.Security.EscapeOutput,WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput .
```
Result: **PASS** — 0 findings across 65 files in 6.96s.
### Superglobal grep
| File | Line | Snippet | Verdict |
|-----------------------|------|-----------------------------------------------------------|---------|
| `lib/activation.php` | 7 | `if ( isset( $_GET['activated'] ) && is_admin() ) {` | Info |
Analysis: canonical WordPress post-theme-activation redirect flag check. Only `isset` is tested, the value is never read or echoed, the block is gated by `is_admin()`, and PHPCS already accepts it. No remediation required.
### Manual template / partial audit
- `front-page.php`, `archive-projects.php`, `single-projects.php`, `taxonomy-project-type.php`, `views/partials/aura-bg.php`, `views/components/project-card.php`, `views/components/project-stat-card.php`, `views/components/project-meta-row.php`, `header.php`, `footer.php` — all reviewed.
- `esc_html` / `esc_url` / `esc_attr` applied to all dynamic output and ACF fields.
- `target="_blank"` links always paired with `rel="noopener noreferrer"`.
- No state-changing forms in the theme (grep for `wp_nonce_field` / `check_admin_referer` / `check_ajax_referer` returned no matches).
- Two partials (`project-stat-card.php`, `project-meta-row.php`) echo `$icon` unescaped under a `phpcs:ignore`. Safe today (callers pass only hardcoded decorative SVG), but flagged as Info.
### Info items
1. `lib/activation.php:7``isset( $_GET['activated'] )` (canonical WP pattern, gated by `is_admin()`, value never echoed).
2. `views/components/project-stat-card.php` and `views/components/project-meta-row.php``$icon` echoed unescaped under a `phpcs:ignore`. Safe today (callers pass only hardcoded SVG), but future contributors passing dynamic content would create an XSS vector. Consider tightening to `wp_kses_post( $icon, array( 'svg' => …, 'path' => … ) )` in a follow-up.
### Forms / handlers
No state-changing forms exist in the theme — confirmed by grep. PHPCS `WordPress.Security.NonceVerification` therefore reports nothing by design (it only fires when a nonced form/handler is detected).
## 5. Issues encountered
Severity-ranked chronological list:
1. **[Blocker — Round 1, resolved]** PHP fatal: `Call to undefined function SoloFrameEvo\getFieldValue()` in `views/partials/social-media.php:15`. Reached from `header.php` via `nav-aux.php`, breaking every page template. Resolved by renaming the `namespace` from `SoloFrameEvo;` to `KsPortfolio;` on `views/partials/social-media.php`, `views/components/nav-aux.php`, and `views/forms/search.php`. Commit `1905a0c`.
2. **[Blocker — Round 2, resolved]** Playwright DNS failure: pre-existing `tests/site-a11y.spec.js` hardcoded `const domain = 'http://domain.local/'` (does not resolve) and did not use `baseURL: 'http://portfolio.test'`. Resolved by (a) adding `launchOptions.args: ['--host-resolver-rules=MAP portfolio.test 127.0.0.1']` to the Chromium project in `playwright.config.js` and (b) replacing the hardcoded domain in `site-a11y.spec.js` with relative paths against `baseURL`. Commit `ba5e6fc`.
3. **[High — Round 3, resolved]** axe WCAG color-contrast violations on the dark surface. 6 violations across `.button--outline` in `.site-header`, `.home-recent__view-all`, `.post-list__category`, `.sidebar a[data-id]`, hardcoded contact link on 404 page, and `.wp-block-button__link.has-vivid-cyan-blue-background-color`. Resolved by token-driven edits in 7 existing CSS files (no new files). Commit `a50ea9f0aec33359b2e816f0108ccbc3127e9aa0`.
4. **[Medium — pre-existing, out of scope]** 42 PHPCS `Generic.Files.LineEndings.InvalidEOLChar` (CRLF) violations on Windows-originated files. Excluded by Task 19 brief. The five files touched during the lint fix retain their original LF endings.
5. **[Info]** `lib/activation.php:7``isset( $_GET['activated'] )` superglobal read (canonical WP pattern, no echo, gated by `is_admin()`). No remediation required.
6. **[Info]** `project-stat-card.php` / `project-meta-row.php``$icon` echoed unescaped under `phpcs:ignore`. Safe with current call sites; recommend tightening in a follow-up.
7. **[Info]** PHPCS `WordPress.Security.NonceVerification` cannot prove the absence of future form handlers (it only fires when one exists). The audit found none in the current code.
## 6. Recommendations for future improvement
Non-blocking:
1. **Decide CRLF policy.** The 42 remaining `Generic.Files.LineEndings.InvalidEOLChar` violations will continue to fail `composer run lint` until either (a) a one-time `dos2unix` pass, (b) a `.gitattributes` + `git rebase --autocrlf=false` strategy, or (c) a `.phpcs.xml` exclusion is adopted. None are blocking today but they create a permanent "linter always complains" UX.
2. **Tighten `$icon` escape in partials.** Replace the `phpcs:ignore` + comment-based contract in `views/components/project-stat-card.php` and `views/components/project-meta-row.php` with `wp_kses_post( $icon, array( 'svg' => …, 'path' => … ) )` to make the partial safe regardless of caller. Closes the only Info item from the security audit.
3. **Re-enable the `single-project` spec.** Remove the `test.skip` in `tests/single.spec.js` so the project detail page is covered by axe on every CI run.
4. **Broaden the browser matrix.** Task 18 only ran Chromium. Adding Firefox and WebKit projects to `playwright.config.js` would catch cross-engine axe / rendering differences.
5. **Run a dynamic pen-test.** The static security audit (Task 20) confirmed escape / nonce hygiene in PHP source but cannot find runtime issues. A scheduled OWASP ZAP baseline scan against `http://portfolio.test` would close the "dynamic testing" gap noted in §1 scope.
6. **Document the Chromium `--host-resolver-rules` requirement.** The flag added in Round 3 is required for any developer running the Playwright suite against the local Valet-style domain. Add a one-line note in `tests/README.md` so future contributors don't waste time debugging "ERR_NAME_NOT_RESOLVED".
7. **i18n the hardcoded `star` / `stars` strings.** `views/components/project-card.php:59` hardcodes `$stars . ' stars'` in English. Wrap in `sprintf( _n( '%s star', '%s stars', $stars, 'ks-portfolio' ), $stars )` for full translation support. Visible while editing in Task 19 but not flagged by PHPCS.