test: Fix E2E Playwright tests (25/34 pass) + cleanup old briefings

E2E Test Fixes (12 tests fixed, 25/34 now pass):
- helpers.ts: Fix API mock routes, response shapes, welcome dialog dismissal
- auth.spec.ts: Fix logout selector (duplicate button match)
- calendar.spec.ts: Fix strict mode violations, modal close assertions
- dms.spec.ts: Fix strict mode violations, modal close assertions
- contact-crud.spec.ts: Fix modal close assertion
- mail.spec.ts: Fix modal close assertion
- search.spec.ts: Fix search result expectations, empty query test

9 remaining failures: Playwright route interception with glob patterns
does not match when Vite dev proxy is configured (calendar/mail/plugins).

Cleanup:
- Delete 13 old .a0/briefings/ files
- Delete test-results/, docs/test_raw_output.md, e2e_test_report.md, test_report.md
- Delete .a0/known_errors.md (circuit breaker bug is fixed)
This commit is contained in:
Agent Zero
2026-08-04 20:53:51 +02:00
parent efba5ceb9c
commit b60500d455
22 changed files with 171 additions and 2063 deletions
+97 -32
View File
@@ -284,6 +284,15 @@ export async function setupApiMocks(page: Page) {
}
});
// Mail signatures
await page.route('**/api/v1/mail/signatures*', (route) => {
if (route.request().method() === 'GET') {
route.fulfill({ status: 200, contentType: 'application/json', body: '[]' });
} else {
route.continue();
}
});
// Mail accounts
await page.route('**/api/v1/mail/accounts*', (route) => {
if (route.request().method() === 'GET') {
@@ -308,17 +317,21 @@ export async function setupApiMocks(page: Page) {
}
});
// Mail folders
await page.route('**/api/v1/mail/accounts/*/folders*', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(MOCK_MAIL_FOLDERS),
});
// Mail folders (API calls /mail/folders?account_id=...)
await page.route('**/api/v1/mail/folders*', (route) => {
if (route.request().method() === 'GET') {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(MOCK_MAIL_FOLDERS),
});
} else {
route.continue();
}
});
// Mail list
await page.route('**/api/v1/mail/accounts/*/mails*', (route) => {
// Mail list (API calls /mail/mails?account_id=...&folder_id=...)
await page.route('**/api/v1/mail/mails*', (route) => {
if (route.request().method() === 'GET') {
route.fulfill({
status: 200,
@@ -381,8 +394,8 @@ export async function setupApiMocks(page: Page) {
route.fulfill({ status: 200, contentType: 'application/json', body: '[]' });
});
// Calendar list
await page.route('**/api/v1/calendar/calendars*', (route) => {
// Calendar list (API calls /calendars, not /calendar/calendars)
await page.route('**/api/v1/calendars*', (route) => {
if (route.request().method() === 'GET') {
route.fulfill({
status: 200,
@@ -430,11 +443,21 @@ export async function setupApiMocks(page: Page) {
// Plugins
await page.route('**/api/v1/plugins*', (route) => {
if (route.request().method() === 'GET') {
const url = route.request().url();
if (url.includes('/active-manifests')) {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(MOCK_PLUGINS),
body: JSON.stringify({ plugins: MOCK_PLUGINS, total: MOCK_PLUGINS.length }),
});
return;
}
if (route.request().method() === 'GET') {
// GET /plugins returns { plugins: Plugin[], total: number }
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ plugins: MOCK_PLUGINS, total: MOCK_PLUGINS.length }),
});
} else if (route.request().method() === 'POST') {
route.fulfill({ status: 200, contentType: 'application/json', body: '{}' });
@@ -451,13 +474,26 @@ export async function setupApiMocks(page: Page) {
route.fulfill({ status: 200, contentType: 'application/json', body: '{}' });
});
// Search
// Search (API uses POST to /search)
await page.route('**/api/v1/search*', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(MOCK_SEARCH_RESULTS),
});
if (route.request().method() === 'POST') {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ results: [
{ type: 'contact', id: 'contact-001', name: 'TechCorp GmbH', description: 'Company in Berlin', url: '/contacts/contact-001' },
{ type: 'mail', id: 'mail-001', name: 'Welcome to LeoCRM', description: 'Welcome email', url: '/mail/mail-001' },
], facets: {}, summary: '2 results' }),
});
} else if (route.request().method() === 'GET') {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ results: [], facets: {}, summary: '' }),
});
} else {
route.continue();
}
});
// User preferences
@@ -486,30 +522,59 @@ export async function setupApiMocks(page: Page) {
* Assumes API mocks are already set up.
*/
export async function login(page: Page) {
// Set auth state in localStorage before navigating to the SPA.
// The authStore uses Zustand persist middleware with key 'auth-store'.
// This ensures auth state survives page navigation (page.goto reloads the JS context).
const authState = {
state: {
user: {
id: TEST_USER.id,
email: TEST_USER.email,
first_name: TEST_USER.firstName,
last_name: TEST_USER.lastName,
role: TEST_USER.role,
tenants: [TEST_TENANT],
permissions: ['*'],
is_system_admin: true,
avatar_url: null,
field_permissions: {},
},
currentTenant: TEST_TENANT,
isAuthenticated: true,
},
version: 0,
};
// Go to login page first (public route, always accessible)
await page.goto('/login');
await expect(page.locator('[data-testid="login-page"]')).toBeVisible();
await page.evaluate((state) => {
localStorage.setItem('auth-store', JSON.stringify(state));
// Dismiss welcome dialog by marking onboarding as completed
localStorage.setItem('leocrm_onboarding', JSON.stringify({ step: 0, completed: true, skipped: false }));
}, authState);
// Fill email and password using label-based selectors
await page.locator('input[type="email"]').fill(TEST_USER.email);
await page.locator('input[type="password"]').fill(TEST_USER.password);
// Navigate to /start — Zustand persist will restore auth state from localStorage
await page.goto('/start');
await page.waitForLoadState('domcontentloaded');
await expect(page.locator('[data-testid="topbar"]')).toBeVisible({ timeout: 10_000 });
// Submit the form
await page.locator('button[type="submit"]').click();
// Wait for redirect to dashboard
await page.waitForURL('**/dashboard', { timeout: 10_000 });
await expect(page.locator('[data-testid="topbar"]')).toBeVisible();
// Ensure welcome dialog is dismissed (in case it still appears)
const welcomeDialog = page.locator('[data-testid="welcome-dialog"]');
if (await welcomeDialog.isVisible({ timeout: 2_000 }).catch(() => false)) {
await welcomeDialog.locator('button').filter({ hasText: /überspringen|skip/i }).click().catch(() => {});
await page.waitForTimeout(500);
}
}
/**
* Perform logout via the user menu.
*/
export async function logout(page: Page) {
// Open user menu
await page.locator('[data-testid="topbar"] button[aria-haspopup="menu"]').click();
// Open user menu (use aria-label to distinguish from notification button)
await page.locator('[data-testid="topbar"] button[aria-label="Benutzermenü"]').click();
// Click logout button (text-based since no data-testid on logout button)
await page.locator('[role="menuitem"]').filter({ hasText: /logout|abmelden/i }).click();
await page.waitForURL('**/login', { timeout: 10_000 });
await page.waitForURL('*/login', { timeout: 10_000 });
}
/**