From 8faed3bb910e9086ea0b90a5fafe102bf70471c1 Mon Sep 17 00:00:00 2001 From: Lukas Briza Date: Fri, 21 Mar 2025 12:08:38 +0100 Subject: [PATCH 1/2] Add `writing-tests-guidelines.md` (#612) --- mkdocs.yml | 4 +- .../contribute/writing-tests-guidelines.md | 291 ++++++++++++++++++ 2 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 src/docs/contribute/writing-tests-guidelines.md diff --git a/mkdocs.yml b/mkdocs.yml index d62737084..3fbe8ed23 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -143,7 +143,9 @@ nav: - Translations: 'docs/customize/translations.md' - Contribute: - General Guidelines: 'docs/contribute/general-guidelines.md' - - Testing Guidelines: 'docs/contribute/testing-guidelines.md' + - Testing Guidelines: + - Testing: 'docs/contribute/testing-guidelines.md' + - Writing Tests: 'docs/contribute/writing-tests-guidelines.md' - API Guidelines: 'docs/contribute/api.md' - Composition: 'docs/contribute/composition.md' - CSS Guidelines: 'docs/contribute/css.md' diff --git a/src/docs/contribute/writing-tests-guidelines.md b/src/docs/contribute/writing-tests-guidelines.md new file mode 100644 index 000000000..d3f9a0c94 --- /dev/null +++ b/src/docs/contribute/writing-tests-guidelines.md @@ -0,0 +1,291 @@ +# Playwright Testing Structure + +This document outlines the structure and guidelines for writing Playwright +tests, ensuring consistency and maintainability throughout the codebase. + +## Folder Structure + +Playwright tests are organized into multiple files and folders, each +serving a specific purpose. + +The complete component structure should be as follows: + +```text +/ +├── __tests__/ +│ ├── .spec.tsx-snapshots/ +│ ├── _propTests/ +│ │ ├── .ts +│ ├── .spec.tsx +│ ├── .story.tsx +├── ...rest component files +``` + +- `` - Root folder of the component, named after +the component itself. +- `.ts` - Defines local test property combinations used only +within the context of the tested component. Global tests shared across +the project are located in `tests/playwright/propTests`. +- `.spec.tsx` - Contains all tests, structured as +described below. +- `.story.tsx` - Includes all component definitions used +in tests. These components should be functional without requiring any +properties to be passed from the tests. + +## File Structure of `.spec.tsx` + +Playwright tests follow a structured format to ensure readability +and scalability. Each displayed level represents a `test.describe(...)` block. +The structure consists of: + +```text +/ +├── base/ +│ ├── visual/ +│ │ ├── fullPage/ +│ ├── non-visual/ +│ ├── functionality/ +├── formLayout/ +│ ├── visual/ +│ ├── non-visual/ +│ ├── functionality/ +``` + +- `` - Groups all tests for the tested component. +- `base` - Contains component tests without any additional layout. +- `visual` - Tests that compare the component state against snapshots. +- `fullPage` - Subgroup of visual tests that must be performed +on a full-scale page. +- `non-visual` - Validates non-functional properties (e.g., `id` or `ref`). +- `functionality` - Validates properties that affect the component's behavior +(e.g., `onChange`). +- `formLayout` - Contains tests for the component wrapped in ``. + +Test block categories can be expanded or removed depending on the nature +of the tested component and whether a predefined test block is applicable +in a specific case. + +## File Structure of `.story.tsx` + +The `.story.tsx` file should include all component variants +tested in `.spec.tsx`. Components should be organized +in the following order: + +1. Component for normal tests (`ForTest`) +2. Component for `ref` attribute tests (`ForRefTest`) +3. Component for layout tests (`ForLayoutTest`) +4. Components for other type of tests that follow conventions above. + +## Anatomy of Test Case + +Each test case should follow the properties defined in the `PropTest` type. +This type includes the properties `name`, `onBeforeTest`, `onBeforeSnapshot`, +and `props`, which define the component setup for the actual test case. + +- `name` - The name of the test case, following the naming conventions +described in the next chapter. +- `onBeforeTest` - A function called before the test and component render. + It should perform any environment tweaks necessary for the defined test. +- `onBeforeSnapshot` - A function called after the component is rendered +and before its comparison against the snapshot. +- `props` - The properties passed to the component in the defined +test scenario. + +## Formatting and Code Style + +### Rules + +- Test for the default component properties should always be placed first. +This test is always represented by `propTests.defaultComponentPropTest`, +defined in the global `propTests`. + +- When possible, try to re-use globally defined `propTests` +for visual tests, located in `tests/playwright/propTests`. + +- It is essential to test all combinations of props that have a significant +visual impact on the appearance of the component. + +- For all possible combinations of multiple `propTests` should be used +function `mixPropTests`. + +### Format + +- Prop test variants should be sorted alphabetically. If there are multiple +prop tests like `feedbackColor`, `neutralColor`, etc., they should still be +ordered alphabetically under the category of color. + + ```jsx + test.describe('blockName', () => { + [ + ...propTests.aPropTest, + ...propTests.bPropTest, + ...propTests.cPropTest, + ].forEach(({ + name, + onBeforeTest, + onBeforeSnapshot, + props, + }) => { + // Rest of test setup. + }); + }); + + ``` + +- Naming convention for propTests `name` property should follow this pattern: + + ```text + someProp:string + someProp:bool=true + someProp:bool=false + someProp:shape[flat] + someProp:shape[nested] + ``` + +## Templates + +### Template for `.story.tsx` + +```tsx +import React from 'react'; +import type { HTMLAttributes } from 'react'; +import { ComponentName } from '..'; + +// Types for story component will be improved when we have full TypeScript support +type ComponentForTestProps = HTMLAttributes; +type ComponentForRefTestProps = ComponentForTestProps & { + testRefAttrName: string; + testRefAttrValue: string; +}; + +export const ComponentForTest = ({ + ...props +} : ComponentForTestProps) => ( + +); + +// Story for `ref` prop, if applicable +export const ComponentForRefTest = ({ + testRefAttrName, + testRefAttrValue, + ...props +} : ComponentForRefTestProps) => { + const ref = useRef(undefined); + + useEffect(() => { + ref.current?.setAttribute(testRefAttrName, testRefAttrValue); + }, [testRefAttrName, testRefAttrValue]); + + return ( + + ); +}; + +``` + +### Template for `.spec.tsx` + +```tsx +import React from 'react'; +import { + expect, + test, +} from '@playwright/experimental-ct-react'; +import { propTests } from '../../../../tests/playwright'; +import { ComponentNameForTest } from './ComponentName.story'; + +test.describe('ComponentName', () => { + test.describe('base', () => { + test.describe('visual', () => { + [ + ...propTests.defaultComponentPropTest, + // ...propTests.propTestA, + // ...mixPropTests([ + // ...propTests.propTestX, + // ...propTests.propTestY, + // ]), + ].forEach(({ + name, + onBeforeTest, + onBeforeSnapshot, + props, + }) => { + test(name, async ({ + mount, + page, + }) => { + if (onBeforeTest) { + await onBeforeTest(page); + } + + const component = await mount( + , + ); + + if (onBeforeSnapshot) { + await onBeforeSnapshot(page, component); + } + + const screenshot = await component.screenshot(); + expect(screenshot).toMatchSnapshot(); + }); + }); + }); + + test.describe('non-visual', () => { + // Test for `id` prop, if applicable + test('id', async ({ mount }) => { + const component = await mount( + , + ); + + await expect(component).toHaveAttribute('id', 'test-id'); + // Test the rest of internal IDs + }); + + // Test for `ref` prop, if applicable + test('ref', async ({ mount }) => { + const component = await mount( + , + ); + + await expect(component).toHaveAttribute('test-ref', 'test-ref-value'); + }); + + // Other non-visual tests + }); + + test.describe('functionality', () => { + // Functional tests + }); + }); + + test.describe('formLayout', () => { + test.describe('visual', () => { + // Visual tests as in `base` block + }); + + test.describe('non-visual', () => { + // Non-visual tests as in `base` block + }); + + test.describe('functionality', () => { + // Functional tests as in `base` block + }); + }); +}); +``` From 292635da43382d287b01361e7556e5fb0d080c0e Mon Sep 17 00:00:00 2001 From: Lukas Briza Date: Tue, 6 May 2025 06:15:31 +0200 Subject: [PATCH 2/2] fixup! Add `writing-tests-guidelines.md` (#612) --- src/docs/contribute/writing-tests-guidelines.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/docs/contribute/writing-tests-guidelines.md b/src/docs/contribute/writing-tests-guidelines.md index d3f9a0c94..7af9944de 100644 --- a/src/docs/contribute/writing-tests-guidelines.md +++ b/src/docs/contribute/writing-tests-guidelines.md @@ -108,6 +108,10 @@ visual impact on the appearance of the component. - For all possible combinations of multiple `propTests` should be used function `mixPropTests`. +- When testing different variants of a property, it's important to include +all possible values, even if some are already used in the +default variant of the component. + ### Format - Prop test variants should be sorted alphabetically. If there are multiple