@uzysjung/agent-harness 26.158.0 → 26.159.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.
@@ -1,310 +0,0 @@
1
- #!/bin/bash
2
- # ============================================================
3
- # prune-ecc.sh
4
- # ECC plugin을 project local로 복사하고 KEEP 외 항목을 prune.
5
- # 글로벌 ~/.claude/는 read-only로만 사용 (D16 안전).
6
- #
7
- # 사용:
8
- # 1. (선행 1회) claude plugin install everything-claude-code@everything-claude-code
9
- # 2. bash prune-ecc.sh # dry-run (기본). 변경 없음.
10
- # 3. bash prune-ecc.sh --apply # 실제 복사 + prune (확인 prompt)
11
- # 4. bash prune-ecc.sh --apply --force # 확인 없이 즉시 진행
12
- # 5. claude --plugin-dir .claude/local-plugins/ecc # 사용
13
- #
14
- # 옵션:
15
- # --apply 실제 복사 + prune. 기본은 dry-run.
16
- # --force 확인 prompt 생략.
17
- # --dest <path> 복사 대상 경로 (기본: .claude/local-plugins/ecc)
18
- # --keep-existing dest가 이미 있으면 덮어쓰기 안 함 (기본은 재복사)
19
- # --copy-only prune 단계 skip. 전체 ECC를 project local로 복사만.
20
- #
21
- # 안전성:
22
- # - 글로벌 ~/.claude/plugins/cache/ : read-only (cp 소스로만 사용)
23
- # - dest 위치 (.claude/local-plugins/ecc) : .gitignore 자동 추가
24
- # - 다른 프로젝트 영향 없음 (이 프로젝트 local만)
25
- # ============================================================
26
- set -u
27
-
28
- RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
29
-
30
- # --- Args ---
31
- APPLY=false
32
- FORCE=false
33
- KEEP_EXISTING=false
34
- COPY_ONLY=false
35
- DEST=".claude/local-plugins/ecc"
36
- while [ $# -gt 0 ]; do
37
- case "$1" in
38
- --apply) APPLY=true; shift ;;
39
- --force) FORCE=true; shift ;;
40
- --keep-existing) KEEP_EXISTING=true; shift ;;
41
- --copy-only) COPY_ONLY=true; shift ;;
42
- --dest)
43
- [ -z "${2:-}" ] || [[ "$2" == --* ]] && { echo "ERROR: --dest requires a value" >&2; exit 1; }
44
- DEST="$2"; shift 2 ;;
45
- -h|--help) sed -n '2,24p' "$0"; exit 0 ;;
46
- *) echo "Unknown arg: $1" >&2; exit 1 ;;
47
- esac
48
- done
49
-
50
- # v26.11.1 — DEST 경계 검증: 글로벌 ~/.claude/ 또는 시스템 경로 차단 (path traversal 방어)
51
- # v0.6.6 — dirname 디렉토리 미존재 시 pwd 기준으로 fallback (이전엔 상대경로 그대로 잔존 → pwd 검증 실패)
52
- DEST_PARENT="$(cd "$(dirname "$DEST")" 2>/dev/null && pwd)" || DEST_PARENT=""
53
- if [ -n "$DEST_PARENT" ]; then
54
- DEST_ABS="$DEST_PARENT/$(basename "$DEST")"
55
- elif [[ "$DEST" = /* ]]; then
56
- DEST_ABS="$DEST"
57
- else
58
- DEST_ABS="$(pwd)/$DEST"
59
- fi
60
- case "$DEST_ABS" in
61
- "$HOME/.claude"|"$HOME/.claude/"*)
62
- echo "ERROR: --dest는 글로벌 ~/.claude/ 영역 불가 (D16)" >&2
63
- echo " 입력: $DEST (절대: $DEST_ABS)" >&2
64
- exit 1 ;;
65
- "/"|"/etc"|"/etc/"*|"/usr/bin"|"/usr/sbin"|"/usr/local/bin"|"/bin"|"/sbin"|"/System"|"/System/"*)
66
- echo "ERROR: --dest는 시스템 디렉토리 불가" >&2
67
- echo " 입력: $DEST (절대: $DEST_ABS)" >&2
68
- exit 1 ;;
69
- esac
70
- # 추가 안전: 현재 pwd 하위만 허용 (project-local 보장)
71
- case "$DEST_ABS" in
72
- "$(pwd)"|"$(pwd)/"*) ;;
73
- *)
74
- echo "ERROR: --dest는 현재 프로젝트 디렉토리 하위만 허용" >&2
75
- echo " 입력: $DEST (절대: $DEST_ABS)" >&2
76
- echo " 현재 pwd: $(pwd)" >&2
77
- exit 1 ;;
78
- esac
79
-
80
- info() { echo -e " ${GREEN}✓${NC} $1"; }
81
- warn() { echo -e " ${YELLOW}!${NC} $1"; }
82
- fail() { echo -e " ${RED}✗${NC} $1" >&2; }
83
- section() { echo ""; echo -e "${CYAN}${BOLD}$1${NC}"; }
84
-
85
- # --- KEEP list (총 98건, v26.58.0 +6) ---
86
- # v26.58.0 (ADR-019): cherrypicks.lock 의 C2 6개 KEEP 추가 (plugin ON 시 prune 으로 사라지는 구멍 차단):
87
- # security-reviewer, silent-failure-hunter, build-error-resolver,
88
- # e2e-testing, agent-introspection-debugging, nextjs-turbopack.
89
- # 직전 baseline 92건 (이전 코멘트 "89건" 은 오기 — 실제 단어 count 검증 시 92).
90
- KEEP_ITEMS="
91
- agent-harness-construction agent-introspection-debugging agentic-engineering
92
- ai-first-engineering api-design aside autonomous-loops backend-patterns
93
- blueprint build-error-resolver build-fix checkpoint claude-api claw
94
- code-review code-reviewer coding-standards configure-ecc content-engine
95
- content-hash-cache-pattern continuous-agent-loop continuous-learning continuous-learning-v2
96
- cost-aware-llm-pipeline crosspost database-migrations database-reviewer deep-research
97
- deployment-patterns dmux-workflows docker-patterns e2e e2e-runner e2e-testing
98
- eval eval-harness evolve exa-search frontend-patterns frontend-slides
99
- harness-audit harness-optimizer instinct-export instinct-import instinct-status
100
- investor-materials investor-outreach iterative-retrieval learn learn-eval
101
- loop-operator loop-start loop-status market-research model-route multi-backend
102
- multi-execute multi-frontend multi-plan multi-workflow nextjs-turbopack orchestrate
103
- plan plankton-code-quality planner postgres-patterns projects promote
104
- prompt-optimize prompt-optimizer python-patterns python-review python-reviewer
105
- python-testing quality-gate refactor-clean regex-vs-llm-structured-text
106
- resume-session save-session search-first security-review security-reviewer
107
- security-scan sessions setup-pm silent-failure-hunter skill-create skill-stocktake
108
- strategic-compact tdd tdd-guide tdd-workflow test-coverage update-codemaps
109
- update-docs verification-loop verify x-api
110
- "
111
-
112
- is_keep() {
113
- # v26.11.1 — grep -F (fixed string)로 정규식 메타문자 안전. 공백으로 단어 경계 강제.
114
- echo " $KEEP_ITEMS " | tr -s ' \n' ' ' | grep -qF " $1 "
115
- }
116
-
117
- # --- Step 1: 글로벌 ECC cache 발견 ---
118
- section "[1/5] Discover global ECC plugin cache (read-only)"
119
- ECC_CACHE_BASE="$HOME/.claude/plugins/cache/everything-claude-code/everything-claude-code"
120
- if [ ! -d "$ECC_CACHE_BASE" ]; then
121
- fail "ECC plugin 미설치 — 다음 명령으로 먼저 설치:"
122
- echo " claude plugin marketplace add affaan-m/everything-claude-code"
123
- echo " claude plugin install everything-claude-code@everything-claude-code"
124
- exit 1
125
- fi
126
- ECC_VERSION=$(ls -1 "$ECC_CACHE_BASE" 2>/dev/null | sort -V | tail -1)
127
- # ECC v1.10.0 구조: 1.10.0/ 자체가 root (skills/, agents/, commands/ top-level).
128
- # everything-claude-code/ 는 빈 디렉토리, ecc2/는 별개 Rust 프로젝트.
129
- ECC_SOURCE="$ECC_CACHE_BASE/$ECC_VERSION"
130
- if [ ! -d "$ECC_SOURCE/skills" ] || [ ! -d "$ECC_SOURCE/agents" ]; then
131
- fail "ECC plugin source 디렉토리 미발견 (skills/ + agents/): $ECC_SOURCE"
132
- exit 1
133
- fi
134
- info "Source: $ECC_SOURCE (v$ECC_VERSION)"
135
-
136
- # --- Step 2: 복사 (또는 기존 사용) ---
137
- section "[2/5] Copy to project local"
138
- if [ -d "$DEST" ] && [ "$KEEP_EXISTING" = true ]; then
139
- info "기존 $DEST 유지 (--keep-existing)"
140
- elif [ "$APPLY" = false ]; then
141
- warn "[DRY-RUN] would copy: $ECC_SOURCE → $DEST"
142
- else
143
- if [ -d "$DEST" ]; then
144
- warn "기존 $DEST 제거 후 재복사"
145
- rm -rf "$DEST"
146
- fi
147
- mkdir -p "$(dirname "$DEST")"
148
- cp -R "$ECC_SOURCE" "$DEST"
149
- info "Copied to: $DEST"
150
- fi
151
-
152
- # --- Step 3: prune 분석 ---
153
- section "[3/5] Prune analysis"
154
- SCAN_DIR="$DEST"
155
- [ ! -d "$SCAN_DIR" ] && SCAN_DIR="$ECC_SOURCE" # dry-run 시 source로 분석
156
-
157
- declare -a PRUNE_TARGETS=()
158
- KEEP_COUNT=0
159
- PRUNE_COUNT=0
160
-
161
- analyze_dir() {
162
- local kind="$1" dir="$2"
163
- [ ! -d "$dir" ] && return
164
- local kept=0 pruned=0
165
- if [ "$kind" = "directory" ]; then
166
- for d in "$dir"/*/; do
167
- [ -d "$d" ] || continue
168
- local name; name=$(basename "$d")
169
- if is_keep "$name"; then
170
- kept=$((kept+1))
171
- else
172
- PRUNE_TARGETS+=("$d")
173
- pruned=$((pruned+1))
174
- fi
175
- done
176
- else
177
- for f in "$dir"/*.md; do
178
- [ -f "$f" ] || continue
179
- local name; name=$(basename "$f" .md)
180
- if is_keep "$name"; then
181
- kept=$((kept+1))
182
- else
183
- PRUNE_TARGETS+=("$f")
184
- pruned=$((pruned+1))
185
- fi
186
- done
187
- fi
188
- KEEP_COUNT=$((KEEP_COUNT+kept))
189
- PRUNE_COUNT=$((PRUNE_COUNT+pruned))
190
- printf " %-50s keep=%3d prune=%3d\n" "${dir#$SCAN_DIR/}" "$kept" "$pruned"
191
- }
192
-
193
- analyze_dir "directory" "$SCAN_DIR/skills"
194
- analyze_dir "file" "$SCAN_DIR/agents"
195
- analyze_dir "file" "$SCAN_DIR/commands"
196
- [ -d "$SCAN_DIR/.opencode/commands" ] && analyze_dir "file" "$SCAN_DIR/.opencode/commands"
197
- [ -d "$SCAN_DIR/.claude/commands" ] && analyze_dir "file" "$SCAN_DIR/.claude/commands"
198
-
199
- echo ""
200
- echo -e " ${BOLD}Total keep: ${GREEN}$KEEP_COUNT${NC}${BOLD} prune: ${RED}$PRUNE_COUNT${NC}"
201
-
202
- # KEEP 누락 검증
203
- section "KEEP missing check (사용자 리스트 중 plugin에 없는 것)"
204
- NOT_FOUND=()
205
- for k in $KEEP_ITEMS; do
206
- if ! find "$SCAN_DIR/skills" "$SCAN_DIR/agents" "$SCAN_DIR/commands" \
207
- "$SCAN_DIR/.opencode/commands" "$SCAN_DIR/.claude/commands" \
208
- -maxdepth 1 \( -type d -name "$k" -o -type f -name "$k.md" \) 2>/dev/null \
209
- | grep -q .; then
210
- NOT_FOUND+=("$k")
211
- fi
212
- done
213
- if [ "${#NOT_FOUND[@]}" -eq 0 ]; then
214
- # 카운트는 KEEP_ITEMS 에서 동적 산출 (하드코딩 시 89→92→98 처럼 drift 누적).
215
- info "모든 KEEP $(echo "$KEEP_ITEMS" | wc -w | tr -d ' ')건 plugin에 존재"
216
- else
217
- warn "KEEP 누락 ${#NOT_FOUND[@]}건 (이름 변경/제거됐을 수 있음):"
218
- printf ' - %s\n' "${NOT_FOUND[@]}"
219
- fi
220
-
221
- # --- Step 4: apply ---
222
- section "[4/5] Apply prune"
223
- if [ "$APPLY" = false ]; then
224
- warn "DRY-RUN — 변경 없음. 실제 복사+prune은 --apply 추가."
225
- echo " bash prune-ecc.sh --apply"
226
- exit 0
227
- fi
228
-
229
- # v26.15.0 — --copy-only: prune 단계 skip
230
- if [ "$COPY_ONLY" = true ]; then
231
- info "copy-only 모드 — prune 단계 skip (전체 ECC 유지)"
232
- DELETED=0
233
- FAILED=0
234
- else
235
- if [ "$FORCE" = false ]; then
236
- echo ""
237
- echo -e " ${YELLOW}경고:${NC} ${PRUNE_COUNT}건을 ${DEST} 하위에서 영구 삭제합니다."
238
- echo " 글로벌 cache는 영향 없음. 다른 프로젝트도 영향 없음."
239
- read -rp " 진행? [y/N]: " ANSWER < /dev/tty 2>/dev/null || ANSWER=""
240
- [[ ! "$ANSWER" =~ ^[Yy]$ ]] && { warn "취소됨"; exit 0; }
241
- fi
242
- DELETED=0
243
- FAILED=0
244
- for target in "${PRUNE_TARGETS[@]}"; do
245
- if rm -rf "$target" 2>/dev/null; then
246
- DELETED=$((DELETED+1))
247
- else
248
- FAILED=$((FAILED+1))
249
- fail "삭제 실패: $target"
250
- fi
251
- done
252
- fi
253
-
254
- # --- Step 5: .gitignore + 사용 안내 ---
255
- section "[5/5] Post-actions"
256
- if ! grep -q "^.claude/local-plugins/" .gitignore 2>/dev/null; then
257
- echo ".claude/local-plugins/" >> .gitignore
258
- info ".gitignore에 .claude/local-plugins/ 추가"
259
- else
260
- info ".gitignore 이미 등록됨"
261
- fi
262
-
263
- # v26.15.0 — DELETED / KEPT 상세 목록 출력
264
- if [ "$COPY_ONLY" = false ] && [ "$DELETED" -gt 0 ]; then
265
- echo ""
266
- echo -e "${BOLD}========== DELETED ($DELETED) ==========${NC}"
267
- for category in skills agents commands; do
268
- local_deleted=()
269
- for target in "${PRUNE_TARGETS[@]}"; do
270
- case "$target" in
271
- *"/$category/"*)
272
- local_deleted+=("$(basename "$target")") ;;
273
- esac
274
- done
275
- if [ "${#local_deleted[@]}" -gt 0 ]; then
276
- echo -e " ${RED}$category/${NC} (${#local_deleted[@]}):"
277
- printf ' - %s\n' "${local_deleted[@]}" | sort
278
- fi
279
- done
280
- fi
281
-
282
- echo ""
283
- echo -e "${BOLD}========== KEPT ($KEEP_COUNT) ==========${NC}"
284
- for category in skills agents commands; do
285
- dir="$DEST/$category"
286
- [ ! -d "$dir" ] && continue
287
- kept_items=()
288
- for item in "$dir"/*; do
289
- [ -e "$item" ] || continue
290
- kept_items+=("$(basename "$item" | sed 's/\.md$//')")
291
- done
292
- if [ "${#kept_items[@]}" -gt 0 ]; then
293
- echo -e " ${GREEN}$category/${NC} (${#kept_items[@]}):"
294
- printf ' - %s\n' "${kept_items[@]}" | sort
295
- fi
296
- done
297
-
298
- echo ""
299
- echo -e "${BOLD}========== Summary ==========${NC}"
300
- echo -e " ${GREEN}Deleted: $DELETED${NC}"
301
- [ "$FAILED" -gt 0 ] && echo -e " ${RED}Failed: $FAILED${NC}"
302
- echo -e " ${BOLD}Remaining (KEEP): $KEEP_COUNT${NC}"
303
- echo -e " ${BOLD}Location: $DEST${NC}"
304
- echo ""
305
- echo -e "${BOLD}사용법:${NC}"
306
- echo " claude --plugin-dir $DEST"
307
- echo ""
308
- echo -e "${BOLD}재실행 필요 시점:${NC}"
309
- echo " - ECC plugin 업데이트 후 (claude plugin update)"
310
- echo " - 글로벌 cache 갱신 후"
@@ -1,326 +0,0 @@
1
- ---
2
- name: e2e-testing
3
- description: Playwright E2E testing patterns, Page Object Model, configuration, CI/CD integration, artifact management, and flaky test strategies.
4
- origin: ECC
5
- ---
6
-
7
- # E2E Testing Patterns
8
-
9
- Comprehensive Playwright patterns for building stable, fast, and maintainable E2E test suites.
10
-
11
- ## Test File Organization
12
-
13
- ```
14
- tests/
15
- ├── e2e/
16
- │ ├── auth/
17
- │ │ ├── login.spec.ts
18
- │ │ ├── logout.spec.ts
19
- │ │ └── register.spec.ts
20
- │ ├── features/
21
- │ │ ├── browse.spec.ts
22
- │ │ ├── search.spec.ts
23
- │ │ └── create.spec.ts
24
- │ └── api/
25
- │ └── endpoints.spec.ts
26
- ├── fixtures/
27
- │ ├── auth.ts
28
- │ └── data.ts
29
- └── playwright.config.ts
30
- ```
31
-
32
- ## Page Object Model (POM)
33
-
34
- ```typescript
35
- import { Page, Locator } from '@playwright/test'
36
-
37
- export class ItemsPage {
38
- readonly page: Page
39
- readonly searchInput: Locator
40
- readonly itemCards: Locator
41
- readonly createButton: Locator
42
-
43
- constructor(page: Page) {
44
- this.page = page
45
- this.searchInput = page.locator('[data-testid="search-input"]')
46
- this.itemCards = page.locator('[data-testid="item-card"]')
47
- this.createButton = page.locator('[data-testid="create-btn"]')
48
- }
49
-
50
- async goto() {
51
- await this.page.goto('/items')
52
- await this.page.waitForLoadState('networkidle')
53
- }
54
-
55
- async search(query: string) {
56
- await this.searchInput.fill(query)
57
- await this.page.waitForResponse(resp => resp.url().includes('/api/search'))
58
- await this.page.waitForLoadState('networkidle')
59
- }
60
-
61
- async getItemCount() {
62
- return await this.itemCards.count()
63
- }
64
- }
65
- ```
66
-
67
- ## Test Structure
68
-
69
- ```typescript
70
- import { test, expect } from '@playwright/test'
71
- import { ItemsPage } from '../../pages/ItemsPage'
72
-
73
- test.describe('Item Search', () => {
74
- let itemsPage: ItemsPage
75
-
76
- test.beforeEach(async ({ page }) => {
77
- itemsPage = new ItemsPage(page)
78
- await itemsPage.goto()
79
- })
80
-
81
- test('should search by keyword', async ({ page }) => {
82
- await itemsPage.search('test')
83
-
84
- const count = await itemsPage.getItemCount()
85
- expect(count).toBeGreaterThan(0)
86
-
87
- await expect(itemsPage.itemCards.first()).toContainText(/test/i)
88
- await page.screenshot({ path: 'artifacts/search-results.png' })
89
- })
90
-
91
- test('should handle no results', async ({ page }) => {
92
- await itemsPage.search('xyznonexistent123')
93
-
94
- await expect(page.locator('[data-testid="no-results"]')).toBeVisible()
95
- expect(await itemsPage.getItemCount()).toBe(0)
96
- })
97
- })
98
- ```
99
-
100
- ## Playwright Configuration
101
-
102
- ```typescript
103
- import { defineConfig, devices } from '@playwright/test'
104
-
105
- export default defineConfig({
106
- testDir: './tests/e2e',
107
- fullyParallel: true,
108
- forbidOnly: !!process.env.CI,
109
- retries: process.env.CI ? 2 : 0,
110
- workers: process.env.CI ? 1 : undefined,
111
- reporter: [
112
- ['html', { outputFolder: 'playwright-report' }],
113
- ['junit', { outputFile: 'playwright-results.xml' }],
114
- ['json', { outputFile: 'playwright-results.json' }]
115
- ],
116
- use: {
117
- baseURL: process.env.BASE_URL || 'http://localhost:3000',
118
- trace: 'on-first-retry',
119
- screenshot: 'only-on-failure',
120
- video: 'retain-on-failure',
121
- actionTimeout: 10000,
122
- navigationTimeout: 30000,
123
- },
124
- projects: [
125
- { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
126
- { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
127
- { name: 'webkit', use: { ...devices['Desktop Safari'] } },
128
- { name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
129
- ],
130
- webServer: {
131
- command: 'npm run dev',
132
- url: 'http://localhost:3000',
133
- reuseExistingServer: !process.env.CI,
134
- timeout: 120000,
135
- },
136
- })
137
- ```
138
-
139
- ## Flaky Test Patterns
140
-
141
- ### Quarantine
142
-
143
- ```typescript
144
- test('flaky: complex search', async ({ page }) => {
145
- test.fixme(true, 'Flaky - Issue #123')
146
- // test code...
147
- })
148
-
149
- test('conditional skip', async ({ page }) => {
150
- test.skip(process.env.CI, 'Flaky in CI - Issue #123')
151
- // test code...
152
- })
153
- ```
154
-
155
- ### Identify Flakiness
156
-
157
- ```bash
158
- npx playwright test tests/search.spec.ts --repeat-each=10
159
- npx playwright test tests/search.spec.ts --retries=3
160
- ```
161
-
162
- ### Common Causes & Fixes
163
-
164
- **Race conditions:**
165
- ```typescript
166
- // Bad: assumes element is ready
167
- await page.click('[data-testid="button"]')
168
-
169
- // Good: auto-wait locator
170
- await page.locator('[data-testid="button"]').click()
171
- ```
172
-
173
- **Network timing:**
174
- ```typescript
175
- // Bad: arbitrary timeout
176
- await page.waitForTimeout(5000)
177
-
178
- // Good: wait for specific condition
179
- await page.waitForResponse(resp => resp.url().includes('/api/data'))
180
- ```
181
-
182
- **Animation timing:**
183
- ```typescript
184
- // Bad: click during animation
185
- await page.click('[data-testid="menu-item"]')
186
-
187
- // Good: wait for stability
188
- await page.locator('[data-testid="menu-item"]').waitFor({ state: 'visible' })
189
- await page.waitForLoadState('networkidle')
190
- await page.locator('[data-testid="menu-item"]').click()
191
- ```
192
-
193
- ## Artifact Management
194
-
195
- ### Screenshots
196
-
197
- ```typescript
198
- await page.screenshot({ path: 'artifacts/after-login.png' })
199
- await page.screenshot({ path: 'artifacts/full-page.png', fullPage: true })
200
- await page.locator('[data-testid="chart"]').screenshot({ path: 'artifacts/chart.png' })
201
- ```
202
-
203
- ### Traces
204
-
205
- ```typescript
206
- await browser.startTracing(page, {
207
- path: 'artifacts/trace.json',
208
- screenshots: true,
209
- snapshots: true,
210
- })
211
- // ... test actions ...
212
- await browser.stopTracing()
213
- ```
214
-
215
- ### Video
216
-
217
- ```typescript
218
- // In playwright.config.ts
219
- use: {
220
- video: 'retain-on-failure',
221
- videosPath: 'artifacts/videos/'
222
- }
223
- ```
224
-
225
- ## CI/CD Integration
226
-
227
- ```yaml
228
- # .github/workflows/e2e.yml
229
- name: E2E Tests
230
- on: [push, pull_request]
231
-
232
- jobs:
233
- test:
234
- runs-on: ubuntu-latest
235
- steps:
236
- - uses: actions/checkout@v4
237
- - uses: actions/setup-node@v4
238
- with:
239
- node-version: 20
240
- - run: npm ci
241
- - run: npx playwright install --with-deps
242
- - run: npx playwright test
243
- env:
244
- BASE_URL: ${{ vars.STAGING_URL }}
245
- - uses: actions/upload-artifact@v4
246
- if: always()
247
- with:
248
- name: playwright-report
249
- path: playwright-report/
250
- retention-days: 30
251
- ```
252
-
253
- ## Test Report Template
254
-
255
- ```markdown
256
- # E2E Test Report
257
-
258
- **Date:** YYYY-MM-DD HH:MM
259
- **Duration:** Xm Ys
260
- **Status:** PASSING / FAILING
261
-
262
- ## Summary
263
- - Total: X | Passed: Y (Z%) | Failed: A | Flaky: B | Skipped: C
264
-
265
- ## Failed Tests
266
-
267
- ### test-name
268
- **File:** `tests/e2e/feature.spec.ts:45`
269
- **Error:** Expected element to be visible
270
- **Screenshot:** artifacts/failed.png
271
- **Recommended Fix:** [description]
272
-
273
- ## Artifacts
274
- - HTML Report: playwright-report/index.html
275
- - Screenshots: artifacts/*.png
276
- - Videos: artifacts/videos/*.webm
277
- - Traces: artifacts/*.zip
278
- ```
279
-
280
- ## Wallet / Web3 Testing
281
-
282
- ```typescript
283
- test('wallet connection', async ({ page, context }) => {
284
- // Mock wallet provider
285
- await context.addInitScript(() => {
286
- window.ethereum = {
287
- isMetaMask: true,
288
- request: async ({ method }) => {
289
- if (method === 'eth_requestAccounts')
290
- return ['0x1234567890123456789012345678901234567890']
291
- if (method === 'eth_chainId') return '0x1'
292
- }
293
- }
294
- })
295
-
296
- await page.goto('/')
297
- await page.locator('[data-testid="connect-wallet"]').click()
298
- await expect(page.locator('[data-testid="wallet-address"]')).toContainText('0x1234')
299
- })
300
- ```
301
-
302
- ## Financial / Critical Flow Testing
303
-
304
- ```typescript
305
- test('trade execution', async ({ page }) => {
306
- // Skip on production — real money
307
- test.skip(process.env.NODE_ENV === 'production', 'Skip on production')
308
-
309
- await page.goto('/markets/test-market')
310
- await page.locator('[data-testid="position-yes"]').click()
311
- await page.locator('[data-testid="trade-amount"]').fill('1.0')
312
-
313
- // Verify preview
314
- const preview = page.locator('[data-testid="trade-preview"]')
315
- await expect(preview).toContainText('1.0')
316
-
317
- // Confirm and wait for blockchain
318
- await page.locator('[data-testid="confirm-trade"]').click()
319
- await page.waitForResponse(
320
- resp => resp.url().includes('/api/trade') && resp.status() === 200,
321
- { timeout: 30000 }
322
- )
323
-
324
- await expect(page.locator('[data-testid="trade-success"]')).toBeVisible()
325
- })
326
- ```
@@ -1,7 +0,0 @@
1
- interface:
2
- display_name: "E2E Testing"
3
- short_description: "Playwright end-to-end testing"
4
- brand_color: "#06B6D4"
5
- default_prompt: "Generate Playwright E2E tests with Page Object Model"
6
- policy:
7
- allow_implicit_invocation: true