Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
fb19ea7
chore: Add visual regression testing
jperals May 11, 2026
47b7f08
Install dependencies with npm i
jperals May 11, 2026
fb78d3a
Use node 20
jperals May 11, 2026
16d40d7
Add pixelmatch types
jperals May 11, 2026
b6f6385
Install Chromedriver in CI
jperals May 11, 2026
0df42fc
Start servers
jperals May 11, 2026
632df9b
Install Puppeteer
jperals May 11, 2026
30bb9db
Capture screenshot area or permutations
jperals May 11, 2026
3b11fe3
Reuse build
jperals May 13, 2026
d59cf2e
Fix wofklow deps
jperals May 13, 2026
f54fbc5
Again
jperals May 13, 2026
286c913
Fix npm install command
jperals May 13, 2026
61d7f03
Wait only for the build
jperals May 13, 2026
9f7c5c5
Revert "Wait only for the build"
jperals May 13, 2026
043bf4e
Revert "Fix npm install command"
jperals May 13, 2026
d1c1a8b
Revert "Again"
jperals May 13, 2026
a6ef608
Revert "Fix wofklow deps"
jperals May 13, 2026
f6661e7
Revert "Reuse build"
jperals May 13, 2026
824aeb3
Include alert tests
jperals May 13, 2026
533cade
Add more alert tests
jperals May 13, 2026
57086c6
chore: Export visual test definitions
jperals May 13, 2026
aa01ece
Use the quick build
jperals May 18, 2026
b6767f6
Fix tsconfig
jperals May 18, 2026
d7182b2
Fix workflow
jperals May 18, 2026
6324d9a
Fix workflow
jperals May 18, 2026
49325de
Use serve
jperals May 18, 2026
d5b8d6c
Run tests on Safari
jperals May 18, 2026
c158a1d
Prevent visual regression workflow from running twice
jperals May 18, 2026
45e188f
Reuse baseline build across browsers
jperals May 18, 2026
5c05262
Limit Safari concurrency
jperals May 18, 2026
d1103c6
Fix workflow
jperals May 18, 2026
7ba5aca
Fix workflows
jperals May 18, 2026
f3744e8
Fix workflow
jperals May 18, 2026
ea3ec1a
Fix workflow
jperals May 18, 2026
034dc87
Fix workflow
jperals May 18, 2026
cdf087d
Fix workflow
jperals May 18, 2026
5230a6d
Fix workflow
jperals May 19, 2026
11ec0be
Add delay between retries in Safari
jperals May 19, 2026
0f6f0e7
Fine tune Safari delay
jperals May 19, 2026
f44e03d
Release Safari session between tests
jperals May 19, 2026
16dd3dd
Do not retry with Safari
jperals May 19, 2026
09c8134
Change target directory
jperals May 19, 2026
83af893
Remove local testing setup
jperals May 21, 2026
574d1a5
Refactor
jperals May 21, 2026
735e123
Refactor
jperals May 27, 2026
a263998
Fix setup
jperals May 27, 2026
77e9470
Fix setup
jperals May 27, 2026
d022c5b
Create one single browser session for all tests
jperals May 27, 2026
c6c3e5d
Try to fix Safari
jperals May 27, 2026
de6525b
Try again
jperals May 27, 2026
38a09d4
Start safaridriver from workflow
jperals May 27, 2026
b607c4d
Move Safaridriver initialization back to local test setup
jperals May 27, 2026
dedf6ec
Remove outdated npm script
jperals May 28, 2026
3781167
Remove Safari setup
jperals May 29, 2026
5fdb3af
use captureScreenshotArea for permutation comparison unless there is …
jperals May 29, 2026
f95ef17
Add more test definitions
jperals May 29, 2026
354af7d
Refine config
jperals May 29, 2026
e3937e7
Shard tests
jperals May 29, 2026
c33545b
Fix config
jperals May 29, 2026
790b506
Fix config
jperals May 29, 2026
eaa3911
Increase timeout
jperals May 29, 2026
ef58b90
Use 3 shards
jperals May 29, 2026
ab2ffef
Fix tests
jperals May 29, 2026
2462deb
Build the test selectors before running the tests
jperals May 29, 2026
507f57c
Optimizations
jperals May 29, 2026
0915756
Upload the entire lib/components directory
jperals May 29, 2026
3909a3f
Remove unnecessary step
jperals May 29, 2026
e37268b
Add more components tests
jperals May 29, 2026
41c993d
Split app layout test definition files
jperals May 30, 2026
10b48b9
Add viewport screenshot type
jperals May 30, 2026
d0a3012
Split app layout responsive test definitions
jperals Jun 5, 2026
94c3f2a
Autogenerate test files
jperals Jun 5, 2026
234bb81
Generate Allure reports
jperals Jun 5, 2026
3537daa
Increase to 10 shards
jperals Jun 5, 2026
3ca00aa
Download Allure directly
jperals Jun 5, 2026
9342f83
Deploy Allure results
jperals Jun 5, 2026
e69aede
Add image diffs to Allure reports
jperals Jun 5, 2026
42cc955
Use Allure 3
jperals Jun 5, 2026
e825732
Fix image attachments
jperals Jun 6, 2026
37f3e17
Optimize comparison
jperals Jun 6, 2026
acb2878
Merge branch 'main' into chore/visual-tests
jperals Jun 6, 2026
23bea36
Export types
jperals Jun 6, 2026
e683584
Generate test definitions
jperals Jun 7, 2026
d615382
Type fixes
jperals Jun 7, 2026
f476cbf
Add all visual regression tests
jperals Jun 8, 2026
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
18 changes: 18 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ jobs:
name: dev-pages-react${{ matrix.react }}
path: pages/lib/static-default

