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 Dec 31, 2024
1 parent 88a5f2f commit 9d3496a
Show file tree
Hide file tree
Showing 112 changed files with 770 additions and 67 deletions.
185 changes: 179 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,33 @@ jobs:
runs-on: ubuntu-latest
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-
- run: corepack enable pnpm

Expand All @@ -92,5 +110,160 @@ jobs:
run_install: false

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

# Run regular storybook tests first to detect diffs
- run: pnpm 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: |
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 "ui-next" "packages/ui-next/__image_snapshots__"
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"

- uses: "google-github-actions/upload-cloud-storage@v2"
if: always()
id: upload-diffs
with:
path: |
packages/ui-next/__image_snapshots__/__diff_output__
packages/keychain/__image_snapshots__/__diff_output__
destination:
"c7e-prod-static/gh/visual-diffs/${{ github.repository }}/${{
github.event.pull_request.number }}"
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}/${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 (`pnpm test:storybook:update`)\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
});
- name: Upload images to PR comment
if: always() && github.event_name == 'pull_request'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
for file in packages/*/__image_snapshots__/__diff_output__/*.png; do
if [ -f "$file" ]; then
gh pr comment ${{ github.event.pull_request.number }} --body-file <(echo "![$(basename $file)]($file)")
fi
done
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;
1 change: 1 addition & 0 deletions packages/keychain/__image_snapshots__/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__diff_output__
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
37 changes: 37 additions & 0 deletions packages/ui-next/.storybook/test-runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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);

// Get the story's container element - selecting the nested content div
const storyContainer = await page.$("#storybook-root > *");
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;
1 change: 1 addition & 0 deletions packages/ui-next/__image_snapshots__/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__diff_output__
13 changes: 11 additions & 2 deletions packages/ui-next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@
"format:check": "prettier --check ./src",
"lint": "eslint .",
"storybook": "storybook dev -p 6003",
"storybook:build": "storybook build"
"storybook:build": "storybook build",
"storybook:serve": "pnpm storybook:build --quiet && pnpm http-server -c-1 storybook-static --port 6007 --silent",
"test:storybook:update": "start-server-and-test 'pnpm storybook:serve' 6007 'pnpm test-storybook --url http://127.0.0.1:6007 -u'",
"test:storybook": "start-server-and-test 'pnpm storybook:serve' 6007 'pnpm test-storybook --url http://127.0.0.1:6007'"
},
"dependencies": {
"@cartridge/utils": "workspace:*",
Expand Down Expand Up @@ -82,7 +85,9 @@
"@storybook/react": "^8.4.7",
"@storybook/react-vite": "^8.4.7",
"@storybook/test": "^8.4.7",
"@storybook/test-runner": "^0.21.0",
"@storybook/theming": "^8.4.7",
"@types/jest-image-snapshot": "^6.4.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@typescript-eslint/eslint-plugin": "^7.0.2",
Expand All @@ -92,12 +97,16 @@
"eslint": "^9.9.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-storybook": "^0.6.13",
"http-server": "^14.1.1",
"jest-image-snapshot": "^6.4.0",
"playwright": "^1.47.1",
"postcss": "^8.4.35",
"start-server-and-test": "^2.0.9",
"storybook": "^8.4.7",
"tailwindcss": "^3.4.3",
"tsc-alias": "^1.8.10",
"typescript": "^5.4.3",
"typescript-transform-paths": "^3.4.7",
"vite": "^5.1.4"
}
}
}
Loading

0 comments on commit 9d3496a

Please sign in to comment.