From dfa1f103af46c579388bf64dca267d5024f3d703 Mon Sep 17 00:00:00 2001 From: Joel Einbinder Date: Mon, 17 Aug 2020 16:53:19 -0700 Subject: [PATCH] feat(screenshot): create directories for screenshot file --- src/chromium/crProtocolHelper.ts | 5 ++++- src/download.ts | 5 ++--- src/helper.ts | 8 ++++++++ src/rpc/client/page.ts | 6 ++++-- src/screenshotter.ts | 6 ++++-- test/chromium/tracing.spec.ts | 8 ++++++++ test/page-screenshot.spec.ts | 19 +++++++++++++++++++ 7 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/chromium/crProtocolHelper.ts b/src/chromium/crProtocolHelper.ts index 5bc95c5fe5..d433ff5108 100644 --- a/src/chromium/crProtocolHelper.ts +++ b/src/chromium/crProtocolHelper.ts @@ -20,6 +20,7 @@ import { Protocol } from './protocol'; import * as fs from 'fs'; import * as util from 'util'; import * as types from '../types'; +import { mkdirIfNeeded } from '../helper'; export function getExceptionMessage(exceptionDetails: Protocol.Runtime.ExceptionDetails): string { if (exceptionDetails.exception) @@ -42,8 +43,10 @@ export async function releaseObject(client: CRSession, objectId: string) { export async function readProtocolStream(client: CRSession, handle: string, path: string | null): Promise { let eof = false; let fd: number | undefined; - if (path) + if (path) { + await mkdirIfNeeded(path); fd = await util.promisify(fs.open)(path, 'w'); + } const bufs = []; while (!eof) { const response = await client.send('IO.read', {handle}); diff --git a/src/download.ts b/src/download.ts index cc1c450ff8..065cf0906d 100644 --- a/src/download.ts +++ b/src/download.ts @@ -20,7 +20,7 @@ import * as util from 'util'; import { Page } from './page'; import { Events } from './events'; import { Readable } from 'stream'; -import { assert } from './helper'; +import { assert, mkdirIfNeeded } from './helper'; export class Download { private _downloadsPath: string; @@ -92,8 +92,7 @@ export class Download { async _saveAs(downloadPath: string) { const fileName = path.join(this._downloadsPath, this._uuid); - // This will harmlessly throw on windows if the dirname is the root directory. - await util.promisify(fs.mkdir)(path.dirname(downloadPath), {recursive: true}).catch(() => {}); + await mkdirIfNeeded(downloadPath); await util.promisify(fs.copyFile)(fileName, downloadPath); } diff --git a/src/helper.ts b/src/helper.ts index c624d5bc06..a9fda3292b 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -21,12 +21,14 @@ import * as fs from 'fs'; import * as os from 'os'; import * as removeFolder from 'rimraf'; import * as util from 'util'; +import * as path from 'path'; import * as types from './types'; import { Progress } from './progress'; import * as debug from 'debug'; const removeFolderAsync = util.promisify(removeFolder); const readFileAsync = util.promisify(fs.readFile.bind(fs)); +const mkdirAsync = util.promisify(fs.mkdir.bind(fs)); export type RegisteredListener = { emitter: EventEmitter; @@ -353,6 +355,12 @@ export function getFromENV(name: string) { return value; } + +export async function mkdirIfNeeded(filePath: string) { + // This will harmlessly throw on windows if the dirname is the root directory. + await mkdirAsync(path.dirname(filePath), {recursive: true}).catch(() => {}); +} + const escapeGlobChars = new Set(['/', '$', '^', '+', '.', '(', ')', '=', '!', '|']); export const helper = Helper; diff --git a/src/rpc/client/page.ts b/src/rpc/client/page.ts index 91ae4618b8..90f8177c53 100644 --- a/src/rpc/client/page.ts +++ b/src/rpc/client/page.ts @@ -16,7 +16,7 @@ */ import { Events } from './events'; -import { assert, helper, Listener } from '../../helper'; +import { assert, helper, Listener, mkdirIfNeeded } from '../../helper'; import { TimeoutSettings } from '../../timeoutSettings'; import { BindingCallChannel, BindingCallInitializer, PageChannel, PageInitializer, PagePdfParams, FrameWaitForSelectorOptions, FrameDispatchEventOptions, FrameSetContentOptions, FrameGotoOptions, PageReloadOptions, PageGoBackOptions, PageGoForwardOptions, PageScreenshotOptions, FrameClickOptions, FrameDblclickOptions, FrameFillOptions, FrameFocusOptions, FrameTextContentOptions, FrameInnerTextOptions, FrameInnerHTMLOptions, FrameGetAttributeOptions, FrameHoverOptions, FrameSetInputFilesOptions, FrameTypeOptions, FramePressOptions, FrameCheckOptions, FrameUncheckOptions } from '../channels'; import { parseError, serializeError } from '../serializers'; @@ -425,8 +425,10 @@ export class Page extends ChannelOwner { async screenshot(options: PageScreenshotOptions & { path?: string } = {}): Promise { return this._wrapApiCall('page.screenshot', async () => { const buffer = Buffer.from((await this._channel.screenshot(options)).binary, 'base64'); - if (options.path) + if (options.path) { + await mkdirIfNeeded(options.path); await fsWriteFileAsync(options.path, buffer); + } return buffer; }); } diff --git a/src/screenshotter.ts b/src/screenshotter.ts index 367072de1f..cf185b83dc 100644 --- a/src/screenshotter.ts +++ b/src/screenshotter.ts @@ -19,7 +19,7 @@ import * as fs from 'fs'; import * as mime from 'mime'; import * as util from 'util'; import * as dom from './dom'; -import { assert, helper } from './helper'; +import { assert, helper, mkdirIfNeeded } from './helper'; import { Page } from './page'; import * as types from './types'; import { rewriteErrorMessage } from './utils/stackTrace'; @@ -153,8 +153,10 @@ export class Screenshotter { if (shouldSetDefaultBackground) await this._page._delegate.setBackgroundColor(); progress.throwIfAborted(); // Avoid side effects. - if (options.path) + if (options.path) { + await mkdirIfNeeded(options.path); await util.promisify(fs.writeFile)(options.path, buffer); + } if ((options as any).__testHookAfterScreenshot) await (options as any).__testHookAfterScreenshot(); return buffer; diff --git a/test/chromium/tracing.spec.ts b/test/chromium/tracing.spec.ts index 2b8940521f..961fce8330 100644 --- a/test/chromium/tracing.spec.ts +++ b/test/chromium/tracing.spec.ts @@ -42,6 +42,14 @@ it.skip(!CHROMIUM)('should output a trace', async({browser, page, server, output expect(fs.existsSync(outputFile)).toBe(true); }); +it.skip(!CHROMIUM)('should create directories as needed', async({browser, page, server, tmpDir}) => { + const filePath = path.join(tmpDir, 'these', 'are', 'directories'); + await (browser as ChromiumBrowser).startTracing(page, {screenshots: true, path: filePath}); + await page.goto(server.PREFIX + '/grid.html'); + await (browser as ChromiumBrowser).stopTracing(); + expect(fs.existsSync(filePath)).toBe(true); +}); + it.skip(!CHROMIUM)('should run with custom categories if provided', async({browser, page, outputFile}) => { await (browser as ChromiumBrowser).startTracing(page, {path: outputFile, categories: ['disabled-by-default-v8.cpu_profiler.hires']}); await (browser as ChromiumBrowser).stopTracing(); diff --git a/test/page-screenshot.spec.ts b/test/page-screenshot.spec.ts index 9660d57cc4..0d0c1845d6 100644 --- a/test/page-screenshot.spec.ts +++ b/test/page-screenshot.spec.ts @@ -16,6 +16,9 @@ */ import './base.fixture'; import utils from './utils'; +import path from 'path'; +import fs from 'fs'; + const { HEADLESS } = testOptions; // Firefox headful produces a different image. @@ -246,3 +249,19 @@ it.skip(ffheadful)('should work with iframe in shadow', async({page, server, gol await page.goto(server.PREFIX + '/grid-iframe-in-shadow.html'); expect(await page.screenshot()).toMatchImage(golden('screenshot-iframe.png')); }); + +it('path option should work', async({page, server, golden, tmpDir}) => { + await page.setViewportSize({width: 500, height: 500}); + await page.goto(server.PREFIX + '/grid.html'); + const outputPath = path.join(tmpDir, 'screenshot.png'); + await page.screenshot({path: outputPath}); + expect(await fs.promises.readFile(outputPath)).toMatchImage(golden('screenshot-sanity.png')); +}); + +it('path option should create subdirectories', async({page, server, golden, tmpDir}) => { + await page.setViewportSize({width: 500, height: 500}); + await page.goto(server.PREFIX + '/grid.html'); + const outputPath = path.join(tmpDir, 'these', 'are', 'directories', 'screenshot.png'); + await page.screenshot({path: outputPath}); + expect(await fs.promises.readFile(outputPath)).toMatchImage(golden('screenshot-sanity.png')); +});