Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core(full-page-screenshot): revise logic for determining dimensions #14920

Merged
merged 19 commits into from
Mar 8, 2024
Merged
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 cli/test/fixtures/scaled-content.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
</head>
<body style="margin: 0;">
<div style="height: 2000px; background-color: yellow;">
<h1>This content is not 1:1 with device pixels.</h1>
<h1 role="bad_role_so_this_is_reported">This content is not 1:1 with device pixels.</h1>
</div>
</body>
</html>
12 changes: 12 additions & 0 deletions cli/test/fixtures/scaled-overflow-content.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=0.5">
<title>Document</title>
</head>
<body style="overflow: scroll;">
<h1 role="bad_role_so_this_is_reported">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</h1>
</body>
</html>
2 changes: 2 additions & 0 deletions cli/test/smokehouse/core-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import formsAutoComplete from './test-definitions/forms-autocomplete.js';
import fpsMax from './test-definitions/fps-max.js';
import fpsMaxPassive from './test-definitions/fps-max-passive.js';
import fpsScaled from './test-definitions/fps-scaled.js';
import fpsOverflowX from './test-definitions/fps-overflow-x.js';
import issuesMixedContent from './test-definitions/issues-mixed-content.js';
import lanternFetch from './test-definitions/lantern-fetch.js';
import lanternIdleCallbackLong from './test-definitions/lantern-idle-callback-long.js';
Expand Down Expand Up @@ -80,6 +81,7 @@ const smokeTests = [
formsAutoComplete,
fpsMax,
fpsMaxPassive,
fpsOverflowX,
fpsScaled,
issuesMixedContent,
lanternFetch,
Expand Down
72 changes: 72 additions & 0 deletions cli/test/smokehouse/test-definitions/fps-overflow-x.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* @license Copyright 2023 The Lighthouse Authors. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/

/** @type {LH.Config} */
const config = {
extends: 'lighthouse:default',
settings: {
onlyCategories: ['performance'],
},
};

/**
* @type {Smokehouse.ExpectedRunnerResult}
*/
const expectations = {
artifacts: {
ViewportDimensions: {
innerWidth: 1325,
innerHeight: 2647,
outerWidth: 412,
outerHeight: 823,
devicePixelRatio: 1.75,
},
},
lhr: {
requestedUrl: 'http://localhost:10200/scaled-overflow-content.html',
finalDisplayedUrl: 'http://localhost:10200/scaled-overflow-content.html',
audits: {},
fullPageScreenshot: {
nodes: {
_includes: [
[
/-BODY$/,
{
top: 21,
bottom: 58,
left: 8,
right: 816,
width: 808,
height: 37,
},
],
[
/-H1$/,
{
top: 21,
bottom: 58,
left: 8,
right: 816,
width: 808,
height: 37,
},
],
],
},
screenshot: {
height: 1646,
width: 824,
},
},
},
};

export default {
id: 'fps-overflow-x',
expectations,
config,
};

11 changes: 11 additions & 0 deletions cli/test/smokehouse/test-definitions/fps-scaled.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ const expectations = {
height: 2000,
},
],
[
/-H1$/,
{
top: 0,
bottom: 37,
left: 0,
right: 824,
width: 824,
height: 37,
},
],
],
},
screenshot: {
Expand Down
47 changes: 19 additions & 28 deletions core/gather/gatherers/full-page-screenshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,6 @@ function getObservedDeviceMetrics() {
};
}

/**
* The screenshot dimensions are sized to `window.outerHeight` / `window.outerWidth`,
* however the bounding boxes of the elements are relative to `window.innerHeight` / `window.innerWidth`.
*/
function getScreenshotAreaSize() {
return {
width: window.innerWidth,
height: window.innerHeight,
};
}

