API Docs for: 0.2.7
Show:

File: lib/helpers/stitching.js

'use strict';

var logMethods = require('../log');
var type = require('../type');
var when = require('../when');

var stitchingScripts = require('../scripts/stitching');

var PNGImage = require('pngjs-image');

module.exports = Stitching;

/**
 * Stitching object
 *
 * @constructor
 * @class Stitching
 * @module WebDriver
 * @submodule Helpers
 * @param {Driver} driver
 */
function Stitching (driver) {
    this._driver = driver;
}


/////////////////////
// Private Methods //
/////////////////////

/**
 * Logs a method call by an event
 *
 * @param {object} event
 * @method _logMethodCall
 * @private
 */
Stitching.prototype._logMethodCall = function (event) {
    event.target = 'Stitching';
    this._driver._logMethodCall(event);
};


/**
 * Performs a context dependent JSON request for the current session.
 * The result is parsed for errors.
 *
 * @method _requestJSON
 * @private
 * @param {String} method
 * @param {String} path
 * @param {*} [body]
 * @return {*}
 */
Stitching.prototype._requestJSON = function (method, path, body) {
    return this._driver._requestJSON(method, path, body);
};


////////////////////
// Public Methods //
////////////////////

/**
 * Gets the driver object.
 * Direct-access. No need to wait.
 *
 * @return {Driver}
 */
Stitching.prototype.getDriver = function () {
    return this._driver;
};


/**
 * Is stitching needed for browser?
 *
 * Some browsers (i.e. Chrome) do only take a screenshot of the view-port instead of the whole document as per standard.
 * This information will then be cached for each driver instance, determining this only once per instance.
 *
 * @method doesNeedStitching
 * @param {object} [options]
 * @param {int} [options.horizontalPadding=0] Padding of the document
 * @return {number}
 */
Stitching.prototype.doesNeedStitching = function (options) {
    var needsStitching = this.getDriver().getValue('needsStitching');

    if (needsStitching === null) {
        return when(this._determineNeedsStitching(options), function (value) {
            this.getDriver().setValue('needsStitching', value); // Cache value
            return value;
        }.bind(this));
    } else {
        return this.getDriver().utils().resolve(needsStitching);
    }
};

/**
 * Determines if the browser needs stitching due to the fact of taking only view-port screenshots instead of
 * whole document ones.
 *
 * Note:
 * ```horizontalPadding``` is any element that extents horizontally
 * outside of the document, i.e. negative absolute position.
 *
 * @method _determineNeedsStitching
 * @param {object} [options]
 * @param {int} [options.horizontalPadding=0] Padding of the document
 * @return {boolean}
 * @private
 */
Stitching.prototype._determineNeedsStitching = function (options) {

	var Screenshot = require('./screenshot'),
		screenshot = new Screenshot(this.getDriver()),

		DevicePixelRatio = require('./devicePixelRatio'),
		devicePixelRatio = new DevicePixelRatio(this.getDriver());

	options = options || {};

	// Determine device-pixel-ratio
    return when(devicePixelRatio.getDevicePixelRatio(options), function (devicePixelRatio) {

        // Reduce size of document to get a small screenshot for stitching determination
        return when(this._execute(stitchingScripts.init, [options.horizontalPadding || 0]), function (initData) {
            initData = JSON.parse(initData);

            // Take screenshot (hopefully very small, but big enough to get the stitching info)
            return when(screenshot.takeProcessedScreenshot(), function (image) {

                // Revert document size
                return when(this._execute(stitchingScripts.revert, [initData]), function () {

                    return this._needsStitchingExceptions(image, initData, devicePixelRatio);

                }.bind(this));
            }.bind(this));
        }.bind(this));
    }.bind(this));
};

/**
 * Processes the exception handling of stitching determination
 *
 * Note:
 *   Overwrite this method if you want to change the stitching behavior of browsers.
 *
 * @param {PNGImage} image
 * @param {object} initData
 * @param {number} devicePixelRatio
 * @returns {boolean}
 * @private
 */
Stitching.prototype._needsStitchingExceptions = function (image, initData, devicePixelRatio) {

    // If the screenshot width and the actual view-port width are pretty much the same (here 10px),
    // even though the document is twice the view-port size, then we need stitching.
    var expectedViewPortWidth = initData.viewPortWidth * 2 * devicePixelRatio,
        actualViewPortWidth = image.getWidth(),
        difference = actualViewPortWidth - expectedViewPortWidth,
        delta = Math.abs(difference),
        needsStitching,

        browserName = this.getDriver().browserName().toLowerCase(),
        browserVersion = this.getDriver().browserVersion();

    // When actual is smaller than expected, then needs stitching
    // When actual is bigger than expected and delta is not off for more than 2% (border issues), then no stitching needed
    needsStitching = (difference <= 0) && (delta >= expectedViewPortWidth * 0.2);

    if (!needsStitching) { // Exceptions
        if ((browserName === 'internet explorer') && (browserVersion >= 10)) {
            needsStitching = true;
        }
    }

    return needsStitching
};


/**
 * Executes a script in the browser and returns the result.
 *
 * This is a convenience method for accessing the execute method.
 *
 * @method _execute
 * @param {String|Function} script
 * @param {Array} [args]
 * @return {*}
 * @private
 */
Stitching.prototype._execute = function (script, args) {
    if (script) {
        return this.getDriver().browser().activeWindow().execute(script, args);

    } else { // Ignore script if there is nothing - might happen with screenshot requests
        return this.getDriver().utils().resolve(undefined);
    }
};

logMethods(Stitching.prototype);