Skip to content
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

Release 0.2.0 #126

Merged
merged 15 commits into from
Jun 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
name: Nightly checks

# runs every day at midnight
on:
schedule:
- cron: "0 0 * * *"

jobs:
test_storybook_prerelease:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x

- name: Install dependencies
uses: bahmutov/npm-install@v1

- name: Get current version of Storybook
run: |
echo "prev_sb_version=$(yarn list @storybook/react --depth=0 2> /dev/null | grep @storybook/react | awk -F'@' '{print $3}')" >> $GITHUB_ENV
echo "prev_sb_csf_version=$(yarn list @storybook/csf --depth=0 2> /dev/null | grep @storybook/csf | awk -F'@' '{print $3}')" >> $GITHUB_ENV

- name: Upgrade to storybook@next
run: |
npx storybook upgrade --prerelease

# TODO: This should not be necessary once @storybook/csf is properly updated
- name: Fix local @storybook/csf version
run: |
yarn add @storybook/[email protected]

- name: Run test runner
uses: mathiasvr/command-output@v1
id: tests
with:
run: |
yarn build
yarn test-storybook:ci

- name: Get prerelease version of Storybook
if: ${{ failure() }}
run: |
echo "sb_version=$(yarn list @storybook/react --depth=0 2> /dev/null | grep @storybook/react | awk -F'@' '{print $3}')" >> $GITHUB_ENV
echo "sb_csf_version=$(yarn list @storybook/csf --depth=0 2> /dev/null | grep @storybook/csf | awk -F'@' '{print $3}')" >> $GITHUB_ENV

- name: Report incoming errors
if: ${{ failure() }}
id: slack
uses: slackapi/[email protected]
with:
channel-id: "${{ secrets.SLACK_CHANNEL_ID }}"
payload: |
{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": ":storybook: :runner: [Test Runner] The Nightly check has failed :alert:",
"emoji": true
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*@storybook/react version:*\n${{ env.prev_sb_version }} >> ${{ env.sb_version }}"
},
{
"type": "mrkdwn",
"text": "*@storybook/csf version:*\n${{ env.prev_sb_csf_version }} >> ${{ env.sb_csf_version }}"
}
],
"accessory": {
"type": "button",
"text": {
"type": "plain_text",
"text": "View failure",
"emoji": true
},
"value": "view_failure",
"url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
"action_id": "button-action"
}
}
]
}
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on: [push]
jobs:
release:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'ci skip') && !contains(github.event.head_commit.message, 'skip ci')"
if: "!contains(github.event.head_commit.message, 'ci skip') && !contains(github.event.head_commit.message, 'skip ci') && !contains(github.event.head_commit.message, 'skip release')"
steps:
- uses: actions/checkout@v2

Expand Down
14 changes: 7 additions & 7 deletions .storybook/main.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
const { STRESS_TEST, STORY_STORE_V7, WITHOUT_DOCS } = process.env;

const stories = [
'../stories/docs/**/*.stories.mdx',
// default title prefix
Expand All @@ -13,16 +11,15 @@ const stories = [
directory: '../stories/molecules',
},
// general glob
'../stories/pages/**/*.stories.*',
'../stories/pages/**/*.stories.@(js|jsx|ts|tsx)',
];


