Skip to content

Commit

Permalink
Merge pull request #406 from storybookjs/feat/log-level
Browse files Browse the repository at this point in the history
Introduce logLevel configuration
  • Loading branch information
yannbf committed Nov 27, 2023
2 parents ad3d401 + dd7eb64 commit 99b800b
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 18 deletions.
1 change: 1 addition & 0 deletions .storybook/test-runner.ts
Expand Up @@ -7,6 +7,7 @@ const customSnapshotsDir = `${process.cwd()}/${snapshotsDir}`;
const skipSnapshots = process.env.SKIP_SNAPSHOTS === 'true';

const config: TestRunnerConfig = {
logLevel: 'verbose',
tags: {
exclude: ['exclude'],
include: [],
Expand Down
21 changes: 21 additions & 0 deletions README.md
Expand Up @@ -36,6 +36,7 @@ Storybook test runner turns all of your stories into executable tests.
- [prepare](#prepare)
- [getHttpHeaders](#gethttpheaders)
- [tags (experimental)](#tags-experimental)
- [logLevel](#loglevel)
- [Utility functions](#utility-functions)
- [getStoryContext](#getstorycontext)
- [waitForPageReady](#waitforpageready)
Expand Down Expand Up @@ -705,6 +706,26 @@ export default config;

`tags` are used for filtering your tests. Learn more [here](#filtering-tests-experimental).

#### logLevel

When tests fail and there were browser logs during the rendering of a story, the test-runner provides the logs alongside the error message. The `logLevel` property defines what kind of logs should be displayed:

- **`info` (default):** Shows console logs, warnings, and errors.
- **`warn`:** Shows only warnings and errors.
- **`error`:** Displays only error messages.
- **`verbose`:** Includes all console outputs, including debug information and stack traces.
- **`none`:** Suppresses all log output.

```ts
// .storybook/test-runner.ts
import type { TestRunnerConfig } from '@storybook/test-runner';

const config: TestRunnerConfig = {
logLevel: 'verbose',
};
export default config;
```

### Utility functions

For more specific use cases, the test runner provides utility functions that could be useful to you.
Expand Down
5 changes: 5 additions & 0 deletions src/playwright/hooks.ts
Expand Up @@ -58,6 +58,11 @@ export interface TestRunnerConfig {
exclude?: string[];
skip?: string[];
};
/**
* Defines the log level of the test runner. Browser logs are printed to the console when reporting errors.
* @default 'info'
*/
logLevel?: 'info' | 'warn' | 'error' | 'verbose' | 'none';
}

export const setPreVisit = (preVisit: TestHook) => {
Expand Down
79 changes: 61 additions & 18 deletions src/setup-page-script.ts
Expand Up @@ -5,13 +5,27 @@
* setup-page.ts will read the contents of this file and replace values that use {{x}} pattern, and they should be put right below:
*/

type ConsoleMethod =
| 'log'
| 'info'
| 'warn'
| 'error'
| 'trace'
| 'debug'
| 'group'
| 'groupCollapsed'
| 'table'
| 'dir';
type LogLevel = 'none' | 'info' | 'warn' | 'error' | 'verbose';

// All of these variables will be replaced once this file is processed.
const storybookUrl: string = '{{storybookUrl}}';
const testRunnerVersion: string = '{{testRunnerVersion}}';
const failOnConsole: string = '{{failOnConsole}}';
const renderedEvent: string = '{{renderedEvent}}';
const viewMode: string = '{{viewMode}}';
const debugPrintLimit = parseInt('{{debugPrintLimit}}', 10);
const TEST_RUNNER_STORYBOOK_URL: string = '{{storybookUrl}}';
const TEST_RUNNER_VERSION: string = '{{testRunnerVersion}}';
const TEST_RUNNER_FAIL_ON_CONSOLE: string = '{{failOnConsole}}';
const TEST_RUNNER_RENDERED_EVENT: string = '{{renderedEvent}}';
const TEST_RUNNER_VIEW_MODE: string = '{{viewMode}}';
const TEST_RUNNER_LOG_LEVEL = '{{logLevel}}' as LogLevel;
const TEST_RUNNER_DEBUG_PRINT_LIMIT = parseInt('{{debugPrintLimit}}', 10);

// Type definitions for globals
declare global {
Expand Down Expand Up @@ -194,7 +208,7 @@ class StorybookTestRunnerError extends Error {
constructor(storyId: string, errorMessage: string, logs: string[] = []) {
super(errorMessage);
this.name = 'StorybookTestRunnerError';
const storyUrl = `${storybookUrl}?path=/story/${storyId}`;
const storyUrl = `${TEST_RUNNER_STORYBOOK_URL}?path=/story/${storyId}`;
const finalStoryUrl = `${storyUrl}&addonPanel=storybook/interactions/panel`;
const separator = '\n\n--------------------------------------------------';
// The original error message will also be collected in the logs, so we filter it to avoid duplication
Expand All @@ -204,7 +218,7 @@ class StorybookTestRunnerError extends Error {

this.message = `\nAn error occurred in the following story. Access the link for full output:\n${finalStoryUrl}\n\nMessage:\n ${truncate(
errorMessage,
debugPrintLimit
TEST_RUNNER_DEBUG_PRINT_LIMIT
)}\n${extraLogs}`;
}
}
Expand Down Expand Up @@ -266,35 +280,64 @@ async function __test(storyId: string): Promise<any> {
);
}

addToUserAgent(`(StorybookTestRunner@${testRunnerVersion})`);
addToUserAgent(`(StorybookTestRunner@${TEST_RUNNER_VERSION})`);

// Collect logs to show upon test error
let logs: string[] = [];
let hasErrors = false;

type ConsoleMethod = 'log' | 'group' | 'warn' | 'error' | 'trace' | 'groupCollapsed';
const logLevelMapping: { [key in ConsoleMethod]: LogLevel[] } = {
log: ['info', 'verbose'],
warn: ['info', 'warn', 'verbose'],
error: ['info', 'warn', 'error', 'verbose'],
info: ['verbose'],
trace: ['verbose'],
debug: ['verbose'],
group: ['verbose'],
groupCollapsed: ['verbose'],
table: ['verbose'],
dir: ['verbose'],
};

const spyOnConsole = (method: ConsoleMethod, name: string): void => {
const originalFn = console[method].bind(console);
console[method] = function () {
if (failOnConsole === 'true' && method === 'error') {
const shouldCollectError = TEST_RUNNER_FAIL_ON_CONSOLE === 'true' && method === 'error';
if (shouldCollectError) {
hasErrors = true;
}
const message = Array.from(arguments).map(composeMessage).join(', ');
const prefix = `${bold(name)}: `;
logs.push(prefix + message);

let message = Array.from(arguments).map(composeMessage).join(', ');
if (method === 'trace') {
const stackTrace = new Error().stack;
message += `\n${stackTrace}\n`;
}

if (logLevelMapping[method].includes(TEST_RUNNER_LOG_LEVEL) || shouldCollectError) {
const prefix = `${bold(name)}: `;
logs.push(prefix + message);
}

originalFn(...arguments);
};
};

// Console methods + color function for their prefix
const spiedMethods: { [key: string]: Colorizer } = {
// info
log: blue,
info: blue,
// warn
warn: yellow,
// error
error: red,
// verbose
dir: magenta,
trace: magenta,
group: magenta,
groupCollapsed: magenta,
table: magenta,
debug: magenta,
};

Object.entries(spiedMethods).forEach(([method, color]) => {
Expand All @@ -309,12 +352,12 @@ async function __test(storyId: string): Promise<any> {

return new Promise((resolve, reject) => {
const listeners = {
[renderedEvent]: () => {
[TEST_RUNNER_RENDERED_EVENT]: () => {
cleanup(listeners);
if (hasErrors) {
return reject(new StorybookTestRunnerError(storyId, 'Browser console errors', logs));
reject(new StorybookTestRunnerError(storyId, 'Browser console errors', logs));
}
return resolve(document.getElementById('root'));
resolve(document.getElementById('root'));
},

storyUnchanged: () => {
Expand Down Expand Up @@ -355,7 +398,7 @@ async function __test(storyId: string): Promise<any> {
channel.on(eventName, listener);
});

channel.emit('setCurrentStory', { storyId, viewMode });
channel.emit('setCurrentStory', { storyId, viewMode: TEST_RUNNER_VIEW_MODE });
});
}

Expand Down
1 change: 1 addition & 0 deletions src/setup-page.ts
Expand Up @@ -69,6 +69,7 @@ export const setupPage = async (page: Page, browserContext: BrowserContext) => {
.replaceAll('{{failOnConsole}}', failOnConsole ?? 'false')
.replaceAll('{{renderedEvent}}', renderedEvent)
.replaceAll('{{testRunnerVersion}}', testRunnerVersion)
.replaceAll('{{logLevel}}', testRunnerConfig.logLevel ?? 'info')
.replaceAll('{{debugPrintLimit}}', debugPrintLimit.toString());

await page.addScriptTag({ content });
Expand Down

0 comments on commit 99b800b

Please sign in to comment.