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:
@@ -0,0 +1,65 @@
|
||||
// @ts-check
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const TRACKER_DIR = path.join(__dirname, '../../user/pages/01.tracker');
|
||||
|
||||
/**
|
||||
* Wait for all filepond items to finish XHR upload.
|
||||
*/
|
||||
async function waitForFilePondUpload(page) {
|
||||
await page.waitForFunction(() => {
|
||||
const items = document.querySelectorAll('.filepond--item[data-filepond-item-state]');
|
||||
return items.length > 0 && [...items].every(
|
||||
el => el.getAttribute('data-filepond-item-state') === 'processing-complete'
|
||||
);
|
||||
}, { timeout: 20_000 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit the post form with minimal required fields and return the unique title marker.
|
||||
* Caller is responsible for cleanup via cleanupEntry().
|
||||
*/
|
||||
async function postEntry(page, { titleTag, content = 'Automated test. Safe to delete.', city = '', country = '' } = {}) {
|
||||
const title = `UI Test ${titleTag} ${Date.now()}`;
|
||||
await page.goto('/post');
|
||||
await page.fill('input[name="data[title]"]', title);
|
||||
await page.fill('textarea[name="data[content]"]', content);
|
||||
if (city) await page.fill('input[name="data[location_city]"]', city);
|
||||
if (country) await page.fill('input[name="data[location_country]"]', country);
|
||||
await page.locator('.btn-post').evaluate(el => el.click());
|
||||
await page.waitForSelector('.form-messages, .notices', { timeout: 15_000 });
|
||||
return titleTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a tracker entry folder by a unique slug fragment, then delete it.
|
||||
*/
|
||||
function cleanupEntry(slugFragment) {
|
||||
if (!slugFragment) return;
|
||||
const entries = fs.readdirSync(TRACKER_DIR);
|
||||
const match = entries.find(e => e.includes(slugFragment));
|
||||
if (match) {
|
||||
fs.rmSync(path.join(TRACKER_DIR, match), { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first entry folder matching a slug fragment and return its full path.
|
||||
*/
|
||||
function findEntry(slugFragment) {
|
||||
const entries = fs.readdirSync(TRACKER_DIR);
|
||||
const match = entries.find(e => e.includes(slugFragment));
|
||||
return match ? path.join(TRACKER_DIR, match) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the entry .md file (entry.md or entry.en.md) from an entry folder.
|
||||
*/
|
||||
function readEntryMd(entryDir) {
|
||||
const name = ['entry.md', 'entry.en.md'].find(f => fs.existsSync(path.join(entryDir, f)));
|
||||
if (!name) return null;
|
||||
return fs.readFileSync(path.join(entryDir, name), 'utf-8');
|
||||
}
|
||||
|
||||
module.exports = { waitForFilePondUpload, postEntry, cleanupEntry, findEntry, readEntryMd, TRACKER_DIR };
|
||||
Reference in New Issue
Block a user