From dfd517b1109d847ffac3a084688b607c638e4df8 Mon Sep 17 00:00:00 2001 From: Andrei Kamarouski Date: Mon, 7 Oct 2024 10:06:28 +0200 Subject: [PATCH 1/5] refactor IDriverPool for more careful driver register/close --- pom.xml | 2 +- .../carina/webdriver/CarinaDriver.java | 29 +- .../carina/webdriver/IDriverPool.java | 506 +++++++++--------- .../carina/webdriver/device/Device.java | 2 +- .../carina/webdriver/DriverPoolTest.java | 49 +- 5 files changed, 297 insertions(+), 291 deletions(-) diff --git a/pom.xml b/pom.xml index c58c41f3..a05cb15a 100644 --- a/pom.xml +++ b/pom.xml @@ -567,7 +567,7 @@ ZBR_Nexus Zebrunner Snapshots - https://nexus.zebrunner.dev/repository/ce-snapshots/ + https://public-nexus.zebrunner.com/repository/ce-snapshots/ diff --git a/src/main/java/com/zebrunner/carina/webdriver/CarinaDriver.java b/src/main/java/com/zebrunner/carina/webdriver/CarinaDriver.java index c0cf140e..472e839e 100644 --- a/src/main/java/com/zebrunner/carina/webdriver/CarinaDriver.java +++ b/src/main/java/com/zebrunner/carina/webdriver/CarinaDriver.java @@ -15,6 +15,8 @@ *******************************************************************************/ package com.zebrunner.carina.webdriver; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; import org.openqa.selenium.Capabilities; import org.openqa.selenium.WebDriver; @@ -22,10 +24,10 @@ import com.zebrunner.carina.webdriver.device.Device; public class CarinaDriver { - private String name; - private WebDriver driver; - private Device device; - private Phase phase; + private final String name; + private final WebDriver driver; + private final Device device; + private final Phase phase; private long threadId; private final Capabilities originalCapabilities; @@ -66,11 +68,28 @@ protected void setThreadId(long threadId) { /** * Get capabilities that used for creating driver.
* For internal usage only - * + * * @return {@link Capabilities} */ public Capabilities getOriginalCapabilities() { return originalCapabilities; } + @Override + public boolean equals(Object o) { + if (this == o) + return true; + + if (o == null || getClass() != o.getClass()) + return false; + + CarinaDriver driver = (CarinaDriver) o; + + return new EqualsBuilder().append(threadId, driver.threadId).append(name, driver.name).isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37).append(name).append(threadId).toHashCode(); + } } diff --git a/src/main/java/com/zebrunner/carina/webdriver/IDriverPool.java b/src/main/java/com/zebrunner/carina/webdriver/IDriverPool.java index b9518eea..27f32a32 100644 --- a/src/main/java/com/zebrunner/carina/webdriver/IDriverPool.java +++ b/src/main/java/com/zebrunner/carina/webdriver/IDriverPool.java @@ -19,28 +19,29 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.Set; -import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import io.appium.java_client.remote.MobileCapabilityType; +import javax.annotation.Nullable; + import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apiguardian.api.API; import org.openqa.selenium.Capabilities; import org.openqa.selenium.MutableCapabilities; import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebDriverException; import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.remote.SessionId; import org.openqa.selenium.support.decorators.Decorated; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.zebrunner.agent.core.registrar.Label; -import com.zebrunner.carina.utils.R; import com.zebrunner.carina.utils.common.CommonUtils; import com.zebrunner.carina.utils.commons.SpecialKeywords; import com.zebrunner.carina.utils.config.Configuration; @@ -49,98 +50,104 @@ import com.zebrunner.carina.webdriver.config.WebDriverConfiguration.Parameter; import com.zebrunner.carina.webdriver.core.factory.DriverFactory; import com.zebrunner.carina.webdriver.device.Device; +import com.zebrunner.carina.webdriver.listener.DriverListener; -import javax.annotation.Nullable; +import io.appium.java_client.remote.MobileCapabilityType; public interface IDriverPool { - Logger POOL_LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - String DEFAULT = "default"; - // unified set of Carina WebDrivers - ConcurrentHashMap driversMap = new ConcurrentHashMap<>(); - @SuppressWarnings("static-access") - Set driversPool = driversMap.newKeySet(); - ThreadLocal currentDevice = new ThreadLocal<>(); + @API(status = API.Status.INTERNAL) + Logger I_DRIVER_POOL_LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + @SuppressWarnings("squid:S2386") + @API(status = API.Status.INTERNAL) + ConcurrentHashMap> DRIVERS_POOL = new ConcurrentHashMap<>(); + + @API(status = API.Status.INTERNAL) + ExecutorService EXECUTOR_SERVICE = new ThreadPoolExecutor(10, Integer.MAX_VALUE, 120L, TimeUnit.SECONDS, + new SynchronousQueue<>()); + + @API(status = API.Status.INTERNAL) + ThreadLocal CURRENT_DEVICE = new ThreadLocal<>(); + + @API(status = API.Status.INTERNAL) Device nullDevice = new Device(); - ThreadLocal customCapabilities = new ThreadLocal<>(); + + @API(status = API.Status.INTERNAL) + ThreadLocal CUSTOM_CAPABILITIES = new ThreadLocal<>(); + + @API(status = API.Status.STABLE) + String DEFAULT = "default"; /** - * Get default driver. If no default driver discovered it will be created. + * Get default driver. If no default driver discovered it will be created for current test (thread). * - * @return default WebDriver + * @return {@link WebDriver} */ + @API(status = API.Status.STABLE) default WebDriver getDriver() { return getDriver(DEFAULT); } /** - * Get driver by name. If no driver discovered it will be created using - * default capabilities. + * Get driver by name. If no driver discovered it will be created for current test (thread). * - * @param name String driver name - * @return WebDriver + * @param name driver name + * @return {@link WebDriver} */ + @API(status = API.Status.STABLE) default WebDriver getDriver(String name) { - //customCapabilities.get() return registered custom capabilities or null as earlier - return getDriver(name, customCapabilities.get(), null); + // CUSTOM_CAPABILITIES.get() return registered custom capabilities or null as earlier + return getDriver(name, CUSTOM_CAPABILITIES.get(), null); } /** - * Get driver by name and Capabilities. + * Get driver by name. If no driver discovered it will be created for current test (thread) with provided capabilities. * - * @param name String driver name - * @param capabilities capabilities - * @return WebDriver + * @param name driver name + * @param capabilities {@link Capabilities} + * @return {@link WebDriver} */ - default WebDriver getDriver(String name, MutableCapabilities capabilities) { + @API(status = API.Status.STABLE) + default WebDriver getDriver(String name, Capabilities capabilities) { return getDriver(name, capabilities, null); } /** - * Get driver by name. If no driver discovered it will be created using - * custom capabilities and selenium server. + * Get driver by name. If no driver discovered it will be created using custom capabilities and custom selenium server. * - * @param name String driver name - * @param capabilities capabilities - * @param seleniumHost String - * @return WebDriver + * @param name driver name + * @param capabilities {@link Capabilities} + * @param seleniumHost selenium host URL + * @return {@link WebDriver} */ - default WebDriver getDriver(String name, MutableCapabilities capabilities, String seleniumHost) { - WebDriver drv = null; - ConcurrentHashMap currentDrivers = getDrivers(); - if (currentDrivers.containsKey(name)) { - CarinaDriver cdrv = currentDrivers.get(name); - drv = cdrv.getDriver(); - if (TestPhase.Phase.BEFORE_SUITE.equals(cdrv.getPhase())) { - POOL_LOGGER.info("Before suite registered driver will be returned."); + @API(status = API.Status.STABLE) + default WebDriver getDriver(String name, Capabilities capabilities, String seleniumHost) { + Optional carinaDriver = getCarinaDriver(name); + if (carinaDriver.isPresent()) { + if (TestPhase.Phase.BEFORE_SUITE.equals(carinaDriver.get().getPhase())) { + I_DRIVER_POOL_LOGGER.info("Before suite registered driver will be returned."); } else { - POOL_LOGGER.debug("{} registered driver will be returned.", cdrv.getPhase()); + I_DRIVER_POOL_LOGGER.debug("{} registered driver will be returned.", carinaDriver.get().getPhase()); } + return carinaDriver.get() + .getDriver(); } - if (drv == null) { - POOL_LOGGER.debug("Starting new driver as nothing was found in the pool"); - drv = createDriver(name, capabilities, seleniumHost); - } - // [VD] do not wrap EventFiringWebDriver here otherwise DriverListener and all logging will be lost! - return drv; + I_DRIVER_POOL_LOGGER.debug("Starting new driver as nothing was found in the pool"); + return createDriver(name, capabilities, seleniumHost).getDriver(); } /** * Get driver by sessionId. * * @param sessionId session id to be used for searching a desired driver - * @return default WebDriver + * @return {@link WebDriver} */ + @API(status = API.Status.INTERNAL) public static WebDriver getDriver(SessionId sessionId) { - for (CarinaDriver carinaDriver : driversPool) { + for (CarinaDriver carinaDriver : getDrivers().values()) { WebDriver drv = carinaDriver.getDriver(); - SessionId drvSessionId; - if (drv instanceof Decorated) { - drvSessionId = ((RemoteWebDriver) (((Decorated) drv).getOriginal())).getSessionId(); - } else { - drvSessionId = ((RemoteWebDriver) drv).getSessionId(); - } - if (sessionId.equals(drvSessionId)) { + if (Objects.requireNonNull(sessionId).equals(DriverListener.castDriver(drv, RemoteWebDriver.class).getSessionId())) { return drv; } } @@ -150,12 +157,13 @@ public static WebDriver getDriver(SessionId sessionId) { /** * Get driver registered to device. If no device discovered null will be returned. * - * @param device Device - * @return WebDriver + * @param device {@link Device} + * @return {@link WebDriver} */ + @API(status = API.Status.INTERNAL) default WebDriver getDriver(Device device) { WebDriver drv = null; - for (CarinaDriver carinaDriver : driversPool) { + for (CarinaDriver carinaDriver : getDrivers().values()) { if (carinaDriver.getDevice().equals(device)) { drv = carinaDriver.getDriver(); } @@ -166,8 +174,9 @@ default WebDriver getDriver(Device device) { /** * Restart default driver * - * @return WebDriver + * @return {@link WebDriver} */ + @API(status = API.Status.STABLE) default WebDriver restartDriver() { return restartDriver(false); } @@ -175,45 +184,43 @@ default WebDriver restartDriver() { /** * Restart default driver on the same device * - * @param isSameDevice boolean restart driver on the same device or not - * @return WebDriver + * @param isSameDevice restart driver on the same device or not + * @return {@link WebDriver} */ + @API(status = API.Status.STABLE) default WebDriver restartDriver(boolean isSameDevice) { - return restartDriver(isSameDevice, null); + return restartDriver(isSameDevice, null); } + /** + * Restart default driver on the same device with additional capabilities + * + * @param isSameDevice restart driver on the same device or not + * @param additionalOptions {@link Capabilities} + * @return {@link WebDriver} + */ + @API(status = API.Status.STABLE) default WebDriver restartDriver(boolean isSameDevice, @Nullable Capabilities additionalOptions) { - WebDriver drv = getDriver(DEFAULT); - Device device = nullDevice; - MutableCapabilities udidCaps = new MutableCapabilities(); - boolean keepProxy = false; + CarinaDriver driver = getCarinaDriver(DEFAULT) + .orElseThrow(() -> new DriverPoolException(String.format("Could not find '%s' driver. " + + "Please check that driver exists before 'restartDriver' method call.", DEFAULT))); + quitDriver(DEFAULT); + + Capabilities capabilities = driver.getOriginalCapabilities() + .merge(additionalOptions); if (isSameDevice) { - keepProxy = true; - device = getDevice(drv); - POOL_LOGGER.debug("Added udid: {} to capabilities for restartDriver on the same device.", device.getUdid()); - udidCaps.setCapability(MobileCapabilityType.UDID, device.getUdid()); - } - udidCaps = udidCaps.merge(additionalOptions); - - Capabilities capabilities = null; - POOL_LOGGER.debug("before restartDriver: {}", driversPool); - for (CarinaDriver carinaDriver : driversPool) { - if (carinaDriver.getDriver().equals(drv)) { - capabilities = carinaDriver.getOriginalCapabilities() - .merge(udidCaps); - quitDriver(carinaDriver, keepProxy); - // [VD] don't remove break or refactor moving removal out of "for" cycle - driversPool.remove(carinaDriver); - break; - } + MutableCapabilities udidCaps = new MutableCapabilities(); + udidCaps.setCapability(MobileCapabilityType.UDID, driver.getDevice().getUdid()); + capabilities = capabilities.merge(udidCaps); } - POOL_LOGGER.debug("after restartDriver: {}", driversPool); - return createDriver(DEFAULT, capabilities, null); + return createDriver(DEFAULT, capabilities, null) + .getDriver(); } /** * Quit default driver */ + @API(status = API.Status.STABLE) default void quitDriver() { quitDriver(DEFAULT); } @@ -221,122 +228,116 @@ default void quitDriver() { /** * Quit driver by name * - * @param name String driver name + * @param name driver name */ + @API(status = API.Status.STABLE) + @SuppressWarnings("squid:S1181") default void quitDriver(String name) { - WebDriver drv = null; - CarinaDriver carinaDrv = null; - Long threadId = Thread.currentThread().getId(); - - POOL_LOGGER.debug("before quitDriver: {}", driversPool); - for (CarinaDriver carinaDriver : driversPool) { - if ((TestPhase.Phase.BEFORE_SUITE.equals(carinaDriver.getPhase()) && name.equals(carinaDriver.getName())) - || (threadId.equals(carinaDriver.getThreadId()) && name.equals(carinaDriver.getName()))) { - drv = carinaDriver.getDriver(); - carinaDrv = carinaDriver; - break; - } - } - - if (drv == null) { - throw new RuntimeException(String.format("Unable to find driver '%s'!", name)); - } - quitDriver(carinaDrv, false); - driversPool.remove(carinaDrv); - POOL_LOGGER.debug("after quitDriver: {}", driversPool); + quitDriver(name, Thread.currentThread().getId()); + } + @API(status = API.Status.INTERNAL) + @SuppressWarnings("squid:S1181") + static void quitDriver(String name, Long threadId) { + DRIVERS_POOL.computeIfAbsent(threadId, k -> new ConcurrentHashMap<>(0)) + .computeIfPresent(name, (k, carinaDriver) -> { + try { + EXECUTOR_SERVICE.submit(new CloseDriverTask(carinaDriver) { + @Override + public void run() { + I_DRIVER_POOL_LOGGER.debug("Starting driver quit process for {}-{}", getCarinaDriver().getThreadId(), + getCarinaDriver().getName()); + try { + getCarinaDriver().getDevice().disconnectRemote(); + } catch (Throwable e) { + I_DRIVER_POOL_LOGGER.warn("Unsuccessful remote disconnect", e); + } + // castDriver to disable DriverListener operations on quit + WebDriver drv = castDriver(getCarinaDriver().getDriver()); + + if (Configuration.get(WebDriverConfiguration.Parameter.CHROME_CLOSURE, Boolean.class).orElse(false)) { + try { + // workaround to not cleaned chrome profiles on hard drive + drv.close(); + } catch (Throwable e) { + I_DRIVER_POOL_LOGGER.warn("Unsuccessful driver close process"); + } + } + try { + drv.quit(); + } catch (Throwable e) { + I_DRIVER_POOL_LOGGER.error("Unable to quit driver! Cause: {}", e.getMessage(), e); + } + I_DRIVER_POOL_LOGGER.debug("Finished driver quit process for {}-{}", getCarinaDriver().getThreadId(), + getCarinaDriver().getName()); + } + }); + } catch (Throwable e) { + I_DRIVER_POOL_LOGGER.warn("Unsuccessful submit driver quit task"); + } + return null; + }); } /** - * Quit current drivers by phase(s). "Current" means assigned to the current test/thread. + * Quit drivers in current thread by phase(s). "Current" means assigned to the current test/thread. * - * @param phase Comma separated driver phases to quit + * @param phase comma separated driver phases to quit */ + @API(status = API.Status.INTERNAL) default void quitDrivers(TestPhase.Phase... phase) { List phases = Arrays.asList(phase); - Set drivers4Remove = new HashSet<>(); + Set drivers4Remove = new HashSet<>(1); Long threadId = Thread.currentThread().getId(); - for (CarinaDriver carinaDriver : driversPool) { - if ((phases.contains(carinaDriver.getPhase()) && threadId.equals(carinaDriver.getThreadId())) - || phases.contains(TestPhase.Phase.ALL)) { - quitDriver(carinaDriver, false); - drivers4Remove.add(carinaDriver); + for (CarinaDriver carinaDriver : getDrivers().values()) { + if ((phases.contains(carinaDriver.getPhase()) && threadId.equals(carinaDriver.getThreadId())) || phases.contains(TestPhase.Phase.ALL)) { + drivers4Remove.add(carinaDriver.getName()); } } - driversPool.removeAll(drivers4Remove); + drivers4Remove.forEach(this::quitDriver); removeCapabilities(); } /** - * Set custom capabilities. + * Set custom capabilities * - * @param caps capabilities + * @param capabilities {@link Capabilities} */ - default void setCapabilities(MutableCapabilities caps) { - customCapabilities.set(caps); + @API(status = API.Status.STABLE) + default void setCapabilities(Capabilities capabilities) { + CUSTOM_CAPABILITIES.set(capabilities); } /** - * Remove custom capabilities. + * Clear custom capabilities. */ + @API(status = API.Status.STABLE) default void removeCapabilities() { - customCapabilities.remove(); + CUSTOM_CAPABILITIES.remove(); } - private void quitDriver(CarinaDriver carinaDriver, @Deprecated boolean keepProxyDuring) { - try { - carinaDriver.getDevice().disconnectRemote(); + @API(status = API.Status.INTERNAL) + abstract class CloseDriverTask implements Runnable { + private final CarinaDriver driver; - // castDriver to disable DriverListener operations on quit - WebDriver drv = castDriver(carinaDriver.getDriver()); - POOL_LOGGER.debug("start driver quit: {}", carinaDriver.getName()); + CloseDriverTask(CarinaDriver driver) { + this.driver = driver; + } - Future future = Executors.newSingleThreadExecutor().submit((Callable) () -> { - if (Configuration.get(WebDriverConfiguration.Parameter.CHROME_CLOSURE, Boolean.class).orElse(false)) { - // workaround to not cleaned chrome profiles on hard drive - POOL_LOGGER.debug("Starting drv.close()"); - drv.close(); - POOL_LOGGER.debug("Finished drv.close()"); - } - POOL_LOGGER.debug("Starting drv.quit()"); - drv.quit(); - POOL_LOGGER.debug("Finished drv.quit()"); - return null; - }); - - // default timeout for driver quit 1/2 of explicit - long timeout = Configuration.getRequired(WebDriverConfiguration.Parameter.EXPLICIT_TIMEOUT, Integer.class) / 2; - try { - future.get(timeout, TimeUnit.SECONDS); - } catch (InterruptedException e) { - POOL_LOGGER.error("InterruptedException: Unable to quit driver!", e); - Thread.currentThread().interrupt(); - } catch (ExecutionException e) { - if (e.getMessage() != null && e.getMessage().contains("not found in active sessions")) { - POOL_LOGGER.warn("Skip driver quit for already disconnected session!"); - } else { - POOL_LOGGER.error("ExecutionException: Unable to quit driver!", e); - } - } catch (java.util.concurrent.TimeoutException e) { - POOL_LOGGER.error("Unable to quit driver for {} sec!", timeout, e); - } - } catch (WebDriverException e) { - POOL_LOGGER.debug("Error message detected during driver quit!", e); - // do nothing - } catch (Exception e) { - POOL_LOGGER.error("Error discovered during driver quit!", e); - } finally { - POOL_LOGGER.debug("finished driver quit: {}", carinaDriver.getName()); - if (!keepProxyDuring) { -// ProxyPool.stopProxy(); -// if (com.zebrunner.carina.proxy.ProxyPool.isProxyRegistered()) { -// com.zebrunner.carina.proxy.ProxyPool.stopProxy(); -// } - } + public CarinaDriver getCarinaDriver() { + return driver; } } - private WebDriver castDriver(WebDriver drv) { + @API(status = API.Status.INTERNAL) + private static Optional getCarinaDriver(String name) { + long threadId = Thread.currentThread().getId(); + return Optional.ofNullable(DRIVERS_POOL.computeIfAbsent(threadId, k -> new ConcurrentHashMap<>(0)) + .getOrDefault(name, null)); + } + + @API(status = API.Status.INTERNAL) + private static WebDriver castDriver(WebDriver drv) { if (drv instanceof Decorated) { drv = (WebDriver) ((Decorated) drv).getOriginal(); } @@ -346,59 +347,54 @@ private WebDriver castDriver(WebDriver drv) { /** * Create driver with custom capabilities * - * @param name String driver name + * @param name String driver name * @param capabilities capabilities * @param seleniumHost String * @return {@link ImmutablePair} with {@link WebDriver} and original {@link Capabilities} */ - private WebDriver createDriver(String name, Capabilities capabilities, String seleniumHost) { + @API(status = API.Status.INTERNAL) + private static CarinaDriver createDriver(String name, Capabilities capabilities, String seleniumHost) { int count = 0; - WebDriver drv = null; + CarinaDriver drv = null; Device device = nullDevice; // 1 - is default run without retry int maxCount = Configuration.getRequired(Parameter.INIT_RETRY_COUNT, Integer.class) + 1; + int maxDriverCount = Configuration.getRequired(Parameter.MAX_DRIVER_COUNT, Integer.class); + long threadId = Thread.currentThread().getId(); while (drv == null && count++ < maxCount) { try { - POOL_LOGGER.debug("initDriver start..."); - long threadId = Thread.currentThread().getId(); - ConcurrentHashMap currentDrivers = getDrivers(); - int maxDriverCount = Configuration.getRequired(Parameter.MAX_DRIVER_COUNT, Integer.class); + Map currentDrivers = getDrivers(); if (currentDrivers.size() == maxDriverCount) { - throw new RuntimeException(String.format("Unable to create new driver as you reached max number of drivers per thread: %s !" + + throw new DriverPoolException(String.format("Unable to create new driver as you reached max number of drivers per thread: %s !" + " Override max_driver_count to allow more drivers per test!", maxDriverCount)); } // [VD] pay attention that similar piece of code is copied into the DriverPoolTest as registerDriver method! if (currentDrivers.containsKey(name)) { // [VD] moved containsKey verification before the driver start - throw new RuntimeException(String.format("Driver '%s' is already registered for thread: %s", name, threadId)); + throw new DriverPoolException(String.format("Driver '%s' is already registered for thread: %s", name, threadId)); } ImmutablePair pair = DriverFactory.create(name, capabilities, seleniumHost); - drv = pair.getLeft(); - - if (currentDevice.get() != null) { - device = currentDevice.get(); + if (CURRENT_DEVICE.get() != null) { + device = CURRENT_DEVICE.get(); } - - CarinaDriver carinaDriver = new CarinaDriver(name, drv, device, TestPhase.getActivePhase(), threadId, pair.getRight()); - driversPool.add(carinaDriver); - POOL_LOGGER.debug("initDriver finish..."); - } catch (Exception e) { + drv = new CarinaDriver(name, pair.getLeft(), device, TestPhase.getActivePhase(), threadId, pair.getRight()); + DRIVERS_POOL.computeIfAbsent(threadId, k -> new ConcurrentHashMap<>(1)) + .put(name, drv); + } catch (Throwable e) { device.disconnectRemote(); - //TODO: [VD] think about excluding device from pool for explicit reasons like out of space etc + // TODO: [VD] think about excluding device from pool for explicit reasons like out of space etc // but initially try to implement it on selenium-hub level - String msg = String.format("Driver initialization '%s' FAILED! Retry %d of %d time - %s", name, count, - maxCount, e.getMessage()); if (count == maxCount) { throw e; } else { // do not provide huge stacktrace as more retries exists. Only latest will generate full error + stacktrace - POOL_LOGGER.error(msg); + I_DRIVER_POOL_LOGGER.error(String.format("Driver initialization '%s' FAILED! Retry %d of %d time - %s", + name, count, maxCount, e.getMessage())); + CommonUtils.pause(Configuration.getRequired(Parameter.INIT_RETRY_INTERVAL, Integer.class)); } - CommonUtils.pause(Configuration.getRequired(Parameter.INIT_RETRY_INTERVAL, Integer.class)); } } - if (drv == null) { throw new RuntimeException("Undefined exception detected! Analyze above logs for details."); } @@ -406,94 +402,91 @@ private WebDriver createDriver(String name, Capabilities capabilities, String se } /** - * Verify if driver is registered in the DriverPool + * Verify if driver with provided name is registered in current thread * - * @param name String driver name - * @return boolean + * @param name driver name + * @return true if registered, false otherwise */ + @API(status = API.Status.STABLE) default boolean isDriverRegistered(String name) { return getDrivers().containsKey(name); } /** - * Return all drivers registered in the DriverPool for this thread including - * on Before Suite/Class/Method stages + * Return drivers registered in the DriverPool for current thread * - * @return ConcurrentHashMap of driver names and Carina WebDrivers + * @return {@link Map} of driver names and {@link CarinaDriver} */ - default ConcurrentHashMap getDrivers() { - Long threadId = Thread.currentThread().getId(); - ConcurrentHashMap currentDrivers = new ConcurrentHashMap<>(); - for (CarinaDriver carinaDriver : driversPool) { - if (TestPhase.Phase.BEFORE_SUITE.equals(carinaDriver.getPhase()) || - threadId.equals(carinaDriver.getThreadId())) { - currentDrivers.put(carinaDriver.getName(), carinaDriver); - } - } - return currentDrivers; + @API(status = API.Status.INTERNAL) + static Map getDrivers() { + long threadId = Thread.currentThread().getId(); + return DRIVERS_POOL.computeIfAbsent(threadId, k -> new ConcurrentHashMap<>(0)); } // ------------------------ DEVICE POOL METHODS ----------------------- /** - * Get device registered to default driver. If no default driver discovered nullDevice will be returned. + * Get device registered to default driver. If no default driver discovered {@link #nullDevice} will be returned. * - * @return default Device + * @return {@link Device} */ + @API(status = API.Status.STABLE) default Device getDevice() { return getDevice(DEFAULT); } /** - * Get device registered to named driver. If no driver discovered nullDevice will be returned. + * Get device registered to the driver with provided name. If no driver discovered {@link #nullDevice} will be returned. * - * @param name String driver name - * @return Device + * @param name driver name + * @return {@link Device} */ + @API(status = API.Status.STABLE) default Device getDevice(String name) { - if (isDriverRegistered(name)) { - return getDrivers().get(name).getDevice(); - } else { - return nullDevice; + Optional drv = getCarinaDriver(name); + if (drv.isPresent()) { + return drv.get() + .getDevice(); } - + return nullDevice; } /** - * Get device registered to driver. If no driver discovered nullDevice will be returned. + * Get device registered for the provided driver. If no driver discovered nullDevice will be returned. * - * @param drv WebDriver - * @return Device + * @param driver {@link WebDriver} + * @return {@link Device} */ - default Device getDevice(WebDriver drv) { + @API(status = API.Status.INTERNAL) + default Device getDevice(WebDriver driver) { Device device = nullDevice; - - for (CarinaDriver carinaDriver : driversPool) { - if (carinaDriver.getDriver().equals(drv)) { - device = carinaDriver.getDevice(); + for (CarinaDriver drv : getDrivers().values()) { + if (drv.getDriver().equals(driver)) { + device = drv.getDevice(); break; } } - return device; } /** - * Register device information for current thread by MobileFactory and clear SysLog for Android only + * Register device information for current thread * - * @param device String Device device - * @return Device device + * @param device {@link Device} + * @return {@link Device} */ + @API(status = API.Status.INTERNAL) static Device registerDevice(Device device) { // register current device to be able to transfer it into Zafira at the end of the test long threadId = Thread.currentThread().getId(); - POOL_LOGGER.debug("Set current device '{}' to thread: {}", device.getName(), threadId); - currentDevice.set(device); - POOL_LOGGER.debug("register device for current thread id: {}; device: '{}'", threadId, device.getName()); - boolean enableAdb = R.CONFIG.getBoolean(SpecialKeywords.ENABLE_ADB); - if (enableAdb) { - device.connectRemote(); - } + I_DRIVER_POOL_LOGGER.debug("Set current device '{}' to thread: {}", device.getName(), threadId); + CURRENT_DEVICE.set(device); + I_DRIVER_POOL_LOGGER.debug("register device for current thread id: {}; device: '{}'", threadId, device.getName()); + Configuration.get(SpecialKeywords.ENABLE_ADB, Boolean.class).ifPresent(enableAdb -> { + if (enableAdb) { + device.connectRemote(); + } + }); return device; } @@ -501,37 +494,30 @@ static Device registerDevice(Device device) { * Return last registered device information for current thread. * * @return Device device + * @deprecated use {@link #getDevice(String)} instead */ - @Deprecated + @Deprecated(forRemoval = true) static Device getDefaultDevice() { long threadId = Thread.currentThread().getId(); - Device device = currentDevice.get(); + Device device = CURRENT_DEVICE.get(); if (device == null) { device = nullDevice; } else if (device.getName().isEmpty()) { - POOL_LOGGER.debug("Current device name is empty! nullDevice was used for thread: {}", threadId); + I_DRIVER_POOL_LOGGER.debug("Current device name is empty! nullDevice was used for thread: {}", threadId); } else { - POOL_LOGGER.debug("Current device name is '{}' for thread: {}", device.getName(), threadId); + I_DRIVER_POOL_LOGGER.debug("Current device name is '{}' for thread: {}", device.getName(), threadId); } return device; } - /** - * Return nullDevice object to avoid NullPointerException and tons of verification across carina-core modules. - * - * @return Device device - */ + @API(status = API.Status.INTERNAL) static Device getNullDevice() { return nullDevice; } - /** - * Verify if device is registered in the Pool - * - * @return boolean - */ + @Deprecated(forRemoval = true) default boolean isDeviceRegistered() { - Device device = currentDevice.get(); + Device device = CURRENT_DEVICE.get(); return device != null && device != nullDevice; } } diff --git a/src/main/java/com/zebrunner/carina/webdriver/device/Device.java b/src/main/java/com/zebrunner/carina/webdriver/device/Device.java index 2635644f..70ccf18d 100644 --- a/src/main/java/com/zebrunner/carina/webdriver/device/Device.java +++ b/src/main/java/com/zebrunner/carina/webdriver/device/Device.java @@ -611,7 +611,7 @@ public Optional generateUiDump(String screenshotName) { return Optional.empty(); } - if (getDrivers().size() == 0) { + if (IDriverPool.getDrivers().size() == 0) { LOGGER.debug("There is no active drivers in the pool."); return Optional.empty(); } diff --git a/src/test/java/com/zebrunner/carina/webdriver/DriverPoolTest.java b/src/test/java/com/zebrunner/carina/webdriver/DriverPoolTest.java index 54cfef42..1c8da7c5 100644 --- a/src/test/java/com/zebrunner/carina/webdriver/DriverPoolTest.java +++ b/src/test/java/com/zebrunner/carina/webdriver/DriverPoolTest.java @@ -19,6 +19,7 @@ import java.lang.invoke.MethodHandles; import java.util.Iterator; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.mockito.Mock; @@ -66,7 +67,7 @@ public void beforeSuite() { this.mockDriverSuite = mock(WebDriver.class); registerDriver(mockDriverSuite, BEFORE_SUITE_DRIVER_NAME); - Assert.assertEquals(driversPool.size(), 1, + Assert.assertEquals(IDriverPool.getDrivers().size(), 1, "Driver pool is empty after before suite driver has been registered"); Assert.assertEquals(getDriver(BEFORE_SUITE_DRIVER_NAME), mockDriverSuite, "Incorrect driver has been returned"); changeBeforeSuiteDriverThread(); @@ -80,7 +81,7 @@ public void beforeSuite() { public void beforeClassGetSuiteDriver() { TestPhase.setActivePhase(Phase.BEFORE_CLASS); Assert.assertEquals(getDriver(BEFORE_SUITE_DRIVER_NAME), mockDriverSuite, "Incorrect driver has been returned"); - Assert.assertTrue(getDrivers().containsKey(BEFORE_SUITE_DRIVER_NAME), "Before suite driver has not been returned by getDrivers()"); + Assert.assertTrue(IDriverPool.getDrivers().containsKey(BEFORE_SUITE_DRIVER_NAME), "Before suite driver has not been returned by getDrivers()"); } @Test(dependsOnMethods = { "beforeClassGetSuiteDriver" }) @@ -98,7 +99,7 @@ public void methodGetSuiteDriver() { @Test(dependsOnMethods = { "methodGetSuiteDriver" }) public void quiteSuiteDriver() { deregisterDriver(mockDriverSuite); - Assert.assertEquals(getDrivers().size(), 0, "Number of registered driver is not valid!"); + Assert.assertEquals(IDriverPool.getDrivers().size(), 0, "Number of registered driver is not valid!"); } @Test(dependsOnMethods = { "quiteSuiteDriver" }) @@ -106,7 +107,7 @@ public void registerDefaultDriver() { R.CONFIG.put("max_driver_count", "2"); registerDriver(mockDriverDefault, DEFAULT); - Assert.assertEquals(getDrivers().size(), 1, "Number of registered driver is not valid!"); + Assert.assertEquals(IDriverPool.getDrivers().size(), 1, "Number of registered driver is not valid!"); Assert.assertTrue(isDriverRegistered(DEFAULT), "Default driver is not registered!"); Assert.assertEquals(getDriver(), mockDriverDefault, "Returned driver is not the same as registered!"); @@ -123,42 +124,42 @@ public void deregisterDefaultDriver() { quitDriver(); deregisterDriver(mockDriverDefault); Assert.assertFalse(isDriverRegistered(DEFAULT), "Default driver is not deregistered!"); - LOGGER.info("drivers count: " + getDrivers().size()); - Assert.assertEquals(getDrivers().size(), 0, "Number of registered driver is not valid!"); + LOGGER.info("drivers count: " + IDriverPool.getDrivers().size()); + Assert.assertEquals(IDriverPool.getDrivers().size(), 0, "Number of registered driver is not valid!"); } @Test(dependsOnMethods = { "deregisterDefaultDriver" }) public void quitDriverByPhase() { TestPhase.setActivePhase(Phase.BEFORE_METHOD); registerDriver(mockDriverDefault, DEFAULT); - Assert.assertEquals(getDrivers().size(), 1, "Number of registered driver is not valid!"); + Assert.assertEquals(IDriverPool.getDrivers().size(), 1, "Number of registered driver is not valid!"); quitDrivers(Phase.BEFORE_METHOD); - Assert.assertEquals(getDrivers().size(), 0, "Number of registered driver is not valid!"); + Assert.assertEquals(IDriverPool.getDrivers().size(), 0, "Number of registered driver is not valid!"); } @Test(dependsOnMethods = { "quitDriverByPhase" }) public void quitDefaultDriver() { TestPhase.setActivePhase(Phase.METHOD); registerDriver(mockDriverDefault, DEFAULT); - Assert.assertEquals(getDrivers().size(), 1, "Number of registered driver is not valid!"); + Assert.assertEquals(IDriverPool.getDrivers().size(), 1, "Number of registered driver is not valid!"); quitDriver(); - Assert.assertEquals(getDrivers().size(), 0, "Number of registered driver is not valid!"); + Assert.assertEquals(IDriverPool.getDrivers().size(), 0, "Number of registered driver is not valid!"); } @Test(dependsOnMethods = { "quitDefaultDriver" }) public void quitDriverByName() { TestPhase.setActivePhase(Phase.METHOD); registerDriver(mockDriverDefault, DEFAULT); - Assert.assertEquals(1, getDrivers().size(), "Number of registered driver is not valid!"); + Assert.assertEquals(1, IDriverPool.getDrivers().size(), "Number of registered driver is not valid!"); quitDriver(DEFAULT); - Assert.assertEquals(0, getDrivers().size(), "Number of registered driver is not valid!"); + Assert.assertEquals(0, IDriverPool.getDrivers().size(), "Number of registered driver is not valid!"); } @Test(dependsOnMethods = { "quitDriverByName" }) public void registerCustom1Driver() { registerDriver(mockDriverCustom1, CUSTOM1); Assert.assertTrue(isDriverRegistered(CUSTOM1), "Custom1 driver is not registered!"); - Assert.assertEquals(getDrivers().size(), 1, "Number of registered driver is not valid!"); + Assert.assertEquals(IDriverPool.getDrivers().size(), 1, "Number of registered driver is not valid!"); } @@ -175,28 +176,28 @@ public void reachMaxDriverCountTest() { registerDriver(mockDriverCustom2, CUSTOM2); Assert.assertFalse(isDriverRegistered(CUSTOM2), CUSTOM2 + " driver is registered in spite of the max_drivercount=2"); - Assert.assertEquals(getDrivers().size(), 2, "Number of registered driver is not valid!"); + Assert.assertEquals(IDriverPool.getDrivers().size(), 2, "Number of registered driver is not valid!"); } @Test(dependsOnMethods = { "reachMaxDriverCountTest" }) public void deregisterCustom1Driver() { deregisterDriver(mockDriverCustom1); Assert.assertFalse(isDriverRegistered(CUSTOM1), CUSTOM1 + " driver is not deregistered!"); - Assert.assertEquals(getDrivers().size(), 1, "Number of registered driver is not valid!"); + Assert.assertEquals(IDriverPool.getDrivers().size(), 1, "Number of registered driver is not valid!"); deregisterDriver(mockDriverDefault); - Assert.assertEquals(getDrivers().size(), 0, "Number of registered driver is not valid!"); + Assert.assertEquals(IDriverPool.getDrivers().size(), 0, "Number of registered driver is not valid!"); } @Test(dependsOnMethods = { "deregisterCustom1Driver" }) public void deregisterAllDrivers() { registerDriver(mockDriverDefault, DEFAULT); - Assert.assertEquals(getDrivers().size(), 1, "Number of registered driver is not valid!"); + Assert.assertEquals(IDriverPool.getDrivers().size(), 1, "Number of registered driver is not valid!"); registerDriver(mockDriverCustom1, CUSTOM1); - Assert.assertEquals(getDrivers().size(), 2, "Number of registered driver is not valid!"); + Assert.assertEquals(IDriverPool.getDrivers().size(), 2, "Number of registered driver is not valid!"); quitDrivers(Phase.ALL); - Assert.assertEquals(getDrivers().size(), 0, "Number of registered driver is not valid!"); + Assert.assertEquals(IDriverPool.getDrivers().size(), 0, "Number of registered driver is not valid!"); } @Test(dependsOnMethods = { "deregisterAllDrivers" }) @@ -204,7 +205,7 @@ public void registerDriverWithDevice() { WebDriver deviceDriver = mock(WebDriver.class); Device device = new Device("name", "type", "os", "osVersion", "udid", "remoteUrl", "vnc", "proxyPort"); registerDriver(deviceDriver, DEFAULT, device); - Assert.assertEquals(getDrivers().size(), 1, "Number of registered driver is not valid!"); + Assert.assertEquals(IDriverPool.getDrivers().size(), 1, "Number of registered driver is not valid!"); Assert.assertEquals(getDriver(), deviceDriver, "Returned driver is not the same as registered!"); Assert.assertEquals(getDevice(), device, "Returned device is not the same as registered!"); @@ -212,7 +213,7 @@ public void registerDriverWithDevice() { } private void changeBeforeSuiteDriverThread() { - for (CarinaDriver cDriver : driversPool) { + for (CarinaDriver cDriver : IDriverPool.getDrivers().values()) { if (Phase.BEFORE_SUITE.equals(cDriver.getPhase())) { long newThreadID = cDriver.getThreadId() + 1; cDriver.setThreadId(newThreadID); @@ -245,7 +246,7 @@ private void registerDriver(WebDriver driver, String name) { */ private void registerDriver(WebDriver driver, String name, Device device) { Long threadId = Thread.currentThread().getId(); - ConcurrentHashMap currentDrivers = getDrivers(); + Map currentDrivers = IDriverPool.getDrivers(); int maxDriverCount = Configuration.getRequired(WebDriverConfiguration.Parameter.MAX_DRIVER_COUNT, Integer.class); @@ -258,7 +259,7 @@ private void registerDriver(WebDriver driver, String name, Device device) { // new 6.0 approach to manipulate drivers via regular Set CarinaDriver carinaDriver = new CarinaDriver(name, driver, device, TestPhase.getActivePhase(), threadId, null); - driversPool.add(carinaDriver); + DRIVERS_POOL.computeIfAbsent(threadId, (k) -> new ConcurrentHashMap<>(0)).put(name, carinaDriver); } /** @@ -270,7 +271,7 @@ private void registerDriver(WebDriver driver, String name, Device device) { */ private void deregisterDriver(WebDriver drv) { - Iterator iter = driversPool.iterator(); + Iterator iter = IDriverPool.getDrivers().values().iterator(); while (iter.hasNext()) { CarinaDriver carinaDriver = iter.next(); From 6131f7dc43e928fc0154111e86fbd55234019879 Mon Sep 17 00:00:00 2001 From: Andrei Kamarouski Date: Wed, 9 Oct 2024 12:16:37 +0200 Subject: [PATCH 2/5] doc(IDriverPool): update comments --- .../carina/webdriver/IDriverPool.java | 52 ++++++++++++++----- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/zebrunner/carina/webdriver/IDriverPool.java b/src/main/java/com/zebrunner/carina/webdriver/IDriverPool.java index 27f32a32..a9b268d4 100644 --- a/src/main/java/com/zebrunner/carina/webdriver/IDriverPool.java +++ b/src/main/java/com/zebrunner/carina/webdriver/IDriverPool.java @@ -329,6 +329,12 @@ public CarinaDriver getCarinaDriver() { } } + /** + * Get {@link CarinaDriver} registered for current thread (test) (if it exists) + * + * @param name driver name + * @return {@link Optional} of {@link CarinaDriver} + */ @API(status = API.Status.INTERNAL) private static Optional getCarinaDriver(String name) { long threadId = Thread.currentThread().getId(); @@ -336,6 +342,12 @@ private static Optional getCarinaDriver(String name) { .getOrDefault(name, null)); } + /** + * Get driver without listeners, for example without {@link DriverListener} + * + * @param drv {@link WebDriver} + * @return {@link WebDriver} + */ @API(status = API.Status.INTERNAL) private static WebDriver castDriver(WebDriver drv) { if (drv instanceof Decorated) { @@ -345,15 +357,15 @@ private static WebDriver castDriver(WebDriver drv) { } /** - * Create driver with custom capabilities + * Create driver * - * @param name String driver name - * @param capabilities capabilities - * @param seleniumHost String + * @param name driver name + * @param capabilities {@link Capabilities} + * @param seleniumHost selenium host url * @return {@link ImmutablePair} with {@link WebDriver} and original {@link Capabilities} */ @API(status = API.Status.INTERNAL) - private static CarinaDriver createDriver(String name, Capabilities capabilities, String seleniumHost) { + private static CarinaDriver createDriver(String name, @Nullable Capabilities capabilities, @Nullable String seleniumHost) { int count = 0; CarinaDriver drv = null; Device device = nullDevice; @@ -402,7 +414,7 @@ private static CarinaDriver createDriver(String name, Capabilities capabilities, } /** - * Verify if driver with provided name is registered in current thread + * Verify if driver with provided name is registered in current thread (test) * * @param name driver name * @return true if registered, false otherwise @@ -413,7 +425,7 @@ default boolean isDriverRegistered(String name) { } /** - * Return drivers registered in the DriverPool for current thread + * Get drivers registered for the current thread (test) * * @return {@link Map} of driver names and {@link CarinaDriver} */ @@ -426,7 +438,7 @@ static Map getDrivers() { // ------------------------ DEVICE POOL METHODS ----------------------- /** - * Get device registered to default driver. If no default driver discovered {@link #nullDevice} will be returned. + * Get device registered to default driver. If no default driver discovered {@link #nullDevice} will be returned * * @return {@link Device} */ @@ -436,7 +448,7 @@ default Device getDevice() { } /** - * Get device registered to the driver with provided name. If no driver discovered {@link #nullDevice} will be returned. + * Get {@link Device} registered to the driver with provided name. If no driver discovered {@link #nullDevice} will be returned * * @param name driver name * @return {@link Device} @@ -452,7 +464,7 @@ default Device getDevice(String name) { } /** - * Get device registered for the provided driver. If no driver discovered nullDevice will be returned. + * Get device registered for the provided driver. If no driver discovered nullDevice will be returned * * @param driver {@link WebDriver} * @return {@link Device} @@ -470,7 +482,7 @@ default Device getDevice(WebDriver driver) { } /** - * Register device information for current thread + * Register device information for current thread (test) * * @param device {@link Device} * @return {@link Device} @@ -491,9 +503,9 @@ static Device registerDevice(Device device) { } /** - * Return last registered device information for current thread. + * Return last registered device information for current thread * - * @return Device device + * @return {@link Device} if device information registered, {@link #nullDevice} otherwise * @deprecated use {@link #getDevice(String)} instead */ @Deprecated(forRemoval = true) @@ -510,11 +522,23 @@ static Device getDefaultDevice() { return device; } - @API(status = API.Status.INTERNAL) + /** + * Get {@link #nullDevice} device. It is not recommended to use such method, + * because nullDevice can be removed in future releases + * + * @return {@link Device} + */ + @API(status = API.Status.DEPRECATED) static Device getNullDevice() { return nullDevice; } + /** + * Check if device is registered for current thread (test) + * + * @return true if registered, false otherwise + * @deprecated should not be used on client / module side + */ @Deprecated(forRemoval = true) default boolean isDeviceRegistered() { Device device = CURRENT_DEVICE.get(); From 3d9db243a6060297fac0b64cfe877adc1ec1f4b5 Mon Sep 17 00:00:00 2001 From: Andrei Kamarouski Date: Wed, 9 Oct 2024 13:19:17 +0200 Subject: [PATCH 3/5] doc(IDriverPool): update comments --- .../carina/webdriver/IDriverPool.java | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/zebrunner/carina/webdriver/IDriverPool.java b/src/main/java/com/zebrunner/carina/webdriver/IDriverPool.java index a9b268d4..edb75b23 100644 --- a/src/main/java/com/zebrunner/carina/webdriver/IDriverPool.java +++ b/src/main/java/com/zebrunner/carina/webdriver/IDriverPool.java @@ -59,23 +59,38 @@ public interface IDriverPool { @API(status = API.Status.INTERNAL) Logger I_DRIVER_POOL_LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + /** + * Store drivers by thread id and driver name + */ @SuppressWarnings("squid:S2386") @API(status = API.Status.INTERNAL) ConcurrentHashMap> DRIVERS_POOL = new ConcurrentHashMap<>(); + /** + * Background process for closing drivers. Scalable depends on requirements. + * In carina-core in the shutdown logic we wait until all tasks will be completed + */ @API(status = API.Status.INTERNAL) ExecutorService EXECUTOR_SERVICE = new ThreadPoolExecutor(10, Integer.MAX_VALUE, 120L, TimeUnit.SECONDS, new SynchronousQueue<>()); + /** + * Store device object for current thread + */ + // todo remove this storage @API(status = API.Status.INTERNAL) ThreadLocal CURRENT_DEVICE = new ThreadLocal<>(); + // todo check if it is possible to remove usage of this object and reuse Optional for methods that return Device object @API(status = API.Status.INTERNAL) Device nullDevice = new Device(); @API(status = API.Status.INTERNAL) ThreadLocal CUSTOM_CAPABILITIES = new ThreadLocal<>(); + /** + * Default driver name + */ @API(status = API.Status.STABLE) String DEFAULT = "default"; @@ -102,19 +117,19 @@ default WebDriver getDriver(String name) { } /** - * Get driver by name. If no driver discovered it will be created for current test (thread) with provided capabilities. + * Get driver by name. If no driver discovered it will be created for current test (thread) with provided capabilities * * @param name driver name * @param capabilities {@link Capabilities} * @return {@link WebDriver} */ @API(status = API.Status.STABLE) - default WebDriver getDriver(String name, Capabilities capabilities) { + default WebDriver getDriver(String name, @Nullable Capabilities capabilities) { return getDriver(name, capabilities, null); } /** - * Get driver by name. If no driver discovered it will be created using custom capabilities and custom selenium server. + * Get driver by name. If no driver discovered it will be created using custom capabilities and custom selenium server host * * @param name driver name * @param capabilities {@link Capabilities} @@ -122,7 +137,7 @@ default WebDriver getDriver(String name, Capabilities capabilities) { * @return {@link WebDriver} */ @API(status = API.Status.STABLE) - default WebDriver getDriver(String name, Capabilities capabilities, String seleniumHost) { + default WebDriver getDriver(String name, @Nullable Capabilities capabilities, @Nullable String seleniumHost) { Optional carinaDriver = getCarinaDriver(name); if (carinaDriver.isPresent()) { if (TestPhase.Phase.BEFORE_SUITE.equals(carinaDriver.get().getPhase())) { @@ -299,7 +314,7 @@ default void quitDrivers(TestPhase.Phase... phase) { } /** - * Set custom capabilities + * Set custom capabilities that will be used for current thread (test). * * @param capabilities {@link Capabilities} */ From a00837e0b8e5ea76ebc9b52bc34ee1179e459af7 Mon Sep 17 00:00:00 2001 From: Andrei Kamarouski Date: Thu, 10 Oct 2024 13:01:29 +0200 Subject: [PATCH 4/5] fix(EventFiringAppiumCommandExecutor): uiautomator should not be overrided by default --- .../webdriver/listener/EventFiringAppiumCommandExecutor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/zebrunner/carina/webdriver/listener/EventFiringAppiumCommandExecutor.java b/src/main/java/com/zebrunner/carina/webdriver/listener/EventFiringAppiumCommandExecutor.java index f0747d9f..b57631a3 100644 --- a/src/main/java/com/zebrunner/carina/webdriver/listener/EventFiringAppiumCommandExecutor.java +++ b/src/main/java/com/zebrunner/carina/webdriver/listener/EventFiringAppiumCommandExecutor.java @@ -87,8 +87,8 @@ public Response execute(Command command) throws WebDriverException { BLOCKING_QUEUE.put(UUID.randomUUID().toString() + System.currentTimeMillis()); String app = CapabilityHelpers.getCapability(capabilities, SupportsAppOption.APP_OPTION, String.class); if (app != null) { - MutableCapabilities appCaps = new MutableCapabilities(); - appCaps.setCapability(SupportsAppOption.APP_OPTION, ARTIFACT_PROVIDERS.get() + MutableCapabilities appCaps = new MutableCapabilities().merge(capabilities); + appCaps.setCapability("appium:" + SupportsAppOption.APP_OPTION, ARTIFACT_PROVIDERS.get() .getDirectLink(app)); FieldUtils.writeField(FieldUtils.getField(Command.class, "payload", true), command, From 8fcad45c011b477aee21999eecd27e724529e412 Mon Sep 17 00:00:00 2001 From: Andrei Kamarouski Date: Wed, 23 Oct 2024 14:50:29 +0200 Subject: [PATCH 5/5] feature(ExtendedWebElement): add NONE element loading strategy --- .../carina/webdriver/decorator/ElementLoadingStrategy.java | 1 + .../carina/webdriver/decorator/ExtendedWebElement.java | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/main/java/com/zebrunner/carina/webdriver/decorator/ElementLoadingStrategy.java b/src/main/java/com/zebrunner/carina/webdriver/decorator/ElementLoadingStrategy.java index 7c348e00..a63c4bcb 100644 --- a/src/main/java/com/zebrunner/carina/webdriver/decorator/ElementLoadingStrategy.java +++ b/src/main/java/com/zebrunner/carina/webdriver/decorator/ElementLoadingStrategy.java @@ -16,6 +16,7 @@ package com.zebrunner.carina.webdriver.decorator; public enum ElementLoadingStrategy { + NONE, BY_PRESENCE, BY_VISIBILITY, BY_PRESENCE_OR_VISIBILITY diff --git a/src/main/java/com/zebrunner/carina/webdriver/decorator/ExtendedWebElement.java b/src/main/java/com/zebrunner/carina/webdriver/decorator/ExtendedWebElement.java index df962094..d310ebd4 100644 --- a/src/main/java/com/zebrunner/carina/webdriver/decorator/ExtendedWebElement.java +++ b/src/main/java/com/zebrunner/carina/webdriver/decorator/ExtendedWebElement.java @@ -1716,6 +1716,9 @@ public List doGetSelectedValues() { @SuppressWarnings("squid:S1452") protected ExpectedCondition getDefaultElementWaitCondition() { clearElementState(); + if(loadingStrategy == ElementLoadingStrategy.NONE) { + return (ExpectedCondition) input -> true; + } List> conditions = new ArrayList<>(); if (loadingStrategy == ElementLoadingStrategy.BY_PRESENCE || loadingStrategy == ElementLoadingStrategy.BY_PRESENCE_OR_VISIBILITY) { if (element != null) {