feat(screenshot): create directories for screenshot file

This commit is contained in:
Joel Einbinder 2020-08-17 16:53:19 -07:00 committed by GitHub
parent 0e9793c452
commit dfa1f103af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 49 additions and 8 deletions

View File

@ -20,6 +20,7 @@ import { Protocol } from './protocol';
import * as fs from 'fs'; import * as fs from 'fs';
import * as util from 'util'; import * as util from 'util';
import * as types from '../types'; import * as types from '../types';
import { mkdirIfNeeded } from '../helper';
export function getExceptionMessage(exceptionDetails: Protocol.Runtime.ExceptionDetails): string { export function getExceptionMessage(exceptionDetails: Protocol.Runtime.ExceptionDetails): string {
if (exceptionDetails.exception) 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<Buffer> { export async function readProtocolStream(client: CRSession, handle: string, path: string | null): Promise<Buffer> {
let eof = false; let eof = false;
let fd: number | undefined; let fd: number | undefined;
if (path) if (path) {
await mkdirIfNeeded(path);
fd = await util.promisify(fs.open)(path, 'w'); fd = await util.promisify(fs.open)(path, 'w');
}
const bufs = []; const bufs = [];
while (!eof) { while (!eof) {
const response = await client.send('IO.read', {handle}); const response = await client.send('IO.read', {handle});

View File

@ -20,7 +20,7 @@ import * as util from 'util';
import { Page } from './page'; import { Page } from './page';
import { Events } from './events'; import { Events } from './events';
import { Readable } from 'stream'; import { Readable } from 'stream';
import { assert } from './helper'; import { assert, mkdirIfNeeded } from './helper';
export class Download { export class Download {
private _downloadsPath: string; private _downloadsPath: string;
@ -92,8 +92,7 @@ export class Download {
async _saveAs(downloadPath: string) { async _saveAs(downloadPath: string) {
const fileName = path.join(this._downloadsPath, this._uuid); const fileName = path.join(this._downloadsPath, this._uuid);
// This will harmlessly throw on windows if the dirname is the root directory. await mkdirIfNeeded(downloadPath);
await util.promisify(fs.mkdir)(path.dirname(downloadPath), {recursive: true}).catch(() => {});
await util.promisify(fs.copyFile)(fileName, downloadPath); await util.promisify(fs.copyFile)(fileName, downloadPath);
} }

View File

@ -21,12 +21,14 @@ import * as fs from 'fs';
import * as os from 'os'; import * as os from 'os';
import * as removeFolder from 'rimraf'; import * as removeFolder from 'rimraf';
import * as util from 'util'; import * as util from 'util';
import * as path from 'path';
import * as types from './types'; import * as types from './types';
import { Progress } from './progress'; import { Progress } from './progress';
import * as debug from 'debug'; import * as debug from 'debug';
const removeFolderAsync = util.promisify(removeFolder); const removeFolderAsync = util.promisify(removeFolder);
const readFileAsync = util.promisify(fs.readFile.bind(fs)); const readFileAsync = util.promisify(fs.readFile.bind(fs));
const mkdirAsync = util.promisify(fs.mkdir.bind(fs));
export type RegisteredListener = { export type RegisteredListener = {
emitter: EventEmitter; emitter: EventEmitter;
@ -353,6 +355,12 @@ export function getFromENV(name: string) {
return value; 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(['/', '$', '^', '+', '.', '(', ')', '=', '!', '|']); const escapeGlobChars = new Set(['/', '$', '^', '+', '.', '(', ')', '=', '!', '|']);
export const helper = Helper; export const helper = Helper;

View File

@ -16,7 +16,7 @@
*/ */
import { Events } from './events'; import { Events } from './events';
import { assert, helper, Listener } from '../../helper'; import { assert, helper, Listener, mkdirIfNeeded } from '../../helper';
import { TimeoutSettings } from '../../timeoutSettings'; 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 { 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'; import { parseError, serializeError } from '../serializers';
@ -425,8 +425,10 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
async screenshot(options: PageScreenshotOptions & { path?: string } = {}): Promise<Buffer> { async screenshot(options: PageScreenshotOptions & { path?: string } = {}): Promise<Buffer> {
return this._wrapApiCall('page.screenshot', async () => { return this._wrapApiCall('page.screenshot', async () => {
const buffer = Buffer.from((await this._channel.screenshot(options)).binary, 'base64'); 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); await fsWriteFileAsync(options.path, buffer);
}
return buffer; return buffer;
}); });
} }

View File

@ -19,7 +19,7 @@ import * as fs from 'fs';
import * as mime from 'mime'; import * as mime from 'mime';
import * as util from 'util'; import * as util from 'util';
import * as dom from './dom'; import * as dom from './dom';
import { assert, helper } from './helper'; import { assert, helper, mkdirIfNeeded } from './helper';
import { Page } from './page'; import { Page } from './page';
import * as types from './types'; import * as types from './types';
import { rewriteErrorMessage } from './utils/stackTrace'; import { rewriteErrorMessage } from './utils/stackTrace';
@ -153,8 +153,10 @@ export class Screenshotter {
if (shouldSetDefaultBackground) if (shouldSetDefaultBackground)
await this._page._delegate.setBackgroundColor(); await this._page._delegate.setBackgroundColor();
progress.throwIfAborted(); // Avoid side effects. progress.throwIfAborted(); // Avoid side effects.
if (options.path) if (options.path) {
await mkdirIfNeeded(options.path);
await util.promisify(fs.writeFile)(options.path, buffer); await util.promisify(fs.writeFile)(options.path, buffer);
}
if ((options as any).__testHookAfterScreenshot) if ((options as any).__testHookAfterScreenshot)
await (options as any).__testHookAfterScreenshot(); await (options as any).__testHookAfterScreenshot();
return buffer; return buffer;

View File

@ -42,6 +42,14 @@ it.skip(!CHROMIUM)('should output a trace', async({browser, page, server, output
expect(fs.existsSync(outputFile)).toBe(true); 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}) => { 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).startTracing(page, {path: outputFile, categories: ['disabled-by-default-v8.cpu_profiler.hires']});
await (browser as ChromiumBrowser).stopTracing(); await (browser as ChromiumBrowser).stopTracing();

View File

@ -16,6 +16,9 @@
*/ */
import './base.fixture'; import './base.fixture';
import utils from './utils'; import utils from './utils';
import path from 'path';
import fs from 'fs';
const { HEADLESS } = testOptions; const { HEADLESS } = testOptions;
// Firefox headful produces a different image. // 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'); await page.goto(server.PREFIX + '/grid-iframe-in-shadow.html');
expect(await page.screenshot()).toMatchImage(golden('screenshot-iframe.png')); 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'));
});