From ca9c5f215c208f0de028e6b0c6ae5c4d123891cc Mon Sep 17 00:00:00 2001 From: Keith Solomon Date: Sun, 16 Aug 2026 14:13:58 -0500 Subject: [PATCH] docs: add Portfolio 2026 testing report --- ...026-08-16-portfolio-2026-testing-report.md | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-16-portfolio-2026-testing-report.md diff --git a/docs/superpowers/specs/2026-08-16-portfolio-2026-testing-report.md b/docs/superpowers/specs/2026-08-16-portfolio-2026-testing-report.md new file mode 100644 index 0000000..21a85cc --- /dev/null +++ b/docs/superpowers/specs/2026-08-16-portfolio-2026-testing-report.md @@ -0,0 +1,129 @@ +# 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 5–18: **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.