Files
Gnuboard7/tests/Playwright
HeuJung 72f46a6468 fix(core): 레이아웃 첨부 URL 을 사이트 상대 경로로 발급하고 저장 게이트가 자기 주소를 외부로 보지 않게 수정
편집기 헤더 「로고 이미지」에 파일을 올린 뒤 저장하면 「HTTPS 프로토콜 URL은
허용되지 않습니다」 422 로 거부되던 결함. 프록시 서빙 URL 이 절대 형태로 발급되어
NoExternalUrls 가 서버 자신의 주소를 외부로 차단했고, 배경 이미지는 style 로 들어가
스캔 밖이어서 로고(propValue)에서만 드러났다.

- proxyUrl 을 사이트 상대 경로로 발급 (발급 시점 도메인·스킴에 묶이지 않는다)
- SiteAssetHosts 신설: app.url host + 운영자가 선언한 공개 자산 디스크 host 를
 브라우저와 같은 정규화 뒤 등가 비교. 요청 Host 헤더는 근거로 쓰지 않고,
 흉내 host·protocol-relative·위험 스킴은 종전대로 차단
- 회귀 테스트 4클래스(71 passed) + 시나리오 매니페스트 + Playwright 2케이스,
 공개 문서(SECURITY·security·storage-driver·api)와 규정 표 갱신

함께 담긴 릴리즈 정비:
- 7.0.11 미출시 섹션의 릴리즈 날짜를 09-09 로 갱신 (코어·board·page·admin_basic·basic·ja 언어팩 3종)
- 배포본에 실리는 주석·독블록·시나리오의 내부 호칭을 중립 표현으로 정리하고,
 동봉 vendor 자산의 우연한 토큰은 누출 검사에서 경로 규칙으로만 제외
- 버전 bump 검사: github_url 없는 번들을 언어팩과 같은 origin/release 페이로드
 기준선으로 판정(상시 경고 제거), 코어 버전 판독이 env 래핑을 읽도록 보정
2026-09-09 09:20:29 +09:00
..

Playwright E2E (코어 영역)

G7 코어의 Playwright (TypeScript) 기반 결정론적 E2E 인프라.

빠른 시작

# PowerShell (개발 환경)
$env:PLAYWRIGHT_BASE_URL='https://g7.dev'
npm run test:e2e            # 전체 spec
npm run test:e2e:smoke      # @smoke 태그 (--grep @smoke)
npm run test:e2e:ui         # Playwright UI 모드 (디버깅)

# 특정 spec / 디렉토리만
npx playwright test tests/Playwright/specs/smoke/admin-base.spec.ts
npx playwright test tests/Playwright/specs/layout-editor --workers=2
# Bash (CI/Linux)
PLAYWRIGHT_BASE_URL=https://g7.dev npx playwright test

루트 package.json 이 제공하는 E2E 스크립트는 test:e2e · test:e2e:smoke · test:e2e:ui 3개다. 그 밖의 범위 지정은 npx playwright test 에 경로나 --grep 을 직접 준다.

브라우저는 Chromium 전용이다 (playwright.config.ts 의 projects 에 chromium 하나). Firefox/WebKit 도입 조건은 docs/testing/e2e-testing.md §3 "브라우저 범위" 참조.

Base URL 해석 우선순위 (하드코딩 회피)

playwright.config.ts 의 resolveBaseUrl() 가 다음 순서로 결정:

  1. PLAYWRIGHT_BASE_URL 환경변수 — CI / 명시적 오버라이드 (최우선)
  2. .env 의 APP_URL — 단 http://localhost 류는 fallback 부적합 (Apache vhost 미경유)
  3. 그 외 — 명시 에러 (의도적으로 base URL 을 지정하지 않으면 spec 미실행)

도메인이 가변(개발자/CI/운영 환경별 다름)이므로 fallback 하드코딩 금지.

디렉토리 구조

