diff --git a/.github/workflows/pre-prod-release.yml b/.github/workflows/pre-prod-release.yml new file mode 100644 index 0000000..4e671a5 --- /dev/null +++ b/.github/workflows/pre-prod-release.yml @@ -0,0 +1,41 @@ +name: Pre-Prod Release + +on: + push: + branches: + - pre-prod + +jobs: + build-and-release: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK 11 + uses: actions/setup-java@v4 + with: + java-version: '11' + distribution: 'temurin' + cache: maven + + - name: Build with Maven + run: mvn clean package + + - name: Upload main JAR artifact + uses: actions/upload-artifact@v4 + with: + name: lambdatest-java-selenium-sdk + path: target/lambdatest-java-selenium-sdk-*.jar + retention-days: 90 + if-no-files-found: error + + - name: Upload agent JAR artifact + uses: actions/upload-artifact@v4 + with: + name: lambdatest-java-selenium-sdk-agent + path: target/lambdatest-java-selenium-sdk-*-agent.jar + retention-days: 90 + if-no-files-found: error + diff --git a/.gitignore b/.gitignore index de02ca2..cc4e4ee 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ MAVEN_CENTRAL* *.tmp *.bak *~ -gradle.properties \ No newline at end of file +gradle.properties +.gradle/ \ No newline at end of file diff --git a/.gradle/8.10/fileHashes/fileHashes.bin b/.gradle/8.10/fileHashes/fileHashes.bin deleted file mode 100644 index f9c9626..0000000 Binary files a/.gradle/8.10/fileHashes/fileHashes.bin and /dev/null differ diff --git a/.gradle/8.10/fileHashes/fileHashes.lock b/.gradle/8.10/fileHashes/fileHashes.lock deleted file mode 100644 index dfce57a..0000000 Binary files a/.gradle/8.10/fileHashes/fileHashes.lock and /dev/null differ diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index 180aea5..ab753ef 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -4,7 +4,7 @@ com.lambdatest lambdatest-java-selenium-sdk LambdaTest Selenium SDK - 1.0.1 + 1.0.2 A Java SDK for integrating Selenium tests with LambdaTest cloud platform https://www.lambdatest.com diff --git a/pom.xml b/pom.xml index 6088200..c266158 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.lambdatest lambdatest-java-selenium-sdk - 1.0.1 + 1.0.2 jar LambdaTest Selenium SDK diff --git a/src/main/java/com/lambdatest/selenium/agent/RemoteWebDriverAdvice.java b/src/main/java/com/lambdatest/selenium/agent/RemoteWebDriverAdvice.java index 688cff1..45bfcd3 100644 --- a/src/main/java/com/lambdatest/selenium/agent/RemoteWebDriverAdvice.java +++ b/src/main/java/com/lambdatest/selenium/agent/RemoteWebDriverAdvice.java @@ -1,6 +1,7 @@ package com.lambdatest.selenium.agent; import java.util.Map; +import java.util.Set; import org.openqa.selenium.MutableCapabilities; @@ -25,11 +26,23 @@ public class RemoteWebDriverAdvice { // ThreadLocal re-entrance guard to prevent multiple enhancement calls in the same thread private static final ThreadLocal isEnhancing = ThreadLocal.withInitial(() -> false); - // Flag to warn only once about missing tunnel (avoid spam in parallel execution) - private static volatile boolean warnedAboutMissingTunnel = false; - // Session-thread manager for validating thread affinity private static final SessionThreadManager sessionThreadManager = SessionThreadManager.getInstance(); + + // LambdaTest-specific keys that should ONLY be in lt:options, not at W3C top level + // These keys cause W3C validation errors in Selenium 4 if set at top level + private static final Set LT_SPECIFIC_KEYS = Set.of( + "build", "name", "projectName", "resolution", + "buildTags", "driver_version", "tags" + ); + + // Selenium 3 capabilities that should be moved to lt:options when using Selenium 4 + // These are legacy capabilities that Selenium 4 rejects at top level + private static final Set SELENIUM3_LEGACY_KEYS = Set.of( + "version", "commandLog", "systemLog", "network.http2", + "DisableXFHeaders", "network.debug", "ignoreFfOptionsArgs", + "updateBuildStatusOnSuccess", "lambda:loadExtension" + ); /** * Static method to enhance capabilities that can be called from ASM bytecode. @@ -72,61 +85,210 @@ public static void enhanceCapabilities(MutableCapabilities capabilities) { Map sdkCapMap = config.getCapabilitiesFromYaml().asMap(); Map userCapMap = capabilities.asMap(); - // Add missing capabilities from SDK config + // Ensure lt:options exists + Map ltOptions; + String ltOptionsKey = userCapMap.keySet().stream() + .filter(k -> k.equalsIgnoreCase("lt:options")) + .findFirst() + .orElse(null); + if (ltOptionsKey != null) { + ltOptions = (Map) userCapMap.get(ltOptionsKey); + } else { + ltOptions = new java.util.HashMap<>(); + capabilities.setCapability("lt:options", ltOptions); + } + + // Merge SDK lt:options into user lt:options first + if (sdkCapMap.containsKey("lt:options")) { + Map sdkLtOptions = (Map) sdkCapMap.get("lt:options"); + ltOptions.putAll(sdkLtOptions); + } + + // Add missing capabilities from SDK config, but filter out LT-specific keys + // LT-specific keys (build, name, projectName, resolution) should ONLY be in lt:options for (Map.Entry entry : sdkCapMap.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); + // Skip lt:options (already handled above) + if ("lt:options".equals(key)) { + continue; + } + + // Skip LT-specific keys - they should only be in lt:options, not at top level + if (LT_SPECIFIC_KEYS.contains(key)) { + // Ensure they're in lt:options instead + if (!ltOptions.containsKey(key)) { + ltOptions.put(key, value); + } + continue; + } + + // Selenium 3 legacy capabilities should be moved to lt:options when using Selenium 4 + // (Selenium 4 rejects these at top level) + if (SELENIUM3_LEGACY_KEYS.contains(key)) { + // Skip empty string values (e.g., lambda:loadExtension: "") + if (value instanceof String && ((String) value).trim().isEmpty()) { + continue; + } + // Move to lt:options instead of top level + if (!ltOptions.containsKey(key)) { + ltOptions.put(key, value); + } + continue; + } + + // Add valid W3C capabilities to top level if (!userCapMap.containsKey(key)) { capabilities.setCapability(key, value); } } - // Ensure lt:options contains credentials - if (userCapMap.containsKey("lt:options")) { - Map ltOptions = (Map) userCapMap.get("lt:options"); - if (sdkCapMap.containsKey("lt:options")) { - Map sdkLtOptions = (Map) sdkCapMap.get("lt:options"); - ltOptions.putAll(sdkLtOptions); + // Clean up: Remove any LT-specific keys that might have been set at top level + // These should only be in lt:options for W3C compliance + for (String ltKey : LT_SPECIFIC_KEYS) { + if (capabilities.asMap().containsKey(ltKey)) { + Object ltValue = capabilities.getCapability(ltKey); + // Ensure it's in lt:options + if (!ltOptions.containsKey(ltKey)) { + ltOptions.put(ltKey, ltValue); + } + // Remove from top level by setting to null (safer than remove on potentially unmodifiable map) + try { + capabilities.setCapability(ltKey, (Object) null); + } catch (Exception e) { + // If setCapability with null doesn't work, try to remove from map + try { + if (capabilities.asMap() instanceof java.util.Map) { + ((java.util.Map) capabilities.asMap()).remove(ltKey); + } + } catch (Exception e2) { + // Ignore - capability might already be removed or map is unmodifiable + } + } + } + } + + // Clean up: Remove Selenium 3 legacy capabilities from top level (Selenium 4 rejects them) + // Move them to lt:options instead + for (String legacyKey : SELENIUM3_LEGACY_KEYS) { + if (capabilities.asMap().containsKey(legacyKey)) { + Object legacyValue = capabilities.getCapability(legacyKey); + // Skip empty string values + if (legacyValue instanceof String && ((String) legacyValue).trim().isEmpty()) { + // Just remove from top level, don't add to lt:options + try { + capabilities.setCapability(legacyKey, (Object) null); + } catch (Exception e) { + try { + if (capabilities.asMap() instanceof java.util.Map) { + ((java.util.Map) capabilities.asMap()).remove(legacyKey); + } + } catch (Exception e2) { + // Ignore + } + } + continue; + } + // Ensure it's in lt:options + if (!ltOptions.containsKey(legacyKey)) { + ltOptions.put(legacyKey, legacyValue); + } + // Remove from top level + try { + capabilities.setCapability(legacyKey, (Object) null); + } catch (Exception e) { + // If setCapability with null doesn't work, try to remove from map + try { + if (capabilities.asMap() instanceof java.util.Map) { + ((java.util.Map) capabilities.asMap()).remove(legacyKey); + } + } catch (Exception e2) { + // Ignore - capability might already be removed or map is unmodifiable + } + } } + } + + // Handle tunnel BEFORE setting lt:options to ensure tunnel name is included + // Check if tunnel is enabled in capabilities + if (ltOptions.containsKey("tunnel")) { + Object tunnelValue = ltOptions.get("tunnel"); + boolean tunnelInCapabilities = false; - // Check if tunnel is enabled in capabilities - if (ltOptions.containsKey("tunnel")) { - Object tunnelValue = ltOptions.get("tunnel"); - boolean tunnelInCapabilities = false; + if (tunnelValue instanceof Boolean) { + tunnelInCapabilities = (Boolean) tunnelValue; + } else if (tunnelValue instanceof String) { + tunnelInCapabilities = Boolean.parseBoolean((String) tunnelValue); + } - if (tunnelValue instanceof Boolean) { - tunnelInCapabilities = (Boolean) tunnelValue; - } else if (tunnelValue instanceof String) { - tunnelInCapabilities = Boolean.parseBoolean((String) tunnelValue); - } + if (tunnelInCapabilities) { + // Tunnel is enabled - ensure it's running and wait for it to be ready + try { + com.lambdatest.selenium.tunnel.TunnelManager tunnelManager = + com.lambdatest.selenium.tunnel.TunnelManager.getInstance(); - if (tunnelInCapabilities) { - // Tunnel is enabled - check if it's running and add tunnel name - try { - com.lambdatest.selenium.tunnel.TunnelManager tunnelManager = - com.lambdatest.selenium.tunnel.TunnelManager.getInstance(); - - if (tunnelManager.isTunnelRunning()) { - String tunnelName = tunnelManager.getTunnelName(); - if (tunnelName != null && !tunnelName.trim().isEmpty()) { - ltOptions.put("tunnelName", tunnelName); - } - } else { - // Tunnel configured but not running - warn once - if (!warnedAboutMissingTunnel) { - warnedAboutMissingTunnel = true; - System.err.println("[LambdaTest SDK] WARNING: tunnel=true in YAML but no tunnel is running."); - System.err.println("[LambdaTest SDK] Tests will continue without tunnel. This may cause connection issues for local resources."); + // Synchronize to ensure only one thread starts the tunnel + synchronized (RemoteWebDriverAdvice.class) { + if (!tunnelManager.isTunnelRunning()) { + // Tunnel is not running - start it automatically + System.out.println("[LambdaTest SDK] Tunnel is enabled but not running. Starting tunnel automatically..."); + + try { + // Reuse config variable from outer scope + String username = config.getUsername(); + String accessKey = config.getAccessKey(); + + // Get tunnel name from config if available + String tunnelName = null; + if (ltOptions.containsKey("tunnelName")) { + Object tunnelNameObj = ltOptions.get("tunnelName"); + if (tunnelNameObj != null) { + tunnelName = tunnelNameObj.toString(); + } + } + + // Start tunnel and wait for it to be ready + // startTunnel() internally waits up to 90 seconds for tunnel to be ready + tunnelManager.startTunnel(username, accessKey, tunnelName); + + System.out.println("[LambdaTest SDK] Tunnel started successfully and ready for use."); + } catch (com.lambdatest.selenium.tunnel.TunnelManager.TunnelException e) { + System.err.println("[LambdaTest SDK] ERROR: Failed to start tunnel: " + e.getMessage()); + System.err.println("[LambdaTest SDK] Tests will fail without tunnel. Please start tunnel manually or fix configuration."); + throw new RuntimeException("Tunnel startup failed: " + e.getMessage(), e); + } catch (Exception e) { + System.err.println("[LambdaTest SDK] ERROR: Unexpected error starting tunnel: " + e.getMessage()); + e.printStackTrace(); + throw new RuntimeException("Tunnel startup failed: " + e.getMessage(), e); } } - } catch (Exception e) { - System.err.println("[LambdaTest SDK] Warning: Error checking tunnel status: " + e.getMessage()); } + + // Verify tunnel is running (should be true if startTunnel() returned successfully, + // or if another thread already started it) + if (!tunnelManager.isTunnelRunning()) { + throw new RuntimeException("Tunnel is not running. This should not happen if startTunnel() completed successfully."); + } + + // Add tunnel name to capabilities + String tunnelName = tunnelManager.getTunnelName(); + if (tunnelName != null && !tunnelName.trim().isEmpty()) { + ltOptions.put("tunnelName", tunnelName); + } + + } catch (Exception e) { + System.err.println("[LambdaTest SDK] ERROR: Tunnel handling failed: " + e.getMessage()); + e.printStackTrace(); + throw new RuntimeException("Tunnel setup failed: " + e.getMessage(), e); } } } + // Ensure lt:options is updated in capabilities AFTER tunnel handling + // This ensures tunnel name is included in the final capabilities + capabilities.setCapability("lt:options", ltOptions); + } catch (Exception e) { System.err.println("[LambdaTest SDK RemoteWebDriverAdvice] Error enhancing capabilities: " + e.getMessage()); e.printStackTrace(); diff --git a/src/main/java/com/lambdatest/selenium/lambdatest/LambdaTestConfig.java b/src/main/java/com/lambdatest/selenium/lambdatest/LambdaTestConfig.java index 90e36ee..b0a4ffb 100644 --- a/src/main/java/com/lambdatest/selenium/lambdatest/LambdaTestConfig.java +++ b/src/main/java/com/lambdatest/selenium/lambdatest/LambdaTestConfig.java @@ -10,6 +10,11 @@ import org.yaml.snakeyaml.Yaml; import com.lambdatest.selenium.tunnel.TunnelManager; +import com.lambdatest.selenium.lambdatest.capabilities.CapabilityProcessor; +import com.lambdatest.selenium.lambdatest.capabilities.Selenium3Capabilities; +import com.lambdatest.selenium.lambdatest.capabilities.Selenium4Capabilities; +import com.lambdatest.selenium.lambdatest.capabilities.BrowserOptionsCapabilities; +import com.lambdatest.selenium.lambdatest.capabilities.CapabilityKeys; /** * YAML configuration reader for LambdaTest Selenium SDK. @@ -119,10 +124,10 @@ public static void injectCapabilities(MutableCapabilities capabilities) { } // Ensure lt:options contains credentials - if (userCapMap.containsKey("lt:options")) { - Map ltOptions = (Map) userCapMap.get("lt:options"); - if (sdkCapMap.containsKey("lt:options")) { - Map sdkLtOptions = (Map) sdkCapMap.get("lt:options"); + if (userCapMap.containsKey(CapabilityKeys.LT_OPTIONS)) { + Map ltOptions = (Map) userCapMap.get(CapabilityKeys.LT_OPTIONS); + if (sdkCapMap.containsKey(CapabilityKeys.LT_OPTIONS)) { + Map sdkLtOptions = (Map) sdkCapMap.get(CapabilityKeys.LT_OPTIONS); ltOptions.putAll(sdkLtOptions); } } @@ -143,7 +148,8 @@ private void loadConfig() { // Try multiple locations for lambdatest.yml String[] locations = { - "lambdatest.yml", // Root directory + CapabilityKeys.CONFIG_FILE_YML, // Root directory + CapabilityKeys.CONFIG_FILE_YAML }; for (String location : locations) { @@ -151,7 +157,7 @@ private void loadConfig() { InputStream inputStream = null; // First try as file path (for root directory) - if (location.equals("lambdatest.yml") || location.equals("lambdatest.yaml")) { + if (location.equals(CapabilityKeys.CONFIG_FILE_YML) || location.equals(CapabilityKeys.CONFIG_FILE_YAML)) { try { inputStream = new java.io.FileInputStream(location); } catch (java.io.FileNotFoundException e) { @@ -170,7 +176,7 @@ private void loadConfig() { inputStream.close(); // Check if config has "platforms" key (new format) - if (rawConfig != null && rawConfig.containsKey("platforms")) { + if (rawConfig != null && rawConfig.containsKey(CapabilityKeys.PLATFORMS)) { config = processPlatformsConfig(rawConfig); } else { // Use flat format (backwards compatibility) @@ -205,7 +211,7 @@ private void loadConfig() { * Selects platform based on LT_PLATFORM_INDEX environment variable (default: 0) */ private Map processPlatformsConfig(Map rawConfig) { - Object platformsObj = rawConfig.get("platforms"); + Object platformsObj = rawConfig.get(CapabilityKeys.PLATFORMS); if (!(platformsObj instanceof List)) { // Invalid format, return empty config @@ -222,9 +228,9 @@ private Map processPlatformsConfig(Map rawConfig // Get platform index from system property or environment variable (default: 0) // Priority: System property (-DLT_PLATFORM_INDEX=1) > Environment variable (export LT_PLATFORM_INDEX=1) int platformIndex = 0; - String platformIndexStr = System.getProperty("LT_PLATFORM_INDEX"); + String platformIndexStr = System.getProperty(CapabilityKeys.ENV_LT_PLATFORM_INDEX); if (platformIndexStr == null || platformIndexStr.trim().isEmpty()) { - platformIndexStr = System.getenv("LT_PLATFORM_INDEX"); + platformIndexStr = System.getenv(CapabilityKeys.ENV_LT_PLATFORM_INDEX); } if (platformIndexStr != null && !platformIndexStr.trim().isEmpty()) { @@ -254,7 +260,7 @@ private Map processPlatformsConfig(Map rawConfig // Merge any root-level configurations (non-platforms keys) for (Map.Entry entry : rawConfig.entrySet()) { String key = entry.getKey(); - if (!key.equals("platforms") && !platformConfig.containsKey(key)) { + if (!key.equals(CapabilityKeys.PLATFORMS) && !platformConfig.containsKey(key)) { platformConfig.put(key, entry.getValue()); } } @@ -276,9 +282,9 @@ public DesiredCapabilities getCapabilities(DesiredCapabilities codeCapabilities) capabilities.merge(codeCapabilities); // Ensure lt:options exists if not already set - if (codeCapabilities.getCapability("lt:options") == null) { + if (codeCapabilities.getCapability(CapabilityKeys.LT_OPTIONS) == null) { Map ltOptions = new HashMap<>(); - capabilities.setCapability("lt:options", ltOptions); + capabilities.setCapability(CapabilityKeys.LT_OPTIONS, ltOptions); } return capabilities; @@ -290,54 +296,147 @@ public DesiredCapabilities getCapabilities(DesiredCapabilities codeCapabilities) /** * Get capabilities from YAML configuration. + * Supports all Selenium 3, Selenium 4, and LambdaTest advanced capabilities. + * + * Supported Browsers (Selenium 3 & 4): + * - Chrome + * - Firefox + * - Safari + * - MS Edge (Microsoft Edge) + * - Opera + * - IE (Internet Explorer) + * + * Browser-specific options are supported for both Selenium 3 and 4: + * - Chrome: chromeOptions / goog:chromeOptions + * - Firefox: firefoxOptions / moz:firefoxOptions + * - Edge: edgeOptions / ms:edgeOptions + * - Safari: safariOptions / safari:options + * - Opera: operaOptions + * - IE: ieOptions / se:ieOptions + * + * Selenium 3 capabilities are set directly on DesiredCapabilities for backwards compatibility. + * Selenium 4 capabilities use the W3C standard format with lt:options. */ public DesiredCapabilities getCapabilitiesFromYaml() { DesiredCapabilities capabilities = new DesiredCapabilities(); - - // LambdaTest options from YAML Map ltOptions = new HashMap<>(); + CapabilityProcessor processor = new CapabilityProcessor(config, capabilities, ltOptions); + + // ============================================================ + // 1. W3C Standard Browser Capabilities (Selenium 3 & 4) + // ============================================================ + processW3CBrowserCapabilities(capabilities); + + // ============================================================ + // 2. Browser-Specific Options (Chrome, Firefox, Edge, etc.) + // ============================================================ + BrowserOptionsCapabilities.processBrowserOptions(config, capabilities); + + // ============================================================ + // 3. LambdaTest Credentials (Required) + // ============================================================ + processCredentials(ltOptions); + + // ============================================================ + // 4. Selenium 3 Capabilities (for backwards compatibility) + // ============================================================ + processor.process(Selenium3Capabilities.getDefinitions()); + + // Handle special case: version -> browserVersion mapping + processVersionCapability(capabilities); + + // ============================================================ + // 5. Selenium 4 / W3C Capabilities (LambdaTest advanced) + // ============================================================ + processor.process(Selenium4Capabilities.getDefinitions()); + + // ============================================================ + // 6. Special Cases (require custom handling) + // ============================================================ + processSpecialCases(capabilities, ltOptions); + + // ============================================================ + // 7. Finalize: Set lt:options on capabilities + // ============================================================ + capabilities.setCapability(CapabilityKeys.LT_OPTIONS, ltOptions); - // Basic browser config - if (config.containsKey("browserName")) { - capabilities.setCapability("browserName", config.get("browserName")); - } - if (config.containsKey("browserVersion")) { - capabilities.setCapability("browserVersion", config.get("browserVersion")); + return capabilities; + } + + /** + * Process W3C standard browser capabilities (browserName, browserVersion, platformName). + */ + private void processW3CBrowserCapabilities(DesiredCapabilities capabilities) { + // browserName (case-sensitive, mandatory) + if (config.containsKey(CapabilityKeys.BROWSER_NAME)) { + capabilities.setCapability(CapabilityKeys.BROWSER_NAME, config.get(CapabilityKeys.BROWSER_NAME)); + } else if (config.containsKey(CapabilityKeys.BROWSER)) { + capabilities.setCapability(CapabilityKeys.BROWSER_NAME, config.get(CapabilityKeys.BROWSER)); } - if (config.containsKey("platformName")) { - capabilities.setCapability("platformName", config.get("platformName")); + + // browserVersion + if (config.containsKey(CapabilityKeys.BROWSER_VERSION)) { + capabilities.setCapability(CapabilityKeys.BROWSER_VERSION, config.get(CapabilityKeys.BROWSER_VERSION)); + } else if (config.containsKey(CapabilityKeys.VERSION)) { + capabilities.setCapability(CapabilityKeys.BROWSER_VERSION, config.get(CapabilityKeys.VERSION)); } - // LambdaTest credentials (required) - put only in lt:options for W3C compliance + // platformName + if (config.containsKey(CapabilityKeys.PLATFORM_NAME)) { + capabilities.setCapability(CapabilityKeys.PLATFORM_NAME, config.get(CapabilityKeys.PLATFORM_NAME)); + } else if (config.containsKey(CapabilityKeys.PLATFORM)) { + capabilities.setCapability(CapabilityKeys.PLATFORM_NAME, config.get(CapabilityKeys.PLATFORM)); + } else if (config.containsKey(CapabilityKeys.OS)) { + capabilities.setCapability(CapabilityKeys.PLATFORM_NAME, config.get(CapabilityKeys.OS)); + } + } + + /** + * Process LambdaTest credentials. + */ + private void processCredentials(Map ltOptions) { try { String username = getUsername(); String accessKey = getAccessKey(); - ltOptions.put("user", username); - ltOptions.put("accessKey", accessKey); + ltOptions.put(CapabilityKeys.USER, username); + ltOptions.put(CapabilityKeys.ACCESS_KEY, accessKey); } catch (Exception e) { + // Credentials will be required when creating WebDriver + throw new RuntimeException("LambdaTest credentials not found. Please set " + CapabilityKeys.ENV_LT_USERNAME + " and " + CapabilityKeys.ENV_LT_ACCESS_KEY + " environment variables or add '" + CapabilityKeys.USERNAME + "' and '" + CapabilityKeys.ACCESSKEY + "' to " + CapabilityKeys.CONFIG_FILE_YML); } - - // LambdaTest specific options - if (config.containsKey("build")) ltOptions.put("build", config.get("build")); - if (config.containsKey("project")) ltOptions.put("project", config.get("project")); - if (config.containsKey("name")) ltOptions.put("name", config.get("name")); - if (config.containsKey("video")) ltOptions.put("video", config.get("video")); - if (config.containsKey("network")) ltOptions.put("network", config.get("network")); - if (config.containsKey("console")) ltOptions.put("console", config.get("console")); - if (config.containsKey("visual")) ltOptions.put("visual", config.get("visual")); - if (config.containsKey("resolution")) ltOptions.put("resolution", config.get("resolution")); - if (config.containsKey("tunnel")) { - Object tunnelValue = config.get("tunnel"); - ltOptions.put("tunnel", tunnelValue); - - // Note: Tunnel will be started when WebDriver is actually created - // This prevents starting it too early before tests run + } + + /** + * Handle version capability special case (Selenium 3 compatibility). + * version should be set on DesiredCapabilities AND as browserVersion for W3C. + */ + private void processVersionCapability(DesiredCapabilities capabilities) { + if (config.containsKey(CapabilityKeys.VERSION) && !config.containsKey(CapabilityKeys.BROWSER_VERSION)) { + Object versionValue = config.get(CapabilityKeys.VERSION); + capabilities.setCapability(CapabilityKeys.VERSION, versionValue); // Selenium 3 + capabilities.setCapability(CapabilityKeys.BROWSER_VERSION, versionValue); // W3C + } + } + + /** + * Process special cases that require custom logic. + */ + private void processSpecialCases(DesiredCapabilities capabilities, Map ltOptions) { + // lambda:userFiles - set directly on capabilities (not in lt:options) + if (config.containsKey(CapabilityKeys.LAMBDA_USER_FILES)) { + capabilities.setCapability(CapabilityKeys.LAMBDA_USER_FILES, config.get(CapabilityKeys.LAMBDA_USER_FILES)); + } else if (config.containsKey(CapabilityKeys.USER_FILES)) { + capabilities.setCapability(CapabilityKeys.LAMBDA_USER_FILES, config.get(CapabilityKeys.USER_FILES)); } - capabilities.setCapability("lt:options", ltOptions); - - - return capabilities; + // project -> projectName mapping for Selenium 3, and project -> lt:options for Selenium 4 + if (config.containsKey(CapabilityKeys.PROJECT)) { + Object projectValue = config.get(CapabilityKeys.PROJECT); + ltOptions.put(CapabilityKeys.PROJECT, projectValue); // Selenium 4 + if (!config.containsKey(CapabilityKeys.PROJECT_NAME)) { + capabilities.setCapability(CapabilityKeys.PROJECT_NAME, projectValue); // Selenium 3 + } + } } /** @@ -355,7 +454,7 @@ public String getHubUrl() { String username = getUsername(); String accessKey = getAccessKey(); - return "https://" + username + ":" + accessKey + "@hub.lambdatest.com/wd/hub"; + return CapabilityKeys.HUB_URL_PREFIX + username + CapabilityKeys.HUB_URL_SEPARATOR + accessKey + CapabilityKeys.HUB_URL_SUFFIX; } /** @@ -363,17 +462,17 @@ public String getHubUrl() { */ public String getUsername() { // Priority 1: Environment variables - String username = System.getenv("LT_USERNAME"); + String username = System.getenv(CapabilityKeys.ENV_LT_USERNAME); if (username != null && !username.trim().isEmpty()) { return username; } // Priority 2: YAML file - if (config != null && config.containsKey("username")) { - return config.get("username").toString(); + if (config != null && config.containsKey(CapabilityKeys.USERNAME)) { + return config.get(CapabilityKeys.USERNAME).toString(); } - throw new RuntimeException("LambdaTest username not found. Please set LT_USERNAME environment variable or add 'username' to lambdatest.yml"); + throw new RuntimeException("LambdaTest username not found. Please set " + CapabilityKeys.ENV_LT_USERNAME + " environment variable or add '" + CapabilityKeys.USERNAME + "' to " + CapabilityKeys.CONFIG_FILE_YML); } /** @@ -381,17 +480,17 @@ public String getUsername() { */ public String getAccessKey() { // Priority 1: Environment variables - String accessKey = System.getenv("LT_ACCESS_KEY"); + String accessKey = System.getenv(CapabilityKeys.ENV_LT_ACCESS_KEY); if (accessKey != null && !accessKey.trim().isEmpty()) { return accessKey; } // Priority 2: YAML file - if (config != null && config.containsKey("accesskey")) { - return config.get("accesskey").toString(); + if (config != null && config.containsKey(CapabilityKeys.ACCESSKEY)) { + return config.get(CapabilityKeys.ACCESSKEY).toString(); } - throw new RuntimeException("LambdaTest access key not found. Please set LT_ACCESS_KEY environment variable or add 'accesskey' to lambdatest.yml"); + throw new RuntimeException("LambdaTest access key not found. Please set " + CapabilityKeys.ENV_LT_ACCESS_KEY + " environment variable or add '" + CapabilityKeys.ACCESSKEY + "' to " + CapabilityKeys.CONFIG_FILE_YML); } /** @@ -426,8 +525,8 @@ private void startTunnelAutomatically() { // Get optional tunnel name from config String tunnelName = null; - if (config.containsKey("tunnelName")) { - tunnelName = config.get("tunnelName").toString(); + if (config.containsKey(CapabilityKeys.TUNNEL_NAME)) { + tunnelName = config.get(CapabilityKeys.TUNNEL_NAME).toString(); } // Start the tunnel diff --git a/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/BrowserOptionsCapabilities.java b/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/BrowserOptionsCapabilities.java new file mode 100644 index 0000000..195769d --- /dev/null +++ b/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/BrowserOptionsCapabilities.java @@ -0,0 +1,67 @@ +package com.lambdatest.selenium.lambdatest.capabilities; + +import java.util.Map; + +import org.openqa.selenium.remote.DesiredCapabilities; + +/** + * Handles browser-specific options (Chrome, Firefox, Edge, Safari, Opera, IE). + * These need special handling because they have different keys for Selenium 3 and 4. + */ +public class BrowserOptionsCapabilities { + + /** + * Process browser-specific options from config. + * Sets both Selenium 3 and Selenium 4 keys for compatibility. + */ + public static void processBrowserOptions(Map config, DesiredCapabilities capabilities) { + // Chrome + processBrowserOption(config, capabilities, "chromeOptions", "goog:chromeOptions"); + + // Firefox + processBrowserOption(config, capabilities, "firefoxOptions", "moz:firefoxOptions"); + + // Edge + processBrowserOption(config, capabilities, "edgeOptions", "ms:edgeOptions"); + + // Safari + processBrowserOption(config, capabilities, "safariOptions", "safari:options"); + + // Opera (no W3C namespace, same for both) + processBrowserOption(config, capabilities, "operaOptions", "operaOptions"); + + // Internet Explorer + processBrowserOption(config, capabilities, "ieOptions", "se:ieOptions", "IEOptions"); + } + + /** + * Process a browser option with Selenium 3 and 4 compatibility. + */ + private static void processBrowserOption(Map config, DesiredCapabilities capabilities, + String selenium3Key, String selenium4Key) { + processBrowserOption(config, capabilities, selenium3Key, selenium4Key, selenium3Key); + } + + /** + * Process a browser option with custom Selenium 3 key. + */ + private static void processBrowserOption(Map config, DesiredCapabilities capabilities, + String selenium3Key, String selenium4Key, String selenium3TargetKey) { + Object options = null; + + // Check Selenium 3 key first + if (config.containsKey(selenium3Key)) { + options = config.get(selenium3Key); + } + // Then check Selenium 4 key + else if (config.containsKey(selenium4Key)) { + options = config.get(selenium4Key); + } + + if (options != null) { + // Set both keys for compatibility + capabilities.setCapability(selenium3TargetKey, options); // Selenium 3 + capabilities.setCapability(selenium4Key, options); // Selenium 4 W3C + } + } +} \ No newline at end of file diff --git a/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/CapabilityDefinition.java b/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/CapabilityDefinition.java new file mode 100644 index 0000000..91c394c --- /dev/null +++ b/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/CapabilityDefinition.java @@ -0,0 +1,86 @@ +package com.lambdatest.selenium.lambdatest.capabilities; + +import java.util.Arrays; +import java.util.List; + +/** + * Definition of a capability with its key, aliases, and target locations. + * This makes it easy to add new capabilities without modifying the main processing logic. + */ +public class CapabilityDefinition { + + private final String primaryKey; + private final List aliases; + private final CapabilityTarget target; + private final String targetKey; // Optional: different key in target (e.g., "project" -> "projectName" for Selenium 3) + + /** + * Where the capability should be set. + */ + public enum CapabilityTarget { + /** Set directly on DesiredCapabilities (Selenium 3 style) */ + DESIRED_CAPABILITIES, + /** Set in lt:options (Selenium 4/W3C style) */ + LT_OPTIONS, + /** Set in both places (for backwards compatibility) */ + BOTH + } + + public CapabilityDefinition(String primaryKey, CapabilityTarget target) { + this(primaryKey, target, null); + } + + public CapabilityDefinition(String primaryKey, CapabilityTarget target, String targetKey) { + this(primaryKey, Arrays.asList(), target, targetKey); + } + + public CapabilityDefinition(String primaryKey, List aliases, CapabilityTarget target) { + this(primaryKey, aliases, target, null); + } + + public CapabilityDefinition(String primaryKey, List aliases, CapabilityTarget target, String targetKey) { + this.primaryKey = primaryKey; + this.aliases = aliases; + this.target = target; + this.targetKey = targetKey; + } + + public String getPrimaryKey() { + return primaryKey; + } + + public List getAliases() { + return aliases; + } + + public CapabilityTarget getTarget() { + return target; + } + + public String getTargetKey() { + return targetKey != null ? targetKey : primaryKey; + } + + /** + * Check if this definition matches the given key (primary key or any alias). + */ + public boolean matches(String key) { + return primaryKey.equals(key) || aliases.contains(key); + } + + /** + * Get the value from config if it exists (checking primary key and aliases). + */ + public Object getValue(java.util.Map config) { + if (config.containsKey(primaryKey)) { + return config.get(primaryKey); + } + for (String alias : aliases) { + if (config.containsKey(alias)) { + return config.get(alias); + } + } + return null; + } +} + diff --git a/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/CapabilityKeys.java b/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/CapabilityKeys.java new file mode 100644 index 0000000..12b8ac7 --- /dev/null +++ b/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/CapabilityKeys.java @@ -0,0 +1,96 @@ +package com.lambdatest.selenium.lambdatest.capabilities; + +/** + * Common capability key constants used across Selenium 3, Selenium 4, and LambdaTest configuration. + * This centralizes capability string definitions to avoid duplication and ensure consistency. + */ +public final class CapabilityKeys { + + // Private constructor to prevent instantiation + private CapabilityKeys() { + throw new UnsupportedOperationException("Utility class cannot be instantiated"); + } + + // W3C Browser Capabilities + public static final String BROWSER_NAME = "browserName"; + public static final String BROWSER = "browser"; + public static final String BROWSER_VERSION = "browserVersion"; + public static final String VERSION = "version"; + public static final String PLATFORM_NAME = "platformName"; + public static final String PLATFORM = "platform"; + public static final String OS = "OS"; + + // LambdaTest Options + public static final String LT_OPTIONS = "lt:options"; + + // Organization capabilities + public static final String BUILD = "build"; + public static final String BUILD_NAME = "buildName"; + public static final String JOB = "job"; + public static final String JOB_NAME = "jobName"; + public static final String NAME = "name"; + public static final String TESTNAME = "testname"; + public static final String SESSIONNAME = "sessionname"; + public static final String TEST = "test"; + public static final String TAGS = "tags"; + public static final String BUILD_TAGS = "buildTags"; + public static final String PROJECT = "project"; + public static final String PROJECT_NAME = "projectName"; + + // Driver and version capabilities + public static final String DRIVER_VERSION = "driver_version"; + public static final String DRIVER_VERSION_ALIAS = "driverVersion"; + public static final String DRIVER = "driver"; + + // Resolution + public static final String RESOLUTION = "resolution"; + public static final String VIEWPORT = "viewport"; + + // Extension loading + public static final String LAMBDA_LOAD_EXTENSION = "lambda:loadExtension"; + public static final String LOAD_EXTENSION = "loadExtension"; + + // Logging capabilities + public static final String COMMAND_LOG = "commandLog"; + public static final String COMMAND_LOGS = "commandLogs"; + public static final String SYSTEM_LOG = "systemLog"; + public static final String SELENIUM_LOGS = "seleniumLogs"; + + // Network capabilities + public static final String NETWORK_HTTP2 = "network.http2"; + public static final String DISABLE_XF_HEADERS = "DisableXFHeaders"; + public static final String NETWORK_DEBUG = "network.debug"; + public static final String IGNORE_FF_OPTIONS_ARGS = "ignoreFfOptionsArgs"; + public static final String UPDATE_BUILD_STATUS_ON_SUCCESS = "updateBuildStatusOnSuccess"; + + // User files + public static final String LAMBDA_USER_FILES = "lambda:userFiles"; + public static final String USER_FILES = "userFiles"; + + // Tunnel + public static final String TUNNEL_NAME = "tunnelName"; + + // Credentials + public static final String USER = "user"; + public static final String ACCESS_KEY = "accessKey"; + public static final String USERNAME = "username"; + public static final String ACCESSKEY = "accesskey"; + + // Environment Variables + public static final String ENV_LT_USERNAME = "LT_USERNAME"; + public static final String ENV_LT_ACCESS_KEY = "LT_ACCESS_KEY"; + public static final String ENV_LT_PLATFORM_INDEX = "LT_PLATFORM_INDEX"; + + // Config Keys + public static final String PLATFORMS = "platforms"; + + // Config File Names + public static final String CONFIG_FILE_YML = "lambdatest.yml"; + public static final String CONFIG_FILE_YAML = "lambdatest.yaml"; + + // Hub URL + public static final String HUB_URL_PREFIX = "https://"; + public static final String HUB_URL_SUFFIX = "@hub.lambdatest.com/wd/hub"; + public static final String HUB_URL_SEPARATOR = ":"; +} + diff --git a/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/CapabilityProcessor.java b/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/CapabilityProcessor.java new file mode 100644 index 0000000..914d86e --- /dev/null +++ b/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/CapabilityProcessor.java @@ -0,0 +1,79 @@ +package com.lambdatest.selenium.lambdatest.capabilities; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.openqa.selenium.remote.DesiredCapabilities; + +/** + * Processes capabilities from configuration and applies them to DesiredCapabilities and lt:options. + * Separates Selenium 3 and Selenium 4 capability handling for clarity. + */ +public class CapabilityProcessor { + + private final Map config; + private final DesiredCapabilities capabilities; + private final Map ltOptions; + + public CapabilityProcessor(Map config, DesiredCapabilities capabilities, Map ltOptions) { + this.config = config; + this.capabilities = capabilities; + this.ltOptions = ltOptions; + } + + /** + * Process a capability definition and apply it to the appropriate target(s). + */ + public void process(CapabilityDefinition definition) { + Object value = definition.getValue(config); + if (value == null) { + return; + } + + String targetKey = definition.getTargetKey(); + + switch (definition.getTarget()) { + case DESIRED_CAPABILITIES: + capabilities.setCapability(targetKey, value); + break; + + case LT_OPTIONS: + ltOptions.put(targetKey, value); + break; + + case BOTH: + capabilities.setCapability(targetKey, value); + ltOptions.put(targetKey, value); + break; + } + } + + /** + * Process multiple capability definitions. + */ + public void process(List definitions) { + for (CapabilityDefinition definition : definitions) { + process(definition); + } + } + + /** + * Process a capability with custom logic (for complex cases). + */ + public void processCustom(String key, CustomCapabilityHandler handler) { + if (config.containsKey(key)) { + handler.handle(config.get(key), capabilities, ltOptions); + } + } + + /** + * Interface for custom capability handling logic. + */ + @FunctionalInterface + public interface CustomCapabilityHandler { + void handle(Object value, DesiredCapabilities capabilities, Map ltOptions); + } +} + diff --git a/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/Selenium3Capabilities.java b/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/Selenium3Capabilities.java new file mode 100644 index 0000000..801b8da --- /dev/null +++ b/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/Selenium3Capabilities.java @@ -0,0 +1,63 @@ +package com.lambdatest.selenium.lambdatest.capabilities; + +import java.util.Arrays; +import java.util.List; + +/** + * Selenium 3 specific capabilities. + * These are set directly on DesiredCapabilities for backwards compatibility. + */ +public class Selenium3Capabilities { + + /** + * Get all Selenium 3 capability definitions. + */ + public static List getDefinitions() { + return Arrays.asList( + // Basic browser capabilities (also in Selenium 4, but needed for Selenium 3 compatibility) + new CapabilityDefinition(CapabilityKeys.VERSION, CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), + + // Organization capabilities + new CapabilityDefinition(CapabilityKeys.BUILD, + Arrays.asList(CapabilityKeys.BUILD_NAME, CapabilityKeys.JOB, CapabilityKeys.JOB_NAME), + CapabilityDefinition.CapabilityTarget.BOTH), + // Note: project is handled separately in special cases due to projectName mapping + new CapabilityDefinition(CapabilityKeys.NAME, + Arrays.asList(CapabilityKeys.TESTNAME, CapabilityKeys.SESSIONNAME, CapabilityKeys.TEST), + CapabilityDefinition.CapabilityTarget.BOTH), + new CapabilityDefinition(CapabilityKeys.TAGS, CapabilityDefinition.CapabilityTarget.BOTH), + new CapabilityDefinition(CapabilityKeys.BUILD_TAGS, CapabilityDefinition.CapabilityTarget.BOTH), + + // Driver and version capabilities + new CapabilityDefinition(CapabilityKeys.DRIVER_VERSION, + Arrays.asList(CapabilityKeys.DRIVER_VERSION_ALIAS, CapabilityKeys.DRIVER), + CapabilityDefinition.CapabilityTarget.BOTH), + + // Resolution + new CapabilityDefinition(CapabilityKeys.RESOLUTION, + Arrays.asList(CapabilityKeys.VIEWPORT), + CapabilityDefinition.CapabilityTarget.BOTH), + + // Extension loading + new CapabilityDefinition(CapabilityKeys.LAMBDA_LOAD_EXTENSION, + Arrays.asList(CapabilityKeys.LOAD_EXTENSION), + CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), + + // Logging capabilities + new CapabilityDefinition(CapabilityKeys.COMMAND_LOG, + Arrays.asList(CapabilityKeys.COMMAND_LOGS), + CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), + new CapabilityDefinition(CapabilityKeys.SYSTEM_LOG, + Arrays.asList(CapabilityKeys.SELENIUM_LOGS), + CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), + + // Network capabilities + new CapabilityDefinition(CapabilityKeys.NETWORK_HTTP2, CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), + new CapabilityDefinition(CapabilityKeys.DISABLE_XF_HEADERS, CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), + new CapabilityDefinition(CapabilityKeys.NETWORK_DEBUG, CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), + new CapabilityDefinition(CapabilityKeys.IGNORE_FF_OPTIONS_ARGS, CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), + new CapabilityDefinition(CapabilityKeys.UPDATE_BUILD_STATUS_ON_SUCCESS, CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES) + ); + } +} + diff --git a/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/Selenium4Capabilities.java b/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/Selenium4Capabilities.java new file mode 100644 index 0000000..638fcbc --- /dev/null +++ b/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/Selenium4Capabilities.java @@ -0,0 +1,82 @@ +package com.lambdatest.selenium.lambdatest.capabilities; + +import java.util.Arrays; +import java.util.List; + +/** + * Selenium 4 / W3C standard capabilities. + * These are set in lt:options for W3C compliance. + */ +public class Selenium4Capabilities { + + /** + * Get all Selenium 4 capability definitions. + */ + public static List getDefinitions() { + return Arrays.asList( + // Driver and Selenium version + new CapabilityDefinition("driver_version", + Arrays.asList("driverVersion", "driver"), + CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + new CapabilityDefinition("selenium_version", + Arrays.asList("seleniumVersion", "seVersion"), + CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + + // Timeout + new CapabilityDefinition("idleTimeout", + Arrays.asList("idle"), + CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + + // Organization (also in Selenium 3, but processed separately for clarity) + new CapabilityDefinition("build", CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + new CapabilityDefinition("project", CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + new CapabilityDefinition("name", CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + new CapabilityDefinition("tags", CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + new CapabilityDefinition("buildTags", CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + + // Debugging capabilities + new CapabilityDefinition("video", CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + new CapabilityDefinition("visual", + Arrays.asList("debug"), + CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + new CapabilityDefinition("network", + Arrays.asList("networkLogs"), + CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + new CapabilityDefinition("console", CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + new CapabilityDefinition("network.mask", CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + new CapabilityDefinition("verboseWebDriverLogging", CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + new CapabilityDefinition("network.full.har", CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + + // Environment + new CapabilityDefinition("resolution", CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + new CapabilityDefinition("timezone", CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + + // Tunnel + new CapabilityDefinition("tunnel", + Arrays.asList("local"), + CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + new CapabilityDefinition("tunnelName", + Arrays.asList("localName"), + CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + + // Auto Healing & Smart Wait + new CapabilityDefinition("autoHeal", CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + new CapabilityDefinition("smartWait", CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + new CapabilityDefinition("smartWaitRetryDelay", CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + + // Geolocation + new CapabilityDefinition("geoLocation", CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + + // Security & Privacy + new CapabilityDefinition("lambdaMaskCommands", CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + + // Network + new CapabilityDefinition("networkThrottling", CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + + // Custom Configuration + new CapabilityDefinition("customHeaders", CapabilityDefinition.CapabilityTarget.LT_OPTIONS), + new CapabilityDefinition("customDnsMap", CapabilityDefinition.CapabilityTarget.LT_OPTIONS) + ); + } +} +