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)
+ );
+ }
+}
+