Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"test-e2e-testplane": "npm run test-e2e-testplane:generate-fixtures && npm run test-e2e-testplane:run-tests",
"test-e2e-testplane:run-tests": "node bin/testplane --config test/e2e/testplane.config.ts",
"test-e2e-testplane:generate-fixtures": "node bin/testplane --config test/e2e/fixtures/basic-report/testplane.config.ts || true",
"test-e2e:gui": "node bin/testplane --config test/e2e/testplane.config.ts gui",
"test-e2e-testplane:gui": "node bin/testplane --config test/e2e/testplane.config.ts gui",
"test-browser-env": "TS_NODE_PROJECT=./test/browser-env/tsconfig.json node bin/testplane -r tsconfig-paths/register --config test/browser-env/testplane.config.ts",
"test-browser-env:gui": "TS_NODE_PROJECT=./test/browser-env/tsconfig.json node bin/testplane gui -r tsconfig-paths/register --config test/browser-env/testplane.config.ts",
"toc": "doctoc docs --title '### Contents'",
Expand Down
13 changes: 12 additions & 1 deletion src/browser/client-scripts/screen-shooter/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ export function computeScrollOffset(element: Element): Coord<"page", "css", "y">
}

export function computeViewportSize(): Size<"css"> {
const visualViewport = window.visualViewport;

// Visual viewport occasionally returns more correct values than innerWidth/Height, but may not be available in older browsers
if (visualViewport && visualViewport.width > 0 && visualViewport.height > 0) {
return {
width: visualViewport.width as Length<"css", "x">,
height: visualViewport.height as Length<"css", "y">
Comment on lines +56 to +57

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize visual viewport dimensions before returning

When visualViewport exposes fractional CSS pixels, such as under pinch/page zoom, these raw dimensions flow through fromCssToDevice unchanged whenever DPR is an integer. Later screenshot cropping and PNG encoding assume integer image sizes (Image.crop stores actualWidth/actualHeight directly, and PNG IHDR writes integer widths), so these captures can fail or produce malformed buffers; round/floor the visual viewport size before returning it.

Useful? React with 👍 / 👎.

};
}

return {
width: window.innerWidth as Length<"css", "x">,
height: window.innerHeight as Length<"css", "y">
Expand Down Expand Up @@ -333,7 +343,8 @@ export function computeSafeArea(
const scrollParentBcr = scrollParent.getBoundingClientRect();
topValue += isRootLikeElement(scrollParent) ? 0 : scrollParentBcr.top;
shouldSkipZIndexCheck =
scrollParent === scrollEl || (isRootLikeElement(scrollParent) && isRootLikeElement(scrollEl));
captureElements.some(capEl => capEl === el || capEl.contains(el)) &&
(scrollParent === scrollEl || (isRootLikeElement(scrollParent) && isRootLikeElement(scrollEl)));

if (!isNaN(topValue)) {
adjustedRect = {
Expand Down
10 changes: 10 additions & 0 deletions src/browser/screen-shooter/elements-screen-shooter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,16 @@ export class ElementsScreenShooter {
opts,
async currentState => {
if (currentState.captureSpecs.length === 0) {
if (iterations > 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require complete coverage before accepting disappeared targets

For a target that needs another scroll chunk and hides after the first scroll, iterations > 0 is true even though hasCapturedTheWholeArea is still false. Returning here makes _scrollThroughCaptureArea treat the empty spec list as finished, so CompositeImage renders only previously registered chunks and the lower part of the requested selector is never captured or compared; this can turn an incomplete capture into a passing/truncated baseline instead of an error.

Useful? React with 👍 / 👎.

debug(
"Capture area disappeared after %d chunk(s), rendering already captured data for selectors: %s",
iterations,
selectorsToCapture.join("; "),
);

return;
}

throw new Error(getEmptyCaptureSpecsErrorMessage(selectorsToCapture));
}

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,19 @@ describe("computeSafeArea", () => {
await browser.assertView("compute-safe-area-stacking-context-filter-in-front");
});

it("should not shrink when root sticky element is behind absolute popup target", async ({ browser }) => {
const { default: html } = await import("./fixtures/safe-areas/root-sticky-behind-absolute-popup.html?raw");
document.body.innerHTML = html;

const selectors = [".popup"];
const safeArea = computeSafeArea(selectors);
const captureSpecs = computeCaptureSpecs(selectors);

visualizeCaptureSpecs(captureSpecs);
visualizeSafeArea(safeArea.top, safeArea.height);
await browser.assertView("compute-safe-area-root-sticky-behind-absolute-popup");
});

it("should shrink for fixed header that creates stacking context via filter and is in front", async ({
browser,
}) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<style>
html,
body {
margin: 0;
padding: 0;
width: 100%;
min-height: 1600px;
font-family: Arial, sans-serif;
background: #eef3f7;
}

.listing {
position: relative;
z-index: 1;
width: 1000px;
min-height: 900px;
margin: 0 auto;
padding-top: 100px;
}

.sticky-filter {
position: sticky;
top: 112px;
z-index: 22;
display: flex;
align-items: center;
width: 1000px;
height: 60px;
box-sizing: border-box;
padding: 0 24px;
background: #304461;
color: #fff;
font-weight: 700;
}

.card {
width: 520px;
height: 260px;
margin-top: 48px;
padding: 28px;
box-sizing: border-box;
background: #fff;
border: 1px solid #b9c7d8;
}

.popup {
position: absolute;
left: 700px;
top: 120px;
z-index: 11010;
width: 360px;
height: 116px;
box-sizing: border-box;
padding: 18px;
background: #fff;
border: 2px solid #55a0ff;
box-shadow: 0 10px 28px rgba(20, 33, 61, 0.22);
font-weight: 700;
}
</style>

<div class="listing">
<div class="sticky-filter">Sticky filter behind popup</div>

<div class="card">
<h3>Listing card</h3>
<p>The sticky filter belongs to page content and is below the popup in stacking order.</p>
</div>
</div>

<div class="popup">Popup target above sticky filter</div>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
86 changes: 86 additions & 0 deletions test/e2e/static/capture-area-disappears-after-scroll.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Capture Area Disappears After Scroll</title>
<style>
html,
body {
margin: 0;
min-height: 1800px;
font-family: Arial, sans-serif;
background: #eef1f5;
}

.bottom-panel {
position: fixed;
left: 0;
top: calc(100vh - 100px);
width: 520px;
height: 100px;
box-sizing: border-box;
padding: 24px;
border-top: 6px solid #6b1111;
background: #d84444;
color: #fff;
font-size: 24px;
font-weight: 700;
}

.capture-target {
position: absolute;
left: 40px;
top: calc(100vh - 350px);
width: 360px;
height: 300px;
box-sizing: border-box;
border: 6px solid #183153;
background: #fff;
color: #183153;
overflow: hidden;
}

body.hide-capture-target .capture-target {
display: none;
}

.stripe {
height: 100px;
box-sizing: border-box;
padding: 30px;
font-size: 26px;
font-weight: 700;
}

.stripe-top {
background: #8ed2ff;
}

.stripe-middle {
background: #fff0a3;
}

.stripe-bottom {
background: #b7edc2;
}
</style>
</head>
<body>
<div class="bottom-panel">Safe area interference</div>

<main class="capture-target" data-testid="scroll-sensitive-popup">
<div class="stripe stripe-top">Popup top</div>
<div class="stripe stripe-middle">Popup middle</div>
<div class="stripe stripe-bottom">Popup bottom</div>
</main>

<script>
window.addEventListener("scroll", function () {
if (window.scrollY > 0) {
document.body.classList.add("hide-capture-target");
}
});
</script>
</body>
</html>
8 changes: 8 additions & 0 deletions test/e2e/tests/assert-view.testplane.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ describe("assertView", () => {
});
});

it("should use captured chunk when capture area disappears after scrolling", async ({ browser }) => {
await browser.url("capture-area-disappears-after-scroll.html");

await browser.assertView("scroll-sensitive-popup", "[data-testid=scroll-sensitive-popup]", {
captureElementFromTop: true,
});
});

it("should treat sticky content inside capture target as interference", async ({ browser }) => {
await browser.url("sticky-interference-behind-capture-target.html");

Expand Down
Loading