Add high-priority Playwright trips coverage (#42)
All checks were successful
Build and Push Image / docker-build-and-push (push) Successful in 2m32s
All checks were successful
Build and Push Image / docker-build-and-push (push) Successful in 2m32s
Co-authored-by: Shaun Campbell <shaun@campbellwireless.net> Reviewed-on: #42 Co-authored-by: AI Agent <ai-agent@campbellwireless.net> Co-committed-by: AI Agent <ai-agent@campbellwireless.net>
This commit was merged in pull request #42.
This commit is contained in:
73
e2e/admin-auth-source.test.ts
Normal file
73
e2e/admin-auth-source.test.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { test, expect, type Locator, type Page } from '@playwright/test';
|
||||||
|
import { TEST_USERS } from './setup/test-users.js';
|
||||||
|
|
||||||
|
const LOGIN_URL = '/trips/login';
|
||||||
|
const ADMIN_USERS_URL = '/trips/admin/users';
|
||||||
|
|
||||||
|
async function loginAsLocalUser(page: Page, username: string, password: string): Promise<void> {
|
||||||
|
await page.goto(LOGIN_URL);
|
||||||
|
await page.fill('input[name="identifier"]', username);
|
||||||
|
await page.fill('input[name="password"]', password);
|
||||||
|
await page.click('button[type="submit"]:has-text("Sign in locally")');
|
||||||
|
await page.waitForURL('**/trips/dashboard', { timeout: 15_000 });
|
||||||
|
}
|
||||||
|
|
||||||
|
function rowForUser(page: Page, username: string): Locator {
|
||||||
|
return page.getByRole('cell', { name: username }).locator('..');
|
||||||
|
}
|
||||||
|
|
||||||
|
test.beforeEach(async ({ context }) => {
|
||||||
|
await context.clearCookies();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('admin sees auth source and can set local password', async ({ page }) => {
|
||||||
|
const suffix = Date.now();
|
||||||
|
const username = `e2e_auth_source_${suffix}`;
|
||||||
|
const fullName = `E2E Auth Source ${suffix}`;
|
||||||
|
const email = `e2e_auth_source_${suffix}@test.local`;
|
||||||
|
const password = `InitPass-${suffix}-123`;
|
||||||
|
|
||||||
|
await loginAsLocalUser(page, TEST_USERS.admin.username, TEST_USERS.admin.password);
|
||||||
|
await page.goto(ADMIN_USERS_URL);
|
||||||
|
await expect(page.getByRole('heading', { name: 'Users' })).toBeVisible();
|
||||||
|
|
||||||
|
const regularRow = rowForUser(page, TEST_USERS.regular.username);
|
||||||
|
await expect(regularRow.getByText('Local', { exact: true })).toBeVisible();
|
||||||
|
|
||||||
|
const adminRow = rowForUser(page, TEST_USERS.admin.username);
|
||||||
|
await expect(adminRow.getByText('Local', { exact: true })).toBeVisible();
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Add User' }).click();
|
||||||
|
const addDialog = page.getByRole('dialog', { name: 'Add user' });
|
||||||
|
await expect(addDialog).toBeVisible();
|
||||||
|
await addDialog.locator('input[type="text"]').nth(0).fill(username);
|
||||||
|
await addDialog.locator('input[type="text"]').nth(1).fill(fullName);
|
||||||
|
await addDialog.locator('input[type="email"]').fill(email);
|
||||||
|
await addDialog.locator('input[type="password"]').nth(0).fill(password);
|
||||||
|
await addDialog.locator('input[type="password"]').nth(1).fill(password);
|
||||||
|
await addDialog.getByRole('button', { name: 'Create user' }).click();
|
||||||
|
|
||||||
|
const disposableUserRow = rowForUser(page, username);
|
||||||
|
await expect(disposableUserRow.getByText('Local', { exact: true })).toBeVisible();
|
||||||
|
await disposableUserRow.getByRole('button', { name: 'Set local password' }).click();
|
||||||
|
const dialog = page.getByRole('dialog', { name: 'Set local password' });
|
||||||
|
await expect(dialog).toBeVisible();
|
||||||
|
|
||||||
|
await dialog.getByRole('button', { name: 'Save password' }).click();
|
||||||
|
await expect(dialog.getByText('Password is required')).toBeVisible();
|
||||||
|
|
||||||
|
await dialog.locator('input[type="password"]').nth(0).fill('short');
|
||||||
|
await dialog.locator('input[type="password"]').nth(1).fill('short');
|
||||||
|
await dialog.getByRole('button', { name: 'Save password' }).click();
|
||||||
|
await expect(dialog.getByText('Password must be at least 12 characters')).toBeVisible();
|
||||||
|
|
||||||
|
await dialog.locator('input[type="password"]').nth(0).fill('long-enough-password');
|
||||||
|
await dialog.locator('input[type="password"]').nth(1).fill('long-enough-password-mismatch');
|
||||||
|
await dialog.getByRole('button', { name: 'Save password' }).click();
|
||||||
|
await expect(dialog.getByText('Passwords do not match')).toBeVisible();
|
||||||
|
|
||||||
|
await dialog.locator('input[type="password"]').nth(0).fill('local-password-123');
|
||||||
|
await dialog.locator('input[type="password"]').nth(1).fill('local-password-123');
|
||||||
|
await dialog.getByRole('button', { name: 'Save password' }).click();
|
||||||
|
await expect(dialog).toBeHidden();
|
||||||
|
});
|
||||||
49
e2e/admin-local-user-flow.test.ts
Normal file
49
e2e/admin-local-user-flow.test.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { test, expect, type Page } from '@playwright/test';
|
||||||
|
import { TEST_USERS } from './setup/test-users.js';
|
||||||
|
|
||||||
|
const LOGIN_URL = '/trips/login';
|
||||||
|
const ADMIN_USERS_URL = '/trips/admin/users';
|
||||||
|
const DASHBOARD_URL = '/trips/dashboard';
|
||||||
|
|
||||||
|
async function loginAsLocalUser(page: Page, username: string, password: string): Promise<void> {
|
||||||
|
await page.goto(LOGIN_URL);
|
||||||
|
await page.fill('input[name="identifier"]', username);
|
||||||
|
await page.fill('input[name="password"]', password);
|
||||||
|
await page.click('button[type="submit"]:has-text("Sign in locally")');
|
||||||
|
await page.waitForURL(`**${DASHBOARD_URL}`, { timeout: 15_000 });
|
||||||
|
}
|
||||||
|
|
||||||
|
test.beforeEach(async ({ context }) => {
|
||||||
|
await context.clearCookies();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('admin can create a local user who can sign in', async ({ page }) => {
|
||||||
|
const suffix = Date.now();
|
||||||
|
const username = `e2e_new_user_${suffix}`;
|
||||||
|
const fullName = `E2E New User ${suffix}`;
|
||||||
|
const email = `e2e_new_user_${suffix}@test.local`;
|
||||||
|
const password = `TempPass-${suffix}-123`;
|
||||||
|
|
||||||
|
await loginAsLocalUser(page, TEST_USERS.admin.username, TEST_USERS.admin.password);
|
||||||
|
await page.goto(ADMIN_USERS_URL);
|
||||||
|
await expect(page.getByRole('heading', { name: 'Users' })).toBeVisible();
|
||||||
|
await page.getByRole('button', { name: 'Add User' }).click();
|
||||||
|
|
||||||
|
const addDialog = page.getByRole('dialog', { name: 'Add user' });
|
||||||
|
await expect(addDialog).toBeVisible();
|
||||||
|
await addDialog.locator('input[type="text"]').nth(0).fill(username);
|
||||||
|
await addDialog.locator('input[type="text"]').nth(1).fill(fullName);
|
||||||
|
await addDialog.locator('input[type="email"]').fill(email);
|
||||||
|
await addDialog.locator('input[type="password"]').nth(0).fill(password);
|
||||||
|
await addDialog.locator('input[type="password"]').nth(1).fill(password);
|
||||||
|
await addDialog.getByRole('button', { name: 'Create user' }).click();
|
||||||
|
|
||||||
|
await expect(page.getByRole('cell', { name: username, exact: true })).toBeVisible();
|
||||||
|
|
||||||
|
await page.context().clearCookies();
|
||||||
|
await loginAsLocalUser(page, username, password);
|
||||||
|
await expect(page).toHaveURL(/\/trips\/dashboard/);
|
||||||
|
|
||||||
|
await page.goto(ADMIN_USERS_URL);
|
||||||
|
await expect(page).toHaveURL(/\/trips\/dashboard/);
|
||||||
|
});
|
||||||
25
e2e/auth-callback.test.ts
Normal file
25
e2e/auth-callback.test.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { test, expect, type Page } from '@playwright/test';
|
||||||
|
import { TEST_USERS } from './setup/test-users.js';
|
||||||
|
|
||||||
|
const LOGIN_URL = '/trips/login';
|
||||||
|
const UPCOMING_URL = '/trips/trips/upcoming';
|
||||||
|
|
||||||
|
async function submitLocalLogin(page: Page, username: string, password: string): Promise<void> {
|
||||||
|
await page.fill('input[name="identifier"]', username);
|
||||||
|
await page.fill('input[name="password"]', password);
|
||||||
|
await page.click('button[type="submit"]:has-text("Sign in locally")');
|
||||||
|
}
|
||||||
|
|
||||||
|
test.beforeEach(async ({ context }) => {
|
||||||
|
await context.clearCookies();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('redirects back to protected route after local login', async ({ page }) => {
|
||||||
|
await page.goto(UPCOMING_URL);
|
||||||
|
await expect(page).toHaveURL(/\/trips\/login/);
|
||||||
|
|
||||||
|
await submitLocalLogin(page, TEST_USERS.regular.username, TEST_USERS.regular.password);
|
||||||
|
await page.waitForURL(/\/trips\/(trips\/upcoming|dashboard)/, { timeout: 15_000 });
|
||||||
|
await expect(page).not.toHaveURL(/\/trips\/auth/);
|
||||||
|
await expect(page).not.toHaveURL(/\/trips\/login/);
|
||||||
|
});
|
||||||
@@ -150,7 +150,7 @@ test.beforeEach(async ({ context }) => {
|
|||||||
await context.clearCookies();
|
await context.clearCookies();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('regular user can plan a detailed future trip and see it in upcoming', async ({ page }) => {
|
test('regular user can plan a detailed future trip with no end date', async ({ page }) => {
|
||||||
const startDate = formatDate(30);
|
const startDate = formatDate(30);
|
||||||
const tripName = `E2E Future Trip ${Date.now()}`;
|
const tripName = `E2E Future Trip ${Date.now()}`;
|
||||||
const tripDescription = 'Future trip created in Playwright e2e scenario';
|
const tripDescription = 'Future trip created in Playwright e2e scenario';
|
||||||
|
|||||||
Reference in New Issue
Block a user