- name: Upload test utils selectors artifact
if: matrix.react == 18
uses: actions/upload-artifact@v4
with:
name: test-utils-selectors
path: lib/components

deploy:
needs: quick-build
name: deploy${{ matrix.react != 16 && format(' (React {0})', matrix.react) || '' }}
Expand All @@ -65,3 +72,14 @@ jobs:
with:
artifact-name: dev-pages-react${{ matrix.react }}
deployment-path: pages/lib/static-default

visual:
name: Visual regression
needs: quick-build
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
uses: ./.github/workflows/visual-regression.yml
secrets: inherit
with:
pr-artifact-name: dev-pages-react18
test-utils-artifact-name: test-utils-selectors
caller-run-id: ${{ github.run_id }}
197 changes: 197 additions & 0 deletions .github/workflows/visual-regression.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
name: Visual Regression Tests

on:
workflow_call:
inputs:
pr-artifact-name:
description: 'Name of the artifact containing PR pages (built by the caller workflow).'
required: true
type: string
test-utils-artifact-name:
description: 'Name of the artifact containing test-utils selectors.'
required: true
type: string
caller-run-id:
description: 'The run ID of the calling workflow, used to download artifacts it uploaded.'
required: true
type: string

defaults:
run:
shell: bash

permissions:
id-token: write
contents: read
actions: read
deployments: write

jobs:
# Build the baseline (main branch) pages once and share them across all browser jobs.
build-baseline:
name: Build baseline pages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm i

# Use a git worktree so the baseline has its own directory and its own
# node_modules. This means a PR that changes package-lock.json will still
# produce a correct baseline: the baseline installs from main's lockfile
# and the PR build installs from the PR's lockfile, so both sides use the
# dependency versions that are correct for their respective source trees.
- name: Create baseline worktree from origin/main
run: git worktree add /tmp/baseline origin/main

- name: Install baseline dependencies
run: npm i
working-directory: /tmp/baseline

- name: Build baseline pages
run: npx gulp quick-build
working-directory: /tmp/baseline
env:
NODE_ENV: production

- name: Bundle baseline pages
run: node_modules/.bin/webpack --config pages/webpack.config.integ.cjs --output-path ${{ github.workspace }}/pages/lib/static-visual-baseline
working-directory: /tmp/baseline
env:
NODE_ENV: production

