test: add randomized oikos everyday flows
This commit is contained in:
@@ -0,0 +1,349 @@
|
|||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
|
||||||
|
const username = process.env.OIKOS_E2E_USERNAME;
|
||||||
|
const password = process.env.OIKOS_E2E_PASSWORD_FILE
|
||||||
|
? fs.readFileSync(process.env.OIKOS_E2E_PASSWORD_FILE, 'utf8').trim()
|
||||||
|
: process.env.OIKOS_E2E_PASSWORD;
|
||||||
|
const mutate = process.env.OIKOS_E2E_MUTATE === '1';
|
||||||
|
|
||||||
|
function seededRandom(seedText) {
|
||||||
|
let seed = 0;
|
||||||
|
for (const char of seedText) seed = (seed * 31 + char.charCodeAt(0)) >>> 0;
|
||||||
|
return () => {
|
||||||
|
seed = (seed * 1664525 + 1013904223) >>> 0;
|
||||||
|
return seed / 0x100000000;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function pick(rand, values) { return values[Math.floor(rand() * values.length)]; }
|
||||||
|
function stamp(testInfo) { return `E2E-${Date.now().toString(36)}-${testInfo.workerIndex}-${Math.floor(Math.random() * 1e6).toString(36)}`; }
|
||||||
|
function futureDate(days) { const d = new Date(); d.setDate(d.getDate() + days); return d.toISOString().slice(0, 10); }
|
||||||
|
|
||||||
|
async function attachDiagnostics(testInfo, diagnostics) {
|
||||||
|
await testInfo.attach('everyday-flow-diagnostics.json', {
|
||||||
|
body: JSON.stringify(diagnostics, null, 2),
|
||||||
|
contentType: 'application/json',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeDiagnostics(page) {
|
||||||
|
const diagnostics = { consoleErrors: [], pageErrors: [], failedRequests: [], urls: [] };
|
||||||
|
page.on('console', (msg) => { if (['error', 'warning'].includes(msg.type())) diagnostics.consoleErrors.push(`${msg.type()}: ${msg.text()}`); });
|
||||||
|
page.on('pageerror', (err) => diagnostics.pageErrors.push(err.stack || err.message));
|
||||||
|
page.on('requestfailed', (req) => diagnostics.failedRequests.push(`${req.method()} ${req.url()} :: ${req.failure()?.errorText || 'failed'}`));
|
||||||
|
page.on('framenavigated', (frame) => { if (frame === page.mainFrame()) diagnostics.urls.push(page.url()); });
|
||||||
|
return diagnostics;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function gotoApp(page, route) {
|
||||||
|
try {
|
||||||
|
await page.goto(route, { waitUntil: 'domcontentloaded' });
|
||||||
|
} catch (err) {
|
||||||
|
if (!String(err.message || '').includes('ERR_ABORTED')) throw err;
|
||||||
|
await page.waitForLoadState('domcontentloaded').catch(() => {});
|
||||||
|
if (!page.url().includes(route.replace(/^\//, ''))) {
|
||||||
|
await page.goto(route, { waitUntil: 'domcontentloaded' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function dismissOnboardingIfPresent(page) {
|
||||||
|
const skip = page.getByRole('button', { name: /^(Skip|Spring over|Senere|Close|Luk)$/i }).first();
|
||||||
|
try { if (await skip.isVisible({ timeout: 1_000 })) await skip.click(); } catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function login(page) {
|
||||||
|
if (!username || !password) throw new Error('Set OIKOS_E2E_USERNAME and OIKOS_E2E_PASSWORD_FILE or OIKOS_E2E_PASSWORD.');
|
||||||
|
await page.addInitScript(() => localStorage.setItem('oikos-onboarded', '1'));
|
||||||
|
await page.goto('/login', { waitUntil: 'domcontentloaded' });
|
||||||
|
await page.locator('#username').fill(username);
|
||||||
|
await page.locator('#password').fill(password);
|
||||||
|
const loginResponsePromise = page.waitForResponse((res) => res.url().includes('/api/v1/auth/login'));
|
||||||
|
await page.locator('#login-btn').click();
|
||||||
|
const loginResponse = await loginResponsePromise;
|
||||||
|
if (!loginResponse.ok()) throw new Error(`Login failed with HTTP ${loginResponse.status()}`);
|
||||||
|
await page.waitForURL((url) => !url.pathname.includes('/login'), { timeout: 20_000 });
|
||||||
|
await dismissOnboardingIfPresent(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function api(page, path, options = {}) {
|
||||||
|
if (page.isClosed()) throw new Error(`Cannot call API on closed page: ${path}`);
|
||||||
|
if (page.url() === 'about:blank') await page.goto('/', { waitUntil: 'domcontentloaded' });
|
||||||
|
try {
|
||||||
|
return await page.evaluate(async ({ path, options }) => {
|
||||||
|
const csrf = document.cookie.split(';').map((c) => c.trim()).find((c) => c.startsWith('csrf-token='))?.slice('csrf-token='.length) || '';
|
||||||
|
const res = await fetch(`/api/v1${path}`, {
|
||||||
|
credentials: 'same-origin',
|
||||||
|
cache: 'no-store',
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(options.method && options.method !== 'GET' ? { 'X-CSRF-Token': decodeURIComponent(csrf) } : {}),
|
||||||
|
...(options.headers || {}),
|
||||||
|
},
|
||||||
|
body: options.body && typeof options.body !== 'string' ? JSON.stringify(options.body) : options.body,
|
||||||
|
});
|
||||||
|
const data = await res.json().catch(() => null);
|
||||||
|
return { ok: res.ok, status: res.status, data };
|
||||||
|
}, { path, options });
|
||||||
|
} catch (err) {
|
||||||
|
if (!String(err.message || '').includes('Execution context was destroyed')) throw err;
|
||||||
|
await page.waitForLoadState('domcontentloaded').catch(() => {});
|
||||||
|
return page.evaluate(async ({ path, options }) => {
|
||||||
|
const csrf = document.cookie.split(';').map((c) => c.trim()).find((c) => c.startsWith('csrf-token='))?.slice('csrf-token='.length) || '';
|
||||||
|
const res = await fetch(`/api/v1${path}`, {
|
||||||
|
credentials: 'same-origin', cache: 'no-store', ...options,
|
||||||
|
headers: { 'Content-Type': 'application/json', ...(options.method && options.method !== 'GET' ? { 'X-CSRF-Token': decodeURIComponent(csrf) } : {}), ...(options.headers || {}) },
|
||||||
|
body: options.body && typeof options.body !== 'string' ? JSON.stringify(options.body) : options.body,
|
||||||
|
});
|
||||||
|
const data = await res.json().catch(() => null);
|
||||||
|
return { ok: res.ok, status: res.status, data };
|
||||||
|
}, { path, options });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cleanupTaggedData(page, tag) {
|
||||||
|
const results = {};
|
||||||
|
const safeDelete = async (label, listPath, extract, deletePath) => {
|
||||||
|
try {
|
||||||
|
const listed = await api(page, listPath);
|
||||||
|
const rows = extract(listed.data).filter((item) => JSON.stringify(item).includes(tag));
|
||||||
|
results[label] = [];
|
||||||
|
for (const row of rows) {
|
||||||
|
const del = await api(page, deletePath(row), { method: 'DELETE' });
|
||||||
|
results[label].push({ id: row.id, ok: del.ok, status: del.status });
|
||||||
|
}
|
||||||
|
} catch (err) { results[label] = [{ error: err.message }]; }
|
||||||
|
};
|
||||||
|
|
||||||
|
await safeDelete('tasks', '/tasks?status=open', (d) => d?.data || [], (row) => `/tasks/${row.id}`);
|
||||||
|
await safeDelete('tasksDone', '/tasks?status=done', (d) => d?.data || [], (row) => `/tasks/${row.id}`);
|
||||||
|
await safeDelete('notes', '/notes', (d) => d?.data || [], (row) => `/notes/${row.id}`);
|
||||||
|
await safeDelete('contacts', '/contacts', (d) => d?.data || [], (row) => `/contacts/${row.id}`);
|
||||||
|
await safeDelete('events', `/calendar?start=${futureDate(-7)}&end=${futureDate(45)}`, (d) => d?.data || [], (row) => `/calendar/${row.id}`);
|
||||||
|
await safeDelete('shoppingLists', '/shopping', (d) => d?.data || [], (row) => `/shopping/${row.id}`);
|
||||||
|
|
||||||
|
const meals = await api(page, `/meals?week=${futureDate(0)}`);
|
||||||
|
results.meals = [];
|
||||||
|
for (const meal of (meals.data?.data || []).filter((item) => JSON.stringify(item).includes(tag))) {
|
||||||
|
const del = await api(page, `/meals/${meal.id}`, { method: 'DELETE' });
|
||||||
|
results.meals.push({ id: meal.id, ok: del.ok, status: del.status });
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function requireMutate() {
|
||||||
|
test.skip(!mutate, 'Set OIKOS_E2E_MUTATE=1 to run randomized everyday write flows.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const everydayUseCases = [
|
||||||
|
'Anonymous family member opens protected app and gets routed to login',
|
||||||
|
'Parent checks dashboard/household sections on desktop and mobile widths',
|
||||||
|
'Parent creates, completes, and cleans up a practical chore task',
|
||||||
|
'Family builds a dedicated grocery list with random staples and checks one item',
|
||||||
|
'Meal planned for a week is converted into a shopping list',
|
||||||
|
'Parent pins a sticky note, searches it, and deletes it',
|
||||||
|
'Parent adds a calendar appointment with random time/location and deletes it',
|
||||||
|
'Parent adds a contact/service provider and finds it via search',
|
||||||
|
'Kitchen/Assist generates a meal-plan draft and exposes Studio actions',
|
||||||
|
'Cross-module search finds freshly created household data',
|
||||||
|
];
|
||||||
|
|
||||||
|
test.describe('Oikos everyday randomized use-case matrix', () => {
|
||||||
|
test('documents the 10 everyday use cases under test', async ({}, testInfo) => {
|
||||||
|
await testInfo.attach('everyday-use-cases.json', {
|
||||||
|
body: JSON.stringify(everydayUseCases.map((flow, index) => ({ id: index + 1, flow })), null, 2),
|
||||||
|
contentType: 'application/json',
|
||||||
|
});
|
||||||
|
expect(everydayUseCases).toHaveLength(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('01 anonymous protected routes show login cleanly', async ({ page }, testInfo) => {
|
||||||
|
const diagnostics = makeDiagnostics(page);
|
||||||
|
for (const route of ['/', '/tasks', '/shopping', '/meals', '/calendar', '/notes']) {
|
||||||
|
await page.context().clearCookies();
|
||||||
|
await gotoApp(page, route);
|
||||||
|
await expect(page.locator('#login-form')).toBeVisible({ timeout: 10_000 });
|
||||||
|
}
|
||||||
|
await attachDiagnostics(testInfo, diagnostics);
|
||||||
|
expect(diagnostics.pageErrors).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('02 everyday navigation is usable on desktop and mobile', async ({ page }, testInfo) => {
|
||||||
|
const diagnostics = makeDiagnostics(page);
|
||||||
|
await login(page);
|
||||||
|
for (const size of [{ width: 1366, height: 900 }, { width: 390, height: 844 }]) {
|
||||||
|
await page.setViewportSize(size);
|
||||||
|
for (const route of ['/', '/tasks', '/shopping', '/meals', '/calendar', '/notes', '/contacts']) {
|
||||||
|
await gotoApp(page, route);
|
||||||
|
await dismissOnboardingIfPresent(page);
|
||||||
|
await expect(page.locator('#main-content')).toBeVisible({ timeout: 15_000 });
|
||||||
|
const overflow = await page.evaluate(() => ({ width: window.innerWidth, scrollWidth: document.documentElement.scrollWidth }));
|
||||||
|
expect(overflow.scrollWidth, `${route} overflowed at ${size.width}px`).toBeLessThanOrEqual(overflow.width + 24);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await attachDiagnostics(testInfo, diagnostics);
|
||||||
|
expect(diagnostics.pageErrors).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('03 task chore can be created, completed, and cleaned up', async ({ page }, testInfo) => {
|
||||||
|
await requireMutate();
|
||||||
|
const diagnostics = makeDiagnostics(page);
|
||||||
|
const rand = seededRandom(testInfo.title);
|
||||||
|
const tag = stamp(testInfo);
|
||||||
|
const title = `${pick(rand, ['Tøm opvaskemaskine', 'Pak skoletasker', 'Vand planter'])} ${tag}`;
|
||||||
|
await login(page);
|
||||||
|
try {
|
||||||
|
const created = await api(page, '/tasks', { method: 'POST', body: { title, description: `Random household chore ${tag}`, priority: pick(rand, ['low', 'medium', 'high']), category: pick(rand, ['household', 'school', 'shopping']), due_date: futureDate(1), status: 'open' } });
|
||||||
|
expect(created.ok, JSON.stringify(created)).toBeTruthy();
|
||||||
|
await gotoApp(page, '/tasks');
|
||||||
|
await expect(page.locator('.task-card', { hasText: title })).toBeVisible();
|
||||||
|
const done = await api(page, `/tasks/${created.data.data.id}/status`, { method: 'PATCH', body: { status: 'done' } });
|
||||||
|
expect(done.ok, JSON.stringify(done)).toBeTruthy();
|
||||||
|
await gotoApp(page, '/tasks');
|
||||||
|
expect((await api(page, `/tasks/${created.data.data.id}`)).data?.data?.status).toBe('done');
|
||||||
|
} finally { diagnostics.cleanup = await cleanupTaggedData(page, tag); }
|
||||||
|
await attachDiagnostics(testInfo, diagnostics);
|
||||||
|
expect(diagnostics.pageErrors).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('04 grocery list supports random staples, checking, and cleanup', async ({ page }, testInfo) => {
|
||||||
|
await requireMutate();
|
||||||
|
const diagnostics = makeDiagnostics(page);
|
||||||
|
const rand = seededRandom(testInfo.title + Date.now());
|
||||||
|
const tag = stamp(testInfo);
|
||||||
|
await login(page);
|
||||||
|
try {
|
||||||
|
const categories = (await api(page, '/shopping/categories')).data?.data || [];
|
||||||
|
const category = categories[0]?.name || 'Sonstiges';
|
||||||
|
const listName = `Weekend indkøb ${tag}`;
|
||||||
|
const list = await api(page, '/shopping', { method: 'POST', body: { name: listName } });
|
||||||
|
expect(list.ok, JSON.stringify(list)).toBeTruthy();
|
||||||
|
const items = ['mælk', 'rugbrød', 'bananer', 'havregryn', 'kylling', 'agurk'].sort(() => rand() - 0.5).slice(0, 4);
|
||||||
|
for (const name of items) {
|
||||||
|
const item = await api(page, `/shopping/${list.data.data.id}/items`, { method: 'POST', body: { name: `${name} ${tag}`, quantity: `${1 + Math.floor(rand() * 4)} stk`, category } });
|
||||||
|
expect(item.ok, JSON.stringify(item)).toBeTruthy();
|
||||||
|
}
|
||||||
|
const listItems = await api(page, `/shopping/${list.data.data.id}/items`);
|
||||||
|
expect(listItems.data.data).toHaveLength(items.length);
|
||||||
|
const first = listItems.data.data[0];
|
||||||
|
const checked = await api(page, `/shopping/items/${first.id}`, { method: 'PATCH', body: { is_checked: 1 } });
|
||||||
|
expect(checked.ok, JSON.stringify(checked)).toBeTruthy();
|
||||||
|
await gotoApp(page, '/shopping');
|
||||||
|
await expect(page.locator('body')).toContainText(listName);
|
||||||
|
} finally { diagnostics.cleanup = await cleanupTaggedData(page, tag); }
|
||||||
|
await attachDiagnostics(testInfo, diagnostics);
|
||||||
|
expect(diagnostics.pageErrors).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('05 week meal ingredients convert to a shopping list', async ({ page }, testInfo) => {
|
||||||
|
await requireMutate();
|
||||||
|
const diagnostics = makeDiagnostics(page);
|
||||||
|
const tag = stamp(testInfo);
|
||||||
|
await login(page);
|
||||||
|
try {
|
||||||
|
const categories = (await api(page, '/shopping/categories')).data?.data || [];
|
||||||
|
const category = categories[0]?.name || 'Sonstiges';
|
||||||
|
const list = await api(page, '/shopping', { method: 'POST', body: { name: `Madplan indkøb ${tag}` } });
|
||||||
|
expect(list.ok, JSON.stringify(list)).toBeTruthy();
|
||||||
|
const meal = await api(page, '/meals', { method: 'POST', body: { title: `Pasta testmiddag ${tag}`, date: futureDate(2), meal_type: 'dinner', meal_category: 'pasta', ingredients: [{ name: `pasta ${tag}`, quantity: '500 g', category }, { name: `tomatsauce ${tag}`, quantity: '1 glas', category }] } });
|
||||||
|
expect(meal.ok, JSON.stringify(meal)).toBeTruthy();
|
||||||
|
const converted = await api(page, '/meals/week-to-shopping-list', { method: 'POST', body: { week: futureDate(2), listId: list.data.data.id } });
|
||||||
|
expect(converted.ok, JSON.stringify(converted)).toBeTruthy();
|
||||||
|
expect(converted.data.data.transferred).toBeGreaterThanOrEqual(2);
|
||||||
|
await gotoApp(page, '/shopping');
|
||||||
|
await expect(page.locator('body')).toContainText(tag);
|
||||||
|
} finally { diagnostics.cleanup = await cleanupTaggedData(page, tag); }
|
||||||
|
await attachDiagnostics(testInfo, diagnostics);
|
||||||
|
expect(diagnostics.pageErrors).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('06 sticky note can be pinned, searched, and cleaned up', async ({ page }, testInfo) => {
|
||||||
|
await requireMutate();
|
||||||
|
const diagnostics = makeDiagnostics(page);
|
||||||
|
const rand = seededRandom(testInfo.title);
|
||||||
|
const tag = stamp(testInfo);
|
||||||
|
const title = `Husk ${pick(rand, ['bibliotek', 'idrætstøj', 'madpakker'])} ${tag}`;
|
||||||
|
await login(page);
|
||||||
|
try {
|
||||||
|
const note = await api(page, '/notes', { method: 'POST', body: { title, content: `Random note content ${tag}`, color: '#FFEB3B', pinned: 1 } });
|
||||||
|
expect(note.ok, JSON.stringify(note)).toBeTruthy();
|
||||||
|
await gotoApp(page, '/notes');
|
||||||
|
await page.locator('#notes-search').fill(tag);
|
||||||
|
await expect(page.locator('.note-card', { hasText: tag })).toBeVisible();
|
||||||
|
} finally { diagnostics.cleanup = await cleanupTaggedData(page, tag); }
|
||||||
|
await attachDiagnostics(testInfo, diagnostics);
|
||||||
|
expect(diagnostics.pageErrors).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('07 calendar appointment can be added and found', async ({ page }, testInfo) => {
|
||||||
|
await requireMutate();
|
||||||
|
const diagnostics = makeDiagnostics(page);
|
||||||
|
const rand = seededRandom(testInfo.title);
|
||||||
|
const tag = stamp(testInfo);
|
||||||
|
const title = `${pick(rand, ['Tandlæge', 'Skole-hjem', 'Fodbold'])} ${tag}`;
|
||||||
|
await login(page);
|
||||||
|
try {
|
||||||
|
const event = await api(page, '/calendar', { method: 'POST', body: { title, start_datetime: `${futureDate(3)}T15:00:00`, end_datetime: `${futureDate(3)}T16:00:00`, all_day: 0, location: `Lokation ${tag}`, description: `Random appointment ${tag}`, color: '#007AFF' } });
|
||||||
|
expect(event.ok, JSON.stringify(event)).toBeTruthy();
|
||||||
|
const upcoming = await api(page, '/calendar/upcoming');
|
||||||
|
expect(JSON.stringify(upcoming.data)).toContain(tag);
|
||||||
|
await gotoApp(page, '/calendar');
|
||||||
|
await expect(page.locator('body')).toContainText(tag);
|
||||||
|
} finally { diagnostics.cleanup = await cleanupTaggedData(page, tag); }
|
||||||
|
await attachDiagnostics(testInfo, diagnostics);
|
||||||
|
expect(diagnostics.pageErrors).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('08 contact/service provider can be added and searched', async ({ page }, testInfo) => {
|
||||||
|
await requireMutate();
|
||||||
|
const diagnostics = makeDiagnostics(page);
|
||||||
|
const tag = stamp(testInfo);
|
||||||
|
await login(page);
|
||||||
|
try {
|
||||||
|
const contact = await api(page, '/contacts', { method: 'POST', body: { name: `VVS Kontakt ${tag}`, category: 'Handwerker', phone: '+4512345678', email: `e2e-${tag.toLowerCase()}@example.invalid`, address: 'Testvej 1', notes: `Random provider ${tag}` } });
|
||||||
|
expect(contact.ok, JSON.stringify(contact)).toBeTruthy();
|
||||||
|
await gotoApp(page, '/contacts');
|
||||||
|
await page.locator('#contacts-search').fill(tag);
|
||||||
|
await expect(page.locator('.contact-item', { hasText: tag })).toBeVisible();
|
||||||
|
} finally { diagnostics.cleanup = await cleanupTaggedData(page, tag); }
|
||||||
|
await attachDiagnostics(testInfo, diagnostics);
|
||||||
|
expect(diagnostics.pageErrors).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('09 Assist meal-plan draft opens Studio with actionable controls', async ({ page }, testInfo) => {
|
||||||
|
const diagnostics = makeDiagnostics(page);
|
||||||
|
await login(page);
|
||||||
|
await page.waitForFunction(() => window.oikos?.openAssist, { timeout: 15_000 });
|
||||||
|
await page.evaluate(() => window.oikos.openAssist({ prompt: 'Lav en praktisk madplan for næste uge ud fra vores opskrifter.', expanded: true }));
|
||||||
|
await expect(page.locator('#oikos-assist-root .assist-panel--open')).toBeVisible();
|
||||||
|
await expect(page.locator('#oikos-assist-root .assist-message--ai').last()).not.toContainText('Tænker…', { timeout: 60_000 });
|
||||||
|
const action = page.locator('[data-assist-kitchen-studio]').last();
|
||||||
|
await expect(action).toHaveCount(1, { timeout: 10_000 });
|
||||||
|
await expect(action).toContainText(/Åbn forslag|Køkken|Studio/i);
|
||||||
|
await page.evaluate(() => document.querySelector('[data-assist-kitchen-studio]')?.click());
|
||||||
|
await expect(page).toHaveURL(/\/meals(?:$|[#?])/, { timeout: 15_000 });
|
||||||
|
await expect(page.locator('[data-oikos-meal-plan-studio]')).toBeVisible({ timeout: 15_000 });
|
||||||
|
await expect(page.locator('[data-assist-studio-title]')).toHaveCount(7, { timeout: 15_000 });
|
||||||
|
await attachDiagnostics(testInfo, diagnostics);
|
||||||
|
expect(diagnostics.pageErrors).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('10 global search finds freshly created household data', async ({ page }, testInfo) => {
|
||||||
|
await requireMutate();
|
||||||
|
const diagnostics = makeDiagnostics(page);
|
||||||
|
const tag = stamp(testInfo);
|
||||||
|
await login(page);
|
||||||
|
try {
|
||||||
|
const task = await api(page, '/tasks', { method: 'POST', body: { title: `Findbar opgave ${tag}`, category: 'household', priority: 'medium', status: 'open' } });
|
||||||
|
const note = await api(page, '/notes', { method: 'POST', body: { title: `Findbar note ${tag}`, content: `Søgbar note ${tag}`, color: '#A5D6A7', pinned: 0 } });
|
||||||
|
expect(task.ok && note.ok, JSON.stringify({ task, note })).toBeTruthy();
|
||||||
|
const search = await api(page, `/search?q=${encodeURIComponent(tag)}`);
|
||||||
|
expect(search.ok, JSON.stringify(search)).toBeTruthy();
|
||||||
|
expect(JSON.stringify(search.data)).toContain(tag);
|
||||||
|
} finally { diagnostics.cleanup = await cleanupTaggedData(page, tag); }
|
||||||
|
await attachDiagnostics(testInfo, diagnostics);
|
||||||
|
expect(diagnostics.pageErrors).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user