function waitForDoubleRaf() {
return new Promise((resolve) => {
requestAnimationFrame(() => requestAnimationFrame(resolve));
Expand All @@ -72,6 +61,21 @@ class FullPageScreenshot extends BaseGatherer {
supportedModes: ['snapshot', 'timespan', 'navigation'],
};

/**
* @param {LH.Gatherer.Context} context
*/
waitForNetworkIdle(context) {
const session = context.driver.defaultSession;
const networkMonitor = context.driver.networkMonitor;
return waitForNetworkIdle(session, networkMonitor, {
pretendDCLAlreadyFired: true,
networkQuietThresholdMs: 1000,
busyEvent: 'network-critical-busy',
idleEvent: 'network-critical-idle',
isIdle: recorder => recorder.isCriticalIdle(),
});
}

/**
* @param {LH.Gatherer.Context} context
* @param {{height: number, width: number, mobile: boolean}} deviceMetrics
Expand All @@ -89,15 +93,7 @@ class FullPageScreenshot extends BaseGatherer {
);
const height = Math.min(fullHeight, MAX_WEBP_SIZE);

// Setup network monitor before we change the viewport.
const networkMonitor = context.driver.networkMonitor;
const waitForNetworkIdleResult = waitForNetworkIdle(session, networkMonitor, {
pretendDCLAlreadyFired: true,
networkQuietThresholdMs: 1000,
busyEvent: 'network-critical-busy',
idleEvent: 'network-critical-idle',
isIdle: recorder => recorder.isCriticalIdle(),
});
const waitForNetworkIdleResult = this.waitForNetworkIdle(context);

await session.sendCommand('Emulation.setDeviceMetricsOverride', {
mobile: deviceMetrics.mobile,
Expand All @@ -123,22 +119,17 @@ class FullPageScreenshot extends BaseGatherer {
* @return {Promise<LH.Result.FullPageScreenshot['screenshot']>}
*/
async _takeScreenshot(context) {
const metrics = await context.driver.defaultSession.sendCommand('Page.getLayoutMetrics');
const result = await context.driver.defaultSession.sendCommand('Page.captureScreenshot', {
format: FULL_PAGE_SCREENSHOT_FORMAT,
quality: FULL_PAGE_SCREENSHOT_QUALITY,
});
const data = `data:image/${FULL_PAGE_SCREENSHOT_FORMAT};base64,` + result.data;

const screenshotAreaSize =
await context.driver.executionContext.evaluate(getScreenshotAreaSize, {
args: [],
useIsolation: true,
});

return {
data,
width: screenshotAreaSize.width,
height: screenshotAreaSize.height,
width: metrics.cssVisualViewport.clientWidth,
height: metrics.cssVisualViewport.clientHeight,
};
}

Expand Down
38 changes: 21 additions & 17 deletions core/test/gather/gatherers/full-page-screenshot-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {getChromePath} from 'chrome-launcher';
import * as LH from '../../../../types/lh.js';
import {createMockContext} from '../../gather/mock-driver.js';
import FullPageScreenshotGatherer from '../../../gather/gatherers/full-page-screenshot.js';
import {fnAny} from '../../test-utils.js';
import lighthouse from '../../../index.js';
import {Server} from '../../../../cli/test/fixtures/static-server.js';

Expand All @@ -17,23 +18,36 @@ let contentSize;
/** @type {{width?: number, height?: number, dpr: number}} */
let screenSize;
/** @type {{width?: number, height?: number}} */
let screenshotAreaSize;
let screenshotSize;
/** @type {string[]} */
let screenshotData;
let mockContext = createMockContext();
let fpsGatherer = new FullPageScreenshotGatherer();

beforeEach(() => {
fpsGatherer = new FullPageScreenshotGatherer();

// Prevent `waitForNetworkIdle` from stalling the tests
fpsGatherer.waitForNetworkIdle = fnAny().mockImplementation(() => ({
promise: Promise.resolve(),
cancel: fnAny(),
}));

contentSize = {width: 100, height: 100};
screenSize = {width: 100, height: 100, dpr: 1};
screenshotAreaSize = contentSize;
screenshotSize = contentSize;
screenshotData = [];
mockContext = createMockContext();
mockContext.driver.defaultSession.sendCommand.mockImplementation((method) => {
if (method === 'Page.getLayoutMetrics') {
return {
cssContentSize: contentSize,
// See comment within _takeScreenshot() implementation
// This is only accessed on the first call to Page.getLayoutMetrics
// At that time the width and height should match the screen size.
cssLayoutViewport: {clientWidth: screenSize.width, clientHeight: screenSize.height},
// This is only accessed on the second call to Page.getLayoutMetrics
// At that time the width and height should match the screenshot size.
cssVisualViewport: {clientWidth: screenshotSize.width, clientHeight: screenshotSize.height},
};
}
if (method === 'Page.captureScreenshot') {
Expand All @@ -55,11 +69,6 @@ beforeEach(() => {
},
deviceScaleFactor: screenSize.dpr,
};
} else if (fn.name === 'getScreenshotAreaSize') {
return {
width: screenshotAreaSize.width,
height: screenshotAreaSize.height,
};
} else if (fn.name === 'waitForDoubleRaf') {
return {};
} else {
Expand All @@ -70,10 +79,9 @@ beforeEach(() => {

describe('FullPageScreenshot gatherer', () => {
it('captures a full-page screenshot', async () => {
const fpsGatherer = new FullPageScreenshotGatherer();
contentSize = {width: 412, height: 2000};
screenSize = {width: 412, height: 412};
screenshotAreaSize = contentSize;
screenshotSize = contentSize;

mockContext.settings = {
...mockContext.settings,
Expand All @@ -99,10 +107,9 @@ describe('FullPageScreenshot gatherer', () => {
});

it('resets the emulation correctly when Lighthouse controls it', async () => {
const fpsGatherer = new FullPageScreenshotGatherer();
contentSize = {width: 412, height: 2000};
screenSize = {width: 412, height: 412};
screenshotAreaSize = contentSize;
screenshotSize = contentSize;

mockContext.settings = {
...mockContext.settings,
Expand Down Expand Up @@ -133,10 +140,9 @@ describe('FullPageScreenshot gatherer', () => {
});

it('resets the emulation correctly when Lighthouse does not control it', async () => {
const fpsGatherer = new FullPageScreenshotGatherer();
contentSize = {width: 500, height: 1500};
screenSize = {width: 500, height: 500, dpr: 2};
screenshotAreaSize = contentSize;
screenshotSize = contentSize;
mockContext.settings = {
...mockContext.settings,
screenEmulation: {
Expand Down Expand Up @@ -179,11 +185,9 @@ describe('FullPageScreenshot gatherer', () => {
});

it('limits the screenshot height to the max Chrome can capture', async () => {
const fpsGatherer = new FullPageScreenshotGatherer();

contentSize = {width: 412, height: 100000};
screenSize = {width: 412, height: 412, dpr: 1};
screenshotAreaSize = contentSize;
screenshotSize = contentSize;
mockContext.settings = {
...mockContext.settings,
formFactor: 'mobile',
Expand Down
Loading