- name: Upload baseline artifact
uses: actions/upload-artifact@v4
with:
name: visual-baseline-pages
path: pages/lib/static-visual-baseline
retention-days: 1

visual:
name: Visual regression (shard ${{ matrix.shard }})
needs: [build-baseline]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [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]
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Setup Chrome and ChromeDriver
uses: browser-actions/setup-chrome@v1
with:
chrome-version: stable

- name: Install dependencies
run: npm i

- name: Download PR pages artifact
uses: actions/download-artifact@v4
with:
name: ${{ inputs.pr-artifact-name }}
path: pages/lib/static-default
github-token: ${{ github.token }}
run-id: ${{ inputs.caller-run-id }}

- name: Download baseline artifact
uses: actions/download-artifact@v4
with:
name: visual-baseline-pages
path: pages/lib/static-visual-baseline

- name: Download test utils artifact
uses: actions/download-artifact@v4
with:
name: ${{ inputs.test-utils-artifact-name }}
path: lib/components
github-token: ${{ github.token }}
run-id: ${{ inputs.caller-run-id }}

# ── Run tests ─────────────────────────────────────────────────────────

- name: Generate visual test files
run: node build-tools/visual/generate-tests.js

- name: Start test server (port 8080)
run: npx --yes serve --no-clipboard --listen 8080 pages/lib/static-default &

- name: Start baseline server (port 8081)
run: npx --yes serve --no-clipboard --listen 8081 pages/lib/static-visual-baseline &

- name: Wait for servers to be ready
run: node_modules/.bin/wait-on http://localhost:8080 http://localhost:8081

- name: Run visual regression tests
run: NODE_OPTIONS=--experimental-vm-modules node_modules/.bin/jest -c jest.visual.config.js --shard=${{ matrix.shard }}/30
env:
TZ: UTC

- name: Upload diff artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: visual-regression-diffs-shard-${{ matrix.shard }}
path: visual-regression-output/
retention-days: 14

- name: Upload Allure results
if: always()
uses: actions/upload-artifact@v4
with:
name: allure-results-shard-${{ matrix.shard }}
path: allure-results/
retention-days: 3

report:
name: Generate Allure Report
if: always()
needs: [visual]
runs-on: ubuntu-latest
steps:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- name: Download all Allure results
uses: actions/download-artifact@v4
with:
pattern: allure-results-shard-*
path: allure-results
merge-multiple: true

- name: Generate Allure HTML report
run: npx --yes allure generate allure-results -o allure-report

- name: Upload Allure report artifact
uses: actions/upload-artifact@v4
with:
name: allure-report
path: allure-report/
retention-days: 14

