Skip to content

Commit

Permalink
Responsive snapshot capture + slow scrolling for CS (#594)
Browse files Browse the repository at this point in the history
* Reponsive snapshot capture + slow scrolling for CS

* Release in alpha

* Add PERCY_ before every env variable

* Change env variable names

* move sleep time to constant

* use constant variable instead of 25k

* Added tests

* Fix test

* Add reload in responsive snapshot capture + tests

* Release 2.2.2-alpha.1

* Disabling CDP Resize

* release v2.2.2-alpha.2

---------

Co-authored-by: amit3200 <[email protected]>
  • Loading branch information
chinmay-browserstack and Amit3200 authored Dec 20, 2024
1 parent d74c48d commit 612e745
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 6 deletions.
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;
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

0 comments on commit 612e745

Please sign in to comment.