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

Responsive snapshot capture + slow scrolling for CS #594

Merged
merged 12 commits into from
Dec 20, 2024
49 changes: 47 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const ENV_INFO = `${seleniumPkg.name}/${seleniumPkg.version}`;
const utils = require('@percy/sdk-utils');
const { DriverMetadata } = require('./driverMetadata');
const log = utils.logger('selenium-webdriver');
const CS_MAX_SCREENSHOT_LIMIT = 25000;
const SCROLL_DEFAULT_SLEEP_TIME = 0.45; // 450ms

const getWidthsForMultiDOM = (userPassedWidths, eligibleWidths) => {
// Deep copy of eligible mobile widths
Expand All @@ -31,7 +33,7 @@ const getWidthsForMultiDOM = (userPassedWidths, eligibleWidths) => {
async function changeWindowDimensionAndWait(driver, width, height, resizeCount) {
try {
const caps = await driver.getCapabilities();
if (typeof driver?.sendDevToolsCommand === 'function' && caps.getBrowserName() === 'chrome') {
if (typeof driver?.sendDevToolsCommand === 'function' && caps.getBrowserName() === 'chrome' && process.env.PERCY_DISABLE_CDP_RESIZE !== 'true') {
await driver?.sendDevToolsCommand('Emulation.setDeviceMetricsOverride', {
height,
width,
Expand Down Expand Up @@ -67,17 +69,30 @@ async function captureResponsiveDOM(driver, options) {
// Setup the resizeCount listener if not present
/* istanbul ignore next: no instrumenting injected code */
await driver.executeScript('PercyDOM.waitForResize()');
let height = currentHeight;
if (process.env.PERCY_RESPONSIVE_CAPTURE_MIN_HEIGHT) {
height = await driver.executeScript(`return window.outerHeight - window.innerHeight + ${utils.percy?.config?.snapshot?.minHeight}`);
}
for (let width of widths) {
if (lastWindowWidth !== width) {
resizeCount++;
await changeWindowDimensionAndWait(driver, width, currentHeight, resizeCount);
await changeWindowDimensionAndWait(driver, width, height, resizeCount);
lastWindowWidth = width;
}

if (process.env.PERCY_RESPONSIVE_CAPTURE_RELOAD_PAGE) {
await driver.navigate().refresh();
await driver.executeScript(await utils.fetchPercyDOM());
}

if (process.env.RESPONSIVE_CAPTURE_SLEEP_TIME) {
await new Promise(resolve => setTimeout(resolve, parseInt(process.env.RESPONSIVE_CAPTURE_SLEEP_TIME) * 1000));
}

if (process.env.PERCY_ENABLE_LAZY_LOADING_SCROLL) {
await module.exports.slowScrollToBottom(driver);
}

let domSnapshot = await captureSerializedDOM(driver, options);
domSnapshot.width = width;
domSnapshots.push(domSnapshot);
Expand Down Expand Up @@ -252,3 +267,33 @@ module.exports.percyScreenshot = async function percyScreenshot(driver, name, op
module.exports.isPercyEnabled = async function isPercyEnabled() {
return await utils.isPercyEnabled();
};

module.exports.slowScrollToBottom = async (driver, scrollSleep = SCROLL_DEFAULT_SLEEP_TIME) => {
if (process.env.PERCY_LAZY_LOAD_SCROLL_TIME) {
scrollSleep = parseFloat(process.env.PERCY_LAZY_LOAD_SCROLL_TIME);
}

const scrollHeightCommand = 'return Math.max(document.body.scrollHeight, document.body.clientHeight, document.body.offsetHeight, document.documentElement.scrollHeight, document.documentElement.clientHeight, document.documentElement.offsetHeight);';
let scrollHeight = Math.min(await driver.executeScript(scrollHeightCommand), CS_MAX_SCREENSHOT_LIMIT);
const clientHeight = await driver.executeScript('return document.documentElement.clientHeight');
let current = 0;

let page = 1;
// Break the loop if maximum scroll height 25000px is reached
while (scrollHeight > current && current < CS_MAX_SCREENSHOT_LIMIT) {
current = clientHeight * page;
page += 1;
await driver.executeScript(`window.scrollTo(0, ${current})`);
await new Promise(resolve => setTimeout(resolve, scrollSleep * 1000));

// Recalculate scroll height for dynamically loaded pages
scrollHeight = await driver.executeScript(scrollHeightCommand);
}
// Get back to top
await driver.executeScript('window.scrollTo(0, 0)');
let sleepAfterScroll = 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

default sleep? variable?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is sleep after we get back to top

Copy link
Contributor Author

Choose a reason for hiding this comment

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

since we are reseting it based on env variable it is variable(let)

if (process.env.PERCY_SLEEP_AFTER_LAZY_LOAD_COMPLETE) {
sleepAfterScroll = parseFloat(process.env.PERCY_SLEEP_AFTER_LAZY_LOAD_COMPLETE);
}
await new Promise(resolve => setTimeout(resolve, sleepAfterScroll * 1000));
};
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@percy/selenium-webdriver",
"description": "Selenium client library for visual testing with Percy",
"version": "2.2.1-beta.0",
"version": "2.2.2-alpha.2",
"license": "MIT",
"author": "Perceptual Inc.",
"repository": "https://github.com/percy/percy-selenium-js",
Expand All @@ -12,7 +12,7 @@
],
"publishConfig": {
"access": "public",
"tag": "beta"
"tag": "alpha"
},
"main": "index.js",
"types": "types/index.d.ts",
Expand Down
79 changes: 77 additions & 2 deletions test/index.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import helpers from '@percy/sdk-utils/test/helpers';
import percySnapshot from '../index.js';
import utils from '@percy/sdk-utils';
import { Cache } from '../cache.js';
const { percyScreenshot } = percySnapshot;
const { percyScreenshot, slowScrollToBottom } = percySnapshot;

describe('percySnapshot', () => {
let driver;
Expand All @@ -14,8 +14,9 @@ describe('percySnapshot', () => {
.forBrowser('firefox').build();

mockedDriver = {
getCapabilities: jasmine.createSpy('sendDevToolsCommand').and.returnValue({ getBrowserName: () => 'chrome'}),
getCapabilities: jasmine.createSpy('sendDevToolsCommand').and.returnValue({ getBrowserName: () => 'chrome' }),
sendDevToolsCommand: jasmine.createSpy('sendDevToolsCommand').and.returnValue(Promise.resolve()),
navigate: jasmine.createSpy('navigate').and.returnValue({ refresh: jasmine.createSpy('refresh') }),
manage: jasmine.createSpy('manage').and.returnValue({
window: jasmine.createSpy('window').and.returnValue({
setRect: jasmine.createSpy('setRect').and.returnValue(Promise.resolve()),
Expand Down Expand Up @@ -187,6 +188,13 @@ describe('percySnapshot', () => {
expect(mockedDriver.executeScript).not.toHaveBeenCalledWith('return window.resizeCount');
});

it('should reload page if PERCY_RESPONSIVE_CAPTURE_RELOAD_PAGE is set', async () => {
process.env.PERCY_RESPONSIVE_CAPTURE_RELOAD_PAGE = true;
await percySnapshot(mockedDriver, 'Test Snapshot', { responsiveSnapshotCapture: true });
expect(mockedDriver.navigate().refresh).toHaveBeenCalled();
delete process.env.PERCY_RESPONSIVE_CAPTURE_RELOAD_PAGE;
});

it('should wait if RESPONSIVE_CAPTURE_SLEEP_TIME is set', async () => {
process.env.RESPONSIVE_CAPTURE_SLEEP_TIME = 1;
spyOn(global, 'setTimeout').and.callThrough();
Expand All @@ -197,6 +205,31 @@ describe('percySnapshot', () => {
delete process.env.RESPONSIVE_CAPTURE_SLEEP_TIME;
});

it('should scroll if PERCY_ENABLE_LAZY_LOADING_SCROLL is set', async () => {
process.env.PERCY_ENABLE_LAZY_LOADING_SCROLL = true;
const mockedScroll = spyOn(percySnapshot, 'slowScrollToBottom').and.resolveTo(true);
await percySnapshot(mockedDriver, 'Test Snapshot', { responsiveSnapshotCapture: true });

expect(mockedScroll).toHaveBeenCalledWith(mockedDriver);
delete process.env.PERCY_ENABLE_LAZY_LOADING_SCROLL;
});

it('should use minHeight if PERCY_RESPONSIVE_CAPTURE_MIN_HEIGHT is set', async () => {
process.env.PERCY_ENABLE_LAZY_LOADING_SCROLL = true;
process.env.PERCY_RESPONSIVE_CAPTURE_MIN_HEIGHT = true;
utils.percy.config = { snapshot: { minHeight: 10 } };

const mockedScroll = spyOn(percySnapshot, 'slowScrollToBottom').and.resolveTo(true);

mockedDriver.executeScript.calls.reset();
await percySnapshot(mockedDriver, 'Test Snapshot', { responsiveSnapshotCapture: true });

expect(mockedDriver.executeScript).toHaveBeenCalledTimes(4);
expect(mockedScroll).toHaveBeenCalledWith(mockedDriver);
delete process.env.PERCY_ENABLE_LAZY_LOADING_SCROLL;
delete process.env.PERCY_RESPONSIVE_CAPTURE_MIN_HEIGHT;
});

it('throw error in SDK if PERCY_RAISE_ERROR is true', async () => {
process.env.PERCY_RAISE_ERROR = 'true';
await helpers.test('error', '/percy/healthcheck');
Expand Down Expand Up @@ -229,6 +262,48 @@ describe('percySnapshot', () => {
});
});

describe('#slowScrollToBottom', () => {
let mockedDriver = { executeScript: jasmine.createSpy('executeScript') };
beforeEach(() => {
mockedDriver.executeScript.calls.reset();
});

it('should scroll to bottom and sleep after loading as set in env', async () => {
process.env.PERCY_SLEEP_AFTER_LAZY_LOAD_COMPLETE = 2;
mockedDriver.executeScript.and.returnValues(9, 5, true, 9, true, 9, true);
spyOn(global, 'setTimeout').and.callThrough();

await slowScrollToBottom(mockedDriver);
expect(setTimeout.calls.allArgs()).toEqual([[jasmine.any(Function), 450], [jasmine.any(Function), 450], [jasmine.any(Function), 2000]]);
expect(mockedDriver.executeScript).toHaveBeenCalledWith('window.scrollTo(0, 0)');
expect(mockedDriver.executeScript).toHaveBeenCalledTimes(7);
delete process.env.PERCY_SLEEP_AFTER_LAZY_LOAD_COMPLETE;
});

it('should scroll to bottom and sleep as set in env', async () => {
process.env.PERCY_LAZY_LOAD_SCROLL_TIME = '1.2';
mockedDriver.executeScript.and.returnValues(9, 5, true, 9, true, 9, true);
spyOn(global, 'setTimeout').and.callThrough();

await slowScrollToBottom(mockedDriver);
expect(setTimeout.calls.allArgs()).toEqual([[jasmine.any(Function), 1200], [jasmine.any(Function), 1200], [jasmine.any(Function), 1000]]);
expect(mockedDriver.executeScript).toHaveBeenCalledWith('window.scrollTo(0, 0)');
expect(mockedDriver.executeScript).toHaveBeenCalledTimes(7);
delete process.env.PERCY_LAZY_LOAD_SCROLL_TIME;
});

it('should scroll upto 25k px and sleep as passed in function', async () => {
mockedDriver.executeScript = jasmine.createSpy('executeScript');
mockedDriver.executeScript.and.returnValues(30000, 15000, true, 30000, true, 30000, true);
spyOn(global, 'setTimeout').and.callThrough();

await slowScrollToBottom(mockedDriver, 2);
expect(setTimeout.calls.allArgs()).toEqual([[jasmine.any(Function), 2000], [jasmine.any(Function), 2000], [jasmine.any(Function), 1000]]);
expect(mockedDriver.executeScript).toHaveBeenCalledWith('window.scrollTo(0, 0)');
expect(mockedDriver.executeScript).toHaveBeenCalledTimes(7);
});
});

describe('percyScreenshot', () => {
class Browser { // Mocking WDIO driver
constructor() {
Expand Down
Loading