1
0
forked from baron/baron-sso

Merge branch 'dev' into feat/id_login

This commit is contained in:
2026-04-01 13:40:45 +09:00
41 changed files with 2079 additions and 397 deletions

View File

@@ -7,32 +7,94 @@ type RequestCapture = {
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;
async function enableFlutterAccessibility(page: Page): Promise<void> {
await page.waitForTimeout(300);
const button = page.getByRole('button', { name: 'Enable accessibility' });
if (await button.count()) {
await button.click({ force: true });
const placeholder = page.locator('flt-semantics-placeholder');
if (await placeholder.count()) {
await placeholder.first().click({ force: true });
}
await page.waitForTimeout(800);
}
}
const RESET_NEW_PASSWORD_X = 640;
const RESET_NEW_PASSWORD_Y = 382;
const RESET_CONFIRM_PASSWORD_X = 640;
const RESET_CONFIRM_PASSWORD_Y = 464;
const RESET_SUBMIT_X = 640;
const RESET_SUBMIT_Y = 534;
type ScreenCoords = {
signinPasswordTabX: number;
signinTabY: number;
signinLoginIdX: number;
signinLoginIdY: number;
signinPasswordX: number;
signinPasswordY: number;
signinSubmitX: number;
signinSubmitY: number;
resetNewPasswordX: number;
resetNewPasswordY: number;
resetConfirmPasswordX: number;
resetConfirmPasswordY: number;
resetSubmitX: number;
resetSubmitY: number;
};
const desktopCoords: ScreenCoords = {
signinPasswordTabX: 522,
signinTabY: 158,
signinLoginIdX: 640,
signinLoginIdY: 245,
signinPasswordX: 640,
signinPasswordY: 311,
signinSubmitX: 640,
signinSubmitY: 381,
resetNewPasswordX: 640,
resetNewPasswordY: 382,
resetConfirmPasswordX: 640,
resetConfirmPasswordY: 464,
resetSubmitX: 640,
resetSubmitY: 534,
};
const mobileCoords: ScreenCoords = {
signinPasswordTabX: 90,
signinTabY: 158,
signinLoginIdX: 206,
signinLoginIdY: 268,
signinPasswordX: 206,
signinPasswordY: 334,
signinSubmitX: 206,
signinSubmitY: 399,
resetNewPasswordX: 206,
resetNewPasswordY: 382,
resetConfirmPasswordX: 206,
resetConfirmPasswordY: 464,
resetSubmitX: 206,
resetSubmitY: 534,
};
function coordsFor(page: Page): ScreenCoords {
const viewport = page.viewportSize();
return (viewport?.width ?? 1280) <= 500 ? mobileCoords : desktopCoords;
}
function isMobileProject(page: Page): boolean {
const viewport = page.viewportSize();
return (viewport?.width ?? 1280) <= 500;
}
async function clickPasswordTab(page: Page): Promise<void> {
if (isMobileProject(page)) {
return;
}
const coords = coordsFor(page);
await page.waitForTimeout(900);
const pane = page.locator('flt-glass-pane');
await pane.click({
position: { x: SIGNIN_PASSWORD_TAB_X, y: SIGNIN_TAB_Y },
position: { x: coords.signinPasswordTabX, y: coords.signinTabY },
force: true,
});
await page.waitForTimeout(120);
await pane.click({
position: { x: SIGNIN_PASSWORD_TAB_X, y: SIGNIN_TAB_Y },
position: { x: coords.signinPasswordTabX, y: coords.signinTabY },
force: true,
});
await page.waitForTimeout(200);
@@ -47,6 +109,68 @@ async function fillAt(page: Page, x: number, y: number, value: string): Promise<
await page.keyboard.type(value);
}
async function fillPasswordLoginForm(
page: Page,
loginId: string,
password: string,
): Promise<void> {
if (isMobileProject(page)) {
await enableFlutterAccessibility(page);
const inputs = page.getByRole('textbox');
await inputs.nth(0).fill(loginId);
await inputs.nth(1).fill(password);
return;
}
const coords = coordsFor(page);
await fillAt(page, coords.signinLoginIdX, coords.signinLoginIdY, loginId);
await fillAt(page, coords.signinPasswordX, coords.signinPasswordY, password);
}
async function submitPasswordLogin(page: Page): Promise<void> {
if (isMobileProject(page)) {
await page.getByRole('button', { name: '로그인' }).click({ force: true });
return;
}
const coords = coordsFor(page);
await page.locator('flt-glass-pane').click({
position: { x: coords.signinSubmitX, y: coords.signinSubmitY },
force: true,
});
}
async function fillResetPasswordForm(page: Page, password: string): Promise<void> {
if (isMobileProject(page)) {
await enableFlutterAccessibility(page);
await page
.getByRole('textbox', { name: /^새 비밀번호$/ })
.fill(password);
await page
.getByRole('textbox', { name: /^새 비밀번호 확인$/ })
.fill(password);
return;
}
const coords = coordsFor(page);
await fillAt(page, coords.resetNewPasswordX, coords.resetNewPasswordY, password);
await fillAt(
page,
coords.resetConfirmPasswordX,
coords.resetConfirmPasswordY,
password,
);
}
async function submitResetPassword(page: Page): Promise<void> {
if (isMobileProject(page)) {
await page.getByRole('button', { name: '비밀번호 변경' }).click({ force: true });
return;
}
const coords = coordsFor(page);
await page.locator('flt-glass-pane').click({
position: { x: coords.resetSubmitX, y: coords.resetSubmitY },
force: true,
});
}
async function mockAuthApis(page: Page, capture: RequestCapture): Promise<void> {
await page.route('**/api/v1/**', async (route: Route) => {
const requestUrl = new URL(route.request().url());
@@ -187,17 +311,17 @@ async function mockAuthApis(page: Page, capture: RequestCapture): Promise<void>
test.describe('UserFront WASM password login and reset', () => {
test.skip(({ isMobile }) => isMobile, 'Desktop only (hardcoded coordinates)');
test('비밀번호 로그인 성공 시 dashboard로 이동하고 토큰을 저장한다', async ({ page }) => {
test.skip(
isMobileProject(page),
'Mobile webapp keeps dedicated auth-routing coverage; password form canvas automation is covered on desktop.',
);
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 fillPasswordLoginForm(page, 'e2e@example.com', 'ValidPass1!');
await submitPasswordLogin(page);
await expect(page).toHaveURL(/\/ko\/dashboard$/);
@@ -211,17 +335,17 @@ test.describe('UserFront WASM password login and reset', () => {
});
test('비밀번호 로그인 실패 시 에러 코드를 사용자에게 표시한다', async ({ page }) => {
test.skip(
isMobileProject(page),
'Mobile webapp keeps dedicated auth-routing coverage; password form canvas automation is covered on desktop.',
);
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 fillPasswordLoginForm(page, 'e2e@example.com', 'WrongPass1!');
await submitPasswordLogin(page);
await expect(page).toHaveURL(/\/ko\/signin$/);
await expect
@@ -247,17 +371,8 @@ test.describe('UserFront WASM password login and reset', () => {
await page.goto('/ko/reset-password?token=reset-token-e2e');
await policyLoaded;
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 fillResetPasswordForm(page, 'ValidPass1!A');
await submitResetPassword(page);
await expect
.poll(

View File

@@ -6,12 +6,50 @@ type ProfileState = {
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 enableFlutterAccessibility(page: Page): Promise<void> {
const button = page.getByRole('button', { name: 'Enable accessibility' });
if (await button.count()) {
await button.click({ force: true });
await page.waitForTimeout(200);
}
}
type ProfileCoords = {
departmentEditX: number;
departmentEditY: number;
departmentInputX: number;
departmentInputY: number;
blurX: number;
blurY: number;
};
const desktopCoords: ProfileCoords = {
departmentEditX: 1170,
departmentEditY: 680,
departmentInputX: 110,
departmentInputY: 685,
blurX: 200,
blurY: 260,
};
const mobileCoords: ProfileCoords = {
departmentEditX: 350,
departmentEditY: 680,
departmentInputX: 110,
departmentInputY: 685,
blurX: 200,
blurY: 260,
};
function coordsFor(page: Page): ProfileCoords {
const viewport = page.viewportSize();
return (viewport?.width ?? 1280) <= 500 ? mobileCoords : desktopCoords;
}
function isMobileProject(page: Page): boolean {
const viewport = page.viewportSize();
return (viewport?.width ?? 1280) <= 500;
}
async function seedTokenLogin(page: Page): Promise<void> {
await page.addInitScript(() => {
@@ -32,26 +70,56 @@ async function fillAt(page: Page, x: number, y: number, value: string): Promise<
}
async function openDepartmentEditor(page: Page): Promise<void> {
if (isMobileProject(page)) {
await enableFlutterAccessibility(page);
await page
.getByRole('group', { name: '소속 QA' })
.getByRole('button', { name: '편집' })
.click({ force: true });
await page.waitForTimeout(200);
return;
}
const coords = coordsFor(page);
await page.locator('flt-glass-pane').click({
position: { x: PROFILE_DEPARTMENT_EDIT_X, y: PROFILE_DEPARTMENT_EDIT_Y },
position: { x: coords.departmentEditX, y: coords.departmentEditY },
force: true,
});
await page.waitForTimeout(200);
}
async function blurDepartmentEditor(page: Page): Promise<void> {
if (isMobileProject(page)) {
await page.getByRole('textbox', { name: '소속' }).blur();
await page.waitForTimeout(250);
return;
}
const coords = coordsFor(page);
await page.locator('flt-glass-pane').click({
position: { x: PROFILE_BLUR_X, y: PROFILE_BLUR_Y },
position: { x: coords.blurX, y: coords.blurY },
force: true,
});
await page.waitForTimeout(250);
}
async function submitDepartmentEditor(page: Page): Promise<void> {
if (isMobileProject(page)) {
await page.getByRole('textbox', { name: '소속' }).press('Enter');
await page.waitForTimeout(250);
return;
}
await page.keyboard.press('Enter');
await page.waitForTimeout(250);
}
async function fillDepartmentField(page: Page, value: string): Promise<void> {
if (isMobileProject(page)) {
await page.getByRole('textbox', { name: '소속' }).fill(value);
return;
}
const coords = coordsFor(page);
await fillAt(page, coords.departmentInputX, coords.departmentInputY, value);
}
async function mockProfileApis(page: Page, state: ProfileState): Promise<void> {
await page.route('**/api/v1/**', async (route: Route) => {
const request = route.request();
@@ -176,7 +244,7 @@ test.describe('UserFront WASM profile department editing', () => {
await waitForInitialProfileLoad(state);
await openDepartmentEditor(page);
await fillAt(page, PROFILE_DEPARTMENT_INPUT_X, PROFILE_DEPARTMENT_INPUT_Y, 'QA-Updated');
await fillDepartmentField(page, 'QA-Updated');
await submitDepartmentEditor(page);
await expect.poll(() => state.putBodies.length).toBe(1);
@@ -203,7 +271,7 @@ test.describe('UserFront WASM profile department editing', () => {
await waitForInitialProfileLoad(state);
await openDepartmentEditor(page);
await fillAt(page, PROFILE_DEPARTMENT_INPUT_X, PROFILE_DEPARTMENT_INPUT_Y, 'QA-Repro');
await fillDepartmentField(page, 'QA-Repro');
await page.reload();
await expect(page).toHaveURL(/\/ko\/profile$/);
@@ -230,7 +298,7 @@ test.describe('UserFront WASM profile department editing', () => {
await waitForInitialProfileLoad(state);
await openDepartmentEditor(page);
await fillAt(page, PROFILE_DEPARTMENT_INPUT_X, PROFILE_DEPARTMENT_INPUT_Y, 'QA');
await fillDepartmentField(page, 'QA');
await blurDepartmentEditor(page);
expect(state.putBodies).toHaveLength(0);
@@ -248,7 +316,7 @@ test.describe('UserFront WASM profile department editing', () => {
await waitForInitialProfileLoad(state);
await openDepartmentEditor(page);
await fillAt(page, PROFILE_DEPARTMENT_INPUT_X, PROFILE_DEPARTMENT_INPUT_Y, '');
await fillDepartmentField(page, '');
await blurDepartmentEditor(page);
expect(state.putBodies).toHaveLength(0);
@@ -267,7 +335,7 @@ test.describe('UserFront WASM profile department editing', () => {
await waitForInitialProfileLoad(state);
await openDepartmentEditor(page);
await fillAt(page, PROFILE_DEPARTMENT_INPUT_X, PROFILE_DEPARTMENT_INPUT_Y, 'QA-1');
await fillDepartmentField(page, 'QA-1');
await submitDepartmentEditor(page);
await expect.poll(() => state.putBodies.length).toBe(1);
@@ -276,7 +344,7 @@ test.describe('UserFront WASM profile department editing', () => {
await page.waitForTimeout(1200);
await openDepartmentEditor(page);
await fillAt(page, PROFILE_DEPARTMENT_INPUT_X, PROFILE_DEPARTMENT_INPUT_Y, 'QA-2');
await fillDepartmentField(page, 'QA-2');
await submitDepartmentEditor(page);
await expect.poll(() => state.putBodies.length).toBe(2);