feat: release Find-Word v0.1

This commit is contained in:
oz
2026-10-14 06:19:24 +00:00
parent 28ee2ce498
commit 1c0069c367
258 changed files with 21509 additions and 0 deletions
+44
View File
@@ -0,0 +1,44 @@
# ACCEPTANCE — Find-Word v0.1
## 제품
- [ ] H1 또는 브랜드 영역에 `단어를 맞춰라` 표시
- [ ] `Designed by OZ at Heat-T Medical Inc.` 표시
- [ ] v0.1 표시
- [ ] 게임 시작부터 결과·재시작까지 완결된 흐름
- [ ] 객관식과 주관식 모두 실제 플레이 가능
- [ ] 최소 10개 문제 유형이 코드·데이터·테스트로 확인됨
- [ ] 최소 8개 배경 환경 프리셋
- [ ] 최소 5개 dB 기반 난이도
- [ ] 문제 조합이 고정 10문제를 넘어 확장 가능
- [ ] 사전 단어 기반 오답과 변형 규칙 metadata
## 오디오
- [ ] Web Audio API로 배경과 목표 음성 gain을 분리
- [ ] 난이도별 상대 dB가 수학적으로 검증됨
- [ ] 첫 재생은 사용자 gesture 이후 실행
- [ ] 재생 중 중복 시작·다음 문제 전환 race 방지
- [ ] 오디오 실패 UI와 재시도
- [ ] provenance 문서와 생성 절차
- [ ] 런타임 외부 TTS/API 호출 없음
## 품질
- [ ] strict TypeScript
- [ ] 직접 작성한 소스에 상세한 한국어 주석
- [ ] unit/component 테스트에서 문제 유형 수, 선택지 무결성, 정답 판정, 주관식 정규화, 난이도 dB, 점수, 오디오 상태 검증
- [ ] lint 통과
- [ ] test 통과
- [ ] production build 통과
- [ ] 접근성 이름, 키보드 focus, reduced motion
- [ ] 360px 모바일과 1280px 데스크톱 레이아웃
- [ ] secret scan 무검출
## 문서
- [ ] README에 개발·테스트·빌드 방법
- [ ] ARCHITECTURE에 데이터·상태·오디오 설계
- [ ] AUDIO_PROVENANCE에 생성·라이선스·재생 방식
- [ ] DEPLOYMENT에 Nginx static 배포와 SPA fallback
- [ ] `.agent/RESULT.md`에 실제 RED/GREEN 및 최종 검증 기록
+31
View File
@@ -0,0 +1,31 @@
# CONTEXT — Find-Word v0.1
## 저장소와 책임 경계
- canonical repository: `https://source.ibit.kr/OZ-Group/Find-Word`
- 작업 브랜치: `feat/v0.1`
- 현재 worktree: `/home/hermes/worktrees/find-word-v0.1`
- Codex는 이 worktree만 수정한다.
- Neo가 최종 diff 검토, 테스트, source push, Heart 서버 배포, Nginx/TLS/DNS 검증을 담당한다.
## 배포 예정 환경
- Ubuntu 24.04
- Nginx 1.24
- Node.js 22는 설치되어 있으나 프로덕션은 정적 `dist/`를 Nginx가 직접 제공하는 구성을 선호한다.
- 예정 도메인 `findword.heart-m.com`은 현재 Heart 서버 IP로 DNS 해석된다.
- 배포 파일·서비스·포트는 아직 존재하지 않는다.
## 제품 해석
- “4개의 단어”를 우선한다. 정답 1개와 변형 규칙이 서로 다른 사전 단어 오답 3개를 표시한다.
- 사용자 지정 표기를 해석하거나 교정하지 않는다. UI·README·문서·테스트에는 정확히 `Designed by OZ at Heat-T Medical Inc.`를 사용한다.
- 전체 판매/의료 판단 서비스가 아니라 청각적 주의·식별 게임이다. 의료 효과·치료·진단을 주장하지 않는다.
- 오디오 난이도는 디지털 gain의 상대 dB로 계산하고 UI에 난이도와 상대 dB를 표시한다.
## 보안·라이선스
- 외부 CDN·분석·런타임 API 의존을 두지 않는다.
- 외부 음원을 다운로드할 경우 명시적 라이선스와 원본 URL이 필요하다. 불확실하면 직접 합성한다.
- 앱은 사용자 입력을 HTML로 삽입하지 않는다.
- 결과는 localStorage에 선택적으로 저장할 수 있으나 개인정보는 저장하지 않는다.
+410
View File
@@ -0,0 +1,410 @@
# Find-Word v0.1 작업 결과
작성일: 2026-10-14 UTC. 작업 위치: `/home/hermes/worktrees/find-word-v0.1`.
입력 문서 `.agent/CONTEXT.md`, `TASK.md`, `ACCEPTANCE.md`를 모두 읽고 적용했다.
기존 입력 문서는 수정하지 않았다.
## 구현 결과
- `단어를 맞춰라`, `Designed by OZ at Heat-T Medical Inc.`, `v0.1` 표시.
- 반응형 시작·게임·결과 화면, 10문제 완주, 재시작, 다시 듣기, 중단, 진행률.
- 객관식·주관식·혼합 모드, 10유형 × 10단어 = 100개 저장 문장.
- 4지선다의 고유한 정답 1개·사전 단어 오답 3개, 변형 규칙과 예상 혼동 난이도.
- 8개 합성 배경 환경, +6/+3/0/−3/−6 dB의 5단계 상대 음량.
- 사용자 gesture에서 AudioContext 시작, 음성·배경 gain 분리, 안내 fade,
재생 중 경합 방지, 취소된 로드의 늦은 완료 무시, 오류와 재시도.
- 점수·정답률·연속 정답·단계별 결과, 한글 NFC/공백/문장부호 정규화,
한글 조합 중 Enter·재시작·재청취의 입력 상태 보호.
- 키보드/터치, 질문·피드백·결과 focus, ARIA, reduced motion, 색 대비.
- TypeScript strict, 직접 작성 소스 22개 파일의 한국어 설계 설명.
- 직접 합성 WAV 101개(10,864,582바이트), 결정론적 배경 합성,
생성 도구 실측 버전·원문·파일 해시 manifest.
- 런타임 외부 CDN/TTS/API/분석 요청 없음. 개인정보 저장·로그인·추적 없음.
서버·DNS·인증정보에 접근하지 않았으며 **배포, Git commit, Git push를 실행하지 않았다.**
Playwright의 localhost preview는 개발 검증용으로만 실행했다.
## 실제 strict TDD 기록
먼저 테스트와 타입 계약/비동작 stub을 작성하고 RED 명령의 종료 코드 **1**을
확인한 뒤 최소 동작을 구현했다. 각 GREEN은 종료 코드 **0**이다. 아래 원문
로그는 `evidence/`에 보관했다. 테스트를 건너뛰거나 실패 조건을 지워 GREEN으로
만들지 않았다. 초기 stub의 throw는 구현 전 상태를 표현한다.
| 순서·핵심 동작 | 실제 RED 명령·결과 | 최소 구현 뒤 GREEN 명령·결과 | 원문 로그 |
| --- | --- | --- | --- |
| 01 데이터: 10유형·단어 무결성·8환경·5단계 | `npm test -- tests/content.test.ts`: **3 failed**, 빈 목록의 개수/환경 불일치 | 같은 명령: **3 passed** | [RED](evidence/01-content-red.log), [GREEN](evidence/01-content-green.log) |
| 02 출제·판정·게임 전이·점수 | `npm test -- tests/game.test.ts`: **6 failed**, 출제 미구현·정규화 불일치 | 같은 명령: **6 passed** | [RED](evidence/02-game-red.log), [GREEN](evidence/02-game-green.log) |
| 03 dB·RMS·결정론적 환경 합성 | `npm test -- tests/dsp.test.ts`: **3 failed**, gain=1·잘못된 RMS·무음 | 같은 명령: **3 passed** | [RED](evidence/03-dsp-red.log), [GREEN](evidence/03-dsp-green.log) |
| 04 오디오 gesture·gain·fade·중복 시작·취소·실패·음량 | `npm test -- tests/audio.test.ts`: **5 failed**, 오디오 미구현 | 같은 명령: **5 passed** | [RED](evidence/04-audio-red.log), [GREEN](evidence/04-audio-green.log) |
| 05 실제 저장 음성·원문·해시·포맷·크기 | `npm test -- tests/assets.test.ts`: **1 failed**, manifest 없음(ENOENT) | `npm run audio:generate` 후 `npm test -- tests/App.test.tsx tests/assets.test.ts`: **2 files / 7 passed** (음성 1 + UI 6) | [RED](evidence/05-assets-red.log), [GREEN](evidence/06-ui-05-assets-green.log) |
| 06 시작→재생→답변→결과→재시작·오류·포커스·보안 출력 | `npm test -- tests/App.test.tsx`: **6 failed**, 빈 화면에서 브랜드/시작/입력 역할 없음 | 위 결합 명령: **7 passed** 중 UI **6 passed** | [RED](evidence/06-ui-red.log), [GREEN](evidence/06-ui-05-assets-green.log) |
| 07 production 음성 검증기 | `npm test -- tests/build.test.ts`: **1 failed**, 자산 수 0 ≠ 101 | 같은 명령: **1 passed**, 정상 통과 및 파일 변조·누락 시 실제 실패 검증 | [RED](evidence/07-build-red.log), [GREEN](evidence/07-build-green.log) |
| 08 오답 변형별 혼동 난이도 | `npm test -- tests/content.test.ts`: **1 failed / 3 passed**, 순위 undefined | 같은 명령: **4 passed** | [RED](evidence/08-rule-red.log), [GREEN](evidence/08-rule-green.log) |
| 09 한글 조합 중 제출 방지 | `npm test -- tests/App.test.tsx`: **1 failed / 6 passed**, 조합 중 정답 피드백이 나타남 | 같은 명령: **7 passed** | [RED](evidence/09-ime-red.log), [GREEN](evidence/09-ime-green.log) |
| 10 한글 조합 도중 세션 재시작 | `npm test -- tests/App.test.tsx -t '한글 조합 도중'`: **1 failed / 7 skipped**, 새 세션 제출이 막힘 | 같은 명령: **1 passed / 7 skipped** | [RED](evidence/10-ime-reset-red.log), [GREEN](evidence/10-ime-reset-green.log) |
| 11 실측 음성 도구 버전 provenance | `npm test -- tests/assets.test.ts -t '도구 버전'`: **1 failed / 1 skipped**, 버전 정보 undefined | 생성기 수정·재생성 후 `npm test -- tests/assets.test.ts`: **2 passed** | [RED](evidence/11-provenance-red.log), [GREEN](evidence/11-provenance-green.log) |
| 12 한글 조합 도중 다시 듣기 | 위 10의 테스트에 재청취 전환을 추가한 동일 명령: **1 failed / 7 skipped**, 제출이 막힘 | 같은 명령: **1 passed / 7 skipped** | [RED](evidence/12-ime-replay-red.log), [GREEN](evidence/12-ime-replay-green.log) |
`-t`의 skipped는 해당 회귀만 고른 실행 결과이며 최종 전체 실행에는 **skip 없음**.
중간 실패도 남겼다.
- DSP의 첫 구현에서 음수 0(`-0`)이 경계 샘플로 남아 **1 failed / 2 passed**였다.
경계값을 명시적으로 0으로 써 수정했다. [중간 로그](evidence/03-dsp-intermediate.log)
- UI 첫 구현에서 라디오의 보조 설명에 ‘직접 입력’이 중복되어 **1 failed / 6 passed**였다.
각 라디오의 접근성 이름을 명확히 했다. [중간 로그](evidence/06-ui-intermediate.log)
- 오디오 최초 RED의 미구현 throw로 미처리 rejection 3개도 보고되었다.
실제 엔진의 Promise 종료·정리 구현 뒤 GREEN과 최종 실행에서는 0개다.
- 첫 TypeScript 검사에서 TypedArray의 ArrayBuffer 타입과 테스트 mock의 순환
추론 오류를 발견했다. 반환 타입·mock 타입을 명시하여 strict 검사를 통과시켰다.
## 브라우저 통합 검증과 접근성 수정
실제 Chrome, production `dist/`, 실제 WAV/Web Audio로 검증했다. 모의 재생으로
시간을 단축하지 않고 `ended`까지 기다렸다. 모든 접근성 검사는 axe 규칙을
비활성화하지 않고 실행했다.
명령: `CHROME_PATH=/usr/bin/google-chrome npm run test:browser`
| 실행 | 실제 결과 | 발견·조치 |
| --- | --- | --- |
| [첫 실행](evidence/browser-first.log) | 2 passed / 2 failed | 시작 화면 글자 대비 3.75, 4.07 < 4.5. 두 색을 어둡게 조정 |
| [두 번째](evidence/browser-second.log) | 2 passed / 2 failed | 게임 화면 글자 대비 4.08, 4.41 < 4.5. 두 색을 조정 |
| [세 번째](evidence/browser-third.log) | 2 passed / 2 failed | 두 크기 모두 10문제 완주·100% 도달. 결과 설명 대비 4.38 < 4.5 수정 |
| [수정 후](evidence/browser-final.log) | **4 passed (1.2m)** | 시작·게임·결과 axe 위반 0, 두 크기 완주·재시작·404 후 재시도 통과 |
최종 빌드에서도 **4 passed (1.2m), 종료 코드 0**을 확인했다. reduced motion의
실제 계산된 `animation-name: none`까지 검증했다.
[최종 브라우저 로그](evidence/final-browser.log)에 실제 결과를 보관했다.
스크린샷:
- [1280px 시작](evidence/desktop-home.png), [게임](evidence/desktop-game.png), [결과](evidence/desktop-results.png)
- [360px 시작](evidence/mobile-home.png), [게임](evidence/mobile-game.png), [결과](evidence/mobile-results.png)
## 최종 검증
환경: Node.js **26.7.0**, npm **11.19.0**, Vite **8.3.4**, Vitest **5.0.4**.
최소 Node 안내는 아래 릴리스 재검토에서 **22.22.2 이상**으로 바로잡았다.
실제 Node 22에서의 별도 실행은 하지 않았으며 현재 실행 환경은 아래 최신 검증을 따른다.
| 명령/검증 | 실제 결과 |
| --- | --- |
| `npm run lint` | **PASS**, ESLint + `tsc --noEmit` 오류 0 · [로그](evidence/final-lint.log) |
| `npm test` | **7 files / 29 tests passed**, skip 0 · [로그](evidence/final-test.log) |
| `npm run build` | **PASS**, `dist/` 생성 및 음성 101개 원본 SHA-256 대조 · [로그](evidence/final-build.log) |
| `CHROME_PATH=/usr/bin/google-chrome npm run test:browser` | **4 passed (1.2m)**, 360px/1280px 완주·오류 복구·접근성 위반 0·외부 요청 0·JS 예외 0 · [로그](evidence/final-browser.log) |
| `npm run audio:generate` 후 이전 manifest `files` 비교 | **101개 원문·바이트 수·SHA-256 전부 동일** · [로그](evidence/audio-reproduce.log) |
| `npm audit --audit-level=high` | **found 0 vulnerabilities** · [로그](evidence/final-audit.log) |
| 비밀값 정적 패턴 검사 | **34개 텍스트 파일, 0 matches** · [로그](evidence/final-security.log) |
| 직접 작성 소스의 한글 설명 | **22개 파일, 누락 0**; Python 생성기 AST 파싱 PASS |
| `git diff --check` | 종료 0. 초기 저장소가 비어 새 파일들은 아직 untracked 상태 |
production 주요 번들: JS **244.26 kB**(gzip 76.95 kB), CSS **14.87 kB**(gzip 4.29 kB),
HTML **0.89 kB**. WAV는 별도로 10.86 MB이고 문항 단위로 로드한다.
비밀값 검사는 소스·테스트·스크립트·문서·루트 metadata·입력 `.agent/*.md`에 대해
AWS access-key 형식, GitHub/OpenAI 토큰 형식, private-key 블록, 긴 password/secret/API-key
문자열 할당을 Python 정규식으로 검사했다. 일치 값은 출력하지 않는다.
의존성 디렉터리와 PCM 바이너리는 대상에서 제외했다. 이는 특정 형식의 정적 검사다.
## 변경 파일
초기 저장소에는 입력 문서만 존재했다. 다음 파일을 새로 작성했다. 파일 전체
목록은 [changed-files.txt](evidence/changed-files.txt)에 남긴다(입력 문서는 보존 표시).
- 앱: `src/App.tsx`, `src/main.tsx`, `src/styles.css`, `src/content.ts`, `src/game.ts`,
`src/audio/dsp.ts`, `src/audio/engine.ts`, `index.html`.
- 설정: `package.json`, `package-lock.json`, `tsconfig.json`, `vite.config.ts`,
`eslint.config.js`, `playwright.config.ts`, `.gitignore`.
- 생성/검증: `scripts/generate_audio.py`, `scripts/check-build.mjs`.
- 테스트: `tests/setup.ts`, `tests/content.test.ts`, `tests/game.test.ts`,
`tests/dsp.test.ts`, `tests/audio.test.ts`, `tests/assets.test.ts`,
`tests/App.test.tsx`, `tests/build.test.ts`, `tests/browser/game.spec.ts`.
- 자산: `public/audio/intro.wav`, 100개의 `<pattern>-<word>.wav`,
`public/audio/manifest.json`.
- 문서: `README.md`, `docs/ARCHITECTURE.md`, `docs/AUDIO_PROVENANCE.md`,
`docs/DEPLOYMENT.md`, `docs/WORDS.md`, `.agent/RESULT.md` 및 `evidence/` 로그·화면.
- 생성된 `dist/`, `node_modules/`, `.cache/`와 Playwright 임시 보고서는 Git 제외.
## 남은 위험·인수 사항
1. 한국어 포먼트 합성은 자연스러운 사람의 발화보다 기계적이다. 자동 검증은
오디오의 존재·디코딩·전력·흐름을 확인했고, 실제 사용자의 발음 이해도와 청감
난이도를 평가하지 않았다. Neo의 직접 청취 및 사용자 평가가 필요하다.
2. 환경음은 장소를 추상화한 합성 잡음이다. 실제 시장·카페의 사람 대화 녹음이
아니다. 상대 dB와 오답 혼동 순위는 의료·청력 측정치가 아니다.
3. 모바일은 Chrome의 360px 터치 에뮬레이션으로 검증했다. 실물 iOS Safari,
Android 장치, Bluetooth 출력, 보조공학 장치의 전체 조합은 미검증이다.
4. 사용자 기록은 메모리에만 있어 새로고침 시 사라진다. 오프라인 PWA는 아니며
음성의 첫 로드에는 정적 호스트 연결이 필요하다. 전체 오디오 용량은 약 10.9MB다.
5. 정답은 정적 번들에 포함된다. 개발자 도구로 답을 알아낼 수 있어 보안 시험이나
순위 경쟁 서비스 용도가 아니다.
6. 서버 배포·Nginx/TLS/DNS는 요청 범위 밖이며 실행하지 않았다. Neo가 별도
검토·커밋·배포하고 실제 도메인에서 MIME·캐시·오디오 404·HTTPS를 확인해야 한다.
## Neo 스크린샷 검수 후 출시 전 UI 수정 — 2026-10-14 UTC
앞의 기록과 기존 스크린샷은 보존하고 이번 작업의 RED/GREEN을 이어서 기록한다.
기록 위치는 `.agent/TASK.md`와 `.agent/ACCEPTANCE.md`의 `.agent/RESULT.md` 지정을 따랐다.
### 변경 결과
- 한국어 `h1`·`h2`·`h3`에 `word-break: keep-all`을 적용했다. 실제 Chrome의
360px computed style이 `normal`인 RED부터 확인했고, 열 가지 질문 모두
360px/1280px에서 `keep-all`과 제목 내부 가로 넘침 여부를 검사한다.
- 게임과 결과 난이도를 `SNR +6 dB`, `SNR +3 dB`, `SNR 0 dB`, `SNR -3 dB`,
`SNR -6 dB`로 표시한다. 게임에서는 “음성이 배경보다 6 dB 커요.”,
“음성과 배경의 크기가 같아요.”, “음성이 배경보다 3 dB 작아요.”처럼
크기 관계를 풀어 설명한다. 모바일에서도 설명을 숨기지 않는다.
- 전체 음량은 시작 화면의 설정 카드 안에서 CTA보다 앞에, 게임에서는 환경
패널 안에 배치했다. `전체 음량` 접근성 이름, label 연결, 엔진 전달,
화면 전환 시 값 유지와 결과 화면의 기존 음량 조절을 보존했다.
- 사운드 룸의 `01 / 08`은 실제 환경 수를 이용한 `8가지 환경`으로 바꿨다.
`플레이 방법`의 `#howto` 앵커는 유지하고 장식 화살표를 `↓`로 바꿨다.
CTA의 표시와 접근성 이름은 `게임 시작 · 소리 듣기`로 통일했다.
- 기존 색상·캐릭터·게임 흐름을 유지했다. README의 CTA와 SNR 설명도 맞췄다.
출제·채점·음원·오디오 gain 구현은 수정하지 않았다.
### 추가 strict TDD 기록
각 RED는 해당 구현을 바꾸기 **전에** 실행해 종료 코드 **1**을 확인했다.
각 GREEN은 같은 테스트 계약으로 종료 코드 **0**을 확인했다. 브라우저는
각 단계의 소스를 먼저 빌드한 뒤 localhost production preview로 검사했다.
| 순서·계약 | 실제 RED | 구현 후 GREEN | 증거 |
| --- | --- | --- | --- |
| 13 한국어 제목의 어절 보존 | `CHROME_PATH=/usr/bin/google-chrome npm run test:browser -- tests/browser/ui.spec.ts --project=mobile`: **1 failed**. 시작·질문 heading의 computed `wordBreak`가 `normal` | 같은 명령: **1 passed**, `keep-all` 및 가로 넘침 없음 | [RED](evidence/13-heading-red.log), [GREEN](evidence/13-heading-green.log), [변경 전 빌드](evidence/13-heading-build-before.log), [변경 후 빌드](evidence/13-heading-build-after.log) |
| 14 다섯 SNR·크기 설명·결과 표시 | `npm test -- tests/App.test.tsx -t '다섯 난이도의 SNR'`: **1 failed / 8 skipped**. 열 문제의 SNR·크기 설명과 결과 표기 누락 | 같은 명령: **1 passed / 8 skipped** | [RED](evidence/14-snr-red.log), [GREEN](evidence/14-snr-green.log) |
| 14 모바일 난이도 설명 가시성 | `CHROME_PATH=/usr/bin/google-chrome npm run test:browser -- tests/browser/ui.spec.ts --project=mobile --grep '난이도 설명'`: **1 failed**. `.db-note`가 hidden이고 새 설명·배지 없음 | 같은 명령: **1 passed** | [RED](evidence/14-snr-browser-red.log), [GREEN](evidence/14-snr-browser-green.log), [빌드](evidence/14-snr-build.log) |
| 15 음량의 소속·문서 순서·이름·값·엔진 | `npm test -- tests/App.test.tsx -t '전체 음량을 시작 CTA'`: **1 failed / 9 skipped**. 설정 카드·환경 패널 바깥이고 CTA 뒤에 있음 | 같은 명령: **1 passed / 9 skipped**. 이름·단일 컨트롤·20→60% 전환 및 엔진 전달 확인 | [RED](evidence/15-volume-red.log), [GREEN](evidence/15-volume-green.log) |
| 15 실제 음량 위치·키보드 | `CHROME_PATH=/usr/bin/google-chrome npm run test:browser -- tests/browser/ui.spec.ts --grep '전체 음량'`: **2 failed**. 두 화면 크기에서 컨트롤이 카드 경계와 CTA 아래로 벗어남 | 같은 명령: **2 passed**, 카드 내부·CTA 이전 배치와 방향키·값 유지 확인 | [RED](evidence/15-volume-browser-red.log), [GREEN](evidence/15-volume-browser-green.log), [빌드](evidence/15-volume-build.log) |
| 16 환경 수·앵커 화살표·명확한 CTA | `npm test -- tests/App.test.tsx -t '시작 안내'`: **3 failed / 10 skipped**. `8가지 환경`·`↓`·새 CTA 없음 | 같은 명령: **3 passed / 10 skipped**, CTA 클릭에서 재생 1회 확인 | [RED](evidence/16-copy-red.log), [GREEN](evidence/16-copy-green.log) |
| 17 음량 이동 후 게임 카드·바닥글 여백 | `CHROME_PATH=/usr/bin/google-chrome npm run test:browser -- tests/browser/ui.spec.ts --grep '전체 음량'`: **2 failed**. 스크린샷 검토에서 발견한 두 화면 크기의 카드·바닥글 간격 **0px < 24px** | 게임 하단에 32px 여백 적용 후 같은 명령: **2 passed** | [RED](evidence/17-spacing-red.log), [GREEN](evidence/17-spacing-green.log) |
필터 실행의 skipped는 해당 회귀만 선택한 결과다. 기존 테스트의 시작 버튼
선택자도 새 접근성 이름에 맞췄으며, 이전 이름까지 허용하는 정규식으로
검증을 완화하지 않았다. 최종 브라우저 테스트에는 음량에서 Tab을 누르면
시작 CTA로 이동하는 검사와 열 가지 질문의 computed style 검사도 포함한다.
전체 검증의 첫 타입 검사에서 새 Testing Library `getByRole` 호출에 지원되지
않는 `exact` 옵션을 발견했다. 문자열 `name` 자체가 정확 일치를 하므로 불필요한
옵션만 제거했다. [중간 lint](evidence/neo-intermediate-lint.log)와
[중간 build](evidence/neo-intermediate-build.log)를 보존했다.
여백 회귀는 첫 전체 브라우저 실행 **10 passed (1.5m)** 뒤 스크린샷 검토에서
추가로 발견했다. [당시 로그](evidence/neo-before-spacing-browser.log)를 보존하고
17의 측정 계약을 먼저 실패시킨 뒤 수정했다. 전체 캡처 전에는 스크롤을
맨 위로 맞춰 화면 밖 fixed 건너뛰기 링크가 Chrome의 full-page 이미지에
들어오는 캡처 아티팩트를 방지했다. 앱의 초점 처리와 링크는 바꾸지 않았다.
### 이번 수정의 최종 전체 검증
17의 여백 수정까지 포함한 최종 소스로 아래 명령을 모두 실행했다. 종료 코드는
전부 **0**이며 unit/component와 브라우저 전체 실행 모두 **skip 없음**이다.
| 명령 | 최종 결과 | 로그 |
| --- | --- | --- |
| `npm run lint` | **PASS**, ESLint와 TypeScript 오류 0 | [lint](evidence/neo-final-lint.log) |
| `npm test` | **7 files / 34 tests passed** | [test](evidence/neo-final-test.log) |
| `npm run build` | **PASS**, production 빌드·음성 101개 무결성 확인 | [build](evidence/neo-final-build.log) |
| `CHROME_PATH=/usr/bin/google-chrome npm run test:browser` | **10 passed (1.4m)**, 실제 Chrome 360px/1280px | [browser](evidence/neo-final-browser.log) |
브라우저에서는 실제 WAV 재생을 기다려 혼합 모드 열 문제를 완주하고,
재청취·100% 결과·재시작·404 후 재시도·초점 복귀를 검증했다. 두 화면 크기에서
시작·게임·결과 axe 위반 **0**, 외부 요청 **0**, JavaScript 예외 **0**이며,
reduced motion과 새 줄바꿈·SNR 설명·음량 위치·키보드 계약도 모두 통과했다.
스크린샷을 직접 열어 시작 CTA 앞 음량, 게임 환경 패널의 SNR·음량, 어절을
보존한 질문, 게임 카드와 바닥글의 여백, 결과 표기를 확인했다.
최종 스크린샷(기존 파일과 구별되는 `neo-` 접두사):
- 1280px: [시작](evidence/neo-desktop-home.png), [게임](evidence/neo-desktop-game.png), [결과](evidence/neo-desktop-results.png), [고정 질문](evidence/neo-desktop-heading.png)
- 360px: [시작](evidence/neo-mobile-home.png), [게임](evidence/neo-mobile-game.png), [결과](evidence/neo-mobile-results.png), [고정 질문](evidence/neo-mobile-heading.png)
이번 변경 파일은 `src/App.tsx`, `src/styles.css`, `tests/App.test.tsx`,
`tests/browser/game.spec.ts`, 신규 `tests/browser/ui.spec.ts`, `README.md`,
이 `.agent/RESULT.md` 및 추가 evidence 로그·스크린샷이다. 의존성과 음원은
변경하지 않았다. `git diff --check`는 종료 0이며 저장소의 기존 untracked 상태는
유지했다. 변경 텍스트 7개 파일의 후행 공백 0과 RESULT의 evidence 링크 존재도
별도로 확인했다.
요청된 UI 수정에서 남은 실패는 없다. 실물 모바일·Safari 및 청감 평가의 기존
검증 범위 한계는 그대로다. 이번에도 로컬 preview 검증만 수행했으며,
**서버 배포, Git commit, Git push는 실행하지 않았다.**
## 독립 릴리스 리뷰·Neo literal-preservation 재검토 반영 — 2026-10-14 UTC
이번 사용자 지시를 이전 입력 문서의 브랜드 해석보다 우선 적용했다.
정확한 표기는 **Designed by OZ at Heat-T Medical Inc.**다. UI, README,
`.agent/CONTEXT.md`·`TASK.md`·`ACCEPTANCE.md`의 요구사항과 이 문서의 표기도
일치시켰다. 이전 실행의 raw 로그와 스크린샷은 당시 상태의 증거이므로 보존한다.
아래 검증이 현재 릴리스 상태이며 앞의 결과·바이트 수·테스트 수는 당시 기록이다.
### 변경 내용
- 브랜드 원문을 고정한 컴포넌트·문서·실제 브라우저 회귀 검증을 추가했다.
회사명을 문맥으로 추측해 교정하던 입력 문서 규칙도 제거했다.
- 프로젝트 자체는 `private: true`, `license: UNLICENSED`, root `LICENSE`의
private / all-rights-reserved로 명시했다. package repository·homepage와
README·배포 문서의 canonical URL은 `https://source.ibit.kr/OZ-Group/Find-Word`다.
- root와 public의 `THIRD_PARTY_NOTICES.md`에 잠긴 React 19.3.0, ReactDOM 19.3.0,
scheduler 0.28.0의 MIT 전문을 포함했다. 빌드 검증기는 전이 의존성을 포함한
런타임 목록을 lockfile에서 찾고, 설치된 LICENSE 전문·버전과 root/public/dist의
고지 사본을 대조한다. notice의 누락·축약을 실제 실패시키는 테스트도 추가했다.
- `Pattern.mechanic`에 10개 고유 연산을 지정했다. 직접 목표 식별, 암호 구별,
잊고 기억하기, 첫째·마지막·가운데, 반복, 정정, 신호 조건, 오른쪽 표지를 각각
다르게 듣고 판단하게 했다. `renderSentence`가 실제 보기의 오답도 문장에
삽입하며 UI와 음성 생성기가 같은 원문을 사용한다. 문장 계약 검증은 100개
조합에서 순서·반복 횟수·취소·조건·방향 단서와 질문 의도를 확인한다.
- 101개 WAV와 manifest를 재생성했다. 합계 **20,497,222바이트**, 16kHz mono
PCM이며 기존 20MiB 제한을 유지했다. 방향은 말로 전달하는 의미 단서다.
- package와 lockfile의 Node 최소 버전은 `>=22.22.2`로 맞췄다. README·배포
문서에 잠긴 jsdom 계열의 24.x 최소 24.15.0 요구도 함께 설명했다.
- Playwright는 `chromiumSandbox: true`가 기본이다. 미설정·빈 값·`0`·`false`는
sandbox를 유지하며, `PLAYWRIGHT_NO_SANDBOX=1`일 때만 끈다. Chrome 경로
지정은 sandbox와 독립적이다. 새 브라우저 스크린샷은 `release-` 접두사로 저장한다.
### 실제 strict TDD
아래 RED는 구현을 바꾸기 전에 실제 실행했고 모두 종료 코드 **1**이었다.
GREEN은 같은 계약으로 모두 종료 코드 **0**이었다. 브라우저 전달 계약은
기존 dist에서 먼저 실패시킨 후 새 소스를 빌드했다. 테스트 실패를 제거하거나
허용치를 완화하지 않았다. `-t`로 지정한 실행의 skip은 선택 실행에 한정된다.
| 순서·계약 | RED 명령·관찰 | GREEN 명령·관찰 | 원문 로그 |
| --- | --- | --- | --- |
| 18 literal 보존 | `npm test -- tests/App.test.tsx tests/release.test.ts -t '브랜드'`: **2 failed**, UI·문서의 기존 표기 불일치 | 같은 명령: **2 passed / 12 skipped** | [RED](evidence/18-literal-red.log), [GREEN](evidence/18-literal-green.log) |
| 19 프로젝트 metadata·MIT 고지·배포 검증기 | `npm test -- tests/release.test.ts tests/build.test.ts`: **4 failed / 2 passed**, metadata·LICENSE·고지 누락 및 검증기의 누락/축약 미검출 | 같은 명령: **6 passed** | [RED](evidence/19-notices-red.log), [GREEN](evidence/19-notices-green.log) |
| 20 고유 청취 연산·문장 계약·출제 | `npm test -- tests/content.test.ts tests/game.test.ts`: **12 failed / 10 passed**, mechanic 없음·경쟁 단어/순서/의미 계약 미충족 | 같은 명령: **22 passed** | [RED](evidence/20-mechanics-red.log), [GREEN](evidence/20-mechanics-green.log) |
| 20 변경 원문과 WAV 대응 | `npm test -- tests/assets.test.ts`: **1 failed / 1 passed**, 기존 manifest 원문이 새 문장과 다름 | `npm run audio:generate` 후 `npm test -- tests/content.test.ts tests/game.test.ts tests/assets.test.ts`: **24 passed**, 문장·해시·PCM·비무음·크기·피크 검증 | [RED](evidence/20-audio-red.log), [생성](evidence/20-audio-generate.log), [GREEN](evidence/20-audio-green.log) |
| 21 Node 최소 버전 | `npm test -- tests/release.test.ts -t 'Node 최소'`: **1 failed / 3 skipped**, package/lock/docs 값이 잠금 의존성 요구와 다름 | 같은 명령: **1 passed / 3 skipped** | [RED](evidence/21-node-red.log), [GREEN](evidence/21-node-green.log) |
| 22 sandbox 기본값과 명시적 opt-in | `npm test -- tests/playwright-config.test.ts`: **5 failed**, 기본 sandbox 비활성화·무조건 적용된 비활성화 플래그 | 같은 명령: **5 passed**, 각 환경변수 값을 별도 프로세스로 검증 | [RED](evidence/22-sandbox-red.log), [GREEN](evidence/22-sandbox-green.log) |
| 23 실제 HTTP 배포 응답 | `env -u PLAYWRIGHT_NO_SANDBOX CHROME_PATH=/usr/bin/google-chrome npm run test:browser -- tests/browser/release.spec.ts --project=mobile`: **1 failed**, 이전 dist의 크레딧 불일치와 notice 대신 SPA HTML 응답 | 새 build 후 전체 브라우저 실행: **12 passed (2.2m)**, 두 화면 크기의 전달 계약 포함 | [RED](evidence/23-delivery-browser-red.log), [전체 GREEN](evidence/release-final-browser.log) |
중간 lint에서 새 sandbox 테스트의 env 객체 타입이 좁게 추론되어 TypeScript
오류 2개가 났다. `NodeJS.ProcessEnv`를 명시하여 수정했다.
[실패 원문](evidence/release-intermediate-lint.log)을 보존했다.
### 최종 검증 결과
환경: Node.js **26.7.0**, npm **11.19.0**, 시스템 Chrome
`/usr/bin/google-chrome`. 최소 Node 22.22.2에서 별도로 실행한 결과는 아니다.
새 소스의 production build를 완료한 뒤 다음 명령으로 브라우저 전체를 실행했다.
```bash
env -u PLAYWRIGHT_NO_SANDBOX CHROME_PATH=/usr/bin/google-chrome DEBUG=pw:browser npm run test:browser
```
| 검증 | 실제 결과·증거 |
| --- | --- |
| `npm run lint` | **PASS**, ESLint + TypeScript strict 오류 0 · [로그](evidence/release-final-lint.log) |
| `npm test` | **9 files / 57 tests passed**, skip 0 · [로그](evidence/release-final-test.log) |
| `npm run build` | **PASS**, WAV 101개 SHA-256 및 root/public/dist 런타임 MIT notice 전문 대조 · [로그](evidence/release-final-build.log) |
| 위 Chrome 전체 명령 | **12 passed (2.2m)**, 360px/1280px, 실제 재생·10문제 완주·재청취·404 복구·키보드·SNR·레이아웃·axe·크레딧·MIT HTTP 응답 · [로그](evidence/release-final-browser.log) |
| sandbox 실제 실행 인자 | Chrome 4회 실행 모두 `--no-sandbox` 없음. opt-in 환경변수를 지운 상태로 실행 · [검증](evidence/release-integrity.log) |
| `npm audit --audit-level=high` | **found 0 vulnerabilities**, 개발 의존성 포함 · [로그](evidence/release-final-audit.log) |
| `npm ls --omit=dev` | React / ReactDOM 19.3.0, 해석 오류 없음 |
| 브랜드·기록·정적 산출물 최종 확인 | 현재 UI·테스트·문서에 대체 브랜드 표기 없음, evidence 링크 존재, 101 WAV/notice 검증 통과 · [로그](evidence/release-integrity.log) |
| `git diff --check` | 종료 코드 0. 시작 시 이미 소스 전체가 untracked였으므로 변경 텍스트의 후행 공백도 별도 검사 |
브라우저의 페이지 JS 예외·외부 페이지 요청·axe 위반은 모두 0이었다.
Chrome 자체 진단 출력과 앱 페이지의 오류·네트워크 검증은 구별했다.
실제 360px 게임 스크린샷에서도 올바른 브랜드, 질문 줄바꿈, 보기·음량·SNR 표시를 확인했다.
- [360px 시작](evidence/release-mobile-home.png), [게임](evidence/release-mobile-game.png), [결과](evidence/release-mobile-results.png)
- [1280px 시작](evidence/release-desktop-home.png), [게임](evidence/release-desktop-game.png), [결과](evidence/release-desktop-results.png)
### 이번 변경 파일과 범위
- 앱·출제: `src/App.tsx`, `src/content.ts`, `src/game.ts`.
- 라이선스·metadata·브라우저 설정: `LICENSE`, `THIRD_PARTY_NOTICES.md`,
`public/THIRD_PARTY_NOTICES.md`, `package.json`, `package-lock.json`, `playwright.config.ts`.
의존성 버전과 integrity는 바꾸지 않았다.
- 생성·빌드: `scripts/generate_audio.py`, `scripts/check-build.mjs`,
`public/audio/`의 101 WAV와 manifest. 안내 WAV도 다시 생성했으며 원문은 유지했다.
- 회귀 검증: `tests/App.test.tsx`, `tests/content.test.ts`, `tests/game.test.ts`,
`tests/assets.test.ts`, `tests/build.test.ts`, 새 `tests/release.test.ts`,
`tests/playwright-config.test.ts`, `tests/browser/release.spec.ts`.
기존 `tests/browser/game.spec.ts`·`ui.spec.ts`는 새 스크린샷 이름을 사용한다.
- 문서·증거: `README.md`, `docs/ARCHITECTURE.md`, `docs/AUDIO_PROVENANCE.md`,
`docs/DEPLOYMENT.md`, `.agent/CONTEXT.md`, `TASK.md`, `ACCEPTANCE.md`,
이 `RESULT.md` 및 새 evidence 파일.
요청된 수정·자동 검증에 남은 실패는 없다. 한국어 합성 발음의 청감과 실물
모바일 Safari는 기존과 같이 별도 인수 대상이다. WAV 용량은 약 20.5MB로 늘었고
질문별로 로드한다. **서버 배포, Git commit, Git push는 실행하지 않았다.**
## Neo canonical 오디오 재시도 assertion 회귀 수정 — 2026-10-14 UTC
### RED 근거와 재현 조건
- **Neo가 보고한 기존 canonical 실패를 이번 RED 근거로 사용한다.**
전체 `npm run test:browser`(12 tests, workers=2)에서 desktop의
`오디오 HTTP 실패를 화면에 표시하고 재시도한다`가 재시도 후
`getByRole("group", { name: "답 선택" })`를 global expect timeout
**20,000ms** 안에 보지 못해 1회 실패했다.
- 같은 수정 전 테스트를 desktop 단독 `--workers=1 --repeat-each=3`으로
실행하면 **16.3~17.3초에 3회 모두 통과**했다는 Neo의 보고다.
실패는 병렬 전체 실행에서 간헐적으로 발생했다. 이는 이번 로컬 실행의
원문 로그가 아니며, 새로 실패를 재현했다고 주장하지 않는다.
- 사용자 메시지에 첨부된 **12 passed (2.2m)** 출력은 성공 실행 기록이다.
이를 실패 로그로 취급하지 않으며 기존 evidence도 보존한다.
### 원인 추적과 최소 수정 범위
`systematic-debugging` 절차로 실패한 assertion에서 상태 전이를 역추적했다.
404가 난 최초 재생은 `src/App.tsx`의 `introDone`을 설정하지 않는다.
재시도는 안내와 목표 WAV를 다시 로드·디코드하고, `src/audio/engine.ts`의
목표 소스 `onended` → `play()` 완료 → `ENDED` → `answer` 상태가 된 뒤에야
답 선택 그룹을 표시한다. 출제 seed는 `Date.now()`여서 실행별 목표 길이도
달라진다. 정상 단독 실행과 병렬 실패의 차이는 이 대기 경로의 시간 여유다.
현재 101개 WAV를 Python `wave`의 프레임 수 / sample rate로 실측했다.
안내는 **8.1205초**, 최장 목표 `message-market.wav`는 **10.1725초**다.
엔진의 시작 여유 **0.05초**와 안내→목표 간격 **0.35초**를 더한 최대
재생 일정은 **18.693초**다. 기존 20초 assertion에는 HTTP 로드·디코드·배경
합성·브라우저 스케줄링·DOM 반영·assertion 관찰을 위한 여유가 **1.307초**만
남는다. Neo의 병렬 실패와 단독 성공은 이 좁은 assertion 예산에 부합한다.
당시 trace가 없어 실패 1회의 세부 지연 분포는 확인할 수 없다.
[실측과 제어 흐름 기록](evidence/24-retry-audio-budget.log)
수정은 `tests/browser/game.spec.ts`의 **재시도 완료 `toBeVisible` 하나에만
30,000ms 상한**을 지정한다. 최대 재생 일정 이후 **11.307초**의 여유를 두며
조건이 충족되면 즉시 통과한다. 제품의 준비 제한 15초·재생 종료 후 5초
watchdog, global expect 20초, test timeout 120초, workers=2는 유지한다.
sleep, 자동 재시도, 오디오 mock, 테스트 skip, 제품 코드 변경은 추가하지 않는다.
### 수정 후 검증
환경: Node.js **26.7.0**, npm **11.19.0**, 시스템 Chrome
`/usr/bin/google-chrome`. 새 production build에서 실제 WAV를 사용했다.
브라우저 실행은 `env -u PLAYWRIGHT_NO_SANDBOX CHROME_PATH=/usr/bin/google-chrome`
접두사로 기본 sandbox를 유지했다. 아래 명령은 모두 종료 코드 **0**이다.
| 검증 명령 | 실제 GREEN 결과 | 로그 |
| --- | --- | --- |
| `npm run lint` | **PASS**, ESLint + TypeScript 오류 0 | [lint](evidence/24-retry-final-lint.log) |
| `npm test` | **9 files / 57 tests passed**, skip 0 | [test](evidence/24-retry-final-test.log) |
| `npm run build` | **PASS**, WAV 101개와 런타임 notice 전문 확인 | [build](evidence/24-retry-final-build.log) |
| `npm run test:browser -- tests/browser/game.spec.ts --project=desktop --workers=1 --repeat-each=3 --grep '오디오 HTTP 실패'` | **3 passed (52.9s)**, 각 **17.7s / 15.5s / 16.8s** | [targeted](evidence/24-retry-targeted-green.log) |
| `npm run test:browser` | **12 passed (2.2m)**, desktop/mobile, workers=2, skip 0 | [browser](evidence/24-retry-final-browser.log) |
전체 병렬 실행의 재시도 테스트는 desktop **19.9초**, mobile **14.8초**에
통과했다. 이 수치는 Playwright가 보고한 **테스트 전체 시간**이며 assertion
자체의 대기 시간으로 해석하지 않는다. 실제 404 오류 표시→재시도→답 선택→
처음으로 이동 후 초점 복귀 계약을 유지한 채 통과했다. 기존의 실제 오디오
10문제 완주, 레이아웃·접근성·외부 요청 없음, 릴리스·UI 검사도 모두 통과했다.
이번 변경은 `tests/browser/game.spec.ts`의 assertion 한 곳과 이 기록,
`evidence/24-retry-*.log`에 한정한다. 시작 시 소스 전체가 untracked였으므로
작업 전 SHA-256 snapshot과 비교해 `src/`, 음원, 의존성, Playwright 설정 및
기존 evidence가 그대로임을 확인했다. 전체 브라우저 실행이 다시 생성한
기존 `release-*.png`는 작업 전 사본으로 복원해 과거 검증 증거를 보존했다.
`git diff --check`, 수정 텍스트의 후행 공백 검사와 evidence 링크 확인도
통과했다. [변경 범위와 무결성 확인](evidence/24-retry-integrity.log)
요청한 반복·전체 검증은 모두 GREEN이다. **Git commit, push, 배포는 실행하지 않았다.**
+77
View File
@@ -0,0 +1,77 @@
# TASK — Find-Word v0.1
## 역할
당신은 구현 담당 Codex다. 이 저장소에서만 작업하고 프로덕션 서버·DNS·인증정보에는 접근하지 않는다. Neo가 코드 리뷰·배포를 별도로 수행한다.
## 제품
- 서비스명: **단어를 맞춰라**
- 표기: **Designed by OZ at Heat-T Medical Inc.**
- 버전: **0.1**
- 배포 예정 도메인: `findword.heart-m.com`
- 언어: 사용자 UI와 상세 소스 주석은 한국어
## 목표
여러 환경 소음 속에 작은 음량으로 섞인 특정 한국어 단어나 문장을 듣고 질문에 답하는 웹 게임을 만든다. 쉬운 단계부터 소음 대비 키 문장의 dB 차이를 늘려 난이도를 높인다. 객관식과 주관식을 모두 지원한다.
## 핵심 시나리오
1. S 캐릭터가 “으하하하~~ 나는 에스다. 널 위한 행운의 단어를 불러주마, 잘 찾아보거라.”라고 안내한다.
2. 웃음과 안내 음성이 배경 소음 속으로 점차 묻힌다.
3. “오늘의 내 행운은 ‘사과’에 있다.” 같은 키 문장을 재생한다.
4. 재생이 끝난 뒤 질문과 네 개의 선택지를 표시한다.
5. 선택지는 정답 1개와 사전에 존재하는 유사어 오답 3개로 구성한다.
6. 오답은 자음·모음 변형 규칙의 서로 다른 난이도를 반영하고, 데이터에 변형 종류를 명시한다.
## 기능 요구사항
- 반응형 SPA, 데스크톱·모바일 키보드/터치 지원
- 시작 화면, 게임 화면, 결과 화면, 다시 듣기, 다음 문제, 전체 진행률
- 사용자가 브라우저 정책상 필요한 “소리 시작” 조작을 직접 누르도록 설계
- Web Audio API로 보이스와 배경을 믹싱하고 목표 음성 gain을 dB 기준으로 적용
- 최소 5단계 난이도. 단계가 높아질수록 목표 문장과 배경의 상대 dB가 낮아짐
- 최소 10개 문제 유형. 객관식·주관식을 포함하고 데이터 조합으로 실제 문제가 더 늘어남
- 최소 8개 배경 환경: 길거리, 쇼핑몰, 극장, 백화점, 시장, 지하철, 카페, 비 오는 거리
- 기본 패턴 문장, 시험 문장, 질문, 답, 오답 후보, 배경 프리셋을 저장된 데이터로 분리
- 사전 단어만 사용하는 4지선다 데이터와 각 오답의 변형 규칙 메타데이터
- 점수, 정답률, 연속 정답, 난이도별 결과
- 접근성: aria, 명확한 focus, 키보드 조작, reduced motion, 색 대비
- 오디오가 없거나 로드 실패해도 사용자에게 명확한 오류와 재시도 제공
- 개인 데이터 수집·로그인·추적기 없음
- 버전 0.1을 UI와 package metadata에 표시
## 오디오 요구사항
- 라이선스 불명확한 외부 자산을 사용하지 않는다.
- 직접 생성한 배경 소음·효과음을 저장하거나, 브라우저에서 결정론적으로 합성하는 프리셋을 사용한다.
- 한국어 목표 음성은 가능하면 직접 생성한 오디오 파일로 저장한다. 네트워크 TTS를 사용했다면 생성 스크립트·출처·생성 절차를 문서화하고 앱은 런타임에 외부 TTS를 호출하지 않는다.
- 모든 자산의 provenance를 `docs/AUDIO_PROVENANCE.md`에 기록한다.
- 오디오 파일을 커밋할 경우 크기를 합리적으로 제한하고 build 결과에 포함되는지 테스트한다.
## 개발 규칙
- strict TDD: 각 동작은 실패하는 테스트를 먼저 작성하고 실제 RED→GREEN 근거를 `.agent/RESULT.md`에 기록한다.
- TypeScript strict mode를 사용한다.
- 모든 직접 작성 소스 코드에 상세한 한국어 주석을 단다. 자명한 문법 줄마다 반복 주석을 붙이지 말고 알고리즘, 상태, 오디오, 난이도, 접근성, 보안 의도를 설명한다.
- generated lockfile과 기계 생성 바이너리는 주석 대상이 아니다.
- 비밀값, 서버 자격증명, API 키를 저장하지 않는다.
- 서버 배포는 하지 않는다.
- Git 커밋은 하지 않는다. Neo가 검증 후 커밋한다.
## 권장 기술
- Vite + React + TypeScript
- Vitest + Testing Library
- 순수 CSS 또는 가벼운 CSS 구조
- 정적 빌드(`dist/`)로 Nginx 배포 가능
## 산출물
- 동작하는 v0.1 소스
- 최소 10개 문제 유형과 조합 가능한 데이터
- 오디오 생성·재생 구조
- unit/component 테스트
- `README.md`, `docs/ARCHITECTURE.md`, `docs/AUDIO_PROVENANCE.md`, `docs/DEPLOYMENT.md`
- `.agent/RESULT.md`: 변경 파일, RED/GREEN 명령과 실제 결과, 전체 검증 결과, 미해결 위험
+12
View File
@@ -0,0 +1,12 @@
> find-word@0.1.0 test
> vitest run tests/content.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 1 passed (1)
Tests 3 passed (3)
Start at 04:54:59
Duration 835ms (environment 78%, setup 13%, transform 6%, tests 1%, worker 1%)
+72
View File
@@ -0,0 +1,72 @@
> find-word@0.1.0 test
> vitest run tests/content.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
❯ tests/content.test.ts (3 tests | 3 failed) 12ms
❯ 저장된 문제·환경 데이터 (3)
× 서로 다른 질문 의도를 가진 열 가지 이상 문장 패턴을 제공한다 5ms
× 네 개의 고유 사전 단어와 서로 다른 오답 규칙을 제공한다 1ms
× 여덟 환경과 단조롭게 낮아지는 다섯 상대 dB를 제공한다 5ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 3 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/content.test.ts > 저장된 문제·환경 데이터 > 서로 다른 질문 의도를 가진 열 가지 이상 문장 패턴을 제공한다
AssertionError: expected 0 to be greater than or equal to 10
❯ tests/content.test.ts:8:29
6| describe('저장된 문제·환경 데이터', () => {
7| it('서로 다른 질문 의도를 가진 열 가지 이상 문장 패턴을 제공한다', () => {
8| expect(PATTERNS.length).toBeGreaterThanOrEqual(10);
| ^
9| expect(new Set(PATTERNS.map(p => p.id)).size).toBe(PATTERNS.length…
10| expect(new Set(PATTERNS.map(p => p.question)).size).toBe(PATTERNS.…
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/3]⎯
FAIL tests/content.test.ts > 저장된 문제·환경 데이터 > 네 개의 고유 사전 단어와 서로 다른 오답 규칙을 제공한다
AssertionError: expected 0 to be greater than or equal to 10
❯ tests/content.test.ts:14:26
12| });
13| it('네 개의 고유 사전 단어와 서로 다른 오답 규칙을 제공한다', () => {
14| expect(WORDS.length).toBeGreaterThanOrEqual(10);
| ^
15| WORDS.forEach(entry => {
16| expect(entry.distractors).toHaveLength(3);
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/3]⎯
FAIL tests/content.test.ts > 저장된 문제·환경 데이터 > 여덟 환경과 단조롭게 낮아지는 다섯 상대 dB를 제공한다
AssertionError: expected [] to deeply equal [ '길거리', '쇼핑몰', '극장', '백화점', …(4) ]
- Expected
+ Received
- [
- "길거리",
- "쇼핑몰",
- "극장",
- "백화점",
- "시장",
- "지하철",
- "카페",
- "비 오는 거리",
- ]
+ []
❯ tests/content.test.ts:36:43
34| });
35| it('여덟 환경과 단조롭게 낮아지는 다섯 상대 dB를 제공한다', () => {
36| expect(ENVIRONMENTS.map(e => e.name)).toEqual(['길거리', '쇼핑몰', '극장',…
| ^
37| expect(new Set(ENVIRONMENTS.map(e => e.seed)).size).toBe(8);
38| expect(LEVELS).toHaveLength(5);
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/3]⎯
Test Files 1 failed (1)
Tests 3 failed (3)
Start at 04:54:05
Duration 899ms (environment 78%, setup 13%, transform 6%, tests 2%, worker 1%)
+12
View File
@@ -0,0 +1,12 @@
> find-word@0.1.0 test
> vitest run tests/game.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 1 passed (1)
Tests 6 passed (6)
Start at 04:56:41
Duration 875ms (environment 75%, setup 13%, transform 5%, tests 5%, worker 1%, import 1%)
+105
View File
@@ -0,0 +1,105 @@
> find-word@0.1.0 test
> vitest run tests/game.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
❯ tests/game.test.ts (6 tests | 6 failed) 14ms
❯ 출제와 정답 판정 (3)
× 열 문제에 모든 유형·다섯 난이도·두 응답 방식을 섞는다 6ms
× 선택한 응답 방식과 환경을 모든 문제에 적용한다 1ms
× 한글 정규형·공백·문장부호만 정리하고 부분 정답은 인정하지 않는다 4ms
❯ 게임 상태·점수·경합 방지 (3)
× 재생 완료 이전에는 답변·다음 문제를 허용하지 않으며 중복 채점을 막는다 1ms
× 오디오 오류 후 재시도와 다시 듣기는 점수를 변경하지 않는다 0ms
× 열 문제를 끝내면 정답률·연속 정답·단계별 결과를 집계한다 0ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 6 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/game.test.ts > 출제와 정답 판정 > 열 문제에 모든 유형·다섯 난이도·두 응답 방식을 섞는다
Error: 출제 미구현
❯ createQuestions src/game.ts:17:106
15| }
16| export type Action = { type: 'PLAY' | 'STARTED' | 'ENDED' | 'NEXT' } |…
17| export function createQuestions(_seed: number, _mode: Mode, _environme…
| ^
18| export function normalizeAnswer(value: string): string { return value;…
19| export function isCorrect(input: string, answer: string): boolean { re…
❯ tests/game.test.ts:8:23
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/6]⎯
FAIL tests/game.test.ts > 출제와 정답 판정 > 선택한 응답 방식과 환경을 모든 문제에 적용한다
Error: 출제 미구현
❯ createQuestions src/game.ts:17:106
15| }
16| export type Action = { type: 'PLAY' | 'STARTED' | 'ENDED' | 'NEXT' } |…
17| export function createQuestions(_seed: number, _mode: Mode, _environme…
| ^
18| export function normalizeAnswer(value: string): string { return value;…
19| export function isCorrect(input: string, answer: string): boolean { re…
❯ tests/game.test.ts:26:12
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/6]⎯
FAIL tests/game.test.ts > 출제와 정답 판정 > 한글 정규형·공백·문장부호만 정리하고 부분 정답은 인정하지 않는다
AssertionError: expected ' “사 과.” ' to be '사과' // Object.is equality
Expected: "사과"
Received: " “사 과.” "
❯ tests/game.test.ts:30:43
28| });
29| it('한글 정규형·공백·문장부호만 정리하고 부분 정답은 인정하지 않는다', () => {
30| expect(normalizeAnswer(' “사 과.” ')).toBe('사과');
| ^
31| expect(isCorrect('사과'.normalize('NFD'), '사과')).toBe(true);
32| expect(isCorrect('사과 또는 바다', '사과')).toBe(false);
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/6]⎯
FAIL tests/game.test.ts > 게임 상태·점수·경합 방지 > 재생 완료 이전에는 답변·다음 문제를 허용하지 않으며 중복 채점을 막는다
Error: 출제 미구현
❯ createQuestions src/game.ts:17:106
15| }
16| export type Action = { type: 'PLAY' | 'STARTED' | 'ENDED' | 'NEXT' } |…
17| export function createQuestions(_seed: number, _mode: Mode, _environme…
| ^
18| export function normalizeAnswer(value: string): string { return value;…
19| export function isCorrect(input: string, answer: string): boolean { re…
❯ tests/game.test.ts:40:29
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/6]⎯
FAIL tests/game.test.ts > 게임 상태·점수·경합 방지 > 오디오 오류 후 재시도와 다시 듣기는 점수를 변경하지 않는다
Error: 출제 미구현
❯ createQuestions src/game.ts:17:106
15| }
16| export type Action = { type: 'PLAY' | 'STARTED' | 'ENDED' | 'NEXT' } |…
17| export function createQuestions(_seed: number, _mode: Mode, _environme…
| ^
18| export function normalizeAnswer(value: string): string { return value;…
19| export function isCorrect(input: string, answer: string): boolean { re…
❯ tests/game.test.ts:56:29
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[5/6]⎯
FAIL tests/game.test.ts > 게임 상태·점수·경합 방지 > 열 문제를 끝내면 정답률·연속 정답·단계별 결과를 집계한다
Error: 출제 미구현
❯ createQuestions src/game.ts:17:106
15| }
16| export type Action = { type: 'PLAY' | 'STARTED' | 'ENDED' | 'NEXT' } |…
17| export function createQuestions(_seed: number, _mode: Mode, _environme…
| ^
18| export function normalizeAnswer(value: string): string { return value;…
19| export function isCorrect(input: string, answer: string): boolean { re…
❯ tests/game.test.ts:69:29
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[6/6]⎯
Test Files 1 failed (1)
Tests 6 failed (6)
Start at 04:56:04
Duration 872ms (environment 78%, setup 13%, transform 6%, tests 2%, worker 1%, import 1%)
+12
View File
@@ -0,0 +1,12 @@
> find-word@0.1.0 test
> vitest run tests/dsp.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 1 passed (1)
Tests 3 passed (3)
Start at 04:58:27
Duration 1.23s (environment 51%, tests 37%, setup 8%, transform 3%, worker 1%)
+37
View File
@@ -0,0 +1,37 @@
> find-word@0.1.0 test
> vitest run tests/dsp.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
❯ tests/dsp.test.ts (3 tests | 1 failed) 82ms
❯ 상대 dB와 결정론적 배경 합성 (3)
× 여덟 환경이 서로 다르고 같은 seed는 같은 안전 범위 신호를 만든다 76ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/dsp.test.ts > 상대 dB와 결정론적 배경 합성 > 여덟 환경이 서로 다르고 같은 seed는 같은 안전 범위 신호를 만든다
AssertionError: expected -0 to be +0 // Object.is equality
- Expected
+ Received
- 0
+ -0
❯ tests/dsp.test.ts:29:25
27| expect(Math.max(...signal.map(Math.abs))).toBeLessThan(1);
28| // 루프 경계에서 불연속 클릭이 생기지 않도록 양 끝을 0으로 감싼다.
29| expect(signal[0]).toBe(0);
| ^
30| expect(signal.at(-1)).toBe(0);
31| signatures.add([...signal.slice(0, 100)].join(','));
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
Test Files 1 failed (1)
Tests 1 failed | 2 passed (3)
Start at 04:57:34
Duration 912ms (environment 72%, setup 12%, tests 10%, transform 5%, worker 1%, import 1%)
+56
View File
@@ -0,0 +1,56 @@
> find-word@0.1.0 test
> vitest run tests/dsp.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
❯ tests/dsp.test.ts (3 tests | 3 failed) 70ms
❯ 상대 dB와 결정론적 배경 합성 (3)
× 20 log10(음성 gain / 배경 gain)이 표시 dB와 일치한다 5ms
× 평균 전력을 맞추고 원본 배열을 변경하지 않으며 무음은 오류로 알린다 1ms
× 여덟 환경이 서로 다르고 같은 seed는 같은 안전 범위 신호를 만든다 62ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 3 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/dsp.test.ts > 상대 dB와 결정론적 배경 합성 > 20 log10(음성 gain / 배경 gain)이 표시 dB와 일치한다
AssertionError: expected +0 to be close to 6, received difference is 6, but expected 5e-11
❯ tests/dsp.test.ts:10:77
8| describe('상대 dB와 결정론적 배경 합성', () => {
9| it('20 log10(음성 gain / 배경 gain)이 표시 dB와 일치한다', () => {
10| for (const level of LEVELS) expect(20 * Math.log10(dbToGain(level.…
| ^
11| expect(dbToGain(-6)).toBeCloseTo(.501187, 5);
12| });
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/3]⎯
FAIL tests/dsp.test.ts > 상대 dB와 결정론적 배경 합성 > 평균 전력을 맞추고 원본 배열을 변경하지 않으며 무음은 오류로 알린다
AssertionError: expected 0.2738612848738597 to be close to 0.12, received difference is 0.1538612848738597, but expected 5e-7
❯ tests/dsp.test.ts:16:25
14| const input = new Float32Array([.4, -.2, .1, -.3]);
15| const output = normalizeRms(input);
16| expect(rms(output)).toBeCloseTo(.12, 6);
| ^
17| expect(output).not.toBe(input);
18| expect(() => normalizeRms(new Float32Array(4))).toThrow('무음');
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/3]⎯
FAIL tests/dsp.test.ts > 상대 dB와 결정론적 배경 합성 > 여덟 환경이 서로 다르고 같은 seed는 같은 안전 범위 신호를 만든다
AssertionError: expected +0 to be close to 0.12, received difference is 0.12, but expected 0.000005
❯ tests/dsp.test.ts:26:27
24| expect(signal).toHaveLength(16000);
25| expect(signal).toEqual(synthesizeBackground(preset, 16000, 1));
26| expect(rms(signal)).toBeCloseTo(.12, 5);
| ^
27| expect(Math.max(...signal.map(Math.abs))).toBeLessThan(1);
28| // 루프 경계에서 불연속 클릭이 생기지 않도록 양 끝을 0으로 감싼다.
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/3]⎯
Test Files 1 failed (1)
Tests 3 failed (3)
Start at 04:57:18
Duration 902ms (environment 72%, setup 12%, tests 9%, transform 5%, worker 1%, import 1%)
+12
View File
@@ -0,0 +1,12 @@
> find-word@0.1.0 test
> vitest run tests/audio.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 1 passed (1)
Tests 5 passed (5)
Start at 04:59:31
Duration 969ms (environment 71%, setup 11%, tests 11%, transform 5%, import 1%, worker 1%)
+179
View File
@@ -0,0 +1,179 @@
> find-word@0.1.0 test
> vitest run tests/audio.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
❯ tests/audio.test.ts (5 tests | 5 failed) 22ms
❯ 오디오 재생 수명과 믹서 (5)
× 생성 시 무음이며 gesture의 play에서만 장치를 연다 7ms
× 동시 시작을 거부하고 정지하면 모든 노드와 대기 Promise를 취소한다 8ms
× 로드 도중 취소한 이전 작업이 늦게 완료되어도 소리를 시작하지 않는다 3ms
× HTTP·디코드·장치 실패를 전달하고 다음 시도는 다시 로드한다 1ms
× 전체 음량은 두 신호의 비율을 보존하며 0부터 1 범위로 제한한다 1ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 5 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/audio.test.ts > 오디오 재생 수명과 믹서 > 생성 시 무음이며 gesture의 play에서만 장치를 연다
AssertionError: expected "vi.fn()" to be called once, but got 0 times
❯ tests/audio.test.ts:35:23
33| const started = vi.fn();
34| const done = f.engine.play(question, true, started);
35| expect(f.factory).toHaveBeenCalledOnce();
| ^
36| expect(f.context.resume).toHaveBeenCalledOnce();
37| await flush();
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/6]⎯
FAIL tests/audio.test.ts > 오디오 재생 수명과 믹서 > 동시 시작을 거부하고 정지하면 모든 노드와 대기 Promise를 취소한다
AssertionError: expected [Function] to throw error including '재생 중' but got '오디오 미구현'
Expected: "재생 중"
Received: "오디오 미구현"
❯ tests/audio.test.ts:54:58
52| const done = f.engine.play(question, false, vi.fn());
53| const cancelled = expect(done).rejects.toMatchObject({ name: 'Abor…
54| await expect(f.engine.play(question, false, vi.fn())).rejects.toTh…
| ^
55| await flush();
56| f.engine.stop();
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/6]⎯
FAIL tests/audio.test.ts > 오디오 재생 수명과 믹서 > 동시 시작을 거부하고 정지하면 모든 노드와 대기 Promise를 취소한다
Error: Promise returned by `expect(actual).rejects.toMatchObject(expected)` was not awaited. This assertion is asynchronous and must be awaited; otherwise, it is not guaranteed to complete before the test finishes:
await expect(actual).rejects.toMatchObject(expected)
❯ tests/audio.test.ts:53:35
51| const f = fixture();
52| const done = f.engine.play(question, false, vi.fn());
53| const cancelled = expect(done).rejects.toMatchObject({ name: 'Abor…
| ^
54| await expect(f.engine.play(question, false, vi.fn())).rejects.toTh…
55| await flush();
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/6]⎯
FAIL tests/audio.test.ts > 오디오 재생 수명과 믹서 > 로드 도중 취소한 이전 작업이 늦게 완료되어도 소리를 시작하지 않는다
AssertionError: expected Error: 오디오 미구현 to match object { name: 'AbortError' }
- Expected
+ Received
- {
- "name": "AbortError",
+ Error {
+ "message": "오디오 미구현",
}
❯ tests/audio.test.ts:68:35
66| f.fetcher.mockImplementationOnce(() => new Promise(resolve => { re…
67| const done = f.engine.play(question, false, vi.fn());
68| const cancelled = expect(done).rejects.toMatchObject({ name: 'Abor…
| ^
69| await flush();
70| f.engine.stop();
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/6]⎯
FAIL tests/audio.test.ts > 오디오 재생 수명과 믹서 > HTTP·디코드·장치 실패를 전달하고 다음 시도는 다시 로드한다
AssertionError: expected [Function] to throw error including '음성 파일' but got '오디오 미구현'
Expected: "음성 파일"
Received: "오디오 미구현"
❯ tests/audio.test.ts:79:58
77| const f = fixture();
78| f.fetcher.mockResolvedValueOnce({ ok: false } as Response);
79| await expect(f.engine.play(question, false, vi.fn())).rejects.toTh…
| ^
80| f.context.decodeAudioData.mockRejectedValueOnce(new Error('decode'…
81| await expect(f.engine.play(question, false, vi.fn())).rejects.toTh…
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[5/6]⎯
FAIL tests/audio.test.ts > 오디오 재생 수명과 믹서 > 전체 음량은 두 신호의 비율을 보존하며 0부터 1 범위로 제한한다
TypeError: Cannot read properties of undefined (reading 'gain')
❯ tests/audio.test.ts:94:24
92| const done = f.engine.play(question, false, vi.fn());
93| await flush();
94| expect(f.gains[0]!.gain.value).toBe(.3);
| ^
95| f.engine.setVolume(8);
96| expect(f.gains[0]!.gain.setTargetAtTime).toHaveBeenCalledWith(1, 2…
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[6/6]⎯
⎯⎯⎯⎯⎯⎯ Unhandled Errors ⎯⎯⎯⎯⎯⎯
Vitest caught 3 unhandled errors during the test run.
This might cause false positive tests. Resolve unhandled errors to make sure your tests are not affected.
⎯⎯⎯⎯ Unhandled Rejection ⎯⎯⎯⎯⎯
Error: 오디오 미구현
❯ AudioEngine.play src/audio/engine.ts:13:99
11| export class AudioEngine implements AudioPort {
12| constructor(_factory: () => AudioContext = () => new AudioContext(),…
13| async play(_question: Question, _intro: boolean, _onStarted: () => v…
| ^
14| stop(): void { /* RED 확인을 위한 인터페이스 자리만 확보한다. */ }
15| dispose(): void { /* 정리 동작은 취소 테스트 후 구현한다. */ }
❯ tests/audio.test.ts:34:27
❯ node_modules/vitest/dist/chunks/run.Cm2sd7-x.js:1632:35
❯ node_modules/vitest/dist/chunks/run.Cm2sd7-x.js:2794:26
❯ node_modules/vitest/dist/chunks/run.Cm2sd7-x.js:3334:20
❯ runWithCancel node_modules/vitest/dist/chunks/run.Cm2sd7-x.js:3329:10
❯ node_modules/vitest/dist/chunks/run.Cm2sd7-x.js:3314:20
❯ runWithTimeout node_modules/vitest/dist/chunks/run.Cm2sd7-x.js:3272:10
This error originated in "tests/audio.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.
⎯⎯⎯⎯ Unhandled Rejection ⎯⎯⎯⎯⎯
AssertionError: expected Error: 오디오 미구현 to match object { name: 'AbortError' }
❯ Assertion.__VITEST_REJECTS__ node_modules/vitest/dist/chunks/index.uwTEEBsB.js:2480:33
❯ Assertion.propertyGetter node_modules/chai/index.js:1607:27
❯ Object.proxyGetter [as get] node_modules/chai/index.js:1696:22
❯ tests/audio.test.ts:53:35
51| const f = fixture();
52| const done = f.engine.play(question, false, vi.fn());
53| const cancelled = expect(done).rejects.toMatchObject({ name: 'Abor…
| ^
54| await expect(f.engine.play(question, false, vi.fn())).rejects.toTh…
55| await flush();
❯ node_modules/vitest/dist/chunks/run.Cm2sd7-x.js:1632:35
❯ node_modules/vitest/dist/chunks/run.Cm2sd7-x.js:2794:26
❯ node_modules/vitest/dist/chunks/run.Cm2sd7-x.js:3334:20
❯ runWithCancel node_modules/vitest/dist/chunks/run.Cm2sd7-x.js:3329:10
This error originated in "tests/audio.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.
⎯⎯⎯⎯ Unhandled Rejection ⎯⎯⎯⎯⎯
Error: 오디오 미구현
❯ AudioEngine.play src/audio/engine.ts:13:99
11| export class AudioEngine implements AudioPort {
12| constructor(_factory: () => AudioContext = () => new AudioContext(),…
13| async play(_question: Question, _intro: boolean, _onStarted: () => v…
| ^
14| stop(): void { /* RED 확인을 위한 인터페이스 자리만 확보한다. */ }
15| dispose(): void { /* 정리 동작은 취소 테스트 후 구현한다. */ }
❯ tests/audio.test.ts:92:27
❯ node_modules/vitest/dist/chunks/run.Cm2sd7-x.js:1632:35
❯ node_modules/vitest/dist/chunks/run.Cm2sd7-x.js:2794:26
❯ node_modules/vitest/dist/chunks/run.Cm2sd7-x.js:3334:20
❯ runWithCancel node_modules/vitest/dist/chunks/run.Cm2sd7-x.js:3329:10
❯ node_modules/vitest/dist/chunks/run.Cm2sd7-x.js:3314:20
❯ runWithTimeout node_modules/vitest/dist/chunks/run.Cm2sd7-x.js:3272:10
This error originated in "tests/audio.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Test Files 1 failed (1)
Tests 5 failed (5)
Errors 3 errors
Start at 04:58:29
Duration 885ms (environment 75%, setup 13%, transform 7%, tests 3%, import 1%, worker 1%)
+30
View File
@@ -0,0 +1,30 @@
> find-word@0.1.0 test
> vitest run tests/assets.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
❯ tests/assets.test.ts (1 test | 1 failed) 7ms
❯ 저장한 한국어 음성 자산 (1)
× 모든 문장과 안내에 대응하는 로컬 WAV·출처·해시를 제공한다 5ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/assets.test.ts > 저장한 한국어 음성 자산 > 모든 문장과 안내에 대응하는 로컬 WAV·출처·해시를 제공한다
Error: ENOENT: no such file or directory, open 'public/audio/manifest.json'
❯ tests/assets.test.ts:11:33
9| describe('저장한 한국어 음성 자산', () => {
10| it('모든 문장과 안내에 대응하는 로컬 WAV·출처·해시를 제공한다', () => {
11| const manifest = JSON.parse(readFileSync('public/audio/manifest.js…
| ^
12| expect(manifest.generator).toContain('eSpeak NG');
13| const expected = [{ file: 'intro.wav', text: INTRO }, ...PATTERNS.…
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
Test Files 1 failed (1)
Tests 1 failed (1)
Start at 04:59:59
Duration 280ms (setup 65%, transform 24%, import 5%, tests 4%, worker 3%)
+12
View File
@@ -0,0 +1,12 @@
> find-word@0.1.0 test
> vitest run tests/App.test.tsx tests/assets.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 2 passed (2)
Tests 7 passed (7)
Start at 05:07:53
Duration 3.03s (tests 71%, environment 18%, setup 7%, transform 3%, import 1%)
+638
View File
@@ -0,0 +1,638 @@
> find-word@0.1.0 test
> vitest run tests/App.test.tsx tests/assets.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
❯ tests/App.test.tsx (6 tests | 1 failed) 1799ms
❯ 시작부터 결과까지 사용자 흐름 (6)
× 선택한 주관식·배경 환경으로 시작하고 빈 답변을 제출하지 않는다 49ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 선택한 주관식·배경 환경으로 시작하고 빈 답변을 제출하지 않는다
TestingLibraryElementError: Found multiple elements with the role "radio" and name `/직접 입력/`
Here are the matching elements:
Ignored nodes: comments, script, style
<input
checked=""
name="mode"
type="radio"
value="mixed"
/>
Ignored nodes: comments, script, style
<input
name="mode"
type="radio"
value="text"
/>
(If this is intentional, then use the `*AllBy*` variant of the query (like `queryAllByText`, `getAllByText`, or `findAllByText`)).
Ignored nodes: comments, script, style
<body>
<div>
<a
class="skip-link"
href="#main"
>
본문으로 건너뛰기
</a>
<header
class="header wrap"
>
<div
class="brand"
>
<span
aria-hidden="true"
class="brand-mark"
>
<i />
<i />
<i />
<i />
</span>
<h1>
단어를 맞춰라
</h1>
<span
class="version"
>
v0.1
</span>
</div>
<div
class="header-actions"
>
<a
href="#howto"
>
플레이 방법 
<span
aria-hidden="true"
>
↗
</span>
</a>
<span
class="little-dot"
/>
소리에 집중하는 시간
</div>
</header>
<main
class="wrap"
id="main"
tabindex="-1"
>
<section
aria-labelledby="hero-title"
class="hero"
>
<div
class="hero-copy"
>
<p
class="eyebrow"
>
<span />
 귀를 기울이면, 발견하는 즐거움
</p>
<h2
id="hero-title"
>
소음 속 작은 단어,
<br />
<em>
들리시나요?
</em>
</h2>
<p
class="lead"
>
도시의 소리 사이에 에스가 숨겨 둔 단어.
<br />
조금 더 귀 기울여, 오늘의 행운을 찾아보세요.
</p>
<div
class="hero-meta"
>
<span>
10개의 질문
</span>
<span>
5단계 도전
</span>
<span>
나만의 속도로
</span>
</div>
</div>
<div
class="sound-room"
>
<div
class="room-top"
>
<span>
<i />
 에스의 사운드 룸
</span>
<span>
01 / 08
</span>
</div>
<div
class="orbit orbit-one"
/>
<div
class="orbit orbit-two"
/>
<span
aria-hidden="true"
class="floating-note note-one"
>
♪
</span>
<span
aria-hidden="true"
class="floating-note note-two"
>
✳
</span>
<div
class="speech-bubble"
>
행운의 단어를 찾아보거라!
</div>
<div
aria-hidden="true"
class="mascot "
>
<span
class="ear left"
/>
<span
class="ear right"
/>
<span
class="eye left"
/>
<span
class="eye right"
/>
<span
class="smile"
/>
<b>
S
</b>
</div>
<div
class="room-bottom"
>
<div
aria-hidden="true"
class="sound-wave "
>
<i
style="height: 10px; animation-delay: 0s;"
/>
<i
style="height: 33.799955450859244px; animation-delay: 0.05s;"
/>
<i
style="height: 16.132986448643948px; animation-delay: 0.1s;"
/>
<i
style="height: 32.219552375865575px; animation-delay: 0.15000000000000002s;"
/>
<i
style="height: 21.858720427326595px; animation-delay: 0.2s;"
/>
<i
style="height: 29.163690702963766px; animation-delay: 0.25s;"
/>
<i
style="height: 26.796992502245015px; animation-delay: 0.30000000000000004s;"
/>
<i
style="height: 24.835290693688798px; animation-delay: 0.35000000000000003s;"
/>
<i
style="height: 30.619883556555898px; animation-delay: 0.4s;"
/>
<i
style="height: 35.39139668035928px; animation-delay: 0.45s;"
/>
<i
style="height: 71.52943948029164px; animation-delay: 0.5s;"
/>
<i
style="height: 19.53593765210872px; animation-delay: 0.55s;"
/>
<i
style="height: 73.98674560913082px; animation-delay: 0.6000000000000001s;"
/>
<i
style="height: 16.952742107140878px; animation-delay: 0.65s;"
/>
<i
style="height: 72.19510052760725px; animation-delay: 0.7000000000000001s;"
/>
<i
style="height: 32.979734657418774px; animation-delay: 0.75s;"
/>
<i
style="height: 66.27347594564635px; animation-delay: 0.8s;"
/>
<i
style="height: 47.480789758355044px; animation-delay: 0.8500000000000001s;"
/>
<i
style="height: 56.615089141222136px; animation-delay: 0.9s;"
/>
<i
style="height: 59.492984932238116px; animation-delay: 0.9500000000000001s;"
/>
<i
style="height: 43.861291911681526px; animation-delay: 1s;"
/>
<i
style="height: 68.21866699734562px; animation-delay: 1.05s;"
/>
<i
style="height: 28.858982496016484px; animation-delay: 1.1s;"
/>
<i
style="height: 73.07841912260051px; animation-delay: 1.1500000000000001s;"
/>
<i
style="height: 12.60436847039135px; animation-delay: 1.2000000000000002s;"
/>
<i
style="height: 73.74953619965402px; animation-delay: 1.25s;"
/>
<i
style="height: 23.8231850360459px; animation-delay: 1.3s;"
/>
<i
style="height: 32.57029511072814px; animation-delay: 1.35s;"
/>
<i
style="height: 20.99981090780238px; animation-delay: 1.4000000000000001s;"
/>
<i
style="height: 29.735764963203792px; animation-delay: 1.4500000000000002s;"
/>
<i
style="height: 26.085500220240995px; animation-delay: 1.5s;"
/>
<i
style="height: 25.59070868046883px; animation-delay: 1.55s;"
/>
<i
style="height: 30.10305417152866px; animation-delay: 1.6s;"
/>
<i
style="height: 20.410372983416416px; animation-delay: 1.6500000000000001s;"
/>
<i
style="height: 32.785692656480826px; animation-delay: 1.7000000000000002s;"
/>
</div>
<span>
소음 사이, 숨겨진 한마디
</span>
</div>
</div>
</section>
<section
aria-labelledby="setup-title"
class="setup-card"
>
<div
class="setup-heading"
>
<span
class="section-number"
>
01
</span>
<div>
<h2
id="setup-title"
>
나의 소리 탐험 준비
</h2>
<p>
편한 방식으로 시작하세요. 난이도는 조금씩 높아져요.
</p>
</div>
<span
class="round-badge"
>
약 3–5분
</span>
</div>
<div
class="setup-grid"
>
<fieldset
class="mode-field"
>
<legend>
어떻게 답할까요?
</legend>
<div
class="mode-options"
>
<label
class="mode-option selected"
>
<input
checked=""
name="mode"
type="radio"
value="mixed"
/>
<span
aria-hidden="true"
class="mode-icon"
>
◈
</span>
<strong>
골고루 풀기
</strong>
<small>
선택 + 직접 입력
</small>
</label>
<label
class="mode-option "
>
<input
name="mode"
type="radio"
value="choice"
/>
<span
aria-hidden="true"
class="mode-icon"
>
☷
</span>
<strong>
보기에서 선택
</strong>
<small>
4개 단어 중 하나
</small>
</label>
<label
class="mode-option "
>
<input
name="mode"
type="radio"
value="text"
/>
<span
aria-hidden="true"
class="mode-icon"
>
✎
</span>
<strong>
직접 입력
</strong>
<small>
기억한 단어 쓰기
</small>
</label>
</div>
</fieldset>
<div
class="environment-field"
>
<label
for="environment"
>
배경 환경
</label>
<select
id="environment"
>
<option
value="rotate"
>
여덟 가지 일상 속으로
</option>
<option
value="street"
>
길거리
</option>
<option
value="mall"
>
쇼핑몰
</option>
<option
value="theater"
>
극장
</option>
<option
value="department"
>
백화점
</option>
<option
value="market"
>
시장
</option>
<option
value="subway"
>
지하철
</option>
<option
value="cafe"
>
카페
</option>
<option
value="rain"
>
비 오는 거리
</option>
</select>
<p>
길거리부터 비 오는 거리까지, 매번 다른 공간을 만나요.
</p>
<div
class="environment-chips"
>
<span>
길거리
</span>
<span>
카페
</span>
<span>
지하철
</span>
<span>
+5개 환경
</span>
</div>
</div>
</div>
<div
class="start-row"
>
<p>
<span
aria-hidden="true"
>
♬
</span>
 편안한 작은 음량으로 시작해 보세요.
</p>
<button
class="primary"
>
소리 시작 
<span
aria-hidden="true"
>
→
</span>
</button>
</div>
</section>
<section
aria-labelledby="howto-title"
class="howto"
id="howto"
>
<h2
id="howto-title"
>
작은 단어를 발견하는 방법
</h2>
<div
class="steps"
>
<article>
<span>
01
</span>
<h3>
먼저, 귀 기울이기
</h3>
<p>
에스의 안내와 소음 속 문장을 들어요.
<br />
처음에는 또렷하게, 점점 더 작게.
</p>
</article>
<article>
<span>
02
</span>
<h3>
기억한 단어 찾기
</h3>
<p>
소리가 끝나면 질문이 나타나요.
<br />
보기에서 고르거나 직접 입력해요.
</p>
</article>
<article>
<span>
03
</span>
<h3>
나의 발견 돌아보기
</h3>
<p>
열 문제 뒤 나의 결과를 확인해요.
<br />
놓쳤다면 언제든 다시 들어도 좋아요.
</p>
</article>
</div>
</section>
<div
class="volume-row"
>
<label
for="volume"
>
<span
aria-hidden="true"
>
◖))
</span>
 전체 음량
</label>
<input
id="volume"
max="100"
min="0"
type="range"
value="35"
/>
<output
for="volume"
>
35
%
</output>
</div>
</main>
<footer
class="footer wrap"
>
<p>
Designed by OZ at HearT-M Medical Inc.
</p>
<p>
청각적 주의·식별 게임 · 의료적 진단이나 치료를 제공하지 않습니다.
</p>
<span>
개인정보 수집 없이, 오직 플레이만.
</span>
</footer>
</div>
</body>
❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19
❯ getElementError node_modules/@testing-library/dom/dist/query-helpers.js:20:35
❯ getMultipleElementsFoundError node_modules/@testing-library/dom/dist/query-helpers.js:23:10
❯ node_modules/@testing-library/dom/dist/query-helpers.js:55:13
❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19
❯ tests/App.test.tsx:87:31
85| it('선택한 주관식·배경 환경으로 시작하고 빈 답변을 제출하지 않는다', async () => {
86| const f = fixture();
87| await f.user.click(screen.getByRole('radio', { name: /직접 입력/ }));
| ^
88| await f.user.selectOptions(screen.getByLabelText('배경 환경'), 'rain');
89| await f.user.click(screen.getByRole('button', { name: /소리 시작/ }));
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
Test Files 1 failed | 1 passed (2)
Tests 1 failed | 6 passed (7)
Start at 05:07:31
Duration 2.86s (tests 69%, environment 19%, setup 6%, transform 3%, import 2%)
+215
View File
@@ -0,0 +1,215 @@
> find-word@0.1.0 test
> vitest run tests/App.test.tsx
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
❯ tests/App.test.tsx (6 tests | 6 failed) 192ms
❯ 시작부터 결과까지 사용자 흐름 (6)
× 브랜드·버전·크레딧을 보여주며 직접 시작하기 전에는 재생하지 않는다 147ms
× 재생 동안 답을 숨기고 중복 클릭을 막으며 끝나면 네 선택지에 접근할 수 있다 11ms
× 혼합 모드 열 문제를 키보드·텍스트로 풀고 결과에서 다시 시작한다 9ms
× 오디오 오류를 알리고 재시도하며 나가면 장치를 정리한다 10ms
× 선택한 주관식·배경 환경으로 시작하고 빈 답변을 제출하지 않는다 7ms
× 음량 조절이 엔진에 전달되고 세션 종료 뒤 늦은 완료는 무시한다 6ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 6 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 브랜드·버전·크레딧을 보여주며 직접 시작하기 전에는 재생하지 않는다
TestingLibraryElementError: Unable to find an accessible element with the role "heading" and name "단어를 맞춰라"
Here are the accessible roles:
main:
Name "":
<main />
--------------------------------------------------
Ignored nodes: comments, script, style
<body>
<div>
<main />
</div>
</body>
❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19
❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38
❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17
❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19
❯ tests/App.test.tsx:25:19
23| it('브랜드·버전·크레딧을 보여주며 직접 시작하기 전에는 재생하지 않는다', () => {
24| const f = fixture();
25| expect(screen.getByRole('heading', { name: '단어를 맞춰라' })).toBeInThe…
| ^
26| expect(screen.getByText('Designed by OZ at HearT-M Medical Inc.'))…
27| expect(screen.getByText('v0.1')).toBeInTheDocument();
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/6]⎯
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 재생 동안 답을 숨기고 중복 클릭을 막으며 끝나면 네 선택지에 접근할 수 있다
TestingLibraryElementError: Unable to find an accessible element with the role "button" and name `/소리 시작/`
Here are the accessible roles:
main:
Name "":
<main />
--------------------------------------------------
Ignored nodes: comments, script, style
<body>
<div>
<main />
</div>
</body>
❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19
❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38
❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17
❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19
❯ tests/App.test.tsx:33:31
31| it('재생 동안 답을 숨기고 중복 클릭을 막으며 끝나면 네 선택지에 접근할 수 있다', async () => {
32| const f = fixture();
33| await f.user.click(screen.getByRole('button', { name: /소리 시작/ }));
| ^
34| expect(f.audio.play).toHaveBeenCalledTimes(1);
35| expect(screen.queryByRole('group', { name: '답 선택' })).not.toBeInTh…
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/6]⎯
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 혼합 모드 열 문제를 키보드·텍스트로 풀고 결과에서 다시 시작한다
TestingLibraryElementError: Unable to find an accessible element with the role "button" and name `/소리 시작/`
Here are the accessible roles:
main:
Name "":
<main />
--------------------------------------------------
Ignored nodes: comments, script, style
<body>
<div>
<main />
</div>
</body>
❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19
❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38
❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17
❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19
❯ tests/App.test.tsx:51:31
49| const f = fixture();
50| const questions = createQuestions(42, 'mixed');
51| await f.user.click(screen.getByRole('button', { name: /소리 시작/ }));
| ^
52| for (let i = 0; i < questions.length; i++) {
53| if (i) await f.user.click(screen.getByRole('button', { name: /문장…
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/6]⎯
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 오디오 오류를 알리고 재시도하며 나가면 장치를 정리한다
TestingLibraryElementError: Unable to find an accessible element with the role "button" and name `/소리 시작/`
Here are the accessible roles:
main:
Name "":
<main />
--------------------------------------------------
Ignored nodes: comments, script, style
<body>
<div>
<main />
</div>
</body>
❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19
❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38
❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17
❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19
❯ tests/App.test.tsx:76:31
74| it('오디오 오류를 알리고 재시도하며 나가면 장치를 정리한다', async () => {
75| const f = fixture();
76| await f.user.click(screen.getByRole('button', { name: /소리 시작/ }));
| ^
77| await f.fail();
78| expect(screen.getByRole('alert')).toHaveTextContent(/소리를 재생하지 못했어요…
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/6]⎯
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 선택한 주관식·배경 환경으로 시작하고 빈 답변을 제출하지 않는다
TestingLibraryElementError: Unable to find an accessible element with the role "radio" and name `/직접 입력/`
Here are the accessible roles:
main:
Name "":
<main />
--------------------------------------------------
Ignored nodes: comments, script, style
<body>
<div>
<main />
</div>
</body>
❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19
❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38
❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17
❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19
❯ tests/App.test.tsx:87:31
85| it('선택한 주관식·배경 환경으로 시작하고 빈 답변을 제출하지 않는다', async () => {
86| const f = fixture();
87| await f.user.click(screen.getByRole('radio', { name: /직접 입력/ }));
| ^
88| await f.user.selectOptions(screen.getByLabelText('배경 환경'), 'rain');
89| await f.user.click(screen.getByRole('button', { name: /소리 시작/ }));
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[5/6]⎯
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 음량 조절이 엔진에 전달되고 세션 종료 뒤 늦은 완료는 무시한다
TestingLibraryElementError: Unable to find an accessible element with the role "slider" and name "전체 음량"
Here are the accessible roles:
main:
Name "":
<main />
--------------------------------------------------
Ignored nodes: comments, script, style
<body>
<div>
<main />
</div>
</body>
❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19
❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38
❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17
❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19
❯ tests/App.test.tsx:100:29
98| it('음량 조절이 엔진에 전달되고 세션 종료 뒤 늦은 완료는 무시한다', async () => {
99| const f = fixture();
100| fireEvent.change(screen.getByRole('slider', { name: '전체 음량' }), { …
| ^
101| expect(f.audio.setVolume).toHaveBeenCalledWith(.2);
102| await f.user.click(screen.getByRole('button', { name: /소리 시작/ }));
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[6/6]⎯
Test Files 1 failed (1)
Tests 6 failed (6)
Start at 05:01:34
Duration 1.08s (environment 60%, tests 20%, setup 10%, transform 5%, import 4%, worker 1%)
+12
View File
@@ -0,0 +1,12 @@
> find-word@0.1.0 test
> vitest run tests/build.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 1 passed (1)
Tests 1 passed (1)
Start at 05:09:30
Duration 438ms (tests 57%, setup 32%, transform 9%, worker 1%, import 1%)
+34
View File
@@ -0,0 +1,34 @@
> find-word@0.1.0 test
> vitest run tests/build.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
❯ tests/build.test.ts (1 test | 1 failed) 61ms
❯ production 자산 검증기 (1)
× 101개 음성 포함을 확인하고 누락·변조는 실패시킨다 60ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/build.test.ts > production 자산 검증기 > 101개 음성 포함을 확인하고 누락·변조는 실패시킨다
AssertionError: expected '0' to be '101' // Object.is equality
Expected: "101"
Received: "0"
❯ tests/build.test.ts:16:31
14| try {
15| cpSync('public/audio', join(temporary, 'audio'), { recursive: tr…
16| expect(verify().trim()).toBe('101');
| ^
17| writeFileSync(join(temporary, 'audio/intro.wav'), 'broken');
18| expect(verify).toThrow();
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
Test Files 1 failed (1)
Tests 1 failed (1)
Start at 05:08:45
Duration 318ms (setup 52%, tests 31%, transform 14%, worker 2%, import 2%)
+12
View File
@@ -0,0 +1,12 @@
> find-word@0.1.0 test
> vitest run tests/content.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 1 passed (1)
Tests 4 passed (4)
Start at 05:11:10
Duration 878ms (environment 78%, setup 14%, transform 5%, tests 1%, worker 1%)
+32
View File
@@ -0,0 +1,32 @@
> find-word@0.1.0 test
> vitest run tests/content.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
❯ tests/content.test.ts (4 tests | 1 failed) 13ms
❯ 저장된 문제·환경 데이터 (4)
× 오답마다 서로 다른 변형 혼동 난이도를 추적할 수 있다 6ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/content.test.ts > 저장된 문제·환경 데이터 > 오답마다 서로 다른 변형 혼동 난이도를 추적할 수 있다
TypeError: actual value must be number or bigint, received "undefined"
❯ tests/content.test.ts:10:42
8| WORDS.forEach(entry => {
9| const ranks = entry.distractors.map(d => RULE_DIFFICULTY[d.rule]…
10| ranks.forEach(rank => expect(rank).toBeGreaterThan(0));
| ^
11| expect(new Set(ranks).size).toBe(3);
12| });
❯ tests/content.test.ts:10:13
❯ tests/content.test.ts:8:11
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
Test Files 1 failed (1)
Tests 1 failed | 3 passed (4)
Start at 05:10:58
Duration 885ms (environment 78%, setup 13%, transform 6%, tests 2%, worker 1%, import 1%)
+12
View File
@@ -0,0 +1,12 @@
> find-word@0.1.0 test
> vitest run tests/App.test.tsx
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 1 passed (1)
Tests 7 passed (7)
Start at 05:16:05
Duration 2.96s (tests 71%, environment 20%, setup 4%, transform 4%, import 1%)
+36
View File
@@ -0,0 +1,36 @@
> find-word@0.1.0 test
> vitest run tests/App.test.tsx
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
❯ tests/App.test.tsx (7 tests | 1 failed) 2021ms
❯ 시작부터 결과까지 사용자 흐름 (7)
× 한글 조합을 확정하는 Enter는 채점하지 않고 다음 제출을 기다린다 345ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 한글 조합을 확정하는 Enter는 채점하지 않고 다음 제출을 기다린다
Error: expect(element).not.toBeInTheDocument()
expected document not to contain element, found <h2
tabindex="-1"
>
정답이에요!
</h2> instead
❯ tests/App.test.tsx:56:46
54| fireEvent.change(textbox, { target: { value: createQuestions(42, "…
55| fireEvent.submit(textbox.closest("form")!);
56| expect(screen.queryByText("정답이에요!")).not.toBeInTheDocument();
| ^
57| fireEvent.compositionEnd(textbox);
58| fireEvent.submit(textbox.closest("form")!);
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
Test Files 1 failed (1)
Tests 1 failed | 6 passed (7)
Start at 05:15:12
Duration 2.96s (tests 71%, environment 20%, transform 4%, setup 3%, import 1%)
+12
View File
@@ -0,0 +1,12 @@
> find-word@0.1.0 test
> vitest run tests/App.test.tsx -t 한글 조합 도중
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 1 passed (1)
Tests 1 passed | 7 skipped (8)
Start at 05:17:57
Duration 1.38s (environment 44%, tests 38%, setup 8%, transform 7%, import 3%, worker 1%)
+395
View File
@@ -0,0 +1,395 @@
> find-word@0.1.0 test
> vitest run tests/App.test.tsx -t 한글 조합 도중
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
❯ tests/App.test.tsx (8 tests | 1 failed | 7 skipped) 488ms
❯ 시작부터 결과까지 사용자 흐름 (8)
× 한글 조합 도중 처음으로 돌아가도 새 세션의 입력은 정상 제출된다 487ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 한글 조합 도중 처음으로 돌아가도 새 세션의 입력은 정상 제출된다
TestingLibraryElementError: Unable to find an element with the text: 정답이에요!. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
Ignored nodes: comments, script, style
<body>
<div>
<a
class="skip-link"
href="#main"
>
본문으로 건너뛰기
</a>
<header
class="header wrap"
>
<div
class="brand"
>
<span
aria-hidden="true"
class="brand-mark"
>
<i />
<i />
<i />
<i />
</span>
<h1>
단어를 맞춰라
</h1>
<span
class="version"
>
v0.1
</span>
</div>
<div
class="header-actions"
>
<button
class="text-button"
>
처음으로
</button>
<span
class="little-dot"
/>
소리에 집중하는 시간
</div>
</header>
<main
class="wrap"
id="main"
tabindex="-1"
>
<section
aria-label="단어 찾기 게임"
class="game-layout"
>
<div
class="game-top"
>
<span
class="eyebrow"
>
나의 소리 탐험
</span>
<span>
문제 
<b>
01
</b>
 / 10
</span>
</div>
<progress
aria-label="전체 진행률"
max="10"
value="0"
/>
<div
class="game-columns"
>
<aside
class="environment-panel"
>
<p
class="eyebrow"
>
지금 듣고 있는 공간
</p>
<h2>
길거리
</h2>
<p>
지나가는 차와 도시의 웅성임
</p>
<div
aria-hidden="true"
class="mascot small"
>
<span
class="ear left"
/>
<span
class="ear right"
/>
<span
class="eye left"
/>
<span
class="eye right"
/>
<span
class="smile"
/>
<b>
S
</b>
</div>
<div
aria-hidden="true"
class="sound-wave "
>
<i
style="height: 10px; animation-delay: 0s;"
/>
<i
style="height: 33.799955450859244px; animation-delay: 0.05s;"
/>
<i
style="height: 16.132986448643948px; animation-delay: 0.1s;"
/>
<i
style="height: 32.219552375865575px; animation-delay: 0.15000000000000002s;"
/>
<i
style="height: 21.858720427326595px; animation-delay: 0.2s;"
/>
<i
style="height: 29.163690702963766px; animation-delay: 0.25s;"
/>
<i
style="height: 26.796992502245015px; animation-delay: 0.30000000000000004s;"
/>
<i
style="height: 24.835290693688798px; animation-delay: 0.35000000000000003s;"
/>
<i
style="height: 30.619883556555898px; animation-delay: 0.4s;"
/>
<i
style="height: 35.39139668035928px; animation-delay: 0.45s;"
/>
<i
style="height: 71.52943948029164px; animation-delay: 0.5s;"
/>
<i
style="height: 19.53593765210872px; animation-delay: 0.55s;"
/>
<i
style="height: 73.98674560913082px; animation-delay: 0.6000000000000001s;"
/>
<i
style="height: 16.952742107140878px; animation-delay: 0.65s;"
/>
<i
style="height: 72.19510052760725px; animation-delay: 0.7000000000000001s;"
/>
<i
style="height: 32.979734657418774px; animation-delay: 0.75s;"
/>
<i
style="height: 66.27347594564635px; animation-delay: 0.8s;"
/>
<i
style="height: 47.480789758355044px; animation-delay: 0.8500000000000001s;"
/>
<i
style="height: 56.615089141222136px; animation-delay: 0.9s;"
/>
<i
style="height: 59.492984932238116px; animation-delay: 0.9500000000000001s;"
/>
<i
style="height: 43.861291911681526px; animation-delay: 1s;"
/>
<i
style="height: 68.21866699734562px; animation-delay: 1.05s;"
/>
<i
style="height: 28.858982496016484px; animation-delay: 1.1s;"
/>
<i
style="height: 73.07841912260051px; animation-delay: 1.1500000000000001s;"
/>
<i
style="height: 12.60436847039135px; animation-delay: 1.2000000000000002s;"
/>
<i
style="height: 73.74953619965402px; animation-delay: 1.25s;"
/>
<i
style="height: 23.8231850360459px; animation-delay: 1.3s;"
/>
<i
style="height: 32.57029511072814px; animation-delay: 1.35s;"
/>
<i
style="height: 20.99981090780238px; animation-delay: 1.4000000000000001s;"
/>
<i
style="height: 29.735764963203792px; animation-delay: 1.4500000000000002s;"
/>
<i
style="height: 26.085500220240995px; animation-delay: 1.5s;"
/>
<i
style="height: 25.59070868046883px; animation-delay: 1.55s;"
/>
<i
style="height: 30.10305417152866px; animation-delay: 1.6s;"
/>
<i
style="height: 20.410372983416416px; animation-delay: 1.6500000000000001s;"
/>
<i
style="height: 32.785692656480826px; animation-delay: 1.7000000000000002s;"
/>
</div>
<div
class="level-tag"
>
1
단계 · 
+
6
 dB
</div>
<p
class="db-note"
>
음성 / 배경의 상대 음량
<br />
소리에 익숙해지기
</p>
<div
class="mini-stats"
>
<span>
점수 
<b>
0
</b>
</span>
<span>
연속 정답 
<b>
0
</b>
</span>
</div>
</aside>
<div
class="question-panel panel"
>
<div
class="question-meta"
>
<span>
마지막 단어
</span>
<span>
주관식
</span>
</div>
<h2
tabindex="-1"
>
마지막에 나온 단어는 무엇인가요?
</h2>
<p
class="muted"
>
기억나는 단어 하나를 입력해 주세요.
</p>
<form>
<label
for="answer"
>
들은 단어
</label>
<input
autocomplete="off"
id="answer"
maxlength="60"
placeholder="여기에 단어를 적어 주세요"
value="기름"
/>
<button
class="primary"
>
정답 확인
</button>
</form>
<div
class="play-controls"
>
<button
class="secondary"
>
↻ 다시 듣기
</button>
<small>
다시 들어도 점수는 줄어들지 않아요.
</small>
</div>
</div>
</div>
</section>
<div
class="volume-row"
>
<label
for="volume"
>
<span
aria-hidden="true"
>
◖))
</span>
 전체 음량
</label>
<input
id="volume"
max="100"
min="0"
type="range"
value="35"
/>
<output
for="volume"
>
35
%
</output>
</div>
</main>
<footer
class="footer wrap"
>
<p>
Designed by OZ at HearT-M Medical Inc.
</p>
<p>
청각적 주의·식별 게임 · 의료적 진단이나 치료를 제공하지 않습니다.
</p>
<span>
개인정보 수집 없이, 오직 플레이만.
</span>
</footer>
</div>
</body>
❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19
❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38
❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17
❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19
❯ tests/App.test.tsx:58:19
56| await f.user.type(screen.getByRole("textbox", { name: "들은 단어" }), …
57| await f.user.click(screen.getByRole("button", { name: "정답 확인" }));
58| expect(screen.getByText("정답이에요!")).toBeInTheDocument();
| ^
59| });
60| it("한글 조합을 확정하는 Enter는 채점하지 않고 다음 제출을 기다린다", async () => {
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
Test Files 1 failed (1)
Tests 1 failed | 7 skipped (8)
Start at 05:17:35
Duration 1.42s (environment 43%, tests 38%, transform 8%, setup 7%, import 3%, worker 1%)
+12
View File
@@ -0,0 +1,12 @@
> find-word@0.1.0 test
> vitest run tests/assets.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 1 passed (1)
Tests 2 passed (2)
Start at 05:18:31
Duration 736ms (tests 79%, setup 17%, transform 2%, import 1%, worker 1%)
+30
View File
@@ -0,0 +1,30 @@
> find-word@0.1.0 test
> vitest run tests/assets.test.ts -t 도구 버전
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
❯ tests/assets.test.ts (2 tests | 1 failed | 1 skipped) 8ms
❯ 저장한 한국어 음성 자산 (2)
× 실제 생성 도구 버전을 기록하여 재생성의 출처를 추적한다 6ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/assets.test.ts > 저장한 한국어 음성 자산 > 실제 생성 도구 버전을 기록하여 재생성의 출처를 추적한다
TypeError: .toMatch() expects to receive a string, but got undefined
❯ tests/assets.test.ts:12:43
10| it("실제 생성 도구 버전을 기록하여 재생성의 출처를 추적한다", () => {
11| const manifest = JSON.parse(readFileSync("public/audio/manifest.js…
12| expect(manifest.toolVersions?.espeak).toMatch(/eSpeak NG.*\d+\.\d+…
| ^
13| expect(manifest.toolVersions?.ffmpeg).toMatch(/ffmpeg version \d+\…
14| });
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
Test Files 1 failed (1)
Tests 1 failed | 1 skipped (2)
Start at 05:17:59
Duration 285ms (setup 63%, transform 25%, import 5%, tests 5%, worker 2%)
+12
View File
@@ -0,0 +1,12 @@
> find-word@0.1.0 test
> vitest run tests/App.test.tsx -t 한글 조합 도중
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 1 passed (1)
Tests 1 passed | 7 skipped (8)
Start at 05:19:29
Duration 1.45s (environment 43%, tests 39%, setup 8%, transform 6%, import 3%, worker 1%)
+395
View File
@@ -0,0 +1,395 @@
> find-word@0.1.0 test
> vitest run tests/App.test.tsx -t 한글 조합 도중
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
❯ tests/App.test.tsx (8 tests | 1 failed | 7 skipped) 542ms
❯ 시작부터 결과까지 사용자 흐름 (8)
× 한글 조합 도중 처음으로 돌아가도 새 세션의 입력은 정상 제출된다 539ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 한글 조합 도중 처음으로 돌아가도 새 세션의 입력은 정상 제출된다
TestingLibraryElementError: Unable to find an element with the text: 정답이에요!. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
Ignored nodes: comments, script, style
<body>
<div>
<a
class="skip-link"
href="#main"
>
본문으로 건너뛰기
</a>
<header
class="header wrap"
>
<div
class="brand"
>
<span
aria-hidden="true"
class="brand-mark"
>
<i />
<i />
<i />
<i />
</span>
<h1>
단어를 맞춰라
</h1>
<span
class="version"
>
v0.1
</span>
</div>
<div
class="header-actions"
>
<button
class="text-button"
>
처음으로
</button>
<span
class="little-dot"
/>
소리에 집중하는 시간
</div>
</header>
<main
class="wrap"
id="main"
tabindex="-1"
>
<section
aria-label="단어 찾기 게임"
class="game-layout"
>
<div
class="game-top"
>
<span
class="eyebrow"
>
나의 소리 탐험
</span>
<span>
문제 
<b>
01
</b>
 / 10
</span>
</div>
<progress
aria-label="전체 진행률"
max="10"
value="0"
/>
<div
class="game-columns"
>
<aside
class="environment-panel"
>
<p
class="eyebrow"
>
지금 듣고 있는 공간
</p>
<h2>
길거리
</h2>
<p>
지나가는 차와 도시의 웅성임
</p>
<div
aria-hidden="true"
class="mascot small"
>
<span
class="ear left"
/>
<span
class="ear right"
/>
<span
class="eye left"
/>
<span
class="eye right"
/>
<span
class="smile"
/>
<b>
S
</b>
</div>
<div
aria-hidden="true"
class="sound-wave "
>
<i
style="height: 10px; animation-delay: 0s;"
/>
<i
style="height: 33.799955450859244px; animation-delay: 0.05s;"
/>
<i
style="height: 16.132986448643948px; animation-delay: 0.1s;"
/>
<i
style="height: 32.219552375865575px; animation-delay: 0.15000000000000002s;"
/>
<i
style="height: 21.858720427326595px; animation-delay: 0.2s;"
/>
<i
style="height: 29.163690702963766px; animation-delay: 0.25s;"
/>
<i
style="height: 26.796992502245015px; animation-delay: 0.30000000000000004s;"
/>
<i
style="height: 24.835290693688798px; animation-delay: 0.35000000000000003s;"
/>
<i
style="height: 30.619883556555898px; animation-delay: 0.4s;"
/>
<i
style="height: 35.39139668035928px; animation-delay: 0.45s;"
/>
<i
style="height: 71.52943948029164px; animation-delay: 0.5s;"
/>
<i
style="height: 19.53593765210872px; animation-delay: 0.55s;"
/>
<i
style="height: 73.98674560913082px; animation-delay: 0.6000000000000001s;"
/>
<i
style="height: 16.952742107140878px; animation-delay: 0.65s;"
/>
<i
style="height: 72.19510052760725px; animation-delay: 0.7000000000000001s;"
/>
<i
style="height: 32.979734657418774px; animation-delay: 0.75s;"
/>
<i
style="height: 66.27347594564635px; animation-delay: 0.8s;"
/>
<i
style="height: 47.480789758355044px; animation-delay: 0.8500000000000001s;"
/>
<i
style="height: 56.615089141222136px; animation-delay: 0.9s;"
/>
<i
style="height: 59.492984932238116px; animation-delay: 0.9500000000000001s;"
/>
<i
style="height: 43.861291911681526px; animation-delay: 1s;"
/>
<i
style="height: 68.21866699734562px; animation-delay: 1.05s;"
/>
<i
style="height: 28.858982496016484px; animation-delay: 1.1s;"
/>
<i
style="height: 73.07841912260051px; animation-delay: 1.1500000000000001s;"
/>
<i
style="height: 12.60436847039135px; animation-delay: 1.2000000000000002s;"
/>
<i
style="height: 73.74953619965402px; animation-delay: 1.25s;"
/>
<i
style="height: 23.8231850360459px; animation-delay: 1.3s;"
/>
<i
style="height: 32.57029511072814px; animation-delay: 1.35s;"
/>
<i
style="height: 20.99981090780238px; animation-delay: 1.4000000000000001s;"
/>
<i
style="height: 29.735764963203792px; animation-delay: 1.4500000000000002s;"
/>
<i
style="height: 26.085500220240995px; animation-delay: 1.5s;"
/>
<i
style="height: 25.59070868046883px; animation-delay: 1.55s;"
/>
<i
style="height: 30.10305417152866px; animation-delay: 1.6s;"
/>
<i
style="height: 20.410372983416416px; animation-delay: 1.6500000000000001s;"
/>
<i
style="height: 32.785692656480826px; animation-delay: 1.7000000000000002s;"
/>
</div>
<div
class="level-tag"
>
1
단계 · 
+
6
 dB
</div>
<p
class="db-note"
>
음성 / 배경의 상대 음량
<br />
소리에 익숙해지기
</p>
<div
class="mini-stats"
>
<span>
점수 
<b>
0
</b>
</span>
<span>
연속 정답 
<b>
0
</b>
</span>
</div>
</aside>
<div
class="question-panel panel"
>
<div
class="question-meta"
>
<span>
마지막 단어
</span>
<span>
주관식
</span>
</div>
<h2
tabindex="-1"
>
마지막에 나온 단어는 무엇인가요?
</h2>
<p
class="muted"
>
기억나는 단어 하나를 입력해 주세요.
</p>
<form>
<label
for="answer"
>
들은 단어
</label>
<input
autocomplete="off"
id="answer"
maxlength="60"
placeholder="여기에 단어를 적어 주세요"
value="기름"
/>
<button
class="primary"
>
정답 확인
</button>
</form>
<div
class="play-controls"
>
<button
class="secondary"
>
↻ 다시 듣기
</button>
<small>
다시 들어도 점수는 줄어들지 않아요.
</small>
</div>
</div>
</div>
</section>
<div
class="volume-row"
>
<label
for="volume"
>
<span
aria-hidden="true"
>
◖))
</span>
 전체 음량
</label>
<input
id="volume"
max="100"
min="0"
type="range"
value="35"
/>
<output
for="volume"
>
35
%
</output>
</div>
</main>
<footer
class="footer wrap"
>
<p>
Designed by OZ at HearT-M Medical Inc.
</p>
<p>
청각적 주의·식별 게임 · 의료적 진단이나 치료를 제공하지 않습니다.
</p>
<span>
개인정보 수집 없이, 오직 플레이만.
</span>
</footer>
</div>
</body>
❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19
❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38
❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17
❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19
❯ tests/App.test.tsx:61:19
59| await f.user.type(screen.getByRole("textbox", { name: "들은 단어" }), …
60| await f.user.click(screen.getByRole("button", { name: "정답 확인" }));
61| expect(screen.getByText("정답이에요!")).toBeInTheDocument();
| ^
62| });
63| it("한글 조합을 확정하는 Enter는 채점하지 않고 다음 제출을 기다린다", async () => {
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
Test Files 1 failed (1)
Tests 1 failed | 7 skipped (8)
Start at 05:18:58
Duration 1.41s (environment 43%, tests 42%, setup 7%, transform 4%, import 3%, worker 1%)
@@ -0,0 +1,15 @@
> find-word@0.1.0 build
> tsc --noEmit && vite build && node scripts/check-build.mjs
vite v8.3.4 building client environment for production...
transforming...
✓ 20 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html 0.89 kB │ gzip: 0.63 kB
dist/assets/index-Bq8F62nv.css 14.90 kB │ gzip: 4.30 kB
dist/assets/index-DKxtGloM.js 244.26 kB │ gzip: 76.95 kB
✓ built in 157ms
빌드 음성 자산 101개 확인
@@ -0,0 +1,15 @@
> find-word@0.1.0 build
> tsc --noEmit && vite build && node scripts/check-build.mjs
vite v8.3.4 building client environment for production...
transforming...
✓ 20 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html 0.89 kB │ gzip: 0.63 kB
dist/assets/index-BEQhGx9x.css 14.87 kB │ gzip: 4.29 kB
dist/assets/index-ZqUFJyNr.js 244.26 kB │ gzip: 76.95 kB
✓ built in 162ms
빌드 음성 자산 101개 확인
+14
View File
@@ -0,0 +1,14 @@
> find-word@0.1.0 test:browser
> playwright test tests/browser/ui.spec.ts --project=mobile
[WebServer] (node:752303) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)
Running 1 test using 1 worker
(node:752323) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 1 [mobile] › tests/browser/ui.spec.ts:6:1 › 한국어 제목은 단어 중간에서 줄바꿈하지 않는다 (13.6s)
1 passed (14.7s)
+112
View File
@@ -0,0 +1,112 @@
> find-word@0.1.0 test:browser
> playwright test tests/browser/ui.spec.ts --project=mobile
[WebServer] (node:752009) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)
Running 1 test using 1 worker
(node:752029) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✘ 1 [mobile] › tests/browser/ui.spec.ts:6:1 › 한국어 제목은 단어 중간에서 줄바꿈하지 않는다 (13.7s)
1) [mobile] › tests/browser/ui.spec.ts:6:1 › 한국어 제목은 단어 중간에서 줄바꿈하지 않는다 ───────────────────────────
Error: expect(received).toEqual(expected) // deep equality
- Expected - 7
+ Received + 7
Array [
Object {
"title": "단어를 맞춰라",
- "wordBreak": "keep-all",
+ "wordBreak": "normal",
},
Object {
"title": "소음 속 작은 단어,들리시나요?",
- "wordBreak": "keep-all",
+ "wordBreak": "normal",
},
Object {
"title": "나의 소리 탐험 준비",
- "wordBreak": "keep-all",
+ "wordBreak": "normal",
},
Object {
"title": "작은 단어를 발견하는 방법",
- "wordBreak": "keep-all",
+ "wordBreak": "normal",
},
Object {
"title": "먼저, 귀 기울이기",
- "wordBreak": "keep-all",
+ "wordBreak": "normal",
},
Object {
"title": "기억한 단어 찾기",
- "wordBreak": "keep-all",
+ "wordBreak": "normal",
},
Object {
"title": "나의 발견 돌아보기",
- "wordBreak": "keep-all",
+ "wordBreak": "normal",
},
]
17 | );
18 | const homeHeadings = await wordBreaks();
> 19 | expect.soft(homeHeadings).toEqual(
| ^
20 | homeHeadings.map((heading) => ({ ...heading, wordBreak: "keep-all" })),
21 | );
22 | await page.getByRole("button", { name: /소리 시작/ }).click();
at /home/hermes/worktrees/find-word-v0.1/tests/browser/ui.spec.ts:19:29
Error: expect(received).toEqual(expected) // deep equality
- Expected - 3
+ Received + 3
Array [
Object {
"title": "단어를 맞춰라",
- "wordBreak": "keep-all",
+ "wordBreak": "normal",
},
Object {
"title": "길거리",
- "wordBreak": "keep-all",
+ "wordBreak": "normal",
},
Object {
"title": "말을 바로잡아 알려준 단어는 무엇인가요?",
- "wordBreak": "keep-all",
+ "wordBreak": "normal",
},
]
30 | });
31 | const gameHeadings = await wordBreaks();
> 32 | expect.soft(gameHeadings).toEqual(
| ^
33 | gameHeadings.map((heading) => ({ ...heading, wordBreak: "keep-all" })),
34 | );
35 | expect(
at /home/hermes/worktrees/find-word-v0.1/tests/browser/ui.spec.ts:32:29
Error Context: test-results/ui-한국어-제목은-단어-중간에서-줄바꿈하지-않는다-mobile/error-context.md
attachment #2: trace (application/zip) ─────────────────────────────────────────────────────────
test-results/ui-한국어-제목은-단어-중간에서-줄바꿈하지-않는다-mobile/trace.zip
Usage:
npx playwright show-trace test-results/ui-한국어-제목은-단어-중간에서-줄바꿈하지-않는다-mobile/trace.zip
────────────────────────────────────────────────────────────────────────────────────────────────
1 failed
[mobile] › tests/browser/ui.spec.ts:6:1 › 한국어 제목은 단어 중간에서 줄바꿈하지 않는다 ────────────────────────────
+14
View File
@@ -0,0 +1,14 @@
> find-word@0.1.0 test:browser
> playwright test tests/browser/ui.spec.ts --project=mobile --grep 난이도 설명
[WebServer] (node:752930) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)
Running 1 test using 1 worker
(node:752950) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 1 [mobile] › tests/browser/ui.spec.ts:4:1 › 난이도 설명은 모바일에서도 보인다 (803ms)
1 passed (2.0s)
+95
View File
@@ -0,0 +1,95 @@
> find-word@0.1.0 test:browser
> playwright test tests/browser/ui.spec.ts --project=mobile --grep 난이도 설명
[WebServer] (node:752587) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)
Running 1 test using 1 worker
(node:752608) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✘ 1 [mobile] › tests/browser/ui.spec.ts:4:1 › 난이도 설명은 모바일에서도 보인다 (4.0s)
1) [mobile] › tests/browser/ui.spec.ts:4:1 › 난이도 설명은 모바일에서도 보인다 ──────────────────────────────────
Error: expect(locator).toBeVisible() failed
Locator: locator('.db-note')
Expected: visible
Received: hidden
Timeout: 1000ms
Call log:
- Expect "soft toBeVisible" locator('.db-note') with timeout 1000ms
- waiting for locator('.db-note')
10 × locator resolved to <p class="db-note">…</p>
- unexpected value "hidden"
7 | // DOM에만 설명을 남기고 모바일 CSS로 숨기면 컴포넌트 검증은 통과하므로
8 | // 실제 화면에서 난이도 배지와 쉬운 설명을 모두 읽을 수 있는지 확인한다.
> 9 | await expect.soft(page.locator(".db-note")).toBeVisible({ timeout: 1000 });
| ^
10 | await expect.soft(page.getByText("1단계 · SNR +6 dB")).toBeVisible({ timeout: 1000 });
11 | await expect.soft(page.locator(".db-note")).toContainText("음성이 배경보다 6 dB 커요.", { timeout: 1000 });
12 | });
at /home/hermes/worktrees/find-word-v0.1/tests/browser/ui.spec.ts:9:47
Error: expect(locator).toBeVisible() failed
Locator: getByText('1단계 · SNR +6 dB')
Expected: visible
Timeout: 1000ms
Error: element(s) not found
Call log:
- Expect "soft toBeVisible" getByText('1단계 · SNR +6 dB') with timeout 1000ms
- waiting for getByText('1단계 · SNR +6 dB')
8 | // 실제 화면에서 난이도 배지와 쉬운 설명을 모두 읽을 수 있는지 확인한다.
9 | await expect.soft(page.locator(".db-note")).toBeVisible({ timeout: 1000 });
> 10 | await expect.soft(page.getByText("1단계 · SNR +6 dB")).toBeVisible({ timeout: 1000 });
| ^
11 | await expect.soft(page.locator(".db-note")).toContainText("음성이 배경보다 6 dB 커요.", { timeout: 1000 });
12 | });
13 |
at /home/hermes/worktrees/find-word-v0.1/tests/browser/ui.spec.ts:10:56
Error: expect(locator).toContainText(expected) failed
Locator: locator('.db-note')
Expected substring: "음성이 배경보다 6 dB 커요."
Received string: "음성 / 배경의 상대 음량소리에 익숙해지기"
Timeout: 1000ms
Call log:
- Expect "soft toContainText" locator('.db-note') with timeout 1000ms
- waiting for locator('.db-note')
11 × locator resolved to <p class="db-note">…</p>
- unexpected value "음성 / 배경의 상대 음량소리에 익숙해지기"
9 | await expect.soft(page.locator(".db-note")).toBeVisible({ timeout: 1000 });
10 | await expect.soft(page.getByText("1단계 · SNR +6 dB")).toBeVisible({ timeout: 1000 });
> 11 | await expect.soft(page.locator(".db-note")).toContainText("음성이 배경보다 6 dB 커요.", { timeout: 1000 });
| ^
12 | });
13 |
14 | // jsdom은 줄바꿈을 계산하지 않는다. 실제 Chrome에서 한국어 제목의
at /home/hermes/worktrees/find-word-v0.1/tests/browser/ui.spec.ts:11:47
Error Context: test-results/ui-난이도-설명은-모바일에서도-보인다-mobile/error-context.md
attachment #2: trace (application/zip) ─────────────────────────────────────────────────────────
test-results/ui-난이도-설명은-모바일에서도-보인다-mobile/trace.zip
Usage:
npx playwright show-trace test-results/ui-난이도-설명은-모바일에서도-보인다-mobile/trace.zip
────────────────────────────────────────────────────────────────────────────────────────────────
1 failed
[mobile] › tests/browser/ui.spec.ts:4:1 › 난이도 설명은 모바일에서도 보인다 ───────────────────────────────────
+15
View File
@@ -0,0 +1,15 @@
> find-word@0.1.0 build
> tsc --noEmit && vite build && node scripts/check-build.mjs
vite v8.3.4 building client environment for production...
transforming...
✓ 20 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html 0.89 kB │ gzip: 0.63 kB
dist/assets/index-Cqbkt0Zp.css 14.92 kB │ gzip: 4.30 kB
dist/assets/index-BlNwW4oG.js 244.36 kB │ gzip: 76.99 kB
✓ built in 169ms
빌드 음성 자산 101개 확인
+12
View File
@@ -0,0 +1,12 @@
> find-word@0.1.0 test
> vitest run tests/App.test.tsx -t 다섯 난이도의 SNR
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 1 passed (1)
Tests 1 passed | 8 skipped (9)
Start at 05:31:02
Duration 3.24s (tests 61%, environment 27%, transform 5%, setup 5%, import 2%, worker 1%)
+104
View File
@@ -0,0 +1,104 @@
> find-word@0.1.0 test
> vitest run tests/App.test.tsx -t 다섯 난이도의 SNR
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
❯ tests/App.test.tsx (9 tests | 1 failed | 8 skipped) 1328ms
❯ 시작부터 결과까지 사용자 흐름 (9)
× 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다 1327ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
Error: expect(received).toBeInTheDocument()
received value must be an HTMLElement or an SVGElement.
Received has type: Null
Received has value: null
❯ tests/App.test.tsx:65:73
63| await f.finish();
64| const [label, description] = levels[Math.floor(index / 2)]!;
65| expect.soft(screen.queryByText(`${question.level}단계 · ${label}`)…
| ^
66| expect.soft(screen.queryByText(description, { exact: false })).t…
67| await f.user.click(screen.getByRole("button", { name: new RegExp…
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/26]⎯
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
Error: expect(received).toBeInTheDocument()
received value must be an HTMLElement or an SVGElement.
Received has type: Null
Received has value: null
❯ tests/App.test.tsx:66:70
64| const [label, description] = levels[Math.floor(index / 2)]!;
65| expect.soft(screen.queryByText(`${question.level}단계 · ${label}`)…
66| expect.soft(screen.queryByText(description, { exact: false })).t…
| ^
67| await f.user.click(screen.getByRole("button", { name: new RegExp…
68| await f.user.click(screen.getByRole("button", { name: index === …
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/26]⎯
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
Error: expect(received).toBeInTheDocument()
received value must be an HTMLElement or an SVGElement.
Received has type: Null
Received has value: null
❯ tests/App.test.tsx:71:69
69| }
70| const table = within(screen.getByRole("table", { name: "난이도별 결과" }…
71| expect.soft(table.queryByRole("columnheader", { name: "SNR" })).to…
| ^
72| for (const [label] of levels) {
73| expect.soft(table.queryByRole("cell", { name: label })).toBeInTh…
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/26]⎯
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 다섯 난이도의 SNR과 음성·배경 크기 관계를 설명하고 결과에도 표시한다
Error: expect(received).toBeInTheDocument()
received value must be an HTMLElement or an SVGElement.
Received has type: Null
Received has value: null
❯ tests/App.test.tsx:73:63
71| expect.soft(table.queryByRole("columnheader", { name: "SNR" })).to…
72| for (const [label] of levels) {
73| expect.soft(table.queryByRole("cell", { name: label })).toBeInTh…
| ^
74| }
75| });
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/26]⎯
Test Files 1 failed (1)
Tests 1 failed | 8 skipped (9)
Start at 05:30:12
Duration 2.39s (tests 63%, environment 27%, setup 5%, transform 2%, import 2%)
@@ -0,0 +1,17 @@
> find-word@0.1.0 test:browser
> playwright test tests/browser/ui.spec.ts --grep 전체 음량
[WebServer] (node:753690) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)
Running 2 tests using 2 workers
(node:753711) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
(node:753710) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 1 [mobile] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 (1.6s)
✓ 2 [desktop] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 (1.7s)
2 passed (3.0s)
+147
View File
@@ -0,0 +1,147 @@
> find-word@0.1.0 test:browser
> playwright test tests/browser/ui.spec.ts --grep 전체 음량
[WebServer] (node:753191) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)
Running 2 tests using 2 workers
(node:753211) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
(node:753212) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✘ 2 [mobile] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 (1.8s)
✘ 1 [desktop] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 (1.8s)
1) [desktop] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 ─────────────────────
Error: expect(received).toBeLessThanOrEqual(expected)
Expected: <= 919.5625
Received: 1203.578125
12 | expect.soft(control.y).toBeGreaterThanOrEqual(bounds.y);
13 | expect.soft(control.x + control.width).toBeLessThanOrEqual(bounds.x + bounds.width);
> 14 | expect.soft(control.y + control.height).toBeLessThanOrEqual(bounds.y + bounds.height);
| ^
15 | };
16 | await expectInside(page.getByRole("region", { name: "나의 소리 탐험 준비" }));
17 | const start = page.getByRole("button", { name: /소리 시작/ });
at expectInside (/home/hermes/worktrees/find-word-v0.1/tests/browser/ui.spec.ts:14:45)
at /home/hermes/worktrees/find-word-v0.1/tests/browser/ui.spec.ts:16:3
Error: expect(received).toBeLessThanOrEqual(expected)
Expected: <= 851.5625
Received: 1203.578125
17 | const start = page.getByRole("button", { name: /소리 시작/ });
18 | const control = (await page.locator(".volume-row").boundingBox())!;
> 19 | expect.soft(control.y + control.height).toBeLessThanOrEqual((await start.boundingBox())!.y);
| ^
20 | await volume.focus();
21 | await volume.press("ArrowRight");
22 | await expect(volume).toHaveValue("36");
at /home/hermes/worktrees/find-word-v0.1/tests/browser/ui.spec.ts:19:43
Error: expect(received).toBeLessThanOrEqual(expected)
Expected: <= 402
Received: 1188
11 | expect.soft(control.x).toBeGreaterThanOrEqual(bounds.x);
12 | expect.soft(control.y).toBeGreaterThanOrEqual(bounds.y);
> 13 | expect.soft(control.x + control.width).toBeLessThanOrEqual(bounds.x + bounds.width);
| ^
14 | expect.soft(control.y + control.height).toBeLessThanOrEqual(bounds.y + bounds.height);
15 | };
16 | await expectInside(page.getByRole("region", { name: "나의 소리 탐험 준비" }));
at expectInside (/home/hermes/worktrees/find-word-v0.1/tests/browser/ui.spec.ts:13:44)
at /home/hermes/worktrees/find-word-v0.1/tests/browser/ui.spec.ts:24:3
Error: expect(received).toBeLessThanOrEqual(expected)
Expected: <= 748.3125
Received: 852.3125
12 | expect.soft(control.y).toBeGreaterThanOrEqual(bounds.y);
13 | expect.soft(control.x + control.width).toBeLessThanOrEqual(bounds.x + bounds.width);
> 14 | expect.soft(control.y + control.height).toBeLessThanOrEqual(bounds.y + bounds.height);
| ^
15 | };
16 | await expectInside(page.getByRole("region", { name: "나의 소리 탐험 준비" }));
17 | const start = page.getByRole("button", { name: /소리 시작/ });
at expectInside (/home/hermes/worktrees/find-word-v0.1/tests/browser/ui.spec.ts:14:45)
at /home/hermes/worktrees/find-word-v0.1/tests/browser/ui.spec.ts:24:3
Error Context: test-results/ui-전체-음량은-설정-흐름-안에-들어가며-키보드로-조절된다-desktop/error-context.md
attachment #2: trace (application/zip) ─────────────────────────────────────────────────────────
test-results/ui-전체-음량은-설정-흐름-안에-들어가며-키보드로-조절된다-desktop/trace.zip
Usage:
npx playwright show-trace test-results/ui-전체-음량은-설정-흐름-안에-들어가며-키보드로-조절된다-desktop/trace.zip
────────────────────────────────────────────────────────────────────────────────────────────────
2) [mobile] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 ──────────────────────
Error: expect(received).toBeLessThanOrEqual(expected)
Expected: <= 1250.015625
Received: 1687.84375
12 | expect.soft(control.y).toBeGreaterThanOrEqual(bounds.y);
13 | expect.soft(control.x + control.width).toBeLessThanOrEqual(bounds.x + bounds.width);
> 14 | expect.soft(control.y + control.height).toBeLessThanOrEqual(bounds.y + bounds.height);
| ^
15 | };
16 | await expectInside(page.getByRole("region", { name: "나의 소리 탐험 준비" }));
17 | const start = page.getByRole("button", { name: /소리 시작/ });
at expectInside (/home/hermes/worktrees/find-word-v0.1/tests/browser/ui.spec.ts:14:45)
at /home/hermes/worktrees/find-word-v0.1/tests/browser/ui.spec.ts:16:3
Error: expect(received).toBeLessThanOrEqual(expected)
Expected: <= 1184.015625
Received: 1687.84375
17 | const start = page.getByRole("button", { name: /소리 시작/ });
18 | const control = (await page.locator(".volume-row").boundingBox())!;
> 19 | expect.soft(control.y + control.height).toBeLessThanOrEqual((await start.boundingBox())!.y);
| ^
20 | await volume.focus();
21 | await volume.press("ArrowRight");
22 | await expect(volume).toHaveValue("36");
at /home/hermes/worktrees/find-word-v0.1/tests/browser/ui.spec.ts:19:43
Error: expect(received).toBeLessThanOrEqual(expected)
Expected: <= 169.03125
Received: 696.890625
12 | expect.soft(control.y).toBeGreaterThanOrEqual(bounds.y);
13 | expect.soft(control.x + control.width).toBeLessThanOrEqual(bounds.x + bounds.width);
> 14 | expect.soft(control.y + control.height).toBeLessThanOrEqual(bounds.y + bounds.height);
| ^
15 | };
16 | await expectInside(page.getByRole("region", { name: "나의 소리 탐험 준비" }));
17 | const start = page.getByRole("button", { name: /소리 시작/ });
at expectInside (/home/hermes/worktrees/find-word-v0.1/tests/browser/ui.spec.ts:14:45)
at /home/hermes/worktrees/find-word-v0.1/tests/browser/ui.spec.ts:24:3
Error Context: test-results/ui-전체-음량은-설정-흐름-안에-들어가며-키보드로-조절된다-mobile/error-context.md
attachment #2: trace (application/zip) ─────────────────────────────────────────────────────────
test-results/ui-전체-음량은-설정-흐름-안에-들어가며-키보드로-조절된다-mobile/trace.zip
Usage:
npx playwright show-trace test-results/ui-전체-음량은-설정-흐름-안에-들어가며-키보드로-조절된다-mobile/trace.zip
────────────────────────────────────────────────────────────────────────────────────────────────
2 failed
[desktop] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 ──────────────────────
[mobile] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 ───────────────────────
+15
View File
@@ -0,0 +1,15 @@
> find-word@0.1.0 build
> tsc --noEmit && vite build && node scripts/check-build.mjs
vite v8.3.4 building client environment for production...
transforming...
✓ 20 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html 0.89 kB │ gzip: 0.63 kB
dist/assets/index-_gwQxvGu.css 15.23 kB │ gzip: 4.38 kB
dist/assets/index-DNw8lyKU.js 244.40 kB │ gzip: 77.01 kB
✓ built in 157ms
빌드 음성 자산 101개 확인
+12
View File
@@ -0,0 +1,12 @@
> find-word@0.1.0 test
> vitest run tests/App.test.tsx -t 전체 음량을 시작 CTA
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 1 passed (1)
Tests 1 passed | 9 skipped (10)
Start at 05:32:46
Duration 2.00s (environment 48%, tests 30%, transform 8%, setup 8%, import 5%, worker 1%)
+77
View File
@@ -0,0 +1,77 @@
> find-word@0.1.0 test
> vitest run tests/App.test.tsx -t 전체 음량을 시작 CTA
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
❯ tests/App.test.tsx (10 tests | 1 failed | 9 skipped) 808ms
❯ 시작부터 결과까지 사용자 흐름 (10)
× 전체 음량을 시작 CTA 앞 설정 카드와 게임 환경 패널에 배치하고 값을 유지한다 805ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 전체 음량을 시작 CTA 앞 설정 카드와 게임 환경 패널에 배치하고 값을 유지한다
Error: expect(element).toContainElement(element)
<section
aria-labelledby="setup-title"
class="setup-card"
/> does not contain: <input
id="volume"
max="100"
min="0"
type="range"
value="35"
/>
❯ tests/App.test.tsx:53:24
51| const volume = screen.getByRole("slider", { name: "전체 음량" });
52| const start = screen.getByRole("button", { name: /소리 시작/ });
53| expect.soft(setup).toContainElement(volume);
| ^
54| // CSS 배치만 바꾸지 않고 키보드·스크린리더도 음량→시작 순서로 읽어야 한다.
55| expect.soft(volume.compareDocumentPosition(start) & Node.DOCUMENT_…
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/3]⎯
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 전체 음량을 시작 CTA 앞 설정 카드와 게임 환경 패널에 배치하고 값을 유지한다
AssertionError: expected +0 not to be +0 // Object.is equality
❯ tests/App.test.tsx:55:95
53| expect.soft(setup).toContainElement(volume);
54| // CSS 배치만 바꾸지 않고 키보드·스크린리더도 음량→시작 순서로 읽어야 한다.
55| expect.soft(volume.compareDocumentPosition(start) & Node.DOCUMENT_…
| ^
56| fireEvent.change(volume, { target: { value: "20" } });
57| await f.user.click(start);
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/3]⎯
FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 전체 음량을 시작 CTA 앞 설정 카드와 게임 환경 패널에 배치하고 값을 유지한다
Error: expect(element).toContainElement(element)
<aside
class="environment-panel"
/> does not contain: <input
id="volume"
max="100"
min="0"
type="range"
value="20"
/>
❯ tests/App.test.tsx:59:52
57| await f.user.click(start);
58| const gameVolume = screen.getByRole("slider", { name: "전체 음량" });
59| expect.soft(screen.getByRole("complementary")).toContainElement(ga…
| ^
60| expect(screen.getAllByRole("slider", { name: "전체 음량" })).toHaveLen…
61| expect(gameVolume).toHaveValue("20");
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/3]⎯
Test Files 1 failed (1)
Tests 1 failed | 9 skipped (10)
Start at 05:31:48
Duration 2.03s (tests 44%, environment 38%, setup 8%, transform 6%, import 3%)
+12
View File
@@ -0,0 +1,12 @@
> find-word@0.1.0 test
> vitest run tests/App.test.tsx -t 시작 안내
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 1 passed (1)
Tests 3 passed | 10 skipped (13)
Start at 05:33:49
Duration 1.24s (environment 49%, tests 27%, transform 10%, setup 9%, import 4%, worker 1%)
File diff suppressed because it is too large Load Diff
+17
View File
@@ -0,0 +1,17 @@
> find-word@0.1.0 test:browser
> playwright test tests/browser/ui.spec.ts --grep 전체 음량
[WebServer] (node:756185) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)
Running 2 tests using 2 workers
(node:756205) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
(node:756206) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 2 [mobile] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 (1.7s)
✓ 1 [desktop] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 (1.8s)
2 passed (3.8s)
+72
View File
@@ -0,0 +1,72 @@
> find-word@0.1.0 test:browser
> playwright test tests/browser/ui.spec.ts --grep 전체 음량
[WebServer] (node:755608) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)
Running 2 tests using 2 workers
(node:755630) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
(node:755629) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✘ 1 [mobile] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 (1.8s)
✘ 2 [desktop] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 (1.9s)
1) [desktop] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 ─────────────────────
Error: expect(received).toBeGreaterThanOrEqual(expected)
Expected: >= 24
Received: 0
44 | const cards = (await page.locator(".game-columns").boundingBox())!;
45 | const footer = (await page.getByRole("contentinfo").boundingBox())!;
> 46 | expect(footer.y - (cards.y + cards.height)).toBeGreaterThanOrEqual(24);
| ^
47 | expect(
48 | await page.evaluate(
49 | () => document.documentElement.scrollWidth <= innerWidth,
at /home/hermes/worktrees/find-word-v0.1/tests/browser/ui.spec.ts:46:47
Error Context: test-results/ui-전체-음량은-설정-흐름-안에-들어가며-키보드로-조절된다-desktop/error-context.md
attachment #2: trace (application/zip) ─────────────────────────────────────────────────────────
test-results/ui-전체-음량은-설정-흐름-안에-들어가며-키보드로-조절된다-desktop/trace.zip
Usage:
npx playwright show-trace test-results/ui-전체-음량은-설정-흐름-안에-들어가며-키보드로-조절된다-desktop/trace.zip
────────────────────────────────────────────────────────────────────────────────────────────────
2) [mobile] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 ──────────────────────
Error: expect(received).toBeGreaterThanOrEqual(expected)
Expected: >= 24
Received: 0
44 | const cards = (await page.locator(".game-columns").boundingBox())!;
45 | const footer = (await page.getByRole("contentinfo").boundingBox())!;
> 46 | expect(footer.y - (cards.y + cards.height)).toBeGreaterThanOrEqual(24);
| ^
47 | expect(
48 | await page.evaluate(
49 | () => document.documentElement.scrollWidth <= innerWidth,
at /home/hermes/worktrees/find-word-v0.1/tests/browser/ui.spec.ts:46:47
Error Context: test-results/ui-전체-음량은-설정-흐름-안에-들어가며-키보드로-조절된다-mobile/error-context.md
attachment #2: trace (application/zip) ─────────────────────────────────────────────────────────
test-results/ui-전체-음량은-설정-흐름-안에-들어가며-키보드로-조절된다-mobile/trace.zip
Usage:
npx playwright show-trace test-results/ui-전체-음량은-설정-흐름-안에-들어가며-키보드로-조절된다-mobile/trace.zip
────────────────────────────────────────────────────────────────────────────────────────────────
2 failed
[desktop] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 ──────────────────────
[mobile] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 ───────────────────────
+12
View File
@@ -0,0 +1,12 @@
> find-word@0.1.0 test
> vitest run tests/App.test.tsx tests/release.test.ts -t 브랜드
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 2 passed (2)
Tests 2 passed | 12 skipped (14)
Start at 05:50:16
Duration 1.25s (environment 49%, setup 21%, tests 18%, transform 7%, import 3%, worker 1%)
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
> find-word@0.1.0 test
> vitest run tests/release.test.ts tests/build.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 2 passed (2)
Tests 6 passed (6)
Start at 05:51:40
Duration 733ms (tests 62%, setup 34%, transform 2%, worker 2%, import 1%)
+146
View File
@@ -0,0 +1,146 @@
> find-word@0.1.0 test
> vitest run tests/release.test.ts tests/build.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
❯ tests/release.test.ts (3 tests | 2 failed) 23ms
❯ 릴리스 문서 계약 (3)
× 프로젝트는 비공개 권리 유보이며 저장소 주소를 명시한다 18ms
× 잠금 파일의 모든 런타임 의존성 notice 전문을 배포용 사본에 보존한다 1ms
❯ tests/build.test.ts (3 tests | 2 failed) 582ms
❯ production 자산 검증기 (3)
× dist의 런타임 notice 누락을 실패시킨다 192ms
× dist의 런타임 notice 축약을 실패시킨다 192ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 4 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/build.test.ts > production 자산 검증기 > dist의 런타임 notice 누락을 실패시킨다
FAIL tests/build.test.ts > production 자산 검증기 > dist의 런타임 notice 축약을 실패시킨다
AssertionError: expected [Function verify] to throw an error
- Expected:
null
+ Received:
undefined
❯ tests/build.test.ts:47:22
45| if (damage === "누락") rmSync(notice, { force: true });
46| else writeFileSync(notice, "React / ReactDOM / scheduler — MIT L…
47| expect(verify).toThrow(/notice/i);
| ^
48| } finally {
49| rmSync(temporary, { recursive: true, force: true });
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/8]⎯
FAIL tests/release.test.ts > 릴리스 문서 계약 > 프로젝트는 비공개 권리 유보이며 저장소 주소를 명시한다
AssertionError: expected undefined to be 'UNLICENSED' // Object.is equality
- Expected:
"UNLICENSED"
+ Received:
undefined
❯ tests/release.test.ts:12:35
10| const lock = JSON.parse(readFileSync("package-lock.json", "utf8"));
11| expect.soft(metadata.private).toBe(true);
12| expect.soft(metadata.license).toBe("UNLICENSED");
| ^
13| expect.soft(lock.packages[""].license).toBe("UNLICENSED");
14| expect.soft(metadata.repository).toEqual({
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/8]⎯
FAIL tests/release.test.ts > 릴리스 문서 계약 > 프로젝트는 비공개 권리 유보이며 저장소 주소를 명시한다
AssertionError: expected undefined to be 'UNLICENSED' // Object.is equality
- Expected:
"UNLICENSED"
+ Received:
undefined
❯ tests/release.test.ts:13:44
11| expect.soft(metadata.private).toBe(true);
12| expect.soft(metadata.license).toBe("UNLICENSED");
13| expect.soft(lock.packages[""].license).toBe("UNLICENSED");
| ^
14| expect.soft(metadata.repository).toEqual({
15| type: "git",
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/8]⎯
FAIL tests/release.test.ts > 릴리스 문서 계약 > 프로젝트는 비공개 권리 유보이며 저장소 주소를 명시한다
AssertionError: expected undefined to deeply equal { type: 'git', …(1) }
- Expected:
{
"type": "git",
"url": "https://source.ibit.kr/OZ-Group/Find-Word.git",
}
+ Received:
undefined
❯ tests/release.test.ts:14:38
12| expect.soft(metadata.license).toBe("UNLICENSED");
13| expect.soft(lock.packages[""].license).toBe("UNLICENSED");
14| expect.soft(metadata.repository).toEqual({
| ^
15| type: "git",
16| url: "https://source.ibit.kr/OZ-Group/Find-Word.git",
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/8]⎯
FAIL tests/release.test.ts > 릴리스 문서 계약 > 프로젝트는 비공개 권리 유보이며 저장소 주소를 명시한다
AssertionError: expected undefined to be 'https://source.ibit.kr/OZ-Group/Find-…' // Object.is equality
- Expected:
"https://source.ibit.kr/OZ-Group/Find-Word"
+ Received:
undefined
❯ tests/release.test.ts:18:36
16| url: "https://source.ibit.kr/OZ-Group/Find-Word.git",
17| });
18| expect.soft(metadata.homepage).toBe("https://source.ibit.kr/OZ-Gro…
| ^
19| const license = readFileSync("LICENSE", "utf8");
20| expect(license).toMatch(/private/i);
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[5/8]⎯
FAIL tests/release.test.ts > 릴리스 문서 계약 > 프로젝트는 비공개 권리 유보이며 저장소 주소를 명시한다
Error: ENOENT: no such file or directory, open 'LICENSE'
❯ tests/release.test.ts:19:21
17| });
18| expect.soft(metadata.homepage).toBe("https://source.ibit.kr/OZ-Gro…
19| const license = readFileSync("LICENSE", "utf8");
| ^
20| expect(license).toMatch(/private/i);
21| expect(license).toContain("All rights reserved.");
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[6/8]⎯
FAIL tests/release.test.ts > 릴리스 문서 계약 > 잠금 파일의 모든 런타임 의존성 notice 전문을 배포용 사본에 보존한다
Error: ENOENT: no such file or directory, open 'THIRD_PARTY_NOTICES.md'
❯ tests/release.test.ts:28:20
26| packages: Record<string, { dev?: boolean; version: string }>;
27| };
28| const notice = readFileSync("THIRD_PARTY_NOTICES.md", "utf8");
| ^
29| expect(readFileSync("public/THIRD_PARTY_NOTICES.md", "utf8")).toBe…
30| for (const [path, pkg] of Object.entries(lock.packages)) {
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[7/8]⎯
Test Files 2 failed (2)
Tests 4 failed | 2 passed (6)
Start at 05:51:01
Duration 884ms (tests 66%, setup 26%, transform 6%, import 1%, worker 1%)
+5
View File
@@ -0,0 +1,5 @@
> find-word@0.1.0 audio:generate
> python3 scripts/generate_audio.py
101개 WAV 생성, 합계 20,497,222 바이트
+12
View File
@@ -0,0 +1,12 @@
> find-word@0.1.0 test
> vitest run tests/content.test.ts tests/game.test.ts tests/assets.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 3 passed (3)
Tests 24 passed (24)
Start at 05:55:53
Duration 2.10s (environment 49%, tests 34%, setup 12%, transform 3%, worker 2%, import 1%)
+540
View File
@@ -0,0 +1,540 @@
> find-word@0.1.0 test
> vitest run tests/assets.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
❯ tests/assets.test.ts (2 tests | 1 failed) 52ms
❯ 저장한 한국어 음성 자산 (2)
× 모든 문장과 안내에 대응하는 로컬 WAV·출처·해시를 제공한다 46ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/assets.test.ts > 저장한 한국어 음성 자산 > 모든 문장과 안내에 대응하는 로컬 WAV·출처·해시를 제공한다
AssertionError: expected [ { file: 'intro.wav', …(1) }, …(100) ] to deeply equal [ { file: 'intro.wav', …(1) }, …(100) ]
- Expected
+ Received
@@ -3,404 +3,404 @@
"file": "intro.wav",
"text": "으하하하~~ 나는 에스다. 널 위한 행운의 단어를 불러주마, 잘 찾아보거라.",
},
{
"file": "luck-apple.wav",
- "text": "오늘의 행운을 담은 단어는 ‘사과’입니다.",
+ "text": "오늘의 내 행운은 사과에 있다.",
},
{
"file": "luck-sea.wav",
- "text": "오늘의 행운을 담은 단어는 ‘바다’입니다.",
+ "text": "오늘의 내 행운은 바다에 있다.",
},
{
"file": "luck-tree.wav",
- "text": "오늘의 행운을 담은 단어는 ‘나무’입니다.",
+ "text": "오늘의 내 행운은 나무에 있다.",
},
{
"file": "luck-hat.wav",
- "text": "오늘의 행운을 담은 단어는 ‘모자’입니다.",
+ "text": "오늘의 내 행운은 모자에 있다.",
},
{
"file": "luck-umbrella.wav",
- "text": "오늘의 행운을 담은 단어는 ‘우산’입니다.",
+ "text": "오늘의 내 행운은 우산에 있다.",
},
{
"file": "luck-market.wav",
- "text": "오늘의 행운을 담은 단어는 ‘시장’입니다.",
+ "text": "오늘의 내 행운은 시장에 있다.",
},
{
"file": "luck-oil.wav",
- "text": "오늘의 행운을 담은 단어는 ‘기름’입니다.",
+ "text": "오늘의 내 행운은 기름에 있다.",
},
{
"file": "luck-sound.wav",
- "text": "오늘의 행운을 담은 단어는 ‘소리’입니다.",
+ "text": "오늘의 내 행운은 소리에 있다.",
},
{
"file": "luck-meat.wav",
- "text": "오늘의 행운을 담은 단어는 ‘고기’입니다.",
+ "text": "오늘의 내 행운은 고기에 있다.",
},
{
"file": "luck-furniture.wav",
- "text": "오늘의 행운을 담은 단어는 ‘가구’입니다.",
+ "text": "오늘의 내 행운은 가구에 있다.",
},
{
"file": "password-apple.wav",
- "text": "연습 암호는 ‘다과’, 진짜 암호는 ‘사과’입니다.",
+ "text": "비밀 암호는 사과입니다.",
},
{
"file": "password-sea.wav",
- "text": "연습 암호는 ‘사다’, 진짜 암호는 ‘바다’입니다.",
+ "text": "비밀 암호는 바다입니다.",
},
{
"file": "password-tree.wav",
- "text": "연습 암호는 ‘나루’, 진짜 암호는 ‘나무’입니다.",
+ "text": "비밀 암호는 나무입니다.",
},
{
"file": "password-hat.wav",
- "text": "연습 암호는 ‘포자’, 진짜 암호는 ‘모자’입니다.",
+ "text": "비밀 암호는 모자입니다.",
},
{
"file": "password-umbrella.wav",
- "text": "연습 암호는 ‘부산’, 진짜 암호는 ‘우산’입니다.",
+ "text": "비밀 암호는 우산입니다.",
},
{
"file": "password-market.wav",
- "text": "연습 암호는 ‘지장’, 진짜 암호는 ‘시장’입니다.",
+ "text": "비밀 암호는 시장입니다.",
},
{
"file": "password-oil.wav",
- "text": "연습 암호는 ‘이름’, 진짜 암호는 ‘기름’입니다.",
+ "text": "비밀 암호는 기름입니다.",
},
{
"file": "password-sound.wav",
- "text": "연습 암호는 ‘오리’, 진짜 암호는 ‘소리’입니다.",
+ "text": "비밀 암호는 소리입니다.",
},
{
"file": "password-meat.wav",
- "text": "연습 암호는 ‘보기’, 진짜 암호는 ‘고기’입니다.",
+ "text": "비밀 암호는 고기입니다.",
},
{
"file": "password-furniture.wav",
- "text": "연습 암호는 ‘하구’, 진짜 암호는 ‘가구’입니다.",
+ "text": "비밀 암호는 가구입니다.",
},
{
"file": "remember-apple.wav",
- "text": "‘다과’. 이 단어는 잊으세요. 이제 ‘사과’, 이 단어를 기억하세요.",
+ "text": "꼭 기억할 단어는 사과입니다.",
},
{
"file": "remember-sea.wav",
- "text": "‘사다’. 이 단어는 잊으세요. 이제 ‘바다’, 이 단어를 기억하세요.",
+ "text": "꼭 기억할 단어는 바다입니다.",
},
{
"file": "remember-tree.wav",
- "text": "‘나루’. 이 단어는 잊으세요. 이제 ‘나무’, 이 단어를 기억하세요.",
+ "text": "꼭 기억할 단어는 나무입니다.",
},
{
"file": "remember-hat.wav",
- "text": "‘포자’. 이 단어는 잊으세요. 이제 ‘모자’, 이 단어를 기억하세요.",
+ "text": "꼭 기억할 단어는 모자입니다.",
},
{
"file": "remember-umbrella.wav",
- "text": "‘부산’. 이 단어는 잊으세요. 이제 ‘우산’, 이 단어를 기억하세요.",
+ "text": "꼭 기억할 단어는 우산입니다.",
},
{
"file": "remember-market.wav",
- "text": "‘지장’. 이 단어는 잊으세요. 이제 ‘시장’, 이 단어를 기억하세요.",
+ "text": "꼭 기억할 단어는 시장입니다.",
},
{
"file": "remember-oil.wav",
- "text": "‘이름’. 이 단어는 잊으세요. 이제 ‘기름’, 이 단어를 기억하세요.",
+ "text": "꼭 기억할 단어는 기름입니다.",
},
{
"file": "remember-sound.wav",
- "text": "‘오리’. 이 단어는 잊으세요. 이제 ‘소리’, 이 단어를 기억하세요.",
+ "text": "꼭 기억할 단어는 소리입니다.",
},
{
"file": "remember-meat.wav",
- "text": "‘보기’. 이 단어는 잊으세요. 이제 ‘고기’, 이 단어를 기억하세요.",
+ "text": "꼭 기억할 단어는 고기입니다.",
},
{
"file": "remember-furniture.wav",
- "text": "‘하구’. 이 단어는 잊으세요. 이제 ‘가구’, 이 단어를 기억하세요.",
+ "text": "꼭 기억할 단어는 가구입니다.",
},
{
"file": "last-apple.wav",
- "text": "세 단어 중 마지막을 고르세요. ‘다과’, ‘사고’, ‘사과’.",
+ "text": "처음 단어는 별, 마지막 단어는 사과입니다.",
},
{
"file": "last-sea.wav",
- "text": "세 단어 중 마지막을 고르세요. ‘사다’, ‘보다’, ‘바다’.",
+ "text": "처음 단어는 별, 마지막 단어는 바다입니다.",
},
{
"file": "last-tree.wav",
- "text": "세 단어 중 마지막을 고르세요. ‘나루’, ‘너무’, ‘나무’.",
+ "text": "처음 단어는 별, 마지막 단어는 나무입니다.",
},
{
"file": "last-hat.wav",
- "text": "세 단어 중 마지막을 고르세요. ‘포자’, ‘모조’, ‘모자’.",
+ "text": "처음 단어는 별, 마지막 단어는 모자입니다.",
},
{
"file": "last-umbrella.wav",
- "text": "세 단어 중 마지막을 고르세요. ‘부산’, ‘오산’, ‘우산’.",
+ "text": "처음 단어는 별, 마지막 단어는 우산입니다.",
},
{
"file": "last-market.wav",
- "text": "세 단어 중 마지막을 고르세요. ‘지장’, ‘사장’, ‘시장’.",
+ "text": "처음 단어는 별, 마지막 단어는 시장입니다.",
},
{
"file": "last-oil.wav",
- "text": "세 단어 중 마지막을 고르세요. ‘이름’, ‘거름’, ‘기름’.",
+ "text": "처음 단어는 별, 마지막 단어는 기름입니다.",
},
{
"file": "last-sound.wav",
- "text": "세 단어 중 마지막을 고르세요. ‘오리’, ‘수리’, ‘소리’.",
+ "text": "처음 단어는 별, 마지막 단어는 소리입니다.",
},
{
"file": "last-meat.wav",
- "text": "세 단어 중 마지막을 고르세요. ‘보기’, ‘고개’, ‘고기’.",
+ "text": "처음 단어는 별, 마지막 단어는 고기입니다.",
},
{
"file": "last-furniture.wav",
- "text": "세 단어 중 마지막을 고르세요. ‘하구’, ‘가교’, ‘가구’.",
+ "text": "처음 단어는 별, 마지막 단어는 가구입니다.",
},
{
"file": "first-apple.wav",
- "text": "세 단어 중 첫 번째를 고르세요. ‘사과’, ‘다과’, ‘사고’.",
+ "text": "첫 번째는 사과, 두 번째는 달입니다.",
},
{
"file": "first-sea.wav",
- "text": "세 단어 중 첫 번째를 고르세요. ‘바다’, ‘사다’, ‘보다’.",
+ "text": "첫 번째는 바다, 두 번째는 달입니다.",
},
{
"file": "first-tree.wav",
- "text": "세 단어 중 첫 번째를 고르세요. ‘나무’, ‘나루’, ‘너무’.",
+ "text": "첫 번째는 나무, 두 번째는 달입니다.",
},
{
"file": "first-hat.wav",
- "text": "세 단어 중 첫 번째를 고르세요. ‘모자’, ‘포자’, ‘모조’.",
+ "text": "첫 번째는 모자, 두 번째는 달입니다.",
},
{
"file": "first-umbrella.wav",
- "text": "세 단어 중 첫 번째를 고르세요. ‘우산’, ‘부산’, ‘오산’.",
+ "text": "첫 번째는 우산, 두 번째는 달입니다.",
},
{
"file": "first-market.wav",
- "text": "세 단어 중 첫 번째를 고르세요. ‘시장’, ‘지장’, ‘사장’.",
+ "text": "첫 번째는 시장, 두 번째는 달입니다.",
},
{
"file": "first-oil.wav",
- "text": "세 단어 중 첫 번째를 고르세요. ‘기름’, ‘이름’, ‘거름’.",
+ "text": "첫 번째는 기름, 두 번째는 달입니다.",
},
{
"file": "first-sound.wav",
- "text": "세 단어 중 첫 번째를 고르세요. ‘소리’, ‘오리’, ‘수리’.",
+ "text": "첫 번째는 소리, 두 번째는 달입니다.",
},
{
"file": "first-meat.wav",
- "text": "세 단어 중 첫 번째를 고르세요. ‘고기’, ‘보기’, ‘고개’.",
+ "text": "첫 번째는 고기, 두 번째는 달입니다.",
},
{
"file": "first-furniture.wav",
- "text": "세 단어 중 첫 번째를 고르세요. ‘가구’, ‘하구’, ‘가교’.",
+ "text": "첫 번째는 가구, 두 번째는 달입니다.",
},
{
"file": "repeat-apple.wav",
- "text": "두 번 들린 단어를 고르세요. ‘사과’, ‘다과’, ‘사과’, ‘사고’.",
+ "text": "사과. 다시 한번, 사과.",
},
{
"file": "repeat-sea.wav",
- "text": "두 번 들린 단어를 고르세요. ‘바다’, ‘사다’, ‘바다’, ‘보다’.",
+ "text": "바다. 다시 한번, 바다.",
},
{
"file": "repeat-tree.wav",
- "text": "두 번 들린 단어를 고르세요. ‘나무’, ‘나루’, ‘나무’, ‘너무’.",
+ "text": "나무. 다시 한번, 나무.",
},
{
"file": "repeat-hat.wav",
- "text": "두 번 들린 단어를 고르세요. ‘모자’, ‘포자’, ‘모자’, ‘모조’.",
+ "text": "모자. 다시 한번, 모자.",
},
{
"file": "repeat-umbrella.wav",
- "text": "두 번 들린 단어를 고르세요. ‘우산’, ‘부산’, ‘우산’, ‘오산’.",
+ "text": "우산. 다시 한번, 우산.",
},
{
"file": "repeat-market.wav",
- "text": "두 번 들린 단어를 고르세요. ‘시장’, ‘지장’, ‘시장’, ‘사장’.",
+ "text": "시장. 다시 한번, 시장.",
},
{
"file": "repeat-oil.wav",
- "text": "두 번 들린 단어를 고르세요. ‘기름’, ‘이름’, ‘기름’, ‘거름’.",
+ "text": "기름. 다시 한번, 기름.",
},
{
"file": "repeat-sound.wav",
- "text": "두 번 들린 단어를 고르세요. ‘소리’, ‘오리’, ‘소리’, ‘수리’.",
+ "text": "소리. 다시 한번, 소리.",
},
{
"file": "repeat-meat.wav",
- "text": "두 번 들린 단어를 고르세요. ‘고기’, ‘보기’, ‘고기’, ‘고개’.",
+ "text": "고기. 다시 한번, 고기.",
},
{
"file": "repeat-furniture.wav",
- "text": "두 번 들린 단어를 고르세요. ‘가구’, ‘하구’, ‘가구’, ‘가교’.",
+ "text": "가구. 다시 한번, 가구.",
},
{
"file": "correction-apple.wav",
- "text": "정답은 ‘다과’입니다. 아니, 잘못 말했어요. ‘사과’로 정정합니다.",
+ "text": "달이 아니라 사과입니다.",
},
{
"file": "correction-sea.wav",
- "text": "정답은 ‘사다’입니다. 아니, 잘못 말했어요. ‘바다’로 정정합니다.",
+ "text": "달이 아니라 바다입니다.",
},
{
"file": "correction-tree.wav",
- "text": "정답은 ‘나루’입니다. 아니, 잘못 말했어요. ‘나무’로 정정합니다.",
+ "text": "달이 아니라 나무입니다.",
},
{
"file": "correction-hat.wav",
- "text": "정답은 ‘포자’입니다. 아니, 잘못 말했어요. ‘모자’로 정정합니다.",
+ "text": "달이 아니라 모자입니다.",
},
{
"file": "correction-umbrella.wav",
- "text": "정답은 ‘부산’입니다. 아니, 잘못 말했어요. ‘우산’로 정정합니다.",
+ "text": "달이 아니라 우산입니다.",
},
{
"file": "correction-market.wav",
- "text": "정답은 ‘지장’입니다. 아니, 잘못 말했어요. ‘시장’로 정정합니다.",
+ "text": "달이 아니라 시장입니다.",
},
{
"file": "correction-oil.wav",
- "text": "정답은 ‘이름’입니다. 아니, 잘못 말했어요. ‘기름’로 정정합니다.",
+ "text": "달이 아니라 기름입니다.",
},
{
"file": "correction-sound.wav",
- "text": "정답은 ‘오리’입니다. 아니, 잘못 말했어요. ‘소리’로 정정합니다.",
+ "text": "달이 아니라 소리입니다.",
},
{
"file": "correction-meat.wav",
- "text": "정답은 ‘보기’입니다. 아니, 잘못 말했어요. ‘고기’로 정정합니다.",
+ "text": "달이 아니라 고기입니다.",
},
{
"file": "correction-furniture.wav",
- "text": "정답은 ‘하구’입니다. 아니, 잘못 말했어요. ‘가구’로 정정합니다.",
+ "text": "달이 아니라 가구입니다.",
},
{
"file": "middle-apple.wav",
- "text": "세 단어 중 가운데를 고르세요. ‘다과’, ‘사과’, ‘사고’.",
+ "text": "별, 사과, 달. 가운데 단어를 기억하세요.",
},
{
"file": "middle-sea.wav",
- "text": "세 단어 중 가운데를 고르세요. ‘사다’, ‘바다’, ‘보다’.",
+ "text": "별, 바다, 달. 가운데 단어를 기억하세요.",
},
{
"file": "middle-tree.wav",
- "text": "세 단어 중 가운데를 고르세요. ‘나루’, ‘나무’, ‘너무’.",
+ "text": "별, 나무, 달. 가운데 단어를 기억하세요.",
},
{
"file": "middle-hat.wav",
- "text": "세 단어 중 가운데를 고르세요. ‘포자’, ‘모자’, ‘모조’.",
+ "text": "별, 모자, 달. 가운데 단어를 기억하세요.",
},
{
"file": "middle-umbrella.wav",
- "text": "세 단어 중 가운데를 고르세요. ‘부산’, ‘우산’, ‘오산’.",
+ "text": "별, 우산, 달. 가운데 단어를 기억하세요.",
},
{
"file": "middle-market.wav",
- "text": "세 단어 중 가운데를 고르세요. ‘지장’, ‘시장’, ‘사장’.",
+ "text": "별, 시장, 달. 가운데 단어를 기억하세요.",
},
{
"file": "middle-oil.wav",
- "text": "세 단어 중 가운데를 고르세요. ‘이름’, ‘기름’, ‘거름’.",
+ "text": "별, 기름, 달. 가운데 단어를 기억하세요.",
},
{
"file": "middle-sound.wav",
- "text": "세 단어 중 가운데를 고르세요. ‘오리’, ‘소리’, ‘수리’.",
+ "text": "별, 소리, 달. 가운데 단어를 기억하세요.",
},
{
"file": "middle-meat.wav",
- "text": "세 단어 중 가운데를 고르세요. ‘보기’, ‘고기’, ‘고개’.",
+ "text": "별, 고기, 달. 가운데 단어를 기억하세요.",
},
{
"file": "middle-furniture.wav",
- "text": "세 단어 중 가운데를 고르세요. ‘하구’, ‘가구’, ‘가교’.",
+ "text": "별, 가구, 달. 가운데 단어를 기억하세요.",
},
{
"file": "message-apple.wav",
- "text": "초록 불이면 앞 단어, 빨간 불이면 뒤 단어입니다. ‘다과’, ‘사과’. 지금은 빨간 불입니다.",
+ "text": "에스가 남긴 메시지는 사과입니다.",
},
{
"file": "message-sea.wav",
- "text": "초록 불이면 앞 단어, 빨간 불이면 뒤 단어입니다. ‘사다’, ‘바다’. 지금은 빨간 불입니다.",
+ "text": "에스가 남긴 메시지는 바다입니다.",
},
{
"file": "message-tree.wav",
- "text": "초록 불이면 앞 단어, 빨간 불이면 뒤 단어입니다. ‘나루’, ‘나무’. 지금은 빨간 불입니다.",
+ "text": "에스가 남긴 메시지는 나무입니다.",
},
{
"file": "message-hat.wav",
- "text": "초록 불이면 앞 단어, 빨간 불이면 뒤 단어입니다. ‘포자’, ‘모자’. 지금은 빨간 불입니다.",
+ "text": "에스가 남긴 메시지는 모자입니다.",
},
{
"file": "message-umbrella.wav",
- "text": "초록 불이면 앞 단어, 빨간 불이면 뒤 단어입니다. ‘부산’, ‘우산’. 지금은 빨간 불입니다.",
+ "text": "에스가 남긴 메시지는 우산입니다.",
},
{
"file": "message-market.wav",
- "text": "초록 불이면 앞 단어, 빨간 불이면 뒤 단어입니다. ‘지장’, ‘시장’. 지금은 빨간 불입니다.",
+ "text": "에스가 남긴 메시지는 시장입니다.",
},
{
"file": "message-oil.wav",
- "text": "초록 불이면 앞 단어, 빨간 불이면 뒤 단어입니다. ‘이름’, ‘기름’. 지금은 빨간 불입니다.",
+ "text": "에스가 남긴 메시지는 기름입니다.",
},
{
"file": "message-sound.wav",
- "text": "초록 불이면 앞 단어, 빨간 불이면 뒤 단어입니다. ‘오리’, ‘소리’. 지금은 빨간 불입니다.",
+ "text": "에스가 남긴 메시지는 소리입니다.",
},
{
"file": "message-meat.wav",
- "text": "초록 불이면 앞 단어, 빨간 불이면 뒤 단어입니다. ‘보기’, ‘고기’. 지금은 빨간 불입니다.",
+ "text": "에스가 남긴 메시지는 고기입니다.",
},
{
"file": "message-furniture.wav",
- "text": "초록 불이면 앞 단어, 빨간 불이면 뒤 단어입니다. ‘하구’, ‘가구’. 지금은 빨간 불입니다.",
+ "text": "에스가 남긴 메시지는 가구입니다.",
},
{
"file": "mission-apple.wav",
- "text": "오른쪽 표지는 ‘사과’, 왼쪽 표지는 ‘다과’입니다.",
+ "text": "이번 임무의 단서는 사과입니다.",
},
{
"file": "mission-sea.wav",
- "text": "오른쪽 표지는 ‘바다’, 왼쪽 표지는 ‘사다’입니다.",
+ "text": "이번 임무의 단서는 바다입니다.",
},
{
"file": "mission-tree.wav",
- "text": "오른쪽 표지는 ‘나무’, 왼쪽 표지는 ‘나루’입니다.",
+ "text": "이번 임무의 단서는 나무입니다.",
},
{
"file": "mission-hat.wav",
- "text": "오른쪽 표지는 ‘모자’, 왼쪽 표지는 ‘포자’입니다.",
+ "text": "이번 임무의 단서는 모자입니다.",
},
{
"file": "mission-umbrella.wav",
- "text": "오른쪽 표지는 ‘우산’, 왼쪽 표지는 ‘부산’입니다.",
+ "text": "이번 임무의 단서는 우산입니다.",
},
{
"file": "mission-market.wav",
- "text": "오른쪽 표지는 ‘시장’, 왼쪽 표지는 ‘지장’입니다.",
+ "text": "이번 임무의 단서는 시장입니다.",
},
{
"file": "mission-oil.wav",
- "text": "오른쪽 표지는 ‘기름’, 왼쪽 표지는 ‘이름’입니다.",
+ "text": "이번 임무의 단서는 기름입니다.",
},
{
"file": "mission-sound.wav",
- "text": "오른쪽 표지는 ‘소리’, 왼쪽 표지는 ‘오리’입니다.",
+ "text": "이번 임무의 단서는 소리입니다.",
},
{
"file": "mission-meat.wav",
- "text": "오른쪽 표지는 ‘고기’, 왼쪽 표지는 ‘보기’입니다.",
+ "text": "이번 임무의 단서는 고기입니다.",
},
{
"file": "mission-furniture.wav",
- "text": "오른쪽 표지는 ‘가구’, 왼쪽 표지는 ‘하구’입니다.",
+ "text": "이번 임무의 단서는 가구입니다.",
},
]
❯ tests/assets.test.ts:36:70
34| ),
35| ];
36| expect(manifest.files.map(({ file, text }) => ({ file, text }))).t…
| ^
37| expected,
38| );
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
Test Files 1 failed (1)
Tests 1 failed | 1 passed (2)
Start at 05:54:06
Duration 554ms (setup 57%, transform 22%, tests 15%, import 4%, worker 2%)
+12
View File
@@ -0,0 +1,12 @@
> find-word@0.1.0 test
> vitest run tests/content.test.ts tests/game.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 2 passed (2)
Tests 22 passed (22)
Start at 05:54:06
Duration 1.18s (environment 80%, setup 13%, transform 3%, tests 3%, worker 1%, import 1%)
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
> find-word@0.1.0 test
> vitest run tests/release.test.ts -t Node 최소
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 1 passed (1)
Tests 1 passed | 3 skipped (4)
Start at 05:55:53
Duration 558ms (setup 82%, transform 8%, worker 4%, tests 3%, import 2%)
+438
View File
@@ -0,0 +1,438 @@
> find-word@0.1.0 test
> vitest run tests/release.test.ts -t Node 최소
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
❯ tests/release.test.ts (4 tests | 1 failed | 3 skipped) 27ms
❯ 릴리스 문서 계약 (4)
× Node 최소 버전은 잠긴 의존성이 요구하는 22.22.2와 일치한다 25ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/release.test.ts > 릴리스 문서 계약 > Node 최소 버전은 잠긴 의존성이 요구하는 22.22.2와 일치한다
AssertionError: expected '>=22.12.0' to be '>=22.22.2' // Object.is equality
Expected: ">=22.22.2"
Received: ">=22.12.0"
❯ tests/release.test.ts:11:40
9| const metadata = JSON.parse(readFileSync("package.json", "utf8"));
10| const lock = JSON.parse(readFileSync("package-lock.json", "utf8"));
11| expect.soft(metadata.engines.node).toBe(">=22.22.2");
| ^
12| expect.soft(lock.packages[""].engines.node).toBe(">=22.22.2");
13| expect(lock.packages["node_modules/jsdom"].engines.node).toContain…
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/6]⎯
FAIL tests/release.test.ts > 릴리스 문서 계약 > Node 최소 버전은 잠긴 의존성이 요구하는 22.22.2와 일치한다
AssertionError: expected '>=22.12.0' to be '>=22.22.2' // Object.is equality
Expected: ">=22.22.2"
Received: ">=22.12.0"
❯ tests/release.test.ts:12:49
10| const lock = JSON.parse(readFileSync("package-lock.json", "utf8"));
11| expect.soft(metadata.engines.node).toBe(">=22.22.2");
12| expect.soft(lock.packages[""].engines.node).toBe(">=22.22.2");
| ^
13| expect(lock.packages["node_modules/jsdom"].engines.node).toContain…
14| for (const path of ["README.md", "docs/DEPLOYMENT.md"]) {
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/6]⎯
FAIL tests/release.test.ts > 릴리스 문서 계약 > Node 최소 버전은 잠긴 의존성이 요구하는 22.22.2와 일치한다
AssertionError: README.md: expected '# 단어를 맞춰라 · Find-Word v0.1\n\n소음 속에 숨…' to contain '22.22.2'
- Expected
+ Received
- 22.22.2
+ # 단어를 맞춰라 · Find-Word v0.1
+
+ 소음 속에 숨은 한국어 단어를 듣고 찾는 반응형 웹 게임입니다.
+
+ **Designed by OZ at Heat-T Medical Inc.**
+
+ Canonical repository: [OZ-Group/Find-Word](https://source.ibit.kr/OZ-Group/Find-Word).
+ 프로젝트 자체는 **private / all-rights-reserved**이며 `package.json`의
+ `license`는 `UNLICENSED`입니다. [LICENSE](LICENSE)를 참고하세요.
+ 런타임 의존성 React, ReactDOM, scheduler의 MIT 전문은
+ [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)에 보존하며,
+ `public/THIRD_PARTY_NOTICES.md`가 빌드 시 `dist/THIRD_PARTY_NOTICES.md`로 복사됩니다.
+ 빌드 검증기는 설치된 의존성의 라이선스 전문·버전과 세 사본을 대조합니다.
+
+ React + TypeScript strict + Vite로 만든 정적 SPA입니다. 로그인, 개인정보 저장,
+ 분석 도구, 외부 CDN, 런타임 TTS/API 요청이 없습니다. 청각적 주의·식별 게임이며
+ 의료적 진단·치료나 효과를 주장하지 않습니다.
+
+ ## 실행
+
+ Node.js **22.12 이상**의 지원되는 LTS 버전을 권장합니다. `package-lock.json`을
+ 포함하므로 새 환경에서는 `npm ci`를 사용합니다. 이번 검증 환경은 Node 26.7.0입니다.
+
+ ```bash
+ npm ci
+ npm run dev
+ ```
+
+ 표시되는 `http://127.0.0.1:5173`에서 사용합니다. 소리는 사용자가 **게임 시작 · 소리 듣기**를
+ 누른 뒤에만 재생합니다. 낮은 음량부터 조절하고 헤드폰이나 스피커를 사용할 수 있습니다.
+
+ ## 플레이
+
+ - 골고루 풀기, 보기에서 선택, 직접 입력 중 선택합니다.
+ - 8개 환경을 순환하거나 하나를 지정합니다.
+ - 에스의 안내가 배경에 묻힌 뒤 목표 문장이 나옵니다. 재생 후 질문에 답합니다.
+ - 10문제 동안 2문제마다 난이도가 올라갑니다: **SNR +6, +3, 0, −3, −6 dB**.
+ 양수는 음성이 배경보다 크고, 0은 같고, 음수는 더 작다는 뜻입니다.
+ - 다시 듣기는 감점하지 않습니다. 정답은 `단계 × 100점`, 연속 정답은 최대 40점의 보너스를 받습니다.
+ - 결과에서 점수·정답률·최고 연속 정답·단계별 결과를 확인하고 다시 시작할 수 있습니다.
+ - 새로고침하면 기록이 사라집니다. 결과를 브라우저나 서버에 저장하지 않습니다.
+
+ 10개 유형 × 10개 단어의 100개 목표 문장을 저장했습니다. 각 세션은 유형과
+ 단어를 섞어 출제하며 정답 위치도 섞습니다. 사전 표제어와 변형 규칙은
+ [WORDS.md](docs/WORDS.md)에 설명했습니다.
+
+ ## 검증과 정적 빌드
+
+ ```bash
+ npm run lint
+ npm test
+ npm run build
+ npm run preview
+ ```
+
+ `lint`는 ESLint와 TypeScript 검사, `test`는 unit/component/자산/빌드 검증기
+ 테스트를 실행합니다. `build`는 `dist/`를 만들고 음성 101개가 원본 해시 그대로
+ 포함되었는지 확인합니다. 빌드에는 음성 합성 도구가 필요하지 않습니다.
+
+ 실제 오디오·360px/1280px 화면·키보드·접근성·오류 재시도 검증:
+
+ ```bash
+ # Chromium이 없는 개발 환경에서 한 번만 실행합니다.
+ npx playwright install chromium
+ npm run test:browser
+ # 이미 설치된 Chrome을 사용할 때의 대안입니다.
+ CHROME_PATH=/usr/bin/google-chrome npm run test:browser
+ ```
+
+ 브라우저 테스트는 기존 `dist/`를 사용하므로 소스 변경 후 먼저 빌드하세요.
+ 실제 음성 재생이 끝나기를 기다려 약 1–2분이 걸립니다. 로컬 4173 포트를
+ 테스트 중에만 열고 종료합니다. 스크린샷은 `.agent/evidence/`에 남습니다.
+
+ ## 음성과 문서
+
+ 한국어 WAV는 직접 쓴 문장을 로컬 eSpeak NG로 합성했습니다. 환경음은
+ 브라우저에서 고정 seed로 합성합니다. 외부 음원이나 신경망 음성 모델을 사용하지 않습니다.
+ 생성 방법, 도구 라이선스, 합성 음질의 한계는 [AUDIO_PROVENANCE.md](docs/AUDIO_PROVENANCE.md)를
+ 참고하세요. 실제 음량은 출력 장치에 따라 달라지며 표기 dB는 디지털 상대값입니다.
+
+ - [설계와 상태 전이](docs/ARCHITECTURE.md)
+ - [Neo를 위한 정적 배포 안내](docs/DEPLOYMENT.md)
+ - [RED/GREEN 및 실제 검증 결과](.agent/RESULT.md)
+
+ 이번 작업에서는 서버 배포와 Git commit/push를 실행하지 않습니다.
+
❯ tests/release.test.ts:16:35
14| for (const path of ["README.md", "docs/DEPLOYMENT.md"]) {
15| const document = readFileSync(path, "utf8");
16| expect.soft(document, path).toContain("22.22.2");
| ^
17| expect.soft(document, path).not.toContain("22.12");
18| }
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/6]⎯
FAIL tests/release.test.ts > 릴리스 문서 계약 > Node 최소 버전은 잠긴 의존성이 요구하는 22.22.2와 일치한다
AssertionError: README.md: expected '# 단어를 맞춰라 · Find-Word v0.1\n\n소음 속에 숨…' not to contain '22.12'
- Expected
+ Received
- 22.12
+ # 단어를 맞춰라 · Find-Word v0.1
+
+ 소음 속에 숨은 한국어 단어를 듣고 찾는 반응형 웹 게임입니다.
+
+ **Designed by OZ at Heat-T Medical Inc.**
+
+ Canonical repository: [OZ-Group/Find-Word](https://source.ibit.kr/OZ-Group/Find-Word).
+ 프로젝트 자체는 **private / all-rights-reserved**이며 `package.json`의
+ `license`는 `UNLICENSED`입니다. [LICENSE](LICENSE)를 참고하세요.
+ 런타임 의존성 React, ReactDOM, scheduler의 MIT 전문은
+ [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)에 보존하며,
+ `public/THIRD_PARTY_NOTICES.md`가 빌드 시 `dist/THIRD_PARTY_NOTICES.md`로 복사됩니다.
+ 빌드 검증기는 설치된 의존성의 라이선스 전문·버전과 세 사본을 대조합니다.
+
+ React + TypeScript strict + Vite로 만든 정적 SPA입니다. 로그인, 개인정보 저장,
+ 분석 도구, 외부 CDN, 런타임 TTS/API 요청이 없습니다. 청각적 주의·식별 게임이며
+ 의료적 진단·치료나 효과를 주장하지 않습니다.
+
+ ## 실행
+
+ Node.js **22.12 이상**의 지원되는 LTS 버전을 권장합니다. `package-lock.json`을
+ 포함하므로 새 환경에서는 `npm ci`를 사용합니다. 이번 검증 환경은 Node 26.7.0입니다.
+
+ ```bash
+ npm ci
+ npm run dev
+ ```
+
+ 표시되는 `http://127.0.0.1:5173`에서 사용합니다. 소리는 사용자가 **게임 시작 · 소리 듣기**를
+ 누른 뒤에만 재생합니다. 낮은 음량부터 조절하고 헤드폰이나 스피커를 사용할 수 있습니다.
+
+ ## 플레이
+
+ - 골고루 풀기, 보기에서 선택, 직접 입력 중 선택합니다.
+ - 8개 환경을 순환하거나 하나를 지정합니다.
+ - 에스의 안내가 배경에 묻힌 뒤 목표 문장이 나옵니다. 재생 후 질문에 답합니다.
+ - 10문제 동안 2문제마다 난이도가 올라갑니다: **SNR +6, +3, 0, −3, −6 dB**.
+ 양수는 음성이 배경보다 크고, 0은 같고, 음수는 더 작다는 뜻입니다.
+ - 다시 듣기는 감점하지 않습니다. 정답은 `단계 × 100점`, 연속 정답은 최대 40점의 보너스를 받습니다.
+ - 결과에서 점수·정답률·최고 연속 정답·단계별 결과를 확인하고 다시 시작할 수 있습니다.
+ - 새로고침하면 기록이 사라집니다. 결과를 브라우저나 서버에 저장하지 않습니다.
+
+ 10개 유형 × 10개 단어의 100개 목표 문장을 저장했습니다. 각 세션은 유형과
+ 단어를 섞어 출제하며 정답 위치도 섞습니다. 사전 표제어와 변형 규칙은
+ [WORDS.md](docs/WORDS.md)에 설명했습니다.
+
+ ## 검증과 정적 빌드
+
+ ```bash
+ npm run lint
+ npm test
+ npm run build
+ npm run preview
+ ```
+
+ `lint`는 ESLint와 TypeScript 검사, `test`는 unit/component/자산/빌드 검증기
+ 테스트를 실행합니다. `build`는 `dist/`를 만들고 음성 101개가 원본 해시 그대로
+ 포함되었는지 확인합니다. 빌드에는 음성 합성 도구가 필요하지 않습니다.
+
+ 실제 오디오·360px/1280px 화면·키보드·접근성·오류 재시도 검증:
+
+ ```bash
+ # Chromium이 없는 개발 환경에서 한 번만 실행합니다.
+ npx playwright install chromium
+ npm run test:browser
+ # 이미 설치된 Chrome을 사용할 때의 대안입니다.
+ CHROME_PATH=/usr/bin/google-chrome npm run test:browser
+ ```
+
+ 브라우저 테스트는 기존 `dist/`를 사용하므로 소스 변경 후 먼저 빌드하세요.
+ 실제 음성 재생이 끝나기를 기다려 약 1–2분이 걸립니다. 로컬 4173 포트를
+ 테스트 중에만 열고 종료합니다. 스크린샷은 `.agent/evidence/`에 남습니다.
+
+ ## 음성과 문서
+
+ 한국어 WAV는 직접 쓴 문장을 로컬 eSpeak NG로 합성했습니다. 환경음은
+ 브라우저에서 고정 seed로 합성합니다. 외부 음원이나 신경망 음성 모델을 사용하지 않습니다.
+ 생성 방법, 도구 라이선스, 합성 음질의 한계는 [AUDIO_PROVENANCE.md](docs/AUDIO_PROVENANCE.md)를
+ 참고하세요. 실제 음량은 출력 장치에 따라 달라지며 표기 dB는 디지털 상대값입니다.
+
+ - [설계와 상태 전이](docs/ARCHITECTURE.md)
+ - [Neo를 위한 정적 배포 안내](docs/DEPLOYMENT.md)
+ - [RED/GREEN 및 실제 검증 결과](.agent/RESULT.md)
+
+ 이번 작업에서는 서버 배포와 Git commit/push를 실행하지 않습니다.
+
❯ tests/release.test.ts:17:39
15| const document = readFileSync(path, "utf8");
16| expect.soft(document, path).toContain("22.22.2");
17| expect.soft(document, path).not.toContain("22.12");
| ^
18| }
19| });
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/6]⎯
FAIL tests/release.test.ts > 릴리스 문서 계약 > Node 최소 버전은 잠긴 의존성이 요구하는 22.22.2와 일치한다
AssertionError: docs/DEPLOYMENT.md: expected '# 정적 배포 인계\n\n이 문서는 **Neo가 검토 후 수행할 절…' to contain '22.22.2'
- Expected
+ Received
- 22.22.2
+ # 정적 배포 인계
+
+ 이 문서는 **Neo가 검토 후 수행할 절차**다. Codex는 서버, DNS, 인증정보에
+ 접근하지 않았고 배포·서비스 생성·Git commit/push도 하지 않았다.
+
+ ## 산출물
+
+ Node.js 22.12 이상 지원 LTS 환경에서 `npm ci`, `npm run lint`, `npm test`,
+ `npm run build`를 실행한다. `dist/` 전체를 하나의 릴리스로 전달한다.
+ `dist/audio/`의 WAV 101개와 manifest가 반드시 포함되어야 한다. 런타임은
+ Nginx 정적 파일만 필요하며 Node 서버·TTS 엔진·DB·환경변수·API 키는 필요 없다.
+
+ Canonical repository는 [OZ-Group/Find-Word](https://source.ibit.kr/OZ-Group/Find-Word)다.
+ 프로젝트 자체는 private / all-rights-reserved이며 [LICENSE](../LICENSE)를 따른다.
+ React, ReactDOM, scheduler의 MIT 전문을 담은 `dist/THIRD_PARTY_NOTICES.md`도
+ 반드시 배포한다. `build` 검증기는 고지 파일이 없거나 축약·변조되면 실패한다.
+
+ 현재 앱은 도메인 루트(`/`) 배포 기준이다. 하위 경로 배포 시 Vite base뿐 아니라
+ 음성 URL도 함께 변경하고 테스트해야 한다. 예정 도메인은 `findword.heart-m.com`이다.
+
+ ## Nginx 예시
+
+ 아래 root는 예시다. 실제 경로·기존 서버 블록·인증서는 Neo가 확인한다.
+ 기존 서비스 설정을 덮어쓰지 않고 검토 후 적용한다.
+
+ ```nginx
+ # 정적 릴리스 디렉터리를 가리킨다. 인증서와 HTTPS 설정은 운영자가 별도 추가한다.
+ server {
+ listen 80;
+ server_name findword.heart-m.com;
+ root /var/www/findword/current;
+ index index.html;
+
+ # 기본 MIME 정의로 JS/CSS/WAV가 올바른 타입으로 제공되게 한다.
+ include /etc/nginx/mime.types;
+ add_header X-Content-Type-Options nosniff always;
+ add_header Referrer-Policy no-referrer always;
+ add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; media-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
+
+ # 파일명이 해시로 바뀌는 번들만 장기 캐시한다.
+ location /assets/ {
+ try_files $uri =404;
+ expires 1y;
+ }
+
+ # 음성 파일명은 재생성해도 같으므로 재검증한다. 누락 시 index.html 대신
+ # 404를 반환해야 앱이 명확한 음성 로드 오류와 재시도를 제공할 수 있다.
+ location /audio/ {
+ try_files $uri =404;
+ expires -1;
+ }
+
+ # SPA의 직접 진입·새로고침을 index.html로 받되 HTML은 재검증한다.
+ location / {
+ try_files $uri $uri/ /index.html;
+ expires -1;
+ }
+ }
+ ```
+
+ ## 인수 확인
+
+ 1. 서버에 적용하기 전 `nginx -t`, 기존 서비스 충돌, 파일 읽기 권한을 확인한다.
+ 2. TLS/HTTPS와 DNS를 운영자가 확인한다. 실제 도메인에서 시작 후 음성이 들리는지 확인한다.
+ 3. `audio/intro.wav`와 목표 WAV가 정상 응답하고 존재하지 않는 오디오는 404인지 확인한다.
+ 4. 모바일 Safari/Chrome, 데스크톱에서 음량·중단·다시 듣기·10문제 완주를 직접 확인한다.
+ 5. 원자적으로 릴리스 디렉터리를 전환하고 이전 디렉터리를 보관해 되돌릴 수 있게 한다.
+
+ 오프라인 캐시를 제공하는 PWA는 아니다. 정적 자산의 최초 로드에는 해당 서버
+ 연결이 필요하다. 페이지·오디오 캐시가 서로 다른 버전으로 섞이지 않게 HTML과
+ 음성의 재검증 정책을 유지한다.
+
❯ tests/release.test.ts:16:35
14| for (const path of ["README.md", "docs/DEPLOYMENT.md"]) {
15| const document = readFileSync(path, "utf8");
16| expect.soft(document, path).toContain("22.22.2");
| ^
17| expect.soft(document, path).not.toContain("22.12");
18| }
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[5/6]⎯
FAIL tests/release.test.ts > 릴리스 문서 계약 > Node 최소 버전은 잠긴 의존성이 요구하는 22.22.2와 일치한다
AssertionError: docs/DEPLOYMENT.md: expected '# 정적 배포 인계\n\n이 문서는 **Neo가 검토 후 수행할 절…' not to contain '22.12'
- Expected
+ Received
- 22.12
+ # 정적 배포 인계
+
+ 이 문서는 **Neo가 검토 후 수행할 절차**다. Codex는 서버, DNS, 인증정보에
+ 접근하지 않았고 배포·서비스 생성·Git commit/push도 하지 않았다.
+
+ ## 산출물
+
+ Node.js 22.12 이상 지원 LTS 환경에서 `npm ci`, `npm run lint`, `npm test`,
+ `npm run build`를 실행한다. `dist/` 전체를 하나의 릴리스로 전달한다.
+ `dist/audio/`의 WAV 101개와 manifest가 반드시 포함되어야 한다. 런타임은
+ Nginx 정적 파일만 필요하며 Node 서버·TTS 엔진·DB·환경변수·API 키는 필요 없다.
+
+ Canonical repository는 [OZ-Group/Find-Word](https://source.ibit.kr/OZ-Group/Find-Word)다.
+ 프로젝트 자체는 private / all-rights-reserved이며 [LICENSE](../LICENSE)를 따른다.
+ React, ReactDOM, scheduler의 MIT 전문을 담은 `dist/THIRD_PARTY_NOTICES.md`도
+ 반드시 배포한다. `build` 검증기는 고지 파일이 없거나 축약·변조되면 실패한다.
+
+ 현재 앱은 도메인 루트(`/`) 배포 기준이다. 하위 경로 배포 시 Vite base뿐 아니라
+ 음성 URL도 함께 변경하고 테스트해야 한다. 예정 도메인은 `findword.heart-m.com`이다.
+
+ ## Nginx 예시
+
+ 아래 root는 예시다. 실제 경로·기존 서버 블록·인증서는 Neo가 확인한다.
+ 기존 서비스 설정을 덮어쓰지 않고 검토 후 적용한다.
+
+ ```nginx
+ # 정적 릴리스 디렉터리를 가리킨다. 인증서와 HTTPS 설정은 운영자가 별도 추가한다.
+ server {
+ listen 80;
+ server_name findword.heart-m.com;
+ root /var/www/findword/current;
+ index index.html;
+
+ # 기본 MIME 정의로 JS/CSS/WAV가 올바른 타입으로 제공되게 한다.
+ include /etc/nginx/mime.types;
+ add_header X-Content-Type-Options nosniff always;
+ add_header Referrer-Policy no-referrer always;
+ add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; media-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
+
+ # 파일명이 해시로 바뀌는 번들만 장기 캐시한다.
+ location /assets/ {
+ try_files $uri =404;
+ expires 1y;
+ }
+
+ # 음성 파일명은 재생성해도 같으므로 재검증한다. 누락 시 index.html 대신
+ # 404를 반환해야 앱이 명확한 음성 로드 오류와 재시도를 제공할 수 있다.
+ location /audio/ {
+ try_files $uri =404;
+ expires -1;
+ }
+
+ # SPA의 직접 진입·새로고침을 index.html로 받되 HTML은 재검증한다.
+ location / {
+ try_files $uri $uri/ /index.html;
+ expires -1;
+ }
+ }
+ ```
+
+ ## 인수 확인
+
+ 1. 서버에 적용하기 전 `nginx -t`, 기존 서비스 충돌, 파일 읽기 권한을 확인한다.
+ 2. TLS/HTTPS와 DNS를 운영자가 확인한다. 실제 도메인에서 시작 후 음성이 들리는지 확인한다.
+ 3. `audio/intro.wav`와 목표 WAV가 정상 응답하고 존재하지 않는 오디오는 404인지 확인한다.
+ 4. 모바일 Safari/Chrome, 데스크톱에서 음량·중단·다시 듣기·10문제 완주를 직접 확인한다.
+ 5. 원자적으로 릴리스 디렉터리를 전환하고 이전 디렉터리를 보관해 되돌릴 수 있게 한다.
+
+ 오프라인 캐시를 제공하는 PWA는 아니다. 정적 자산의 최초 로드에는 해당 서버
+ 연결이 필요하다. 페이지·오디오 캐시가 서로 다른 버전으로 섞이지 않게 HTML과
+ 음성의 재검증 정책을 유지한다.
+
❯ tests/release.test.ts:17:39
15| const document = readFileSync(path, "utf8");
16| expect.soft(document, path).toContain("22.22.2");
17| expect.soft(document, path).not.toContain("22.12");
| ^
18| }
19| });
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[6/6]⎯
Test Files 1 failed (1)
Tests 1 failed | 3 skipped (4)
Start at 05:54:57
Duration 357ms (setup 64%, transform 18%, tests 14%, worker 2%, import 2%)
+12
View File
@@ -0,0 +1,12 @@
> find-word@0.1.0 test
> vitest run tests/playwright-config.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 1 passed (1)
Tests 5 passed (5)
Start at 05:55:53
Duration 3.51s (tests 89%, setup 10%, transform 1%)
+78
View File
@@ -0,0 +1,78 @@
> find-word@0.1.0 test
> vitest run tests/playwright-config.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
❯ tests/playwright-config.test.ts (5 tests | 5 failed) 2254ms
❯ 브라우저 sandbox 기본 정책 (5)
× opt-in=undefined이면 sandbox를 켠다 472ms
× opt-in=이면 sandbox를 켠다 441ms
× opt-in=0이면 sandbox를 켠다 447ms
× opt-in=false이면 sandbox를 켠다 450ms
× 명시적인 PLAYWRIGHT_NO_SANDBOX=1에서만 sandbox를 끈다 443ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 5 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/playwright-config.test.ts > 브라우저 sandbox 기본 정책 > opt-in=undefined이면 sandbox를 켠다
FAIL tests/playwright-config.test.ts > 브라우저 sandbox 기본 정책 > opt-in=이면 sandbox를 켠다
FAIL tests/playwright-config.test.ts > 브라우저 sandbox 기본 정책 > opt-in=0이면 sandbox를 켠다
FAIL tests/playwright-config.test.ts > 브라우저 sandbox 기본 정책 > opt-in=false이면 sandbox를 켠다
AssertionError: expected undefined to be true // Object.is equality
- Expected:
true
+ Received:
undefined
❯ tests/playwright-config.test.ts:22:42
20| it.each([undefined, "", "0", "false"])("opt-in=%s이면 sandbox를 켠다", (v…
21| const options = launchOptions(value);
22| expect.soft(options.chromiumSandbox).toBe(true);
| ^
23| expect.soft(options.args ?? []).not.toContain("--no-sandbox");
24| expect(options.executablePath).toBe("/usr/bin/google-chrome");
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/9]⎯
FAIL tests/playwright-config.test.ts > 브라우저 sandbox 기본 정책 > opt-in=undefined이면 sandbox를 켠다
FAIL tests/playwright-config.test.ts > 브라우저 sandbox 기본 정책 > opt-in=이면 sandbox를 켠다
FAIL tests/playwright-config.test.ts > 브라우저 sandbox 기본 정책 > opt-in=0이면 sandbox를 켠다
FAIL tests/playwright-config.test.ts > 브라우저 sandbox 기본 정책 > opt-in=false이면 sandbox를 켠다
AssertionError: expected [ '--no-sandbox' ] to not include '--no-sandbox'
❯ tests/playwright-config.test.ts:23:41
21| const options = launchOptions(value);
22| expect.soft(options.chromiumSandbox).toBe(true);
23| expect.soft(options.args ?? []).not.toContain("--no-sandbox");
| ^
24| expect(options.executablePath).toBe("/usr/bin/google-chrome");
25| });
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/9]⎯
FAIL tests/playwright-config.test.ts > 브라우저 sandbox 기본 정책 > 명시적인 PLAYWRIGHT_NO_SANDBOX=1에서만 sandbox를 끈다
AssertionError: expected undefined to be false // Object.is equality
- Expected:
false
+ Received:
undefined
❯ tests/playwright-config.test.ts:27:48
25| });
26| it("명시적인 PLAYWRIGHT_NO_SANDBOX=1에서만 sandbox를 끈다", () => {
27| expect(launchOptions("1").chromiumSandbox).toBe(false);
| ^
28| });
29| });
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/9]⎯
Test Files 1 failed (1)
Tests 5 failed (5)
Start at 05:54:57
Duration 2.57s (tests 93%, setup 5%, transform 1%)
+162
View File
@@ -0,0 +1,162 @@
> find-word@0.1.0 test:browser
> playwright test tests/browser/release.spec.ts --project=mobile
[WebServer] (node:763846) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)
Running 1 test using 1 worker
(node:763874) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✘ 1 [mobile] › tests/browser/release.spec.ts:6:1 › 릴리스는 사용자 크레딧과 런타임 MIT notice 전문을 제공한다 (1.7s)
1) [mobile] › tests/browser/release.spec.ts:6:1 › 릴리스는 사용자 크레딧과 런타임 MIT notice 전문을 제공한다 ──────────
Error: expect(locator).toBeVisible() failed
Locator: getByText('Designed by OZ at Heat-T Medical Inc.', { exact: true })
Expected: visible
Timeout: 1000ms
Error: element(s) not found
Call log:
- Expect "soft toBeVisible" getByText('Designed by OZ at Heat-T Medical Inc.', { exact: true }) with timeout 1000ms
- waiting for getByText('Designed by OZ at Heat-T Medical Inc.', { exact: true })
7 | page,
8 | request,
> 9 | }) => {
| ^
10 | await page.goto("/");
11 | await expect
12 | .soft(
at /home/hermes/worktrees/find-word-v0.1/tests/browser/release.spec.ts:9:6
Error: expect(received).toBe(expected) // Object.is equality
- Expected - 77
+ Received + 20
- # Third-party notices
-
- Find-Word is private / proprietary; all rights reserved for original project
- materials. The following runtime components retain their own MIT licenses.
- These notices must accompany the static distribution.
-
- ## react 19.3.0
-
- MIT License
-
- Copyright (c) Meta Platforms, Inc. and affiliates.
-
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
-
- The above copyright notice and this permission notice shall be included in all
- copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- SOFTWARE.
-
- ## react-dom 19.3.0
-
- MIT License
-
- Copyright (c) Meta Platforms, Inc. and affiliates.
-
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
-
- The above copyright notice and this permission notice shall be included in all
- copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- SOFTWARE.
-
- ## scheduler 0.28.0
-
- MIT License
-
- Copyright (c) Meta Platforms, Inc. and affiliates.
-
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
-
- The above copyright notice and this permission notice shall be included in all
- copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- SOFTWARE.
+ <!doctype html>
+ <!-- 문서 언어를 한국어로 명시하여 보조 기기의 발음과 줄바꿈 기준을 맞춘다.
+ 외부 폰트·CDN을 사용하지 않으며 모든 실행 자산은 같은 출처에서 제공한다. -->
+ <html lang="ko">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <meta name="theme-color" content="#f8f9f4" />
+ <meta
+ name="description"
+ content="소음 속 작은 한국어 단어를 찾는 청각적 주의·식별 게임. 에스와 함께 나만의 속도로 소리를 탐험하세요."
+ />
+ <title>단어를 맞춰라 · Find-Word v0.1</title>
+ <script type="module" crossorigin src="/assets/index-JDsKSYsp.js"></script>
+ <link rel="stylesheet" crossorigin href="/assets/index-KT6VDfPn.css">
+ </head>
+ <body>
+ <div id="root"></div>
+ </body>
+ </html>
↵
10 | await page.goto("/");
11 | await expect
> 12 | .soft(
| ^
13 | page.getByText("Designed by OZ at Heat-T Medical Inc.", { exact: true }),
14 | )
15 | .toBeVisible({ timeout: 1000 });
at /home/hermes/worktrees/find-word-v0.1/tests/browser/release.spec.ts:12:33
Error Context: test-results/release-릴리스는-사용자-크레딧과-런타임-MIT-notice-전문을-제공한다-mobile/error-context.md
attachment #2: trace (application/zip) ─────────────────────────────────────────────────────────
test-results/release-릴리스는-사용자-크레딧과-런타임-MIT-notice-전문을-제공한다-mobile/trace.zip
Usage:
npx playwright show-trace test-results/release-릴리스는-사용자-크레딧과-런타임-MIT-notice-전문을-제공한다-mobile/trace.zip
────────────────────────────────────────────────────────────────────────────────────────────────
1 failed
[mobile] › tests/browser/release.spec.ts:6:1 › 릴리스는 사용자 크레딧과 런타임 MIT notice 전문을 제공한다 ───────────
+11
View File
@@ -0,0 +1,11 @@
Measured with Python wave: frames / sample rate, public/audio/*.wav
WAV count: 101
intro.wav: 8.1205 s
Longest target: message-market.wav: 10.1725 s
AudioEngine scheduling: start +0.05 s; intro-to-target gap +0.35 s
Maximum scheduled intro + target: 18.6930 s
Old assertion headroom (20 s): 1.3070 s
New assertion headroom (30 s): 11.3070 s
Product timeouts remain: preparation 15000 ms; playback scheduled end + 5 s.
Retry control flow: failed play does not set introDone; retry includes intro;
target.onended -> task.finish -> play resolves -> ENDED -> answer group visible.
@@ -0,0 +1,33 @@
> find-word@0.1.0 test:browser
> playwright test
[WebServer] (node:770976) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)
Running 12 tests using 2 workers
(node:770997) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
(node:770996) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 1 [desktop] › tests/browser/game.spec.ts:127:1 › 오디오 HTTP 실패를 화면에 표시하고 재시도한다 (19.9s)
✓ 3 [desktop] › tests/browser/release.spec.ts:6:1 › 릴리스는 사용자 크레딧과 런타임 MIT notice 전문을 제공한다 (317ms)
✓ 4 [desktop] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 (1.1s)
✓ 5 [desktop] › tests/browser/ui.spec.ts:54:1 › 난이도 설명은 모바일에서도 보인다 (823ms)
✓ 6 [desktop] › tests/browser/ui.spec.ts:74:1 › 한국어 제목은 단어 중간에서 줄바꿈하지 않는다 (17.7s)
(node:771406) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 2 [desktop] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 (1.5m)
(node:771655) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 8 [mobile] › tests/browser/game.spec.ts:127:1 › 오디오 HTTP 실패를 화면에 표시하고 재시도한다 (14.8s)
✓ 9 [mobile] › tests/browser/release.spec.ts:6:1 › 릴리스는 사용자 크레딧과 런타임 MIT notice 전문을 제공한다 (310ms)
✓ 10 [mobile] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 (948ms)
✓ 11 [mobile] › tests/browser/ui.spec.ts:54:1 › 난이도 설명은 모바일에서도 보인다 (781ms)
✓ 12 [mobile] › tests/browser/ui.spec.ts:74:1 › 한국어 제목은 단어 중간에서 줄바꿈하지 않는다 (17.2s)
✓ 7 [mobile] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 (1.5m)
12 passed (2.2m)
Exit code: 0
+18
View File
@@ -0,0 +1,18 @@
$ npm run build
> find-word@0.1.0 build
> tsc --noEmit && vite build && node scripts/check-build.mjs
vite v8.3.4 building client environment for production...
transforming...
✓ 20 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html 0.89 kB │ gzip: 0.63 kB
dist/assets/index-KT6VDfPn.css 15.25 kB │ gzip: 4.39 kB
dist/assets/index-BJ9zMFYJ.js 245.34 kB │ gzip: 77.26 kB
✓ built in 163ms
빌드 음성 자산 101개 및 런타임 notice 전문 확인
Exit code: 0
+7
View File
@@ -0,0 +1,7 @@
$ npm run lint
> find-word@0.1.0 lint
> eslint . && tsc --noEmit
Exit code: 0
+19
View File
@@ -0,0 +1,19 @@
$ npm test
> find-word@0.1.0 test
> vitest run
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 9 passed (9)
Tests 57 passed (57)
Start at 06:11:14
Duration 7.00s (tests 67%, environment 25%, setup 7%, import 1%)
Isolate 9 workers spawned · ~662ms startup each (spawn + environment, per file)
at least ~1.32s faster with isolate: false — reuses workers across files instead of one per file
Exit code: 0
+27
View File
@@ -0,0 +1,27 @@
Worktree snapshot comparison (SHA-256; ignored build/cache/dependency outputs excluded)
Existing files changed: .agent/RESULT.md, tests/browser/game.spec.ts
New evidence logs: .agent/evidence/24-retry-audio-budget.log, .agent/evidence/24-retry-final-browser.log, .agent/evidence/24-retry-final-build.log, .agent/evidence/24-retry-final-lint.log, .agent/evidence/24-retry-final-test.log, .agent/evidence/24-retry-targeted-green.log
Removed files: none
src/, public/, dependencies, Playwright config, all other existing files: unchanged
Historical release screenshots: restored byte-for-byte from pre-run copies
git diff --check: exit 0 (existing source tree was untracked)
Changed text files: no trailing whitespace
RESULT evidence links checked: 114
Test patch:
--- .cache/retry-timeout/game.spec.before.ts 2026-10-14 05:57:19.919982148 +0000
+++ tests/browser/game.spec.ts 2026-10-14 06:10:58.987220682 +0000
@@ -137,7 +137,11 @@
);
await page.unroute("**/audio/*.wav");
await page.getByRole("button", { name: "다시 시도" }).click();
- await expect(page.getByRole("group", { name: "답 선택" })).toBeVisible();
+ // 재시도는 안내부터 재생한다. 최대 8.1205 + 10.1725 + 시작·전환 0.4초에
+ // 로드·디코드·병렬 브라우저 스케줄링 여유를 더해 이 assertion만 30초로 제한한다.
+ await expect(page.getByRole("group", { name: "답 선택" })).toBeVisible({
+ timeout: 30_000,
+ });
await page.getByRole("button", { name: "처음으로" }).click();
await expect(
page.getByRole("button", { name: "게임 시작 · 소리 듣기" }),
Exit code: 0
@@ -0,0 +1,22 @@
> find-word@0.1.0 test:browser
> playwright test tests/browser/game.spec.ts --project=desktop --workers=1 --repeat-each=3 --grep 오디오 HTTP 실패
[WebServer] (node:770385) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)
Running 3 tests using 1 worker
(node:770405) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 1 [desktop] › tests/browser/game.spec.ts:127:1 › 오디오 HTTP 실패를 화면에 표시하고 재시도한다 (17.7s)
(node:770579) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 2 [desktop] › tests/browser/game.spec.ts:127:1 › 오디오 HTTP 실패를 화면에 표시하고 재시도한다 (15.5s)
(node:770757) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 3 [desktop] › tests/browser/game.spec.ts:127:1 › 오디오 HTTP 실패를 화면에 표시하고 재시도한다 (16.8s)
3 passed (52.9s)
Exit code: 0
+6
View File
@@ -0,0 +1,6 @@
> find-word@0.1.0 audio:generate
> python3 scripts/generate_audio.py
101개 WAV 생성, 합계 10,864,582 바이트
재생성 검증: 101개 WAV의 원문·크기·SHA-256 모두 이전과 동일
+23
View File
@@ -0,0 +1,23 @@
> find-word@0.1.0 test:browser
> playwright test
[WebServer] (node:746238) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)
Running 4 tests using 2 workers
(node:746258) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
(node:746259) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 2 [desktop] › tests/browser/game.spec.ts:95:1 › 오디오 HTTP 실패를 화면에 표시하고 재시도한다 (14.4s)
(node:746526) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 1 [desktop] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 (56.6s)
(node:746780) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 4 [mobile] › tests/browser/game.spec.ts:95:1 › 오디오 HTTP 실패를 화면에 표시하고 재시도한다 (12.1s)
✓ 3 [mobile] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 (56.0s)
4 passed (1.2m)
+234
View File
@@ -0,0 +1,234 @@
> find-word@0.1.0 test:browser
> playwright test
[WebServer] (node:743569) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)
Running 4 tests using 2 workers
(node:743591) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
(node:743590) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✘ 1 [desktop] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 (2.4s)
(node:743836) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✘ 3 [mobile] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 (2.0s)
(node:743961) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 2 [desktop] › tests/browser/game.spec.ts:58:1 › 오디오 HTTP 실패를 화면에 표시하고 재시도한다 (16.3s)
✓ 4 [mobile] › tests/browser/game.spec.ts:58:1 › 오디오 HTTP 실패를 화면에 표시하고 재시도한다 (12.9s)
1) [desktop] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 ───────────────
Error: expect(received).toEqual(expected) // deep equality
- Expected - 1
+ Received + 93
- Array []
+ Array [
+ Object {
+ "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds",
+ "help": "Elements must meet minimum color contrast ratio thresholds",
+ "helpUrl": "https://dequeuniversity.com/rules/axe/4.13/color-contrast?application=playwright",
+ "id": "color-contrast",
+ "impact": "serious",
+ "nodes": Array [
+ Object {
+ "all": Array [],
+ "any": Array [
+ Object {
+ "data": Object {
+ "bgColor": "#eef2e4",
+ "contrastRatio": 3.75,
+ "expectedContrastRatio": "4.5:1",
+ "fgColor": "#6e805d",
+ "fontSize": "9.8pt (13px)",
+ "fontWeight": "normal",
+ "messageKey": null,
+ },
+ "id": "color-contrast",
+ "impact": "serious",
+ "message": "Element has insufficient color contrast of 3.75 (foreground color: #6e805d, background color: #eef2e4, font size: 9.8pt (13px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "relatedNodes": Array [
+ Object {
+ "html": "<span class=\"section-number\">01</span>",
+ "target": Array [
+ ".section-number",
+ ],
+ },
+ ],
+ },
+ ],
+ "failureSummary": "Fix any of the following:
+ Element has insufficient color contrast of 3.75 (foreground color: #6e805d, background color: #eef2e4, font size: 9.8pt (13px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "html": "<span class=\"section-number\">01</span>",
+ "impact": "serious",
+ "none": Array [],
+ "target": Array [
+ ".section-number",
+ ],
+ },
+ Object {
+ "all": Array [],
+ "any": Array [
+ Object {
+ "data": Object {
+ "bgColor": "#f0f4e9",
+ "contrastRatio": 4.07,
+ "expectedContrastRatio": "4.5:1",
+ "fgColor": "#707967",
+ "fontSize": "7.5pt (10px)",
+ "fontWeight": "normal",
+ "messageKey": null,
+ },
+ "id": "color-contrast",
+ "impact": "serious",
+ "message": "Element has insufficient color contrast of 4.07 (foreground color: #707967, background color: #f0f4e9, font size: 7.5pt (10px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "relatedNodes": Array [
+ Object {
+ "html": "<label class=\"mode-option selected\"><input aria-label=\"골고루 풀기\" type=\"radio\" value=\"mixed\" checked=\"\" name=\"mode\" style=\"\"><span class=\"mode-icon\" aria-hidden=\"true\">◈</span><strong>골고루 풀기</strong><small>선택 + 직접 입력</small></label>",
+ "target": Array [
+ ".selected",
+ ],
+ },
+ ],
+ },
+ ],
+ "failureSummary": "Fix any of the following:
+ Element has insufficient color contrast of 4.07 (foreground color: #707967, background color: #f0f4e9, font size: 7.5pt (10px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "html": "<small>선택 + 직접 입력</small>",
+ "impact": "serious",
+ "none": Array [],
+ "target": Array [
+ ".selected > small",
+ ],
+ },
+ ],
+ "tags": Array [
+ "cat.color",
+ "wcag2aa",
+ "wcag143",
+ "TTv5",
+ "TT13.c",
+ "EN-301-549",
+ "EN-9.1.4.3",
+ "ACT",
+ "RGAAv4",
+ "RGAA-3.2.1",
+ ],
+ },
+ ]
19 | expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
20 | await page.screenshot({ path: `.agent/evidence/${info.project.name}-home.png`, fullPage: true });
> 21 | expect((await new AxeBuilder({ page }).analyze()).violations).toEqual([]);
| ^
22 | await page.emulateMedia({ reducedMotion: 'reduce' });
23 | await page.getByRole('button', { name: /소리 시작/ }).click();
24 | await expect(page.getByRole('button', { name: /듣는 중/ })).toBeDisabled();
at /home/hermes/worktrees/find-word-v0.1/tests/browser/game.spec.ts:21:65
Error Context: test-results/game-실제-오디오로-혼합-모드-완주·레이아웃·접근성·외부-요청-없음-desktop/error-context.md
attachment #2: trace (application/zip) ─────────────────────────────────────────────────────────
test-results/game-실제-오디오로-혼합-모드-완주·레이아웃·접근성·외부-요청-없음-desktop/trace.zip
Usage:
npx playwright show-trace test-results/game-실제-오디오로-혼합-모드-완주·레이아웃·접근성·외부-요청-없음-desktop/trace.zip
────────────────────────────────────────────────────────────────────────────────────────────────
2) [mobile] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 ────────────────
Error: expect(received).toEqual(expected) // deep equality
- Expected - 1
+ Received + 58
- Array []
+ Array [
+ Object {
+ "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds",
+ "help": "Elements must meet minimum color contrast ratio thresholds",
+ "helpUrl": "https://dequeuniversity.com/rules/axe/4.13/color-contrast?application=playwright",
+ "id": "color-contrast",
+ "impact": "serious",
+ "nodes": Array [
+ Object {
+ "all": Array [],
+ "any": Array [
+ Object {
+ "data": Object {
+ "bgColor": "#f0f4e9",
+ "contrastRatio": 4.07,
+ "expectedContrastRatio": "4.5:1",
+ "fgColor": "#707967",
+ "fontSize": "6.8pt (9px)",
+ "fontWeight": "normal",
+ "messageKey": null,
+ },
+ "id": "color-contrast",
+ "impact": "serious",
+ "message": "Element has insufficient color contrast of 4.07 (foreground color: #707967, background color: #f0f4e9, font size: 6.8pt (9px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "relatedNodes": Array [
+ Object {
+ "html": "<label class=\"mode-option selected\"><input aria-label=\"골고루 풀기\" type=\"radio\" value=\"mixed\" checked=\"\" name=\"mode\" style=\"\"><span class=\"mode-icon\" aria-hidden=\"true\">◈</span><strong>골고루 풀기</strong><small>선택 + 직접 입력</small></label>",
+ "target": Array [
+ ".selected",
+ ],
+ },
+ ],
+ },
+ ],
+ "failureSummary": "Fix any of the following:
+ Element has insufficient color contrast of 4.07 (foreground color: #707967, background color: #f0f4e9, font size: 6.8pt (9px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "html": "<small>선택 + 직접 입력</small>",
+ "impact": "serious",
+ "none": Array [],
+ "target": Array [
+ ".selected > small",
+ ],
+ },
+ ],
+ "tags": Array [
+ "cat.color",
+ "wcag2aa",
+ "wcag143",
+ "TTv5",
+ "TT13.c",
+ "EN-301-549",
+ "EN-9.1.4.3",
+ "ACT",
+ "RGAAv4",
+ "RGAA-3.2.1",
+ ],
+ },
+ ]
19 | expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
20 | await page.screenshot({ path: `.agent/evidence/${info.project.name}-home.png`, fullPage: true });
> 21 | expect((await new AxeBuilder({ page }).analyze()).violations).toEqual([]);
| ^
22 | await page.emulateMedia({ reducedMotion: 'reduce' });
23 | await page.getByRole('button', { name: /소리 시작/ }).click();
24 | await expect(page.getByRole('button', { name: /듣는 중/ })).toBeDisabled();
at /home/hermes/worktrees/find-word-v0.1/tests/browser/game.spec.ts:21:65
Error Context: test-results/game-실제-오디오로-혼합-모드-완주·레이아웃·접근성·외부-요청-없음-mobile/error-context.md
attachment #2: trace (application/zip) ─────────────────────────────────────────────────────────
test-results/game-실제-오디오로-혼합-모드-완주·레이아웃·접근성·외부-요청-없음-mobile/trace.zip
Usage:
npx playwright show-trace test-results/game-실제-오디오로-혼합-모드-완주·레이아웃·접근성·외부-요청-없음-mobile/trace.zip
────────────────────────────────────────────────────────────────────────────────────────────────
2 failed
[desktop] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 ────────────────
[mobile] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 ─────────────────
2 passed (21.4s)
+269
View File
@@ -0,0 +1,269 @@
> find-word@0.1.0 test:browser
> playwright test
[WebServer] (node:744290) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)
Running 4 tests using 2 workers
(node:744311) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
(node:744312) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 2 [desktop] › tests/browser/game.spec.ts:58:1 › 오디오 HTTP 실패를 화면에 표시하고 재시도한다 (14.5s)
(node:744575) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✘ 1 [desktop] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 (16.2s)
(node:744703) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 4 [mobile] › tests/browser/game.spec.ts:58:1 › 오디오 HTTP 실패를 화면에 표시하고 재시도한다 (12.7s)
✘ 3 [mobile] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 (15.3s)
1) [desktop] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 ───────────────
Error: expect(received).toEqual(expected) // deep equality
- Expected - 1
+ Received + 93
- Array []
+ Array [
+ Object {
+ "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds",
+ "help": "Elements must meet minimum color contrast ratio thresholds",
+ "helpUrl": "https://dequeuniversity.com/rules/axe/4.13/color-contrast?application=playwright",
+ "id": "color-contrast",
+ "impact": "serious",
+ "nodes": Array [
+ Object {
+ "all": Array [],
+ "any": Array [
+ Object {
+ "data": Object {
+ "bgColor": "#edf1e4",
+ "contrastRatio": 4.08,
+ "expectedContrastRatio": "4.5:1",
+ "fgColor": "#69795a",
+ "fontSize": "8.3pt (11px)",
+ "fontWeight": "normal",
+ "messageKey": null,
+ },
+ "id": "color-contrast",
+ "impact": "serious",
+ "message": "Element has insufficient color contrast of 4.08 (foreground color: #69795a, background color: #edf1e4, font size: 8.3pt (11px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "relatedNodes": Array [
+ Object {
+ "html": "<span>객관식</span>",
+ "target": Array [
+ ".question-meta > span:nth-child(2)",
+ ],
+ },
+ ],
+ },
+ ],
+ "failureSummary": "Fix any of the following:
+ Element has insufficient color contrast of 4.08 (foreground color: #69795a, background color: #edf1e4, font size: 8.3pt (11px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "html": "<span>객관식</span>",
+ "impact": "serious",
+ "none": Array [],
+ "target": Array [
+ ".question-meta > span:nth-child(2)",
+ ],
+ },
+ Object {
+ "all": Array [],
+ "any": Array [
+ Object {
+ "data": Object {
+ "bgColor": "#fffefa",
+ "contrastRatio": 4.41,
+ "expectedContrastRatio": "4.5:1",
+ "fgColor": "#707b65",
+ "fontSize": "8.3pt (11px)",
+ "fontWeight": "normal",
+ "messageKey": null,
+ },
+ "id": "color-contrast",
+ "impact": "serious",
+ "message": "Element has insufficient color contrast of 4.41 (foreground color: #707b65, background color: #fffefa, font size: 8.3pt (11px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "relatedNodes": Array [
+ Object {
+ "html": "<div class=\"question-panel panel\">",
+ "target": Array [
+ ".question-panel",
+ ],
+ },
+ ],
+ },
+ ],
+ "failureSummary": "Fix any of the following:
+ Element has insufficient color contrast of 4.41 (foreground color: #707b65, background color: #fffefa, font size: 8.3pt (11px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "html": "<small>다시 들어도 점수는 줄어들지 않아요.</small>",
+ "impact": "serious",
+ "none": Array [],
+ "target": Array [
+ "small",
+ ],
+ },
+ ],
+ "tags": Array [
+ "cat.color",
+ "wcag2aa",
+ "wcag143",
+ "TTv5",
+ "TT13.c",
+ "EN-301-549",
+ "EN-9.1.4.3",
+ "ACT",
+ "RGAAv4",
+ "RGAA-3.2.1",
+ ],
+ },
+ ]
32 | expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
33 | await page.screenshot({ path: `.agent/evidence/${info.project.name}-game.png`, fullPage: true });
> 34 | expect((await new AxeBuilder({ page }).analyze()).violations).toEqual([]);
| ^
35 | await page.getByRole('button', { name: /다시 듣기/ }).click();
36 | await expect(target).toBeVisible();
37 | }
at /home/hermes/worktrees/find-word-v0.1/tests/browser/game.spec.ts:34:69
Error Context: test-results/game-실제-오디오로-혼합-모드-완주·레이아웃·접근성·외부-요청-없음-desktop/error-context.md
attachment #2: trace (application/zip) ─────────────────────────────────────────────────────────
test-results/game-실제-오디오로-혼합-모드-완주·레이아웃·접근성·외부-요청-없음-desktop/trace.zip
Usage:
npx playwright show-trace test-results/game-실제-오디오로-혼합-모드-완주·레이아웃·접근성·외부-요청-없음-desktop/trace.zip
────────────────────────────────────────────────────────────────────────────────────────────────
2) [mobile] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 ────────────────
Error: expect(received).toEqual(expected) // deep equality
- Expected - 1
+ Received + 93
- Array []
+ Array [
+ Object {
+ "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds",
+ "help": "Elements must meet minimum color contrast ratio thresholds",
+ "helpUrl": "https://dequeuniversity.com/rules/axe/4.13/color-contrast?application=playwright",
+ "id": "color-contrast",
+ "impact": "serious",
+ "nodes": Array [
+ Object {
+ "all": Array [],
+ "any": Array [
+ Object {
+ "data": Object {
+ "bgColor": "#edf1e4",
+ "contrastRatio": 4.08,
+ "expectedContrastRatio": "4.5:1",
+ "fgColor": "#69795a",
+ "fontSize": "8.3pt (11px)",
+ "fontWeight": "normal",
+ "messageKey": null,
+ },
+ "id": "color-contrast",
+ "impact": "serious",
+ "message": "Element has insufficient color contrast of 4.08 (foreground color: #69795a, background color: #edf1e4, font size: 8.3pt (11px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "relatedNodes": Array [
+ Object {
+ "html": "<span>객관식</span>",
+ "target": Array [
+ ".question-meta > span:nth-child(2)",
+ ],
+ },
+ ],
+ },
+ ],
+ "failureSummary": "Fix any of the following:
+ Element has insufficient color contrast of 4.08 (foreground color: #69795a, background color: #edf1e4, font size: 8.3pt (11px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "html": "<span>객관식</span>",
+ "impact": "serious",
+ "none": Array [],
+ "target": Array [
+ ".question-meta > span:nth-child(2)",
+ ],
+ },
+ Object {
+ "all": Array [],
+ "any": Array [
+ Object {
+ "data": Object {
+ "bgColor": "#fffefa",
+ "contrastRatio": 4.41,
+ "expectedContrastRatio": "4.5:1",
+ "fgColor": "#707b65",
+ "fontSize": "8.3pt (11px)",
+ "fontWeight": "normal",
+ "messageKey": null,
+ },
+ "id": "color-contrast",
+ "impact": "serious",
+ "message": "Element has insufficient color contrast of 4.41 (foreground color: #707b65, background color: #fffefa, font size: 8.3pt (11px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "relatedNodes": Array [
+ Object {
+ "html": "<div class=\"question-panel panel\">",
+ "target": Array [
+ ".question-panel",
+ ],
+ },
+ ],
+ },
+ ],
+ "failureSummary": "Fix any of the following:
+ Element has insufficient color contrast of 4.41 (foreground color: #707b65, background color: #fffefa, font size: 8.3pt (11px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "html": "<small>다시 들어도 점수는 줄어들지 않아요.</small>",
+ "impact": "serious",
+ "none": Array [],
+ "target": Array [
+ "small",
+ ],
+ },
+ ],
+ "tags": Array [
+ "cat.color",
+ "wcag2aa",
+ "wcag143",
+ "TTv5",
+ "TT13.c",
+ "EN-301-549",
+ "EN-9.1.4.3",
+ "ACT",
+ "RGAAv4",
+ "RGAA-3.2.1",
+ ],
+ },
+ ]
32 | expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
33 | await page.screenshot({ path: `.agent/evidence/${info.project.name}-game.png`, fullPage: true });
> 34 | expect((await new AxeBuilder({ page }).analyze()).violations).toEqual([]);
| ^
35 | await page.getByRole('button', { name: /다시 듣기/ }).click();
36 | await expect(target).toBeVisible();
37 | }
at /home/hermes/worktrees/find-word-v0.1/tests/browser/game.spec.ts:34:69
Error Context: test-results/game-실제-오디오로-혼합-모드-완주·레이아웃·접근성·외부-요청-없음-mobile/error-context.md
attachment #2: trace (application/zip) ─────────────────────────────────────────────────────────
test-results/game-실제-오디오로-혼합-모드-완주·레이아웃·접근성·외부-요청-없음-mobile/trace.zip
Usage:
npx playwright show-trace test-results/game-실제-오디오로-혼합-모드-완주·레이아웃·접근성·외부-요청-없음-mobile/trace.zip
────────────────────────────────────────────────────────────────────────────────────────────────
2 failed
[desktop] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 ────────────────
[mobile] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 ─────────────────
2 passed (32.5s)
+339
View File
@@ -0,0 +1,339 @@
> find-word@0.1.0 test:browser
> playwright test
[WebServer] (node:745155) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)
Running 4 tests using 2 workers
(node:745175) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
(node:745176) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 2 [desktop] › tests/browser/game.spec.ts:95:1 › 오디오 HTTP 실패를 화면에 표시하고 재시도한다 (14.5s)
(node:745459) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✘ 1 [desktop] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 (56.8s)
(node:745658) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✘ 3 [mobile] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 (56.0s)
✓ 4 [mobile] › tests/browser/game.spec.ts:95:1 › 오디오 HTTP 실패를 화면에 표시하고 재시도한다 (14.2s)
1) [desktop] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 ───────────────
Error: expect(received).toEqual(expected) // deep equality
- Expected - 1
+ Received + 128
- Array []
+ Array [
+ Object {
+ "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds",
+ "help": "Elements must meet minimum color contrast ratio thresholds",
+ "helpUrl": "https://dequeuniversity.com/rules/axe/4.13/color-contrast?application=playwright",
+ "id": "color-contrast",
+ "impact": "serious",
+ "nodes": Array [
+ Object {
+ "all": Array [],
+ "any": Array [
+ Object {
+ "data": Object {
+ "bgColor": "#f0f4e8",
+ "contrastRatio": 4.38,
+ "expectedContrastRatio": "4.5:1",
+ "fgColor": "#65765a",
+ "fontSize": "8.3pt (11px)",
+ "fontWeight": "normal",
+ "messageKey": null,
+ },
+ "id": "color-contrast",
+ "impact": "serious",
+ "message": "Element has insufficient color contrast of 4.38 (foreground color: #65765a, background color: #f0f4e8, font size: 8.3pt (11px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "relatedNodes": Array [
+ Object {
+ "html": "<div class=\"stats\"><div><strong>3,300</strong><span>총 점수</span></div><div><strong>100%</strong><span>정답률 · 10/10</span></div><div><strong>10<small>회</small></strong><span>최고 연속 정답</span></div></div>",
+ "target": Array [
+ ".stats",
+ ],
+ },
+ ],
+ },
+ ],
+ "failureSummary": "Fix any of the following:
+ Element has insufficient color contrast of 4.38 (foreground color: #65765a, background color: #f0f4e8, font size: 8.3pt (11px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "html": "<span>총 점수</span>",
+ "impact": "serious",
+ "none": Array [],
+ "target": Array [
+ ".stats > div:nth-child(1) > span",
+ ],
+ },
+ Object {
+ "all": Array [],
+ "any": Array [
+ Object {
+ "data": Object {
+ "bgColor": "#f0f4e8",
+ "contrastRatio": 4.38,
+ "expectedContrastRatio": "4.5:1",
+ "fgColor": "#65765a",
+ "fontSize": "8.3pt (11px)",
+ "fontWeight": "normal",
+ "messageKey": null,
+ },
+ "id": "color-contrast",
+ "impact": "serious",
+ "message": "Element has insufficient color contrast of 4.38 (foreground color: #65765a, background color: #f0f4e8, font size: 8.3pt (11px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "relatedNodes": Array [
+ Object {
+ "html": "<div class=\"stats\"><div><strong>3,300</strong><span>총 점수</span></div><div><strong>100%</strong><span>정답률 · 10/10</span></div><div><strong>10<small>회</small></strong><span>최고 연속 정답</span></div></div>",
+ "target": Array [
+ ".stats",
+ ],
+ },
+ ],
+ },
+ ],
+ "failureSummary": "Fix any of the following:
+ Element has insufficient color contrast of 4.38 (foreground color: #65765a, background color: #f0f4e8, font size: 8.3pt (11px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "html": "<span>정답률 · 10/10</span>",
+ "impact": "serious",
+ "none": Array [],
+ "target": Array [
+ ".stats > div:nth-child(2) > span",
+ ],
+ },
+ Object {
+ "all": Array [],
+ "any": Array [
+ Object {
+ "data": Object {
+ "bgColor": "#f0f4e8",
+ "contrastRatio": 4.38,
+ "expectedContrastRatio": "4.5:1",
+ "fgColor": "#65765a",
+ "fontSize": "8.3pt (11px)",
+ "fontWeight": "normal",
+ "messageKey": null,
+ },
+ "id": "color-contrast",
+ "impact": "serious",
+ "message": "Element has insufficient color contrast of 4.38 (foreground color: #65765a, background color: #f0f4e8, font size: 8.3pt (11px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "relatedNodes": Array [
+ Object {
+ "html": "<div class=\"stats\"><div><strong>3,300</strong><span>총 점수</span></div><div><strong>100%</strong><span>정답률 · 10/10</span></div><div><strong>10<small>회</small></strong><span>최고 연속 정답</span></div></div>",
+ "target": Array [
+ ".stats",
+ ],
+ },
+ ],
+ },
+ ],
+ "failureSummary": "Fix any of the following:
+ Element has insufficient color contrast of 4.38 (foreground color: #65765a, background color: #f0f4e8, font size: 8.3pt (11px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "html": "<span>최고 연속 정답</span>",
+ "impact": "serious",
+ "none": Array [],
+ "target": Array [
+ "div:nth-child(3) > span",
+ ],
+ },
+ ],
+ "tags": Array [
+ "cat.color",
+ "wcag2aa",
+ "wcag143",
+ "TTv5",
+ "TT13.c",
+ "EN-301-549",
+ "EN-9.1.4.3",
+ "ACT",
+ "RGAAv4",
+ "RGAA-3.2.1",
+ ],
+ },
+ ]
86 | fullPage: true,
87 | });
> 88 | expect((await new AxeBuilder({ page }).analyze()).violations).toEqual([]);
| ^
89 | await page.getByRole("button", { name: "다시 시작" }).click();
90 | await expect(page.getByRole("button", { name: /소리 시작/ })).toBeVisible();
91 | expect(external).toEqual([]);
at /home/hermes/worktrees/find-word-v0.1/tests/browser/game.spec.ts:88:65
Error Context: test-results/game-실제-오디오로-혼합-모드-완주·레이아웃·접근성·외부-요청-없음-desktop/error-context.md
attachment #2: trace (application/zip) ─────────────────────────────────────────────────────────
test-results/game-실제-오디오로-혼합-모드-완주·레이아웃·접근성·외부-요청-없음-desktop/trace.zip
Usage:
npx playwright show-trace test-results/game-실제-오디오로-혼합-모드-완주·레이아웃·접근성·외부-요청-없음-desktop/trace.zip
────────────────────────────────────────────────────────────────────────────────────────────────
2) [mobile] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 ────────────────
Error: expect(received).toEqual(expected) // deep equality
- Expected - 1
+ Received + 128
- Array []
+ Array [
+ Object {
+ "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds",
+ "help": "Elements must meet minimum color contrast ratio thresholds",
+ "helpUrl": "https://dequeuniversity.com/rules/axe/4.13/color-contrast?application=playwright",
+ "id": "color-contrast",
+ "impact": "serious",
+ "nodes": Array [
+ Object {
+ "all": Array [],
+ "any": Array [
+ Object {
+ "data": Object {
+ "bgColor": "#f0f4e8",
+ "contrastRatio": 4.38,
+ "expectedContrastRatio": "4.5:1",
+ "fgColor": "#65765a",
+ "fontSize": "7.5pt (10px)",
+ "fontWeight": "normal",
+ "messageKey": null,
+ },
+ "id": "color-contrast",
+ "impact": "serious",
+ "message": "Element has insufficient color contrast of 4.38 (foreground color: #65765a, background color: #f0f4e8, font size: 7.5pt (10px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "relatedNodes": Array [
+ Object {
+ "html": "<div class=\"stats\"><div><strong>3,300</strong><span>총 점수</span></div><div><strong>100%</strong><span>정답률 · 10/10</span></div><div><strong>10<small>회</small></strong><span>최고 연속 정답</span></div></div>",
+ "target": Array [
+ ".stats",
+ ],
+ },
+ ],
+ },
+ ],
+ "failureSummary": "Fix any of the following:
+ Element has insufficient color contrast of 4.38 (foreground color: #65765a, background color: #f0f4e8, font size: 7.5pt (10px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "html": "<span>총 점수</span>",
+ "impact": "serious",
+ "none": Array [],
+ "target": Array [
+ ".stats > div:nth-child(1) > span",
+ ],
+ },
+ Object {
+ "all": Array [],
+ "any": Array [
+ Object {
+ "data": Object {
+ "bgColor": "#f0f4e8",
+ "contrastRatio": 4.38,
+ "expectedContrastRatio": "4.5:1",
+ "fgColor": "#65765a",
+ "fontSize": "7.5pt (10px)",
+ "fontWeight": "normal",
+ "messageKey": null,
+ },
+ "id": "color-contrast",
+ "impact": "serious",
+ "message": "Element has insufficient color contrast of 4.38 (foreground color: #65765a, background color: #f0f4e8, font size: 7.5pt (10px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "relatedNodes": Array [
+ Object {
+ "html": "<div class=\"stats\"><div><strong>3,300</strong><span>총 점수</span></div><div><strong>100%</strong><span>정답률 · 10/10</span></div><div><strong>10<small>회</small></strong><span>최고 연속 정답</span></div></div>",
+ "target": Array [
+ ".stats",
+ ],
+ },
+ ],
+ },
+ ],
+ "failureSummary": "Fix any of the following:
+ Element has insufficient color contrast of 4.38 (foreground color: #65765a, background color: #f0f4e8, font size: 7.5pt (10px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "html": "<span>정답률 · 10/10</span>",
+ "impact": "serious",
+ "none": Array [],
+ "target": Array [
+ ".stats > div:nth-child(2) > span",
+ ],
+ },
+ Object {
+ "all": Array [],
+ "any": Array [
+ Object {
+ "data": Object {
+ "bgColor": "#f0f4e8",
+ "contrastRatio": 4.38,
+ "expectedContrastRatio": "4.5:1",
+ "fgColor": "#65765a",
+ "fontSize": "7.5pt (10px)",
+ "fontWeight": "normal",
+ "messageKey": null,
+ },
+ "id": "color-contrast",
+ "impact": "serious",
+ "message": "Element has insufficient color contrast of 4.38 (foreground color: #65765a, background color: #f0f4e8, font size: 7.5pt (10px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "relatedNodes": Array [
+ Object {
+ "html": "<div class=\"stats\"><div><strong>3,300</strong><span>총 점수</span></div><div><strong>100%</strong><span>정답률 · 10/10</span></div><div><strong>10<small>회</small></strong><span>최고 연속 정답</span></div></div>",
+ "target": Array [
+ ".stats",
+ ],
+ },
+ ],
+ },
+ ],
+ "failureSummary": "Fix any of the following:
+ Element has insufficient color contrast of 4.38 (foreground color: #65765a, background color: #f0f4e8, font size: 7.5pt (10px), font weight: normal). Expected contrast ratio of 4.5:1",
+ "html": "<span>최고 연속 정답</span>",
+ "impact": "serious",
+ "none": Array [],
+ "target": Array [
+ "div:nth-child(3) > span",
+ ],
+ },
+ ],
+ "tags": Array [
+ "cat.color",
+ "wcag2aa",
+ "wcag143",
+ "TTv5",
+ "TT13.c",
+ "EN-301-549",
+ "EN-9.1.4.3",
+ "ACT",
+ "RGAAv4",
+ "RGAA-3.2.1",
+ ],
+ },
+ ]
86 | fullPage: true,
87 | });
> 88 | expect((await new AxeBuilder({ page }).analyze()).violations).toEqual([]);
| ^
89 | await page.getByRole("button", { name: "다시 시작" }).click();
90 | await expect(page.getByRole("button", { name: /소리 시작/ })).toBeVisible();
91 | expect(external).toEqual([]);
at /home/hermes/worktrees/find-word-v0.1/tests/browser/game.spec.ts:88:65
Error Context: test-results/game-실제-오디오로-혼합-모드-완주·레이아웃·접근성·외부-요청-없음-mobile/error-context.md
attachment #2: trace (application/zip) ─────────────────────────────────────────────────────────
test-results/game-실제-오디오로-혼합-모드-완주·레이아웃·접근성·외부-요청-없음-mobile/trace.zip
Usage:
npx playwright show-trace test-results/game-실제-오디오로-혼합-모드-완주·레이아웃·접근성·외부-요청-없음-mobile/trace.zip
────────────────────────────────────────────────────────────────────────────────────────────────
2 failed
[desktop] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 ────────────────
[mobile] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 ─────────────────
2 passed (1.2m)
+181
View File
@@ -0,0 +1,181 @@
# Git commit/push 없음. 보존한 입력 문서와 새 작성 파일 목록.
PRESERVED .agent/ACCEPTANCE.md
PRESERVED .agent/CONTEXT.md
ADDED .agent/RESULT.md
PRESERVED .agent/TASK.md
ADDED .agent/evidence/01-content-green.log
ADDED .agent/evidence/01-content-red.log
ADDED .agent/evidence/02-game-green.log
ADDED .agent/evidence/02-game-red.log
ADDED .agent/evidence/03-dsp-green.log
ADDED .agent/evidence/03-dsp-intermediate.log
ADDED .agent/evidence/03-dsp-red.log
ADDED .agent/evidence/04-audio-green.log
ADDED .agent/evidence/04-audio-red.log
ADDED .agent/evidence/05-assets-red.log
ADDED .agent/evidence/06-ui-05-assets-green.log
ADDED .agent/evidence/06-ui-intermediate.log
ADDED .agent/evidence/06-ui-red.log
ADDED .agent/evidence/07-build-green.log
ADDED .agent/evidence/07-build-red.log
ADDED .agent/evidence/08-rule-green.log
ADDED .agent/evidence/08-rule-red.log
ADDED .agent/evidence/09-ime-green.log
ADDED .agent/evidence/09-ime-red.log
ADDED .agent/evidence/10-ime-reset-green.log
ADDED .agent/evidence/10-ime-reset-red.log
ADDED .agent/evidence/11-provenance-green.log
ADDED .agent/evidence/11-provenance-red.log
ADDED .agent/evidence/12-ime-replay-green.log
ADDED .agent/evidence/12-ime-replay-red.log
ADDED .agent/evidence/audio-reproduce.log
ADDED .agent/evidence/browser-final.log
ADDED .agent/evidence/browser-first.log
ADDED .agent/evidence/browser-second.log
ADDED .agent/evidence/browser-third.log
ADDED .agent/evidence/changed-files.txt
ADDED .agent/evidence/desktop-game.png
ADDED .agent/evidence/desktop-home.png
ADDED .agent/evidence/desktop-results.png
ADDED .agent/evidence/final-audit.log
ADDED .agent/evidence/final-browser.log
ADDED .agent/evidence/final-build.log
ADDED .agent/evidence/final-lint.log
ADDED .agent/evidence/final-security.log
ADDED .agent/evidence/final-test.log
ADDED .agent/evidence/mobile-game.png
ADDED .agent/evidence/mobile-home.png
ADDED .agent/evidence/mobile-results.png
ADDED .gitignore
ADDED README.md
ADDED docs/ARCHITECTURE.md
ADDED docs/AUDIO_PROVENANCE.md
ADDED docs/DEPLOYMENT.md
ADDED docs/WORDS.md
ADDED eslint.config.js
ADDED index.html
ADDED package-lock.json
ADDED package.json
ADDED playwright.config.ts
ADDED public/audio/correction-apple.wav
ADDED public/audio/correction-furniture.wav
ADDED public/audio/correction-hat.wav
ADDED public/audio/correction-market.wav
ADDED public/audio/correction-meat.wav
ADDED public/audio/correction-oil.wav
ADDED public/audio/correction-sea.wav
ADDED public/audio/correction-sound.wav
ADDED public/audio/correction-tree.wav
ADDED public/audio/correction-umbrella.wav
ADDED public/audio/first-apple.wav
ADDED public/audio/first-furniture.wav
ADDED public/audio/first-hat.wav
ADDED public/audio/first-market.wav
ADDED public/audio/first-meat.wav
ADDED public/audio/first-oil.wav
ADDED public/audio/first-sea.wav
ADDED public/audio/first-sound.wav
ADDED public/audio/first-tree.wav
ADDED public/audio/first-umbrella.wav
ADDED public/audio/intro.wav
ADDED public/audio/last-apple.wav
ADDED public/audio/last-furniture.wav
ADDED public/audio/last-hat.wav
ADDED public/audio/last-market.wav
ADDED public/audio/last-meat.wav
ADDED public/audio/last-oil.wav
ADDED public/audio/last-sea.wav
ADDED public/audio/last-sound.wav
ADDED public/audio/last-tree.wav
ADDED public/audio/last-umbrella.wav
ADDED public/audio/luck-apple.wav
ADDED public/audio/luck-furniture.wav
ADDED public/audio/luck-hat.wav
ADDED public/audio/luck-market.wav
ADDED public/audio/luck-meat.wav
ADDED public/audio/luck-oil.wav
ADDED public/audio/luck-sea.wav
ADDED public/audio/luck-sound.wav
ADDED public/audio/luck-tree.wav
ADDED public/audio/luck-umbrella.wav
ADDED public/audio/manifest.json
ADDED public/audio/message-apple.wav
ADDED public/audio/message-furniture.wav
ADDED public/audio/message-hat.wav
ADDED public/audio/message-market.wav
ADDED public/audio/message-meat.wav
ADDED public/audio/message-oil.wav
ADDED public/audio/message-sea.wav
ADDED public/audio/message-sound.wav
ADDED public/audio/message-tree.wav
ADDED public/audio/message-umbrella.wav
ADDED public/audio/middle-apple.wav
ADDED public/audio/middle-furniture.wav
ADDED public/audio/middle-hat.wav
ADDED public/audio/middle-market.wav
ADDED public/audio/middle-meat.wav
ADDED public/audio/middle-oil.wav
ADDED public/audio/middle-sea.wav
ADDED public/audio/middle-sound.wav
ADDED public/audio/middle-tree.wav
ADDED public/audio/middle-umbrella.wav
ADDED public/audio/mission-apple.wav
ADDED public/audio/mission-furniture.wav
ADDED public/audio/mission-hat.wav
ADDED public/audio/mission-market.wav
ADDED public/audio/mission-meat.wav
ADDED public/audio/mission-oil.wav
ADDED public/audio/mission-sea.wav
ADDED public/audio/mission-sound.wav
ADDED public/audio/mission-tree.wav
ADDED public/audio/mission-umbrella.wav
ADDED public/audio/password-apple.wav
ADDED public/audio/password-furniture.wav
ADDED public/audio/password-hat.wav
ADDED public/audio/password-market.wav
ADDED public/audio/password-meat.wav
ADDED public/audio/password-oil.wav
ADDED public/audio/password-sea.wav
ADDED public/audio/password-sound.wav
ADDED public/audio/password-tree.wav
ADDED public/audio/password-umbrella.wav
ADDED public/audio/remember-apple.wav
ADDED public/audio/remember-furniture.wav
ADDED public/audio/remember-hat.wav
ADDED public/audio/remember-market.wav
ADDED public/audio/remember-meat.wav
ADDED public/audio/remember-oil.wav
ADDED public/audio/remember-sea.wav
ADDED public/audio/remember-sound.wav
ADDED public/audio/remember-tree.wav
ADDED public/audio/remember-umbrella.wav
ADDED public/audio/repeat-apple.wav
ADDED public/audio/repeat-furniture.wav
ADDED public/audio/repeat-hat.wav
ADDED public/audio/repeat-market.wav
ADDED public/audio/repeat-meat.wav
ADDED public/audio/repeat-oil.wav
ADDED public/audio/repeat-sea.wav
ADDED public/audio/repeat-sound.wav
ADDED public/audio/repeat-tree.wav
ADDED public/audio/repeat-umbrella.wav
ADDED scripts/check-build.mjs
ADDED scripts/generate_audio.py
ADDED src/App.tsx
ADDED src/audio/dsp.ts
ADDED src/audio/engine.ts
ADDED src/content.ts
ADDED src/game.ts
ADDED src/main.tsx
ADDED src/styles.css
ADDED tests/App.test.tsx
ADDED tests/assets.test.ts
ADDED tests/audio.test.ts
ADDED tests/browser/game.spec.ts
ADDED tests/build.test.ts
ADDED tests/content.test.ts
ADDED tests/dsp.test.ts
ADDED tests/game.test.ts
ADDED tests/setup.ts
ADDED tsconfig.json
ADDED vite.config.ts
Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

+1
View File
@@ -0,0 +1 @@
found 0 vulnerabilities
+23
View File
@@ -0,0 +1,23 @@
> find-word@0.1.0 test:browser
> playwright test
[WebServer] (node:748800) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)
Running 4 tests using 2 workers
(node:748834) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
(node:748833) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 1 [desktop] › tests/browser/game.spec.ts:99:1 › 오디오 HTTP 실패를 화면에 표시하고 재시도한다 (16.1s)
(node:749105) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 2 [desktop] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 (57.2s)
(node:749299) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 4 [mobile] › tests/browser/game.spec.ts:99:1 › 오디오 HTTP 실패를 화면에 표시하고 재시도한다 (12.8s)
✓ 3 [mobile] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 (54.9s)
4 passed (1.2m)
+15
View File
@@ -0,0 +1,15 @@
> find-word@0.1.0 build
> tsc --noEmit && vite build && node scripts/check-build.mjs
vite v8.3.4 building client environment for production...
transforming...
✓ 20 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html 0.89 kB │ gzip: 0.63 kB
dist/assets/index-BEQhGx9x.css 14.87 kB │ gzip: 4.29 kB
dist/assets/index-ZqUFJyNr.js 244.26 kB │ gzip: 76.95 kB
✓ built in 154ms
빌드 음성 자산 101개 확인
+3
View File
@@ -0,0 +1,3 @@
> find-word@0.1.0 lint
> eslint . && tsc --noEmit
+4
View File
@@ -0,0 +1,4 @@
Secret pattern scan: 34 text files, 0 matches.
Korean source explanations: 22 files, 0 missing.
Python generator syntax: PASS.
Scope: source, tests, scripts, docs, root metadata, .agent requirements; dependency binaries and PCM excluded.
+16
View File
@@ -0,0 +1,16 @@
> find-word@0.1.0 test
> vitest run
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 7 passed (7)
Tests 29 passed (29)
Start at 05:19:37
Duration 4.63s (tests 49%, environment 38%, setup 9%, transform 2%, import 1%, worker 1%)
Environment jsdom was created 7 times · 4.24s total, 38% of tracked time
create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files)
learn more: https://vitest.dev/guide/improving-performance#test-environments
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

@@ -0,0 +1,29 @@
> find-word@0.1.0 test:browser
> playwright test
[WebServer] (node:754672) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)
Running 10 tests using 2 workers
(node:754693) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
(node:754694) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 2 [desktop] › tests/browser/game.spec.ts:124:1 › 오디오 HTTP 실패를 화면에 표시하고 재시도한다 (16.6s)
✓ 3 [desktop] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 (1.1s)
✓ 4 [desktop] › tests/browser/ui.spec.ts:50:1 › 난이도 설명은 모바일에서도 보인다 (756ms)
✓ 5 [desktop] › tests/browser/ui.spec.ts:70:1 › 한국어 제목은 단어 중간에서 줄바꿈하지 않는다 (11.8s)
(node:755063) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 1 [desktop] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 (58.8s)
(node:755272) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 7 [mobile] › tests/browser/game.spec.ts:124:1 › 오디오 HTTP 실패를 화면에 표시하고 재시도한다 (14.2s)
✓ 8 [mobile] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 (939ms)
✓ 9 [mobile] › tests/browser/ui.spec.ts:50:1 › 난이도 설명은 모바일에서도 보인다 (711ms)
✓ 10 [mobile] › tests/browser/ui.spec.ts:70:1 › 한국어 제목은 단어 중간에서 줄바꿈하지 않는다 (11.9s)
✓ 6 [mobile] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 (56.3s)
10 passed (1.5m)
Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

+29
View File
@@ -0,0 +1,29 @@
> find-word@0.1.0 test:browser
> playwright test
[WebServer] (node:756541) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)
Running 10 tests using 2 workers
(node:756562) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
(node:756561) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 1 [desktop] › tests/browser/game.spec.ts:127:1 › 오디오 HTTP 실패를 화면에 표시하고 재시도한다 (14.0s)
✓ 3 [desktop] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 (1.3s)
✓ 4 [desktop] › tests/browser/ui.spec.ts:54:1 › 난이도 설명은 모바일에서도 보인다 (1.0s)
✓ 5 [desktop] › tests/browser/ui.spec.ts:74:1 › 한국어 제목은 단어 중간에서 줄바꿈하지 않는다 (11.8s)
(node:756932) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 2 [desktop] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 (57.0s)
(node:757164) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 7 [mobile] › tests/browser/game.spec.ts:127:1 › 오디오 HTTP 실패를 화면에 표시하고 재시도한다 (14.2s)
✓ 8 [mobile] › tests/browser/ui.spec.ts:4:1 › 전체 음량은 설정 흐름 안에 들어가며 키보드로 조절된다 (881ms)
✓ 9 [mobile] › tests/browser/ui.spec.ts:54:1 › 난이도 설명은 모바일에서도 보인다 (792ms)
✓ 6 [mobile] › tests/browser/game.spec.ts:7:1 › 실제 오디오로 혼합 모드 완주·레이아웃·접근성·외부 요청 없음 (55.8s)
✓ 10 [mobile] › tests/browser/ui.spec.ts:74:1 › 한국어 제목은 단어 중간에서 줄바꿈하지 않는다 (11.5s)
10 passed (1.4m)
+15
View File
@@ -0,0 +1,15 @@
> find-word@0.1.0 build
> tsc --noEmit && vite build && node scripts/check-build.mjs
vite v8.3.4 building client environment for production...
transforming...
✓ 20 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html 0.89 kB │ gzip: 0.63 kB
dist/assets/index-KT6VDfPn.css 15.25 kB │ gzip: 4.39 kB
dist/assets/index-JDsKSYsp.js 244.43 kB │ gzip: 77.02 kB
✓ built in 302ms
빌드 음성 자산 101개 확인
+3
View File
@@ -0,0 +1,3 @@
> find-word@0.1.0 lint
> eslint . && tsc --noEmit
+16
View File
@@ -0,0 +1,16 @@
> find-word@0.1.0 test
> vitest run
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1
Test Files 7 passed (7)
Tests 34 passed (34)
Start at 05:37:00
Duration 11.09s (tests 54%, environment 36%, setup 8%, import 1%, worker 1%, transform 1%)
Environment jsdom was created 7 times · 8.03s total, 36% of tracked time
create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files)
learn more: https://vitest.dev/guide/improving-performance#test-environments
@@ -0,0 +1,9 @@
> find-word@0.1.0 build
> tsc --noEmit && vite build && node scripts/check-build.mjs
tests/App.test.tsx(62,71): error TS2769: No overload matches this call.
Overload 1 of 2, '(role: ByRoleMatcher, options?: ByRoleOptions | undefined): HTMLElement', gave the following error.
Object literal may only specify known properties, and 'exact' does not exist in type 'ByRoleOptions'.
Overload 2 of 2, '(role: ByRoleMatcher, options?: ByRoleOptions | undefined): HTMLElement', gave the following error.
Object literal may only specify known properties, and 'exact' does not exist in type 'ByRoleOptions'.
@@ -0,0 +1,9 @@
> find-word@0.1.0 lint
> eslint . && tsc --noEmit
tests/App.test.tsx(62,71): error TS2769: No overload matches this call.
Overload 1 of 2, '(role: ByRoleMatcher, options?: ByRoleOptions | undefined): HTMLElement', gave the following error.
Object literal may only specify known properties, and 'exact' does not exist in type 'ByRoleOptions'.
Overload 2 of 2, '(role: ByRoleMatcher, options?: ByRoleOptions | undefined): HTMLElement', gave the following error.
Object literal may only specify known properties, and 'exact' does not exist in type 'ByRoleOptions'.
Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Some files were not shown because too many files have changed in this diff Show More