You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

83 lines
4.0 KiB
TypeScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// frontend/playwright/inbox.spec.ts
// 백엔드를 LLM_PROVIDER=heuristic 로 기동한 상태를 가정(결정성·속도·폴백 배지).
// 캡처는 인박스 시드를 변경하므로 단일 워커 직렬.
import AxeBuilder from "@axe-core/playwright";
import { expect, test } from "@playwright/test";
test.describe.configure({ mode: "serial" });
test("캡처 → 분류 → 좋아요 → 작업 페이지에 등장(연합)", async ({ page }) => {
await page.goto("/inbox");
const input = page.getByLabel("인박스에 빠르게 캡처");
await input.fill("다음 주에 한국 놀러가는 비행기 티켓 사기");
await input.press("Enter");
const fresh = page.locator(".sb-cap.fresh").first();
await expect(fresh.getByText("작업", { exact: true })).toBeVisible();
await expect(fresh.locator(".r-chip.proj")).toContainText("개인 여행 — 한국");
await expect(fresh.locator(".r-chip.auto")).toContainText("가격 추적 알림 켜둠");
await fresh.getByRole("button", { name: /좋아요, 그렇게 해줘/ }).click();
await expect(fresh.locator(".sb-done")).toBeVisible();
// 작업 페이지(개인 스코프)에서 확인 — 연합 (.first(): 전체 스위트에서 중복 캡처 대비)
await page.goto("/tasks");
await page.locator(".tree-row.folder", { hasText: "개인" }).click();
await expect(
page.locator(".kcard-title", { hasText: "다음 주에 한국 놀러가는 비행기 티켓 사기" }).first(),
).toBeVisible();
});
test("다르게 분류 — 타입 순환 task→event", async ({ page }) => {
await page.goto("/inbox");
const input = page.getByLabel("인박스에 빠르게 캡처");
await input.fill("리포트 회신 보내기");
await input.press("Enter");
const fresh = page.locator(".sb-cap.fresh").first();
await expect(fresh.getByText("작업", { exact: true })).toBeVisible();
await fresh.getByRole("button", { name: /다르게 분류/ }).click();
await expect(fresh.getByText("일정", { exact: true })).toBeVisible();
});
const GOLDEN = [
{ raw: "다음 주에 한국 놀러가는 비행기 티켓 사기", type: "작업", proj: "개인 여행 — 한국" },
{ raw: "수요일 11시 자전거 수리 맡기기", type: "일정", proj: "개인 캘린더" },
{ raw: "엄마 생신 선물 미리 알아보기", type: "작업", proj: "가족" },
{ raw: "온보딩 환영 화면에 짧은 애니메이션 넣으면 어떨까", type: "아이디어", proj: "아이디어 보드" },
];
for (const g of GOLDEN) {
test(`골든: "${g.raw}" → ${g.type}`, async ({ page }) => {
await page.goto("/inbox");
const input = page.getByLabel("인박스에 빠르게 캡처");
await input.fill(g.raw);
await input.press("Enter");
const fresh = page.locator(".sb-cap.fresh").first();
await expect(fresh.getByText(g.type, { exact: true })).toBeVisible();
await expect(fresh.locator(".r-chip.proj")).toContainText(g.proj);
});
}
test("LLM 폴백 — 규칙 기반(오프라인) 배지", async ({ page }) => {
await page.goto("/inbox");
const input = page.getByLabel("인박스에 빠르게 캡처");
await input.fill("엄마 생신 선물 미리 알아보기");
await input.press("Enter");
const fresh = page.locator(".sb-cap.fresh").first();
await expect(fresh.getByText("작업", { exact: true })).toBeVisible();
await expect(fresh.getByText("규칙 기반(오프라인)")).toBeVisible();
});
test("음성 스텁 — voice 아이콘 행", async ({ page }) => {
await page.goto("/inbox");
await page.getByLabel(/음성으로 캡처/).click();
await expect(page.locator(".cap-k.voice").first()).toBeVisible();
});
test("a11y — axe 위반 없음(color-contrast 제외)", async ({ page }) => {
await page.goto("/inbox");
await expect(page.getByRole("heading", { name: "스마트 인박스", exact: true })).toBeVisible();
const r = await new AxeBuilder({ page })
.withTags(["wcag2a", "wcag2aa"])
.disableRules(["color-contrast"])
.analyze();
expect(r.violations).toEqual([]);
});