test(frontend): add real-auth Playwright e2e suite

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-04 21:55:10 +03:00
parent 0ef7728ac0
commit d2f044db07
5 changed files with 182 additions and 4 deletions

47
frontend/e2e/auth.e2e.ts Normal file
View File

@@ -0,0 +1,47 @@
import { test, expect } from '@playwright/test';
import { uniqueUser, registerAndLogin } from './helpers';
test('registering via the form creates an account', async ({ page }) => {
const user = uniqueUser();
await page.goto('/auth');
await page.getByTestId('auth-tab-register').click();
await page.locator('#username').fill(user.username);
await page.locator('#password').fill(user.password);
const register = page.waitForResponse((r) => r.url().includes('/api/auth/register'));
await page.getByTestId('auth-submit').click();
expect((await register).status()).toBe(201);
});
test('logging in via the form reaches the app', async ({ page }) => {
const user = uniqueUser();
// pre-create the account, then exercise the login form (single clean navigation)
const reg = await page.request.post('/api/auth/register', { data: user });
expect(reg.ok()).toBeTruthy();
await page.goto('/auth');
await page.locator('#username').fill(user.username);
await page.locator('#password').fill(user.password);
await page.getByTestId('auth-submit').click();
await expect(page.getByRole('button', { name: 'Import' })).toBeVisible();
});
test('login with wrong credentials shows an error', async ({ page }) => {
await page.goto('/auth');
await page.locator('#username').fill('nope_does_not_exist');
await page.locator('#password').fill('wrongpassword');
await page.getByTestId('auth-submit').click();
await expect(page.getByText('Invalid credentials')).toBeVisible();
});
test('signing out returns to the auth page', async ({ page }) => {
await registerAndLogin(page);
await page.goto('/');
await expect(page.getByRole('button', { name: 'Import' })).toBeVisible();
await page.getByRole('button', { name: 'Sign out' }).click();
await expect(page).toHaveURL(/\/auth$/);
await expect(page.getByTestId('auth-tab-login')).toBeVisible();
});

78
frontend/e2e/files.e2e.ts Normal file
View File

@@ -0,0 +1,78 @@
import { test, expect } from '@playwright/test';
import { registerAndLogin, textFile } from './helpers';
// every test runs as a fresh logged-in user, so the file list starts empty
// and tests never see each other's files (the list is per-user on the backend).
test.beforeEach(async ({ page }) => {
await registerAndLogin(page);
await page.goto('/');
});
test('a new user sees the empty state', async ({ page }) => {
await expect(page.getByText('No files yet.')).toBeVisible();
});
test('uploading a file shows it in the list', async ({ page }) => {
await page.locator('input[type="file"]').setInputFiles(textFile('hello.txt'));
await expect(page.getByText('hello.txt')).toBeVisible();
});
test('downloading a file triggers a download', async ({ page }) => {
await page.locator('input[type="file"]').setInputFiles(textFile('report.txt'));
await expect(page.getByText('report.txt')).toBeVisible();
const downloadPromise = page.waitForEvent('download');
await page.locator('[title="Download"]').first().click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toBe('report.txt');
});
test('deleting a file with confirmation removes it', async ({ page }) => {
await page.locator('input[type="file"]').setInputFiles(textFile('trash.txt'));
await expect(page.getByText('trash.txt')).toBeVisible();
await page.locator('[title="Delete"]').first().click();
await expect(page.getByText('Confirm action')).toBeVisible();
await page.locator('[title="Confirm"]').click();
await expect(page.getByText('trash.txt')).toHaveCount(0);
});
test('search filters the file list', async ({ page }) => {
const input = page.locator('input[type="file"]');
await input.setInputFiles(textFile('alpha.txt'));
await expect(page.getByText('alpha.txt')).toBeVisible();
await input.setInputFiles(textFile('bravo.txt'));
await expect(page.getByText('bravo.txt')).toBeVisible();
await page.getByPlaceholder('Search files...').fill('alpha');
await expect(page.getByText('alpha.txt')).toBeVisible();
await expect(page.getByText('bravo.txt')).toHaveCount(0);
});
test('previewing a video opens the player', async ({ page }) => {
await page.locator('input[type="file"]').setInputFiles({
name: 'clip.mp4',
mimeType: 'video/mp4',
buffer: Buffer.from('fake mp4 bytes')
});
await expect(page.getByText('clip.mp4')).toBeVisible();
await page.getByText('clip.mp4').dblclick();
await expect(page.getByRole('button', { name: 'Close' })).toBeVisible();
await expect(page.locator('video')).toHaveCount(1);
await page.keyboard.press('Escape');
await expect(page.getByRole('button', { name: 'Close' })).toHaveCount(0);
});
test('previewing a non-video shows no preview available', async ({ page }) => {
await page.locator('input[type="file"]').setInputFiles(textFile('notes.txt'));
await expect(page.getByText('notes.txt')).toBeVisible();
await page.getByText('notes.txt').dblclick();
await expect(page.getByText('No preview available')).toBeVisible();
await page.keyboard.press('Escape');
await expect(page.getByText('No preview available')).toHaveCount(0);
});

26
frontend/e2e/helpers.ts Normal file
View File

@@ -0,0 +1,26 @@
import { expect, type Page } from '@playwright/test';
/** A fresh, unique account per call so tests never collide in the shared test DB. */
export function uniqueUser() {
const id = `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
return { username: `e2e_${id}`, password: 'password123' };
}
/**
* Registers a brand-new user and logs in via the real /api/auth flow.
* `page.request` shares the browser context's cookie jar, so the session
* cookie it sets makes subsequent page navigations authenticated.
*/
export async function registerAndLogin(page: Page) {
const user = uniqueUser();
const register = await page.request.post('/api/auth/register', { data: user });
expect(register.ok(), 'register should succeed').toBeTruthy();
const login = await page.request.post('/api/auth/login', { data: user });
expect(login.ok(), 'login should succeed').toBeTruthy();
return user;
}
/** A small in-memory upload fixture (no files on disk needed). */
export function textFile(name: string) {
return { name, mimeType: 'text/plain', buffer: Buffer.from('e2e test content') };
}