Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix!: Make Saucelabs integration use what Saucelabs recommends #1578

Merged
merged 4 commits into from
Nov 25, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
</license>
</licenses>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<Implementation-Version>${project.version}</Implementation-Version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ protected List<FrameworkMethod> computeTestMethods() {
}

if (SauceLabsIntegration.isConfiguredForSauceLabs()) {
methodCapabilities.setCapability(
SauceLabsIntegration.setSauceLabsOption(
methodCapabilities,
SauceLabsIntegration.CapabilityType.NAME,
method.getName());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,60 +10,156 @@
package com.vaadin.testbench.parallel;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Map;


import org.openqa.selenium.remote.DesiredCapabilities;

/**
* Integration methods for Sauce Labs testing used by {@link ParallelTest}
* Integration methods for Sauce Labs testing.
*
*/
public class SauceLabsIntegration {
private static final Logger logger = Logger
.getLogger(SauceLabsIntegration.class.getName());

private static final String SAUCE_DEFAULT_HUB_URL = "https://ondemand.us-west-1.saucelabs.com/wd/hub";
private static final String SAUCE_USERNAME_ENV = "SAUCE_USERNAME";
private static final String SAUCE_USERNAME_PROP = "sauce.user";
private static final String SAUCE_ACCESS_KEY_ENV = "SAUCE_ACCESS_KEY";
private static final String SAUCE_ACCESS_KEY_PROP = "sauce.sauceAccessKey";
private static final String SAUCE_TUNNELID_PROP = "sauce.tunnelId";
private static final String SAUCE_TUNNELID_ENV = "SAUCE_TUNNEL_ID";
private static final String SAUCE_HUB_URL_PROP = "sauce.hubUrl";
private static final String SAUCE_HUB_URL_ENV = "SAUCE_HUB_URL";

/**
* Sets needed desired capabilities, mainly tunnel identifier, based on the
* given sauce.options String.
*
* Sets needed desired capabilities for authentication and using the correct
* sauce tunnel (if in use).
* <p>
*
* @see #getSauceUser()
* @see #getSauceAccessKey()
* @see #getSauceTunnelIdentifier()
*
* @param desiredCapabilities
* DesiredCapabilities for RemoteWebDriver. Must not be null.
* @param sauceOptions
* options to be parsed and added as capabilities to the given
* DesiredCapabilities object
* the capabilities object to populate, not null
*/
static void setDesiredCapabilities(
public static void setDesiredCapabilities(
DesiredCapabilities desiredCapabilities) {
String sauceOptions = System.getProperty("sauce.options");
if (sauceOptions == null || sauceOptions.isEmpty()) {
logger.log(Level.FINE,
"Null or empty sauce.options given. Ignoring.");
return;

String username = getSauceUser();
String accessKey = getSauceAccessKey();
String tunnelId = getSauceTunnelIdentifier();

if (username != null) {
setSauceLabsOption(desiredCapabilities, "username", username);
} else {
logger.log(Level.FINE,"You can give a Sauce Labs user name using -D"
+ SAUCE_USERNAME_PROP + "=<username> or by "
+ SAUCE_USERNAME_ENV + " environment variable.");
}
if (accessKey != null) {
setSauceLabsOption(desiredCapabilities, "access_key", accessKey);
} else {
logger.log(Level.FINE,"You can give a Sauce Labs access key using -D"
+ SAUCE_ACCESS_KEY_PROP + "=<accesskey> or by "
+ SAUCE_ACCESS_KEY_ENV + " environment variable.");
}
String tunnelId = getTunnelIdentifier(sauceOptions, null);

if (tunnelId != null) {
desiredCapabilities.setCapability("tunnelIdentifier", tunnelId);
setSauceLabsOption(desiredCapabilities, "tunnelIdentifier",
tunnelId);
}
}

/**
* Sets the given SauceLabs option to the given value.
* <p>
* The available SauceLabs options are listed at
* https://docs.saucelabs.com/dev/test-configuration-options/.
*
* @param desiredCapabilities
* the desired capabilities object
* @param key
* the option key
* @param value
* the option value
*/
public static void setSauceLabsOption(
DesiredCapabilities desiredCapabilities, String key, Object value) {
// We always make a copy of the options because all clone/merge
// operations in
// DesiredCapability do a shallow clone
Map<String, Object> sauceOptions = new HashMap<>();
Map<String, Object> currentOptions = getSauceLabsOptions(
desiredCapabilities);
if (currentOptions != null) {
sauceOptions.putAll(currentOptions);
}
sauceOptions.put(key, value);

desiredCapabilities.setCapability("sauce:options", sauceOptions);
}

private static Map<String, Object> getSauceLabsOptions(
DesiredCapabilities desiredCapabilities) {
return (Map<String, Object>) desiredCapabilities
.getCapability("sauce:options");
}

/**
* Gets the given SauceLabs option.
* <p>
* The available SauceLabs options are listed at
* https://docs.saucelabs.com/dev/test-configuration-options/.
*
* @param desiredCapabilities
* the desired capabilities object
* @param key
* the option key
* @return the option value that was set or null
*/
public static Object getSauceLabsOption(
DesiredCapabilities desiredCapabilities, String key) {
Map<String, Object> sauceOptions = getSauceLabsOptions(
desiredCapabilities);
if (sauceOptions == null) {
return null;
}
return sauceOptions.get(key);
}

/**
* @param options
* the command line options used to launch Sauce Connect
* @param defaultValue
* the default value to use for the identifier if none specified
* in the options
* @return String representing the tunnel identifier
* Gets the configured Saucelabs tunnel identifier.
* <p>
* Reads from the {@value #SAUCE_TUNNELID_PROP} system property or the
* {@value #SAUCE_TUNNELID_ENV} environment variable.
* <p>
* If both system property and environment variable are defined, the system
* property is used.
*
* @return the configured Saucelabs tunnel identifier or null
*/
static String getTunnelIdentifier(String options, String defaultValue) {
public static String getSauceTunnelIdentifier() {
String tunnelId = getSystemPropertyOrEnv(SAUCE_TUNNELID_PROP,
SAUCE_TUNNELID_ENV);
if (tunnelId == null) {
// For backwards compatibility only
String sauceOptions = System.getProperty("sauce.options");
tunnelId = getTunnelIdentifierFromOptions(sauceOptions);
}

return tunnelId;
}

private static String getTunnelIdentifierFromOptions(String options) {
if (options == null || options.isEmpty()) {
return defaultValue;
return null;
}
Iterator<String> tokensIterator = Arrays.asList(options.split(" "))
.iterator();
Expand All @@ -75,48 +171,65 @@ static String getTunnelIdentifier(String options, String defaultValue) {
return tokensIterator.next();
}
}
return defaultValue;
return null;
}

/**
* Returns the HubUrl for running tests in Sauce Labs tunnel. Reads required
* credentials from sauce.user and sauce.sauceAccessKey or environment
* variables SAUCE_USERNAME and SAUCE_ACCESS_KEY. If both system property
* and environment variable are defined, the system property is used.
*
* Returns the HubUrl for running tests in Sauce Labs.
* <p>
* The available SauceLabs URLs are listed at
* https://docs.saucelabs.com/basics/data-center-endpoints/#data-center-endpoints.
*
* @return url String to be used in Sauce Labs test run
*/
static String getHubUrl() {
String username = getSauceUser();
String accessKey = getSauceAccessKey();

if (username == null) {
logger.log(Level.FINE,
"You can give a Sauce Labs user name using -D"
+ SAUCE_USERNAME_PROP + "=<username> or by "
+ SAUCE_USERNAME_ENV + " environment variable.");
}
if (accessKey == null) {
logger.log(Level.FINE,
"You can give a Sauce Labs access key using -D"
+ SAUCE_ACCESS_KEY_PROP + "=<accesskey> or by "
+ SAUCE_ACCESS_KEY_ENV + " environment variable.");
public static String getHubUrl() {
String hubUrl = getSystemPropertyOrEnv(SAUCE_HUB_URL_PROP,
SAUCE_HUB_URL_ENV);
if (hubUrl == null) {
hubUrl = SAUCE_DEFAULT_HUB_URL;
}
return "http://" + username + ":" + accessKey
+ "@localhost:4445/wd/hub";
return hubUrl;
}

static boolean isConfiguredForSauceLabs() {
/**
* Checks if parameters needed to run in Saucelabs have been set.
*
* @return true if the Saucelabs configuration was found
*/
public static boolean isConfiguredForSauceLabs() {
String user = getSauceUser();
String accessKey = getSauceAccessKey();
return user != null && !user.isEmpty() && accessKey != null && !accessKey.isEmpty();
return user != null && !user.isEmpty() && accessKey != null
&& !accessKey.isEmpty();
}

static String getSauceUser() {
/**
* Gets the configured Saucelabs user name.
* <p>
* Reads from the {@value #SAUCE_USERNAME_PROP} system property or the
* {@value #SAUCE_USERNAME_ENV} environment variable.
* <p>
* If both system property and environment variable are defined, the system
* property is used.
*
* @return the configured Saucelabs user name or null
*/
public static String getSauceUser() {
return getSystemPropertyOrEnv(SAUCE_USERNAME_PROP, SAUCE_USERNAME_ENV);
}

static String getSauceAccessKey() {
/**
* Gets the configured Saucelabs access key.
* <p>
* Reads from the {@value #SAUCE_ACCESS_KEY_PROP} system property or the
* {@value #SAUCE_ACCESS_KEY_ENV} environment variable.
* <p>
* If both system property and environment variable are defined, the system
* property is used.
*
* @return the configured Saucelabs access key or null
*/
public static String getSauceAccessKey() {
return getSystemPropertyOrEnv(SAUCE_ACCESS_KEY_PROP,
SAUCE_ACCESS_KEY_ENV);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,14 @@ public void tbMethodNameInCapabilities() throws InitializationError {
List<FrameworkMethod> testMethods = parallelRunner.computeTestMethods();
Assert.assertEquals(4, testMethods.size());
for (FrameworkMethod testMethod : testMethods) {
Assert.assertEquals(testMethod.getName(),
((TBMethod) testMethod).getCapabilities().getCapability(
SauceLabsIntegration.CapabilityType.NAME));
DesiredCapabilities cap = ((TBMethod) testMethod).getCapabilities();
Assert.assertEquals("bar",
SauceLabsIntegration.getSauceLabsOption(cap, "foo"));
if (SauceLabsIntegration.isConfiguredForSauceLabs()) {
Assert.assertEquals(testMethod.getName(),
SauceLabsIntegration.getSauceLabsOption(cap,
SauceLabsIntegration.CapabilityType.NAME));
}
}
}

Expand All @@ -58,8 +63,13 @@ public void dummy2() {

@BrowserConfiguration
public List<DesiredCapabilities> getBrowsers() {
return Arrays.asList(Browser.CHROME.getDesiredCapabilities(),
List<DesiredCapabilities> caps = Arrays.asList(
Browser.CHROME.getDesiredCapabilities(),
Browser.FIREFOX.getDesiredCapabilities());
for (DesiredCapabilities cap : caps) {
SauceLabsIntegration.setSauceLabsOption(cap, "foo", "bar");
}
return caps;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,24 +44,4 @@ public void hubFromAnnotationOrSystemProperty() {
}
}

@Test
public void sauceURLFromSystemProperty() {
String oldUser = System.getProperty(SAUCE_USER_PROPERTY);
String oldAccess = System.getProperty(SAUCE_ACCESS_KEY_PROPERTY);
try {
System.setProperty(SAUCE_USER_PROPERTY, "user1234");
System.setProperty(SAUCE_ACCESS_KEY_PROPERTY, "access1234");

Assert.assertEquals(
"http://user1234:access1234@localhost:4445/wd/hub",
getHubURL());
} finally {
if (oldUser != null) {
System.setProperty(SAUCE_USER_PROPERTY, oldUser);
}
if (oldAccess != null) {
System.setProperty(SAUCE_ACCESS_KEY_PROPERTY, oldAccess);
}
}
}
}
Loading