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 3 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
11 changes: 11 additions & 0 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { isTestRunner } from '../dist/esm/is-test-runner';

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

return StoryFn();
};

export const decorators = [withSkippableTests];
31 changes: 28 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
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)
- [isTestRunner](#istestrunner)
- [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,25 @@ module.exports = {
};
```

#### isTestRunner

The `isTestRunner` function can be used to determine if a story is rendering in the context of the test runner.

```js
import { isTestRunner } from '@storybook/test-runner/is-test-runner';
export const MyStory = () => (
<div>
<p>Is this story running in the test runner?</p>
<p>{isTestRunner() ? 'Yes' : 'No'}</p>
</div>
);
```

The result of `isTestRunner()` will be true in the following scenarios:

1. In the browser, when the story is rendered while running the test runner
2. In node, if you prepend your Storybook script with `STORYBOOK_TEST_RUNNER=true`
tmeasday marked this conversation as resolved.
Show resolved Hide resolved

## Troubleshooting

#### The error output in the CLI is too short
Expand Down
2 changes: 1 addition & 1 deletion bin/test-storybook.js
Original file line number Diff line number Diff line change
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
24 changes: 18 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,21 @@
"license": "MIT",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"types": "dist/ts/index.d.ts",
"exports": {
".": {
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js",
"types": "./dist/ts/index.d.ts"
},
"./is-test-runner": {
"import": "./dist/esm/is-test-runner.mjs",
"require": "./dist/cjs/is-test-runner.cjs"
},
"./package.json": "./package.json"
},
"bin": {
"test-storybook": "./bin/test-storybook.js"
},
"files": [
"bin",
"dist/**/*",
Expand All @@ -30,8 +44,8 @@
"scripts": {
"clean": "rimraf ./dist",
"buildBabel": "concurrently \"yarn buildBabel:cjs\" \"yarn buildBabel:esm\"",
"buildBabel:cjs": "babel ./src -d ./dist/cjs --extensions \".js,.jsx,.ts,.tsx\"",
"buildBabel:esm": "babel ./src -d ./dist/esm --env-name esm --extensions \".js,.jsx,.ts,.tsx\"",
"buildBabel:cjs": "babel ./src -d ./dist/cjs --extensions \".js,.jsx,.ts,.tsx,.cjs,.mjs\"",
"buildBabel:esm": "babel ./src -d ./dist/esm --env-name esm --extensions \".js,.jsx,.ts,.tsx,.cjs,.mjs\"",
"buildTsc": "tsc --declaration --emitDeclarationOnly --outDir ./dist/ts",
"prebuild": "yarn clean",
"build": "concurrently \"yarn buildBabel\" \"yarn buildTsc\"",
Expand All @@ -52,9 +66,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 +136,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
8 changes: 8 additions & 0 deletions src/is-test-runner/is-test-runner.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Returns whether the story is rendering inside of the Storybook test runner.
*/
module.exports = {
isTestRunner: function () {
return process?.env?.STORYBOOK_TEST_RUNNER === 'true';
}
}
10 changes: 10 additions & 0 deletions src/is-test-runner/is-test-runner.mjs
Original file line number Diff line number Diff line change
@@ -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/)
);
}
18 changes: 16 additions & 2 deletions src/setup-page.ts
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { expect } from '@storybook/jest';
import { isTestRunner } from '../../dist/esm/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,
},
};