deploy-report:
name: Deploy Allure Report
if: always()
needs: [report]
uses: cloudscape-design/actions/.github/workflows/deploy.yml@main
secrets: inherit
with:
artifact-name: allure-report
deployment-path: allure-report
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ coverage
lib
# generated sources
src/index.ts
test/visual
test/definitions/index.ts
allure-results
allure-report
src/test-utils/dom/index.ts
src/test-utils/selectors
src/icon/generated
Expand Down
4 changes: 4 additions & 0 deletions build-tools/tasks/package-json.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ const devPagesPackageJson = generatePackageJson(path.join(workspace.targetPath,

const testDefinitionsPackageJson = generatePackageJson(path.join(workspace.targetPath, 'test-definitions'), {
name: '@cloudscape-design/test-definitions',
exports: {
'.': './index.js',
'./types': './types.js',
},
});

module.exports = parallel([
Expand Down
11 changes: 10 additions & 1 deletion build-tools/tasks/test-definitions.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
const { series } = require('gulp');
const execa = require('execa');
const { task } = require('../utils/gulp-utils');

module.exports = task('test-definitions', () =>
// Generate test/definitions/index.ts from the visual definition files before
// compiling, so that downstream consumers always get a complete barrel export.
const generate = task('test-definitions:generate', () =>
execa('node', ['build-tools/visual/generate-tests.js'], { stdio: 'inherit' })
);

const compile = task('test-definitions:compile', () =>
execa('tsc', ['-p', 'tsconfig.test-definitions.json'], { stdio: 'inherit' })
);

module.exports = series(generate, compile);
106 changes: 106 additions & 0 deletions build-tools/visual/generate-tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

/**
* Auto-generates:
* 1. test/visual/*.test.ts — one test runner per definition file (for Jest sharding)
* 2. test/definitions/index.ts — barrel that exports allSuites from all definitions
*
* Each definition file that exports a default TestSuite gets included.
* Helper files (those without a default export, e.g. shared utils like
* app-layout-responsive-tests.ts) are skipped.
*
* Run this script before executing the visual test suite:
* node build-tools/visual/generate-tests.js
*/
const fs = require('fs');
const path = require('path');

const definitionsDir = path.resolve(__dirname, '../../test/definitions/visual');
const testOutputDir = path.resolve(__dirname, '../../test/visual');
const indexOutputPath = path.resolve(__dirname, '../../test/definitions/index.ts');

// Files that are shared helpers (export named functions, not a default suite).
const HELPER_SUFFIXES = ['-tests'];

function isHelperFile(basename) {
return HELPER_SUFFIXES.some(suffix => basename.endsWith(suffix));
}

function toCamelCase(basename) {
return basename.replace(/-([a-z0-9])/g, (_, char) => char.toUpperCase());
}

function generate() {
// Ensure output directory exists
if (!fs.existsSync(testOutputDir)) {
fs.mkdirSync(testOutputDir, { recursive: true });
}

const files = fs.readdirSync(definitionsDir).filter(f => f.endsWith('.ts') && !f.endsWith('.d.ts'));

const suiteFiles = [];

for (const file of files) {
const basename = file.replace(/\.ts$/, '');

if (isHelperFile(basename)) {
continue;
}

// Verify the file has a default export by scanning for the pattern
const content = fs.readFileSync(path.join(definitionsDir, file), 'utf-8');
if (!content.includes('export default')) {
continue;
}

suiteFiles.push(basename);

// Generate test/visual/<basename>.test.ts
const testContent = [
'// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.',
'// SPDX-License-Identifier: Apache-2.0',
'// Auto-generated by build-tools/visual/generate-tests.js — do not edit manually.',
`import { runTestSuites } from '../definitions/utils';`,
`import suite from '../definitions/visual/${basename}';`,
'',
'runTestSuites([suite]);',
'',
].join('\n');

fs.writeFileSync(path.join(testOutputDir, `${basename}.test.ts`), testContent);
}

// Generate test/definitions/index.ts
// Sort by module path for consistent import ordering
suiteFiles.sort();

const imports = suiteFiles.map(basename => {
const varName = toCamelCase(basename);
return `import ${varName} from './visual/${basename}';`;
});

const arrayEntries = suiteFiles.map(basename => ` ${toCamelCase(basename)},`);

const indexContent = [
'// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.',
'// SPDX-License-Identifier: Apache-2.0',
'// Auto-generated by build-tools/visual/generate-tests.js — do not edit manually.',
'',
`import { TestSuite } from './types';`,
`export { TestSuite, TestDefinition, ScreenshotType, ScreenshotTestConfiguration } from './types';`,
...imports,
'',
'export const allSuites: TestSuite[] = [',
...arrayEntries,
'];',
'',
].join('\n');

fs.writeFileSync(indexOutputPath, indexContent);

console.log(`Generated ${suiteFiles.length} visual test files in test/visual/`);
console.log(`Generated test/definitions/index.ts with ${suiteFiles.length} suites`);
}

generate();
7 changes: 7 additions & 0 deletions build-tools/visual/global-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

module.exports = async () => {
const { startWebdriver } = require('@cloudscape-design/browser-test-tools/chrome-launcher');
await startWebdriver();
};
7 changes: 7 additions & 0 deletions build-tools/visual/global-teardown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

module.exports = () => {
const { shutdownWebdriver } = require('@cloudscape-design/browser-test-tools/chrome-launcher');
shutdownWebdriver();
};
Loading
Loading