520be53901
- Add proper Alembic initial migration (0001_initial_schema.py) - Migrate refresh tokens from JSON file to SQLite (RefreshTokenTable) - Remove Neko-Sama provider entirely (redirects to Gupy, not a host) - Fix provider health check always showing UNKNOWN - Run check_all_health() on startup - Fix POST /providers/health/check background task bug - Add HTMX refresh after manual health check trigger - Fix anime search relevance scoring with MIN_RELEVANCE_THRESHOLD=0.5 - Replace bare 'except:' with 'except Exception:' across codebase - Add Playwright E2E test suite (12 tests, auth setup, helpers) - Fix toast container blocking clicks via pointer-events: none - Remove obsolete Jest/Vite test files and config - Clean up obsolete test_watchlist scripts - Update sonarr model comment for active providers
103 lines
3.4 KiB
TypeScript
103 lines
3.4 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { switchTab, waitForHtmx, collectJsErrors } from './helpers';
|
|
|
|
/**
|
|
* User Journey E2E Tests
|
|
*
|
|
* Tests authenticated user flows. Auth is handled by auth.setup.ts + storageState.
|
|
*/
|
|
test.describe('User Journey E2E', () => {
|
|
test('should browse homepage without JS errors', async ({ page }) => {
|
|
const jsErrors = collectJsErrors(page);
|
|
await page.goto('/web');
|
|
|
|
// Main content should be visible
|
|
await expect(page.locator('#main-content')).toBeVisible();
|
|
await expect(page.locator('header h1')).toContainText('Ohm Stream');
|
|
|
|
// At least one tab visible
|
|
await expect(page.locator('.tab').first()).toBeVisible();
|
|
|
|
// Authenticated user info should be visible
|
|
await expect(page.locator('#userInfo')).toBeVisible();
|
|
|
|
expect(jsErrors).toHaveLength(0);
|
|
});
|
|
|
|
test('should search for anime', async ({ page }) => {
|
|
// Mock the anime search API to return deterministic HTML
|
|
await page.route('/api/anime/search?**', async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'text/html',
|
|
body: `
|
|
<div class="sr-card">
|
|
<h3>Naruto Shippuden</h3>
|
|
<p>Anime-Sama</p>
|
|
</div>
|
|
<div class="sr-card">
|
|
<h3>Boruto: Naruto Next Generations</h3>
|
|
<p>Neko-Sama</p>
|
|
</div>
|
|
`,
|
|
});
|
|
});
|
|
|
|
await page.goto('/web');
|
|
await switchTab(page, 'Anime');
|
|
await page.locator('#tab-anime').waitFor({ state: 'visible', timeout: 5000 });
|
|
|
|
await page.fill('#animeSearchInput', 'Naruto');
|
|
|
|
// Click search button to trigger submit
|
|
await page.click('#tab-anime button[type="submit"]');
|
|
|
|
// Wait for results to appear
|
|
await page.locator('#animeSearchResults .sr-card').first().waitFor({ state: 'visible', timeout: 10000 });
|
|
|
|
// Results container should be visible and contain mocked data
|
|
await expect(page.locator('#animeSearchResults')).toBeVisible();
|
|
await expect(page.locator('#animeSearchResults')).toContainText('Naruto Shippuden');
|
|
});
|
|
|
|
test('should update settings', async ({ page }) => {
|
|
await page.goto('/web');
|
|
await switchTab(page, 'Paramètres');
|
|
|
|
// Wait for settings form loaded via HTMX
|
|
await page.locator('#default_lang').waitFor({ state: 'visible', timeout: 15000 });
|
|
|
|
await page.selectOption('#default_lang', 'vf');
|
|
|
|
const [response] = await Promise.all([
|
|
page.waitForResponse(
|
|
(resp) => resp.url().includes('/api/settings') && resp.request().method() === 'PATCH'
|
|
),
|
|
page.locator('button:has-text("Enregistrer les preferences")').click(),
|
|
]);
|
|
|
|
expect(response.status()).toBe(200);
|
|
|
|
// Verify the setting was updated in the UI
|
|
await expect(page.locator('#default_lang')).toHaveValue('vf');
|
|
});
|
|
|
|
test('should logout successfully', async ({ page }) => {
|
|
await page.goto('/web');
|
|
|
|
const [response] = await Promise.all([
|
|
page.waitForResponse((resp) => resp.url().includes('/api/auth/logout')),
|
|
page.locator('#userInfo button:has-text("Déconnexion")').click(),
|
|
]);
|
|
|
|
expect(response.status()).toBeLessThan(400);
|
|
|
|
// Should redirect to login
|
|
await page.waitForURL('**/login**', { timeout: 10000 });
|
|
|
|
// The auth token must be cleared from localStorage
|
|
const token = await page.evaluate(() => localStorage.getItem('auth_token'));
|
|
expect(token).toBeNull();
|
|
});
|
|
});
|