# Instructions

- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.

# Test info

- Name: review-fixes.spec.ts >> 리뷰: 한국어 모드·결과 어절은 320px와 360px에서도 갈라지거나 넘치지 않는다
- Location: tests/browser/review-fixes.spec.ts:24:1

# Error details

```
Error: expect(received).toEqual(expected) // deep equality

- Expected  - 1
+ Received  + 4

- Array []
+ Array [
+   "split: 탐험에서도",
+   "split: 라운드",
+ ]
```

```
Error: expect(received).toEqual(expected) // deep equality

- Expected  - 1
+ Received  + 3

- Array []
+ Array [
+   "split: 입력",
+ ]
```

```
Error: expect(received).toEqual(expected) // deep equality

- Expected  - 1
+ Received  + 4

- Array []
+ Array [
+   "split: 만나요",
+   "split: 최대",
+ ]
```

# Page snapshot

```yaml
- generic [ref=f1e2]:
  - link "본문으로 건너뛰기" [ref=f1e3] [cursor=pointer]:
    - /url: "#main"
  - banner [ref=f1e4]:
    - generic [ref=f1e5]:
      - heading "단어를 맞춰라" [level=1] [ref=f1e11]
      - generic [ref=f1e12]: v0.2.3
    - generic: 소리에 집중하는 시간
  - main [ref=f1e13]:
    - generic [ref=f1e14]:
      - text: 지금까지의 소리 탐험
      - generic [ref=f1e15]: S
      - heading "문제 풀이를 종료했어요." [active] [level=2] [ref=f1e23]
      - paragraph [ref=f1e24]: 작은 소리에 집중한 당신, 다음 탐험에서도 만나요.
      - paragraph [ref=f1e25]: 응답 0문항 · 정답 0문항
      - generic [ref=f1e26]:
        - generic [ref=f1e27]:
          - strong [ref=f1e28]: "0"
          - text: 총 점수
        - generic [ref=f1e29]:
          - strong [ref=f1e30]: 0%
          - generic [ref=f1e31]: 정답률 · 0/0
        - generic [ref=f1e32]:
          - strong [ref=f1e33]: 0회
          - text: 최고 연속 정답
      - table "난이도별 결과" [ref=f1e34]:
        - rowgroup [ref=f1e35]:
          - row [ref=f1e36]:
            - columnheader "난이도" [ref=f1e37]
            - columnheader "SNR" [ref=f1e38]
            - columnheader "맞힌 문제" [ref=f1e39]
        - rowgroup [ref=f1e40]:
          - row [ref=f1e41]:
            - rowheader "1단계" [ref=f1e42]
            - cell "SNR +6 dB" [ref=f1e43]
            - cell "0 / 0" [ref=f1e44]
          - row [ref=f1e45]:
            - rowheader "2단계" [ref=f1e46]
            - cell "SNR +3 dB" [ref=f1e47]
            - cell "0 / 0" [ref=f1e48]
          - row [ref=f1e49]:
            - rowheader "3단계" [ref=f1e50]
            - cell "SNR 0 dB" [ref=f1e51]
            - cell "0 / 0" [ref=f1e52]
          - row [ref=f1e53]:
            - rowheader "4단계" [ref=f1e54]
            - cell "SNR -3 dB" [ref=f1e55]
            - cell "0 / 0" [ref=f1e56]
          - row [ref=f1e57]:
            - rowheader "5단계" [ref=f1e58]
            - cell "SNR -6 dB" [ref=f1e59]
            - cell "0 / 0" [ref=f1e60]
      - paragraph [ref=f1e61]: 단계 × 100점 + 연속 정답 보너스 (라운드 내 최대 20점)
      - button "처음 화면으로" [ref=f1e62] [cursor=pointer]
    - generic [ref=f1e63]:
      - generic [ref=f1e64]: ◖)) 전체 음량
      - slider "전체 음량" [ref=f1e65] [cursor=pointer]: "35"
      - status [ref=f1e66]: 35%
  - contentinfo [ref=f1e67]:
    - paragraph [ref=f1e68]: Designed by OZ at Heat-T Medical Inc.
    - paragraph [ref=f1e69]: 청각적 주의·식별 게임 · 의료적 진단이나 치료를 제공하지 않습니다.
    - generic [ref=f1e70]: 개인정보 수집 없이, 오직 플레이만.
    - link "환경음 출처·라이선스" [ref=f1e71] [cursor=pointer]:
      - /url: /AMBIENCE_NOTICES.md
```

