From 168756dbeedc1e86eaff6fc35d5e03e224cf6bb6 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Wed, 20 May 2026 16:22:25 +0200 Subject: [PATCH 01/45] chore: Generate test definitions --- build-tools/tasks/index.js | 1 + build-tools/tasks/package-json.js | 5 ++++ build-tools/tasks/test-definitions.js | 8 ++++++ gulpfile.js | 4 ++- test/definitions/index.ts | 10 +++++++ test/definitions/types.ts | 27 ++++++++++++++++++ test/definitions/visual/action-card.ts | 32 ++++++++++++++++++++++ test/definitions/visual/alert.ts | 38 ++++++++++++++++++++++++++ tsconfig.test-definitions.json | 20 ++++++++++++++ 9 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 build-tools/tasks/test-definitions.js create mode 100644 test/definitions/index.ts create mode 100644 test/definitions/types.ts create mode 100644 test/definitions/visual/action-card.ts create mode 100644 test/definitions/visual/alert.ts create mode 100644 tsconfig.test-definitions.json diff --git a/build-tools/tasks/index.js b/build-tools/tasks/index.js index 982a8f6a72..d7db3c0784 100644 --- a/build-tools/tasks/index.js +++ b/build-tools/tasks/index.js @@ -21,4 +21,5 @@ module.exports = { themeableSource: require('./themeable-source'), bundleVendorFiles: require('./bundle-vendor-files'), sizeLimit: require('./size-limit'), + testDefinitions: require('./test-definitions'), }; diff --git a/build-tools/tasks/package-json.js b/build-tools/tasks/package-json.js index a0d076df35..a7c69a53f5 100644 --- a/build-tools/tasks/package-json.js +++ b/build-tools/tasks/package-json.js @@ -101,6 +101,10 @@ const devPagesPackageJson = generatePackageJson(path.join(workspace.targetPath, name: '@cloudscape-design/dev-pages', }); +const testDefinitionsPackageJson = generatePackageJson(path.join(workspace.targetPath, 'test-definitions'), { + name: '@cloudscape-design/test-definitions', +}); + module.exports = parallel([ ...themes.flatMap(theme => [ generatePackageJson( @@ -130,5 +134,6 @@ module.exports = parallel([ componentsThemeablePackageJson, copyTask('package-lock', ['package-lock.json'], path.join(workspace.targetPath, 'dev-pages', 'internal')), devPagesPackageJson, + testDefinitionsPackageJson, ]); module.exports.generatePackageJson = generatePackageJson; diff --git a/build-tools/tasks/test-definitions.js b/build-tools/tasks/test-definitions.js new file mode 100644 index 0000000000..db82381589 --- /dev/null +++ b/build-tools/tasks/test-definitions.js @@ -0,0 +1,8 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +const execa = require('execa'); +const { task } = require('../utils/gulp-utils'); + +module.exports = task('test-definitions', () => + execa('tsc', ['-p', 'tsconfig.test-definitions.json'], { stdio: 'inherit' }) +); diff --git a/gulpfile.js b/gulpfile.js index 9d290b74f1..6e3f389082 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -23,6 +23,7 @@ const { themeableSource, bundleVendorFiles, sizeLimit, + testDefinitions, } = require('./build-tools/tasks'); const quickBuild = series( @@ -35,7 +36,8 @@ const quickBuild = series( exports.clean = clean; exports['quick-build'] = quickBuild; exports.i18n = generateI18nMessages; -exports.build = series(quickBuild, parallel(buildPages, themeableSource, docs, sizeLimit)); +exports.build = series(quickBuild, parallel(buildPages, themeableSource, docs, sizeLimit, testDefinitions)); +exports['build:test-definitions'] = testDefinitions; exports.test = series(unit, integ, a11y); exports['test:unit'] = unit; exports['test:integ'] = integ; diff --git a/test/definitions/index.ts b/test/definitions/index.ts new file mode 100644 index 0000000000..91e90a89f7 --- /dev/null +++ b/test/definitions/index.ts @@ -0,0 +1,10 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Each component has its own test definition file. +// Import them here manually to form the full test suite. +import { TestSuite } from './types'; +import actionCard from './visual/action-card'; +import alert from './visual/alert'; + +export const allSuites: TestSuite[] = [actionCard, alert]; diff --git a/test/definitions/types.ts b/test/definitions/types.ts new file mode 100644 index 0000000000..8cdca9996a --- /dev/null +++ b/test/definitions/types.ts @@ -0,0 +1,27 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import type { ScreenshotPageObject } from '@cloudscape-design/browser-test-tools/page-objects'; + +export interface ScreenshotTestConfiguration { + width?: number; + height?: number; +} + +// 'screenshotArea' — captures the .screenshot-area element on the page. +// 'permutations' — captures the entire page and crops permutations out of it. +export type ScreenshotType = 'screenshotArea' | 'permutations'; + +export interface TestDefinition { + description: string; + path: string; + screenshotType: ScreenshotType; + queryParams?: Record; + configuration?: ScreenshotTestConfiguration; + setup?: (page: ScreenshotPageObject) => Promise; +} + +export interface TestSuite { + componentName?: string; + description: string; + tests: Array; +} diff --git a/test/definitions/visual/action-card.ts b/test/definitions/visual/action-card.ts new file mode 100644 index 0000000000..81426c4e26 --- /dev/null +++ b/test/definitions/visual/action-card.ts @@ -0,0 +1,32 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestSuite } from '../types'; + +const suite: TestSuite = { + description: 'Action card', + componentName: 'action-card', + tests: [ + { + description: 'permutations', + path: 'action-card/permutations', + screenshotType: 'permutations', + }, + { + description: 'variant permutations', + path: 'action-card/variant-permutations', + screenshotType: 'permutations', + }, + { + description: 'padding permutations', + path: 'action-card/padding-permutations', + screenshotType: 'permutations', + }, + { + description: 'simple', + path: 'action-card/simple', + screenshotType: 'screenshotArea', + }, + ], +}; + +export default suite; diff --git a/test/definitions/visual/alert.ts b/test/definitions/visual/alert.ts new file mode 100644 index 0000000000..3220c79e7e --- /dev/null +++ b/test/definitions/visual/alert.ts @@ -0,0 +1,38 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { TestSuite } from '../types'; + +const suite: TestSuite = { + description: 'Alert', + componentName: 'alert', + tests: [ + { + description: 'simple', + path: 'alert/simple', + screenshotType: 'screenshotArea', + }, + { + description: 'style custom page', + path: 'alert/style-custom-types', + screenshotType: 'screenshotArea', + }, + ...[600, 1280].map(width => ({ + description: `width ${width}px`, + tests: [ + { + description: 'permutations', + path: 'alert/permutations', + screenshotType: 'permutations' as const, + }, + { + description: 'custom types', + path: 'alert/style-custom-types', + screenshotType: 'screenshotArea' as const, + }, + ], + })), + ], +}; + +export default suite; diff --git a/tsconfig.test-definitions.json b/tsconfig.test-definitions.json new file mode 100644 index 0000000000..30e82b4047 --- /dev/null +++ b/tsconfig.test-definitions.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "lib": ["ES2021", "DOM"], + "target": "ES2019", + "types": [], + "module": "CommonJS", + "moduleResolution": "node", + "esModuleInterop": true, + "strict": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "inlineSources": true, + "rootDir": "test/definitions", + "outDir": "lib/test-definitions", + "skipLibCheck": true + }, + "include": ["test/definitions", "test/types.ts"], + "exclude": [] +} From 9c480cf77e7e7681b104d36ca68edf2deb60894e Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 11 May 2026 19:31:39 +0200 Subject: [PATCH 02/45] chore: Add visual regression testing --- .github/workflows/visual-regression.yml | 86 +++++++++++++++ build-tools/tasks/visual.js | 107 +++++++++++++++++++ build-tools/visual/global-setup.js | 4 + build-tools/visual/global-teardown.js | 4 + build-tools/visual/setup.js | 18 ++++ docs/RUNNING_TESTS.md | 59 +++++++++- eslint.config.mjs | 2 +- gulpfile.js | 2 + jest.visual.config.js | 25 +++++ package.json | 1 + test/visual/compare-screenshots.ts | 76 +++++++++++++ test/visual/definitions/alert.ts | 15 +++ test/visual/definitions/button.ts | 15 +++ test/visual/definitions/date-range-picker.ts | 40 +++++++ test/visual/definitions/index.ts | 12 +++ test/visual/definitions/table.ts | 15 +++ test/visual/types.ts | 23 ++++ test/visual/visual.test.ts | 6 ++ 18 files changed, 504 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/visual-regression.yml create mode 100644 build-tools/tasks/visual.js create mode 100644 build-tools/visual/global-setup.js create mode 100644 build-tools/visual/global-teardown.js create mode 100644 build-tools/visual/setup.js create mode 100644 jest.visual.config.js create mode 100644 test/visual/compare-screenshots.ts create mode 100644 test/visual/definitions/alert.ts create mode 100644 test/visual/definitions/button.ts create mode 100644 test/visual/definitions/date-range-picker.ts create mode 100644 test/visual/definitions/index.ts create mode 100644 test/visual/definitions/table.ts create mode 100644 test/visual/types.ts create mode 100644 test/visual/visual.test.ts diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml new file mode 100644 index 0000000000..a211178f4c --- /dev/null +++ b/.github/workflows/visual-regression.yml @@ -0,0 +1,86 @@ +name: Visual Regression Tests + +on: + pull_request: + branches: + - main + +defaults: + run: + shell: bash + +permissions: + id-token: write + contents: read + +jobs: + visual: + name: Visual regression + 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: 18 + cache: npm + + - name: Install ChromeDriver + run: npm install -g chromedriver + + # ── Build PR (test) pages ────────────────────────────────────────────── + # Install the PR's dependencies and build its pages. + - name: Install PR dependencies + run: npm ci + + - name: Build PR pages + run: npx gulp quick-build + env: + NODE_ENV: production + + - name: Bundle PR pages + run: node_modules/.bin/webpack --config pages/webpack.config.integ.cjs --output-path pages/lib/static-default + env: + NODE_ENV: production + + # ── Build baseline (main) pages ──────────────────────────────────────── + # 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 ci + 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 + + # ── Run tests ───────────────────────────────────────────────────────── + - name: Run visual regression tests + run: NODE_OPTIONS=--experimental-vm-modules node_modules/.bin/jest -c jest.visual.config.js + env: + TZ: UTC + + - name: Upload diff artifacts + if: failure() + uses: actions/upload-artifact@v4 + with: + name: visual-regression-diffs + path: visual-regression-output/ + retention-days: 14 diff --git a/build-tools/tasks/visual.js b/build-tools/tasks/visual.js new file mode 100644 index 0000000000..0864790afb --- /dev/null +++ b/build-tools/tasks/visual.js @@ -0,0 +1,107 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +const execa = require('execa'); +const path = require('path'); +const fs = require('fs'); +const waitOn = require('wait-on'); +const { task } = require('../utils/gulp-utils.js'); +const { parseArgs } = require('node:util'); + +const BASELINE_WORKTREE = '/tmp/visual-baseline'; +const BASELINE_OUTPUT = path.resolve('pages/lib/static-visual-baseline'); +const TEST_OUTPUT = path.resolve('pages/lib/static-default'); + +// Port assignments: +// 8080 — test build (PR / local changes) +// 8081 — baseline build (main branch) +const TEST_PORT = 8080; +const BASELINE_PORT = 8081; + +/** + * Serves a pre-built static directory using webpack-dev-server in static mode. + */ +function serveStatic(dir, port) { + return execa( + 'node_modules/.bin/webpack', + ['serve', '--config', 'pages/webpack.config.integ.cjs', '--port', String(port), '--static', dir, '--no-hot'], + { env: { ...process.env, NODE_ENV: 'development' } } + ); +} + +/** + * Builds the dev pages from the source tree at `cwd` into `outputPath`. + * Uses the node_modules present in `cwd`. + */ +async function buildPages(cwd, outputPath) { + await execa('npx', ['gulp', 'quick-build'], { + stdio: 'inherit', + cwd, + env: { ...process.env, NODE_ENV: 'production' }, + }); + await execa( + path.join(cwd, 'node_modules/.bin/webpack'), + ['--config', 'pages/webpack.config.integ.cjs', '--output-path', outputPath], + { stdio: 'inherit', cwd, env: { ...process.env, NODE_ENV: 'production' } } + ); +} + +module.exports = task('test:visual', async () => { + const options = { + shard: { type: 'string' }, + // Pass --skip-build to skip the build steps when artifacts are already present. + skipBuild: { type: 'boolean' }, + }; + const { shard, skipBuild } = parseArgs({ options, strict: false }).values; + + const cwd = process.cwd(); + + if (!skipBuild) { + // ── 1. Build the test (PR) pages ──────────────────────────────────────── + console.log('Building test pages (current branch)…'); + await buildPages(cwd, TEST_OUTPUT); + + // ── 2. Build the baseline (main) pages ────────────────────────────────── + // Create a worktree for origin/main so it gets its own node_modules. + // This correctly handles PRs that change package-lock.json: each side + // installs from its own lockfile. + console.log('Setting up baseline worktree from origin/main…'); + if (fs.existsSync(BASELINE_WORKTREE)) { + await execa('git', ['worktree', 'remove', '--force', BASELINE_WORKTREE]); + } + await execa('git', ['worktree', 'add', BASELINE_WORKTREE, 'origin/main']); + + try { + console.log('Installing baseline dependencies…'); + await execa('npm', ['ci'], { stdio: 'inherit', cwd: BASELINE_WORKTREE }); + + console.log('Building baseline pages (origin/main)…'); + await buildPages(BASELINE_WORKTREE, BASELINE_OUTPUT); + } finally { + await execa('git', ['worktree', 'remove', '--force', BASELINE_WORKTREE]); + } + } + + // ── 3. Start both static servers ────────────────────────────────────────── + console.log(`Starting test server on :${TEST_PORT} (${TEST_OUTPUT})…`); + const testServer = serveStatic(TEST_OUTPUT, TEST_PORT); + + console.log(`Starting baseline server on :${BASELINE_PORT} (${BASELINE_OUTPUT})…`); + const baselineServer = serveStatic(BASELINE_OUTPUT, BASELINE_PORT); + + try { + await waitOn({ resources: [`http://localhost:${TEST_PORT}`, `http://localhost:${BASELINE_PORT}`] }); + + // ── 4. Run visual tests ────────────────────────────────────────────────── + const jestArgs = ['-c', 'jest.visual.config.js']; + if (shard) { + jestArgs.push(`--shard=${shard}`); + } + await execa('jest', jestArgs, { + stdio: 'inherit', + env: { ...process.env, NODE_OPTIONS: '--experimental-vm-modules' }, + }); + } finally { + testServer.cancel(); + baselineServer.cancel(); + } +}); diff --git a/build-tools/visual/global-setup.js b/build-tools/visual/global-setup.js new file mode 100644 index 0000000000..075ee6e398 --- /dev/null +++ b/build-tools/visual/global-setup.js @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +const { startWebdriver } = require('@cloudscape-design/browser-test-tools/chrome-launcher'); +module.exports = () => startWebdriver(); diff --git a/build-tools/visual/global-teardown.js b/build-tools/visual/global-teardown.js new file mode 100644 index 0000000000..57ad21b454 --- /dev/null +++ b/build-tools/visual/global-teardown.js @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +const { shutdownWebdriver } = require('@cloudscape-design/browser-test-tools/chrome-launcher'); +module.exports = () => shutdownWebdriver(); diff --git a/build-tools/visual/setup.js b/build-tools/visual/setup.js new file mode 100644 index 0000000000..2625a43809 --- /dev/null +++ b/build-tools/visual/setup.js @@ -0,0 +1,18 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +/* global jest */ +const { configure } = require('@cloudscape-design/browser-test-tools/use-browser'); + +// The PR build (the code under test) is served on port 8080. +// The baseline build (main branch, same node_modules) is served on port 8081. +configure({ + browserName: 'ChromeHeadlessIntegration', + browserCreatorOptions: { + seleniumUrl: 'http://localhost:9515', + }, + webdriverOptions: { + baseUrl: 'http://localhost:8080', + }, +}); + +jest.retryTimes(2, { logErrorsBeforeRetry: true }); diff --git a/docs/RUNNING_TESTS.md b/docs/RUNNING_TESTS.md index 525cdf181d..c5c483e981 100644 --- a/docs/RUNNING_TESTS.md +++ b/docs/RUNNING_TESTS.md @@ -60,11 +60,60 @@ TZ=UTC npx jest -u -c jest.unit.config.js src/ ``` ## Visual Regression Tests -> **Note:** The components repository does not have visual regression tests on GitHub. This section applies to other repositories such as chat-components, code-view, chart-components, and board-components. +Visual regression tests run automatically when opening a pull request in GitHub (see `.github/workflows/visual-regression.yml`). -Visual regression tests for permutation pages run automatically when opening a pull request in GitHub. +They compare permutation pages between the PR build and a baseline build of `main`, both served locally in the same CI job. Each side installs from its own `package-lock.json` via a git worktree, so dependency changes in the PR are handled correctly and unpinned updates in sister repositories affect both sides equally. -To check results: look at the "Visual Regression Tests" action in the PR. The "Test for regressions" step logs which pages failed. For a full report, download the `visual-regression-snapshots-results` artifact from the action summary. +### How it works -If there are unexpected regressions, fix your pull request. -If the changes are expected, call this out in your pull request comments. +1. The PR pages are built and served on port 8080. +2. A git worktree of `origin/main` is created, its dependencies installed, and its pages built and served on port 8081. +3. The single test runner (`test/visual/visual.test.ts`) iterates over all test definitions, captures the `.screenshot-area` element from both servers for each test, and fails if any pixels differ. + +### Running locally + +``` +npm run test:visual +``` + +This handles the full build and comparison in one command. If both outputs are already built, skip the build step: + +``` +NODE_OPTIONS=--experimental-vm-modules node_modules/.bin/jest -c jest.visual.config.js +``` + +(Requires both servers to be running — start the PR build with `npm run start:integ` on port 8080 and the baseline build on port 8081, or set `NEW_HOST` / `OLD_HOST` env vars to point at different hosts.) + +### Adding tests for a new component + +Create `test/visual/definitions/.ts`: + +```ts +import { TestSuite } from '../types'; + +const suite: TestSuite = { + description: 'my-component', + tests: [ + { + description: 'permutations', + path: 'my-component/permutations', + }, + ], +}; + +export default suite; +``` + +Then import and add it to `test/visual/definitions/index.ts`: + +```ts +import myComponent from './my-component'; + +export const allSuites: TestSuite[] = [..., myComponent]; +``` + +### Reviewing failures + +If the CI job fails, download the `visual-regression-diffs` artifact from the Actions summary. + +If the diff is expected (intentional visual change), note it in your PR description. diff --git a/eslint.config.mjs b/eslint.config.mjs index 0d9423aa5b..f03eb9ce60 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -225,7 +225,7 @@ export default tsEslint.config( }, }, { - files: ['**/__integ__/**', '**/__motion__/**', '**/__a11y__/**'], + files: ['**/__integ__/**', '**/__motion__/**', '**/__a11y__/**', 'test/visual/**'], rules: { // useBrowser is not a hook 'react-hooks/rules-of-hooks': 'off', diff --git a/gulpfile.js b/gulpfile.js index 6e3f389082..221c065982 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -19,6 +19,7 @@ const { generateI18nMessages, integ, motion, + visual, copyFiles, themeableSource, bundleVendorFiles, @@ -43,6 +44,7 @@ exports['test:unit'] = unit; exports['test:integ'] = integ; exports['test:a11y'] = a11y; exports['test:motion'] = motion; +exports['test:visual'] = visual; exports.watch = () => { watch( diff --git a/jest.visual.config.js b/jest.visual.config.js new file mode 100644 index 0000000000..7f1d020880 --- /dev/null +++ b/jest.visual.config.js @@ -0,0 +1,25 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +const path = require('path'); +const os = require('os'); + +module.exports = { + verbose: true, + testEnvironment: 'node', + transform: { + '^.+\\.tsx?$': [ + 'ts-jest', + { + tsconfig: 'tsconfig.integ.json', + }, + ], + }, + reporters: ['default', 'github-actions'], + testTimeout: 120_000, // 2min — pages can be tall and slow to capture + maxWorkers: os.cpus().length * (process.env.GITHUB_ACTION ? 3 : 1), + globalSetup: '/build-tools/visual/global-setup.js', + globalTeardown: '/build-tools/visual/global-teardown.js', + setupFilesAfterEnv: [path.join(__dirname, 'build-tools', 'visual', 'setup.js')], + moduleFileExtensions: ['js', 'ts'], + testMatch: ['/test/visual/visual.test.ts'], +}; diff --git a/package.json b/package.json index 39b9206218..bfb1d807fd 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "test:a11y": "gulp test:a11y", "test:integ": "gulp test:integ", "test:motion": "gulp test:motion", + "test:visual": "gulp test:visual", "lint": "npm-run-all --parallel lint:*", "lint:eslint": "eslint .", "lint:stylelint": "stylelint --ignore-path .gitignore '{src,pages}/**/*.{css,scss}'", diff --git a/test/visual/compare-screenshots.ts b/test/visual/compare-screenshots.ts new file mode 100644 index 0000000000..51254363ac --- /dev/null +++ b/test/visual/compare-screenshots.ts @@ -0,0 +1,76 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import pixelmatch from 'pixelmatch'; +import { PNG } from 'pngjs'; + +import { ScreenshotPageObject } from '@cloudscape-design/browser-test-tools/page-objects'; +import useBrowser from '@cloudscape-design/browser-test-tools/use-browser'; + +import { TestDefinition, TestSuite } from './types'; + +const screenshotAreaSelector = '.screenshot-area'; +const defaultWindowSize = { width: 1600, height: 800 }; + +// NEW_HOST serves the PR's pages, OLD_HOST serves the baseline (main) pages. +const newHost = process.env.NEW_HOST || 'http://localhost:8080'; +const oldHost = process.env.OLD_HOST || 'http://localhost:8081'; + +async function captureScreenshot( + browser: WebdriverIO.Browser, + url: string, + setup?: (page: ScreenshotPageObject) => Promise +): Promise { + await browser.url(url); + const page = new ScreenshotPageObject(browser); + await page.waitForVisible(screenshotAreaSelector); + if (setup) { + await setup(page); + } + const { image } = await page.captureBySelector(screenshotAreaSelector); + return image; +} + +function buildUrl(host: string, path: string, queryParams?: Record): string { + const params = new URLSearchParams(queryParams); + const qs = params.toString(); + return `${host}/#/${path}${qs ? `?${qs}` : ''}`; +} + +function compareImages(newImage: PNG, oldImage: PNG): number { + const { width, height } = newImage; + const diff = new PNG({ width, height }); + return pixelmatch(newImage.data, oldImage.data, diff.data, width, height, { threshold: 0.1 }); +} + +function isTestDefinition(item: TestDefinition | TestSuite): item is TestDefinition { + return (item as TestDefinition).path !== undefined; +} + +export function runTestSuites(suites: Array) { + for (const item of suites) { + if (isTestDefinition(item)) { + runSingleTest(item); + } else { + describe(item.description, () => { + runTestSuites(item.tests); + }); + } + } +} + +function runSingleTest(testDef: TestDefinition) { + const windowSize = { ...defaultWindowSize, ...testDef.configuration }; + + test( + testDef.description, + useBrowser(windowSize, async browser => { + const newUrl = buildUrl(newHost, testDef.path, testDef.queryParams); + const newScreenshot = await captureScreenshot(browser, newUrl, testDef.setup); + + const oldUrl = buildUrl(oldHost, testDef.path, testDef.queryParams); + const oldScreenshot = await captureScreenshot(browser, oldUrl, testDef.setup); + const diffPixels = compareImages(newScreenshot, oldScreenshot); + expect(diffPixels).toBe(0); + }) + ); +} diff --git a/test/visual/definitions/alert.ts b/test/visual/definitions/alert.ts new file mode 100644 index 0000000000..37f0985f24 --- /dev/null +++ b/test/visual/definitions/alert.ts @@ -0,0 +1,15 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestSuite } from '../types'; + +const suite: TestSuite = { + description: 'alert', + tests: [ + { + description: 'permutations', + path: 'alert/permutations', + }, + ], +}; + +export default suite; diff --git a/test/visual/definitions/button.ts b/test/visual/definitions/button.ts new file mode 100644 index 0000000000..cb7d590a59 --- /dev/null +++ b/test/visual/definitions/button.ts @@ -0,0 +1,15 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestSuite } from '../types'; + +const suite: TestSuite = { + description: 'button', + tests: [ + { + description: 'permutations', + path: 'button/permutations', + }, + ], +}; + +export default suite; diff --git a/test/visual/definitions/date-range-picker.ts b/test/visual/definitions/date-range-picker.ts new file mode 100644 index 0000000000..6800eebc90 --- /dev/null +++ b/test/visual/definitions/date-range-picker.ts @@ -0,0 +1,40 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestSuite } from '../types'; + +const suite: TestSuite = { + description: 'date-range-picker', + tests: [ + { + description: 'with value', + path: 'date-range-picker/with-value', + }, + { + description: 'range calendar', + path: 'date-range-picker/range-calendar', + }, + { + description: 'month calendar permutations', + path: 'date-range-picker/month-calendar-permutations', + }, + { + description: 'year calendar permutations', + path: 'date-range-picker/year-calendar-permutations', + }, + { + description: 'in small viewport', + path: 'date-range-picker/small-viewport', + configuration: { width: 400 }, + }, + { + description: 'with dropdown open', + path: 'date-range-picker/with-value', + setup: async page => { + await page.click('[data-testid="date-range-picker-trigger"]'); + await page.waitForVisible('.awsui-context-content-header'); + }, + }, + ], +}; + +export default suite; diff --git a/test/visual/definitions/index.ts b/test/visual/definitions/index.ts new file mode 100644 index 0000000000..27f5c9e7ca --- /dev/null +++ b/test/visual/definitions/index.ts @@ -0,0 +1,12 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Each component has its own test definition file. +// Import them here manually to form the full test suite. +import { TestSuite } from '../types'; +import alert from './alert'; +import button from './button'; +import dateRangePicker from './date-range-picker'; +import table from './table'; + +export const allSuites: TestSuite[] = [alert, button, dateRangePicker, table]; diff --git a/test/visual/definitions/table.ts b/test/visual/definitions/table.ts new file mode 100644 index 0000000000..6529046d7b --- /dev/null +++ b/test/visual/definitions/table.ts @@ -0,0 +1,15 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestSuite } from '../types'; + +const suite: TestSuite = { + description: 'table', + tests: [ + { + description: 'permutations', + path: 'table/permutations', + }, + ], +}; + +export default suite; diff --git a/test/visual/types.ts b/test/visual/types.ts new file mode 100644 index 0000000000..7c4c90809a --- /dev/null +++ b/test/visual/types.ts @@ -0,0 +1,23 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { ScreenshotPageObject } from '@cloudscape-design/browser-test-tools/page-objects'; + +export interface ScreenshotTestConfiguration { + width?: number; + height?: number; +} + +export type TestCallback = (page: ScreenshotPageObject) => Promise; + +export interface TestDefinition { + description: string; + path: string; + queryParams?: Record; + configuration?: ScreenshotTestConfiguration; + setup?: TestCallback; +} + +export interface TestSuite { + description: string; + tests: Array; +} diff --git a/test/visual/visual.test.ts b/test/visual/visual.test.ts new file mode 100644 index 0000000000..06ef0fa8a4 --- /dev/null +++ b/test/visual/visual.test.ts @@ -0,0 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { runTestSuites } from './compare-screenshots'; +import { allSuites } from './definitions'; + +runTestSuites(allSuites); From 34e68b4622ff838aa6e472a36e6a4c3a5406ef44 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 11 May 2026 19:36:06 +0200 Subject: [PATCH 03/45] Install dependencies with npm i --- .github/workflows/visual-regression.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index a211178f4c..33e01c79fd 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -34,7 +34,7 @@ jobs: # ── Build PR (test) pages ────────────────────────────────────────────── # Install the PR's dependencies and build its pages. - name: Install PR dependencies - run: npm ci + run: npm i - name: Build PR pages run: npx gulp quick-build @@ -56,7 +56,7 @@ jobs: run: git worktree add /tmp/baseline origin/main - name: Install baseline dependencies - run: npm ci + run: npm i working-directory: /tmp/baseline - name: Build baseline pages From dd528816b11c4788382ee44cb5bd7b832c6fc3d6 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 11 May 2026 20:05:09 +0200 Subject: [PATCH 04/45] Use node 20 --- .github/workflows/visual-regression.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index 33e01c79fd..642ab08bac 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -25,7 +25,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 cache: npm - name: Install ChromeDriver From 253403c8a518e6af5f0ee014acc2c492501e25a2 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 11 May 2026 20:12:53 +0200 Subject: [PATCH 05/45] Add pixelmatch types --- package-lock.json | 11 +++++++++++ package.json | 1 + 2 files changed, 12 insertions(+) diff --git a/package-lock.json b/package-lock.json index 45f6aee09e..a6e3312478 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,6 +52,7 @@ "@types/jest": "^29.5.13", "@types/lodash": "^4.14.176", "@types/node": "^20.17.14", + "@types/pixelmatch": "^5.2.6", "@types/react": "^16.14.20", "@types/react-dom": "^16.9.14", "@types/react-is": "^18.2.0", @@ -4846,6 +4847,16 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/pixelmatch": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/@types/pixelmatch/-/pixelmatch-5.2.6.tgz", + "integrity": "sha512-wC83uexE5KGuUODn6zkm9gMzTwdY5L0chiK+VrKcDfEjzxh1uadlWTvOmAbCpnM9zx/Ww3f8uKlYQVnO/TrqVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/pngjs": { "version": "6.0.5", "dev": true, diff --git a/package.json b/package.json index bfb1d807fd..0d985a210e 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "@types/jest": "^29.5.13", "@types/lodash": "^4.14.176", "@types/node": "^20.17.14", + "@types/pixelmatch": "^5.2.6", "@types/react": "^16.14.20", "@types/react-dom": "^16.9.14", "@types/react-is": "^18.2.0", From 41fbf2a452dfab054b5d3f3b562a658c3b5511ba Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 11 May 2026 20:44:04 +0200 Subject: [PATCH 06/45] Install Chromedriver in CI --- .github/workflows/visual-regression.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index 642ab08bac..90af77becf 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -28,8 +28,10 @@ jobs: node-version: 20 cache: npm - - name: Install ChromeDriver - run: npm install -g chromedriver + - name: Setup Chrome and ChromeDriver + uses: browser-actions/setup-chrome@v1 + with: + chrome-version: stable # ── Build PR (test) pages ────────────────────────────────────────────── # Install the PR's dependencies and build its pages. From 5bb923292498959e835d01add00ba3770e1998b2 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 11 May 2026 21:01:25 +0200 Subject: [PATCH 07/45] Start servers --- .github/workflows/visual-regression.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index 90af77becf..7ee7d42669 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -74,6 +74,19 @@ jobs: NODE_ENV: production # ── Run tests ───────────────────────────────────────────────────────── + - name: Start test server (port 8080) + run: node_modules/.bin/webpack serve --config pages/webpack.config.integ.cjs --port 8080 --static pages/lib/static-default --no-hot & + env: + NODE_ENV: development + + - name: Start baseline server (port 8081) + run: node_modules/.bin/webpack serve --config pages/webpack.config.integ.cjs --port 8081 --static pages/lib/static-visual-baseline --no-hot & + env: + NODE_ENV: development + + - 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 env: From 9381e02da5045384aff399b1df6f1c562a21b975 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 11 May 2026 21:39:49 +0200 Subject: [PATCH 08/45] Install Puppeteer --- package-lock.json | 75 ++++++++++++++++++++++++++++++++++++++++++++--- package.json | 1 + 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index a6e3312478..7ab8555dfe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -97,6 +97,7 @@ "mockdate": "^3.0.5", "npm-run-all": "^4.1.5", "prettier": "^3.6.1", + "puppeteer-core": "^24.43.1", "react": "^16.14.0", "react-dom": "^16.14.0", "react-dom18": "npm:react-dom@^18.3.1", @@ -3522,9 +3523,9 @@ } }, "node_modules/@puppeteer/browsers": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.0.tgz", - "integrity": "sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA==", + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.2.tgz", + "integrity": "sha512-5EUZSUIc37H6aIXyWO0Z4y8NlF8NnjgmqeQgOGiswAU7pY0HOo16ho4+alIWmSfdZnjqBRawMsP3I5YqLSn6kw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -7273,6 +7274,20 @@ "node": ">=6.0" } }, + "node_modules/chromium-bidi": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz", + "integrity": "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, "node_modules/ci-info": { "version": "3.9.0", "dev": true, @@ -8789,6 +8804,13 @@ "dev": true, "license": "MIT" }, + "node_modules/devtools-protocol": { + "version": "0.0.1608973", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1608973.tgz", + "integrity": "sha512-Tpm17fxYzt+J7VrGdc1k8YdRqS3YV7se/M6KeemEqvUbq/n7At1rWVuXMxQgpWkdwSdIEKYbU//Bve+Shm4YNQ==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/diff-sequences": { "version": "29.6.3", "dev": true, @@ -17746,6 +17768,25 @@ "node": ">=6" } }, + "node_modules/puppeteer-core": { + "version": "24.43.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.43.1.tgz", + "integrity": "sha512-T5ScUMAsmhdNbgDR41AGESYeS6V9MSgetkSnVhhW+gXvzC42VesKCn5ld87gAZDJ6vLHL9GkRvY9WtQWSnwFbw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.13.2", + "chromium-bidi": "14.0.0", + "debug": "^4.4.3", + "devtools-protocol": "0.0.1608973", + "typed-query-selector": "^2.12.2", + "webdriver-bidi-protocol": "0.4.1", + "ws": "^8.20.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/pure-rand": { "version": "6.1.0", "dev": true, @@ -21158,6 +21199,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typed-query-selector": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz", + "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==", + "dev": true, + "license": "MIT" + }, "node_modules/typedarray": { "version": "0.0.6", "dev": true, @@ -21675,6 +21723,13 @@ "node": ">=18.20.0" } }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.1.tgz", + "integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/webdriver/node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -22281,7 +22336,9 @@ } }, "node_modules/ws": { - "version": "8.18.2", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", "dev": true, "license": "MIT", "engines": { @@ -22450,6 +22507,16 @@ "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 0d985a210e..22336b244c 100644 --- a/package.json +++ b/package.json @@ -121,6 +121,7 @@ "mockdate": "^3.0.5", "npm-run-all": "^4.1.5", "prettier": "^3.6.1", + "puppeteer-core": "^24.43.1", "react": "^16.14.0", "react-dom": "^16.14.0", "react-dom18": "npm:react-dom@^18.3.1", From c3493938b24df37085314a87e21f70a640d238d0 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 11 May 2026 22:19:28 +0200 Subject: [PATCH 09/45] Capture screenshot area or permutations --- test/visual/compare-screenshots.ts | 51 +++++++++++++++++--- test/visual/definitions/action-card.ts | 31 ++++++++++++ test/visual/definitions/alert.ts | 11 +++++ test/visual/definitions/button.ts | 15 ------ test/visual/definitions/date-range-picker.ts | 40 --------------- test/visual/definitions/index.ts | 7 +-- test/visual/definitions/table.ts | 15 ------ test/visual/types.ts | 5 ++ 8 files changed, 93 insertions(+), 82 deletions(-) create mode 100644 test/visual/definitions/action-card.ts delete mode 100644 test/visual/definitions/button.ts delete mode 100644 test/visual/definitions/date-range-picker.ts delete mode 100644 test/visual/definitions/table.ts diff --git a/test/visual/compare-screenshots.ts b/test/visual/compare-screenshots.ts index 51254363ac..8847e10d21 100644 --- a/test/visual/compare-screenshots.ts +++ b/test/visual/compare-screenshots.ts @@ -3,6 +3,7 @@ import pixelmatch from 'pixelmatch'; import { PNG } from 'pngjs'; +import { parsePng } from '@cloudscape-design/browser-test-tools/image-utils'; import { ScreenshotPageObject } from '@cloudscape-design/browser-test-tools/page-objects'; import useBrowser from '@cloudscape-design/browser-test-tools/use-browser'; @@ -15,19 +16,52 @@ const defaultWindowSize = { width: 1600, height: 800 }; const newHost = process.env.NEW_HOST || 'http://localhost:8080'; const oldHost = process.env.OLD_HOST || 'http://localhost:8081'; +/** + * Captures the .screenshot-area element on a focused page. + * Uses a standard ScreenshotPageObject (no forced scroll-and-merge). + */ +async function captureScreenshotArea(browser: WebdriverIO.Browser, url: string): Promise { + await browser.url(url); + const page = new ScreenshotPageObject(browser); + await page.waitForVisible(screenshotAreaSelector); + const { image } = await page.captureBySelector(screenshotAreaSelector); + return image; +} + +/** + * Captures the full page as a PNG for permutation pages. + * Uses fullPageScreenshot which handles pages taller than the viewport. + */ +async function capturePermutations(browser: WebdriverIO.Browser, url: string): Promise { + await browser.url(url); + const page = new ScreenshotPageObject(browser); + await page.waitForVisible(screenshotAreaSelector); + const base64 = await page.fullPageScreenshot(); + return parsePng(base64); +} + async function captureScreenshot( browser: WebdriverIO.Browser, url: string, + testDef: TestDefinition, setup?: (page: ScreenshotPageObject) => Promise ): Promise { - await browser.url(url); - const page = new ScreenshotPageObject(browser); - await page.waitForVisible(screenshotAreaSelector); if (setup) { + await browser.url(url); + const page = new ScreenshotPageObject(browser); + await page.waitForVisible(screenshotAreaSelector); await setup(page); + if (testDef.screenshotType === 'permutations') { + const base64 = await page.fullPageScreenshot(); + return parsePng(base64); + } + const { image } = await page.captureBySelector(screenshotAreaSelector); + return image; } - const { image } = await page.captureBySelector(screenshotAreaSelector); - return image; + if (testDef.screenshotType === 'permutations') { + return capturePermutations(browser, url); + } + return captureScreenshotArea(browser, url); } function buildUrl(host: string, path: string, queryParams?: Record): string { @@ -65,12 +99,15 @@ function runSingleTest(testDef: TestDefinition) { testDef.description, useBrowser(windowSize, async browser => { const newUrl = buildUrl(newHost, testDef.path, testDef.queryParams); - const newScreenshot = await captureScreenshot(browser, newUrl, testDef.setup); + const newScreenshot = await captureScreenshot(browser, newUrl, testDef, testDef.setup); const oldUrl = buildUrl(oldHost, testDef.path, testDef.queryParams); - const oldScreenshot = await captureScreenshot(browser, oldUrl, testDef.setup); + const oldScreenshot = await captureScreenshot(browser, oldUrl, testDef, testDef.setup); const diffPixels = compareImages(newScreenshot, oldScreenshot); expect(diffPixels).toBe(0); }) ); } + +// Export the capture functions for use in custom setup callbacks if needed. +export { captureScreenshotArea, capturePermutations }; diff --git a/test/visual/definitions/action-card.ts b/test/visual/definitions/action-card.ts new file mode 100644 index 0000000000..8730ac9504 --- /dev/null +++ b/test/visual/definitions/action-card.ts @@ -0,0 +1,31 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestSuite } from '../types'; + +const suite: TestSuite = { + description: 'action-card', + tests: [ + { + description: 'permutations', + path: 'action-card/permutations', + screenshotType: 'permutations', + }, + { + description: 'variant permutations', + path: 'action-card/variant-permutations', + screenshotType: 'permutations', + }, + { + description: 'padding permutations', + path: 'action-card/padding-permutations', + screenshotType: 'permutations', + }, + { + description: 'simple', + path: 'action-card/simple', + screenshotType: 'screenshotArea', + }, + ], +}; + +export default suite; diff --git a/test/visual/definitions/alert.ts b/test/visual/definitions/alert.ts index 37f0985f24..a272e4dfee 100644 --- a/test/visual/definitions/alert.ts +++ b/test/visual/definitions/alert.ts @@ -8,6 +8,17 @@ const suite: TestSuite = { { description: 'permutations', path: 'alert/permutations', + screenshotType: 'permutations', + }, + { + description: 'simple', + path: 'alert/simple', + screenshotType: 'screenshotArea', + }, + { + description: 'custom types', + path: 'alert/style-custom-types', + screenshotType: 'screenshotArea', }, ], }; diff --git a/test/visual/definitions/button.ts b/test/visual/definitions/button.ts deleted file mode 100644 index cb7d590a59..0000000000 --- a/test/visual/definitions/button.ts +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import { TestSuite } from '../types'; - -const suite: TestSuite = { - description: 'button', - tests: [ - { - description: 'permutations', - path: 'button/permutations', - }, - ], -}; - -export default suite; diff --git a/test/visual/definitions/date-range-picker.ts b/test/visual/definitions/date-range-picker.ts deleted file mode 100644 index 6800eebc90..0000000000 --- a/test/visual/definitions/date-range-picker.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import { TestSuite } from '../types'; - -const suite: TestSuite = { - description: 'date-range-picker', - tests: [ - { - description: 'with value', - path: 'date-range-picker/with-value', - }, - { - description: 'range calendar', - path: 'date-range-picker/range-calendar', - }, - { - description: 'month calendar permutations', - path: 'date-range-picker/month-calendar-permutations', - }, - { - description: 'year calendar permutations', - path: 'date-range-picker/year-calendar-permutations', - }, - { - description: 'in small viewport', - path: 'date-range-picker/small-viewport', - configuration: { width: 400 }, - }, - { - description: 'with dropdown open', - path: 'date-range-picker/with-value', - setup: async page => { - await page.click('[data-testid="date-range-picker-trigger"]'); - await page.waitForVisible('.awsui-context-content-header'); - }, - }, - ], -}; - -export default suite; diff --git a/test/visual/definitions/index.ts b/test/visual/definitions/index.ts index 27f5c9e7ca..55c1374550 100644 --- a/test/visual/definitions/index.ts +++ b/test/visual/definitions/index.ts @@ -4,9 +4,6 @@ // Each component has its own test definition file. // Import them here manually to form the full test suite. import { TestSuite } from '../types'; -import alert from './alert'; -import button from './button'; -import dateRangePicker from './date-range-picker'; -import table from './table'; +import actionCard from './action-card'; -export const allSuites: TestSuite[] = [alert, button, dateRangePicker, table]; +export const allSuites: TestSuite[] = [actionCard]; diff --git a/test/visual/definitions/table.ts b/test/visual/definitions/table.ts deleted file mode 100644 index 6529046d7b..0000000000 --- a/test/visual/definitions/table.ts +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import { TestSuite } from '../types'; - -const suite: TestSuite = { - description: 'table', - tests: [ - { - description: 'permutations', - path: 'table/permutations', - }, - ], -}; - -export default suite; diff --git a/test/visual/types.ts b/test/visual/types.ts index 7c4c90809a..f0fa665863 100644 --- a/test/visual/types.ts +++ b/test/visual/types.ts @@ -9,9 +9,14 @@ export interface ScreenshotTestConfiguration { export type TestCallback = (page: ScreenshotPageObject) => Promise; +// 'screenshotArea' — captures the .screenshot-area element on a focused page. +// 'permutations' — captures the entire page and crops permutations out of it. +export type ScreenshotType = 'screenshotArea' | 'permutations'; + export interface TestDefinition { description: string; path: string; + screenshotType: ScreenshotType; queryParams?: Record; configuration?: ScreenshotTestConfiguration; setup?: TestCallback; From 002f2f8a76b29c5fc6e394fc45ed1d83ecc21cd1 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Wed, 13 May 2026 11:54:13 +0200 Subject: [PATCH 10/45] Reuse build --- .github/workflows/deploy.yml | 9 +++++ .github/workflows/visual-regression.yml | 45 ++++++++++++------------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5593984949..01bb5a45d2 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -65,3 +65,12 @@ 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_name == 'pull_request' && 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 \ No newline at end of file diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index 7ee7d42669..f358626055 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -1,9 +1,12 @@ name: Visual Regression Tests on: - pull_request: - branches: - - main + workflow_call: + inputs: + pr-artifact-name: + description: Name of the GitHub Actions artifact containing the PR's built dev pages + required: true + type: string defaults: run: @@ -33,27 +36,21 @@ jobs: with: chrome-version: stable - # ── Build PR (test) pages ────────────────────────────────────────────── - # Install the PR's dependencies and build its pages. - - name: Install PR dependencies + - name: Install dependencies run: npm i - - name: Build PR pages - run: npx gulp quick-build - env: - NODE_ENV: production - - - name: Bundle PR pages - run: node_modules/.bin/webpack --config pages/webpack.config.integ.cjs --output-path pages/lib/static-default - env: - NODE_ENV: production - - # ── Build baseline (main) pages ──────────────────────────────────────── - # 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. + # ── PR pages: reuse the artifact built by the build job ─────────────── + - name: Download PR pages artifact + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.pr-artifact-name }} + path: pages/lib/static-default + + # ── Baseline (main) pages: build from origin/main ───────────────────── + # GitHub Actions artifacts are scoped to a workflow run, so there is no + # built-in way to reuse a previous main-branch artifact without the API. + # We build main locally instead — it shares node_modules with the PR + # checkout, so both sides resolve the same dependency versions. - name: Create baseline worktree from origin/main run: git worktree add /tmp/baseline origin/main @@ -73,8 +70,8 @@ jobs: env: NODE_ENV: production - # ── Run tests ───────────────────────────────────────────────────────── - - name: Start test server (port 8080) + # ── Start both servers and run tests ────────────────────────────────── + - name: Start PR server (port 8080) run: node_modules/.bin/webpack serve --config pages/webpack.config.integ.cjs --port 8080 --static pages/lib/static-default --no-hot & env: NODE_ENV: development From 707a8b221dbea71a1e752536cfa621149088dfe4 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Wed, 13 May 2026 12:16:27 +0200 Subject: [PATCH 11/45] Fix wofklow deps --- .github/workflows/visual-regression.yml | 98 ------------------------- 1 file changed, 98 deletions(-) delete mode 100644 .github/workflows/visual-regression.yml diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml deleted file mode 100644 index f358626055..0000000000 --- a/.github/workflows/visual-regression.yml +++ /dev/null @@ -1,98 +0,0 @@ -name: Visual Regression Tests - -on: - workflow_call: - inputs: - pr-artifact-name: - description: Name of the GitHub Actions artifact containing the PR's built dev pages - required: true - type: string - -defaults: - run: - shell: bash - -permissions: - id-token: write - contents: read - -jobs: - visual: - name: Visual regression - 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: Setup Chrome and ChromeDriver - uses: browser-actions/setup-chrome@v1 - with: - chrome-version: stable - - - name: Install dependencies - run: npm i - - # ── PR pages: reuse the artifact built by the build job ─────────────── - - name: Download PR pages artifact - uses: actions/download-artifact@v4 - with: - name: ${{ inputs.pr-artifact-name }} - path: pages/lib/static-default - - # ── Baseline (main) pages: build from origin/main ───────────────────── - # GitHub Actions artifacts are scoped to a workflow run, so there is no - # built-in way to reuse a previous main-branch artifact without the API. - # We build main locally instead — it shares node_modules with the PR - # checkout, so both sides resolve the same dependency versions. - - 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 - - # ── Start both servers and run tests ────────────────────────────────── - - name: Start PR server (port 8080) - run: node_modules/.bin/webpack serve --config pages/webpack.config.integ.cjs --port 8080 --static pages/lib/static-default --no-hot & - env: - NODE_ENV: development - - - name: Start baseline server (port 8081) - run: node_modules/.bin/webpack serve --config pages/webpack.config.integ.cjs --port 8081 --static pages/lib/static-visual-baseline --no-hot & - env: - NODE_ENV: development - - - 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 - env: - TZ: UTC - - - name: Upload diff artifacts - if: failure() - uses: actions/upload-artifact@v4 - with: - name: visual-regression-diffs - path: visual-regression-output/ - retention-days: 14 From 798ccfe022c2ea5d5ecc1d97373fc33a7892d07a Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Wed, 13 May 2026 12:36:12 +0200 Subject: [PATCH 12/45] Again --- .github/workflows/visual-regression.yml | 120 ++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 .github/workflows/visual-regression.yml diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml new file mode 100644 index 0000000000..bbe372b7d6 --- /dev/null +++ b/.github/workflows/visual-regression.yml @@ -0,0 +1,120 @@ +name: Visual Regression Tests + +on: + workflow_run: + workflows: + - Build, lint and test + types: + - completed + +defaults: + run: + shell: bash + +permissions: + id-token: write + contents: read + actions: read + +jobs: + visual: + name: Visual regression + # Only run on PRs from the same repo (not forks), and only when the build succeeded. + if: > + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.head_repository.full_name == github.repository + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + # Check out the PR head commit, not the merge commit that workflow_run uses. + ref: ${{ github.event.workflow_run.head_sha }} + fetch-depth: 0 + + - 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 ci + + # ── PR pages: download the artifact produced by the build workflow ───── + - name: Download PR pages artifact + uses: actions/github-script@v7 + with: + script: | + const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: ${{ github.event.workflow_run.id }}, + }); + const artifact = artifacts.data.artifacts.find(a => a.name === 'dev-pages-react18'); + if (!artifact) throw new Error('dev-pages-react18 artifact not found'); + const download = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: artifact.id, + archive_format: 'zip', + }); + const fs = require('fs'); + fs.writeFileSync('/tmp/dev-pages.zip', Buffer.from(download.data)); + + - name: Extract PR pages artifact + run: | + mkdir -p pages/lib/static-default + unzip /tmp/dev-pages.zip -d pages/lib/static-default + + # ── Baseline (main) pages: build from origin/main ───────────────────── + - name: Create baseline worktree from origin/main + run: git worktree add /tmp/baseline origin/main + + - name: Install baseline dependencies + run: npm ci + 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 + + # ── Start both servers and run tests ────────────────────────────────── + - name: Start PR server (port 8080) + run: node_modules/.bin/webpack serve --config pages/webpack.config.integ.cjs --port 8080 --static pages/lib/static-default --no-hot & + env: + NODE_ENV: development + + - name: Start baseline server (port 8081) + run: node_modules/.bin/webpack serve --config pages/webpack.config.integ.cjs --port 8081 --static pages/lib/static-visual-baseline --no-hot & + env: + NODE_ENV: development + + - 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 + env: + TZ: UTC + + - name: Upload diff artifacts + if: failure() + uses: actions/upload-artifact@v4 + with: + name: visual-regression-diffs + path: visual-regression-output/ + retention-days: 14 From 89023affbf854ea063b26082aa8fbc51a15b828b Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Wed, 13 May 2026 12:50:50 +0200 Subject: [PATCH 13/45] Fix npm install command --- .github/workflows/visual-regression.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index bbe372b7d6..858a35a01e 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -44,7 +44,7 @@ jobs: chrome-version: stable - name: Install dependencies - run: npm ci + run: npm i # ── PR pages: download the artifact produced by the build workflow ───── - name: Download PR pages artifact @@ -77,7 +77,7 @@ jobs: run: git worktree add /tmp/baseline origin/main - name: Install baseline dependencies - run: npm ci + run: npm i working-directory: /tmp/baseline - name: Build baseline pages From 3a890120aff0a120ce0a0063079cad4c99ce369e Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Wed, 13 May 2026 13:09:44 +0200 Subject: [PATCH 14/45] Wait only for the build --- .github/workflows/build-lint-test.yml | 14 ++++------- .github/workflows/build.yml | 31 +++++++++++++++++++++++++ .github/workflows/visual-regression.yml | 2 +- 3 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build-lint-test.yml b/.github/workflows/build-lint-test.yml index d9e7487d3d..553dbc4d02 100644 --- a/.github/workflows/build-lint-test.yml +++ b/.github/workflows/build-lint-test.yml @@ -1,15 +1,11 @@ name: Build, lint and test on: - pull_request: - branches: - - main - merge_group: - branches: - - main - push: - branches: - - main + workflow_run: + workflows: + - Build + types: + - completed permissions: id-token: write diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000..9ddca205f7 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,31 @@ +name: Build + +on: + pull_request: + branches: + - main + merge_group: + branches: + - main + push: + branches: + - main + +permissions: + id-token: write + actions: read + contents: read + security-events: write + +jobs: + build: + name: build${{ matrix.react != 16 && format(' (React {0})', matrix.react) || '' }} + strategy: + matrix: + react: [16, 18] + uses: cloudscape-design/actions/.github/workflows/build-lint-test.yml@main + secrets: inherit + with: + artifact-path: pages/lib/static-default + artifact-name: dev-pages-react${{ matrix.react }} + react-version: ${{ matrix.react }} diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index 858a35a01e..224e7bb94a 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -3,7 +3,7 @@ name: Visual Regression Tests on: workflow_run: workflows: - - Build, lint and test + - Build types: - completed From a3738d290f821d0740f3684bbf1190109c325291 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Wed, 13 May 2026 14:05:37 +0200 Subject: [PATCH 15/45] Revert "Wait only for the build" This reverts commit 23a62c420b23f83e90190060649e3b6895e84e50. --- .github/workflows/build-lint-test.yml | 14 +++++++---- .github/workflows/build.yml | 31 ------------------------- .github/workflows/visual-regression.yml | 2 +- 3 files changed, 10 insertions(+), 37 deletions(-) delete mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build-lint-test.yml b/.github/workflows/build-lint-test.yml index 553dbc4d02..d9e7487d3d 100644 --- a/.github/workflows/build-lint-test.yml +++ b/.github/workflows/build-lint-test.yml @@ -1,11 +1,15 @@ name: Build, lint and test on: - workflow_run: - workflows: - - Build - types: - - completed + pull_request: + branches: + - main + merge_group: + branches: + - main + push: + branches: + - main permissions: id-token: write diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 9ddca205f7..0000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Build - -on: - pull_request: - branches: - - main - merge_group: - branches: - - main - push: - branches: - - main - -permissions: - id-token: write - actions: read - contents: read - security-events: write - -jobs: - build: - name: build${{ matrix.react != 16 && format(' (React {0})', matrix.react) || '' }} - strategy: - matrix: - react: [16, 18] - uses: cloudscape-design/actions/.github/workflows/build-lint-test.yml@main - secrets: inherit - with: - artifact-path: pages/lib/static-default - artifact-name: dev-pages-react${{ matrix.react }} - react-version: ${{ matrix.react }} diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index 224e7bb94a..858a35a01e 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -3,7 +3,7 @@ name: Visual Regression Tests on: workflow_run: workflows: - - Build + - Build, lint and test types: - completed From 2954192017ceda56193f813b9e0d9929161e82be Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Wed, 13 May 2026 14:05:44 +0200 Subject: [PATCH 16/45] Revert "Fix npm install command" This reverts commit ffb88c1feb2fa9076c3463d4800c921f05fa66d7. --- .github/workflows/visual-regression.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index 858a35a01e..bbe372b7d6 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -44,7 +44,7 @@ jobs: chrome-version: stable - name: Install dependencies - run: npm i + run: npm ci # ── PR pages: download the artifact produced by the build workflow ───── - name: Download PR pages artifact @@ -77,7 +77,7 @@ jobs: run: git worktree add /tmp/baseline origin/main - name: Install baseline dependencies - run: npm i + run: npm ci working-directory: /tmp/baseline - name: Build baseline pages From a31a44bbf0e4e564dbb181d6c1ee19ea7990bf8b Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Wed, 13 May 2026 14:05:46 +0200 Subject: [PATCH 17/45] Revert "Again" This reverts commit 263a253c442ce54ea385252ef8291cce91839172. --- .github/workflows/visual-regression.yml | 120 ------------------------ 1 file changed, 120 deletions(-) delete mode 100644 .github/workflows/visual-regression.yml diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml deleted file mode 100644 index bbe372b7d6..0000000000 --- a/.github/workflows/visual-regression.yml +++ /dev/null @@ -1,120 +0,0 @@ -name: Visual Regression Tests - -on: - workflow_run: - workflows: - - Build, lint and test - types: - - completed - -defaults: - run: - shell: bash - -permissions: - id-token: write - contents: read - actions: read - -jobs: - visual: - name: Visual regression - # Only run on PRs from the same repo (not forks), and only when the build succeeded. - if: > - github.event.workflow_run.event == 'pull_request' && - github.event.workflow_run.conclusion == 'success' && - github.event.workflow_run.head_repository.full_name == github.repository - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - # Check out the PR head commit, not the merge commit that workflow_run uses. - ref: ${{ github.event.workflow_run.head_sha }} - fetch-depth: 0 - - - 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 ci - - # ── PR pages: download the artifact produced by the build workflow ───── - - name: Download PR pages artifact - uses: actions/github-script@v7 - with: - script: | - const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: ${{ github.event.workflow_run.id }}, - }); - const artifact = artifacts.data.artifacts.find(a => a.name === 'dev-pages-react18'); - if (!artifact) throw new Error('dev-pages-react18 artifact not found'); - const download = await github.rest.actions.downloadArtifact({ - owner: context.repo.owner, - repo: context.repo.repo, - artifact_id: artifact.id, - archive_format: 'zip', - }); - const fs = require('fs'); - fs.writeFileSync('/tmp/dev-pages.zip', Buffer.from(download.data)); - - - name: Extract PR pages artifact - run: | - mkdir -p pages/lib/static-default - unzip /tmp/dev-pages.zip -d pages/lib/static-default - - # ── Baseline (main) pages: build from origin/main ───────────────────── - - name: Create baseline worktree from origin/main - run: git worktree add /tmp/baseline origin/main - - - name: Install baseline dependencies - run: npm ci - 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 - - # ── Start both servers and run tests ────────────────────────────────── - - name: Start PR server (port 8080) - run: node_modules/.bin/webpack serve --config pages/webpack.config.integ.cjs --port 8080 --static pages/lib/static-default --no-hot & - env: - NODE_ENV: development - - - name: Start baseline server (port 8081) - run: node_modules/.bin/webpack serve --config pages/webpack.config.integ.cjs --port 8081 --static pages/lib/static-visual-baseline --no-hot & - env: - NODE_ENV: development - - - 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 - env: - TZ: UTC - - - name: Upload diff artifacts - if: failure() - uses: actions/upload-artifact@v4 - with: - name: visual-regression-diffs - path: visual-regression-output/ - retention-days: 14 From db1ac57a2782d25592da1f35fe5839014f8539d5 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Wed, 13 May 2026 14:05:52 +0200 Subject: [PATCH 18/45] Revert "Fix wofklow deps" This reverts commit b1eee0b5ba3596aa6e90c6be8e917edf7af898ba. --- .github/workflows/visual-regression.yml | 98 +++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 .github/workflows/visual-regression.yml diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml new file mode 100644 index 0000000000..f358626055 --- /dev/null +++ b/.github/workflows/visual-regression.yml @@ -0,0 +1,98 @@ +name: Visual Regression Tests + +on: + workflow_call: + inputs: + pr-artifact-name: + description: Name of the GitHub Actions artifact containing the PR's built dev pages + required: true + type: string + +defaults: + run: + shell: bash + +permissions: + id-token: write + contents: read + +jobs: + visual: + name: Visual regression + 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: Setup Chrome and ChromeDriver + uses: browser-actions/setup-chrome@v1 + with: + chrome-version: stable + + - name: Install dependencies + run: npm i + + # ── PR pages: reuse the artifact built by the build job ─────────────── + - name: Download PR pages artifact + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.pr-artifact-name }} + path: pages/lib/static-default + + # ── Baseline (main) pages: build from origin/main ───────────────────── + # GitHub Actions artifacts are scoped to a workflow run, so there is no + # built-in way to reuse a previous main-branch artifact without the API. + # We build main locally instead — it shares node_modules with the PR + # checkout, so both sides resolve the same dependency versions. + - 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 + + # ── Start both servers and run tests ────────────────────────────────── + - name: Start PR server (port 8080) + run: node_modules/.bin/webpack serve --config pages/webpack.config.integ.cjs --port 8080 --static pages/lib/static-default --no-hot & + env: + NODE_ENV: development + + - name: Start baseline server (port 8081) + run: node_modules/.bin/webpack serve --config pages/webpack.config.integ.cjs --port 8081 --static pages/lib/static-visual-baseline --no-hot & + env: + NODE_ENV: development + + - 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 + env: + TZ: UTC + + - name: Upload diff artifacts + if: failure() + uses: actions/upload-artifact@v4 + with: + name: visual-regression-diffs + path: visual-regression-output/ + retention-days: 14 From 436304f640e0784e1668e580ff0a8fe3f5342f7b Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Wed, 13 May 2026 14:05:53 +0200 Subject: [PATCH 19/45] Revert "Reuse build" This reverts commit 1f71f10a69ef3b99627b3f8bec675dc7a2e4802f. --- .github/workflows/visual-regression.yml | 45 +++++++++++++------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index f358626055..7ee7d42669 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -1,12 +1,9 @@ name: Visual Regression Tests on: - workflow_call: - inputs: - pr-artifact-name: - description: Name of the GitHub Actions artifact containing the PR's built dev pages - required: true - type: string + pull_request: + branches: + - main defaults: run: @@ -36,21 +33,27 @@ jobs: with: chrome-version: stable - - name: Install dependencies + # ── Build PR (test) pages ────────────────────────────────────────────── + # Install the PR's dependencies and build its pages. + - name: Install PR dependencies run: npm i - # ── PR pages: reuse the artifact built by the build job ─────────────── - - name: Download PR pages artifact - uses: actions/download-artifact@v4 - with: - name: ${{ inputs.pr-artifact-name }} - path: pages/lib/static-default - - # ── Baseline (main) pages: build from origin/main ───────────────────── - # GitHub Actions artifacts are scoped to a workflow run, so there is no - # built-in way to reuse a previous main-branch artifact without the API. - # We build main locally instead — it shares node_modules with the PR - # checkout, so both sides resolve the same dependency versions. + - name: Build PR pages + run: npx gulp quick-build + env: + NODE_ENV: production + + - name: Bundle PR pages + run: node_modules/.bin/webpack --config pages/webpack.config.integ.cjs --output-path pages/lib/static-default + env: + NODE_ENV: production + + # ── Build baseline (main) pages ──────────────────────────────────────── + # 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 @@ -70,8 +73,8 @@ jobs: env: NODE_ENV: production - # ── Start both servers and run tests ────────────────────────────────── - - name: Start PR server (port 8080) + # ── Run tests ───────────────────────────────────────────────────────── + - name: Start test server (port 8080) run: node_modules/.bin/webpack serve --config pages/webpack.config.integ.cjs --port 8080 --static pages/lib/static-default --no-hot & env: NODE_ENV: development From 3dff97fdbd195416770af498b470d254a508feec Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Wed, 13 May 2026 15:20:37 +0200 Subject: [PATCH 20/45] Include alert tests --- test/visual/definitions/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/visual/definitions/index.ts b/test/visual/definitions/index.ts index 55c1374550..318ce7c68b 100644 --- a/test/visual/definitions/index.ts +++ b/test/visual/definitions/index.ts @@ -5,5 +5,6 @@ // Import them here manually to form the full test suite. import { TestSuite } from '../types'; import actionCard from './action-card'; +import alert from './alert'; -export const allSuites: TestSuite[] = [actionCard]; +export const allSuites: TestSuite[] = [actionCard, alert]; From 10e97bddc3b93a1b0134ae812dfc05523635178d Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Wed, 13 May 2026 15:21:01 +0200 Subject: [PATCH 21/45] Add more alert tests --- test/visual/definitions/alert.ts | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/test/visual/definitions/alert.ts b/test/visual/definitions/alert.ts index a272e4dfee..30792c0d2d 100644 --- a/test/visual/definitions/alert.ts +++ b/test/visual/definitions/alert.ts @@ -1,25 +1,36 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 + import { TestSuite } from '../types'; const suite: TestSuite = { description: 'alert', tests: [ - { - description: 'permutations', - path: 'alert/permutations', - screenshotType: 'permutations', - }, { description: 'simple', path: 'alert/simple', screenshotType: 'screenshotArea', }, { - description: 'custom types', + description: 'style custom page', path: 'alert/style-custom-types', screenshotType: 'screenshotArea', }, + ...[600, 1280].map(width => ({ + description: `width ${width}px`, + tests: [ + { + description: 'permutations', + path: 'alert/permutations', + screenshotType: 'permutations' as const, + }, + { + description: 'custom types', + path: 'alert/style-custom-types', + screenshotType: 'screenshotArea' as const, + }, + ], + })), ], }; From c228a0417e73a08eedc4f414405090c595d2080b Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Wed, 13 May 2026 15:46:26 +0200 Subject: [PATCH 22/45] chore: Export visual test definitions --- build-tools/tasks/index.js | 5 +++++ build-tools/tasks/visual-definitions.js | 8 ++++++++ gulpfile.js | 2 ++ test/visual/types.ts | 2 +- tsconfig.visual-definitions.json | 19 +++++++++++++++++++ 5 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 build-tools/tasks/visual-definitions.js create mode 100644 tsconfig.visual-definitions.json diff --git a/build-tools/tasks/index.js b/build-tools/tasks/index.js index d7db3c0784..413695be14 100644 --- a/build-tools/tasks/index.js +++ b/build-tools/tasks/index.js @@ -21,5 +21,10 @@ module.exports = { themeableSource: require('./themeable-source'), bundleVendorFiles: require('./bundle-vendor-files'), sizeLimit: require('./size-limit'), +<<<<<<< HEAD testDefinitions: require('./test-definitions'), +======= + visual: require('./visual'), + visualDefinitions: require('./visual-definitions'), +>>>>>>> 4213557f5 (chore: Export visual test definitions) }; diff --git a/build-tools/tasks/visual-definitions.js b/build-tools/tasks/visual-definitions.js new file mode 100644 index 0000000000..34dfe16adf --- /dev/null +++ b/build-tools/tasks/visual-definitions.js @@ -0,0 +1,8 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +const execa = require('execa'); +const { task } = require('../utils/gulp-utils'); + +module.exports = task('visual-definitions', () => + execa('tsc', ['-p', 'tsconfig.visual-definitions.json'], { stdio: 'inherit' }) +); diff --git a/gulpfile.js b/gulpfile.js index 221c065982..09e0833868 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -20,6 +20,7 @@ const { integ, motion, visual, + visualDefinitions, copyFiles, themeableSource, bundleVendorFiles, @@ -45,6 +46,7 @@ exports['test:integ'] = integ; exports['test:a11y'] = a11y; exports['test:motion'] = motion; exports['test:visual'] = visual; +exports['build:visual-definitions'] = visualDefinitions; exports.watch = () => { watch( diff --git a/test/visual/types.ts b/test/visual/types.ts index f0fa665863..c4c23b622f 100644 --- a/test/visual/types.ts +++ b/test/visual/types.ts @@ -1,6 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { ScreenshotPageObject } from '@cloudscape-design/browser-test-tools/page-objects'; +import type { ScreenshotPageObject } from '@cloudscape-design/browser-test-tools/page-objects'; export interface ScreenshotTestConfiguration { width?: number; diff --git a/tsconfig.visual-definitions.json b/tsconfig.visual-definitions.json new file mode 100644 index 0000000000..103f77ee47 --- /dev/null +++ b/tsconfig.visual-definitions.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "lib": ["ES2021"], + "target": "ES2019", + "types": [], + "module": "CommonJS", + "moduleResolution": "node", + "esModuleInterop": true, + "strict": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "inlineSources": true, + "rootDir": "test/visual", + "outDir": "lib/visual-test-definitions" + }, + "include": ["test/visual/definitions", "test/visual/types.ts"], + "exclude": [] +} From ac894c623666862e5829ef6714d3fa49aaaa1a73 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 18 May 2026 15:17:49 +0200 Subject: [PATCH 23/45] Use the quick build --- .github/workflows/visual-regression.yml | 31 ++++++++++++++++--------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index 7ee7d42669..35ae55ba9b 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -13,6 +13,11 @@ permissions: id-token: write contents: read +inputs: + pr-artifact-name: + description: 'Name of the artifact containing PR pages (built by quick-build job). If not provided, pages will be built locally.' + required: false + jobs: visual: name: Visual regression @@ -33,20 +38,24 @@ jobs: with: chrome-version: stable - # ── Build PR (test) pages ────────────────────────────────────────────── - # Install the PR's dependencies and build its pages. - - name: Install PR dependencies - run: npm i - - - name: Build PR pages - run: npx gulp quick-build + # ── Build or download PR (test) pages ───────────────────────────────── + # When called from deploy.yml with pr-artifact-name, download the artifact. + # When run standalone (no pr-artifact-name provided), build the pages locally. + - name: Build PR pages locally + if: ${{ !inputs.pr-artifact-name }} + run: | + npm i + npx gulp quick-build + node_modules/.bin/webpack --config pages/webpack.config.integ.cjs --output-path pages/lib/static-default env: NODE_ENV: production - - name: Bundle PR pages - run: node_modules/.bin/webpack --config pages/webpack.config.integ.cjs --output-path pages/lib/static-default - env: - NODE_ENV: production + - name: Download PR pages artifact + if: ${{ inputs.pr-artifact-name }} + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.pr-artifact-name }} + path: pages/lib/static-default # ── Build baseline (main) pages ──────────────────────────────────────── # Use a git worktree so the baseline has its own directory and its own From 9ed4c537a33b2b2936e82a3f8d6c2d69139bbf02 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 18 May 2026 18:16:20 +0200 Subject: [PATCH 24/45] Fix tsconfig --- tsconfig.visual-definitions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.visual-definitions.json b/tsconfig.visual-definitions.json index 103f77ee47..a31ccd02d3 100644 --- a/tsconfig.visual-definitions.json +++ b/tsconfig.visual-definitions.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "lib": ["ES2021"], + "lib": ["ES2021", "DOM"], "target": "ES2019", "types": [], "module": "CommonJS", From d583942b53ae8c6f87e401a2719d98940e989246 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 18 May 2026 18:20:07 +0200 Subject: [PATCH 25/45] Fix workflow --- .github/workflows/visual-regression.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index 35ae55ba9b..c6d1bf031a 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -4,6 +4,12 @@ on: pull_request: branches: - main + workflow_call: + inputs: + pr-artifact-name: + description: 'Name of the artifact containing PR pages (built by quick-build job). If not provided, pages will be built locally.' + required: false + type: string defaults: run: @@ -13,11 +19,6 @@ permissions: id-token: write contents: read -inputs: - pr-artifact-name: - description: 'Name of the artifact containing PR pages (built by quick-build job). If not provided, pages will be built locally.' - required: false - jobs: visual: name: Visual regression From 143e8958a4be174e54ee93419065ff4021dc4fcd Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 18 May 2026 18:41:58 +0200 Subject: [PATCH 26/45] Fix workflow --- .github/workflows/visual-regression.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index c6d1bf031a..d3b36a4ac9 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -39,13 +39,15 @@ jobs: with: chrome-version: stable + - name: Install dependencies + run: npm i + # ── Build or download PR (test) pages ───────────────────────────────── # When called from deploy.yml with pr-artifact-name, download the artifact. # When run standalone (no pr-artifact-name provided), build the pages locally. - name: Build PR pages locally if: ${{ !inputs.pr-artifact-name }} run: | - npm i npx gulp quick-build node_modules/.bin/webpack --config pages/webpack.config.integ.cjs --output-path pages/lib/static-default env: From 2e2aa0e8a215c9609e50c82b35953d42563f2a15 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 18 May 2026 19:01:10 +0200 Subject: [PATCH 27/45] Use serve --- .github/workflows/visual-regression.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index d3b36a4ac9..742cc72f68 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -87,12 +87,12 @@ jobs: # ── Run tests ───────────────────────────────────────────────────────── - name: Start test server (port 8080) - run: node_modules/.bin/webpack serve --config pages/webpack.config.integ.cjs --port 8080 --static pages/lib/static-default --no-hot & + run: npx serve --no-clipboard --listen 8080 pages/lib/static-default & env: NODE_ENV: development - name: Start baseline server (port 8081) - run: node_modules/.bin/webpack serve --config pages/webpack.config.integ.cjs --port 8081 --static pages/lib/static-visual-baseline --no-hot & + run: npx serve --no-clipboard --listen 8081 pages/lib/static-visual-baseline & env: NODE_ENV: development From 113f6dc302828d79f39364cf3ef963be6978eeab Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 18 May 2026 19:10:56 +0200 Subject: [PATCH 28/45] Run tests on Safari --- .github/workflows/visual-regression.yml | 27 ++++++++++++++++--------- build-tools/visual/global-setup.js | 20 ++++++++++++++++-- build-tools/visual/global-teardown.js | 12 +++++++++-- build-tools/visual/setup.js | 6 ++++-- 4 files changed, 49 insertions(+), 16 deletions(-) diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index 742cc72f68..39cea38599 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -21,8 +21,16 @@ permissions: jobs: visual: - name: Visual regression - runs-on: ubuntu-latest + name: Visual regression (${{ matrix.browser }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - browser: chrome + os: ubuntu-latest + - browser: safari + os: macos-latest steps: - uses: actions/checkout@v4 with: @@ -35,16 +43,19 @@ jobs: cache: npm - name: Setup Chrome and ChromeDriver + if: matrix.browser == 'chrome' uses: browser-actions/setup-chrome@v1 with: chrome-version: stable + - name: Enable SafariDriver + if: matrix.browser == 'safari' + run: sudo safaridriver --enable + - name: Install dependencies run: npm i # ── Build or download PR (test) pages ───────────────────────────────── - # When called from deploy.yml with pr-artifact-name, download the artifact. - # When run standalone (no pr-artifact-name provided), build the pages locally. - name: Build PR pages locally if: ${{ !inputs.pr-artifact-name }} run: | @@ -61,11 +72,6 @@ jobs: path: pages/lib/static-default # ── Build baseline (main) pages ──────────────────────────────────────── - # 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 @@ -103,11 +109,12 @@ jobs: run: NODE_OPTIONS=--experimental-vm-modules node_modules/.bin/jest -c jest.visual.config.js env: TZ: UTC + BROWSER: ${{ matrix.browser }} - name: Upload diff artifacts if: failure() uses: actions/upload-artifact@v4 with: - name: visual-regression-diffs + name: visual-regression-diffs-${{ matrix.browser }} path: visual-regression-output/ retention-days: 14 diff --git a/build-tools/visual/global-setup.js b/build-tools/visual/global-setup.js index 075ee6e398..f27f7d04cb 100644 --- a/build-tools/visual/global-setup.js +++ b/build-tools/visual/global-setup.js @@ -1,4 +1,20 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -const { startWebdriver } = require('@cloudscape-design/browser-test-tools/chrome-launcher'); -module.exports = () => startWebdriver(); +const { spawn } = require('child_process'); +const waitOn = require('wait-on'); + +let driverProcess; + +module.exports = async () => { + if (process.env.BROWSER === 'safari') { + driverProcess = spawn('safaridriver', ['--port', '4444']); + driverProcess.on('error', err => { + throw err; + }); + await waitOn({ resources: ['http-get://localhost:4444/status'], timeout: 10000 }); + } else { + const { startWebdriver } = require('@cloudscape-design/browser-test-tools/chrome-launcher'); + await startWebdriver(); + } + global.__DRIVER_PROCESS__ = driverProcess; +}; diff --git a/build-tools/visual/global-teardown.js b/build-tools/visual/global-teardown.js index 57ad21b454..366c3f7660 100644 --- a/build-tools/visual/global-teardown.js +++ b/build-tools/visual/global-teardown.js @@ -1,4 +1,12 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -const { shutdownWebdriver } = require('@cloudscape-design/browser-test-tools/chrome-launcher'); -module.exports = () => shutdownWebdriver(); +module.exports = () => { + if (process.env.BROWSER === 'safari') { + if (global.__DRIVER_PROCESS__) { + global.__DRIVER_PROCESS__.kill(); + } + } else { + const { shutdownWebdriver } = require('@cloudscape-design/browser-test-tools/chrome-launcher'); + shutdownWebdriver(); + } +}; diff --git a/build-tools/visual/setup.js b/build-tools/visual/setup.js index 2625a43809..79050cab6a 100644 --- a/build-tools/visual/setup.js +++ b/build-tools/visual/setup.js @@ -3,12 +3,14 @@ /* global jest */ const { configure } = require('@cloudscape-design/browser-test-tools/use-browser'); +const isSafari = process.env.BROWSER === 'safari'; + // The PR build (the code under test) is served on port 8080. // The baseline build (main branch, same node_modules) is served on port 8081. configure({ - browserName: 'ChromeHeadlessIntegration', + browserName: isSafari ? 'Safari' : 'ChromeHeadlessIntegration', browserCreatorOptions: { - seleniumUrl: 'http://localhost:9515', + seleniumUrl: isSafari ? 'http://localhost:4444' : 'http://localhost:9515', }, webdriverOptions: { baseUrl: 'http://localhost:8080', From 65537b8d8e1a3e31186ce0c8d10cd85a3d138eec Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 18 May 2026 19:27:05 +0200 Subject: [PATCH 29/45] Prevent visual regression workflow from running twice --- .github/workflows/visual-regression.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index 39cea38599..09685d9bf5 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -1,9 +1,6 @@ name: Visual Regression Tests on: - pull_request: - branches: - - main workflow_call: inputs: pr-artifact-name: From 6e4cce88ba6a65829288970010724f4f455118d8 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 18 May 2026 19:45:39 +0200 Subject: [PATCH 30/45] Reuse baseline build across browsers --- .github/workflows/visual-regression.yml | 99 ++++++++++++++++++------- 1 file changed, 73 insertions(+), 26 deletions(-) diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index 09685d9bf5..2c9e633b05 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -17,17 +17,10 @@ permissions: contents: read jobs: - visual: - name: Visual regression (${{ matrix.browser }}) - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - browser: chrome - os: ubuntu-latest - - browser: safari - os: macos-latest + # 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: @@ -39,20 +32,12 @@ jobs: node-version: 20 cache: npm - - name: Setup Chrome and ChromeDriver - if: matrix.browser == 'chrome' - uses: browser-actions/setup-chrome@v1 - with: - chrome-version: stable - - - name: Enable SafariDriver - if: matrix.browser == 'safari' - run: sudo safaridriver --enable - - name: Install dependencies run: npm i # ── Build or download PR (test) pages ───────────────────────────────── + # When called from deploy.yml with pr-artifact-name, download the artifact. + # When run standalone (no pr-artifact-name provided), build the pages locally. - name: Build PR pages locally if: ${{ !inputs.pr-artifact-name }} run: | @@ -69,6 +54,11 @@ jobs: path: pages/lib/static-default # ── Build baseline (main) pages ──────────────────────────────────────── + # 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 @@ -83,21 +73,78 @@ jobs: 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 + run: node_modules/.bin/webpack --config pages/webpack.config.integ.cjs --output-path 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 + + - name: Upload PR pages artifact + if: ${{ !inputs.pr-artifact-name }} + uses: actions/upload-artifact@v4 + with: + name: visual-pr-pages + path: pages/lib/static-default + retention-days: 1 + + visual: + name: Visual regression (${{ matrix.browser }}) + needs: build-baseline + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - browser: chrome + os: ubuntu-latest + - browser: safari + os: macos-latest + 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 + if: matrix.browser == 'chrome' + uses: browser-actions/setup-chrome@v1 + with: + chrome-version: stable + + - name: Enable SafariDriver + if: matrix.browser == 'safari' + run: sudo safaridriver --enable + + - name: Install dependencies + run: npm i + + - name: Download PR pages artifact + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.pr-artifact-name || 'visual-pr-pages' }} + path: pages/lib/static-default + + - name: Download baseline artifact + uses: actions/download-artifact@v4 + with: + name: visual-baseline-pages + path: pages/lib/static-visual-baseline + # ── Run tests ───────────────────────────────────────────────────────── - name: Start test server (port 8080) run: npx serve --no-clipboard --listen 8080 pages/lib/static-default & - env: - NODE_ENV: development - name: Start baseline server (port 8081) run: npx serve --no-clipboard --listen 8081 pages/lib/static-visual-baseline & - env: - NODE_ENV: development - name: Wait for servers to be ready run: node_modules/.bin/wait-on http://localhost:8080 http://localhost:8081 From 71786e573121df0a64bc10616297c73518fd5aec Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 18 May 2026 19:46:53 +0200 Subject: [PATCH 31/45] Limit Safari concurrency --- jest.visual.config.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jest.visual.config.js b/jest.visual.config.js index 7f1d020880..0d3abc58fd 100644 --- a/jest.visual.config.js +++ b/jest.visual.config.js @@ -16,7 +16,9 @@ module.exports = { }, reporters: ['default', 'github-actions'], testTimeout: 120_000, // 2min — pages can be tall and slow to capture - maxWorkers: os.cpus().length * (process.env.GITHUB_ACTION ? 3 : 1), + // Safari's WebDriver only supports one concurrent session, so tests must run serially. + // Chrome can run multiple workers to speed things up. + maxWorkers: process.env.BROWSER === 'safari' ? 1 : os.cpus().length * (process.env.GITHUB_ACTION ? 3 : 1), globalSetup: '/build-tools/visual/global-setup.js', globalTeardown: '/build-tools/visual/global-teardown.js', setupFilesAfterEnv: [path.join(__dirname, 'build-tools', 'visual', 'setup.js')], From 312a363807c716e2af5eca9c6ec598938ca7f83d Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 18 May 2026 20:05:39 +0200 Subject: [PATCH 32/45] Fix workflow --- .github/workflows/deploy.yml | 3 ++- .github/workflows/visual-regression.yml | 34 +++++++++++++++---------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 01bb5a45d2..8705c33048 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -73,4 +73,5 @@ jobs: uses: ./.github/workflows/visual-regression.yml secrets: inherit with: - pr-artifact-name: dev-pages-react18 \ No newline at end of file + pr-artifact-name: dev-pages-react18 + caller-run-id: ${{ github.run_id }} \ No newline at end of file diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index 2c9e633b05..bedbb5435e 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -7,6 +7,10 @@ on: description: 'Name of the artifact containing PR pages (built by quick-build job). If not provided, pages will be built locally.' required: false type: string + caller-run-id: + description: 'The run ID of the calling workflow, used to download artifacts it uploaded.' + required: false + type: string defaults: run: @@ -15,9 +19,11 @@ defaults: permissions: id-token: write contents: read + actions: read jobs: # Build the baseline (main branch) pages once and share them across all browser jobs. + # Also stages the PR pages artifact within this run so all jobs use the same run context. build-baseline: name: Build baseline pages runs-on: ubuntu-latest @@ -35,9 +41,10 @@ jobs: - name: Install dependencies run: npm i - # ── Build or download PR (test) pages ───────────────────────────────── - # When called from deploy.yml with pr-artifact-name, download the artifact. - # When run standalone (no pr-artifact-name provided), build the pages locally. + # ── Stage PR (test) pages ────────────────────────────────────────────── + # Download from the caller's run (deploy.yml) or build locally. + # Either way, re-upload as 'visual-pr-pages' within this run so the + # matrix jobs can download it without needing cross-run artifact access. - name: Build PR pages locally if: ${{ !inputs.pr-artifact-name }} run: | @@ -46,12 +53,21 @@ jobs: env: NODE_ENV: production - - name: Download PR pages artifact + - name: Download PR pages artifact from caller run if: ${{ inputs.pr-artifact-name }} 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: Upload PR pages artifact (for matrix jobs) + uses: actions/upload-artifact@v4 + with: + name: visual-pr-pages + path: pages/lib/static-default + retention-days: 1 # ── Build baseline (main) pages ──────────────────────────────────────── # Use a git worktree so the baseline has its own directory and its own @@ -85,14 +101,6 @@ jobs: path: pages/lib/static-visual-baseline retention-days: 1 - - name: Upload PR pages artifact - if: ${{ !inputs.pr-artifact-name }} - uses: actions/upload-artifact@v4 - with: - name: visual-pr-pages - path: pages/lib/static-default - retention-days: 1 - visual: name: Visual regression (${{ matrix.browser }}) needs: build-baseline @@ -130,7 +138,7 @@ jobs: - name: Download PR pages artifact uses: actions/download-artifact@v4 with: - name: ${{ inputs.pr-artifact-name || 'visual-pr-pages' }} + name: visual-pr-pages path: pages/lib/static-default - name: Download baseline artifact From ad9052decacee3465de2f14b0a30fb0d48ee5d12 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 18 May 2026 20:27:19 +0200 Subject: [PATCH 33/45] Fix workflows --- .github/workflows/deploy.yml | 57 +++++++++++++++++++++++- .github/workflows/visual-regression.yml | 59 ++++++++++--------------- 2 files changed, 78 insertions(+), 38 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8705c33048..8a3b4459b7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -53,6 +53,58 @@ jobs: name: dev-pages-react${{ matrix.react }} path: pages/lib/static-default + # Build the baseline (main branch) pages in parallel with quick-build. + # The result is passed to the visual regression workflow so it doesn't + # need to rebuild the baseline for each browser. + build-baseline: + name: Build baseline pages + if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} + 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 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 + deploy: needs: quick-build name: deploy${{ matrix.react != 16 && format(' (React {0})', matrix.react) || '' }} @@ -68,10 +120,11 @@ jobs: visual: name: Visual regression - needs: quick-build + needs: [quick-build, build-baseline] if: ${{ github.event_name == 'pull_request' && 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 - caller-run-id: ${{ github.run_id }} \ No newline at end of file + baseline-artifact-name: visual-baseline-pages + caller-run-id: ${{ github.run_id }} diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index bedbb5435e..5fab2453e2 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -7,6 +7,10 @@ on: description: 'Name of the artifact containing PR pages (built by quick-build job). If not provided, pages will be built locally.' required: false type: string + baseline-artifact-name: + description: 'Name of the artifact containing baseline pages (built by build-baseline job in the caller workflow).' + required: true + type: string caller-run-id: description: 'The run ID of the calling workflow, used to download artifacts it uploaded.' required: false @@ -22,15 +26,13 @@ permissions: actions: read jobs: - # Build the baseline (main branch) pages once and share them across all browser jobs. - # Also stages the PR pages artifact within this run so all jobs use the same run context. - build-baseline: - name: Build baseline pages + # Stage the PR pages within this run so matrix jobs can download them without + # needing cross-run artifact access. Runs in parallel with stage-baseline. + stage-pr-pages: + name: Stage PR pages runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v4 @@ -41,10 +43,6 @@ jobs: - name: Install dependencies run: npm i - # ── Stage PR (test) pages ────────────────────────────────────────────── - # Download from the caller's run (deploy.yml) or build locally. - # Either way, re-upload as 'visual-pr-pages' within this run so the - # matrix jobs can download it without needing cross-run artifact access. - name: Build PR pages locally if: ${{ !inputs.pr-artifact-name }} run: | @@ -69,32 +67,21 @@ jobs: path: pages/lib/static-default retention-days: 1 - # ── Build baseline (main) pages ──────────────────────────────────────── - # 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 pages/lib/static-visual-baseline - working-directory: /tmp/baseline - env: - NODE_ENV: production + # Stage the baseline pages within this run so matrix jobs can download them + # without needing cross-run artifact access. Runs in parallel with stage-pr-pages. + stage-baseline: + name: Stage baseline pages + runs-on: ubuntu-latest + steps: + - name: Download baseline artifact from caller run + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.baseline-artifact-name }} + path: pages/lib/static-visual-baseline + github-token: ${{ github.token }} + run-id: ${{ inputs.caller-run-id }} - - name: Upload baseline artifact + - name: Upload baseline artifact (for matrix jobs) uses: actions/upload-artifact@v4 with: name: visual-baseline-pages @@ -103,7 +90,7 @@ jobs: visual: name: Visual regression (${{ matrix.browser }}) - needs: build-baseline + needs: [stage-pr-pages, stage-baseline] runs-on: ${{ matrix.os }} strategy: fail-fast: false From 8abbc3f9f75ee22f37b687aeca7bd1f1abf0b740 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 18 May 2026 21:22:00 +0200 Subject: [PATCH 34/45] Fix workflow --- .github/workflows/visual-regression.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index 5fab2453e2..9b68239196 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -14,7 +14,7 @@ on: caller-run-id: description: 'The run ID of the calling workflow, used to download artifacts it uploaded.' required: false - type: string + type: number defaults: run: From 6c81a0432e7778b3cbcb8367ff5d8f1e5044657f Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 18 May 2026 21:41:50 +0200 Subject: [PATCH 35/45] Fix workflow --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8a3b4459b7..45f7ac8fbb 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -121,7 +121,7 @@ jobs: visual: name: Visual regression needs: [quick-build, build-baseline] - if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }} + if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} uses: ./.github/workflows/visual-regression.yml secrets: inherit with: From e253fc8023542cbdd4d23904312534ae1cfe36d6 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 18 May 2026 21:47:43 +0200 Subject: [PATCH 36/45] Fix workflow --- .github/workflows/visual-regression.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index 9b68239196..5fab2453e2 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -14,7 +14,7 @@ on: caller-run-id: description: 'The run ID of the calling workflow, used to download artifacts it uploaded.' required: false - type: number + type: string defaults: run: From 8c01dcf3a23c21ea15e2bd0b60d3ffe74b621efa Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Tue, 19 May 2026 01:56:14 +0200 Subject: [PATCH 37/45] Fix workflow --- .github/workflows/deploy.yml | 55 +---------------------- .github/workflows/visual-regression.yml | 59 ++++++++++++++++++------- 2 files changed, 43 insertions(+), 71 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 45f7ac8fbb..2add3494cd 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -53,58 +53,6 @@ jobs: name: dev-pages-react${{ matrix.react }} path: pages/lib/static-default - # Build the baseline (main branch) pages in parallel with quick-build. - # The result is passed to the visual regression workflow so it doesn't - # need to rebuild the baseline for each browser. - build-baseline: - name: Build baseline pages - if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} - 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 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 - deploy: needs: quick-build name: deploy${{ matrix.react != 16 && format(' (React {0})', matrix.react) || '' }} @@ -120,11 +68,10 @@ jobs: visual: name: Visual regression - needs: [quick-build, build-baseline] + 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 - baseline-artifact-name: visual-baseline-pages caller-run-id: ${{ github.run_id }} diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index 5fab2453e2..8c3c44c0ff 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -7,10 +7,6 @@ on: description: 'Name of the artifact containing PR pages (built by quick-build job). If not provided, pages will be built locally.' required: false type: string - baseline-artifact-name: - description: 'Name of the artifact containing baseline pages (built by build-baseline job in the caller workflow).' - required: true - type: string caller-run-id: description: 'The run ID of the calling workflow, used to download artifacts it uploaded.' required: false @@ -27,7 +23,7 @@ permissions: jobs: # Stage the PR pages within this run so matrix jobs can download them without - # needing cross-run artifact access. Runs in parallel with stage-baseline. + # needing cross-run artifact access. Runs in parallel with build-baseline. stage-pr-pages: name: Stage PR pages runs-on: ubuntu-latest @@ -67,21 +63,50 @@ jobs: path: pages/lib/static-default retention-days: 1 - # Stage the baseline pages within this run so matrix jobs can download them - # without needing cross-run artifact access. Runs in parallel with stage-pr-pages. - stage-baseline: - name: Stage baseline pages + # Build the baseline (main branch) pages once and share them across all browser jobs. + # Runs in parallel with stage-pr-pages. + build-baseline: + name: Build baseline pages runs-on: ubuntu-latest steps: - - name: Download baseline artifact from caller run - uses: actions/download-artifact@v4 + - uses: actions/checkout@v4 with: - name: ${{ inputs.baseline-artifact-name }} - path: pages/lib/static-visual-baseline - github-token: ${{ github.token }} - run-id: ${{ inputs.caller-run-id }} + 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 pages/lib/static-visual-baseline + working-directory: /tmp/baseline + env: + NODE_ENV: production - - name: Upload baseline artifact (for matrix jobs) + - name: Upload baseline artifact uses: actions/upload-artifact@v4 with: name: visual-baseline-pages @@ -90,7 +115,7 @@ jobs: visual: name: Visual regression (${{ matrix.browser }}) - needs: [stage-pr-pages, stage-baseline] + needs: [stage-pr-pages, build-baseline] runs-on: ${{ matrix.os }} strategy: fail-fast: false From 2c300e93a3f5126ec40033fa5d58de5df23db270 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Tue, 19 May 2026 02:07:06 +0200 Subject: [PATCH 38/45] Fix workflow --- .github/workflows/visual-regression.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index 8c3c44c0ff..4f62e0fa16 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -101,7 +101,7 @@ jobs: NODE_ENV: production - name: Bundle baseline pages - run: node_modules/.bin/webpack --config pages/webpack.config.integ.cjs --output-path pages/lib/static-visual-baseline + 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 From d6023a704877f24c8cfa222d212f2503473ee1f3 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Tue, 19 May 2026 12:01:18 +0200 Subject: [PATCH 39/45] Add delay between retries in Safari --- build-tools/visual/setup.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/build-tools/visual/setup.js b/build-tools/visual/setup.js index 79050cab6a..a9013a3d61 100644 --- a/build-tools/visual/setup.js +++ b/build-tools/visual/setup.js @@ -1,6 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -/* global jest */ +/* global jest, afterEach */ const { configure } = require('@cloudscape-design/browser-test-tools/use-browser'); const isSafari = process.env.BROWSER === 'safari'; @@ -18,3 +18,11 @@ configure({ }); jest.retryTimes(2, { logErrorsBeforeRetry: true }); + +// Safari's WebDriver needs a moment to fully release a session before a new one +// can be created. Without this delay, retried tests hit "already paired" errors. +if (isSafari) { + afterEach(async () => { + await new Promise(resolve => setTimeout(resolve, 1000)); + }); +} From 7b7cdfd41fcebe49cb2488eb53666f2bddf2feab Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Tue, 19 May 2026 12:21:48 +0200 Subject: [PATCH 40/45] Fine tune Safari delay --- build-tools/visual/setup.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build-tools/visual/setup.js b/build-tools/visual/setup.js index a9013a3d61..e6d166d7f3 100644 --- a/build-tools/visual/setup.js +++ b/build-tools/visual/setup.js @@ -1,6 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -/* global jest, afterEach */ +/* global jest, beforeEach */ const { configure } = require('@cloudscape-design/browser-test-tools/use-browser'); const isSafari = process.env.BROWSER === 'safari'; @@ -20,9 +20,9 @@ configure({ jest.retryTimes(2, { logErrorsBeforeRetry: true }); // Safari's WebDriver needs a moment to fully release a session before a new one -// can be created. Without this delay, retried tests hit "already paired" errors. +// can be created. Without this delay, the next test hits "already paired" errors. if (isSafari) { - afterEach(async () => { - await new Promise(resolve => setTimeout(resolve, 1000)); + beforeEach(async () => { + await new Promise(resolve => setTimeout(resolve, 5000)); }); } From e72d1b9b8154d8ae4c0c04cbeaebd2d06a89d0c6 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Tue, 19 May 2026 14:00:13 +0200 Subject: [PATCH 41/45] Release Safari session between tests --- build-tools/visual/global-setup.js | 6 ++---- build-tools/visual/setup.js | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/build-tools/visual/global-setup.js b/build-tools/visual/global-setup.js index f27f7d04cb..b4acf683c9 100644 --- a/build-tools/visual/global-setup.js +++ b/build-tools/visual/global-setup.js @@ -3,18 +3,16 @@ const { spawn } = require('child_process'); const waitOn = require('wait-on'); -let driverProcess; - module.exports = async () => { if (process.env.BROWSER === 'safari') { - driverProcess = spawn('safaridriver', ['--port', '4444']); + const driverProcess = spawn('safaridriver', ['--port', '4444']); driverProcess.on('error', err => { throw err; }); await waitOn({ resources: ['http-get://localhost:4444/status'], timeout: 10000 }); + global.__DRIVER_PROCESS__ = driverProcess; } else { const { startWebdriver } = require('@cloudscape-design/browser-test-tools/chrome-launcher'); await startWebdriver(); } - global.__DRIVER_PROCESS__ = driverProcess; }; diff --git a/build-tools/visual/setup.js b/build-tools/visual/setup.js index e6d166d7f3..d8e7ab5779 100644 --- a/build-tools/visual/setup.js +++ b/build-tools/visual/setup.js @@ -1,6 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 /* global jest, beforeEach */ +const { spawn } = require('child_process'); +const waitOn = require('wait-on'); const { configure } = require('@cloudscape-design/browser-test-tools/use-browser'); const isSafari = process.env.BROWSER === 'safari'; @@ -19,10 +21,18 @@ configure({ jest.retryTimes(2, { logErrorsBeforeRetry: true }); -// Safari's WebDriver needs a moment to fully release a session before a new one -// can be created. Without this delay, the next test hits "already paired" errors. +// Local safaridriver only supports one session at a time and doesn't reliably +// release the session lock between tests. Restarting the process before each +// test guarantees a clean state. This is not needed with BrowserStack. if (isSafari) { + let safariDriverProcess; + beforeEach(async () => { - await new Promise(resolve => setTimeout(resolve, 5000)); + if (safariDriverProcess) { + safariDriverProcess.kill(); + await new Promise(resolve => setTimeout(resolve, 500)); + } + safariDriverProcess = spawn('safaridriver', ['--port', '4444']); + await waitOn({ resources: ['http-get://localhost:4444/status'], timeout: 10000 }); }); } From 441f5a69834f16ca0cc126621dd7d05784d1eeb1 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Tue, 19 May 2026 17:07:46 +0200 Subject: [PATCH 42/45] Do not retry with Safari --- build-tools/visual/setup.js | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/build-tools/visual/setup.js b/build-tools/visual/setup.js index d8e7ab5779..c63a18416f 100644 --- a/build-tools/visual/setup.js +++ b/build-tools/visual/setup.js @@ -1,8 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -/* global jest, beforeEach */ -const { spawn } = require('child_process'); -const waitOn = require('wait-on'); +/* global jest */ const { configure } = require('@cloudscape-design/browser-test-tools/use-browser'); const isSafari = process.env.BROWSER === 'safari'; @@ -19,20 +17,9 @@ configure({ }, }); -jest.retryTimes(2, { logErrorsBeforeRetry: true }); - -// Local safaridriver only supports one session at a time and doesn't reliably -// release the session lock between tests. Restarting the process before each -// test guarantees a clean state. This is not needed with BrowserStack. -if (isSafari) { - let safariDriverProcess; - - beforeEach(async () => { - if (safariDriverProcess) { - safariDriverProcess.kill(); - await new Promise(resolve => setTimeout(resolve, 500)); - } - safariDriverProcess = spawn('safaridriver', ['--port', '4444']); - await waitOn({ resources: ['http-get://localhost:4444/status'], timeout: 10000 }); - }); +// Retries help with flaky tests, but Safari's single-session constraint means +// a retry can hit "already paired" if the previous attempt's session hasn't +// fully released. Disable retries for Safari. +if (!isSafari) { + jest.retryTimes(2, { logErrorsBeforeRetry: true }); } From d72fb4c61901039d1c8ee0592061e0a19542e50e Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Tue, 19 May 2026 18:43:41 +0200 Subject: [PATCH 43/45] Change target directory --- build-tools/tasks/index.js | 5 -- build-tools/tasks/visual-definitions.js | 8 -- build-tools/tasks/visual.js | 107 ------------------------ tsconfig.visual-definitions.json | 2 +- 4 files changed, 1 insertion(+), 121 deletions(-) delete mode 100644 build-tools/tasks/visual-definitions.js delete mode 100644 build-tools/tasks/visual.js diff --git a/build-tools/tasks/index.js b/build-tools/tasks/index.js index 413695be14..d7db3c0784 100644 --- a/build-tools/tasks/index.js +++ b/build-tools/tasks/index.js @@ -21,10 +21,5 @@ module.exports = { themeableSource: require('./themeable-source'), bundleVendorFiles: require('./bundle-vendor-files'), sizeLimit: require('./size-limit'), -<<<<<<< HEAD testDefinitions: require('./test-definitions'), -======= - visual: require('./visual'), - visualDefinitions: require('./visual-definitions'), ->>>>>>> 4213557f5 (chore: Export visual test definitions) }; diff --git a/build-tools/tasks/visual-definitions.js b/build-tools/tasks/visual-definitions.js deleted file mode 100644 index 34dfe16adf..0000000000 --- a/build-tools/tasks/visual-definitions.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -const execa = require('execa'); -const { task } = require('../utils/gulp-utils'); - -module.exports = task('visual-definitions', () => - execa('tsc', ['-p', 'tsconfig.visual-definitions.json'], { stdio: 'inherit' }) -); diff --git a/build-tools/tasks/visual.js b/build-tools/tasks/visual.js deleted file mode 100644 index 0864790afb..0000000000 --- a/build-tools/tasks/visual.js +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -const execa = require('execa'); -const path = require('path'); -const fs = require('fs'); -const waitOn = require('wait-on'); -const { task } = require('../utils/gulp-utils.js'); -const { parseArgs } = require('node:util'); - -const BASELINE_WORKTREE = '/tmp/visual-baseline'; -const BASELINE_OUTPUT = path.resolve('pages/lib/static-visual-baseline'); -const TEST_OUTPUT = path.resolve('pages/lib/static-default'); - -// Port assignments: -// 8080 — test build (PR / local changes) -// 8081 — baseline build (main branch) -const TEST_PORT = 8080; -const BASELINE_PORT = 8081; - -/** - * Serves a pre-built static directory using webpack-dev-server in static mode. - */ -function serveStatic(dir, port) { - return execa( - 'node_modules/.bin/webpack', - ['serve', '--config', 'pages/webpack.config.integ.cjs', '--port', String(port), '--static', dir, '--no-hot'], - { env: { ...process.env, NODE_ENV: 'development' } } - ); -} - -/** - * Builds the dev pages from the source tree at `cwd` into `outputPath`. - * Uses the node_modules present in `cwd`. - */ -async function buildPages(cwd, outputPath) { - await execa('npx', ['gulp', 'quick-build'], { - stdio: 'inherit', - cwd, - env: { ...process.env, NODE_ENV: 'production' }, - }); - await execa( - path.join(cwd, 'node_modules/.bin/webpack'), - ['--config', 'pages/webpack.config.integ.cjs', '--output-path', outputPath], - { stdio: 'inherit', cwd, env: { ...process.env, NODE_ENV: 'production' } } - ); -} - -module.exports = task('test:visual', async () => { - const options = { - shard: { type: 'string' }, - // Pass --skip-build to skip the build steps when artifacts are already present. - skipBuild: { type: 'boolean' }, - }; - const { shard, skipBuild } = parseArgs({ options, strict: false }).values; - - const cwd = process.cwd(); - - if (!skipBuild) { - // ── 1. Build the test (PR) pages ──────────────────────────────────────── - console.log('Building test pages (current branch)…'); - await buildPages(cwd, TEST_OUTPUT); - - // ── 2. Build the baseline (main) pages ────────────────────────────────── - // Create a worktree for origin/main so it gets its own node_modules. - // This correctly handles PRs that change package-lock.json: each side - // installs from its own lockfile. - console.log('Setting up baseline worktree from origin/main…'); - if (fs.existsSync(BASELINE_WORKTREE)) { - await execa('git', ['worktree', 'remove', '--force', BASELINE_WORKTREE]); - } - await execa('git', ['worktree', 'add', BASELINE_WORKTREE, 'origin/main']); - - try { - console.log('Installing baseline dependencies…'); - await execa('npm', ['ci'], { stdio: 'inherit', cwd: BASELINE_WORKTREE }); - - console.log('Building baseline pages (origin/main)…'); - await buildPages(BASELINE_WORKTREE, BASELINE_OUTPUT); - } finally { - await execa('git', ['worktree', 'remove', '--force', BASELINE_WORKTREE]); - } - } - - // ── 3. Start both static servers ────────────────────────────────────────── - console.log(`Starting test server on :${TEST_PORT} (${TEST_OUTPUT})…`); - const testServer = serveStatic(TEST_OUTPUT, TEST_PORT); - - console.log(`Starting baseline server on :${BASELINE_PORT} (${BASELINE_OUTPUT})…`); - const baselineServer = serveStatic(BASELINE_OUTPUT, BASELINE_PORT); - - try { - await waitOn({ resources: [`http://localhost:${TEST_PORT}`, `http://localhost:${BASELINE_PORT}`] }); - - // ── 4. Run visual tests ────────────────────────────────────────────────── - const jestArgs = ['-c', 'jest.visual.config.js']; - if (shard) { - jestArgs.push(`--shard=${shard}`); - } - await execa('jest', jestArgs, { - stdio: 'inherit', - env: { ...process.env, NODE_OPTIONS: '--experimental-vm-modules' }, - }); - } finally { - testServer.cancel(); - baselineServer.cancel(); - } -}); diff --git a/tsconfig.visual-definitions.json b/tsconfig.visual-definitions.json index a31ccd02d3..fbc322c92a 100644 --- a/tsconfig.visual-definitions.json +++ b/tsconfig.visual-definitions.json @@ -12,7 +12,7 @@ "sourceMap": true, "inlineSources": true, "rootDir": "test/visual", - "outDir": "lib/visual-test-definitions" + "outDir": "lib/test-definitions" }, "include": ["test/visual/definitions", "test/visual/types.ts"], "exclude": [] From adda500c1bd0e428e7348bbcbd698c7776f25a83 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Thu, 21 May 2026 16:35:14 +0200 Subject: [PATCH 44/45] Remove local testing setup --- gulpfile.js | 4 ---- tsconfig.visual-definitions.json | 19 ------------------- 2 files changed, 23 deletions(-) delete mode 100644 tsconfig.visual-definitions.json diff --git a/gulpfile.js b/gulpfile.js index 09e0833868..6e3f389082 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -19,8 +19,6 @@ const { generateI18nMessages, integ, motion, - visual, - visualDefinitions, copyFiles, themeableSource, bundleVendorFiles, @@ -45,8 +43,6 @@ exports['test:unit'] = unit; exports['test:integ'] = integ; exports['test:a11y'] = a11y; exports['test:motion'] = motion; -exports['test:visual'] = visual; -exports['build:visual-definitions'] = visualDefinitions; exports.watch = () => { watch( diff --git a/tsconfig.visual-definitions.json b/tsconfig.visual-definitions.json deleted file mode 100644 index fbc322c92a..0000000000 --- a/tsconfig.visual-definitions.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compilerOptions": { - "lib": ["ES2021", "DOM"], - "target": "ES2019", - "types": [], - "module": "CommonJS", - "moduleResolution": "node", - "esModuleInterop": true, - "strict": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "inlineSources": true, - "rootDir": "test/visual", - "outDir": "lib/test-definitions" - }, - "include": ["test/visual/definitions", "test/visual/types.ts"], - "exclude": [] -} From fe43d8b579b195e2e4412aa23c32b3c6a863cfb3 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Thu, 21 May 2026 16:44:41 +0200 Subject: [PATCH 45/45] Refactor --- docs/RUNNING_TESTS.md | 4 +- .../visual/compare-screenshots.ts | 4 +- test/{visual => }/visual.test.ts | 2 +- test/visual/definitions/action-card.ts | 31 ---------------- test/visual/definitions/alert.ts | 37 ------------------- test/visual/definitions/index.ts | 10 ----- test/visual/types.ts | 28 -------------- 7 files changed, 6 insertions(+), 110 deletions(-) rename test/{ => definitions}/visual/compare-screenshots.ts (96%) rename test/{visual => }/visual.test.ts (70%) delete mode 100644 test/visual/definitions/action-card.ts delete mode 100644 test/visual/definitions/alert.ts delete mode 100644 test/visual/definitions/index.ts delete mode 100644 test/visual/types.ts diff --git a/docs/RUNNING_TESTS.md b/docs/RUNNING_TESTS.md index c5c483e981..6e0eb84503 100644 --- a/docs/RUNNING_TESTS.md +++ b/docs/RUNNING_TESTS.md @@ -86,7 +86,7 @@ NODE_OPTIONS=--experimental-vm-modules node_modules/.bin/jest -c jest.visual.con ### Adding tests for a new component -Create `test/visual/definitions/.ts`: +Create `test/definitions/visual/.ts`: ```ts import { TestSuite } from '../types'; @@ -104,7 +104,7 @@ const suite: TestSuite = { export default suite; ``` -Then import and add it to `test/visual/definitions/index.ts`: +Then import and add it to `test/definitions/visual/index.ts`: ```ts import myComponent from './my-component'; diff --git a/test/visual/compare-screenshots.ts b/test/definitions/visual/compare-screenshots.ts similarity index 96% rename from test/visual/compare-screenshots.ts rename to test/definitions/visual/compare-screenshots.ts index 8847e10d21..f81b8afc56 100644 --- a/test/visual/compare-screenshots.ts +++ b/test/definitions/visual/compare-screenshots.ts @@ -7,7 +7,7 @@ import { parsePng } from '@cloudscape-design/browser-test-tools/image-utils'; import { ScreenshotPageObject } from '@cloudscape-design/browser-test-tools/page-objects'; import useBrowser from '@cloudscape-design/browser-test-tools/use-browser'; -import { TestDefinition, TestSuite } from './types'; +import { TestDefinition, TestSuite } from '../types'; const screenshotAreaSelector = '.screenshot-area'; const defaultWindowSize = { width: 1600, height: 800 }; @@ -97,6 +97,8 @@ function runSingleTest(testDef: TestDefinition) { test( testDef.description, + // useBrowser is not a React hook, despite the name + // eslint-disable-next-line react-hooks/rules-of-hooks useBrowser(windowSize, async browser => { const newUrl = buildUrl(newHost, testDef.path, testDef.queryParams); const newScreenshot = await captureScreenshot(browser, newUrl, testDef, testDef.setup); diff --git a/test/visual/visual.test.ts b/test/visual.test.ts similarity index 70% rename from test/visual/visual.test.ts rename to test/visual.test.ts index 06ef0fa8a4..bc7369d6a9 100644 --- a/test/visual/visual.test.ts +++ b/test/visual.test.ts @@ -1,6 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { runTestSuites } from './compare-screenshots'; import { allSuites } from './definitions'; +import { runTestSuites } from './definitions/visual/compare-screenshots'; runTestSuites(allSuites); diff --git a/test/visual/definitions/action-card.ts b/test/visual/definitions/action-card.ts deleted file mode 100644 index 8730ac9504..0000000000 --- a/test/visual/definitions/action-card.ts +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import { TestSuite } from '../types'; - -const suite: TestSuite = { - description: 'action-card', - tests: [ - { - description: 'permutations', - path: 'action-card/permutations', - screenshotType: 'permutations', - }, - { - description: 'variant permutations', - path: 'action-card/variant-permutations', - screenshotType: 'permutations', - }, - { - description: 'padding permutations', - path: 'action-card/padding-permutations', - screenshotType: 'permutations', - }, - { - description: 'simple', - path: 'action-card/simple', - screenshotType: 'screenshotArea', - }, - ], -}; - -export default suite; diff --git a/test/visual/definitions/alert.ts b/test/visual/definitions/alert.ts deleted file mode 100644 index 30792c0d2d..0000000000 --- a/test/visual/definitions/alert.ts +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { TestSuite } from '../types'; - -const suite: TestSuite = { - description: 'alert', - tests: [ - { - description: 'simple', - path: 'alert/simple', - screenshotType: 'screenshotArea', - }, - { - description: 'style custom page', - path: 'alert/style-custom-types', - screenshotType: 'screenshotArea', - }, - ...[600, 1280].map(width => ({ - description: `width ${width}px`, - tests: [ - { - description: 'permutations', - path: 'alert/permutations', - screenshotType: 'permutations' as const, - }, - { - description: 'custom types', - path: 'alert/style-custom-types', - screenshotType: 'screenshotArea' as const, - }, - ], - })), - ], -}; - -export default suite; diff --git a/test/visual/definitions/index.ts b/test/visual/definitions/index.ts deleted file mode 100644 index 318ce7c68b..0000000000 --- a/test/visual/definitions/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Each component has its own test definition file. -// Import them here manually to form the full test suite. -import { TestSuite } from '../types'; -import actionCard from './action-card'; -import alert from './alert'; - -export const allSuites: TestSuite[] = [actionCard, alert]; diff --git a/test/visual/types.ts b/test/visual/types.ts deleted file mode 100644 index c4c23b622f..0000000000 --- a/test/visual/types.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import type { ScreenshotPageObject } from '@cloudscape-design/browser-test-tools/page-objects'; - -export interface ScreenshotTestConfiguration { - width?: number; - height?: number; -} - -export type TestCallback = (page: ScreenshotPageObject) => Promise; - -// 'screenshotArea' — captures the .screenshot-area element on a focused page. -// 'permutations' — captures the entire page and crops permutations out of it. -export type ScreenshotType = 'screenshotArea' | 'permutations'; - -export interface TestDefinition { - description: string; - path: string; - screenshotType: ScreenshotType; - queryParams?: Record; - configuration?: ScreenshotTestConfiguration; - setup?: TestCallback; -} - -export interface TestSuite { - description: string; - tests: Array; -}