package au.com.agic.apptesting.utils.impl; import au.com.agic.apptesting.constants.Constants; import au.com.agic.apptesting.exception.ConfigurationException; import au.com.agic.apptesting.exception.DriverException; import au.com.agic.apptesting.profiles.configuration.UrlMapping; import au.com.agic.apptesting.utils.*; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.openqa.selenium.WebDriver; import org.openqa.selenium.remote.DesiredCapabilities; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.validation.constraints.NotNull; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; /** * A service that generates local web driver instances to test on the local pc. Assumes that Chrome * is present and installed in the default location, and that the webdriver.chrome.driver system * property has been set, and is pointing to a version of the driver downloaded from * http://chromedriver.storage.googleapis.com/index.html */ public class LocalThreadWebDriverMapImpl implements ThreadWebDriverMap { private static final Logger LOGGER = LoggerFactory.getLogger(LocalThreadWebDriverMapImpl.class); private static final SystemPropertyUtils SYSTEM_PROPERTY_UTILS = new SystemPropertyUtilsImpl(); private static final WebDriverFactory WEB_DRIVER_FACTORY = new WebDriverFactoryImpl(); /** * The mapping between thread ids and the feature state objects that they use for the tests */ private final Map<String, FeatureState> threadIdToCapMap = new HashMap<>(); /** * The mapping between thread ids and the webdrivers that they use for the tests */ private final Map<String, WebDriver> threadIdToDriverMap = new HashMap<>(); /** * The index of the Url we are going to be testing */ private int currentUrl; /** * The index of the data set we are going to be testing */ private int currentDataset; /** * The list of URLs associated with the application we are testing */ private List<UrlMapping> originalApplicationUrls; /** * The directory that holds reports and other test script outputs */ private String reportDirectory; /** * The values that can be input into the app */ private Map<Integer, Map<String, String>> originalDataSets; /** * A list of temp folders to delete once the test is finished */ private List<File> tempFolders; /** * The port for the proxy */ private List<ProxyDetails<?>> proxies; @Override public void initialise( @NotNull final List<DesiredCapabilities> desiredCapabilities, @NotNull final List<UrlMapping> applicationUrls, @NotNull final Map<Integer, Map<String, String>> datasets, @NotNull final String myReportDirectory, @NotNull final List<File> myTempFolders, @NotNull final List<ProxyDetails<?>> myProxies) { checkNotNull(desiredCapabilities); checkNotNull(applicationUrls); checkNotNull(datasets); checkNotNull(myReportDirectory); checkNotNull(myTempFolders); checkNotNull(myProxies); originalApplicationUrls = new ArrayList<>(applicationUrls); originalDataSets = new HashMap<>(datasets); reportDirectory = myReportDirectory; tempFolders = new ArrayList<>(myTempFolders); proxies = new ArrayList<>(myProxies); } @NotNull @Override public synchronized FeatureState getDesiredCapabilitiesForThread(@NotNull final String name) { checkArgument(StringUtils.isNotBlank(name)); checkArgument(name.startsWith(Constants.THREAD_NAME_PREFIX)); if (threadIdToCapMap.containsKey(name)) { return threadIdToCapMap.get(name); } /* We have allocated our available configurations */ final int urlCount = Math.max(originalApplicationUrls.size(), 1); if (currentUrl >= urlCount) { throw new ConfigurationException("Configuration pool has been exhausted! " + currentUrl + " is greater than or equal to " + urlCount); } /* Get the details that the requesting thread will need */ final UrlMapping url = originalApplicationUrls.isEmpty() ? null : originalApplicationUrls.get(currentUrl); final Map<String, String> dataSet = originalDataSets.containsKey(currentDataset) ? new HashMap<>(originalDataSets.get(currentDataset)) : new HashMap<>(); /* Tick over to the next url when all the capabilities have been consumed */ ++currentDataset; if (currentDataset >= getMaxDataSets()) { currentDataset = 0; ++currentUrl; } final FeatureState featureState = new FeatureStateImpl( url, dataSet, reportDirectory, proxies); threadIdToCapMap.put(name, featureState); return featureState; } @NotNull @Override public synchronized WebDriver getWebDriverForThread(@NotNull final String name, final boolean createIfMissing) { checkArgument(StringUtils.isNotEmpty(name)); if (threadIdToDriverMap.containsKey(name)) { return threadIdToDriverMap.get(name); } if (createIfMissing) { LOGGER.info("WEBAPPTESTER-INFO-0006: Creating WebDriver"); final WebDriver webDriver = WEB_DRIVER_FACTORY.createWebDriver(proxies, tempFolders); threadIdToDriverMap.put(name, webDriver); return webDriver; } throw new DriverException("Could not find or create web driver"); } @Override public synchronized void clearWebDriverForThread(@NotNull final String name, final boolean quitDriver) { checkArgument(StringUtils.isNotEmpty(name)); if (threadIdToDriverMap.containsKey(name)) { if (quitDriver) { LOGGER.info("WEBAPPTESTER-INFO-0007: Quitting WebDriver"); threadIdToDriverMap.get(name).quit(); } threadIdToDriverMap.remove(name); } } @Override public synchronized int getNumberCapabilities() { return Math.max(originalApplicationUrls.size(), 1) * Math.max(getMaxDataSets(), 1); } @Override public List<File> getTempFolders() { return tempFolders; } private Integer getMaxDataSets() { try { final String maxDataSets = SYSTEM_PROPERTY_UTILS.getProperty(Constants.NUMBER_DATA_SETS_SYSTEM_PROPERTY); if (StringUtils.isNotBlank(maxDataSets)) { final Integer maxDataSetsNumber = Integer.parseInt( SYSTEM_PROPERTY_UTILS.getProperty(Constants.NUMBER_DATA_SETS_SYSTEM_PROPERTY)); return Math.min(originalDataSets.size(), maxDataSetsNumber); } } catch (final NumberFormatException ignored) { /* Input was not a number, so ignore it */ } return originalDataSets.size(); } @Override public synchronized void shutdown() { for (final WebDriver webdriver : threadIdToDriverMap.values()) { try { if (!WEB_DRIVER_FACTORY.leaveWindowsOpen()) { webdriver.quit(); } } catch (final Exception ignored) { // do nothing and continue closing the other webdrivers } } /* Clear the map */ threadIdToDriverMap.clear(); threadIdToCapMap.clear(); /* Attempt to delete all the temp folders */ getTempFolders().forEach(FileUtils::deleteQuietly); /* Reset the list of available configurations */ currentUrl = 0; } @Override public synchronized void shutdown(@NotNull final String name) { checkArgument(StringUtils.isNotBlank(name)); this.clearWebDriverForThread(name, !WEB_DRIVER_FACTORY.leaveWindowsOpen()); } }