From 3ea28fc5a57f32777db775fddfcd40a3e28dc1ac Mon Sep 17 00:00:00 2001 From: Abhishek Gadekar Date: Wed, 3 Dec 2025 13:55:01 +0530 Subject: [PATCH 1/8] add all capabilities for selenium3 & 4 --- .gradle/8.10/fileHashes/fileHashes.bin | Bin 26147 -> 26197 bytes .gradle/8.10/fileHashes/fileHashes.lock | Bin 17 -> 17 bytes ...25762FBD83437C838F5964CCB45E77C85BE8799150 | 1 + dependency-reduced-pom.xml | 2 +- pom.xml | 2 +- .../selenium/agent/RemoteWebDriverAdvice.java | 116 +++++++++----- .../selenium/lambdatest/LambdaTestConfig.java | 148 +++++++++++++++--- .../BrowserOptionsCapabilities.java | 83 ++++++++++ .../capabilities/CapabilityDefinition.java | 86 ++++++++++ .../capabilities/CapabilityProcessor.java | 79 ++++++++++ .../capabilities/Selenium3Capabilities.java | 63 ++++++++ .../capabilities/Selenium4Capabilities.java | 82 ++++++++++ 12 files changed, 599 insertions(+), 63 deletions(-) create mode 100644 .gradle/nb-cache/trust/02EB122FE4B7F629413C7D25762FBD83437C838F5964CCB45E77C85BE8799150 create mode 100644 src/main/java/com/lambdatest/selenium/lambdatest/capabilities/BrowserOptionsCapabilities.java create mode 100644 src/main/java/com/lambdatest/selenium/lambdatest/capabilities/CapabilityDefinition.java create mode 100644 src/main/java/com/lambdatest/selenium/lambdatest/capabilities/CapabilityProcessor.java create mode 100644 src/main/java/com/lambdatest/selenium/lambdatest/capabilities/Selenium3Capabilities.java create mode 100644 src/main/java/com/lambdatest/selenium/lambdatest/capabilities/Selenium4Capabilities.java diff --git a/.gradle/8.10/fileHashes/fileHashes.bin b/.gradle/8.10/fileHashes/fileHashes.bin index f9c96260ea277ee672f596c5bd81758c16e67918..a6e2de68fb6f236598734a95d20f04ec41277705 100644 GIT binary patch delta 103 zcmZ2{hVkkd#tkMCjI$=2N+bw$tb4~XU#!c70SwZVH}^_#^D#DVRQw^n(O?GWW=D@l wvPQ;0NlhSD1YrTC@b}k0U;DRfmyWlqrv3p(1_s7ix14&l!6q=ciUDao0Jj|;8UO$Q delta 35 tcmV+;0Nnr8%mJg!0kAX}0hyCE7+|xl7y}LgX|W;r7qKv~39~^+;vM?X4Uzx= diff --git a/.gradle/8.10/fileHashes/fileHashes.lock b/.gradle/8.10/fileHashes/fileHashes.lock index dfce57a822dee4a57c7bc18dacfded5fc641e1d7..88feeb22e287b0f1fabf1290e10d4742a5b018f7 100644 GIT binary patch literal 17 VcmZSXSO1kdH|N(s1~6c}1OPqI1?>O; literal 17 VcmZSXSO1kdH|N(s1~6be3jjUK1?T_( diff --git a/.gradle/nb-cache/trust/02EB122FE4B7F629413C7D25762FBD83437C838F5964CCB45E77C85BE8799150 b/.gradle/nb-cache/trust/02EB122FE4B7F629413C7D25762FBD83437C838F5964CCB45E77C85BE8799150 new file mode 100644 index 0000000..34dcae9 --- /dev/null +++ b/.gradle/nb-cache/trust/02EB122FE4B7F629413C7D25762FBD83437C838F5964CCB45E77C85BE8799150 @@ -0,0 +1 @@ +DDDFE19D9303B09E1BA21B6DA564DA286AEAEBE5DFC943499A81CFCD41C58B16 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..70b6384 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; @@ -30,6 +31,10 @@ public class RemoteWebDriverAdvice { // 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"); /** * Static method to enhance capabilities that can be called from ASM bytecode. @@ -72,57 +77,96 @@ 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; + if (userCapMap.containsKey("lt:options")) { + ltOptions = (Map) userCapMap.get("lt:options"); + } 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; + } + + // 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 + capabilities.asMap().remove(ltKey); } + } + + // Ensure lt:options is updated in capabilities + capabilities.setCapability("lt:options", ltOptions); + + // 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 - check if it's running and add tunnel name + 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."); - } + 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."); } - } catch (Exception e) { - System.err.println("[LambdaTest SDK] Warning: Error checking tunnel status: " + e.getMessage()); } + } catch (Exception e) { + System.err.println("[LambdaTest SDK] Warning: Error checking tunnel status: " + e.getMessage()); } } } diff --git a/src/main/java/com/lambdatest/selenium/lambdatest/LambdaTestConfig.java b/src/main/java/com/lambdatest/selenium/lambdatest/LambdaTestConfig.java index 90e36ee..effe146 100644 --- a/src/main/java/com/lambdatest/selenium/lambdatest/LambdaTestConfig.java +++ b/src/main/java/com/lambdatest/selenium/lambdatest/LambdaTestConfig.java @@ -10,6 +10,10 @@ 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; /** * YAML configuration reader for LambdaTest Selenium SDK. @@ -144,6 +148,7 @@ private void loadConfig() { // Try multiple locations for lambdatest.yml String[] locations = { "lambdatest.yml", // Root directory + "lambdatest.yaml" }; for (String location : locations) { @@ -290,54 +295,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("lt:options", ltOptions); - // Basic browser config + return capabilities; + } + + /** + * Process W3C standard browser capabilities (browserName, browserVersion, platformName). + */ + private void processW3CBrowserCapabilities(DesiredCapabilities capabilities) { + // browserName (case-sensitive, mandatory) if (config.containsKey("browserName")) { capabilities.setCapability("browserName", config.get("browserName")); + } else if (config.containsKey("browser")) { + capabilities.setCapability("browserName", config.get("browser")); } + + // browserVersion if (config.containsKey("browserVersion")) { capabilities.setCapability("browserVersion", config.get("browserVersion")); + } else if (config.containsKey("version")) { + capabilities.setCapability("browserVersion", config.get("version")); } + + // platformName if (config.containsKey("platformName")) { capabilities.setCapability("platformName", config.get("platformName")); + } else if (config.containsKey("platform")) { + capabilities.setCapability("platformName", config.get("platform")); + } else if (config.containsKey("OS")) { + capabilities.setCapability("platformName", config.get("OS")); } - - // LambdaTest credentials (required) - put only in lt:options for W3C compliance + } + + /** + * Process LambdaTest credentials. + */ + private void processCredentials(Map ltOptions) { try { String username = getUsername(); String accessKey = getAccessKey(); ltOptions.put("user", username); ltOptions.put("accessKey", accessKey); } catch (Exception e) { + // Credentials will be required when creating WebDriver + throw new RuntimeException("LambdaTest credentials not found. Please set LT_USERNAME and LT_ACCESS_KEY environment variables or add 'username' and 'accesskey' to lambdatest.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("version") && !config.containsKey("browserVersion")) { + Object versionValue = config.get("version"); + capabilities.setCapability("version", versionValue); // Selenium 3 + capabilities.setCapability("browserVersion", 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("lambda:userFiles")) { + capabilities.setCapability("lambda:userFiles", config.get("lambda:userFiles")); + } else if (config.containsKey("userFiles")) { + capabilities.setCapability("lambda:userFiles", config.get("userFiles")); } - capabilities.setCapability("lt:options", ltOptions); - - - return capabilities; + // project -> projectName mapping for Selenium 3, and project -> lt:options for Selenium 4 + if (config.containsKey("project")) { + Object projectValue = config.get("project"); + ltOptions.put("project", projectValue); // Selenium 4 + if (!config.containsKey("projectName")) { + capabilities.setCapability("projectName", projectValue); // Selenium 3 + } + } } /** 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..104aa5b --- /dev/null +++ b/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/BrowserOptionsCapabilities.java @@ -0,0 +1,83 @@ +package com.lambdatest.selenium.lambdatest.capabilities; + +import java.util.HashMap; +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 { + + /** + * Browser option mapping: Selenium 3 key -> Selenium 4 W3C key + */ + private static final Map BROWSER_OPTION_MAPPINGS = new HashMap<>(); + static { + BROWSER_OPTION_MAPPINGS.put("chromeOptions", "goog:chromeOptions"); + BROWSER_OPTION_MAPPINGS.put("firefoxOptions", "moz:firefoxOptions"); + BROWSER_OPTION_MAPPINGS.put("edgeOptions", "ms:edgeOptions"); + BROWSER_OPTION_MAPPINGS.put("safariOptions", "safari:options"); + BROWSER_OPTION_MAPPINGS.put("ieOptions", "se:ieOptions"); + } + + /** + * 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) + if (config.containsKey("operaOptions")) { + Object operaOptions = config.get("operaOptions"); + capabilities.setCapability("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/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..d88c9f5 --- /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("version", CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), + + // Organization capabilities + new CapabilityDefinition("build", + Arrays.asList("buildName", "job", "jobName"), + CapabilityDefinition.CapabilityTarget.BOTH), + // Note: project is handled separately in special cases due to projectName mapping + new CapabilityDefinition("name", + Arrays.asList("testname", "sessionname", "test"), + CapabilityDefinition.CapabilityTarget.BOTH), + new CapabilityDefinition("tags", CapabilityDefinition.CapabilityTarget.BOTH), + new CapabilityDefinition("buildTags", CapabilityDefinition.CapabilityTarget.BOTH), + + // Driver and version capabilities + new CapabilityDefinition("driver_version", + Arrays.asList("driverVersion", "driver"), + CapabilityDefinition.CapabilityTarget.BOTH), + + // Resolution + new CapabilityDefinition("resolution", + Arrays.asList("viewport"), + CapabilityDefinition.CapabilityTarget.BOTH), + + // Extension loading + new CapabilityDefinition("lambda:loadExtension", + Arrays.asList("loadExtension"), + CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), + + // Logging capabilities + new CapabilityDefinition("commandLog", + Arrays.asList("commandLogs"), + CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), + new CapabilityDefinition("systemLog", + Arrays.asList("seleniumLogs"), + CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), + + // Network capabilities + new CapabilityDefinition("network.http2", CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), + new CapabilityDefinition("DisableXFHeaders", CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), + new CapabilityDefinition("network.debug", CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), + new CapabilityDefinition("ignoreFfOptionsArgs", CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), + new CapabilityDefinition("updateBuildStatusOnSuccess", 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) + ); + } +} + From 84364ba1ac1aa832a35b105888f73474f02eaa20 Mon Sep 17 00:00:00 2001 From: Abhishek Gadekar Date: Wed, 3 Dec 2025 14:01:18 +0530 Subject: [PATCH 2/8] remove gradle files --- .gitignore | 3 ++- .gradle/8.10/fileHashes/fileHashes.bin | Bin 26197 -> 0 bytes .gradle/8.10/fileHashes/fileHashes.lock | Bin 17 -> 0 bytes ...7D25762FBD83437C838F5964CCB45E77C85BE8799150 | 1 - 4 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 .gradle/8.10/fileHashes/fileHashes.bin delete mode 100644 .gradle/8.10/fileHashes/fileHashes.lock delete mode 100644 .gradle/nb-cache/trust/02EB122FE4B7F629413C7D25762FBD83437C838F5964CCB45E77C85BE8799150 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 a6e2de68fb6f236598734a95d20f04ec41277705..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26197 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 RV@T=LIt`vSumXR>`(NVE<;4I1 diff --git a/.gradle/8.10/fileHashes/fileHashes.lock b/.gradle/8.10/fileHashes/fileHashes.lock deleted file mode 100644 index 88feeb22e287b0f1fabf1290e10d4742a5b018f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 VcmZSXSO1kdH|N(s1~6c}1OPqI1?>O; diff --git a/.gradle/nb-cache/trust/02EB122FE4B7F629413C7D25762FBD83437C838F5964CCB45E77C85BE8799150 b/.gradle/nb-cache/trust/02EB122FE4B7F629413C7D25762FBD83437C838F5964CCB45E77C85BE8799150 deleted file mode 100644 index 34dcae9..0000000 --- a/.gradle/nb-cache/trust/02EB122FE4B7F629413C7D25762FBD83437C838F5964CCB45E77C85BE8799150 +++ /dev/null @@ -1 +0,0 @@ -DDDFE19D9303B09E1BA21B6DA564DA286AEAEBE5DFC943499A81CFCD41C58B16 From 5770cfce469afe4e6df7439833d06e1f64a64796 Mon Sep 17 00:00:00 2001 From: Abhishek Gadekar Date: Wed, 3 Dec 2025 14:25:37 +0530 Subject: [PATCH 3/8] add support for legacy selenium3 caps --- .../selenium/agent/RemoteWebDriverAdvice.java | 81 ++++++++++++++++++- .../BrowserOptionsCapabilities.java | 18 +---- 2 files changed, 80 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/lambdatest/selenium/agent/RemoteWebDriverAdvice.java b/src/main/java/com/lambdatest/selenium/agent/RemoteWebDriverAdvice.java index 70b6384..70c1011 100644 --- a/src/main/java/com/lambdatest/selenium/agent/RemoteWebDriverAdvice.java +++ b/src/main/java/com/lambdatest/selenium/agent/RemoteWebDriverAdvice.java @@ -34,7 +34,18 @@ public class RemoteWebDriverAdvice { // 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"); + 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. @@ -112,6 +123,20 @@ public static void enhanceCapabilities(MutableCapabilities capabilities) { 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); @@ -127,8 +152,60 @@ public static void enhanceCapabilities(MutableCapabilities capabilities) { 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 - capabilities.asMap().remove(ltKey); + 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 + } + } } } diff --git a/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/BrowserOptionsCapabilities.java b/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/BrowserOptionsCapabilities.java index 104aa5b..195769d 100644 --- a/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/BrowserOptionsCapabilities.java +++ b/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/BrowserOptionsCapabilities.java @@ -1,6 +1,5 @@ package com.lambdatest.selenium.lambdatest.capabilities; -import java.util.HashMap; import java.util.Map; import org.openqa.selenium.remote.DesiredCapabilities; @@ -11,18 +10,6 @@ */ public class BrowserOptionsCapabilities { - /** - * Browser option mapping: Selenium 3 key -> Selenium 4 W3C key - */ - private static final Map BROWSER_OPTION_MAPPINGS = new HashMap<>(); - static { - BROWSER_OPTION_MAPPINGS.put("chromeOptions", "goog:chromeOptions"); - BROWSER_OPTION_MAPPINGS.put("firefoxOptions", "moz:firefoxOptions"); - BROWSER_OPTION_MAPPINGS.put("edgeOptions", "ms:edgeOptions"); - BROWSER_OPTION_MAPPINGS.put("safariOptions", "safari:options"); - BROWSER_OPTION_MAPPINGS.put("ieOptions", "se:ieOptions"); - } - /** * Process browser-specific options from config. * Sets both Selenium 3 and Selenium 4 keys for compatibility. @@ -41,10 +28,7 @@ public static void processBrowserOptions(Map config, DesiredCapa processBrowserOption(config, capabilities, "safariOptions", "safari:options"); // Opera (no W3C namespace, same for both) - if (config.containsKey("operaOptions")) { - Object operaOptions = config.get("operaOptions"); - capabilities.setCapability("operaOptions", operaOptions); - } + processBrowserOption(config, capabilities, "operaOptions", "operaOptions"); // Internet Explorer processBrowserOption(config, capabilities, "ieOptions", "se:ieOptions", "IEOptions"); From 82982721caa358ba234cab684eeffc3451ee11c3 Mon Sep 17 00:00:00 2001 From: Abhishek Gadekar Date: Tue, 9 Dec 2025 19:59:50 +0530 Subject: [PATCH 4/8] address comments --- .../selenium/agent/RemoteWebDriverAdvice.java | 8 +- .../selenium/lambdatest/LambdaTestConfig.java | 113 +++++++++--------- .../capabilities/CapabilityKeys.java | 96 +++++++++++++++ .../capabilities/Selenium3Capabilities.java | 44 +++---- 4 files changed, 181 insertions(+), 80 deletions(-) create mode 100644 src/main/java/com/lambdatest/selenium/lambdatest/capabilities/CapabilityKeys.java diff --git a/src/main/java/com/lambdatest/selenium/agent/RemoteWebDriverAdvice.java b/src/main/java/com/lambdatest/selenium/agent/RemoteWebDriverAdvice.java index 70c1011..fa9eb69 100644 --- a/src/main/java/com/lambdatest/selenium/agent/RemoteWebDriverAdvice.java +++ b/src/main/java/com/lambdatest/selenium/agent/RemoteWebDriverAdvice.java @@ -90,8 +90,12 @@ public static void enhanceCapabilities(MutableCapabilities capabilities) { // Ensure lt:options exists Map ltOptions; - if (userCapMap.containsKey("lt:options")) { - ltOptions = (Map) userCapMap.get("lt:options"); + 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); diff --git a/src/main/java/com/lambdatest/selenium/lambdatest/LambdaTestConfig.java b/src/main/java/com/lambdatest/selenium/lambdatest/LambdaTestConfig.java index effe146..b0a4ffb 100644 --- a/src/main/java/com/lambdatest/selenium/lambdatest/LambdaTestConfig.java +++ b/src/main/java/com/lambdatest/selenium/lambdatest/LambdaTestConfig.java @@ -14,6 +14,7 @@ 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. @@ -123,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); } } @@ -147,8 +148,8 @@ private void loadConfig() { // Try multiple locations for lambdatest.yml String[] locations = { - "lambdatest.yml", // Root directory - "lambdatest.yaml" + CapabilityKeys.CONFIG_FILE_YML, // Root directory + CapabilityKeys.CONFIG_FILE_YAML }; for (String location : locations) { @@ -156,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) { @@ -175,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) @@ -210,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 @@ -227,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()) { @@ -259,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()); } } @@ -281,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; @@ -357,7 +358,7 @@ public DesiredCapabilities getCapabilitiesFromYaml() { // ============================================================ // 7. Finalize: Set lt:options on capabilities // ============================================================ - capabilities.setCapability("lt:options", ltOptions); + capabilities.setCapability(CapabilityKeys.LT_OPTIONS, ltOptions); return capabilities; } @@ -367,26 +368,26 @@ public DesiredCapabilities getCapabilitiesFromYaml() { */ private void processW3CBrowserCapabilities(DesiredCapabilities capabilities) { // browserName (case-sensitive, mandatory) - if (config.containsKey("browserName")) { - capabilities.setCapability("browserName", config.get("browserName")); - } else if (config.containsKey("browser")) { - capabilities.setCapability("browserName", config.get("browser")); + 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)); } // browserVersion - if (config.containsKey("browserVersion")) { - capabilities.setCapability("browserVersion", config.get("browserVersion")); - } else if (config.containsKey("version")) { - capabilities.setCapability("browserVersion", config.get("version")); + 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)); } // platformName - if (config.containsKey("platformName")) { - capabilities.setCapability("platformName", config.get("platformName")); - } else if (config.containsKey("platform")) { - capabilities.setCapability("platformName", config.get("platform")); - } else if (config.containsKey("OS")) { - capabilities.setCapability("platformName", config.get("OS")); + 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)); } } @@ -397,11 +398,11 @@ 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 LT_USERNAME and LT_ACCESS_KEY environment variables or add 'username' and 'accesskey' to lambdatest.yml"); + 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); } } @@ -410,10 +411,10 @@ private void processCredentials(Map ltOptions) { * version should be set on DesiredCapabilities AND as browserVersion for W3C. */ private void processVersionCapability(DesiredCapabilities capabilities) { - if (config.containsKey("version") && !config.containsKey("browserVersion")) { - Object versionValue = config.get("version"); - capabilities.setCapability("version", versionValue); // Selenium 3 - capabilities.setCapability("browserVersion", versionValue); // W3C + 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 } } @@ -422,18 +423,18 @@ private void processVersionCapability(DesiredCapabilities capabilities) { */ private void processSpecialCases(DesiredCapabilities capabilities, Map ltOptions) { // lambda:userFiles - set directly on capabilities (not in lt:options) - if (config.containsKey("lambda:userFiles")) { - capabilities.setCapability("lambda:userFiles", config.get("lambda:userFiles")); - } else if (config.containsKey("userFiles")) { - capabilities.setCapability("lambda:userFiles", config.get("userFiles")); + 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)); } // project -> projectName mapping for Selenium 3, and project -> lt:options for Selenium 4 - if (config.containsKey("project")) { - Object projectValue = config.get("project"); - ltOptions.put("project", projectValue); // Selenium 4 - if (!config.containsKey("projectName")) { - capabilities.setCapability("projectName", projectValue); // Selenium 3 + 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 } } } @@ -453,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; } /** @@ -461,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); } /** @@ -479,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); } /** @@ -524,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/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/Selenium3Capabilities.java b/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/Selenium3Capabilities.java index d88c9f5..801b8da 100644 --- a/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/Selenium3Capabilities.java +++ b/src/main/java/com/lambdatest/selenium/lambdatest/capabilities/Selenium3Capabilities.java @@ -15,48 +15,48 @@ public class Selenium3Capabilities { public static List getDefinitions() { return Arrays.asList( // Basic browser capabilities (also in Selenium 4, but needed for Selenium 3 compatibility) - new CapabilityDefinition("version", CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), + new CapabilityDefinition(CapabilityKeys.VERSION, CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), // Organization capabilities - new CapabilityDefinition("build", - Arrays.asList("buildName", "job", "jobName"), + 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("name", - Arrays.asList("testname", "sessionname", "test"), + new CapabilityDefinition(CapabilityKeys.NAME, + Arrays.asList(CapabilityKeys.TESTNAME, CapabilityKeys.SESSIONNAME, CapabilityKeys.TEST), CapabilityDefinition.CapabilityTarget.BOTH), - new CapabilityDefinition("tags", CapabilityDefinition.CapabilityTarget.BOTH), - new CapabilityDefinition("buildTags", CapabilityDefinition.CapabilityTarget.BOTH), + new CapabilityDefinition(CapabilityKeys.TAGS, CapabilityDefinition.CapabilityTarget.BOTH), + new CapabilityDefinition(CapabilityKeys.BUILD_TAGS, CapabilityDefinition.CapabilityTarget.BOTH), // Driver and version capabilities - new CapabilityDefinition("driver_version", - Arrays.asList("driverVersion", "driver"), + new CapabilityDefinition(CapabilityKeys.DRIVER_VERSION, + Arrays.asList(CapabilityKeys.DRIVER_VERSION_ALIAS, CapabilityKeys.DRIVER), CapabilityDefinition.CapabilityTarget.BOTH), // Resolution - new CapabilityDefinition("resolution", - Arrays.asList("viewport"), + new CapabilityDefinition(CapabilityKeys.RESOLUTION, + Arrays.asList(CapabilityKeys.VIEWPORT), CapabilityDefinition.CapabilityTarget.BOTH), // Extension loading - new CapabilityDefinition("lambda:loadExtension", - Arrays.asList("loadExtension"), + new CapabilityDefinition(CapabilityKeys.LAMBDA_LOAD_EXTENSION, + Arrays.asList(CapabilityKeys.LOAD_EXTENSION), CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), // Logging capabilities - new CapabilityDefinition("commandLog", - Arrays.asList("commandLogs"), + new CapabilityDefinition(CapabilityKeys.COMMAND_LOG, + Arrays.asList(CapabilityKeys.COMMAND_LOGS), CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), - new CapabilityDefinition("systemLog", - Arrays.asList("seleniumLogs"), + new CapabilityDefinition(CapabilityKeys.SYSTEM_LOG, + Arrays.asList(CapabilityKeys.SELENIUM_LOGS), CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), // Network capabilities - new CapabilityDefinition("network.http2", CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), - new CapabilityDefinition("DisableXFHeaders", CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), - new CapabilityDefinition("network.debug", CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), - new CapabilityDefinition("ignoreFfOptionsArgs", CapabilityDefinition.CapabilityTarget.DESIRED_CAPABILITIES), - new CapabilityDefinition("updateBuildStatusOnSuccess", CapabilityDefinition.CapabilityTarget.DESIRED_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) ); } } From b7d6e568b50e233e391ecc4b9d6ccbbfdc7a4d7d Mon Sep 17 00:00:00 2001 From: Abhishek Gadekar Date: Wed, 10 Dec 2025 13:57:15 +0530 Subject: [PATCH 5/8] fix tunnel syncronization --- .../selenium/agent/RemoteWebDriverAdvice.java | 67 ++++++++++++++----- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/lambdatest/selenium/agent/RemoteWebDriverAdvice.java b/src/main/java/com/lambdatest/selenium/agent/RemoteWebDriverAdvice.java index fa9eb69..a9aa999 100644 --- a/src/main/java/com/lambdatest/selenium/agent/RemoteWebDriverAdvice.java +++ b/src/main/java/com/lambdatest/selenium/agent/RemoteWebDriverAdvice.java @@ -26,9 +26,6 @@ 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(); @@ -228,26 +225,64 @@ public static void enhanceCapabilities(MutableCapabilities capabilities) { } if (tunnelInCapabilities) { - // Tunnel is enabled - check if it's running and add tunnel name + // 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 (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); + } } } + + // 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] Warning: Error checking tunnel status: " + e.getMessage()); + System.err.println("[LambdaTest SDK] ERROR: Tunnel handling failed: " + e.getMessage()); + e.printStackTrace(); + throw new RuntimeException("Tunnel setup failed: " + e.getMessage(), e); } } } From 3a5acd60bd97aeef64f74715e6ab8587e9cc54bd Mon Sep 17 00:00:00 2001 From: Abhishek Gadekar Date: Wed, 10 Dec 2025 15:09:39 +0530 Subject: [PATCH 6/8] add tunnel name --- .../lambdatest/selenium/agent/RemoteWebDriverAdvice.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/lambdatest/selenium/agent/RemoteWebDriverAdvice.java b/src/main/java/com/lambdatest/selenium/agent/RemoteWebDriverAdvice.java index a9aa999..45bfcd3 100644 --- a/src/main/java/com/lambdatest/selenium/agent/RemoteWebDriverAdvice.java +++ b/src/main/java/com/lambdatest/selenium/agent/RemoteWebDriverAdvice.java @@ -210,9 +210,7 @@ public static void enhanceCapabilities(MutableCapabilities capabilities) { } } - // Ensure lt:options is updated in capabilities - capabilities.setCapability("lt:options", ltOptions); - + // 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"); @@ -287,6 +285,10 @@ public static void enhanceCapabilities(MutableCapabilities capabilities) { } } + // 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(); From b63bbbbd1eea583f972fa988e7420375aee5f62b Mon Sep 17 00:00:00 2001 From: Abhishek Gadekar Date: Thu, 11 Dec 2025 19:24:35 +0530 Subject: [PATCH 7/8] add ci --- .github/workflows/pre-prod-release.yml | 41 ++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/pre-prod-release.yml diff --git a/.github/workflows/pre-prod-release.yml b/.github/workflows/pre-prod-release.yml new file mode 100644 index 0000000..b442453 --- /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 8 + uses: actions/setup-java@v4 + with: + java-version: '8' + 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 + From 2dc064bb01b2983657822f55ba8f106877a37051 Mon Sep 17 00:00:00 2001 From: Abhishek Gadekar Date: Thu, 11 Dec 2025 19:26:48 +0530 Subject: [PATCH 8/8] update base java --- .github/workflows/pre-prod-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pre-prod-release.yml b/.github/workflows/pre-prod-release.yml index b442453..4e671a5 100644 --- a/.github/workflows/pre-prod-release.yml +++ b/.github/workflows/pre-prod-release.yml @@ -13,10 +13,10 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up JDK 8 + - name: Set up JDK 11 uses: actions/setup-java@v4 with: - java-version: '8' + java-version: '11' distribution: 'temurin' cache: maven