Skip to content
This repository has been archived by the owner on Sep 21, 2021. It is now read-only.

Commit

Permalink
Merge pull request #84 from zalando/screen-resolution
Browse files Browse the repository at this point in the history
Adding support for custom screen resolution in the capabilities
  • Loading branch information
diemol authored Mar 22, 2017
2 parents 81d7dbb + 552f955 commit 205ae11
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 16 deletions.
4 changes: 2 additions & 2 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,8 @@ RUN set -x \
# Sauce Connect Tunneling #
# ------------------------#
# https://docs.saucelabs.com/reference/sauce-connect/
# Layer size: medium: 12.42 MB
ENV SAUCE_CONN_VER="sc-4.4.4-linux" \
# Layer size: medium: ~13 MB
ENV SAUCE_CONN_VER="sc-4.4.5-linux" \
SAUCE_CONN_DOWN_URL="https://saucelabs.com/downloads"
RUN cd /tmp \
&& wget -nv "${SAUCE_CONN_DOWN_URL}/${SAUCE_CONN_VER}.tar.gz" \
Expand Down
13 changes: 13 additions & 0 deletions docs/usage_examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
* [Test name](#test-name)
* [Group name](#group-name)
* [Idle Timeout](#idle-timeout)
* [Screen resolution](#screen-resolution)


## Initial setup
Expand Down Expand Up @@ -246,4 +247,16 @@ capability in your test. Example code in Java for the capability (it sets the `i
desiredCapabilities.setCapability("idleTimeout", 150);
```

### Screen resolution
You can pass a custom screen resolution for your test, just include a `screenResolution` with the desired value. E.g.
`screenResolution=1280x1024`. Also supported for the same purpose `resolution` and `screen-resolution`. Example code
in Java for the capability `screenResolution`

```java
DesiredCapabilities desiredCapabilities = new DesiredCapabilities();
desiredCapabilities.setCapability(CapabilityType.BROWSER_NAME, BrowserType.FIREFOX);
desiredCapabilities.setCapability(CapabilityType.PLATFORM, Platform.LINUX);
desiredCapabilities.setCapability("screenResolution", "1280x720");
```


4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@
<selenium-server.patch-level.version>0</selenium-server.patch-level.version>
<docker-client.version>8.1.2</docker-client.version>
<junit.version>4.12</junit.version>
<mockito.version>2.7.17</mockito.version>
<mockito.version>2.7.18</mockito.version>
<awaitility.version>2.0.0</awaitility.version>
<testng.version>6.11</testng.version>
<slf4j.version>1.7.24</slf4j.version>
<slf4j.version>1.7.25</slf4j.version>
<surefire.version>2.19.1</surefire.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.openqa.grid.internal.SessionTerminationReason;
import org.openqa.grid.internal.TestSession;
import org.openqa.grid.internal.TestSlot;
import org.openqa.grid.internal.utils.CapabilityMatcher;
import org.openqa.grid.selenium.proxy.DefaultRemoteProxy;
import org.openqa.grid.web.servlet.handler.RequestType;
import org.openqa.grid.web.servlet.handler.WebDriverRequest;
Expand Down Expand Up @@ -64,6 +65,7 @@ public class DockerSeleniumRemoteProxy extends DefaultRemoteProxy {
private boolean stopSessionRequestReceived = false;
private DockerSeleniumNodePoller dockerSeleniumNodePollerThread = null;
private GoogleAnalyticsApi ga = new GoogleAnalyticsApi();
private CapabilityMatcher capabilityHelper;

public DockerSeleniumRemoteProxy(RegistrationRequest request, Registry registry) {
super(request, registry);
Expand Down Expand Up @@ -136,6 +138,14 @@ public TestSession getNewSession(Map<String, Object> requestedCapability) {
return null;
}

@Override
public CapabilityMatcher getCapabilityHelper() {
if (capabilityHelper == null) {
capabilityHelper = new DockerSeleniumCapabilityMatcher(this);
}
return capabilityHelper;
}

private long getConfiguredIdleTimeout(Map<String, Object> requestedCapability) {
long configuredIdleTimeout;
try {
Expand Down Expand Up @@ -332,7 +342,7 @@ void copyVideos(final String containerId) throws IOException, DockerException, I
IOUtils.copy(tarStream, outputStream);
outputStream.close();
Dashboard.updateDashboard(testInformation);
LOGGER.log(Level.INFO, "{0} Video files copies to: {1}", new Object[]{getId(),
LOGGER.log(Level.INFO, "{0} Video files copied to: {1}", new Object[]{getId(),
testInformation.getVideoFolderPath()});
}
} catch (Exception e) {
Expand Down Expand Up @@ -399,6 +409,8 @@ public void run() {
"thread, stopping thread execution.", e);
Thread.currentThread().interrupt();
dockerSeleniumRemoteProxy.ga.trackException(e);
dockerSeleniumRemoteProxy.stopPolling();
dockerSeleniumRemoteProxy.startPolling();
return;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ public class DockerSeleniumStarterRemoteProxy extends DefaultRemoteProxy impleme
private static int firefoxContainersOnStartup;
private static int maxDockerSeleniumContainers;
private static String timeZone;
private static int configuredScreenWidth;
private static int screenWidth;
private static int configuredScreenHeight;
private static int screenHeight;
private static String containerName;
private List<Integer> allocatedPorts = new ArrayList<>();
Expand Down Expand Up @@ -110,9 +112,11 @@ private static void readConfigurationFromEnvVariables() {

int sWidth = env.getIntEnvVariable(ZALENIUM_SCREEN_WIDTH, DEFAULT_SCREEN_WIDTH);
setScreenWidth(sWidth);
setConfiguredScreenWidth(sWidth);

int sHeight = env.getIntEnvVariable(ZALENIUM_SCREEN_HEIGHT, DEFAULT_SCREEN_HEIGHT);
setScreenHeight(sHeight);
setConfiguredScreenHeight(sHeight);

String tz = env.getStringEnvVariable(ZALENIUM_TZ, DEFAULT_TZ);
setTimeZone(tz);
Expand Down Expand Up @@ -263,6 +267,22 @@ protected static void setScreenHeight(int screenHeight) {
DockerSeleniumStarterRemoteProxy.screenHeight = screenHeight <= 0 ? DEFAULT_SCREEN_HEIGHT : screenHeight;
}

public static int getConfiguredScreenWidth() {
return configuredScreenWidth <= 0 ? DEFAULT_SCREEN_WIDTH : configuredScreenWidth;
}

public static void setConfiguredScreenWidth(int configuredScreenWidth) {
DockerSeleniumStarterRemoteProxy.configuredScreenWidth = configuredScreenWidth;
}

public static int getConfiguredScreenHeight() {
return configuredScreenHeight <= 0 ? DEFAULT_SCREEN_HEIGHT : configuredScreenHeight;
}

public static void setConfiguredScreenHeight(int configuredScreenHeight) {
DockerSeleniumStarterRemoteProxy.configuredScreenHeight = configuredScreenHeight;
}

@VisibleForTesting
protected static void setEnv(final Environment env) {
DockerSeleniumStarterRemoteProxy.env = env;
Expand Down Expand Up @@ -308,14 +328,37 @@ public TestSession getNewSession(Map<String, Object> requestedCapability) {
return null;
}

LOGGER.log(Level.INFO, LOGGING_PREFIX + "Starting new node for {0}.", requestedCapability);
// Check and configure specific screen resolution capabilities when they have been passed in the test config.
configureScreenResolutionFromCapabilities(requestedCapability);

String browserName = requestedCapability.get(CapabilityType.BROWSER_NAME).toString();

/*
Here a docker-selenium container will be started and it will register to the hub
We check first if a node has been created for this request already. If so, we skip it
but increment the number of times it has been received. In case something went wrong with the node
creation, we remove the mark* after 10 times and we create a node again.
* The mark is an added custom capability
*/
startDockerSeleniumContainer(browserName);
String waitingForNode = String.format("waitingFor_%s_Node", browserName.toUpperCase());
if (!requestedCapability.containsKey(waitingForNode)) {
LOGGER.log(Level.INFO, LOGGING_PREFIX + "Starting new node for {0}.", requestedCapability);
if (startDockerSeleniumContainer(browserName)) {
requestedCapability.put(waitingForNode, 1);
}
} else {
int attempts = (int) requestedCapability.get(waitingForNode);
attempts++;
if (attempts >= 20) {
LOGGER.log(Level.INFO, LOGGING_PREFIX + "Request has waited 20 attempts for a node, something " +
"went wrong with the previous attempts, creating a new node for {0}.", requestedCapability);
startDockerSeleniumContainer(browserName, true);
requestedCapability.put(waitingForNode, 1);
} else {
requestedCapability.put(waitingForNode, attempts);
LOGGER.log(Level.INFO, LOGGING_PREFIX + "Request waiting for a node new node for {0}.", requestedCapability);
}
}
return null;
}

Expand All @@ -334,7 +377,7 @@ public void beforeRegistration() {
@Override
public CapabilityMatcher getCapabilityHelper() {
if (capabilityHelper == null) {
capabilityHelper = new DockerSeleniumCapabilityMatcher();
capabilityHelper = new DockerSeleniumCapabilityMatcher(this);
}
return capabilityHelper;
}
Expand All @@ -348,10 +391,14 @@ public float getResourceUsageInPercent() {
return 98;
}

@VisibleForTesting
boolean startDockerSeleniumContainer(String browser) {
return startDockerSeleniumContainer(browser, false);
}

@VisibleForTesting
boolean startDockerSeleniumContainer(String browser, boolean forceCreation) {

if (validateAmountOfDockerSeleniumContainers()) {
if (validateAmountOfDockerSeleniumContainers() || forceCreation) {

String hostIpAddress = "localhost";

Expand Down Expand Up @@ -453,6 +500,48 @@ private void createStartupContainers() {
}).start();
}

/*
This method will search for a screenResolution capability to be passed when creating a docker-selenium node.
*/
private void configureScreenResolutionFromCapabilities(Map<String, Object> requestedCapability) {
boolean wasConfiguredScreenWidthAndHeightChanged = false;
String[] screenResolutionNames = {"screenResolution", "resolution", "screen-resolution"};
for (String screenResolutionName : screenResolutionNames) {
if (requestedCapability.containsKey(screenResolutionName)) {
String screenResolution = requestedCapability.get(screenResolutionName).toString();
try {
int screenWidth = Integer.parseInt(screenResolution.split("x")[0]);
int screenHeight = Integer.parseInt(screenResolution.split("x")[1]);
if (screenWidth > 0 && screenHeight > 0) {
setScreenHeight(screenHeight);
setScreenWidth(screenWidth);
wasConfiguredScreenWidthAndHeightChanged = true;
} else {
setScreenWidth(getConfiguredScreenWidth());
setScreenHeight(getConfiguredScreenHeight());
LOGGER.log(Level.FINE, "One of the values provided for screenResolution is negative, " +
"defaults will be used. Passed value -> " + screenResolution);
}
} catch (Exception e) {
setScreenWidth(getConfiguredScreenWidth());
setScreenHeight(getConfiguredScreenHeight());
LOGGER.log(Level.FINE, "Values provided for screenResolution are not valid integers or " +
"either the width or the height is missing, defaults will be used. Passed value -> "
+ screenResolution);
}
}
}
// If the screen resolution parameters were not changed, we just set the defaults again.
// Also in the capabilities, to avoid the situation where a request grabs the node from other request
// just because the platform, version, and browser match.
if (!wasConfiguredScreenWidthAndHeightChanged) {
setScreenWidth(getConfiguredScreenWidth());
setScreenHeight(getConfiguredScreenHeight());
String screenResolution = String.format("%sx%s", getScreenWidth(), getScreenHeight());
requestedCapability.put("screenResolution", screenResolution);
}
}

private int getNumberOfRunningContainers() {
try {
List<Container> containerList = dockerClient.listContainers(DockerClient.ListContainersParam.allContainers());
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/de/zalando/tip/zalenium/util/Dashboard.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public class Dashboard {

private static CommonProxyUtilities commonProxyUtilities = new CommonProxyUtilities();

private static int executedTests = 0;

public static synchronized void updateDashboard(TestInformation testInformation) throws IOException {
String currentLocalPath = commonProxyUtilities.currentLocalPath();
String localVideosPath = currentLocalPath + "/" + VIDEOS_FOLDER_NAME;
Expand All @@ -36,11 +38,16 @@ public static synchronized void updateDashboard(TestInformation testInformation)
}
FileUtils.writeStringToFile(testList, testEntry, StandardCharsets.UTF_8);

executedTests++;

File dashboardHtml = new File(localVideosPath, "dashboard.html");
String dashboard = FileUtils.readFileToString(new File(currentLocalPath, "dashboard_template.html"), StandardCharsets.UTF_8);
dashboard = dashboard.replace("{testList}", testEntry);
FileUtils.writeStringToFile(dashboardHtml, dashboard, StandardCharsets.UTF_8);

File testCountFile = new File(localVideosPath, "amount_of_run_tests.txt");
FileUtils.writeStringToFile(testCountFile, String.valueOf(executedTests), StandardCharsets.UTF_8);

File zalandoIco = new File(localVideosPath, "zalando.ico");
if (!zalandoIco.exists()) {
FileUtils.copyFile(new File(currentLocalPath, "zalando.ico"), zalandoIco);
Expand All @@ -55,7 +62,6 @@ public static synchronized void updateDashboard(TestInformation testInformation)
if (!jsFolder.exists()) {
FileUtils.copyDirectory(new File(currentLocalPath + "/js"), jsFolder);
}

}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package de.zalando.tip.zalenium.util;

import de.zalando.tip.zalenium.proxy.DockerSeleniumRemoteProxy;
import de.zalando.tip.zalenium.proxy.DockerSeleniumStarterRemoteProxy;
import org.openqa.grid.internal.utils.DefaultCapabilityMatcher;
import org.openqa.grid.selenium.proxy.DefaultRemoteProxy;
import org.openqa.selenium.remote.CapabilityType;

import java.util.Map;
Expand All @@ -11,7 +14,13 @@
* The purpose of this class is to let docker-selenium process requests where a capability "version=latest" is present
*/
public class DockerSeleniumCapabilityMatcher extends DefaultCapabilityMatcher {
private static final Logger logger = Logger.getLogger(DockerSeleniumCapabilityMatcher.class.getName());
private final Logger logger = Logger.getLogger(DockerSeleniumCapabilityMatcher.class.getName());
private DefaultRemoteProxy proxy;

public DockerSeleniumCapabilityMatcher(DefaultRemoteProxy defaultRemoteProxy) {
super();
proxy = defaultRemoteProxy;
}

@Override
public boolean matches(Map<String, Object> nodeCapability, Map<String, Object> requestedCapability) {
Expand All @@ -23,18 +32,46 @@ public boolean matches(Map<String, Object> nodeCapability, Map<String, Object> r
without the version. If not, we put the requested capability back in the requestedCapability object, so it
can be matched by any of the Cloud Testing Providers.
*/
boolean browserVersionCapabilityMatches = false;
if (requestedCapability.containsKey(CapabilityType.VERSION)) {
String requestedVersion = requestedCapability.get(CapabilityType.VERSION).toString();
if ("latest".equalsIgnoreCase(requestedVersion)) {
requestedCapability.remove(CapabilityType.VERSION);
if (super.matches(nodeCapability, requestedCapability)) {
return true;
browserVersionCapabilityMatches = true;
} else {
requestedCapability.put(CapabilityType.VERSION, requestedVersion);
return false;
}
}
}
return super.matches(nodeCapability, requestedCapability);

boolean screenResolutionCapabilityMatches = true;
boolean containsScreenResolutionCapability = false;
// This validation is only done for docker-selenium nodes
if (proxy instanceof DockerSeleniumRemoteProxy) {
String[] screenResolutionNames = {"screenResolution", "resolution", "screen-resolution"};
for (String screenResolutionName : screenResolutionNames) {
if (requestedCapability.containsKey(screenResolutionName)) {
screenResolutionCapabilityMatches = nodeCapability.containsKey(screenResolutionName) &&
requestedCapability.get(screenResolutionName).equals(nodeCapability.get(screenResolutionName));
containsScreenResolutionCapability = true;
}
}
// This is done to avoid having the test run on a node with a configured screen resolution different from
// the global configured one. But not putting it to tests that should go to a cloud provider.
if (!containsScreenResolutionCapability && super.matches(nodeCapability, requestedCapability)) {
String screenResolution = String.format("%sx%s", DockerSeleniumStarterRemoteProxy.getConfiguredScreenWidth(),
DockerSeleniumStarterRemoteProxy.getConfiguredScreenHeight());
requestedCapability.put("screenResolution", screenResolution);
}
}

// If the browser version has been matched, then implicitly the matcher from the super class has also been
// invoked.
if (browserVersionCapabilityMatches) {
return screenResolutionCapabilityMatches;
} else {
return super.matches(nodeCapability, requestedCapability) && screenResolutionCapabilityMatches;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public void setUp() throws DockerException, InterruptedException, IOException {
RegistrationRequest request = TestUtils.getRegistrationRequestForTesting(40000,
DockerSeleniumRemoteProxy.class.getCanonicalName());
request.getConfiguration().capabilities.clear();
request.getConfiguration().capabilities.addAll(DockerSeleniumStarterRemoteProxy.getCapabilities());
request.getConfiguration().capabilities.addAll(TestUtils.getDockerSeleniumCapabilitiesForTesting());

// Creating the proxy
proxy = DockerSeleniumRemoteProxy.getNewInstance(request, registry);
Expand Down
Loading

0 comments on commit 205ae11

Please sign in to comment.