Fix fresh browser screenshot prep race

This commit is contained in:
Mohamed Boudra
2026-07-03 07:18:45 +02:00
parent f22865df62
commit cda1ba43cc
9 changed files with 497 additions and 44 deletions

View File

@@ -48,7 +48,8 @@
const activeTokens = new Set();
let nextTokenId = 0;
for (let index = 0; index < webviewCount; index += 1) {
function appendHarnessWebview(sourceUrl) {
const index = webviews.length;
const webview = document.createElement("webview");
webview.id = `target-webview-${index + 1}`;
webview.className = "capture-harness-webview";
@@ -57,9 +58,15 @@
webview.setAttribute("allowpopups", "true");
webview.setAttribute("spellcheck", "false");
webview.setAttribute("autosize", "on");
webview.src = params.get("targetUrl") || "bright.html";
webview.src = sourceUrl;
applyStackedWebviewStyle(webview);
host.appendChild(webview);
webviews.push(webview);
return index;
}
for (let index = 0; index < webviewCount; index += 1) {
appendHarnessWebview(params.get("targetUrl") || "bright.html");
}
function applyHostParking() {
@@ -203,6 +210,9 @@
typeof webview.getWebContentsId === "function" ? webview.getWebContentsId() : null,
);
},
addWebview(sourceUrl) {
return appendHarnessWebview(sourceUrl || params.get("targetUrl") || "bright.html");
},
async prepareForPixelCapture(targetIndex = 0) {
const token = `capture-${++nextTokenId}`;
activeTokens.add(token);

View File

@@ -143,7 +143,7 @@ async function saveImage(image, outputPath) {
await fsp.writeFile(outputPath, image.toPNG());
}
async function waitForGuestLoad(contents) {
async function waitForGuestLoad(contents, input = {}) {
await new Promise((resolve) => {
if (!contents.isLoading()) {
resolve();
@@ -152,7 +152,10 @@ async function waitForGuestLoad(contents) {
contents.once("did-finish-load", resolve);
contents.once("did-fail-load", resolve);
});
await new Promise((resolve) => setTimeout(resolve, 500));
const settleMs = input.settleMs ?? 500;
if (settleMs > 0) {
await new Promise((resolve) => setTimeout(resolve, settleMs));
}
}
async function renderer(win, expression) {
@@ -228,14 +231,23 @@ async function captureFullPage(contents) {
}
}
async function captureWithPrep({ win, contents, mode, repeatIndex, targetIndex, guestMetrics }) {
async function captureWithPrep({
win,
contents,
mode,
repeatIndex,
targetIndex,
guestMetrics,
repeatTotal = REPEAT_COUNT,
label = "prep",
}) {
const preparation = await renderer(
win,
`window.captureHarness.prepareForPixelCapture(${JSON.stringify(targetIndex)})`,
);
const outputPath = path.join(
OUT_DIR,
`${mode}-webview-${targetIndex + 1}-prep-${repeatIndex}.png`,
`${mode}-webview-${targetIndex + 1}-${label}-${repeatIndex}.png`,
);
try {
const image =
@@ -251,11 +263,11 @@ async function captureWithPrep({ win, contents, mode, repeatIndex, targetIndex,
const bright = analysis.brightRatio.toFixed(4);
if (!analysis.pass) {
fail(
`${mode} webview ${targetIndex + 1} prep ${repeatIndex}/${REPEAT_COUNT} size=${size} logical=${logicalSize} bright=${bright} text=${analysis.textNonUniform} file=${outputPath}`,
`${mode} webview ${targetIndex + 1} ${label} ${repeatIndex}/${repeatTotal} size=${size} logical=${logicalSize} bright=${bright} text=${analysis.textNonUniform} file=${outputPath}`,
);
}
pass(
`${mode} webview ${targetIndex + 1} prep ${repeatIndex}/${REPEAT_COUNT} size=${size} logical=${logicalSize} bright=${bright} text=${analysis.textNonUniform} file=${outputPath}`,
`${mode} webview ${targetIndex + 1} ${label} ${repeatIndex}/${repeatTotal} size=${size} logical=${logicalSize} bright=${bright} text=${analysis.textNonUniform} file=${outputPath}`,
);
return analysis;
} finally {
@@ -308,14 +320,58 @@ async function expectLegacySecondWebviewFailure({ win, contents, mode, guestMetr
}
}
async function captureFreshWebviewImmediately({ win, waitForNextAttachedGuest }) {
const freshGuestPromise = waitForNextAttachedGuest();
const targetIndex = await renderer(
win,
`window.captureHarness.addWebview(${JSON.stringify(fileUrl(path.join(ROOT, "bright.html")))})`,
);
const guest = await withTimeout(freshGuestPromise, "fresh did-attach-webview");
await waitForGuestLoad(guest, { settleMs: 0 });
const guestMetrics = await readGuestMetrics(guest);
if (guestMetrics.innerWidth !== VIEWPORT_WIDTH || guestMetrics.innerHeight !== VIEWPORT_HEIGHT) {
fail(
`fresh guest viewport sizing webview ${targetIndex + 1} inner=${guestMetrics.innerWidth}x${guestMetrics.innerHeight} expected=${VIEWPORT_WIDTH}x${VIEWPORT_HEIGHT}`,
);
}
pass(
`fresh guest viewport sizing webview ${targetIndex + 1} inner=${guestMetrics.innerWidth}x${guestMetrics.innerHeight} dpr=${guestMetrics.devicePixelRatio}`,
);
await captureWithPrep({
win,
contents: guest,
mode: "viewport",
repeatIndex: 1,
targetIndex,
guestMetrics,
repeatTotal: 1,
label: "fresh-immediate",
});
await captureWithPrep({
win,
contents: guest,
mode: "full-page",
repeatIndex: 1,
targetIndex,
guestMetrics,
repeatTotal: 1,
label: "fresh-immediate",
});
}
async function main() {
ensureDirSync(OUT_DIR);
const attachedGuests = [];
const freshGuestWaiters = [];
let resolveGuests;
const guestsPromise = new Promise((resolve) => {
resolveGuests = resolve;
});
const waitForNextAttachedGuest = () =>
new Promise((resolve) => {
freshGuestWaiters.push(resolve);
});
const win = new BrowserWindow({
width: 1000,
height: 700,
@@ -335,6 +391,10 @@ async function main() {
});
win.webContents.on("did-attach-webview", (_event, contents) => {
attachedGuests.push(contents);
const waiter = freshGuestWaiters.shift();
if (waiter) {
waiter(contents);
}
if (attachedGuests.length >= 2) {
resolveGuests(attachedGuests);
}
@@ -398,6 +458,9 @@ async function main() {
await renderer(win, "window.captureHarness.restoreParking()");
await captureFreshWebviewImmediately({ win, waitForNextAttachedGuest });
await renderer(win, "window.captureHarness.restoreParking()");
const results = [];
for (const targetIndex of [0, 1]) {
for (let index = 1; index <= REPEAT_COUNT; index += 1) {