1
0
forked from baron/baron-sso

ci: add code check badges and coverage reports

This commit is contained in:
2026-05-29 12:05:43 +09:00
parent c489c7c38f
commit a830242947
164 changed files with 9059 additions and 2012 deletions

View File

@@ -1,9 +1,10 @@
import { expect, test, type BrowserContext, type Page } from '@playwright/test';
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 ?? '';
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<{
@@ -18,20 +19,20 @@ type SessionApiResponse = {
function ensureCredentials(): void {
if (!LOGIN_ID || !PASSWORD) {
test.skip(true, 'E2E credentials are required');
test.skip(true, "E2E credentials are required");
}
}
async function enableFlutterAccessibility(page: Page): Promise<void> {
await page.waitForTimeout(300);
const button = page.getByRole('button', { name: 'Enable accessibility' });
const button = page.getByRole("button", { name: "Enable accessibility" });
if (await button.count()) {
try {
await button.click({ force: true });
} catch {
return;
}
const placeholder = page.locator('flt-semantics-placeholder');
const placeholder = page.locator("flt-semantics-placeholder");
if (await placeholder.count()) {
await placeholder.first().click({ force: true });
}
@@ -41,7 +42,7 @@ async function enableFlutterAccessibility(page: Page): Promise<void> {
async function clickPasswordTab(page: Page): Promise<void> {
await page.waitForTimeout(900);
const pane = page.locator('flt-glass-pane');
const pane = page.locator("flt-glass-pane");
await pane.click({
position: { x: 522, y: 158 },
force: true,
@@ -54,20 +55,27 @@ async function clickPasswordTab(page: Page): Promise<void> {
await page.waitForTimeout(200);
}
async function fillAt(page: Page, x: number, y: number, value: string): Promise<void> {
const pane = page.locator('flt-glass-pane');
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.press("Control+A");
await page.keyboard.press("Backspace");
await page.keyboard.type(value);
}
async function loginViaUserFront(page: Page): Promise<void> {
await page.waitForURL(/\/ko\/(signin|login)/, { timeout: 30_000 });
const loginIdInput = page.getByPlaceholder(/이메일 또는 휴대폰 번호|email|phone/i);
const loginIdInput = page.getByPlaceholder(
/이메일 또는 휴대폰 번호|email|phone/i,
);
const passwordInput = page.getByPlaceholder(/비밀번호|password/i);
const submitButton = page.getByRole('button', { name: /로그인|Login/i });
const submitButton = page.getByRole("button", { name: /로그인|Login/i });
if ((await loginIdInput.count()) >= 1 && (await passwordInput.count()) >= 1) {
await loginIdInput.first().fill(LOGIN_ID);
@@ -79,7 +87,7 @@ async function loginViaUserFront(page: Page): Promise<void> {
await clickPasswordTab(page);
await fillAt(page, 640, 245, LOGIN_ID);
await fillAt(page, 640, 311, PASSWORD);
await page.locator('flt-glass-pane').click({
await page.locator("flt-glass-pane").click({
position: { x: 640, y: 381 },
force: true,
});
@@ -91,7 +99,7 @@ async function ensureConsentIfNeeded(page: Page): Promise<void> {
}
const allowButton = page
.getByRole('button')
.getByRole("button")
.filter({ hasText: /허용|동의|Accept|Allow/i })
.first();
@@ -100,15 +108,17 @@ async function ensureConsentIfNeeded(page: Page): Promise<void> {
}
}
async function captureUserSessionsOnReload(page: Page): Promise<SessionApiResponse> {
async function captureUserSessionsOnReload(
page: Page,
): Promise<SessionApiResponse> {
const responsePromise = page.waitForResponse(
(response) =>
response.request().method() === 'GET' &&
response.url().includes('/api/v1/user/sessions'),
response.request().method() === "GET" &&
response.url().includes("/api/v1/user/sessions"),
{ timeout: 30_000 },
);
await page.reload({ waitUntil: 'domcontentloaded' });
await page.reload({ waitUntil: "domcontentloaded" });
const response = await responsePromise;
return (await response.json()) as SessionApiResponse;
}
@@ -116,7 +126,7 @@ async function captureUserSessionsOnReload(page: Page): Promise<SessionApiRespon
async function loginUserFront(context: BrowserContext): Promise<Page> {
const page = await context.newPage();
await page.goto(`${USERFRONT_BASE_URL}/ko/signin`, {
waitUntil: 'domcontentloaded',
waitUntil: "domcontentloaded",
});
await loginViaUserFront(page);
await expect(page).toHaveURL(/\/ko\/dashboard/, { timeout: 60_000 });
@@ -125,8 +135,10 @@ async function loginUserFront(context: BrowserContext): Promise<Page> {
async function loginAdminFront(context: BrowserContext): Promise<Page> {
const page = await context.newPage();
await page.goto(ADMINFRONT_URL, { waitUntil: 'domcontentloaded' });
const ssoButton = page.getByRole('button', { name: /SSO 계정으로 로그인|SSO/i });
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);
@@ -136,35 +148,38 @@ async function loginAdminFront(context: BrowserContext): Promise<Page> {
const origin = window.location.origin;
const authority = `${USERFRONT_BASE_URL}/oidc`;
const params = new URLSearchParams({
client_id: 'adminfront',
client_id: "adminfront",
redirect_uri: `${origin}/auth/callback`,
response_type: 'code',
scope: 'openid offline_access profile email',
response_type: "code",
scope: "openid offline_access profile email",
state: `pw-${Date.now()}`,
nonce: `pw-${Date.now()}`,
code_challenge: 'test-code-challenge-test-code-challenge-test',
code_challenge_method: 'plain',
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 page.goto(authorizeUrl, { waitUntil: "domcontentloaded" });
}
await loginViaUserFront(page);
await ensureConsentIfNeeded(page);
await page.waitForURL(/localhost:5173|\/auth\/callback|\/dashboard|\/tenants/, {
timeout: 60_000,
});
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 ({
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 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);
@@ -172,16 +187,20 @@ test.describe('cross-browser session debug', () => {
const sessionsPayload = await captureUserSessionsOnReload(userfrontPage);
const items = sessionsPayload.items ?? [];
const adminfrontItems = items.filter((item) =>
(item.client_id ?? '').toLowerCase().includes('adminfront'),
(item.client_id ?? "").toLowerCase().includes("adminfront"),
);
const unknownCards = await userfrontPage.locator('text=세션 정보').allTextContents();
const adminFrontCards = await userfrontPage.locator('text=AdminFront').allTextContents();
const unknownCards = await userfrontPage
.locator("text=세션 정보")
.allTextContents();
const adminFrontCards = await userfrontPage
.locator("text=AdminFront")
.allTextContents();
await testInfo.attach('user-sessions.json', {
await testInfo.attach("user-sessions.json", {
body: JSON.stringify(sessionsPayload, null, 2),
contentType: 'application/json',
contentType: "application/json",
});
await testInfo.attach('card-summary.json', {
await testInfo.attach("card-summary.json", {
body: JSON.stringify(
{
unknownCards,
@@ -192,7 +211,7 @@ test.describe('cross-browser session debug', () => {
null,
2,
),
contentType: 'application/json',
contentType: "application/json",
});
expect(adminfrontItems.length).toBeGreaterThan(0);