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 bb39dcc
Show file tree
Hide file tree
Showing 113 changed files with 733 additions and 70 deletions.
147 changes: 140 additions & 7 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,120 @@ jobs:
run_install: false

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

# Run tests and capture the output
- run: pnpm test:storybook:update
id: test-storybook

# Check for visual differences and collect diff info
- name: Check for visual differences
id: check-diffs
run: |
# Create a temporary file to store diff info
echo "DIFF_INFO=" > diff_info.txt
# Function to check both diff outputs and snapshot updates
check_snapshots() {
local pkg=$1
local dir=$2
# Check diff output directory
if [ -d "${dir}/__diff_output__" ]; then
for file in "${dir}/__diff_output__"/*.png; do
if [ -f "$file" ]; then
echo "${pkg}:${file}:diff" >> diff_info.txt
fi
done
fi
# Check for updated snapshots
git diff --name-only | 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 failed flag if we found any changes
if [ -s diff_info.txt ]; then
echo "SNAPSHOT_FAILED=true" >> $GITHUB_ENV
echo "DIFF_FILES=$(cat diff_info.txt)" >> $GITHUB_ENV
fi
# Create PR comment with results
- uses: actions/github-script@v7
if: github.event_name == 'pull_request'
with:
script: |
const fs = require('fs');
let comment = '### 🎨 Visual Regression Test Results\n\n';
if (process.env.SNAPSHOT_FAILED === 'true') {
comment += '❌ Visual changes detected\n\n';
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: [] }
};
diffFiles.forEach(diff => {
const [pkg, path, type] = diff.split(':');
if (path) {
const fileName = path.split('/').pop();
const storyName = fileName.replace('.png', '').replace('-diff', '');
const base64Image = fs.readFileSync(path, 'base64');
if (type === 'diff') {
changes[pkg].diffs.push({ storyName, base64Image });
} else {
changes[pkg].updates.push({ storyName, base64Image });
}
}
});
// 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\n\n';
pkgChanges.diffs.forEach(({ storyName, base64Image }) => {
comment += `<details><summary><code>${storyName}</code></summary>\n\n`;
comment += `<img src="data:image/png;base64,${base64Image}" width="600" />\n\n`;
comment += '</details>\n\n';
});
}
if (pkgChanges.updates.length > 0) {
comment += '##### 🔄 Updated Snapshots\n\n';
pkgChanges.updates.forEach(({ storyName, base64Image }) => {
comment += `<details><summary><code>${storyName}</code></summary>\n\n`;
comment += `<img src="data:image/png;base64,${base64Image}" width="600" />\n\n`;
comment += '</details>\n\n';
});
}
}
}
comment += '\n---\n';
comment += `[📎 Download all artifacts](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID})`;
} else {
comment += '✅ No visual changes detected!\n';
}
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
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"
}
}
}
4 changes: 2 additions & 2 deletions packages/ui-next/src/components/primitives/checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ const Checkbox = React.forwardRef<
checked === "indeterminate"
? "minus-line"
: checked
? "line"
: "unchecked-line"
? "line"
: "unchecked-line"
}
/>
</CheckboxPrimitive.Root>
Expand Down
Loading

0 comments on commit bb39dcc

Please sign in to comment.