Files
Kimi Agent 520be53901
CI / Test (Python 3.11) (push) Has been cancelled
CI / Test (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Type Check (push) Has been cancelled
CI / Summary (push) Has been cancelled
fix: migrations, auth, providers health check, E2E tests, remove neko-sama
- 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
2026-05-12 11:45:56 +00:00

82 lines
2.4 KiB
TypeScript

import { Page, expect } from '@playwright/test';
export const TEST_USER = {
username: 'e2e_testuser',
password: 'TestPassword123!',
};
/**
* Log in via the UI login form.
*/
export async function login(page: Page, username: string, password: string) {
await page.goto('/login');
await page.fill('#loginUsername', username);
await page.fill('#loginPassword', password);
const [response] = await Promise.all([
page.waitForResponse((resp) => resp.url().includes('/api/auth/login')),
page.click('#loginSubmit'),
]);
expect(response.status()).toBeLessThan(400);
// Wait for success message or redirect
await Promise.race([
page.locator('#authSuccess').waitFor({ state: 'visible', timeout: 5000 }),
page.waitForURL('**/web**', { timeout: 5000 }),
]);
}
/**
* Register a new unique user via the UI form.
*/
export async function register(page: Page, username: string, password: string) {
await page.goto('/login');
await page.click('text=Inscription');
await page.fill('#registerUsername', username);
await page.fill('#registerPassword', password);
await page.fill('#registerPasswordConfirm', password);
const [response] = await Promise.all([
page.waitForResponse((resp) => resp.url().includes('/api/auth/register')),
page.click('#registerSubmit'),
]);
expect(response.status()).toBeLessThan(400);
await page.locator('#authSuccess').waitFor({ state: 'visible', timeout: 5000 });
}
/**
* Switch to a tab by name (Accueil, Anime, Série, Watchlist, etc.)
*/
export async function switchTab(page: Page, tabName: string) {
// Wait for tabs to be rendered
await page.locator('nav#mainTabs .tab').first().waitFor({ state: 'visible', timeout: 5000 });
const tab = page.locator('nav#mainTabs .tab', { hasText: new RegExp(tabName, 'i') });
await tab.waitFor({ state: 'visible', timeout: 5000 });
await tab.click();
await expect(tab).toHaveClass(/active/);
}
/**
* Wait for HTMX content to settle (no more hx-request in flight).
*/
export async function waitForHtmx(page: Page, timeout = 10000) {
await page.waitForFunction(
() => document.querySelectorAll('.htmx-request').length === 0,
{ timeout }
);
}
/**
* Check that no unhandled JS errors occurred on the page.
*/
export function collectJsErrors(page: Page): string[] {
const errors: string[] = [];
page.on('pageerror', (err) => errors.push(err.message));
return errors;
}