Skip to content

Commit

Permalink
Merge pull request #198 from storybookjs/feat/is-test-runner-utility
Browse files Browse the repository at this point in the history
Add isTestRunner utility
  • Loading branch information
yannbf committed Oct 7, 2022
2 parents 1742e05 + d45feb9 commit 5e677bd
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 9 deletions.
10 changes: 10 additions & 0 deletions .storybook/is-test-runner.js
@@ -0,0 +1,10 @@
/**
* Returns whether the story is rendering inside of the Storybook test runner.
*/
export function isTestRunner() {
return !!(
typeof window !== 'undefined' &&
window &&
window.navigator.userAgent.match(/StorybookTestRunner/)
);
}
11 changes: 11 additions & 0 deletions .storybook/preview.js
@@ -0,0 +1,11 @@
import { isTestRunner } from './is-test-runner';

const withSkippableTests = (StoryFn, { parameters }) => {
if (parameters.test?.skip && isTestRunner()) {
return () => {};
}

return StoryFn();
};

export const decorators = [withSkippableTests];
36 changes: 33 additions & 3 deletions README.md
Expand Up @@ -27,7 +27,9 @@ Storybook test runner turns all of your stories into executable tests.
- [DOM snapshot recipe](#dom-snapshot-recipe)
- [Image snapshot recipe](#image-snapshot-recipe)
- [Render lifecycle](#render-lifecycle)
- [Global utility functions](#global-utility-functions)
- [Utility functions](#utility-functions)
- [getStoryContext](#getstorycontext)
- [StorybookTestRunner user agent](#storybooktestrunner-user-agent)
- [Troubleshooting](#troubleshooting)
- [The error output in the CLI is too short](#the-error-output-in-the-cli-is-too-short)
- [The test runner seems flaky and keeps timing out](#the-test-runner-seems-flaky-and-keeps-timing-out)
Expand Down Expand Up @@ -475,7 +477,11 @@ it('button--basic', async () => {
});
```

### Global utility functions
### Utility functions

For more specific use cases, the test runner provides utility functions that could be useful to you.

#### getStoryContext

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:

Expand Down Expand Up @@ -506,7 +512,7 @@ module.exports = {
// Apply story-level a11y rules
await configureAxe(page, {
rules: storyContext.parameters?.a11y?.config?.rules,
})
});

// from Storybook 7.0 onwards, the selector should be #storybook-root
await checkA11y(page, '#root', {
Expand All @@ -521,6 +527,29 @@ module.exports = {
};
```
#### StorybookTestRunner user agent
The test-runner adds a `StorybookTestRunner` entry to the browser's user agent. You can use it to determine if a story is rendering in the context of the test runner. This might be useful if you want to disable certain features in your stories when running in the test runner, though it's likely an edge case.
```js
export const MyStory = () => {
const isTestRunner = window.navigator.userAgent.match(/StorybookTestRunner/);
return (
<div>
<p>Is this story running in the test runner?</p>
<p>{isTestRunner ? 'Yes' : 'No'}</p>
</div>
);
};
```
Given that this check is happening in the browser, it is only applicable in the following scenarios:
- inside of a render/template function of a story
- inside of a play function
- inside of preview.js
- inside any other code that is executed in the browser
## Troubleshooting
#### The error output in the CLI is too short
Expand Down Expand Up @@ -570,3 +599,4 @@ For more context, [here's some explanation](https://github.com/facebook/jest/iss
Future plans involve adding support for the following features:
- 📄 Run addon reports
- ⚙️ Spawning Storybook via the test runner in a single command
2 changes: 1 addition & 1 deletion bin/test-storybook.js
Expand Up @@ -9,13 +9,13 @@ const fs = require('fs');
const dedent = require('ts-dedent').default;
const path = require('path');
const tempy = require('tempy');
const semver = require('semver');
const { getCliOptions, getStorybookMetadata } = require('../dist/cjs/util');
const { transformPlaywrightJson } = require('../dist/cjs/playwright/transformPlaywrightJson');

// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'test';
process.env.NODE_ENV = 'test';
process.env.STORYBOOK_TEST_RUNNER = 'true';
process.env.PUBLIC_URL = '';

// Makes the script crash on unhandled rejections instead of silently
Expand Down
7 changes: 4 additions & 3 deletions package.json
Expand Up @@ -19,6 +19,9 @@
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"types": "dist/ts/index.d.ts",
"bin": {
"test-storybook": "./bin/test-storybook.js"
},
"files": [
"bin",
"dist/**/*",
Expand Down Expand Up @@ -52,9 +55,6 @@
"generate-dynamic-stories": "node scripts/generate-dynamic-stories.js",
"prepare": "husky install"
},
"bin": {
"test-storybook": "./bin/test-storybook.js"
},
"devDependencies": {
"@auto-it/released": "^10.37.1",
"@babel/cli": "^7.12.1",
Expand Down Expand Up @@ -125,6 +125,7 @@
"jest-watch-typeahead": "^2.0.0",
"node-fetch": "^2",
"playwright": "^1.14.0",
"read-pkg-up": "^7.0.1",
"regenerator-runtime": "^0.13.9",
"semver": "^7.3.7",
"tempy": "^1.0.1",
Expand Down
18 changes: 16 additions & 2 deletions src/setup-page.ts
@@ -1,5 +1,5 @@
import type { Page } from 'playwright';
import dedent from 'ts-dedent';
import readPackageUp from 'read-pkg-up';

const sanitizeURL = (url: string) => {
let finalURL = url;
Expand All @@ -25,8 +25,9 @@ const sanitizeURL = (url: string) => {
export const setupPage = async (page: Page) => {
const targetURL = new URL('iframe.html', process.env.TARGET_URL).toString();
const viewMode = process.env.VIEW_MODE || 'story';
const isCoverageMode = process.env.STORYBOOK_COLLECT_COVERAGE === 'true';
const renderedEvent = viewMode === 'docs' ? 'docsRendered' : 'storyRendered';
const { packageJson } = await readPackageUp();
const { version: testRunnerVersion } = packageJson;

const referenceURL = process.env.REFERENCE_URL && sanitizeURL(process.env.REFERENCE_URL);
const debugPrintLimit = process.env.DEBUG_PRINT_LIMIT
Expand Down Expand Up @@ -102,6 +103,17 @@ export const setupPage = async (page: Page) => {
return input;
}
function addToUserAgent(extra) {
const originalUserAgent = globalThis.navigator.userAgent;
if (!originalUserAgent.includes(extra)) {
Object.defineProperty(globalThis.navigator, 'userAgent', {
get: function () {
return [originalUserAgent, extra].join(' ');
},
});
}
};
class StorybookTestRunnerError extends Error {
constructor(storyId, errorMessage, logs) {
super(errorMessage);
Expand Down Expand Up @@ -166,6 +178,8 @@ export const setupPage = async (page: Page) => {
);
}
addToUserAgent(\`(StorybookTestRunner@${testRunnerVersion})\`);
// collect logs to show upon test error
let logs = [];
Expand Down
23 changes: 23 additions & 0 deletions stories/atoms/Button.stories.js
@@ -1,5 +1,6 @@
import React from 'react';
import { expect } from '@storybook/jest';
import { isTestRunner } from '../../.storybook/is-test-runner';
import { within, waitFor, userEvent, waitForElementToBeRemoved } from '@storybook/testing-library';

import { Button } from './Button';
Expand Down Expand Up @@ -109,3 +110,25 @@ WithLoaders.play = async ({ args, canvasElement }) => {
await userEvent.click(todoItem);
await expect(args.onSubmit).toHaveBeenCalledWith('delectus aut autem');
};

export const UserAgent = () => (
<div>
<p>
<strong>isTestRunner:</strong> {isTestRunner().toString()}
</p>
<p>
<strong>User agent:</strong> {window.navigator.userAgent}
</p>
</div>
);
UserAgent.play = async () => {
if (isTestRunner()) {
await expect(window.navigator.userAgent).toContain('StorybookTestRunner');
}
};
UserAgent.parameters = {
tests: {
skip: true,
disableSnapshots: true,
},
};

0 comments on commit 5e677bd

Please sign in to comment.