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

Add isTestRunner utility #198

Merged
merged 9 commits into from Oct 7, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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,
},
};