Skip to content

Commit 3ea28fc

Browse files
add all capabilities for selenium3 & 4
1 parent 4fdae01 commit 3ea28fc

File tree

12 files changed

+599
-63
lines changed

12 files changed

+599
-63
lines changed
50 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DDDFE19D9303B09E1BA21B6DA564DA286AEAEBE5DFC943499A81CFCD41C58B16

dependency-reduced-pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<groupId>com.lambdatest</groupId>
55
<artifactId>lambdatest-java-selenium-sdk</artifactId>
66
<name>LambdaTest Selenium SDK</name>
7-
<version>1.0.1</version>
7+
<version>1.0.2</version>
88
<description>A Java SDK for integrating Selenium tests with LambdaTest cloud platform</description>
99
<url>https://www.lambdatest.com</url>
1010
<licenses>

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.lambdatest</groupId>
88
<artifactId>lambdatest-java-selenium-sdk</artifactId>
9-
<version>1.0.1</version>
9+
<version>1.0.2</version>
1010
<packaging>jar</packaging>
1111

1212
<name>LambdaTest Selenium SDK</name>

src/main/java/com/lambdatest/selenium/agent/RemoteWebDriverAdvice.java

Lines changed: 80 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.lambdatest.selenium.agent;
22

33
import java.util.Map;
4+
import java.util.Set;
45

56
import org.openqa.selenium.MutableCapabilities;
67

