import { type BrowserContext, expect, type Page, test } from "@playwright/test"; const USERFRONT_BASE_URL = process.env.USERFRONT_BASE_URL ?? "https://sso.example.test"; const ADMINFRONT_URL = process.env.ADMINFRONT_URL ?? "http://localhost:5173"; const LOGIN_ID = process.env.E2E_LOGIN_ID ?? ""; const PASSWORD = process.env.E2E_PASSWORD ?? ""; type SessionApiResponse = { items?: Array<{ session_id?: string; client_id?: string; app_name?: string; is_current?: boolean; user_agent?: string; ip_address?: string; }>; }; function ensureCredentials(): void { if (!LOGIN_ID || !PASSWORD) { test.skip(true, "E2E credentials are required"); } } async function clickPasswordTab(page: Page): Promise { await page.waitForTimeout(900); const pane = page.locator("flt-glass-pane"); await pane.click({ position: { x: 522, y: 158 }, force: true, }); await page.waitForTimeout(120); await pane.click({ position: { x: 522, y: 158 }, force: true, }); await page.waitForTimeout(200); } async function fillAt( page: Page, x: number, y: number, value: string, ): Promise { 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 loginViaUserFront(page: Page): Promise { await page.waitForURL(/\/ko\/(signin|login)/, { timeout: 30_000 }); const loginIdInput = page.getByPlaceholder( /이메일 또는 휴대폰 번호|email|phone/i, ); const passwordInput = page.getByPlaceholder(/비밀번호|password/i); const submitButton = page.getByRole("button", { name: /로그인|Login/i }); if ((await loginIdInput.count()) >= 1 && (await passwordInput.count()) >= 1) { await loginIdInput.first().fill(LOGIN_ID); await passwordInput.first().fill(PASSWORD); await submitButton.click(); return; } await clickPasswordTab(page); await fillAt(page, 640, 245, LOGIN_ID); await fillAt(page, 640, 311, PASSWORD); await page.locator("flt-glass-pane").click({ position: { x: 640, y: 381 }, force: true, }); } async function ensureConsentIfNeeded(page: Page): Promise { if (!/\/ko\/consent/.test(page.url())) { return; } const allowButton = page .getByRole("button") .filter({ hasText: /허용|동의|Accept|Allow/i }) .first(); if (await allowButton.count()) { await allowButton.click({ force: true }); } } async function captureUserSessionsOnReload( page: Page, ): Promise { const responsePromise = page.waitForResponse( (response) => response.request().method() === "GET" && response.url().includes("/api/v1/user/sessions"), { timeout: 30_000 }, ); await page.reload({ waitUntil: "domcontentloaded" }); const response = await responsePromise; return (await response.json()) as SessionApiResponse; } async function loginUserFront(context: BrowserContext): Promise { const page = await context.newPage(); await page.goto(`${USERFRONT_BASE_URL}/ko/signin`, { waitUntil: "domcontentloaded", }); await loginViaUserFront(page); await expect(page).toHaveURL(/\/ko\/dashboard/, { timeout: 60_000 }); return page; } async function loginAdminFront(context: BrowserContext): Promise { const page = await context.newPage(); await page.goto(ADMINFRONT_URL, { waitUntil: "domcontentloaded" }); const ssoButton = page.getByRole("button", { name: /SSO 계정으로 로그인|SSO/i, }); if (await ssoButton.count()) { await ssoButton.click({ force: true }); await page.waitForTimeout(1500); } if (/\/login$/.test(page.url())) { const authorizeUrl = await page.evaluate(() => { const origin = window.location.origin; const authority = `${USERFRONT_BASE_URL}/oidc`; const params = new URLSearchParams({ client_id: "adminfront", redirect_uri: `${origin}/auth/callback`, response_type: "code", scope: "openid profile email", state: `pw-${Date.now()}`, nonce: `pw-${Date.now()}`, code_challenge: "test-code-challenge-test-code-challenge-test", code_challenge_method: "plain", }); return `${authority}/oauth2/auth?${params.toString()}`; }); await page.goto(authorizeUrl, { waitUntil: "domcontentloaded" }); } await loginViaUserFront(page); await ensureConsentIfNeeded(page); await page.waitForURL( /localhost:5173|\/auth\/callback|\/dashboard|\/tenants/, { timeout: 60_000, }, ); return page; } test.describe("cross-browser session debug", () => { test("userfront session card should map adminfront session metadata across contexts", async ({ browser, }, testInfo) => { ensureCredentials(); const userfrontContext = await browser.newContext({ locale: "ko-KR" }); const adminfrontContext = await browser.newContext({ locale: "ko-KR" }); const userfrontPage = await loginUserFront(userfrontContext); const adminfrontPage = await loginAdminFront(adminfrontContext); const sessionsPayload = await captureUserSessionsOnReload(userfrontPage); const items = sessionsPayload.items ?? []; const adminfrontItems = items.filter((item) => (item.client_id ?? "").toLowerCase().includes("adminfront"), ); const unknownCards = await userfrontPage .locator("text=세션 정보") .allTextContents(); const adminFrontCards = await userfrontPage .locator("text=AdminFront") .allTextContents(); await testInfo.attach("user-sessions.json", { body: JSON.stringify(sessionsPayload, null, 2), contentType: "application/json", }); await testInfo.attach("card-summary.json", { body: JSON.stringify( { unknownCards, adminFrontCards, currentUrl: userfrontPage.url(), adminfrontUrl: adminfrontPage.url(), }, null, 2, ), contentType: "application/json", }); expect(adminfrontItems.length).toBeGreaterThan(0); }); });