Files
Gnuboard7/tests/Playwright/specs/admin/settings-asset-url-mode-detect.spec.ts
T
HeuJung 65f1c70ffe refactor(admin-template): 에셋 파일 서빙 방식을 지역화에서 전용 카드로 분리
에셋 파일 서빙 방식은 언어·시간대와 무관한 인프라(서버 라우팅) 설정이라
지역화 카드에 묶이는 것이 의미상 어긋난다. tab_content_general 하위에
전용 카드(card_asset_serving)를 신설해 필드·자동 감지 버튼·인라인 안내를
이동하고, 라벨/설명을 카드 헤더로 승격해 다른 카드와 형태를 맞췄다.

배치 회귀 가드: 레이아웃 구조 테스트 + E2E 로 전용 카드에 항목이 있고
지역화 카드에는 없음을 단언.
2026-07-23 21:13:39 +09:00

96 lines
4.7 KiB
TypeScript

/**
* E2E: 환경설정 > 일반 — 자산 URL 방식 "자동 감지" 버튼 (#486)
*
* @scenario admin_settings_asset_url_mode_detect
* @effects detect_button_styled, detect_result_inline
*
* 배경: 자동 감지 버튼이 다른 버튼(모달 취소·푸터 저장)과 달리 btn 베이스 클래스가 빠져
* 평문처럼 보였고, 감지 결과가 토스트로 떠 사라져 버렸다. 버튼을 다른 버튼과 동일한 박스
* 스타일로 맞추고, 결과를 필드 하단 인라인 안내로 상시 노출하도록 바꾼 것을 브라우저에서 확인한다.
*
* 검증:
* 1. 감지 버튼이 btn 베이스 클래스를 갖고, 실제 렌더 박스가 관리자 다른 버튼과 동형이다
* 2. 버튼을 누르면 결과가 필드 하단 인라인 안내로 뜨고(토스트가 아니라), $t: 원문이 아닌 번역문이다
*/
import { test, expect, issueToken, authenticatePage } from '../../fixtures/auth';
const FIELD = '#field_asset_url_mode';
const DETECT_BUTTON = '#asset_url_mode_detect_button';
const RESULT_IDS = [
'#asset_url_mode_msg_extension',
'#asset_url_mode_msg_extensionless',
'#asset_url_mode_msg_unavailable',
];
/** 관리자 환경설정 일반 탭 진입 */
async function gotoGeneralTab(page: import('@playwright/test').Page): Promise<void> {
await page.goto('/admin/settings?tab=general');
await page.waitForLoadState('domcontentloaded', { timeout: 30_000 });
await expect(page.locator(DETECT_BUTTON)).toBeAttached({ timeout: 20_000 });
}
// @scenario tab=general, permitted=yes
// @effects detect_button_styled
test('#486 - 자동 감지 버튼이 관리자 다른 버튼과 동일한 btn 박스 스타일이다', async ({ page }) => {
const token = issueToken('core.settings.read', 'core.settings.update');
await authenticatePage(page, token);
await gotoGeneralTab(page);
expect(page.url()).not.toMatch(/\/admin\/login/);
const button = page.locator(DETECT_BUTTON);
await expect(button).toBeAttached();
// 에셋 서빙 방식은 언어/시간대와 무관한 인프라 설정이므로 지역화 카드가 아니라
// 전용 카드(#card_asset_serving)에 있어야 한다 — 배치 회귀 가드.
await expect(page.locator('#card_asset_serving')).toBeAttached();
await expect(page.locator(`#card_asset_serving ${FIELD}`)).toBeAttached();
await expect(page.locator(`#card_localization ${FIELD}`)).toHaveCount(0);
// btn 베이스 클래스 누락이 이 이슈의 원인이었다 (평문처럼 보임) — 클래스 회귀 가드.
await expect(button).toHaveClass(/\bbtn\b/);
await expect(button).toHaveClass(/btn-secondary/);
// 렌더 박스가 실제로 버튼처럼 보이는지: 테두리 또는 배경이 있어야 한다 (평문 텍스트가 아님).
const looksLikeButton = await button.evaluate((el) => {
const s = getComputedStyle(el as HTMLElement);
const hasBorder = s.borderTopWidth !== '0px' && s.borderTopStyle !== 'none';
const hasBg = s.backgroundColor !== 'rgba(0, 0, 0, 0)' && s.backgroundColor !== 'transparent';
const hasPad = parseFloat(s.paddingLeft) > 0 && parseFloat(s.paddingTop) > 0;
return (hasBorder || hasBg) && hasPad;
});
expect(looksLikeButton, '감지 버튼이 박스(테두리/배경/패딩) 없이 평문처럼 렌더된다').toBe(true);
});
// @scenario tab=general, permitted=yes
// @effects detect_result_inline
test('#486 - 감지 결과가 토스트가 아니라 필드 하단 인라인 안내로 뜬다', async ({ page }) => {
const token = issueToken('core.settings.read', 'core.settings.update');
await authenticatePage(page, token);
await gotoGeneralTab(page);
// 감지 전에는 어떤 결과 안내도 떠 있지 않다 (상시 노출이 아니라 감지 후에만).
for (const id of RESULT_IDS) {
await expect(page.locator(id)).toHaveCount(0);
}
await page.locator(DETECT_BUTTON).click();
// 결과 안내가 자산 URL 필드 안에(= 필드 하단 인라인) 뜬다. 환경에 따라 판정이 다르므로
// 세 결과 중 하나가 필드 내부에 나타나는 것으로 확인한다.
const inlineResult = page.locator(RESULT_IDS.map((id) => `${FIELD} ${id}`).join(', '));
await expect(inlineResult.first()).toBeVisible({ timeout: 15_000 });
// 인라인 문구는 다국어 키가 아니라 번역문이어야 한다 (회귀 가드).
const text = (await inlineResult.first().innerText()).trim();
expect(text).not.toContain('$t:');
expect(text.length).toBeGreaterThan(0);
// 결과가 토스트로는 발화되지 않는다 — 토스트 호스트에 감지 문구가 실리지 않는다.
const toastHost = page.locator('#global_toast, [data-testid="toast"], .toast');
if (await toastHost.count()) {
await expect(toastHost).not.toContainText(text);
}
});