// @ts-check const { test, expect } = require('@playwright/test'); const path = require('path'); const fs = require('fs'); const USER = process.env.GRAV_TEST_USER; const PASS = process.env.GRAV_TEST_PASS; const TRACKER_DIR = path.join(__dirname, '../../user/pages/01.tracker'); const TEST_PHOTO = path.join(__dirname, '../fixtures/test-photo.jpg'); let createdSlug = null; test.beforeAll(() => { if (!USER || !PASS) throw new Error('GRAV_TEST_USER and GRAV_TEST_PASS must be set in .env'); }); test.afterAll(() => { if (createdSlug) { const dir = path.join(TRACKER_DIR, createdSlug); if (fs.existsSync(dir)) { fs.rmSync(dir, { recursive: true }); console.log(` [cleanup] removed ${createdSlug}`); } } }); test('login, post entry with photo, verify in tracker', async ({ page }) => { // ── 1. Login ───────────────────────────────────────────────────────────── await page.goto('/login'); await page.fill('input[name="username"]', USER); await page.fill('input[name="password"]', PASS); await page.click('input[type="submit"], button[type="submit"]'); // Wait for login to complete — page leaves /login regardless of redirect target await page.waitForFunction(() => !window.location.pathname.includes('/login'), { timeout: 10_000 }); await expect(page.locator('.site-header')).toBeVisible(); // ── 2. Navigate to post form ───────────────────────────────────────────── await page.goto('/post'); await expect(page.locator('.post-form-wrap')).toBeVisible(); // ── 3. Fill in required fields ──────────────────────────────────────────── const title = `UI Test ${Date.now()}`; await page.fill('input[name="data[title]"]', title); await page.fill('textarea[name="data[content]"]', 'Automated UI test entry. Safe to delete.'); await page.fill('input[name="data[location_city]"]', 'Testville'); await page.fill('input[name="data[location_country]"]', 'Testland'); // ── 4. Upload photo via filepond ───────────────────────────────────────── const fileInput = page.locator('input[type="file"]').first(); await fileInput.setInputFiles(TEST_PHOTO); // Wait for filepond to finish the XHR upload (status indicator disappears) await page.waitForFunction(() => { const items = document.querySelectorAll('.filepond--item[data-filepond-item-state]'); return [...items].every(el => el.getAttribute('data-filepond-item-state') === 'idle'); }, { timeout: 15_000 }); // ── 5. Submit ──────────────────────────────────────────────────────────── await page.click('.btn-post'); // Wait for success message or redirect back to /post await page.waitForSelector('.form-messages, .notices', { timeout: 15_000 }); // ── 6. Verify entry created on disk ────────────────────────────────────── const todayPrefix = new Date().toISOString().slice(0, 10); // YYYY-MM-DD const entries = fs.readdirSync(TRACKER_DIR).filter(d => d.startsWith(todayPrefix)); // Find the one matching our title slug const titleSlug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); const match = entries.find(e => e.includes('ui-test')); expect(match, 'Entry folder should exist on disk').toBeTruthy(); createdSlug = match; // ── 7. Verify entry.md (or entry.en.md) contains the title ─────────────── const entryDir = path.join(TRACKER_DIR, match); const mdFile = ['entry.md', 'entry.en.md'].find(f => fs.existsSync(path.join(entryDir, f))); expect(mdFile, 'entry markdown file should exist').toBeTruthy(); const mdContent = fs.readFileSync(path.join(entryDir, mdFile), 'utf-8'); expect(mdContent).toContain('UI Test'); // ── 8. Verify photo was saved ───────────────────────────────────────────── const files = fs.readdirSync(entryDir); const photos = files.filter(f => /\.(jpg|jpeg|png|webp|heic)$/i.test(f)); expect(photos.length, 'At least one photo should be saved in the entry folder').toBeGreaterThan(0); // ── 9. Verify entry appears on /tracker ─────────────────────────────────── await page.goto('/tracker'); await expect(page.locator('body')).toContainText('UI Test'); });