feat(screencast): add private recording APIs and basic test (#3296)
This commit is contained in:
parent
8bb6d73b44
commit
d8d845afcc
|
|
@ -208,6 +208,14 @@ export class CRPage implements PageDelegate {
|
||||||
await this._mainFrameSession._client.send('Emulation.setDefaultBackgroundColorOverride', { color });
|
await this._mainFrameSession._client.send('Emulation.setDefaultBackgroundColorOverride', { color });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async startVideoRecording(options: types.VideoRecordingOptions): Promise<void> {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
async stopVideoRecording(): Promise<void> {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
async takeScreenshot(format: 'png' | 'jpeg', documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined): Promise<Buffer> {
|
async takeScreenshot(format: 'png' | 'jpeg', documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined): Promise<Buffer> {
|
||||||
const { visualViewport } = await this._mainFrameSession._client.send('Page.getLayoutMetrics');
|
const { visualViewport } = await this._mainFrameSession._client.send('Page.getLayoutMetrics');
|
||||||
if (!documentRect) {
|
if (!documentRect) {
|
||||||
|
|
|
||||||
|
|
@ -347,6 +347,19 @@ export class FFPage implements PageDelegate {
|
||||||
throw new Error('Not implemented');
|
throw new Error('Not implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async startVideoRecording(options: types.VideoRecordingOptions): Promise<void> {
|
||||||
|
this._session.send('Page.startVideoRecording', {
|
||||||
|
file: options.outputFile,
|
||||||
|
width: options.width,
|
||||||
|
height: options.height,
|
||||||
|
scale: options.scale,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async stopVideoRecording(): Promise<void> {
|
||||||
|
await this._session.send('Page.stopVideoRecording');
|
||||||
|
}
|
||||||
|
|
||||||
async takeScreenshot(format: 'png' | 'jpeg', documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined): Promise<Buffer> {
|
async takeScreenshot(format: 'png' | 'jpeg', documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined): Promise<Buffer> {
|
||||||
if (!documentRect) {
|
if (!documentRect) {
|
||||||
const context = await this._page.mainFrame()._utilityContext();
|
const context = await this._page.mainFrame()._utilityContext();
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,8 @@ export interface PageDelegate {
|
||||||
canScreenshotOutsideViewport(): boolean;
|
canScreenshotOutsideViewport(): boolean;
|
||||||
resetViewport(): Promise<void>; // Only called if canScreenshotOutsideViewport() returns false.
|
resetViewport(): Promise<void>; // Only called if canScreenshotOutsideViewport() returns false.
|
||||||
setBackgroundColor(color?: { r: number; g: number; b: number; a: number; }): Promise<void>;
|
setBackgroundColor(color?: { r: number; g: number; b: number; a: number; }): Promise<void>;
|
||||||
|
startVideoRecording(options: types.VideoRecordingOptions): Promise<void>;
|
||||||
|
stopVideoRecording(): Promise<void>;
|
||||||
takeScreenshot(format: string, documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined): Promise<Buffer>;
|
takeScreenshot(format: string, documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined): Promise<Buffer>;
|
||||||
|
|
||||||
isElementHandle(remoteObject: any): boolean;
|
isElementHandle(remoteObject: any): boolean;
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,13 @@ export type ScreenshotOptions = ElementScreenshotOptions & {
|
||||||
clip?: Rect,
|
clip?: Rect,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type VideoRecordingOptions = {
|
||||||
|
outputFile: string,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
scale?: number,
|
||||||
|
};
|
||||||
|
|
||||||
export type URLMatch = string | RegExp | ((url: URL) => boolean);
|
export type URLMatch = string | RegExp | ((url: URL) => boolean);
|
||||||
|
|
||||||
export type Credentials = {
|
export type Credentials = {
|
||||||
|
|
|
||||||
|
|
@ -704,6 +704,19 @@ export class WKPage implements PageDelegate {
|
||||||
await this._session.send('Page.setDefaultBackgroundColorOverride', { color });
|
await this._session.send('Page.setDefaultBackgroundColorOverride', { color });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async startVideoRecording(options: types.VideoRecordingOptions): Promise<void> {
|
||||||
|
this._pageProxySession.send('Screencast.startVideoRecording', {
|
||||||
|
file: options.outputFile,
|
||||||
|
width: options.width,
|
||||||
|
height: options.height,
|
||||||
|
scale: options.scale,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async stopVideoRecording(): Promise<void> {
|
||||||
|
await this._pageProxySession.send('Screencast.stopVideoRecording');
|
||||||
|
}
|
||||||
|
|
||||||
async takeScreenshot(format: string, documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined): Promise<Buffer> {
|
async takeScreenshot(format: string, documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined): Promise<Buffer> {
|
||||||
const rect = (documentRect || viewportRect)!;
|
const rect = (documentRect || viewportRect)!;
|
||||||
const result = await this._session.send('Page.snapshotRect', { ...rect, coordinateSystem: documentRect ? 'Page' : 'Viewport' });
|
const result = await this._session.send('Page.snapshotRect', { ...rect, coordinateSystem: documentRect ? 'Page' : 'Viewport' });
|
||||||
|
|
|
||||||
117
test/screencast.spec.js
Normal file
117
test/screencast.spec.js
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
/**
|
||||||
|
* Copyright Microsoft Corporation. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const os = require('os');
|
||||||
|
const path = require('path');
|
||||||
|
const url = require('url');
|
||||||
|
const {mkdtempAsync, removeFolderAsync} = require('./utils');
|
||||||
|
|
||||||
|
const {FFOX, CHROMIUM, WEBKIT, MAC, LINUX, WIN, HEADLESS, USES_HOOKS} = testOptions;
|
||||||
|
|
||||||
|
registerFixture('persistentDirectory', async ({}, test) => {
|
||||||
|
const persistentDirectory = await mkdtempAsync(path.join(os.tmpdir(), 'playwright-test-'));
|
||||||
|
try {
|
||||||
|
await test(persistentDirectory);
|
||||||
|
} finally {
|
||||||
|
await removeFolderAsync(persistentDirectory);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerFixture('firefox', async ({playwright}, test) => {
|
||||||
|
if (WEBKIT && !LINUX) {
|
||||||
|
const firefox = await playwright.firefox.launch();
|
||||||
|
try {
|
||||||
|
await test(firefox);
|
||||||
|
} finally {
|
||||||
|
await firefox.close();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await test(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it.fail(CHROMIUM)('should capture static page', async({page, persistentDirectory, firefox, toImpl}) => {
|
||||||
|
if (!toImpl)
|
||||||
|
return;
|
||||||
|
const videoFile = path.join(persistentDirectory, 'v.webm');
|
||||||
|
await page.evaluate(() => document.body.style.backgroundColor = 'red');
|
||||||
|
await toImpl(page)._delegate.startVideoRecording({outputFile: videoFile, width: 640, height: 480});
|
||||||
|
// TODO: in WebKit figure out why video size is not reported correctly for
|
||||||
|
// static pictures.
|
||||||
|
if (HEADLESS && WEBKIT)
|
||||||
|
await page.setViewportSize({width: 1270, height: 950});
|
||||||
|
await new Promise(r => setTimeout(r, 300));
|
||||||
|
await toImpl(page)._delegate.stopVideoRecording();
|
||||||
|
expect(fs.existsSync(videoFile)).toBe(true);
|
||||||
|
|
||||||
|
if (WEBKIT && !LINUX) {
|
||||||
|
// WebKit on Mac & Windows cannot replay webm/vp8 video, so we launch Firefox.
|
||||||
|
const context = await firefox.newContext();
|
||||||
|
page = await context.newPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.goto(url.pathToFileURL(videoFile).href);
|
||||||
|
await page.$eval('video', v => {
|
||||||
|
return new Promise(fulfil => {
|
||||||
|
// In case video playback autostarts.
|
||||||
|
v.pause();
|
||||||
|
v.onplaying = fulfil;
|
||||||
|
v.play();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await page.$eval('video', v => {
|
||||||
|
v.pause();
|
||||||
|
const result = new Promise(f => v.onseeked = f);
|
||||||
|
v.currentTime = v.duration - 0.01;
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
const duration = await page.$eval('video', v => v.duration);
|
||||||
|
expect(duration).toBeGreaterThan(0);
|
||||||
|
const videoWidth = await page.$eval('video', v => v.videoWidth);
|
||||||
|
expect(videoWidth).toBe(640);
|
||||||
|
const videoHeight = await page.$eval('video', v => v.videoHeight);
|
||||||
|
expect(videoHeight).toBe(480);
|
||||||
|
|
||||||
|
const pixels = await page.$eval('video', video => {
|
||||||
|
let canvas = document.createElement("canvas");
|
||||||
|
canvas.width = video.videoWidth;
|
||||||
|
canvas.height = video.videoHeight;
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
context.drawImage(video, 0, 0);
|
||||||
|
const imgd = context.getImageData(0, 0, 10, 10);
|
||||||
|
return Array.from(imgd.data);
|
||||||
|
});
|
||||||
|
const expectAlmostRed = (i) => {
|
||||||
|
const r = pixels[i];
|
||||||
|
const g = pixels[i + 1];
|
||||||
|
const b = pixels[i + 2];
|
||||||
|
const alpha = pixels[i + 3];
|
||||||
|
expect(r).toBeGreaterThan(240);
|
||||||
|
expect(g).toBeLessThan(50);
|
||||||
|
expect(b).toBeLessThan(50);
|
||||||
|
expect(alpha).toBe(255);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
for (var i = 0, n = pixels.length; i < n; i += 4)
|
||||||
|
expectAlmostRed(i);
|
||||||
|
} catch(e) {
|
||||||
|
// Log pixel values on failure.
|
||||||
|
console.log(pixels);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue