Skip to content

Commit

Permalink
Setup keychain image snapshots
Browse files Browse the repository at this point in the history
  • Loading branch information
tarrencev committed Jan 3, 2025
1 parent 88a5f2f commit dcd2ba6
Show file tree
Hide file tree
Showing 114 changed files with 840 additions and 68 deletions.
218 changes: 211 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,57 @@ jobs:

storybook:
runs-on: ubuntu-latest
permissions:
contents: "read"
id-token: "write"
pull-requests: "write"
container:
image: mcr.microsoft.com/playwright:v1.48.0
options: --ipc=host
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: dorny/paths-filter@v3
id: changes
with:
filters: |
ui:
- 'packages/ui-next/**'
- 'packages/keychain/**'
- '**/package.json'
- '**/pnpm-lock.yaml'
- if: steps.changes.outputs.ui == 'true'
uses: actions/setup-node@v4
with:
node-version: 20.x
- uses: actions/cache@v4

- if: steps.changes.outputs.ui == 'true'
uses: actions/cache@v4
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
path: |
~/.pnpm-store
**/node_modules
key:
${{ runner.os }}-storybook-${{ hashFiles('**/pnpm-lock.yaml') }}-${{
github.sha }}
restore-keys: |
${{ runner.os }}-pnpm-
${{ runner.os }}-storybook-${{ hashFiles('**/pnpm-lock.yaml') }}-
${{ runner.os }}-storybook-
- name: Set timezone
run: |
ln -sf /usr/share/zoneinfo/UTC /etc/localtime
echo "UTC" > /etc/timezone
- name: Setup environment
run: |
# Set consistent environment variables
echo "CI=true" >> $GITHUB_ENV
echo "TZ=UTC" >> $GITHUB_ENV
# Set consistent screen resolution
Xvfb :99 -screen 0 1280x1024x24 &
echo "DISPLAY=:99" >> $GITHUB_ENV
- run: corepack enable pnpm

Expand All @@ -92,5 +132,169 @@ jobs:
run_install: false

- run: pnpm install --frozen-lockfile
- run: pnpm keychain exec playwright install
- run: pnpm test:storybook

# Run regular storybook tests first to detect diffs
- run: pnpm keychain test:storybook
id: test-storybook
continue-on-error: true

# Check for visual differences and collect info
- name: Check for visual differences
id: check-diffs
if: always() # Run even if previous step failed
run: |
# Configure git safety
git config --global --add safe.directory '*'
# Fetch the base branch first
git fetch origin ${{ github.base_ref }}
touch diff_info.txt
# Function to check diffs and PR snapshot changes
check_snapshots() {
local pkg=$1
local dir=$2
# Check diff output directory
if [ -d "${dir}/__diff_output__" ]; then
find "${dir}/__diff_output__" -name "*.png" -type f | while read -r file; do
echo "${pkg}:${file}:diff" >> diff_info.txt
done
fi
# Check for snapshot changes in the PR
git diff --name-only origin/${{ github.base_ref }} | grep "^${dir}/.*\.png$" | while read -r file; do
if [ -f "$file" ]; then
echo "${pkg}:${file}:update" >> diff_info.txt
fi
done
}
# Check both packages
check_snapshots "keychain" "packages/keychain/__image_snapshots__"
# Set environment variables
if [ -s diff_info.txt ]; then
echo "snapshot_failed=true" >> "$GITHUB_ENV"
echo "diff_files<<EOF" >> "$GITHUB_ENV"
cat diff_info.txt >> "$GITHUB_ENV"
echo "EOF" >> "$GITHUB_ENV"
else
echo "snapshot_failed=false" >> "$GITHUB_ENV"
fi
# Upload diff images to GCP
- uses: "google-github-actions/auth@v2"
with:
project_id: c7e-prod
workload_identity_provider: "projects/276773611885/locations/global/workloadIdentityPools/github/providers/controller-repo"

