From d07baa02f95efe991b36b7d2d32a0bf4b117bb68 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Fri, 24 Jul 2026 12:33:25 +0200 Subject: [PATCH 01/10] chore: Add visual tests for more components (C-I) --- test/definitions/index.ts | 57 ++++ test/definitions/visual/cards.ts | 16 ++ test/definitions/visual/checkbox.ts | 36 +++ test/definitions/visual/code-editor.ts | 67 +++++ .../visual/collection-preferences.ts | 65 +++++ test/definitions/visual/column-layout.ts | 31 +++ test/definitions/visual/container-sticky.ts | 68 +++++ test/definitions/visual/container.ts | 58 ++++ .../visual/content-layout-permutations.ts | 256 ++++++++++++++++++ test/definitions/visual/content-layout.ts | 59 ++++ test/definitions/visual/copy-to-clipboard.ts | 29 ++ test/definitions/visual/date-input.ts | 22 ++ test/definitions/visual/date-picker.ts | 54 ++++ test/definitions/visual/date-range-picker.ts | 107 ++++++++ test/definitions/visual/expandable-section.ts | 59 ++++ test/definitions/visual/flashbar-stacked.ts | 43 +++ test/definitions/visual/flashbar.ts | 56 ++++ test/definitions/visual/form-field.ts | 24 ++ test/definitions/visual/form.ts | 22 ++ test/definitions/visual/header.ts | 18 ++ test/definitions/visual/input.ts | 22 ++ test/definitions/visual/item-card.ts | 32 +++ test/visual/cards.test.ts | 6 + test/visual/checkbox.test.ts | 6 + test/visual/code-editor.test.ts | 6 + test/visual/collection-preferences.test.ts | 6 + test/visual/column-layout.test.ts | 6 + test/visual/container-sticky.test.ts | 6 + test/visual/container.test.ts | 6 + .../content-layout-permutations.test.ts | 6 + test/visual/content-layout.test.ts | 6 + test/visual/copy-to-clipboard.test.ts | 6 + test/visual/date-input.test.ts | 6 + test/visual/date-picker.test.ts | 6 + test/visual/date-range-picker.test.ts | 6 + test/visual/expandable-section.test.ts | 6 + test/visual/flashbar-stacked.test.ts | 6 + test/visual/flashbar.test.ts | 6 + test/visual/form-field.test.ts | 6 + test/visual/form.test.ts | 6 + test/visual/header.test.ts | 6 + test/visual/input.test.ts | 6 + test/visual/item-card.test.ts | 6 + 43 files changed, 1327 insertions(+) create mode 100644 test/definitions/visual/cards.ts create mode 100644 test/definitions/visual/checkbox.ts create mode 100644 test/definitions/visual/code-editor.ts create mode 100644 test/definitions/visual/collection-preferences.ts create mode 100644 test/definitions/visual/column-layout.ts create mode 100644 test/definitions/visual/container-sticky.ts create mode 100644 test/definitions/visual/container.ts create mode 100644 test/definitions/visual/content-layout-permutations.ts create mode 100644 test/definitions/visual/content-layout.ts create mode 100644 test/definitions/visual/copy-to-clipboard.ts create mode 100644 test/definitions/visual/date-input.ts create mode 100644 test/definitions/visual/date-picker.ts create mode 100644 test/definitions/visual/date-range-picker.ts create mode 100644 test/definitions/visual/expandable-section.ts create mode 100644 test/definitions/visual/flashbar-stacked.ts create mode 100644 test/definitions/visual/flashbar.ts create mode 100644 test/definitions/visual/form-field.ts create mode 100644 test/definitions/visual/form.ts create mode 100644 test/definitions/visual/header.ts create mode 100644 test/definitions/visual/input.ts create mode 100644 test/definitions/visual/item-card.ts create mode 100644 test/visual/cards.test.ts create mode 100644 test/visual/checkbox.test.ts create mode 100644 test/visual/code-editor.test.ts create mode 100644 test/visual/collection-preferences.test.ts create mode 100644 test/visual/column-layout.test.ts create mode 100644 test/visual/container-sticky.test.ts create mode 100644 test/visual/container.test.ts create mode 100644 test/visual/content-layout-permutations.test.ts create mode 100644 test/visual/content-layout.test.ts create mode 100644 test/visual/copy-to-clipboard.test.ts create mode 100644 test/visual/date-input.test.ts create mode 100644 test/visual/date-picker.test.ts create mode 100644 test/visual/date-range-picker.test.ts create mode 100644 test/visual/expandable-section.test.ts create mode 100644 test/visual/flashbar-stacked.test.ts create mode 100644 test/visual/flashbar.test.ts create mode 100644 test/visual/form-field.test.ts create mode 100644 test/visual/form.test.ts create mode 100644 test/visual/header.test.ts create mode 100644 test/visual/input.test.ts create mode 100644 test/visual/item-card.test.ts diff --git a/test/definitions/index.ts b/test/definitions/index.ts index 299c8e1b1a..5bd0193013 100644 --- a/test/definitions/index.ts +++ b/test/definitions/index.ts @@ -28,6 +28,27 @@ import breadcrumbGroupSuite from './visual/breadcrumb-group'; import buttonSuite from './visual/button'; import buttonDropdownSuite from './visual/button-dropdown'; import buttonGroupSuite from './visual/button-group'; +import cardsSuite from './visual/cards'; +import checkboxSuite from './visual/checkbox'; +import codeEditorSuite from './visual/code-editor'; +import collectionPreferencesSuite from './visual/collection-preferences'; +import columnLayoutSuite from './visual/column-layout'; +import containerSuite from './visual/container'; +import containerStickySuite from './visual/container-sticky'; +import contentLayoutSuite from './visual/content-layout'; +import contentLayoutPermutationsSuite from './visual/content-layout-permutations'; +import copyToClipboardSuite from './visual/copy-to-clipboard'; +import dateInputSuite from './visual/date-input'; +import datePickerSuite from './visual/date-picker'; +import dateRangePickerSuite from './visual/date-range-picker'; +import expandableSectionSuite from './visual/expandable-section'; +import flashbarSuite from './visual/flashbar'; +import flashbarStackedSuite from './visual/flashbar-stacked'; +import formSuite from './visual/form'; +import formFieldSuite from './visual/form-field'; +import headerSuite from './visual/header'; +import inputSuite from './visual/input'; +import itemCardSuite from './visual/item-card'; // Per-component exports (grouped by component) export const actionCard: TestSuite[] = [actionCardSuite]; @@ -54,6 +75,24 @@ export const breadcrumbGroup: TestSuite[] = [breadcrumbGroupSuite]; export const button: TestSuite[] = [buttonSuite]; export const buttonDropdown: TestSuite[] = [buttonDropdownSuite]; export const buttonGroup: TestSuite[] = [buttonGroupSuite]; +export const cards: TestSuite[] = [cardsSuite]; +export const checkbox: TestSuite[] = [checkboxSuite]; +export const codeEditor: TestSuite[] = [codeEditorSuite]; +export const collectionPreferences: TestSuite[] = [collectionPreferencesSuite]; +export const columnLayout: TestSuite[] = [columnLayoutSuite]; +export const container: TestSuite[] = [containerSuite, containerStickySuite]; +export const contentLayout: TestSuite[] = [contentLayoutSuite, contentLayoutPermutationsSuite]; +export const copyToClipboard: TestSuite[] = [copyToClipboardSuite]; +export const dateInput: TestSuite[] = [dateInputSuite]; +export const datePicker: TestSuite[] = [datePickerSuite]; +export const dateRangePicker: TestSuite[] = [dateRangePickerSuite]; +export const expandableSection: TestSuite[] = [expandableSectionSuite]; +export const flashbar: TestSuite[] = [flashbarSuite, flashbarStackedSuite]; +export const form: TestSuite[] = [formSuite]; +export const formField: TestSuite[] = [formFieldSuite]; +export const header: TestSuite[] = [headerSuite]; +export const input: TestSuite[] = [inputSuite]; +export const itemCard: TestSuite[] = [itemCardSuite]; export const allSuites: TestSuite[] = [ ...actionCard, @@ -69,4 +108,22 @@ export const allSuites: TestSuite[] = [ ...button, ...buttonDropdown, ...buttonGroup, + ...cards, + ...checkbox, + ...codeEditor, + ...collectionPreferences, + ...columnLayout, + ...container, + ...contentLayout, + ...copyToClipboard, + ...dateInput, + ...datePicker, + ...dateRangePicker, + ...expandableSection, + ...flashbar, + ...form, + ...formField, + ...header, + ...input, + ...itemCard, ]; diff --git a/test/definitions/visual/cards.ts b/test/definitions/visual/cards.ts new file mode 100644 index 0000000000..5779061a6c --- /dev/null +++ b/test/definitions/visual/cards.ts @@ -0,0 +1,16 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestSuite } from '../types'; + +const suite: TestSuite = { + description: 'Cards', + componentName: 'cards', + tests: [2200, 1920, 1400, 1200, 992, 768].map(width => ({ + description: `permutations at ${width}`, + path: 'cards/permutations', + screenshotType: 'permutations' as const, + configuration: { width }, + })), +}; + +export default suite; diff --git a/test/definitions/visual/checkbox.ts b/test/definitions/visual/checkbox.ts new file mode 100644 index 0000000000..d7c0f05363 --- /dev/null +++ b/test/definitions/visual/checkbox.ts @@ -0,0 +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: 'Checkbox', + componentName: 'checkbox', + tests: [ + { + description: 'Permutations', + path: 'checkbox/permutations', + screenshotType: 'permutations', + }, + { + description: 'Checkbox is focused', + path: 'checkbox/focus-test', + screenshotType: 'screenshotArea', + setup: async ({ page }) => { + await page.click('#focus-target'); + await page.focusNextElement(); + }, + }, + { + description: 'Checkbox has label with a correct width', + path: 'checkbox/labels-highlight', + screenshotType: 'screenshotArea', + }, + { + description: 'Style custom page', + path: 'checkbox/style-custom', + screenshotType: 'screenshotArea', + }, + ], +}; + +export default suite; diff --git a/test/definitions/visual/code-editor.ts b/test/definitions/visual/code-editor.ts new file mode 100644 index 0000000000..a1550118e2 --- /dev/null +++ b/test/definitions/visual/code-editor.ts @@ -0,0 +1,67 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestSuite } from '../types'; + +const ACE_SELECTOR = '.ace_editor.ace-dawn, .ace_editor.ace-tomorrow-night-bright'; + +const suite: TestSuite = { + description: 'Code editor', + componentName: 'code-editor', + tests: [ + { + description: 'simple', + path: 'code-editor/simple', + screenshotType: 'screenshotArea', + setup: async ({ page }) => { + await page.waitForVisible(ACE_SELECTOR); + }, + }, + { + description: 'error', + path: 'code-editor/error', + screenshotType: 'screenshotArea', + }, + { + description: 'loading', + path: 'code-editor/loading', + screenshotType: 'screenshotArea', + }, + { + description: 'theme resolution', + path: 'code-editor/themes', + screenshotType: 'screenshotArea', + setup: async ({ page }) => { + await page.waitForVisible(ACE_SELECTOR); + }, + }, + { + description: 'permutations', + path: 'code-editor/permutations', + screenshotType: 'permutations', + setup: async ({ page }) => { + await page.waitForVisible(ACE_SELECTOR + ' .ace_error'); + await page.waitForVisible('.ace_gutter-cell.ace_gutter-active-line.ace_error'); + }, + }, + { + description: 'listens to mode change', + path: 'code-editor/simple', + screenshotType: 'screenshotArea', + setup: async ({ page }) => { + await page.waitForVisible(ACE_SELECTOR); + await page.click('#mode-toggle'); + }, + }, + { + description: 'compare simple on small screen', + path: 'code-editor/simple', + screenshotType: 'screenshotArea', + configuration: { width: 360 }, + setup: async ({ page }) => { + await page.waitForVisible(ACE_SELECTOR); + }, + }, + ], +}; + +export default suite; diff --git a/test/definitions/visual/collection-preferences.ts b/test/definitions/visual/collection-preferences.ts new file mode 100644 index 0000000000..4748b24143 --- /dev/null +++ b/test/definitions/visual/collection-preferences.ts @@ -0,0 +1,65 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestDefinition, TestSuite } from '../types'; + +const suite: TestSuite = { + description: 'CollectionPreferences', + componentName: 'collection-preferences', + tests: [ + ...( + [ + [600, 1100], + [1280, 700], + ] as [number, number][] + ).flatMap(([width, height]) => [ + { + description: `complete at ${width}x${height}`, + path: 'collection-preferences/simple', + screenshotType: 'viewport' as const, + configuration: { width, height }, + setup: async ({ page }) => { + await page.click('.cp-1 button'); + }, + }, + { + description: `visible content only at ${width}x${height}`, + path: 'collection-preferences/simple', + screenshotType: 'viewport' as const, + setup: async ({ page }) => { + await page.click('.cp-4 button'); + }, + }, + ]), + { + description: 'custom', + path: 'collection-preferences/simple', + screenshotType: 'viewport', + setup: async ({ page }) => { + await page.click('.cp-2 button'); + }, + }, + { + description: 'drag handle focused', + path: 'collection-preferences/reorder-content', + screenshotType: 'viewport', + configuration: { width: 900, height: 650 }, + setup: async ({ page }) => { + await page.click('.cp-1 button'); + await page.keys(Array(5).fill('Tab')); + }, + }, + { + description: 'reordering active', + path: 'collection-preferences/reorder-content', + screenshotType: 'viewport', + configuration: { width: 900, height: 650 }, + setup: async ({ page }) => { + await page.click('.cp-1 button'); + await page.keys(Array(5).fill('Tab')); + await page.keys(['Space']); + }, + }, + ], +}; + +export default suite; diff --git a/test/definitions/visual/column-layout.ts b/test/definitions/visual/column-layout.ts new file mode 100644 index 0000000000..478ee9d0c2 --- /dev/null +++ b/test/definitions/visual/column-layout.ts @@ -0,0 +1,31 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestDefinition, TestSuite } from '../types'; + +const TEST_WIDTHS: [string, number][] = [ + ['default', 400], + ['xxs', 500], + ['xs', 800], + ['m', 1200], +]; + +const suite: TestSuite = { + description: 'ColumnLayout', + componentName: 'column-layout', + tests: TEST_WIDTHS.flatMap(([breakpoint, width]) => [ + { + description: `column-layout at "${breakpoint}"`, + path: 'column-layout/simple', + screenshotType: 'screenshotArea' as const, + configuration: { width }, + }, + { + description: `permutations at "${breakpoint}"`, + path: 'column-layout/permutations', + screenshotType: 'permutations' as const, + configuration: { width }, + }, + ]), +}; + +export default suite; diff --git a/test/definitions/visual/container-sticky.ts b/test/definitions/visual/container-sticky.ts new file mode 100644 index 0000000000..60b2a6f217 --- /dev/null +++ b/test/definitions/visual/container-sticky.ts @@ -0,0 +1,68 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestDefinition, TestSuite } from '../types'; + +const suite: TestSuite = { + description: 'Container sticky permutations', + componentName: 'container', + tests: [1400, 600].flatMap(width => [ + { + description: `simple - at ${width}px`, + path: 'container/sticky-permutations', + screenshotType: 'viewport' as const, + configuration: { width }, + setup: async ({ page }) => { + await page.windowScrollTo({ top: 200 }); + }, + }, + { + description: `with notifications - at ${width}px`, + path: 'container/sticky-permutations', + screenshotType: 'viewport' as const, + configuration: { width }, + queryParams: { hasNotifications: 'true' }, + setup: async ({ page }) => { + await page.windowScrollTo({ top: 200 }); + }, + }, + { + description: `with breadcrumbs - at ${width}px`, + path: 'container/sticky-permutations', + screenshotType: 'viewport' as const, + configuration: { width }, + queryParams: { hasBreadcrumbs: 'true' }, + setup: async ({ page }) => { + await page.windowScrollTo({ top: 200 }); + }, + }, + { + description: `with an alert - at ${width}px`, + path: 'container/sticky-permutations', + screenshotType: 'viewport' as const, + configuration: { width }, + queryParams: { hasNotifications: 'true', hasAlert: 'true' }, + setup: async ({ page }) => { + await page.windowScrollTo({ top: 200 }); + }, + }, + { + description: `with an alert - at ${width}px without scroll`, + path: 'container/sticky-permutations', + screenshotType: 'viewport' as const, + configuration: { width }, + queryParams: { hasNotifications: 'true', hasAlert: 'true' }, + }, + { + description: `with high-contrast header - at ${width}px`, + path: 'container/sticky-permutations', + screenshotType: 'viewport' as const, + configuration: { width }, + queryParams: { hasNotifications: 'true', highContrast: 'true' }, + setup: async ({ page }) => { + await page.windowScrollTo({ top: 200 }); + }, + }, + ]), +}; + +export default suite; diff --git a/test/definitions/visual/container.ts b/test/definitions/visual/container.ts new file mode 100644 index 0000000000..5038b51381 --- /dev/null +++ b/test/definitions/visual/container.ts @@ -0,0 +1,58 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestDefinition, TestSuite } from '../types'; + +const suite: TestSuite = { + description: 'Container and header', + componentName: 'container', + tests: [ + { + description: 'simple', + path: 'container/simple', + screenshotType: 'screenshotArea', + }, + { + description: 'fit height with footer', + path: 'container/fit-height', + screenshotType: 'screenshotArea', + }, + { + description: 'fit height without footer', + path: 'container/fit-height', + screenshotType: 'screenshotArea', + queryParams: { hideFooters: 'true' }, + }, + { + description: 'correctly displays container with side media', + path: 'container/media', + screenshotType: 'screenshotArea', + queryParams: { position: 'side', width: '33%', content: '16-9' }, + }, + { + description: 'correctly displays container with top media', + path: 'container/media', + screenshotType: 'screenshotArea', + queryParams: { position: 'top', height: '150px', content: '4-3' }, + }, + ...(['side', 'top'] as const).flatMap(position => + [465, 688, 1120].map(width => ({ + description: `media ${position} permutations at ${width}`, + path: `container/media-${position}-permutations`, + screenshotType: 'permutations' as const, + configuration: { width }, + })) + ), + { + description: 'stacked', + path: 'container/stacked-components', + screenshotType: 'screenshotArea', + }, + { + description: 'style-custom', + path: 'container/style-custom', + screenshotType: 'screenshotArea', + }, + ], +}; + +export default suite; diff --git a/test/definitions/visual/content-layout-permutations.ts b/test/definitions/visual/content-layout-permutations.ts new file mode 100644 index 0000000000..c665254d47 --- /dev/null +++ b/test/definitions/visual/content-layout-permutations.ts @@ -0,0 +1,256 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestDefinition, TestSuite } from '../types'; + +function contentLayoutPermutation(params: Record, width = 1400): TestDefinition { + return { + description: `${JSON.stringify(params)} at ${width}`, + path: 'content-layout/permutations', + screenshotType: 'screenshotArea' as const, + configuration: { width }, + queryParams: params, + }; +} + +const suite: TestSuite = { + description: 'ContentLayout permutations', + componentName: 'content-layout', + tests: [ + // default and high-contrast headerVariants with all background styles + ...(['default', 'high-contrast'] as const).flatMap(headerVariant => + (['none', 'gradient', 'image'] as const).flatMap(headerBackgroundStyle => + [1400, 600].flatMap(width => [ + contentLayoutPermutation( + { + headerVariant, + headerBackgroundStyle, + hasContainer: 'true', + defaultPadding: 'true', + hasBreadcrumbs: 'false', + hasNotifications: 'false', + disableOverlap: 'false', + hasAppLayout: 'false', + hasAppLayoutWithOpenNavigation: 'false', + hasSecondaryHeader: 'false', + removeHeader: 'false', + }, + width + ), + contentLayoutPermutation( + { + headerVariant, + headerBackgroundStyle, + hasContainer: 'true', + defaultPadding: 'true', + hasBreadcrumbs: 'true', + hasNotifications: 'true', + disableOverlap: 'false', + hasAppLayout: 'false', + hasAppLayoutWithOpenNavigation: 'false', + hasSecondaryHeader: 'false', + removeHeader: 'false', + }, + width + ), + ]) + ) + ), + // maxContentWidth variants + ...(['default', 'high-contrast'] as const).flatMap(headerVariant => + (['none', 'gradient', 'image'] as const).map(headerBackgroundStyle => + contentLayoutPermutation({ + headerVariant, + headerBackgroundStyle, + hasContainer: 'true', + defaultPadding: 'true', + hasBreadcrumbs: 'true', + hasNotifications: 'true', + disableOverlap: 'false', + hasAppLayout: 'false', + hasAppLayoutWithOpenNavigation: 'false', + hasSecondaryHeader: 'false', + removeHeader: 'false', + maxContentWidth: '1000', + }) + ) + ), + // divider headerVariant + ...[1400, 600].flatMap(width => [ + contentLayoutPermutation( + { + headerVariant: 'divider', + defaultPadding: 'true', + hasContainer: 'true', + hasBreadcrumbs: 'false', + hasNotifications: 'false', + disableOverlap: 'false', + hasAppLayout: 'false', + hasAppLayoutWithOpenNavigation: 'false', + hasSecondaryHeader: 'false', + removeHeader: 'false', + }, + width + ), + contentLayoutPermutation( + { + headerVariant: 'divider', + defaultPadding: 'true', + hasContainer: 'true', + hasBreadcrumbs: 'true', + hasNotifications: 'true', + disableOverlap: 'true', + hasAppLayout: 'false', + hasAppLayoutWithOpenNavigation: 'false', + hasSecondaryHeader: 'false', + removeHeader: 'false', + maxContentWidth: '1000', + }, + width + ), + ]), + // with app layout + contentLayoutPermutation({ + headerVariant: 'divider', + defaultPadding: 'true', + hasContainer: 'true', + hasBreadcrumbs: 'false', + hasNotifications: 'false', + disableOverlap: 'true', + hasAppLayout: 'true', + hasAppLayoutWithOpenNavigation: 'false', + hasSecondaryHeader: 'false', + removeHeader: 'false', + }), + contentLayoutPermutation({ + headerVariant: 'high-contrast', + headerBackgroundStyle: 'gradient', + defaultPadding: 'true', + hasContainer: 'true', + hasBreadcrumbs: 'true', + hasNotifications: 'true', + disableOverlap: 'false', + hasAppLayout: 'true', + hasAppLayoutWithOpenNavigation: 'false', + hasSecondaryHeader: 'false', + removeHeader: 'false', + maxContentWidth: '800', + }), + // with open navigation + contentLayoutPermutation({ + headerVariant: 'high-contrast', + defaultPadding: 'true', + hasContainer: 'true', + hasBreadcrumbs: 'false', + hasNotifications: 'false', + disableOverlap: 'false', + hasAppLayout: 'true', + hasAppLayoutWithOpenNavigation: 'true', + hasSecondaryHeader: 'false', + removeHeader: 'false', + }), + contentLayoutPermutation({ + headerVariant: 'divider', + defaultPadding: 'true', + hasContainer: 'true', + hasBreadcrumbs: 'true', + hasNotifications: 'true', + disableOverlap: 'false', + hasAppLayout: 'true', + hasAppLayoutWithOpenNavigation: 'true', + hasSecondaryHeader: 'false', + removeHeader: 'false', + maxContentWidth: '700', + }), + // with secondary header + ...[1400, 600].map(width => + contentLayoutPermutation( + { + headerVariant: 'high-contrast', + defaultPadding: 'true', + hasContainer: 'true', + hasBreadcrumbs: 'false', + hasNotifications: 'false', + disableOverlap: 'false', + hasAppLayout: 'false', + hasAppLayoutWithOpenNavigation: 'false', + hasSecondaryHeader: 'true', + removeHeader: 'false', + }, + width + ) + ), + contentLayoutPermutation({ + headerVariant: 'high-contrast', + defaultPadding: 'true', + hasContainer: 'true', + hasBreadcrumbs: 'false', + hasNotifications: 'false', + disableOverlap: 'false', + hasAppLayout: 'false', + hasAppLayoutWithOpenNavigation: 'false', + hasSecondaryHeader: 'true', + removeHeader: 'false', + maxContentWidth: '900', + }), + // without header + ...[1400, 600].flatMap(width => + [true, false].flatMap(defaultPadding => + (['default', 'high-contrast', 'divider'] as const).map(headerVariant => + contentLayoutPermutation( + { + headerVariant, + defaultPadding: `${defaultPadding}`, + removeHeader: 'true', + hasContainer: `${defaultPadding}`, + hasBreadcrumbs: 'false', + hasNotifications: 'false', + disableOverlap: 'false', + hasAppLayout: 'false', + hasAppLayoutWithOpenNavigation: 'false', + hasSecondaryHeader: 'false', + }, + width + ) + ) + ) + ), + // without default padding + ...[1400, 600].map(width => + contentLayoutPermutation( + { + headerVariant: 'default', + defaultPadding: 'false', + hasContainer: 'false', + hasBreadcrumbs: 'false', + hasNotifications: 'false', + disableOverlap: 'false', + hasAppLayout: 'false', + hasAppLayoutWithOpenNavigation: 'false', + hasSecondaryHeader: 'false', + removeHeader: 'false', + }, + width + ) + ), + // with disabled overlap + ...[1400, 600].map(width => + contentLayoutPermutation( + { + headerVariant: 'high-contrast', + defaultPadding: 'true', + hasContainer: 'true', + hasBreadcrumbs: 'false', + hasNotifications: 'false', + disableOverlap: 'true', + hasAppLayout: 'false', + hasAppLayoutWithOpenNavigation: 'false', + hasSecondaryHeader: 'false', + removeHeader: 'false', + }, + width + ) + ), + ], +}; + +export default suite; diff --git a/test/definitions/visual/content-layout.ts b/test/definitions/visual/content-layout.ts new file mode 100644 index 0000000000..7f3a608d65 --- /dev/null +++ b/test/definitions/visual/content-layout.ts @@ -0,0 +1,59 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestDefinition, TestSuite } from '../types'; + +const suite: TestSuite = { + description: 'ContentLayout', + componentName: 'content-layout', + tests: [ + { + description: 'fill content area', + path: 'content-layout/fill-content-area', + screenshotType: 'screenshotArea', + }, + { + description: 'standalone', + path: 'content-layout/standalone', + screenshotType: 'screenshotArea', + }, + { + description: 'with absolute components', + path: 'content-layout/with-absolute-components', + screenshotType: 'screenshotArea', + }, + { + description: 'form with form header', + path: 'content-layout/with-header-toggles', + screenshotType: 'screenshotArea', + }, + { + description: 'form with content layout header', + path: 'content-layout/with-header-toggles', + screenshotType: 'screenshotArea', + setup: async ({ page }) => { + await page.click('[data-testid="toggle-form-header"] input'); + await page.click('[data-testid="toggle-content-layout"] input'); + }, + }, + // without header (from content-layout.test.ts) + ...[1400, 600].flatMap(width => + [false, true].flatMap(hasBreadcrumbs => + [false, true].flatMap(hasNotifications => + [true, false].map(hasOverlap => ({ + description: `without header at ${width} ${hasBreadcrumbs ? 'with' : 'without'} breadcrumbs, ${hasNotifications ? 'with' : 'without'} notifications, ${hasOverlap ? 'with' : 'without'} overlap`, + path: 'content-layout/without-header', + screenshotType: 'screenshotArea' as const, + configuration: { width }, + queryParams: { + hasBreadcrumbs: `${hasBreadcrumbs}`, + hasNotifications: `${hasNotifications}`, + disableOverlap: `${!hasOverlap}`, + }, + })) + ) + ) + ), + ], +}; + +export default suite; diff --git a/test/definitions/visual/copy-to-clipboard.ts b/test/definitions/visual/copy-to-clipboard.ts new file mode 100644 index 0000000000..d8db12599e --- /dev/null +++ b/test/definitions/visual/copy-to-clipboard.ts @@ -0,0 +1,29 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestSuite } from '../types'; + +const suite: TestSuite = { + description: 'CopyToClipboard', + componentName: 'copy-to-clipboard', + tests: [ + { + description: 'Variants', + path: 'copy-to-clipboard/simple', + screenshotType: 'screenshotArea', + }, + { + description: 'copy-to-clipboard below bottom split panel is not visible', + path: 'copy-to-clipboard/scenario-split-panel', + screenshotType: 'screenshotArea', + configuration: { width: 1280, height: 900 }, + setup: async ({ page, browser }) => { + await page.click('[aria-label="Copy dummy text"]'); + await browser!.execute((sel: string) => { + document.querySelector(sel)?.scrollIntoView(); + }, '[data-testid="scroll-me"]'); + }, + }, + ], +}; + +export default suite; diff --git a/test/definitions/visual/date-input.ts b/test/definitions/visual/date-input.ts new file mode 100644 index 0000000000..628e120d9f --- /dev/null +++ b/test/definitions/visual/date-input.ts @@ -0,0 +1,22 @@ +// 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 input', + componentName: 'date-input', + tests: [ + { + description: 'Permutations: states', + path: 'date-input/permutations-states', + screenshotType: 'permutations', + }, + { + description: 'Permutations: formats', + path: 'date-input/permutations-formats', + screenshotType: 'permutations', + }, + ], +}; + +export default suite; diff --git a/test/definitions/visual/date-picker.ts b/test/definitions/visual/date-picker.ts new file mode 100644 index 0000000000..c2cc20645b --- /dev/null +++ b/test/definitions/visual/date-picker.ts @@ -0,0 +1,54 @@ +// 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 picker', + componentName: 'date-picker', + tests: [ + { + description: 'Permutations: states', + path: 'date-picker/permutations', + screenshotType: 'permutations', + setup: async ({ page }) => { + await page.click('[data-testid="date-picker-expanded-example"] button'); + }, + }, + { + description: 'Permutations: formats', + path: 'date-picker/permutations-formats', + screenshotType: 'permutations', + }, + { + description: 'focus ring on selected month', + path: 'date-picker/month-picker', + screenshotType: 'screenshotArea', + setup: async ({ page, wrapper }) => { + await page.click(wrapper.findDatePicker().findOpenCalendarButton().toSelector()); + await page.keys(['Tab', 'Tab', 'Tab']); + }, + }, + { + description: 'focus ring on current month', + path: 'date-picker/month-picker', + screenshotType: 'screenshotArea', + setup: async ({ page, wrapper }) => { + await page.click(wrapper.findDatePicker().findOpenCalendarButton().toSelector()); + await page.keys(['Tab', 'Tab', 'Tab']); + await page.keys(['ArrowRight']); + }, + }, + { + description: 'focus ring on non selected, non current month', + path: 'date-picker/month-picker', + screenshotType: 'screenshotArea', + setup: async ({ page, wrapper }) => { + await page.click(wrapper.findDatePicker().findOpenCalendarButton().toSelector()); + await page.keys(['Tab', 'Tab', 'Tab']); + await page.keys(['ArrowRight', 'ArrowRight']); + }, + }, + ], +}; + +export default suite; diff --git a/test/definitions/visual/date-range-picker.ts b/test/definitions/visual/date-range-picker.ts new file mode 100644 index 0000000000..c18c4a1750 --- /dev/null +++ b/test/definitions/visual/date-range-picker.ts @@ -0,0 +1,107 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestDefinition, TestSuite } from '../types'; + +const suite: TestSuite = { + description: 'Date Range Picker', + componentName: 'date-range-picker', + tests: [ + ...[450, 1200].flatMap(width => [ + { + description: `Absolute range at ${width}px`, + path: 'date-range-picker/with-value', + screenshotType: 'screenshotArea' as const, + configuration: { width, height: 950 }, + setup: async ({ page }) => { + await page.click('#focusable-before'); + await page.focusNextElement(); + await page.keys(['Enter']); + await page.click('[data-date="2018-01-09"]'); + if (width >= 1000) { + await page.click('[data-date="2018-02-24"]'); + } else { + await page.click('[data-date="2018-01-27"]'); + } + }, + }, + { + description: `Relative range at ${width}px`, + path: 'date-range-picker/with-value', + screenshotType: 'screenshotArea' as const, + configuration: { width, height: 950 }, + setup: async ({ page }) => { + await page.click('#focusable-before'); + await page.focusNextElement(); + await page.keys(['Enter']); + await page.focusNextElement(); + if (width >= 1000) { + await page.keys(['ArrowLeft']); + await page.keys(['Enter']); + } else { + await page.keys(['Space']); + await page.keys(['ArrowUp']); + await page.keys(['Enter']); + } + await page.focusNextElement(); + await page.keys(['ArrowDown', 'ArrowDown', 'ArrowDown', 'ArrowDown']); + }, + }, + ]), + { + description: 'Absolute range input permutations for day granularity', + path: 'date-range-picker/absolute-format-day-picker.permutations', + screenshotType: 'screenshotArea', + }, + { + description: 'Absolute range input permutations for month granularity', + path: 'date-range-picker/absolute-format-month-picker.permutations', + screenshotType: 'permutations', + }, + { + description: 'Calendar permutations for day granularity', + path: 'date-range-picker/month-calendar-permutations', + screenshotType: 'permutations', + }, + { + description: 'Calendar permutations for month granularity', + path: 'date-range-picker/year-calendar-permutations', + screenshotType: 'permutations', + }, + { + description: 'selects text when double-clicking calendar header', + path: 'date-range-picker/with-value', + screenshotType: 'screenshotArea', + setup: async ({ page, wrapper, browser }) => { + await page.click('#focusable-before'); + await page.focusNextElement(); + await page.keys(['Enter']); + const headerSelector = wrapper.findDateRangePicker().findDropdown().findHeader().find('h2 span').toSelector(); + await browser!.execute((sel: string) => { + const el = document.querySelector(sel); + if (el) { + el.dispatchEvent(new MouseEvent('dblclick', { bubbles: true })); + } + }, headerSelector); + }, + }, + { + description: 'does not select text when double-clicking next button', + path: 'date-range-picker/with-value', + screenshotType: 'screenshotArea', + setup: async ({ page, wrapper, browser }) => { + await page.click('#focusable-before'); + await page.focusNextElement(); + await page.keys(['Enter']); + const nextButtonSelector = wrapper.findDateRangePicker().findDropdown().findNextMonthButton().toSelector(); + await browser!.execute((sel: string) => { + const el = document.querySelector(sel); + if (el) { + el.dispatchEvent(new MouseEvent('dblclick', { bubbles: true })); + } + }, nextButtonSelector); + }, + }, + ], +}; + +export default suite; diff --git a/test/definitions/visual/expandable-section.ts b/test/definitions/visual/expandable-section.ts new file mode 100644 index 0000000000..3d1dcadab2 --- /dev/null +++ b/test/definitions/visual/expandable-section.ts @@ -0,0 +1,59 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestDefinition, TestSuite } from '../types'; + +const allVariants = ['container', 'default', 'footer', 'navigation']; +const variantsWithDescription = ['container', 'default', 'footer']; +const variantsWithInteractiveElements = ['container']; + +const headerText = 'Header text'; +const headerDescription = 'Header description'; + +function focusTest(options: Record): TestDefinition { + return { + description: `focus - ${JSON.stringify(options)}`, + path: 'expandable-section/focus', + screenshotType: 'screenshotArea' as const, + queryParams: options, + setup: async ({ page }) => { + await page.click('#focus-target'); + await page.focusNextElement(); + }, + }; +} + +const suite: TestSuite = { + description: 'Expandable section', + componentName: 'expandable-section', + tests: [ + { + description: 'permutations', + path: 'expandable-section/permutations', + screenshotType: 'permutations', + }, + { + description: 'container variant', + path: 'expandable-section/container-variant.permutations', + screenshotType: 'permutations', + }, + // Focus tests - with only heading + ...allVariants.map(variant => focusTest({ headerText, variant })), + // Focus tests - with heading and description + ...variantsWithDescription.map(variant => focusTest({ headerText, headerDescription, variant })), + // Focus tests - with interactive elements + ...variantsWithInteractiveElements.map(variant => + focusTest({ headerText, hasHeaderInfo: 'true', hasHeaderActions: 'true', variant }) + ), + // Focus tests - with interactive elements and description + ...variantsWithInteractiveElements.map(variant => + focusTest({ headerText, headerDescription, hasHeaderInfo: 'true', hasHeaderActions: 'true', variant }) + ), + { + description: 'stacked variant', + path: 'expandable-section/stacked-variant.permutations', + screenshotType: 'permutations', + }, + ], +}; + +export default suite; diff --git a/test/definitions/visual/flashbar-stacked.ts b/test/definitions/visual/flashbar-stacked.ts new file mode 100644 index 0000000000..2e14e597fa --- /dev/null +++ b/test/definitions/visual/flashbar-stacked.ts @@ -0,0 +1,43 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestDefinition, TestSuite } from '../types'; + +const widths = [380, 450, 600, 1200]; + +const suite: TestSuite = { + description: 'Flashbar stacked notifications', + componentName: 'flashbar', + tests: widths.flatMap(width => [ + { + description: `${width}px, collapsed`, + path: 'flashbar/collapsible.visual-tests', + screenshotType: 'screenshotArea' as const, + configuration: { width }, + }, + { + description: `${width}px, collapsed, notifications bar button focused`, + path: 'flashbar/collapsible.visual-tests', + screenshotType: 'screenshotArea' as const, + configuration: { width }, + setup: async ({ page }) => { + await page.click('#focus-target'); + await page.focusNextElement(); + }, + }, + { + description: `${width}px, expanded`, + path: 'flashbar/collapsible.visual-tests', + screenshotType: 'screenshotArea' as const, + configuration: { width }, + pixelDiffTolerance: 5, + setup: async ({ page }) => { + await page.click('#focus-target'); + await page.focusNextElement(); + await page.keys(['Space']); + await page.waitForJsTimers(500); + }, + }, + ]), +}; + +export default suite; diff --git a/test/definitions/visual/flashbar.ts b/test/definitions/visual/flashbar.ts new file mode 100644 index 0000000000..af200176ff --- /dev/null +++ b/test/definitions/visual/flashbar.ts @@ -0,0 +1,56 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestDefinition, TestSuite } from '../types'; + +const suite: TestSuite = { + description: 'Flashbar', + componentName: 'flashbar', + tests: [ + ...[600, 1280].flatMap(width => [ + { + description: `permutations at ${width}`, + path: 'flashbar/permutations', + screenshotType: 'permutations' as const, + configuration: { width }, + }, + { + description: `runtime-action at ${width}`, + path: 'flashbar/runtime-action', + screenshotType: 'permutations' as const, + configuration: { width }, + }, + ]), + { + description: 'content permutations', + path: 'flashbar/content-permutations', + screenshotType: 'permutations', + }, + { + description: 'style-custom', + path: 'flashbar/style-custom', + screenshotType: 'screenshotArea', + }, + { + description: 'small screen button layout', + path: 'flashbar/small-screen', + screenshotType: 'screenshotArea', + configuration: { width: 550 }, + }, + { + description: 'stacking of multiple flashbar items', + path: 'flashbar/stacking', + screenshotType: 'screenshotArea', + }, + { + description: 'focus border color', + path: 'flashbar/dismissal', + screenshotType: 'screenshotArea', + setup: async ({ page }) => { + await page.click('#focus-target'); + await page.focusNextElement(); + }, + }, + ], +}; + +export default suite; diff --git a/test/definitions/visual/form-field.ts b/test/definitions/visual/form-field.ts new file mode 100644 index 0000000000..7e45e111eb --- /dev/null +++ b/test/definitions/visual/form-field.ts @@ -0,0 +1,24 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestDefinition, TestSuite } from '../types'; + +const suite: TestSuite = { + description: 'FormField', + componentName: 'form-field', + tests: [576, 768, 992, 1200].flatMap(width => [ + { + description: `Permutations at ${width}px`, + path: 'form-field/permutations', + screenshotType: 'permutations' as const, + configuration: { width }, + }, + { + description: `Scenarios at ${width}px`, + path: 'form-field/form-field-columns', + screenshotType: 'screenshotArea' as const, + configuration: { width }, + }, + ]), +}; + +export default suite; diff --git a/test/definitions/visual/form.ts b/test/definitions/visual/form.ts new file mode 100644 index 0000000000..b5d155bbb6 --- /dev/null +++ b/test/definitions/visual/form.ts @@ -0,0 +1,22 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestSuite } from '../types'; + +const suite: TestSuite = { + description: 'Form', + componentName: 'form', + tests: [ + { + description: 'permutations', + path: 'form/permutations', + screenshotType: 'permutations', + }, + { + description: 'simple', + path: 'form/simple', + screenshotType: 'screenshotArea', + }, + ], +}; + +export default suite; diff --git a/test/definitions/visual/header.ts b/test/definitions/visual/header.ts new file mode 100644 index 0000000000..d63eb6e16f --- /dev/null +++ b/test/definitions/visual/header.ts @@ -0,0 +1,18 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestDefinition, TestSuite } from '../types'; + +const suite: TestSuite = { + description: 'Header', + componentName: 'header', + tests: [1, 2, 3].flatMap(level => + [1500, 850, 400].map(width => ({ + description: `level-${level} at ${width}px`, + path: `header/level-${level}`, + screenshotType: 'screenshotArea' as const, + configuration: { width }, + })) + ), +}; + +export default suite; diff --git a/test/definitions/visual/input.ts b/test/definitions/visual/input.ts new file mode 100644 index 0000000000..924414f2a2 --- /dev/null +++ b/test/definitions/visual/input.ts @@ -0,0 +1,22 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TestSuite } from '../types'; + +const suite: TestSuite = { + description: 'Input', + componentName: 'input', + tests: [ + { + description: 'permutations', + path: 'input/permutations', + screenshotType: 'permutations', + }, + { + description: 'style permutations', + path: 'input/style-permutations', + screenshotType: 'permutations', + }, + ], +}; + +export default suite; diff --git a/test/definitions/visual/item-card.ts b/test/definitions/visual/item-card.ts new file mode 100644 index 0000000000..3c6712c57b --- /dev/null +++ b/test/definitions/visual/item-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: 'Item card', + componentName: 'item-card', + tests: [ + { + description: 'permutations', + path: 'item-card/permutations', + screenshotType: 'permutations', + }, + { + description: 'variant permutations', + path: 'item-card/variant-permutations', + screenshotType: 'permutations', + }, + { + description: 'padding permutations', + path: 'item-card/padding-permutations', + screenshotType: 'permutations', + }, + { + description: 'style custom', + path: 'item-card/style-custom', + screenshotType: 'screenshotArea', + }, + ], +}; + +export default suite; diff --git a/test/visual/cards.test.ts b/test/visual/cards.test.ts new file mode 100644 index 0000000000..ecb282b266 --- /dev/null +++ b/test/visual/cards.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 '../definitions/utils'; +import suite from '../definitions/visual/cards'; + +runTestSuites([suite]); diff --git a/test/visual/checkbox.test.ts b/test/visual/checkbox.test.ts new file mode 100644 index 0000000000..f569e2e7ab --- /dev/null +++ b/test/visual/checkbox.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 '../definitions/utils'; +import suite from '../definitions/visual/checkbox'; + +runTestSuites([suite]); diff --git a/test/visual/code-editor.test.ts b/test/visual/code-editor.test.ts new file mode 100644 index 0000000000..735638ea5d --- /dev/null +++ b/test/visual/code-editor.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 '../definitions/utils'; +import suite from '../definitions/visual/code-editor'; + +runTestSuites([suite]); diff --git a/test/visual/collection-preferences.test.ts b/test/visual/collection-preferences.test.ts new file mode 100644 index 0000000000..ac54029622 --- /dev/null +++ b/test/visual/collection-preferences.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 '../definitions/utils'; +import suite from '../definitions/visual/collection-preferences'; + +runTestSuites([suite]); diff --git a/test/visual/column-layout.test.ts b/test/visual/column-layout.test.ts new file mode 100644 index 0000000000..55749a8e8b --- /dev/null +++ b/test/visual/column-layout.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 '../definitions/utils'; +import suite from '../definitions/visual/column-layout'; + +runTestSuites([suite]); diff --git a/test/visual/container-sticky.test.ts b/test/visual/container-sticky.test.ts new file mode 100644 index 0000000000..ce014d9805 --- /dev/null +++ b/test/visual/container-sticky.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 '../definitions/utils'; +import suite from '../definitions/visual/container-sticky'; + +runTestSuites([suite]); diff --git a/test/visual/container.test.ts b/test/visual/container.test.ts new file mode 100644 index 0000000000..d738bd8429 --- /dev/null +++ b/test/visual/container.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 '../definitions/utils'; +import suite from '../definitions/visual/container'; + +runTestSuites([suite]); diff --git a/test/visual/content-layout-permutations.test.ts b/test/visual/content-layout-permutations.test.ts new file mode 100644 index 0000000000..20de7543dc --- /dev/null +++ b/test/visual/content-layout-permutations.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 '../definitions/utils'; +import suite from '../definitions/visual/content-layout-permutations'; + +runTestSuites([suite]); diff --git a/test/visual/content-layout.test.ts b/test/visual/content-layout.test.ts new file mode 100644 index 0000000000..cc62ebf2a4 --- /dev/null +++ b/test/visual/content-layout.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 '../definitions/utils'; +import suite from '../definitions/visual/content-layout'; + +runTestSuites([suite]); diff --git a/test/visual/copy-to-clipboard.test.ts b/test/visual/copy-to-clipboard.test.ts new file mode 100644 index 0000000000..8f6f3afa5a --- /dev/null +++ b/test/visual/copy-to-clipboard.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 '../definitions/utils'; +import suite from '../definitions/visual/copy-to-clipboard'; + +runTestSuites([suite]); diff --git a/test/visual/date-input.test.ts b/test/visual/date-input.test.ts new file mode 100644 index 0000000000..d711364611 --- /dev/null +++ b/test/visual/date-input.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 '../definitions/utils'; +import suite from '../definitions/visual/date-input'; + +runTestSuites([suite]); diff --git a/test/visual/date-picker.test.ts b/test/visual/date-picker.test.ts new file mode 100644 index 0000000000..fd7087585b --- /dev/null +++ b/test/visual/date-picker.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 '../definitions/utils'; +import suite from '../definitions/visual/date-picker'; + +runTestSuites([suite]); diff --git a/test/visual/date-range-picker.test.ts b/test/visual/date-range-picker.test.ts new file mode 100644 index 0000000000..df5ed2d355 --- /dev/null +++ b/test/visual/date-range-picker.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 '../definitions/utils'; +import suite from '../definitions/visual/date-range-picker'; + +runTestSuites([suite]); diff --git a/test/visual/expandable-section.test.ts b/test/visual/expandable-section.test.ts new file mode 100644 index 0000000000..b052d5cf5f --- /dev/null +++ b/test/visual/expandable-section.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 '../definitions/utils'; +import suite from '../definitions/visual/expandable-section'; + +runTestSuites([suite]); diff --git a/test/visual/flashbar-stacked.test.ts b/test/visual/flashbar-stacked.test.ts new file mode 100644 index 0000000000..5e37f288af --- /dev/null +++ b/test/visual/flashbar-stacked.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 '../definitions/utils'; +import suite from '../definitions/visual/flashbar-stacked'; + +runTestSuites([suite]); diff --git a/test/visual/flashbar.test.ts b/test/visual/flashbar.test.ts new file mode 100644 index 0000000000..d02aaae84c --- /dev/null +++ b/test/visual/flashbar.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 '../definitions/utils'; +import suite from '../definitions/visual/flashbar'; + +runTestSuites([suite]); diff --git a/test/visual/form-field.test.ts b/test/visual/form-field.test.ts new file mode 100644 index 0000000000..8406e9351b --- /dev/null +++ b/test/visual/form-field.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 '../definitions/utils'; +import suite from '../definitions/visual/form-field'; + +runTestSuites([suite]); diff --git a/test/visual/form.test.ts b/test/visual/form.test.ts new file mode 100644 index 0000000000..89a44f2ec8 --- /dev/null +++ b/test/visual/form.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 '../definitions/utils'; +import suite from '../definitions/visual/form'; + +runTestSuites([suite]); diff --git a/test/visual/header.test.ts b/test/visual/header.test.ts new file mode 100644 index 0000000000..c08e211517 --- /dev/null +++ b/test/visual/header.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 '../definitions/utils'; +import suite from '../definitions/visual/header'; + +runTestSuites([suite]); diff --git a/test/visual/input.test.ts b/test/visual/input.test.ts new file mode 100644 index 0000000000..361de80dd5 --- /dev/null +++ b/test/visual/input.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 '../definitions/utils'; +import suite from '../definitions/visual/input'; + +runTestSuites([suite]); diff --git a/test/visual/item-card.test.ts b/test/visual/item-card.test.ts new file mode 100644 index 0000000000..1c06f6493b --- /dev/null +++ b/test/visual/item-card.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 '../definitions/utils'; +import suite from '../definitions/visual/item-card'; + +runTestSuites([suite]); From f53ab2a99ed85949aea72eae0874e475eac0bd64 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Fri, 24 Jul 2026 17:44:26 +0200 Subject: [PATCH 02/10] Fix types --- test/definitions/visual/container-sticky.ts | 87 ++++++++------------- 1 file changed, 31 insertions(+), 56 deletions(-) diff --git a/test/definitions/visual/container-sticky.ts b/test/definitions/visual/container-sticky.ts index 60b2a6f217..d16f05262b 100644 --- a/test/definitions/visual/container-sticky.ts +++ b/test/definitions/visual/container-sticky.ts @@ -2,66 +2,41 @@ // SPDX-License-Identifier: Apache-2.0 import { TestDefinition, TestSuite } from '../types'; +function stickyTest( + width: number, + description: string, + params?: Record, + withScroll = true +): TestDefinition { + return { + description, + path: 'container/sticky-permutations', + screenshotType: 'viewport', + configuration: { width }, + ...(params && { queryParams: params }), + ...(withScroll && { + setup: async ({ page }) => { + await page.windowScrollTo({ top: 200 }); + }, + }), + }; +} + const suite: TestSuite = { description: 'Container sticky permutations', componentName: 'container', tests: [1400, 600].flatMap(width => [ - { - description: `simple - at ${width}px`, - path: 'container/sticky-permutations', - screenshotType: 'viewport' as const, - configuration: { width }, - setup: async ({ page }) => { - await page.windowScrollTo({ top: 200 }); - }, - }, - { - description: `with notifications - at ${width}px`, - path: 'container/sticky-permutations', - screenshotType: 'viewport' as const, - configuration: { width }, - queryParams: { hasNotifications: 'true' }, - setup: async ({ page }) => { - await page.windowScrollTo({ top: 200 }); - }, - }, - { - description: `with breadcrumbs - at ${width}px`, - path: 'container/sticky-permutations', - screenshotType: 'viewport' as const, - configuration: { width }, - queryParams: { hasBreadcrumbs: 'true' }, - setup: async ({ page }) => { - await page.windowScrollTo({ top: 200 }); - }, - }, - { - description: `with an alert - at ${width}px`, - path: 'container/sticky-permutations', - screenshotType: 'viewport' as const, - configuration: { width }, - queryParams: { hasNotifications: 'true', hasAlert: 'true' }, - setup: async ({ page }) => { - await page.windowScrollTo({ top: 200 }); - }, - }, - { - description: `with an alert - at ${width}px without scroll`, - path: 'container/sticky-permutations', - screenshotType: 'viewport' as const, - configuration: { width }, - queryParams: { hasNotifications: 'true', hasAlert: 'true' }, - }, - { - description: `with high-contrast header - at ${width}px`, - path: 'container/sticky-permutations', - screenshotType: 'viewport' as const, - configuration: { width }, - queryParams: { hasNotifications: 'true', highContrast: 'true' }, - setup: async ({ page }) => { - await page.windowScrollTo({ top: 200 }); - }, - }, + stickyTest(width, `simple - at ${width}px`), + stickyTest(width, `with notifications - at ${width}px`, { hasNotifications: 'true' }), + stickyTest(width, `with breadcrumbs - at ${width}px`, { hasBreadcrumbs: 'true' }), + stickyTest(width, `with an alert - at ${width}px`, { hasNotifications: 'true', hasAlert: 'true' }), + stickyTest( + width, + `with an alert - at ${width}px without scroll`, + { hasNotifications: 'true', hasAlert: 'true' }, + false + ), + stickyTest(width, `with high-contrast header - at ${width}px`, { hasNotifications: 'true', highContrast: 'true' }), ]), }; From bde504ccb1ad1e14276ee7c9c1154750e92567c9 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Fri, 24 Jul 2026 18:59:39 +0200 Subject: [PATCH 03/10] Fix wait for Ace --- test/definitions/visual/code-editor.ts | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/test/definitions/visual/code-editor.ts b/test/definitions/visual/code-editor.ts index a1550118e2..b188ec4625 100644 --- a/test/definitions/visual/code-editor.ts +++ b/test/definitions/visual/code-editor.ts @@ -1,8 +1,21 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +import VisualTestPageObject from '../page-object'; import { TestSuite } from '../types'; -const ACE_SELECTOR = '.ace_editor.ace-dawn, .ace_editor.ace-tomorrow-night-bright'; +async function waitForAceTheme(page: VisualTestPageObject) { + await page.waitForAssertion(async () => { + const found = await (page as any).browser.execute(() => { + const el: HTMLElement | null = document.querySelector( + '.ace_editor.ace-dawn, .ace_editor.ace-tomorrow-night-bright' + ); + return el !== null && el.offsetHeight > 0; + }); + if (!found) { + throw new Error('Ace editor with theme class not found or not visible'); + } + }); +} const suite: TestSuite = { description: 'Code editor', @@ -13,7 +26,7 @@ const suite: TestSuite = { path: 'code-editor/simple', screenshotType: 'screenshotArea', setup: async ({ page }) => { - await page.waitForVisible(ACE_SELECTOR); + await waitForAceTheme(page); }, }, { @@ -31,7 +44,7 @@ const suite: TestSuite = { path: 'code-editor/themes', screenshotType: 'screenshotArea', setup: async ({ page }) => { - await page.waitForVisible(ACE_SELECTOR); + await waitForAceTheme(page); }, }, { @@ -39,7 +52,7 @@ const suite: TestSuite = { path: 'code-editor/permutations', screenshotType: 'permutations', setup: async ({ page }) => { - await page.waitForVisible(ACE_SELECTOR + ' .ace_error'); + await waitForAceTheme(page); await page.waitForVisible('.ace_gutter-cell.ace_gutter-active-line.ace_error'); }, }, @@ -48,7 +61,7 @@ const suite: TestSuite = { path: 'code-editor/simple', screenshotType: 'screenshotArea', setup: async ({ page }) => { - await page.waitForVisible(ACE_SELECTOR); + await waitForAceTheme(page); await page.click('#mode-toggle'); }, }, @@ -58,7 +71,7 @@ const suite: TestSuite = { screenshotType: 'screenshotArea', configuration: { width: 360 }, setup: async ({ page }) => { - await page.waitForVisible(ACE_SELECTOR); + await waitForAceTheme(page); }, }, ], From 4fbdc330f4311ad8ef07f893dcf549c2fb2facc3 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Fri, 24 Jul 2026 19:06:19 +0200 Subject: [PATCH 04/10] Remove some tests --- test/definitions/visual/flashbar-stacked.ts | 43 ---------------- test/definitions/visual/flashbar.ts | 56 --------------------- test/definitions/visual/form-field.ts | 24 --------- test/definitions/visual/form.ts | 22 -------- test/definitions/visual/header.ts | 18 ------- test/definitions/visual/input.ts | 22 -------- test/definitions/visual/item-card.ts | 32 ------------ test/visual/flashbar-stacked.test.ts | 6 --- test/visual/flashbar.test.ts | 6 --- test/visual/form-field.test.ts | 6 --- test/visual/form.test.ts | 6 --- test/visual/header.test.ts | 6 --- test/visual/input.test.ts | 6 --- test/visual/item-card.test.ts | 6 --- 14 files changed, 259 deletions(-) delete mode 100644 test/definitions/visual/flashbar-stacked.ts delete mode 100644 test/definitions/visual/flashbar.ts delete mode 100644 test/definitions/visual/form-field.ts delete mode 100644 test/definitions/visual/form.ts delete mode 100644 test/definitions/visual/header.ts delete mode 100644 test/definitions/visual/input.ts delete mode 100644 test/definitions/visual/item-card.ts delete mode 100644 test/visual/flashbar-stacked.test.ts delete mode 100644 test/visual/flashbar.test.ts delete mode 100644 test/visual/form-field.test.ts delete mode 100644 test/visual/form.test.ts delete mode 100644 test/visual/header.test.ts delete mode 100644 test/visual/input.test.ts delete mode 100644 test/visual/item-card.test.ts diff --git a/test/definitions/visual/flashbar-stacked.ts b/test/definitions/visual/flashbar-stacked.ts deleted file mode 100644 index 2e14e597fa..0000000000 --- a/test/definitions/visual/flashbar-stacked.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import { TestDefinition, TestSuite } from '../types'; - -const widths = [380, 450, 600, 1200]; - -const suite: TestSuite = { - description: 'Flashbar stacked notifications', - componentName: 'flashbar', - tests: widths.flatMap(width => [ - { - description: `${width}px, collapsed`, - path: 'flashbar/collapsible.visual-tests', - screenshotType: 'screenshotArea' as const, - configuration: { width }, - }, - { - description: `${width}px, collapsed, notifications bar button focused`, - path: 'flashbar/collapsible.visual-tests', - screenshotType: 'screenshotArea' as const, - configuration: { width }, - setup: async ({ page }) => { - await page.click('#focus-target'); - await page.focusNextElement(); - }, - }, - { - description: `${width}px, expanded`, - path: 'flashbar/collapsible.visual-tests', - screenshotType: 'screenshotArea' as const, - configuration: { width }, - pixelDiffTolerance: 5, - setup: async ({ page }) => { - await page.click('#focus-target'); - await page.focusNextElement(); - await page.keys(['Space']); - await page.waitForJsTimers(500); - }, - }, - ]), -}; - -export default suite; diff --git a/test/definitions/visual/flashbar.ts b/test/definitions/visual/flashbar.ts deleted file mode 100644 index af200176ff..0000000000 --- a/test/definitions/visual/flashbar.ts +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import { TestDefinition, TestSuite } from '../types'; - -const suite: TestSuite = { - description: 'Flashbar', - componentName: 'flashbar', - tests: [ - ...[600, 1280].flatMap(width => [ - { - description: `permutations at ${width}`, - path: 'flashbar/permutations', - screenshotType: 'permutations' as const, - configuration: { width }, - }, - { - description: `runtime-action at ${width}`, - path: 'flashbar/runtime-action', - screenshotType: 'permutations' as const, - configuration: { width }, - }, - ]), - { - description: 'content permutations', - path: 'flashbar/content-permutations', - screenshotType: 'permutations', - }, - { - description: 'style-custom', - path: 'flashbar/style-custom', - screenshotType: 'screenshotArea', - }, - { - description: 'small screen button layout', - path: 'flashbar/small-screen', - screenshotType: 'screenshotArea', - configuration: { width: 550 }, - }, - { - description: 'stacking of multiple flashbar items', - path: 'flashbar/stacking', - screenshotType: 'screenshotArea', - }, - { - description: 'focus border color', - path: 'flashbar/dismissal', - screenshotType: 'screenshotArea', - setup: async ({ page }) => { - await page.click('#focus-target'); - await page.focusNextElement(); - }, - }, - ], -}; - -export default suite; diff --git a/test/definitions/visual/form-field.ts b/test/definitions/visual/form-field.ts deleted file mode 100644 index 7e45e111eb..0000000000 --- a/test/definitions/visual/form-field.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import { TestDefinition, TestSuite } from '../types'; - -const suite: TestSuite = { - description: 'FormField', - componentName: 'form-field', - tests: [576, 768, 992, 1200].flatMap(width => [ - { - description: `Permutations at ${width}px`, - path: 'form-field/permutations', - screenshotType: 'permutations' as const, - configuration: { width }, - }, - { - description: `Scenarios at ${width}px`, - path: 'form-field/form-field-columns', - screenshotType: 'screenshotArea' as const, - configuration: { width }, - }, - ]), -}; - -export default suite; diff --git a/test/definitions/visual/form.ts b/test/definitions/visual/form.ts deleted file mode 100644 index b5d155bbb6..0000000000 --- a/test/definitions/visual/form.ts +++ /dev/null @@ -1,22 +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: 'Form', - componentName: 'form', - tests: [ - { - description: 'permutations', - path: 'form/permutations', - screenshotType: 'permutations', - }, - { - description: 'simple', - path: 'form/simple', - screenshotType: 'screenshotArea', - }, - ], -}; - -export default suite; diff --git a/test/definitions/visual/header.ts b/test/definitions/visual/header.ts deleted file mode 100644 index d63eb6e16f..0000000000 --- a/test/definitions/visual/header.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import { TestDefinition, TestSuite } from '../types'; - -const suite: TestSuite = { - description: 'Header', - componentName: 'header', - tests: [1, 2, 3].flatMap(level => - [1500, 850, 400].map(width => ({ - description: `level-${level} at ${width}px`, - path: `header/level-${level}`, - screenshotType: 'screenshotArea' as const, - configuration: { width }, - })) - ), -}; - -export default suite; diff --git a/test/definitions/visual/input.ts b/test/definitions/visual/input.ts deleted file mode 100644 index 924414f2a2..0000000000 --- a/test/definitions/visual/input.ts +++ /dev/null @@ -1,22 +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: 'Input', - componentName: 'input', - tests: [ - { - description: 'permutations', - path: 'input/permutations', - screenshotType: 'permutations', - }, - { - description: 'style permutations', - path: 'input/style-permutations', - screenshotType: 'permutations', - }, - ], -}; - -export default suite; diff --git a/test/definitions/visual/item-card.ts b/test/definitions/visual/item-card.ts deleted file mode 100644 index 3c6712c57b..0000000000 --- a/test/definitions/visual/item-card.ts +++ /dev/null @@ -1,32 +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: 'Item card', - componentName: 'item-card', - tests: [ - { - description: 'permutations', - path: 'item-card/permutations', - screenshotType: 'permutations', - }, - { - description: 'variant permutations', - path: 'item-card/variant-permutations', - screenshotType: 'permutations', - }, - { - description: 'padding permutations', - path: 'item-card/padding-permutations', - screenshotType: 'permutations', - }, - { - description: 'style custom', - path: 'item-card/style-custom', - screenshotType: 'screenshotArea', - }, - ], -}; - -export default suite; diff --git a/test/visual/flashbar-stacked.test.ts b/test/visual/flashbar-stacked.test.ts deleted file mode 100644 index 5e37f288af..0000000000 --- a/test/visual/flashbar-stacked.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import { runTestSuites } from '../definitions/utils'; -import suite from '../definitions/visual/flashbar-stacked'; - -runTestSuites([suite]); diff --git a/test/visual/flashbar.test.ts b/test/visual/flashbar.test.ts deleted file mode 100644 index d02aaae84c..0000000000 --- a/test/visual/flashbar.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import { runTestSuites } from '../definitions/utils'; -import suite from '../definitions/visual/flashbar'; - -runTestSuites([suite]); diff --git a/test/visual/form-field.test.ts b/test/visual/form-field.test.ts deleted file mode 100644 index 8406e9351b..0000000000 --- a/test/visual/form-field.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import { runTestSuites } from '../definitions/utils'; -import suite from '../definitions/visual/form-field'; - -runTestSuites([suite]); diff --git a/test/visual/form.test.ts b/test/visual/form.test.ts deleted file mode 100644 index 89a44f2ec8..0000000000 --- a/test/visual/form.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import { runTestSuites } from '../definitions/utils'; -import suite from '../definitions/visual/form'; - -runTestSuites([suite]); diff --git a/test/visual/header.test.ts b/test/visual/header.test.ts deleted file mode 100644 index c08e211517..0000000000 --- a/test/visual/header.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import { runTestSuites } from '../definitions/utils'; -import suite from '../definitions/visual/header'; - -runTestSuites([suite]); diff --git a/test/visual/input.test.ts b/test/visual/input.test.ts deleted file mode 100644 index 361de80dd5..0000000000 --- a/test/visual/input.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import { runTestSuites } from '../definitions/utils'; -import suite from '../definitions/visual/input'; - -runTestSuites([suite]); diff --git a/test/visual/item-card.test.ts b/test/visual/item-card.test.ts deleted file mode 100644 index 1c06f6493b..0000000000 --- a/test/visual/item-card.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import { runTestSuites } from '../definitions/utils'; -import suite from '../definitions/visual/item-card'; - -runTestSuites([suite]); From 461f948fe0a5fa8dcbb4da3899f1f1f62b174ee0 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Fri, 24 Jul 2026 19:06:35 +0200 Subject: [PATCH 05/10] Increase shards --- .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 03fef630db..429e8d61bb 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -33,7 +33,7 @@ jobs: strategy: fail-fast: false matrix: - shard: [1, 2] + shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] steps: - uses: actions/checkout@v4 @@ -111,7 +111,7 @@ jobs: - name: Run visual regression tests run: | echo "NEW_HOST=${NEW_HOST}" - NODE_OPTIONS=--experimental-vm-modules node_modules/.bin/jest -c jest.visual.config.js --shard="${SHARD}/2" + NODE_OPTIONS=--experimental-vm-modules node_modules/.bin/jest -c jest.visual.config.js --shard="${SHARD}/10" env: TZ: UTC SHARD: ${{ matrix.shard }} From 535ba8ba124d00895d6cf5076d99e15ed29a8c39 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Fri, 24 Jul 2026 19:18:31 +0200 Subject: [PATCH 06/10] Missing changes --- test/definitions/index.ts | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/test/definitions/index.ts b/test/definitions/index.ts index 5bd0193013..ac3bc792ca 100644 --- a/test/definitions/index.ts +++ b/test/definitions/index.ts @@ -42,13 +42,6 @@ import dateInputSuite from './visual/date-input'; import datePickerSuite from './visual/date-picker'; import dateRangePickerSuite from './visual/date-range-picker'; import expandableSectionSuite from './visual/expandable-section'; -import flashbarSuite from './visual/flashbar'; -import flashbarStackedSuite from './visual/flashbar-stacked'; -import formSuite from './visual/form'; -import formFieldSuite from './visual/form-field'; -import headerSuite from './visual/header'; -import inputSuite from './visual/input'; -import itemCardSuite from './visual/item-card'; // Per-component exports (grouped by component) export const actionCard: TestSuite[] = [actionCardSuite]; @@ -87,12 +80,6 @@ export const dateInput: TestSuite[] = [dateInputSuite]; export const datePicker: TestSuite[] = [datePickerSuite]; export const dateRangePicker: TestSuite[] = [dateRangePickerSuite]; export const expandableSection: TestSuite[] = [expandableSectionSuite]; -export const flashbar: TestSuite[] = [flashbarSuite, flashbarStackedSuite]; -export const form: TestSuite[] = [formSuite]; -export const formField: TestSuite[] = [formFieldSuite]; -export const header: TestSuite[] = [headerSuite]; -export const input: TestSuite[] = [inputSuite]; -export const itemCard: TestSuite[] = [itemCardSuite]; export const allSuites: TestSuite[] = [ ...actionCard, @@ -120,10 +107,4 @@ export const allSuites: TestSuite[] = [ ...datePicker, ...dateRangePicker, ...expandableSection, - ...flashbar, - ...form, - ...formField, - ...header, - ...input, - ...itemCard, ]; From 16c06ce930360fd04b943c26202ba2a9fd9d6136 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 27 Jul 2026 08:45:54 +0200 Subject: [PATCH 07/10] Fix waiting for Ace --- test/definitions/visual/code-editor.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/test/definitions/visual/code-editor.ts b/test/definitions/visual/code-editor.ts index b188ec4625..f21c8c4a4a 100644 --- a/test/definitions/visual/code-editor.ts +++ b/test/definitions/visual/code-editor.ts @@ -4,8 +4,21 @@ import VisualTestPageObject from '../page-object'; import { TestSuite } from '../types'; async function waitForAceTheme(page: VisualTestPageObject) { + // Force a full page reload so that the CSP meta tag is re-evaluated + // with the code-editor hash (which adds worker-src: blob:). + // Without this, hash-only navigation from a prior test leaves the old CSP + // in place and ace's web worker is blocked. + const browser = (page as any).browser; + // Navigate away and back to guarantee a fresh document load. + // Unlike location.reload(), this is synchronous from WebdriverIO's + // perspective — the subsequent browser.url() won't execute until + // about:blank has fully loaded, ensuring no stale DOM races. + const currentUrl = await browser.getUrl(); + await browser.url('about:blank'); + await browser.url(currentUrl); + await page.waitForVisible('.screenshot-area'); await page.waitForAssertion(async () => { - const found = await (page as any).browser.execute(() => { + const found = await browser.execute(() => { const el: HTMLElement | null = document.querySelector( '.ace_editor.ace-dawn, .ace_editor.ace-tomorrow-night-bright' ); From a29d7ffc44579551bbcbcf096607ad2bf46d83ed Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 27 Jul 2026 09:13:29 +0200 Subject: [PATCH 08/10] Wait for longer --- test/definitions/visual/code-editor.ts | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/test/definitions/visual/code-editor.ts b/test/definitions/visual/code-editor.ts index f21c8c4a4a..31ec1d3c27 100644 --- a/test/definitions/visual/code-editor.ts +++ b/test/definitions/visual/code-editor.ts @@ -17,17 +17,19 @@ async function waitForAceTheme(page: VisualTestPageObject) { await browser.url('about:blank'); await browser.url(currentUrl); await page.waitForVisible('.screenshot-area'); - await page.waitForAssertion(async () => { - const found = await browser.execute(() => { - const el: HTMLElement | null = document.querySelector( - '.ace_editor.ace-dawn, .ace_editor.ace-tomorrow-night-bright' - ); - return el !== null && el.offsetHeight > 0; - }); - if (!found) { - throw new Error('Ace editor with theme class not found or not visible'); - } - }); + // Ace loads asynchronously and needs time to initialize + apply theme. + // waitForAssertion only retries 5 times (~500ms) which isn't enough in CI. + // Use waitUntil with a generous timeout instead. + await browser.waitUntil( + () => + browser.execute(() => { + const el: HTMLElement | null = document.querySelector( + '.ace_editor.ace-dawn, .ace_editor.ace-tomorrow-night-bright' + ); + return el !== null && el.offsetHeight > 0; + }), + { timeout: 30000, timeoutMsg: 'Ace editor with theme class not found or not visible within 30s' } + ); } const suite: TestSuite = { From 5f67ae066012fab012b5dafe49a64c7739615ffa Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 27 Jul 2026 10:12:54 +0200 Subject: [PATCH 09/10] Do not reuse browser --- test/definitions/utils.ts | 116 ++++++++++++------------- test/definitions/visual/code-editor.ts | 17 +--- 2 files changed, 59 insertions(+), 74 deletions(-) diff --git a/test/definitions/utils.ts b/test/definitions/utils.ts index a70701ff1e..1e22d52b03 100644 --- a/test/definitions/utils.ts +++ b/test/definitions/utils.ts @@ -60,81 +60,79 @@ async function preparePage( } export function runTestSuites(suites: Array) { - let browser: WebdriverIO.Browser; - - beforeAll(async () => { - const { default: getBrowserCreator } = await import('@cloudscape-design/browser-test-tools/browser'); - const creator = getBrowserCreator('ChromeHeadlessIntegration', 'local', { - seleniumUrl: 'http://localhost:9515', - }); - browser = await creator.getBrowser({ width: defaultWindowSize.width, height: defaultWindowSize.height }); - }); - - afterAll(async () => { - await browser?.deleteSession(); - }); - - registerSuites(suites, () => browser); + registerSuites(suites); } -function registerSuites(suites: Array, getBrowser: () => WebdriverIO.Browser) { +function registerSuites(suites: Array) { for (const item of suites) { if (isTestDefinition(item)) { - registerTest(item, getBrowser); + registerTest(item); } else { describe(item.description, () => { - registerSuites(item.tests, getBrowser); + registerSuites(item.tests); }); } } } -function registerTest(testDef: TestDefinition, getBrowser: () => WebdriverIO.Browser) { +function registerTest(testDef: TestDefinition) { test(testDef.description, async () => { - const browser = getBrowser(); - const page = new VisualTestPageObject(browser); - - const newUrl = buildUrl(newHost, testDef.path, testDef.queryParams); - const oldUrl = buildUrl(oldHost, testDef.path, testDef.queryParams); - - const tolerance = testDef.pixelDiffTolerance ?? 0; + const { default: getBrowserCreator } = await import('@cloudscape-design/browser-test-tools/browser'); + const creator = getBrowserCreator('ChromeHeadlessIntegration', 'local', { + seleniumUrl: 'http://localhost:9515', + }); + const windowSize = { + width: testDef.configuration?.width ?? defaultWindowSize.width, + height: testDef.configuration?.height ?? defaultWindowSize.height, + }; + const browser = await creator.getBrowser(windowSize); + try { + const page = new VisualTestPageObject(browser); + + const newUrl = buildUrl(newHost, testDef.path, testDef.queryParams); + const oldUrl = buildUrl(oldHost, testDef.path, testDef.queryParams); + + const tolerance = testDef.pixelDiffTolerance ?? 0; + + if (testDef.screenshotType === 'permutations') { + await preparePage(browser, page, newUrl, testDef, testDef.configuration); + const newPermutations = await page.capturePermutations(); + + await preparePage(browser, page, oldUrl, testDef, testDef.configuration); + const oldPermutations = await page.capturePermutations(); + + expect(newPermutations.length).toBe(oldPermutations.length); + + // Compare each permutation individually, wrapping each in an Allure step. + let failures = 0; + for (let i = 0; i < newPermutations.length; i++) { + const id = newPermutations[i].id || ''; + const index = `#${(i + 1).toString().padStart(3, '0')}`; + await step(`Permutation ${index}`, async () => { + const permResult = await compareScreenshots(newPermutations[i], oldPermutations[i]); + await attachDiffImages(permResult, index); + if (permResult.diffPixels > tolerance) { + failures++; + throw new Error(`Permutation ${index} differs by ${permResult.diffPixels} pixels\n${id}`); + } + }); + } + expect(failures).toBe(0); + return; + } - if (testDef.screenshotType === 'permutations') { + // Non-permutation: single screenshot comparison await preparePage(browser, page, newUrl, testDef, testDef.configuration); - const newPermutations = await page.capturePermutations(); + const newRaw = await captureSingleScreenshot(page, testDef); await preparePage(browser, page, oldUrl, testDef, testDef.configuration); - const oldPermutations = await page.capturePermutations(); - - expect(newPermutations.length).toBe(oldPermutations.length); - - // Compare each permutation individually, wrapping each in an Allure step. - let failures = 0; - for (let i = 0; i < newPermutations.length; i++) { - const id = newPermutations[i].id || ''; - const index = `#${(i + 1).toString().padStart(3, '0')}`; - await step(`Permutation ${index}`, async () => { - const permResult = await compareScreenshots(newPermutations[i], oldPermutations[i]); - await attachDiffImages(permResult, index); - if (permResult.diffPixels > tolerance) { - failures++; - throw new Error(`Permutation ${index} differs by ${permResult.diffPixels} pixels\n${id}`); - } - }); - } - expect(failures).toBe(0); - return; - } - - // Non-permutation: single screenshot comparison - await preparePage(browser, page, newUrl, testDef, testDef.configuration); - const newRaw = await captureSingleScreenshot(page, testDef); + const oldRaw = await captureSingleScreenshot(page, testDef); - await preparePage(browser, page, oldUrl, testDef, testDef.configuration); - const oldRaw = await captureSingleScreenshot(page, testDef); - - const result = await compareScreenshots(newRaw, oldRaw); - await attachDiffImages(result, testDef.description); - expect(result.diffPixels).toBeLessThanOrEqual(tolerance); + const result = await compareScreenshots(newRaw, oldRaw); + await attachDiffImages(result, testDef.description); + expect(result.diffPixels).toBeLessThanOrEqual(tolerance); + } finally { + await browser.deleteSession(); + } }); } diff --git a/test/definitions/visual/code-editor.ts b/test/definitions/visual/code-editor.ts index 31ec1d3c27..66569322c1 100644 --- a/test/definitions/visual/code-editor.ts +++ b/test/definitions/visual/code-editor.ts @@ -4,22 +4,9 @@ import VisualTestPageObject from '../page-object'; import { TestSuite } from '../types'; async function waitForAceTheme(page: VisualTestPageObject) { - // Force a full page reload so that the CSP meta tag is re-evaluated - // with the code-editor hash (which adds worker-src: blob:). - // Without this, hash-only navigation from a prior test leaves the old CSP - // in place and ace's web worker is blocked. + // Each test gets a fresh browser session, so CSP is always correct. + // Ace loads asynchronously — wait for it to initialize and apply the theme. const browser = (page as any).browser; - // Navigate away and back to guarantee a fresh document load. - // Unlike location.reload(), this is synchronous from WebdriverIO's - // perspective — the subsequent browser.url() won't execute until - // about:blank has fully loaded, ensuring no stale DOM races. - const currentUrl = await browser.getUrl(); - await browser.url('about:blank'); - await browser.url(currentUrl); - await page.waitForVisible('.screenshot-area'); - // Ace loads asynchronously and needs time to initialize + apply theme. - // waitForAssertion only retries 5 times (~500ms) which isn't enough in CI. - // Use waitUntil with a generous timeout instead. await browser.waitUntil( () => browser.execute(() => { From e7dfaa24675112c2b3474963aaac681d20bb4620 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Mon, 27 Jul 2026 10:39:50 +0200 Subject: [PATCH 10/10] Debug --- test/definitions/visual/code-editor.ts | 37 +++++++++++++++++++------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/test/definitions/visual/code-editor.ts b/test/definitions/visual/code-editor.ts index 66569322c1..9f6fb46785 100644 --- a/test/definitions/visual/code-editor.ts +++ b/test/definitions/visual/code-editor.ts @@ -7,16 +7,33 @@ async function waitForAceTheme(page: VisualTestPageObject) { // Each test gets a fresh browser session, so CSP is always correct. // Ace loads asynchronously — wait for it to initialize and apply the theme. const browser = (page as any).browser; - await browser.waitUntil( - () => - browser.execute(() => { - const el: HTMLElement | null = document.querySelector( - '.ace_editor.ace-dawn, .ace_editor.ace-tomorrow-night-bright' - ); - return el !== null && el.offsetHeight > 0; - }), - { timeout: 30000, timeoutMsg: 'Ace editor with theme class not found or not visible within 30s' } - ); + try { + await browser.waitUntil( + () => + browser.execute(() => { + const el: HTMLElement | null = document.querySelector( + '.ace_editor.ace-dawn, .ace_editor.ace-tomorrow-night-bright' + ); + return el !== null && el.offsetHeight > 0; + }), + { timeout: 30000, timeoutMsg: 'Ace editor with theme class not found or not visible within 30s' } + ); + } catch (e) { + // Capture a debug screenshot and page state on failure + const screenshot = await browser.takeScreenshot(); + const debugInfo = await browser.execute(() => { + const ace = document.querySelector('.ace_editor'); + const classes = ace ? ace.className : 'NO .ace_editor ELEMENT FOUND'; + const cspMeta = document.querySelector('meta[http-equiv="Content-Security-Policy"]'); + const csp = cspMeta ? cspMeta.getAttribute('content') : 'NO CSP META TAG'; + const url = location.href; + return { classes, csp, url, bodyHTML: document.body.innerHTML.slice(0, 2000) }; + }); + const { attachment } = await import('allure-js-commons'); + await attachment('debug-screenshot-on-failure', Buffer.from(screenshot, 'base64'), 'image/png'); + await attachment('debug-page-state', JSON.stringify(debugInfo, null, 2), 'application/json'); + throw e; + } } const suite: TestSuite = {