mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
test(screencast): video recording during cros-process navigation (#3396)
This commit is contained in:
parent
915902c858
commit
16b471fac6
10
test/assets/background-color.html
Normal file
10
test/assets/background-color.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<html>
|
||||||
|
<script>
|
||||||
|
function changeBackground() {
|
||||||
|
const color = location.hash.substr(1);
|
||||||
|
document.body.style.backgroundColor = color;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<body onload='changeBackground()'>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import './base.fixture';
|
import './base.fixture';
|
||||||
import { FirefoxBrowser } from '..';
|
import { Page } from '..';
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
@ -23,10 +23,11 @@ const url = require('url');
|
|||||||
const {mkdtempAsync, removeFolderAsync} = require('./utils');
|
const {mkdtempAsync, removeFolderAsync} = require('./utils');
|
||||||
|
|
||||||
const {FFOX, CHROMIUM, WEBKIT, MAC, LINUX, WIN, HEADLESS, USES_HOOKS} = testOptions;
|
const {FFOX, CHROMIUM, WEBKIT, MAC, LINUX, WIN, HEADLESS, USES_HOOKS} = testOptions;
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface FixtureState {
|
interface FixtureState {
|
||||||
persistentDirectory: string;
|
persistentDirectory: string;
|
||||||
firefox: FirefoxBrowser;
|
videoPlayer: VideoPlayer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,20 +40,144 @@ registerFixture('persistentDirectory', async ({}, test) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
registerFixture('firefox', async ({playwright}, test) => {
|
registerFixture('videoPlayer', async ({playwright, context}, test) => {
|
||||||
|
let firefox;
|
||||||
if (WEBKIT && !LINUX) {
|
if (WEBKIT && !LINUX) {
|
||||||
const firefox = await playwright.firefox.launch();
|
// WebKit on Mac & Windows cannot replay webm/vp8 video, so we launch Firefox.
|
||||||
try {
|
firefox = await playwright.firefox.launch();
|
||||||
await test(firefox);
|
context = await firefox.newContext();
|
||||||
} finally {
|
}
|
||||||
|
|
||||||
|
let page;
|
||||||
|
try {
|
||||||
|
page = await context.newPage();
|
||||||
|
const player = new VideoPlayer(page);
|
||||||
|
await test(player);
|
||||||
|
} finally {
|
||||||
|
if (firefox)
|
||||||
await firefox.close();
|
await firefox.close();
|
||||||
}
|
else
|
||||||
} else {
|
await page.close();
|
||||||
await test(null);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it.fail(CHROMIUM)('should capture static page', async({page, persistentDirectory, firefox, toImpl}) => {
|
function almostRed(r, g, b, alpha) {
|
||||||
|
expect(r).toBeGreaterThan(240);
|
||||||
|
expect(g).toBeLessThan(50);
|
||||||
|
expect(b).toBeLessThan(50);
|
||||||
|
expect(alpha).toBe(255);
|
||||||
|
}
|
||||||
|
|
||||||
|
function almostBlack(r, g, b, alpha) {
|
||||||
|
expect(r).toBeLessThan(10);
|
||||||
|
expect(g).toBeLessThan(10);
|
||||||
|
expect(b).toBeLessThan(10);
|
||||||
|
expect(alpha).toBe(255);
|
||||||
|
}
|
||||||
|
|
||||||
|
function almostGrey(r, g, b, alpha) {
|
||||||
|
expect(r).toBeGreaterThanOrEqual(90);
|
||||||
|
expect(g).toBeGreaterThanOrEqual(90);
|
||||||
|
expect(b).toBeGreaterThanOrEqual(90);
|
||||||
|
expect(r).toBeLessThan(110);
|
||||||
|
expect(g).toBeLessThan(110);
|
||||||
|
expect(b).toBeLessThan(110);
|
||||||
|
expect(alpha).toBe(255);
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectAll(pixels, rgbaPredicate) {
|
||||||
|
const checkPixel = (i) => {
|
||||||
|
const r = pixels[i];
|
||||||
|
const g = pixels[i + 1];
|
||||||
|
const b = pixels[i + 2];
|
||||||
|
const alpha = pixels[i + 3];
|
||||||
|
rgbaPredicate(r, g, b, alpha);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
for (var i = 0, n = pixels.length; i < n; i += 4)
|
||||||
|
checkPixel(i);
|
||||||
|
} catch(e) {
|
||||||
|
// Log pixel values on failure.
|
||||||
|
console.log(pixels);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VideoPlayer {
|
||||||
|
private readonly _page: Page;
|
||||||
|
constructor(page: Page) {
|
||||||
|
this._page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
async load(videoFile) {
|
||||||
|
await this._page.goto(url.pathToFileURL(videoFile).href);
|
||||||
|
await this._page.$eval('video', (v:HTMLVideoElement) => {
|
||||||
|
return new Promise(fulfil => {
|
||||||
|
// In case video playback autostarts.
|
||||||
|
v.pause();
|
||||||
|
v.onplaying = fulfil;
|
||||||
|
v.play();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await this._page.$eval('video', (v:HTMLVideoElement) => {
|
||||||
|
v.pause();
|
||||||
|
const result = new Promise(f => v.onseeked = f);
|
||||||
|
v.currentTime = v.duration;
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async duration() {
|
||||||
|
return await this._page.$eval('video', (v:HTMLVideoElement) => v.duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
async videoWidth() {
|
||||||
|
return await this._page.$eval('video', (v:HTMLVideoElement) => v.videoWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
async videoHeight() {
|
||||||
|
return await this._page.$eval('video', (v:HTMLVideoElement) => v.videoHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
async seek(timestamp) {
|
||||||
|
await this._page.$eval('video', (v:HTMLVideoElement, timestamp) => {
|
||||||
|
v.pause();
|
||||||
|
const result = new Promise(f => v.onseeked = f);
|
||||||
|
v.currentTime = timestamp;
|
||||||
|
return result;
|
||||||
|
}, timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
async seekLastNonEmptyFrame() {
|
||||||
|
const duration = await this.duration();
|
||||||
|
let time = duration - 0.01;
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
await this.seek(time);
|
||||||
|
const pixels = await this.pixels();
|
||||||
|
if (!pixels.every(p => p === 0))
|
||||||
|
return;
|
||||||
|
time -= 0.1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async pixels() {
|
||||||
|
const pixels = await this._page.$eval('video', (video:HTMLVideoElement) => {
|
||||||
|
let canvas = document.createElement("canvas");
|
||||||
|
if (!video.videoWidth || !video.videoHeight)
|
||||||
|
throw new Error("Video element is empty");
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
return pixels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: the test fails in headful Firefox when running under xvfb.
|
||||||
|
it.fail(CHROMIUM || (FFOX && !HEADLESS))('should capture static page', async({page, persistentDirectory, videoPlayer, toImpl}) => {
|
||||||
if (!toImpl)
|
if (!toImpl)
|
||||||
return;
|
return;
|
||||||
const videoFile = path.join(persistentDirectory, 'v.webm');
|
const videoFile = path.join(persistentDirectory, 'v.webm');
|
||||||
@ -66,60 +191,48 @@ it.fail(CHROMIUM)('should capture static page', async({page, persistentDirectory
|
|||||||
await toImpl(page)._delegate.stopVideoRecording();
|
await toImpl(page)._delegate.stopVideoRecording();
|
||||||
expect(fs.existsSync(videoFile)).toBe(true);
|
expect(fs.existsSync(videoFile)).toBe(true);
|
||||||
|
|
||||||
if (WEBKIT && !LINUX) {
|
await videoPlayer.load(videoFile);
|
||||||
// WebKit on Mac & Windows cannot replay webm/vp8 video, so we launch Firefox.
|
const duration = await videoPlayer.duration();
|
||||||
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);
|
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 => {
|
expect(await videoPlayer.videoWidth()).toBe(640);
|
||||||
let canvas = document.createElement("canvas");
|
expect(await videoPlayer.videoHeight()).toBe(480);
|
||||||
canvas.width = video.videoWidth;
|
|
||||||
canvas.height = video.videoHeight;
|
await videoPlayer.seekLastNonEmptyFrame();
|
||||||
const context = canvas.getContext('2d');
|
const pixels = await videoPlayer.pixels();
|
||||||
context.drawImage(video, 0, 0);
|
expectAll(pixels, almostRed);
|
||||||
const imgd = context.getImageData(0, 0, 10, 10);
|
});
|
||||||
return Array.from(imgd.data);
|
|
||||||
});
|
// TODO: the test fails in headful Firefox when running under xvfb.
|
||||||
const expectAlmostRed = (i) => {
|
it.fail(CHROMIUM || (FFOX && !HEADLESS))('should capture navigation', async({page, persistentDirectory, server, videoPlayer, toImpl}) => {
|
||||||
const r = pixels[i];
|
if (!toImpl)
|
||||||
const g = pixels[i + 1];
|
return;
|
||||||
const b = pixels[i + 2];
|
const videoFile = path.join(persistentDirectory, 'v.webm');
|
||||||
const alpha = pixels[i + 3];
|
await page.goto(server.PREFIX + '/background-color.html#rgb(0,0,0)');
|
||||||
expect(r).toBeGreaterThan(240);
|
await toImpl(page)._delegate.startVideoRecording({outputFile: videoFile, width: 640, height: 480});
|
||||||
expect(g).toBeLessThan(50);
|
// TODO: in WebKit figure out why video size is not reported correctly for
|
||||||
expect(b).toBeLessThan(50);
|
// static pictures.
|
||||||
expect(alpha).toBe(255);
|
if (HEADLESS && WEBKIT)
|
||||||
|
await page.setViewportSize({width: 1270, height: 950});
|
||||||
|
await new Promise(r => setTimeout(r, 300));
|
||||||
|
await page.goto(server.CROSS_PROCESS_PREFIX + '/background-color.html#rgb(100,100,100)');
|
||||||
|
await new Promise(r => setTimeout(r, 300));
|
||||||
|
await toImpl(page)._delegate.stopVideoRecording();
|
||||||
|
expect(fs.existsSync(videoFile)).toBe(true);
|
||||||
|
|
||||||
|
await videoPlayer.load(videoFile);
|
||||||
|
const duration = await videoPlayer.duration();
|
||||||
|
expect(duration).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
{
|
||||||
|
await videoPlayer.seek(0);
|
||||||
|
const pixels = await videoPlayer.pixels();
|
||||||
|
expectAll(pixels, almostBlack);
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
for (var i = 0, n = pixels.length; i < n; i += 4)
|
{
|
||||||
expectAlmostRed(i);
|
await videoPlayer.seekLastNonEmptyFrame();
|
||||||
} catch(e) {
|
const pixels = await videoPlayer.pixels();
|
||||||
// Log pixel values on failure.
|
expectAll(pixels, almostGrey);
|
||||||
console.log(pixels);
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user