|
|
// 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();
|
|
|
|
|
|
// 작업 페이지(개인 스코프)에서 확인 — 연합
|
|
|
await page.goto("/tasks");
|
|
|
await page.locator(".tree-row.folder", { hasText: "개인" }).click();
|
|
|
await expect(
|
|
|
page.locator(".kcard-title", { hasText: "다음 주에 한국 놀러가는 비행기 티켓 사기" }),
|
|
|
).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([]);
|
|
|
});
|