-
Couldn't load subscription status.
- Fork 7
Add writing-tests-guidelines.md (#612)
#613
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
lukasbriza
wants to merge
2
commits into
master
Choose a base branch
from
documentation/playwright-test-rules-documentation
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,295 @@ | ||
| # 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 | ||
| <ComponentName>/ | ||
| ├── __tests__/ | ||
| │ ├── <ComponentName>.spec.tsx-snapshots/ | ||
| │ ├── _propTests/ | ||
| │ │ ├── <propTestName>.ts | ||
| │ ├── <ComponentName>.spec.tsx | ||
| │ ├── <ComponentName>.story.tsx | ||
| ├── ...rest component files | ||
| ``` | ||
|
|
||
| - `<ComponentName>` - Root folder of the component, named after | ||
| the component itself. | ||
| - `<propTestName>.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`. | ||
| - `<ComponentName>.spec.tsx` - Contains all tests, structured as | ||
| described below. | ||
| - `<ComponentName>.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 `<ComponentName>.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 | ||
| <ComponentName>/ | ||
| ├── base/ | ||
| │ ├── visual/ | ||
| │ │ ├── fullPage/ | ||
| │ ├── non-visual/ | ||
| │ ├── functionality/ | ||
| ├── formLayout/ | ||
| │ ├── visual/ | ||
| │ ├── non-visual/ | ||
| │ ├── functionality/ | ||
| ``` | ||
|
|
||
| - `<ComponentName>` - 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 `<FormLayout/>`. | ||
|
|
||
| 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 `<ComponentName>.story.tsx` | ||
|
|
||
| The `<ComponentName>.story.tsx` file should include all component variants | ||
| tested in `<ComponentName>.spec.tsx`. Components should be organized | ||
| in the following order: | ||
|
|
||
| 1. Component for normal tests (`<ComponentName>ForTest`) | ||
| 2. Component for `ref` attribute tests (`<ComponentName>ForRefTest`) | ||
| 3. Component for layout tests (`<ComponentName>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`. | ||
|
|
||
| - 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 | ||
| 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 `<ComponentName>.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<HTMLDivElement>; | ||
| type ComponentForRefTestProps = ComponentForTestProps & { | ||
| testRefAttrName: string; | ||
| testRefAttrValue: string; | ||
| }; | ||
|
|
||
| export const ComponentForTest = ({ | ||
| ...props | ||
| } : ComponentForTestProps) => ( | ||
| <ComponentName | ||
| requiredPropA="value-a" | ||
| requiredPropB="value-b" | ||
| {...props} | ||
| /> | ||
| ); | ||
|
|
||
| // Story for `ref` prop, if applicable | ||
| export const ComponentForRefTest = ({ | ||
| testRefAttrName, | ||
| testRefAttrValue, | ||
| ...props | ||
| } : ComponentForRefTestProps) => { | ||
| const ref = useRef<HTMLDivElement>(undefined); | ||
|
|
||
| useEffect(() => { | ||
| ref.current?.setAttribute(testRefAttrName, testRefAttrValue); | ||
| }, [testRefAttrName, testRefAttrValue]); | ||
|
|
||
| return ( | ||
| <Component | ||
| {...props} | ||
| ref={ref} | ||
| /> | ||
| ); | ||
| }; | ||
|
|
||
| ``` | ||
|
|
||
| ### Template for `<ComponentName>.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( | ||
| <ComponentNameForTest | ||
| {...props} | ||
| />, | ||
| ); | ||
|
|
||
| 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( | ||
| <ComponentForTest | ||
| id="test-id" | ||
| />, | ||
| ); | ||
|
|
||
| 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( | ||
| <ComponentForRefTest | ||
| testRefAttrName="test-ref" | ||
| testRefAttrValue="test-ref-value" | ||
| />, | ||
| ); | ||
|
|
||
| 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 | ||
| }); | ||
| }); | ||
| }); | ||
| ``` | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.