tests/Playwright/
├── fixtures/
│   ├── auth.ts                        (Sanctum 토큰 발급 + addInitScript 주입)
│   ├── ecommerce-auth.ts              (구매자 세션 fixture)
│   ├── layout-editor.ts               (편집기 진입/저장 헬퍼)
│   ├── seed-layout.ts                 (시드 화면 준비/정리)
│   └── seed-layouts/                  (시드 화면 원본 JSON)
├── global-setup.ts                    (시드 화면 설치)
├── global-teardown.ts                 (시드 화면 제거)
├── specs/
│   ├── smoke/                         (핵심 화면 — 14 파일)
│   ├── admin/                         (관리자 화면 — 17 파일)
│   ├── layout-editor/                 (레이아웃 편집기 — 46 파일)
│   ├── pagination/                    (목록 페이지네이션 — 4 파일)
│   ├── search/                        (검색 — 1 파일)
│   └── *.spec.ts                      (엔진/렌더링 회귀 — 14 파일)
├── .auth/                             (gitignore — storageState 캐시)
└── README.md                          (본 문서)

@smoke 표기는 70곳(25개 파일)이며, 그중 1곳은 test.describe 단위라 --grep @smoke 로 선택되는 테스트 수는 표기 수보다 많다. 파일·건수는 변하므로 판단이 필요할 때 직접 센다.

코어/확장 분리 원칙

기존 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 작성자는 가드를 인지할 필요 없음.

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/**', ...)

핵심 원칙: 코어는 모듈 도메인을 모른다. 모듈 도메인 시드 커맨드를 코어에 두면 코어↔모듈 의존이 뒤집힘 (정적 검사 위반).

시나리오 매니페스트 ↔ spec 매핑

시나리오 매니페스트(tests/scenarios/*.yaml)의 축과 spec 은 파일명으로 대응시킨다 — layout-editor-table-editor.yaml ↔ specs/layout-editor/table-editor.spec.ts 처럼 매니페스트 이름에서 도메인 접두어를 뗀 형태가 spec 파일명이 된다.

각 spec 은 test.describe.parallel(axisName) 으로 묶고 matrix 배열로 매개변수화한다. 케이스 docblock 에 // @scenario k1=v1, k2=v2 / // @effects e1, e2 를 마킹하면 매니페스트의 cross_product · effects 와 자동으로 대조된다.

@scenario 의 축 구분자는 쉼표뿐이다. k1=v1 × k2=v2 처럼 곱셈 기호로 적으면 첫 축만 인식되고 나머지는 조용히 누락된다.

외부 의존 시나리오 — mock-first 패턴 (결제 등)

토스페이먼츠 등 외부 도메인 결제 흐름은 결정론적 자동화에 mock-first 전략 필수.

// 토스 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(),
  }),
}));

분기별(성공/실패/취소/금액 불일치/중복) 가맹점 측 처리(주문 상태 전환, 재고 차감, 알림 발송) 정확성 검증. 실 카드 결제 자동화는 권장하지 않음 — 수동 검증.

회귀 / Flaky 처리

  • retries: process.env.CI ? 2 : 0 — CI 에서만 자동 retry
  • trace: 'retain-on-failure' — 실패 시 trace.zip 보존 (npx playwright show-trace ...)
  • 2주 내 2회 이상 fail = @flaky 태그 격리 + RCA

디버깅 워크플로우

# 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/layout-editor/auth-guard-and-locales.spec.ts

# trace 열기
npx playwright show-trace test-results/<...>/trace.zip

Playwright 가 회귀를 발견하면 → Claude chrome-devtools-mcp 로 라이브 재현/진단 → spec 으로 승격.

참고

  • 가이드: docs/testing/e2e-testing.md
  • 시나리오 매니페스트: tests/scenarios/ (레이아웃 편집기 축은 layout-editor-*.yaml)
  • 단위 시뮬레이션 (Vitest): resources/js/core/template-engine/__tests__/
  • 모듈 sample skeleton: modules/_bundled/sirsoft-ecommerce/tests/Playwright/README.md