Skip to content

Commit

Permalink
Merge pull request #790 from Mike-Dax/forward-console
Browse files Browse the repository at this point in the history
Forward console logs from the story context to the user's CLI
  • Loading branch information
Quramy committed Jul 24, 2023
2 parents fe823f1 + 42f5429 commit a226224
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 66 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ Options:
--disableWaitAssets Disable waiting for requested assets [boolean] [default: false]
--silent [boolean] [default: false]
--verbose [boolean] [default: false]
--forwardConsoleLogs Forward in-page console logs to the user's console. [boolean] [default: false]
--serverCmd Command line to launch Storybook server. [string] [default: ""]
--serverTimeout Timeout [msec] for starting Storybook server. [number] [default: 60000]
--shard The sharding options for this run. In the format <shardNumber>/<totalShards>.
Expand Down
157 changes: 93 additions & 64 deletions packages/storycap/src/node/capturing-browser.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { EventEmitter } from 'events';
import path from 'path';
import type { Viewport } from 'puppeteer-core';
import type { ConsoleMessage, Viewport } from 'puppeteer-core';
import {
Story,
StorybookConnection,
Expand All @@ -21,6 +21,7 @@ import {
pickupWithVariantKey,
InvalidVariantKeysReason,
} from '../shared/screenshot-options-helper';
import { Logger } from './logger';

/**
*
Expand Down Expand Up @@ -361,87 +362,115 @@ export class CapturingBrowser extends StoryPreviewBrowser {
story: Story,
variantKey: VariantKey,
retryCount: number,
logger: Logger,
forwardConsoleLogs: boolean,
): Promise<ScreenshotResult> {
this.currentRequestId = requestId;
this.currentVariantKey = variantKey;
this.currentStoryRetryCount = retryCount;
let emittedScreenshotOptions: ScreenshotOptions | undefined;
this.resourceWatcher.clear();

await this.setCurrentStory(story, { forceRerender: true });
function onConsoleLog(msg: ConsoleMessage) {
const niceMessage = `From ${requestId} (${msg.type()}): ${msg.text()}`;

if (forwardConsoleLogs) {
switch (msg.type()) {
case 'warning':
logger.warn(niceMessage);
break;
case 'error':
logger.error(niceMessage);
break;
default:
logger.log(niceMessage);
break;
}
} else {
logger.debug(niceMessage);
}
}

this.page.on('console', onConsoleLog);

try {
await this.setCurrentStory(story, { forceRerender: true });

if (this.mode === 'managed') {
// Screenshot options are emitted form the browser process when managed mode.
emittedScreenshotOptions = await this.waitForOptionsFromBrowser();
if (!this.currentStory) {
throw new InvalidCurrentStoryStateError();
if (this.mode === 'managed') {
// Screenshot options are emitted form the browser process when managed mode.
emittedScreenshotOptions = await this.waitForOptionsFromBrowser();
if (!this.currentStory) {
throw new InvalidCurrentStoryStateError();
}
if (!emittedScreenshotOptions) {
// End this capturing process as failure of timeout if emitter don't resolve screenshot options.
return { buffer: null, succeeded: false, variantKeysToPush: [], defaultVariantSuffix: '' };
}
} else {
await sleep(this.opt.delay);
await this.waitBrowserMetricsStable('preEmit');
// Use only `baseScreenshotOptions` when simple mode.
emittedScreenshotOptions = pickupWithVariantKey(this.baseScreenshotOptions, this.currentVariantKey);
}
if (!emittedScreenshotOptions) {
// End this capturing process as failure of timeout if emitter don't resolve screenshot options.
return { buffer: null, succeeded: false, variantKeysToPush: [], defaultVariantSuffix: '' };

const mergedScreenshotOptions = mergeScreenshotOptions(this.baseScreenshotOptions, emittedScreenshotOptions);

// Get keys for variants included in the screenshot options in order to queue capturing them after this sequence.
const [invalidReason, keys] = extractVariantKeys(mergedScreenshotOptions);
const variantKeysToPush = this.currentVariantKey.isDefault ? keys : [];
this.logInvalidVariantKeysReason(invalidReason);

// End this capturing process as success if `skip` set true.
if (mergedScreenshotOptions.skip) {
await this.waitForDebugInput();
return { buffer: null, succeeded: true, variantKeysToPush, defaultVariantSuffix: '' };
}
} else {
await sleep(this.opt.delay);
await this.waitBrowserMetricsStable('preEmit');
// Use only `baseScreenshotOptions` when simple mode.
emittedScreenshotOptions = pickupWithVariantKey(this.baseScreenshotOptions, this.currentVariantKey);
}

const mergedScreenshotOptions = mergeScreenshotOptions(this.baseScreenshotOptions, emittedScreenshotOptions);
this.touched = false;

// Get keys for variants included in the screenshot options in order to queue capturing them after this sequence.
const [invalidReason, keys] = extractVariantKeys(mergedScreenshotOptions);
const variantKeysToPush = this.currentVariantKey.isDefault ? keys : [];
this.logInvalidVariantKeysReason(invalidReason);
// Change browser's viewport if needed.
const vpChanged = await this.setViewport(mergedScreenshotOptions);
// Skip to capture if the viewport option is invalid.
if (!vpChanged) return { buffer: null, succeeded: true, variantKeysToPush: [], defaultVariantSuffix: '' };

// End this capturing process as success if `skip` set true.
if (mergedScreenshotOptions.skip) {
await this.waitForDebugInput();
return { buffer: null, succeeded: true, variantKeysToPush, defaultVariantSuffix: '' };
}
// Modify elements state.
await this.setHover(mergedScreenshotOptions);
await this.setFocus(mergedScreenshotOptions);
await this.setClick(mergedScreenshotOptions);
await this.waitIfTouched();

this.touched = false;
// Wait until browser main thread gets stable.
await this.waitForResources(mergedScreenshotOptions);
await this.waitBrowserMetricsStable('postEmit');

// Change browser's viewport if needed.
const vpChanged = await this.setViewport(mergedScreenshotOptions);
// Skip to capture if the viewport option is invalid.
if (!vpChanged) return { buffer: null, succeeded: true, variantKeysToPush: [], defaultVariantSuffix: '' };

// Modify elements state.
await this.setHover(mergedScreenshotOptions);
await this.setFocus(mergedScreenshotOptions);
await this.setClick(mergedScreenshotOptions);
await this.waitIfTouched();

// Wait until browser main thread gets stable.
await this.waitForResources(mergedScreenshotOptions);
await this.waitBrowserMetricsStable('postEmit');

await this.page.evaluate(() => new Promise(res => (window as any).requestIdleCallback(res, { timeout: 3000 })));

// Get PNG image buffer
const rawBuffer = await this.page.screenshot({
fullPage: emittedScreenshotOptions.fullPage,
omitBackground: emittedScreenshotOptions.omitBackground,
captureBeyondViewport: emittedScreenshotOptions.captureBeyondViewport,
clip: emittedScreenshotOptions.clip ?? undefined,
});
await this.page.evaluate(() => new Promise(res => (window as any).requestIdleCallback(res, { timeout: 3000 })));

let buffer: Buffer | null = null;
if (Buffer.isBuffer(rawBuffer)) {
buffer = rawBuffer;
}
// Get PNG image buffer
const rawBuffer = await this.page.screenshot({
fullPage: emittedScreenshotOptions.fullPage,
omitBackground: emittedScreenshotOptions.omitBackground,
captureBeyondViewport: emittedScreenshotOptions.captureBeyondViewport,
clip: emittedScreenshotOptions.clip ?? undefined,
});

// We should reset elements state(e.g. focusing, hovering, clicking) for future screenshot for this story.
await this.resetIfTouched(mergedScreenshotOptions);
let buffer: Buffer | null = null;
if (Buffer.isBuffer(rawBuffer)) {
buffer = rawBuffer;
}

await this.waitForDebugInput();
// We should reset elements state(e.g. focusing, hovering, clicking) for future screenshot for this story.
await this.resetIfTouched(mergedScreenshotOptions);

return {
buffer,
succeeded: true,
variantKeysToPush,
defaultVariantSuffix: emittedScreenshotOptions.defaultVariantSuffix,
};
await this.waitForDebugInput();

return {
buffer,
succeeded: true,
variantKeysToPush,
defaultVariantSuffix: emittedScreenshotOptions.defaultVariantSuffix,
};
} finally {
this.page.off('console', onConsoleLog);
}
}
}
7 changes: 7 additions & 0 deletions packages/storycap/src/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ function createOptions(): MainOptions {
})
.option('silent', { boolean: true, default: false })
.option('verbose', { boolean: true, default: false })
.option('forwardConsoleLogs', {
boolean: true,
default: false,
description: "Forward in-page console logs to the user's console.",
})
.option('serverCmd', { string: true, default: '', description: 'Command line to launch Storybook server.' })
.option('serverTimeout', {
number: true,
Expand Down Expand Up @@ -114,6 +119,7 @@ function createOptions(): MainOptions {
parallel,
silent,
verbose,
forwardConsoleLogs,
serverTimeout,
serverCmd,
shard,
Expand Down Expand Up @@ -179,6 +185,7 @@ function createOptions(): MainOptions {
stateChangeDelay,
disableCssAnimation,
disableWaitAssets,
forwardConsoleLogs,
chromiumChannel: chromiumChannel as ChromeChannel,
chromiumPath,
launchOptions: puppeteerLaunchConfig,
Expand Down
8 changes: 7 additions & 1 deletion packages/storycap/src/node/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,13 @@ export async function main(mainOptions: MainOptions) {

try {
// Execution caputuring procedure.
const captured = await createScreenshotService({ workers, stories: shardedStories, fileSystem, logger }).execute();
const captured = await createScreenshotService({
workers,
stories: shardedStories,
fileSystem,
logger,
forwardConsoleLogs: mainOptions.forwardConsoleLogs,
}).execute();
logger.debug('Ended ScreenshotService execution.');
return captured;
} catch (error) {
Expand Down
6 changes: 5 additions & 1 deletion packages/storycap/src/node/screenshot-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export type ScreenshotServiceOptions = {
workers: CapturingBrowser[];
fileSystem: FileSystem;
stories: Story[];
forwardConsoleLogs: boolean;
};

/**
Expand All @@ -63,14 +64,17 @@ export function createScreenshotService({
logger,
stories,
workers,
forwardConsoleLogs,
}: ScreenshotServiceOptions): ScreenshotService {
const service = createExecutionService(
workers,
stories.map(story => createRequest({ story })),
({ rid, story, variantKey, count }, { push }) =>
async worker => {
// Delegate the request to the worker.
const [result, elapsedTime] = await time(worker.screenshot(rid, story, variantKey, count));
const [result, elapsedTime] = await time(
worker.screenshot(rid, story, variantKey, count, logger, forwardConsoleLogs),
);

const { succeeded, buffer, variantKeysToPush, defaultVariantSuffix } = result;

Expand Down
1 change: 1 addition & 0 deletions packages/storycap/src/node/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface MainOptions extends BaseBrowserOptions {
exclude: string[];
disableCssAnimation: boolean;
disableWaitAssets: boolean;
forwardConsoleLogs: boolean;
parallel: number;
shard: ShardOptions;
metricsWatchRetryCount: number;
Expand Down
1 change: 1 addition & 0 deletions packages/storycap/src/shared/screenshot-options-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const defaultScreenshotOptions = {
omitBackground: false,
captureBeyondViewport: true,
clip: null,
forwardConsoleLogs: false,
} as const;

/**
Expand Down

0 comments on commit a226224

Please sign in to comment.