feat(screencast): scale fixes (#6475)

This commit is contained in:
Pavel Feldman 2021-05-11 13:21:01 -07:00 committed by GitHub
parent 2ea465bc82
commit d08c50d277
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 111 additions and 22 deletions

View file

@ -8,17 +8,17 @@
}, },
{ {
"name": "firefox", "name": "firefox",
"revision": "1257", "revision": "1258",
"installByDefault": true "installByDefault": true
}, },
{ {
"name": "firefox-stable", "name": "firefox-stable",
"revision": "1247", "revision": "1248",
"installByDefault": false "installByDefault": false
}, },
{ {
"name": "webkit", "name": "webkit",
"revision": "1472", "revision": "1476",
"installByDefault": true, "installByDefault": true,
"revisionOverrides": { "revisionOverrides": {
"mac10.14": "1443" "mac10.14": "1443"

View file

@ -289,13 +289,13 @@ export class CRPage implements PageDelegate {
return this._sessionForHandle(handle)._scrollRectIntoViewIfNeeded(handle, rect); return this._sessionForHandle(handle)._scrollRectIntoViewIfNeeded(handle, rect);
} }
async setScreencastEnabled(enabled: boolean): Promise<void> { async setScreencastOptions(options: { width: number, height: number, quality: number } | null): Promise<void> {
if (enabled) { if (options) {
await this._mainFrameSession._startScreencast(this, { await this._mainFrameSession._startScreencast(this, {
format: 'jpeg', format: 'jpeg',
quality: 90, quality: options.quality,
maxWidth: 800, maxWidth: options.width,
maxHeight: 600, maxHeight: options.height
}); });
} else { } else {
await this._mainFrameSession._stopScreencast(this); await this._mainFrameSession._stopScreencast(this);

View file

@ -479,9 +479,9 @@ export class FFPage implements PageDelegate {
}); });
} }
async setScreencastEnabled(enabled: boolean): Promise<void> { async setScreencastOptions(options: { width: number, height: number, quality: number } | null): Promise<void> {
if (enabled) { if (options) {
const { screencastId } = await this._session.send('Page.startScreencast', { width: 800, height: 600, quality: 70 }); const { screencastId } = await this._session.send('Page.startScreencast', options);
this._screencastId = screencastId; this._screencastId = screencastId;
} else { } else {
await this._session.send('Page.stopScreencast'); await this._session.send('Page.stopScreencast');

View file

@ -70,7 +70,7 @@ export interface PageDelegate {
getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null>; getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null>;
getFrameElement(frame: frames.Frame): Promise<dom.ElementHandle>; getFrameElement(frame: frames.Frame): Promise<dom.ElementHandle>;
scrollRectIntoViewIfNeeded(handle: dom.ElementHandle, rect?: types.Rect): Promise<'error:notvisible' | 'error:notconnected' | 'done'>; scrollRectIntoViewIfNeeded(handle: dom.ElementHandle, rect?: types.Rect): Promise<'error:notvisible' | 'error:notconnected' | 'done'>;
setScreencastEnabled(enabled: boolean): Promise<void>; setScreencastOptions(options: { width: number, height: number, quality: number } | null): Promise<void>;
getAccessibilityTree(needle?: dom.ElementHandle): Promise<{tree: accessibility.AXNode, needle: accessibility.AXNode | null}>; getAccessibilityTree(needle?: dom.ElementHandle): Promise<{tree: accessibility.AXNode, needle: accessibility.AXNode | null}>;
pdf?: (options?: types.PDFOptions) => Promise<Buffer>; pdf?: (options?: types.PDFOptions) => Promise<Buffer>;
@ -501,8 +501,8 @@ export class Page extends SdkObject {
return this._pageBindings.get(identifier) || this._browserContext._pageBindings.get(identifier); return this._pageBindings.get(identifier) || this._browserContext._pageBindings.get(identifier);
} }
setScreencastEnabled(enabled: boolean) { setScreencastOptions(options: { width: number, height: number, quality: number } | null) {
this._delegate.setScreencastEnabled(enabled).catch(e => debugLogger.log('error', e)); this._delegate.setScreencastOptions(options).catch(e => debugLogger.log('error', e));
} }
} }

View file

@ -102,7 +102,7 @@ export class Tracing implements InstrumentationListener {
for (const { sdkObject, metadata } of this._pendingCalls.values()) for (const { sdkObject, metadata } of this._pendingCalls.values())
await this.onAfterCall(sdkObject, metadata); await this.onAfterCall(sdkObject, metadata);
for (const page of this._context.pages()) for (const page of this._context.pages())
page.setScreencastEnabled(false); page.setScreencastOptions(null);
// Ensure all writes are finished. // Ensure all writes are finished.
await this._appendEventChain; await this._appendEventChain;
@ -185,7 +185,7 @@ export class Tracing implements InstrumentationListener {
}; };
this._appendTraceEvent(event); this._appendTraceEvent(event);
if (screenshots) if (screenshots)
page.setScreencastEnabled(true); page.setScreencastOptions({ width: 800, height: 600, quality: 90 });
this._eventListeners.push( this._eventListeners.push(
helper.addEventListener(page, Page.Events.Dialog, (dialog: Dialog) => { helper.addEventListener(page, Page.Events.Dialog, (dialog: Dialog) => {

View file

@ -8050,6 +8050,7 @@ the top of the viewport and Y increases as it proceeds towards the bottom of the
file: string; file: string;
width: number; width: number;
height: number; height: number;
toolbarHeight: number;
scale?: number; scale?: number;
} }
export type startVideoReturnValue = { export type startVideoReturnValue = {
@ -8071,6 +8072,7 @@ the top of the viewport and Y increases as it proceeds towards the bottom of the
export type startScreencastParameters = { export type startScreencastParameters = {
width: number; width: number;
height: number; height: number;
toolbarHeight: number;
quality: number; quality: number;
} }
export type startScreencastReturnValue = { export type startScreencastReturnValue = {

View file

@ -739,6 +739,12 @@ export class WKPage implements PageDelegate {
await this._session.send('Page.setDefaultBackgroundColorOverride', { color }); await this._session.send('Page.setDefaultBackgroundColorOverride', { color });
} }
private _toolbarHeight(): number {
if (this._page._browserContext._browser?.options.headful)
return hostPlatform.startsWith('10.15') ? 55 : 59;
return 0;
}
private async _startVideo(options: types.PageScreencastOptions): Promise<void> { private async _startVideo(options: types.PageScreencastOptions): Promise<void> {
assert(!this._recordingVideoFile); assert(!this._recordingVideoFile);
const START_VIDEO_PROTOCOL_COMMAND = hostPlatform === 'mac10.14' ? 'Screencast.start' : 'Screencast.startVideo'; const START_VIDEO_PROTOCOL_COMMAND = hostPlatform === 'mac10.14' ? 'Screencast.start' : 'Screencast.startVideo';
@ -746,6 +752,7 @@ export class WKPage implements PageDelegate {
file: options.outputFile, file: options.outputFile,
width: options.width, width: options.width,
height: options.height, height: options.height,
toolbarHeight: this._toolbarHeight()
}); });
this._recordingVideoFile = options.outputFile; this._recordingVideoFile = options.outputFile;
this._browserContext._browser._videoStarted(this._browserContext, screencastId, options.outputFile, this.pageOrError()); this._browserContext._browser._videoStarted(this._browserContext, screencastId, options.outputFile, this.pageOrError());
@ -827,9 +834,10 @@ export class WKPage implements PageDelegate {
}); });
} }
async setScreencastEnabled(enabled: boolean): Promise<void> { async setScreencastOptions(options: { width: number, height: number, quality: number } | null): Promise<void> {
if (enabled) { if (options) {
const { generation } = await this._pageProxySession.send('Screencast.startScreencast', { width: 800, height: 600, quality: 70 }); const so = { ...options, toolbarHeight: this._toolbarHeight() };
const { generation } = await this._pageProxySession.send('Screencast.startScreencast', so);
this._screencastGeneration = generation; this._screencastGeneration = generation;
} else { } else {
await this._pageProxySession.send('Screencast.stopScreencast'); await this._pageProxySession.send('Screencast.stopScreencast');

View file

@ -15,14 +15,16 @@
*/ */
import path from 'path'; import path from 'path';
import { expect, contextTest as test } from './config/browserTest'; import { expect, contextTest as test, browserTest } from './config/browserTest';
import yauzl from 'yauzl'; import yauzl from 'yauzl';
import removeFolder from 'rimraf'; import removeFolder from 'rimraf';
import jpeg from 'jpeg-js';
const traceDir = path.join(__dirname, '..', 'test-results', 'trace-' + process.env.FOLIO_WORKER_INDEX); const traceDir = path.join(__dirname, '..', 'test-results', 'trace-' + process.env.FOLIO_WORKER_INDEX);
test.useOptions({ traceDir }); test.useOptions({ traceDir });
test.beforeEach(async () => { test.beforeEach(async ({ browserName, headful }) => {
test.fixme(browserName === 'chromium' && headful, 'Chromium screencast on headful has a min width issue');
await new Promise(f => removeFolder(traceDir, f)); await new Promise(f => removeFolder(traceDir, f));
}); });
@ -99,6 +101,61 @@ test('should collect two traces', async ({ context, page, server }, testInfo) =>
} }
}); });
for (const params of [
{
id: 'fit',
width: 500,
height: 500,
}, {
id: 'crop',
width: 400, // Less than 450 to test firefox
height: 800,
}, {
id: 'scale',
width: 1024,
height: 768,
}]) {
browserTest(`should produce screencast frames ${params.id}`, async ({ video, contextFactory, browserName }, testInfo) => {
browserTest.fixme(browserName === 'chromium' && video, 'Same screencast resolution conflicts');
const scale = Math.min(800 / params.width, 600 / params.height, 1);
const previewWidth = params.width * scale;
const previewHeight = params.height * scale;
const context = await contextFactory({ viewport: { width: params.width, height: params.height }});
await (context as any)._tracing.start({ name: 'test', screenshots: true, snapshots: true });
const page = await context.newPage();
await page.setContent('<body style="box-sizing: border-box; width: 100%; height: 100%; margin:0; background: red; border: 50px solid blue"></body>');
// Make sure we have a chance to paint.
for (let i = 0; i < 10; ++i)
await page.evaluate(() => new Promise(requestAnimationFrame));
await (context as any)._tracing.stop();
await (context as any)._tracing.export(testInfo.outputPath('trace.zip'));
const { events, resources } = await parseTrace(testInfo.outputPath('trace.zip'));
const frames = events.filter(e => e.type === 'screencast-frame');
// Check all frame sizes
for (const frame of frames) {
expect(frame.width).toBe(params.width);
expect(frame.height).toBe(params.height);
const buffer = resources.get('resources/' + frame.sha1);
const image = jpeg.decode(buffer);
expect(image.width).toBe(previewWidth);
expect(image.height).toBe(previewHeight);
}
// Check last frame content.
const frame = frames[frames.length - 1]; // pick last frame.
const buffer = resources.get('resources/' + frame.sha1);
const image = jpeg.decode(buffer);
expect(image.data.byteLength).toBe(previewWidth * previewHeight * 4);
expectRed(image.data, previewWidth * previewHeight * 4 / 2 + previewWidth * 4 / 2); // center is red
expectBlue(image.data, previewWidth * 5 * 4 + previewWidth * 4 / 2); // top
expectBlue(image.data, previewWidth * (previewHeight - 5) * 4 + previewWidth * 4 / 2); // bottom
expectBlue(image.data, previewWidth * previewHeight * 4 / 2 + 5 * 4); // left
expectBlue(image.data, previewWidth * previewHeight * 4 / 2 + (previewWidth - 5) * 4); // right
});
}
async function parseTrace(file: string): Promise<{ events: any[], resources: Map<string, Buffer> }> { async function parseTrace(file: string): Promise<{ events: any[], resources: Map<string, Buffer> }> {
const entries = await new Promise<any[]>(f => { const entries = await new Promise<any[]>(f => {
const entries: Promise<any>[] = []; const entries: Promise<any>[] = [];
@ -128,4 +185,26 @@ async function parseTrace(file: string): Promise<{ events: any[], resources: Map
events, events,
resources, resources,
}; };
} }
function expectRed(pixels: Buffer, offset: number) {
const r = pixels.readUInt8(offset);
const g = pixels.readUInt8(offset + 1);
const b = pixels.readUInt8(offset + 2);
const a = pixels.readUInt8(offset + 3);
expect(r).toBeGreaterThan(200);
expect(g).toBeLessThan(70);
expect(b).toBeLessThan(70);
expect(a).toBe(255);
}
function expectBlue(pixels: Buffer, offset: number) {
const r = pixels.readUInt8(offset);
const g = pixels.readUInt8(offset + 1);
const b = pixels.readUInt8(offset + 2);
const a = pixels.readUInt8(offset + 3);
expect(r).toBeLessThan(70);
expect(g).toBeLessThan(70);
expect(b).toBeGreaterThan(200);
expect(a).toBe(255);
}