Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,29 @@ jobs:
- uses: actions/upload-artifact@v6
with:
path: results

cache-baseline:
name: Cache Baseline
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Cache Screenshots
id: screenshot-cache
uses: actions/cache@v5
with:
path: build
key: screenshots-${{ github.sha }}
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
- name: Install Dependencies
if: steps.screenshot-cache.outputs.cache-hit != 'true'
run: |
pnpm install
pnpm exec playwright install --with-deps
- name: Capture Screenshots
if: steps.screenshot-cache.outputs.cache-hit != 'true'
run: pnpm run test:screenshots
114 changes: 90 additions & 24 deletions .github/workflows/visual-diff.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
name: Visual Diff

on:
pull_request: {}
pull_request:
types: [labeled, unlabeled, opened, synchronize, reopened]

concurrency:
group: visual-diff-${{ github.head_ref || github.ref }}
Expand All @@ -11,39 +12,71 @@ jobs:
baseline:
name: Baseline
runs-on: ubuntu-latest
outputs:
ref: ${{ steps.ref.outputs.ref }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.base_ref }}
- id: ref
run: |
echo "$(git rev-parse HEAD)"
echo "ref=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Cache Screenshots
id: screenshot-cache
uses: actions/cache@v5
with:
path: build
key: screenshots-${{ steps.ref.outputs.ref }}
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
- run: pnpm install
- run: pnpm exec playwright install --with-deps
- name: Install Dependencies
if: steps.screenshot-cache.outputs.cache-hit != 'true'
run: |
pnpm install
pnpm exec playwright install --with-deps
- name: Capture Screenshots
if: steps.screenshot-cache.outputs.cache-hit != 'true'
run: pnpm run test:screenshots
- uses: actions/upload-artifact@v6
- uses: actions/upload-artifact@v7
with:
name: baseline
path: build

candidate:
name: Candidate
runs-on: ubuntu-latest
outputs:
ref: ${{ steps.ref.outputs.ref }}
steps:
- uses: actions/checkout@v6
- id: ref
run: |
echo "$(git rev-parse HEAD)"
echo "ref=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Cache Screenshots
id: screenshot-cache
uses: actions/cache@v5
with:
path: build
key: screenshots-${{ steps.ref.outputs.ref }}-${{ matrix.name }}
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
- run: pnpm install
- run: pnpm exec playwright install --with-deps
- name: Install Dependencies
if: steps.screenshot-cache.outputs.cache-hit != 'true'
run: |
pnpm install
pnpm exec playwright install --with-deps
- name: Capture Screenshots
run: pnpm run test:screenshots
- uses: actions/upload-artifact@v6
if: steps.screenshot-cache.outputs.cache-hit != 'true'
run: pnpm test:screenshots
- uses: actions/upload-artifact@v7
with:
name: candidate
path: build
Expand All @@ -54,35 +87,68 @@ jobs:
runs-on: ubuntu-latest
env:
OUTPUT_DIR: visual-diff-${{ github.event.pull_request.number }}
BASELINE_REF: ${{needs.baseline.outputs.ref}}
CANDIDATE_REF: ${{needs.candidate.outputs.ref}}
steps:
- uses: actions/checkout@v6
- uses: actions/download-artifact@v7
- run: ls -lh baseline candidate
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
node-version: 24
- run: pnpm install
- run: pnpm build
- uses: pnpm/action-setup@v4
- name: Restore Comparison Cache
id: comparison-cache
uses: actions/cache/restore@v5
with:
path: |
${{ env.OUTPUT_DIR }}
results
key: comparison-${{ env.BASELINE_REF }}-${{ env.CANDIDATE_REF }}
- uses: actions/download-artifact@v8
if: steps.comparison-cache.outputs.cache-hit != 'true'
- run: ls -lh baseline candidate 2>/dev/null || true
- run: |
mkdir -p ./results
echo ${{ github.event.pull_request.number }} > ./results/pr_number
- run: pnpm install
if: steps.comparison-cache.outputs.cache-hit != 'true'
- run: pnpm build
if: steps.comparison-cache.outputs.cache-hit != 'true'
- name: Create Visual Diff
run: node ./dist/bin/visual-differ.js baseline candidate ${{ env.OUTPUT_DIR }} > results/visual-diff.txt
- if: always()
run: cp ${{ env.OUTPUT_DIR }}/report.md results/
- if: always()
run: cat results/report.md results/visual-diff.txt
- uses: actions/upload-artifact@v6
if: steps.comparison-cache.outputs.cache-hit != 'true'
run: |
set +e
node ./dist/bin/visual-differ.js --threshold=0.1 baseline candidate ${{ env.OUTPUT_DIR }} > results/visual-diff.txt
exit_code=$?
echo $exit_code > ./results/exit_code
cp ${{ env.OUTPUT_DIR }}/report.md results/
cat results/report.md results/visual-diff.txt
- uses: actions/upload-artifact@v7
id: upload-output
if: always()
with:
name: ${{ env.OUTPUT_DIR }}
path: ${{ env.OUTPUT_DIR }}
- if: always()
run: echo ${{ steps.upload-output.outputs.artifact-url }} > ./results/artifact_url
- uses: actions/upload-artifact@v6
if: always()
- run: |
echo ${{ steps.upload-output.outputs.artifact-url }} > ./results/artifact_url
echo ${{ contains(github.event.pull_request.labels.*.name, 'approve visual diff') }} > ./results/approved
- name: Save Comparison Cache
if: steps.comparison-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: |
${{ env.OUTPUT_DIR }}
results
key: ${{ steps.comparison-cache.outputs.cache-primary-key }}
- uses: actions/upload-artifact@v7
with:
name: results
path: results
- name: Check Status
run: |
exitCode=$(cat ./results/exit_code)
approved=$(cat ./results/approved)
echo "visual-differ exit code: $exitCode"
echo "approve visual diff label present: $approved"
if [ "$exitCode" != "0" ] && [ "$approved" != "true" ]; then
echo "Visual diff detected and 'approve visual diff' label not present."
exit 1
fi
73 changes: 72 additions & 1 deletion .github/workflows/visual_diff_results.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,39 @@ on:
- completed

