test: fix "should capture navigation" flakiness on firefox-headed (#34291)

This commit is contained in:
Andrey Lushnikov 2025-01-10 16:51:28 -05:00 committed by GitHub
parent 423005a7ab
commit 2cd5003062
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 65 additions and 60 deletions

View File

@ -14,14 +14,13 @@
* limitations under the License.
*/
import { browserTest as it, expect } from '../config/browserTest';
import { spawnSync } from 'child_process';
import fs from 'fs';
import path from 'path';
import type { Page } from 'playwright-core';
import { spawnSync } from 'child_process';
import { PNG, jpegjs } from 'playwright-core/lib/utilsBundle';
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';
const ffmpeg = registry.findExecutable('ffmpeg')!.executablePath('javascript');
@ -56,18 +55,11 @@ export class VideoPlayer {
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) {
const frame = this.frame(f, offset);
let hasColor = false;
for (let i = 0; i < frame.data.length; i += 4) {
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);
if (framePredicate(frame.data))
return frame;
}
}
@ -88,46 +80,52 @@ export class VideoPlayer {
}
}
function almostRed(r, g, b, alpha) {
expect(r).toBeGreaterThan(185);
expect(g).toBeLessThan(70);
expect(b).toBeLessThan(70);
expect(alpha).toBe(255);
type Pixel = { r: number, g: number, b: number, alpha: number };
type PixelPredicate = (pixel: Pixel) => boolean;
function isAlmostRed({ r, g, b, alpha }: Pixel): boolean {
return r > 185 && g < 70 && b < 70 && alpha === 255;
}
function almostBlack(r, g, b, alpha) {
expect(r).toBeLessThan(70);
expect(g).toBeLessThan(70);
expect(b).toBeLessThan(70);
expect(alpha).toBe(255);
function isAlmostBlack({ r, g, b, alpha }: Pixel): boolean {
return r < 70 && g < 70 && b < 70 && alpha === 255;
}
function almostGray(r, g, b, alpha) {
expect(r).toBeGreaterThan(70);
expect(g).toBeGreaterThan(70);
expect(b).toBeGreaterThan(70);
expect(r).toBeLessThan(185);
expect(g).toBeLessThan(185);
expect(b).toBeLessThan(185);
expect(alpha).toBe(255);
function isAlmostGray({ r, g, b, alpha }: Pixel): boolean {
return r > 70 && r < 185 &&
g > 70 && g < 185 &&
b > 70 && b < 185 &&
alpha === 255;
}
function expectAll(pixels: Buffer, 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 (let i = 0, n = pixels.length; i < n; i += 4)
checkPixel(i);
} catch (e) {
// Log pixel values on failure.
rewriteErrorMessage(e, e.message + `\n\nActual pixels=[${pixels.join(',')}]`);
throw e;
function findPixel(pixels: Buffer, pixelPredicate: PixelPredicate): Pixel|undefined {
for (let i = 0, n = pixels.length; i < n; i += 4) {
const pixel = {
r: pixels[i],
g: pixels[i + 1],
b: pixels[i + 2],
alpha: pixels[i + 3],
};
if (pixelPredicate(pixel))
return pixel;
}
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) {
@ -145,11 +143,11 @@ function expectRedFrames(videoFile: string, size: { width: number, height: numbe
{
const pixels = videoPlayer.seekLastFrame().data;
expectAll(pixels, almostRed);
expectAll(pixels, isAlmostRed);
}
{
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);
{
const pixels = videoPlayer.seekFirstNonEmptyFrame().data;
expectAll(pixels, almostBlack);
// Find a frame with all almost-black pixels.
const frame = videoPlayer.findFrame(pixels => everyPixel(pixels, isAlmostBlack));
expect(frame).not.toBeUndefined();
}
{
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;
expectAll(pixels, almostRed);
expectAll(pixels, isAlmostRed);
}
});
@ -506,19 +505,19 @@ it.describe('screencast', () => {
{
const pixels = videoPlayer.seekLastFrame({ x: 0, y: 0 }).data;
expectAll(pixels, almostRed);
expectAll(pixels, isAlmostRed);
}
{
const pixels = videoPlayer.seekLastFrame({ x: 300, y: 0 }).data;
expectAll(pixels, almostGray);
expectAll(pixels, isAlmostGray);
}
{
const pixels = videoPlayer.seekLastFrame({ x: 0, y: 200 }).data;
expectAll(pixels, almostGray);
expectAll(pixels, isAlmostGray);
}
{
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;
expectAll(pixels, almostRed);
expectAll(pixels, isAlmostRed);
}
});
@ -754,7 +753,7 @@ it.describe('screencast', () => {
// Bottom right corner should be part of the red border.
// 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;
expectAll(pixels, almostRed);
expectAll(pixels, isAlmostRed);
});
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.
// 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;
expectAll(pixels, almostRed);
expectAll(pixels, isAlmostRed);
});
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.height).toBe(size.height);
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);
});
});