1
0
forked from baron/baron-sso

e2e 구조변경

This commit is contained in:
Lectom C Han
2026-02-24 15:23:36 +09:00
parent 3fdcaa5832
commit 4ffe5110dd
46 changed files with 2735 additions and 393 deletions

View File

@@ -0,0 +1,143 @@
import { expect, test, type Page, type Route } from '@playwright/test';
type MockOptions = {
sessionStatus?: number;
captureApprove?: (pendingRef: string | null) => void;
};
async function seedTokenLogin(page: Page): Promise<void> {
await page.addInitScript(() => {
window.localStorage.setItem('baron_auth_token', 'e30.e30.e30');
window.localStorage.setItem('baron_auth_provider', 'ory');
window.localStorage.removeItem('baron_auth_cookie_mode');
window.localStorage.removeItem('baron_auth_pending_provider');
});
}
async function mockUserfrontApis(
page: Page,
options: MockOptions = {},
): Promise<void> {
const sessionStatus = options.sessionStatus ?? 200;
await page.route('**/api/v1/**', async (route: Route) => {
const requestUrl = new URL(route.request().url());
const path = requestUrl.pathname;
if (path.endsWith('/api/v1/user/me')) {
if (sessionStatus === 200) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
id: 'e2e-user',
email: 'e2e@example.com',
name: 'E2E User',
phone: '+821012341234',
department: 'QA',
affiliationType: 'employee',
companyCode: 'BARON',
tenant: {
id: 'tenant-1',
name: 'Baron',
slug: 'baron',
description: 'E2E tenant',
},
}),
});
return;
}
await route.fulfill({
status: sessionStatus,
contentType: 'application/json',
body: JSON.stringify({ error: 'unauthorized' }),
});
return;
}
if (path.endsWith('/api/v1/user/rp/linked')) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ items: [] }),
});
return;
}
if (path.endsWith('/api/v1/audit/auth/timeline')) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ items: [], next_cursor: '' }),
});
return;
}
if (path.endsWith('/api/v1/auth/qr/approve')) {
const body = route.request().postDataJSON() as { pendingRef?: string };
options.captureApprove?.(body.pendingRef ?? null);
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ ok: true }),
});
return;
}
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({}),
});
});
}
test.describe('UserFront WASM auth routing', () => {
test('비로그인 /ko 진입 시 /ko/signin 으로 리다이렉트된다', async ({ page }) => {
await mockUserfrontApis(page, { sessionStatus: 401 });
await page.goto('/ko');
await expect(page).toHaveURL(/\/ko\/signin(?:\?.*)?$/);
});
test('로그인 상태 /ko 진입 후 새로고침해도 /ko/dashboard 를 유지한다', async ({
page,
}) => {
await seedTokenLogin(page);
await mockUserfrontApis(page);
await page.goto('/ko');
await expect(page).toHaveURL(/\/ko\/dashboard$/);
await page.reload();
await expect(page).toHaveURL(/\/ko\/dashboard$/);
});
test('비로그인 /ko/approve 는 signin(+notice)으로 이동한다', async ({ page }) => {
await mockUserfrontApis(page, { sessionStatus: 401 });
await page.goto('/ko/approve?ref=e2e-ref');
await expect(page).toHaveURL(/\/ko\/signin\?notice=qr_login_required$/);
});
test('로그인 상태 /ko/approve 는 승인 API 호출 후 dashboard로 이동한다', async ({
page,
}) => {
let approvedRef: string | null = null;
await seedTokenLogin(page);
await mockUserfrontApis(page, {
captureApprove: (pendingRef) => {
approvedRef = pendingRef;
},
});
await page.goto('/ko/approve?ref=e2e-approve-ref');
await expect(page).toHaveURL(/\/ko\/dashboard$/);
expect(approvedRef).toBe('e2e-approve-ref');
});
});

View File