jobs:
check-artifact:
name: Check for results artifact
runs-on: ubuntu-latest
permissions:
actions: read
outputs:
found: ${{ steps.check.outputs.found }}
steps:
- name: Check for "results" artifact
id: check
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});

const match = allArtifacts.data.artifacts.find(a => a.name === 'results');

if (match) {
core.setOutput('found', 'true');
} else {
core.setOutput('found', 'false');
console.log('No artifact uploaded.');
}

comment:
name: Comment on Pull Request
needs: check-artifact
if: ${{ needs.check-artifact.outputs.found == 'true' }}
runs-on: ubuntu-latest
permissions:
actions: read
Expand All @@ -23,27 +54,33 @@ jobs:
script: |
const fs = require('fs');
const path = require('path');

let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});

let matchArtifact = allArtifacts.data.artifacts.find((artifact) => {
return artifact.name === 'results';
});

let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});

const temp = '${{ runner.temp }}/artifacts';
if (!fs.existsSync(temp)){
fs.mkdirSync(temp);
}
fs.writeFileSync(path.join(temp, 'results.zip'), Buffer.from(download.data));

- name: Unzip artifact
run: unzip "${{ runner.temp }}/artifacts/results.zip" -d "${{ runner.temp }}/artifacts"

- name: Extract Details
uses: actions/github-script@v8
id: data
Expand All @@ -52,49 +89,83 @@ jobs:
const fs = require('fs');
const path = require('path');
const temp = '${{ runner.temp }}/artifacts';

const report = fs.readFileSync(path.join(temp, 'report.md'), "utf8");
const issue_number = Number(fs.readFileSync(path.join(temp, 'pr_number')));
const exit_code = Number(fs.readFileSync(path.join(temp, 'exit_code')));
const approved = fs.readFileSync(path.join(temp, 'approved'), "utf8");
const url = fs.readFileSync(path.join(temp, 'artifact_url'), "utf8");

return { report, issue_number, url };
const rhett = { report, issue_number, exit_code, approved, url };
console.log(rhett);
return rhett;

- name: Decode Output
id: decoded
run: |
JSON='${{ steps.data.outputs.result }}'

ISSUE_NUMBER="$(echo "$JSON" | jq -r '.issue_number')"
EXIT_CODE="$(echo "$JSON" | jq -r '.exit_code')"
APPROVED="$(echo "$JSON" | jq -r '.approved')"
URL="$(echo "$JSON" | jq -r '.url')"
REPORT="$(echo "$JSON" | jq -r '.report')"

{
echo "issue_number=$ISSUE_NUMBER"
echo "exit_code=$EXIT_CODE"
echo "approved=$APPROVED"
echo "url=$URL"
echo "report<<EOF"
echo "$REPORT"
echo "EOF"
} >> "$GITHUB_OUTPUT"

- name: Show Output
run: |
echo "issue_number:"
echo "${{ steps.decoded.outputs.issue_number }}"

echo "exit_code:"
echo "${{ steps.decoded.outputs.exit_code }}"

echo "approved:"
echo "${{ steps.decoded.outputs.approved }}"

echo "url:"
echo "${{ steps.decoded.outputs.url }}"

echo "report:"
echo "${{ steps.decoded.outputs.report }}"

- name: Approval Results
id: approval-results
run: |
exitCode="${{ steps.decoded.outputs.exit_code }}"
approved="${{ steps.decoded.outputs.approved }}"
echo "visual-differ exit code: $exitCode"
echo "approve visual diff label present: $approved"
if [ "$exitCode" != "0" ] && [ "$approved" == "true" ]; then
APPROVAL_RESULTS="### ✅ Visual Diff Approved"
else
APPROVAL_RESULTS=""
fi
echo "results=$APPROVAL_RESULTS" >> "$GITHUB_OUTPUT"

- name: Find Previous Comment
uses: peter-evans/find-comment@v4
id: find
with:
issue-number: ${{ steps.decoded.outputs.issue_number }}
body-includes: ${{ env.MARKER }}

- name: Create or Update Comment
uses: peter-evans/create-or-update-comment@v5
with:
comment-id: ${{ steps.find.outputs.comment-id }}
issue-number: ${{ steps.decoded.outputs.issue_number }}
body: |
${{ steps.approval-results.outputs.results }}
${{ steps.decoded.outputs.report }}

Download the [results](${{ steps.decoded.outputs.url }}).
Expand Down
Loading