test(frontend): add real-auth Playwright e2e suite
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
47
frontend/e2e/auth.e2e.ts
Normal file
47
frontend/e2e/auth.e2e.ts
Normal 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
78
frontend/e2e/files.e2e.ts
Normal 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
26
frontend/e2e/helpers.ts
Normal 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') };
|
||||
}
|
||||
Reference in New Issue
Block a user