Files
homepage/tests/e2e/01-page.spec.js
T
ducoterra f5f6d2322a unify experience bullets to past tense and reorder project cards
All timeline bullets now open with past-tense action verbs (Huntington
gerunds become Built/Created/Led; stray 'Lead a high-performing team'
becomes 'Led'). The Local AI Stack card moves to second position,
right after Brain of Reese; the test-locked grid test is updated in
the same change.

LLM-Generated: true
2026-10-23 13:28:33 -04:00

126 lines
6.3 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('featured projects show the six updated cards in order', async ({ page }) => {
const cards = page.locator('#projects .project-card');
await expect(cards).toHaveCount(6);
const titles = await cards.locator('.project-info h3').allTextContents();
expect(titles).toEqual([
'Brain of Reese',
'Local AI Stack',
'Reverse Proxy Infrastructure',
'Dynamic DNS Service',
'Deployment Automation',
'ServWaf',
]);
// Brain of Reese leads, with its source link
const brain = cards.first();
await expect(brain.locator('.project-tech span')).toHaveCount(4);
await expect(brain).toHaveText(/RAG chatbot/i);
await expect(brain.locator('a[href="https://gitea.reeseapps.com/Experimental/brain-of-reese"]')).toBeAttached();
// Local AI Stack: second card, Ollama replaced by vLLM, Gufo, llama.cpp
const ai = cards.nth(1);
await expect(ai.locator('.project-tech span')).toHaveText(['vLLM', 'Gufo', 'llama.cpp', 'LocalAI', 'LiteLLM', 'Langfuse']);
await expect(ai).not.toContainText('Ollama');
// ServWaf is closed source — description only, no source link
await expect(cards.last()).toHaveText(/Web Application Firewall/i);
await expect(cards.last().locator('.project-links')).not.toBeAttached();
// removed cards are gone from the grid
expect(titles).not.toContain('Self-Hosted Services');
expect(titles).not.toContain('Kubernetes Cluster');
});
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();
});
});