# Test source

```ts
  1  | import { expect, test, type Page } from "@playwright/test";
  2  | import { WORDS } from "../../src/content";
  3  | 
  4  | // 글자별 스타일 추측 대신 실제 Range 줄 상자를 비교해 어절 내부 분리를 검출한다.
  5  | async function copyLayout(page: Page, selector: string) {
  6  |   return page.locator(selector).evaluateAll((elements) => elements.flatMap((element) => {
  7  |     const problems: string[] = [];
  8  |     const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
  9  |     while (walker.nextNode()) {
  10 |       const node = walker.currentNode;
  11 |       for (const match of (node.textContent ?? "").matchAll(/[가-힣]+/g)) {
  12 |         const range = document.createRange();
  13 |         range.setStart(node, match.index!);
  14 |         range.setEnd(node, match.index! + match[0].length);
  15 |         if (new Set(Array.from(range.getClientRects(), (r) => r.y)).size > 1)
  16 |           problems.push(`split: ${match[0]}`);
  17 |       }
  18 |     }
  19 |     if (element.scrollWidth > element.clientWidth) problems.push(`overflow: ${element.textContent}`);
  20 |     return problems;
  21 |   }));
  22 | }
  23 | 
  24 | test("리뷰: 한국어 모드·결과 어절은 320px와 360px에서도 갈라지거나 넘치지 않는다", async ({ page }, info) => {
  25 |   for (const width of [320, 360]) {
  26 |     await page.setViewportSize({ width, height: 800 });
  27 |     await page.goto("/");
  28 |     expect.soft(await copyLayout(page, ".mode-option small")).toEqual([]);
  29 |     await page.screenshot({ path: info.outputPath(`copy-${width}-home.png`), fullPage: true });
  30 |     await page.getByRole("button", { name: "게임 시작 · 소리 듣기" }).click();
  31 |     await page.getByRole("button", { name: "문제 풀이 종료" }).click();
> 32 |     expect.soft(await copyLayout(page, ".results-panel h2, .results-panel p, .stats span, .results-panel th, .results-panel td")).toEqual([]);
     |                                                                                                                                   ^ Error: expect(received).toEqual(expected) // deep equality
  33 |     expect.soft(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
  34 |     await page.screenshot({ path: info.outputPath(`copy-${width}-results.png`), fullPage: true });
  35 |   }
  36 | });
  37 | 
  38 | test("리뷰: 실제 진행 막대는 현재 문항의 1/3·2/3·3/3을 채운다", async ({ page }, info) => {
  39 |   let target = "";
  40 |   page.on("request", (r) => {
  41 |     const path = new URL(r.url()).pathname;
  42 |     if (path.startsWith("/audio/") && path !== "/audio/intro.wav") target = path;
  43 |   });
  44 |   await page.goto("/");
  45 |   await page.getByRole("radio", { name: "보기에서 선택" }).check();
  46 |   await page.getByRole("button", { name: "게임 시작 · 소리 듣기" }).click();
  47 |   for (let index = 0; index < 3; index++) {
  48 |     const bar = page.getByRole("progressbar");
  49 |     expect.soft(await bar.evaluate((el) => (el as HTMLProgressElement).position)).toBeCloseTo((index + 1) / 3);
  50 |     await expect.soft(bar).toHaveAccessibleName("현재 문항 진행률", { timeout: 1000 });
  51 |     await bar.screenshot({ path: info.outputPath(`progress-${index + 1}.png`) });
  52 |     if (index) await page.getByRole("button", { name: /문장 듣기/ }).click();
  53 |     await expect(page.getByRole("group", { name: "답 선택" })).toBeVisible({ timeout: 30000 });
  54 |     const answer = WORDS.find((w) => target.endsWith(`-${w.id}.wav`))!.answer;
  55 |     await page.getByRole("button", { name: new RegExp(answer) }).click();
  56 |     await page.getByRole("button", { name: index === 2 ? /결과 보기/ : /다음 문제/ }).click();
  57 |   }
  58 |   await page.screenshot({ path: info.outputPath("normal-results.png"), fullPage: true });
  59 |   expect(await copyLayout(page, ".results-panel h2, .results-panel p, .stats span")).toEqual([]);
  60 | });
  61 | 
```