Files
tech-log-frontend/tests/visual/tech-log.visual.spec.ts
T
DongHyeonkaandClaude Opus 5 60c8c82097 fix: 편집과 미리보기를 한 화면에서 보고, 저장·게시를 아래에 고정한다
편집기는 탭이었다. 고친 것이 어떻게 보이는지 확인하려면 편집하던 자리를 화면에서
치워야 했고, 돌아오면 스크롤 위치도 잃었다. 두 패널을 나란히 두면 그 왕복이 통째로
없어진다.

작업 상태와 저장·게시는 오른쪽 세로 rail 이었다. 그 자리는 편집기와 미리보기가 함께
쓸 가로 폭을 가져갔고, 편집 칸이 길어질수록 rail 은 위에 붙은 채 본문만 멀어졌다.
화면 아래에 고정하면 폭을 돌려주면서 스크롤 위치와 무관하게 손이 닿는다.

탭을 없앴으므로 각 패널이 스스로 이름을 가져야 한다. `aria-labelledby` 로 제목을
가리켜 landmark 로 만든다 — 탭 목록이 하던 "여기는 편집, 저기는 미리보기" 안내를
대신한다. `aside` 와 「작업 상태」라는 이름은 그대로 둔다. 자리가 바뀐 것이지 이
묶음이 무엇인지가 바뀐 것은 아니다.

미리보기가 늘 떠 있게 되면서 Picker 테스트의 단언도 함께 고쳤다. 「Picker 가
좁아졌는가」를 화면 전체에 묻고 있었는데, 이제는 미리보기 쪽의 같은 Asset 까지
세게 된다. 그 김에 `.studio-asset-panel` 의 `aria-labelledby` 가 role 없는 div 에서
아무 이름도 만들지 못하던 것도 `role="group"` 으로 실효화했다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0189NzCryfeqDzS81EWidnBx
2026-08-26 15:49:18 +09:00

167 lines
6.4 KiB
TypeScript

