173 lines
7.9 KiB
Markdown
173 lines
7.9 KiB
Markdown
# Playwright E2E (코어 영역)
|
||
|
||
G7 코어의 Playwright (TypeScript) 기반 결정론적 E2E 인프라.
|
||
|
||
## 빠른 시작
|
||
|
||
```powershell
|
||
# PowerShell (PO 환경)
|
||
$env:PLAYWRIGHT_BASE_URL='https://g7.dev'
|
||
npm run test:e2e # 전체 spec
|
||
npm run test:e2e:smoke # @smoke 태그 (homepage / login / admin-dashboard 3건)
|
||
npm run test:e2e:wysiwyg # 위지윅 매트릭스 90건 (Phase M)
|
||
npm run test:e2e:ui # Playwright UI 모드 (디버깅)
|
||
```
|
||
|
||
```bash
|
||
# Bash (CI/Linux)
|
||
PLAYWRIGHT_BASE_URL=https://g7.dev npx playwright test
|
||
```
|
||
|
||
## Base URL 해석 우선순위 (하드코딩 회피)
|
||
|
||
`playwright.config.ts` 의 `resolveBaseUrl()` 가 다음 순서로 결정:
|
||
|
||
1. **`PLAYWRIGHT_BASE_URL` 환경변수** — CI / 명시적 오버라이드 (최우선)
|
||
2. **`.env` 의 `APP_URL`** — 단 `http://localhost` 류는 fallback 부적합 (Apache vhost 미경유)
|
||
3. **그 외** — 명시 에러 (PO 가 의도적으로 base URL 을 지정하지 않으면 spec 미실행)
|
||
|
||
도메인이 가변(개발자/CI/PO 환경별 다름)이므로 fallback 하드코딩 금지.
|
||
|
||
## 디렉토리 구조
|
||
|
||
```
|
||
tests/Playwright/
|
||
├── fixtures/
|
||
│ └── auth.ts (Sanctum 토큰 발급 + addInitScript 주입)
|
||
├── specs/
|
||
│ ├── smoke/ (Phase 1 — 3건)
|
||
│ │ ├── homepage.spec.ts
|
||
│ │ ├── login.spec.ts
|
||
│ │ └── admin-dashboard.spec.ts
|
||
│ └── wysiwyg/ (Phase 2 — 90건, 위지윅 편집기 Phase M)
|
||
│ ├── auth-guard.spec.ts (12: access_outcome × user_permission)
|
||
│ ├── handler-suppression.spec.ts (6: suppressed_handler 축)
|
||
│ ├── anchor-intercept.spec.ts (45: anchor_kind × modifier_key)
|
||
│ └── url-template-param.spec.ts (27: url_template_param × anchor_kind)
|
||
├── .auth/ (gitignore — storageState 캐시)
|
||
└── README.md (본 문서)
|
||
```
|
||
|
||
## 코어/확장 분리 원칙
|
||
|
||
기존 PHPUnit / Vitest 와 동일 원칙. **모듈/플러그인/템플릿 E2E spec 을 코어 디렉토리에 작성 금지.**
|
||
|
||
| 영역 | spec 위치 | config 위치 |
|
||
|---|---|---|
|
||
| **코어** | `tests/Playwright/specs/` | `playwright.config.ts` (루트) |
|
||
| 모듈 | `modules/_bundled/{id}/tests/Playwright/specs/` | 모듈 자체 `playwright.config.ts` |
|
||
| 플러그인 | `plugins/_bundled/{id}/tests/Playwright/specs/` | 플러그인 자체 |
|
||
| 템플릿 | `templates/_bundled/{id}/tests/Playwright/specs/` | 템플릿 자체 |
|
||
|
||
확장이 자체 E2E spec 을 추가하는 sample skeleton 은
|
||
`modules/_bundled/sirsoft-ecommerce/tests/Playwright/` 참조.
|
||
|
||
## 인증 가드 (PlaywrightIssueToken 3중 보안)
|
||
|
||
`app/Console/Commands/PlaywrightIssueToken.php` 는 다음 3중 가드를 강제한다:
|
||
|
||
1. **CLI 한정** — `php_sapi_name() === 'cli'` (production 웹 요청에서 절대 도달 불가)
|
||
2. **`G7_PLAYWRIGHT_BYPASS=1` 환경변수 옵트인** — `.env` 영구 수정 불요, 인라인 부착만으로 활성화
|
||
3. **`APP_DEBUG=true` inline override** — `production + debug=false` 환경에서도 토큰 발급 가능
|
||
|
||
호출은 `fixtures/auth.ts` 의 `issueToken()` 헬퍼가 자동으로 `G7_PLAYWRIGHT_BYPASS=1` 을 부착하므로
|
||
spec 작성자는 가드를 인지할 필요 없음.
|
||
|
||
```typescript
|
||
import { issueToken, authenticatePage } from '../../fixtures/auth';
|
||
|
||
test('...', async ({ page }) => {
|
||
const token = issueToken('core.templates.layouts.edit');
|
||
await authenticatePage(page, token);
|
||
await page.goto('/admin/dashboard');
|
||
});
|
||
```
|
||
|
||
권한 식별자는 임의 string — 코어/모듈/플러그인 공통 API. `Permission::firstOrCreate` 가 자동 생성.
|
||
|
||
## 데이터 생성 위치 — 책임 분리 매트릭스
|
||
|
||
E2E spec 이 "특정 유저 + 특정 역할 + 특정 도메인 상황" 에서 동작하려면 백엔드 데이터 생성 코드가
|
||
필요하다. **그 코드는 데이터를 소유한 영역에 위치해야 한다** — 기존 Seeder/Factory 분리 원칙과 동일.
|
||
|
||
| 데이터 종류 | 책임 영역 | 위치 |
|
||
|---|---|---|
|
||
| 코어 권한 (`core.*`), 역할, 유저, Sanctum 토큰 | 코어 | `app/Console/Commands/PlaywrightIssueToken.php` |
|
||
| 모듈 권한 (`sirsoft-ecommerce.*`) | 모듈 (코어 커맨드의 `--permissions=` 임의 식별자) | — |
|
||
| 모듈 도메인 데이터 (상품/카테고리/주문) | 모듈 | `modules/_bundled/{id}/src/Console/Commands/PlaywrightSeed{id}.php` |
|
||
| 플러그인 도메인 데이터 (결제 키 등) | 플러그인 | `plugins/_bundled/{id}/src/Console/Commands/PlaywrightSeed{id}.php` |
|
||
| 외부 의존 (토스 결제창 응답 등) | spec 안 mock | `page.route('https://api.tosspayments.com/**', ...)` |
|
||
|
||
**핵심 원칙**: 코어는 모듈 도메인을 모른다. 모듈 도메인 시드 커맨드를 코어에 두면 코어↔모듈 의존이 뒤집힘 (audit 룰 위반).
|
||
|
||
## 시나리오 매니페스트 ↔ spec 매핑
|
||
|
||
| YAML axis | spec 파일 | 케이스 |
|
||
|---|---|---|
|
||
| `tests/scenarios/wysiwyg-editor-access-guard.yaml` `cross_product[0]` (access_outcome × user_permission) | `specs/wysiwyg/auth-guard.spec.ts` | 12 |
|
||
| 동일 yaml `cross_product[1]` (anchor_kind × modifier_key) | `specs/wysiwyg/anchor-intercept.spec.ts` | 45 |
|
||
| 동일 yaml `cross_product[2]` (suppressed_handler) | `specs/wysiwyg/handler-suppression.spec.ts` | 6 |
|
||
| 동일 yaml `cross_product[3]` (url_template_param × anchor_kind) | `specs/wysiwyg/url-template-param.spec.ts` | 27 |
|
||
|
||
각 spec 은 `test.describe.parallel(axisName)` 으로 묶고 matrix 배열로 매개변수화.
|
||
케이스 docblock 에 `// @scenario k1=v1, k2=v2` / `// @effects e1, e2` 마킹.
|
||
|
||
## 외부 의존 시나리오 — mock-first 패턴 (결제 등)
|
||
|
||
토스페이먼츠 등 외부 도메인 결제 흐름은 결정론적 자동화에 **mock-first 전략** 필수.
|
||
|
||
```typescript
|
||
// 토스 SDK 호출 가로채기 — 결제창 띄우지 않고 즉시 success callback URL 로 redirect
|
||
await page.route('https://js.tosspayments.com/**', route => route.fulfill({
|
||
contentType: 'application/javascript',
|
||
body: `
|
||
window.TossPayments = () => ({
|
||
requestPayment: async (method, params) => {
|
||
window.location.href = params.successUrl
|
||
+ '?paymentKey=test_pk_xxx&orderId=' + params.orderId + '&amount=' + params.amount;
|
||
}
|
||
});
|
||
`,
|
||
}));
|
||
|
||
// 토스 confirm API mock — 가맹점 측 callback 흐름 검증
|
||
await page.route('https://api.tosspayments.com/v1/payments/confirm', route => route.fulfill({
|
||
status: 200, contentType: 'application/json',
|
||
body: JSON.stringify({
|
||
paymentKey: 'test_pk_xxx', orderId: 'order_001', status: 'DONE',
|
||
method: '카드', totalAmount: 10000, approvedAt: new Date().toISOString(),
|
||
}),
|
||
}));
|
||
```
|
||
|
||
분기별(성공/실패/취소/금액 불일치/중복) 가맹점 측 처리(주문 상태 전환, 재고 차감, 알림 발송) 정확성 검증.
|
||
실 카드 결제 자동화는 권장하지 않음 — PO 수동 검증.
|
||
|
||
## 회귀 / Flaky 처리
|
||
|
||
- `retries: process.env.CI ? 2 : 0` — CI 에서만 자동 retry
|
||
- `trace: 'retain-on-failure'` — 실패 시 trace.zip 보존 (`npx playwright show-trace ...`)
|
||
- 2주 내 2회 이상 fail = `@flaky` 태그 격리 + RCA
|
||
|
||
## 디버깅 워크플로우
|
||
|
||
```powershell
|
||
# UI 모드 — 단계별 step 시각화
|
||
$env:PLAYWRIGHT_BASE_URL='https://g7.dev'; npm run test:e2e:ui
|
||
|
||
# 특정 spec 만 실행
|
||
PLAYWRIGHT_BASE_URL=https://g7.dev npx playwright test tests/Playwright/specs/wysiwyg/auth-guard.spec.ts
|
||
|
||
# trace 열기
|
||
npx playwright show-trace test-results/<...>/trace.zip
|
||
```
|
||
|
||
Playwright 가 회귀를 발견하면 → Claude `chrome-devtools-mcp` 로 라이브 재현/진단 → spec 으로 승격.
|
||
|
||
## 참고
|
||
|
||
- 가이드: `docs/testing/e2e-testing.md`
|
||
- 시나리오 매니페스트: `tests/scenarios/wysiwyg-editor-access-guard.yaml`
|
||
- 단위 시뮬레이션 (Vitest): `resources/js/core/template-engine/wysiwyg/__tests__/previewNavigation.test.ts` 등
|
||
- 모듈 sample skeleton: `modules/_bundled/sirsoft-ecommerce/tests/Playwright/README.md`
|