@@ -0,0 +1,257 @@
import { expect, test, type Page, type Route } from '@playwright/test';
type RequestCapture = {
loginBody?: Record<string, unknown>;
resetBody?: Record<string, unknown>;
resetToken?: string | null;
clientLogs: string[];
};
const SIGNIN_PASSWORD_TAB_X = 522;
const SIGNIN_TAB_Y = 158;
const SIGNIN_LOGIN_ID_X = 640;
const SIGNIN_LOGIN_ID_Y = 245;
const SIGNIN_PASSWORD_X = 640;
const SIGNIN_PASSWORD_Y = 311;
const SIGNIN_SUBMIT_X = 640;
const SIGNIN_SUBMIT_Y = 381;
const RESET_NEW_PASSWORD_X = 640;
const RESET_NEW_PASSWORD_Y = 401;
const RESET_CONFIRM_PASSWORD_X = 640;
const RESET_CONFIRM_PASSWORD_Y = 464;
const RESET_SUBMIT_X = 640;
const RESET_SUBMIT_Y = 534;
async function clickPasswordTab(page: Page): Promise<void> {
await page.waitForTimeout(900);
const pane = page.locator('flt-glass-pane');
await pane.click({
position: { x: SIGNIN_PASSWORD_TAB_X, y: SIGNIN_TAB_Y },
force: true,
});
await page.waitForTimeout(120);
await pane.click({
position: { x: SIGNIN_PASSWORD_TAB_X, y: SIGNIN_TAB_Y },
force: true,
});
await page.waitForTimeout(200);
}
async function fillAt(page: Page, x: number, y: number, value: string): Promise<void> {
const pane = page.locator('flt-glass-pane');
await pane.click({ position: { x, y }, force: true });
await page.waitForTimeout(100);
await page.keyboard.press('Control+A');
await page.keyboard.press('Backspace');
await page.keyboard.type(value);
}
async function mockAuthApis(page: Page, capture: RequestCapture): Promise<void> {
await page.route('**/api/v1/**', async (route: Route) => {
const requestUrl = new URL(route.request().url());
const path = requestUrl.pathname;
if (path.endsWith('/api/v1/auth/password/login')) {
capture.loginBody = (route.request().postDataJSON() ?? {}) as Record<
string,
unknown
>;
const loginId = String(capture.loginBody.loginId ?? '');
const password = String(capture.loginBody.password ?? '');
if (loginId === 'e2e@example.com' && password === 'ValidPass1!') {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
sessionJwt: 'e30.e30.e30',
provider: 'ory',
}),
});
return;
}
await route.fulfill({
status: 401,
contentType: 'application/json',
body: JSON.stringify({ error: 'password_or_email_mismatch' }),
});
return;
}
if (path.endsWith('/api/v1/auth/password/policy')) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
minLength: 12,
minCharacterTypes: 3,
lowercase: true,
uppercase: true,
number: true,
nonAlphanumeric: true,
}),
});
return;
}
if (path.endsWith('/api/v1/auth/password/reset/complete')) {
capture.resetBody = (route.request().postDataJSON() ?? {}) as Record<
string,
unknown
>;
capture.resetToken = requestUrl.searchParams.get('token');
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ status: 'ok' }),
});
return;
}
if (path.endsWith('/api/v1/client-log')) {
const payload = (route.request().postDataJSON() ?? {}) as {
message?: string;
};
if (payload.message != null) {
capture.clientLogs.push(payload.message);
}
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ ok: true }),
});
return;
}
if (path.endsWith('/api/v1/user/me')) {
const authHeader = route.request().headers()['authorization'] ?? '';
if (!authHeader.startsWith('Bearer ')) {
await route.fulfill({
status: 401,
contentType: 'application/json',
body: JSON.stringify({ error: 'unauthorized' }),
});
return;
}
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
id: 'e2e-user',
email: 'e2e@example.com',
name: 'E2E User',
phone: '+821012341234',
department: 'QA',
affiliationType: 'employee',
companyCode: 'BARON',
tenant: {
id: 'tenant-1',
name: 'Baron',
slug: 'baron',
description: 'E2E tenant',
},
}),
});
return;
}
if (path.endsWith('/api/v1/user/rp/linked')) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ items: [] }),
});
return;
}
if (path.endsWith('/api/v1/audit/auth/timeline')) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ items: [], next_cursor: '' }),
});
return;
}
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({}),
});
});
}
test.describe('UserFront WASM password login and reset', () => {
test('비밀번호 로그인 성공 시 dashboard로 이동하고 토큰을 저장한다', async ({ page }) => {
const capture: RequestCapture = { clientLogs: [] };
await mockAuthApis(page, capture);
await page.goto('/ko/signin');
await clickPasswordTab(page);
await fillAt(page, SIGNIN_LOGIN_ID_X, SIGNIN_LOGIN_ID_Y, 'e2e@example.com');
await fillAt(page, SIGNIN_PASSWORD_X, SIGNIN_PASSWORD_Y, 'ValidPass1!');
await page.locator('flt-glass-pane').click({
position: { x: SIGNIN_SUBMIT_X, y: SIGNIN_SUBMIT_Y },
force: true,
});
await expect(page).toHaveURL(/\/ko\/dashboard$/);
expect(capture.loginBody?.loginId).toBe('e2e@example.com');
expect(capture.loginBody?.password).toBe('ValidPass1!');
const storedToken = await page.evaluate(() =>
window.localStorage.getItem('baron_auth_token'),
);
expect(storedToken).toBe('e30.e30.e30');
});
test('비밀번호 로그인 실패 시 에러 코드를 사용자에게 표시한다', async ({ page }) => {
const capture: RequestCapture = { clientLogs: [] };
await mockAuthApis(page, capture);
await page.goto('/ko/signin');
await clickPasswordTab(page);
await fillAt(page, SIGNIN_LOGIN_ID_X, SIGNIN_LOGIN_ID_Y, 'e2e@example.com');
await fillAt(page, SIGNIN_PASSWORD_X, SIGNIN_PASSWORD_Y, 'WrongPass1!');
await page.locator('flt-glass-pane').click({
position: { x: SIGNIN_SUBMIT_X, y: SIGNIN_SUBMIT_Y },
force: true,
});
await expect(page).toHaveURL(/\/ko\/signin$/);
await expect
.poll(() =>
capture.clientLogs.some((message) =>
message.includes('password_or_email_mismatch'),
),
)
.toBe(true);
});
test('reset-password에서 변경 성공 시 signin으로 이동한다', async ({ page }) => {
const capture: RequestCapture = { clientLogs: [] };
await mockAuthApis(page, capture);
await page.goto('/ko/reset-password?token=reset-token-e2e');
await page.waitForTimeout(900);
await fillAt(page, RESET_NEW_PASSWORD_X, RESET_NEW_PASSWORD_Y, 'ValidPass1!A');
await fillAt(
page,
RESET_CONFIRM_PASSWORD_X,
RESET_CONFIRM_PASSWORD_Y,
'ValidPass1!A',
);
await page.locator('flt-glass-pane').click({
position: { x: RESET_SUBMIT_X, y: RESET_SUBMIT_Y },
force: true,
});
await expect(page).toHaveURL(/\/ko\/signin$/);
expect(capture.resetToken).toBe('reset-token-e2e');
expect(capture.resetBody?.newPassword).toBe('ValidPass1!A');
});
});