import { expect, test } from "../support/browser/strict-browser-test.ts";
import {
gotoTechLog,
TECH_LOG_BREAKPOINT_WIDTHS,
TECH_LOG_CANONICAL_ROUTES,
TECH_LOG_PUBLIC_FIXTURE_PATHS,
TECH_LOG_STUDIO_STATE_PATHS,
TECH_LOG_UNKNOWN_PUBLIC_PATHS,
} from "../support/browser/tech-log-fixtures.ts";
function fixtureSnapshotName(path: string) {
return `tech-log-public-fixture-${path.slice(1).replaceAll("/", "-")}-1440.png`;
}
for (const route of TECH_LOG_CANONICAL_ROUTES) {
for (const width of [360, 1440] as const) {
test(`${route.routeId} matches the reviewed ${width}px visual`, async ({ page }) => {
await page.setViewportSize({ width, height: 1000 });
await gotoTechLog(page, route.path);
await expect(page).toHaveScreenshot(`${route.routeId.toLowerCase()}-${width}.png`, {
fullPage: true,
});
});
}
}
for (const path of TECH_LOG_PUBLIC_FIXTURE_PATHS) {
test(`known Public fixture ${path} matches its reviewed wide visual`, async ({
page,
}) => {
await page.setViewportSize({ width: 1440, height: 1000 });
await gotoTechLog(page, path);
await expect(page).toHaveScreenshot(fixtureSnapshotName(path), {
fullPage: true,
});
});
}
for (const width of TECH_LOG_BREAKPOINT_WIDTHS) {
test(`Public home matches the reviewed ${width}px breakpoint visual`, async ({ page }) => {
await page.setViewportSize({ width, height: 1000 });
await gotoTechLog(page, "/");
await expect(page).toHaveScreenshot(`tech-log-home-breakpoint-${width}.png`, {
fullPage: true,
});
});
}
for (const [state, path] of TECH_LOG_STUDIO_STATE_PATHS) {
test(`Studio ${state} matches its reviewed wide state visual`, async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 1000 });
await gotoTechLog(page, path);
await expect(page).toHaveScreenshot(`tech-log-studio-${state}-1440.png`, {
fullPage: true,
});
});
}
for (const [route, path] of TECH_LOG_UNKNOWN_PUBLIC_PATHS) {
for (const width of [360, 1440] as const) {
test(`unknown Public ${route} matches the source ${width}px response`, async ({ page }) => {
await page.setViewportSize({ width, height: 1000 });
await gotoTechLog(page, path);
await expect(page.getByText("Not Found", { exact: true })).toBeVisible();
await expect(page.locator(".site-frame")).toHaveCount(0);
await expect(page).toHaveScreenshot(`tech-log-unknown-${route}-${width}.png`, {
fullPage: true,
});
});
}
}
test("open Public search dialog matches the source without a mask", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await gotoTechLog(page, "/");
await page.getByRole("button", { name: "검색 열기" }).click();
await expect(page.getByRole("dialog", { name: "검색" })).toBeVisible();
await expect(page).toHaveScreenshot("tech-log-search-dialog-390.png", {
fullPage: true,
});
});
test("open Studio mobile menu matches the source without a mask", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await gotoTechLog(page, "/studio");
await page.getByRole("button", { name: "Studio 메뉴 열기" }).click();
await expect(page).toHaveScreenshot("tech-log-studio-mobile-menu-390.png", {
fullPage: true,
});
});
test("Studio immediate preview matches the source without a mask", async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 1000 });
await gotoTechLog(
page,
"/studio/documents/11111111-1111-4111-8111-111111111111/edit",
);
await expect(page.getByRole("region", { name: "즉시 미리보기" })).toBeVisible();
await expect(page).toHaveScreenshot("tech-log-studio-immediate-preview-1440.png", {
fullPage: true,
});
});
test("Studio dirty-leave dialog matches the source without a mask", async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 1000 });
await gotoTechLog(
page,
"/studio/documents/11111111-1111-4111-8111-111111111111/edit",
);
await page.getByLabel("요약").fill("저장하지 않은 시각 검증 변경");
await page.getByRole("link", { name: "게시 기록", exact: true }).first().click();
await expect(
page.getByRole("dialog", { name: "저장하지 않은 변경" }),
).toBeVisible();
await expect(page).toHaveScreenshot("tech-log-studio-dirty-leave-dialog-1440.png", {
fullPage: true,
});
});
test("Studio newly-created current preview matches the source without a mask", async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 1000 });
await gotoTechLog(
page,
"/studio/documents/11111111-1111-4111-8111-111111111113/preview",
);
await page.getByRole("button", { name: "Public Preview 만들기" }).click();
await expect(page.getByText("현재 저장 버전의 Public Preview입니다.")).toBeVisible();
await expect(page).toHaveScreenshot("tech-log-studio-current-preview-created-1440.png", {
fullPage: true,
});
});
test("Studio unpublish confirmation matches the source without a mask", async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 1000 });
await gotoTechLog(page, "/studio/publications");
await page
.getByRole("button", {
name: /컬렉션 Fetch Join과 페이징은 왜 충돌하는가 게시 취소/,
})
.click();
await expect(page.getByRole("dialog", { name: "게시를 취소할까요?" })).toBeVisible();
await expect(page).toHaveScreenshot("tech-log-studio-unpublish-dialog-1440.png", {
fullPage: true,
});
});
test("Studio warning acknowledgement publish state matches the source without a mask", async ({
page,
}) => {
await page.setViewportSize({ width: 1440, height: 1000 });
await gotoTechLog(
page,
"/studio/documents/11111111-1111-4111-8111-111111111116/validation",
);
await page.getByRole("button", { name: "검증하기" }).click();
await page.getByText("경고를 확인하고 Preview를 만들 수 있습니다").waitFor();
await page.getByRole("link", { name: "Public Preview 만들기" }).click();
await page.getByRole("button", { name: "Public Preview 만들기" }).click();
await page.getByText("현재 저장 버전의 Public Preview입니다.").waitFor();
await page.getByRole("link", { name: "게시 준비로 이동" }).click();
await page.getByRole("checkbox").check();
await expect(page.getByRole("button", { name: /게시$/ })).toBeEnabled();
await expect(page).toHaveScreenshot("tech-log-studio-warning-acknowledged-1440.png", {
fullPage: true,
});
});