-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaction.yml
More file actions
250 lines (227 loc) · 8.57 KB
/
Copy pathaction.yml
File metadata and controls
250 lines (227 loc) · 8.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# Repo-root composite-action shim.
#
# GitHub resolves `uses: <owner>/<repo>@<ref>` against an `action.yml` at the
# repository ROOT — it does NOT honour a sub-path `action.yml` by default. The
# canonical manifest lives at `packages/cli/action.yml`; this root shim mirrors
# it byte-for-byte (same inputs, outputs, and steps) so that
# `uses: ravidsrk/frontguard@v0` resolves end-to-end.
#
# Why the steps are duplicated rather than delegated via `uses: ./packages/cli`:
# a local-path `uses:` inside a composite action is resolved against the
# CONSUMER's checkout, which will not contain `packages/cli`. Re-implementing
# the steps here is the only form that works for an external consumer pinning
# `ravidsrk/frontguard@v0`.
#
# GENERATED BODY — do not edit the steps below. Edit `packages/cli/action.template.yml`
# and run `node packages/cli/scripts/sync-root-action.mjs`.
#
# Sub-path consumers (`uses: ravidsrk/frontguard/packages/cli@0.2.2`) continue
# to resolve against `packages/cli/action.yml` — that file is intentionally
# preserved.
name: 'Frontguard Visual Regression'
description: 'AI-powered frontend visual regression testing. Detect, understand, and fix visual bugs before production.'
author: 'ravidsrk'
branding:
icon: 'eye'
color: 'blue'
inputs:
url:
description: 'Base URL to test. Auto-detects Vercel/Netlify preview URLs if not set.'
required: false
routes:
description: 'Comma-separated routes to test (optional — auto-discovers if not set)'
required: false
viewports:
description: 'Comma-separated viewport widths (default: 375,768,1440)'
required: false
default: '375,768,1440'
browsers:
description: 'Comma-separated browsers: chromium,firefox,webkit (default: chromium)'
required: false
default: 'chromium'
threshold:
description: 'Pixel diff threshold percentage (default: 0.1)'
required: false
default: '0.1'
config:
description: 'Path to frontguard.config.ts'
required: false
ai-provider:
description: 'AI provider: openai or anthropic'
required: false
ai-model:
description: 'AI model name (e.g., gpt-4o, claude-3-5-sonnet-20241022)'
required: false
update-baselines:
description: 'Accept current screenshots as new baselines'
required: false
default: 'false'
github-token:
description: 'GitHub token for PR comments (defaults to github.token)'
required: false
default: ${{ github.token }}
outputs:
result:
description: 'JSON result of the visual regression test run'
regressions:
description: 'Number of regressions found'
status:
description: 'pass, fail, or error'
runs:
using: 'composite'
steps:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Frontguard
shell: bash
env:
FRONTGUARD_CLI_VERSION: '0.2.2'
run: npm install -g "@frontguard/cli@${FRONTGUARD_CLI_VERSION}"
- name: Install Playwright browsers
shell: bash
env:
IN_BROWSERS: ${{ inputs.browsers }}
run: |
set -euo pipefail
if ! echo "$IN_BROWSERS" | grep -Eq '^(chromium|firefox|webkit)(,(chromium|firefox|webkit))*$'; then
echo "Invalid browsers input: $IN_BROWSERS" >&2
exit 1
fi
IFS=',' read -ra BROWSER_ARR <<< "$IN_BROWSERS"
npx playwright install --with-deps "${BROWSER_ARR[@]}"
- name: Detect Preview URL
id: preview-url
shell: bash
env:
IN_URL: ${{ inputs.url }}
run: |
set -euo pipefail
write_github_output() {
local key="$1"
local value="$2"
local delim="ghadelim_$(openssl rand -hex 8)"
{
printf '%s<<%s\n' "$key" "$delim"
printf '%s\n' "$value"
printf '%s\n' "$delim"
} >> "$GITHUB_OUTPUT"
}
if [ -n "$IN_URL" ]; then
write_github_output url "$IN_URL"
echo "Preview URL: $IN_URL (from input)"
elif [ -n "$VERCEL_URL" ]; then
PREVIEW_URL="https://$VERCEL_URL"
write_github_output url "$PREVIEW_URL"
echo "Preview URL: $PREVIEW_URL (Vercel)"
elif [ -n "$VERCEL_PREVIEW_URL" ]; then
write_github_output url "$VERCEL_PREVIEW_URL"
echo "Preview URL: $VERCEL_PREVIEW_URL (Vercel)"
elif [ -n "$DEPLOY_PRIME_URL" ]; then
write_github_output url "$DEPLOY_PRIME_URL"
echo "Preview URL: $DEPLOY_PRIME_URL (Netlify)"
elif [ -n "$DEPLOY_URL" ]; then
write_github_output url "$DEPLOY_URL"
echo "Preview URL: $DEPLOY_URL (Netlify)"
elif [ -n "$CF_PAGES_URL" ]; then
write_github_output url "$CF_PAGES_URL"
echo "Preview URL: $CF_PAGES_URL (Cloudflare Pages)"
elif [ -n "$RAILWAY_STATIC_URL" ]; then
PREVIEW_URL="https://$RAILWAY_STATIC_URL"
write_github_output url "$PREVIEW_URL"
echo "Preview URL: $PREVIEW_URL (Railway)"
elif [ -n "$RENDER_EXTERNAL_URL" ]; then
write_github_output url "$RENDER_EXTERNAL_URL"
echo "Preview URL: $RENDER_EXTERNAL_URL (Render)"
else
write_github_output url ""
echo "⚠️ No preview URL detected. Set the 'url' input or ensure your deployment sets environment variables."
fi
- name: Run Frontguard
id: frontguard
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.github-token }}
FRONTGUARD_OPENAI_KEY: ${{ env.FRONTGUARD_OPENAI_KEY }}
FRONTGUARD_ANTHROPIC_KEY: ${{ env.FRONTGUARD_ANTHROPIC_KEY }}
IN_URL: ${{ steps.preview-url.outputs.url }}
IN_ROUTES: ${{ inputs.routes }}
IN_VIEWPORTS: ${{ inputs.viewports }}
IN_BROWSERS: ${{ inputs.browsers }}
IN_THRESHOLD: ${{ inputs.threshold }}
IN_CONFIG: ${{ inputs.config }}
IN_UPDATE_BASELINES: ${{ inputs.update-baselines }}
run: |
set -euo pipefail
write_github_output() {
local key="$1"
local value="$2"
local delim="ghadelim_$(openssl rand -hex 8)"
{
printf '%s<<%s\n' "$key" "$delim"
printf '%s\n' "$value"
printf '%s\n' "$delim"
} >> "$GITHUB_OUTPUT"
}
if ! echo "$IN_VIEWPORTS" | grep -Eq '^[0-9]+(,[0-9]+)*$'; then
echo "Invalid viewports input: $IN_VIEWPORTS" >&2
exit 1
fi
if ! echo "$IN_BROWSERS" | grep -Eq '^(chromium|firefox|webkit)(,(chromium|firefox|webkit))*$'; then
echo "Invalid browsers input: $IN_BROWSERS" >&2
exit 1
fi
if ! echo "$IN_THRESHOLD" | grep -Eq '^[0-9]+(\.[0-9]+)?$'; then
echo "Invalid threshold input: $IN_THRESHOLD" >&2
exit 1
fi
CMD=(frontguard run)
if [ -n "$IN_URL" ]; then CMD+=(--url "$IN_URL"); fi
if [ -n "$IN_ROUTES" ]; then CMD+=(--routes "$IN_ROUTES"); fi
CMD+=(--viewports "$IN_VIEWPORTS")
CMD+=(--browsers "$IN_BROWSERS")
CMD+=(--threshold "$IN_THRESHOLD")
if [ -n "$IN_CONFIG" ]; then CMD+=(--config "$IN_CONFIG"); fi
if [ "$IN_UPDATE_BASELINES" = "true" ]; then CMD+=(--update-baselines); fi
CMD+=(--output json)
set +e
"${CMD[@]}" > /tmp/frontguard-result.json 2>/tmp/frontguard-stderr.log
RUN_EXIT=$?
set -e
if [ -s /tmp/frontguard-stderr.log ]; then
cat /tmp/frontguard-stderr.log >&2
fi
RUN_FAILED=0
REGRESSIONS=""
if [ "$RUN_EXIT" -ne 0 ]; then
RUN_FAILED=1
fi
if [ ! -s /tmp/frontguard-result.json ]; then
RUN_FAILED=1
printf '{}' > /tmp/frontguard-result.json
elif ! jq -e '(.summary.regressions | type) == "number"' /tmp/frontguard-result.json >/dev/null 2>&1; then
RUN_FAILED=1
else
REGRESSIONS=$(jq -r '.summary.regressions' /tmp/frontguard-result.json)
fi
write_github_output result "$(cat /tmp/frontguard-result.json)"
if [ "$RUN_FAILED" -eq 1 ]; then
write_github_output status "error"
write_github_output regressions ""
echo "Frontguard run failed (exit=$RUN_EXIT) or produced non-parseable JSON" >&2
exit 1
fi
write_github_output regressions "$REGRESSIONS"
if [ "$REGRESSIONS" -gt 0 ]; then
write_github_output status "fail"
exit 1
fi
write_github_output status "pass"
- name: Upload Report
if: always()
uses: actions/upload-artifact@v7
with:
name: frontguard-report
path: frontguard-report/
if-no-files-found: ignore