// frontend/playwright/multimodal.spec.ts — 멀티모달 캡처(이미지 캡션 / 음성 폴백) → 분류 import { expect, test } from "@playwright/test"; import { resetSeed } from "./fixtures/seed-reset"; test.describe.configure({ mode: "serial" }); test.beforeEach(async () => { await resetSeed(); }); // 1x1 PNG const PNG = Buffer.from( "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", "base64", ); test("이미지 캡처 → 캡션 → 인박스 분류(image 행)", async ({ page }) => { await page.goto("/inbox"); await expect(page.getByRole("button", { name: "이미지로 캡처" })).toBeVisible(); await page.locator('.sb-cmd input[type="file"]').setInputFiles({ name: "clip.png", mimeType: "image/png", buffer: PNG, }); // 캡션 텍스트가 raw 로 채워진 image 캡처 행이 등장 + 분류 진행 const row = page.locator(".sb-cap", { hasText: "이미지 캡처" }).first(); await expect(row).toBeVisible(); await expect(row.locator(".cap-k.image")).toBeVisible(); }); test("마이크 미지원/거부 → 음성 폴백 문구로 캡처(graceful degrade)", async ({ page }) => { // getUserMedia 미지원 환경을 강제 → phase-4 톤 폴백 경로 결정적 검증 await page.addInitScript(() => { // getUserMedia 미지원 환경 시뮬레이션(mediaDevices 를 빈 객체로 치환) Object.defineProperty(navigator, "mediaDevices", { value: {}, configurable: true }); }); await page.goto("/inbox"); await page.getByRole("button", { name: "음성으로 캡처" }).click(); await expect( page.locator(".sb-cap", { hasText: "텍스트로 적어주세요" }).first(), ).toBeVisible(); });