'use strict';
var util = require('util');
var logMethods = require('./log');
var type = require('./type');
var when = require('./when');
var Element = require('./element');
var GlobalTouch = require('./globalTouch');
var GlobalMouse = require('./globalMouse');
var Alert = require('./alert');
var Navigator = require('./navigator');
var Frame = require('./frame');
var WindowHandler = require('./window');
var Screenshot = require('./helpers/screenshot');
var fs = require('fs');
var path = require('path');
module.exports = ActiveWindow;
/**
* Active window object
*
* @constructor
* @class ActiveWindow
* @module WebDriver
* @uses WindowHandler
* @extends WindowHandler
* @submodule Navigation
* @param {Driver} driver
* @param {string} id
*/
function ActiveWindow (driver, id) {
this._driver = driver;
this._id = id;
}
util.inherits(ActiveWindow, WindowHandler);
/////////////////////
// Private Methods //
/////////////////////
/**
* Logs a method call by an event
*
* @param {object} event
* @method _logMethodCall
* @private
*/
ActiveWindow.prototype._logMethodCall = function (event) {
event.target = 'ActiveWindow';
this._driver._logMethodCall(event);
};
////////////////////
// Public Methods //
////////////////////
/**
* Gets the driver object.
* Direct-access. No need to wait.
*
* @return {Driver}
*/
ActiveWindow.prototype.getDriver = function () {
return this._driver;
};
/**
* Execute a script on the browser and return the result.
*
* Source should be either a function body as a string or a function.
* Keep in mind that if it is a function it will not have access to
* any variables from the node.js process.
*
* @method execute
* @param {String|Function} script
* @param {Array} [args]
* @return {*}
*/
ActiveWindow.prototype.execute = function (script, args) {
type('script', script, 'Function|String');
type('args', args, 'Array.<Any>?');
return this._driver._requestJSON('POST', '/execute', { script: codeToString(script), args: args || [] });
};
/**
* Execute a script asynchronously on the browser.
*
* Source should be either a function body as a string or a function.
* Keep in mind that if it is a function it will not have access to
* any variables from the node.js process.
*
* @method asyncExecute
* @param {String|Function} script
* @param {Array} [args]
*/
ActiveWindow.prototype.asyncExecute = function (script, args) {
type('script', script, 'Function|String');
type('args', args, 'Array.<Any>?');
return this._driver._requestJSON('POST', '/execute_async', { script: codeToString(script), args: args || [] });
};
/**
* Type a string of characters into the browser
*
* Note: Modifier keys is kept between calls, so mouse interactions can be performed
* while modifier keys are depressed.
*
* @method sendKeys
* @param {String|Array.<String>} str
*/
ActiveWindow.prototype.sendKeys = function (str) {
type('str', str, 'String|Array.<String>');
return this._driver._requestJSON('POST', '/keys', { value: Array.isArray(str) ? str : [str] });
};
/**
* Take a screenshot of the current page
*
* @method takeScreenshot
* @param {Object} [options]
* @return {Buffer} Binary image buffer
* @deprecated
*/
ActiveWindow.prototype.takeScreenshot = function (options) {
return this.documentScreenshot(options);
};
/**
* Captures the complete document
*
* @method captureDocument
* @param {Object} [options]
* @param {int} [options.horizontalPadding=0] Padding of the document for adjustment
* @param {function} [options.eachFn] Will execute method on client before each screenshot is taken. First parameter is index of screenshot.
* @param {function} [options.completeFn] Will execute method on client after all screenshots are taken.
* @param {object[]|Element[]|string[]} [options.blockOuts] List of areas/elements that should be blocked-out
* @param {object} [options.blockOutColor=black] Color to be used for blocking-out areas {red, green, blue, alpha}
* @param {int} [options.wait=100] Wait in ms before each screenshot
* @return {Buffer} Binary image buffer
*/
ActiveWindow.prototype.captureDocument = function (options) {
options = options || {};
var screenshot = new Screenshot(this.getDriver());
options.context = this;
return screenshot.documentScreenshot(options);
};
/**
* Captures the complete document
*
* @method documentScreenshot
* @param {Object} [options]
* @param {int} [options.horizontalPadding=0] Padding of the document for adjustment
* @param {function} [options.eachFn] Will execute method on client before each screenshot is taken. First parameter is index of screenshot.
* @param {function} [options.completeFn] Will execute method on client after all screenshots are taken.
* @param {object[]|Element[]|string[]} [options.blockOuts] List of areas/elements that should be blocked-out
* @param {object} [options.blockOutColor=black] Color to be used for blocking-out areas {red, green, blue, alpha}
* @param {int} [options.wait=100] Wait in ms before each screenshot
* @return {Buffer} Binary image buffer
* @deprecated Use captureDocument.
*/
ActiveWindow.prototype.documentScreenshot = ActiveWindow.prototype.captureDocument;
/**
* Captures the currently visible view-port
*
* @method captureViewPort
* @param {Object} [options]
* @param {int} [options.horizontalPadding=0] Padding of the document for adjustment
* @param {function} [options.eachFn] Will execute method on client before each screenshot is taken. First parameter is index of screenshot.
* @param {function} [options.completeFn] Will execute method on client after all screenshots are taken.
* @param {object[]|Element[]|string[]} [options.blockOuts] List of areas/elements that should be blocked-out
* @param {object} [options.blockOutColor=black] Color to be used for blocking-out areas {red, green, blue, alpha}
* @param {int} [options.wait=100] Wait in ms before each screenshot
* @return {Buffer} Binary image buffer
*/
ActiveWindow.prototype.captureViewPort = function (options) {
options = options || {};
var screenshot = new Screenshot(this.getDriver());
options.context = this;
return screenshot.viewPortScreenshot(options);
};
/**
* Captures the currently visible view-port
*
* @method viewPortScreenshot
* @param {Object} [options]
* @param {int} [options.horizontalPadding=0] Padding of the document for adjustment
* @param {function} [options.eachFn] Will execute method on client before each screenshot is taken. First parameter is index of screenshot.
* @param {function} [options.completeFn] Will execute method on client after all screenshots are taken.
* @param {object[]|Element[]|string[]} [options.blockOuts] List of areas/elements that should be blocked-out
* @param {object} [options.blockOutColor=black] Color to be used for blocking-out areas {red, green, blue, alpha}
* @param {int} [options.wait=100] Wait in ms before each screenshot
* @return {Buffer} Binary image buffer
* @deprecated Use captureViewPort.
*/
ActiveWindow.prototype.viewPortScreenshot = ActiveWindow.prototype.captureViewPort;
/**
* Captures a specific area of the document
*
* @method captureArea
* @param {int} [x=0] X-coordinate for area
* @param {int} [y=0] Y-coordinate for area
* @param {int} [width=document.width-x] Width of area to be captured
* @param {int} [height=document.height-y] Height of area to be captured
* @param {Object} [options]
* @param {int} [options.horizontalPadding=0] Padding of the document for adjustment
* @param {function} [options.eachFn] Will execute method on client before each screenshot is taken. First parameter is index of screenshot.
* @param {function} [options.completeFn] Will execute method on client after all screenshots are taken.
* @param {object[]|Element[]|string[]} [options.blockOuts] List of areas/elements that should be blocked-out
* @param {object} [options.blockOutColor=black] Color to be used for blocking-out areas {red, green, blue, alpha}
* @param {int} [options.wait=100] Wait in ms before each screenshot
* @return {Buffer} Binary image buffer
*/
ActiveWindow.prototype.captureArea = function (x, y, width, height, options) {
options = options || {};
var screenshot = new Screenshot(this.getDriver());
options.context = this;
return screenshot.areaScreenshot(x, y, width, height, options);
};
/**
* Captures a specific area of the document
*
* @method areaScreenshot
* @param {int} [x=0] X-coordinate for area
* @param {int} [y=0] Y-coordinate for area
* @param {int} [width=document.width-x] Width of area to be captured
* @param {int} [height=document.height-y] Height of area to be captured
* @param {Object} [options]
* @param {int} [options.horizontalPadding=0] Padding of the document for adjustment
* @param {function} [options.eachFn] Will execute method on client before each screenshot is taken. First parameter is index of screenshot.
* @param {function} [options.completeFn] Will execute method on client after all screenshots are taken.
* @param {object[]|Element[]|string[]} [options.blockOuts] List of areas/elements that should be blocked-out
* @param {object} [options.blockOutColor=black] Color to be used for blocking-out areas {red, green, blue, alpha}
* @param {int} [options.wait=100] Wait in ms before each screenshot
* @return {Buffer} Binary image buffer
* @deprecated Use captureArea.
*/
ActiveWindow.prototype.areaScreenshot = ActiveWindow.prototype.captureArea;
/**
* Take a screenshot of the current page and save to a file
*
* @method saveScreenshot
* @param {string} path Path where the file should be saved to
* @param {int} [options.horizontalPadding=0] Padding of the document for adjustment
* @param {function} [options.eachFn] Will execute method on client before each screenshot is taken. First parameter is index of screenshot.
* @param {function} [options.completeFn] Will execute method on client after all screenshots are taken.
* @param {object[]|Element[]|string[]} [options.blockOuts] List of areas/elements that should be blocked-out
* @param {object} [options.blockOutColor=black] Color to be used for blocking-out areas {red, green, blue, alpha}
* @param {int} [options.wait=100] Wait in ms before each screenshot
* @param {Object} [options]
*/
ActiveWindow.prototype.saveScreenshot = function (path, options) {
return when(this.captureDocument(options), function (buffer) {
if (this._driver.isSync()) {
fs.writeFileSync(path, buffer);
} else {
return new Promise(function (resolve, reject) {
fs.writeFile(path, buffer, function (err) {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
}.bind(this));
};
/**
* Compares the document with a previous screenshot, showing differences between them
*
* @method compareDocument
* @param {string} title Unique title for comparison
* @param {object} [options]
* @param {int} [options.horizontalPadding=0] Padding of the document for adjustment
* @param {function} [options.eachFn] Will execute method on client before each screenshot is taken. First parameter is index of screenshot.
* @param {function} [options.completeFn] Will execute method on client after all screenshots are taken.
* @param {object[]|Element[]|string[]} [options.blockOuts] List of areas/elements that should be blocked-out
* @param {object} [options.blockOutColor=black] Color to be used for blocking-out areas {red, green, blue, alpha}
* @param {int} [options.wait=100] Wait in ms before each screenshot
* @param {object} [options.compare] Options for the comparison. See Blink-Diff documentation.
* @param {object} [options.compare.id=1] Additional identifier to differentiate comparisons even more.
* @return {boolean|null} Are the screenshots the same? (NULL if there is nothing to compare to)
*/
ActiveWindow.prototype.compareDocument = function (title, options) {
options = options || {};
return when(this.captureDocument(options), function (buffer) {
return this.getDriver()._comparison.compare(title, buffer, options.compare);
}.bind(this));
};
/**
* Compares the view-port with a previous screenshot, showing differences between them
*
* @method compareViewPort
* @param {string} title Unique title for comparison
* @param {object} [options]
* @param {int} [options.horizontalPadding=0] Padding of the document for adjustment
* @param {function} [options.eachFn] Will execute method on client before each screenshot is taken. First parameter is index of screenshot.
* @param {function} [options.completeFn] Will execute method on client after all screenshots are taken.
* @param {object[]|Element[]|string[]} [options.blockOuts] List of areas/elements that should be blocked-out
* @param {object} [options.blockOutColor=black] Color to be used for blocking-out areas {red, green, blue, alpha}
* @param {int} [options.wait=100] Wait in ms before each screenshot
* @param {object} [options.compare] Options for the comparison. See Blink-Diff documentation.
* @param {object} [options.compare.id=1] Additional identifier to differentiate comparisons even more.
* @return {boolean|null} Are the screenshots the same? (NULL if there is nothing to compare to)
*/
ActiveWindow.prototype.compareViewPort = function (title, options) {
options = options || {};
return when(this.captureViewPort(options), function (buffer) {
return this.getDriver()._comparison.compare(title, buffer, options.compare);
}.bind(this));
};
/**
* Compares an area of the document with a previous screenshot, showing differences between them
*
* @method compareArea
* @param {string} title Unique title for comparison
* @param {int} [x=0] X-coordinate for area
* @param {int} [y=0] Y-coordinate for area
* @param {int} [width=document.width-x] Width of area to be captured
* @param {int} [height=document.height-y] Height of area to be captured
* @param {object} [options]
* @param {int} [options.horizontalPadding=0] Padding of the document for adjustment
* @param {function} [options.eachFn] Will execute method on client before each screenshot is taken. First parameter is index of screenshot.
* @param {function} [options.completeFn] Will execute method on client after all screenshots are taken.
* @param {object[]|Element[]|string[]} [options.blockOuts] List of areas/elements that should be blocked-out
* @param {object} [options.blockOutColor=black] Color to be used for blocking-out areas {red, green, blue, alpha}
* @param {int} [options.wait=100] Wait in ms before each screenshot
* @param {object} [options.compare] Options for the comparison. See Blink-Diff documentation.
* @param {object} [options.compare.id=1] Additional identifier to differentiate comparisons even more.
* @return {boolean|null} Are the screenshots the same? (NULL if there is nothing to compare to)
*/
ActiveWindow.prototype.compareArea = function (title, x, y, width, height, options) {
options = options || {};
return when(this.captureArea(x, y, width, height, options), function (buffer) {
return this.getDriver()._comparison.compare(title, buffer, options.compare);
}.bind(this));
};
/**
* Get the element on the page that currently has focus
*
* @method getActiveElement
* @return {Element}
*/
ActiveWindow.prototype.getActiveElement = function () {
return when(this._driver._requestJSON('POST', '/element/active'), function (element) {
return new Element(this._driver, this._driver.browser(), '<active>', element);
}.bind(this));
};
/**
* Get an element via a selector.
* Will throw an error if the element does not exist.
*
* @method getElement
* @param {String} selector
* @param {String} [selectorType='css selector']
* @return {Element}
*/
ActiveWindow.prototype.getElement = function (selector, selectorType) {
type('selector', selector, 'String');
type('selectorType', selectorType, 'String?');
return when(this._driver._requestJSON('POST', '/element', {
using: selectorType || Element.SELECTOR_CSS,
value: selector
}), function (element) {
return new Element(this._driver, this._driver.browser(), selector, element);
}.bind(this));
};
/**
* Get elements via a selector.
*
* @method getElements
* @param {String} selector
* @param {String} [selectorType='css selector']
* @return {Array.<Element>}
*/
ActiveWindow.prototype.getElements = function (selector, selectorType) {
type('selector', selector, 'String');
type('selectorType', selectorType, 'String?');
return when(this._driver._requestJSON('POST', '/elements', {
using: selectorType || Element.SELECTOR_CSS,
value: selector
}), function (elements) {
return elements.map(function (element) {
return new Element(this._driver, this._driver.browser(), selector, element);
}.bind(this));
}.bind(this));
};
/**
* Does a specific element exist?
*
* @method hasElement
* @param {String} selector
* @param {String} [selectorType='css selector']
* @return {boolean}
*/
ActiveWindow.prototype.hasElement = function (selector, selectorType) {
return when(this.getElements(selector, selectorType), function (elements) {
return (elements.length > 0);
});
};
/**
* Close the current window
*
* @method close
*/
ActiveWindow.prototype.close = function () {
return this._driver._requestJSON('DELETE', '/window');
};
/**
* Get the current page title
*
* @method getTitle
* @return {String}
*/
ActiveWindow.prototype.getTitle = function () {
return this._driver._requestJSON('GET', '/title');
};
/**
* Get the current page source
*
* @method getSource
* @return {String}
*/
ActiveWindow.prototype.getSource = function () {
return this._driver._requestJSON('GET', '/source');
};
/**
* Get the global-touch object.
* Direct-access. No need to wait.
*
* @method touch
* @return {GlobalTouch}
*/
ActiveWindow.prototype.touch = function () {
return new GlobalTouch(this._driver);
};
/**
* Get the global-mouse object.
* Direct-access. No need to wait.
*
* @method mouse
* @return {GlobalMouse}
*/
ActiveWindow.prototype.mouse = function () {
return new GlobalMouse(this._driver);
};
/**
* Get the Navigator object.
* Direct-access. No need to wait.
*
* @method navigator
* @return {Navigator}
*/
ActiveWindow.prototype.navigator = function () {
return new Navigator(this._driver);
};
/**
* Get the Frame object.
* Direct-access. No need to wait.
*
* @method frame
* @return {Frame}
*/
ActiveWindow.prototype.frame = function () {
return new Frame(this._driver);
};
/**
* Get the Alert object.
* Direct-access. No need to wait.
*
* @method alert
* @return {Alert}
*/
ActiveWindow.prototype.alert = function () {
return new Alert(this._driver);
};
/**
* Gets the scroll coordinates
*
* @method getScrollPosition
* @return {Object} Position as { x:<Number>, y:<Number> }
*/
ActiveWindow.prototype.getScrollPosition = function () {
return this.execute(function () {
return {
x: window.pageXOffset || document.body.scrollLeft,
y: window.pageYOffset || document.body.scrollTop
};
});
};
/**
* Scrolls to a specific coordinate
*
* @method scrollTo
* @param {Number} [x=0]
* @param {Number} [y=0]
*/
ActiveWindow.prototype.scrollTo = function (x, y) {
return this.execute(function (x, y) {
window.scrollTo(x || 0, y || 0);
}, [x, y]);
};
/**
* Scrolls by a specific coordinate, relative to the current position
*
* @method scrollBy
* @param {Number} [x=0]
* @param {Number} [y=0]
*/
ActiveWindow.prototype.scrollBy = function (x, y) {
return this.execute(function (x, y) {
window.scrollBy(x || 0, y || 0);
}, [x, y]);
};
logMethods(ActiveWindow.prototype);
///////////////
// Utilities //
///////////////
/**
* Convert code to string before execution
*
* @param {String|Function} code
* @return {String}
*/
function codeToString (code) {
if (typeof code === 'function') {
code = 'return (' + code + ').apply(null, arguments);';
}
return code;
}