- name: Debug directory structure
run: |
echo "Checking directories..."
ls -la packages/ui-next/__image_snapshots__ || echo "ui-next snapshots dir not found"
ls -la packages/keychain/__image_snapshots__ || echo "keychain snapshots dir not found"
echo "Current working directory: $PWD"
find . -name "__diff_output__"
- uses: "google-github-actions/upload-cloud-storage@v2"
if: env.snapshot_failed == 'true'
id: upload-diffs
with:
path: "packages"
destination:
"c7e-prod-static/gh/visual-diffs/${{ github.repository }}/${{
github.event.pull_request.number }}"
glob: "*/__image_snapshots__/__diff_output__/**"
parent: false

# Create PR comment with results
- uses: actions/github-script@v7
if: always() && github.event_name == 'pull_request'
with:
script: |
const fs = require('fs');
let comment = '### 🎨 Visual Regression Test Results\n\n';
const testStatus = process.env.snapshot_failed === 'true'
? '❌ Visual differences detected'
: '✅ No visual changes detected';
comment += `${testStatus}\n\n`;
if (process.env.snapshot_failed === 'true') {
const diffFiles = process.env.diff_files.split('\n').filter(Boolean);
// Group changes by package and type
const changes = {
'ui-next': { diffs: [], updates: [] },
'keychain': { diffs: [], updates: [] }
};
// Process all files
diffFiles.forEach(diff => {
const [pkg, path, type] = diff.split(':');
if (path && fs.existsSync(path)) {
const fileName = path.split('/').pop();
const storyName = fileName.replace('.png', '').replace('-diff', '');
const imageUrl = `https://static.cartridge.gg/gh/visual-diffs/${process.env.GITHUB_REPOSITORY}/${context.payload.pull_request.number}/${pkg}/__image_snapshots__/__diff_output__/${fileName}`;
if (type === 'diff') {
changes[pkg].diffs.push({ storyName, imageUrl });
} else {
changes[pkg].updates.push({ storyName, imageUrl });
}
}
});
// Generate comment sections for each package
for (const [pkg, pkgChanges] of Object.entries(changes)) {
if (pkgChanges.diffs.length > 0 || pkgChanges.updates.length > 0) {
comment += `\n#### 📦 ${pkg}\n\n`;
if (pkgChanges.diffs.length > 0) {
comment += '##### ⚠️ Visual Differences Detected\n\n';
comment += 'The following components have visual differences that need review:\n\n';
pkgChanges.diffs.forEach(({ storyName, imageUrl }) => {
comment += `<details><summary><code>${storyName}</code></summary>\n\n`;
comment += `![${storyName}](${imageUrl})\n\n`;
comment += '</details>\n\n';
});
}
if (pkgChanges.updates.length > 0) {
comment += '##### 🔄 Snapshot Updates in PR\n\n';
comment += 'The following snapshots have been updated in this PR:\n\n';
pkgChanges.updates.forEach(({ storyName, imageUrl }) => {
comment += `<details><summary><code>${storyName}</code></summary>\n\n`;
comment += `![${storyName}](${imageUrl})\n\n`;
comment += '</details>\n\n';
});
}
}
}
comment += '\n---\n';
if (Object.values(changes).some(pkg => pkg.diffs.length > 0)) {
comment += '⚠️ **Action Required**: Please review the visual differences and:\n';
comment += '1. Update the snapshots locally if the changes are intended (`scripts/update-storybook-snapshot.sh`)\n';
comment += '2. Fix the components if the changes are unintended\n\n';
}
}
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
# Fail the job if there were visual differences
- name: Check for failures
if: always()
run: |
if [[ "${{ env.snapshot_failed }}" == "true" ]]; then
echo "Visual differences detected in Storybook tests"
exit 1
fi
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ node_modules/
*.tsbuildinfo

.turbo
.pnpm-store

