com.applitools.eyes.EyesBase Maven / Gradle / Ivy
Show all versions of eyes-sdk-java-jersey2x Show documentation
package com.applitools.eyes;
import com.applitools.utils.*;
import org.apache.commons.codec.binary.Base64;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayDeque;
import java.util.Queue;
/**
* Applitools Eyes Base for Java API .
*/
abstract class EyesBase {
private static final int DEFAULT_MATCH_TIMEOUT = 2; // Seconds
protected static final int USE_DEFAULT_TIMEOUT = -1;
public static final String DEFAULT_CHARSET_NAME = "UTF-8";
private boolean shouldMatchWindowRunOnceOnTimeout;
protected ServerConnector serverConnector;
private MatchWindowTask matchWindowTask;
protected RunningSession runningSession;
protected SessionStartInfo sessionStartInfo;
protected RectangleSize viewportSize;
protected EyesScreenshot lastScreenshot;
protected PropertyHandler scaleProviderHandler;
protected PositionProvider positionProvider;
// Will be checked before any argument validation. If true,
// all method will immediately return without performing any action.
private boolean isDisabled;
protected Logger logger;
private boolean isOpen;
private String agentId;
/**
* Will be set for separately for each test.
*/
private String currentAppName;
/**
* The default app name if no current name was provided. If this is
* {@code null} then there is no default appName.
*/
private String appName;
private SessionType sessionType;
private String testName;
private ImageMatchSettings defaultMatchSettings;
private int matchTimeout;
private BatchInfo batch;
private String hostApp;
private String hostOS;
private String baselineName;
private ScaleMethod scaleMethod;
private String branchName;
private String parentBranchName;
private FailureReports failureReports;
private final Queue userInputs;
// Used for automatic save of a test run.
private boolean saveNewTests, saveFailedTests;
/**
* Creates a new {@code EyesBase}instance that interacts with the Eyes
* Server at the specified url.
*
* @param serverUrl The Eyes server URL.
*/
public EyesBase(URI serverUrl) {
if (isDisabled) {
userInputs = null;
return;
}
ArgumentGuard.notNull(serverUrl, "serverUrl");
logger = new Logger();
scaleProviderHandler = new SimplePropertyHandler();
scaleProviderHandler.set(new NullScaleProvider());
positionProvider = new InvalidPositionProvider();
scaleMethod = ScaleMethod.getDefault();
viewportSize = null;
serverConnector = new ServerConnector(logger, getBaseAgentId(),
serverUrl);
matchTimeout = DEFAULT_MATCH_TIMEOUT;
runningSession = null;
defaultMatchSettings = new ImageMatchSettings();
failureReports = FailureReports.ON_CLOSE;
userInputs = new ArrayDeque();
// New tests are automatically saved by default.
saveNewTests = true;
saveFailedTests = false;
agentId = null;
lastScreenshot = null;
}
@SuppressWarnings("UnusedDeclaration")
/**
* Sets the user given agent id of the SDK. {@code null} is referred to
* as no id.
*
* @param agentId The agent ID to set.
*/
public void setAgentId(String agentId) {
this.agentId = agentId;
}
/**
* @return The user given agent id of the SDK.
*/
public String getAgentId() {
return agentId;
}
/**
* Sets the API key of your applitools Eyes account.
*
* @param apiKey The api key to set.
*/
@SuppressWarnings("UnusedDeclaration")
public void setApiKey(String apiKey) {
ArgumentGuard.notNull(apiKey, "apiKey");
serverConnector.setApiKey(apiKey);
}
/**
* @return The currently set API key or {@code null} if no key is set.
*/
public String getApiKey() {
return serverConnector.getApiKey();
}
/**
* Sets the current server URL used by the rest client.
* @param serverUrl The URI of the rest server, or {@code null} to use
* the default server.
*/
@SuppressWarnings("UnusedDeclaration")
public void setServerUrl(URI serverUrl) {
if (serverUrl == null) {
serverConnector.setServerUrl(getDefaultServerUrl());
} else {
serverConnector.setServerUrl(serverUrl);
}
}
/**
*
* @return The URI of the eyes server.
*/
@SuppressWarnings("UnusedDeclaration")
public URI getServerUrl() {
return serverConnector.getServerUrl();
}
/**
* Sets the proxy settings to be used by the rest client.
* @param proxySettings The proxy settings to be used by the rest client.
* If {@code null} then no proxy is set.
*/
@SuppressWarnings("UnusedDeclaration")
public void setProxy(ProxySettings proxySettings) {
serverConnector.setProxy(proxySettings);
}
/**
*
* @return The current proxy settings used by the server connector,
* or {@code null} if no proxy is set.
*/
@SuppressWarnings("UnusedDeclaration")
public ProxySettings getProxy() {
return serverConnector.getProxy();
}
/**
*
* @param isDisabled If true, all interactions with this API will be
* silently ignored.
*/
@SuppressWarnings("UnusedDeclaration")
public void setIsDisabled(boolean isDisabled) {
this.isDisabled = isDisabled;
}
/**
* @return Whether eyes is disabled.
*/
@SuppressWarnings("UnusedDeclaration")
public boolean getIsDisabled() {
return isDisabled;
}
@SuppressWarnings("unused")
/**
*
* @param appName The name of the application under test.
*/
public void setAppName(String appName) {
this.appName = appName;
}
@SuppressWarnings("unused")
/**
*
* @return The name of the application under test.
*/
public String getAppName() {
return currentAppName != null ? currentAppName : appName;
}
/**
* Sets the branch in which the baseline for subsequent test runs resides.
* If the branch does not already exist it will be created under the
* specified parent branch (see {@link #setParentBranchName}).
* Changes to the baseline or model of a branch do not propagate to other
* branches.
*
* @param branchName Branch name or {@code null} to specify the default
* branch.
*/
@SuppressWarnings("UnusedDeclaration")
public void setBranchName(String branchName) {
this.branchName = branchName;
}
/**
*
* @return The current branch (see {@link #setBranchName(String)}).
*/
@SuppressWarnings("UnusedDeclaration")
public String getBranchName() {
return branchName;
}
/**
* Sets the branch under which new branches are created. (see {@link
* #setBranchName(String)}.
*
* @param branchName Branch name or {@code null} to specify the default
* branch.
*/
@SuppressWarnings("UnusedDeclaration")
public void setParentBranchName(String branchName) {
this.parentBranchName = branchName;
}
/**
*
* @return The name of the current parent branch under which new branches
* will be created. (see {@link #setParentBranchName(String)}).
*/
@SuppressWarnings("UnusedDeclaration")
public String getParentBranchName() {
return parentBranchName;
}
/**
* Clears the user inputs list.
*/
protected void clearUserInputs() {
if (isDisabled) {
return;
}
userInputs.clear();
}
/**
* @return User inputs collected between {@code checkWindowBase}
* invocations.
*/
protected Trigger[] getUserInputs() {
if (isDisabled) {
return null;
}
Trigger[] result = new Trigger[userInputs.size()];
return userInputs.toArray(result);
}
/**
* Sets the maximal time (in seconds) a match operation tries to perform
* a match.
*
* @param seconds Total number of seconds to wait for a match.
*/
@SuppressWarnings("UnusedDeclaration")
public void setMatchTimeout(int seconds) {
if (getIsDisabled()) {
logger.verbose("Ignored");
return;
}
logger.verbose("setMatchTimeout(" + seconds + ")");
ArgumentGuard.greaterThanOrEqualToZero(seconds, "seconds");
this.matchTimeout = seconds;
logger.log("Match timeout set to " + seconds + " second(s)");
}
/**
* @return The maximum time in seconds {@link #checkWindowBase
* (RegionProvider, String, boolean, int)} waits for a match.
*/
@SuppressWarnings("UnusedDeclaration")
public int getMatchTimeout() {
return matchTimeout;
}
/**
* Set whether or not new tests are saved by default.
*
* @param saveNewTests True if new tests should be saved by default.
* False otherwise.
*/
@SuppressWarnings("UnusedDeclaration")
public void setSaveNewTests(boolean saveNewTests) {
this.saveNewTests = saveNewTests;
}
/**
* @return True if new tests are saved by default.
*/
@SuppressWarnings("UnusedDeclaration")
public boolean getSaveNewTests() {
return saveNewTests;
}
/**
* Set whether or not failed tests are saved by default.
*
* @param saveFailedTests True if failed tests should be saved by
* default, false otherwise.
*/
@SuppressWarnings("UnusedDeclaration")
public void setSaveFailedTests(boolean saveFailedTests) {
this.saveFailedTests = saveFailedTests;
}
/**
* @return True if failed tests are saved by default.
*/
@SuppressWarnings("UnusedDeclaration")
public boolean getSaveFailedTests() {
return saveFailedTests;
}
/**
* Sets the batch in which context future tests will run or {@code null}
* if tests are to run standalone.
*
* @param batch The batch info to set.
*/
@SuppressWarnings("UnusedDeclaration")
public void setBatch(BatchInfo batch) {
if (isDisabled) {
logger.verbose("Ignored");
return;
}
logger.verbose("setBatch(" + batch + ")");
this.batch = batch;
}
/**
* @return The currently set batch info.
*/
@SuppressWarnings("UnusedDeclaration")
public BatchInfo getBatch() {
return batch;
}
/**
* @param failureReports The failure reports setting.
* @see FailureReports
*/
@SuppressWarnings("UnusedDeclaration")
public void setFailureReports(FailureReports failureReports) {
this.failureReports = failureReports;
}
/**
* @return the failure reports setting.
*/
@SuppressWarnings("UnusedDeclaration")
public FailureReports getFailureReports() {
return failureReports;
}
@SuppressWarnings("UnusedDeclaration")
/**
* Updates the match settings to be used for the session.
*
* @param defaultMatchSettings The match settings to be used for the
* session.
*/
public void setDefaultMatchSettings(ImageMatchSettings
defaultMatchSettings) {
ArgumentGuard.notNull(defaultMatchSettings, "defaultMatchSettings");
this.defaultMatchSettings = defaultMatchSettings;
}
@SuppressWarnings("UnusedDeclaration")
/**
*
* @return The match settings used for the session.
*/
public ImageMatchSettings getDefaultMatchSettings() {
return defaultMatchSettings;
}
/**
* This function is deprecated. Please use
* {@link #setDefaultMatchSettings} instead.
*
* The test-wide match level to use when checking application screenshot
* with the expected output.
*
* @param matchLevel The match level setting.
* @see com.applitools.eyes.MatchLevel
*/
@SuppressWarnings("UnusedDeclaration")
public void setMatchLevel(MatchLevel matchLevel) {
this.defaultMatchSettings.setMatchLevel(matchLevel);
}
/**
* @deprecated Please use{@link #getDefaultMatchSettings} instead.
* @return The test-wide match level.
*/
@SuppressWarnings("UnusedDeclaration")
public MatchLevel getMatchLevel() {
return defaultMatchSettings.getMatchLevel();
}
/**
* @return The base agent id of the SDK.
*/
protected abstract String getBaseAgentId();
/**
* @return The full agent id composed of both the base agent id and the
* user given agent id.
*/
protected String getFullAgentId() {
String agentId = getAgentId();
if (agentId == null) {
return getBaseAgentId();
}
return String.format("%s [%s]", agentId, getBaseAgentId());
}
/**
* @return Whether a session is open.
*/
@SuppressWarnings("UnusedDeclaration")
public boolean getIsOpen() {
return isOpen;
}
public static URI getDefaultServerUrl() {
try {
return new URI("https://eyessdk.applitools.com");
} catch (URISyntaxException ex) {
throw new EyesException(ex.getMessage(), ex);
}
}
/**
* Sets a handler of log messages generated by this API.
*
* @param logHandler Handles log messages generated by this API.
*/
@SuppressWarnings("UnusedDeclaration")
public void setLogHandler(LogHandler logHandler) {
logger.setLogHandler(logHandler);
}
/**
* @return The currently set log handler.
*/
@SuppressWarnings("UnusedDeclaration")
public LogHandler getLogHandler() {
return logger.getLogHandler();
}
@SuppressWarnings("unused")
/**
* Manually set the scale ratio for the images being validated.
* @param scaleRatio The scale ratio to use, or {@code null} to reset
* back to automatic scaling.
*/
public void setScaleRatio(Double scaleRatio) {
if (scaleRatio != null) {
scaleProviderHandler = new ReadOnlyPropertyHandler(
logger, new FixedScaleProvider(scaleRatio));
} else {
scaleProviderHandler = new SimplePropertyHandler();
scaleProviderHandler.set(new NullScaleProvider());
}
}
@SuppressWarnings("unused")
/**
*
* @return The ratio used to scale the images being validated.
*/
public double getScaleRatio() {
return scaleProviderHandler.get().getScaleRatio();
}
/**
*
* @param method The method used to perform scaling.
*/
protected void setScaleMethod(ScaleMethod method) {
ArgumentGuard.notNull(method, "method");
scaleMethod = method;
}
protected ScaleMethod getScaleMethod() {
return scaleMethod;
}
/**
* See {@link #close(boolean)}.
* {@code throwEx} defaults to {@code true}.
*
* @return The test results.
*/
public TestResults close() {
return close(true);
}
/**
* Ends the test.
*
* @param throwEx If true, an exception will be thrown for failed/new tests.
* @return The test results.
* @throws TestFailedException if a mismatch was found and throwEx is true.
* @throws NewTestException if this is a new test was found and throwEx
* is true.
*/
public TestResults close(boolean throwEx) {
try {
if (isDisabled) {
logger.verbose("Ignored");
return null;
}
logger.verbose(String.format("close(%b)", throwEx));
ArgumentGuard.isValidState(isOpen, "Eyes not open");
isOpen = false;
lastScreenshot = null;
clearUserInputs();
if (runningSession == null) {
logger.verbose("Server session was not started");
logger.log("--- Empty test ended.");
return new TestResults();
}
boolean isNewSession = runningSession.getIsNewSession();
String sessionResultsUrl = runningSession.getUrl();
logger.verbose("Ending server session...");
boolean save = (isNewSession && saveNewTests)
|| (!isNewSession && saveFailedTests);
logger.verbose("Automatically save test? " + String.valueOf(save));
TestResults results =
serverConnector.stopSession(runningSession, false,
save);
results.setNew(isNewSession);
results.setUrl(sessionResultsUrl);
logger.verbose(results.toString());
String instructions;
if (!isNewSession &&
(0 < results.getMismatches() || 0 < results.getMissing())) {
logger.log("--- Failed test ended. See details at "
+ sessionResultsUrl);
if (throwEx) {
String message =
"'" + sessionStartInfo.getScenarioIdOrName()
+ "' of '"
+ sessionStartInfo.getAppIdOrName()
+ "'. See details at " + sessionResultsUrl;
throw new TestFailedException(results, message);
}
return results;
}
if (isNewSession) {
instructions = "Please approve the new baseline at "
+ sessionResultsUrl;
logger.log("--- New test ended. " + instructions);
if (throwEx) {
String message =
"'" + sessionStartInfo.getScenarioIdOrName()
+ "' of '" + sessionStartInfo
.getAppIdOrName()
+ "'. " + instructions;
throw new NewTestException(results, message);
}
return results;
}
// Test passed
logger.log("--- Test passed. See details at " + sessionResultsUrl);
return results;
} finally {
// Making sure that we reset the running session even if an
// exception was thrown during close.
runningSession = null;
currentAppName = null;
logger.getLogHandler().close();
}
}
/**
* Ends the test.
*
* @param isDeadlineExceeded If {@code true} the test will fail (unless
* it's a new test).
* @throws TestFailedException
* @throws NewTestException
*/
protected void closeResponseTime(boolean isDeadlineExceeded) {
try {
if (isDisabled) {
logger.verbose("Ignored");
}
logger.verbose(String.format("closeResponseTime(%b)",
isDeadlineExceeded));
ArgumentGuard.isValidState(isOpen, "Eyes not open");
isOpen = false;
if (runningSession == null) {
logger.verbose("Server session was not started");
logger.log("--- Empty test ended.");
return;
}
boolean isNewSession = runningSession.getIsNewSession();
String sessionResultsUrl = runningSession.getUrl();
logger.verbose("Ending server session...");
boolean save = (isNewSession && saveNewTests);
logger.verbose("Automatically save test? " + String.valueOf(save));
TestResults results =
serverConnector.stopSession(runningSession, false,
save);
results.setNew(isNewSession);
results.setUrl(sessionResultsUrl);
logger.verbose(results.toString());
String instructions;
if (isDeadlineExceeded && !isNewSession) {
logger.log("--- Failed test ended. See details at "
+ sessionResultsUrl);
String message =
"'" + sessionStartInfo.getScenarioIdOrName()
+ "' of '"
+ sessionStartInfo.getAppIdOrName()
+ "'. See details at " + sessionResultsUrl;
throw new TestFailedException(results, message);
}
if (isNewSession) {
instructions = "Please approve the new baseline at "
+ sessionResultsUrl;
logger.log("--- New test ended. " + instructions);
String message =
"'" + sessionStartInfo.getScenarioIdOrName()
+ "' of '" + sessionStartInfo
.getAppIdOrName()
+ "'. " + instructions;
throw new NewTestException(results, message);
}
// Test passed
logger.log("--- Test passed. See details at " + sessionResultsUrl);
} finally {
// Making sure that we reset the running session even if an
// exception was thrown during close.
runningSession = null;
currentAppName = null;
logger.getLogHandler().close();
}
}
/**
* If a test is running, aborts it. Otherwise, does nothing.
*/
public void abortIfNotClosed() {
try {
if (isDisabled) {
logger.verbose("Ignored");
return;
}
isOpen = false;
lastScreenshot = null;
clearUserInputs();
if (null == runningSession) {
logger.verbose("Closed");
return;
}
logger.verbose("Aborting server session...");
try {
// When aborting we do not save the test.
serverConnector.stopSession(runningSession, true, false);
logger.log("--- Test aborted.");
} catch (EyesException ex) {
logger.log(
"Failed to abort server session: " + ex.getMessage());
}
} finally {
runningSession = null;
logger.getLogHandler().close();
}
}
/**
* @param hostOS The host OS running the AUT.
*/
@SuppressWarnings("UnusedDeclaration")
public void setHostOS(String hostOS) {
logger.log("Host OS: " + hostOS);
if(hostOS == null || hostOS.isEmpty()) {
this.hostOS = null;
}
else {
this.hostOS = hostOS.trim();
}
}
/**
* @return get the host OS running the AUT.
*/
@SuppressWarnings("UnusedDeclaration")
public String getHostOS() {
return hostOS;
}
/**
* @param hostApp The application running the AUT (e.g., Chrome).
*/
@SuppressWarnings("UnusedDeclaration")
public void setHostApp(String hostApp) {
logger.log("Host App: " + hostApp);
if (hostApp == null || hostApp.isEmpty()) {
this.hostApp = null;
} else {
this.hostApp = hostApp.trim();
}
}
/**
* @return The application name running the AUT.
*/
@SuppressWarnings("UnusedDeclaration")
public String getHostApp() {
return hostApp;
}
/**
* @param baselineName If specified, determines the baseline to compare
* with and disables automatic baseline inference.
*/
@SuppressWarnings("UnusedDeclaration")
public void setBaselineName(String baselineName) {
logger.log("Baseline name: " + baselineName);
if(baselineName == null || baselineName.isEmpty()) {
this.baselineName = null;
}
else {
this.baselineName = baselineName.trim();
}
}
/**
* @return The baseline name, if specified.
*/
@SuppressWarnings("UnusedDeclaration")
public String getBaselineName() {
return baselineName;
}
/**
* Superseded by {@link #setHostOS(String)} and {@link #setHostApp
* (String)}.
* Sets the OS (e.g., Windows) and application (e.g., Chrome) that host the
* application under test.
*
* @param hostOS The name of the OS hosting the application under test or
* {@code null} to auto-detect.
* @param hostApp The name of the application hosting the application under
* test or {@code null} to auto-detect.
*/
@Deprecated
@SuppressWarnings("UnusedDeclaration")
public void setAppEnvironment(String hostOS, String hostApp) {
if (isDisabled) {
logger.verbose("Ignored");
return;
}
logger.log("Warning: SetAppEnvironment is deprecated! Please use " +
"'setHostOS' and 'setHostApp'");
logger.verbose("setAppEnvironment(" + hostOS + ", " + hostApp + ")");
setHostOS(hostOS);
setHostApp(hostApp);
}
/**
* @return The currently set position provider.
*/
protected PositionProvider getPositionProvider() {
return positionProvider;
}
/**
* @param positionProvider The position provider to be used.
*/
protected void setPositionProvider(PositionProvider positionProvider) {
this.positionProvider = positionProvider;
}
@SuppressWarnings("UnusedDeclaration")
/**
* See {@link #checkWindowBase(RegionProvider, String, boolean, int)}.
* {@code retryTimeout} defaults to {@code USE_DEFAULT_TIMEOUT}.
*
* @param regionProvider Returns the region to check or the empty
* rectangle to check the entire window.
* @param tag An optional tag to be associated with the snapshot.
* @param ignoreMismatch Whether to ignore this check if a mismatch is
* found.
* @return The result of matching the output with the expected output.
*/
protected MatchResult checkWindowBase(RegionProvider regionProvider,
String tag, boolean ignoreMismatch) {
return checkWindowBase(regionProvider, tag, ignoreMismatch,
USE_DEFAULT_TIMEOUT);
}
/**
* Takes a snapshot of the application under test and matches it with the
* expected output.
*
* @param regionProvider Returns the region to check or the empty
* rectangle to check the entire window.
* @param tag An optional tag to be associated with the
* snapshot.
* @param ignoreMismatch Whether to ignore this check if a mismatch is
* found.
* @param retryTimeout The amount of time to retry matching in
* milliseconds or a negative value to use the
* default retry timeout.
* @return The result of matching the output with the expected output.
* @throws com.applitools.eyes.TestFailedException Thrown if a mismatch is
* detected and immediate failure reports are enabled.
*/
protected MatchResult checkWindowBase(RegionProvider regionProvider,
String tag, boolean ignoreMismatch,
int retryTimeout) {
MatchResult result;
if (getIsDisabled()) {
logger.verbose("Ignored");
result = new MatchResult();
result.setAsExpected(true);
return result;
}
ArgumentGuard.isValidState(getIsOpen(), "Eyes not open");
ArgumentGuard.notNull(regionProvider, "regionProvider");
logger.verbose(String.format(
"CheckWindowBase(regionProvider, '%s', %b, %d)",
tag, ignoreMismatch, retryTimeout));
if (tag == null) {
tag = "";
}
if (runningSession == null) {
logger.verbose("No running session, calling start session..");
startSession();
logger.verbose("Done!");
matchWindowTask = new MatchWindowTask(
logger,
serverConnector,
runningSession,
matchTimeout,
// A callback which will call getAppOutput
new AppOutputProvider() {
public AppOutputWithScreenshot getAppOutput(
RegionProvider regionProvider_,
EyesScreenshot lastScreenshot_) {
return getAppOutputWithScreenshot(
regionProvider_, lastScreenshot_);
}
}
);
}
logger.verbose("Calling match window...");
result = matchWindowTask.matchWindow(getUserInputs(), lastScreenshot,
regionProvider, tag,
shouldMatchWindowRunOnceOnTimeout, ignoreMismatch,
retryTimeout);
logger.verbose("MatchWindow Done!");
if (!result.getAsExpected()) {
if (!ignoreMismatch) {
clearUserInputs();
lastScreenshot = result.getScreenshot();
}
shouldMatchWindowRunOnceOnTimeout = true;
if (!runningSession.getIsNewSession()) {
logger.log(String.format("Mismatch! (%s)", tag));
}
if (getFailureReports() == FailureReports.IMMEDIATE) {
throw new TestFailedException(String.format(
"Mismatch found in '%s' of '%s'",
sessionStartInfo.getScenarioIdOrName(),
sessionStartInfo.getAppIdOrName()));
}
} else { // Match successful
clearUserInputs();
lastScreenshot = result.getScreenshot();
}
logger.verbose("Done!");
return result;
}
/**
* Runs a timing test.
*
* @param regionProvider Returns the region to check or the empty
* rectangle to check the entire window.
* @param action An action to run in parallel to starting the
* test, or {@code null} if no such action is
* required.
* @param deadline The expected amount of time until finding a
* match. (Seconds)
* @param timeout The maximum amount of time to retry matching.
* (Seconds)
* @param matchInterval The interval for testing for a match.
* (Milliseconds)
* @return The earliest match found, or {@code null} if no match was found.
*/
protected MatchWindowDataWithScreenshot testResponseTimeBase(
RegionProvider regionProvider, Runnable action, int deadline,
int timeout, long matchInterval) {
if (getIsDisabled()) {
logger.verbose("Ignored");
return null;
}
ArgumentGuard.isValidState(getIsOpen(), "Eyes not open");
ArgumentGuard.notNull(regionProvider, "regionProvider");
ArgumentGuard.greaterThanZero(deadline, "deadline");
ArgumentGuard.greaterThanZero(timeout, "timeout");
ArgumentGuard.greaterThanZero(matchInterval, "matchInterval");
logger.verbose(String.format(
"testResponseTimeBase(regionProvider, %d, %d, %d)",
deadline, timeout, matchInterval));
if (runningSession == null) {
logger.verbose("No running session, calling start session..");
startSession();
logger.verbose("Done!");
}
//If there's an action to do
Thread actionThread = null;
if (action != null) {
logger.verbose("Starting webdriver action.");
actionThread = new Thread(action);
actionThread.start();
}
long startTime = System.currentTimeMillis();
// A callback which will call getAppOutput
AppOutputProvider appOutputProvider = new AppOutputProvider() {
public AppOutputWithScreenshot getAppOutput(
RegionProvider regionProvider_,
EyesScreenshot lastScreenshot_) {
// FIXME - If we use compression here it hurts us later
// (because of another screenshot order).
// return getAppOutputWithScreenshot(regionProvider_,
// lastScreenshot_);
return getAppOutputWithScreenshot(regionProvider_, null);
}
};
MatchWindowDataWithScreenshot result;
if (runningSession.getIsNewSession()) {
ResponseTimeAlgorithm.runNewProgressionSession(logger,
serverConnector, runningSession, appOutputProvider,
regionProvider, startTime, deadline);
// Since there's never a match for a new session..
result = null;
} else {
result =
ResponseTimeAlgorithm.runProgressionSessionForExistingBaseline(
logger, serverConnector, runningSession,
appOutputProvider, regionProvider, startTime,
deadline, timeout, matchInterval);
}
if (actionThread != null) {
// FIXME - Replace join with wait to according to the parameters
logger.verbose("Making sure 'action' thread had finished...");
try {
actionThread.join(30000);
} catch (InterruptedException e) {
logger.verbose(
"Got interrupted while waiting for 'action' to finish!");
}
}
logger.verbose("Done!");
return result;
}
/**
* Starts a test.
*
* @param appName The name of the application under test.
* @param testName The test name.
* @param viewportSize The client's viewport size (i.e., the visible part
* of the document's body) or {@code null} to allow
* any viewport size.
* @param sessionType The type of test (e.g., Progression for timing
* tests), or {@code null} to use the default.
*/
public void openBase(String appName, String testName,
RectangleSize viewportSize, SessionType sessionType) {
logger.getLogHandler().open();
try {
if (isDisabled) {
logger.verbose("Ignored");
return;
}
// If there's no default application name, one must be provided
// for the current test.
if (this.appName == null) {
ArgumentGuard.notNull(appName, "appName");
}
ArgumentGuard.notNull(testName, "testName");
logger.log("Agent = " + getFullAgentId());
logger.verbose(String.format("openBase('%s', '%s', '%s')", appName,
testName, viewportSize));
if (getApiKey() == null) {
String errMsg =
"API key is missing! Please set it using setApiKey()";
logger.log(errMsg);
throw new EyesException(errMsg);
}
logger.log(String.format("Eyes server URL is '%s'",
serverConnector.getServerUrlBase()));
logger.verbose(String.format("Timeout = '%d'",
serverConnector.getTimeout()));
logger.log(String.format("matchTimeout = '%d' ", matchTimeout));
logger.log(String.format("Default match settings = '%s' ",
defaultMatchSettings));
logger.log(String.format("FailureReports = '%s' ", failureReports));
if (isOpen) {
abortIfNotClosed();
String errMsg = "A test is already running";
logger.log(errMsg);
throw new EyesException(errMsg);
}
this.currentAppName = appName != null ? appName : this.appName;
this.testName = testName;
this.viewportSize = viewportSize;
this.sessionType =
sessionType != null ? sessionType : SessionType.SEQUENTIAL;
scaleProviderHandler.set(new NullScaleProvider());
setScaleMethod(ScaleMethod.getDefault());
isOpen = true;
} catch (EyesException e) {
logger.log(String.format("%s", e.getMessage()));
logger.getLogHandler().close();
throw e;
}
}
/**
* @return The viewport size of the AUT.
*/
protected abstract RectangleSize getViewportSize();
/**
* @param size The required viewport size.
*/
protected abstract void setViewportSize(RectangleSize size);
/**
* @return The inferred environment string
* or {@code null} if none is available. The inferred string is in the
* format "source:info" where source is either "useragent" or "pos".
* Information associated with a "useragent" source is a valid browser user
* agent string. Information associated with a "pos" source is a string of
* the format "process-name;os-name" where "process-name" is the name of the
* main module of the executed process and "os-name" is the OS name.
*/
protected abstract String getInferredEnvironment();
/**
* @return An updated screenshot.
*/
protected abstract EyesScreenshot getScreenshot();
/**
* @return The current title of of the AUT.
*/
protected abstract String getTitle();
// FIXME add "GetScreenshotUrl"
// FIXME add CloseOrAbort ??
/**
* Adds a trigger to the current list of user inputs.
*
* @param trigger The trigger to add to the user inputs list.
*/
protected void addUserInput(Trigger trigger) {
if (isDisabled) {
return;
}
ArgumentGuard.notNull(trigger, "trigger");
userInputs.add(trigger);
}
/**
* Adds a text trigger.
*
* @param control The control's position relative to the window.
* @param text The trigger's text.
*/
protected void addTextTriggerBase(Region control, String text) {
if (getIsDisabled()) {
logger.verbose(String.format("Ignoring '%s' (disabled)", text));
return;
}
ArgumentGuard.notNull(control, "control");
ArgumentGuard.notNull(text, "text");
// We don't want to change the objects we received.
control = new Region(control);
if (lastScreenshot == null) {
logger.verbose(String.format("Ignoring '%s' (no screenshot)",
text));
return;
}
control = lastScreenshot.getIntersectedRegion(control,
CoordinatesType.CONTEXT_RELATIVE,
CoordinatesType.SCREENSHOT_AS_IS);
if (control.isEmpty()) {
logger.verbose(String.format("Ignoring '%s' (out of bounds)",
text));
return;
}
Trigger trigger = new TextTrigger(control, text);
addUserInput(trigger);
logger.verbose(String.format("Added %s", trigger));
}
/**
* Adds a mouse trigger.
*
* @param action Mouse action.
* @param control The control on which the trigger is activated
* (location is relative to the window).
* @param cursor The cursor's position relative to the control.
*/
protected void addMouseTriggerBase(MouseAction action, Region control,
Location cursor) {
if (getIsDisabled()) {
logger.verbose(String.format("Ignoring %s (disabled)", action));
return;
}
ArgumentGuard.notNull(action, "action");
ArgumentGuard.notNull(control, "control");
ArgumentGuard.notNull(cursor, "cursor");
// Triggers are actually performed on the previous window.
if (lastScreenshot == null) {
logger.verbose(String.format("Ignoring %s (no screenshot)",
action));
return;
}
// Getting the location of the cursor in the screenshot
Location cursorInScreenshot = new Location(cursor);
// First we need to getting the cursor's coordinates relative to the
// context (and not to the control).
cursorInScreenshot.offset(control.getLocation());
try {
cursorInScreenshot = lastScreenshot.getLocationInScreenshot(
cursorInScreenshot, CoordinatesType.CONTEXT_RELATIVE);
} catch (OutOfBoundsException e) {
logger.verbose(String.format("Ignoring %s (out of bounds)",
action));
return;
}
Region controlScreenshotIntersect =
lastScreenshot.getIntersectedRegion(control,
CoordinatesType.CONTEXT_RELATIVE,
CoordinatesType.SCREENSHOT_AS_IS);
// If the region is NOT empty, we'll give the coordinates relative to
// the control.
if (!controlScreenshotIntersect.isEmpty()) {
Location l = controlScreenshotIntersect.getLocation();
cursorInScreenshot.offset(-l.getX(), -l.getY());
}
Trigger trigger = new MouseTrigger(action, controlScreenshotIntersect,
cursorInScreenshot);
addUserInput(trigger);
logger.verbose(String.format("Added %s", trigger));
}
// FIXME add getScreenshot (Wrapper) ?? (Check EyesBase in .NET)
/**
* Application environment is the environment (e.g., the host OS) which
* runs the application under test.
* @return The current application environment.
*/
protected AppEnvironment getAppEnvironment() {
AppEnvironment appEnv = new AppEnvironment();
// If hostOS isn't set, we'll try and extract and OS ourselves.
if (hostOS != null) {
appEnv.setOs(hostOS);
}
if (hostApp != null) {
appEnv.setHostingApp(hostApp);
}
appEnv.setInferred(getInferredEnvironment());
appEnv.setDisplaySize(viewportSize);
return appEnv;
}
/**
* Start eyes session on the eyes server.
*/
protected void startSession() {
logger.verbose("startSession()");
if (viewportSize == null) {
viewportSize = getViewportSize();
} else {
setViewportSize(viewportSize);
}
BatchInfo testBatch;
if (batch == null) {
logger.verbose("No batch set");
testBatch = new BatchInfo(null);
} else {
logger.verbose("Batch is " + batch);
testBatch = batch;
}
AppEnvironment appEnv = getAppEnvironment();
logger.verbose("Application environment is " + appEnv);
sessionStartInfo = new SessionStartInfo(getBaseAgentId(), sessionType,
getAppName(), null, testName, testBatch, baselineName, appEnv,
defaultMatchSettings, branchName, parentBranchName);
logger.verbose("Starting server session...");
runningSession = serverConnector.startSession(sessionStartInfo);
logger.verbose("Server session ID is " + runningSession.getId());
String testInfo = "'" + testName + "' of '" + getAppName() + "' " +
appEnv;
if (runningSession.getIsNewSession()) {
logger.log("--- New test started - " + testInfo);
shouldMatchWindowRunOnceOnTimeout = true;
} else {
logger.log("--- Test started - " + testInfo);
shouldMatchWindowRunOnceOnTimeout = false;
}
}
/**
* @param regionProvider A callback for getting the region of the
* screenshot which will be set in the
* application output.
* @param lastScreenshot Previous application screenshot (used for
* compression) or {@code null} if not available.
* @return The updated app output and screenshot.
*/
private AppOutputWithScreenshot getAppOutputWithScreenshot(
RegionProvider regionProvider, EyesScreenshot lastScreenshot) {
logger.verbose("getting screenshot...");
// Getting the screenshot (abstract function implemented by each SDK).
EyesScreenshot screenshot = getScreenshot();
logger.verbose("Done getting screenshot!");
// Cropping by region if necessary
Region region = regionProvider.getRegion();
if (!region.isEmpty()) {
screenshot = screenshot.getSubScreenshot(region,
regionProvider.getCoordinatesType(), false);
}
logger.verbose("Compressing screenshot...");
String compressResult =
compressScreenshot64(screenshot, lastScreenshot);
logger.verbose("Done! Getting title...");
String title = getTitle();
logger.verbose("Done!");
AppOutputWithScreenshot result = new AppOutputWithScreenshot(
new AppOutput(title, compressResult), screenshot);
logger.verbose("Done!");
return result;
}
/**
* Compresses a given screenshot.
*
* @param screenshot The screenshot to compress.
* @param lastScreenshot The previous screenshot, or null.
* @return A base64 encoded compressed screenshot.
*/
private String compressScreenshot64(EyesScreenshot screenshot,
EyesScreenshot lastScreenshot) {
ArgumentGuard.notNull(screenshot, "screenshot");
BufferedImage screenshotImage = screenshot.getImage();
byte[] uncompressed =
ImageUtils.encodeAsPng(screenshotImage);
BufferedImage source = (lastScreenshot != null) ?
lastScreenshot.getImage() : null;
// Compressing the screenshot
byte[] compressedScreenshot;
try {
compressedScreenshot = ImageDeltaCompressor.compressByRawBlocks(
screenshotImage, uncompressed, source);
} catch (IOException e) {
throw new EyesException("Failed to compress screenshot!", e);
}
return Base64.encodeBase64String(compressedScreenshot);
}
}