if (STRESS_TEST) {
if (process.env.STRESS_TEST) {
stories.push('../stories/stress-test/*.stories.@(js|jsx|ts|tsx)');
}

const addons = [
WITHOUT_DOCS
process.env.WITHOUT_DOCS
? {
name: '@storybook/addon-essentials',
options: {
Expand All @@ -37,7 +34,10 @@ module.exports = {
stories,
addons,
features: {
storyStoreV7: STORY_STORE_V7 ? true : false,
storyStoreV7: process.env.STORY_STORE_V7 ? true : false,
buildStoriesJson: true,
},
core: {
disableTelemetry: true
}
};
15 changes: 15 additions & 0 deletions .storybook/test-runner.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { toMatchImageSnapshot } from 'jest-image-snapshot';
import { getStoryContext } from '../dist/cjs/playwright/hooks';
import type { TestRunnerConfig } from '../dist/ts';

const snapshotsDir = process.env.SNAPSHOTS_DIR || '__snapshots__';
Expand All @@ -9,13 +10,27 @@ const config: TestRunnerConfig = {
expect.extend({ toMatchImageSnapshot });
},
async postRender(page, context) {
// Get entire context of a story, including parameters, args, argTypes, etc.
const { parameters } = await getStoryContext(page, context);

if (parameters?.tests?.disableSnapshots) {
console.log('skipping story ', context.id);
return;
}

// Visual snapshot tests
const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot({
customSnapshotsDir,
customSnapshotIdentifier: context.id,
failureThreshold: 0.03,
failureThresholdType: 'percent',
});

const elementHandler = await page.$('#root');
const innerHTML = await elementHandler.innerHTML();
// HTML snapshot tests
expect(innerHTML).toMatchSnapshot();
},
};

Expand Down
66 changes: 61 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ Read the announcement: [Interaction Testing with Storybook](https://storybook.js
- [Image snapshot recipe](#image-snapshot-recipe)
- [Render lifecycle](#render-lifecycle)
- [Troubleshooting](#troubleshooting)
- [The test runner seems flaky and keeps timing out](#the-test-runner-seems-flaky-and-keeps-timing-out)
- [Adding the test runner to other CI environments](#adding-the-test-runner-to-other-ci-environments)
- [The test runner seems flaky and keeps timing out](#the-test-runner-seems-flaky-and-keeps-timing-out)
- [Adding the test runner to other CI environments](#adding-the-test-runner-to-other-ci-environments)
- [Future work](#future-work)

## Features
Expand All @@ -45,7 +45,7 @@ yarn add @storybook/test-runner -D
Jest is a peer dependency. If you don't have it, also install it

```jsx
yarn add jest -D
yarn add jest@27 -D
```

<details>
Expand Down Expand Up @@ -170,7 +170,7 @@ module.exports = {
};
```

Once you have a valid `stories.json` file, your Storybook will be compatible with the "stories.json mode".
Once you have a valid `stories.json` file, your Storybook will be compatible with the "stories.json mode".

By default, the test runner will detect whether your Storybook URL is local or remote, and if it is remote, it will run in "stories.json mode" automatically. To disable it, you can pass the `--no-stories-json` flag:

Expand Down Expand Up @@ -265,9 +265,25 @@ All three functions can be set up in the configuration file `.storybook/test-run

> **NOTE:** 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.

### DOM snapshot recipe

The `postRender` function provides a [Playwright page](https://playwright.dev/docs/api/class-page) instance, of which you can use for DOM snapshot testing:

```js
// .storybook/test-runner.js
module.exports = {
async postRender(page, context) {
// the #root element wraps the story
const elementHandler = await page.$('#root');
const innerHTML = await elementHandler.innerHTML();
expect(innerHTML).toMatchSnapshot();
},
};
```

### Image snapshot recipe

Consider, for example, the following recipe to take image snapshots:
Here's a slightly different recipe for image snapshot testing:

```js
// .storybook/test-runner.js
Expand Down Expand Up @@ -314,6 +330,46 @@ it('button--basic', async () => {
});
```

### Global utility functions

While running tests using the hooks, you might want to get information from a story, such as the parameters passed to it, or its args. The test runner now provides a `getStoryContext` utility function that fetches the story context for the current story:

```js
await getStoryContext(page, context);
```

You can use it for multiple use cases, and here's an example that combines the story context and accessibility testing:

```js
// .storybook/test-runner.js
const { getStoryContext } = require('@storybook/test-runner');
const { injectAxe, checkA11y } = require('axe-playwright');

module.exports = {
async preRender(page, context) {
await injectAxe(page);
},
async postRender(page, context) {
// Get entire context of a story, including parameters, args, argTypes, etc.
const storyContext = await getStoryContext(page, context);

// Do not test a11y for stories that disable a11y
if (storyContext.parameters?.a11y?.disable) {
return;
}

await checkA11y(page, '#root', {
detailedReport: true,
detailedReportOptions: {
html: true,
},
// pass axe options defined in @storybook/addon-a11y
axeOptions: storyContext.parameters?.a11y?.options
})
},
};
```

## Troubleshooting

#### Errors with Jest 28
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@
"commander": "^9.0.0",
"global": "^4.4.0",
"is-localhost-ip": "^1.4.0",
"jest-playwright-preset": "^1.7.0",
"jest-playwright-preset": "^1.7.2",
"jest-serializer-html": "^7.1.0",
"jest-watch-typeahead": "^1.0.0",
"node-fetch": "^2",
"playwright": "^1.14.0",
Expand Down
1 change: 1 addition & 0 deletions src/config/jest-playwright.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const getJestConfig = () => {
globalTeardown: '@storybook/test-runner/playwright/global-teardown.js',
testEnvironment: '@storybook/test-runner/playwright/custom-environment.js',
setupFilesAfterEnv: ['@storybook/test-runner/playwright/jest-setup.js'],
snapshotSerializers: ['jest-serializer-html'],
testEnvironmentOptions: {
'jest-playwright': {
browsers: TEST_BROWSERS.split(',')
Expand Down
8 changes: 8 additions & 0 deletions src/playwright/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import global from 'global';
import type { Page } from 'playwright';
import type { StoryContext } from '@storybook/csf';

export type TestContext = {
id: string;
Expand All @@ -22,3 +23,10 @@ export const setPreRender = (preRender: TestHook) => {
export const setPostRender = (postRender: TestHook) => {
global.__sbPostRender = postRender;
};

export const getStoryContext = async (page: Page, context: TestContext): Promise<StoryContext> => {
// @ts-ignore
return page.evaluate(({ storyId }) => globalThis.__getContext(storyId), {
storyId: context.id,
});
};
6 changes: 5 additions & 1 deletion src/setup-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ export const setupPage = async (page) => {
});
}

async function __getContext(storyId) {
return globalThis.__STORYBOOK_PREVIEW__.storyStore.loadStory({ storyId });
}

async function __test(storyId) {
try {
await __waitForElement('#root');
Expand All @@ -104,7 +108,7 @@ export const setupPage = async (page) => {
throw new StorybookTestRunnerError(storyId, message);
}

const channel = window.__STORYBOOK_ADDONS_CHANNEL__;
const channel = globalThis.__STORYBOOK_ADDONS_CHANNEL__;
if(!channel) {
throw new StorybookTestRunnerError(
storyId,
Expand Down
2 changes: 1 addition & 1 deletion src/util/getStorybookMetadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as storybookMain from './getStorybookMain';

import { getStorybookMetadata } from './getStorybookMetadata';

describe.only('getStorybookMetadata', () => {
describe('getStorybookMetadata', () => {
afterAll(() => {
process.env.STORYBOOK_CONFIG_DIR = undefined;
});
Expand Down
11 changes: 11 additions & 0 deletions stories/atoms/Button.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ Primary.args = {
label: 'Button',
};

export const Secondary = Template.bind({});
Secondary.args = {
...Primary.args,
primary: false,
};
Secondary.parameters = {
tests: {
disableSnapshots: true
}
}

export const Demo = (args) => (
<button type="button" onClick={() => args.onSubmit('clicked')}>
Click
Expand Down
Loading