From 839fac16b94ad6894605571e81ab638dabbecfc2 Mon Sep 17 00:00:00 2001 From: Michael Orenstein Date: Mon, 24 Jul 2023 14:29:44 +0930 Subject: [PATCH 1/2] feat: Forward console logs to main cli --- README.md | 1 + .../storycap/src/node/capturing-browser.ts | 157 +++++++++++------- packages/storycap/src/node/cli.ts | 3 + packages/storycap/src/node/main.ts | 8 +- .../storycap/src/node/screenshot-service.ts | 6 +- packages/storycap/src/node/types.ts | 1 + .../src/shared/screenshot-options-helper.ts | 1 + 7 files changed, 111 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 5793a28f6..4dd76062f 100644 --- a/README.md +++ b/README.md @@ -363,6 +363,7 @@ Options: --disableWaitAssets Disable waiting for requested assets [boolean] [default: false] --silent [boolean] [default: false] --verbose [boolean] [default: false] + --forwardConsoleLogs [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 /. diff --git a/packages/storycap/src/node/capturing-browser.ts b/packages/storycap/src/node/capturing-browser.ts index e0d635afd..6bc41de8e 100644 --- a/packages/storycap/src/node/capturing-browser.ts +++ b/packages/storycap/src/node/capturing-browser.ts @@ -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, @@ -21,6 +21,7 @@ import { pickupWithVariantKey, InvalidVariantKeysReason, } from '../shared/screenshot-options-helper'; +import { Logger } from './logger'; /** * @@ -361,6 +362,8 @@ export class CapturingBrowser extends StoryPreviewBrowser { story: Story, variantKey: VariantKey, retryCount: number, + logger: Logger, + forwardConsoleLogs: boolean, ): Promise { this.currentRequestId = requestId; this.currentVariantKey = variantKey; @@ -368,80 +371,106 @@ export class CapturingBrowser extends StoryPreviewBrowser { 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); + } } } diff --git a/packages/storycap/src/node/cli.ts b/packages/storycap/src/node/cli.ts index 65e40ba98..9983064b0 100644 --- a/packages/storycap/src/node/cli.ts +++ b/packages/storycap/src/node/cli.ts @@ -36,6 +36,7 @@ function createOptions(): MainOptions { }) .option('silent', { boolean: true, default: false }) .option('verbose', { boolean: true, default: false }) + .option('forwardConsoleLogs', { boolean: true, default: false }) .option('serverCmd', { string: true, default: '', description: 'Command line to launch Storybook server.' }) .option('serverTimeout', { number: true, @@ -114,6 +115,7 @@ function createOptions(): MainOptions { parallel, silent, verbose, + forwardConsoleLogs, serverTimeout, serverCmd, shard, @@ -179,6 +181,7 @@ function createOptions(): MainOptions { stateChangeDelay, disableCssAnimation, disableWaitAssets, + forwardConsoleLogs, chromiumChannel: chromiumChannel as ChromeChannel, chromiumPath, launchOptions: puppeteerLaunchConfig, diff --git a/packages/storycap/src/node/main.ts b/packages/storycap/src/node/main.ts index 4429e82a7..5d195484b 100644 --- a/packages/storycap/src/node/main.ts +++ b/packages/storycap/src/node/main.ts @@ -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) { diff --git a/packages/storycap/src/node/screenshot-service.ts b/packages/storycap/src/node/screenshot-service.ts index dbc866e51..20d444e17 100644 --- a/packages/storycap/src/node/screenshot-service.ts +++ b/packages/storycap/src/node/screenshot-service.ts @@ -48,6 +48,7 @@ export type ScreenshotServiceOptions = { workers: CapturingBrowser[]; fileSystem: FileSystem; stories: Story[]; + forwardConsoleLogs: boolean; }; /** @@ -63,6 +64,7 @@ export function createScreenshotService({ logger, stories, workers, + forwardConsoleLogs, }: ScreenshotServiceOptions): ScreenshotService { const service = createExecutionService( workers, @@ -70,7 +72,9 @@ export function createScreenshotService({ ({ 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; diff --git a/packages/storycap/src/node/types.ts b/packages/storycap/src/node/types.ts index 647b04319..0a99ce609 100644 --- a/packages/storycap/src/node/types.ts +++ b/packages/storycap/src/node/types.ts @@ -40,6 +40,7 @@ export interface MainOptions extends BaseBrowserOptions { exclude: string[]; disableCssAnimation: boolean; disableWaitAssets: boolean; + forwardConsoleLogs: boolean; parallel: number; shard: ShardOptions; metricsWatchRetryCount: number; diff --git a/packages/storycap/src/shared/screenshot-options-helper.ts b/packages/storycap/src/shared/screenshot-options-helper.ts index 8996b0499..490e18a89 100644 --- a/packages/storycap/src/shared/screenshot-options-helper.ts +++ b/packages/storycap/src/shared/screenshot-options-helper.ts @@ -13,6 +13,7 @@ const defaultScreenshotOptions = { omitBackground: false, captureBeyondViewport: true, clip: null, + forwardConsoleLogs: false, } as const; /** From 42f5429fe5743bd183b909bb5ea750aee94245c7 Mon Sep 17 00:00:00 2001 From: Michael Orenstein Date: Mon, 24 Jul 2023 15:13:28 +0930 Subject: [PATCH 2/2] chore: Cli note for forwarding console logs --- README.md | 2 +- packages/storycap/src/node/cli.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4dd76062f..9afca2e93 100644 --- a/README.md +++ b/README.md @@ -363,7 +363,7 @@ Options: --disableWaitAssets Disable waiting for requested assets [boolean] [default: false] --silent [boolean] [default: false] --verbose [boolean] [default: false] - --forwardConsoleLogs [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 /. diff --git a/packages/storycap/src/node/cli.ts b/packages/storycap/src/node/cli.ts index 9983064b0..e5f042dd1 100644 --- a/packages/storycap/src/node/cli.ts +++ b/packages/storycap/src/node/cli.ts @@ -36,7 +36,11 @@ function createOptions(): MainOptions { }) .option('silent', { boolean: true, default: false }) .option('verbose', { boolean: true, default: false }) - .option('forwardConsoleLogs', { 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,