|
| 1 | +# Playwright Testing Structure |
| 2 | + |
| 3 | +This document outlines the structure and guidelines for writing Playwright |
| 4 | +tests, ensuring consistency and maintainability throughout the codebase. |
| 5 | + |
| 6 | +## Folder Structure |
| 7 | + |
| 8 | +Playwright tests are organized into multiple files and folders, each |
| 9 | +serving a specific purpose. |
| 10 | +The complete component structure should be as follows: |
| 11 | + |
| 12 | +<ComponentName>/ |
| 13 | +├── __tests__/ |
| 14 | +│ ├── <ComponentName>.spec.tsx-snapshots/ |
| 15 | +│ ├── _propTests/ |
| 16 | +│ │ ├── <propTestName>.ts |
| 17 | +│ ├── <ComponentName>.spec.tsx |
| 18 | +│ ├── <ComponentName>.story.tsx |
| 19 | +├── ...rest component files |
| 20 | + |
| 21 | +- `<ComponentName>` - Root folder of the component, named after |
| 22 | +the component itself. |
| 23 | +- `<propTestName>.ts` - Defines local test property combinations used only |
| 24 | +within the context of the tested component. Global tests shared across |
| 25 | +the project are located in `tests/playwright/propTests`. |
| 26 | +- `<ComponentName>.spec.tsx` - Contains all tests, structured as |
| 27 | +described below. |
| 28 | +- `<ComponentName>.story.tsx` - Includes all component definitions used |
| 29 | +in tests. These components should be functional without requiring any |
| 30 | +properties to be passed from the tests. |
| 31 | + |
| 32 | +## <ComponentName>.spec.tsx File Structure |
| 33 | + |
| 34 | +Playwright tests follow a structured format to ensure readability |
| 35 | +and scalability. Each displayed level represents a `test.describe(...)` block. |
| 36 | +The structure consists of: |
| 37 | + |
| 38 | +<ComponentName>/ |
| 39 | +├── base/ |
| 40 | +│ ├── visual/ |
| 41 | +│ │ ├── fullPage/ |
| 42 | +│ ├── non-visual/ |
| 43 | +│ ├── functionality/ |
| 44 | +├── formLayout/ |
| 45 | +│ ├── visual/ |
| 46 | +│ ├── non-visual/ |
| 47 | +│ ├── functionality/ |
| 48 | + |
| 49 | +- `<ComponentName>` - Groups all tests for the tested component. |
| 50 | +- `base` - Contains component tests without any additional layout. |
| 51 | +- `visual` - Tests that compare the component state against snapshots. |
| 52 | +- `fullPage` - Subgroup of visual tests that must be performed |
| 53 | +on a full-scale page. |
| 54 | +- `non-visual` - Validates non-functional properties (e.g., `id` or `ref`). |
| 55 | +- `functionality` - Validates properties that affect the component's behavior |
| 56 | +(e.g., `onChange`). |
| 57 | +- `formLayout` - Contains tests for the component wrapped in `<FormLayout/>`. |
| 58 | + |
| 59 | +Test block categories can be expanded or removed depending on the nature |
| 60 | +of the tested component and whether a predefined test block is applicable |
| 61 | +in a specific case. |
| 62 | + |
| 63 | +## <ComponentName>.story.tsx File Structure |
| 64 | + |
| 65 | +The `<ComponentName>.story.tsx` file should include all component variants |
| 66 | +tested in `<ComponentName>.spec.tsx`. Components should be organized |
| 67 | +in the following order: |
| 68 | + |
| 69 | +1. Component for normal tests (`<ComponentName>ForTest`) |
| 70 | +2. Component for `ref` attribute tests (`<ComponentName>ForRefTest`) |
| 71 | +3. Component for layout tests (`<ComponentName>ForLayoutTest`) |
| 72 | +4. Components for other type of tests that follow conventions above. |
| 73 | + |
| 74 | +## Anatomy of Test Case |
| 75 | + |
| 76 | +Each test case should follow the properties defined in the `PropTest` type. |
| 77 | +This type includes the properties `name`, `onBeforeTest`, `onBeforeSnapshot`, |
| 78 | +and `props`, which define the component setup for the actual test case. |
| 79 | + |
| 80 | +- `name` - The name of the test case, following the naming conventions |
| 81 | +described in the next chapter. |
| 82 | +- `onBeforeTest` - A function called before the test and component render. |
| 83 | + It should perform any environment tweaks necessary for the defined test. |
| 84 | +- `onBeforeSnapshot` - A function called after the component is rendered |
| 85 | +and before its comparison against the snapshot. |
| 86 | +- `props` - The properties passed to the component in the defined |
| 87 | +test scenario. |
| 88 | + |
| 89 | +## Formatting and code style |
| 90 | + |
| 91 | +- When writing visual tests, the test for the default component properties |
| 92 | +should always be placed first. This test is always represented by |
| 93 | +`propTests.defaultComponentPropTest`, defined in the global `propTests`. |
| 94 | + |
| 95 | +- Prop test variants should be sorted alphabetically. If there are multiple |
| 96 | +prop tests like `feedbackColor`, `neutralColor`, etc., they should still be |
| 97 | +ordered alphabetically under the category of color. |
| 98 | + |
| 99 | + ```jsx |
| 100 | + test.describe('blockName', () => { |
| 101 | + [ |
| 102 | + ...propTests.aPropTest, |
| 103 | + ...propTests.bPropTest, |
| 104 | + ...propTests.cPropTest, |
| 105 | + ].forEach(({ |
| 106 | + name, |
| 107 | + onBeforeTest, |
| 108 | + onBeforeSnapshot, |
| 109 | + props, |
| 110 | + }) => { |
| 111 | + // Rest of test setup. |
| 112 | + }); |
| 113 | + }); |
| 114 | + |
| 115 | + ``` |
| 116 | + |
| 117 | +- When possible, try to re-use globally predefined `propTests` |
| 118 | +for visual tests,located in `tests/playwright/propTests`. |
| 119 | + |
| 120 | +- Naming convention for propTests `name` property should follow this pattern: |
| 121 | + |
| 122 | + ```text |
| 123 | + someProp:string |
| 124 | + someProp:bool=true |
| 125 | + someProp:bool=false |
| 126 | + someProp:shape[flat] |
| 127 | + someProp:shape[nested] |
| 128 | + ``` |
| 129 | + |
| 130 | +- For all possible combinations of multiple `propTests` should be used |
| 131 | +function `mixPropTests`. |
| 132 | + |
| 133 | +## Test Structure Example |
| 134 | + |
| 135 | +```jsx |
| 136 | +test.describe('ComponentName', () => { |
| 137 | + test.describe('base', () => { |
| 138 | + test.describe('visual', () => { |
| 139 | + [ |
| 140 | + // propTests and mix of propTests |
| 141 | + ].forEach(({ |
| 142 | + name, |
| 143 | + onBeforeTest, |
| 144 | + onBeforeSnapshot, |
| 145 | + props, |
| 146 | + }) => { |
| 147 | + test(name, async ({ |
| 148 | + mount, |
| 149 | + page, |
| 150 | + }) => { |
| 151 | + if (onBeforeTest) { |
| 152 | + await onBeforeTest(page); |
| 153 | + } |
| 154 | + |
| 155 | + const component = await mount( |
| 156 | + <ComponentName |
| 157 | + {...props} |
| 158 | + />, |
| 159 | + ); |
| 160 | + |
| 161 | + if (onBeforeSnapshot) { |
| 162 | + await onBeforeSnapshot(page, component); |
| 163 | + } |
| 164 | + |
| 165 | + const screenshot = await component.screenshot(); |
| 166 | + expect(screenshot).toMatchSnapshot(); |
| 167 | + }); |
| 168 | + }); |
| 169 | + }); |
| 170 | + |
| 171 | + test.describe('non-visual', () => { |
| 172 | + /* Non-visual tests. */ |
| 173 | + }); |
| 174 | + |
| 175 | + test.describe('functionality', () => { |
| 176 | + /* Functional tests. */ |
| 177 | + }); |
| 178 | + }); |
| 179 | + |
| 180 | + test.describe('formLayout', () => { |
| 181 | + test.describe('visual', () => { |
| 182 | + /* Visual tests as in `base` block. */ |
| 183 | + }); |
| 184 | + |
| 185 | + test.describe('non-visual', () => { |
| 186 | + /* Non-visual tests as in `base` block. */ |
| 187 | + }); |
| 188 | + |
| 189 | + test.describe('functionality', () => { |
| 190 | + /* Functional tests as in `base` block. */ |
| 191 | + }); |
| 192 | + }); |
| 193 | +}); |
| 194 | +``` |
0 commit comments