@mandujs/cli 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/commands/init.ts +44 -0
- package/src/commands/registry.ts +1 -0
- package/src/main.ts +2 -0
- package/templates/default/.github/README.md +189 -0
- package/templates/default/.github/workflows/ate-e2e-subset.yml +138 -0
- package/templates/default/.github/workflows/ate-e2e.yml +47 -0
- package/templates/default/scripts/analyze-impact.ts +119 -0
- package/templates/realtime-chat/.github/README.md +189 -0
- package/templates/realtime-chat/.github/workflows/ate-e2e-subset.yml +138 -0
- package/templates/realtime-chat/.github/workflows/ate-e2e.yml +47 -0
- package/templates/realtime-chat/scripts/analyze-impact.ts +119 -0
package/package.json
CHANGED
package/src/commands/init.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface InitOptions {
|
|
|
17
17
|
ui?: UILibrary;
|
|
18
18
|
theme?: boolean;
|
|
19
19
|
minimal?: boolean;
|
|
20
|
+
withCi?: boolean;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
const ALLOWED_TEMPLATES = ["default", "realtime-chat"] as const;
|
|
@@ -214,6 +215,7 @@ export async function init(options: InitOptions = {}): Promise<boolean> {
|
|
|
214
215
|
const css: CSSFramework = options.minimal ? "none" : (options.css || "tailwind");
|
|
215
216
|
const ui: UILibrary = options.minimal ? "none" : (options.ui || "shadcn");
|
|
216
217
|
const theme = options.theme || false;
|
|
218
|
+
const withCi = options.withCi || false;
|
|
217
219
|
|
|
218
220
|
console.log(`🥟 Mandu Init`);
|
|
219
221
|
console.log(`📁 프로젝트: ${projectName}`);
|
|
@@ -223,6 +225,9 @@ export async function init(options: InitOptions = {}): Promise<boolean> {
|
|
|
223
225
|
if (theme) {
|
|
224
226
|
console.log(`🌙 테마: Dark mode 지원`);
|
|
225
227
|
}
|
|
228
|
+
if (withCi) {
|
|
229
|
+
console.log(`🔄 CI/CD: GitHub Actions 워크플로우 포함`);
|
|
230
|
+
}
|
|
226
231
|
console.log();
|
|
227
232
|
|
|
228
233
|
// Check if target directory exists
|
|
@@ -269,6 +274,11 @@ export async function init(options: InitOptions = {}): Promise<boolean> {
|
|
|
269
274
|
// Create .mandu directory for build output
|
|
270
275
|
await fs.mkdir(path.join(targetDir, ".mandu/client"), { recursive: true });
|
|
271
276
|
|
|
277
|
+
// Setup CI/CD workflows if requested
|
|
278
|
+
if (withCi) {
|
|
279
|
+
await setupCiWorkflows(targetDir);
|
|
280
|
+
}
|
|
281
|
+
|
|
272
282
|
// Create minimal layout.tsx if css=none (without globals.css import)
|
|
273
283
|
if (css === "none") {
|
|
274
284
|
await createMinimalLayout(targetDir, projectName);
|
|
@@ -630,3 +640,37 @@ async function setupLockfile(targetDir: string): Promise<LockfileResult> {
|
|
|
630
640
|
};
|
|
631
641
|
}
|
|
632
642
|
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* CI/CD 워크플로우 파일 생성 (.github/workflows)
|
|
646
|
+
*/
|
|
647
|
+
async function setupCiWorkflows(targetDir: string): Promise<void> {
|
|
648
|
+
const workflowsDir = path.join(targetDir, ".github/workflows");
|
|
649
|
+
await fs.mkdir(workflowsDir, { recursive: true });
|
|
650
|
+
|
|
651
|
+
const templatesDir = getTemplatesDir();
|
|
652
|
+
const sourceWorkflowsDir = path.join(templatesDir, "default/.github/workflows");
|
|
653
|
+
|
|
654
|
+
try {
|
|
655
|
+
// Copy all workflow files
|
|
656
|
+
const workflowFiles = await fs.readdir(sourceWorkflowsDir);
|
|
657
|
+
for (const file of workflowFiles) {
|
|
658
|
+
const src = path.join(sourceWorkflowsDir, file);
|
|
659
|
+
const dest = path.join(workflowsDir, file);
|
|
660
|
+
const content = await fs.readFile(src, "utf-8");
|
|
661
|
+
await fs.writeFile(dest, content);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Copy impact analysis script
|
|
665
|
+
const scriptsDir = path.join(targetDir, "scripts");
|
|
666
|
+
await fs.mkdir(scriptsDir, { recursive: true });
|
|
667
|
+
|
|
668
|
+
const analyzeImpactSrc = path.join(templatesDir, "default/scripts/analyze-impact.ts");
|
|
669
|
+
const analyzeImpactDest = path.join(scriptsDir, "analyze-impact.ts");
|
|
670
|
+
const analyzeImpactContent = await fs.readFile(analyzeImpactSrc, "utf-8");
|
|
671
|
+
await fs.writeFile(analyzeImpactDest, analyzeImpactContent);
|
|
672
|
+
} catch (error) {
|
|
673
|
+
console.warn(`⚠️ CI/CD 워크플로우 설정 경고:`, error);
|
|
674
|
+
// CI 설정 실패는 프로젝트 생성을 중단하지 않음
|
|
675
|
+
}
|
|
676
|
+
}
|
package/src/commands/registry.ts
CHANGED
package/src/main.ts
CHANGED
|
@@ -74,6 +74,7 @@ Options:
|
|
|
74
74
|
--ui <library> init 시 UI 라이브러리: shadcn, ark, none (기본: shadcn)
|
|
75
75
|
--theme init 시 다크모드 테마 시스템 추가
|
|
76
76
|
--minimal init 시 CSS/UI 없이 최소 템플릿 생성 (--css none --ui none)
|
|
77
|
+
--with-ci init 시 GitHub Actions CI/CD 워크플로우 포함 (ATE E2E 테스트)
|
|
77
78
|
--file <path> spec-upsert spec 파일/monitor 로그 파일 경로
|
|
78
79
|
--watch build/guard arch 파일 감시 모드
|
|
79
80
|
--output <path> routes/openapi/doctor/contract/guard 출력 경로
|
|
@@ -111,6 +112,7 @@ Notes:
|
|
|
111
112
|
|
|
112
113
|
Examples:
|
|
113
114
|
bunx mandu init --name my-app # Tailwind + shadcn/ui 기본
|
|
115
|
+
bunx mandu init --name my-app --with-ci # CI/CD 워크플로우 포함
|
|
114
116
|
bunx mandu init --name chat-app --template realtime-chat # 실시간 채팅 스타터 템플릿
|
|
115
117
|
bunx mandu init my-app --minimal # CSS/UI 없이 최소 템플릿
|
|
116
118
|
bunx mandu dev
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# GitHub Actions CI/CD for Mandu ATE
|
|
2
|
+
|
|
3
|
+
이 디렉토리는 Mandu ATE (Automation Test Engine) E2E 테스트를 위한 GitHub Actions 워크플로우를 포함합니다.
|
|
4
|
+
|
|
5
|
+
## 📋 워크플로우 개요
|
|
6
|
+
|
|
7
|
+
### 1. `ate-e2e.yml` - 전체 E2E 테스트 실행
|
|
8
|
+
|
|
9
|
+
**트리거:**
|
|
10
|
+
- Pull Request (모든 브랜치)
|
|
11
|
+
- `main` 브랜치로 Push
|
|
12
|
+
|
|
13
|
+
**동작:**
|
|
14
|
+
1. Bun 환경 설정
|
|
15
|
+
2. 의존성 설치
|
|
16
|
+
3. Playwright 브라우저 설치 (Chromium)
|
|
17
|
+
4. ATE E2E 파이프라인 실행 (`bun run test:e2e:ci`)
|
|
18
|
+
5. 테스트 리포트 아티팩트 업로드
|
|
19
|
+
|
|
20
|
+
**사용 시나리오:**
|
|
21
|
+
- 모든 E2E 테스트를 실행하여 전체 앱 동작 검증
|
|
22
|
+
- 메인 브랜치 병합 전 안정성 확인
|
|
23
|
+
|
|
24
|
+
### 2. `ate-e2e-subset.yml` - Impact Analysis 기반 서브셋 테스트
|
|
25
|
+
|
|
26
|
+
**트리거:**
|
|
27
|
+
- Pull Request (opened, synchronize, reopened)
|
|
28
|
+
|
|
29
|
+
**동작:**
|
|
30
|
+
1. **Impact Analysis 단계:**
|
|
31
|
+
- PR의 base와 head 간 변경 파일 분석
|
|
32
|
+
- `scripts/analyze-impact.ts`로 영향받는 테스트 식별
|
|
33
|
+
- 영향 범위가 없으면 테스트 스킵
|
|
34
|
+
|
|
35
|
+
2. **서브셋 테스트 실행:**
|
|
36
|
+
- 영향받는 테스트만 선택적으로 실행
|
|
37
|
+
- Playwright의 `--grep` 옵션으로 필터링
|
|
38
|
+
- 테스트 결과를 PR 코멘트로 자동 게시
|
|
39
|
+
|
|
40
|
+
**사용 시나리오:**
|
|
41
|
+
- 빠른 피드백 루프 (변경 영향 범위만 테스트)
|
|
42
|
+
- CI 실행 시간 최적화
|
|
43
|
+
- 리소스 절약
|
|
44
|
+
|
|
45
|
+
## 🚀 시작하기
|
|
46
|
+
|
|
47
|
+
### 프로젝트 초기화 시 CI/CD 포함
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
bunx mandu init --name my-app --with-ci
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
이 명령은 자동으로 다음을 생성합니다:
|
|
54
|
+
- `.github/workflows/ate-e2e.yml`
|
|
55
|
+
- `.github/workflows/ate-e2e-subset.yml`
|
|
56
|
+
- `scripts/analyze-impact.ts`
|
|
57
|
+
|
|
58
|
+
### 기존 프로젝트에 추가
|
|
59
|
+
|
|
60
|
+
1. 이 디렉토리의 워크플로우 파일들을 복사:
|
|
61
|
+
```bash
|
|
62
|
+
cp -r .github/workflows your-project/.github/workflows
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
2. Impact Analysis 스크립트 복사:
|
|
66
|
+
```bash
|
|
67
|
+
cp scripts/analyze-impact.ts your-project/scripts/
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
3. `package.json`에 CI 스크립트 추가:
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"scripts": {
|
|
74
|
+
"test:e2e:ci": "bun run test:auto --ci"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 📊 Impact Analysis 커스터마이징
|
|
80
|
+
|
|
81
|
+
`scripts/analyze-impact.ts` 파일의 `IMPACT_MAP`을 수정하여 프로젝트 구조에 맞게 조정할 수 있습니다:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
const IMPACT_MAP: ImpactMap = {
|
|
85
|
+
// API routes → API 테스트
|
|
86
|
+
"app/api/**": ["**/api*.spec.ts", "**/api*.test.ts"],
|
|
87
|
+
|
|
88
|
+
// Client components → UI 테스트
|
|
89
|
+
"src/client/**": ["**/ui*.spec.ts", "**/component*.spec.ts"],
|
|
90
|
+
|
|
91
|
+
// 커스텀 매핑 추가
|
|
92
|
+
"src/features/auth/**": ["**/auth*.spec.ts"],
|
|
93
|
+
};
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 🔍 워크플로우 상태 확인
|
|
97
|
+
|
|
98
|
+
### GitHub Actions UI
|
|
99
|
+
1. Repository → Actions 탭
|
|
100
|
+
2. 워크플로우 실행 목록 확인
|
|
101
|
+
3. 각 실행 클릭하여 상세 로그 확인
|
|
102
|
+
|
|
103
|
+
### PR 코멘트
|
|
104
|
+
`ate-e2e-subset.yml`는 테스트 결과를 PR에 자동으로 코멘트로 추가합니다:
|
|
105
|
+
```
|
|
106
|
+
## 🧪 ATE E2E Test Results (Subset)
|
|
107
|
+
|
|
108
|
+
**Affected tests**: **/ui*.spec.ts|**/component*.spec.ts
|
|
109
|
+
|
|
110
|
+
✅ Passed: 12
|
|
111
|
+
❌ Failed: 0
|
|
112
|
+
⏭️ Skipped: 3
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 아티팩트 다운로드
|
|
116
|
+
테스트 실패 시 다음 아티팩트를 다운로드하여 분석:
|
|
117
|
+
- `playwright-report`: HTML 리포트
|
|
118
|
+
- `test-results`: 스크린샷, 비디오 등
|
|
119
|
+
|
|
120
|
+
## 🛠️ 고급 설정
|
|
121
|
+
|
|
122
|
+
### 환경 변수 설정
|
|
123
|
+
|
|
124
|
+
GitHub Repository Settings → Secrets and variables → Actions에서 환경 변수 추가:
|
|
125
|
+
|
|
126
|
+
```yaml
|
|
127
|
+
env:
|
|
128
|
+
API_BASE_URL: ${{ secrets.API_BASE_URL }}
|
|
129
|
+
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 다중 브라우저 테스트
|
|
133
|
+
|
|
134
|
+
`ate-e2e.yml`을 수정하여 여러 브라우저에서 테스트:
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
strategy:
|
|
138
|
+
matrix:
|
|
139
|
+
browser: [chromium, firefox, webkit]
|
|
140
|
+
steps:
|
|
141
|
+
- name: Install Playwright browsers
|
|
142
|
+
run: bunx playwright install --with-deps ${{ matrix.browser }}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 병렬 실행
|
|
146
|
+
|
|
147
|
+
```yaml
|
|
148
|
+
strategy:
|
|
149
|
+
matrix:
|
|
150
|
+
shard: [1, 2, 3, 4]
|
|
151
|
+
steps:
|
|
152
|
+
- name: Run tests
|
|
153
|
+
run: bun run test:e2e:ci --shard=${{ matrix.shard }}/4
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Slack 알림 추가
|
|
157
|
+
|
|
158
|
+
```yaml
|
|
159
|
+
- name: Notify Slack
|
|
160
|
+
if: failure()
|
|
161
|
+
uses: slackapi/slack-github-action@v1
|
|
162
|
+
with:
|
|
163
|
+
webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
|
164
|
+
payload: |
|
|
165
|
+
{
|
|
166
|
+
"text": "E2E Tests Failed: ${{ github.event.pull_request.html_url }}"
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## 📝 트러블슈팅
|
|
171
|
+
|
|
172
|
+
### 테스트가 스킵되는 경우
|
|
173
|
+
- `scripts/analyze-impact.ts`의 IMPACT_MAP 확인
|
|
174
|
+
- 변경된 파일이 매핑에 포함되어 있는지 확인
|
|
175
|
+
- 로그에서 "Analyzing changes" 출력 확인
|
|
176
|
+
|
|
177
|
+
### Playwright 브라우저 설치 실패
|
|
178
|
+
- `bunx playwright install --with-deps chromium` 직접 실행
|
|
179
|
+
- Ubuntu 버전 확인 (ubuntu-latest 사용 권장)
|
|
180
|
+
|
|
181
|
+
### 아티팩트 업로드 실패
|
|
182
|
+
- `.mandu/reports/` 디렉토리 존재 확인
|
|
183
|
+
- 테스트 실행이 성공적으로 완료되었는지 확인
|
|
184
|
+
|
|
185
|
+
## 📚 참고 자료
|
|
186
|
+
|
|
187
|
+
- [Mandu ATE 문서](../../docs/ATE.md)
|
|
188
|
+
- [GitHub Actions 문서](https://docs.github.com/en/actions)
|
|
189
|
+
- [Playwright CI 가이드](https://playwright.dev/docs/ci)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
name: ATE E2E Tests (Subset - Impact Analysis)
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, synchronize, reopened]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
analyze-changes:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
timeout-minutes: 5
|
|
11
|
+
outputs:
|
|
12
|
+
affected-tests: ${{ steps.impact.outputs.tests }}
|
|
13
|
+
should-run: ${{ steps.impact.outputs.should-run }}
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout code
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 0
|
|
20
|
+
|
|
21
|
+
- name: Setup Bun
|
|
22
|
+
uses: oven-sh/setup-bun@v1
|
|
23
|
+
with:
|
|
24
|
+
bun-version: latest
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: bun install
|
|
28
|
+
|
|
29
|
+
- name: Analyze impact
|
|
30
|
+
id: impact
|
|
31
|
+
run: |
|
|
32
|
+
# Get changed files between base and head
|
|
33
|
+
BASE_SHA="${{ github.event.pull_request.base.sha }}"
|
|
34
|
+
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
|
|
35
|
+
|
|
36
|
+
echo "Analyzing changes between $BASE_SHA and $HEAD_SHA"
|
|
37
|
+
|
|
38
|
+
# Get list of changed files
|
|
39
|
+
CHANGED_FILES=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA")
|
|
40
|
+
|
|
41
|
+
echo "Changed files:"
|
|
42
|
+
echo "$CHANGED_FILES"
|
|
43
|
+
|
|
44
|
+
# Create a temporary file for impact analysis
|
|
45
|
+
echo "$CHANGED_FILES" > changed-files.txt
|
|
46
|
+
|
|
47
|
+
# Run impact analysis (assuming you have a script for this)
|
|
48
|
+
# This is a placeholder - implement actual impact analysis logic
|
|
49
|
+
if [ -f "scripts/analyze-impact.ts" ]; then
|
|
50
|
+
AFFECTED_TESTS=$(bun run scripts/analyze-impact.ts changed-files.txt)
|
|
51
|
+
else
|
|
52
|
+
# Fallback: run all tests if impact analysis script doesn't exist
|
|
53
|
+
AFFECTED_TESTS="all"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
echo "Affected tests: $AFFECTED_TESTS"
|
|
57
|
+
|
|
58
|
+
# Set outputs
|
|
59
|
+
if [ "$AFFECTED_TESTS" = "none" ]; then
|
|
60
|
+
echo "should-run=false" >> $GITHUB_OUTPUT
|
|
61
|
+
echo "tests=" >> $GITHUB_OUTPUT
|
|
62
|
+
else
|
|
63
|
+
echo "should-run=true" >> $GITHUB_OUTPUT
|
|
64
|
+
echo "tests=$AFFECTED_TESTS" >> $GITHUB_OUTPUT
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
e2e-subset:
|
|
68
|
+
needs: analyze-changes
|
|
69
|
+
if: needs.analyze-changes.outputs.should-run == 'true'
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
timeout-minutes: 15
|
|
72
|
+
|
|
73
|
+
steps:
|
|
74
|
+
- name: Checkout code
|
|
75
|
+
uses: actions/checkout@v4
|
|
76
|
+
|
|
77
|
+
- name: Setup Bun
|
|
78
|
+
uses: oven-sh/setup-bun@v1
|
|
79
|
+
with:
|
|
80
|
+
bun-version: latest
|
|
81
|
+
|
|
82
|
+
- name: Install dependencies
|
|
83
|
+
run: bun install
|
|
84
|
+
|
|
85
|
+
- name: Install Playwright browsers
|
|
86
|
+
run: bunx playwright install --with-deps chromium
|
|
87
|
+
|
|
88
|
+
- name: Run affected E2E tests
|
|
89
|
+
run: |
|
|
90
|
+
AFFECTED_TESTS="${{ needs.analyze-changes.outputs.affected-tests }}"
|
|
91
|
+
|
|
92
|
+
if [ "$AFFECTED_TESTS" = "all" ]; then
|
|
93
|
+
echo "Running all E2E tests"
|
|
94
|
+
bun run test:e2e:ci
|
|
95
|
+
else
|
|
96
|
+
echo "Running subset of E2E tests: $AFFECTED_TESTS"
|
|
97
|
+
# Pass affected test patterns to Playwright
|
|
98
|
+
bun run test:e2e:ci --grep "$AFFECTED_TESTS"
|
|
99
|
+
fi
|
|
100
|
+
env:
|
|
101
|
+
CI: true
|
|
102
|
+
|
|
103
|
+
- name: Upload Playwright Report
|
|
104
|
+
if: always()
|
|
105
|
+
uses: actions/upload-artifact@v4
|
|
106
|
+
with:
|
|
107
|
+
name: playwright-report-subset
|
|
108
|
+
path: .mandu/reports/
|
|
109
|
+
retention-days: 7
|
|
110
|
+
|
|
111
|
+
- name: Comment PR with test results
|
|
112
|
+
if: always()
|
|
113
|
+
uses: actions/github-script@v7
|
|
114
|
+
with:
|
|
115
|
+
script: |
|
|
116
|
+
const fs = require('fs');
|
|
117
|
+
const affectedTests = '${{ needs.analyze-changes.outputs.affected-tests }}';
|
|
118
|
+
|
|
119
|
+
let body = '## 🧪 ATE E2E Test Results (Subset)\n\n';
|
|
120
|
+
body += `**Affected tests**: ${affectedTests === 'all' ? 'All tests' : affectedTests}\n\n`;
|
|
121
|
+
|
|
122
|
+
// Try to read test results summary
|
|
123
|
+
try {
|
|
124
|
+
const summary = fs.readFileSync('.mandu/reports/summary.json', 'utf8');
|
|
125
|
+
const results = JSON.parse(summary);
|
|
126
|
+
body += `✅ Passed: ${results.passed}\n`;
|
|
127
|
+
body += `❌ Failed: ${results.failed}\n`;
|
|
128
|
+
body += `⏭️ Skipped: ${results.skipped}\n`;
|
|
129
|
+
} catch (e) {
|
|
130
|
+
body += '_Test summary not available_\n';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
github.rest.issues.createComment({
|
|
134
|
+
issue_number: context.issue.number,
|
|
135
|
+
owner: context.repo.owner,
|
|
136
|
+
repo: context.repo.repo,
|
|
137
|
+
body: body
|
|
138
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: ATE E2E Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
e2e:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
timeout-minutes: 15
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- name: Checkout code
|
|
15
|
+
uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Setup Bun
|
|
18
|
+
uses: oven-sh/setup-bun@v1
|
|
19
|
+
with:
|
|
20
|
+
bun-version: latest
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: bun install
|
|
24
|
+
|
|
25
|
+
- name: Install Playwright browsers
|
|
26
|
+
run: bunx playwright install --with-deps chromium
|
|
27
|
+
|
|
28
|
+
- name: Run ATE E2E pipeline
|
|
29
|
+
run: bun run test:e2e:ci
|
|
30
|
+
env:
|
|
31
|
+
CI: true
|
|
32
|
+
|
|
33
|
+
- name: Upload Playwright Report
|
|
34
|
+
if: always()
|
|
35
|
+
uses: actions/upload-artifact@v4
|
|
36
|
+
with:
|
|
37
|
+
name: playwright-report
|
|
38
|
+
path: .mandu/reports/
|
|
39
|
+
retention-days: 7
|
|
40
|
+
|
|
41
|
+
- name: Upload test results
|
|
42
|
+
if: always()
|
|
43
|
+
uses: actions/upload-artifact@v4
|
|
44
|
+
with:
|
|
45
|
+
name: test-results
|
|
46
|
+
path: .mandu/test-results/
|
|
47
|
+
retention-days: 7
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Impact Analysis Script
|
|
5
|
+
* 변경된 파일 목록을 기반으로 영향받는 E2E 테스트 식별
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* bun run scripts/analyze-impact.ts changed-files.txt
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from "fs/promises";
|
|
12
|
+
import path from "path";
|
|
13
|
+
|
|
14
|
+
interface ImpactMap {
|
|
15
|
+
[pattern: string]: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 파일 패턴과 영향받는 테스트 매핑
|
|
20
|
+
* 프로젝트 구조에 맞게 커스터마이즈 가능
|
|
21
|
+
*/
|
|
22
|
+
const IMPACT_MAP: ImpactMap = {
|
|
23
|
+
// API routes → API 테스트
|
|
24
|
+
"app/api/**": ["**/api*.spec.ts", "**/api*.test.ts"],
|
|
25
|
+
|
|
26
|
+
// Client components → UI 테스트
|
|
27
|
+
"src/client/**": ["**/ui*.spec.ts", "**/component*.spec.ts"],
|
|
28
|
+
|
|
29
|
+
// Server logic → Integration 테스트
|
|
30
|
+
"src/server/**": ["**/integration*.spec.ts", "**/server*.spec.ts"],
|
|
31
|
+
|
|
32
|
+
// Shared contracts → 모든 통합 테스트
|
|
33
|
+
"src/shared/contracts/**": ["**/integration*.spec.ts", "**/e2e*.spec.ts"],
|
|
34
|
+
|
|
35
|
+
// Database/Schema → 데이터 관련 테스트
|
|
36
|
+
"src/shared/schema/**": ["**/db*.spec.ts", "**/data*.spec.ts"],
|
|
37
|
+
|
|
38
|
+
// Config files → 모든 테스트
|
|
39
|
+
"*.config.*": ["**/*.spec.ts"],
|
|
40
|
+
"package.json": ["**/*.spec.ts"],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Glob 패턴 매칭 (간단한 구현)
|
|
45
|
+
*/
|
|
46
|
+
function matchPattern(filePath: string, pattern: string): boolean {
|
|
47
|
+
const regexPattern = pattern
|
|
48
|
+
.replace(/\*\*/g, ".*")
|
|
49
|
+
.replace(/\*/g, "[^/]*")
|
|
50
|
+
.replace(/\./g, "\\.");
|
|
51
|
+
|
|
52
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
53
|
+
return regex.test(filePath);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 변경된 파일 목록을 기반으로 영향받는 테스트 패턴 추출
|
|
58
|
+
*/
|
|
59
|
+
function analyzeImpact(changedFiles: string[]): string[] {
|
|
60
|
+
const affectedTestPatterns = new Set<string>();
|
|
61
|
+
|
|
62
|
+
for (const file of changedFiles) {
|
|
63
|
+
const normalizedPath = file.replace(/\\/g, "/");
|
|
64
|
+
|
|
65
|
+
for (const [pattern, tests] of Object.entries(IMPACT_MAP)) {
|
|
66
|
+
if (matchPattern(normalizedPath, pattern)) {
|
|
67
|
+
tests.forEach((test) => affectedTestPatterns.add(test));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return Array.from(affectedTestPatterns);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Main
|
|
77
|
+
*/
|
|
78
|
+
async function main() {
|
|
79
|
+
const args = process.argv.slice(2);
|
|
80
|
+
|
|
81
|
+
if (args.length === 0) {
|
|
82
|
+
console.error("Usage: bun run scripts/analyze-impact.ts <changed-files.txt>");
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const changedFilesPath = args[0];
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
// Read changed files
|
|
90
|
+
const content = await fs.readFile(changedFilesPath, "utf-8");
|
|
91
|
+
const changedFiles = content
|
|
92
|
+
.split("\n")
|
|
93
|
+
.map((line) => line.trim())
|
|
94
|
+
.filter((line) => line.length > 0);
|
|
95
|
+
|
|
96
|
+
if (changedFiles.length === 0) {
|
|
97
|
+
console.log("none");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Analyze impact
|
|
102
|
+
const affectedTests = analyzeImpact(changedFiles);
|
|
103
|
+
|
|
104
|
+
if (affectedTests.length === 0) {
|
|
105
|
+
console.log("none");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Output affected test patterns (Playwright grep format)
|
|
110
|
+
const grepPattern = affectedTests.join("|");
|
|
111
|
+
console.log(grepPattern);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error("Error analyzing impact:", error);
|
|
114
|
+
// Fallback: run all tests
|
|
115
|
+
console.log("all");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
main();
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# GitHub Actions CI/CD for Mandu ATE
|
|
2
|
+
|
|
3
|
+
이 디렉토리는 Mandu ATE (Automation Test Engine) E2E 테스트를 위한 GitHub Actions 워크플로우를 포함합니다.
|
|
4
|
+
|
|
5
|
+
## 📋 워크플로우 개요
|
|
6
|
+
|
|
7
|
+
### 1. `ate-e2e.yml` - 전체 E2E 테스트 실행
|
|
8
|
+
|
|
9
|
+
**트리거:**
|
|
10
|
+
- Pull Request (모든 브랜치)
|
|
11
|
+
- `main` 브랜치로 Push
|
|
12
|
+
|
|
13
|
+
**동작:**
|
|
14
|
+
1. Bun 환경 설정
|
|
15
|
+
2. 의존성 설치
|
|
16
|
+
3. Playwright 브라우저 설치 (Chromium)
|
|
17
|
+
4. ATE E2E 파이프라인 실행 (`bun run test:e2e:ci`)
|
|
18
|
+
5. 테스트 리포트 아티팩트 업로드
|
|
19
|
+
|
|
20
|
+
**사용 시나리오:**
|
|
21
|
+
- 모든 E2E 테스트를 실행하여 전체 앱 동작 검증
|
|
22
|
+
- 메인 브랜치 병합 전 안정성 확인
|
|
23
|
+
|
|
24
|
+
### 2. `ate-e2e-subset.yml` - Impact Analysis 기반 서브셋 테스트
|
|
25
|
+
|
|
26
|
+
**트리거:**
|
|
27
|
+
- Pull Request (opened, synchronize, reopened)
|
|
28
|
+
|
|
29
|
+
**동작:**
|
|
30
|
+
1. **Impact Analysis 단계:**
|
|
31
|
+
- PR의 base와 head 간 변경 파일 분석
|
|
32
|
+
- `scripts/analyze-impact.ts`로 영향받는 테스트 식별
|
|
33
|
+
- 영향 범위가 없으면 테스트 스킵
|
|
34
|
+
|
|
35
|
+
2. **서브셋 테스트 실행:**
|
|
36
|
+
- 영향받는 테스트만 선택적으로 실행
|
|
37
|
+
- Playwright의 `--grep` 옵션으로 필터링
|
|
38
|
+
- 테스트 결과를 PR 코멘트로 자동 게시
|
|
39
|
+
|
|
40
|
+
**사용 시나리오:**
|
|
41
|
+
- 빠른 피드백 루프 (변경 영향 범위만 테스트)
|
|
42
|
+
- CI 실행 시간 최적화
|
|
43
|
+
- 리소스 절약
|
|
44
|
+
|
|
45
|
+
## 🚀 시작하기
|
|
46
|
+
|
|
47
|
+
### 프로젝트 초기화 시 CI/CD 포함
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
bunx mandu init --name my-app --with-ci
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
이 명령은 자동으로 다음을 생성합니다:
|
|
54
|
+
- `.github/workflows/ate-e2e.yml`
|
|
55
|
+
- `.github/workflows/ate-e2e-subset.yml`
|
|
56
|
+
- `scripts/analyze-impact.ts`
|
|
57
|
+
|
|
58
|
+
### 기존 프로젝트에 추가
|
|
59
|
+
|
|
60
|
+
1. 이 디렉토리의 워크플로우 파일들을 복사:
|
|
61
|
+
```bash
|
|
62
|
+
cp -r .github/workflows your-project/.github/workflows
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
2. Impact Analysis 스크립트 복사:
|
|
66
|
+
```bash
|
|
67
|
+
cp scripts/analyze-impact.ts your-project/scripts/
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
3. `package.json`에 CI 스크립트 추가:
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"scripts": {
|
|
74
|
+
"test:e2e:ci": "bun run test:auto --ci"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 📊 Impact Analysis 커스터마이징
|
|
80
|
+
|
|
81
|
+
`scripts/analyze-impact.ts` 파일의 `IMPACT_MAP`을 수정하여 프로젝트 구조에 맞게 조정할 수 있습니다:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
const IMPACT_MAP: ImpactMap = {
|
|
85
|
+
// API routes → API 테스트
|
|
86
|
+
"app/api/**": ["**/api*.spec.ts", "**/api*.test.ts"],
|
|
87
|
+
|
|
88
|
+
// Client components → UI 테스트
|
|
89
|
+
"src/client/**": ["**/ui*.spec.ts", "**/component*.spec.ts"],
|
|
90
|
+
|
|
91
|
+
// 커스텀 매핑 추가
|
|
92
|
+
"src/features/auth/**": ["**/auth*.spec.ts"],
|
|
93
|
+
};
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 🔍 워크플로우 상태 확인
|
|
97
|
+
|
|
98
|
+
### GitHub Actions UI
|
|
99
|
+
1. Repository → Actions 탭
|
|
100
|
+
2. 워크플로우 실행 목록 확인
|
|
101
|
+
3. 각 실행 클릭하여 상세 로그 확인
|
|
102
|
+
|
|
103
|
+
### PR 코멘트
|
|
104
|
+
`ate-e2e-subset.yml`는 테스트 결과를 PR에 자동으로 코멘트로 추가합니다:
|
|
105
|
+
```
|
|
106
|
+
## 🧪 ATE E2E Test Results (Subset)
|
|
107
|
+
|
|
108
|
+
**Affected tests**: **/ui*.spec.ts|**/component*.spec.ts
|
|
109
|
+
|
|
110
|
+
✅ Passed: 12
|
|
111
|
+
❌ Failed: 0
|
|
112
|
+
⏭️ Skipped: 3
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 아티팩트 다운로드
|
|
116
|
+
테스트 실패 시 다음 아티팩트를 다운로드하여 분석:
|
|
117
|
+
- `playwright-report`: HTML 리포트
|
|
118
|
+
- `test-results`: 스크린샷, 비디오 등
|
|
119
|
+
|
|
120
|
+
## 🛠️ 고급 설정
|
|
121
|
+
|
|
122
|
+
### 환경 변수 설정
|
|
123
|
+
|
|
124
|
+
GitHub Repository Settings → Secrets and variables → Actions에서 환경 변수 추가:
|
|
125
|
+
|
|
126
|
+
```yaml
|
|
127
|
+
env:
|
|
128
|
+
API_BASE_URL: ${{ secrets.API_BASE_URL }}
|
|
129
|
+
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 다중 브라우저 테스트
|
|
133
|
+
|
|
134
|
+
`ate-e2e.yml`을 수정하여 여러 브라우저에서 테스트:
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
strategy:
|
|
138
|
+
matrix:
|
|
139
|
+
browser: [chromium, firefox, webkit]
|
|
140
|
+
steps:
|
|
141
|
+
- name: Install Playwright browsers
|
|
142
|
+
run: bunx playwright install --with-deps ${{ matrix.browser }}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 병렬 실행
|
|
146
|
+
|
|
147
|
+
```yaml
|
|
148
|
+
strategy:
|
|
149
|
+
matrix:
|
|
150
|
+
shard: [1, 2, 3, 4]
|
|
151
|
+
steps:
|
|
152
|
+
- name: Run tests
|
|
153
|
+
run: bun run test:e2e:ci --shard=${{ matrix.shard }}/4
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Slack 알림 추가
|
|
157
|
+
|
|
158
|
+
```yaml
|
|
159
|
+
- name: Notify Slack
|
|
160
|
+
if: failure()
|
|
161
|
+
uses: slackapi/slack-github-action@v1
|
|
162
|
+
with:
|
|
163
|
+
webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
|
164
|
+
payload: |
|
|
165
|
+
{
|
|
166
|
+
"text": "E2E Tests Failed: ${{ github.event.pull_request.html_url }}"
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## 📝 트러블슈팅
|
|
171
|
+
|
|
172
|
+
### 테스트가 스킵되는 경우
|
|
173
|
+
- `scripts/analyze-impact.ts`의 IMPACT_MAP 확인
|
|
174
|
+
- 변경된 파일이 매핑에 포함되어 있는지 확인
|
|
175
|
+
- 로그에서 "Analyzing changes" 출력 확인
|
|
176
|
+
|
|
177
|
+
### Playwright 브라우저 설치 실패
|
|
178
|
+
- `bunx playwright install --with-deps chromium` 직접 실행
|
|
179
|
+
- Ubuntu 버전 확인 (ubuntu-latest 사용 권장)
|
|
180
|
+
|
|
181
|
+
### 아티팩트 업로드 실패
|
|
182
|
+
- `.mandu/reports/` 디렉토리 존재 확인
|
|
183
|
+
- 테스트 실행이 성공적으로 완료되었는지 확인
|
|
184
|
+
|
|
185
|
+
## 📚 참고 자료
|
|
186
|
+
|
|
187
|
+
- [Mandu ATE 문서](../../docs/ATE.md)
|
|
188
|
+
- [GitHub Actions 문서](https://docs.github.com/en/actions)
|
|
189
|
+
- [Playwright CI 가이드](https://playwright.dev/docs/ci)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
name: ATE E2E Tests (Subset - Impact Analysis)
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, synchronize, reopened]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
analyze-changes:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
timeout-minutes: 5
|
|
11
|
+
outputs:
|
|
12
|
+
affected-tests: ${{ steps.impact.outputs.tests }}
|
|
13
|
+
should-run: ${{ steps.impact.outputs.should-run }}
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout code
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 0
|
|
20
|
+
|
|
21
|
+
- name: Setup Bun
|
|
22
|
+
uses: oven-sh/setup-bun@v1
|
|
23
|
+
with:
|
|
24
|
+
bun-version: latest
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: bun install
|
|
28
|
+
|
|
29
|
+
- name: Analyze impact
|
|
30
|
+
id: impact
|
|
31
|
+
run: |
|
|
32
|
+
# Get changed files between base and head
|
|
33
|
+
BASE_SHA="${{ github.event.pull_request.base.sha }}"
|
|
34
|
+
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
|
|
35
|
+
|
|
36
|
+
echo "Analyzing changes between $BASE_SHA and $HEAD_SHA"
|
|
37
|
+
|
|
38
|
+
# Get list of changed files
|
|
39
|
+
CHANGED_FILES=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA")
|
|
40
|
+
|
|
41
|
+
echo "Changed files:"
|
|
42
|
+
echo "$CHANGED_FILES"
|
|
43
|
+
|
|
44
|
+
# Create a temporary file for impact analysis
|
|
45
|
+
echo "$CHANGED_FILES" > changed-files.txt
|
|
46
|
+
|
|
47
|
+
# Run impact analysis (assuming you have a script for this)
|
|
48
|
+
# This is a placeholder - implement actual impact analysis logic
|
|
49
|
+
if [ -f "scripts/analyze-impact.ts" ]; then
|
|
50
|
+
AFFECTED_TESTS=$(bun run scripts/analyze-impact.ts changed-files.txt)
|
|
51
|
+
else
|
|
52
|
+
# Fallback: run all tests if impact analysis script doesn't exist
|
|
53
|
+
AFFECTED_TESTS="all"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
echo "Affected tests: $AFFECTED_TESTS"
|
|
57
|
+
|
|
58
|
+
# Set outputs
|
|
59
|
+
if [ "$AFFECTED_TESTS" = "none" ]; then
|
|
60
|
+
echo "should-run=false" >> $GITHUB_OUTPUT
|
|
61
|
+
echo "tests=" >> $GITHUB_OUTPUT
|
|
62
|
+
else
|
|
63
|
+
echo "should-run=true" >> $GITHUB_OUTPUT
|
|
64
|
+
echo "tests=$AFFECTED_TESTS" >> $GITHUB_OUTPUT
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
e2e-subset:
|
|
68
|
+
needs: analyze-changes
|
|
69
|
+
if: needs.analyze-changes.outputs.should-run == 'true'
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
timeout-minutes: 15
|
|
72
|
+
|
|
73
|
+
steps:
|
|
74
|
+
- name: Checkout code
|
|
75
|
+
uses: actions/checkout@v4
|
|
76
|
+
|
|
77
|
+
- name: Setup Bun
|
|
78
|
+
uses: oven-sh/setup-bun@v1
|
|
79
|
+
with:
|
|
80
|
+
bun-version: latest
|
|
81
|
+
|
|
82
|
+
- name: Install dependencies
|
|
83
|
+
run: bun install
|
|
84
|
+
|
|
85
|
+
- name: Install Playwright browsers
|
|
86
|
+
run: bunx playwright install --with-deps chromium
|
|
87
|
+
|
|
88
|
+
- name: Run affected E2E tests
|
|
89
|
+
run: |
|
|
90
|
+
AFFECTED_TESTS="${{ needs.analyze-changes.outputs.affected-tests }}"
|
|
91
|
+
|
|
92
|
+
if [ "$AFFECTED_TESTS" = "all" ]; then
|
|
93
|
+
echo "Running all E2E tests"
|
|
94
|
+
bun run test:e2e:ci
|
|
95
|
+
else
|
|
96
|
+
echo "Running subset of E2E tests: $AFFECTED_TESTS"
|
|
97
|
+
# Pass affected test patterns to Playwright
|
|
98
|
+
bun run test:e2e:ci --grep "$AFFECTED_TESTS"
|
|
99
|
+
fi
|
|
100
|
+
env:
|
|
101
|
+
CI: true
|
|
102
|
+
|
|
103
|
+
- name: Upload Playwright Report
|
|
104
|
+
if: always()
|
|
105
|
+
uses: actions/upload-artifact@v4
|
|
106
|
+
with:
|
|
107
|
+
name: playwright-report-subset
|
|
108
|
+
path: .mandu/reports/
|
|
109
|
+
retention-days: 7
|
|
110
|
+
|
|
111
|
+
- name: Comment PR with test results
|
|
112
|
+
if: always()
|
|
113
|
+
uses: actions/github-script@v7
|
|
114
|
+
with:
|
|
115
|
+
script: |
|
|
116
|
+
const fs = require('fs');
|
|
117
|
+
const affectedTests = '${{ needs.analyze-changes.outputs.affected-tests }}';
|
|
118
|
+
|
|
119
|
+
let body = '## 🧪 ATE E2E Test Results (Subset)\n\n';
|
|
120
|
+
body += `**Affected tests**: ${affectedTests === 'all' ? 'All tests' : affectedTests}\n\n`;
|
|
121
|
+
|
|
122
|
+
// Try to read test results summary
|
|
123
|
+
try {
|
|
124
|
+
const summary = fs.readFileSync('.mandu/reports/summary.json', 'utf8');
|
|
125
|
+
const results = JSON.parse(summary);
|
|
126
|
+
body += `✅ Passed: ${results.passed}\n`;
|
|
127
|
+
body += `❌ Failed: ${results.failed}\n`;
|
|
128
|
+
body += `⏭️ Skipped: ${results.skipped}\n`;
|
|
129
|
+
} catch (e) {
|
|
130
|
+
body += '_Test summary not available_\n';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
github.rest.issues.createComment({
|
|
134
|
+
issue_number: context.issue.number,
|
|
135
|
+
owner: context.repo.owner,
|
|
136
|
+
repo: context.repo.repo,
|
|
137
|
+
body: body
|
|
138
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: ATE E2E Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
e2e:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
timeout-minutes: 15
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- name: Checkout code
|
|
15
|
+
uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Setup Bun
|
|
18
|
+
uses: oven-sh/setup-bun@v1
|
|
19
|
+
with:
|
|
20
|
+
bun-version: latest
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: bun install
|
|
24
|
+
|
|
25
|
+
- name: Install Playwright browsers
|
|
26
|
+
run: bunx playwright install --with-deps chromium
|
|
27
|
+
|
|
28
|
+
- name: Run ATE E2E pipeline
|
|
29
|
+
run: bun run test:e2e:ci
|
|
30
|
+
env:
|
|
31
|
+
CI: true
|
|
32
|
+
|
|
33
|
+
- name: Upload Playwright Report
|
|
34
|
+
if: always()
|
|
35
|
+
uses: actions/upload-artifact@v4
|
|
36
|
+
with:
|
|
37
|
+
name: playwright-report
|
|
38
|
+
path: .mandu/reports/
|
|
39
|
+
retention-days: 7
|
|
40
|
+
|
|
41
|
+
- name: Upload test results
|
|
42
|
+
if: always()
|
|
43
|
+
uses: actions/upload-artifact@v4
|
|
44
|
+
with:
|
|
45
|
+
name: test-results
|
|
46
|
+
path: .mandu/test-results/
|
|
47
|
+
retention-days: 7
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Impact Analysis Script
|
|
5
|
+
* 변경된 파일 목록을 기반으로 영향받는 E2E 테스트 식별
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* bun run scripts/analyze-impact.ts changed-files.txt
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from "fs/promises";
|
|
12
|
+
import path from "path";
|
|
13
|
+
|
|
14
|
+
interface ImpactMap {
|
|
15
|
+
[pattern: string]: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 파일 패턴과 영향받는 테스트 매핑
|
|
20
|
+
* 프로젝트 구조에 맞게 커스터마이즈 가능
|
|
21
|
+
*/
|
|
22
|
+
const IMPACT_MAP: ImpactMap = {
|
|
23
|
+
// API routes → API 테스트
|
|
24
|
+
"app/api/**": ["**/api*.spec.ts", "**/api*.test.ts"],
|
|
25
|
+
|
|
26
|
+
// Client components → UI 테스트
|
|
27
|
+
"src/client/**": ["**/ui*.spec.ts", "**/component*.spec.ts"],
|
|
28
|
+
|
|
29
|
+
// Server logic → Integration 테스트
|
|
30
|
+
"src/server/**": ["**/integration*.spec.ts", "**/server*.spec.ts"],
|
|
31
|
+
|
|
32
|
+
// Shared contracts → 모든 통합 테스트
|
|
33
|
+
"src/shared/contracts/**": ["**/integration*.spec.ts", "**/e2e*.spec.ts"],
|
|
34
|
+
|
|
35
|
+
// Database/Schema → 데이터 관련 테스트
|
|
36
|
+
"src/shared/schema/**": ["**/db*.spec.ts", "**/data*.spec.ts"],
|
|
37
|
+
|
|
38
|
+
// Config files → 모든 테스트
|
|
39
|
+
"*.config.*": ["**/*.spec.ts"],
|
|
40
|
+
"package.json": ["**/*.spec.ts"],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Glob 패턴 매칭 (간단한 구현)
|
|
45
|
+
*/
|
|
46
|
+
function matchPattern(filePath: string, pattern: string): boolean {
|
|
47
|
+
const regexPattern = pattern
|
|
48
|
+
.replace(/\*\*/g, ".*")
|
|
49
|
+
.replace(/\*/g, "[^/]*")
|
|
50
|
+
.replace(/\./g, "\\.");
|
|
51
|
+
|
|
52
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
53
|
+
return regex.test(filePath);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 변경된 파일 목록을 기반으로 영향받는 테스트 패턴 추출
|
|
58
|
+
*/
|
|
59
|
+
function analyzeImpact(changedFiles: string[]): string[] {
|
|
60
|
+
const affectedTestPatterns = new Set<string>();
|
|
61
|
+
|
|
62
|
+
for (const file of changedFiles) {
|
|
63
|
+
const normalizedPath = file.replace(/\\/g, "/");
|
|
64
|
+
|
|
65
|
+
for (const [pattern, tests] of Object.entries(IMPACT_MAP)) {
|
|
66
|
+
if (matchPattern(normalizedPath, pattern)) {
|
|
67
|
+
tests.forEach((test) => affectedTestPatterns.add(test));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return Array.from(affectedTestPatterns);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Main
|
|
77
|
+
*/
|
|
78
|
+
async function main() {
|
|
79
|
+
const args = process.argv.slice(2);
|
|
80
|
+
|
|
81
|
+
if (args.length === 0) {
|
|
82
|
+
console.error("Usage: bun run scripts/analyze-impact.ts <changed-files.txt>");
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const changedFilesPath = args[0];
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
// Read changed files
|
|
90
|
+
const content = await fs.readFile(changedFilesPath, "utf-8");
|
|
91
|
+
const changedFiles = content
|
|
92
|
+
.split("\n")
|
|
93
|
+
.map((line) => line.trim())
|
|
94
|
+
.filter((line) => line.length > 0);
|
|
95
|
+
|
|
96
|
+
if (changedFiles.length === 0) {
|
|
97
|
+
console.log("none");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Analyze impact
|
|
102
|
+
const affectedTests = analyzeImpact(changedFiles);
|
|
103
|
+
|
|
104
|
+
if (affectedTests.length === 0) {
|
|
105
|
+
console.log("none");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Output affected test patterns (Playwright grep format)
|
|
110
|
+
const grepPattern = affectedTests.join("|");
|
|
111
|
+
console.log(grepPattern);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error("Error analyzing impact:", error);
|
|
114
|
+
// Fallback: run all tests
|
|
115
|
+
console.log("all");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
main();
|