Files
m038 a50e7f5386 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>
2026-06-18 22:34:11 +02:00

63 lines
3.2 KiB
JavaScript
Raw Permalink 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
// Tests: A1A5 — authentication and access control
// A1/A2/A3 run WITHOUT storageState to test the login page itself.
// A4 uses a fresh context without storageState to verify access control.
// A5 uses storageState (already logged in).
const { test, expect } = require('@playwright/test');
const USER = process.env.GRAV_TEST_USER;
const PASS = process.env.GRAV_TEST_PASS;
// ── A1A3: require a clean session (no prior auth) ────────────────────────────
test.describe('login page (unauthenticated)', () => {
test.use({ storageState: { cookies: [], origins: [] } });
// A1: Login page loads
test('A1: login page renders the login form', async ({ page }) => {
await page.goto('/login');
await expect(page.locator('input[name="username"]')).toBeVisible();
await expect(page.locator('input[name="password"]')).toBeVisible();
await expect(page.locator('button[type="submit"], input[type="submit"]')).toBeVisible();
});
// A2: Invalid credentials
test('A2: invalid credentials show an error message', async ({ page }) => {
await page.goto('/login');
await page.fill('input[name="username"]', 'wronguser');
await page.fill('input[name="password"]', 'wrongpass');
await page.click('button[type="submit"], input[type="submit"]');
// Grav stays on the login page and shows a flash error
await expect(page.locator('body')).toContainText(/invalid|incorrect|failed/i, { timeout: 8_000 });
await expect(page).toHaveURL(/login/);
});
// A3: Valid login
test('A3: valid credentials show success message', async ({ page }) => {
if (!USER || !PASS) test.skip(true, 'GRAV_TEST_USER / GRAV_TEST_PASS not set');
await page.goto('/login');
await page.fill('input[name="username"]', USER);
await page.fill('input[name="password"]', PASS);
await page.click('button[type="submit"], input[type="submit"]');
await expect(page.locator('body')).toContainText('successfully logged in', { timeout: 10_000 });
});
});
// ── A4: /post without auth shows a login form ─────────────────────────────────
// Uses a fresh context with no storageState.
test('A4: /post without auth renders an inline login form', async ({ browser }) => {
const ctx = await browser.newContext({ storageState: { cookies: [], origins: [] } });
const page = await ctx.newPage();
const baseURL = process.env.GRAV_BASE_URL || 'http://localhost:8081';
await page.goto(`${baseURL}/post`);
// Grav renders a login form inline at the /post URL (section#grav-login)
await expect(page.locator('#grav-login')).toBeVisible({ timeout: 8_000 });
await ctx.close();
});
// ── A5: /post with auth shows the post form ───────────────────────────────────
test('A5: /post with auth renders the post form', async ({ page }) => {
await page.goto('/post');
await expect(page.locator('.post-form-wrap')).toBeVisible();
await expect(page.locator('input[name="data[title]"]')).toBeVisible();
});