feat(gateway): reorganize admin console navigation
This commit is contained in:
@@ -134,14 +134,13 @@ const installFixture = async (page: Page) => {
|
||||
test('operates OAuth grace and scheduled deletion with reasoned audit history', async ({ page }, testInfo) => {
|
||||
const mutations = await installFixture(page);
|
||||
page.on('dialog', (dialog) => dialog.accept());
|
||||
await page.goto('admin');
|
||||
await page.goto('admin/users');
|
||||
await page.getByPlaceholder('검색 값 입력').fill('target');
|
||||
await page.getByRole('button', { name: '조회', exact: true }).click();
|
||||
|
||||
await expect(page.getByText('Kakao 인증: 미완료')).toBeVisible();
|
||||
await expect(page.getByRole('cell', { name: 'che:default' })).toBeVisible();
|
||||
await expect(page.getByText('SUCCEEDED · admin.users.updateSanctions').first()).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: '전체 관리자 감사 원장' })).toBeVisible();
|
||||
await page.screenshot({ path: testInfo.outputPath('gateway-admin-account-controls-desktop.png'), fullPage: true });
|
||||
const deletionButton = page.getByRole('button', { name: '보존 기간 후 탈퇴 예약', exact: true });
|
||||
const baseDeleteColor = await deletionButton.evaluate((button) => getComputedStyle(button).backgroundColor);
|
||||
@@ -163,6 +162,11 @@ test('operates OAuth grace and scheduled deletion with reasoned audit history',
|
||||
expect(mutations.some(({ operation }) => operation === 'admin.users.updateKakaoGrace')).toBe(true);
|
||||
expect(mutations.some(({ operation }) => operation === 'admin.users.scheduleDeletion')).toBe(true);
|
||||
|
||||
await page.getByRole('link', { name: '감사 로그' }).click();
|
||||
await expect(page).toHaveURL(/\/gateway\/admin\/audit$/);
|
||||
await expect(page.getByRole('heading', { name: '전체 관리자 감사 원장' })).toBeVisible();
|
||||
await expect(page.getByText('SUCCEEDED · admin.users.updateKakaoGrace').first()).toBeVisible();
|
||||
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
const geometry = await page
|
||||
.getByRole('heading', { name: '전체 관리자 감사 원장' })
|
||||
@@ -173,6 +177,9 @@ test('operates OAuth grace and scheduled deletion with reasoned audit history',
|
||||
});
|
||||
expect(geometry.left).toBeGreaterThanOrEqual(0);
|
||||
expect(geometry.right).toBeLessThanOrEqual(geometry.viewportWidth);
|
||||
await writeFile(testInfo.outputPath('gateway-admin-account-controls-mobile-geometry.json'), JSON.stringify(geometry));
|
||||
await writeFile(
|
||||
testInfo.outputPath('gateway-admin-account-controls-mobile-geometry.json'),
|
||||
JSON.stringify(geometry)
|
||||
);
|
||||
await page.screenshot({ path: testInfo.outputPath('gateway-admin-account-controls-mobile.png'), fullPage: true });
|
||||
});
|
||||
|
||||
@@ -211,8 +211,8 @@ const installFixture = async (
|
||||
|
||||
test('reports clock-shift acceptance separately from actual application', async ({ page }) => {
|
||||
const fixture = await installFixture(page, { deferRequest: true, pendingProfileReads: 1 });
|
||||
await page.goto('admin');
|
||||
await expect(page.getByRole('heading', { name: '관리자 콘솔' })).toBeVisible();
|
||||
await page.goto('admin/servers');
|
||||
await expect(page.getByRole('heading', { name: '서버 관리' })).toBeVisible();
|
||||
|
||||
const duration = page.locator('input[type="number"][min="1"][max="1440"]');
|
||||
const accelerate = page.getByRole('button', { name: '가속', exact: true });
|
||||
@@ -249,7 +249,7 @@ test('blocks another clock shift while any recent action is pending', async ({ p
|
||||
}),
|
||||
],
|
||||
});
|
||||
await page.goto('admin');
|
||||
await page.goto('admin/servers');
|
||||
await page.locator('input[type="number"][min="1"][max="1440"]').fill('15');
|
||||
|
||||
await expect(page.getByRole('button', { name: '가속', exact: true })).toBeDisabled();
|
||||
@@ -264,7 +264,7 @@ test('renders a failed terminal outcome without calling it applied', async ({ pa
|
||||
}),
|
||||
],
|
||||
});
|
||||
await page.goto('admin');
|
||||
await page.goto('admin/servers');
|
||||
|
||||
const failed = page.getByText('FAILED · ACCELERATE 15분');
|
||||
await expect(failed).toBeVisible();
|
||||
@@ -282,7 +282,7 @@ test('renders an ignored terminal outcome without calling it applied', async ({
|
||||
}),
|
||||
],
|
||||
});
|
||||
await page.goto('admin');
|
||||
await page.goto('admin/servers');
|
||||
|
||||
const ignored = page.getByText('IGNORED · DELAY 15분');
|
||||
await expect(ignored).toBeVisible();
|
||||
@@ -291,32 +291,20 @@ test('renders an ignored terminal outcome without calling it applied', async ({
|
||||
await expect(page.getByText(/적용됨|요청 완료/)).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('keeps profile installation disabled while its queued operation is active', async ({ page }, testInfo) => {
|
||||
const fixture = await installFixture(page, { deferInstall: true });
|
||||
page.on('dialog', (dialog) => dialog.accept());
|
||||
await page.goto('admin');
|
||||
test('directs profile deployment to the centralized version page', async ({ page }) => {
|
||||
await installFixture(page);
|
||||
await page.goto('admin/servers');
|
||||
|
||||
const installButton = page.getByRole('button', { name: '설치 적용' });
|
||||
const click = installButton.click();
|
||||
await expect.poll(() => fixture.requestBodies.length).toBe(1);
|
||||
await expect(page.getByRole('button', { name: '등록 중…' })).toBeDisabled();
|
||||
fixture.releaseInstall();
|
||||
await click;
|
||||
|
||||
const operationLink = page.getByRole('link', { name: /77777777-7777-4777-8777-777777777777 상태 보기/ });
|
||||
await expect(operationLink).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: '설치 작업 진행 중' })).toBeDisabled();
|
||||
const releaseLink = page.getByRole('link', { name: '버전 업데이트 열기' });
|
||||
await expect(releaseLink).toBeVisible();
|
||||
await expect(releaseLink).toHaveAttribute('href', '/gateway/admin/releases');
|
||||
await expect(page.getByRole('button', { name: '설치 적용' })).toHaveCount(0);
|
||||
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
const linkGeometry = await operationLink.evaluate((element) => {
|
||||
const linkGeometry = await releaseLink.evaluate((element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return { left: rect.left, right: rect.right, viewportWidth: window.innerWidth };
|
||||
});
|
||||
expect(linkGeometry.left).toBeGreaterThanOrEqual(0);
|
||||
expect(linkGeometry.right).toBeLessThanOrEqual(linkGeometry.viewportWidth);
|
||||
await page.screenshot({ path: testInfo.outputPath('admin-install-active-mobile.png'), fullPage: true });
|
||||
|
||||
await operationLink.click();
|
||||
await expect(page).toHaveURL(/\/gateway\/admin\/server-operations\?operationId=77777777/);
|
||||
await expect(page.getByTestId('operations-table')).toContainText('77777777-7777-4777-8777-777777777777');
|
||||
});
|
||||
|
||||
@@ -27,7 +27,7 @@ const login = async (page: Page, username: string, password: string): Promise<vo
|
||||
const hweRow = (page: Page) => page.locator('tbody tr').filter({ hasText: /^hwe섭/ });
|
||||
|
||||
const resetScenario = async (page: Page, scenarioId: string, sourceCommit: string): Promise<void> => {
|
||||
await page.goto('/gateway/admin/server-operations');
|
||||
await page.goto('/gateway/admin/releases');
|
||||
await page.getByTestId('profile-select').selectOption('hwe:2');
|
||||
await page.getByTestId('source-commit').check();
|
||||
await page.getByTestId('source-ref').fill(sourceCommit);
|
||||
|
||||
@@ -85,8 +85,8 @@ test('admin resets and opens hwe, then two users create generals and reach main'
|
||||
await login(page, adminUsername, await readPassword('admin'));
|
||||
await page.getByRole('link', { name: '관리자 페이지' }).click();
|
||||
await expect(page).toHaveURL(/\/gateway\/admin$/);
|
||||
await page.getByRole('link', { name: '서버 배포 · 시나리오 초기화' }).click();
|
||||
await expect(page).toHaveURL(/\/gateway\/admin\/server-operations$/);
|
||||
await page.getByRole('link', { name: '버전 업데이트' }).first().click();
|
||||
await expect(page).toHaveURL(/\/gateway\/admin\/releases$/);
|
||||
|
||||
await page.getByTestId('profile-select').selectOption(profileKey);
|
||||
const profileStatus = page.getByTestId('selected-profile-status');
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect, test, type Page, type Route } from '@playwright/test';
|
||||
import { writeFile } from 'node:fs/promises';
|
||||
|
||||
const response = (data: unknown) => ({ result: { data } });
|
||||
|
||||
@@ -53,7 +54,7 @@ const installGatewayFixture = async (page: Page, roles: string[]) => {
|
||||
});
|
||||
};
|
||||
|
||||
test('bootstrap superuser can navigate from the lobby to server operations', async ({ page }) => {
|
||||
test('bootstrap superuser can navigate the administrator workspace from the lobby', async ({ page }, testInfo) => {
|
||||
await installGatewayFixture(page, ['superuser']);
|
||||
|
||||
await page.goto('lobby');
|
||||
@@ -62,9 +63,39 @@ test('bootstrap superuser can navigate from the lobby to server operations', asy
|
||||
await adminLink.click();
|
||||
|
||||
await expect(page).toHaveURL(/\/gateway\/admin$/);
|
||||
await expect(page.getByRole('heading', { name: '관리자 콘솔' })).toBeVisible();
|
||||
await page.getByRole('link', { name: '서버 배포 · 시나리오 초기화' }).click();
|
||||
await expect(page).toHaveURL(/\/gateway\/admin\/server-operations$/);
|
||||
await expect(page.getByRole('heading', { name: '운영 개요' })).toBeVisible();
|
||||
const navigation = page.getByRole('navigation', { name: '관리자 메뉴' });
|
||||
await expect(navigation).toBeVisible();
|
||||
const userLink = navigation.getByRole('link', { name: '사용자 관리' });
|
||||
const baseColor = await userLink.evaluate((element) => getComputedStyle(element).color);
|
||||
await userLink.hover();
|
||||
await expect.poll(() => userLink.evaluate((element) => getComputedStyle(element).color)).not.toBe(baseColor);
|
||||
await page.screenshot({ path: testInfo.outputPath('admin-overview-desktop.png'), fullPage: true });
|
||||
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await expect(navigation).toBeHidden();
|
||||
await page.getByRole('button', { name: '관리자 메뉴' }).click();
|
||||
await expect(navigation).toBeVisible();
|
||||
const geometry = await navigation.evaluate((element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return { left: rect.left, right: rect.right, top: rect.top, viewportWidth: window.innerWidth };
|
||||
});
|
||||
expect(geometry.left).toBeGreaterThanOrEqual(0);
|
||||
expect(geometry.right).toBeLessThanOrEqual(geometry.viewportWidth);
|
||||
expect(geometry.top).toBeGreaterThanOrEqual(0);
|
||||
await writeFile(testInfo.outputPath('admin-overview-mobile-geometry.json'), JSON.stringify(geometry));
|
||||
await page.screenshot({ path: testInfo.outputPath('admin-overview-mobile-menu.png'), fullPage: true });
|
||||
|
||||
await navigation.getByRole('link', { name: '버전 업데이트' }).click();
|
||||
await expect(page).toHaveURL(/\/gateway\/admin\/releases$/);
|
||||
await expect(page.getByRole('heading', { name: '버전 업데이트' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('legacy server operations URL keeps query parameters and redirects to releases', async ({ page }) => {
|
||||
await installGatewayFixture(page, ['superuser']);
|
||||
|
||||
await page.goto('admin/server-operations?operationId=legacy-operation');
|
||||
await expect(page).toHaveURL(/\/gateway\/admin\/releases\?operationId=legacy-operation$/);
|
||||
});
|
||||
|
||||
test('scoped administrators see the same navigation while ordinary users do not', async ({ browser }) => {
|
||||
|
||||
@@ -218,9 +218,9 @@ test('separates branch and commit semantics and submits a reset from the dedicat
|
||||
await installFixture(page, state);
|
||||
page.on('dialog', (dialog) => dialog.accept());
|
||||
|
||||
await page.goto('admin/server-operations');
|
||||
await page.goto('admin/releases');
|
||||
await expect(page.getByTestId('server-operations-page')).toBeVisible();
|
||||
await expect(page).toHaveURL(/\/gateway\/admin\/server-operations$/);
|
||||
await expect(page).toHaveURL(/\/gateway\/admin\/releases$/);
|
||||
await expect(page.getByTestId('source-help')).toContainText('실제로 시작될 때');
|
||||
await expect(page.getByTestId('scenario-select')).toHaveValue('2');
|
||||
|
||||
@@ -293,7 +293,7 @@ test('starts and stops all runtime roles through the operation controls', async
|
||||
await installFixture(page, state);
|
||||
page.on('dialog', (dialog) => dialog.accept());
|
||||
|
||||
await page.goto('admin/server-operations');
|
||||
await page.goto('admin/releases');
|
||||
await page.getByTestId('start-server').click();
|
||||
await expect(page.getByText('시작 작업을 요청했습니다.')).toBeVisible();
|
||||
await expect(page.getByText('RUNNING', { exact: true }).first()).toBeVisible();
|
||||
@@ -312,7 +312,7 @@ test('separates DB-preserving profile deployment from DB reset', async ({ page }
|
||||
await installFixture(page, state);
|
||||
page.on('dialog', (dialog) => dialog.accept());
|
||||
|
||||
await page.goto('admin/server-operations');
|
||||
await page.goto('admin/releases');
|
||||
await expect(page.getByText('Game frontend')).toBeVisible();
|
||||
await page.getByTestId('request-deploy').click();
|
||||
|
||||
@@ -327,7 +327,7 @@ test('controls gateway deployment and rollback through the external controller q
|
||||
await installFixture(page, state);
|
||||
page.on('dialog', (dialog) => dialog.accept());
|
||||
|
||||
await page.goto('admin/server-operations');
|
||||
await page.goto('admin/releases');
|
||||
const panel = page.getByTestId('gateway-release-panel');
|
||||
await expect(panel).toBeVisible();
|
||||
await expect(panel).toContainText('aaaaaaaaaaaa');
|
||||
@@ -375,7 +375,7 @@ test('renders a failed reset, retries it as a new operation, and reaches success
|
||||
await installFixture(page, state);
|
||||
page.on('dialog', (dialog) => dialog.accept());
|
||||
|
||||
await page.goto('admin/server-operations');
|
||||
await page.goto('admin/releases');
|
||||
await expect(page.getByText('FAILED', { exact: true })).toBeVisible();
|
||||
await expect(page.getByRole('cell', { name: 'fedcba987654', exact: true })).toBeVisible();
|
||||
const failure = page.getByText(longError);
|
||||
|
||||
Reference in New Issue
Block a user