agentic-loop 3.1.6 → 3.2.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/ralph/setup.sh +76 -11
- package/ralph/verify.sh +12 -143
- package/templates/PROMPT.md +26 -43
- package/ralph/api.sh +0 -216
- package/ralph/browser-verify/README.md +0 -135
- package/ralph/browser-verify/verify.ts +0 -450
- package/ralph/playwright.sh +0 -238
- package/ralph/verify/browser.sh +0 -324
- package/ralph/verify/review.sh +0 -152
package/ralph/playwright.sh
DELETED
|
@@ -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
|
-
}
|
package/ralph/verify/browser.sh
DELETED
|
@@ -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
|
-
}
|