# 12 · 검증 스크립트 작성

**언제 읽나**: 회귀를 막는 테스트를 만들 때. 또는 "완료" 선언 전에 증거가 필요할 때.
**원칙**: 유닛 테스트가 아니다. **설계 규칙 1개당 스크립트 1개**를 만든다.

---

## 규칙

1. 파일명은 지키는 성질로 짓는다: `test_scale_invariance.mjs`, `test_quota.mjs`, `test_export.mjs`.
2. 파일 첫 줄 주석에 **어떤 규칙을 지키는지** 쓴다.
3. 조건을 **실제로 만든다.** 추정하지 않는다. (예: 쿼터 초과를 확인하려면 실제로 가득 채운다)
4. 결과를 **숫자로** 출력한다. 나중 회귀 비교의 기준이 된다.
5. 마지막에 `process.exit(통과 ? 0 : 1)` 을 호출한다. 이것이 없으면 실패를 아무도 모른다.
6. ❌ Playwright를 프로젝트 의존성으로 넣지 않는다. 필요할 때만 설치한다.

---

## 템플릿 — 그대로 복사해서 TODO만 채운다

```js
// <무엇을 지키는가> 검증 — 규칙 <번호>: "<규칙 원문>"
// 실행: npm run dev 후  node docs/verify/test_xxx.mjs
import { chromium } from 'playwright';

const BASE = process.env.BASE_URL ?? 'http://localhost:5173';
const b = await chromium.launch();
const page = await b.newPage({ viewport: { width: 1440, height: 900 } });

// 관측 장치를 탐색보다 "먼저" 건다
const errors = [];
page.on('pageerror', (e) => errors.push(`pageerror: ${e.message}`));

let pass = true;
const check = (label, ok, extra = '') => {
  if (!ok) pass = false;
  console.log(`  ${ok ? '✓' : '✗'} ${label}${extra ? ' — ' + extra : ''}`);
};

// 결정론적 시작점
await page.goto(BASE, { waitUntil: 'networkidle' });
await page.evaluate(() => localStorage.clear());
await page.reload({ waitUntil: 'networkidle' });

// TODO 1: 조건을 실제로 만든다
// TODO 2: 결과를 측정한다 (숫자로)
// TODO 3: check()로 단언한다

check('미처리 예외 없음', errors.filter(e => e.startsWith('pageerror')).length === 0);
console.log(pass ? '\n통과' : '\n실패');
await b.close();
process.exit(pass ? 0 : 1);
```

---

## 설치와 실행

```bash
npm i -D playwright && npx playwright install chromium

npm run dev &
for f in docs/verify/test_*.mjs; do
  echo "── $f"
  node "$f" || echo "FAILED: $f"
done

# 배포본 검증
BASE_URL=https://example.pages.dev node docs/verify/test_deployed.mjs
```

---

## ⚠️ 가장 흔한 사고

**UI를 바꾸면 검증 스크립트의 셀렉터도 같이 고쳐야 한다.**
실제 사례: 좌측 메뉴가 "버튼"에서 "탭"으로 바뀌었는데 스크립트 3개를 갱신하지 않아
한동안 **조용히 통과**하고 있었다. 전체를 한 번에 돌려보고서야 발견했다.

규칙:
- UI를 바꾸는 커밋에는 검증 스크립트 수정을 포함한다.
- ❌ 스위트를 부분만 돌리지 않는다. 항상 전부 돌린다.

---

## 측정 대상 예시 (무엇을 숫자로 뽑나)

| 지키려는 규칙 | 측정 방법 |
|---|---|
| 창 크기와 무관한 레이아웃 | 창 3종에서 아트보드 폭·줄 수를 출력해 **같은지** 비교 |
| 내보내기 크기 고정 | 창 3종에서 출력 픽셀 크기를 비교 |
| 저장·복원 | 새로고침 후 요소 개수·좌표 비교, Undo/Redo 10회 왕복 |
| 쿼터 초과 처리 | 실제로 가득 채워 상태 전환·앱 생존·미처리 예외 0건 확인 |
| 대용량 성능 | 20페이지로 실측 후 **50페이지도 실제로** 돌린다 (추정으로 끝내지 않는다) |