View File

@@ -0,0 +1,275 @@
import { expect, test, type Page, type Route } from '@playwright/test';
type ProfileState = {
department: string;
getMeCount: number;
putBodies: Array<Record<string, unknown>>;
};
const PROFILE_DEPARTMENT_EDIT_X = 1170;
const PROFILE_DEPARTMENT_EDIT_Y = 680;
const PROFILE_DEPARTMENT_INPUT_X = 110;
const PROFILE_DEPARTMENT_INPUT_Y = 685;
const PROFILE_BLUR_X = 200;
const PROFILE_BLUR_Y = 260;
async function seedTokenLogin(page: Page): Promise<void> {
await page.addInitScript(() => {
window.localStorage.setItem('baron_auth_token', 'e30.e30.e30');
window.localStorage.setItem('baron_auth_provider', 'ory');
window.localStorage.removeItem('baron_auth_cookie_mode');
window.localStorage.removeItem('baron_auth_pending_provider');
});
}
async function fillAt(page: Page, x: number, y: number, value: string): Promise<void> {
const pane = page.locator('flt-glass-pane');
await pane.click({ position: { x, y }, force: true });
await page.waitForTimeout(100);
await page.keyboard.press('Control+A');
await page.keyboard.press('Backspace');
await page.keyboard.type(value);
}
async function openDepartmentEditor(page: Page): Promise<void> {
await page.locator('flt-glass-pane').click({
position: { x: PROFILE_DEPARTMENT_EDIT_X, y: PROFILE_DEPARTMENT_EDIT_Y },
force: true,
});
await page.waitForTimeout(200);
}
async function blurDepartmentEditor(page: Page): Promise<void> {
await page.locator('flt-glass-pane').click({
position: { x: PROFILE_BLUR_X, y: PROFILE_BLUR_Y },
force: true,
});
await page.waitForTimeout(250);
}
async function mockProfileApis(page: Page, state: ProfileState): Promise<void> {
await page.route('**/api/v1/**', async (route: Route) => {
const request = route.request();
const requestUrl = new URL(request.url());
const path = requestUrl.pathname;
const method = request.method().toUpperCase();
if (path.endsWith('/api/v1/user/me') && method === 'GET') {
const authHeader = request.headers()['authorization'] ?? '';
if (!authHeader.startsWith('Bearer ')) {
await route.fulfill({
status: 401,
contentType: 'application/json',
body: JSON.stringify({ error: 'unauthorized' }),
});
return;
}
state.getMeCount += 1;
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
id: 'e2e-user',
email: 'e2e@example.com',
name: 'E2E User',
phone: '+821012341234',
department: state.department,
affiliationType: 'employee',
companyCode: 'BARON',
tenant: {
id: 'tenant-1',
name: 'Baron',
slug: 'baron',
description: 'E2E tenant',
},
}),
});
return;
}
if (path.endsWith('/api/v1/user/me') && method === 'PUT') {
const body = (request.postDataJSON() ?? {}) as Record<string, unknown>;
state.putBodies.push(body);
const nextDepartment = String(body.department ?? '').trim();
if (nextDepartment !== '') {
state.department = nextDepartment;
}
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
status: 'success',
updatedAt: '2026-02-24T00:00:00Z',
}),
});
return;
}
if (path.endsWith('/api/v1/user/rp/linked')) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ items: [] }),
});
return;
}
if (path.endsWith('/api/v1/audit/auth/timeline')) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ items: [], next_cursor: '' }),
});
return;
}
if (path.endsWith('/api/v1/client-log')) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ ok: true }),
});
return;
}
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ ok: true }),
});
});
}
async function openProfilePage(page: Page): Promise<void> {
await page.goto('/ko/profile');
await expect(page).toHaveURL(/\/ko\/profile$/);
await page.waitForTimeout(1200);
}
async function waitForInitialProfileLoad(state: ProfileState): Promise<void> {
await expect.poll(() => state.getMeCount).toBeGreaterThan(0);
}
test.describe('UserFront WASM profile department editing', () => {
test.afterEach(async ({ page }) => {
await page.unroute('**/api/v1/**');
});
test('소속 수정 후 포커스 아웃하면 저장 요청이 전송되고 새로고침 후 최신 값으로 재조회된다', async ({
page,
}) => {
const state: ProfileState = {
department: 'QA',
getMeCount: 0,
putBodies: [],
};
await seedTokenLogin(page);
await mockProfileApis(page, state);
await openProfilePage(page);
await waitForInitialProfileLoad(state);
await openDepartmentEditor(page);
await fillAt(page, PROFILE_DEPARTMENT_INPUT_X, PROFILE_DEPARTMENT_INPUT_Y, 'QA-Updated');
await blurDepartmentEditor(page);
await expect.poll(() => state.putBodies.length).toBe(1);
expect(state.putBodies[0]?.department).toBe('QA-Updated');
expect(state.department).toBe('QA-Updated');
const getCountBeforeReload = state.getMeCount;
await page.reload();
await expect(page).toHaveURL(/\/ko\/profile$/);
await expect.poll(() => state.getMeCount).toBeGreaterThan(getCountBeforeReload);
});
test('재현: 소속 입력만 하고 즉시 새로고침하면 저장 요청이 전송되지 않는다', async ({
page,
}) => {
const state: ProfileState = {
department: 'QA',
getMeCount: 0,
putBodies: [],
};
await seedTokenLogin(page);
await mockProfileApis(page, state);
await openProfilePage(page);
await waitForInitialProfileLoad(state);
await openDepartmentEditor(page);
await fillAt(page, PROFILE_DEPARTMENT_INPUT_X, PROFILE_DEPARTMENT_INPUT_Y, 'QA-Repro');
await page.reload();
await expect(page).toHaveURL(/\/ko\/profile$/);
expect(state.putBodies).toHaveLength(0);
expect(state.department).toBe('QA');
});
test('소속에 기존값을 그대로 입력하고 포커스 아웃하면 저장 요청이 전송되지 않는다', async ({
page,
}) => {
const state: ProfileState = {
department: 'QA',
getMeCount: 0,
putBodies: [],
};
await seedTokenLogin(page);
await mockProfileApis(page, state);
await openProfilePage(page);
await waitForInitialProfileLoad(state);
await openDepartmentEditor(page);
await fillAt(page, PROFILE_DEPARTMENT_INPUT_X, PROFILE_DEPARTMENT_INPUT_Y, 'QA');
await blurDepartmentEditor(page);
expect(state.putBodies).toHaveLength(0);
});
test('소속을 빈 값으로 입력하면 저장 요청이 전송되지 않는다', async ({ page }) => {
const state: ProfileState = {
department: 'QA',
getMeCount: 0,
putBodies: [],
};
await seedTokenLogin(page);
await mockProfileApis(page, state);
await openProfilePage(page);
await waitForInitialProfileLoad(state);
await openDepartmentEditor(page);
await fillAt(page, PROFILE_DEPARTMENT_INPUT_X, PROFILE_DEPARTMENT_INPUT_Y, '');
await blurDepartmentEditor(page);
expect(state.putBodies).toHaveLength(0);
expect(state.department).toBe('QA');
});
test('소속을 수정한 뒤 새로고침 후 다시 수정해도 저장 요청이 누락되지 않는다', async ({ page }) => {
const state: ProfileState = {
department: 'QA',
getMeCount: 0,
putBodies: [],
};
await seedTokenLogin(page);
await mockProfileApis(page, state);
await openProfilePage(page);
await waitForInitialProfileLoad(state);
await openDepartmentEditor(page);
await fillAt(page, PROFILE_DEPARTMENT_INPUT_X, PROFILE_DEPARTMENT_INPUT_Y, 'QA-1');
await blurDepartmentEditor(page);
await expect.poll(() => state.putBodies.length).toBe(1);
await page.reload();
await expect(page).toHaveURL(/\/ko\/profile$/);
await page.waitForTimeout(1200);
await openDepartmentEditor(page);
await fillAt(page, PROFILE_DEPARTMENT_INPUT_X, PROFILE_DEPARTMENT_INPUT_Y, 'QA-2');
await blurDepartmentEditor(page);
await expect.poll(() => state.putBodies.length).toBe(2);
expect(state.putBodies[0]?.department).toBe('QA-1');
expect(state.putBodies[1]?.department).toBe('QA-2');
expect(state.department).toBe('QA-2');
});
});

