Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I screenshot the full height of a mobile form factor?

When testing mobile form factors, Chrome screenshots just the visible window. I'm fine with that being the standard and expected behaviour. However, I additionally want to capture the full scrolled height of the page so I can inspect the rendering of the entire page.

I thought the simplest solution was to set the chrome window height to be a sufficiently large value, and job done. However, the Chrome window height seems bound by my physical screen height, ie. I set it to 5,000 with browser.manage().window().setSize(375,5000);, but it only resizes to a height of 1,200.

I already know [According to the WebDriver specification][1], the [takeScreenshot() function][2] is not supposed to capture the entire page, but should make a screenshot of the visible area only.

like image 429
pinoyyid Avatar asked Jan 05 '16 08:01

pinoyyid


1 Answers

OP EDIT: I went with the final option below which I've labelled "Working solution!!"

Below are the different grouped by type strategies to solve the problem.


Scroll, take screenshot, append

Quoting the author of the screenshot system at the CrossBrowserTesting.com:

As the author of the screenshot system for CrossBrowserTesting.com, I can tell you that the only way we've been able to get full page screenshots consistently and reliably across browsers is to scroll, capture, and append images.

Here is a sample working implementation of scrolling and taking visible area screenshots using cnn.com as an example target. Using scrollHeight, clientHeight and scrollTop to determine where we are on a vertical scroll position and how much more to scroll down. Since we are dealing with promises in a loop, we have to make a recursive function with a "we are at the bottom" base condition:

var fs = require('fs'),
    Utils = {
        screenShotDirectory: '',

        writeScreenShot: function (data, filename) {
            var stream = fs.createWriteStream(this.screenShotDirectory + filename);

            stream.write(new Buffer(data, 'base64'));
            stream.end();
        },

        getSizes: function () {
            return browser.executeScript("return {scrollHeight: document.body.scrollHeight, clientHeight: document.body.clientHeight, scrollTop: document.body.scrollTop};");
        },

        scrollToBottom: function (height, index) {
            var self = this;

            self.getSizes().then(function (data) {
                // continue only if we are not at the bottom of the page
                if (data.scrollTop + data.clientHeight < data.scrollHeight) {
                    browser.executeScript("window.scrollTo(0, arguments[0]);", height).then(function () {
                        browser.takeScreenshot().then(function (png) {
                            self.writeScreenShot(png, "test" + index + ".png");
                        });
                    });
                    self.scrollToBottom(height + data.clientHeight, index + 1);
                }
            });
        }
    };

describe("Scrolling and saving screenshots", function () {
    beforeEach(function () {
        browser.ignoreSynchronization = true;
        browser.get("http://www.cnn.com/");
    });

    it("should capture an entire page", function () {
        Utils.getSizes().then(function (data) {
            Utils.scrollToBottom(data.clientHeight * 2, 1);
        });
    });
});

It would produce multiple test<index>.png images that you can then glue together.

To concatenate images in a "single column image", you may, for instance, use the GraphicsMagick Image Processing System through the gm nodejs module. The .montage() method with the concatenate option in the 1x mode would be helpful. Sample code:

var gm = require('gm');

Utils.getSizes().then(function (data) {
    var index = Utils.scrollToBottom(data.clientHeight * 2, 1);

    var op = gm();
    for (var i = 1; i <= index; i++) {
        op = op.in("test" + i + ".png");
    }

    op = op.montage().mode("concatenate").tile("1x");
    
    op.write('output.png', function (err) {
        if (err) console.log(err);
    });
});

Change the Browser

In Chrome, you would always get only the visible area on the resulting screenshot, here is the relevant chromedriver issue with a lot of information about the issue and multiple workarounds:

  • ChromeDriver2 take screenshot is not full page

Somewhat surprisingly, it should though work in Firefox - switch to it if possible:

Chrome screenshots that take up the entire screen are not like Firefox's. Firefox will capture the entire screen, even parts of it that are not currently viewable. Chrome will not!


Tweak the Screen Size

Another option would be to use services like BrowserStack or SauceLabs to start your tests on a specific platform in a specific browser and, using a specific large enough resolution. Protractor supports Sauce Labs and BrowserStack out-of-the-box.

Example configuration for BrowserStack:

exports.config: {
  browserstackUser: "user",
  browserstackKey: "key",

  capabilities: {
    'browserstack.local': true,
    'browserstack.debug': true,
 
    browserName: "Chrome",
    os: "Windows",
    os_version: "8",
    resolution: "2048x1536"
  },
}

Then, maximize the browser window (inside onPrepare(), for instance):

browser.driver.manage().window().maximize();

And make a screenshot.


Working solution!!!

Another option could be to run tests in a Virtual Display. I you would follow this blogpost and use Xvfb, when you will run the Xvfb server, you may specify the resolution:

/usr/bin/Xvfb :99 -ac -screen 0 2048x6000x24 &

Also see related information on this topic here:

  • AngularJS Headless End to End Testing With Protractor and Selenium
  • What is a good headless browser to run with protractor?

You may also use the docker-selenium solution which allows to configure the screen size.

like image 187
alecxe Avatar answered Sep 28 '22 08:09

alecxe