Files
homepage/tests/e2e/01-page.spec.js
T
ducoterra 2f6f2dc0b9
Build and Push Container / build-and-push (push) Successful in 16s
phase: 04_huntington_role
**Phase 04 final verification — all green**

Implemented/verified
- Both phase tasks already complete (`complete/04_huntington_role/01`,`02`); this pass was final verification + docs.
- `src/index.html` HNB "Lead MLOps Engineer · Jun 2026 – Present" entry first with 3 bullets; Nimbis closed at `Jan 2022 – Apr 2026`.

Tests / lint / gate
- `./build.sh` → `dist/` rebuilt (9 hashed assets) · `npm test` → **47 passed, 0 failed**
- `bash .agents/validate.sh` → **exit 0**: 47/47 E2E, `HEADER CHECK PASSED` (real podman image, `/`, `/index.html`, hashed css), hygiene checks clean
- `node --check` all `src/*.js` + `tests/e2e/*.js` + `playwright.config.js` OK; `bash -n` on `build.sh`, `scripts/check-headers.sh`, `.agents/validate.sh` OK (no JS lint toolchain by design)

Completion criteria
- `grep -c "Huntington National Bank" dist/index.html` = **1**; `Jun 2026 – Present` at dist:118 — PASS
- Nimbis `Jan 2022 – Apr 2026` at dist:138; only one `– Present` in file — PASS
- Regression test `experience timeline leads with Huntington National Bank role` present at `tests/e2e/01-page.spec.js:28` and passing — PASS
- `./build.sh && npm test` fully green (47 ≥ 45) — PASS · No behavior change: terminal/vim/achievements/menu specs all pass — PASS
- Coverage >90% on new/modified code: vacuous (HTML + test only, no JS/CSS touched) — PASS

Documentation (fixed)
- README: `check-headers.sh` claimed "aborting at the first failing path" — it actually checks all three paths then exits non-zero; also added the real webServer command (`--bind 0.0.0.0`) and `npm run test:headed`.
- AGENTS.md: `innerHTML` allowance scoped precisely (only the `cmdText === 'neofetch'` branch in terminal.js; vim/achievements only static or `escapeHtml()`-escaped).

Notable: `.agents/PLAN.md` §4/§5 still say "44 tests" (now 47) — PLAN.md is locked to me; flagged for user approval.

Next pending phase: `.agents/phases/todo/05_update_projects_section`
2026-10-23 00:36:58 -04:00

97 lines
4.7 KiB
JavaScript
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.
// @ts-check
const { test, expect } = require('@playwright/test');
test.describe('Page structure', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('loads with correct title and lang', async ({ page }) => {
await expect(page).toHaveTitle(/Reese Wells/);
await expect(page.locator('html')).toHaveAttribute('lang', 'en');
});
test('nav has all seven section links pointing at existing targets', async ({ page }) => {
const links = page.locator('#navLinks a');
await expect(links).toHaveCount(7);
const hrefs = await links.evaluateAll(as => as.map(a => a.getAttribute('href')));
expect(hrefs).toEqual(['#hero', '#about', '#experience', '#skills', '#projects', '#contact', '#gpg']);
for (const href of hrefs) {
await expect(page.locator(href)).toBeAttached();
}
});
test('all major sections exist and are in the DOM', async ({ page }) => {
for (const id of ['hero', 'about', 'experience', 'skills', 'projects', 'contact', 'gpg']) {
await expect(page.locator(`#${id}`)).toBeAttached();
}
});
test('experience timeline leads with Huntington National Bank role', async ({ page }) => {
const items = page.locator('#experience .timeline-item');
await expect(items).toHaveCount(5);
const first = items.first().locator('.timeline-content');
await expect(first.locator('h3')).toHaveText('Lead MLOps Engineer');
await expect(first.locator('.timeline-company')).toHaveText('Huntington National Bank');
await expect(first.locator('.timeline-date')).toHaveText('Jun 2026 – Present');
const bullets = first.locator('.timeline-list li');
await expect(bullets).toHaveCount(3);
await expect(bullets.nth(0)).toContainText('agentic vulnerability remediation');
await expect(bullets.nth(1)).toContainText('LLM harness skills');
await expect(bullets.nth(1)).toContainText('ServiceNow');
await expect(bullets.nth(2)).toContainText('developer landing zone');
// previous role's end date was closed out
const nimbisManager = items.nth(1).locator('.timeline-content');
await expect(nimbisManager.locator('h3')).toHaveText('DevOps Manager');
await expect(nimbisManager.locator('.timeline-date')).toHaveText('Jan 2022 – Apr 2026');
});
test('profile image loads', async ({ page }) => {
const img = page.locator('#about img');
await expect(img).toBeVisible();
await expect.poll(async () =>
page.evaluate(() => document.querySelector('#about img').naturalWidth)
).toBeGreaterThan(0);
});
test('server rack background is generated with a terminal unit', async ({ page }) => {
const rack = page.locator('.server-rack-bg .rack-container');
await expect(rack).toBeAttached();
await expect(rack.locator('.rack-unit')).not.toHaveCount(0);
await expect(page.locator('.terminal-display')).toBeAttached();
});
test('GPG section shows both fingerprints and key blocks', async ({ page }) => {
await expect(page.locator('#gpg .gpg-key-card')).toHaveCount(2);
await expect(page.locator('#gpg .gpg-key-fingerprint code').first()).toHaveText(
'7FC1 B297 0011 4F4F C589 E706 5FDD CFA5 44D7 7B8C');
await expect(page.locator('#gpg .gpg-key-fingerprint code').last()).toHaveText(
'2FF3 619F A6CA 2A4C FA2D 3532 816E 5FE7 8271 602B');
const blocks = page.locator('#gpg .gpg-key-block');
await expect(blocks).toHaveCount(2);
for (const block of [blocks.first(), blocks.last()]) {
await expect(block).toContainText('BEGIN PGP PUBLIC KEY BLOCK');
await expect(block).toContainText('END PGP PUBLIC KEY BLOCK');
}
});
test('external links all carry rel="noopener"', async ({ page }) => {
const links = page.locator('a[target="_blank"]');
const count = await links.count();
expect(count).toBeGreaterThan(0);
for (let i = 0; i < count; i++) {
await expect(links.nth(i)).toHaveAttribute('rel', /noopener/);
}
});
test('hashed assets are referenced and served', async ({ page }) => {
const html = await page.content();
expect(html).toMatch(/<link rel="stylesheet" href="style\.[0-9a-f]{32}\.css"/);
expect(html).toMatch(/<script src="terminal\.[0-9a-f]{32}\.js" defer="?">/);
expect(html).toMatch(/<script src="script\.[0-9a-f]{32}\.js" defer="?">/);
const resp = await page.request.get(page.url().replace(/\/$/, '') + '/' +
(await page.locator('link[rel="stylesheet"]').getAttribute('href')));
expect(resp.ok()).toBeTruthy();
});
});