View File

@@ -0,0 +1,320 @@
import { expect, test, type Page, type Route } from '@playwright/test';
async function seedTokenLogin(page: Page): Promise<void> {
await page.addInitScript(() => {
window.localStorage.setItem('baron_auth_token', 'e30.e30.e30');
window.localStorage.setItem('baron_auth_provider', 'ory');
window.localStorage.removeItem('baron_auth_cookie_mode');
window.localStorage.removeItem('baron_auth_pending_provider');
});
}
async function mockInventoryApis(page: Page): Promise<void> {
await page.route('**/api/v1/**', async (route: Route) => {
const requestUrl = new URL(route.request().url());
const path = requestUrl.pathname;
const method = route.request().method().toUpperCase();
if (path.endsWith('/api/v1/user/me')) {
const authHeader = route.request().headers()['authorization'] ?? '';
if (authHeader.startsWith('Bearer ')) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
id: 'e2e-user',
email: 'e2e@example.com',
name: 'E2E User',
phone: '+821012341234',
department: 'QA',
affiliationType: 'employee',
companyCode: 'BARON',
tenant: {
id: 'tenant-1',
name: 'Baron',
slug: 'baron',
description: 'E2E tenant',
},
}),
});
return;
}
await route.fulfill({
status: 401,
contentType: 'application/json',
body: JSON.stringify({ error: 'unauthorized' }),
});
return;
}
if (path.endsWith('/api/v1/user/rp/linked')) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ items: [] }),
});
return;
}
if (path.endsWith('/api/v1/audit/auth/timeline')) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ items: [], next_cursor: '' }),
});
return;
}
if (path.endsWith('/api/v1/auth/password/policy')) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
minLength: 12,
minCharacterTypes: 3,
lowercase: true,
uppercase: true,
number: true,
nonAlphanumeric: true,
}),
});
return;
}
if (path.endsWith('/api/v1/auth/magic-link/verify')) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ status: 'approved' }),
});
return;
}
if (path.endsWith('/api/v1/auth/login/code/verify')) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ status: 'approved' }),
});
return;
}
if (path.endsWith('/api/v1/auth/login/code/verify-short')) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ status: 'approved' }),
});
return;
}
if (path.endsWith('/api/v1/auth/consent') && method === 'GET') {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
client: {
client_name: 'E2E Client',
client_id: 'e2e-client',
},
requested_scope: ['openid'],
scope_details: {
openid: {
description: 'OpenID',
mandatory: true,
},
},
}),
});
return;
}
if (path.endsWith('/api/v1/auth/qr/approve')) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ ok: true }),
});
return;
}
if (path.endsWith('/api/v1/client-log')) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ ok: true }),
});
return;
}
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({}),
});
});
}
test.describe('UserFront WASM route inventory (unauth)', () => {
test.beforeEach(async ({ page }) => {
await mockInventoryApis(page);
});
test('route: /', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveURL(/\/(ko|en)\/signin(?:\?.*)?$/);
});
test('route: /ko', async ({ page }) => {
await page.goto('/ko');
await expect(page).toHaveURL(/\/ko\/signin(?:\?.*)?$/);
});
test('route: /ko/dashboard', async ({ page }) => {
await page.goto('/ko/dashboard');
await expect(page).toHaveURL(/\/ko\/signin$/);
});
test('route: /ko/profile', async ({ page }) => {
await page.goto('/ko/profile');
await expect(page).toHaveURL(/\/ko\/signin$/);
});
test('route: /ko/admin/users', async ({ page }) => {
await page.goto('/ko/admin/users');
await expect(page).toHaveURL(/\/ko\/signin$/);
});
test('route: /ko/scan', async ({ page }) => {
await page.goto('/ko/scan');
await expect(page).toHaveURL(/\/ko\/signin$/);
});
test('route: /ko/signin', async ({ page }) => {
await page.goto('/ko/signin');
await expect(page).toHaveURL(/\/ko\/signin$/);
});
test('route: /ko/login', async ({ page }) => {
await page.goto('/ko/login');
await expect(page).toHaveURL(/\/ko\/login$/);
});
test('route: /ko/signup', async ({ page }) => {
await page.goto('/ko/signup');
await expect(page).toHaveURL(/\/ko\/signup$/);
});
test('route: /ko/registration', async ({ page }) => {
await page.goto('/ko/registration');
await expect(page).toHaveURL(/\/ko\/registration$/);
});
test('route: /ko/verify', async ({ page }) => {
await page.goto('/ko/verify');
await expect(page).toHaveURL(/\/ko\/verify$/);
});
test('route: /ko/verify/:token', async ({ page }) => {
await page.goto('/ko/verify/e2e-token');
await expect(page).toHaveURL(/\/ko\/verify\/e2e-token$/);
});
test('route: /ko/verification', async ({ page }) => {
await page.goto('/ko/verification');
await expect(page).toHaveURL(/\/ko\/verification$/);
});
test('route: /ko/l/:shortCode', async ({ page }) => {
await page.goto('/ko/l/AB123456');
await expect(page).toHaveURL(/\/ko\/l\/AB123456$/);
});
test('route: /ko/forgot-password', async ({ page }) => {
await page.goto('/ko/forgot-password');
await expect(page).toHaveURL(/\/ko\/forgot-password$/);
});
test('route: /ko/recovery', async ({ page }) => {
await page.goto('/ko/recovery');
await expect(page).toHaveURL(/\/ko\/recovery$/);
});
test('route: /ko/reset-password', async ({ page }) => {
await page.goto('/ko/reset-password?token=e2e-reset-token');
await expect(page).toHaveURL(/\/ko\/reset-password\?token=e2e-reset-token$/);
});
test('route: /ko/error', async ({ page }) => {
await page.goto('/ko/error?error=invalid_request');
await expect(page).toHaveURL(/\/ko\/error\?error=invalid_request$/);
});
test('route: /ko/settings', async ({ page }) => {
await page.goto('/ko/settings');
await expect(page).toHaveURL(/\/ko\/settings$/);
});
test('route: /ko/consent (missing challenge)', async ({ page }) => {
await page.goto('/ko/consent');
await expect(page).toHaveURL(/\/ko\/consent$/);
});
test('route: /ko/consent?consent_challenge=...', async ({ page }) => {
await page.goto('/ko/consent?consent_challenge=e2e-consent');
await expect(page).toHaveURL(/\/ko\/consent\?consent_challenge=e2e-consent$/);
});
test('route: /ko/approve?ref=...', async ({ page }) => {
await page.goto('/ko/approve?ref=e2e-ref');
await expect(page).toHaveURL(/\/ko\/signin\?notice=qr_login_required$/);
});
test('route: /ko/ql/:ref', async ({ page }) => {
await page.goto('/ko/ql/e2e-ref');
await expect(page).toHaveURL(/\/ko\/signin\?notice=qr_login_required$/);
});
});
test.describe('UserFront WASM route inventory (authed)', () => {
test.beforeEach(async ({ page }) => {
await seedTokenLogin(page);
await mockInventoryApis(page);
});
test('route: /ko -> /ko/dashboard', async ({ page }) => {
await page.goto('/ko');
await expect(page).toHaveURL(/\/ko\/dashboard$/);
});
test('route: /ko/dashboard', async ({ page }) => {
await page.goto('/ko/dashboard');
await expect(page).toHaveURL(/\/ko\/dashboard$/);
});
test('route: /ko/profile', async ({ page }) => {
await page.goto('/ko/profile');
await expect(page).toHaveURL(/\/ko\/profile$/);
});
test('route: /ko/admin/users', async ({ page }) => {
await page.goto('/ko/admin/users');
await expect(page).toHaveURL(/\/ko\/admin\/users$/);
});
test('route: /ko/scan', async ({ page }) => {
await page.goto('/ko/scan');
await expect(page).toHaveURL(/\/ko\/scan$/);
});
test('route: /ko/approve?ref=... -> /ko/dashboard', async ({ page }) => {
await page.goto('/ko/approve?ref=e2e-ref');
await expect(page).toHaveURL(/\/ko\/dashboard$/);
});
test('route: /ko/ql/:ref -> /ko/dashboard', async ({ page }) => {
await page.goto('/ko/ql/e2e-ref');
await expect(page).toHaveURL(/\/ko\/dashboard$/);
});
});