agentic-loop 3.1.6 → 3.2.1

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,238 +0,0 @@
1
- #!/usr/bin/env bash
2
- # shellcheck shell=bash
3
- # playwright.sh - Playwright test integration for ralph
4
-
5
- # Ensure Playwright is installed and configured
6
- ensure_playwright() {
7
- # Check if playwright config exists
8
- if [[ ! -f "playwright.config.ts" ]] && [[ ! -f "playwright.config.js" ]]; then
9
- print_info "Playwright not configured, initializing..."
10
-
11
- # Check if npx is available
12
- if ! command -v npx &>/dev/null; then
13
- print_error "npx not found - cannot install Playwright"
14
- return 1
15
- fi
16
-
17
- # Install Playwright package and browsers
18
- npm install playwright 2>/dev/null && npx playwright install chromium 2>/dev/null || {
19
- print_warning "Could not install Playwright - run: npm install playwright && npx playwright install chromium"
20
- }
21
-
22
- # Create minimal config if it doesn't exist
23
- if [[ ! -f "playwright.config.ts" ]] && [[ ! -f "playwright.config.js" ]]; then
24
- print_info "Creating playwright.config.ts..."
25
- cat > playwright.config.ts << 'EOF'
26
- import { defineConfig, devices } from '@playwright/test';
27
-
28
- export default defineConfig({
29
- testDir: './tests/e2e',
30
- fullyParallel: true,
31
- forbidOnly: !!process.env.CI,
32
- retries: process.env.CI ? 2 : 0,
33
- workers: process.env.CI ? 1 : undefined,
34
- reporter: 'html',
35
- use: {
36
- baseURL: process.env.BASE_URL || 'http://localhost:3000',
37
- trace: 'on-first-retry',
38
- screenshot: 'only-on-failure',
39
- },
40
- projects: [
41
- {
42
- name: 'chromium',
43
- use: { ...devices['Desktop Chrome'] },
44
- },
45
- {
46
- name: 'mobile',
47
- use: { ...devices['iPhone 13'] },
48
- },
49
- ],
50
- });
51
- EOF
52
- fi
53
-
54
- # Create test directory
55
- mkdir -p tests/e2e
56
-
57
- print_success "Playwright initialized"
58
- fi
59
-
60
- return 0
61
- }
62
-
63
- # Find test file for a story - uses explicit config or story-level testFile
64
- find_story_test_file() {
65
- local story="$1"
66
- local test_dir="$2"
67
-
68
- # 1. Check story for explicit testFile path (preferred)
69
- local explicit_file
70
- explicit_file=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .testFile // empty' "$RALPH_DIR/prd.json" 2>/dev/null)
71
- if [[ -n "$explicit_file" && -f "$explicit_file" ]]; then
72
- echo "$explicit_file"
73
- return 0
74
- fi
75
-
76
- # 2. Check config.json for e2e test directory pattern
77
- local config_test_dir
78
- config_test_dir=$(get_config '.playwright.testDir' "")
79
- if [[ -n "$config_test_dir" ]]; then
80
- test_dir="$config_test_dir"
81
- fi
82
-
83
- # 3. Standard naming: {testDir}/{story-id}.spec.ts
84
- if [[ -f "${test_dir}/${story}.spec.ts" ]]; then
85
- echo "${test_dir}/${story}.spec.ts"
86
- return 0
87
- fi
88
- if [[ -f "${test_dir}/${story}.spec.js" ]]; then
89
- echo "${test_dir}/${story}.spec.js"
90
- return 0
91
- fi
92
-
93
- # 4. Slug-based naming from story title
94
- local story_slug
95
- story_slug=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .title // ""' "$RALPH_DIR/prd.json" 2>/dev/null | \
96
- tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//' | sed 's/-$//')
97
-
98
- if [[ -n "$story_slug" && -f "${test_dir}/${story_slug}.spec.ts" ]]; then
99
- echo "${test_dir}/${story_slug}.spec.ts"
100
- return 0
101
- fi
102
-
103
- return 1
104
- }
105
-
106
- # Run Playwright tests for a specific story or all tests
107
- run_playwright_tests() {
108
- local story="$1"
109
-
110
- # Check if Playwright is enabled in config
111
- local pw_enabled
112
- pw_enabled=$(get_config '.playwright.enabled' "true")
113
- if [[ "$pw_enabled" == "false" ]]; then
114
- echo " (Playwright disabled in config, skipping)"
115
- return 0
116
- fi
117
-
118
- # Ensure Playwright is set up
119
- if ! ensure_playwright; then
120
- return 1
121
- fi
122
-
123
- # Check if npx is available
124
- if ! command -v npx &>/dev/null; then
125
- print_error "npx not found - cannot run Playwright"
126
- return 1
127
- fi
128
-
129
- # Get test directory from config or use default
130
- local test_dir
131
- test_dir=$(get_config '.playwright.testDir' "tests/e2e")
132
-
133
- # Find the test file for this story
134
- local test_file
135
- test_file=$(find_story_test_file "$story" "$test_dir")
136
-
137
- local log_file
138
- log_file=$(create_temp_file ".log") || return 1
139
-
140
- echo -n " Running Playwright tests... "
141
-
142
- if [[ -n "$test_file" && -f "$test_file" ]]; then
143
- # Run story-specific test
144
- echo -n "(${test_file##*/}) "
145
- if npx playwright test "$test_file" --reporter=line > "$log_file" 2>&1; then
146
- print_success "passed"
147
- rm -f "$log_file"
148
- return 0
149
- fi
150
- else
151
- # No story-specific test - check if e2e was expected
152
- local e2e_required
153
- e2e_required=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .e2e // false' "$RALPH_DIR/prd.json" 2>/dev/null)
154
-
155
- if [[ "$e2e_required" == "true" ]]; then
156
- # Show where we looked and how to fix
157
- print_warning "missing (e2e: true but no test file found)"
158
- echo ""
159
- echo " Looked for: ${test_dir}/${story}.spec.ts"
160
- echo ""
161
- echo " Fix options:"
162
- echo " 1. Add 'testFile' to story: \"testFile\": \"apps/web/tests/e2e/my-test.spec.ts\""
163
- echo " 2. Set testDir in .ralph/config.json: \"playwright\": {\"testDir\": \"apps/web/tests/e2e\"}"
164
- echo " 3. Name test file: ${test_dir}/${story}.spec.ts"
165
- rm -f "$log_file"
166
- return 1 # Fail if e2e was expected but not created
167
- fi
168
-
169
- # Check if any tests exist to run
170
- if [[ -z "$(find "$test_dir" -name '*.spec.ts' -o -name '*.spec.js' 2>/dev/null | head -1)" ]]; then
171
- echo "skipped (not required for this story)"
172
- rm -f "$log_file"
173
- return 0
174
- fi
175
-
176
- # Run all tests
177
- if npx playwright test --reporter=line > "$log_file" 2>&1; then
178
- print_success "passed"
179
- rm -f "$log_file"
180
- return 0
181
- fi
182
- fi
183
-
184
- # Tests failed
185
- print_error "failed"
186
- echo ""
187
- echo " Playwright output (last $MAX_LOG_LINES lines):"
188
- tail -"$MAX_LOG_LINES" "$log_file" | sed 's/^/ /'
189
-
190
- # Save failure for context
191
- cp "$log_file" "$RALPH_DIR/last_playwright_failure.log"
192
-
193
- rm -f "$log_file"
194
- return 1
195
- }
196
-
197
- # Run Playwright with accessibility checks
198
- run_playwright_a11y() {
199
- local story="$1"
200
- local url="$2"
201
-
202
- # Check if @axe-core/playwright is available
203
- if ! npm list @axe-core/playwright &>/dev/null 2>&1; then
204
- print_info "Installing @axe-core/playwright for accessibility testing..."
205
- npm install -D @axe-core/playwright 2>/dev/null || {
206
- print_warning "Could not install axe-core, skipping a11y tests"
207
- return 0
208
- }
209
- fi
210
-
211
- # a11y tests are typically part of the Playwright tests
212
- # This function can be extended to run standalone a11y audits
213
- return 0
214
- }
215
-
216
- # Run Playwright at specific viewport (for mobile testing)
217
- run_playwright_mobile() {
218
- local story="$1"
219
-
220
- local log_file
221
- log_file=$(create_temp_file ".log") || return 1
222
-
223
- echo -n " Running mobile viewport tests... "
224
-
225
- # Run tests with mobile project
226
- if npx playwright test --project=mobile --reporter=line > "$log_file" 2>&1; then
227
- print_success "passed"
228
- rm -f "$log_file"
229
- return 0
230
- else
231
- print_error "failed"
232
- echo ""
233
- echo " Mobile test output:"
234
- tail -"$MAX_OUTPUT_PREVIEW_LINES" "$log_file" | sed 's/^/ /'
235
- rm -f "$log_file"
236
- return 1
237
- fi
238
- }
@@ -1,324 +0,0 @@
1
- #!/usr/bin/env bash
2
- # shellcheck shell=bash
3
- # browser.sh - Browser validation module for ralph
4
-
5
- # Browser validation for frontend stories using Playwright
6
- run_browser_validation() {
7
- local story="$1"
8
-
9
- # Check if browser validation is enabled in config
10
- local browser_enabled
11
- browser_enabled=$(get_config '.verification.browserEnabled' "true")
12
- if [[ "$browser_enabled" == "false" ]]; then
13
- echo " (browser validation disabled in config, skipping)"
14
- return 0
15
- fi
16
-
17
- # Get base URL from config (required for relative URLs)
18
- local base_url
19
- base_url=$(get_config '.testUrlBase' "")
20
-
21
- # Check if Docker mode - Playwright needs special handling
22
- local docker_enabled
23
- docker_enabled=$(get_config '.docker.enabled' "false")
24
- if [[ "$docker_enabled" == "true" ]]; then
25
- echo " (Docker mode: using curl check - set verification.browserEnabled=false to hide this)"
26
- # In Docker, fall back to curl unless they've set up remote browser
27
- local url
28
- url=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .testUrl // empty' "$RALPH_DIR/prd.json" 2>/dev/null)
29
- if [[ -n "$url" ]]; then
30
- # Check for unsubstituted template variables
31
- if [[ "$url" =~ \{[a-zA-Z_][a-zA-Z0-9_]*\} ]]; then
32
- print_warning " testUrl has unsubstituted variable: $url"
33
- return 0
34
- fi
35
- # Handle relative URLs
36
- if [[ "$url" =~ ^/ ]]; then
37
- if [[ -z "$base_url" ]]; then
38
- print_error "testUrlBase not set in config.json (needed for relative URL: $url)"
39
- return 1
40
- fi
41
- url="${base_url}${url}"
42
- fi
43
- return run_curl_check "$url"
44
- fi
45
- return 0
46
- fi
47
-
48
- local url
49
- url=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .testUrl // empty' "$RALPH_DIR/prd.json" 2>/dev/null)
50
-
51
- if [[ -z "$url" ]]; then
52
- echo " skipped (no testUrl - infrastructure or backend story)"
53
- return 0
54
- fi
55
-
56
- # Check for unsubstituted template variables like {collectionId}
57
- if [[ "$url" =~ \{[a-zA-Z_][a-zA-Z0-9_]*\} ]]; then
58
- print_warning " testUrl has unsubstituted variable: $url"
59
- echo " Add concrete ID to testUrl in PRD, or set in .ralph/config.json testUrlVars"
60
- echo " skipped"
61
- return 0
62
- fi
63
-
64
- # Handle relative URLs by prepending base URL from config
65
- if [[ "$url" =~ ^/ ]]; then
66
- if [[ -z "$base_url" ]]; then
67
- print_error "testUrlBase not set in config.json (needed for relative URL: $url)"
68
- return 1
69
- fi
70
- url="${base_url}${url}"
71
- fi
72
-
73
- if ! validate_url "$url"; then
74
- print_error "Invalid URL: $url"
75
- return 1
76
- fi
77
-
78
- echo " URL: $url"
79
-
80
- # Check if npx is available
81
- if ! command -v npx &>/dev/null; then
82
- print_warning " npx not found, skipping browser validation"
83
- return 0
84
- fi
85
-
86
- # Check if Playwright package is installed
87
- local playwright_installed=false
88
- if npm list playwright &>/dev/null || npm list -g playwright &>/dev/null; then
89
- playwright_installed=true
90
- fi
91
-
92
- # Check if browser binaries are installed (look for chromium in cache)
93
- # macOS uses ~/Library/Caches, Linux uses ~/.cache
94
- local browser_installed=false
95
- local playwright_cache=""
96
- if [[ -d "$HOME/Library/Caches/ms-playwright" ]]; then
97
- playwright_cache="$HOME/Library/Caches/ms-playwright"
98
- elif [[ -d "$HOME/.cache/ms-playwright" ]]; then
99
- playwright_cache="$HOME/.cache/ms-playwright"
100
- fi
101
- if [[ -n "$playwright_cache" ]] && ls "$playwright_cache"/chromium-* &>/dev/null; then
102
- browser_installed=true
103
- fi
104
-
105
- # If either is missing, auto-install (Ralph runs autonomously)
106
- if [[ "$playwright_installed" == "false" ]] || [[ "$browser_installed" == "false" ]]; then
107
- echo ""
108
- print_info " Installing Playwright for browser verification (~150MB)..."
109
- if npm install playwright &>/dev/null && npx playwright install chromium &>/dev/null; then
110
- print_success " Playwright installed!"
111
- else
112
- print_warning " Installation failed, falling back to curl check"
113
- return run_curl_check "$url"
114
- fi
115
- fi
116
-
117
- # Get selectors to check from story (if defined)
118
- local selectors
119
- selectors=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .selectors // [] | @json' "$RALPH_DIR/prd.json" 2>/dev/null)
120
- if [[ "$selectors" == "null" || -z "$selectors" ]]; then
121
- selectors="[]"
122
- fi
123
-
124
- # Screenshot path
125
- mkdir -p "$RALPH_DIR/screenshots"
126
- local screenshot_path="$RALPH_DIR/screenshots/${story}.png"
127
-
128
- # Check mobile too?
129
- local check_mobile
130
- check_mobile=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .mobile // empty' "$RALPH_DIR/prd.json" 2>/dev/null)
131
-
132
- # Build command - use the browser-verify skill
133
- local verify_script="$RALPH_LIB/browser-verify/verify.ts"
134
-
135
- if [[ ! -f "$verify_script" ]]; then
136
- print_warning " browser-verify.ts not found, falling back to curl check"
137
- return run_curl_check "$url"
138
- fi
139
-
140
- # Check for auth config (env vars take precedence over config file)
141
- local auth_login=""
142
- local login_url
143
- local test_user
144
- local test_password
145
- login_url=$(get_config '.auth.loginUrl' "")
146
- test_user="${RALPH_TEST_USER:-$(get_config '.auth.testUser' "")}"
147
- test_password="${RALPH_TEST_PASSWORD:-$(get_config '.auth.testPassword' "")}"
148
-
149
- if [[ -n "$login_url" && -n "$test_user" && -n "$test_password" ]]; then
150
- # Build auth login JSON
151
- local username_selector
152
- local password_selector
153
- local submit_selector
154
- local success_indicator
155
- username_selector=$(get_config '.auth.usernameSelector' "input[name='email'], input[name='username'], input[type='email']")
156
- password_selector=$(get_config '.auth.passwordSelector' "input[name='password'], input[type='password']")
157
- submit_selector=$(get_config '.auth.submitSelector' "button[type='submit'], input[type='submit']")
158
- success_indicator=$(get_config '.auth.successIndicator' "")
159
-
160
- auth_login=$(jq -n \
161
- --arg loginUrl "$login_url" \
162
- --arg usernameSelector "$username_selector" \
163
- --arg passwordSelector "$password_selector" \
164
- --arg submitSelector "$submit_selector" \
165
- --arg username "$test_user" \
166
- --arg password "$test_password" \
167
- --arg successIndicator "$success_indicator" \
168
- '{
169
- loginUrl: $loginUrl,
170
- usernameSelector: $usernameSelector,
171
- passwordSelector: $passwordSelector,
172
- submitSelector: $submitSelector,
173
- username: $username,
174
- password: $password,
175
- successIndicator: (if $successIndicator == "" then null else $successIndicator end)
176
- }')
177
- fi
178
-
179
- echo " Running Playwright verification..."
180
-
181
- local result
182
- local exit_code=0
183
-
184
- # Build the command with optional auth
185
- local cmd_args=(
186
- npx tsx "$verify_script" "$url"
187
- --selectors "$selectors"
188
- --screenshot "$screenshot_path"
189
- --timeout "$BROWSER_PAGE_TIMEOUT_MS"
190
- )
191
-
192
- if [[ -n "$auth_login" ]]; then
193
- cmd_args+=(--auth-login "$auth_login")
194
- fi
195
-
196
- # Run browser verification with wrapper timeout (in case Playwright hangs)
197
- result=$(run_with_timeout "$BROWSER_TIMEOUT_SECONDS" "${cmd_args[@]}" 2>&1) || exit_code=$?
198
-
199
- # Check for timeout
200
- if [[ $exit_code -eq 124 ]]; then
201
- print_error "failed (timed out after ${BROWSER_TIMEOUT_SECONDS}s)"
202
- echo " The page may be stuck loading or the dev server isn't responding."
203
- echo " Check: curl -I $url"
204
- return run_curl_check "$url"
205
- fi
206
-
207
- # Check if we got any output
208
- if [[ -z "$result" ]]; then
209
- print_error "failed (no output from Playwright)"
210
- echo " Exit code: $exit_code"
211
- echo " This usually means Playwright crashed or isn't installed correctly."
212
- echo " Try: npm install playwright && npx playwright install chromium"
213
- return run_curl_check "$url"
214
- fi
215
-
216
- # Check if result is valid JSON
217
- if ! echo "$result" | jq -e . >/dev/null 2>&1; then
218
- print_error "failed (invalid response)"
219
- echo " Raw output:"
220
- echo "$result" | head -20 | sed 's/^/ /'
221
- return run_curl_check "$url"
222
- fi
223
-
224
- # Parse result
225
- local passed
226
- passed=$(echo "$result" | jq -r '.pass // false' 2>/dev/null)
227
-
228
- if [[ "$passed" == "true" ]]; then
229
- local load_time
230
- load_time=$(echo "$result" | jq -r '.loadTime // 0' 2>/dev/null)
231
- print_success "passed (${load_time}ms)"
232
-
233
- # Show any warnings
234
- local warnings
235
- warnings=$(echo "$result" | jq -r '.warnings[]?' 2>/dev/null)
236
- if [[ -n "$warnings" ]]; then
237
- echo "$warnings" | sed 's/^/ Warning: /'
238
- fi
239
-
240
- # Run mobile check if required
241
- if [[ -n "$check_mobile" ]]; then
242
- echo -n " Mobile viewport... "
243
- local mobile_result
244
- mobile_result=$(run_with_timeout "$BROWSER_TIMEOUT_SECONDS" npx tsx "$verify_script" "$url" \
245
- --selectors "$selectors" \
246
- --screenshot "$RALPH_DIR/screenshots/${story}-mobile.png" \
247
- --mobile \
248
- 2>&1) || true
249
-
250
- local mobile_passed
251
- mobile_passed=$(echo "$mobile_result" | jq -r '.pass // false' 2>/dev/null)
252
-
253
- if [[ "$mobile_passed" == "true" ]]; then
254
- print_success "passed"
255
- else
256
- print_warning "issues found"
257
- echo "$mobile_result" | jq -r '.errors[]?' 2>/dev/null | head -3 | sed 's/^/ /'
258
- fi
259
- fi
260
-
261
- return 0
262
- else
263
- print_error "failed"
264
- echo ""
265
-
266
- # Show errors
267
- echo " Errors:"
268
- echo "$result" | jq -r '.errors[]?' 2>/dev/null | sed 's/^/ /'
269
-
270
- # Show console errors if any
271
- local console_errors
272
- console_errors=$(echo "$result" | jq -r '.consoleErrors[]?' 2>/dev/null)
273
- if [[ -n "$console_errors" ]]; then
274
- echo ""
275
- echo " Console errors:"
276
- echo "$console_errors" | head -5 | sed 's/^/ /'
277
- fi
278
-
279
- # Show missing elements if any
280
- local missing
281
- missing=$(echo "$result" | jq -r '.elementsMissing[]?' 2>/dev/null)
282
- if [[ -n "$missing" ]]; then
283
- echo ""
284
- echo " Missing elements:"
285
- echo "$missing" | sed 's/^/ /'
286
- fi
287
-
288
- # Save for failure context
289
- echo "$result" > "$RALPH_DIR/last_browser_failure.json"
290
-
291
- return 1
292
- fi
293
- }
294
-
295
- # Fallback curl check when Playwright isn't available
296
- run_curl_check() {
297
- local url="$1"
298
-
299
- local http_response
300
- http_response=$(curl -s -o /dev/null -w "%{http_code}" --max-time "$CURL_TIMEOUT_SECONDS" "$url" 2>/dev/null) || http_response="000"
301
-
302
- if [[ "$http_response" == "000" ]]; then
303
- print_error "Cannot reach $url - server not responding"
304
- return 1
305
- elif [[ "$http_response" -ge 500 ]]; then
306
- print_error "Server error $http_response at $url"
307
- return 1
308
- elif [[ "$http_response" -ge 400 ]]; then
309
- print_warning "HTTP $http_response (may be expected for auth pages)"
310
- return 0
311
- fi
312
-
313
- # Check for error messages in response
314
- local page_content
315
- page_content=$(curl -s --max-time "$CURL_TIMEOUT_SECONDS" "$url" 2>/dev/null)
316
-
317
- if echo "$page_content" | grep -qi "something went wrong\|error.*occurred\|500 internal\|503 service\|oops\!" 2>/dev/null; then
318
- print_error "Page contains error message"
319
- return 1
320
- fi
321
-
322
- print_success "HTTP $http_response"
323
- return 0
324
- }