mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: remove screenshot path from the server side (#3519)
Also fixes auto-detection of mime type based on path and adds tests.
This commit is contained in:
parent
20c6b85178
commit
e7e8524e14
@ -15,8 +15,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as util from 'util';
|
|
||||||
import { ConsoleMessage } from './console';
|
import { ConsoleMessage } from './console';
|
||||||
import * as dom from './dom';
|
import * as dom from './dom';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
@ -640,31 +638,23 @@ export class Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async addScriptTag(options: {
|
async addScriptTag(options: {
|
||||||
url?: string; path?: string;
|
url?: string,
|
||||||
content?: string;
|
content?: string,
|
||||||
type?: string;
|
type?: string,
|
||||||
}): Promise<dom.ElementHandle> {
|
}): Promise<dom.ElementHandle> {
|
||||||
const {
|
const {
|
||||||
url = null,
|
url = null,
|
||||||
path = null,
|
|
||||||
content = null,
|
content = null,
|
||||||
type = ''
|
type = ''
|
||||||
} = options;
|
} = options;
|
||||||
if (!url && !path && !content)
|
if (!url && !content)
|
||||||
throw new Error('Provide an object with a `url`, `path` or `content` property');
|
throw new Error('Provide an object with a `url`, `path` or `content` property');
|
||||||
|
|
||||||
const context = await this._mainContext();
|
const context = await this._mainContext();
|
||||||
return this._raceWithCSPError(async () => {
|
return this._raceWithCSPError(async () => {
|
||||||
if (url !== null)
|
if (url !== null)
|
||||||
return (await context.evaluateHandleInternal(addScriptUrl, { url, type })).asElement()!;
|
return (await context.evaluateHandleInternal(addScriptUrl, { url, type })).asElement()!;
|
||||||
let result;
|
const result = (await context.evaluateHandleInternal(addScriptContent, { content: content!, type })).asElement()!;
|
||||||
if (path !== null) {
|
|
||||||
let contents = await util.promisify(fs.readFile)(path, 'utf8');
|
|
||||||
contents += '\n//# sourceURL=' + path.replace(/\n/g, '');
|
|
||||||
result = (await context.evaluateHandleInternal(addScriptContent, { content: contents, type })).asElement()!;
|
|
||||||
} else {
|
|
||||||
result = (await context.evaluateHandleInternal(addScriptContent, { content: content!, type })).asElement()!;
|
|
||||||
}
|
|
||||||
// Another round trip to the browser to ensure that we receive CSP error messages
|
// Another round trip to the browser to ensure that we receive CSP error messages
|
||||||
// (if any) logged asynchronously in a separate task on the content main thread.
|
// (if any) logged asynchronously in a separate task on the content main thread.
|
||||||
if (this._page._delegate.cspErrorsAsynchronousForInlineScipts)
|
if (this._page._delegate.cspErrorsAsynchronousForInlineScipts)
|
||||||
@ -699,26 +689,18 @@ export class Frame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async addStyleTag(options: { url?: string; path?: string; content?: string; }): Promise<dom.ElementHandle> {
|
async addStyleTag(options: { url?: string, content?: string }): Promise<dom.ElementHandle> {
|
||||||
const {
|
const {
|
||||||
url = null,
|
url = null,
|
||||||
path = null,
|
|
||||||
content = null
|
content = null
|
||||||
} = options;
|
} = options;
|
||||||
if (!url && !path && !content)
|
if (!url && !content)
|
||||||
throw new Error('Provide an object with a `url`, `path` or `content` property');
|
throw new Error('Provide an object with a `url`, `path` or `content` property');
|
||||||
|
|
||||||
const context = await this._mainContext();
|
const context = await this._mainContext();
|
||||||
return this._raceWithCSPError(async () => {
|
return this._raceWithCSPError(async () => {
|
||||||
if (url !== null)
|
if (url !== null)
|
||||||
return (await context.evaluateHandleInternal(addStyleUrl, url)).asElement()!;
|
return (await context.evaluateHandleInternal(addStyleUrl, url)).asElement()!;
|
||||||
|
|
||||||
if (path !== null) {
|
|
||||||
let contents = await util.promisify(fs.readFile)(path, 'utf8');
|
|
||||||
contents += '\n/*# sourceURL=' + path.replace(/\n/g, '') + '*/';
|
|
||||||
return (await context.evaluateHandleInternal(addStyleContent, contents)).asElement()!;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (await context.evaluateHandleInternal(addStyleContent, content!)).asElement()!;
|
return (await context.evaluateHandleInternal(addStyleContent, content!)).asElement()!;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1822,14 +1822,12 @@ export type ElementHandleQuerySelectorAllResult = {
|
|||||||
export type ElementHandleScreenshotParams = {
|
export type ElementHandleScreenshotParams = {
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
type?: 'png' | 'jpeg',
|
type?: 'png' | 'jpeg',
|
||||||
path?: string,
|
|
||||||
quality?: number,
|
quality?: number,
|
||||||
omitBackground?: boolean,
|
omitBackground?: boolean,
|
||||||
};
|
};
|
||||||
export type ElementHandleScreenshotOptions = {
|
export type ElementHandleScreenshotOptions = {
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
type?: 'png' | 'jpeg',
|
type?: 'png' | 'jpeg',
|
||||||
path?: string,
|
|
||||||
quality?: number,
|
quality?: number,
|
||||||
omitBackground?: boolean,
|
omitBackground?: boolean,
|
||||||
};
|
};
|
||||||
|
@ -18,13 +18,15 @@ import { ElementHandleChannel, JSHandleInitializer, ElementHandleScrollIntoViewI
|
|||||||
import { Frame } from './frame';
|
import { Frame } from './frame';
|
||||||
import { FuncOn, JSHandle, serializeArgument, parseResult } from './jsHandle';
|
import { FuncOn, JSHandle, serializeArgument, parseResult } from './jsHandle';
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { helper, assert } from '../../helper';
|
import { helper, assert, mkdirIfNeeded } from '../../helper';
|
||||||
import { SelectOption, FilePayload, Rect, SelectOptionOptions } from './types';
|
import { SelectOption, FilePayload, Rect, SelectOptionOptions } from './types';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as mime from 'mime';
|
import * as mime from 'mime';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as util from 'util';
|
import * as util from 'util';
|
||||||
|
|
||||||
|
const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs));
|
||||||
|
|
||||||
export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
|
export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
|
||||||
readonly _elementChannel: ElementHandleChannel;
|
readonly _elementChannel: ElementHandleChannel;
|
||||||
|
|
||||||
@ -175,9 +177,16 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async screenshot(options: ElementHandleScreenshotOptions = {}): Promise<Buffer> {
|
async screenshot(options: ElementHandleScreenshotOptions & { path?: string } = {}): Promise<Buffer> {
|
||||||
return this._wrapApiCall('elementHandle.screenshot', async () => {
|
return this._wrapApiCall('elementHandle.screenshot', async () => {
|
||||||
return Buffer.from((await this._elementChannel.screenshot(options)).binary, 'base64');
|
const type = determineScreenshotType(options);
|
||||||
|
const result = await this._elementChannel.screenshot({ ...options, type });
|
||||||
|
const buffer = Buffer.from(result.binary, 'base64');
|
||||||
|
if (options.path) {
|
||||||
|
await mkdirIfNeeded(options.path);
|
||||||
|
await fsWriteFileAsync(options.path, buffer);
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,3 +271,15 @@ export async function convertInputFiles(files: string | FilePayload | string[] |
|
|||||||
}));
|
}));
|
||||||
return filePayloads;
|
return filePayloads;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function determineScreenshotType(options: { path?: string, type?: 'png' | 'jpeg' }): 'png' | 'jpeg' | undefined {
|
||||||
|
if (options.path) {
|
||||||
|
const mimeType = mime.getType(options.path);
|
||||||
|
if (mimeType === 'image/png')
|
||||||
|
return 'png';
|
||||||
|
else if (mimeType === 'image/jpeg')
|
||||||
|
return 'jpeg';
|
||||||
|
throw new Error(`path: unsupported mime type "${mimeType}"`);
|
||||||
|
}
|
||||||
|
return options.type;
|
||||||
|
}
|
||||||
|
@ -27,7 +27,7 @@ import { ChannelOwner } from './channelOwner';
|
|||||||
import { ConsoleMessage } from './consoleMessage';
|
import { ConsoleMessage } from './consoleMessage';
|
||||||
import { Dialog } from './dialog';
|
import { Dialog } from './dialog';
|
||||||
import { Download } from './download';
|
import { Download } from './download';
|
||||||
import { ElementHandle } from './elementHandle';
|
import { ElementHandle, determineScreenshotType } from './elementHandle';
|
||||||
import { Worker } from './worker';
|
import { Worker } from './worker';
|
||||||
import { Frame, FunctionWithSource, verifyLoadState, WaitForNavigationOptions } from './frame';
|
import { Frame, FunctionWithSource, verifyLoadState, WaitForNavigationOptions } from './frame';
|
||||||
import { Keyboard, Mouse } from './input';
|
import { Keyboard, Mouse } from './input';
|
||||||
@ -425,7 +425,9 @@ 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 type = determineScreenshotType(options);
|
||||||
|
const result = await this._channel.screenshot({ ...options, type });
|
||||||
|
const buffer = Buffer.from(result.binary, 'base64');
|
||||||
if (options.path) {
|
if (options.path) {
|
||||||
await mkdirIfNeeded(options.path);
|
await mkdirIfNeeded(options.path);
|
||||||
await fsWriteFileAsync(options.path, buffer);
|
await fsWriteFileAsync(options.path, buffer);
|
||||||
|
@ -1513,7 +1513,6 @@ ElementHandle:
|
|||||||
literals:
|
literals:
|
||||||
- png
|
- png
|
||||||
- jpeg
|
- jpeg
|
||||||
path: string?
|
|
||||||
quality: number?
|
quality: number?
|
||||||
omitBackground: boolean?
|
omitBackground: boolean?
|
||||||
returns:
|
returns:
|
||||||
|
@ -718,7 +718,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
|||||||
scheme.ElementHandleScreenshotParams = tObject({
|
scheme.ElementHandleScreenshotParams = tObject({
|
||||||
timeout: tOptional(tNumber),
|
timeout: tOptional(tNumber),
|
||||||
type: tOptional(tEnum(['png', 'jpeg'])),
|
type: tOptional(tEnum(['png', 'jpeg'])),
|
||||||
path: tOptional(tString),
|
|
||||||
quality: tOptional(tNumber),
|
quality: tOptional(tNumber),
|
||||||
omitBackground: tOptional(tBoolean),
|
omitBackground: tOptional(tBoolean),
|
||||||
});
|
});
|
||||||
|
@ -15,11 +15,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as mime from 'mime';
|
|
||||||
import * as util from 'util';
|
|
||||||
import * as dom from './dom';
|
import * as dom from './dom';
|
||||||
import { assert, helper, mkdirIfNeeded } from './helper';
|
import { assert, helper } 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,10 +150,6 @@ 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) {
|
|
||||||
await mkdirIfNeeded(options.path);
|
|
||||||
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;
|
||||||
@ -206,13 +199,6 @@ function validateScreenshotOptions(options: types.ScreenshotOptions): 'png' | 'j
|
|||||||
if (options.type) {
|
if (options.type) {
|
||||||
assert(options.type === 'png' || options.type === 'jpeg', 'Unknown options.type value: ' + options.type);
|
assert(options.type === 'png' || options.type === 'jpeg', 'Unknown options.type value: ' + options.type);
|
||||||
format = options.type;
|
format = options.type;
|
||||||
} else if (options.path) {
|
|
||||||
const mimeType = mime.getType(options.path);
|
|
||||||
if (mimeType === 'image/png')
|
|
||||||
format = 'png';
|
|
||||||
else if (mimeType === 'image/jpeg')
|
|
||||||
format = 'jpeg';
|
|
||||||
assert(format, 'Unsupported screenshot mime type: ' + mimeType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!format)
|
if (!format)
|
||||||
|
@ -51,7 +51,6 @@ export type WaitForNavigationOptions = TimeoutOptions & {
|
|||||||
|
|
||||||
export type ElementScreenshotOptions = TimeoutOptions & {
|
export type ElementScreenshotOptions = TimeoutOptions & {
|
||||||
type?: 'png' | 'jpeg',
|
type?: 'png' | 'jpeg',
|
||||||
path?: string,
|
|
||||||
quality?: number,
|
quality?: number,
|
||||||
omitBackground?: boolean,
|
omitBackground?: boolean,
|
||||||
};
|
};
|
||||||
|
@ -19,6 +19,8 @@ import './base.fixture';
|
|||||||
import utils from './utils';
|
import utils from './utils';
|
||||||
const {WIRE, HEADLESS} = testOptions;
|
const {WIRE, HEADLESS} = testOptions;
|
||||||
import {PNG} from 'pngjs';
|
import {PNG} from 'pngjs';
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
// Firefox headful produces a different image.
|
// Firefox headful produces a different image.
|
||||||
const ffheadful = FFOX && !HEADLESS;
|
const ffheadful = FFOX && !HEADLESS;
|
||||||
@ -369,3 +371,13 @@ it.skip(ffheadful)('should take screenshot of disabled button', async({page}) =>
|
|||||||
const screenshot = await button.screenshot();
|
const screenshot = await button.screenshot();
|
||||||
expect(screenshot).toBeInstanceOf(Buffer);
|
expect(screenshot).toBeInstanceOf(Buffer);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.skip(ffheadful)('path option should create subdirectories', async({page, server, golden, tmpDir}) => {
|
||||||
|
await page.setViewportSize({width: 500, height: 500});
|
||||||
|
await page.goto(server.PREFIX + '/grid.html');
|
||||||
|
await page.evaluate(() => window.scrollBy(50, 100));
|
||||||
|
const elementHandle = await page.$('.box:nth-of-type(3)');
|
||||||
|
const outputPath = path.join(tmpDir, 'these', 'are', 'directories', 'screenshot.png');
|
||||||
|
await elementHandle.screenshot({path: outputPath});
|
||||||
|
expect(await fs.promises.readFile(outputPath)).toMatchImage(golden('screenshot-element-bounding-box.png'));
|
||||||
|
});
|
||||||
|
@ -265,3 +265,17 @@ it.skip(ffheadful)('path option should create subdirectories', async({page, serv
|
|||||||
await page.screenshot({path: outputPath});
|
await page.screenshot({path: outputPath});
|
||||||
expect(await fs.promises.readFile(outputPath)).toMatchImage(golden('screenshot-sanity.png'));
|
expect(await fs.promises.readFile(outputPath)).toMatchImage(golden('screenshot-sanity.png'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.skip(ffheadful)('path option should detect jpeg', async({page, server, golden, tmpDir}) => {
|
||||||
|
await page.setViewportSize({ width: 100, height: 100 });
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
const outputPath = path.join(tmpDir, 'screenshot.jpg');
|
||||||
|
const screenshot = await page.screenshot({omitBackground: true, path: outputPath});
|
||||||
|
expect(await fs.promises.readFile(outputPath)).toMatchImage(golden('white.jpg'));
|
||||||
|
expect(screenshot).toMatchImage(golden('white.jpg'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip(ffheadful)('path option should throw for unsupported mime type', async({page, server, golden, tmpDir}) => {
|
||||||
|
const error = await page.screenshot({ path: 'file.txt' }).catch(e => e);
|
||||||
|
expect(error.message).toContain('path: unsupported mime type "text/plain"');
|
||||||
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user