fix: version neural audio requests for v0.1.1

This commit is contained in:
oz
2026-10-14 07:31:47 +00:00
parent a92b2ba1e9
commit c92660b514
38 changed files with 3400 additions and 42 deletions
+74 -27
View File
@@ -1,6 +1,7 @@
import { expect, test } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";
import { WORDS } from "../../src/content";
import { isAudioRequest } from "./audio-requests";
// 실제 WAV를 디코드하고 Web Audio의 ended를 기다린다. 모의 엔진을 사용한
// 컴포넌트 검증과 달리 배포 산출물·자동 재생 정책·레이아웃을 함께 확인한다.
@@ -10,11 +11,14 @@ test("실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부
const external: string[] = [];
const errors: string[] = [];
let audioPath = "";
const audioUrls: URL[] = [];
page.on("request", (request) => {
if (!request.url().startsWith("http://127.0.0.1:4173"))
external.push(request.url());
if (request.url().endsWith(".wav") && !request.url().endsWith("intro.wav"))
audioPath = new URL(request.url()).pathname;
const url = new URL(request.url());
if (url.origin !== "http://127.0.0.1:4173") external.push(request.url());
if (isAudioRequest(url)) {
audioUrls.push(url);
if (url.pathname !== "/audio/intro.wav") audioPath = url.pathname;
}
});
page.on("pageerror", (error) => errors.push(error.message));
await page.goto("/");
@@ -29,12 +33,17 @@ test("실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부
).toBe(true);
await page.evaluate(() => window.scrollTo(0, 0));
await page.screenshot({
path: `.agent/evidence-v0.1.1/${info.project.name}-home.png`,
path: info.outputPath("home.png"),
fullPage: true,
});
expect((await new AxeBuilder({ page }).analyze()).violations).toEqual([]);
await page.emulateMedia({ reducedMotion: "reduce" });
await page.getByRole("button", { name: "게임 시작 · 소리 듣기" }).click();
await expect.poll(() => audioUrls.length).toBe(2);
expect(audioUrls.map((url) => url.searchParams.get("v"))).toEqual([
"0.1.1",
"0.1.1",
]);
await expect(page.getByRole("button", { name: /듣는 중/ })).toBeDisabled();
await expect(page.locator(".sound-wave.active i").first()).toHaveCSS(
"animation-name",
@@ -67,7 +76,7 @@ test("실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부
).toBe(true);
await page.evaluate(() => window.scrollTo(0, 0));
await page.screenshot({
path: `.agent/evidence-v0.1.1/${info.project.name}-game.png`,
path: info.outputPath("game.png"),
fullPage: true,
});
expect((await new AxeBuilder({ page }).analyze()).violations).toEqual([]);
@@ -99,7 +108,7 @@ test("실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부
).toBe(true);
await page.evaluate(() => window.scrollTo(0, 0));
await page.screenshot({
path: `.agent/evidence-v0.1.1/${info.project.name}-results.png`,
path: info.outputPath("results.png"),
fullPage: true,
});
expect((await new AxeBuilder({ page }).analyze()).violations).toEqual([]);
@@ -107,28 +116,66 @@ test("실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부
await expect(
page.getByRole("button", { name: "게임 시작 · 소리 듣기" }),
).toBeVisible();
expect(new Set(audioUrls.map((url) => url.searchParams.get("v")))).toEqual(
new Set(["0.1.1"]),
);
expect(
audioUrls.filter((url) => url.pathname !== "/audio/intro.wav"),
).toHaveLength(11);
expect(external).toEqual([]);
expect(errors).toEqual([]);
});
test("오디오 HTTP 실패를 화면에 표시하고 재시도한다", async ({ page }) => {
await page.route("**/audio/*.wav", (route) =>
route.fulfill({ status: 404, body: "missing" }),
);
await page.goto("/");
await page.getByRole("button", { name: "게임 시작 · 소리 듣기" }).click();
await expect(page.getByRole("alert")).toContainText(
"소리를 재생하지 못했어요",
);
await page.unroute("**/audio/*.wav");
await page.getByRole("button", { name: "다시 시도" }).click();
// 재시도는 안내부터 재생한다. v0.1.1 최대 7.056 + 11.256 + 시작·전환 0.4초에
// 로드·디코드·병렬 브라우저 스케줄링 여유를 더해 이 assertion만 30초로 제한한다.
await expect(page.getByRole("group", { name: "답 선택" })).toBeVisible({
timeout: 30_000,
for (const failed of ["intro", "target"]) {
test(`${failed} WAV query URL의 404를 표시하고 같은 URL로 재시도·정답 mapping한다`, async ({
page,
}) => {
const audioUrls: string[] = [];
const missing: string[] = [];
page.on("request", (request) => {
if (isAudioRequest(new URL(request.url()))) audioUrls.push(request.url());
});
const matchesFailure = (url: URL) =>
isAudioRequest(url) &&
(failed === "intro"
? url.pathname === "/audio/intro.wav"
: url.pathname !== "/audio/intro.wav");
await page.route(matchesFailure, (route) => {
missing.push(route.request().url());
return route.fulfill({ status: 404, body: "missing" });
});
await page.goto("/");
await page.getByRole("button", { name: "게임 시작 · 소리 듣기" }).click();
await expect(page.getByRole("alert")).toContainText(
"소리를 재생하지 못했어요",
);
expect(missing).toHaveLength(1);
expect(new URL(missing[0]!).searchParams.get("v")).toBe("0.1.1");
expect(audioUrls).toHaveLength(2);
await page.unroute(matchesFailure);
await page.getByRole("button", { name: "다시 시도" }).click();
// 안내 7.056 + 최대 목표 11.256초와 로드·디코드 여유를 포함한다.
await expect(page.getByRole("group", { name: "답 선택" })).toBeVisible({
timeout: 30_000,
});
expect(audioUrls).toHaveLength(4);
expect(audioUrls.slice(2).sort()).toEqual(audioUrls.slice(0, 2).sort());
expect(
new Set(audioUrls.map((url) => new URL(url).searchParams.get("v"))),
).toEqual(new Set(["0.1.1"]));
const targetPath = audioUrls
.map((url) => new URL(url).pathname)
.find((path) => path !== "/audio/intro.wav")!;
const answer = WORDS.find((word) =>
targetPath.endsWith(`-${word.id}.wav`),
)!.answer;
await page.getByRole("button", { name: new RegExp(answer) }).click();
await expect(
page.getByRole("heading", { name: "정답이에요!" }),
).toBeVisible();
await page.getByRole("button", { name: "처음으로" }).click();
await expect(
page.getByRole("button", { name: "게임 시작 · 소리 듣기" }),
).toBeFocused();
});
await page.getByRole("button", { name: "처음으로" }).click();
await expect(
page.getByRole("button", { name: "게임 시작 · 소리 듣기" }),
).toBeFocused();
});
}