Repository: saucelabs/sauce_bindings (language: python)

Issue:
[java] move system and capability commands into their own classes
I'm going to step through the ideas I discussed today in smaller incremental commits.

@mmerrell if you have time, can you tell me if this is the right way in Java to pull out methods from a class?
I'm creating/storing an instance of CapabilitiesManager in SauceOptions, and then using EnvironmentManager with static methods.

This is ready to merge if everyone is good with it.
[java] implement Sauce Configs for Chrome
Ok, I was definitely making this too hard... We were trying to decide between option 4 & option 6 from [this list](https://gist.github.com/titusfortner/3ed01696fbbb76ff99fa0c90f2fda063), and this PR I think takes the best from each of them and does it in a rather light weight solution.

The point of these changes is to provide a way for a user to only have access to the methods/parameters that make sense for the type of test being run. (e.g. Chrome tests can't set IE Driver Version, etc)
This isn't as much of an issue with just browser implementations, but will be essential for dealing with Mobile & Visual, etc.

So, SauceOptions is going to store the state of *everything, but access to the everything is going to be controlled by the config classes.

Config classes will only have setters, no getters and is only going to store state within a SauceOptions instance (which means we can't use lombok setters since we aren't setting state on fields directly). We're also using generics to return an instance of the subclass rather than having it return the superclass. Then the user calls `build()` to generate the `SauceOptions` instance, so they don't ever need to know which class of configs they are using under the hood. 

I've marked the configs class constructor as "Deprecated" to encourage users to use it from the static method in SauceOptions, but if someone *does use the config class directly, or doesn't call `build()` on it, the code still lets them pass it into a `SauceSession` constructor and the constructor will just call the build method on it.

The capabilities are defined based on: https://docs.google.com/spreadsheets/d/1u_28C3lqLlmZElBDb6KuG6qSYuCEK32R40RyjOvWnBE/edit

Hierarchy:
SauceConfigs
----> SauceConfigsBrowser
--------> SauceConfigsChrome 

There isn't any way that we're going to be able to avoid all duplication going forward (especially once we add in mobile things), but this approach should do a fair job of removing most of the duplication.

Next steps:
1. Implement Edge, Firefox, IE & Safari based on which of these are allowed per browser:
```
seleniumVersion
chromedriverVersion
iedriverVersion
geckodriverVersion
avoidProxy
extendedDebugging
```
2. Override methods in subclass to ensure incompatible values aren't being set  (e.g. `setPlatformName(SaucePlatform:MAC_SIERRA)` on a `SauceConfigsChrome` instance).
3. Add Visual support in `SauceOptions` and `SauceBrowserConfigs`
4. Add Mobile support in `SauceOptions` and new subclasses of `SauceConfigs`

Considerations:
1. We could deprecate the other constructors of `SauceOptions` to ensure everyone goes through the Configs classes
2. We could put the static methods in a `SauceOptionsFactory` class instead of `SauceOptions` if that makes more sense



Discussion / hints:
@nadvolod ok made the update, this should be good.


Fix patch:
diff --git a/docs/BASIC_OPTIONS.md b/docs/BASIC_OPTIONS.md
index 3c61b646..b69bfd70 100644
--- a/docs/BASIC_OPTIONS.md
+++ b/docs/BASIC_OPTIONS.md
@@ -28,10 +28,10 @@ public class BasicOptionsTest {
     @Test
     public void basicOptions() {
         // 1. Specify the 3 basic parameters of a SauceOptions instance
-        SauceOptions sauceOptions = new SauceOptions();
-        sauceOptions.setBrowserName(Browser.FIREFOX);
-        sauceOptions.setBrowserVersion("73.0");
-        sauceOptions.setPlatformName(SaucePlatform.WINDOWS_8);
+        SauceOptions sauceOptions = SauceOptions.chrome()
+                .setBrowserVersion("85.0")
+                .setPlatformName(SaucePlatform.WINDOWS_8)
+                .build();
 
         // 2. Create Session object with the Options object instance
         SauceSession session = new SauceSession(sauceOptions);
diff --git a/docs/BROWSER_OPTIONS.md b/docs/BROWSER_OPTIONS.md
index f7297d71..01f08952 100644
--- a/docs/BROWSER_OPTIONS.md
+++ b/docs/BROWSER_OPTIONS.md
@@ -26,7 +26,7 @@ public class BrowserOptionsTest {
         browserOptions.addArguments("--foo");
 
         // 2. Create Sauce Options object with the Browser Options object instance
-        SauceOptions sauceOptions = new SauceOptions(browserOptions);
+        SauceOptions sauceOptions = SauceOptions.firefox(browserOptions);
 
         // 3. Create Session object with the Sauce Options object instance
         SauceSession session = new SauceSession(sauceOptions);
diff --git a/docs/SAUCE_OPTIONS.md b/docs/SAUCE_OPTIONS.md
index 95a39090..04bcda34 100644
--- a/docs/SAUCE_OPTIONS.md
+++ b/docs/SAUCE_OPTIONS.md
@@ -22,10 +22,11 @@ public class SauceLabsOptionsTest {
     @Test
     public void sauceOptions() {
         // 1. Specify Sauce Specific Options
-        SauceOptions sauceOptions = new SauceOptions();
-        sauceOptions.setExtendedDebugging(true);
-        sauceOptions.setIdleTimeout(100);
-        sauceOptions.setTimeZone("Alaska");
+        SauceOptions sauceOptions = SauceOptions.firefox()
+                .setExtendedDebugging()
+                .setIdleTimeout(100)
+                .setTimeZone("Alaska")
+                .build();
 
         // 2. Create Session object with the Options object instance
         SauceSession session = new SauceSession(sauceOptions);
diff --git a/java/src/main/java/com/saucelabs/saucebindings/InvalidSauceOptionsArgumentException.java b/java/src/main/java/com/saucelabs/saucebindings/InvalidSauceOptionsArgumentException.java
deleted file mode 100644
index b2cc95d3..00000000
--- a/java/src/main/java/com/saucelabs/saucebindings/InvalidSauceOptionsArgumentException.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.saucelabs.saucebindings;
-
-class InvalidSauceOptionsArgumentException extends RuntimeException {
-    public InvalidSauceOptionsArgumentException(String message) {
-        super(message);
-    }
-}
diff --git a/java/src/main/java/com/saucelabs/saucebindings/SauceOptions.java b/java/src/main/java/com/saucelabs/saucebindings/SauceOptions.java
index 1da0523e..1cd4d385 100644
--- a/java/src/main/java/com/saucelabs/saucebindings/SauceOptions.java
+++ b/java/src/main/java/com/saucelabs/saucebindings/SauceOptions.java
@@ -1,352 +1,588 @@
 package com.saucelabs.saucebindings;
 
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.Setter;
-import lombok.experimental.Accessors;
+import com.saucelabs.saucebindings.options.BaseOptions;
+import com.saucelabs.saucebindings.options.CapabilityManager;
+import com.saucelabs.saucebindings.options.SauceLabsOptions;
 import org.openqa.selenium.MutableCapabilities;
-import org.openqa.selenium.Proxy;
 import org.openqa.selenium.chrome.ChromeOptions;
 import org.openqa.selenium.edge.EdgeOptions;
 import org.openqa.selenium.firefox.FirefoxOptions;
 import org.openqa.selenium.ie.InternetExplorerOptions;
 import org.openqa.selenium.safari.SafariOptions;
 
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.net.URL;
-import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
-
-@Accessors(chain = true)
-@Setter @Getter
-public class SauceOptions {
-    @Setter(AccessLevel.NONE) private MutableCapabilities seleniumCapabilities;
-    public TimeoutStore timeout = new TimeoutStore();
-
-    // w3c Settings
-    private Browser browserName = Browser.CHROME;
-    private String browserVersion = "latest";
-    private SaucePlatform platformName = SaucePlatform.WINDOWS_10;
-    private PageLoadStrategy pageLoadStrategy;
-    private Boolean acceptInsecureCerts = null;
-    private Proxy proxy;
-    private Boolean setWindowRect = null;
-    @Getter(AccessLevel.NONE) private Map<Timeouts, Integer> timeouts;
-    private Boolean strictFileInteractability = null;
-    private UnhandledPromptBehavior unhandledPromptBehavior;
-
-    // Sauce Settings
-    private Boolean avoidProxy = null;
-    private String build;
-    private Boolean capturePerformance = null;
-    private String chromedriverVersion;
-    private Integer commandTimeout = null;
-    private Map<String, Object> customData = null;
-    private Boolean extendedDebugging = null;
-    private Integer idleTimeout = null;
-    private String iedriverVersion;
-    private Integer maxDuration = null;
-    private String name;
-    private String parentTunnel;
-    private Map<Prerun, Object> prerun;
-    private URL prerunUrl;
-    private Integer priority = null;
-    private JobVisibility jobVisibility; // the actual key for this is a Java reserved keyword "public"
-    private Boolean recordLogs = null;
-    private Boolean recordScreenshots = null;
-    private Boolean recordVideo = null;
-    private String screenResolution;
-    private String seleniumVersion;
-    private List<String> tags = null;
-    private String timeZone;
-    private String tunnelIdentifier;
-    private Boolean videoUploadOnPass = null;
-
-    public static final List<String> primaryEnum = Arrays.asList(
-            "browserName",
-            "jobVisibility",
-            "pageLoadStrategy",
-            "platformName",
-            "timeouts",
-            "unhandledPromptBehavior"
-    );
-
-    public static final List<String> secondaryEnum = Arrays.asList(
-            "prerun",
-            "timeouts"
-    );
-
-    public static final List<String> w3cDefinedOptions = Arrays.asList(
-            "browserName",
-            "browserVersion",
-            "platformName",
-            "pageLoadStrategy",
-            "acceptInsecureCerts",
-            "proxy",
-            "setWindowRect",
-            "timeouts",
-            "strictFileInteractability",
-            "unhandledPromptBehavior");
-
-    public static final List<String> sauceDefinedOptions = Arrays.asList(
-            "avoidProxy",
-            "build",
-            "capturePerformance",
-            "chromedriverVersion",
-            "commandTimeout",
-            "customData",
-            "extendedDebugging",
-            "idleTimeout",
-            "iedriverVersion",
-            "maxDuration",
-            "name",
-            "parentTunnel",
-            "prerun",
-            "priority",
-            // public, do not use, reserved keyword, using jobVisibility
-            "recordLogs",
-            "recordScreenshots",
-            "recordVideo",
-            "screenResolution",
-            "seleniumVersion",
-            "tags",
-            "timeZone",
-            "tunnelIdentifier",
-            "videoUploadOnPass");
-
-    public static final Map<String, String> knownCITools;
-    static {
-        knownCITools = new HashMap<>();
-        knownCITools.put("Jenkins", "BUILD_TAG");
-        knownCITools.put("Bamboo", "bamboo_agentId");
-        knownCITools.put("Travis", "TRAVIS_JOB_ID");
-        knownCITools.put("Circle", "CIRCLE_JOB");
-        knownCITools.put("GitLab", "CI");
-        knownCITools.put("TeamCity", "TEAMCITY_PROJECT_NAME");
-    }
 
+public class SauceOptions extends com.saucelabs.saucebindings.options.SauceOptions {
+
+    /**
+     * @deprecated Use com.saucelabs.saucebindings.options.SauceOptions() instead
+     */
+    @Deprecated
     public SauceOptions() {
         this(new MutableCapabilities());
     }
 
+    /**
+     * @deprecated Use com.saucelabs.saucebindings.options.SauceOptions.chrome(options) instead
+     */
+    @Deprecated
     public SauceOptions(ChromeOptions options) {
         this(new MutableCapabilities(options));
     }
 
+    /**
+     * @deprecated Use com.saucelabs.saucebindings.options.SauceOptions.edge(options) instead
+     */
+    @Deprecated
     public SauceOptions(EdgeOptions options) {
         this(new MutableCapabilities(options));
     }
 
+    /**
+     * @deprecated Use com.saucelabs.saucebindings.options.SauceOptions.firefox(options) instead
+     */
+    @Deprecated
     public SauceOptions(FirefoxOptions options) {
         this(new MutableCapabilities(options));
     }
 
+    /**
+     * @deprecated Use com.saucelabs.saucebindings.options.SauceOptions.ie(options) instead
+     */
+    @Deprecated
     public SauceOptions(InternetExplorerOptions options) {
         this(new MutableCapabilities(options));
     }
 
+    /**
+     * @deprecated Use com.saucelabs.saucebindings.options.SauceOptions.safari(options) instead
+     */
+    @Deprecated
     public SauceOptions(SafariOptions options) {
         this(new MutableCapabilities(options));
     }
 
-    public Map<Timeouts, Integer> getTimeouts() {
-        if (timeout.getTimeouts().isEmpty()) {
-            return timeouts;
-        }
-        return timeout.getTimeouts();
-    }
-
     private SauceOptions(MutableCapabilities options) {
-        seleniumCapabilities = new MutableCapabilities(options.asMap());
+        capabilities = new MutableCapabilities(options.asMap());
+        capabilityManager = new CapabilityManager(this);
         if (options.getCapability("browserName") != null) {
             setCapability("browserName", options.getCapability("browserName"));
         }
     }
 
-    public MutableCapabilities toCapabilities() {
-        MutableCapabilities sauceCapabilities = addAuthentication();
+    /**
+     * @deprecated Use getCapabilities() instead
+     * @return instance capabilities that will get sent to the RemoteWebDriver
+     */
+    @Deprecated
+    public MutableCapabilities getSeleniumCapabilities() {
+        return capabilities;
+    }
 
-        if (getCapability("jobVisibility") != null) {
-            sauceCapabilities.setCapability("public", getCapability("jobVisibility"));
-        }
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param avoidProxy
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setAvoidProxy(Boolean avoidProxy) {
+        return sauce().setAvoidProxy(avoidProxy);
+    }
 
-        if (getCapability("prerunUrl") != null) {
-            sauceCapabilities.setCapability("prerun", getCapability("prerunUrl"));
-        }
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param build
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setBuild(String build) {
+        return sauce().setBuild(build);
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param capturePerformance
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setCapturePerformance(Boolean capturePerformance) {
+        return sauce().setCapturePerformance(capturePerformance);
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param chromedriverVersion
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setChromedriverVersion(String chromedriverVersion) {
+        return sauce().setChromedriverVersion(chromedriverVersion);
+    }
 
-        w3cDefinedOptions.forEach((capability) -> {
-            addCapabilityIfDefined(seleniumCapabilities, capability);
-        });
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param commandTimeout
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setCommandTimeout(Integer commandTimeout) {
+        return sauce().setCommandTimeout(commandTimeout);
+    }
 
-        sauceDefinedOptions.forEach((capability) -> {
-            addCapabilityIfDefined(sauceCapabilities, capability);
-        });
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param customData
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setCustomData(Map<String, Object> customData) {
+        return sauce().setCustomData(customData);
+    }
 
-        seleniumCapabilities.setCapability("sauce:options", sauceCapabilities);
-        return seleniumCapabilities;
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param extendedDebugging
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setExtendedDebugging(Boolean extendedDebugging) {
+        return sauce().setExtendedDebugging(extendedDebugging);
     }
 
-    private void addCapabilityIfDefined(MutableCapabilities capabilities, String capability) {
-        Object value = getCapability(capability);
-        if (value != null) {
-            capabilities.setCapability(capability, value);
-        }
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param idleTimeout
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setIdleTimeout(Integer idleTimeout) {
+        return sauce().setIdleTimeout(idleTimeout);
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param iedriverVersion
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setIedriverVersion(String iedriverVersion) {
+        return sauce().setIedriverVersion(iedriverVersion);
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param maxDuration
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setMaxDuration(Integer maxDuration) {
+        return sauce().setMaxDuration(maxDuration);
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param name
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setName(String name) {
+        return sauce().setName(name);
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param parentTunnel
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setParentTunnel(String parentTunnel) {
+        return sauce().setParentTunnel(parentTunnel);
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param prerun
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setPrerun(Map<Prerun, Object> prerun) {
+        return sauce().setPrerun(prerun);
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param prerunUrl
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setPrerunUrl(URL prerunUrl) {
+        return sauce().setPrerunUrl(prerunUrl);
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param priority
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setPriority(Integer priority) {
+        return sauce().setPriority(priority);
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param jobVisibility
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setJobVisibility(JobVisibility jobVisibility) {
+        return sauce().setJobVisibility(jobVisibility);
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param recordLogs
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setRecordLogs(Boolean recordLogs) {
+        return sauce().setRecordLogs(recordLogs);
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param recordScreenshots
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setRecordScreenshots(Boolean recordScreenshots) {
+        return sauce().setRecordScreenshots(recordScreenshots);
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param recordVideo
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setRecordVideo(Boolean recordVideo) {
+        return sauce().setRecordVideo(recordVideo);
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param screenResolution
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setScreenResolution(String screenResolution) {
+        return sauce().setScreenResolution(screenResolution);
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param seleniumVersion
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setSeleniumVersion(String seleniumVersion) {
+        return sauce().setSeleniumVersion(seleniumVersion);
     }
 
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param tags
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setTags(List<String> tags) {
+        return sauce().setTags(tags);
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param timeZone
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setTimeZone(String timeZone) {
+        return sauce().setTimeZone(timeZone);
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param tunnelIdentifier
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setTunnelIdentifier (String tunnelIdentifier) {
+        return sauce().setTunnelIdentifier(tunnelIdentifier);
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @param videoUploadOnPass
+     * @return
+     */
+    @Deprecated
+    public BaseOptions setVideoUploadOnPass(Boolean videoUploadOnPass) {
+        return sauce().setVideoUploadOnPass(videoUploadOnPass);
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public Boolean getAvoidProxy() {
+        return sauce().getAvoidProxy();
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
     public String getBuild() {
-        if (build != null) {
-            return build;
-        } else if (getEnvironmentVariable(knownCITools.get("Jenkins")) != null) {
-            return getEnvironmentVariable("BUILD_NAME") + ": " + getEnvironmentVariable("BUILD_NUMBER");
-        } else if (getEnvironmentVariable(knownCITools.get("Bamboo")) != null) {
-            return getEnvironmentVariable("bamboo_shortJobName") + ": " + getEnvironmentVariable("bamboo_buildNumber");
-        } else if (getEnvironmentVariable(knownCITools.get("Travis")) != null) {
-            return getEnvironmentVariable("TRAVIS_JOB_NAME") + ": " + getEnvironmentVariable("TRAVIS_JOB_NUMBER");
-        } else if (getEnvironmentVariable(knownCITools.get("Circle")) != null) {
-            return getEnvironmentVariable("CIRCLE_JOB") + ": " + getEnvironmentVariable("CIRCLE_BUILD_NUM");
-        } else if (getEnvironmentVariable(knownCITools.get("GitLab")) != null) {
-            return getEnvironmentVariable("CI_JOB_NAME") + ": " + getEnvironmentVariable("CI_JOB_ID");
-        } else if (getEnvironmentVariable(knownCITools.get("TeamCity")) != null) {
-            return getEnvironmentVariable("TEAMCITY_PROJECT_NAME") + ": " + getEnvironmentVariable("BUILD_NUMBER");
-        } else {
-            return "Build Time: " + System.currentTimeMillis();
-        }
+        return sauce().getBuild();
     }
 
-    public boolean isKnownCI() {
-        return !knownCITools.keySet().stream().allMatch((key) -> getEnvironmentVariable(key) == null);
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public Boolean getCapturePerformance() {
+        return sauce().getCapturePerformance();
     }
 
-    // Use Case is pulling serialized information from JSON/YAML, converting it to a HashMap and passing it in
-    // This is a preferred pattern as it avoids conditionals in code
-    public void mergeCapabilities(Map<String, Object> capabilities) {
-        capabilities.forEach(this::setCapability);
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public String getChromedriverVersion() {
+        return sauce().getChromedriverVersion();
     }
 
-    // This might be made public in future version; For now, no good reason to prefer it over direct accessor
-    private Object getCapability(String capability) {
-        try {
-            String getter = "get" + capability.substring(0, 1).toUpperCase() + capability.substring(1);
-            Method declaredMethod = null;
-            declaredMethod = SauceOptions.class.getDeclaredMethod(getter);
-            return declaredMethod.invoke(this);
-        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
-            e.printStackTrace();
-            return null;
-        }
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public Integer getCommandTimeout() {
+        return sauce().getCommandTimeout();
     }
 
-    public void setCapability(String key, Object value) {
-        if (primaryEnum.contains(key) && value.getClass().equals(String.class)) {
-            setEnumCapability(key, (String) value);
-        } else if (secondaryEnum.contains(key) && isKeyString((HashMap) value)) {
-            setEnumCapability(key, (HashMap) value);
-        } else {
-            try {
-                Class<?> type = SauceOptions.class.getDeclaredField(key).getType();
-                String setter = "set" + key.substring(0, 1).toUpperCase() + key.substring(1);
-                Method method = SauceOptions.class.getDeclaredMethod(setter, type);
-                method.invoke(this, value);
-            } catch (NoSuchFieldException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
-                e.printStackTrace();
-            }
-        }
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public Map<String, Object> getCustomData() {
+        return sauce().getCustomData();
     }
 
-    private boolean isKeyString(HashMap map) {
-        return map.keySet().toArray()[0].getClass().equals(String.class);
-    }
-
-    // this method is only used when setting capabilities from mergeCapabilities method
-    private void setEnumCapability(String key, HashMap value) {
-        if ("prerun".equals(key)) {
-            Map<Prerun, Object> prerunMap = new HashMap<>();
-            value.forEach((oldKey, val) -> {
-                enumValidator("Prerun", Prerun.keys(), (String) oldKey);
-                String keyString = Prerun.fromString((String) oldKey);
-                prerunMap.put(Prerun.valueOf(keyString), val);
-            });
-            setPrerun(prerunMap);
-        } else if ("timeouts".equals(key)) {
-            Map<Timeouts, Integer> timeoutsMap = new HashMap<>();
-            value.forEach((oldKey, val) -> {
-                enumValidator("Timeouts", Timeouts.keys(), (String) oldKey);
-                String keyString = Timeouts.fromString((String) oldKey);
-                timeoutsMap.put(Timeouts.valueOf(keyString), (Integer) val);
-            });
-            setTimeouts(timeoutsMap);
-        }
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public Boolean getExtendedDebugging() {
+        return sauce().getExtendedDebugging();
     }
 
-    // this method is only used when setting capabilities from mergeCapabilities method
-    private void setEnumCapability(String key, String value) {
-        switch (key) {
-            case "browserName":
-                enumValidator("Browser", Browser.keys(), value);
-                setBrowserName(Browser.valueOf(Browser.fromString(value)));
-                break;
-            case "platformName":
-                enumValidator("SaucePlatform", SaucePlatform.keys(), value);
-                setPlatformName(SaucePlatform.valueOf(SaucePlatform.fromString(value)));
-                break;
-            case "jobVisibility":
-                enumValidator("JobVisibility", JobVisibility.keys(), value);
-                setJobVisibility(JobVisibility.valueOf(JobVisibility.fromString(value)));
-                break;
-            case "pageLoadStrategy":
-                enumValidator("PageLoadStrategy", PageLoadStrategy.keys(), value);
-                setPageLoadStrategy(PageLoadStrategy.valueOf(PageLoadStrategy.fromString(value)));
-                break;
-            case "unhandledPromptBehavior":
-                enumValidator("UnhandledPromptBehavior", UnhandledPromptBehavior.keys(), value);
-                setUnhandledPromptBehavior(UnhandledPromptBehavior.valueOf(UnhandledPromptBehavior.fromString(value)));
-                break;
-            default:
-                break;
-        }
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public Integer getIdleTimeout() {
+        return sauce().getIdleTimeout();
     }
 
-    private void enumValidator(String name, Set values, String value) {
-        if (!values.contains(value)) {
-            String message = value + " is not a valid " + name + ", please choose from: " + values;
-            throw new InvalidSauceOptionsArgumentException(message);
-        }
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public String getIedriverVersion() {
+        return sauce().getIedriverVersion();
     }
 
-    private MutableCapabilities addAuthentication() {
-        MutableCapabilities caps = new MutableCapabilities();
-        caps.setCapability("username", getSauceUsername());
-        caps.setCapability("accessKey", getSauceAccessKey());
-        return caps;
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public Integer getMaxDuration() {
+        return sauce().getMaxDuration();
     }
 
-    protected String getSauceUsername() {
-        return tryToGetVariable("SAUCE_USERNAME", "Sauce Username was not provided");
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public String getName() {
+        return sauce().getName();
     }
 
-    private String tryToGetVariable(String key, String errorMessage) {
-        if (getSystemProperty(key) != null) {
-            return getSystemProperty(key);
-        } else if (getEnvironmentVariable(key) != null) {
-            return getEnvironmentVariable(key);
-        } else {
-            throw new SauceEnvironmentVariablesNotSetException(errorMessage);
-        }
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public String getParentTunnel() {
+        return sauce().getParentTunnel();
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public Map<Prerun, Object> getPrerun() {
+        return sauce().getPrerun();
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public URL getPrerunUrl() {
+        return sauce().getPrerunUrl();
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public Integer getPriority() {
+        return sauce().getPriority();
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public JobVisibility getJobVisibility() {
+        return sauce().getJobVisibility();
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public Boolean getRecordLogs() {
+        return sauce().getRecordLogs();
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public Boolean getRecordScreenshots() {
+        return sauce().getRecordScreenshots();
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public Boolean getRecordVideo() {
+        return sauce().getRecordVideo();
     }
 
-    protected String getSauceAccessKey() {
-        return tryToGetVariable("SAUCE_ACCESS_KEY", "Sauce Access Key was not provided");
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public String getScreenResolution() {
+        return sauce().getScreenResolution();
     }
 
-    protected String getSystemProperty(String key) {
-        return System.getProperty(key);
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public String getSeleniumVersion() {
+        return sauce().getSeleniumVersion();
     }
 
-    protected String getEnvironmentVariable(String key) {
-        return System.getenv(key);
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public List<String> getTags() {
+        return sauce().getTags();
     }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public String getTimeZone() {
+        return sauce().getTimeZone();
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public String getTunnelIdentifier () {
+        return sauce().getTunnelIdentifier();
+    }
+
+    /**
+     * @deprecated Use with sauce() or even better, use the new Builder Pattern
+     * @return
+     */
+    @Deprecated
+    public Boolean getVideoUploadOnPass() {
+        return sauce().getVideoUploadOnPass();
+    }
+
+    /**
+     * @deprecated Use validOptions instead
+     * @return
+     */
+    @Deprecated
+    public final List<String> w3cDefinedOptions = this.validOptions;
+
+    /**
+     * @deprecated Use sauce().validOptions instead
+     * @return
+     */
+    @Deprecated
+    public final List<String> sauceDefinedOptions = sauce().validOptions;
+
+    /**
+     * @deprecated Use sauce().validOptions instead
+     * @return
+     */
+    @Deprecated
+    public boolean isKnownCI() {
+        return sauce().isKnownCI();
+    }
+
+    /**
+     * @deprecated Use SauceLabsOptions.knownCITools instead
+     * @return
+     */
+    @Deprecated
+    public static final Map<String, String> knownCITools = SauceLabsOptions.knownCITools;
 }
diff --git a/java/src/main/java/com/saucelabs/saucebindings/SauceSession.java b/java/src/main/java/com/saucelabs/saucebindings/SauceSession.java
index b20d6f3f..faaf91fa 100755
--- a/java/src/main/java/com/saucelabs/saucebindings/SauceSession.java
+++ b/java/src/main/java/com/saucelabs/saucebindings/SauceSession.java
@@ -1,5 +1,7 @@
 package com.saucelabs.saucebindings;
 
+import com.saucelabs.saucebindings.options.BaseConfigurations;
+import com.saucelabs.saucebindings.options.SauceOptions;
 import lombok.Getter;
 import lombok.Setter;
 import org.openqa.selenium.InvalidArgumentException;
@@ -20,6 +22,16 @@ public SauceSession() {
         this(new SauceOptions());
     }
 
+    /**
+     * Ideally the end user calls build() on Configurations instance
+     * this constructor is being accommodating in case they do not
+     *
+     * @param configs
+     */
+    public SauceSession(BaseConfigurations configs) {
+        this(configs.build());
+    }
+
     public SauceSession(SauceOptions options) {
         sauceOptions = options;
     }
@@ -61,7 +73,7 @@ private void updateResult(String result) {
         // The first print statement will automatically populate links on Jenkins to Sauce
         // The second print statement will output the job link to logging/console
         if (this.driver != null) {
-            String sauceReporter = String.format("SauceOnDemandSessionID=%s job-name=%s", this.driver.getSessionId(), this.sauceOptions.getName());
+            String sauceReporter = String.format("SauceOnDemandSessionID=%s job-name=%s", this.driver.getSessionId(), this.sauceOptions.sauce().getName());
             String sauceTestLink = String.format("Test Job Link: https://app.saucelabs.com/tests/%s", this.driver.getSessionId());
             System.out.print(sauceReporter + "\n" + sauceTestLink + "\n");
         }
diff --git a/java/src/main/java/com/saucelabs/saucebindings/SystemManager.java b/java/src/main/java/com/saucelabs/saucebindings/SystemManager.java
new file mode 100644
index 00000000..12b19dcd
--- /dev/null
+++ b/java/src/main/java/com/saucelabs/saucebindings/SystemManager.java
@@ -0,0 +1,37 @@
+package com.saucelabs.saucebindings;
+
+public class SystemManager {
+
+    /**
+     * Shortcut for getting the System Property or the Environment Variable
+     * If the no value is found, an exception is thrown with the provided message
+     *
+     * @param key           the name of the property or environment variable
+     * @param errorMessage  the error message if the property or environment variable is not found
+     * @return              the value of the provided field name
+     */
+    public static String get(String key, String errorMessage) {
+        String value = get(key);
+        if (value == null) {
+            throw new SauceEnvironmentVariablesNotSetException(errorMessage);
+        } else {
+            return value;
+        }
+    }
+
+    /**
+     * Shortcut for getting the System Property or the Environment Variable
+     *
+     * @param key           the name of the property or environment variable
+     * @return              the value of the provided field name
+     */
+    public static String get(String key) {
+        if (System.getProperty(key) != null) {
+            return System.getProperty(key);
+        } else if (System.getenv(key) != null) {
+            return System.getenv(key);
+        } else {
+            return null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/java/src/main/java/com/saucelabs/saucebindings/examples/BasicOptionsTest.java b/java/src/main/java/com/saucelabs/saucebindings/examples/BasicOptionsTest.java
index 825021ed..5b5aca35 100644
--- a/java/src/main/java/com/saucelabs/saucebindings/examples/BasicOptionsTest.java
+++ b/java/src/main/java/com/saucelabs/saucebindings/examples/BasicOptionsTest.java
@@ -1,5 +1,6 @@
 package com.saucelabs.saucebindings.examples;
 
+import com.saucelabs.saucebindings.options.SauceOptions;
 import com.saucelabs.saucebindings.*;
 import org.junit.Test;
 import org.openqa.selenium.remote.RemoteWebDriver;
diff --git a/java/src/main/java/com/saucelabs/saucebindings/examples/BrowserOptionsTest.java b/java/src/main/java/com/saucelabs/saucebindings/examples/BrowserOptionsTest.java
index 1d5e3aca..943a4a83 100644
--- a/java/src/main/java/com/saucelabs/saucebindings/examples/BrowserOptionsTest.java
+++ b/java/src/main/java/com/saucelabs/saucebindings/examples/BrowserOptionsTest.java
@@ -1,6 +1,7 @@
 package com.saucelabs.saucebindings.examples;
 
 import com.saucelabs.saucebindings.*;
+import com.saucelabs.saucebindings.options.SauceOptions;
 import org.junit.Test;
 import org.openqa.selenium.firefox.FirefoxOptions;
 import org.openqa.selenium.remote.RemoteWebDriver;
@@ -14,7 +15,7 @@ public void browserOptions() {
         browserOptions.addArguments("--foo");
 
         // 2. Create Sauce Options object with the Browser Options object instance
-        SauceOptions sauceOptions = new SauceOptions(browserOptions);
+        SauceOptions sauceOptions = SauceOptions.firefox(browserOptions).build();
 
         // 3. Create Session object with the Sauce Options object instance
         SauceSession session = new SauceSession(sauceOptions);
diff --git a/java/src/main/java/com/saucelabs/saucebindings/examples/SauceLabsOptionsTest.java b/java/src/main/java/com/saucelabs/saucebindings/examples/SauceLabsOptionsTest.java
index 97b7abe3..014c0e91 100644
--- a/java/src/main/java/com/saucelabs/saucebindings/examples/SauceLabsOptionsTest.java
+++ b/java/src/main/java/com/saucelabs/saucebindings/examples/SauceLabsOptionsTest.java
@@ -1,6 +1,7 @@
 package com.saucelabs.saucebindings.examples;
 
 import com.saucelabs.saucebindings.*;
+import com.saucelabs.saucebindings.options.SauceOptions;
 import org.junit.Test;
 import org.openqa.selenium.remote.RemoteWebDriver;
 
@@ -10,9 +11,9 @@ public class SauceLabsOptionsTest {
     public void sauceOptions() {
         // 1. Specify Sauce Specific Options
         SauceOptions sauceOptions = new SauceOptions();
-        sauceOptions.setExtendedDebugging(true);
-        sauceOptions.setIdleTimeout(100);
-        sauceOptions.setTimeZone("Alaska");
+        sauceOptions.sauce().setExtendedDebugging(true);
+        sauceOptions.sauce().setIdleTimeout(100);
+        sauceOptions.sauce().setTimeZone("Alaska");
 
         // 2. Create Session object with the Options object instance
         SauceSession session = new SauceSession(sauceOptions);
diff --git a/java/src/main/java/com/saucelabs/saucebindings/options/BaseConfigurations.java b/java/src/main/java/com/saucelabs/saucebindings/options/BaseConfigurations.java
new file mode 100644
index 00000000..26647814
--- /dev/null
+++ b/java/src/main/java/com/saucelabs/saucebindings/options/BaseConfigurations.java
@@ -0,0 +1,60 @@
+package com.saucelabs.saucebindings.options;
+
+import com.saucelabs.saucebindings.JobVisibility;
+import com.saucelabs.saucebindings.SaucePlatform;
+
+import java.util.List;
+import java.util.Map;
+
+public abstract class BaseConfigurations<T extends BaseConfigurations<T>> {
+    // Needs to be instantiated in subclass
+    SauceOptions sauceOptions = null;
+
+    // Available on all configs desktop & mobile
+
+    // Override this in subclasses to ensure valid enum
+    public T setPlatformName(SaucePlatform platform) {
+        sauceOptions.setPlatformName(platform);
+        return (T) this;
+    }
+
+    public T setName(String name) {
+        sauceOptions.sauce().setName(name);
+        return (T) this;
+    }
+
+    public T setBuild(String build) {
+        sauceOptions.sauce().setBuild(build);
+        return (T) this;
+    }
+    
+    public T setTags(List<String> tags) {
+        sauceOptions.sauce().setTags(tags);
+        return (T) this;
+    }
+    
+    public T setCustomData(Map<String, Object> data) {
+        sauceOptions.sauce().setCustomData(data);
+        return (T) this;
+    }
+    
+    // This corresponds to "public" keyword
+    public T setJobVisibility(JobVisibility visibility) {
+        sauceOptions.sauce().setJobVisibility(visibility);
+        return (T) this;
+    }
+    
+    public T setTunnelIdentifier(String identifier) {
+        sauceOptions.sauce().setTunnelIdentifier(identifier);
+        return (T) this;
+    }
+
+    public T setParentTunnel(String identifier) {
+        sauceOptions.sauce().setParentTunnel(identifier);
+        return (T) this;
+    }
+
+    public SauceOptions build() {
+        return sauceOptions;
+    }
+}
diff --git a/java/src/main/java/com/saucelabs/saucebindings/options/BaseOptions.java b/java/src/main/java/com/saucelabs/saucebindings/options/BaseOptions.java
new file mode 100644
index 00000000..a58b6629
--- /dev/null
+++ b/java/src/main/java/com/saucelabs/saucebindings/options/BaseOptions.java
@@ -0,0 +1,25 @@
+package com.saucelabs.saucebindings.options;
+
+import lombok.Getter;
+import org.openqa.selenium.MutableCapabilities;
+
+import java.util.List;
+import java.util.Map;
+
+public abstract class BaseOptions {
+    @Getter protected MutableCapabilities capabilities = new MutableCapabilities();
+    protected CapabilityManager capabilityManager;
+    @Getter public final List<String> validOptions = null;
+
+    // Use Case is pulling serialized information from JSON/YAML, converting it to a HashMap and passing it in
+    // This is a preferred pattern as it avoids conditionals in code
+    public void mergeCapabilities(Map<String, Object> mergingCapabilities) {
+        mergingCapabilities.forEach(this::setCapability);
+    }
+
+    // This dynamically calls setter
+    // Applicable enums must override this method in subclass
+    protected void setCapability(String key, Object value) {
+        capabilityManager.setCapability(key, value);
+    };
+}
diff --git a/java/src/main/java/com/saucelabs/saucebindings/options/CapabilityManager.java b/java/src/main/java/com/saucelabs/saucebindings/options/CapabilityManager.java
new file mode 100644
index 00000000..3136d6da
--- /dev/null
+++ b/java/src/main/java/com/saucelabs/saucebindings/options/CapabilityManager.java
@@ -0,0 +1,100 @@
+package com.saucelabs.saucebindings.options;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.Set;
+
+public class CapabilityManager {
+    private final BaseOptions options;
+
+    /**
+     * Class constructor created for a specific SauceOptions instance
+     *
+     * @param options the SauceOptions instance using this capabilities manager
+     */
+    public CapabilityManager(BaseOptions options) {
+        this.options = options;
+    }
+
+    /**
+     * Add values of option class's valid capabilities to the option class's capabilities object
+     */
+    public void addCapabilities() {
+        options.getValidOptions().forEach((capability) -> {
+            Object value = getCapability(capability);
+            if (value != null) {
+                options.capabilities.setCapability(capability, value);
+            }
+        });
+    }
+
+    /**
+     * Dynamically sets the provided value onto the associated options instance
+     * Alternate way of setting values, primarily used as part of merge() class
+     *
+     * @param key   Name of the capability to set on the options instance
+     * @param value Value of the capability to set on the options instance
+     * @see SauceOptions#mergeCapabilities(Map)
+     */
+    public void setCapability(String key, Object value) {
+        try {
+            Class<?> type = getField(options.getClass(), key).getType();
+            String setter = "set" + key.substring(0, 1).toUpperCase() + key.substring(1);
+            Method method = options.getClass().getMethod(setter, type);
+            method.invoke(options, value);
+        } catch (NoSuchFieldException e) {
+            throw new InvalidSauceOptionsArgumentException(key + " is not a valid configuration value");
+        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+            throw new RuntimeException("Unable to set capability for " + key, e);
+        }
+    }
+
+    /**
+     * Dynamically obtain the value of the Field set on the options instance
+     *
+     * @param capability    the name of the field to get the value from
+     * @return              the value of the provided field name
+     */
+    public Object getCapability(String capability) {
+        try {
+            String getter = "get" + capability.substring(0, 1).toUpperCase() + capability.substring(1);
+            Method method = options.getClass().getMethod(getter);
+            return method.invoke(options);
+        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+            throw new RuntimeException("Unable to get capability for " + capability, e);
+        }
+    }
+
+    /**
+     * This ensures that a parameter passed in by the mergeCapabilities method matches a valid enum
+     *
+     * @param name      Which enum we are working with for better error message
+     * @param values    Valid options for the provided capability
+     * @param value     Value of the option we want to merge into the capabilities
+     * @see SauceOptions#mergeCapabilities(Map)
+     */
+    public void validateCapability(String name, Set values, String value) {
+        if (!values.contains(value)) {
+            String message = value + " is not a valid " + name + ", please choose from: " + values;
+            throw new InvalidSauceOptionsArgumentException(message);
+        }
+    }
+
+    /**
+     * Recursively searches superclasses to find desired Field even when private
+     */
+    private Field getField(Class<?> optionsClass, String fieldName) throws NoSuchFieldException {
+        try {
+            return optionsClass.getDeclaredField(fieldName);
+        } catch (NoSuchFieldException e) {
+            Class<?> superclass = optionsClass.getSuperclass();
+            if (superclass == null) {
+                throw e;
+            } else {
+                return getField(optionsClass.getSuperclass(), fieldName);
+            }
+        }
+    }
+}
diff --git a/java/src/main/java/com/saucelabs/saucebindings/options/ChromeConfigurations.java b/java/src/main/java/com/saucelabs/saucebindings/options/ChromeConfigurations.java
new file mode 100644
index 00000000..8c0cf94d
--- /dev/null
+++ b/java/src/main/java/com/saucelabs/saucebindings/options/ChromeConfigurations.java
@@ -0,0 +1,30 @@
+package com.saucelabs.saucebindings.options;
+
+import org.openqa.selenium.chrome.ChromeOptions;
+
+public class ChromeConfigurations extends VDCConfigurations<ChromeConfigurations> {
+    ChromeConfigurations(ChromeOptions chromeOptions) {
+        sauceOptions = new SauceOptions(chromeOptions);
+    }
+
+    public ChromeConfigurations setCapturePerformance() {
+        sauceOptions.sauce().setExtendedDebugging(true);
+        sauceOptions.sauce().setCapturePerformance(true);
+        if (sauceOptions.sauce().getName() == null) {
+            throw new InvalidSauceOptionsArgumentException("Need to call `setName()` before `setCapturePerformance`");
+        }
+        return this;
+    }
+
+    // Use case is a different point release than the driver provided by default
+    // TODO - consider ensuring this matches browser Version
+    public ChromeConfigurations setChromedriverVersion(String version) {
+        sauceOptions.sauce().setChromedriverVersion(version);
+        return this;
+    }
+
+    public ChromeConfigurations setExtendedDebugging() {
+        sauceOptions.sauce().setExtendedDebugging(true);
+        return this;
+    }
+}
diff --git a/java/src/main/java/com/saucelabs/saucebindings/options/EdgeConfigurations.java b/java/src/main/java/com/saucelabs/saucebindings/options/EdgeConfigurations.java
new file mode 100644
index 00000000..04b0f78f
--- /dev/null
+++ b/java/src/main/java/com/saucelabs/saucebindings/options/EdgeConfigurations.java
@@ -0,0 +1,30 @@
+package com.saucelabs.saucebindings.options;
+
+import org.openqa.selenium.edge.EdgeOptions;
+
+public class EdgeConfigurations extends VDCConfigurations<EdgeConfigurations> {
+    EdgeConfigurations(EdgeOptions edgeOptions) {
+        sauceOptions = new SauceOptions(edgeOptions);
+    }
+
+    // TODO: Figure out if extendedDebugging or SeleniumVersion applies
+
+    public EdgeConfigurations setSeleniumVersion(String version) {
+        sauceOptions.sauce().setSeleniumVersion(version);
+        return this;
+    }
+
+    public EdgeConfigurations setCapturePerformance() {
+        sauceOptions.sauce().setExtendedDebugging(true);
+        sauceOptions.sauce().setCapturePerformance(true);
+        if (sauceOptions.sauce().getName() == null) {
+            throw new InvalidSauceOptionsArgumentException("Need to call `setName()` before `setCapturePerformance`");
+        }
+        return this;
+    }
+
+    public EdgeConfigurations setExtendedDebugging() {
+        sauceOptions.sauce().setExtendedDebugging(true);
+        return this;
+    }
+}
diff --git a/java/src/main/java/com/saucelabs/saucebindings/options/FirefoxConfigurations.java b/java/src/main/java/com/saucelabs/saucebindings/options/FirefoxConfigurations.java
new file mode 100644
index 00000000..d0bedbdb
--- /dev/null
+++ b/java/src/main/java/com/saucelabs/saucebindings/options/FirefoxConfigurations.java
@@ -0,0 +1,24 @@
+package com.saucelabs.saucebindings.options;
+
+import org.openqa.selenium.firefox.FirefoxOptions;
+
+public class FirefoxConfigurations extends VDCConfigurations<FirefoxConfigurations> {
+    FirefoxConfigurations(FirefoxOptions firefoxOptions) {
+        sauceOptions = new SauceOptions(firefoxOptions);
+    }
+
+    public FirefoxConfigurations setSeleniumVersion(String version) {
+        sauceOptions.sauce().setSeleniumVersion(version);
+        return this;
+    }
+
+    public FirefoxConfigurations setGeckodriverVersion(String version) {
+        sauceOptions.sauce().setGeckodriverVersion(version);
+        return this;
+    }
+
+    public FirefoxConfigurations setExtendedDebugging() {
+        sauceOptions.sauce().setExtendedDebugging(true);
+        return this;
+    }
+}
diff --git a/java/src/main/java/com/saucelabs/saucebindings/options/InternetExplorerConfigurations.java b/java/src/main/java/com/saucelabs/saucebindings/options/InternetExplorerConfigurations.java
new file mode 100644
index 00000000..d4014bd7
--- /dev/null
+++ b/java/src/main/java/com/saucelabs/saucebindings/options/InternetExplorerConfigurations.java
@@ -0,0 +1,24 @@
+package com.saucelabs.saucebindings.options;
+
+import org.openqa.selenium.ie.InternetExplorerOptions;
+
+public class InternetExplorerConfigurations extends VDCConfigurations<InternetExplorerConfigurations> {
+    InternetExplorerConfigurations(InternetExplorerOptions internetExplorerOptions) {
+        sauceOptions = new SauceOptions(internetExplorerOptions);
+    }
+
+    public InternetExplorerConfigurations setSeleniumVersion(String version) {
+        sauceOptions.sauce().setSeleniumVersion(version);
+        return this;
+    }
+
+    public InternetExplorerConfigurations setIedriverVersion(String version) {
+        sauceOptions.sauce().setIedriverVersion(version);
+        return this;
+    }
+
+    public InternetExplorerConfigurations setAvoidProxy() {
+        sauceOptions.sauce().setAvoidProxy(true);
+        return this;
+    }
+}
diff --git a/java/src/main/java/com/saucelabs/saucebindings/options/InvalidSauceOptionsArgumentException.java b/java/src/main/java/com/saucelabs/saucebindings/options/InvalidSauceOptionsArgumentException.java
new file mode 100644
index 00000000..c157bdd2
--- /dev/null
+++ b/java/src/main/java/com/saucelabs/saucebindings/options/InvalidSauceOptionsArgumentException.java
@@ -0,0 +1,7 @@
+package com.saucelabs.saucebindings.options;
+
+public class InvalidSauceOptionsArgumentException extends RuntimeException {
+    public InvalidSauceOptionsArgumentException(String message) {
+        super(message);
+    }
+}
diff --git a/java/src/main/java/com/saucelabs/saucebindings/options/SafariConfigurations.java b/java/src/main/java/com/saucelabs/saucebindings/options/SafariConfigurations.java
new file mode 100644
index 00000000..5327f0db
--- /dev/null
+++ b/java/src/main/java/com/saucelabs/saucebindings/options/SafariConfigurations.java
@@ -0,0 +1,21 @@
+package com.saucelabs.saucebindings.options;
+
+import com.saucelabs.saucebindings.SaucePlatform;
+import org.openqa.selenium.safari.SafariOptions;
+
+public class SafariConfigurations extends VDCConfigurations<SafariConfigurations> {
+    SafariConfigurations(SafariOptions safariOptions) {
+        sauceOptions = new SauceOptions(safariOptions);
+        sauceOptions.setPlatformName(SaucePlatform.MAC_CATALINA);
+    }
+
+    public SafariConfigurations setAvoidProxy() {
+        sauceOptions.sauce().setAvoidProxy(true);
+        return this;
+    }
+
+    public SafariConfigurations setSeleniumVersion(String version) {
+        sauceOptions.sauce().setSeleniumVersion(version);
+        return this;
+    }
+}
diff --git a/java/src/main/java/com/saucelabs/saucebindings/options/SauceLabsOptions.java b/java/src/main/java/com/saucelabs/saucebindings/options/SauceLabsOptions.java
new file mode 100644
index 00000000..bbc1c412
--- /dev/null
+++ b/java/src/main/java/com/saucelabs/saucebindings/options/SauceLabsOptions.java
@@ -0,0 +1,167 @@
+package com.saucelabs.saucebindings.options;
+
+import com.saucelabs.saucebindings.JobVisibility;
+import com.saucelabs.saucebindings.Prerun;
+import com.saucelabs.saucebindings.SystemManager;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import org.openqa.selenium.MutableCapabilities;
+
+import java.net.URL;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Accessors(chain = true) @Setter @Getter
+public class SauceLabsOptions extends BaseOptions {
+    // https://wiki.saucelabs.com/display/DOCS/Test+Configuration+Options
+    private Boolean avoidProxy = null;
+    private String build;
+    private Boolean capturePerformance = null;
+    private String chromedriverVersion;
+    private Integer commandTimeout = null;
+    private Map<String, Object> customData = null;
+    private Boolean extendedDebugging = null;
+    private String geckodriverVersion;
+    private Integer idleTimeout = null;
+    private String iedriverVersion;
+    private Integer maxDuration = null;
+    private String name;
+    private String parentTunnel;
+    private Map<Prerun, Object> prerun;
+    private URL prerunUrl;
+    private Integer priority = null;
+    private JobVisibility jobVisibility; // the actual key for this is a Java reserved keyword "public"; uses enum
+    private Boolean recordLogs = null;
+    private Boolean recordScreenshots = null;
+    private Boolean recordVideo = null;
+    private String screenResolution;
+    private String seleniumVersion;
+    private List<String> tags = null;
+    private String timeZone;
+    private String tunnelIdentifier;
+    private Boolean videoUploadOnPass = null;
+
+    public final List<String> validOptions = Arrays.asList(
+            "avoidProxy",
+            "build",
+            "capturePerformance",
+            "chromedriverVersion",
+            "commandTimeout",
+            "customData",
+            "extendedDebugging",
+            "geckodriverVersion",
+            "idleTimeout",
+            "iedriverVersion",
+            "jobVisibility",
+            "maxDuration",
+            "name",
+            "parentTunnel",
+            "prerun",
+            "prerunUrl",
+            "priority",
+            // public, do not use, reserved keyword, using jobVisibility with enum
+            "recordLogs",
+            "recordScreenshots",
+            "recordVideo",
+            "screenResolution",
+            "seleniumVersion",
+            "tags",
+            "timeZone",
+            "tunnelIdentifier",
+            "videoUploadOnPass");
+
+    public SauceLabsOptions() {
+        capabilityManager = new CapabilityManager(this);
+    }
+
+    public MutableCapabilities toCapabilities() {
+        capabilities.setCapability("username", getSauceUsername());
+        capabilities.setCapability("accessKey", getSauceAccessKey());
+
+        Object visibilityValue = capabilityManager.getCapability("jobVisibility");
+        if (visibilityValue != null) {
+            capabilities.setCapability("public", visibilityValue);
+            setJobVisibility(null);
+        }
+
+        Object prerunValue = capabilityManager.getCapability("prerunUrl");
+        if (prerunValue != null) {
+            capabilities.setCapability("prerun", prerunValue);
+            setPrerunUrl(null);
+        }
+
+        capabilityManager.addCapabilities();
+        return capabilities;
+    }
+
+    /**
+     * This method is to handle special cases and enums as necessary
+     *
+     * @param key   Which capability to set on this instance's Selenium MutableCapabilities instance
+     * @param value The value of the capability getting set
+     */
+    @Override
+    protected void setCapability(String key, Object value) {
+        if ("jobVisibility".equals(key)) {
+            capabilityManager.validateCapability("JobVisibility", JobVisibility.keys(), (String) value);
+            setJobVisibility(JobVisibility.valueOf(JobVisibility.fromString((String) value)));
+        } else if ("prerun".equals(key)) {
+            Map<Prerun, Object> prerunMap = new HashMap<>();
+            ((Map) value).forEach((oldKey, val) -> {
+                capabilityManager.validateCapability("Prerun", Prerun.keys(), (String) oldKey);
+                String keyString = Prerun.fromString((String) oldKey);
+                prerunMap.put(Prerun.valueOf(keyString), val);
+            });
+            setPrerun(prerunMap);
+        } else {
+            super.setCapability(key, value);
+        }
+    }
+
+    public String getBuild() {
+        if (build != null) {
+            return build;
+        } else if (SystemManager.get(knownCITools.get("Jenkins")) != null) {
+            return SystemManager.get("BUILD_NAME") + ": " + SystemManager.get("BUILD_NUMBER");
+        } else if (SystemManager.get(knownCITools.get("Bamboo")) != null) {
+            return SystemManager.get("bamboo_shortJobName") + ": " + SystemManager.get("bamboo_buildNumber");
+        } else if (SystemManager.get(knownCITools.get("Travis")) != null) {
+            return SystemManager.get("TRAVIS_JOB_NAME") + ": " + SystemManager.get("TRAVIS_JOB_NUMBER");
+        } else if (SystemManager.get(knownCITools.get("Circle")) != null) {
+            return SystemManager.get("CIRCLE_JOB") + ": " + SystemManager.get("CIRCLE_BUILD_NUM");
+        } else if (SystemManager.get(knownCITools.get("GitLab")) != null) {
+            return SystemManager.get("CI_JOB_NAME") + ": " + SystemManager.get("CI_JOB_ID");
+        } else if (SystemManager.get(knownCITools.get("TeamCity")) != null) {
+            return SystemManager.get("TEAMCITY_PROJECT_NAME") + ": " + SystemManager.get("BUILD_NUMBER");
+        } else {
+            return "Build Time: " + System.currentTimeMillis();
+        }
+    }
+
+    public boolean isKnownCI() {
+        return !knownCITools.keySet().stream().allMatch((key) -> SystemManager.get(key) == null);
+    }
+
+    public static final Map<String, String> knownCITools;
+
+    static {
+        knownCITools = new HashMap<>();
+        knownCITools.put("Jenkins", "BUILD_TAG");
+        knownCITools.put("Bamboo", "bamboo_agentId");
+        knownCITools.put("Travis", "TRAVIS_JOB_ID");
+        knownCITools.put("Circle", "CIRCLE_JOB");
+        knownCITools.put("GitLab", "CI");
+        knownCITools.put("TeamCity", "TEAMCITY_PROJECT_NAME");
+    }
+
+    protected String getSauceUsername() {
+        return SystemManager.get("SAUCE_USERNAME", "Sauce Username was not provided");
+    }
+
+    protected String getSauceAccessKey() {
+        return SystemManager.get("SAUCE_ACCESS_KEY", "Sauce Access Key was not provided");
+    }
+}
diff --git a/java/src/main/java/com/saucelabs/saucebindings/options/SauceOptions.java b/java/src/main/java/com/saucelabs/saucebindings/options/SauceOptions.java
new file mode 100644
index 00000000..b06b780e
--- /dev/null
+++ b/java/src/main/java/com/saucelabs/saucebindings/options/SauceOptions.java
@@ -0,0 +1,172 @@
+package com.saucelabs.saucebindings.options;
+
+import com.saucelabs.saucebindings.*;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import org.openqa.selenium.MutableCapabilities;
+import org.openqa.selenium.Proxy;
+import org.openqa.selenium.chrome.ChromeOptions;
+import org.openqa.selenium.edge.EdgeOptions;
+import org.openqa.selenium.firefox.FirefoxOptions;
+import org.openqa.selenium.ie.InternetExplorerOptions;
+import org.openqa.selenium.safari.SafariOptions;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Accessors(chain = true)
+@Setter @Getter
+public class SauceOptions extends BaseOptions {
+    @Setter(AccessLevel.NONE) @Getter(AccessLevel.NONE) private SauceLabsOptions sauceLabsOptions = null;
+    public TimeoutStore timeout = new TimeoutStore();
+
+    // w3c Settings
+    protected Browser browserName = Browser.CHROME;
+    protected String browserVersion = "latest";
+    protected SaucePlatform platformName = SaucePlatform.WINDOWS_10;
+    protected PageLoadStrategy pageLoadStrategy;
+    protected Boolean acceptInsecureCerts = null;
+    protected Proxy proxy;
+    protected Boolean setWindowRect = null;
+    @Getter(AccessLevel.NONE) protected Map<Timeouts, Integer> timeouts;
+    protected Boolean strictFileInteractability = null;
+    protected UnhandledPromptBehavior unhandledPromptBehavior;
+
+    public final List<String> validOptions = Arrays.asList(
+            "browserName",
+            "browserVersion",
+            "platformName",
+            "pageLoadStrategy",
+            "acceptInsecureCerts",
+            "proxy",
+            "setWindowRect",
+            "timeouts",
+            "strictFileInteractability",
+            "unhandledPromptBehavior");
+
+    public static ChromeConfigurations chrome() {
+        return chrome(new ChromeOptions());
+    }
+
+    public static ChromeConfigurations chrome(ChromeOptions chromeOptions) {
+        return new ChromeConfigurations(chromeOptions);
+    }
+
+    public static EdgeConfigurations edge() {
+        return edge(new EdgeOptions());
+    }
+
+    public static EdgeConfigurations edge(EdgeOptions edgeOptions) {
+        return new EdgeConfigurations(edgeOptions);
+    }
+
+    public static FirefoxConfigurations firefox() {
+        return firefox(new FirefoxOptions());
+    }
+
+    public static FirefoxConfigurations firefox(FirefoxOptions firefoxOptions) {
+        return new FirefoxConfigurations(firefoxOptions);
+    }
+
+    public static InternetExplorerConfigurations ie() {
+        return ie(new InternetExplorerOptions());
+    }
+
+    public static InternetExplorerConfigurations ie(InternetExplorerOptions internetExplorerOptions) {
+        return new InternetExplorerConfigurations(internetExplorerOptions);
+    }
+
+    public static SafariConfigurations safari() {
+        return safari(new SafariOptions());
+    }
+
+    public static SafariConfigurations safari(SafariOptions safariOptions) {
+        return new SafariConfigurations(safariOptions);
+    }
+
+    public SauceLabsOptions sauce() {
+        return sauceLabsOptions;
+    }
+
+    public SauceOptions() {
+        this(new MutableCapabilities());
+    }
+
+    public Map<Timeouts, Integer> getTimeouts() {
+        if (timeout.getTimeouts().isEmpty()) {
+            return timeouts;
+        }
+        return timeout.getTimeouts();
+    }
+
+    SauceOptions(MutableCapabilities options) {
+        capabilities = new MutableCapabilities(options.asMap());
+        capabilityManager = new CapabilityManager(this);
+        sauceLabsOptions = new SauceLabsOptions();
+        if (options.getCapability("browserName") != null) {
+            setCapability("browserName", options.getCapability("browserName"));
+        }
+    }
+
+    public MutableCapabilities toCapabilities() {
+        capabilityManager.addCapabilities();
+        capabilities.setCapability("sauce:options", sauce().toCapabilities());
+        return capabilities;
+    }
+
+    /**
+     * This method is to handle special cases and enums as necessary
+     *
+     * @param key   Which capability to set on this instance's Selenium MutableCapabilities instance
+     * @param value The value of the capability getting set
+     */
+    @Override
+    public void setCapability(String key, Object value) {
+        switch (key) {
+            case "browserName":
+                capabilityManager.validateCapability("Browser", Browser.keys(), (String) value);
+                setBrowserName(Browser.valueOf(Browser.fromString((String) value)));
+                break;
+            case "platformName":
+                capabilityManager.validateCapability("SaucePlatform", SaucePlatform.keys(), (String) value);
+                setPlatformName(SaucePlatform.valueOf(SaucePlatform.fromString((String) value)));
+                break;
+            case "pageLoadStrategy":
+                capabilityManager.validateCapability("PageLoadStrategy", PageLoadStrategy.keys(), (String) value);
+                setPageLoadStrategy(PageLoadStrategy.valueOf(PageLoadStrategy.fromString((String) value)));
+                break;
+            case "unhandledPromptBehavior":
+                capabilityManager.validateCapability("UnhandledPromptBehavior", UnhandledPromptBehavior.keys(), (String) value);
+                setUnhandledPromptBehavior(UnhandledPromptBehavior.valueOf(UnhandledPromptBehavior.fromString((String) value)));
+                break;
+            case "timeouts":
+                Map<Timeouts, Integer> timeoutsMap = new HashMap<>();
+                ((Map) value).forEach((oldKey, val) -> {
+                    capabilityManager.validateCapability("Timeouts", Timeouts.keys(), (String) oldKey);
+                    String keyString = Timeouts.fromString((String) oldKey);
+                    timeoutsMap.put(Timeouts.valueOf(keyString), (Integer) val);
+                });
+                setTimeouts(timeoutsMap);
+                break;
+            case "sauce":
+                sauce().mergeCapabilities((HashMap<String, Object>) value);
+                break;
+            default:
+                if (sauce().getValidOptions().contains(key)) {
+                    deprecatedSetCapability(key, value);
+                } else {
+                    super.setCapability(key, value);
+                }
+        }
+    }
+
+    private void deprecatedSetCapability(String key, Object value) {
+        System.out.println("WARNING: using merge() of Map with value of (" + key + ") is DEPRECATED");
+        System.out.println("place this value inside a nested Map with the keyword 'sauce'");
+        sauce().setCapability(key, value);
+    }
+}
diff --git a/java/src/main/java/com/saucelabs/saucebindings/options/VDCConfigurations.java b/java/src/main/java/com/saucelabs/saucebindings/options/VDCConfigurations.java
new file mode 100644
index 00000000..d6934897
--- /dev/null
+++ b/java/src/main/java/com/saucelabs/saucebindings/options/VDCConfigurations.java
@@ -0,0 +1,121 @@
+package com.saucelabs.saucebindings.options;
+
+import com.saucelabs.saucebindings.PageLoadStrategy;
+import com.saucelabs.saucebindings.Prerun;
+import com.saucelabs.saucebindings.UnhandledPromptBehavior;
+import org.openqa.selenium.Proxy;
+
+import java.net.URL;
+import java.time.Duration;
+import java.util.Map;
+
+public abstract class VDCConfigurations<T extends VDCConfigurations<T>> extends BaseConfigurations<T> {
+
+    // W3C Values
+
+    // TODO: Set restrictions on allowed versions in subclass
+    public T setBrowserVersion(String browserVersion) {
+        sauceOptions.setBrowserVersion(browserVersion);
+        return (T) this;
+    }
+
+    public T setPageLoadStrategy(PageLoadStrategy pageLoadStrategy) {
+        sauceOptions.setPageLoadStrategy(pageLoadStrategy);
+        return (T) this;
+    }
+
+    public T setAcceptInsecureCerts() {
+        sauceOptions.setAcceptInsecureCerts(true);
+        return (T) this;
+    }
+
+    public T setProxy(Proxy proxy) {
+        sauceOptions.setProxy(proxy);
+        return (T) this;
+    }
+
+    public T setStrictFileInteractability() {
+        sauceOptions.setStrictFileInteractability(true);
+        return (T) this;
+    }
+
+    public T setUnhandledPromptBehavior(UnhandledPromptBehavior behavior) {
+        sauceOptions.setUnhandledPromptBehavior(behavior);
+        return (T) this;
+    }
+
+    // Sauce Values common to all browser sessions:
+    public T disableRecordVideo() {
+        sauceOptions.sauce().setRecordVideo(false);
+        return (T) this;
+    }
+
+    public T disableVideoUploadOnPass() {
+        sauceOptions.sauce().setVideoUploadOnPass(false);
+        return (T) this;
+    }
+
+    public T disableRecordScreenshots() {
+        sauceOptions.sauce().setRecordScreenshots(false);
+        return (T) this;
+    }
+
+    public T disableRecordLogs() {
+        sauceOptions.sauce().setRecordLogs(false);
+        return (T) this;
+    }
+
+    public T setMaxDuration(Duration duration) {
+        sauceOptions.sauce().setMaxDuration((int) duration.getSeconds());
+        return (T) this;
+    }
+    public T setCommandTimeout(Duration duration) {
+        sauceOptions.sauce().setCommandTimeout((int) duration.getSeconds());
+        return (T) this;
+    }
+
+    public T setIdleTimeout(Duration duration) {
+        sauceOptions.sauce().setIdleTimeout((int) duration.getSeconds());
+        return (T) this;
+    }
+
+    public T setPrerun(Map<Prerun, Object> prerun) {
+        sauceOptions.sauce().setPrerun(prerun);
+        return (T) this;
+    }
+
+    public T setPrerunUrl(URL url) {
+        sauceOptions.sauce().setPrerunUrl(url);
+        return (T) this;
+    }
+
+    public T setPriority(Integer priority) {
+        sauceOptions.sauce().setPriority(priority);
+        return (T) this;
+    }
+
+    public T setScreenResolution(String resolution) {
+        sauceOptions.sauce().setScreenResolution(resolution);
+        return (T) this;
+    }
+
+    public T setTimeZone(String timeZone) {
+        sauceOptions.sauce().setTimeZone(timeZone);
+        return (T) this;
+    }
+
+    public T setImplicitWaitTimeout(Duration timeout) {
+        sauceOptions.timeout.setImplicitWait((int) timeout.getSeconds());
+        return (T) this;
+    }
+
+    public T setPageLoadTimeout(Duration timeout) {
+        sauceOptions.timeout.setPageLoad((int) timeout.getSeconds());
+        return (T) this;
+    }
+
+    public T setScriptTimeout(Duration timeout) {
+        sauceOptions.timeout.setScript((int) timeout.getSeconds());
+        return (T) this;
+    }
+}


Test patch:
diff --git a/java/src/test/java/com/saucelabs/saucebindings/SauceOptionsTest.java b/java/src/test/java/com/saucelabs/saucebindings/SauceOptionsDeprecatedTest.java
similarity index 91%
rename from java/src/test/java/com/saucelabs/saucebindings/SauceOptionsTest.java
rename to java/src/test/java/com/saucelabs/saucebindings/SauceOptionsDeprecatedTest.java
index 3d220db4..b7bc66b2 100644
--- a/java/src/test/java/com/saucelabs/saucebindings/SauceOptionsTest.java
+++ b/java/src/test/java/com/saucelabs/saucebindings/SauceOptionsDeprecatedTest.java
@@ -1,5 +1,6 @@
 package com.saucelabs.saucebindings;
 
+import com.saucelabs.saucebindings.options.InvalidSauceOptionsArgumentException;
 import lombok.SneakyThrows;
 import org.junit.Rule;
 import org.junit.Test;
@@ -13,9 +14,7 @@
 import org.openqa.selenium.safari.SafariOptions;
 import org.yaml.snakeyaml.Yaml;
 
-import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -23,12 +22,11 @@
 import java.util.Map;
 
 import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
+import static org.junit.Assert.assertNotNull;
 import static org.openqa.selenium.UnexpectedAlertBehaviour.DISMISS;
 
-public class SauceOptionsTest {
-    private SauceOptions sauceOptions = spy(new SauceOptions());
+public class SauceOptionsDeprecatedTest {
+    private SauceOptions sauceOptions = new SauceOptions();
 
     @Rule
     public MockitoRule initRule = MockitoJUnit.rule();
@@ -223,23 +221,19 @@ public void acceptsSafariOptionsClass() {
 
     @Test
     public void createsDefaultBuildName() {
-        doReturn("Not Empty").when(sauceOptions).getEnvironmentVariable("BUILD_TAG");
-        doReturn("TEMP BUILD").when(sauceOptions).getEnvironmentVariable("BUILD_NAME");
-        doReturn("11").when(sauceOptions).getEnvironmentVariable("BUILD_NUMBER");
-
-        assertEquals("TEMP BUILD: 11", sauceOptions.getBuild());
+        assertNotNull(sauceOptions.getBuild());
     }
 
     @SneakyThrows
     public Map<String, Object> serialize(String key) {
-        InputStream input = new FileInputStream(new File("src/test/java/com/saucelabs/saucebindings/options.yml"));
+        InputStream input = new FileInputStream("src/test/java/com/saucelabs/saucebindings/options.yml");
         Yaml yaml = new Yaml();
         Map<String, Object> data = yaml.load(input);
         return (Map<String, Object>) data.get(key);
     }
 
     @Test
-    public void setsCapabilitiesFromMap() throws FileNotFoundException {
+    public void setsCapabilitiesFromMap() {
         Map<String, Object> map = serialize("exampleValues");
 
         sauceOptions.mergeCapabilities(map);
@@ -348,11 +342,8 @@ public void setsBadPageLoadFromMap() {
 
     @Test
     public void parsesCapabilitiesFromW3CValues() {
-        doReturn("test-name").when(sauceOptions).getEnvironmentVariable("SAUCE_USERNAME");
-        doReturn("test-accesskey").when(sauceOptions).getEnvironmentVariable("SAUCE_ACCESS_KEY");
-
         sauceOptions.setBrowserName(Browser.FIREFOX);
-        sauceOptions.setPlatformName(SaucePlatform.MAC_BIG_SUR);
+        sauceOptions.setPlatformName(SaucePlatform.MAC_HIGH_SIERRA);
         sauceOptions.setBrowserVersion("77");
         sauceOptions.setAcceptInsecureCerts(true);
         sauceOptions.setPageLoadStrategy(PageLoadStrategy.EAGER);
@@ -372,7 +363,7 @@ public void parsesCapabilitiesFromW3CValues() {
         MutableCapabilities expectedCapabilities = new MutableCapabilities();
         expectedCapabilities.setCapability("browserName", "firefox");
         expectedCapabilities.setCapability("browserVersion", "77");
-        expectedCapabilities.setCapability("platformName", "macOS 11.00");
+        expectedCapabilities.setCapability("platformName", "macOS 10.13");
         expectedCapabilities.setCapability("acceptInsecureCerts", true);
         expectedCapabilities.setCapability("setWindowRect", true);
         expectedCapabilities.setCapability("strictFileInteractability", true);
@@ -382,8 +373,8 @@ public void parsesCapabilitiesFromW3CValues() {
 
         MutableCapabilities sauceCapabilities = new MutableCapabilities();
         sauceCapabilities.setCapability("build", "Build Name");
-        sauceCapabilities.setCapability("username", "test-name");
-        sauceCapabilities.setCapability("accessKey", "test-accesskey");
+        sauceCapabilities.setCapability("username", SystemManager.get("SAUCE_USERNAME"));
+        sauceCapabilities.setCapability("accessKey", SystemManager.get("SAUCE_ACCESS_KEY"));
         expectedCapabilities.setCapability("sauce:options", sauceCapabilities);
         MutableCapabilities actualCapabilities = sauceOptions.toCapabilities();
 
@@ -392,9 +383,6 @@ public void parsesCapabilitiesFromW3CValues() {
 
     @Test
     public void parsesCapabilitiesFromSauceValues() {
-        doReturn("test-name").when(sauceOptions).getEnvironmentVariable("SAUCE_USERNAME");
-        doReturn("test-accesskey").when(sauceOptions).getEnvironmentVariable("SAUCE_ACCESS_KEY");
-
         Map<String, Object> customData = new HashMap<>();
         customData.put("foo", "foo");
         customData.put("bar", "bar");
@@ -465,8 +453,8 @@ public void parsesCapabilitiesFromSauceValues() {
         sauceCapabilities.setCapability("timeZone", "San Francisco");
         sauceCapabilities.setCapability("tunnelIdentifier", "tunnelname");
         sauceCapabilities.setCapability("videoUploadOnPass", false);
-        sauceCapabilities.setCapability("username", "test-name");
-        sauceCapabilities.setCapability("accessKey", "test-accesskey");
+        sauceCapabilities.setCapability("username", SystemManager.get("SAUCE_USERNAME"));
+        sauceCapabilities.setCapability("accessKey", SystemManager.get("SAUCE_ACCESS_KEY"));
 
         MutableCapabilities expectedCapabilities = new MutableCapabilities();
         expectedCapabilities.setCapability("browserName", "chrome");
@@ -487,10 +475,7 @@ public void parsesCapabilitiesFromSeleniumValues() {
         firefoxOptions.addPreference("foo", "bar");
         firefoxOptions.setUnhandledPromptBehaviour(DISMISS);
 
-        sauceOptions = spy(new SauceOptions(firefoxOptions));
-        doReturn("test-name").when(sauceOptions).getEnvironmentVariable("SAUCE_USERNAME");
-        doReturn("test-accesskey").when(sauceOptions).getEnvironmentVariable("SAUCE_ACCESS_KEY");
-
+        sauceOptions = new SauceOptions(firefoxOptions);
         sauceOptions.setBuild("Build Name");
 
         MutableCapabilities expectedCapabilities = new MutableCapabilities();
@@ -501,8 +486,8 @@ public void parsesCapabilitiesFromSeleniumValues() {
 
         MutableCapabilities sauceCapabilities = new MutableCapabilities();
         sauceCapabilities.setCapability("build", "Build Name");
-        sauceCapabilities.setCapability("username", "test-name");
-        sauceCapabilities.setCapability("accessKey", "test-accesskey");
+        sauceCapabilities.setCapability("username", SystemManager.get("SAUCE_USERNAME"));
+        sauceCapabilities.setCapability("accessKey", SystemManager.get("SAUCE_ACCESS_KEY"));
         expectedCapabilities.setCapability("sauce:options", sauceCapabilities);
         MutableCapabilities actualCapabilities = sauceOptions.toCapabilities();
 
@@ -518,10 +503,7 @@ public void parsesW3CAndSauceAndSeleniumSettings() {
         firefoxOptions.addArguments("--foo");
         firefoxOptions.setUnhandledPromptBehaviour(DISMISS);
 
-        sauceOptions = spy(new SauceOptions(firefoxOptions));
-
-        doReturn("test-name").when(sauceOptions).getEnvironmentVariable("SAUCE_USERNAME");
-        doReturn("test-accesskey").when(sauceOptions).getEnvironmentVariable("SAUCE_ACCESS_KEY");
+        sauceOptions = new SauceOptions(firefoxOptions);
 
         expectedCapabilities.merge(firefoxOptions);
         expectedCapabilities.setCapability("browserVersion", "latest");
@@ -555,8 +537,8 @@ public void parsesW3CAndSauceAndSeleniumSettings() {
 
         sauceOptions.setJobVisibility(JobVisibility.SHARE);
         sauceCapabilities.setCapability("public", JobVisibility.SHARE);
-        sauceCapabilities.setCapability("username", "test-name");
-        sauceCapabilities.setCapability("accessKey", "test-accesskey");
+        sauceCapabilities.setCapability("username", SystemManager.get("SAUCE_USERNAME"));
+        sauceCapabilities.setCapability("accessKey", SystemManager.get("SAUCE_ACCESS_KEY"));
 
         expectedCapabilities.setCapability("sauce:options", sauceCapabilities);
         MutableCapabilities actualCapabilities = sauceOptions.toCapabilities();
diff --git a/java/src/test/java/com/saucelabs/saucebindings/SauceSessionTest.java b/java/src/test/java/com/saucelabs/saucebindings/SauceSessionTest.java
index 86a23154..e6f158c5 100644
--- a/java/src/test/java/com/saucelabs/saucebindings/SauceSessionTest.java
+++ b/java/src/test/java/com/saucelabs/saucebindings/SauceSessionTest.java
@@ -1,5 +1,6 @@
 package com.saucelabs.saucebindings;
 
+import com.saucelabs.saucebindings.options.SauceOptions;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -51,6 +52,17 @@ public void sauceSessionUsesProvidedSauceOptions() {
         verify(sauceOptions).toCapabilities();
     }
 
+    @Test
+    public void sauceSessionUsesProvidedSauceConfigs() {
+        SauceSession sauceSession = new SauceSession(SauceOptions.chrome()
+                .setPlatformName(SaucePlatform.MAC_MOJAVE));
+        SauceOptions sauceOptions = sauceSession.getSauceOptions();
+
+        assertEquals(Browser.CHROME, sauceOptions.getBrowserName());
+        assertEquals(SaucePlatform.MAC_MOJAVE, sauceOptions.getPlatformName());
+        assertEquals("latest", sauceOptions.getBrowserVersion());
+    }
+
     @Test
     public void defaultsToUSWestDataCenter() {
         String expectedDataCenterEndpoint = DataCenter.US_WEST.getValue();
@@ -71,18 +83,6 @@ public void setsSauceURLDirectly() throws MalformedURLException {
         assertEquals(expetedSauceUrl, sauceSession.getSauceUrl().toString());
     }
 
-    @Test(expected = SauceEnvironmentVariablesNotSetException.class)
-    public void startThrowsErrorWithoutUsername() {
-        doReturn(null).when(sauceOptions).getEnvironmentVariable("SAUCE_USERNAME");
-        sauceOptsSession.start();
-    }
-
-    @Test(expected = SauceEnvironmentVariablesNotSetException.class)
-    public void startThrowsErrorWithoutAccessKey() {
-        doReturn(null).when(sauceOptions).getEnvironmentVariable("SAUCE_ACCESS_KEY");
-        sauceOptsSession.start();
-    }
-
     @Test
     public void stopCallsDriverQuitPassing() {
         sauceSession.start();
diff --git a/java/src/test/java/com/saucelabs/saucebindings/deprecated_options.yml b/java/src/test/java/com/saucelabs/saucebindings/deprecated_options.yml
new file mode 100644
index 00000000..62e4607b
--- /dev/null
+++ b/java/src/test/java/com/saucelabs/saucebindings/deprecated_options.yml
@@ -0,0 +1,56 @@
+exampleValues:
+  browserName: 'firefox'
+  browserVersion: '68'
+  platformName: 'macOS 10.13'
+  acceptInsecureCerts: true
+  pageLoadStrategy: 'eager'
+  setWindowRect: true
+  unhandledPromptBehavior: "accept"
+  strictFileInteractability: true
+  timeouts:
+    implicit: 1
+    pageLoad: 59
+    script: 29
+  avoidProxy: true
+  build: 'Sample Build Name'
+  capturePerformance: true
+  chromedriverVersion: '71'
+  commandTimeout: 2
+  customData:
+    foo: 'foo'
+    bar: 'bar'
+  extendedDebugging: true
+  idleTimeout: 3
+  iedriverVersion: '3.141.0'
+  maxDuration: 300
+  name: 'Sample Test Name'
+  parentTunnel: 'Mommy'
+  prerun:
+    executable: "http://url.to/your/executable.exe"
+    args:
+      - --silent
+      - -a
+      - -q
+    background: false
+    timeout: 120
+  priority: 0
+  jobVisibility: 'team'
+  recordLogs: false
+  recordScreenshots: false
+  recordVideo: false
+  screenResolution: '10x10'
+  seleniumVersion: '3.141.59'
+  tags:
+    - foo
+    - bar
+    - foobar
+  timeZone: 'San Francisco'
+  tunnelIdentifier: 'tunnelname'
+  videoUploadOnPass: false
+
+badJobVisibility:
+  jobVisibility: "me"
+
+badPrerun:
+  prerun:
+    nope: ""
diff --git a/java/src/test/java/com/saucelabs/saucebindings/integration/DesktopBrowserTest.java b/java/src/test/java/com/saucelabs/saucebindings/integration/DesktopBrowserTest.java
index 695f32cd..0bd9b68b 100644
--- a/java/src/test/java/com/saucelabs/saucebindings/integration/DesktopBrowserTest.java
+++ b/java/src/test/java/com/saucelabs/saucebindings/integration/DesktopBrowserTest.java
@@ -1,7 +1,7 @@
 package com.saucelabs.saucebindings.integration;
 
 import com.saucelabs.saucebindings.DataCenter;
-import com.saucelabs.saucebindings.SauceOptions;
+import com.saucelabs.saucebindings.options.SauceOptions;
 import com.saucelabs.saucebindings.SaucePlatform;
 import com.saucelabs.saucebindings.SauceSession;
 import org.junit.After;
diff --git a/java/src/test/java/com/saucelabs/saucebindings/options.yml b/java/src/test/java/com/saucelabs/saucebindings/options.yml
index 5e48f3c9..a911ebe4 100644
--- a/java/src/test/java/com/saucelabs/saucebindings/options.yml
+++ b/java/src/test/java/com/saucelabs/saucebindings/options.yml
@@ -11,42 +11,43 @@ exampleValues:
     implicit: 1
     pageLoad: 59
     script: 29
-  avoidProxy: true
-  build: 'Sample Build Name'
-  capturePerformance: true
-  chromedriverVersion: '71'
-  commandTimeout: 2
-  customData:
-    foo: 'foo'
-    bar: 'bar'
-  extendedDebugging: true
-  idleTimeout: 3
-  iedriverVersion: '3.141.0'
-  maxDuration: 300
-  name: 'Sample Test Name'
-  parentTunnel: 'Mommy'
-  prerun:
-    executable: "http://url.to/your/executable.exe"
-    args:
-      - --silent
-      - -a
-      - -q
-    background: false
-    timeout: 120
-  priority: 0
-  jobVisibility: 'team'
-  recordLogs: false
-  recordScreenshots: false
-  recordVideo: false
-  screenResolution: '10x10'
-  seleniumVersion: '3.141.59'
-  tags:
-    - foo
-    - bar
-    - foobar
-  timeZone: 'San Francisco'
-  tunnelIdentifier: 'tunnelname'
-  videoUploadOnPass: false
+  sauce:
+    avoidProxy: true
+    build: 'Sample Build Name'
+    capturePerformance: true
+    chromedriverVersion: '71'
+    commandTimeout: 2
+    customData:
+      foo: 'foo'
+      bar: 'bar'
+    extendedDebugging: true
+    idleTimeout: 3
+    iedriverVersion: '3.141.0'
+    maxDuration: 300
+    name: 'Sample Test Name'
+    parentTunnel: 'Mommy'
+    prerun:
+      executable: "http://url.to/your/executable.exe"
+      args:
+        - --silent
+        - -a
+        - -q
+      background: false
+      timeout: 120
+    priority: 0
+    jobVisibility: 'team'
+    recordLogs: false
+    recordScreenshots: false
+    recordVideo: false
+    screenResolution: '10x10'
+    seleniumVersion: '3.141.59'
+    tags:
+      - foo
+      - bar
+      - foobar
+    timeZone: 'San Francisco'
+    tunnelIdentifier: 'tunnelname'
+    videoUploadOnPass: false
 
 badBrowser:
   browserName: "netscape"
@@ -55,7 +56,8 @@ badPlatform:
   platformName: "MAC"
 
 badJobVisibility:
-  jobVisibility: "me"
+  sauce:
+    jobVisibility: "me"
 
 badPageLoad:
   browserName: "fast"
@@ -68,12 +70,19 @@ badTimeout:
     bad: 1
 
 badPrerun:
-  prerun:
-    nope: ""
+  sauce:
+    prerun:
+      nope: ""
 
 invalidOption:
   foo: "bar"
   browser_Nme: "firefox"
   browserVersion: "70"
   platformName: "MacOS 10.12"
-  recordScreenshots: false
+
+invalidSauceOption:
+  browser_Nme: "firefox"
+  browserVersion: "70"
+  platformName: "MacOS 10.12"
+  sauce:
+    foo: "bar"
diff --git a/java/src/test/java/com/saucelabs/saucebindings/options/ChromeConfigurationsTest.java b/java/src/test/java/com/saucelabs/saucebindings/options/ChromeConfigurationsTest.java
new file mode 100644
index 00000000..039ba28d
--- /dev/null
+++ b/java/src/test/java/com/saucelabs/saucebindings/options/ChromeConfigurationsTest.java
@@ -0,0 +1,152 @@
+package com.saucelabs.saucebindings.options;
+
+import com.saucelabs.saucebindings.*;
+import org.junit.Assert;
+import org.junit.Test;
+import org.openqa.selenium.UnexpectedAlertBehaviour;
+import org.openqa.selenium.chrome.ChromeOptions;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class ChromeConfigurationsTest {
+
+    @Test
+    public void buildsDefaultSauceOptions() {
+        SauceOptions sauceOptions = SauceOptions.chrome().build();
+
+        Assert.assertEquals(Browser.CHROME, sauceOptions.getBrowserName());
+        Assert.assertEquals(SaucePlatform.WINDOWS_10, sauceOptions.getPlatformName());
+        assertEquals("latest", sauceOptions.getBrowserVersion());
+    }
+
+    @Test
+    public void acceptsChromeOptionsClass() {
+        ChromeOptions chromeOptions = new ChromeOptions();
+        chromeOptions.addArguments("--foo");
+        chromeOptions.setUnhandledPromptBehaviour(UnexpectedAlertBehaviour.DISMISS);
+
+        SauceOptions sauceOptions = SauceOptions.chrome(chromeOptions).build();
+
+        assertEquals(Browser.CHROME, sauceOptions.getBrowserName());
+        assertEquals(chromeOptions, sauceOptions.getCapabilities());
+    }
+
+    @Test
+    public void fluentOrderDoesNotMatter() {
+        // Setting these in order from superclass methods to subclass to ensure proper return instances
+        SauceOptions sauceOptions = SauceOptions.chrome()
+                .setPlatformName(SaucePlatform.MAC_HIGH_SIERRA)
+                .setBrowserVersion("68")
+                .setExtendedDebugging()
+                .build();
+
+        assertEquals(SaucePlatform.MAC_HIGH_SIERRA, sauceOptions.getPlatformName());
+        assertEquals("68", sauceOptions.getBrowserVersion());
+        assertTrue(sauceOptions.sauce().getExtendedDebugging());
+    }
+
+    @Test
+    public void acceptsOtherW3CValues() {
+        SauceOptions sauceOptions = SauceOptions.chrome()
+                .setAcceptInsecureCerts()
+                .setPageLoadStrategy(PageLoadStrategy.EAGER)
+                .setUnhandledPromptBehavior(UnhandledPromptBehavior.IGNORE)
+                .setStrictFileInteractability()
+                .setImplicitWaitTimeout(Duration.ofSeconds(1))
+                .setPageLoadTimeout(Duration.ofSeconds(100))
+                .setScriptTimeout(Duration.ofSeconds(10))
+                .build();
+
+        Map<Timeouts, Integer> timeouts = new HashMap<>();
+        timeouts.put(Timeouts.IMPLICIT, 1);
+        timeouts.put(Timeouts.PAGE_LOAD, 100);
+        timeouts.put(Timeouts.SCRIPT, 10);
+
+        assertEquals(true, sauceOptions.getAcceptInsecureCerts());
+        assertEquals(PageLoadStrategy.EAGER, sauceOptions.getPageLoadStrategy());
+        assertEquals(UnhandledPromptBehavior.IGNORE, sauceOptions.getUnhandledPromptBehavior());
+        assertEquals(true, sauceOptions.getStrictFileInteractability());
+        assertEquals(timeouts, sauceOptions.getTimeouts());
+    }
+
+    @Test(expected = InvalidSauceOptionsArgumentException.class)
+    public void capturePerformanceRequiresName() {
+        SauceOptions.chrome().setCapturePerformance().build();
+    }
+
+    @Test
+    public void acceptsSauceLabsSettings() {
+        Map<String, Object> customData = new HashMap<>();
+        customData.put("foo", "foo");
+        customData.put("bar", "bar");
+
+        List<String> args = new ArrayList<>();
+        args.add("--silent");
+        args.add("-a");
+        args.add("-q");
+
+        Map<Prerun, Object> prerun = new HashMap<>();
+        prerun.put(Prerun.EXECUTABLE, "http://url.to/your/executable.exe");
+        prerun.put(Prerun.ARGS, args);
+        prerun.put(Prerun.BACKGROUND, false);
+        prerun.put(Prerun.TIMEOUT, 120);
+
+        List<String> tags = new ArrayList<>();
+        tags.add("Foo");
+        tags.add("Bar");
+        tags.add("Foobar");
+
+        SauceOptions sauceOptions = SauceOptions.chrome()
+                .setBuild("Sample Build Name")
+                .setName("Test name")
+                .setCapturePerformance()
+                .setChromedriverVersion("71")
+                .setCommandTimeout(Duration.ofSeconds(2))
+                .setCustomData(customData)
+                .setExtendedDebugging()
+                .setIdleTimeout(Duration.ofSeconds(20))
+                .setMaxDuration(Duration.ofSeconds(300))
+                .setParentTunnel("Mommy")
+                .setPrerun(prerun)
+                .setPriority(0)
+                .setJobVisibility(JobVisibility.TEAM)
+                .disableRecordLogs()
+                .disableRecordScreenshots()
+                .disableRecordVideo()
+                .setScreenResolution("1024x768")
+                .setTags(tags)
+                .setTimeZone("San Francisco")
+                .setTunnelIdentifier("tunnelname")
+                .disableVideoUploadOnPass()
+                .build();
+
+        assertEquals("Sample Build Name", sauceOptions.sauce().getBuild());
+        assertEquals(true, sauceOptions.sauce().getCapturePerformance());
+        assertEquals("71", sauceOptions.sauce().getChromedriverVersion());
+        assertEquals(Integer.valueOf(2), sauceOptions.sauce().getCommandTimeout());
+        assertEquals(customData, sauceOptions.sauce().getCustomData());
+        assertEquals(true, sauceOptions.sauce().getExtendedDebugging());
+        assertEquals(Integer.valueOf(20), sauceOptions.sauce().getIdleTimeout());
+        assertEquals(Integer.valueOf(300), sauceOptions.sauce().getMaxDuration());
+        assertEquals("Test name", sauceOptions.sauce().getName());
+        assertEquals("Mommy", sauceOptions.sauce().getParentTunnel());
+        assertEquals(prerun, sauceOptions.sauce().getPrerun());
+        assertEquals(Integer.valueOf(0), sauceOptions.sauce().getPriority());
+        assertEquals(JobVisibility.TEAM, sauceOptions.sauce().getJobVisibility());
+        assertEquals(false, sauceOptions.sauce().getRecordLogs());
+        assertEquals(false, sauceOptions.sauce().getRecordScreenshots());
+        assertEquals(false, sauceOptions.sauce().getRecordVideo());
+        assertEquals("1024x768", sauceOptions.sauce().getScreenResolution());
+        assertEquals(tags, sauceOptions.sauce().getTags());
+        assertEquals("San Francisco", sauceOptions.sauce().getTimeZone());
+        assertEquals("tunnelname", sauceOptions.sauce().getTunnelIdentifier());
+        assertEquals(false, sauceOptions.sauce().getVideoUploadOnPass());
+    }
+}
diff --git a/java/src/test/java/com/saucelabs/saucebindings/options/EdgeConfigurationsTest.java b/java/src/test/java/com/saucelabs/saucebindings/options/EdgeConfigurationsTest.java
new file mode 100644
index 00000000..e1a5e878
--- /dev/null
+++ b/java/src/test/java/com/saucelabs/saucebindings/options/EdgeConfigurationsTest.java
@@ -0,0 +1,145 @@
+package com.saucelabs.saucebindings.options;
+
+import com.saucelabs.saucebindings.*;
+import org.junit.Assert;
+import org.junit.Test;
+import org.openqa.selenium.edge.EdgeOptions;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class EdgeConfigurationsTest {
+
+    @Test
+    public void buildsDefaultSauceOptions() {
+        SauceOptions sauceOptions = SauceOptions.edge().build();
+
+        Assert.assertEquals(Browser.EDGE, sauceOptions.getBrowserName());
+        Assert.assertEquals(SaucePlatform.WINDOWS_10, sauceOptions.getPlatformName());
+        assertEquals("latest", sauceOptions.getBrowserVersion());
+    }
+
+    @Test
+    public void acceptsEdgeOptionsClass() {
+        EdgeOptions edgeOptions = new EdgeOptions();
+        edgeOptions.setPageLoadStrategy("eager");
+
+        SauceOptions sauceOptions = SauceOptions.edge(edgeOptions).build();
+
+        assertEquals(Browser.EDGE, sauceOptions.getBrowserName());
+        assertEquals(edgeOptions, sauceOptions.getCapabilities());
+    }
+
+    @Test
+    public void fluentOrderDoesNotMatter() {
+        // Setting these in order from superclass methods to subclass to ensure proper return instances
+        SauceOptions sauceOptions = SauceOptions.edge()
+                .setPlatformName(SaucePlatform.MAC_HIGH_SIERRA)
+                .setBrowserVersion("68")
+                .setExtendedDebugging()
+                .build();
+
+        assertEquals(SaucePlatform.MAC_HIGH_SIERRA, sauceOptions.getPlatformName());
+        assertEquals("68", sauceOptions.getBrowserVersion());
+        assertTrue(sauceOptions.sauce().getExtendedDebugging());
+    }
+
+    @Test
+    public void acceptsOtherW3CValues() {
+        SauceOptions sauceOptions = SauceOptions.edge()
+                .setAcceptInsecureCerts()
+                .setPageLoadStrategy(PageLoadStrategy.EAGER)
+                .setUnhandledPromptBehavior(UnhandledPromptBehavior.IGNORE)
+                .setStrictFileInteractability()
+                .setImplicitWaitTimeout(Duration.ofSeconds(1))
+                .setPageLoadTimeout(Duration.ofSeconds(100))
+                .setScriptTimeout(Duration.ofSeconds(10))
+                .build();
+
+        Map<Timeouts, Integer> timeouts = new HashMap<>();
+        timeouts.put(Timeouts.IMPLICIT, 1);
+        timeouts.put(Timeouts.PAGE_LOAD, 100);
+        timeouts.put(Timeouts.SCRIPT, 10);
+
+        assertEquals(true, sauceOptions.getAcceptInsecureCerts());
+        assertEquals(PageLoadStrategy.EAGER, sauceOptions.getPageLoadStrategy());
+        assertEquals(UnhandledPromptBehavior.IGNORE, sauceOptions.getUnhandledPromptBehavior());
+        assertEquals(true, sauceOptions.getStrictFileInteractability());
+        assertEquals(timeouts, sauceOptions.getTimeouts());
+    }
+
+    @Test
+    public void acceptsSauceLabsSettings() {
+        Map<String, Object> customData = new HashMap<>();
+        customData.put("foo", "foo");
+        customData.put("bar", "bar");
+
+        List<String> args = new ArrayList<>();
+        args.add("--silent");
+        args.add("-a");
+        args.add("-q");
+
+        Map<Prerun, Object> prerun = new HashMap<>();
+        prerun.put(Prerun.EXECUTABLE, "http://url.to/your/executable.exe");
+        prerun.put(Prerun.ARGS, args);
+        prerun.put(Prerun.BACKGROUND, false);
+        prerun.put(Prerun.TIMEOUT, 120);
+
+        List<String> tags = new ArrayList<>();
+        tags.add("Foo");
+        tags.add("Bar");
+        tags.add("Foobar");
+
+        SauceOptions sauceOptions = SauceOptions.edge()
+                .setBuild("Sample Build Name")
+                .setName("Test name")
+                .setCapturePerformance()
+                .setCommandTimeout(Duration.ofSeconds(2))
+                .setCustomData(customData)
+                .setExtendedDebugging()
+                .setIdleTimeout(Duration.ofSeconds(20))
+                .setMaxDuration(Duration.ofSeconds(300))
+                .setParentTunnel("Mommy")
+                .setPrerun(prerun)
+                .setPriority(0)
+                .setJobVisibility(JobVisibility.TEAM)
+                .setSeleniumVersion("3.141.0")
+                .disableRecordLogs()
+                .disableRecordScreenshots()
+                .disableRecordVideo()
+                .setScreenResolution("1024x768")
+                .setSeleniumVersion("3.141.0")
+                .setTags(tags)
+                .setTimeZone("San Francisco")
+                .setTunnelIdentifier("tunnelname")
+                .disableVideoUploadOnPass()
+                .build();
+
+        assertEquals("Sample Build Name", sauceOptions.sauce().getBuild());
+        assertEquals(Integer.valueOf(2), sauceOptions.sauce().getCommandTimeout());
+        assertEquals(customData, sauceOptions.sauce().getCustomData());
+        assertEquals(true, sauceOptions.sauce().getExtendedDebugging());
+        assertEquals(Integer.valueOf(20), sauceOptions.sauce().getIdleTimeout());
+        assertEquals(Integer.valueOf(300), sauceOptions.sauce().getMaxDuration());
+        assertEquals("Test name", sauceOptions.sauce().getName());
+        assertEquals("Mommy", sauceOptions.sauce().getParentTunnel());
+        assertEquals(prerun, sauceOptions.sauce().getPrerun());
+        assertEquals(Integer.valueOf(0), sauceOptions.sauce().getPriority());
+        assertEquals(JobVisibility.TEAM, sauceOptions.sauce().getJobVisibility());
+        assertEquals(false, sauceOptions.sauce().getRecordLogs());
+        assertEquals(false, sauceOptions.sauce().getRecordScreenshots());
+        assertEquals(false, sauceOptions.sauce().getRecordVideo());
+        assertEquals("3.141.0", sauceOptions.sauce().getSeleniumVersion());
+        assertEquals("1024x768", sauceOptions.sauce().getScreenResolution());
+        assertEquals(tags, sauceOptions.sauce().getTags());
+        assertEquals("San Francisco", sauceOptions.sauce().getTimeZone());
+        assertEquals("tunnelname", sauceOptions.sauce().getTunnelIdentifier());
+        assertEquals(false, sauceOptions.sauce().getVideoUploadOnPass());
+    }
+}
diff --git a/java/src/test/java/com/saucelabs/saucebindings/options/FirefoxConfigurationsTest.java b/java/src/test/java/com/saucelabs/saucebindings/options/FirefoxConfigurationsTest.java
new file mode 100644
index 00000000..28679020
--- /dev/null
+++ b/java/src/test/java/com/saucelabs/saucebindings/options/FirefoxConfigurationsTest.java
@@ -0,0 +1,147 @@
+package com.saucelabs.saucebindings.options;
+
+import com.saucelabs.saucebindings.*;
+import org.junit.Assert;
+import org.junit.Test;
+import org.openqa.selenium.UnexpectedAlertBehaviour;
+import org.openqa.selenium.firefox.FirefoxOptions;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class FirefoxConfigurationsTest {
+
+    @Test
+    public void buildsDefaultSauceOptions() {
+        SauceOptions sauceOptions = SauceOptions.firefox().build();
+
+        Assert.assertEquals(Browser.FIREFOX, sauceOptions.getBrowserName());
+        Assert.assertEquals(SaucePlatform.WINDOWS_10, sauceOptions.getPlatformName());
+        assertEquals("latest", sauceOptions.getBrowserVersion());
+    }
+
+    @Test
+    public void acceptsFirefoxOptionsClass() {
+        FirefoxOptions firefoxOptions = new FirefoxOptions();
+        firefoxOptions.addArguments("--foo");
+        firefoxOptions.setUnhandledPromptBehaviour(UnexpectedAlertBehaviour.DISMISS);
+
+        SauceOptions sauceOptions = SauceOptions.firefox(firefoxOptions).build();
+
+        assertEquals(Browser.FIREFOX, sauceOptions.getBrowserName());
+        assertEquals(firefoxOptions, sauceOptions.getCapabilities());
+    }
+
+    @Test
+    public void fluentOrderDoesNotMatter() {
+        // Setting these in order from superclass methods to subclass to ensure proper return instances
+        SauceOptions sauceOptions = SauceOptions.firefox()
+                .setPlatformName(SaucePlatform.MAC_HIGH_SIERRA)
+                .setBrowserVersion("68")
+                .setExtendedDebugging()
+                .build();
+
+        assertEquals(SaucePlatform.MAC_HIGH_SIERRA, sauceOptions.getPlatformName());
+        assertEquals("68", sauceOptions.getBrowserVersion());
+        assertTrue(sauceOptions.sauce().getExtendedDebugging());
+    }
+
+    @Test
+    public void acceptsOtherW3CValues() {
+        SauceOptions sauceOptions = SauceOptions.firefox()
+                .setAcceptInsecureCerts()
+                .setPageLoadStrategy(PageLoadStrategy.EAGER)
+                .setUnhandledPromptBehavior(UnhandledPromptBehavior.IGNORE)
+                .setStrictFileInteractability()
+                .setImplicitWaitTimeout(Duration.ofSeconds(1))
+                .setPageLoadTimeout(Duration.ofSeconds(100))
+                .setScriptTimeout(Duration.ofSeconds(10))
+                .build();
+
+        Map<Timeouts, Integer> timeouts = new HashMap<>();
+        timeouts.put(Timeouts.IMPLICIT, 1);
+        timeouts.put(Timeouts.PAGE_LOAD, 100);
+        timeouts.put(Timeouts.SCRIPT, 10);
+
+        assertEquals(true, sauceOptions.getAcceptInsecureCerts());
+        assertEquals(PageLoadStrategy.EAGER, sauceOptions.getPageLoadStrategy());
+        assertEquals(UnhandledPromptBehavior.IGNORE, sauceOptions.getUnhandledPromptBehavior());
+        assertEquals(true, sauceOptions.getStrictFileInteractability());
+        assertEquals(timeouts, sauceOptions.getTimeouts());
+    }
+
+    @Test
+    public void acceptsSauceLabsSettings() {
+        Map<String, Object> customData = new HashMap<>();
+        customData.put("foo", "foo");
+        customData.put("bar", "bar");
+
+        List<String> args = new ArrayList<>();
+        args.add("--silent");
+        args.add("-a");
+        args.add("-q");
+
+        Map<Prerun, Object> prerun = new HashMap<>();
+        prerun.put(Prerun.EXECUTABLE, "http://url.to/your/executable.exe");
+        prerun.put(Prerun.ARGS, args);
+        prerun.put(Prerun.BACKGROUND, false);
+        prerun.put(Prerun.TIMEOUT, 120);
+
+        List<String> tags = new ArrayList<>();
+        tags.add("Foo");
+        tags.add("Bar");
+        tags.add("Foobar");
+
+        SauceOptions sauceOptions = SauceOptions.firefox()
+                .setBuild("Sample Build Name")
+                .setName("Test name")
+                .setGeckodriverVersion("0.28")
+                .setCommandTimeout(Duration.ofSeconds(2))
+                .setCustomData(customData)
+                .setExtendedDebugging()
+                .setIdleTimeout(Duration.ofSeconds(20))
+                .setMaxDuration(Duration.ofSeconds(300))
+                .setParentTunnel("Mommy")
+                .setPrerun(prerun)
+                .setPriority(0)
+                .setJobVisibility(JobVisibility.TEAM)
+                .setSeleniumVersion("3.141.0")
+                .disableRecordLogs()
+                .disableRecordScreenshots()
+                .disableRecordVideo()
+                .setScreenResolution("1024x768")
+                .setTags(tags)
+                .setTimeZone("San Francisco")
+                .setTunnelIdentifier("tunnelname")
+                .disableVideoUploadOnPass()
+                .build();
+
+        assertEquals("Sample Build Name", sauceOptions.sauce().getBuild());
+        assertEquals(Integer.valueOf(2), sauceOptions.sauce().getCommandTimeout());
+        assertEquals(customData, sauceOptions.sauce().getCustomData());
+        assertEquals(true, sauceOptions.sauce().getExtendedDebugging());
+        assertEquals("0.28", sauceOptions.sauce().getGeckodriverVersion());
+        assertEquals(Integer.valueOf(20), sauceOptions.sauce().getIdleTimeout());
+        assertEquals(Integer.valueOf(300), sauceOptions.sauce().getMaxDuration());
+        assertEquals("Test name", sauceOptions.sauce().getName());
+        assertEquals("Mommy", sauceOptions.sauce().getParentTunnel());
+        assertEquals(prerun, sauceOptions.sauce().getPrerun());
+        assertEquals(Integer.valueOf(0), sauceOptions.sauce().getPriority());
+        assertEquals(JobVisibility.TEAM, sauceOptions.sauce().getJobVisibility());
+        assertEquals(false, sauceOptions.sauce().getRecordLogs());
+        assertEquals(false, sauceOptions.sauce().getRecordScreenshots());
+        assertEquals(false, sauceOptions.sauce().getRecordVideo());
+        assertEquals("3.141.0", sauceOptions.sauce().getSeleniumVersion());
+        assertEquals("1024x768", sauceOptions.sauce().getScreenResolution());
+        assertEquals(tags, sauceOptions.sauce().getTags());
+        assertEquals("San Francisco", sauceOptions.sauce().getTimeZone());
+        assertEquals("tunnelname", sauceOptions.sauce().getTunnelIdentifier());
+        assertEquals(false, sauceOptions.sauce().getVideoUploadOnPass());
+    }
+}
diff --git a/java/src/test/java/com/saucelabs/saucebindings/options/InternetExplorerConfigurationsTest.java b/java/src/test/java/com/saucelabs/saucebindings/options/InternetExplorerConfigurationsTest.java
new file mode 100644
index 00000000..67a3c991
--- /dev/null
+++ b/java/src/test/java/com/saucelabs/saucebindings/options/InternetExplorerConfigurationsTest.java
@@ -0,0 +1,144 @@
+package com.saucelabs.saucebindings.options;
+
+import com.saucelabs.saucebindings.*;
+import org.junit.Assert;
+import org.junit.Test;
+import org.openqa.selenium.UnexpectedAlertBehaviour;
+import org.openqa.selenium.ie.InternetExplorerOptions;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class InternetExplorerConfigurationsTest {
+
+    @Test
+    public void buildsDefaultSauceOptions() {
+        SauceOptions sauceOptions = SauceOptions.ie().build();
+
+        Assert.assertEquals(Browser.INTERNET_EXPLORER, sauceOptions.getBrowserName());
+        Assert.assertEquals(SaucePlatform.WINDOWS_10, sauceOptions.getPlatformName());
+        assertEquals("latest", sauceOptions.getBrowserVersion());
+    }
+
+    @Test
+    public void acceptsIEOptionsClass() {
+        InternetExplorerOptions internetExplorerOptions = new InternetExplorerOptions();
+        internetExplorerOptions.requireWindowFocus();
+        internetExplorerOptions.setCapability("UnexpectedAlertBehaviour", UnexpectedAlertBehaviour.DISMISS);
+
+        SauceOptions sauceOptions = SauceOptions.ie(internetExplorerOptions).build();
+
+        assertEquals(Browser.INTERNET_EXPLORER, sauceOptions.getBrowserName());
+        assertEquals(internetExplorerOptions, sauceOptions.getCapabilities());
+    }
+
+    @Test
+    public void fluentOrderDoesNotMatter() {
+        // Setting these in order from superclass methods to subclass to ensure proper return instances
+        SauceOptions sauceOptions = SauceOptions.ie()
+                .setPlatformName(SaucePlatform.WINDOWS_8)
+                .setBrowserVersion("10")
+                .setAvoidProxy()
+                .build();
+
+        assertEquals(SaucePlatform.WINDOWS_8, sauceOptions.getPlatformName());
+        assertEquals("10", sauceOptions.getBrowserVersion());
+        assertTrue(sauceOptions.sauce().getAvoidProxy());
+    }
+
+    @Test
+    public void acceptsOtherW3CValues() {
+        SauceOptions sauceOptions = SauceOptions.ie()
+                .setAcceptInsecureCerts()
+                .setPageLoadStrategy(PageLoadStrategy.EAGER)
+                .setUnhandledPromptBehavior(UnhandledPromptBehavior.IGNORE)
+                .setStrictFileInteractability()
+                .setImplicitWaitTimeout(Duration.ofSeconds(1))
+                .setPageLoadTimeout(Duration.ofSeconds(100))
+                .setScriptTimeout(Duration.ofSeconds(10))
+                .build();
+
+        Map<Timeouts, Integer> timeouts = new HashMap<>();
+        timeouts.put(Timeouts.IMPLICIT, 1);
+        timeouts.put(Timeouts.PAGE_LOAD, 100);
+        timeouts.put(Timeouts.SCRIPT, 10);
+
+        assertEquals(true, sauceOptions.getAcceptInsecureCerts());
+        assertEquals(PageLoadStrategy.EAGER, sauceOptions.getPageLoadStrategy());
+        assertEquals(UnhandledPromptBehavior.IGNORE, sauceOptions.getUnhandledPromptBehavior());
+        assertEquals(true, sauceOptions.getStrictFileInteractability());
+        assertEquals(timeouts, sauceOptions.getTimeouts());
+    }
+
+    @Test
+    public void acceptsSauceLabsSettings() {
+        Map<String, Object> customData = new HashMap<>();
+        customData.put("foo", "foo");
+        customData.put("bar", "bar");
+
+        List<String> args = new ArrayList<>();
+        args.add("--silent");
+        args.add("-a");
+        args.add("-q");
+
+        Map<Prerun, Object> prerun = new HashMap<>();
+        prerun.put(Prerun.EXECUTABLE, "http://url.to/your/executable.exe");
+        prerun.put(Prerun.ARGS, args);
+        prerun.put(Prerun.BACKGROUND, false);
+        prerun.put(Prerun.TIMEOUT, 120);
+
+        List<String> tags = new ArrayList<>();
+        tags.add("Foo");
+        tags.add("Bar");
+        tags.add("Foobar");
+
+        SauceOptions sauceOptions = SauceOptions.ie()
+                .setAvoidProxy()
+                .setBuild("Sample Build Name")
+                .setName("Test name")
+                .setCommandTimeout(Duration.ofSeconds(2))
+                .setCustomData(customData)
+                .setIdleTimeout(Duration.ofSeconds(20))
+                .setMaxDuration(Duration.ofSeconds(300))
+                .setParentTunnel("Mommy")
+                .setPrerun(prerun)
+                .setPriority(0)
+                .setJobVisibility(JobVisibility.TEAM)
+                .setSeleniumVersion("3.141.0")
+                .disableRecordLogs()
+                .disableRecordScreenshots()
+                .disableRecordVideo()
+                .setScreenResolution("1024x768")
+                .setTags(tags)
+                .setTimeZone("San Francisco")
+                .setTunnelIdentifier("tunnelname")
+                .disableVideoUploadOnPass()
+                .build();
+
+        assertEquals("Sample Build Name", sauceOptions.sauce().getBuild());
+        assertEquals(Integer.valueOf(2), sauceOptions.sauce().getCommandTimeout());
+        assertEquals(customData, sauceOptions.sauce().getCustomData());
+        assertEquals(Integer.valueOf(20), sauceOptions.sauce().getIdleTimeout());
+        assertEquals(Integer.valueOf(300), sauceOptions.sauce().getMaxDuration());
+        assertEquals("Test name", sauceOptions.sauce().getName());
+        assertEquals("Mommy", sauceOptions.sauce().getParentTunnel());
+        assertEquals(prerun, sauceOptions.sauce().getPrerun());
+        assertEquals(Integer.valueOf(0), sauceOptions.sauce().getPriority());
+        assertEquals(JobVisibility.TEAM, sauceOptions.sauce().getJobVisibility());
+        assertEquals(false, sauceOptions.sauce().getRecordLogs());
+        assertEquals(false, sauceOptions.sauce().getRecordScreenshots());
+        assertEquals(false, sauceOptions.sauce().getRecordVideo());
+        assertEquals("3.141.0", sauceOptions.sauce().getSeleniumVersion());
+        assertEquals("1024x768", sauceOptions.sauce().getScreenResolution());
+        assertEquals(tags, sauceOptions.sauce().getTags());
+        assertEquals("San Francisco", sauceOptions.sauce().getTimeZone());
+        assertEquals("tunnelname", sauceOptions.sauce().getTunnelIdentifier());
+        assertEquals(false, sauceOptions.sauce().getVideoUploadOnPass());
+    }
+}
diff --git a/java/src/test/java/com/saucelabs/saucebindings/options/SafariConfigurationsTest.java b/java/src/test/java/com/saucelabs/saucebindings/options/SafariConfigurationsTest.java
new file mode 100644
index 00000000..3a27f758
--- /dev/null
+++ b/java/src/test/java/com/saucelabs/saucebindings/options/SafariConfigurationsTest.java
@@ -0,0 +1,144 @@
+package com.saucelabs.saucebindings.options;
+
+import com.saucelabs.saucebindings.*;
+import org.junit.Assert;
+import org.junit.Test;
+import org.openqa.selenium.UnexpectedAlertBehaviour;
+import org.openqa.selenium.safari.SafariOptions;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class SafariConfigurationsTest {
+
+    @Test
+    public void buildsDefaultSauceOptions() {
+        SauceOptions sauceOptions = SauceOptions.safari().build();
+
+        Assert.assertEquals(Browser.SAFARI, sauceOptions.getBrowserName());
+        Assert.assertEquals(SaucePlatform.MAC_CATALINA, sauceOptions.getPlatformName());
+        assertEquals("latest", sauceOptions.getBrowserVersion());
+    }
+
+    @Test
+    public void acceptsSafariOptionsClass() {
+        SafariOptions safariOptions = new SafariOptions();
+        safariOptions.setAutomaticProfiling(true);
+        safariOptions.setCapability("UnexpectedAlertBehaviour", UnexpectedAlertBehaviour.DISMISS);
+
+        SauceOptions sauceOptions = SauceOptions.safari(safariOptions).build();
+
+        assertEquals(Browser.SAFARI, sauceOptions.getBrowserName());
+        assertEquals(safariOptions, sauceOptions.getCapabilities());
+    }
+
+    @Test
+    public void fluentOrderDoesNotMatter() {
+        // Setting these in order from superclass methods to subclass to ensure proper return instances
+        SauceOptions sauceOptions = SauceOptions.safari()
+                .setPlatformName(SaucePlatform.MAC_HIGH_SIERRA)
+                .setBrowserVersion("12")
+                .setAvoidProxy()
+                .build();
+
+        assertEquals(SaucePlatform.MAC_HIGH_SIERRA, sauceOptions.getPlatformName());
+        assertEquals("12", sauceOptions.getBrowserVersion());
+        assertTrue(sauceOptions.sauce().getAvoidProxy());
+    }
+
+    @Test
+    public void acceptsOtherW3CValues() {
+        SauceOptions sauceOptions = SauceOptions.safari()
+                .setAcceptInsecureCerts()
+                .setPageLoadStrategy(PageLoadStrategy.EAGER)
+                .setUnhandledPromptBehavior(UnhandledPromptBehavior.IGNORE)
+                .setStrictFileInteractability()
+                .setImplicitWaitTimeout(Duration.ofSeconds(1))
+                .setPageLoadTimeout(Duration.ofSeconds(100))
+                .setScriptTimeout(Duration.ofSeconds(10))
+                .build();
+
+        Map<Timeouts, Integer> timeouts = new HashMap<>();
+        timeouts.put(Timeouts.IMPLICIT, 1);
+        timeouts.put(Timeouts.PAGE_LOAD, 100);
+        timeouts.put(Timeouts.SCRIPT, 10);
+
+        assertEquals(true, sauceOptions.getAcceptInsecureCerts());
+        assertEquals(PageLoadStrategy.EAGER, sauceOptions.getPageLoadStrategy());
+        assertEquals(UnhandledPromptBehavior.IGNORE, sauceOptions.getUnhandledPromptBehavior());
+        assertEquals(true, sauceOptions.getStrictFileInteractability());
+        assertEquals(timeouts, sauceOptions.getTimeouts());
+    }
+
+    @Test
+    public void acceptsSauceLabsSettings() {
+        Map<String, Object> customData = new HashMap<>();
+        customData.put("foo", "foo");
+        customData.put("bar", "bar");
+
+        List<String> args = new ArrayList<>();
+        args.add("--silent");
+        args.add("-a");
+        args.add("-q");
+
+        Map<Prerun, Object> prerun = new HashMap<>();
+        prerun.put(Prerun.EXECUTABLE, "http://url.to/your/executable.exe");
+        prerun.put(Prerun.ARGS, args);
+        prerun.put(Prerun.BACKGROUND, false);
+        prerun.put(Prerun.TIMEOUT, 120);
+
+        List<String> tags = new ArrayList<>();
+        tags.add("Foo");
+        tags.add("Bar");
+        tags.add("Foobar");
+
+        SauceOptions sauceOptions = SauceOptions.safari()
+                .setAvoidProxy()
+                .setBuild("Sample Build Name")
+                .setName("Test name")
+                .setCommandTimeout(Duration.ofSeconds(2))
+                .setCustomData(customData)
+                .setIdleTimeout(Duration.ofSeconds(20))
+                .setMaxDuration(Duration.ofSeconds(300))
+                .setParentTunnel("Mommy")
+                .setPrerun(prerun)
+                .setPriority(0)
+                .setJobVisibility(JobVisibility.TEAM)
+                .setSeleniumVersion("3.141.0")
+                .disableRecordLogs()
+                .disableRecordScreenshots()
+                .disableRecordVideo()
+                .setScreenResolution("1024x768")
+                .setTags(tags)
+                .setTimeZone("San Francisco")
+                .setTunnelIdentifier("tunnelname")
+                .disableVideoUploadOnPass()
+                .build();
+
+        assertEquals("Sample Build Name", sauceOptions.sauce().getBuild());
+        assertEquals(Integer.valueOf(2), sauceOptions.sauce().getCommandTimeout());
+        assertEquals(customData, sauceOptions.sauce().getCustomData());
+        assertEquals(Integer.valueOf(20), sauceOptions.sauce().getIdleTimeout());
+        assertEquals(Integer.valueOf(300), sauceOptions.sauce().getMaxDuration());
+        assertEquals("Test name", sauceOptions.sauce().getName());
+        assertEquals("Mommy", sauceOptions.sauce().getParentTunnel());
+        assertEquals(prerun, sauceOptions.sauce().getPrerun());
+        assertEquals(Integer.valueOf(0), sauceOptions.sauce().getPriority());
+        assertEquals(JobVisibility.TEAM, sauceOptions.sauce().getJobVisibility());
+        assertEquals(false, sauceOptions.sauce().getRecordLogs());
+        assertEquals(false, sauceOptions.sauce().getRecordScreenshots());
+        assertEquals(false, sauceOptions.sauce().getRecordVideo());
+        assertEquals("3.141.0", sauceOptions.sauce().getSeleniumVersion());
+        assertEquals("1024x768", sauceOptions.sauce().getScreenResolution());
+        assertEquals(tags, sauceOptions.sauce().getTags());
+        assertEquals("San Francisco", sauceOptions.sauce().getTimeZone());
+        assertEquals("tunnelname", sauceOptions.sauce().getTunnelIdentifier());
+        assertEquals(false, sauceOptions.sauce().getVideoUploadOnPass());
+    }
+}
diff --git a/java/src/test/java/com/saucelabs/saucebindings/options/SauceOptionsTest.java b/java/src/test/java/com/saucelabs/saucebindings/options/SauceOptionsTest.java
new file mode 100644
index 00000000..ecbd2929
--- /dev/null
+++ b/java/src/test/java/com/saucelabs/saucebindings/options/SauceOptionsTest.java
@@ -0,0 +1,485 @@
+package com.saucelabs.saucebindings.options;
+
+import com.saucelabs.saucebindings.*;
+import lombok.SneakyThrows;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.openqa.selenium.MutableCapabilities;
+import org.openqa.selenium.firefox.FirefoxOptions;
+import org.yaml.snakeyaml.Yaml;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.openqa.selenium.UnexpectedAlertBehaviour.DISMISS;
+
+public class SauceOptionsTest {
+    private SauceOptions sauceOptions = new SauceOptions();
+
+    @Rule
+    public MockitoRule initRule = MockitoJUnit.rule();
+
+    @Test
+    public void usesLatestChromeWindowsVersionsByDefault() {
+        Assert.assertEquals(Browser.CHROME, sauceOptions.getBrowserName());
+        Assert.assertEquals(SaucePlatform.WINDOWS_10, sauceOptions.getPlatformName());
+        assertEquals("latest", sauceOptions.getBrowserVersion());
+    }
+
+    @Test
+    public void updatesBrowserBrowserVersionPlatformVersionValues() {
+        sauceOptions.setBrowserName(Browser.FIREFOX);
+        sauceOptions.setPlatformName(SaucePlatform.MAC_HIGH_SIERRA);
+        sauceOptions.setBrowserVersion("68");
+
+        assertEquals(Browser.FIREFOX, sauceOptions.getBrowserName());
+        assertEquals("68", sauceOptions.getBrowserVersion());
+        assertEquals(SaucePlatform.MAC_HIGH_SIERRA, sauceOptions.getPlatformName());
+    }
+
+    @Test
+    public void fluentPatternWorks() {
+        sauceOptions.setBrowserName(Browser.FIREFOX)
+                .setBrowserVersion("68")
+                .setPlatformName(SaucePlatform.MAC_HIGH_SIERRA);
+
+        assertEquals(Browser.FIREFOX, sauceOptions.getBrowserName());
+        assertEquals("68", sauceOptions.getBrowserVersion());
+        assertEquals(SaucePlatform.MAC_HIGH_SIERRA, sauceOptions.getPlatformName());
+    }
+
+    @Test
+    public void acceptsOtherW3CValues() {
+        sauceOptions.setAcceptInsecureCerts(true);
+        sauceOptions.setPageLoadStrategy(PageLoadStrategy.EAGER);
+        sauceOptions.setSetWindowRect(true);
+        sauceOptions.setUnhandledPromptBehavior(UnhandledPromptBehavior.IGNORE);
+        sauceOptions.setStrictFileInteractability(true);
+
+        sauceOptions.timeout.setImplicitWait(1);
+        sauceOptions.timeout.setPageLoad(100);
+        sauceOptions.timeout.setScript(10);
+
+        Map<Timeouts, Integer> timeouts = new HashMap<>();
+        timeouts.put(Timeouts.IMPLICIT, 1);
+        timeouts.put(Timeouts.PAGE_LOAD, 100);
+        timeouts.put(Timeouts.SCRIPT, 10);
+
+        assertEquals(true, sauceOptions.getAcceptInsecureCerts());
+        assertEquals(PageLoadStrategy.EAGER, sauceOptions.getPageLoadStrategy());
+        assertEquals(true, sauceOptions.getSetWindowRect());
+        assertEquals(UnhandledPromptBehavior.IGNORE, sauceOptions.getUnhandledPromptBehavior());
+        assertEquals(true, sauceOptions.getStrictFileInteractability());
+        assertEquals(timeouts, sauceOptions.getTimeouts());
+    }
+
+    @Test
+    public void acceptsSauceLabsSettings() {
+        Map<String, Object> customData = new HashMap<>();
+        customData.put("foo", "foo");
+        customData.put("bar", "bar");
+
+        List<String> args = new ArrayList<>();
+        args.add("--silent");
+        args.add("-a");
+        args.add("-q");
+
+        Map<Prerun, Object> prerun = new HashMap<>();
+        prerun.put(Prerun.EXECUTABLE, "http://url.to/your/executable.exe");
+        prerun.put(Prerun.ARGS, args);
+        prerun.put(Prerun.BACKGROUND, false);
+        prerun.put(Prerun.TIMEOUT, 120);
+
+        List<String> tags = new ArrayList<>();
+        tags.add("Foo");
+        tags.add("Bar");
+        tags.add("Foobar");
+
+        sauceOptions.sauce().setAvoidProxy(true);
+        sauceOptions.sauce().setBuild("Sample Build Name");
+        sauceOptions.sauce().setCapturePerformance(true);
+        sauceOptions.sauce().setChromedriverVersion("71");
+        sauceOptions.sauce().setCommandTimeout(2);
+        sauceOptions.sauce().setCustomData(customData);
+        sauceOptions.sauce().setExtendedDebugging(true);
+        sauceOptions.sauce().setIdleTimeout(3);
+        sauceOptions.sauce().setIedriverVersion("3.141.0");
+        sauceOptions.sauce().setMaxDuration(300);
+        sauceOptions.sauce().setName("Test name");
+        sauceOptions.sauce().setParentTunnel("Mommy");
+        sauceOptions.sauce().setPrerun(prerun);
+        sauceOptions.sauce().setPriority(0);
+        sauceOptions.sauce().setJobVisibility(JobVisibility.TEAM);
+        sauceOptions.sauce().setRecordLogs(false);
+        sauceOptions.sauce().setRecordScreenshots(false);
+        sauceOptions.sauce().setRecordVideo(false);
+        sauceOptions.sauce().setScreenResolution("10x10");
+        sauceOptions.sauce().setSeleniumVersion("3.141.59");
+        sauceOptions.sauce().setTags(tags);
+        sauceOptions.sauce().setTimeZone("San Francisco");
+        sauceOptions.sauce().setTunnelIdentifier("tunnelname");
+        sauceOptions.sauce().setVideoUploadOnPass(false);
+
+        assertEquals(true, sauceOptions.sauce().getAvoidProxy());
+        assertEquals("Sample Build Name", sauceOptions.sauce().getBuild());
+        assertEquals(true, sauceOptions.sauce().getCapturePerformance());
+        assertEquals("71", sauceOptions.sauce().getChromedriverVersion());
+        assertEquals(Integer.valueOf(2), sauceOptions.sauce().getCommandTimeout());
+        assertEquals(customData, sauceOptions.sauce().getCustomData());
+        assertEquals(true, sauceOptions.sauce().getExtendedDebugging());
+        assertEquals(Integer.valueOf(3), sauceOptions.sauce().getIdleTimeout());
+        assertEquals("3.141.0", sauceOptions.sauce().getIedriverVersion());
+        assertEquals(Integer.valueOf(300), sauceOptions.sauce().getMaxDuration());
+        assertEquals("Test name", sauceOptions.sauce().getName());
+        assertEquals("Mommy", sauceOptions.sauce().getParentTunnel());
+        assertEquals(prerun, sauceOptions.sauce().getPrerun());
+        assertEquals(Integer.valueOf(0), sauceOptions.sauce().getPriority());
+        assertEquals(JobVisibility.TEAM, sauceOptions.sauce().getJobVisibility());
+        assertEquals(false, sauceOptions.sauce().getRecordLogs());
+        assertEquals(false, sauceOptions.sauce().getRecordScreenshots());
+        assertEquals(false, sauceOptions.sauce().getRecordVideo());
+        assertEquals("10x10", sauceOptions.sauce().getScreenResolution());
+        assertEquals("3.141.59", sauceOptions.sauce().getSeleniumVersion());
+        assertEquals(tags, sauceOptions.sauce().getTags());
+        assertEquals("San Francisco", sauceOptions.sauce().getTimeZone());
+        assertEquals("tunnelname", sauceOptions.sauce().getTunnelIdentifier());
+        assertEquals(false, sauceOptions.sauce().getVideoUploadOnPass());
+    }
+
+    @Test
+    public void createsDefaultBuildName() {
+        assertNotNull(sauceOptions.sauce().getBuild());
+    }
+
+    @SneakyThrows
+    public Map<String, Object> serialize(String key) {
+        InputStream input = new FileInputStream(new File("src/test/java/com/saucelabs/saucebindings/options.yml"));
+        Yaml yaml = new Yaml();
+        Map<String, Object> data = yaml.load(input);
+        return (Map<String, Object>) data.get(key);
+    }
+
+    @Test
+    public void setsCapabilitiesFromMap() {
+        Map<String, Object> map = serialize("exampleValues");
+
+        sauceOptions.mergeCapabilities(map);
+
+        Map<String, Object> customData = new HashMap<>();
+        customData.put("foo", "foo");
+        customData.put("bar", "bar");
+
+        List<String> args = new ArrayList<>();
+        args.add("--silent");
+        args.add("-a");
+        args.add("-q");
+
+        Map<Prerun, Object> prerun = new HashMap<>();
+        prerun.put(Prerun.EXECUTABLE, "http://url.to/your/executable.exe");
+        prerun.put(Prerun.ARGS, args);
+        prerun.put(Prerun.BACKGROUND, false);
+        prerun.put(Prerun.TIMEOUT, 120);
+
+        List<String> tags = new ArrayList<>();
+        tags.add("foo");
+        tags.add("bar");
+        tags.add("foobar");
+
+        Map<Timeouts, Integer> timeouts = new HashMap<>();
+        timeouts.put(Timeouts.IMPLICIT, 1);
+        timeouts.put(Timeouts.PAGE_LOAD, 59);
+        timeouts.put(Timeouts.SCRIPT, 29);
+
+        assertEquals(Browser.FIREFOX, sauceOptions.getBrowserName());
+        assertEquals("68", sauceOptions.getBrowserVersion());
+        assertEquals(SaucePlatform.MAC_HIGH_SIERRA, sauceOptions.getPlatformName());
+        assertEquals(true, sauceOptions.getAcceptInsecureCerts());
+        assertEquals(PageLoadStrategy.EAGER, sauceOptions.getPageLoadStrategy());
+        assertEquals(true, sauceOptions.getSetWindowRect());
+        assertEquals(UnhandledPromptBehavior.ACCEPT, sauceOptions.getUnhandledPromptBehavior());
+        assertEquals(true, sauceOptions.getStrictFileInteractability());
+        assertEquals(timeouts, sauceOptions.getTimeouts());
+        assertEquals(true, sauceOptions.sauce().getAvoidProxy());
+        assertEquals("Sample Build Name", sauceOptions.sauce().getBuild());
+        assertEquals(true, sauceOptions.sauce().getCapturePerformance());
+        assertEquals("71", sauceOptions.sauce().getChromedriverVersion());
+        assertEquals(Integer.valueOf(2), sauceOptions.sauce().getCommandTimeout());
+        assertEquals(customData, sauceOptions.sauce().getCustomData());
+        assertEquals(true, sauceOptions.sauce().getExtendedDebugging());
+        assertEquals(Integer.valueOf(3), sauceOptions.sauce().getIdleTimeout());
+        assertEquals("3.141.0", sauceOptions.sauce().getIedriverVersion());
+        assertEquals(Integer.valueOf(300), sauceOptions.sauce().getMaxDuration());
+        assertEquals("Sample Test Name", sauceOptions.sauce().getName());
+        assertEquals("Mommy", sauceOptions.sauce().getParentTunnel());
+        assertEquals(prerun, sauceOptions.sauce().getPrerun());
+        assertEquals(Integer.valueOf(0), sauceOptions.sauce().getPriority());
+        assertEquals(JobVisibility.TEAM, sauceOptions.sauce().getJobVisibility());
+        assertEquals(false, sauceOptions.sauce().getRecordLogs());
+        assertEquals(false, sauceOptions.sauce().getRecordScreenshots());
+        assertEquals(false, sauceOptions.sauce().getRecordVideo());
+        assertEquals("10x10", sauceOptions.sauce().getScreenResolution());
+        assertEquals("3.141.59", sauceOptions.sauce().getSeleniumVersion());
+        assertEquals(tags, sauceOptions.sauce().getTags());
+        assertEquals("San Francisco", sauceOptions.sauce().getTimeZone());
+        assertEquals("tunnelname", sauceOptions.sauce().getTunnelIdentifier());
+        assertEquals(false, sauceOptions.sauce().getVideoUploadOnPass());
+    }
+
+    @Test(expected = InvalidSauceOptionsArgumentException.class)
+    public void setsBadBrowserFromMap()  {
+        Map<String, Object> map = serialize("badBrowser");
+        sauceOptions.mergeCapabilities(map);
+    }
+
+    @Test(expected = InvalidSauceOptionsArgumentException.class)
+    public void setsBadPlatformFromMap() {
+        Map<String, Object> map = serialize("badPlatform");
+        sauceOptions.mergeCapabilities(map);
+    }
+
+    @Test(expected = InvalidSauceOptionsArgumentException.class)
+    public void setsBadJobVisibilityFromMap() {
+        Map<String, Object> map = serialize("badJobVisibility");
+        sauceOptions.mergeCapabilities(map);
+    }
+
+    @Test(expected = InvalidSauceOptionsArgumentException.class)
+    public void setsBadPromptFromMap() {
+        Map<String, Object> map = serialize("badPrompt");
+        sauceOptions.mergeCapabilities(map);
+    }
+
+    @Test(expected = InvalidSauceOptionsArgumentException.class)
+    public void setsBadTimeoutFromMap() {
+        Map<String, Object> map = serialize("badTimeout");
+        sauceOptions.mergeCapabilities(map);
+    }
+
+    @Test(expected = InvalidSauceOptionsArgumentException.class)
+    public void setsBadPrerunFromMap() {
+        Map<String, Object> map = serialize("badPrerun");
+        sauceOptions.mergeCapabilities(map);
+    }
+
+    @Test(expected = InvalidSauceOptionsArgumentException.class)
+    public void setsBadPageLoadFromMap() {
+        Map<String, Object> map = serialize("badPageLoad");
+        sauceOptions.mergeCapabilities(map);
+    }
+
+    @Test
+    public void parsesCapabilitiesFromW3CValues() {
+        sauceOptions.setBrowserName(Browser.FIREFOX);
+        sauceOptions.setPlatformName(SaucePlatform.MAC_BIG_SUR);
+        sauceOptions.setBrowserVersion("77");
+        sauceOptions.setAcceptInsecureCerts(true);
+        sauceOptions.setPageLoadStrategy(PageLoadStrategy.EAGER);
+        sauceOptions.setSetWindowRect(true);
+        sauceOptions.setUnhandledPromptBehavior(UnhandledPromptBehavior.IGNORE);
+        sauceOptions.setStrictFileInteractability(true);
+        sauceOptions.timeout.setImplicitWait(1);
+        sauceOptions.timeout.setPageLoad(100);
+        sauceOptions.timeout.setScript(10);
+        sauceOptions.sauce().setBuild("Build Name");
+
+        Map<Timeouts, Integer> timeouts = new HashMap<>();
+        timeouts.put(Timeouts.IMPLICIT, 1);
+        timeouts.put(Timeouts.SCRIPT, 10);
+        timeouts.put(Timeouts.PAGE_LOAD, 100);
+
+        MutableCapabilities expectedCapabilities = new MutableCapabilities();
+        expectedCapabilities.setCapability("browserName", "firefox");
+        expectedCapabilities.setCapability("browserVersion", "77");
+        expectedCapabilities.setCapability("platformName", "macOS 11.00");
+        expectedCapabilities.setCapability("acceptInsecureCerts", true);
+        expectedCapabilities.setCapability("setWindowRect", true);
+        expectedCapabilities.setCapability("strictFileInteractability", true);
+        expectedCapabilities.setCapability("pageLoadStrategy", PageLoadStrategy.EAGER);
+        expectedCapabilities.setCapability("timeouts", timeouts);
+        expectedCapabilities.setCapability("unhandledPromptBehavior", UnhandledPromptBehavior.IGNORE);
+
+        MutableCapabilities sauceCapabilities = new MutableCapabilities();
+        sauceCapabilities.setCapability("build", "Build Name");
+        sauceCapabilities.setCapability("username", SystemManager.get("SAUCE_USERNAME"));
+        sauceCapabilities.setCapability("accessKey", SystemManager.get("SAUCE_ACCESS_KEY"));
+        expectedCapabilities.setCapability("sauce:options", sauceCapabilities);
+        MutableCapabilities actualCapabilities = sauceOptions.toCapabilities();
+
+        assertEquals(expectedCapabilities.asMap().toString(), actualCapabilities.asMap().toString());
+    }
+
+    @Test
+    public void parsesCapabilitiesFromSauceValues() {
+        Map<String, Object> customData = new HashMap<>();
+        customData.put("foo", "foo");
+        customData.put("bar", "bar");
+
+        List<String> args = new ArrayList<>();
+        args.add("--silent");
+        args.add("-a");
+        args.add("-q");
+
+        Map<Prerun, Object> prerun = new HashMap<>();
+        prerun.put(Prerun.EXECUTABLE, "http://url.to/your/executable.exe");
+        prerun.put(Prerun.ARGS, args);
+        prerun.put(Prerun.BACKGROUND, false);
+        prerun.put(Prerun.TIMEOUT, 120);
+
+        List<String> tags = new ArrayList<>();
+        tags.add("Foo");
+        tags.add("Bar");
+        tags.add("Foobar");
+
+        sauceOptions.sauce().setAvoidProxy(true);
+        sauceOptions.sauce().setBuild("Sample Build Name");
+        sauceOptions.sauce().setCapturePerformance(true);
+        sauceOptions.sauce().setChromedriverVersion("71");
+        sauceOptions.sauce().setCommandTimeout(2);
+        sauceOptions.sauce().setCustomData(customData);
+        sauceOptions.sauce().setExtendedDebugging(true);
+        sauceOptions.sauce().setIdleTimeout(3);
+        sauceOptions.sauce().setIedriverVersion("3.141.0");
+        sauceOptions.sauce().setMaxDuration(300);
+        sauceOptions.sauce().setName("Test name");
+        sauceOptions.sauce().setParentTunnel("Mommy");
+        sauceOptions.sauce().setPrerun(prerun);
+        sauceOptions.sauce().setPriority(0);
+        sauceOptions.sauce().setJobVisibility(JobVisibility.TEAM);
+        sauceOptions.sauce().setRecordLogs(false);
+        sauceOptions.sauce().setRecordScreenshots(false);
+        sauceOptions.sauce().setRecordVideo(false);
+        sauceOptions.sauce().setScreenResolution("10x10");
+        sauceOptions.sauce().setSeleniumVersion("3.141.59");
+        sauceOptions.sauce().setTags(tags);
+        sauceOptions.sauce().setTimeZone("San Francisco");
+        sauceOptions.sauce().setTunnelIdentifier("tunnelname");
+        sauceOptions.sauce().setVideoUploadOnPass(false);
+
+        MutableCapabilities sauceCapabilities = new MutableCapabilities();
+        sauceCapabilities.setCapability("avoidProxy", true);
+        sauceCapabilities.setCapability("build", "Sample Build Name");
+        sauceCapabilities.setCapability("capturePerformance", true);
+        sauceCapabilities.setCapability("chromedriverVersion", "71");
+        sauceCapabilities.setCapability("commandTimeout", 2);
+        sauceCapabilities.setCapability("customData", customData);
+        sauceCapabilities.setCapability("extendedDebugging", true);
+        sauceCapabilities.setCapability("idleTimeout", 3);
+        sauceCapabilities.setCapability("iedriverVersion", "3.141.0");
+        sauceCapabilities.setCapability("maxDuration", 300);
+        sauceCapabilities.setCapability("name", "Test name");
+        sauceCapabilities.setCapability("parentTunnel", "Mommy");
+        sauceCapabilities.setCapability("prerun", prerun);
+        sauceCapabilities.setCapability("priority", 0);
+        sauceCapabilities.setCapability("public", "team");
+        sauceCapabilities.setCapability("recordLogs", false);
+        sauceCapabilities.setCapability("recordScreenshots", false);
+        sauceCapabilities.setCapability("recordVideo", false);
+        sauceCapabilities.setCapability("screenResolution", "10x10");
+        sauceCapabilities.setCapability("seleniumVersion", "3.141.59");
+        sauceCapabilities.setCapability("tags", tags);
+        sauceCapabilities.setCapability("timeZone", "San Francisco");
+        sauceCapabilities.setCapability("tunnelIdentifier", "tunnelname");
+        sauceCapabilities.setCapability("videoUploadOnPass", false);
+        sauceCapabilities.setCapability("username", SystemManager.get("SAUCE_USERNAME"));
+        sauceCapabilities.setCapability("accessKey", SystemManager.get("SAUCE_ACCESS_KEY"));
+
+        MutableCapabilities expectedCapabilities = new MutableCapabilities();
+        expectedCapabilities.setCapability("browserName", "chrome");
+        expectedCapabilities.setCapability("browserVersion", "latest");
+        expectedCapabilities.setCapability("platformName", "Windows 10");
+
+        expectedCapabilities.setCapability("sauce:options", sauceCapabilities);
+        MutableCapabilities actualCapabilities = sauceOptions.toCapabilities();
+
+        // toString() serializes the enums
+        assertEquals(expectedCapabilities.asMap().toString(), actualCapabilities.asMap().toString());
+    }
+
+    @Test
+    public void parsesCapabilitiesFromSeleniumValues() {
+        FirefoxOptions firefoxOptions = new FirefoxOptions();
+        firefoxOptions.addArguments("--foo");
+        firefoxOptions.addPreference("foo", "bar");
+        firefoxOptions.setUnhandledPromptBehaviour(DISMISS);
+
+        sauceOptions = SauceOptions.firefox(firefoxOptions).build();
+        sauceOptions.sauce().setBuild("Build Name");
+
+        MutableCapabilities expectedCapabilities = new MutableCapabilities();
+        expectedCapabilities.setCapability("browserName", "firefox");
+        expectedCapabilities.setCapability("browserVersion", "latest");
+        expectedCapabilities.setCapability("platformName", "Windows 10");
+        expectedCapabilities.merge(firefoxOptions);
+
+        MutableCapabilities sauceCapabilities = new MutableCapabilities();
+        sauceCapabilities.setCapability("build", "Build Name");
+        sauceCapabilities.setCapability("username", SystemManager.get("SAUCE_USERNAME"));
+        sauceCapabilities.setCapability("accessKey", SystemManager.get("SAUCE_ACCESS_KEY"));
+        expectedCapabilities.setCapability("sauce:options", sauceCapabilities);
+        MutableCapabilities actualCapabilities = sauceOptions.toCapabilities();
+
+        assertEquals(expectedCapabilities.asMap().toString(), actualCapabilities.asMap().toString());
+    }
+
+    @Test
+    public void parsesW3CAndSauceAndSeleniumSettings() {
+        MutableCapabilities expectedCapabilities = new MutableCapabilities();
+        MutableCapabilities sauceCapabilities = new MutableCapabilities();
+
+        FirefoxOptions firefoxOptions = new FirefoxOptions();
+        firefoxOptions.addArguments("--foo");
+        firefoxOptions.setUnhandledPromptBehaviour(DISMISS);
+
+        sauceOptions = SauceOptions.firefox(firefoxOptions).build();
+
+        expectedCapabilities.merge(firefoxOptions);
+        expectedCapabilities.setCapability("browserVersion", "latest");
+        expectedCapabilities.setCapability("platformName", "Windows 10");
+        expectedCapabilities.setCapability("acceptInsecureCerts", true);
+
+        sauceOptions.sauce().setBuild("CUSTOM BUILD: 12");
+        sauceCapabilities.setCapability("build", "CUSTOM BUILD: 12");
+
+        sauceOptions.setPageLoadStrategy(PageLoadStrategy.EAGER);
+        expectedCapabilities.setCapability("pageLoadStrategy", PageLoadStrategy.EAGER);
+        sauceOptions.setAcceptInsecureCerts(true);
+        expectedCapabilities.setCapability("acceptInsecureCerts", true);
+        sauceOptions.timeout.setImplicitWait(1)
+                .setPageLoad(100)
+                .setScript(10);
+        Map<Timeouts, Integer> timeouts = new HashMap<>();
+        timeouts.put(Timeouts.IMPLICIT, 1);
+        timeouts.put(Timeouts.SCRIPT, 10);
+        timeouts.put(Timeouts.PAGE_LOAD, 100);
+        expectedCapabilities.setCapability("timeouts", timeouts);
+        sauceOptions.setUnhandledPromptBehavior(UnhandledPromptBehavior.IGNORE);
+        expectedCapabilities.setCapability("unhandledPromptBehavior", UnhandledPromptBehavior.IGNORE);
+
+        sauceOptions.sauce().setExtendedDebugging(true);
+        sauceCapabilities.setCapability("extendedDebugging", true);
+        sauceOptions.sauce().setName("Test name");
+        sauceCapabilities.setCapability("name", "Test name");
+        sauceOptions.sauce().setParentTunnel("Mommy");
+        sauceCapabilities.setCapability("parentTunnel", "Mommy");
+
+        sauceOptions.sauce().setJobVisibility(JobVisibility.SHARE);
+        sauceCapabilities.setCapability("public", JobVisibility.SHARE);
+        sauceCapabilities.setCapability("username", SystemManager.get("SAUCE_USERNAME"));
+        sauceCapabilities.setCapability("accessKey", SystemManager.get("SAUCE_ACCESS_KEY"));
+
+        expectedCapabilities.setCapability("sauce:options", sauceCapabilities);
+        MutableCapabilities actualCapabilities = sauceOptions.toCapabilities();
+
+        assertEquals(expectedCapabilities.asMap().toString(), actualCapabilities.asMap().toString());
+    }
+}