
com.applitools.eyes.selenium.SeleniumEyes Maven / Gradle / Ivy
/*
* Applitools SDK for Selenium integration.
*/
package com.applitools.eyes.selenium;
import com.applitools.ICheckSettings;
import com.applitools.eyes.*;
import com.applitools.eyes.capture.AppOutputWithScreenshot;
import com.applitools.eyes.capture.EyesScreenshotFactory;
import com.applitools.eyes.capture.ImageProvider;
import com.applitools.eyes.config.Configuration;
import com.applitools.eyes.config.ConfigurationProvider;
import com.applitools.eyes.debug.DebugScreenshotsProvider;
import com.applitools.eyes.exceptions.TestFailedException;
import com.applitools.eyes.fluent.GetSimpleRegion;
import com.applitools.eyes.fluent.ICheckSettingsInternal;
import com.applitools.eyes.fluent.SimpleRegionByRectangle;
import com.applitools.eyes.positioning.PositionProvider;
import com.applitools.eyes.scaling.FixedScaleProviderFactory;
import com.applitools.eyes.scaling.NullScaleProvider;
import com.applitools.eyes.selenium.capture.*;
import com.applitools.eyes.selenium.fluent.*;
import com.applitools.eyes.selenium.frames.Frame;
import com.applitools.eyes.selenium.frames.FrameChain;
import com.applitools.eyes.selenium.positioning.*;
import com.applitools.eyes.selenium.regionVisibility.MoveToRegionVisibilityStrategy;
import com.applitools.eyes.selenium.regionVisibility.NopRegionVisibilityStrategy;
import com.applitools.eyes.selenium.regionVisibility.RegionVisibilityStrategy;
import com.applitools.eyes.selenium.wrappers.EyesRemoteWebElement;
import com.applitools.eyes.selenium.wrappers.EyesTargetLocator;
import com.applitools.eyes.selenium.wrappers.EyesSeleniumDriver;
import com.applitools.eyes.triggers.MouseAction;
import com.applitools.eyes.visualgrid.model.RenderingInfo;
import com.applitools.utils.*;
import org.apache.http.annotation.Obsolete;
import org.openqa.selenium.*;
import org.openqa.selenium.remote.RemoteWebDriver;
import java.awt.image.BufferedImage;
import java.net.URI;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
/**
* The main API gateway for the SDK.
*/
@SuppressWarnings("WeakerAccess")
public class SeleniumEyes extends EyesBase implements ISeleniumEyes, IBatchCloser {
private FrameChain originalFC;
private WebElement userDefinedSRE;
private PositionProvider currentFramePositionProvider;
/**
* The constant UNKNOWN_DEVICE_PIXEL_RATIO.
*/
public static final double UNKNOWN_DEVICE_PIXEL_RATIO = 0;
/**
* The constant DEFAULT_DEVICE_PIXEL_RATIO.
*/
public static final double DEFAULT_DEVICE_PIXEL_RATIO = 1;
private static final int USE_DEFAULT_MATCH_TIMEOUT = -1;
// Seconds
private static final int RESPONSE_TIME_DEFAULT_DEADLINE = 10;
// Seconds
private static final int RESPONSE_TIME_DEFAULT_DIFF_FROM_DEADLINE = 20;
private EyesSeleniumDriver driver;
private boolean doNotGetTitle;
public boolean checkFrameOrElement;
private Region regionToCheck;
private String originalOverflow;
private ImageRotation rotation;
private double devicePixelRatio;
private PropertyHandler regionVisibilityStrategyHandler;
private SeleniumJavaScriptExecutor jsExecutor;
private UserAgent userAgent;
private ImageProvider imageProvider;
private RegionPositionCompensation regionPositionCompensation;
private Region effectiveViewport;
private EyesScreenshotFactory screenshotFactory;
private String cachedAUTSessionId;
private final ConfigurationProvider configurationProvider;
private ClassicRunner runner;
/**
* Should stitch content boolean.
* @return the boolean
*/
@Obsolete
public boolean shouldStitchContent() {
return false;
}
/**
* The interface Web driver action.
*/
@SuppressWarnings("UnusedDeclaration")
public interface WebDriverAction {
/**
* Drive.
* @param driver the driver
*/
void drive(WebDriver driver);
}
/**
* Creates a new SeleniumEyes instance that interacts with the SeleniumEyes cloud
* service.
*/
public SeleniumEyes(ConfigurationProvider configurationProvider, ClassicRunner runner) {
super();
this.configurationProvider = configurationProvider;
checkFrameOrElement = false;
doNotGetTitle = false;
devicePixelRatio = UNKNOWN_DEVICE_PIXEL_RATIO;
regionVisibilityStrategyHandler = new SimplePropertyHandler<>();
regionVisibilityStrategyHandler.set(new MoveToRegionVisibilityStrategy(logger));
this.runner = runner;
}
@Override
public String getBaseAgentId() {
return "eyes.selenium.java/" + ClassVersionGetter.CURRENT_VERSION;
}
public void apiKey(String apiKey) {
setApiKey(apiKey);
}
public void serverUrl(String serverUrl) {
setServerUrl(serverUrl);
}
public void serverUrl(URI serverUrl) {
setServerUrl(serverUrl);
}
/**
* Gets driver.
* @return the driver
*/
public WebDriver getDriver() {
return driver;
}
/**
* Gets original fc.
* @return the original fc
*/
public FrameChain getOriginalFC() {
return originalFC;
}
/**
* Gets current frame position provider.
* @return the current frame position provider
*/
public PositionProvider getCurrentFramePositionProvider() {
return currentFramePositionProvider;
}
/**
* Gets region to check.
* @return the region to check
*/
public Region getRegionToCheck() {
return regionToCheck;
}
/**
* Sets region to check.
* @param regionToCheck the region to check
*/
public void setRegionToCheck(Region regionToCheck) {
this.regionToCheck = regionToCheck;
}
/**
* Turns on/off the automatic scrolling to a region being checked by
* {@code checkRegion}.
* @param shouldScroll Whether to automatically scroll to a region being validated.
*/
public void setScrollToRegion(boolean shouldScroll) {
if (shouldScroll) {
regionVisibilityStrategyHandler = new ReadOnlyPropertyHandler(logger, new MoveToRegionVisibilityStrategy(logger));
} else {
regionVisibilityStrategyHandler = new ReadOnlyPropertyHandler(logger, new NopRegionVisibilityStrategy(logger));
}
}
/**
* Gets scroll to region.
* @return Whether to automatically scroll to a region being validated.
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean getScrollToRegion() {
return !(regionVisibilityStrategyHandler.get() instanceof NopRegionVisibilityStrategy);
}
/**
* Gets rotation.
* @return The image rotation model.
*/
public ImageRotation getRotation() {
return rotation;
}
/**
* Sets rotation.
* @param rotation The image rotation model.
*/
public void setRotation(ImageRotation rotation) {
this.rotation = rotation;
if (driver != null) {
driver.setRotation(rotation);
}
}
/**
* @return The device pixel ratio, or {@link #UNKNOWN_DEVICE_PIXEL_RATIO}
* if the DPR is not known yet or if it wasn't possible to extract it.
*/
public double getDevicePixelRatio() {
return devicePixelRatio;
}
@Override
public WebDriver open(WebDriver driver, String appName, String testName, RectangleSize viewportSize) throws EyesException {
getConfigurationInstance().setAppName(appName).setTestName(testName);
if (viewportSize != null && !viewportSize.isEmpty()) {
getConfigurationInstance().setViewportSize(new RectangleSize(viewportSize));
}
return open(driver);
}
/**
* Open web driver.
* @param driver the driver
* @return the web driver
* @throws EyesException the eyes exception
*/
public WebDriver open(WebDriver driver) throws EyesException {
openLogger();
this.cachedAUTSessionId = null;
if (getIsDisabled()) {
logger.verbose("Ignored");
return driver;
}
initDriver(driver);
this.jsExecutor = new SeleniumJavaScriptExecutor(this.driver);
String uaString = this.driver.getUserAgent();
if (uaString != null) {
if (uaString.startsWith("useragent:")) {
uaString = uaString.substring(10);
}
userAgent = UserAgent.parseUserAgentString(uaString, true);
}
initDevicePixelRatio();
screenshotFactory = new EyesWebDriverScreenshotFactory(logger, this.driver);
imageProvider = ImageProviderFactory.getImageProvider(userAgent, this, logger, this.driver);
regionPositionCompensation = RegionPositionCompensationFactory.getRegionPositionCompensation(userAgent, this, logger);
if (!getConfigurationInstance().isVisualGrid()) {
openBase();
}
//updateScalingParams();
this.driver.setRotation(rotation);
this.runner.addBatch(this.getConfigurationInstance().getBatch().getId(), this);
return this.driver;
}
private void initDevicePixelRatio() {
logger.verbose("Trying to extract device pixel ratio...");
try {
devicePixelRatio = driver.getDevicePixelRatio();
} catch (Exception ex) {
logger.verbose("Failed to extract device pixel ratio! Using default.");
devicePixelRatio = DEFAULT_DEVICE_PIXEL_RATIO;
}
logger.verbose("Device pixel ratio: " + devicePixelRatio);
}
private void initDriver(WebDriver driver) {
if (driver instanceof RemoteWebDriver) {
this.driver = new EyesSeleniumDriver(logger, this, (RemoteWebDriver) driver);
} else if (driver instanceof EyesSeleniumDriver) {
this.driver = (EyesSeleniumDriver) driver;
} else {
String errMsg = "Driver is not a RemoteWebDriver (" +
driver.getClass().getName() + ")";
logger.log(errMsg);
throw new EyesException(errMsg);
}
if (EyesDriverUtils.isMobileDevice(driver)) {
regionVisibilityStrategyHandler.set(new NopRegionVisibilityStrategy(logger));
}
}
/**
* Gets scroll root element.
* @return the scroll root element
*/
public WebElement getScrollRootElement() {
if (this.userDefinedSRE == null) {
this.userDefinedSRE = EyesSeleniumUtils.getDefaultRootElement(logger, driver);
}
return this.userDefinedSRE;
}
private PositionProvider createPositionProvider() {
return createPositionProvider(this.userDefinedSRE);
}
private PositionProvider createPositionProvider(WebElement scrollRootElement) {
// Setting the correct position provider.
StitchMode stitchMode = getConfigurationInstance().getStitchMode();
logger.verbose("initializing position provider. stitchMode: " + stitchMode);
switch (stitchMode) {
case CSS:
return new CssTranslatePositionProvider(logger, this.jsExecutor, scrollRootElement);
default:
return ScrollPositionProviderFactory.getScrollPositionProvider(userAgent, logger, this.jsExecutor, scrollRootElement);
}
}
/**
* See {@link #checkWindow(String)}.
* {@code tag} defaults to {@code null}.
* Default match timeout is used.
*/
public void checkWindow() {
checkWindow((String) null);
}
/**
* See {@link #checkWindow(int, String)}.
* Default match timeout is used.
* @param tag An optional tag to be associated with the snapshot.
*/
public void checkWindow(String tag) {
check(tag, Target.window());
}
/**
* Takes a snapshot of the application under test and matches it with
* the expected output.
* @param matchTimeout The amount of time to retry matching (Milliseconds).
* @param tag An optional tag to be associated with the snapshot.
* @throws TestFailedException Thrown if a mismatch is detected and
* immediate failure reports are enabled.
*/
public void checkWindow(int matchTimeout, String tag) {
check(tag, Target.window().timeout(matchTimeout));
}
/**
* Takes multiple screenshots at once (given all ICheckSettings
objects are on the same level).
* @param checkSettings Multiple ICheckSettings
object representing different regions in the viewport.
*/
public void check(ICheckSettings... checkSettings) {
if (getIsDisabled()) {
logger.log(String.format("check(ICheckSettings[%d]): Ignored", checkSettings.length));
return;
}
Boolean forceFullPageScreenshot = getConfigurationInstance().getForceFullPageScreenshot();
boolean originalForceFPS = forceFullPageScreenshot == null ? false : forceFullPageScreenshot;
if (checkSettings.length > 1) {
getConfigurationInstance().setForceFullPageScreenshot(true);
}
logger.verbose(getConfigurationInstance().toString());
Dictionary getRegions = new Hashtable<>();
Dictionary checkSettingsInternalDictionary = new Hashtable<>();
for (int i = 0; i < checkSettings.length; ++i) {
ICheckSettings settings = checkSettings[i];
ICheckSettingsInternal checkSettingsInternal = (ICheckSettingsInternal) settings;
checkSettingsInternalDictionary.put(i, checkSettingsInternal);
Region targetRegion = checkSettingsInternal.getTargetRegion();
if (targetRegion != null) {
getRegions.put(i, new SimpleRegionByRectangle(targetRegion));
} else {
ISeleniumCheckTarget seleniumCheckTarget =
(settings instanceof ISeleniumCheckTarget) ? (ISeleniumCheckTarget) settings : null;
if (seleniumCheckTarget != null) {
seleniumCheckTarget.init(logger, driver);
WebElement targetElement = getTargetElement(seleniumCheckTarget);
if (targetElement == null && seleniumCheckTarget.getFrameChain().size() == 1) {
targetElement = EyesSeleniumUtils.findFrameByFrameCheckTarget(seleniumCheckTarget.getFrameChain().get(0), driver);
}
if (targetElement != null) {
getRegions.put(i, new SimpleRegionByElement(targetElement));
}
}
}
//check(settings);
}
this.userDefinedSRE = EyesSeleniumUtils.getScrollRootElement(logger, driver, (IScrollRootElementContainer) checkSettings[0]);
this.currentFramePositionProvider = null;
setPositionProvider(createPositionProvider());
matchRegions(getRegions, checkSettingsInternalDictionary, checkSettings);
getConfigurationInstance().setForceFullPageScreenshot(originalForceFPS);
}
private void matchRegions(Dictionary getRegions,
Dictionary checkSettingsInternalDictionary,
ICheckSettings[] checkSettings) {
if (getRegions.size() == 0) return;
this.originalFC = driver.getFrameChain().clone();
Region bBox = findBoundingBox(getRegions, checkSettings);
MatchWindowTask mwt = new MatchWindowTask(logger, getServerConnector(), runningSession, getConfigurationInstance().getMatchTimeout(), this);
ScaleProviderFactory scaleProviderFactory = updateScalingParams();
FullPageCaptureAlgorithm algo = createFullPageCaptureAlgorithm(scaleProviderFactory, new RenderingInfo());
Object activeElement = null;
if (getConfigurationInstance().getHideCaret()) {
try {
activeElement = driver.executeScript("var activeElement = document.activeElement; activeElement && activeElement.blur(); return activeElement;");
} catch (WebDriverException e) {
logger.verbose("WARNING: Cannot hide caret! " + e.getMessage());
}
}
Region region = Region.EMPTY;
boolean hasFrames = driver.getFrameChain().size() > 0;
if (hasFrames) {
region = new Region(bBox.getLocation(), ((EyesRemoteWebElement) userDefinedSRE).getClientSize());
} else {
WebElement defaultRootElement = EyesSeleniumUtils.getDefaultRootElement(logger, driver);
if (!userDefinedSRE.equals(defaultRootElement)) {
EyesRemoteWebElement eyesScrollRootElement;
if (userDefinedSRE instanceof EyesRemoteWebElement) {
eyesScrollRootElement = (EyesRemoteWebElement) userDefinedSRE;
} else {
eyesScrollRootElement = new EyesRemoteWebElement(logger, driver, userDefinedSRE);
}
Point location = eyesScrollRootElement.getLocation();
SizeAndBorders sizeAndBorders = eyesScrollRootElement.getSizeAndBorders();
region = new Region(
location.getX() + sizeAndBorders.getBorders().getLeft(),
location.getY() + sizeAndBorders.getBorders().getTop(),
sizeAndBorders.getSize().getWidth(),
sizeAndBorders.getSize().getHeight());
}
}
region.intersect(effectiveViewport);
markElementForLayoutRCA(null);
BufferedImage screenshotImage = algo.getStitchedRegion(
region, bBox, positionProviderHandler.get(), positionProviderHandler.get(), RectangleSize.EMPTY);
debugScreenshotsProvider.save(screenshotImage, "original");
EyesWebDriverScreenshot screenshot = new EyesWebDriverScreenshot(logger, driver, screenshotImage, EyesWebDriverScreenshot.ScreenshotType.VIEWPORT, Location.ZERO);
for (int i = 0; i < checkSettings.length; ++i) {
if (((Hashtable) getRegions).containsKey(i)) {
GetSimpleRegion simpleRegion = getRegions.get(i);
ICheckSettingsInternal checkSettingsInternal = checkSettingsInternalDictionary.get(i);
List subScreenshots = getSubScreenshots(hasFrames ? Region.EMPTY : bBox, screenshot, simpleRegion);
matchRegion(checkSettingsInternal, mwt, subScreenshots);
}
}
if (getConfigurationInstance().getHideCaret() && activeElement != null) {
try {
driver.executeScript("arguments[0].focus();", activeElement);
} catch (WebDriverException e) {
logger.verbose("WARNING: Could not return focus to active element! " + e.getMessage());
}
}
((EyesTargetLocator) driver.switchTo()).frames(this.originalFC);
}
private List getSubScreenshots(Region bBox, EyesWebDriverScreenshot screenshot, GetSimpleRegion getSimpleRegion) {
List subScreenshots = new ArrayList<>();
for (Region r : getSimpleRegion.getRegions(screenshot)) {
logger.verbose("original sub-region: " + r);
r = r.offset(-bBox.getLeft(), -bBox.getTop());
//r = regionPositionCompensation.compensateRegionPosition(r, devicePixelRatio);
//logger.verbose("sub-region after compensation: " + r);
EyesScreenshot subScreenshot = screenshot.getSubScreenshotForRegion(r, false);
subScreenshots.add(subScreenshot);
}
return subScreenshots;
}
private void matchRegion(ICheckSettingsInternal checkSettingsInternal, MatchWindowTask mwt, List subScreenshots) {
String name = checkSettingsInternal.getName();
String source = EyesDriverUtils.isMobileDevice(driver) ? null : driver.getCurrentUrl();
for (EyesScreenshot subScreenshot : subScreenshots) {
debugScreenshotsProvider.save(subScreenshot.getImage(), String.format("subscreenshot_%s", name));
ImageMatchSettings ims = mwt.createImageMatchSettings(checkSettingsInternal, subScreenshot, this);
Location location = subScreenshot.getLocationInScreenshot(Location.ZERO, CoordinatesType.SCREENSHOT_AS_IS);
AppOutput appOutput = new AppOutput(name, ImageUtils.encodeAsPng(subScreenshot.getImage()), null, null);
AppOutputWithScreenshot appOutputWithScreenshot = new AppOutputWithScreenshot(appOutput, subScreenshot, location);
MatchResult matchResult = mwt.performMatch(new ArrayList(), appOutputWithScreenshot, name, false,
ims, this, null, source);
logger.verbose("matchResult.asExcepted: " + matchResult.getAsExpected());
}
}
private Region findBoundingBox(Dictionary getRegions, ICheckSettings[] checkSettings) {
RectangleSize rectSize = getViewportSize();
logger.verbose("rectSize: " + rectSize);
EyesScreenshot screenshot = new EyesWebDriverScreenshot(logger, driver,
new BufferedImage(rectSize.getWidth(), rectSize.getHeight(), BufferedImage.TYPE_INT_RGB));
return findBoundingBox(getRegions, checkSettings, screenshot);
}
private Region findBoundingBox(Dictionary getRegions, ICheckSettings[] checkSettings, EyesScreenshot screenshot) {
Region bBox = null;
for (int i = 0; i < checkSettings.length; ++i) {
GetSimpleRegion simpleRegion = getRegions.get(i);
if (simpleRegion != null) {
List regions = simpleRegion.getRegions(screenshot);
for (Region region : regions) {
if (bBox == null) {
bBox = new Region(region);
} else {
bBox = bBox.expandToContain(region);
}
}
}
}
Location offset = screenshot.getLocationInScreenshot(Location.ZERO, CoordinatesType.CONTEXT_AS_IS);
return bBox.offset(offset);
}
private WebElement getTargetElement(ISeleniumCheckTarget seleniumCheckTarget) {
assert seleniumCheckTarget != null;
By targetSelector = seleniumCheckTarget.getTargetSelector();
WebElement targetElement = seleniumCheckTarget.getTargetElement();
if (targetElement == null && targetSelector != null) {
targetElement = this.driver.findElement(targetSelector);
} else if (targetElement != null && !(targetElement instanceof EyesRemoteWebElement)) {
targetElement = new EyesRemoteWebElement(logger, driver, targetElement);
}
return targetElement;
}
/**
* Check.
* @param name the name
* @param checkSettings the check settings
*/
public void check(String name, ICheckSettings checkSettings) {
if (getIsDisabled()) {
logger.log(String.format("check('%s', %s): Ignored", name, checkSettings));
return;
}
ArgumentGuard.isValidState(isOpen, "Eyes not open");
ArgumentGuard.notNull(checkSettings, "checkSettings");
if (name != null) {
checkSettings = checkSettings.withName(name);
}
this.check(checkSettings);
}
@Override
public void setIsDisabled(boolean disabled) {
super.setIsDisabled(disabled);
}
@Override
public String tryCaptureDom() {
String fullWindowDom = "";
FrameChain fc = driver.getFrameChain().clone();
try {
Frame frame = fc.peek();
WebElement scrollRootElement = null;
if (frame != null) {
scrollRootElement = frame.getScrollRootElement();
}
if (scrollRootElement == null) {
scrollRootElement = EyesSeleniumUtils.getDefaultRootElement(logger, driver);
}
PositionProvider positionProvider = ScrollPositionProviderFactory.getScrollPositionProvider(userAgent, logger, jsExecutor, scrollRootElement);
DomCapture domCapture = new DomCapture(this);
fullWindowDom = domCapture.getFullWindowDom(positionProvider);
} catch (Exception e) {
GeneralUtils.logExceptionStackTrace(logger, e);
} finally {
((EyesTargetLocator) driver.switchTo()).frames(fc);
}
return fullWindowDom;
}
@Override
protected void setEffectiveViewportSize(RectangleSize size) {
this.effectiveViewport = new Region(Location.ZERO, size);
logger.verbose("setting effective viewport size to " + size);
}
public void check(ICheckSettings checkSettings) {
if (getIsDisabled()) {
logger.log(String.format("check(%s): Ignored", checkSettings));
return;
}
try {
ArgumentGuard.isValidState(isOpen, "Eyes not open");
ArgumentGuard.notNull(checkSettings, "checkSettings");
ArgumentGuard.notOfType(checkSettings, ISeleniumCheckTarget.class, "checkSettings");
boolean isMobileDevice = EyesDriverUtils.isMobileDevice(driver);
String source = null;
if (!isMobileDevice) {
source = driver.getCurrentUrl();
logger.verbose("URL: " + source);
}
ICheckSettingsInternal checkSettingsInternal = (ICheckSettingsInternal) checkSettings;
ISeleniumCheckTarget seleniumCheckTarget = (ISeleniumCheckTarget) checkSettings;
CheckState state = new CheckState();
seleniumCheckTarget.setState(state);
Boolean forceFullPageScreenshot = getConfigurationInstance().getForceFullPageScreenshot();
Boolean fully = checkSettingsInternal.getStitchContent();
state.setStitchContent((fully != null && fully) || (forceFullPageScreenshot != null && forceFullPageScreenshot));
// Ensure frame is not used as a region
((SeleniumCheckSettings) checkSettings).sanitizeSettings(logger, driver, state.isStitchContent());
seleniumCheckTarget.init(logger, driver);
Region targetRegion = checkSettingsInternal.getTargetRegion();
logger.verbose("setting userDefinedSRE ...");
this.userDefinedSRE = tryGetUserDefinedSREFromSREContainer(seleniumCheckTarget, driver);
WebElement scrollRootElement = this.userDefinedSRE;
if (scrollRootElement == null && !isMobileDevice) {
scrollRootElement = EyesSeleniumUtils.getDefaultRootElement(logger, driver);
}
logger.verbose("userDefinedSRE set to " + ((this.userDefinedSRE != null) ? userDefinedSRE.toString() : "null"));
logger.verbose("scrollRootElement set to " + scrollRootElement);
currentFramePositionProvider = null;
super.positionProviderHandler.set(PositionProviderFactory.getPositionProvider(logger, getConfigurationInstance().getStitchMode(), jsExecutor, scrollRootElement, userAgent));
CaretVisibilityProvider caretVisibilityProvider = new CaretVisibilityProvider(logger, driver, getConfigurationInstance());
PageState pageState = new PageState(logger, driver, getConfigurationInstance().getStitchMode(), userAgent);
pageState.preparePage(seleniumCheckTarget, getConfigurationInstance(), scrollRootElement);
FrameChain frameChainAfterSwitchToTarget = driver.getFrameChain().clone();
RectangleSize viewportSize = this.effectiveViewport.getSize();
Region effectiveViewport = computeEffectiveViewport(frameChainAfterSwitchToTarget, viewportSize);
state.setEffectiveViewport(effectiveViewport);
// new Rectangle(Point.Empty, viewportSize_);
WebElement targetElement = getTargetElementFromSettings(seleniumCheckTarget);
caretVisibilityProvider.hideCaret();
//////////////////////////////////////////////////////////////////
// Cases:
// Target.Region(x,y,w,h).Fully(true) - TODO - NOT TESTED!
// Target.Region(x,y,w,h).Fully(false)
// Target.Region(element).Fully(true)
// Target.Region(element).Fully(false)
// Target.Frame(frame).Fully(true)
// Target.Frame(frame).Region(x,y,w,h).Fully(true)
// Target.Frame(frame).Region(x,y,w,h).Fully(false) - TODO - NOT TESTED!
// Target.Frame(frame).Region(element).Fully(true)
// Target.Frame(frame).Region(element).Fully(false) - TODO - NOT TESTED!
// Target.Window().Fully(true)
// Target.Window().Fully(false)
// Algorithm:
// 1. Save current page state
// 2. Switch to frame
// 3. Maximize desired region or element visibility
// 4. Capture desired region of element
// 5. Go back to original frame
// 6. Restore page state
if (targetElement != null) {
if (isMobileDevice) {
checkNativeElement(checkSettingsInternal, targetElement);
} else if (state.isStitchContent()) {
checkFullElement(checkSettingsInternal, targetElement, targetRegion, state, source);
} else {
// TODO Verify: if element is outside the viewport, we should still capture entire (outer) bounds
checkElement(checkSettingsInternal, targetElement, targetRegion, state, source);
}
} else if (targetRegion != null) {
// Coordinates should always be treated as "Fully" in case they get out of the viewport.
boolean originalFully = state.isStitchContent();
state.setStitchContent(true);
checkFullRegion(checkSettingsInternal, targetRegion, state, source);
state.setStitchContent(originalFully);
} else if (!isMobileDevice && seleniumCheckTarget.getFrameChain().size() > 0) {
if (state.isStitchContent()) {
checkFullFrame(checkSettingsInternal, state, source);
} else {
logger.log("Target.Frame(frame).Fully(false)");
logger.log("WARNING: This shouldn't have been called, as it is covered by `CheckElement_(...)`");
}
} else {
if (state.isStitchContent()) {
checkFullWindow(checkSettingsInternal, state, scrollRootElement, source);
} else {
checkWindow(checkSettingsInternal, source);
}
}
caretVisibilityProvider.restoreCaret();
pageState.restorePageState();
} catch (Exception ex) {
GeneralUtils.logExceptionStackTrace(logger, ex);
throw ex;
}
}
private void checkFullFrame(ICheckSettingsInternal checkSettingsInternal, CheckState state, String source) {
logger.verbose("Target.Frame(frame).Fully(true)");
FrameChain currentFrameChain = driver.getFrameChain().clone();
Location visualOffset = getFrameChainOffset(currentFrameChain);
Frame currentFrame = currentFrameChain.peek();
state.setEffectiveViewport(state.getEffectiveViewport().getIntersected(new Region(visualOffset, currentFrame.getInnerSize())));
WebElement currentFrameSRE = getCurrentFrameScrollRootElement();
RectangleSize currentSREScrollSize = EyesRemoteWebElement.getScrollSize(currentFrameSRE, driver, logger);
state.setFullRegion(new Region(state.getEffectiveViewport().getLocation(), currentSREScrollSize));
checkWindowBase(null, checkSettingsInternal, source);
}
private void checkFullRegion(ICheckSettingsInternal checkSettingsInternal, Region targetRegion, CheckState state, String source) {
if (((ISeleniumCheckTarget) checkSettingsInternal).getFrameChain().size() > 0) {
logger.verbose("Target.Frame(frame).Region(x,y,w,h).Fully(true)");
} else {
logger.verbose("Target.Region(x,y,w,h).Fully(true)");
}
FrameChain currentFrameChain = driver.getFrameChain().clone();
Frame currentFrame = currentFrameChain.peek();
if (currentFrame != null) {
Region currentFrameBoundsWithoutBorders = removeBorders(currentFrame.getBounds(), currentFrame.getBorderWidths());
state.setEffectiveViewport(state.getEffectiveViewport().getIntersected(currentFrameBoundsWithoutBorders));
WebElement currentFrameSRE = getCurrentFrameScrollRootElement();
RectangleSize currentSREScrollSize = EyesRemoteWebElement.getScrollSize(currentFrameSRE, driver, logger);
state.setFullRegion(new Region(state.getEffectiveViewport().getLocation(), currentSREScrollSize));
} else {
Location visualOffset = getFrameChainOffset(currentFrameChain);
targetRegion = targetRegion.offset(visualOffset);
}
checkWindowBase(targetRegion, checkSettingsInternal, source);
}
private static Region removeBorders(Region region, Borders borders) {
return new Region(
region.getLeft() + borders.getLeft(),
region.getTop() + borders.getTop(),
region.getWidth() - borders.getHorizontal(),
region.getHeight() - borders.getVertical(),
region.getCoordinatesType());
}
private void checkElement(ICheckSettingsInternal checkSettingsInternal, WebElement targetElement,
Region targetRegion, CheckState state, String source) {
List frameLocators = ((ISeleniumCheckTarget) checkSettingsInternal).getFrameChain();
if (frameLocators.size() > 0) {
logger.verbose("Target.Frame(frame).Region(element).Fully(false)");
} else {
logger.verbose("Target.Region(element).Fully(false)");
}
FrameChain currentFrameChain = driver.getFrameChain().clone();
Region bounds = EyesRemoteWebElement.getClientBounds(targetElement, driver, logger);
Location visualOffset = getFrameChainOffset(currentFrameChain);
bounds = bounds.offset(visualOffset);
WebElement currentFrameSRE = getCurrentFrameScrollRootElement();
PositionProvider currentFramePositionProvider = getPositionProviderForScrollRootElement(currentFrameSRE);
Location currentFramePosition = currentFramePositionProvider.getCurrentPosition();
currentFramePositionProvider.setPosition(bounds.offset(currentFramePosition).getLocation());
Region actualElementBounds = EyesRemoteWebElement.getClientBounds(targetElement, driver, logger);
actualElementBounds = actualElementBounds.offset(visualOffset);
Location actualFramePosition = new Location(bounds.getLeft() - actualElementBounds.getLeft(),
bounds.getTop() - actualElementBounds.getTop());
bounds = bounds.offset(-actualFramePosition.getX(), -actualFramePosition.getY());
EyesTargetLocator switchTo = (EyesTargetLocator) driver.switchTo();
FrameChain fcClone = currentFrameChain.clone();
while (!state.getEffectiveViewport().isIntersected(bounds) && fcClone.size() > 0) {
fcClone.pop();
switchTo.parentFrame();
currentFrameSRE = getCurrentFrameScrollRootElement();
currentFramePositionProvider = getPositionProviderForScrollRootElement(currentFrameSRE);
currentFramePosition = currentFramePositionProvider.getCurrentPosition();
bounds = bounds.offset(currentFramePosition);
actualFramePosition = currentFramePositionProvider.setPosition(bounds.getLocation());
bounds = bounds.offset(-actualFramePosition.getX(), -actualFramePosition.getY());
}
switchTo.frames(currentFrameChain);
Region crop = computeCropRectangle(bounds, targetRegion);
if (crop == null) {
crop = bounds;
}
checkWindowBase(crop, checkSettingsInternal, source);
}
private void checkFullElement(ICheckSettingsInternal checkSettingsInternal, WebElement targetElement,
Region targetRegion, CheckState state, String source) {
if (((ISeleniumCheckTarget) checkSettingsInternal).getFrameChain().size() > 0) {
logger.verbose("Target.Frame(frame)...Region(element).Fully(true)");
} else {
logger.verbose("Target.Region(element).Fully(true)");
}
// Hide scrollbars
String originalOverflow = EyesDriverUtils.setOverflow(driver, "hidden", targetElement);
// Get element's scroll size and bounds
RectangleSize scrollSize = EyesRemoteWebElement.getScrollSize(targetElement, driver, logger);
Region elementBounds = EyesRemoteWebElement.getClientBounds(targetElement, driver, logger);
Region elementInnerBounds = EyesRemoteWebElement.getClientBoundsWithoutBorders(targetElement, driver, logger);
boolean isScrollableElement = scrollSize.getHeight() > elementInnerBounds.getHeight() || scrollSize.getWidth() > elementInnerBounds.getWidth();
if (isScrollableElement) {
elementBounds = elementInnerBounds;
}
initPositionProvidersForCheckElement(isScrollableElement, targetElement, state);
Location originalElementLocation = elementBounds.getLocation();
String positionStyle = ((EyesRemoteWebElement) targetElement).getComputedStyle("position");
if (!positionStyle.equalsIgnoreCase("fixed")) {
elementBounds = bringRegionToView(elementBounds, state.getEffectiveViewport().getLocation());
Region currentElementRegion;
if (isScrollableElement) {
currentElementRegion = EyesRemoteWebElement.getClientBoundsWithoutBorders(targetElement, driver, logger);
} else {
currentElementRegion = EyesRemoteWebElement.getClientBounds(targetElement, driver, logger);
}
if (getConfigurationInstance().getStitchMode().equals(StitchMode.CSS)) {
elementBounds = currentElementRegion;
}
}
Region fullElementBounds = new Region(elementBounds);
fullElementBounds.setWidth(Math.max(fullElementBounds.getWidth(), scrollSize.getWidth()));
fullElementBounds.setHeight(Math.max(fullElementBounds.getHeight(), scrollSize.getHeight()));
FrameChain currentFrameChain = driver.getFrameChain().clone();
Location screenshotOffset = getFrameChainOffset(currentFrameChain);
logger.verbose("screenshotOffset: " + screenshotOffset);
fullElementBounds = fullElementBounds.offset(screenshotOffset);
state.setFullRegion(fullElementBounds);
// Now we have a 2-step part:
// 1. Intersect the SRE and the effective viewport.
if (getConfigurationInstance().getStitchMode() == StitchMode.CSS && userDefinedSRE != null) {
Region viewportInScreenshot = state.getEffectiveViewport();
int elementTranslationWidth = originalElementLocation.getX() - elementBounds.getLeft();
int elementTranslationHeight = originalElementLocation.getY() - elementBounds.getTop();
state.setEffectiveViewport(new Region(
viewportInScreenshot.getLeft(),
viewportInScreenshot.getTop(),
viewportInScreenshot.getWidth() - elementTranslationWidth,
viewportInScreenshot.getHeight() - elementTranslationHeight));
}
// In CSS stitch mode, if the element is not scrollable but the SRE is (i.e., "Modal" case),
// we move the SRE to (0,0) but then we translate the element itself to get the full contents.
// However, in Scroll stitch mode, we scroll the SRE itself to the get full contents, and it
// already has an offset caused by "BringRegionToView", so we should consider this offset.
if (getConfigurationInstance().getStitchMode() == StitchMode.SCROLL && !isScrollableElement) {
EyesRemoteWebElement sre = (EyesRemoteWebElement) getCurrentFrameScrollRootElement();
state.setStitchOffset(new RectangleSize(sre.getScrollLeft(), sre.getScrollTop()));
}
// 2. Intersect the element and the effective viewport
Region elementBoundsInScreenshotCoordinates = elementBounds.offset(screenshotOffset);
Region intersectedViewport = state.getEffectiveViewport().getIntersected(elementBoundsInScreenshotCoordinates);
state.setEffectiveViewport(intersectedViewport);
Region crop = computeCropRectangle(fullElementBounds, targetRegion);
checkWindowBase(crop, checkSettingsInternal, source);
EyesDriverUtils.setOverflow(driver, originalOverflow, targetElement);
}
private void checkNativeElement(ICheckSettingsInternal checkSettingsInternal, WebElement targetElement) {
final Rectangle rect = targetElement.getRect();
Region region = checkSettingsInternal.getTargetRegion();
if (region == null) {
region = new Region(rect.x, rect.y, rect.width, rect.height);
}
checkWindowBase(region, checkSettingsInternal, null);
}
private void checkWindow(ICheckSettingsInternal checkSettingsInternal, String source) {
logger.verbose("Target.Window()");
checkWindowBase(null, checkSettingsInternal, source);
}
private void checkFullWindow(ICheckSettingsInternal checkSettingsInternal, CheckState state, WebElement scrollRootElement, String source) {
logger.verbose("Target.Window().Fully(true)");
initPositionProvidersForCheckWindow(state, scrollRootElement);
checkWindowBase(null, checkSettingsInternal, source);
}
private void initPositionProvidersForCheckWindow(CheckState state, WebElement scrollRootElement) {
if (getConfigurationInstance().getStitchMode() == StitchMode.SCROLL) {
state.setStitchPositionProvider(new SeleniumScrollPositionProvider(logger, driver, scrollRootElement));
} else {
// Stitch mode == CSS
if (userDefinedSRE != null) {
state.setStitchPositionProvider(new ElementPositionProvider(logger, driver, userDefinedSRE));
} else {
state.setStitchPositionProvider(new CssTranslatePositionProvider(logger, driver, scrollRootElement));
state.setOriginPositionProvider(new SeleniumScrollPositionProvider(logger, driver, scrollRootElement));
}
}
}
private static Region computeCropRectangle(Region fullRect, Region cropRect) {
if (cropRect == null) {
return null;
}
Region crop = new Region(fullRect);
Location cropLocation = crop.getLocation();
Region cropRectClone = cropRect.offset(cropLocation);
crop.intersect(cropRectClone);
return crop;
}
private Location getFrameChainOffset(FrameChain frameChain) {
Location offset = Location.ZERO;
for (Frame frame : frameChain) {
offset = offset.offset(frame.getLocation());
}
return offset;
}
private Region bringRegionToView(Region bounds, Location viewportLocation) {
WebElement currentFrameSRE = getCurrentFrameScrollRootElement();
StitchMode stitchMode = getConfigurationInstance().getStitchMode();
PositionProvider currentFramePositionProvider = PositionProviderFactory.getPositionProvider(
logger, stitchMode, jsExecutor, currentFrameSRE, userAgent);
Location currentFramePosition = currentFramePositionProvider.getCurrentPosition();
Location boundsPosition = bounds.getLocation();
Location newFramePosition = boundsPosition.offset(-viewportLocation.getX(), -viewportLocation.getY());
if (stitchMode.equals(StitchMode.SCROLL)) {
newFramePosition = newFramePosition.offset(currentFramePosition);
}
Location actualFramePosition = currentFramePositionProvider.setPosition(newFramePosition);
bounds = bounds.offset(-actualFramePosition.getX(), -actualFramePosition.getY());
bounds = bounds.offset(currentFramePosition);
return bounds;
}
private void initPositionProvidersForCheckElement(boolean isScrollableElement, WebElement targetElement, CheckState state) {
// User might still call "fully" on a non-scrollable element, adjust the position provider accordingly.
if (isScrollableElement) {
state.setStitchPositionProvider(new ElementPositionProvider(logger, driver, targetElement));
} else { // Not a scrollable element but an element enclosed within a scroll-root-element
WebElement scrollRootElement = getCurrentFrameScrollRootElement();
if (getConfigurationInstance().getStitchMode() == StitchMode.CSS) {
state.setStitchPositionProvider(new CssTranslatePositionProvider(logger, driver, targetElement));
state.setOriginPositionProvider(new NullPositionProvider());
} else {
state.setStitchPositionProvider(new SeleniumScrollPositionProvider(logger, driver, scrollRootElement));
}
}
logger.verbose("isScrollableElement ?" + isScrollableElement);
}
public static WebElement tryGetUserDefinedSREFromSREContainer(IScrollRootElementContainer scrollRootElementContainer, EyesSeleniumDriver driver) {
WebElement scrollRootElement = scrollRootElementContainer.getScrollRootElement();
if (scrollRootElement == null) {
By scrollRootSelector = scrollRootElementContainer.getScrollRootSelector();
if (scrollRootSelector != null) {
scrollRootElement = driver.findElement(scrollRootSelector);
}
}
return scrollRootElement;
}
public static WebElement getScrollRootElementFromSREContainer(Logger logger, IScrollRootElementContainer scrollRootElementContainer, EyesSeleniumDriver driver) {
WebElement scrollRootElement = tryGetUserDefinedSREFromSREContainer(scrollRootElementContainer, driver);
if (scrollRootElement == null) {
scrollRootElement = EyesSeleniumUtils.getDefaultRootElement(logger, driver);
}
return scrollRootElement;
}
private PositionProvider getPositionProviderForScrollRootElement(WebElement scrollRootElement) {
return getPositionProviderForScrollRootElement(logger, driver, getConfigurationInstance().getStitchMode(), userAgent, scrollRootElement);
}
public static PositionProvider getPositionProviderForScrollRootElement(Logger logger, EyesSeleniumDriver driver, StitchMode stitchMode, UserAgent ua, WebElement scrollRootElement) {
PositionProvider positionProvider = PositionProviderFactory.tryGetPositionProviderForElement(scrollRootElement);
if (positionProvider == null) {
logger.verbose("creating a new position provider.");
WebElement defaultSRE = EyesSeleniumUtils.getDefaultRootElement(logger, driver);
if (scrollRootElement.equals(defaultSRE)) {
positionProvider = PositionProviderFactory.getPositionProvider(logger, stitchMode, driver, scrollRootElement, ua);
} else {
positionProvider = new ElementPositionProvider(logger, driver, scrollRootElement);
}
}
logger.verbose("position provider: " + positionProvider);
return positionProvider;
}
private Region computeEffectiveViewport(FrameChain frameChain, RectangleSize initialSize) {
Region viewport = new Region(Location.ZERO, initialSize);
if (userDefinedSRE != null) {
Region sreInnerBounds = EyesRemoteWebElement.getClientBoundsWithoutBorders(userDefinedSRE, driver, logger);
viewport.intersect(sreInnerBounds);
}
Location offset = Location.ZERO;
for (Frame frame : frameChain) {
offset = offset.offset(frame.getLocation());
Region frameViewport = new Region(offset, frame.getInnerSize());
viewport.intersect(frameViewport);
Region frameSreInnerBounds = frame.getScrollRootElementInnerBounds();
if (frameSreInnerBounds.isSizeEmpty()) {
continue;
}
frameSreInnerBounds = frameSreInnerBounds.offset(offset);
viewport.intersect(frameSreInnerBounds);
}
return viewport;
}
private WebElement getTargetElementFromSettings(ISeleniumCheckTarget seleniumCheckTarget) {
By targetSelector = seleniumCheckTarget.getTargetSelector();
WebElement targetElement = seleniumCheckTarget.getTargetElement();
if (targetElement == null && targetSelector != null) {
targetElement = driver.findElement(targetSelector);
} else if (targetElement != null && !(targetElement instanceof EyesRemoteWebElement)) {
targetElement = new EyesRemoteWebElement(logger, driver, targetElement);
}
return targetElement;
}
@Override
public void closeBatch(String batchId) {
this.getServerConnector().closeBatch(batchId);
}
/**
* Updates the state of scaling related parameters.
* @return the scale provider factory
*/
protected ScaleProviderFactory updateScalingParams() {
// Update the scaling params only if we haven't done so yet, and the user hasn't set anything else manually.
if (scaleProviderHandler.get() instanceof NullScaleProvider) {
ScaleProviderFactory factory;
logger.verbose("Trying to extract device pixel ratio...");
try {
devicePixelRatio = driver.getDevicePixelRatio();
} catch (Exception e) {
logger.verbose(
"Failed to extract device pixel ratio! Using default.");
devicePixelRatio = DEFAULT_DEVICE_PIXEL_RATIO;
}
logger.verbose(String.format("Device pixel ratio: %f", devicePixelRatio));
if (!EyesDriverUtils.isMobileDevice(driver)) {
logger.verbose("Setting web scale provider...");
factory = getScaleProviderFactory();
} else {
logger.verbose("Setting native app scale provider...");
factory = new FixedScaleProviderFactory(logger, 1 / devicePixelRatio, scaleProviderHandler);
}
logger.verbose("Done!");
return factory;
}
// If we already have a scale provider set, we'll just use it, and pass a mock as provider handler.
PropertyHandler nullProvider = new SimplePropertyHandler<>();
return new ScaleProviderIdentityFactory(logger, scaleProviderHandler.get(), nullProvider);
}
private ScaleProviderFactory getScaleProviderFactory() {
WebElement element = EyesSeleniumUtils.getDefaultRootElement(logger, driver);
RectangleSize entireSize = EyesDriverUtils.getEntireElementSize(logger, jsExecutor, element);
return new ContextBasedScaleProviderFactory(logger, entireSize, getConfigurationInstance().getViewportSize(),
devicePixelRatio, false, scaleProviderHandler);
}
/**
* Gets current frame scroll root element.
* @return the current frame scroll root element
*/
public WebElement getCurrentFrameScrollRootElement() {
return EyesSeleniumUtils.getCurrentFrameScrollRootElement(logger, driver, userDefinedSRE);
}
/**
* Verifies the current frame.
* @param matchTimeout The amount of time to retry matching. (Milliseconds)
* @param tag An optional tag to be associated with the snapshot.
*/
protected void checkCurrentFrame(int matchTimeout, String tag, String source) {
try {
logger.verbose(String.format("CheckCurrentFrame(%d, '%s')", matchTimeout, tag));
checkFrameOrElement = true;
logger.verbose("Getting screenshot as base64..");
String screenshot64 = driver.getScreenshotAs(OutputType.BASE64);
logger.verbose("Done! Creating image object...");
BufferedImage screenshotImage = ImageUtils.imageFromBase64(screenshot64);
// FIXME - Scaling should be handled in a single place instead
ScaleProvider scaleProvider = updateScalingParams().getScaleProvider(screenshotImage.getWidth());
screenshotImage = ImageUtils.scaleImage(screenshotImage, scaleProvider.getScaleRatio());
logger.verbose("Done! Building required object...");
final EyesWebDriverScreenshot screenshot = new EyesWebDriverScreenshot(logger, driver, screenshotImage);
logger.verbose("Done!");
logger.verbose("replacing regionToCheck");
setRegionToCheck(screenshot.getFrameWindow());
super.checkWindowBase(null, tag, matchTimeout, source);
} finally {
checkFrameOrElement = false;
regionToCheck = null;
}
}
/**
* See {@link #checkFrame(String, int, String)}.
* {@code tag} defaults to {@code null}. Default match timeout is used.
* @param frameNameOrId the frame name or id
*/
public void checkFrame(String frameNameOrId) {
check(null, Target.frame(frameNameOrId));
}
/**
* See {@link #checkFrame(String, int, String)}.
* Default match timeout is used.
* @param frameNameOrId the frame name or id
* @param tag the tag
*/
public void checkFrame(String frameNameOrId, String tag) {
check(tag, Target.frame(frameNameOrId).fully());
}
/**
* Matches the frame given as parameter, by switching into the frame and
* using stitching to get an image of the frame.
* @param frameNameOrId The name or id of the frame to check. (The same name/id as would be used in a call to driver.switchTo().frame()).
* @param matchTimeout The amount of time to retry matching. (Milliseconds)
* @param tag An optional tag to be associated with the match.
*/
public void checkFrame(String frameNameOrId, int matchTimeout, String tag) {
check(tag, Target.frame(frameNameOrId).timeout(matchTimeout).fully());
}
/**
* See {@link #checkFrame(int, int, String)}.
* {@code tag} defaults to {@code null}. Default match timeout is used.
* @param frameIndex the frame index
*/
public void checkFrame(int frameIndex) {
checkFrame(frameIndex, USE_DEFAULT_MATCH_TIMEOUT, null);
}
/**
* See {@link #checkFrame(int, int, String)}.
* Default match timeout is used.
* @param frameIndex the frame index
* @param tag the tag
*/
public void checkFrame(int frameIndex, String tag) {
checkFrame(frameIndex, USE_DEFAULT_MATCH_TIMEOUT, tag);
}
/**
* Matches the frame given as parameter, by switching into the frame and
* using stitching to get an image of the frame.
* @param frameIndex The index of the frame to switch to. (The same index as would be used in a call to driver.switchTo().frame()).
* @param matchTimeout The amount of time to retry matching. (Milliseconds)
* @param tag An optional tag to be associated with the match.
*/
public void checkFrame(int frameIndex, int matchTimeout, String tag) {
if (getIsDisabled()) {
logger.log(String.format("CheckFrame(%d, %d, '%s'): Ignored", frameIndex, matchTimeout, tag));
return;
}
ArgumentGuard.greaterThanOrEqualToZero(frameIndex, "frameIndex");
logger.log(String.format("CheckFrame(%d, %d, '%s')", frameIndex, matchTimeout, tag));
check(tag, Target.frame(frameIndex).timeout(matchTimeout).fully());
}
/**
* See {@link #checkFrame(WebElement, int, String)}.
* {@code tag} defaults to {@code null}.
* Default match timeout is used.
* @param frameReference the frame reference
*/
public void checkFrame(WebElement frameReference) {
checkFrame(frameReference, USE_DEFAULT_MATCH_TIMEOUT, null);
}
/**
* See {@link #checkFrame(WebElement, int, String)}.
* Default match timeout is used.
* @param frameReference the frame reference
* @param tag the tag
*/
public void checkFrame(WebElement frameReference, String tag) {
checkFrame(frameReference, USE_DEFAULT_MATCH_TIMEOUT, tag);
}
/**
* Matches the frame given as parameter, by switching into the frame and
* using stitching to get an image of the frame.
* @param frameReference The element which is the frame to switch to. (as would be used in a call to driver.switchTo().frame() ).
* @param matchTimeout The amount of time to retry matching (milliseconds).
* @param tag An optional tag to be associated with the match.
*/
public void checkFrame(WebElement frameReference, int matchTimeout, String tag) {
check(tag, Target.frame(frameReference).timeout(matchTimeout));
}
/**
* Matches the frame given by the frames path, by switching into the frame
* and using stitching to get an image of the frame.
* @param framePath The path to the frame to check. This is a list of frame names/IDs (where each frame is nested in the previous frame).
* @param matchTimeout The amount of time to retry matching (milliseconds).
* @param tag An optional tag to be associated with the match.
*/
public void checkFrame(String[] framePath, int matchTimeout, String tag) {
SeleniumCheckSettings settings = Target.frame(framePath[0]);
for (int i = 1; i < framePath.length; i++) {
settings.frame(framePath[i]);
}
check(tag, settings.timeout(matchTimeout).fully());
}
/**
* Switches into the given frame, takes a snapshot of the application under
* test and matches a region specified by the given selector.
* @param framePath The path to the frame to check. This is a list of
* frame names/IDs (where each frame is nested in the previous frame).
* @param selector A Selector specifying the region to check.
* @param matchTimeout The amount of time to retry matching (milliseconds).
* @param tag An optional tag to be associated with the snapshot.
* @param stitchContent Whether or not to stitch the internal content of the
* region (i.e., perform {@link #checkElement(By, int, String)} on the region.
*/
public void checkRegionInFrame(String[] framePath, By selector,
int matchTimeout, String tag,
boolean stitchContent) {
SeleniumCheckSettings settings = Target.frame(framePath[0]);
for (int i = 1; i < framePath.length; i++) {
settings = settings.frame(framePath[i]);
}
check(tag, settings.region(selector).timeout(matchTimeout).fully(stitchContent));
}
/**
* Takes a snapshot of the application under test and matches a specific
* element with the expected region output.
* @param element The element to check.
* @param matchTimeout The amount of time to retry matching. (Milliseconds)
* @param tag An optional tag to be associated with the snapshot.
* @throws TestFailedException if a mismatch is detected and immediate failure reports are enabled
*/
public void checkElement(WebElement element, int matchTimeout, String tag) {
check(tag, Target.region(element).timeout(matchTimeout).fully());
}
/**
* See {@link #checkElement(By, String)}.
* {@code tag} defaults to {@code null}.
* @param selector the selector
*/
public void checkElement(By selector) {
check(null, Target.region(selector).fully());
}
/**
* See {@link #checkElement(By, int, String)}.
* Default match timeout is used.
* @param selector the selector
* @param tag the tag
*/
public void checkElement(By selector, String tag) {
check(tag, Target.region(selector).fully());
}
/**
* Takes a snapshot of the application under test and matches an element
* specified by the given selector with the expected region output.
* @param selector Selects the element to check.
* @param matchTimeout The amount of time to retry matching. (Milliseconds)
* @param tag An optional tag to be associated with the screenshot.
* @throws TestFailedException if a mismatch is detected and immediate failure reports are enabled
*/
public void checkElement(By selector, int matchTimeout, String tag) {
check(tag, Target.region(selector).timeout(matchTimeout).fully());
}
/**
* Adds a mouse trigger.
* @param action Mouse action.
* @param control The control on which the trigger is activated (context relative coordinates).
* @param cursor The cursor's position relative to the control.
*/
public void addMouseTrigger(MouseAction action, Region control, Location cursor) {
if (getIsDisabled()) {
logger.verbose(String.format("Ignoring %s (disabled)", action));
return;
}
// Triggers are actually performed on the previous window.
if (lastScreenshot == null) {
logger.verbose(String.format("Ignoring %s (no screenshot)", action));
return;
}
if (!FrameChain.isSameFrameChain(driver.getFrameChain(),
((EyesWebDriverScreenshot) lastScreenshot).getFrameChain())) {
logger.verbose(String.format("Ignoring %s (different frame)", action));
return;
}
addMouseTriggerBase(action, control, cursor);
}
/**
* Adds a mouse trigger.
* @param action Mouse action.
* @param element The WebElement on which the click was called.
*/
public void addMouseTrigger(MouseAction action, WebElement element) {
if (getIsDisabled()) {
logger.verbose(String.format("Ignoring %s (disabled)", action));
return;
}
ArgumentGuard.notNull(element, "element");
Point pl = element.getLocation();
Dimension ds = element.getSize();
Region elementRegion = new Region(pl.getX(), pl.getY(), ds.getWidth(),
ds.getHeight());
// Triggers are actually performed on the previous window.
if (lastScreenshot == null) {
logger.verbose(String.format("Ignoring %s (no screenshot)", action));
return;
}
if (!FrameChain.isSameFrameChain(driver.getFrameChain(),
((EyesWebDriverScreenshot) lastScreenshot).getFrameChain())) {
logger.verbose(String.format("Ignoring %s (different frame)", action));
return;
}
// Get the element region which is intersected with the screenshot,
// so we can calculate the correct cursor position.
elementRegion = lastScreenshot.getIntersectedRegion
(elementRegion, CoordinatesType.CONTEXT_RELATIVE);
addMouseTriggerBase(action, elementRegion,
elementRegion.getMiddleOffset());
}
/**
* Adds a keyboard trigger.
* @param control The control's context-relative region.
* @param text The trigger's text.
*/
public void addTextTrigger(Region control, String text) {
if (getIsDisabled()) {
logger.verbose(String.format("Ignoring '%s' (disabled)", text));
return;
}
if (lastScreenshot == null) {
logger.verbose(String.format("Ignoring '%s' (no screenshot)", text));
return;
}
if (!FrameChain.isSameFrameChain(driver.getFrameChain(),
((EyesWebDriverScreenshot) lastScreenshot).getFrameChain())) {
logger.verbose(String.format("Ignoring '%s' (different frame)", text));
return;
}
addTextTriggerBase(control, text);
}
/**
* Adds a keyboard trigger.
* @param element The element for which we sent keys.
* @param text The trigger's text.
*/
public void addTextTrigger(WebElement element, String text) {
if (getIsDisabled()) {
logger.verbose(String.format("Ignoring '%s' (disabled)", text));
return;
}
ArgumentGuard.notNull(element, "element");
Point pl = element.getLocation();
Dimension ds = element.getSize();
Region elementRegion = new Region(pl.getX(), pl.getY(), ds.getWidth(), ds.getHeight());
addTextTrigger(elementRegion, text);
}
/**
* Use this method only if you made a previous call to {@link #open
* (WebDriver, String, String)} or one of its variants.
*
* {@inheritDoc}
*/
@Override
public RectangleSize getViewportSize() {
if (!isOpen && driver == null) {
logger.log("Called getViewportSize before calling open");
return null;
}
RectangleSize vpSize;
if (!EyesDriverUtils.isMobileDevice(driver)) {
if (imageProvider instanceof MobileScreenshotImageProvider) {
BufferedImage image = imageProvider.getImage();
vpSize = new RectangleSize((int) Math.round(image.getWidth() / devicePixelRatio), (int) Math.round(image.getHeight() / devicePixelRatio));
} else {
vpSize = EyesDriverUtils.getViewportSize(driver);
}
} else {
vpSize = getViewportSize(driver);
}
return vpSize;
}
/**
* @param driver The driver to use for getting the viewport.
* @return The viewport size of the current context.
*/
static RectangleSize getViewportSize(WebDriver driver) {
ArgumentGuard.notNull(driver, "driver");
return EyesDriverUtils.getViewportSizeOrDisplaySize(new Logger(), driver);
}
/**
* Use this method only if you made a previous call to {@link #open
* (WebDriver, String, String)} or one of its variants.
*
* {@inheritDoc}
*/
@Override
protected Configuration setViewportSize(RectangleSize size) {
if (!EyesDriverUtils.isMobileDevice(driver)) {
FrameChain originalFrame = driver.getFrameChain();
driver.switchTo().defaultContent();
try {
EyesDriverUtils.setViewportSize(logger, driver, size);
effectiveViewport = new Region(Location.ZERO, size);
} catch (EyesException e1) {
// Just in case the user catches this error
((EyesTargetLocator) driver.switchTo()).frames(originalFrame);
GeneralUtils.logExceptionStackTrace(logger, e1);
throw new TestFailedException("Failed to set the viewport size", e1);
}
((EyesTargetLocator) driver.switchTo()).frames(originalFrame);
}
getConfigurationInstance().setViewportSize(new RectangleSize(size.getWidth(), size.getHeight()));
return getConfigurationInstance();
}
@Override
protected EyesScreenshot getScreenshot(Region targetRegion, ICheckSettingsInternal checkSettingsInternal) {
ScaleProviderFactory scaleProviderFactory = updateScalingParams();
EyesWebDriverScreenshot result;
CheckState state = ((ISeleniumCheckTarget) checkSettingsInternal).getState();
WebElement targetElement = state.getTargetElementInternal();
boolean stitchContent = state.isStitchContent();
Region effectiveViewport = state.getEffectiveViewport();
Region fullRegion = state.getFullRegion();
if (effectiveViewport.contains(fullRegion) && !fullRegion.isEmpty()) {
logger.verbose("effectiveViewport: " + effectiveViewport + " ; fullRegion: " + fullRegion);
result = getViewportScreenshot(scaleProviderFactory);
result = result.getSubScreenshot(fullRegion, true);
} else if (targetElement != null || stitchContent) {
result = getFrameOrElementScreenshot(scaleProviderFactory, state);
} else {
result = getViewportScreenshot(scaleProviderFactory);
}
if (targetRegion != null) {
result = result.getSubScreenshot(targetRegion, false);
debugScreenshotsProvider.save(result.getImage(), "SUB_SCREENSHOT");
}
if (!EyesDriverUtils.isMobileDevice(driver)) {
result.setDomUrl(tryCaptureAndPostDom(checkSettingsInternal));
}
return result;
}
private EyesWebDriverScreenshot getViewportScreenshot(ScaleProviderFactory scaleProviderFactory) {
try {
Thread.sleep(getWaitBeforeScreenshots());
} catch (InterruptedException e) {
e.printStackTrace();
}
EyesWebDriverScreenshot result = getScaledAndCroppedScreenshot(scaleProviderFactory);
return result;
}
boolean shouldTakeFullPageScreenshot(ICheckSettingsInternal checkSettingsInternal) {
Boolean isFully = checkSettingsInternal.getStitchContent();
if (isFully != null) {
return isFully;
}
Boolean isForceFullPage = getConfigurationInstance().getForceFullPageScreenshot();
if (isForceFullPage == null) {
return false;
}
return isForceFullPage;
}
private EyesWebDriverScreenshot getFrameOrElementScreenshot(ScaleProviderFactory scaleProviderFactory, CheckState state) {
RenderingInfo renderingInfo = serverConnector.getRenderInfo();
FullPageCaptureAlgorithm algo = createFullPageCaptureAlgorithm(scaleProviderFactory, renderingInfo);
EyesWebDriverScreenshot result;
logger.verbose("Check frame/element requested");
PositionProvider positionProvider = state.getStitchPositionProvider();
PositionProvider originPositionProvider = state.getOriginPositionProvider();
if (positionProvider == null) {
logger.verbose("positionProvider is null, updating it.");
WebElement scrollRootElement = getCurrentFrameScrollRootElement();
positionProvider = getPositionProviderForScrollRootElement(logger, driver, getConfigurationInstance().getStitchMode(), userAgent, scrollRootElement);
}
if (originPositionProvider == null) {
originPositionProvider = new NullPositionProvider();
}
markElementForLayoutRCA(positionProvider);
BufferedImage entireFrameOrElement = algo.getStitchedRegion(state.getEffectiveViewport(), state.getFullRegion(), positionProvider, originPositionProvider, state.getStitchOffset());
logger.verbose("Building screenshot object...");
Location frameLocationInScreenshot = new Location(-state.getFullRegion().getLeft(), -state.getFullRegion().getTop());
result = new EyesWebDriverScreenshot(logger, driver, entireFrameOrElement,
new RectangleSize(entireFrameOrElement.getWidth(), entireFrameOrElement.getHeight()),
frameLocationInScreenshot);
return result;
}
private EyesWebDriverScreenshot getScaledAndCroppedScreenshot(ScaleProviderFactory scaleProviderFactory) {
BufferedImage screenshotImage = this.imageProvider.getImage();
debugScreenshotsProvider.save(screenshotImage, "original");
ScaleProvider scaleProvider = scaleProviderFactory.getScaleProvider(screenshotImage.getWidth());
CutProvider cutProvider = cutProviderHandler.get();
if (scaleProvider.getScaleRatio() != 1.0) {
screenshotImage = ImageUtils.scaleImage(screenshotImage, scaleProvider.getScaleRatio());
debugScreenshotsProvider.save(screenshotImage, "scaled");
cutProvider.scale(scaleProvider.getScaleRatio());
}
if (!(cutProvider instanceof NullCutProvider)) {
screenshotImage = cutProvider.cut(screenshotImage);
debugScreenshotsProvider.save(screenshotImage, "cut");
}
EyesWebDriverScreenshot result = new EyesWebDriverScreenshot(logger, driver, screenshotImage);
return result;
}
private long getWaitBeforeScreenshots() {
return getConfigurationInstance().getWaitBeforeScreenshots();
}
private void markElementForLayoutRCA(PositionProvider elemPositionProvider) {
ISeleniumPositionProvider positionProvider = elemPositionProvider != null ? (ISeleniumPositionProvider) elemPositionProvider : ((ISeleniumPositionProvider) getPositionProvider());
WebElement scrolledElement = positionProvider.getScrolledElement();
if (scrolledElement != null) {
try {
jsExecutor.executeScript("var e = arguments[0]; if (e != null) e.setAttribute('data-applitools-scroll','true');", scrolledElement);
} catch (Exception e) {
GeneralUtils.logExceptionStackTrace(logger, e);
}
}
}
private FullPageCaptureAlgorithm createFullPageCaptureAlgorithm(ScaleProviderFactory scaleProviderFactory, RenderingInfo renderingInfo) {
ISizeAdjuster sizeAdjuster = ImageProviderFactory.getImageSizeAdjuster(userAgent, jsExecutor);
return new FullPageCaptureAlgorithm(logger, regionPositionCompensation,
getConfigurationInstance().getWaitBeforeScreenshots(), debugScreenshotsProvider, screenshotFactory,
scaleProviderFactory, cutProviderHandler.get(), getConfigurationInstance().getStitchOverlap(),
imageProvider, renderingInfo.getMaxImageHeight(), renderingInfo.getMaxImageArea(), sizeAdjuster);
}
@Override
protected String getTitle() {
if (!doNotGetTitle && !EyesDriverUtils.isMobileDevice(driver)) {
try {
return driver.getTitle();
} catch (Exception ex) {
logger.verbose("failed (" + ex.getMessage() + ")");
doNotGetTitle = true;
}
}
return "";
}
@Override
protected String getInferredEnvironment() {
String userAgent = driver.getUserAgent();
if (userAgent != null) {
return "useragent:" + userAgent;
}
return null;
}
/**
* {@inheritDoc}
*
* This override also checks for mobile operating system.
*/
@Override
protected AppEnvironment getAppEnvironment() {
AppEnvironment appEnv = super.getAppEnvironment();
RemoteWebDriver underlyingDriver = driver.getRemoteWebDriver();
// If hostOs isn't set, we'll try and extract and OS ourselves.
if (appEnv.getOs() == null) {
logger.log("No OS set, checking for mobile OS...");
if (EyesDriverUtils.isMobileDevice(underlyingDriver)) {
String platformName = null;
logger.log("Mobile device detected! Checking device type..");
if (EyesDriverUtils.isAndroid(underlyingDriver)) {
logger.log("Android detected.");
platformName = "Android";
} else if (EyesDriverUtils.isIOS(underlyingDriver)) {
logger.log("iOS detected.");
platformName = "iOS";
} else {
logger.log("Unknown device type.");
}
// We only set the OS if we identified the device type.
if (platformName != null) {
String os = platformName;
String platformVersion =
EyesDriverUtils.getPlatformVersion(underlyingDriver);
if (platformVersion != null) {
String majorVersion =
platformVersion.split("\\.", 2)[0];
if (!majorVersion.isEmpty()) {
os += " " + majorVersion;
}
}
logger.verbose("Setting OS: " + os);
appEnv.setOs(os);
}
appEnv.setDeviceInfo(EyesDriverUtils.getMobileDeviceName(driver.getRemoteWebDriver()));
} else {
logger.log("No mobile OS detected.");
}
}
logger.log("Done!");
return appEnv;
}
@Override
public void setIsDisabled(Boolean disabled) {
super.setIsDisabled(disabled);
}
@Override
protected String getAUTSessionId() {
try {
if (this.cachedAUTSessionId == null) {
this.cachedAUTSessionId = driver.getRemoteWebDriver().getSessionId().toString();
}
return this.cachedAUTSessionId;
} catch (Exception e) {
logger.log("WARNING: Failed to get AUT session ID! (maybe driver is not available?). Error: "
+ e.getMessage());
return "";
}
}
@Override
public TestResults close(boolean throwEx) {
TestResults results = null;
try {
results = super.close(throwEx);
} catch (Throwable e) {
logger.log(e.getMessage());
if (throwEx) {
throw e;
}
}
if (runner != null) {
this.runner.aggregateResult(results);
}
this.cachedAUTSessionId = null;
getServerConnector().closeConnector();
return results;
}
/**
* The type Eyes selenium agent setup.
*/
@SuppressWarnings("UnusedDeclaration")
class EyesSeleniumAgentSetup {
/**
* The type Web driver info.
*/
class WebDriverInfo {
/**
* Gets name.
* @return the name
*/
public String getName() {
return remoteWebDriver.getClass().getName();
}
/**
* Gets capabilities.
* @return the capabilities
*/
public Capabilities getCapabilities() {
return remoteWebDriver.getCapabilities();
}
}
/**
* Instantiates a new Eyes selenium agent setup.
*/
public EyesSeleniumAgentSetup() {
remoteWebDriver = driver.getRemoteWebDriver();
}
private RemoteWebDriver remoteWebDriver;
/**
* Gets selenium session id.
* @return the selenium session id
*/
public String getSeleniumSessionId() {
return remoteWebDriver.getSessionId().toString();
}
/**
* Gets web driver.
* @return the web driver
*/
public WebDriverInfo getWebDriver() {
return new WebDriverInfo();
}
/**
* Gets stitch mode.
* @return the stitch mode
*/
public StitchMode getStitchMode() {
return SeleniumEyes.this.getConfigurationInstance().getStitchMode();
}
/**
* Gets hide scrollbars.
* @return the hide scrollbars
*/
public boolean getHideScrollbars() {
return SeleniumEyes.this.getConfigurationInstance().getHideScrollbars();
}
/**
* Gets force full page screenshot.
* @return the force full page screenshot
*/
public boolean getForceFullPageScreenshot() {
Boolean forceFullPageScreenshot = getConfigurationInstance().getForceFullPageScreenshot();
if (forceFullPageScreenshot == null) return false;
return forceFullPageScreenshot;
}
}
@Override
public Object getAgentSetup() {
return new EyesSeleniumAgentSetup();
}
@Override
public Boolean isSendDom() {
return !EyesDriverUtils.isMobileDevice(driver) && super.isSendDom();
}
@Override
protected Configuration getConfigurationInstance() {
return configurationProvider.get();
}
/**
* For test purposes only.
*/
void setDebugScreenshotProvider(DebugScreenshotsProvider debugScreenshotProvider) {
this.debugScreenshotsProvider = debugScreenshotProvider;
}
public UserAgent getUserAgent() {
return userAgent;
}
/**
* Gets scale provider.
* @return the scale provider
*/
public ScaleProvider getScaleProvider() {
return scaleProviderHandler.get();
}
public CutProvider getCutProvider() {
return cutProviderHandler.get();
}
}