@@ -30,6 +31,10 @@ public class RemoteWebDriverAdvice {
3031

3132
// Session-thread manager for validating thread affinity
3233
private static final SessionThreadManager sessionThreadManager = SessionThreadManager.getInstance();
34+
35+
// LambdaTest-specific keys that should ONLY be in lt:options, not at W3C top level
36+
// These keys cause W3C validation errors in Selenium 4 if set at top level
37+
private static final Set<String> LT_SPECIFIC_KEYS = Set.of("build", "name", "projectName", "resolution");
3338

3439
/**
3540
* Static method to enhance capabilities that can be called from ASM bytecode.
@@ -72,57 +77,96 @@ public static void enhanceCapabilities(MutableCapabilities capabilities) {
7277
Map<String, Object> sdkCapMap = config.getCapabilitiesFromYaml().asMap();
7378
Map<String, Object> userCapMap = capabilities.asMap();
7479

75-
// Add missing capabilities from SDK config
80+
// Ensure lt:options exists
81+
Map<String, Object> ltOptions;
82+
if (userCapMap.containsKey("lt:options")) {
83+
ltOptions = (Map<String, Object>) userCapMap.get("lt:options");
84+
} else {
85+
ltOptions = new java.util.HashMap<>();
86+
capabilities.setCapability("lt:options", ltOptions);
87+
}
88+
89+
// Merge SDK lt:options into user lt:options first
90+
if (sdkCapMap.containsKey("lt:options")) {
91+
Map<String, Object> sdkLtOptions = (Map<String, Object>) sdkCapMap.get("lt:options");
92+
ltOptions.putAll(sdkLtOptions);
93+
}
94+
95+
// Add missing capabilities from SDK config, but filter out LT-specific keys
96+
// LT-specific keys (build, name, projectName, resolution) should ONLY be in lt:options
7697
for (Map.Entry<String, Object> entry : sdkCapMap.entrySet()) {
7798
String key = entry.getKey();
7899
Object value = entry.getValue();
79100

101+
// Skip lt:options (already handled above)
102+
if ("lt:options".equals(key)) {
103+
continue;
104+
}
105+
106+
// Skip LT-specific keys - they should only be in lt:options, not at top level
107+
if (LT_SPECIFIC_KEYS.contains(key)) {
108+
// Ensure they're in lt:options instead
109+
if (!ltOptions.containsKey(key)) {
110+
ltOptions.put(key, value);
111+
}
112+
continue;
113+
}
114+
115+
// Add valid W3C capabilities to top level
80116
if (!userCapMap.containsKey(key)) {
81117
capabilities.setCapability(key, value);
82118
}
83119
}
84120

85-
// Ensure lt:options contains credentials
86-
if (userCapMap.containsKey("lt:options")) {
87-
Map<String, Object> ltOptions = (Map<String, Object>) userCapMap.get("lt:options");
88-
if (sdkCapMap.containsKey("lt:options")) {
89-
Map<String, Object> sdkLtOptions = (Map<String, Object>) sdkCapMap.get("lt:options");
90-
ltOptions.putAll(sdkLtOptions);
121+
// Clean up: Remove any LT-specific keys that might have been set at top level
122+
// These should only be in lt:options for W3C compliance
123+
for (String ltKey : LT_SPECIFIC_KEYS) {
124+
if (capabilities.asMap().containsKey(ltKey)) {
125+
Object ltValue = capabilities.getCapability(ltKey);
126+
// Ensure it's in lt:options
127+
if (!ltOptions.containsKey(ltKey)) {
128+
ltOptions.put(ltKey, ltValue);
129+
}
130+
// Remove from top level
131+
capabilities.asMap().remove(ltKey);
91132
}
133+
}
134+
135+
// Ensure lt:options is updated in capabilities
136+
capabilities.setCapability("lt:options", ltOptions);
137+
138+
// Check if tunnel is enabled in capabilities
139+
if (ltOptions.containsKey("tunnel")) {
140+
Object tunnelValue = ltOptions.get("tunnel");
141+
boolean tunnelInCapabilities = false;
92142

93-
// Check if tunnel is enabled in capabilities
94-
if (ltOptions.containsKey("tunnel")) {
95-
Object tunnelValue = ltOptions.get("tunnel");
96-
boolean tunnelInCapabilities = false;
143+
if (tunnelValue instanceof Boolean) {
144+
tunnelInCapabilities = (Boolean) tunnelValue;
145+
} else if (tunnelValue instanceof String) {
146+
tunnelInCapabilities = Boolean.parseBoolean((String) tunnelValue);
147+
}
97148

98-
if (tunnelValue instanceof Boolean) {
99-
tunnelInCapabilities = (Boolean) tunnelValue;
100-
} else if (tunnelValue instanceof String) {
101-
tunnelInCapabilities = Boolean.parseBoolean((String) tunnelValue);
102-
}
149+
if (tunnelInCapabilities) {
150+
// Tunnel is enabled - check if it's running and add tunnel name
151+
try {
152+
com.lambdatest.selenium.tunnel.TunnelManager tunnelManager =
153+
com.lambdatest.selenium.tunnel.TunnelManager.getInstance();
103154

104-
if (tunnelInCapabilities) {
105-
// Tunnel is enabled - check if it's running and add tunnel name
106-
try {
107-
com.lambdatest.selenium.tunnel.TunnelManager tunnelManager =
108-
com.lambdatest.selenium.tunnel.TunnelManager.getInstance();
109-
110-
if (tunnelManager.isTunnelRunning()) {
111-
String tunnelName = tunnelManager.getTunnelName();
112-
if (tunnelName != null && !tunnelName.trim().isEmpty()) {
113-
ltOptions.put("tunnelName", tunnelName);
114-
}
115-
} else {
116-
// Tunnel configured but not running - warn once
117-
if (!warnedAboutMissingTunnel) {
118-
warnedAboutMissingTunnel = true;
119-
System.err.println("[LambdaTest SDK] WARNING: tunnel=true in YAML but no tunnel is running.");
120-
System.err.println("[LambdaTest SDK] Tests will continue without tunnel. This may cause connection issues for local resources.");
121-
}
155+
if (tunnelManager.isTunnelRunning()) {
156+
String tunnelName = tunnelManager.getTunnelName();
157+
if (tunnelName != null && !tunnelName.trim().isEmpty()) {
158+
ltOptions.put("tunnelName", tunnelName);
159+
}
160+
} else {
161+
// Tunnel configured but not running - warn once
162+
if (!warnedAboutMissingTunnel) {
163+
warnedAboutMissingTunnel = true;
164+
System.err.println("[LambdaTest SDK] WARNING: tunnel=true in YAML but no tunnel is running.");
165+
System.err.println("[LambdaTest SDK] Tests will continue without tunnel. This may cause connection issues for local resources.");
122166
}
123-
} catch (Exception e) {
124-
System.err.println("[LambdaTest SDK] Warning: Error checking tunnel status: " + e.getMessage());
125167
}
168+
} catch (Exception e) {
169+
System.err.println("[LambdaTest SDK] Warning: Error checking tunnel status: " + e.getMessage());
126170
}
127171
}
128172
}

src/main/java/com/lambdatest/selenium/lambdatest/LambdaTestConfig.java

Lines changed: 123 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
import org.yaml.snakeyaml.Yaml;
1111

1212
import com.lambdatest.selenium.tunnel.TunnelManager;
13+
import com.lambdatest.selenium.lambdatest.capabilities.CapabilityProcessor;
14+
import com.lambdatest.selenium.lambdatest.capabilities.Selenium3Capabilities;
15+
import com.lambdatest.selenium.lambdatest.capabilities.Selenium4Capabilities;
16+
import com.lambdatest.selenium.lambdatest.capabilities.BrowserOptionsCapabilities;
1317

1418
/**
1519
* YAML configuration reader for LambdaTest Selenium SDK.
@@ -144,6 +148,7 @@ private void loadConfig() {
144148
// Try multiple locations for lambdatest.yml
145149
String[] locations = {
146150
"lambdatest.yml", // Root directory
151+
"lambdatest.yaml"
147152
};
148153

149154
for (String location : locations) {
@@ -290,54 +295,147 @@ public DesiredCapabilities getCapabilities(DesiredCapabilities codeCapabilities)
290295

291296
/**
292297
* Get capabilities from YAML configuration.
298+
* Supports all Selenium 3, Selenium 4, and LambdaTest advanced capabilities.
299+
*
300+
* Supported Browsers (Selenium 3 & 4):
301+
* - Chrome
302+
* - Firefox
303+
* - Safari
304+
* - MS Edge (Microsoft Edge)
305+
* - Opera
306+
* - IE (Internet Explorer)
307+
*
308+
* Browser-specific options are supported for both Selenium 3 and 4:
309+
* - Chrome: chromeOptions / goog:chromeOptions
310+
* - Firefox: firefoxOptions / moz:firefoxOptions
311+
* - Edge: edgeOptions / ms:edgeOptions
312+
* - Safari: safariOptions / safari:options
313+
* - Opera: operaOptions
314+
* - IE: ieOptions / se:ieOptions
315+
*
316+
* Selenium 3 capabilities are set directly on DesiredCapabilities for backwards compatibility.
317+
* Selenium 4 capabilities use the W3C standard format with lt:options.
293318
*/
294319
public DesiredCapabilities getCapabilitiesFromYaml() {
295320
DesiredCapabilities capabilities = new DesiredCapabilities();
296-
297-
// LambdaTest options from YAML
298321
Map<String, Object> ltOptions = new HashMap<>();
322+
CapabilityProcessor processor = new CapabilityProcessor(config, capabilities, ltOptions);
323+
324+
// ============================================================
325+
// 1. W3C Standard Browser Capabilities (Selenium 3 & 4)
326+
// ============================================================
327+
processW3CBrowserCapabilities(capabilities);
328+
329+
// ============================================================
330+
// 2. Browser-Specific Options (Chrome, Firefox, Edge, etc.)
331+
// ============================================================
332+
BrowserOptionsCapabilities.processBrowserOptions(config, capabilities);
333+
334+
// ============================================================
335+
// 3. LambdaTest Credentials (Required)
336+
// ============================================================
337+
processCredentials(ltOptions);
338+
339+
// ============================================================
340+
// 4. Selenium 3 Capabilities (for backwards compatibility)
341+
// ============================================================
342+
processor.process(Selenium3Capabilities.getDefinitions());
343+
344+
// Handle special case: version -> browserVersion mapping
345+
processVersionCapability(capabilities);
346+
347+
// ============================================================
348+
// 5. Selenium 4 / W3C Capabilities (LambdaTest advanced)
349+
// ============================================================
350+
processor.process(Selenium4Capabilities.getDefinitions());
351+
352+
// ============================================================
353+
// 6. Special Cases (require custom handling)
354+
// ============================================================
355+
processSpecialCases(capabilities, ltOptions);
356+
357+
// ============================================================
358+
// 7. Finalize: Set lt:options on capabilities
359+
// ============================================================
360+
capabilities.setCapability("lt:options", ltOptions);
299361

300-
// Basic browser config
362+
return capabilities;
363+
}
364+
365+
/**
366+
* Process W3C standard browser capabilities (browserName, browserVersion, platformName).
367+
*/
368+
private void processW3CBrowserCapabilities(DesiredCapabilities capabilities) {
369+
// browserName (case-sensitive, mandatory)
301370
if (config.containsKey("browserName")) {
302371
capabilities.setCapability("browserName", config.get("browserName"));
372+
} else if (config.containsKey("browser")) {
373+
capabilities.setCapability("browserName", config.get("browser"));
303374
}
375+
376+
// browserVersion
304377
if (config.containsKey("browserVersion")) {
305378
capabilities.setCapability("browserVersion", config.get("browserVersion"));
379+
} else if (config.containsKey("version")) {
380+
capabilities.setCapability("browserVersion", config.get("version"));
306381
}
382+
383+
// platformName
307384
if (config.containsKey("platformName")) {
308385
capabilities.setCapability("platformName", config.get("platformName"));
386+
} else if (config.containsKey("platform")) {
387+
capabilities.setCapability("platformName", config.get("platform"));
388+
} else if (config.containsKey("OS")) {
389+
capabilities.setCapability("platformName", config.get("OS"));
309390
}
310-
311-
// LambdaTest credentials (required) - put only in lt:options for W3C compliance
391+
}
392+
393+
/**
394+
* Process LambdaTest credentials.
395+
*/
396+
private void processCredentials(Map<String, Object> ltOptions) {
312397
try {
313398
String username = getUsername();
314399
String accessKey = getAccessKey();
315400
ltOptions.put("user", username);
316401
ltOptions.put("accessKey", accessKey);
317402
} catch (Exception e) {
403+
// Credentials will be required when creating WebDriver
404+
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");
318405
}
319-
320-
// LambdaTest specific options
321-
if (config.containsKey("build")) ltOptions.put("build", config.get("build"));
322-
if (config.containsKey("project")) ltOptions.put("project", config.get("project"));
323-
if (config.containsKey("name")) ltOptions.put("name", config.get("name"));
324-
if (config.containsKey("video")) ltOptions.put("video", config.get("video"));
325-
if (config.containsKey("network")) ltOptions.put("network", config.get("network"));
326-
if (config.containsKey("console")) ltOptions.put("console", config.get("console"));
327-
if (config.containsKey("visual")) ltOptions.put("visual", config.get("visual"));
328-
if (config.containsKey("resolution")) ltOptions.put("resolution", config.get("resolution"));
329-
if (config.containsKey("tunnel")) {
330-
Object tunnelValue = config.get("tunnel");
331-
ltOptions.put("tunnel", tunnelValue);
332-
333-
// Note: Tunnel will be started when WebDriver is actually created
334-
// This prevents starting it too early before tests run
406+
}
407+
408+
/**
409+
* Handle version capability special case (Selenium 3 compatibility).
410+
* version should be set on DesiredCapabilities AND as browserVersion for W3C.
411+
*/
412+
private void processVersionCapability(DesiredCapabilities capabilities) {
413+
if (config.containsKey("version") && !config.containsKey("browserVersion")) {
414+
Object versionValue = config.get("version");
415+
capabilities.setCapability("version", versionValue); // Selenium 3
416+
capabilities.setCapability("browserVersion", versionValue); // W3C
417+
}
418+
}
419+
420+
/**
421+
* Process special cases that require custom logic.
422+
*/
423+
private void processSpecialCases(DesiredCapabilities capabilities, Map<String, Object> ltOptions) {
424+
// lambda:userFiles - set directly on capabilities (not in lt:options)
425+
if (config.containsKey("lambda:userFiles")) {
426+
capabilities.setCapability("lambda:userFiles", config.get("lambda:userFiles"));
427+
} else if (config.containsKey("userFiles")) {
428+
capabilities.setCapability("lambda:userFiles", config.get("userFiles"));
335429
}
336430

337-
capabilities.setCapability("lt:options", ltOptions);
338-
339-
340-
return capabilities;
431+
// project -> projectName mapping for Selenium 3, and project -> lt:options for Selenium 4
432+
if (config.containsKey("project")) {
433+
Object projectValue = config.get("project");
434+
ltOptions.put("project", projectValue); // Selenium 4
435+
if (!config.containsKey("projectName")) {
436+
capabilities.setCapability("projectName", projectValue); // Selenium 3
437+
}
438+
}
341439
}
342440

343441
/**

0 commit comments

Comments
 (0)