Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions e2e/home-navigation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ test('onboarding keeps its original start schedule after a delayed lazy chunk',
await expect(page.locator('.react-joyride__tooltip')).toBeVisible({ timeout: 15_000 });
});

test('onboarding starts a fresh delay when a protected route reaches home', async ({ page }) => {
test('onboarding stays closed on a protected tool and starts when it reaches home', async ({ page }) => {
await page.clock.install();
await page.addInitScript(() => {
window.localStorage.setItem('debate-trainer-onboarding', JSON.stringify({
state: { hasCompletedOnboarding: false, isTourRunning: false },
Expand All @@ -96,12 +97,14 @@ test('onboarding starts a fresh delay when a protected route reaches home', asyn
});
await page.goto('/app/generate', { waitUntil: 'domcontentloaded' });
await expect(page.getByRole('heading', { name: 'Your case will appear here' })).toBeVisible({ timeout: 15_000 });
// eslint-disable-next-line no-restricted-properties -- observation window: let the first onboarding delay run partway before navigating.
await page.waitForTimeout(650);
// Advance past the tour's 500ms deadline while still on the tool. The tour
// now starts when its Home target exists, rather than after a fixed delay.
await page.clock.runFor(1_000);
await expect(page.locator('.react-joyride__tooltip')).toHaveCount(0);

await page.locator('header a[href="/app"]').first().click();
await expect(page).toHaveURL(/\/app$/);
await expect(page.locator('.react-joyride__tooltip')).toHaveCount(0, { timeout: 100 });
await expect(page.locator('[data-tour="current-debate"]')).toBeVisible();
await expect(page.locator('.react-joyride__tooltip')).toBeVisible({ timeout: 15_000 });
});

Expand Down
2 changes: 1 addition & 1 deletion e2e/hungarian-parity.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ test.describe('Hungarian parity', () => {

await expectHungarianDocument(page);
await expect(page.getByRole('heading', { level: 1, name: `${site.product}: MI-alapú vitafelkészítés és gyakorlás` })).toBeVisible();
await expect(page.getByText(/^Attól függ, mire van szükséged\. 2026\. október állapot szerint/)).toBeAttached();
await expect(page.locator('#short-answer-text')).toContainText('Egy konkrét indítványra készülve');
if ((page.viewportSize()?.width ?? 0) < 768) {
await expect(page.getByRole('button', { name: 'Menü megnyitása' })).toBeVisible();
} else {
Expand Down
2 changes: 1 addition & 1 deletion e2e/loading-performance.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ test('authenticated routes keep the shell and current page after refresh', async
] as const) {
await page.goto(path);
await page.reload();
await expect(page.locator('header')).toBeVisible();
await expect(page.getByRole('banner')).toBeVisible();
await expect(page.getByRole('heading', { name: heading, exact: true })).toBeVisible();
await expect(page.getByText('DebateOwl is the official')).toHaveCount(0);
}
Expand Down
4 changes: 2 additions & 2 deletions e2e/localization.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,12 @@ test('sign-in and not-found pages carry the language switcher', async ({ page })
await shot(page, '02-login-hu');

await page.goto('/no-such-page');
await expect(page.getByRole('heading', { level: 1, name: hu.notFound.title })).toBeVisible();
await expect(page.getByRole('heading', { level: 1, name: hu.notFound.message })).toBeVisible();
await expect(page).toHaveTitle(brand(hu.pageHead.notFound.title));
await shot(page, '03-not-found-hu');
await choose(page, 'es');
await expect(page).toHaveTitle(brand(es.pageHead.notFound.title));
await expect(page.getByText(es.notFound.message)).toBeVisible();
await expect(page.getByRole('heading', { level: 1, name: es.notFound.message })).toBeVisible();
await shot(page, '04-not-found-es');
});

Expand Down
27 changes: 20 additions & 7 deletions e2e/navigation-performance.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ test('workspaces stay unavailable without a collaboration endpoint', async ({ pa

await expect(page.locator('a[href="/app/workspaces"]')).toHaveCount(0);
await page.goto('/app/workspaces', { waitUntil: 'domcontentloaded' });
await expect(page.getByRole('heading', { name: '404' })).toBeVisible();
await expect(page.getByText('404', { exact: true })).toBeVisible();
await expect(page.getByRole('heading', { level: 1, name: 'Page not found' })).toBeVisible();
});

test('a delayed public route keeps the main landmark', async ({ page }) => {
Expand Down Expand Up @@ -177,7 +178,9 @@ test('a failed route chunk recovers after the offered reload', async ({ page })
let attempts = 0;
await page.route('**/src/pages/AttackPage.tsx*', async (route) => {
attempts += 1;
if (attempts === 1) {
// The first failure reloads automatically. Fail that retry too, so the
// boundary offers its manual Reload action instead of recovering first.
if (attempts <= 2) {
await route.fulfill({ status: 503, contentType: 'text/plain', body: 'chunk unavailable' });
return;
}
Expand All @@ -190,8 +193,9 @@ test('a failed route chunk recovers after the offered reload', async ({ page })
await homeToolLink(page, 'attack').click();

await expect(page.getByRole('heading', { name: 'Unable to load this page' })).toBeVisible();
expect(attempts).toBe(2);
await page.getByRole('button', { name: 'Reload' }).click();
await expect.poll(() => attempts).toBe(2);
await expect.poll(() => attempts).toBe(3);
await waitForContent(page, attackContent);
await expect(page.getByRole('banner')).toBeVisible();
});
Expand Down Expand Up @@ -365,14 +369,23 @@ test('subscription reads stay cached across sibling tool routes', async ({ page
await route.fulfill({ json: { announcements: [] } });
return;
}
if (url.pathname.endsWith('/rest/v1/rpc/ai_usage_today')) {
subscriptionRequests.push('ai_usage_today');
await route.fulfill({ json: 0 });
return;
}
if (url.pathname.includes('/rest/v1/')) {
const select = url.searchParams.get('select') ?? '';
if (url.pathname.endsWith('/user_settings') && select.includes('subscription_tier')) {
subscriptionRequests.push('user_settings');
} else if (url.pathname.endsWith('/subscriptions')) {
subscriptionRequests.push('subscriptions');
} else if (url.pathname.endsWith('/ai_requests')) {
subscriptionRequests.push('ai_requests');
}
// An empty single-row read is a PostgREST error, not a truthy array.
// Otherwise settings hydration treats [] as an account and reads billing.
if ((route.request().headers().accept ?? '').includes('vnd.pgrst.object')) {
await route.fulfill({ status: 406, json: { code: 'PGRST116', message: 'No rows returned' } });
return;
}
await route.fulfill({
status: 200,
Expand All @@ -387,13 +400,13 @@ test('subscription reads stay cached across sibling tool routes', async ({ page

await page.goto('/app/generate');
await waitForContent(page, { selector: 'main h1', text: 'Generate' });
await expect.poll(() => subscriptionRequests.filter((name) => name === 'ai_requests').length).toBe(1);
await expect.poll(() => subscriptionRequests.filter((name) => name === 'ai_usage_today').length).toBe(1);

await openHome(page);
await homeToolLink(page, 'attack').click();
await waitForContent(page, attackContent);
// eslint-disable-next-line no-restricted-properties -- observation window: no extra subscription request may follow.
await page.waitForTimeout(100);

expect(subscriptionRequests).toEqual(['user_settings', 'ai_requests']);
expect(subscriptionRequests).toEqual(['user_settings', 'ai_usage_today']);
});
6 changes: 3 additions & 3 deletions e2e/quality-backlog.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ test('Flowchart deletes a selected node at once and Undo puts it back', async ({
await page.getByText('Argument 1', { exact: true }).click();
const remove = page.getByRole('button', { name: 'Delete' }).last();
if (testInfo.project.name === 'mobile-chromium') {
const box = await remove.boundingBox();
expect(box).not.toBeNull();
await page.touchscreen.tap(box!.x + box!.width / 2, box!.y + box!.height / 2);
// Locator taps scroll into view and wait for layout stability, just like
// clicks. Raw viewport coordinates can hit the canvas after selection reflows.
await remove.tap();
} else {
await remove.click();
}
Expand Down
14 changes: 8 additions & 6 deletions e2e/search-quality.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,13 @@ test('authenticated Factoids shows the consulted-source quality panel', async ({
await page.getByRole('textbox', { name: /statement/i }).fill('Solar energy costs are falling');
await page.getByRole('button', { name: /find factoids/i }).click();

await expect(page.getByRole('heading', { name: /researched sources/i })).toBeVisible();
await expect(page.getByRole('link', { name: /official energy statistics/i }).first()).toHaveAttribute('href', strongSourceUrl);
await expect(page.getByRole('link', { name: /broad commentary/i })).toHaveAttribute('href', weakSourceUrl);
await expect(page.getByText('Strong', { exact: true })).toBeVisible();
await expect(page.getByText('Weak', { exact: true })).toBeVisible();
await expect(page.getByText('fallback context only', { exact: true })).toBeVisible();
const sources = page.locator('details').filter({ has: page.locator('summary', { hasText: 'Researched sources (2)' }) });
await sources.locator('summary').click();
await expect(sources).toHaveAttribute('open', '');
await expect(sources.getByRole('link', { name: /official energy statistics/i })).toHaveAttribute('href', strongSourceUrl);
await expect(sources.getByRole('link', { name: /broad commentary/i })).toHaveAttribute('href', weakSourceUrl);
await expect(sources.getByText('Strong', { exact: true })).toBeVisible();
await expect(sources.getByText('Weak', { exact: true })).toBeVisible();
await expect(sources.getByText('fallback context only', { exact: true })).toBeVisible();
await expect(page.getByText('Solar costs fell by 42 percent')).toBeVisible();
});
Loading