104 lines
4.9 KiB
JavaScript
104 lines
4.9 KiB
JavaScript
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;
|
|
|
|
async function attachDiagnostics(testInfo, diagnostics) {
|
|
await testInfo.attach('oikos-flow-diagnostics.json', {
|
|
body: JSON.stringify(diagnostics, null, 2),
|
|
contentType: 'application/json',
|
|
});
|
|
}
|
|
|
|
|
|
async function dismissOnboardingIfPresent(page) {
|
|
const skip = page.getByRole('button', { name: /^(Skip|Spring over|Senere|Close|Luk)$/i }).first();
|
|
try {
|
|
if (await skip.isVisible({ timeout: 2_000 })) await skip.click();
|
|
} catch {
|
|
// Onboarding is optional; ignore when it is not present.
|
|
}
|
|
}
|
|
|
|
async function login(page) {
|
|
if (!username || !password) {
|
|
throw new Error('Set OIKOS_E2E_USERNAME and OIKOS_E2E_PASSWORD_FILE (preferred) or OIKOS_E2E_PASSWORD for the live Oikos E2E flow.');
|
|
}
|
|
|
|
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 expect(page.locator('#main-content')).toBeVisible();
|
|
await dismissOnboardingIfPresent(page);
|
|
}
|
|
|
|
test.describe('Oikos Kitchen + Assist meal-planning flow', () => {
|
|
test('Kitchen opens quickly, Studio is present, and Assist routes meal plans into Studio', async ({ page }, testInfo) => {
|
|
const diagnostics = {
|
|
consoleErrors: [],
|
|
pageErrors: [],
|
|
failedRequests: [],
|
|
timings: {},
|
|
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());
|
|
});
|
|
|
|
await page.addInitScript(() => localStorage.setItem('oikos-onboarded', '1'));
|
|
await login(page);
|
|
await page.evaluate(() => localStorage.setItem('oikos-onboarded', '1'));
|
|
|
|
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
|
await dismissOnboardingIfPresent(page);
|
|
const kitchenNav = page.locator('#sidebar-kitchen-nav, #kitchen-btn').filter({ visible: true }).first();
|
|
await expect(kitchenNav).toBeVisible();
|
|
|
|
const kitchenStart = Date.now();
|
|
await kitchenNav.click();
|
|
await page.waitForURL(/\/meals(?:$|[?#])/, { timeout: 15_000 });
|
|
await expect(page.locator('[data-oikos-meal-plan-studio]')).toBeVisible({ timeout: 15_000 });
|
|
await expect(page.getByRole('button', { name: /Meal Plan Studio|Generér ugeplan|Regenerér/i }).first()).toBeVisible();
|
|
diagnostics.timings.kitchenOpenMs = Date.now() - kitchenStart;
|
|
|
|
expect(diagnostics.timings.kitchenOpenMs, 'Kitchen menu should open fast enough for real use').toBeLessThan(6_000);
|
|
|
|
const studioHost = page.locator('[data-oikos-meal-plan-studio]');
|
|
await expect(studioHost).toContainText(/Meal Plan Studio/i);
|
|
|
|
const assistPrompt = 'Lav en praktisk madplan for næste uge ud fra vores opskrifter.';
|
|
const assistStart = Date.now();
|
|
await page.evaluate((prompt) => window.oikos?.openAssist?.({ prompt, expanded: true }), assistPrompt);
|
|
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 });
|
|
diagnostics.timings.assistMealPlanMs = Date.now() - assistStart;
|
|
|
|
const action = page.locator('[data-assist-kitchen-studio]').last();
|
|
await expect(action, 'Assist meal-plan response should include “Åbn forslag i Køkken”, not only text').toBeVisible({ timeout: 10_000 });
|
|
|
|
await action.click();
|
|
await page.waitForURL(/\/meals(?:$|[?#])/, { timeout: 15_000 });
|
|
await expect(studioHost.locator('[data-assist-studio-confirm]')).toBeVisible({ timeout: 15_000 });
|
|
await expect(studioHost.locator('[data-assist-studio-title]')).toHaveCount(7, { timeout: 15_000 });
|
|
await expect(studioHost).toContainText(/Madlavere & familiepræferencer|Børnenes madplan|Måltidshistorik/i);
|
|
|
|
expect(diagnostics.pageErrors, 'No browser page errors during Kitchen/Assist flow').toEqual([]);
|
|
await attachDiagnostics(testInfo, diagnostics);
|
|
});
|
|
});
|