feat: add comprehensive Playwright UI test suite

25 tests across auth (A1-A5), posting (P1-P5), validation (V1-V4),
tracker (T1-T5), and nav (N1-N5). Uses storageState for single login
per run. Replaces post-with-photo.spec.js with post.spec.js.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-18 22:34:11 +02:00
parent 545e3f5ba0
commit a50e7f5386
12 changed files with 567 additions and 91 deletions
+66
View File
@@ -0,0 +1,66 @@
// @ts-check
// Tests: T1T5 — tracker feed and individual entry pages
const { test, expect } = require('@playwright/test');
// Known fixture entries that always exist in the repo
const KNOWN_SLUG = '2026-03-25-1540-wheels-down-narita.entry';
const KNOWN_TITLE = 'Wheels Down at Narita';
const KNOWN_CITY = 'Tokyo';
const KNOWN_COUNTRY = 'Japan';
// Use two fixture entries with different dates to verify descending order
const NEWER_SLUG = '2026-06-17.entry'; // most recent fixture (June 17)
const OLDER_SLUG = '2026-03-25-1540-wheels-down-narita.entry'; // oldest fixture (March 25)
// ── T1: Tracker page loads ─────────────────────────────────────────────────────
test('T1: /tracker loads and shows at least one entry card', async ({ page }) => {
await page.goto('/tracker');
await expect(page.locator('.entry-card').first()).toBeVisible();
await expect(page.locator('.site-header')).toBeVisible();
});
// ── T2: Entries are newest-first ──────────────────────────────────────────────
// Verify using two known fixture entries rather than all entries
// (the tracker may contain noisy test-run debris with inconsistent dates).
test('T2: tracker shows newer entries before older entries', async ({ page }) => {
await page.goto('/tracker');
// Both fixture entries must be visible on the page
const newerCard = page.locator(`.entry-card a[href*="${NEWER_SLUG}"]`);
const olderCard = page.locator(`.entry-card a[href*="${OLDER_SLUG}"]`);
await expect(newerCard).toBeVisible();
await expect(olderCard).toBeVisible();
// The newer entry should appear higher in the DOM (lower index)
const newerIdx = await newerCard.evaluate(el => {
return [...document.querySelectorAll('.entry-card')].findIndex(c => c.contains(el));
});
const olderIdx = await olderCard.evaluate(el => {
return [...document.querySelectorAll('.entry-card')].findIndex(c => c.contains(el));
});
expect(newerIdx).toBeLessThan(olderIdx);
});
// ── T3: Individual entry page loads ───────────────────────────────────────────
test('T3: individual entry page loads at /tracker/{slug}', async ({ page }) => {
await page.goto(`/tracker/${KNOWN_SLUG}`);
await expect(page.locator('article.entry')).toBeVisible();
await expect(page.locator('.site-header')).toBeVisible();
});
// ── T4: Entry page shows title, date, and content ─────────────────────────────
test('T4: entry page shows title and body content', async ({ page }) => {
await page.goto(`/tracker/${KNOWN_SLUG}`);
await expect(page.locator('.entry-title')).toContainText(KNOWN_TITLE);
await expect(page.locator('.entry-body')).not.toBeEmpty();
await expect(page.locator('time.entry-date')).toBeVisible();
});
// ── T5: Entry page shows location when present ────────────────────────────────
test('T5: entry page shows city and country when set', async ({ page }) => {
await page.goto(`/tracker/${KNOWN_SLUG}`);
await expect(page.locator('.entry-location')).toContainText(KNOWN_CITY);
await expect(page.locator('.entry-location')).toContainText(KNOWN_COUNTRY);
});