UNPKG

eyes.selenium.v68patch

Version:

Applitools Eyes SDK For Selenium JavaScript WebDriver

419 lines (355 loc) 18.9 kB
(function () { 'use strict'; var EyesSDK = require('eyes.sdk'), EyesUtils = require('eyes.utils'), ScrollPositionProvider = require('./ScrollPositionProvider'), FrameChain = require('./FrameChain'), Frame = require('./Frame'); var EyesScreenshot = EyesSDK.EyesScreenshot, CoordinatesType = EyesSDK.CoordinatesType, ArgumentGuard = EyesUtils.ArgumentGuard, GeneralUtils = EyesUtils.GeneralUtils, GeometryUtils = EyesUtils.GeometryUtils; /** * @readonly * @enum {number} */ var ScreenshotType = { VIEWPORT: 1, ENTIRE_FRAME: 2 }; /** * * @param {Object} logger * @param {FrameChain} frameChain * @param {ScreenshotType} screenshotType * @returns {{x: number, y: number}} */ var calcFrameLocationInScreenshot = function (logger, frameChain, screenshotType) { logger.verbose("Getting first frame.."); var firstFrame = frameChain.getFrame(0); logger.verbose("Done!"); var locationInScreenshot = GeometryUtils.createLocationFromLocation(firstFrame.getLocation()); // We only consider scroll of the default content if this is a viewport screenshot. if (screenshotType == ScreenshotType.VIEWPORT) { var defaultContentScroll = firstFrame.getParentScrollPosition(); locationInScreenshot = GeometryUtils.locationOffset(locationInScreenshot, defaultContentScroll); } logger.verbose("Iterating over frames.."); var frame; for (var i = 1, l = frameChain.size(); i < l; ++i) { logger.verbose("Getting next frame..."); frame = frameChain.getFrames()[i]; logger.verbose("Done!"); var frameLocation = frame.getLocation(); // For inner frames we must consider the scroll var frameParentScrollPosition = frame.getParentScrollPosition(); // Offsetting the location in the screenshot locationInScreenshot = GeometryUtils.locationOffset(locationInScreenshot, { x: frameLocation.x - frameParentScrollPosition.x, y: frameLocation.y - frameParentScrollPosition.y }); } logger.verbose("Done!"); return locationInScreenshot; }; /** * @param {Object} logger A Logger instance. * @param {EyesWebDriver} driver The web driver used to get the screenshot. * @param {Object} image The actual screenshot image. * @param {Object} promiseFactory * @augments EyesScreenshot * @constructor */ function EyesWebDriverScreenshot(logger, driver, image, promiseFactory) { EyesScreenshot.call(this, image); ArgumentGuard.notNull(logger, "logger"); ArgumentGuard.notNull(driver, "driver"); this._logger = logger; this._driver = driver; this._image = image; this._promiseFactory = promiseFactory; this._frameChain = driver.getFrameChain(); EyesScreenshot.call(this._image); } EyesWebDriverScreenshot.prototype = new EyesScreenshot(); EyesWebDriverScreenshot.prototype.constructor = EyesWebDriverScreenshot; /** * @param {ScreenshotType} [screenshotType] The screenshot's type (e.g., viewport/full page). * @param {{x: number, y: number}} [frameLocationInScreenshot] The current frame's location in the screenshot. * @param {{width: number, height: number}} [frameSize] The full internal size of the frame. * @returns {Promise<void>} */ EyesWebDriverScreenshot.prototype.buildScreenshot = function (screenshotType, frameLocationInScreenshot, frameSize) { var that = this, viewportSize, imageSize; var positionProvider = new ScrollPositionProvider(this._logger, this._driver, this._promiseFactory); return this._driver.getDefaultContentViewportSize(false).then(function (vs) { viewportSize = vs; return that._image.getSize(); }).then(function (is) { imageSize = is; return positionProvider.getEntireSize(); }).then(function (ppEs) { // If we're inside a frame, then the frame size is given by the frame // chain. Otherwise, it's the size of the entire page. if (!frameSize) { if (that._frameChain.size() !== 0) { frameSize = that._frameChain.getCurrentFrameSize(); } else { // get entire page size might throw an exception for applications // which don't support Javascript (e.g., Appium). In that case // we'll use the viewport size as the frame's size. if (ppEs) { frameSize = ppEs; } else { frameSize = viewportSize; } } } return positionProvider.getCurrentPosition(); }).then(function (ppCp) { // Getting the scroll position. For native Appium apps we can't get the scroll position, so we use (0,0) if (ppCp) { that._currentFrameScrollPosition = ppCp; } else { that._currentFrameScrollPosition = GeometryUtils.createLocation(0, 0); } if (screenshotType == null) { if (imageSize.width <= viewportSize.width && imageSize.height <= viewportSize.height) { screenshotType = ScreenshotType.VIEWPORT; } else { screenshotType = ScreenshotType.ENTIRE_FRAME; } } that._screenshotType = screenshotType; // This is used for frame related calculations. if (frameLocationInScreenshot == null) { if (that._frameChain.size() > 0) { frameLocationInScreenshot = calcFrameLocationInScreenshot(that._logger, that._frameChain, that._screenshotType); } else { frameLocationInScreenshot = GeometryUtils.createLocation(0, 0); } } that._frameLocationInScreenshot = frameLocationInScreenshot; that._logger.verbose("Calculating frame window.."); that._frameWindow = GeometryUtils.createRegionFromLocationAndSize(frameLocationInScreenshot, frameSize); GeometryUtils.intersect(that._frameWindow, GeometryUtils.createRegion(0, 0, imageSize.width, imageSize.height)); if (that._frameWindow.width <= 0 || that._frameWindow.height <= 0) { throw new Error("Got empty frame window for screenshot!"); } that._logger.verbose("EyesWebDriverScreenshot - Done!"); }); }; /** * @return {{left: number, top: number, width: number, height: number}} The region of the frame which is available in the screenshot, * in screenshot coordinates. */ EyesWebDriverScreenshot.prototype.getFrameWindow = function () { return this._frameWindow; }; /** * @return {FrameChain} A copy of the frame chain which was available when the * screenshot was created. */ EyesWebDriverScreenshot.prototype.getFrameChain = function () { return new FrameChain(this._logger, this._frameWindow); }; //noinspection JSUnusedGlobalSymbols /** * Returns a part of the screenshot based on the given region. * * @param {{left: number, top: number, width: number, height: number}} region The region for which we should get the sub screenshot. * @param {CoordinatesType} coordinatesType How should the region be calculated on the screenshot image. * @param {boolean} throwIfClipped Throw an EyesException if the region is not fully contained in the screenshot. * @return {Promise<EyesWebDriverScreenshot>} A screenshot instance containing the given region. */ EyesWebDriverScreenshot.prototype.convertLocationFromRegion = function (region, coordinatesType, throwIfClipped) { this._logger.verbose("getSubScreenshot(", region, ", ", coordinatesType, ", ", throwIfClipped, ")"); ArgumentGuard.notNull(region, "region"); ArgumentGuard.notNull(coordinatesType, "coordinatesType"); // We calculate intersection based on as-is coordinates. var asIsSubScreenshotRegion = this.getIntersectedRegion(region, coordinatesType, CoordinatesType.SCREENSHOT_AS_IS); var sizeFromRegion = GeometryUtils.createSizeFromRegion(region); var sizeFromSubRegion = GeometryUtils.createSizeFromRegion(asIsSubScreenshotRegion); if (GeometryUtils.isRegionEmpty(asIsSubScreenshotRegion) || (throwIfClipped && !(sizeFromRegion.height == sizeFromSubRegion.height && sizeFromRegion.width == sizeFromSubRegion.width))) { throw new Error("Region ", region, ", (", coordinatesType, ") is out of screenshot bounds ", this._frameWindow); } var subScreenshotImage = this._image.cropImage(asIsSubScreenshotRegion); // The frame location in the sub screenshot is the negative of the // context-as-is location of the region. var contextAsIsRegionLocation = this.convertLocationFromLocation(GeometryUtils.createLocationFromRegion(asIsSubScreenshotRegion), CoordinatesType.SCREENSHOT_AS_IS, CoordinatesType.CONTEXT_AS_IS); var frameLocationInSubScreenshot = GeometryUtils.createLocation(-contextAsIsRegionLocation.x, -contextAsIsRegionLocation.y); var that = this, result = new EyesWebDriverScreenshot(this._logger, this._driver, subScreenshotImage, this._promiseFactory); return result.buildScreenshot(this._screenshotType, frameLocationInSubScreenshot, null).then(function () { that._logger.verbose("Done!"); return result; }); }; //noinspection JSUnusedGlobalSymbols /** * Converts a location's coordinates with the {@code from} coordinates type * to the {@code to} coordinates type. * * @param {{x: number, y: number}} location The location which coordinates needs to be converted. * @param {CoordinatesType} from The current coordinates type for {@code location}. * @param {CoordinatesType} to The target coordinates type for {@code location}. * @return {{x: number, y: number}} A new location which is the transformation of {@code location} to the {@code to} coordinates type. */ EyesWebDriverScreenshot.prototype.convertLocationFromLocation = function (location, from, to) { ArgumentGuard.notNull(location, "location"); ArgumentGuard.notNull(from, "from"); ArgumentGuard.notNull(to, "to"); var result = {x: location.x, y: location.y}; if (from == to) { return result; } // If we're not inside a frame, and the screenshot is the entire // page, then the context as-is/relative are the same (notice // screenshot as-is might be different, e.g., // if it is actually a sub-screenshot of a region). if (this._frameChain.size() == 0 && this._screenshotType == ScreenshotType.ENTIRE_FRAME) { if ((from == CoordinatesType.CONTEXT_RELATIVE || from == CoordinatesType.CONTEXT_AS_IS) && to == CoordinatesType.SCREENSHOT_AS_IS) { // If this is not a sub-screenshot, this will have no effect. result = GeometryUtils.locationOffset(result, this._frameLocationInScreenshot); } else if (from == CoordinatesType.SCREENSHOT_AS_IS && (to == CoordinatesType.CONTEXT_RELATIVE || to == CoordinatesType.CONTEXT_AS_IS)) { result = GeometryUtils.locationOffset(result, { x: -this._frameLocationInScreenshot.x, y: -this._frameLocationInScreenshot.y }); } return result; } switch (from) { case CoordinatesType.CONTEXT_AS_IS: switch (to) { case CoordinatesType.CONTEXT_RELATIVE: result = GeometryUtils.locationOffset(result, this._currentFrameScrollPosition); break; case CoordinatesType.SCREENSHOT_AS_IS: result = GeometryUtils.locationOffset(result, this._frameLocationInScreenshot); break; default: throw new Error("Cannot convert from '" + from + "' to '" + to + "'"); } break; case CoordinatesType.CONTEXT_RELATIVE: switch (to) { case CoordinatesType.SCREENSHOT_AS_IS: // First, convert context-relative to context-as-is. result = GeometryUtils.locationOffset(result, {x: -this._currentFrameScrollPosition.x, y: -this._currentFrameScrollPosition.y}); // Now convert context-as-is to screenshot-as-is. result = GeometryUtils.locationOffset(result, this._frameLocationInScreenshot); break; case CoordinatesType.CONTEXT_AS_IS: result = GeometryUtils.locationOffset(result, {x: -this._currentFrameScrollPosition.x, y: -this._currentFrameScrollPosition.y}); break; default: throw new Error("Cannot convert from '" + from + "' to '" + to + "'"); } break; case CoordinatesType.SCREENSHOT_AS_IS: switch (to) { case CoordinatesType.CONTEXT_RELATIVE: // First convert to context-as-is. result = GeometryUtils.locationOffset(result, { x: -this._frameLocationInScreenshot.x, y: -this._frameLocationInScreenshot.y }); // Now convert to context-relative. result = GeometryUtils.locationOffset(result, {x: -this._currentFrameScrollPosition.x, y: -this._currentFrameScrollPosition.y}); break; case CoordinatesType.CONTEXT_AS_IS: result = GeometryUtils.locationOffset(result, { x: -this._frameLocationInScreenshot.x, y: -this._frameLocationInScreenshot.y }); break; default: throw new Error("Cannot convert from '" + from + "' to '" + to + "'"); } break; default: throw new Error("Cannot convert from '" + from + "' to '" + to + "'"); } return result; }; //noinspection JSUnusedGlobalSymbols /** * @param {{x: number, y: number}} location * @param {CoordinatesType} coordinatesType * @returns {{x: number, y: number}} */ EyesWebDriverScreenshot.prototype.getLocationInScreenshot = function (location, coordinatesType) { this._location = this.convertLocationFromLocation(location, coordinatesType, CoordinatesType.SCREENSHOT_AS_IS); // Making sure it's within the screenshot bounds if (!GeometryUtils.isRegionContainsLocation(this._frameWindow, location)) { throw new Error("Location " + location + " ('" + coordinatesType + "') is not visible in screenshot!"); } return this._location; }; //noinspection JSUnusedGlobalSymbols /** * * @param {{left: number, top: number, width: number, height: number}} region * @param {CoordinatesType} originalCoordinatesType * @param {CoordinatesType} resultCoordinatesType * @returns {{left: number, top: number, width: number, height: number}} */ EyesWebDriverScreenshot.prototype.getIntersectedRegion = function (region, originalCoordinatesType, resultCoordinatesType) { if (GeometryUtils.isRegionEmpty(region)) { return GeneralUtils.clone(region); } var intersectedRegion = this.convertRegionLocation(region, originalCoordinatesType, CoordinatesType.SCREENSHOT_AS_IS); switch (originalCoordinatesType) { // If the request was context based, we intersect with the frame // window. case CoordinatesType.CONTEXT_AS_IS: case CoordinatesType.CONTEXT_RELATIVE: GeometryUtils.intersect(intersectedRegion, this._frameWindow); break; // If the request is screenshot based, we intersect with the image case CoordinatesType.SCREENSHOT_AS_IS: GeometryUtils.intersect(intersectedRegion, GeometryUtils.createRegion(0, 0, this._image.width, this._image.height)); break; default: throw new Error("Unknown coordinates type: '" + originalCoordinatesType + "'"); } // If the intersection is empty we don't want to convert the // coordinates. if (GeometryUtils.isRegionEmpty(intersectedRegion)) { return intersectedRegion; } // Converting the result to the required coordinates type. intersectedRegion = this.convertRegionLocation(intersectedRegion, CoordinatesType.SCREENSHOT_AS_IS, resultCoordinatesType); return intersectedRegion; }; //noinspection JSUnusedGlobalSymbols /** * Gets the elements region in the screenshot. * * @param {WebElement} element The element which region we want to intersect. * @return {Promise.<{left: number, top: number, width: number, height: number}>} The intersected region, in {@code SCREENSHOT_AS_IS} coordinates * type. */ EyesWebDriverScreenshot.prototype.getIntersectedRegionFromElement = function (element) { ArgumentGuard.notNull(element, "element"); var pl, ds; return element.getLocation().then(function (location) { pl = location; return element.getSize(); }).then(function (size) { ds = size; // Since the element coordinates are in context relative var region = this.getIntersectedRegion(GeometryUtils.createRegionFromLocationAndSize(pl, ds), CoordinatesType.CONTEXT_RELATIVE, CoordinatesType.CONTEXT_RELATIVE); if (!GeometryUtils.isRegionEmpty(region)) { region = this.convertRegionLocation(region, CoordinatesType.CONTEXT_RELATIVE, CoordinatesType.SCREENSHOT_AS_IS); } return region; }); }; module.exports = EyesWebDriverScreenshot; }());