import { test, expect } from '../../fixtures/multi-client'; import { LoginPage } from '../../pages/login.page'; import { RegisterPage } from '../../pages/register.page'; interface TestUser { username: string; displayName: string; password: string; } test.describe('Login returnUrl handling', () => { test.describe.configure({ timeout: 120_000 }); test('unwraps nested login returnUrl chains after successful login', async ({ createClient }) => { const client = await createClient(); const { page } = client; const suffix = uniqueName('nested-return'); const user: TestUser = { username: `user_${suffix}`, displayName: 'Return Url User', password: 'TestPass123!' }; await test.step('Create an account', async () => { const registerPage = new RegisterPage(page); await registerPage.goto(); await registerPage.register(user.username, user.displayName, user.password); await expect(page).toHaveURL(/\/dashboard/, { timeout: 15_000 }); }); await test.step('Log out and open a deeply nested login returnUrl', async () => { await logout(page); const nestedReturnUrl = '/login?returnUrl=%2Flogin%3FreturnUrl%3D%252Fservers'; await page.goto(`/login?returnUrl=${encodeURIComponent(nestedReturnUrl)}`, { waitUntil: 'domcontentloaded' }); await expect(page.locator('#login-username')).toBeVisible({ timeout: 15_000 }); }); await test.step('Login lands on the original destination instead of looping on /login', async () => { const loginPage = new LoginPage(page); await loginPage.login(user.username, user.password); await expect(page).toHaveURL(/\/servers/, { timeout: 15_000 }); await expect(page).not.toHaveURL(/returnUrl=.*login/); }); }); test('redirects unauthenticated /servers visits to login and returns there after login', async ({ createClient }) => { const client = await createClient(); const { page } = client; const suffix = uniqueName('servers-return'); const user: TestUser = { username: `user_${suffix}`, displayName: 'Servers Return User', password: 'TestPass123!' }; await test.step('Create an account and log out', async () => { const registerPage = new RegisterPage(page); await registerPage.goto(); await registerPage.register(user.username, user.displayName, user.password); await expect(page).toHaveURL(/\/dashboard/, { timeout: 15_000 }); await logout(page); }); await test.step('Visiting /servers sends the user to a single-level login returnUrl', async () => { await page.goto('/servers', { waitUntil: 'domcontentloaded' }); await expect(page).toHaveURL(/\/login/, { timeout: 15_000 }); await expect(page).toHaveURL(/returnUrl=%2Fservers/); await expect(page).not.toHaveURL(/returnUrl=.*login/); }); await test.step('Logging in returns to /servers', async () => { const loginPage = new LoginPage(page); await loginPage.login(user.username, user.password); await expect(page).toHaveURL(/\/servers/, { timeout: 15_000 }); await expect(page.locator('app-server-browser')).toBeVisible({ timeout: 15_000 }); }); }); test('lets a returning user log back in after an expired session redirect', async ({ createClient }) => { const client = await createClient(); const { page } = client; const suffix = uniqueName('expired-session'); const user: TestUser = { username: `user_${suffix}`, displayName: 'Expired Session User', password: 'TestPass123!' }; await test.step('Create an account', async () => { const registerPage = new RegisterPage(page); await registerPage.goto(); await registerPage.register(user.username, user.displayName, user.password); await expect(page).toHaveURL(/\/dashboard/, { timeout: 15_000 }); }); await test.step('Simulate an expired session while keeping the persisted user id', async () => { await page.evaluate(() => { const storageKey = 'metoyou.authTokens'; const raw = localStorage.getItem(storageKey); if (!raw) { return; } const parsed = JSON.parse(raw) as Record; const expiredStore = Object.fromEntries( Object.entries(parsed).map(([url, entry]) => [url, { ...entry, expiresAt: 0 }]) ); localStorage.setItem(storageKey, JSON.stringify(expiredStore)); }); await page.goto('/servers', { waitUntil: 'domcontentloaded' }); await expect(page).toHaveURL(/\/login/, { timeout: 15_000 }); await expect(page).toHaveURL(/returnUrl=%2Fservers/); await expect(page).not.toHaveURL(/returnUrl=.*login/); }); await test.step('The user can authenticate again and reach /servers', async () => { const loginPage = new LoginPage(page); await loginPage.login(user.username, user.password); await expect(page).toHaveURL(/\/servers/, { timeout: 15_000 }); await expect(page.locator('app-server-browser')).toBeVisible({ timeout: 15_000 }); }); }); }); async function logout(page: import('@playwright/test').Page): Promise { const menuButton = page.getByRole('button', { name: 'Menu' }); const logoutButton = page.getByRole('button', { name: 'Logout' }); await expect(menuButton).toBeVisible({ timeout: 10_000 }); await menuButton.click(); await expect(logoutButton).toBeVisible({ timeout: 10_000 }); await logoutButton.click(); await expect(page).toHaveURL(/\/login/, { timeout: 15_000 }); } function uniqueName(prefix: string): string { return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36) .slice(2, 8)}`; }