test: fix "should capture navigation" flakiness on firefox-headed (#34291)
This commit is contained in:
parent
423005a7ab
commit
2cd5003062
|
|
@ -14,14 +14,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { browserTest as it, expect } from '../config/browserTest';
|
import { spawnSync } from 'child_process';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import type { Page } from 'playwright-core';
|
import type { Page } from 'playwright-core';
|
||||||
import { spawnSync } from 'child_process';
|
|
||||||
import { PNG, jpegjs } from 'playwright-core/lib/utilsBundle';
|
import { PNG, jpegjs } from 'playwright-core/lib/utilsBundle';
|
||||||
import { registry } from '../../packages/playwright-core/lib/server';
|
import { registry } from '../../packages/playwright-core/lib/server';
|
||||||
import { rewriteErrorMessage } from '../../packages/playwright-core/lib/utils/stackTrace';
|
import { expect, browserTest as it } from '../config/browserTest';
|
||||||
import { parseTraceRaw } from '../config/utils';
|
import { parseTraceRaw } from '../config/utils';
|
||||||
|
|
||||||
const ffmpeg = registry.findExecutable('ffmpeg')!.executablePath('javascript');
|
const ffmpeg = registry.findExecutable('ffmpeg')!.executablePath('javascript');
|
||||||
|
|
@ -56,18 +55,11 @@ export class VideoPlayer {
|
||||||
this.videoHeight = parseInt(resolutionMatch![2], 10);
|
this.videoHeight = parseInt(resolutionMatch![2], 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
seekFirstNonEmptyFrame(offset?: { x: number, y: number }): any | undefined {
|
findFrame(framePredicate: (pixels: Buffer) => boolean, offset?: { x: number, y: number }): any |undefined {
|
||||||
for (let f = 1; f <= this.frames; ++f) {
|
for (let f = 1; f <= this.frames; ++f) {
|
||||||
const frame = this.frame(f, offset);
|
const frame = this.frame(f, offset);
|
||||||
let hasColor = false;
|
if (framePredicate(frame.data))
|
||||||
for (let i = 0; i < frame.data.length; i += 4) {
|
return frame;
|
||||||
if (frame.data[i + 0] < 230 || frame.data[i + 1] < 230 || frame.data[i + 2] < 230) {
|
|
||||||
hasColor = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hasColor)
|
|
||||||
return this.frame(f, offset);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,46 +80,52 @@ export class VideoPlayer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function almostRed(r, g, b, alpha) {
|
type Pixel = { r: number, g: number, b: number, alpha: number };
|
||||||
expect(r).toBeGreaterThan(185);
|
type PixelPredicate = (pixel: Pixel) => boolean;
|
||||||
expect(g).toBeLessThan(70);
|
|
||||||
expect(b).toBeLessThan(70);
|
function isAlmostRed({ r, g, b, alpha }: Pixel): boolean {
|
||||||
expect(alpha).toBe(255);
|
return r > 185 && g < 70 && b < 70 && alpha === 255;
|
||||||
}
|
}
|
||||||
|
|
||||||
function almostBlack(r, g, b, alpha) {
|
function isAlmostBlack({ r, g, b, alpha }: Pixel): boolean {
|
||||||
expect(r).toBeLessThan(70);
|
return r < 70 && g < 70 && b < 70 && alpha === 255;
|
||||||
expect(g).toBeLessThan(70);
|
|
||||||
expect(b).toBeLessThan(70);
|
|
||||||
expect(alpha).toBe(255);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function almostGray(r, g, b, alpha) {
|
function isAlmostGray({ r, g, b, alpha }: Pixel): boolean {
|
||||||
expect(r).toBeGreaterThan(70);
|
return r > 70 && r < 185 &&
|
||||||
expect(g).toBeGreaterThan(70);
|
g > 70 && g < 185 &&
|
||||||
expect(b).toBeGreaterThan(70);
|
b > 70 && b < 185 &&
|
||||||
expect(r).toBeLessThan(185);
|
alpha === 255;
|
||||||
expect(g).toBeLessThan(185);
|
|
||||||
expect(b).toBeLessThan(185);
|
|
||||||
expect(alpha).toBe(255);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function expectAll(pixels: Buffer, rgbaPredicate) {
|
function findPixel(pixels: Buffer, pixelPredicate: PixelPredicate): Pixel|undefined {
|
||||||
const checkPixel = i => {
|
for (let i = 0, n = pixels.length; i < n; i += 4) {
|
||||||
const r = pixels[i];
|
const pixel = {
|
||||||
const g = pixels[i + 1];
|
r: pixels[i],
|
||||||
const b = pixels[i + 2];
|
g: pixels[i + 1],
|
||||||
const alpha = pixels[i + 3];
|
b: pixels[i + 2],
|
||||||
rgbaPredicate(r, g, b, alpha);
|
alpha: pixels[i + 3],
|
||||||
};
|
};
|
||||||
try {
|
if (pixelPredicate(pixel))
|
||||||
for (let i = 0, n = pixels.length; i < n; i += 4)
|
return pixel;
|
||||||
checkPixel(i);
|
|
||||||
} catch (e) {
|
|
||||||
// Log pixel values on failure.
|
|
||||||
rewriteErrorMessage(e, e.message + `\n\nActual pixels=[${pixels.join(',')}]`);
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function everyPixel(pixels: Buffer, pixelPredicate: PixelPredicate) {
|
||||||
|
const badPixel = findPixel(pixels, pixel => !pixelPredicate(pixel));
|
||||||
|
return !badPixel;
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectAll(pixels: Buffer, pixelPredicate: PixelPredicate) {
|
||||||
|
const badPixel = findPixel(pixels, pixel => !pixelPredicate(pixel));
|
||||||
|
if (!badPixel)
|
||||||
|
return;
|
||||||
|
const rgba = [badPixel.r, badPixel.g, badPixel.b, badPixel.alpha].join(', ');
|
||||||
|
throw new Error([
|
||||||
|
`Expected all pixels to satisfy ${pixelPredicate.name}, found bad pixel (${rgba})`,
|
||||||
|
`Actual pixels=[${pixels.join(',')}]`,
|
||||||
|
].join('\n'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function findVideos(videoDir: string) {
|
function findVideos(videoDir: string) {
|
||||||
|
|
@ -145,11 +143,11 @@ function expectRedFrames(videoFile: string, size: { width: number, height: numbe
|
||||||
|
|
||||||
{
|
{
|
||||||
const pixels = videoPlayer.seekLastFrame().data;
|
const pixels = videoPlayer.seekLastFrame().data;
|
||||||
expectAll(pixels, almostRed);
|
expectAll(pixels, isAlmostRed);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const pixels = videoPlayer.seekLastFrame({ x: size.width - 20, y: 0 }).data;
|
const pixels = videoPlayer.seekLastFrame({ x: size.width - 20, y: 0 }).data;
|
||||||
expectAll(pixels, almostRed);
|
expectAll(pixels, isAlmostRed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -399,13 +397,14 @@ it.describe('screencast', () => {
|
||||||
expect(duration).toBeGreaterThan(0);
|
expect(duration).toBeGreaterThan(0);
|
||||||
|
|
||||||
{
|
{
|
||||||
const pixels = videoPlayer.seekFirstNonEmptyFrame().data;
|
// Find a frame with all almost-black pixels.
|
||||||
expectAll(pixels, almostBlack);
|
const frame = videoPlayer.findFrame(pixels => everyPixel(pixels, isAlmostBlack));
|
||||||
|
expect(frame).not.toBeUndefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const pixels = videoPlayer.seekLastFrame().data;
|
const pixels = videoPlayer.seekLastFrame().data;
|
||||||
expectAll(pixels, almostGray);
|
expectAll(pixels, isAlmostGray);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -435,7 +434,7 @@ it.describe('screencast', () => {
|
||||||
|
|
||||||
{
|
{
|
||||||
const pixels = videoPlayer.seekLastFrame({ x: 95, y: 45 }).data;
|
const pixels = videoPlayer.seekLastFrame({ x: 95, y: 45 }).data;
|
||||||
expectAll(pixels, almostRed);
|
expectAll(pixels, isAlmostRed);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -506,19 +505,19 @@ it.describe('screencast', () => {
|
||||||
|
|
||||||
{
|
{
|
||||||
const pixels = videoPlayer.seekLastFrame({ x: 0, y: 0 }).data;
|
const pixels = videoPlayer.seekLastFrame({ x: 0, y: 0 }).data;
|
||||||
expectAll(pixels, almostRed);
|
expectAll(pixels, isAlmostRed);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const pixels = videoPlayer.seekLastFrame({ x: 300, y: 0 }).data;
|
const pixels = videoPlayer.seekLastFrame({ x: 300, y: 0 }).data;
|
||||||
expectAll(pixels, almostGray);
|
expectAll(pixels, isAlmostGray);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const pixels = videoPlayer.seekLastFrame({ x: 0, y: 200 }).data;
|
const pixels = videoPlayer.seekLastFrame({ x: 0, y: 200 }).data;
|
||||||
expectAll(pixels, almostGray);
|
expectAll(pixels, isAlmostGray);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const pixels = videoPlayer.seekLastFrame({ x: 300, y: 200 }).data;
|
const pixels = videoPlayer.seekLastFrame({ x: 300, y: 200 }).data;
|
||||||
expectAll(pixels, almostRed);
|
expectAll(pixels, isAlmostRed);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -603,7 +602,7 @@ it.describe('screencast', () => {
|
||||||
|
|
||||||
{
|
{
|
||||||
const pixels = videoPlayer.seekLastFrame().data;
|
const pixels = videoPlayer.seekLastFrame().data;
|
||||||
expectAll(pixels, almostRed);
|
expectAll(pixels, isAlmostRed);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -754,7 +753,7 @@ it.describe('screencast', () => {
|
||||||
// Bottom right corner should be part of the red border.
|
// Bottom right corner should be part of the red border.
|
||||||
// However, headed browsers on mac have rounded corners, so offset by 10.
|
// However, headed browsers on mac have rounded corners, so offset by 10.
|
||||||
const pixels = videoPlayer.seekLastFrame({ x: size.width - 20, y: size.height - 20 }).data;
|
const pixels = videoPlayer.seekLastFrame({ x: size.width - 20, y: size.height - 20 }).data;
|
||||||
expectAll(pixels, almostRed);
|
expectAll(pixels, isAlmostRed);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should capture full viewport on hidpi', async ({ browserType, browserName, headless, isWindows, isLinux, isHeadlessShell }, testInfo) => {
|
it('should capture full viewport on hidpi', async ({ browserType, browserName, headless, isWindows, isLinux, isHeadlessShell }, testInfo) => {
|
||||||
|
|
@ -791,7 +790,7 @@ it.describe('screencast', () => {
|
||||||
// Bottom right corner should be part of the red border.
|
// Bottom right corner should be part of the red border.
|
||||||
// However, headed browsers on mac have rounded corners, so offset by 10.
|
// However, headed browsers on mac have rounded corners, so offset by 10.
|
||||||
const pixels = videoPlayer.seekLastFrame({ x: size.width - 20, y: size.height - 20 }).data;
|
const pixels = videoPlayer.seekLastFrame({ x: size.width - 20, y: size.height - 20 }).data;
|
||||||
expectAll(pixels, almostRed);
|
expectAll(pixels, isAlmostRed);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with video+trace', async ({ browser, trace, headless, browserName, isHeadlessShell }, testInfo) => {
|
it('should work with video+trace', async ({ browser, trace, headless, browserName, isHeadlessShell }, testInfo) => {
|
||||||
|
|
@ -827,7 +826,13 @@ it.describe('screencast', () => {
|
||||||
expect(image.width).toBe(size.width);
|
expect(image.width).toBe(size.width);
|
||||||
expect(image.height).toBe(size.height);
|
expect(image.height).toBe(size.height);
|
||||||
const offset = size.width * size.height / 2 * 4 + size.width * 4 / 2; // Center should be red.
|
const offset = size.width * size.height / 2 * 4 + size.width * 4 / 2; // Center should be red.
|
||||||
almostRed(image.data.readUInt8(offset), image.data.readUInt8(offset + 1), image.data.readUInt8(offset + 2), image.data.readUInt8(offset + 3));
|
const pixel: Pixel = {
|
||||||
|
r: image.data.readUInt8(offset),
|
||||||
|
g: image.data.readUInt8(offset + 1),
|
||||||
|
b: image.data.readUInt8(offset + 2),
|
||||||
|
alpha: image.data.readUInt8(offset + 3),
|
||||||
|
};
|
||||||
|
expect(isAlmostRed(pixel)).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue