All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.seleniumtests.connectors.selenium.SeleniumRobotSnapshotServerConnector Maven / Gradle / Ivy

There is a newer version: 5.1.15
Show newest version
/**
 * Orignal work: Copyright 2015 www.seleniumtests.com
 * Modified work: Copyright 2016 www.infotel.com
 * 				Copyright 2017-2019 B.Hecquet
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.seleniumtests.connectors.selenium;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

import com.seleniumtests.reporter.info.FileLinkInfo;
import com.seleniumtests.reporter.info.Info;
import com.seleniumtests.reporter.logger.FileContent;
import com.seleniumtests.reporter.logger.TestStep;
import org.openqa.selenium.Rectangle;
import org.testng.ITestResult;

import com.seleniumtests.core.SeleniumTestsContextManager;
import com.seleniumtests.customexception.ConfigurationException;
import com.seleniumtests.customexception.SeleniumRobotServerException;
import com.seleniumtests.driver.BrowserType;
import com.seleniumtests.driver.screenshots.ScreenShot;
import com.seleniumtests.reporter.logger.Snapshot;
import com.seleniumtests.util.helper.WaitHelper;

import kong.unirest.HttpResponse;
import kong.unirest.MultipartBody;
import kong.unirest.UnirestException;
import kong.unirest.json.JSONArray;
import kong.unirest.json.JSONException;
import kong.unirest.json.JSONObject;

public class SeleniumRobotSnapshotServerConnector extends SeleniumRobotServerConnector {
	
	public static final String FIELD_EXCLUDE_ZONES = "excludeZones";
	private static final String NAPSHOT_DOES_NOT_EXIST_ERROR = "Provided snapshot does not exist";
	private static final String FIELD_IMAGE = "image";
	private static final String FIELD_IS_OK_WITH_SNAPSHOTS = "isOkWithSnapshots";
	private static final String FIELD_COMPUTING_ERROR = "computingError";
	private static final String FIELD_STEP = "step";
	private static final String FIELD_TEST_STEPS = "testSteps";
	private static final String FIELD_SESSION = "session";
	private static final String FIELD_TEST_CASE = "testCase";
	private static final String FIELD_STATUS = "status";
	public static final String SESSION_API_URL = "/snapshot/api/session/";
	public static final String TESTCASEINSESSION_API_URL = "/snapshot/api/testcaseinsession/";
	public static final String TESTSTEP_API_URL = "/snapshot/api/teststep/";
	public static final String STEPRESULT_API_URL = "/snapshot/api/stepresult/";
	public static final String EXCLUDE_API_URL = "/snapshot/api/exclude/";
	public static final String TESTINFO_API_URL = "/snapshot/api/testinfo/";
	public static final String FILE_API_URL = "/snapshot/api/file/";
	public static final String LOGS_API_URL = "/snapshot/api/logs/";
	public static final String SNAPSHOT_API_URL = "/snapshot/upload/image";
	public static final String STEP_REFERENCE_API_URL = "/snapshot/stepReference/";
	public static final String DETECT_API_URL = "/snapshot/detect/";
	private String sessionUUID;
	private static SeleniumRobotSnapshotServerConnector snapshotConnector;

	protected static final int MAX_TESTSESSION_NAME_LENGTH = 100;
	protected static final int MAX_BROWSER_NAME_LENGTH = 100;
	protected static final int MAX_TESTCASEINSESSION_NAME_LENGTH = 100;
	protected static final int MAX_SNAPSHOT_NAME_LENGTH = 100;
	protected static final int MAX_TESTSTEP_NAME_LENGTH = 100;
	
	public enum SnapshotComparisonResult {
		OK,
		KO,
		NOT_DONE
	}
	
	public static SeleniumRobotSnapshotServerConnector getInstance() {
		if (snapshotConnector == null) {
			snapshotConnector = new SeleniumRobotSnapshotServerConnector(
					SeleniumTestsContextManager.getGlobalContext().seleniumServer().getSeleniumRobotServerActive(),
					SeleniumTestsContextManager.getGlobalContext().seleniumServer().getSeleniumRobotServerUrl(),
					SeleniumTestsContextManager.getGlobalContext().seleniumServer().getSeleniumRobotServerToken()
					);
		} 
		return snapshotConnector;
	}

	public SeleniumRobotSnapshotServerConnector(final boolean useRequested, final String url) {
		this(useRequested, url, null);
	}
	
	public SeleniumRobotSnapshotServerConnector(final boolean useRequested, final String url, String authToken) {
		super(useRequested, url, authToken);
		if (!active) {
			return; 
		}
		active = isAlive();
		if (active) {
			getInfoFromServer();
		}
	}
	
	@Override
	public boolean isAlive() {
		return isAlive("/snapshot/");
	}

	public Integer createSession(String sessionName) {
		BrowserType browser = SeleniumTestsContextManager.getGlobalContext().getBrowser();
		browser = browser == null ? BrowserType.NONE : browser;

		return createSession(sessionName, browser.toString(), null, LocalDateTime.now());
	}

	/**
	 * Create a test session
	 * @param sessionName		name of the session
	 * @param browserOrApp		name of the browser or application that is run
	 * @return
	 */
	public Integer createSession(String sessionName, String browserOrApp, String startedBy, LocalDateTime startDate) {
		if (!active) {
			return null;
		}
		if (applicationId == null) {
			throw new SeleniumRobotServerException(String.format("Application %s has not been created", SeleniumTestsContextManager.getApplicationName()));
		}
		if (environmentId == null) {
			throw new SeleniumRobotServerException(String.format("Environment %s has not been created", SeleniumTestsContextManager.getGlobalContext().getTestEnv()));
		}
		if (versionId == null) {
			createVersion();
		}
		
		String strippedSessionName = sessionName.length() > MAX_TESTSESSION_NAME_LENGTH ? sessionName.substring(0, MAX_TESTSESSION_NAME_LENGTH): sessionName;
		String strippedBrowserName = browserOrApp.length() > MAX_BROWSER_NAME_LENGTH ? browserOrApp.substring(0, MAX_BROWSER_NAME_LENGTH): browserOrApp;

		try {
			sessionUUID = UUID.randomUUID().toString(); // for uniqueness of the session

			MultipartBody request = buildPostRequest(url + SESSION_API_URL)
					.field("sessionId", sessionUUID)
					.field("date", startDate.format(DateTimeFormatter.ISO_DATE_TIME))
					.field("browser", strippedBrowserName)
					.field("environment", SeleniumTestsContextManager.getGlobalContext().getTestEnv())
					.field("version", versionId.toString())
					.field(FIELD_NAME, strippedSessionName)
					.field("compareSnapshot", String.valueOf(SeleniumTestsContextManager.getGlobalContext().seleniumServer().getSeleniumRobotServerCompareSnapshot()))
					.field("compareSnapshotBehaviour", SeleniumTestsContextManager.getGlobalContext().seleniumServer().getSeleniumRobotServerCompareSnapshotBehaviour().toString())
					.field("ttl", String.format("%d days", SeleniumTestsContextManager.getGlobalContext().seleniumServer().getSeleniumRobotServerCompareSnapshotTtl()));

			if (startedBy != null) {
				request.field("startedBy", startedBy);
			}

			JSONObject sessionJson = getJSonResponse(request); // format is 'x days' as this is the way Django expect a duration in days
			return sessionJson.getInt("id");
		} catch (UnirestException | JSONException | SeleniumRobotServerException e) {
			throw new SeleniumRobotServerException("cannot create session", e);
		}
	}
	
	/**
	 * Create link between test case and session
	 * @param sessionId		the sessionId which should have been created before
	 * @param testCaseId	the test case Id to link to this session
	 * @param name			name of the test case in this session. This is to distinguish the test case (e.g: 'test1') and its full name (e.g: 'test1-1'), when executed with dataprovider
	 * @param status		status of the test, as reported by TestNG (SKIP, SUCCESS, FAILURE, ...)
	 * @param gridNode		name of the grid node where test run
	 * @param startDate		start date
	 * @return	the id of the created testCaseInSession
	 */

	public Integer createTestCaseInSession(Integer sessionId, Integer testCaseId, String name, String status, String gridNode, String description, LocalDateTime startDate) {
		if (!active) {
			return null;
		}
		if (sessionId == null) {
			throw new ConfigurationException("testcaseInSessionId should not be null");
		}
		if (testCaseId == null) {
			throw new ConfigurationException("Test case must be previously defined");
		}
		
		String strippedName = name.length() > MAX_TESTCASEINSESSION_NAME_LENGTH ? name.substring(0, MAX_TESTCASEINSESSION_NAME_LENGTH): name;
		
		try {
			JSONObject testInSessionJson = getJSonResponse(buildPostRequest(url + TESTCASEINSESSION_API_URL)
					.field(FIELD_TEST_CASE, testCaseId)
					.field(FIELD_SESSION, sessionId.toString())
					.field(FIELD_STATUS, status)
					.field("gridNode", gridNode)
					.field(FIELD_NAME, strippedName)
					.field("description", description == null ? "": description)
					.field("date", startDate.format(DateTimeFormatter.ISO_DATE_TIME)));
			return testInSessionJson.getInt("id");
		} catch (UnirestException | JSONException | SeleniumRobotServerException e) {
			throw new SeleniumRobotServerException("cannot create test case", e);
		}
	}
	
	/**
	 * Returns testStepName or a shorter version if it's too long
	 * @return
	 */
	private String getTestStepName(String testStepName) {
		String stepName = testStepName.split(" with args")[0];
		return stepName.length() > MAX_TESTSTEP_NAME_LENGTH ? stepName.substring(0, MAX_TESTSTEP_NAME_LENGTH): stepName;
	}
	

	/**
	 * Create test step and add it to the current test case
	 * @param testStep				name of the test step
	 * @param testCaseInSessionId	id of the test case in session, so that we can add this step to the test case
	 * @return	id of the created teststep
	 */
	public Integer createTestStep(String testStep, Integer testCaseInSessionId) {
		if (!active) {
			return null;
		}
		
		String strippedName = getTestStepName(testStep);
		
		try {
			JSONObject stepJson = getJSonResponse(buildPostRequest(url + TESTSTEP_API_URL)
					.field(FIELD_NAME, strippedName));
			Integer testStepId = stepJson.getInt("id");
			addCurrentTestStepToTestCase(testStepId, testCaseInSessionId);
			return testStepId;
		} catch (UnirestException | JSONException | SeleniumRobotServerException e) {
			throw new SeleniumRobotServerException("cannot create test step", e);
		}
	}
	
	/**
	 * Get reference snapshot from server, copy it to a temp file
	 * This is useful when a step fails and we want to get the reference to allow comparison
	 */
	public File getReferenceSnapshot(Integer stepResultId) {
		if (!active) {
			return null;
		}

		checkStepResult(stepResultId);
		
		try {

			File tmpFile = File.createTempFile("img", ".png");
			HttpResponse response = buildGetRequest(url + STEP_REFERENCE_API_URL + stepResultId + "/").asBytes();
			
			
			if (response.getStatus() == 200) {
				Files.write(tmpFile.toPath(), response.getBody());
				return tmpFile;
			} else {
				logger.warn("No reference found");
				return null;
			}
			
		} catch (UnirestException | SeleniumRobotServerException | IOException e) {
			throw new SeleniumRobotServerException("cannot get reference snapshot", e);
		}
	}

	/**
	 * @param stepResultId
	 */
	private void checkStepResult(Integer stepResultId) {
		if (stepResultId == null) {
			throw new ConfigurationException("Step result must be previously recorded");
		}
	}
	
	/**
	 * Create snapshot that shows the status of a step
	 */
	public void createStepReferenceSnapshot(Snapshot snapshot, Integer stepResultId) {
		if (!active) {
			return ;
		}

		checkStepResult(stepResultId);
		if (snapshot == null || snapshot.getScreenshot() == null || snapshot.getScreenshot().getImage() == null) {
			throw new SeleniumRobotServerException(NAPSHOT_DOES_NOT_EXIST_ERROR);
		}
		
		try {
			File pictureFile = snapshot.getScreenshot().getImage().getFile();
			
			getJSonResponse(buildPostRequest(url + STEP_REFERENCE_API_URL)
					.field("stepResult", stepResultId)
					.field(FIELD_IMAGE, pictureFile)
					
					);
			
		} catch (UnirestException | JSONException | SeleniumRobotServerException e) {
			throw new SeleniumRobotServerException("cannot create step reference snapshot", e);
		}
	}
	
	/**
	 * Send snapshot to server, for comparison, and check there is no difference with the reference picture
	 * This method will return true if
	 * - comparison is OK
	 * It returns null if:
	 * - server is inactive
	 * - computing error occurred
	 * - server side error ( e.g: if the server is not up to date)
	 */
	public SnapshotComparisonResult checkSnapshotHasNoDifferences(Snapshot snapshot, String testName, String stepName) {
		
		if (!active) {
			return SnapshotComparisonResult.NOT_DONE;
		}
		if (testName == null) {
			throw new ConfigurationException("testName must not be null");
		}
		if (stepName == null) {
			throw new ConfigurationException("stepName must not be null");
		}
		if (snapshot == null || snapshot.getScreenshot() == null || snapshot.getScreenshot().getImage() == null) {
			throw new SeleniumRobotServerException(NAPSHOT_DOES_NOT_EXIST_ERROR);
		}
		
		String snapshotName = snapshot.getName().length() > MAX_SNAPSHOT_NAME_LENGTH ? snapshot.getName().substring(0, MAX_SNAPSHOT_NAME_LENGTH): snapshot.getName();
		
		try {
			File pictureFile = snapshot.getScreenshot().getImage().getFile();
			BrowserType browser = SeleniumTestsContextManager.getGlobalContext().getBrowser();
			browser = browser == null ? BrowserType.NONE : browser;
			String strippedTestName = getTestName(testName);
			String strippedStepName = getTestStepName(stepName);
			
			MultipartBody request = buildPutRequest(url + SNAPSHOT_API_URL)
					.socketTimeout(5000)
					.field(FIELD_IMAGE, pictureFile)
					.field(FIELD_NAME, snapshotName)
					.field("compare", snapshot.getCheckSnapshot().getName())
					.field("diffTolerance", String.valueOf(snapshot.getCheckSnapshot().getErrorThreshold()))
					.field("versionId", versionId.toString())
					.field("environmentId", environmentId.toString())
					.field("browser", browser.getBrowserType())
					.field("testCaseName", strippedTestName)
					.field("stepName", strippedStepName);
			
			if (snapshot.getCheckSnapshot() != null && !snapshot.getCheckSnapshot().getExcludeElementsRect().isEmpty()) {
				JSONArray excludeZonesJson = new JSONArray(snapshot
						.getCheckSnapshot()
						.getExcludeElementsRect()
						.stream()
						.map(r -> new JSONObject(r))
						.collect(Collectors.toList()));
				request = request.field(FIELD_EXCLUDE_ZONES, excludeZonesJson.toString());
			}
			
			JSONObject snapshotJson = getJSonResponse(request);
			
			if (snapshotJson != null) {
				String computingError = snapshotJson.getString(FIELD_COMPUTING_ERROR);
				Float diffPixelPercentage = snapshotJson.getFloat("diffPixelPercentage");
				Boolean tooManyDiffs = snapshotJson.getBoolean("tooManyDiffs");
				
				if (!computingError.isEmpty()) {
					return SnapshotComparisonResult.NOT_DONE;
				} else if (Boolean.TRUE.equals(tooManyDiffs)) {
					logger.error(String.format("Snapshot comparison for %s has a difference of %.2f%% with reference", snapshot.getName(), diffPixelPercentage));
					return SnapshotComparisonResult.KO;
				} else {
					return SnapshotComparisonResult.OK;
				}
			} else {
				return SnapshotComparisonResult.NOT_DONE;
			}
			
		} catch (UnirestException | JSONException | SeleniumRobotServerException e) {
			// in case selenium server is not up to date, we shall not raise an error / retry
			logger.error("cannot send snapshot to server", e);
			return SnapshotComparisonResult.NOT_DONE;
		}
		
		
		
	}
	
	/**
	 * Create snapshot on server that will be used to show differences between 2 versions of the application
	 */
	public Integer createSnapshot(Snapshot snapshot, Integer stepResultId, List excludeZones) {
		if (!active) {
			return null;
		}

		checkStepResult(stepResultId);
		if (snapshot == null || snapshot.getScreenshot() == null || snapshot.getScreenshot().getImage() == null) {
			throw new SeleniumRobotServerException(NAPSHOT_DOES_NOT_EXIST_ERROR);
		}
		
		String snapshotName = snapshot.getName().length() > MAX_SNAPSHOT_NAME_LENGTH ? snapshot.getName().substring(0, MAX_SNAPSHOT_NAME_LENGTH): snapshot.getName();
		
		try {
			File pictureFile = snapshot.getScreenshot().getImage().getFile();
			
			MultipartBody request = buildPostRequest(url + SNAPSHOT_API_URL)
					.field("stepResult", stepResultId)
					.field(FIELD_IMAGE, pictureFile)
					.field(FIELD_NAME, snapshotName)
					.field("compare", snapshot.getCheckSnapshot().getName())
					.field("diffTolerance", String.valueOf(snapshot.getCheckSnapshot().getErrorThreshold()));
			
			if (excludeZones != null && !excludeZones.isEmpty()) {
			
				JSONArray excludeZonesJson = new JSONArray(excludeZones.stream().map(r -> new JSONObject(r)).collect(Collectors.toList()));
				request = request.field(FIELD_EXCLUDE_ZONES, excludeZonesJson.toString());
			}
			
			JSONObject snapshotJson = getJSonResponse(request
					);
			return snapshotJson.getInt("id");
			
		} catch (UnirestException | JSONException | SeleniumRobotServerException e) {
			throw new SeleniumRobotServerException("cannot create test snapshot", e);
		}
	}
	
	/**
	 * Send exclude zones, stored in snapshot to the server
	 */
	public Integer createExcludeZones(Rectangle excludeZone, Integer snapshotId) {
		if (!active) {
			return null;
		}
		if (snapshotId == null) {
			throw new ConfigurationException("snapshotId must be provided");
		}

		try {
			JSONObject excludeJson = getJSonResponse(buildPostRequest(url + EXCLUDE_API_URL)
					.field("snapshot", snapshotId)
					.field("x", String.valueOf(excludeZone.x))
					.field("y", String.valueOf(excludeZone.y))
					.field("width", String.valueOf(excludeZone.width))
					.field("height", String.valueOf(excludeZone.height))
					);
			return excludeJson.getInt("id");
			
		} catch (UnirestException | JSONException | SeleniumRobotServerException e) {
			throw new SeleniumRobotServerException("cannot create exclude zone", e);
		}
	}

	/**
	 * Upload file to selenium server
	 * @param file			the file to upload. Its id should be referenced somewhere so that it can be used
	 * @param stepResultId	id of the step result
	 * @return				the id of the file
	 */
	public Integer uploadFile(File file, Integer stepResultId) {
		if (!active) {
			return null;
		}
		if (stepResultId == null) {
			throw new ConfigurationException("stepResultId must be provided");
		}
		if (file == null) {
			throw new ConfigurationException("file must be provided");
		} else if (!file.exists()) {
			throw new ConfigurationException(String.format("File %s does not exist", file.getAbsolutePath()));
		}

		try {
			JSONObject fileJson = getJSonResponse(buildPostRequest(url + FILE_API_URL)
					.field("file", file)
					.field("stepResult", String.valueOf(stepResultId))
			);
			return fileJson.getInt("id");

		} catch (UnirestException | JSONException | SeleniumRobotServerException e) {
			throw new SeleniumRobotServerException("cannot upload file", e);
		}
	}
	/**
	 * Upload execution logs for the test case
	 * @param file			the file to upload. Its id should be referenced somewhere so that it can be used
	 * @param testCaseId	id of the test case
	 * @return				the id of the file
	 */
	public Integer uploadLogs(File file, Integer testCaseId) {
		if (!active) {
			return null;
		}
		if (testCaseId == null) {
			throw new ConfigurationException("testCaseId must be provided");
		}
		if (file == null) {
			throw new ConfigurationException("file must be provided");
		} else if (!file.exists()) {
			throw new ConfigurationException(String.format("File %s does not exist", file.getAbsolutePath()));
		}

		try {
			JSONObject fileJson = getJSonResponse(buildPostRequest(url + LOGS_API_URL)
					.field("file", file)
					.field("testCase", String.valueOf(testCaseId))
			);
			return fileJson.getInt("id");

		} catch (UnirestException | JSONException | SeleniumRobotServerException e) {
			throw new SeleniumRobotServerException("cannot upload logs", e);
		}
	}

	/**
	 * Record the test info
	 * We expect files to have been already uploaded, so that id has been given to files
	 * @param infos
	 * @param testCaseInSessionId
	 */
	public void recordTestInfo(Map infos, Integer testCaseInSessionId) {
		if (!active) {
			return;
		}
		if (infos == null) {
			throw new ConfigurationException("An infos map must be provided");
		}
		if (testCaseInSessionId == null) {
			throw new ConfigurationException("TestCaseInSession must be previously defined");
		}

		for (Map.Entry info: infos.entrySet()) {

			if (info instanceof FileLinkInfo && ((FileLinkInfo) info).getFileContent().getId() == null) {
				logger.error(String.format("File has not been uploaded", ((FileLinkInfo) info).getFileContent().getName()));
			}

			try {
				getStringResponse(buildPostRequest(url + TESTINFO_API_URL)
						.field(FIELD_TEST_CASE, testCaseInSessionId.toString())
						.field("name", info.getKey())
						.field("info", info.getValue().toJson().toString()));
			} catch (UnirestException | JSONException | SeleniumRobotServerException e) {
				logger.error("cannot create test info", e);
			}
		}
	}

	/**
	 * Record step result
	 * for each file present in step
	 * @param testStep				the test step
	 * @param testCaseInSessionId   test case in session id as recorded on server
	 * @param testStepId			test step id as recorded on server
	 * @return the stepResult id stored on server
	 */
	public Integer recordStepResult(TestStep testStep, Integer testCaseInSessionId, Integer testStepId) {
		if (testStep == null) {
			throw new ConfigurationException("A test step must be provided");
		}


		Integer stepResultId = recordStepResult(!testStep.getFailed(), "", testStep.getDuration(), testCaseInSessionId, testStepId);
		
		return stepResultId;
	}
	
	/**
	 * Record step result
	 * @param result				step result (true of false)
	 * @param logs					step details
	 * @param duration				step duration in milliseconds
	 * @param testCaseInSessionId   test case in session id as recorded on server
	 * @param testStepId			test step id as recorded on server
	 * @return the stepResult id stored on server
	 */
	public Integer recordStepResult(Boolean result, String logs, long duration, Integer testCaseInSessionId, Integer testStepId) {
		if (!active) {
			return null;
		}
		if (testCaseInSessionId == null) {
			throw new ConfigurationException("TestCaseInSession must be previously defined");
		}
		if (testStepId == null) {
			throw new ConfigurationException("Test step and test case in session must be previously defined");
		}
		try {
			JSONObject resultJson = getJSonResponse(buildPostRequest(url + STEPRESULT_API_URL)
					.field(FIELD_STEP, testStepId)
					.field(FIELD_TEST_CASE, testCaseInSessionId.toString())
					.field("result", result.toString())
					.field("duration", String.valueOf(duration))
					.field("stacktrace", logs)
					);
			return resultJson.getInt("id");
		} catch (UnirestException | JSONException | SeleniumRobotServerException e) {
			throw new SeleniumRobotServerException("cannot create step result", e);
		}
	}

	public Integer updateStepResult(String logs, Integer stepResultId) {
		if (!active) {
			return null;
		}

		if (stepResultId == null) {
			throw new ConfigurationException("step result id must be defined");
		}
		try {
			JSONObject resultJson = getJSonResponse(buildPatchRequest(url + STEPRESULT_API_URL + stepResultId + "/")
					.field("stacktrace", logs)
					);
			return resultJson.getInt("id");
		} catch (UnirestException | JSONException | SeleniumRobotServerException e) {
			throw new SeleniumRobotServerException("cannot update step result", e);
		}
	}

	/**
	 * Returns list of test steps in a test case
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public List getStepListFromTestCase(Integer testCaseInSessionId) {
		if (testCaseInSessionId == null) {
			return new ArrayList<>();
		}
		
		try {

			JSONObject sessionJson = getJSonResponse(buildGetRequest(url + TESTCASEINSESSION_API_URL + testCaseInSessionId));
			return (List) sessionJson.getJSONArray(FIELD_TEST_STEPS)
					.toList()
					.stream()
					.map(Object::toString)
					.collect(Collectors.toList());

		} catch (UnirestException | JSONException | SeleniumRobotServerException e) {
			throw new SeleniumRobotServerException("cannot get test step list", e);
		}
	}
	
	/**
	 * Add the current test case (should have been previously created) to this test session
	 */
	public void addCurrentTestStepToTestCase(Integer testStepId, Integer testCaseInSessionId) {
		if (testStepId == null || testCaseInSessionId == null) {
			throw new ConfigurationException("Test step and Test case in session must be previously created");
		}
		
		try {
			// get list of tests associated to this session
			List testSteps = getStepListFromTestCase(testCaseInSessionId);
			if (!testSteps.contains(testStepId.toString())) {
				testSteps.add(testStepId.toString());
			}
			addTestStepsToTestCases(testSteps, testCaseInSessionId);
			
		} catch (UnirestException | JSONException | SeleniumRobotServerException e) {
			throw new SeleniumRobotServerException("cannot add test step to test case", e);
		}
	}
	
	public JSONObject addTestStepsToTestCases(List testSteps, Integer testCaseInSessionId)  {
		if (testSteps.isEmpty()) {
			return new JSONObject();
		}
		
		MultipartBody request = buildPatchRequest(url + TESTCASEINSESSION_API_URL + testCaseInSessionId + "/").field(FIELD_TEST_STEPS, testSteps.get(0));
		for (String tc: testSteps.subList(1, testSteps.size())) {
			request = request.field(FIELD_TEST_STEPS, tc);
		}
		return getJSonResponse(request);
	}
	
	public void addLogsToTestCaseInSession(Integer testCaseInSessionId, String logs) {
		if (testCaseInSessionId == null) {
			throw new ConfigurationException("testcaseInSessionId should not be null");
		}
		
		try {
			getJSonResponse(buildPatchRequest(url + TESTCASEINSESSION_API_URL + testCaseInSessionId + "/").field("stacktrace", logs));
		} catch (UnirestException e) {
			throw new SeleniumRobotServerException("cannot add logs to test case", e);
		}
	}
	
	public String getSessionUUID() {
		return sessionUUID;
	}
	
	/**
	 * Get the comparison result of snapshots. If we cannot get the information, return true
	 * @param testCaseInSessionId		id of the test case in this test sessions.
	 * @param errorMessage				the messages coming from server if some comparison error occurred (error during computing). 
	 * 									An empty errorMessage means all snapshots have been processed whatever the comparison result is
	 * @return							integer with test result. Values are the one from ITestResult
	 */
	public int getTestCaseInSessionComparisonResult(Integer testCaseInSessionId, StringBuilder errorMessage) {
		
		logger.info("Getting snapshot comparison result for " + testCaseInSessionId);
		try {
			JSONObject response = null;
			for (int i = 0; i < 5; i++) {
				response = getJSonResponse(buildGetRequest(url + TESTCASEINSESSION_API_URL + testCaseInSessionId));
				
				// 'isOkWithSnapshots' can take 3 values
				// - 'true' if all comparison are OK
				// - 'false' if at least 1 comparison fails
				// - 'null' if all comparison failed to be computed (e.g: due to bug on server) or at least one comparison is OK but all others are not computed
				// So if no computing error is returned, having 'null' means that no snapshot has been sent to server (according to 'snapshotServer/models.py' code)
				if (response.optBoolean("computed", false) && response.has(FIELD_IS_OK_WITH_SNAPSHOTS)) {
					return displaySnapshotComparisonError(response, errorMessage);
					
				} else {
					WaitHelper.waitForSeconds(1);
				}
			}
			if (response != null) {
				logger.info("Comparison result took too long to compute");
				return displaySnapshotComparisonError(response, errorMessage);
			} else {
				logger.error("Comparison result is null, setting to 'true'");
				return ITestResult.SKIP;
			}
			
		} catch (UnirestException e) {
			logger.error("Cannot get comparison result for this test case", e);
			return ITestResult.SKIP;
		}
		
	}
	
	private int displaySnapshotComparisonError(JSONObject response, StringBuilder errorMessage) {
		if (!response.optBoolean(FIELD_IS_OK_WITH_SNAPSHOTS, false) 
				&& response.optJSONArray(FIELD_COMPUTING_ERROR) != null 
				&& !response.optJSONArray(FIELD_COMPUTING_ERROR).isEmpty()) {
			logger.error("Errors while computing snapshot comparisons: \n" + response.optJSONArray(FIELD_COMPUTING_ERROR).join("\n"));
			errorMessage.append(response.optJSONArray(FIELD_COMPUTING_ERROR).join("\n"));
		}
		
		if (response.has(FIELD_IS_OK_WITH_SNAPSHOTS)) {
			if (response.isNull(FIELD_IS_OK_WITH_SNAPSHOTS)) {
				return ITestResult.SKIP;
			} else {
				return response.getBoolean(FIELD_IS_OK_WITH_SNAPSHOTS) ? ITestResult.SUCCESS: ITestResult.FAILURE;
			}
		} else {
			return ITestResult.SKIP;
		}
	}
	
	private JSONObject detectInPicture(Snapshot snapshot, String task) {
		if (snapshot == null) {
			throw new SeleniumRobotServerException("Provided snapshot does not exist");
		}
		return detectInPicture(snapshot.getScreenshot(), task);
	}
	private JSONObject detectInPicture(ScreenShot screenShot, String task) {
		
		if (!active) {
			return null;
		}

		if (screenShot == null || screenShot.getImage() == null) {
			throw new SeleniumRobotServerException("Provided screenshot does not exist");
		}
		
		try {
			File pictureFile = screenShot.getImage().getFile();
			
			return getJSonResponse(buildPostRequest(url + DETECT_API_URL)
					.field("task", task)
					.field(FIELD_IMAGE, pictureFile)
					
					);
			
			
		} catch (UnirestException | JSONException | SeleniumRobotServerException e) {
			throw new SeleniumRobotServerException(String.format("%s detection failed", task), e);
		}
	}
	
	/**
	 * Method that sends a picture to seleniumRobot server "/detect" endpoint
	 * Server will try to detect error messages in the provided picture and return a JSON of this type
	 "{
        "fields": [
            {
                "class_id": 2,
                "top": 216,
                "bottom": 239,
                "left": 277,
                "right": 342,
                "class_name": "error_field",
                "text": null,
                "related_field": null,
                "with_label": false,
                "width": 65,
                "height": 23
            },
        [...]
        ],
        "labels": [
            {
                "top": 3,
                "left": 16,
                "width": 72,
                "height": 16,
                "text": "Join Us",
                "right": 88,
                "bottom": 19
            },
        [...]
        ]

    "error": null,
    "version": "afcc45"
}
	 * @param snapshot
	 * @return
	 */
	public JSONObject detectErrorInPicture(Snapshot snapshot) {
		return detectInPicture(snapshot, "error");
	}
	public JSONObject detectErrorInPicture(ScreenShot screenshot) {
		return detectInPicture(screenshot, "error");
	}
	
	/**
	 * Method that sends a picture to seleniumRobot server "/detect" endpoint
	 * Server will try to detect fields in the provided picture and return a JSON of this type
	 "{
      "fields": [
            {
                "class_id": 2,
                "top": 216,
                "bottom": 239,
                "left": 277,
                "right": 342,
                "class_name": "button",
                "text": null,
                "related_field": null,
                "with_label": false,
                "width": 65,
                "height": 23
            },
        [...]
        ],
    "labels": [
            {
                "top": 3,
                "left": 16,
                "width": 72,
                "height": 16,
                "text": "Join Us",
                "right": 88,
                "bottom": 19
            },
        [...]
        ]

    "error": null,
    "version": "afcc45"
}
	 * @param snapshot
	 * @return
	 */
	public JSONObject detectFieldsInPicture(Snapshot snapshot) {
		return detectInPicture(snapshot, "field");
	}
	public JSONObject detectFieldsInPicture(ScreenShot screenshot) {
		return detectInPicture(screenshot, "field");
	}
	
	/**
	 * Returns the detected information of a step reference, if it exists
	 * @param stepResultId		the step result id
	 * @param computeVersion	the "image-field-detector" version used to compute field information
	 * 							It's used to check that the information returned by the reference has been computed with the same version of the model
	 * @return
	 */
	public JSONObject getStepReferenceDetectFieldInformation(Integer stepResultId, String computeVersion) {
		
		if (!active) {
			return null;
		}
		
		if (computeVersion == null) {
			throw new ConfigurationException("computeVersion is mandatory: version of the model to use to compute fields");
		}

		checkStepResult(stepResultId);
		
		try {
			
			return getJSonResponse(buildGetRequest(url + DETECT_API_URL)
					.queryString("stepResultId", stepResultId)
					.queryString("version", computeVersion)
					.queryString("output", "json")
					);
			
		} catch (UnirestException | SeleniumRobotServerException e) {
			throw new SeleniumRobotServerException("Cannot get field detector information for reference snapshot", e);
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy