From 1ef5deeb9841e81ccba0e432a5a596db8c0fe831 Mon Sep 17 00:00:00 2001 From: AI Agent Date: Sun, 22 Feb 2026 21:27:41 +0000 Subject: [PATCH 1/2] e2e) add admin auth source and login flows --- e2e/admin-auth-source.test.ts | 55 +++++++++++++++++++++++++++++ e2e/admin-local-user-flow.test.ts | 58 +++++++++++++++++++++++++++++++ e2e/auth-callback.test.ts | 25 +++++++++++++ e2e/trip-planning.test.ts | 2 +- 4 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 e2e/admin-auth-source.test.ts create mode 100644 e2e/admin-local-user-flow.test.ts create mode 100644 e2e/auth-callback.test.ts diff --git a/e2e/admin-auth-source.test.ts b/e2e/admin-auth-source.test.ts new file mode 100644 index 0000000..af37ffd --- /dev/null +++ b/e2e/admin-auth-source.test.ts @@ -0,0 +1,55 @@ +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 { + 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 }) => { + 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 regularRow.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(); +}); diff --git a/e2e/admin-local-user-flow.test.ts b/e2e/admin-local-user-flow.test.ts new file mode 100644 index 0000000..163ff92 --- /dev/null +++ b/e2e/admin-local-user-flow.test.ts @@ -0,0 +1,58 @@ +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 { + 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 }); +} + +async function clearSessionState(page: Page): Promise { + await page.context().clearCookies(); + await page.goto('about:blank'); + await page.evaluate(() => { + localStorage.clear(); + sessionStorage.clear(); + }); +} + +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 })).toBeVisible(); + + await clearSessionState(page); + await loginAsLocalUser(page, username, password); + await expect(page).toHaveURL(/\/trips\/dashboard/); + + await page.goto(ADMIN_USERS_URL); + await expect(page).toHaveURL(/\/trips\/dashboard/); +}); diff --git a/e2e/auth-callback.test.ts b/e2e/auth-callback.test.ts new file mode 100644 index 0000000..1be5b81 --- /dev/null +++ b/e2e/auth-callback.test.ts @@ -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/upcoming'; + +async function submitLocalLogin(page: Page, username: string, password: string): Promise { + 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\/(upcoming|dashboard)/, { timeout: 15_000 }); + await expect(page).not.toHaveURL(/\/trips\/auth/); + await expect(page).not.toHaveURL(/\/trips\/login/); +}); diff --git a/e2e/trip-planning.test.ts b/e2e/trip-planning.test.ts index bc2f09f..8cafcef 100644 --- a/e2e/trip-planning.test.ts +++ b/e2e/trip-planning.test.ts @@ -150,7 +150,7 @@ test.beforeEach(async ({ context }) => { 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 tripName = `E2E Future Trip ${Date.now()}`; const tripDescription = 'Future trip created in Playwright e2e scenario'; -- 2.49.1 From b54dd351fcef96537570ac3cdae64b67e8afd791 Mon Sep 17 00:00:00 2001 From: Shaun Campbell Date: Sun, 22 Feb 2026 17:27:22 -0500 Subject: [PATCH 2/2] test) fixing tests --- e2e/admin-auth-source.test.ts | 20 +++++++++++++++++++- e2e/admin-local-user-flow.test.ts | 13 ++----------- e2e/auth-callback.test.ts | 4 ++-- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/e2e/admin-auth-source.test.ts b/e2e/admin-auth-source.test.ts index af37ffd..cf9ed95 100644 --- a/e2e/admin-auth-source.test.ts +++ b/e2e/admin-auth-source.test.ts @@ -21,6 +21,12 @@ test.beforeEach(async ({ context }) => { }); 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(); @@ -31,7 +37,19 @@ test('admin sees auth source and can set local password', async ({ page }) => { const adminRow = rowForUser(page, TEST_USERS.admin.username); await expect(adminRow.getByText('Local', { exact: true })).toBeVisible(); - await regularRow.getByRole('button', { name: 'Set local password' }).click(); + 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(); diff --git a/e2e/admin-local-user-flow.test.ts b/e2e/admin-local-user-flow.test.ts index 163ff92..3c7fd8b 100644 --- a/e2e/admin-local-user-flow.test.ts +++ b/e2e/admin-local-user-flow.test.ts @@ -13,15 +13,6 @@ async function loginAsLocalUser(page: Page, username: string, password: string): await page.waitForURL(`**${DASHBOARD_URL}`, { timeout: 15_000 }); } -async function clearSessionState(page: Page): Promise { - await page.context().clearCookies(); - await page.goto('about:blank'); - await page.evaluate(() => { - localStorage.clear(); - sessionStorage.clear(); - }); -} - test.beforeEach(async ({ context }) => { await context.clearCookies(); }); @@ -47,9 +38,9 @@ test('admin can create a local user who can sign in', async ({ page }) => { 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 })).toBeVisible(); + await expect(page.getByRole('cell', { name: username, exact: true })).toBeVisible(); - await clearSessionState(page); + await page.context().clearCookies(); await loginAsLocalUser(page, username, password); await expect(page).toHaveURL(/\/trips\/dashboard/); diff --git a/e2e/auth-callback.test.ts b/e2e/auth-callback.test.ts index 1be5b81..87302f8 100644 --- a/e2e/auth-callback.test.ts +++ b/e2e/auth-callback.test.ts @@ -2,7 +2,7 @@ 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/upcoming'; +const UPCOMING_URL = '/trips/trips/upcoming'; async function submitLocalLogin(page: Page, username: string, password: string): Promise { await page.fill('input[name="identifier"]', username); @@ -19,7 +19,7 @@ test('redirects back to protected route after local login', async ({ page }) => await expect(page).toHaveURL(/\/trips\/login/); await submitLocalLogin(page, TEST_USERS.regular.username, TEST_USERS.regular.password); - await page.waitForURL(/\/trips\/(upcoming|dashboard)/, { timeout: 15_000 }); + await page.waitForURL(/\/trips\/(trips\/upcoming|dashboard)/, { timeout: 15_000 }); await expect(page).not.toHaveURL(/\/trips\/auth/); await expect(page).not.toHaveURL(/\/trips\/login/); }); -- 2.49.1