mirror of
https://github.com/storybookjs/storybook.git
synced 2025-03-21 05:02:39 +08:00
Storybook 6.5 testing updates
This commit is contained in:
parent
22336259d4
commit
c5c64b4984
@ -5,6 +5,8 @@ import { Canvas, Meta, Story } from '@storybook/addon-docs';
|
||||
|
||||
import { within, userEvent } from '@storybook/testing-library';
|
||||
|
||||
import { expect } from '@storybook/jest';
|
||||
|
||||
import { LoginForm } from './LoginForm.component';
|
||||
|
||||
<Meta title="Form" component={LoginForm} />
|
||||
@ -22,17 +24,26 @@ export const Template = (args) => ({ props: args });
|
||||
// Starts querying the component from its root element
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// 👇 Simulate interactions with the component
|
||||
await userEvent.type(canvas.getByTestId('email'), 'email@provider.com', {
|
||||
delay: 100,
|
||||
});
|
||||
|
||||
await userEvent.type(canvas.getByTestId('password'), 'a-random-password', {
|
||||
delay: 100,
|
||||
});
|
||||
|
||||
// See https://storybook.js.org/docs/angular/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
|
||||
// 👇 Assert DOM structure
|
||||
await expect(
|
||||
canvas.getByText(
|
||||
'Everything is perfect. Your account is ready and we should probably get you started!'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
}}>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
|
||||
</Canvas>
|
||||
```
|
@ -5,6 +5,8 @@ import { Meta, Story } from '@storybook/angular';
|
||||
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { expect } from '@storybook/jest';
|
||||
|
||||
import { LoginForm } from './LoginForm.component';
|
||||
|
||||
export default {
|
||||
@ -27,14 +29,23 @@ FilledForm.play = async ({ canvasElement }) => {
|
||||
// Starts querying the component from its root element
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// 👇 Simulate interactions with the component
|
||||
await userEvent.type(canvas.getByTestId('email'), 'email@provider.com', {
|
||||
delay: 100,
|
||||
});
|
||||
|
||||
await userEvent.type(canvas.getByTestId('password'), 'a-random-password', {
|
||||
delay: 100,
|
||||
});
|
||||
|
||||
// See https://storybook.js.org/docs/angular/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
|
||||
// 👇 Assert DOM structure
|
||||
await expect(
|
||||
canvas.getByText(
|
||||
'Everything is perfect. Your account is ready and we should probably get you started!'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
};
|
||||
```
|
@ -8,7 +8,7 @@ describe('Login Form', () => {
|
||||
cy.visit('/iframe.html?id=components-login-form--example');
|
||||
cy.get('#login-form').within(() => {
|
||||
cy.log('**enter the email**');
|
||||
cy.get('#email').should('have.value', 'your-own-emailaddress@provider.com');
|
||||
cy.get('#email').should('have.value', 'email@provider.com');
|
||||
cy.log('**enter password**');
|
||||
cy.get('#password').should('have.value', 'a-random-password');
|
||||
});
|
||||
|
@ -7,7 +7,7 @@ test('Login Form inputs', async ({ page }) => {
|
||||
await page.goto('http://localhost:6006/iframe.html?id=components-login-form--example');
|
||||
const email = await page.inputValue('#email');
|
||||
const password = await page.inputValue('#password');
|
||||
await expect(email).toBe('your-own-emailaddress@provider.com');
|
||||
await expect(email).toBe('email@provider.com');
|
||||
await expect(password).toBe('a-random-password');
|
||||
});
|
||||
```
|
3
docs/snippets/common/storybook-a11y-install.npm.js.mdx
Normal file
3
docs/snippets/common/storybook-a11y-install.npm.js.mdx
Normal file
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
npm install @storybook/addon-a11y --save-dev
|
||||
```
|
3
docs/snippets/common/storybook-a11y-install.yarn.js.mdx
Normal file
3
docs/snippets/common/storybook-a11y-install.yarn.js.mdx
Normal file
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
yarn add --dev @storybook/addon-a11y
|
||||
```
|
@ -2,11 +2,11 @@
|
||||
// .storybook/main.js
|
||||
|
||||
module.exports = {
|
||||
stories:[],
|
||||
addons:[
|
||||
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
||||
addons: [
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-essentials',
|
||||
'@storybook/preset-create-react-app',
|
||||
'@storybook/addon-a11y', //👈 The a11y addon goes here
|
||||
],
|
||||
};
|
||||
```
|
||||
```
|
17
docs/snippets/common/storybook-a11y-register.ts.mdx
Normal file
17
docs/snippets/common/storybook-a11y-register.ts.mdx
Normal file
@ -0,0 +1,17 @@
|
||||
```ts
|
||||
// .storybook/main.ts
|
||||
|
||||
// Imports Storybook's configuration API
|
||||
import type { StorybookConfig } from '@storybook/core-common';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
||||
addons: [
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-essentials',
|
||||
'@storybook/addon-a11y', //👈 The a11y addon goes here
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
npm install @storybook/testing-library @storybook/jest @storybook/addon-interactions --save-dev
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
yarn add --dev @storybook/testing-library @storybook/jest @storybook/addon-interactions
|
||||
```
|
@ -0,0 +1,14 @@
|
||||
```js
|
||||
// .storybook/main.js
|
||||
|
||||
module.exports = {
|
||||
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
||||
addons: [
|
||||
// Other Storybook addons
|
||||
'@storybook/addon-interactions', // 👈 Addon is registered here
|
||||
],
|
||||
features: {
|
||||
interactionsDebugger: true, // 👈 Enable playback controls
|
||||
},
|
||||
};
|
||||
```
|
@ -0,0 +1,19 @@
|
||||
```ts
|
||||
// .storybook/main.ts
|
||||
|
||||
// Imports Storybook's configuration API
|
||||
import type { StorybookConfig } from '@storybook/core-common';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
||||
addons: [
|
||||
// Other Storybook addons
|
||||
'@storybook/addon-interactions', // 👈 Addon is registered here
|
||||
],
|
||||
features: {
|
||||
interactionsDebugger: true, // 👈 Enable playback controls
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
```
|
@ -1,4 +1,3 @@
|
||||
```shell
|
||||
# Starts Storybook in development mode
|
||||
npm run storybook
|
||||
```
|
@ -1,4 +1,3 @@
|
||||
```shell
|
||||
# Starts Storybook in development mode
|
||||
yarn storybook
|
||||
```
|
@ -0,0 +1,22 @@
|
||||
```js
|
||||
// .storybook/test-runner.js
|
||||
|
||||
const { injectAxe, checkA11y } = require('axe-playwright');
|
||||
|
||||
module.exports = {
|
||||
/* Hook to execute before a story is rendered.
|
||||
* The page argument is the Playwright's page object for the story.
|
||||
* The context argument is a Storybook object containing the story's id, title, and name.
|
||||
*/
|
||||
async preRender(page, context) {
|
||||
await injectAxe(page);
|
||||
},
|
||||
/* Hook to execute after a story is rendered.
|
||||
* The page argument is the Playwright's page object for the story
|
||||
* The context argument is a Storybook object containing the story's id, title, and name.
|
||||
*/
|
||||
async postRender(page, context) {
|
||||
await checkA11y(page, context.title.includes('Page') ? undefined : '#root');
|
||||
},
|
||||
};
|
||||
```
|
@ -0,0 +1,26 @@
|
||||
```ts
|
||||
// .storybook/test-runner.ts
|
||||
|
||||
import { injectAxe, checkA11y } from 'axe-playwright';
|
||||
|
||||
import type { TestRunnerConfig } from '@storybook/test-runner';
|
||||
|
||||
const a11yConfig: TestRunnerConfig = {
|
||||
/* Hook to execute before a story is rendered.
|
||||
* The page argument is the Playwright's page object for the story.
|
||||
* The context argument is a Storybook object containing the story's id, title, and name.
|
||||
*/
|
||||
async preRender(page, context) {
|
||||
await injectAxe(page);
|
||||
},
|
||||
/* Hook to execute after a story is rendered.
|
||||
* The page argument is the Playwright's page object for the story
|
||||
* The context argument is a Storybook object containing the story's id, title, and name.
|
||||
*/
|
||||
async postRender(page, context) {
|
||||
await checkA11y(page, context.title.includes('Page') ? undefined : '#root');
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = a11yConfig;
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
npm install axe-playwright --save-dev
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
yarn add --dev axe-playwright
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
npm run test-storybook -- --no-stories-json
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
yarn test-storybook --no-stories-json
|
||||
```
|
3
docs/snippets/common/storybook-test-runner-error.js.mdx
Normal file
3
docs/snippets/common/storybook-test-runner-error.js.mdx
Normal file
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
Timeout - Async callback was not invoked within the 15000 ms timeout specified by jest.setTimeout
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
npm run test-storybook -- --watch
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
yarn test-storybook --watch
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
TARGET_URL=https://the-storybook-url-here.com yarn test-storybook
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
npm run test-storybook -- --url https://the-storybook-url-here.com
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
yarn test-storybook --url https://the-storybook-url-here.com
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
npm run test-storybook
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
yarn test-storybook
|
||||
```
|
@ -0,0 +1,24 @@
|
||||
```js
|
||||
// .storybook/test-runner.js
|
||||
|
||||
module.exports = {
|
||||
// Hook to execute once the test runner is ready
|
||||
setup() {
|
||||
// Add your configuration here.
|
||||
},
|
||||
/* Hook to execute before a story is rendered.
|
||||
* The page argument is the Playwright's page object for the story.
|
||||
* The context argument is a Storybook object containing the story's id, title, and name.
|
||||
*/
|
||||
async preRender(page, context) {
|
||||
// Add your configuration here.
|
||||
},
|
||||
/* Hook to execute after a story is rendered.
|
||||
* The page argument is the Playwright's page object for the story
|
||||
* The context argument is a Storybook object containing the story's id, title, and name.
|
||||
*/
|
||||
async postRender(page, context) {
|
||||
// Add your configuration here.
|
||||
},
|
||||
};
|
||||
```
|
@ -0,0 +1,28 @@
|
||||
```ts
|
||||
// .storybook/test-runner.ts
|
||||
|
||||
import type { TestRunnerConfig } from '@storybook/test-runner';
|
||||
|
||||
const config: TestRunnerConfig = {
|
||||
// Hook to execute once the test runner is ready
|
||||
setup() {
|
||||
// Add your configuration here.
|
||||
},
|
||||
/* Hook to execute before a story is rendered.
|
||||
* The page argument is the Playwright's page object for the story.
|
||||
* The context argument is a Storybook object containing the story's id, title, and name.
|
||||
*/
|
||||
async preRender(page, context) {
|
||||
// Add your configuration here.
|
||||
},
|
||||
/* Hook to execute after a story is rendered.
|
||||
* The page argument is the Playwright's page object for the story
|
||||
* The context argument is a Storybook object containing the story's id, title, and name.
|
||||
*/
|
||||
async postRender(page, context) {
|
||||
// Add your configuration here.
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
npm install @storybook/test-runner jest --save-dev
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
yarn add --dev @storybook/test-runner jest
|
||||
```
|
@ -0,0 +1,26 @@
|
||||
```yml
|
||||
# .github/workflows/storybook-tests.yml
|
||||
|
||||
name: 'Storybook Tests'
|
||||
on: push
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14.x'
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
- name: Install Playwright
|
||||
run: npx playwright install --with-deps
|
||||
- name: Build Storybook
|
||||
run: yarn build-storybook --quiet
|
||||
- name: Serve Storybook and run tests
|
||||
run: |
|
||||
npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
|
||||
"npx http-server storybook-static --port 6006 --silent" \
|
||||
"npx wait-on tcp:6006 && yarn test-storybook"
|
||||
```
|
@ -0,0 +1,24 @@
|
||||
```yml
|
||||
# .github/workflows/storybook-tests.yml
|
||||
|
||||
name: Storybook Tests
|
||||
on: deployment_status
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.deployment_status.state == 'success'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14.x'
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
- name: Install Playwright
|
||||
run: npx playwright install --with-deps
|
||||
- name: Run Storybook tests
|
||||
run: yarn test-storybook
|
||||
env:
|
||||
TARGET_URL: '${{ github.event.deployment_status.target_url }}'
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
npm run test-storybook -- --stories-json
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
yarn test-storybook --stories-json
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
npm install --save-dev @storybook/testing-( react | vue | vue3 | angular)
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
yarn add --dev @storybook/testing-( react | vue | vue3 | angular )
|
||||
```
|
@ -1,23 +0,0 @@
|
||||
```js
|
||||
// MyComponent.test.js
|
||||
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { composeStories } from '@storybook/testing-react';
|
||||
|
||||
import { axe, toHaveNoViolations } from 'jest-axe';
|
||||
|
||||
import * as MyComponentStories from './MyComponent.stories';
|
||||
|
||||
const { Accessible } = composeStories(MyComponentStories);
|
||||
|
||||
expect.extend(toHaveNoViolations);
|
||||
|
||||
test('Example accessibility test', async () => {
|
||||
const { container } = render(<Accessible />);
|
||||
|
||||
const AxeResults = await axe(container);
|
||||
|
||||
expect(AxeResults).toHaveNoViolations();
|
||||
});
|
||||
```
|
@ -5,6 +5,8 @@ import React from 'react';
|
||||
|
||||
import { within, userEvent } from '@storybook/testing-library';
|
||||
|
||||
import { expect } from '@storybook/jest';
|
||||
|
||||
import { LoginForm } from './LoginForm';
|
||||
|
||||
export default {
|
||||
@ -25,6 +27,7 @@ FilledForm.play = async ({ canvasElement }) => {
|
||||
// Starts querying the component from its root element
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// 👇 Simulate interactions with the component
|
||||
await userEvent.type(canvas.getByTestId('email'), 'email@provider.com', {
|
||||
delay: 100,
|
||||
});
|
||||
@ -34,5 +37,12 @@ FilledForm.play = async ({ canvasElement }) => {
|
||||
|
||||
// See https://storybook.js.org/docs/react/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
|
||||
// 👇 Assert DOM structure
|
||||
await expect(
|
||||
canvas.getByText(
|
||||
'Everything is perfect. Your account is ready and we should probably get you started!'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
};
|
||||
```
|
||||
```
|
@ -5,6 +5,8 @@ import { Canvas, Meta, Story } from '@storybook/addon-docs';
|
||||
|
||||
import { within, userEvent } from '@storybook/testing-library';
|
||||
|
||||
import { expect } from '@storybook/jest';
|
||||
|
||||
import { LoginForm } from './LoginForm';
|
||||
|
||||
<Meta title="Form" component={LoginForm} />
|
||||
@ -22,16 +24,26 @@ export const Template = (args) => <LoginForm {...args} />;
|
||||
// Starts querying the component from its root element
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await userEvent.type(canvas.getByTestId('email'), 'email@provider.com', {
|
||||
delay: 100,
|
||||
});
|
||||
await userEvent.type(canvas.getByTestId('password'), 'a-random-password', {
|
||||
delay: 100,
|
||||
});
|
||||
// See https://storybook.js.org/docs/react/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
}}>
|
||||
{Template.bind({})}
|
||||
// 👇 Simulate interactions with the component
|
||||
await userEvent.type(canvas.getByTestId('email'), 'email@provider.com', {
|
||||
delay: 100,
|
||||
});
|
||||
|
||||
await userEvent.type(canvas.getByTestId('password'), 'a-random-password', {
|
||||
delay: 100,
|
||||
});
|
||||
|
||||
// See https://storybook.js.org/docs/react/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
|
||||
// 👇 Assert DOM structure
|
||||
await expect(
|
||||
canvas.getByText(
|
||||
'Everything is perfect. Your account is ready and we should probably get you started!'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
}}>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
```
|
||||
|
@ -7,6 +7,8 @@ import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
|
||||
import { within, userEvent } from '@storybook/testing-library';
|
||||
|
||||
import { expect } from '@storybook/jest';
|
||||
|
||||
import { LoginForm } from './LoginForm';
|
||||
|
||||
export default {
|
||||
@ -27,6 +29,7 @@ FilledForm.play = async ({ canvasElement }) => {
|
||||
// Starts querying the component from its root element
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// 👇 Simulate interactions with the component
|
||||
await userEvent.type(canvas.getByTestId('email'), 'email@provider.com', {
|
||||
delay: 100,
|
||||
});
|
||||
@ -36,5 +39,12 @@ FilledForm.play = async ({ canvasElement }) => {
|
||||
|
||||
// See https://storybook.js.org/docs/react/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
|
||||
// 👇 Assert DOM structure
|
||||
await expect(
|
||||
canvas.getByText(
|
||||
'Everything is perfect. Your account is ready and we should probably get you started!'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
};
|
||||
```
|
@ -3,6 +3,8 @@
|
||||
|
||||
import { within, userEvent } from '@storybook/testing-library';
|
||||
|
||||
import { expect } from '@storybook/jest';
|
||||
|
||||
import LoginForm from './LoginForm.svelte';
|
||||
|
||||
export default {
|
||||
@ -26,14 +28,23 @@ FilledForm.play = async ({ canvasElement }) => {
|
||||
// Starts querying the component from its root element
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// 👇 Simulate interactions with the component
|
||||
await userEvent.type(canvas.getByTestId('email'), 'email@provider.com', {
|
||||
delay: 100,
|
||||
});
|
||||
|
||||
await userEvent.type(canvas.getByTestId('password'), 'a-random-password', {
|
||||
delay: 100,
|
||||
});
|
||||
|
||||
// See https://storybook.js.org/docs/svelte/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
|
||||
// 👇 Assert DOM structure
|
||||
await expect(
|
||||
canvas.getByText(
|
||||
'Everything is perfect. Your account is ready and we should probably get you started!'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
};
|
||||
```
|
@ -5,6 +5,8 @@ import { Canvas, Meta, Story } from '@storybook/addon-docs';
|
||||
|
||||
import { within, userEvent } from '@storybook/testing-library';
|
||||
|
||||
import { expect } from '@storybook/jest';
|
||||
|
||||
import LoginForm from './LoginForm.svelte';
|
||||
|
||||
<Meta title="Form" component={LoginForm} />
|
||||
@ -22,19 +24,27 @@ export const Template = (args) => ({
|
||||
<Story
|
||||
name="Filled Form"
|
||||
play={async ({ canvasElement }) => {
|
||||
|
||||
// Starts querying the component from its root element
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// 👇 Simulate interactions
|
||||
await userEvent.type(canvas.getByTestId('email'), 'email@provider.com', {
|
||||
delay: 100,
|
||||
});
|
||||
|
||||
await userEvent.type(canvas.getByTestId('password'), 'a-random-password', {
|
||||
delay: 100,
|
||||
});
|
||||
|
||||
// See https://storybook.js.org/docs/svelte/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
|
||||
// 👇 Assert DOM structure
|
||||
await expect(
|
||||
canvas.getByText(
|
||||
'Everything is perfect. Your account is ready and we should probably get you started!'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
}}>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
|
@ -1,23 +0,0 @@
|
||||
```js
|
||||
// MyComponent.test.js
|
||||
|
||||
import { render } from '@testing-library/vue';
|
||||
|
||||
import { composeStories } from '@storybook/testing-vue';
|
||||
|
||||
import { axe, toHaveNoViolations } from 'jest-axe';
|
||||
|
||||
import * as MyComponentStories from './MyComponent.stories';
|
||||
|
||||
const { Accessible } = composeStories(MyComponentStories);
|
||||
|
||||
expect.extend(toHaveNoViolations);
|
||||
|
||||
test('Example accessibility test', async () => {
|
||||
const { container } = render(Accessible());
|
||||
|
||||
const AxeResults = await axe(container);
|
||||
|
||||
expect(AxeResults).toHaveNoViolations();
|
||||
});
|
||||
```
|
@ -3,6 +3,8 @@
|
||||
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { expect } from '@storybook/jest';
|
||||
|
||||
import LoginForm from './LoginForm.vue';
|
||||
|
||||
export default {
|
||||
@ -27,14 +29,23 @@ FilledForm.play = async ({ canvasElement }) => {
|
||||
// Starts querying the component from its root element
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// 👇 Simulate interactions with the component
|
||||
await userEvent.type(canvas.getByTestId('email'), 'email@provider.com', {
|
||||
delay: 100,
|
||||
});
|
||||
|
||||
await userEvent.type(canvas.getByTestId('password'), 'a-random-password', {
|
||||
delay: 100,
|
||||
});
|
||||
|
||||
// See https://storybook.js.org/docs/vue/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
|
||||
// 👇 Assert DOM structure
|
||||
await expect(
|
||||
canvas.getByText(
|
||||
'Everything is perfect. Your account is ready and we should probably get you started!'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
};
|
||||
```
|
@ -3,6 +3,8 @@
|
||||
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { expect } from '@storybook/jest';
|
||||
|
||||
import LoginForm from './LoginForm.vue';
|
||||
|
||||
export default {
|
||||
@ -29,14 +31,23 @@ FilledForm.play = async ({ canvasElement }) => {
|
||||
// Starts querying the component from its root element
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// 👇 Simulate interactions with the component
|
||||
await userEvent.type(canvas.getByTestId('email'), 'email@provider.com', {
|
||||
delay: 100,
|
||||
});
|
||||
|
||||
await userEvent.type(canvas.getByTestId('password'), 'a-random-password', {
|
||||
delay: 100,
|
||||
});
|
||||
|
||||
// See https://storybook.js.org/docs/vue/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
|
||||
// 👇 Assert DOM structure
|
||||
await expect(
|
||||
canvas.getByText(
|
||||
'Everything is perfect. Your account is ready and we should probably get you started!'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
};
|
||||
```
|
@ -5,6 +5,8 @@ import { Canvas, Meta, Story } from '@storybook/addon-docs';
|
||||
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { expect } from '@storybook/jest';
|
||||
|
||||
import LoginForm from './LoginForm.vue';
|
||||
|
||||
<Meta title="Form" component={LoginForm} />
|
||||
@ -17,7 +19,7 @@ export const Template = (args, { argTypes }) => ({
|
||||
|
||||
<Canvas>
|
||||
<Story name="Empty Form">
|
||||
{Template.bind({})}
|
||||
{Template.bind(())}
|
||||
</Story>
|
||||
|
||||
<Story
|
||||
@ -26,15 +28,24 @@ export const Template = (args, { argTypes }) => ({
|
||||
// Starts querying the component from its root element
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// 👇 Simulate interactions with the component
|
||||
await userEvent.type(canvas.getByTestId('email'), 'email@provider.com', {
|
||||
delay: 100,
|
||||
});
|
||||
|
||||
await userEvent.type(canvas.getByTestId('password'), 'a-random-password', {
|
||||
delay: 100,
|
||||
});
|
||||
|
||||
// See https://storybook.js.org/docs/vue/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
|
||||
// See https://storybook.js.org/docs/angular/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
|
||||
// 👇 Assert DOM structure
|
||||
await expect(
|
||||
canvas.getByText(
|
||||
'Everything is perfect. Your account is ready and we should probably get you started!'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
}}>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
|
@ -5,6 +5,8 @@ import { Canvas, Meta, Story } from '@storybook/addon-docs';
|
||||
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { expect } from '@storybook/jest';
|
||||
|
||||
import LoginForm from './LoginForm.vue';
|
||||
|
||||
<Meta title="Form" component={LoginForm} />
|
||||
@ -22,21 +24,35 @@ export const Template = (args) => ({
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
|
||||
<Canvas>
|
||||
<Story name="Empty Form">
|
||||
{Template.bind(())}
|
||||
</Story>
|
||||
|
||||
<Story
|
||||
name="Filled Form"
|
||||
play={async ({ canvasElement }) => {
|
||||
// Starts querying the component from its root element
|
||||
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// 👇 Simulate interactions with the component
|
||||
await userEvent.type(canvas.getByTestId('email'), 'email@provider.com', {
|
||||
delay: 100,
|
||||
});
|
||||
|
||||
await userEvent.type(canvas.getByTestId('password'), 'a-random-password', {
|
||||
delay: 100,
|
||||
});
|
||||
// See https://storybook.js.org/docs/vue/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
|
||||
|
||||
// See https://storybook.js.org/docs/angular/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
|
||||
// 👇 Assert DOM structure
|
||||
await expect(
|
||||
canvas.getByText(
|
||||
'Everything is perfect. Your account is ready and we should probably get you started!'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
}}>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
|
@ -179,6 +179,11 @@ module.exports = {
|
||||
title: 'Introduction',
|
||||
type: 'link',
|
||||
},
|
||||
{
|
||||
pathSegment: 'test-runner',
|
||||
title: 'Test runner',
|
||||
type: 'link',
|
||||
},
|
||||
{
|
||||
pathSegment: 'visual-testing',
|
||||
title: 'Visual tests',
|
||||
|
@ -6,29 +6,37 @@ Accessibility is the practice of making websites inclusive to all. That means su
|
||||
|
||||
Accessibility tests audit the rendered DOM against a set of heuristics based on [WCAG](https://www.w3.org/WAI/standards-guidelines/wcag/) rules and other industry-accepted best practices. They act as the first line of QA to catch blatant accessibility violations.
|
||||
|
||||
Storybook’s official [a11y addon](https://storybook.js.org/addons/@storybook/addon-a11y) runs accessibility audits while you’re developing components to give you a fast feedback loop. It's powered by Deque's [axe-core](https://github.com/dequelabs/axe-core), which automatically catches up to [57% of WCAG issues](https://www.deque.com/blog/automated-testing-study-identifies-57-percent-of-digital-accessibility-issues/).
|
||||
|
||||

|
||||
|
||||
## Setup a11y addon
|
||||
## Accessibility testing with a11y addon
|
||||
|
||||
To enable accessibility testing with Storybook, you'll need to install the [`@storybook/addon-a11y`](https://storybook.js.org/addons/@storybook/addon-a11y/) addon. Run the following command:
|
||||
Storybook provides an official [a11y addon](https://storybook.js.org/addons/@storybook/addon-a11y). Powered by Deque's [axe-core](https://github.com/dequelabs/axe-core), which automatically catches up to [57% of WCAG issues](https://www.deque.com/blog/automated-testing-study-identifies-57-percent-of-digital-accessibility-issues/).
|
||||
|
||||
```shell
|
||||
# With npm
|
||||
npm install @storybook/addon-a11y --save-dev
|
||||
### Set up the a11y addon
|
||||
|
||||
# With yarn
|
||||
yarn add --dev @storybook/addon-a11y
|
||||
```
|
||||
If you want to run accessibility tests using the [addon](https://storybook.js.org/addons/@storybook/addon-a11y/), you'll need additional steps to set it up. Documented below is our recommendation.
|
||||
|
||||
Update your Storybook configuration (in `.storybook/main.js`) to include the accessibility addon:
|
||||
Run the following command to install the addon.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/storybook-a11y-install.yarn.js.mdx',
|
||||
'common/storybook-a11y-install.npm.js.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
Update your Storybook configuration (in `.storybook/main.js|ts`) to include the accessibility addon.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/storybook-a11y-register.js.mdx',
|
||||
'common/storybook-a11y-register.ts.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
@ -73,7 +81,7 @@ Cycling through both stories, you will see that the `Inaccessible` story contain
|
||||
|
||||
### Configure
|
||||
|
||||
Out of the box, Storybook's accessibility addon includes a set of accessibility rules that cover most issues. You can also fine tune the [addon configuration](https://github.com/storybookjs/storybook/tree/next/addons/a11y#parameters) or override [Axe's ruleset](https://github.com/storybookjs/storybook/tree/next/addons/a11y#handling-failing-rules) to best suit your needs.
|
||||
Out of the box, Storybook's accessibility addon includes a set of accessibility rules that cover most issues. You can also fine-tune the [addon configuration](https://github.com/storybookjs/storybook/tree/next/addons/a11y#parameters) or override [Axe's ruleset](https://github.com/storybookjs/storybook/tree/next/addons/a11y#handling-failing-rules) to best suit your needs.
|
||||
|
||||
#### Global a11y configuration
|
||||
|
||||
@ -148,35 +156,61 @@ Disable accessibility testing for stories or components by adding the following
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## Automate accessibility tests with Jest
|
||||
## Automate accessibility tests with test runner
|
||||
|
||||
Accessibility testing with Storybook shortens the feedback loop which means you fix issues faster. Reuse stories in a Jest test and run an accessibility audit on them using the [jest-axe integration](https://github.com/nickcolley/jest-axe). That also unlocks the ability to integrate accessibility tests into your functional testing pipeline.
|
||||
Accessibility testing with Storybook shortens the feedback loop, which means you fix issues faster. You can then integrate these accessibility tests into your test automation pipeline using the Storybook [test runner](./test-runner.md#test-hook-api-experimental) and [axe-playwright](https://github.com/abhinaba-ghosh/axe-playwright).
|
||||
|
||||
For example, include the following test file to run an accessibility test on a story:
|
||||
### Setup
|
||||
|
||||
To enable accessibility testing with the test runner, you will need to take additional steps to set it up properly. Detailed below is our recommendation to configure and execute them.
|
||||
|
||||
Run the following command to install the required dependencies.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'react/accessibility-testing-with-jest-axe.js.mdx',
|
||||
'vue/accessibility-testing-with-jest-axe.js.mdx',
|
||||
'common/storybook-test-runner-axe-playwright.yarn.js.mdx',
|
||||
'common/storybook-test-runner-axe-playwright.npm.js.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
When you execute your test script, it will run the accessibility audit along with any interaction tests you might have.
|
||||
Add a new [configuration file](./test-runner.md#test-hook-api-experimental) inside your Storybook directory with the following inside:
|
||||
|
||||

|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/storybook-test-runner-a11y-config.js.mdx',
|
||||
'common/storybook-test-runner-a11y-config.ts.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<div class="aside">
|
||||
|
||||
💡 `preRender` and `postRender` are convenient hooks that allow you to extend the test runner's default configuration. They are **experimental** and subject to changes. Read more about them [here](./test-runner.md#test-hook-api-experimental).
|
||||
|
||||
</div>
|
||||
|
||||
By default, Axe assumes that you're testing a page and checks whether you've specified an `<h1>` and `<main>`. However, most of your stories are for components and not pages. That's why we use the `context.title.includes('Page')` check to [enable/disable](https://github.com/abhinaba-ghosh/axe-playwright#context-optional) Axe's page-level rules.
|
||||
|
||||
When you execute the test runner (i.e., `yarn test-storybook`), it will run the accessibility audit and any [interaction tests](./interaction-testing.md) you might have configured for each component story.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
#### What’s the difference between browser-based and linter-based accessibility tests?
|
||||
|
||||
Browser-based accessibility tests, like found in Storybook, evaluates the rendered DOM because that gives you the highest accuracy. Auditing code that hasn't been compiled yet is one step removed from the real thing so you won't catch everything the user might experience.
|
||||
Browser-based accessibility tests, like found in Storybook, evaluate the rendered DOM because that gives you the highest accuracy. Auditing code that hasn't been compiled yet is one step removed from the real thing, so you won't catch everything the user might experience.
|
||||
|
||||
#### Learn about other UI tests
|
||||
|
||||
- [Test runner](./test-runner.md) to automate test execution
|
||||
- [Visual tests](./visual-testing.md) for appearance
|
||||
- Accessibility tests for accessibility
|
||||
- [Interaction tests](./interaction-testing.md) for user behavior simulation
|
||||
|
Binary file not shown.
Binary file not shown.
@ -21,13 +21,22 @@ Storybook has test addons for core frameworks React, Vue (2,3), and Angular. Thi
|
||||
|
||||
Run the following command to add Storybook's testing addon into your environment:
|
||||
|
||||
```shell
|
||||
# With npm, don't forget to select only your framework
|
||||
npm install --save-dev @storybook/testing-( react | vue | vue3 | angular)
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
# With yarn, don't forget to select only your framework
|
||||
yarn add --dev @storybook/testing-( react | vue | vue3 | angular )
|
||||
```
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/storybook-testing-addon-install.yarn.js.mdx',
|
||||
'common/storybook-testing-addon-install.npm.js.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<div class="aside">
|
||||
|
||||
💡 When running the command to install the addon, don't forget to select **only** your framework.
|
||||
|
||||
</div>
|
||||
|
||||
### Optional configuration
|
||||
|
||||
@ -171,6 +180,7 @@ Once you execute Playwright, it opens a new browser window, loads Storybook's is
|
||||
|
||||
#### Learn about other UI tests
|
||||
|
||||
- [Test runner](./test-runner.md) to automate test execution
|
||||
- [Visual tests](./visual-testing.md) for appearance
|
||||
- [Accessibility tests](./accessibility-testing.md) for accessibility
|
||||
- [Interaction tests](./interaction-testing.md) for user behavior simulation
|
||||
|
@ -6,17 +6,52 @@ As you build more complex UIs like pages, components become responsible for more
|
||||
|
||||
In a nutshell, you start by supplying the appropriate props for the initial state of a component. Then simulate user behavior such as clicks and form entries. Finally, check whether the UI and component state update correctly.
|
||||
|
||||
In Storybook, this familiar workflow happens in your browser. That makes it easier to debug failures because you're running tests in the same environment as you develop components—the browser.
|
||||
|
||||

|
||||
|
||||
## Setup interactions addon
|
||||
## How does component testing in Storybook work?
|
||||
|
||||
You can set up interaction testing in Storybook using the `play` function and [`@storybook/addon-interactions`](https://storybook.js.org/addons/@storybook/addon-interactions/).
|
||||
You start by writing a [**story**](../writing-stories/introduction.md) to set up the component's initial state. Then simulate user behavior using the **play** function. Finally, use the Storybook **test-runner** to verify the DOM structure. Additionally, you can automate test execution via the [command line](./test-runner.md#cli-options) or in your [CI environment](./test-runner.md#set-up-ci-to-run-tests).
|
||||
|
||||
- The [`play`](../writing-stories/play-function.md) function is a small snippet of code that runs after a story finishes rendering. You can use this to test user workflows.
|
||||
- The test is written using Storybook-instrumented versions of [Jest](https://jestjs.io/) and [Testing Library](https://testing-library.com/).
|
||||
- [`@storybook/addon-interactions`](https://storybook.js.org/addons/@storybook/addon-interactions/) visualizes the test in Storybook and playback interface for convenient browser-based debugging.
|
||||
- [`@storybook/test-runner`](https://github.com/storybookjs/test-runner) is a standalone utility—powered by [Playwright](https://playwright.dev/)—that executes all your interactions tests and catches broken stories.
|
||||
|
||||
- [`@storybook/addon-interactions`](/addons/@storybook/addon-interactions/) includes helper utilities and a playback interface that simulates user behavior in the browser. It’s powered Testing Library and includes convenient instrumentation for debugging.
|
||||
## Set up the interactions addon
|
||||
|
||||
Here's an example of how to set up interaction testing in Storybook with the `play` function:
|
||||
To enable interaction testing with Storybook, you'll need to take additional steps to set it up properly. We recommend you go through the [test runner documentation](./test-runner.md) before proceeding with the rest of the required configuration.
|
||||
|
||||
Run the following command to install the interactions addon and related dependencies.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/storybook-addon-interactions-addon-full-install.yarn.js.mdx',
|
||||
'common/storybook-addon-interactions-addon-full-install.npm.js.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
Update your Storybook configuration (in `.storybook/main.js|ts`) to include the interactions addon and enable playback controls for debugging.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/storybook-main-enable-interactive-debugger.js.mdx',
|
||||
'common/storybook-main-enable-interactive-debugger.ts.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## Write an interaction test
|
||||
|
||||
The test itself is defined inside a `play` function connected to a story. Here's an example of how to set up an interaction test with Storybook and the `play` function:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
@ -49,7 +84,7 @@ Once the story loads in the UI, it simulates the user's behavior and verifies th
|
||||
|
||||
## API for user-events
|
||||
|
||||
Under the hood, Storybook’s interaction addon mirrors Testing Library’s `user-events` API. If you’re familiar with [Testing Library](https://testing-library.com/) you should be at home in Storybook.
|
||||
Under the hood, Storybook’s interaction addon mirrors Testing Library’s `user-events` API. If you’re familiar with [Testing Library](https://testing-library.com/), you should be at home in Storybook.
|
||||
|
||||
Below is an abridged API for user-event. For more, check out the [official user-event docs](https://testing-library.com/docs/ecosystem-user-event/).
|
||||
|
||||
@ -65,6 +100,17 @@ Below is an abridged API for user-event. For more, check out the [official user-
|
||||
| `type` | Writes text inside inputs, or textareas <br/>`userEvent.type(await within(canvasElement).getByRole('my-input'),'Some text');` |
|
||||
| `unhover` | Unhovers out of element <br/>`userEvent.unhover(await within(canvasElement).getByLabelText(/Example/i));` |
|
||||
|
||||
### Interactive debugger
|
||||
|
||||
If you check your interactions panel, you'll see the step-by-step flow. It also offers a handy set of UI controls to pause, resume, rewind, and step through each interaction.
|
||||
|
||||
<video autoPlay muted playsInline loop>
|
||||
<source
|
||||
src="addon-interactions-playback-controls-optimized.mp4"
|
||||
type="video/mp4"
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Permalinks for reproductions
|
||||
|
||||
The `play` function is executed after the story is rendered. If there’s an error, it’ll be shown in the interaction addon panel to help with debugging.
|
||||
@ -75,6 +121,33 @@ Since Storybook is a webapp, anyone with the URL can reproduce the error with th
|
||||
|
||||
Streamline interaction testing further by automatically [publishing Storybook](../sharing/publish-storybook.md) in pull requests. That gives teams a universal reference point to test and debug stories.
|
||||
|
||||
## Execute tests with the test-runner
|
||||
|
||||
Storybook only runs the interaction test when you're viewing a story. Therefore, you'd have to go through each story to run all your checks. As your Storybook grows, it becomes unrealistic to review each change manually. Storybook [test-runner](https://github.com/storybookjs/test-runner) automates the process by running all tests for you. To execute the test-runner, open a new terminal window and run the following command:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/storybook-test-runner-execute.yarn.js.mdx',
|
||||
'common/storybook-test-runner-execute.npm.js.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||

|
||||
|
||||
<div class="aside">
|
||||
|
||||
💡 If you need, you can provide additional flags to the test-runner. Read the [documentation](./test-runner.md#cli-options) to learn more.
|
||||
|
||||
</div>
|
||||
|
||||
## Automate
|
||||
|
||||
Once you're ready to push your code into a pull request, you'll want to automatically run all your checks using a Continuous Integration (CI) service before merging it. Read our [documentation](./test-runner.md#set-up-ci-to-run-tests) for a detailed guide on setting up a CI environment to run tests.
|
||||
|
||||
---
|
||||
|
||||
#### What’s the difference between interaction tests and visual tests?
|
||||
@ -83,6 +156,7 @@ Interaction tests can be expensive to maintain when applied wholesale to every c
|
||||
|
||||
#### Learn about other UI tests
|
||||
|
||||
- [Test runner](./test-runner.md) to automate test execution
|
||||
- [Visual tests](./visual-testing.md) for appearance
|
||||
- [Accessibility tests](accessibility-testing.md) for accessibility
|
||||
- Interaction tests for user behavior simulation
|
||||
|
@ -10,8 +10,11 @@ That means stories are a pragmatic starting point for your UI testing strategy.
|
||||
|
||||
The simplest testing method is manual “spot checking”. You run Storybook locally, then eyeball every story to verify its appearance and behavior. [Publish](../sharing/publish-storybook.md) your Storybook online to share reproductions and get teammates involved.
|
||||
|
||||
Storybook also comes with tools, test runners, and handy integrations with the larger JavaScript ecosystem to expand your UI test coverage. These docs detail how you can use Storybook for UI testing.
|
||||
To test a component in isolation, you often have to mock data, dependencies or even network requests. Check out our guide on [mocking in Storybook](../writing-stories/build-pages-with-storybook.md#mocking-connected-components) for more info.
|
||||
|
||||
Storybook also comes with tools, [test runners](./test-runner.md), and [handy integrations](./importing-stories-in-tests.md) with the larger JavaScript ecosystem to expand your UI test coverage. These docs detail how you can use Storybook for UI testing.
|
||||
|
||||
- [**Test runner**](./test-runner.md) to automatically test your entire Storybook and catch broken stories.
|
||||
- [**Visual tests**](./visual-testing.md) capture a screenshot of every story then compare it against baselines to detect appearance and integration issues
|
||||
- [**Accessibility tests**](./accessibility-testing.md) catch usability issues related to visual, hearing, mobility, cognitive, speech, or neurological disabilities
|
||||
- [**Interaction tests**](./interaction-testing.md) verify component functionality by simulating user behaviour, firing events, and ensuring that state is updated as expected
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 37 KiB |
@ -110,6 +110,7 @@ Visual tests capture images of stories and compare them against image baselines.
|
||||
|
||||
#### Learn about other UI tests
|
||||
|
||||
- [Test runner](./test-runner.md) to automate test execution
|
||||
- [Visual tests](./visual-testing.md) for appearance
|
||||
- [Accessibility tests](./accessibility-testing.md) for accessibility
|
||||
- [Interaction tests](./interaction-testing.md) for user behavior simulation
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
BIN
docs/writing-tests/test-runner-a11y-optimized.png
Normal file
BIN
docs/writing-tests/test-runner-a11y-optimized.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
293
docs/writing-tests/test-runner.md
Normal file
293
docs/writing-tests/test-runner.md
Normal file
@ -0,0 +1,293 @@
|
||||
---
|
||||
title: 'Test runner'
|
||||
---
|
||||
|
||||
Storybook test runner turns all of your stories into executable tests. It is powered by [Jest](https://jestjs.io/) and [Playwright](https://playwright.dev/).
|
||||
|
||||
- For those [without a play function](../writing-stories/introduction.md): it verifies whether the story renders without any errors.
|
||||
- For those [with a play function](../writing-stories/play-function.md): it also checks for errors in the play function and all assertions passed.
|
||||
|
||||
These tests run in a live browser and can be executed via the [command line](#cli-options) or your [CI server](#set-up-ci-to-run-tests).
|
||||
|
||||
## Setup
|
||||
|
||||
The test runner is a standalone, framework-agnostic addon that runs parallel to your Storybook. You will need to take some additional steps to set it up properly. Detailed below is our recommendation to configure and execute it.
|
||||
|
||||
Run the following command to install the addon and the required dependencies.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/storybook-test-runner-install.yarn.js.mdx',
|
||||
'common/storybook-test-runner-install.npm.js.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
Update your `package.json` scripts and enable the test runner.
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"test-storybook": "test-storybook"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Start your Storybook with:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'angular/storybook-run-dev.with-builder.js.mdx',
|
||||
'common/storybook-run-dev.npm.js.mdx',
|
||||
'common/storybook-run-dev.yarn.js.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<div class="aside">
|
||||
💡 Storybook's test runner requires a running Storybook instance to run all the existing tests.
|
||||
</div>
|
||||
|
||||
Finally, open a new terminal window and run the test runner with:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/storybook-test-runner-execute.yarn.js.mdx',
|
||||
'common/storybook-test-runner-execute.npm.js.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## Configure
|
||||
|
||||
Test runner offers zero-config support for Storybook. However, you can run `test-storybook --eject` for more fine-grained control. It generates a `test-runner-jest.config.js` file at the root of your project, which you can modify. Additionally, you can extend the generated configuration file and provide [testEnvironmentOptions](https://github.com/playwright-community/jest-playwright#configuration) as the test runner also uses [jest-playwright](https://github.com/playwright-community/jest-playwright) under the hood.
|
||||
|
||||
### CLI Options
|
||||
|
||||
The test runner is powered by [Jest](https://jestjs.io/) and accepts all its [CLI options](https://jestjs.io/docs/cli) (e.g., `--watch`, `--maxWorkers`).
|
||||
If you're already using any of those flags in your project, you should be able to migrate them into Storybook's test runner without any issues. Listed below are all the available flags and examples of using them.
|
||||
|
||||
| Options | Description |
|
||||
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `--help` | Output usage information <br/>`test-storybook --help` |
|
||||
| `-s`, `--stories-json` | Run in stories json mode. Automatically detected (requires a compatible Storybook) <br/>`test-storybook --stories-json` |
|
||||
| `--no-stories-json` | Disables stories json mode <br/>`test-storybook --no-stories-json` |
|
||||
| `-c`, `--config-dir [dir-name]` | Directory where to load Storybook configurations from <br/>`test-storybook -c .storybook` |
|
||||
| `--watch` | Run in watch mode <br/>`test-storybook --watch` |
|
||||
| `--url` | Define the URL to run tests in. Useful for custom Storybook URLs <br/>`test-storybook --url http://the-storybook-url-here.com` |
|
||||
| `--browsers` | Define browsers to run tests in. One or multiple of: chromium, firefox, webkit <br/>`test-storybook --browsers firefox chromium` |
|
||||
| `--maxWorkers [amount]` | Specifies the maximum number of workers the worker-pool will spawn for running tests <br/>`test-storybook --maxWorkers=2` |
|
||||
| `--no-cache` | Disable the cache <br/>`test-storybook --no-cache` |
|
||||
| `--clearCache` | Deletes the Jest cache directory and then exits without running tests <br/>`test-storybook --clearCache` |
|
||||
| `--verbose` | Display individual test results with the test suite hierarchy <br/>`test-storybook --verbose` |
|
||||
| `-u`, `--updateSnapshot` | Use this flag to re-record every snapshot that fails during this test run <br/>`test-storybook -u` |
|
||||
| `--eject` | Creates a local configuration file to override defaults of the test-runner <br/>`test-storybook --eject` |
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/storybook-test-runner-execute-with-flags.yarn.js.mdx',
|
||||
'common/storybook-test-runner-execute-with-flags.npm.js.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
### Run tests against a deployed Storybook
|
||||
|
||||
By default, the test runner assumes that you're running it against a locally served Storybook on port `6006`. If you want to define a target URL to run against deployed Storybooks, you can use the `--url` flag or set the `TARGET_URL` environment variable. For example:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/storybook-test-runner-execute-with-url.yarn.js.mdx',
|
||||
'common/storybook-test-runner-execute-with-url.npm.js.mdx',
|
||||
'common/storybook-test-runner-execute-with-url.env-var.js.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## Set up CI to run tests
|
||||
|
||||
You can also configure the test runner to run tests on a CI environment. Documented below are some recipes to help you get started.
|
||||
|
||||
### Run against deployed Storybooks
|
||||
|
||||
If you're already using a CI provider (e.g., [GitHub Actions](https://github.com/features/actions), [GitLab Pipelines](https://docs.gitlab.com/ee/ci/pipelines/), [CircleCI](https://circleci.com/)) to build and publish your Storybook, you can configure your environment and run the test runner against the deployed version. Here's a recipe that uses third-party libraries (i.e., [concurrently](https://www.npmjs.com/package/concurrently), [http-server](https://www.npmjs.com/package/http-server), and [wait-on](https://www.npmjs.com/package/wait-on)).
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/storybook-test-runner-local-build-workflow.yml.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
### Run against deployed Storybooks via Github Actions deployment
|
||||
|
||||
If you're publishing your Storybook with services such as [Vercel](https://vercel.com/) or [Netlify](https://www.netlify.com/), they emit a `deployment_status` event in GitHub Actions. You can use it and set the `deployment_status.target_url` as the `TARGET_URL` environment variable. Here's how:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/storybook-test-runner-with-deploy-event-workflow.yml.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<div class="aside">
|
||||
|
||||
💡 The published Storybook must be publicly available for this example to work. We recommend running the test server using the recipe [above](#run-against-locally-built-storybooks) if it requires authentication.
|
||||
|
||||
</div>
|
||||
|
||||
### What's the difference between Chromatic and Test runner?
|
||||
|
||||
Test Runner is a generic testing tool that can run locally or on CI and be configured or extended to run all kinds of tests.
|
||||
|
||||
[Chromatic](https://www.chromatic.com/) is a cloud-based service that runs [visual](./visual-testing.md) and [interaction tests](./interaction-testing.md) (and soon accessibility tests) without setting up the test runner. It also syncs with your git provider and manages access control for private projects.
|
||||
|
||||
However, you might want to pair the test runner and Chromatic in some cases.
|
||||
|
||||
- Use it locally and Chromatic on your CI.
|
||||
- Use Chromatic for visual and interaction tests and run other custom tests using the test runner.
|
||||
|
||||
## Advanced configuration
|
||||
|
||||
### Test hook API (experimental)
|
||||
|
||||
The test runner renders a story and executes its [play function](writing-stories/play-function.md) if one exists. However, certain behaviors are impossible to achieve via the play function, which executes in the browser. For example, if you want the test runner to take visual snapshots for you, this is possible via Playwright/Jest but must be executed in Node.
|
||||
|
||||
The test runner exports test hooks that can be overridden globally to enable use cases like visual or DOM snapshots. These hooks give you access to the test lifecycle before and after the story is rendered.
|
||||
Listed below are the available hooks and an overview of how to use them.
|
||||
|
||||
| Hook | Description |
|
||||
| ------------ | ----------------------------------------------------------------------------- |
|
||||
| `setup` | Executes once before all the tests run<br/>`setup() {}` |
|
||||
| `preRender` | Executes before a story is rendered<br/>`async preRender(page, context) {}` |
|
||||
| `postRender` | Executes after the story is rendered<br/>`async postRender(page, context) {}` |
|
||||
|
||||
<div class="aside">
|
||||
|
||||
💡 These test hooks are experimental and may be subject to breaking changes. We encourage you to test as much as possible within the story's [play function](../writing-stories/play-function.md).
|
||||
|
||||
</div>
|
||||
|
||||
To enable the hooks API, you'll need to add a new configuration file inside your Storybook directory and set them up as follows:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/storybook-test-runner-hooks-example.js.mdx',
|
||||
'common/storybook-test-runner-hooks-example.ts.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<div class="aside">
|
||||
|
||||
💡 Except for the `setup` function, all other functions run asynchronously. Both `preRender` and `postRender` functions include two additional arguments, a [Playwright page](https://playwright.dev/docs/pages) and a context object which contains the `id`, `title`, and the `name` of the story.
|
||||
|
||||
</div>
|
||||
|
||||
When the test runner executes, your existing tests will go through the following lifecycle:
|
||||
|
||||
- The `setup` function is executed before all the tests run.
|
||||
- The context object is generated containing the required information.
|
||||
- Playwright navigates to the story's page.
|
||||
- The `preRender` function is executed.
|
||||
- The story is rendered, and any existing `play` functions are executed.
|
||||
- The `postRender` function is executed.
|
||||
|
||||
### Stories.json mode
|
||||
|
||||
When testing a local Storybook, the test runner transforms your story files into tests. For a remote Storybook, it uses the Storybook's [stories.json](../configure/overview.md#feature-flags) file (a static index of all the stories) to run the tests. Suppose you run into a situation where the local and remote Storybooks appear out of sync, or you might not even have access to the code. In that case, the `stories.json` file is guaranteed to be the most accurate representation of the deployed Storybook you are testing. To test a local Storybook using this feature, use the `--stories-json` flag as follows:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/storybook-test-runner-with-stories-json.yarn.js.mdx',
|
||||
'common/storybook-test-runner-with-stories-json.npm.js.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<div class="aside">
|
||||
|
||||
💡 The `stories.json` mode is not compatible with watch mode.
|
||||
|
||||
</div>
|
||||
|
||||
To disable it, use the `--no-stories-json` flag:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/storybook-test-runner-disable-stories-json.yarn.js.mdx',
|
||||
'common/storybook-test-runner-disable-stories-json.npm.js.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### The test runner seems flaky and keeps timing out
|
||||
|
||||
If your tests time out with the following message:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<CodeSnippets
|
||||
paths={[
|
||||
'common/storybook-test-runner-error.js.mdx',
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
It might be that Playwright couldn't handle testing the number of stories you have in your project. Perhaps you have a large number of stories, or your CI environment has a really low RAM configuration. In such cases, you should limit the number of workers that run in parallel by adjusting your command as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"test-storybook:ci": "concurrently -k -s first -n \"SB,TEST\" -c \"magenta,blue\" \"yarn build-storybook --quiet && npx http-server storybook-static --port 6006 --silent\" \"wait-on tcp:6006 && yarn test-storybook --maxWorkers=2\""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Run the test runner in other CI environments
|
||||
|
||||
As the test runner is based on Playwright, you might need to use specific docker images or other configurations depending on your CI setup. In that case, you can refer to the [Playwright CI docs](https://playwright.dev/docs/ci) for more information.
|
||||
|
||||
#### Learn about other UI tests
|
||||
|
||||
- Test runner to automate test execution
|
||||
- [Visual tests](./visual-testing.md) for appearance
|
||||
- [Accessibility tests](./accessibility-testing.md) for accessibility
|
||||
- [Interaction tests](./interaction-testing.md) for user behavior simulation
|
||||
- [Snapshot tests](./snapshot-testing.md) for rendering errors and warnings
|
||||
- [Import stories in other tests](./importing-stories-in-tests.md) for other tools
|
@ -74,6 +74,7 @@ Snapshot tests compare the rendered markup of every story against known baseline
|
||||
|
||||
#### Learn about other UI tests
|
||||
|
||||
- [Test runner](./test-runner.md) to automate test execution
|
||||
- Visual tests for appearance
|
||||
- [Accessibility tests](./accessibility-testing.md) for accessibility
|
||||
- [Interaction tests](./interaction-testing.md) for user behavior simulation
|
||||
|
Loading…
x
Reference in New Issue
Block a user