**/.pnpm-debug.log*

Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,20 @@
"example:svelte": "pnpm --filter @cartridge/controller-example-svelte",
"test": "pnpm keychain test",
"test:ci": "pnpm keychain test:ci",
"test:storybook": "pnpm turbo build:deps test:storybook"
"test:storybook": "pnpm turbo build:deps test:storybook",
"test:storybook:update": "pnpm turbo build:deps test:storybook:update"
},
"dependencies": {
"@cartridge/presets": "github:cartridge-gg/presets#b0def0f"
},
"devDependencies": {
"@changesets/changelog-github": "^0.4.2",
"@changesets/cli": "^2.20.0",
"playwright": "^1.47.1",
"prettier": "^2.7.1",
"tsup": "^8.0.1",
"turbo": "^2.0.12",
"vercel": "^37.4.2",
"@types/react": "^18.3.12"
}
}
}
40 changes: 40 additions & 0 deletions packages/keychain/.storybook/test-runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { TestRunnerConfig, waitForPageReady } from "@storybook/test-runner";
import { toMatchImageSnapshot } from "jest-image-snapshot";
import path from "path";

const customSnapshotsDir = path.join(process.cwd(), "__image_snapshots__");

const config: TestRunnerConfig = {
setup() {
expect.extend({ toMatchImageSnapshot });
},
async postVisit(page, context) {
// Wait for the page to be ready before taking a screenshot
await waitForPageReady(page);

// Wait an extra second for transitions to complete
await page.waitForTimeout(1000);

// Get the story's container element - selecting the nested content div
const storyContainer = await page.$("#storybook-root > div > div");
if (!storyContainer) {
throw new Error("Could not find story content element");
}

// Get browser name to handle different browsers if needed
const browserName =
page.context().browser()?.browserType().name() ?? "unknown";

// Take screenshot of just the story container
const image = await storyContainer.screenshot();
expect(image).toMatchImageSnapshot({
customSnapshotsDir,
customSnapshotIdentifier: `${context.id}-${browserName}`,
// Add some threshold to handle minor rendering differences
failureThreshold: 0.01,
failureThresholdType: "percent",
});
},
};

export default config;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.diff
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 7 additions & 4 deletions packages/keychain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
"test:ci": "vitest run",
"storybook": "storybook dev -p 6001",
"storybook:build": "storybook build",
"test:storybook": "pnpm concurrently -k -s first -n \"SB,TEST\" -c \"magenta,blue\" \"pnpm storybook:build --quiet && pnpm http-server storybook-static --port 6006 --silent\" \"pnpm wait-on tcp:6006 && pnpm test-storybook\""
"storybook:serve": "pnpm storybook:build --quiet && pnpm http-server -c-1 storybook-static --port 6006 --silent",
"test:storybook:update": "start-server-and-test 'pnpm storybook:serve' 6006 'pnpm test-storybook -u'",
"test:storybook": "start-server-and-test 'pnpm storybook:serve' 6006 'pnpm test-storybook'"
},
"dependencies": {
"@cartridge/account-wasm": "workspace:*",
Expand Down Expand Up @@ -66,30 +68,31 @@
"@storybook/test": "^8.4.7",
"@storybook/test-runner": "^0.21.0",
"@testing-library/react": "^13.4.0",
"@types/jest-image-snapshot": "^6.4.0",
"@types/js-cookie": "^3.0.2",
"@types/node": "^20.6.0",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react-swc": "^3.5.0",
"@vitest/coverage-v8": "2.1.8",
"autoprefixer": "^10.4.18",
"concurrently": "^9.0.1",
"eslint": "^9.12.0",
"eslint-plugin-storybook": "^0.6.13",
"http-server": "^14.1.1",
"jest-image-snapshot": "^6.4.0",
"jsdom": "^25.0.1",
"playwright": "^1.47.1",
"postcss": "^8.4.35",
"prettier": "^2.7.1",
"rollup-plugin-visualizer": "^5.12.0",
"start-server-and-test": "^2.0.9",
"storybook": "^8.4.7",
"tailwindcss": "^3.4.3",
"typescript": "^5.4.5",
"vite": "^6.0.3",
"vite-plugin-node-polyfills": "^0.22.0",
"vite-plugin-top-level-await": "^1.4.4",
"vite-plugin-wasm": "^3.3.0",
"vitest": "^2.1.8",
"wait-on": "^8.0.1"
"vitest": "^2.1.8"
},
"peerDependencies": {
"@chakra-ui/react": "^2.8.1",
Expand Down
Loading

0 comments on commit dcd2ba6

Please sign in to comment.