Skip to content

Commit 583fbd8

Browse files
fix for methods + tunnel + enhanchments
1 parent 423604d commit 583fbd8

File tree

24 files changed

+588
-525
lines changed

24 files changed

+588
-525
lines changed
100 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
#Sun Nov 02 07:31:08 IST 2025
2-
gradle.version=8.5
1+
#Sun Nov 02 16:54:31 IST 2025
2+
gradle.version=8.10
Binary file not shown.

.gradle/nb-cache/subprojects.ser

0 Bytes
Binary file not shown.

pom.xml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
<groupId>com.lambdatest</groupId>
88
<artifactId>lambdatest-java-selenium-sdk</artifactId>
99
<!-- ⚠️ IMPORTANT: Keep this version in sync with gradle.properties -->
10-
<!-- Single source of truth: gradle.properties (line 2) -->
11-
<version>1.0.4</version>
10+
<version>1.0.1</version>
1211
<packaging>jar</packaging>
1312

1413
<name>LambdaTest Selenium SDK</name>

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

Lines changed: 118 additions & 302 deletions
Large diffs are not rendered by default.
Lines changed: 55 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
package com.lambdatest.selenium.agent;
22

3-
import java.net.URL;
43
import java.util.Map;
54

6-
import org.openqa.selenium.Capabilities;
75
import org.openqa.selenium.MutableCapabilities;
86

97
import com.lambdatest.selenium.lambdatest.LambdaTestConfig;
8+
import com.lambdatest.selenium.lambdatest.SessionThreadManager;
109

11-
import net.bytebuddy.asm.Advice;
12-
10+
/**
11+
* ByteBuddy Advice for RemoteWebDriver constructor interception.
12+
*
13+
* This class enhances capabilities before RemoteWebDriver is created,
14+
* adding LambdaTest-specific configuration from lambdatest.yml.
15+
*/
1316
public class RemoteWebDriverAdvice {
1417

1518
// Track which capabilities objects have been enhanced to prevent double-processing
@@ -24,77 +27,16 @@ public class RemoteWebDriverAdvice {
2427

2528
// Flag to warn only once about missing tunnel (avoid spam in parallel execution)
2629
private static volatile boolean warnedAboutMissingTunnel = false;
27-
28-
@Advice.OnMethodEnter
29-
static void onEnter(@Advice.Argument(0) URL hubUrl, @Advice.Argument(1) Capabilities capabilities) {
30-
31-
32-
// Check if capabilities can be modified
33-
if (!(capabilities instanceof MutableCapabilities)) {
34-
return;
35-
}
36-
37-
MutableCapabilities mutableCapabilities = (MutableCapabilities) capabilities;
38-
39-
// Only intercept if it's a LambdaTest URL or if lt:options is present
40-
boolean isLambdaTestUrl = hubUrl.toString().contains("lambdatest.com");
41-
boolean hasLtOptions = capabilities.asMap().containsKey("lt:options");
42-
43-
if (isLambdaTestUrl || hasLtOptions) {
44-
45-
try {
46-
// Check if SDK capabilities are stored by TestNG listener
47-
String storedCapabilities = System.getProperty("lambdatest.sdk.capabilities");
48-
if (storedCapabilities != null) {
49-
50-
// Load configuration from lambdatest.yml
51-
LambdaTestConfig config = LambdaTestConfig.getInstance();
52-
Map<String, Object> sdkCapMap = config.getCapabilitiesFromYaml().asMap();
53-
Map<String, Object> userCapMap = mutableCapabilities.asMap();
54-
55-
// Add missing capabilities from SDK config
56-
for (Map.Entry<String, Object> entry : sdkCapMap.entrySet()) {
57-
String key = entry.getKey();
58-
Object value = entry.getValue();
59-
60-
if (!userCapMap.containsKey(key)) {
61-
mutableCapabilities.setCapability(key, value);
62-
}
63-
}
64-
65-
// Ensure lt:options contains credentials
66-
if (userCapMap.containsKey("lt:options")) {
67-
Map<String, Object> ltOptions = (Map<String, Object>) userCapMap.get("lt:options");
68-
if (sdkCapMap.containsKey("lt:options")) {
69-
Map<String, Object> sdkLtOptions = (Map<String, Object>) sdkCapMap.get("lt:options");
70-
ltOptions.putAll(sdkLtOptions);
71-
}
72-
}
73-
74-
75-
} else {
76-
}
77-
78-
} catch (Exception e) {
79-
}
80-
} else {
81-
}
82-
}
83-
84-
/**
85-
* Set flag to indicate SDK is creating RemoteWebDriver internally.
86-
* This prevents recursive enhancement.
87-
*/
88-
public static void setInternalDriverCreation(boolean value) {
89-
internalDriverCreation.set(value);
90-
}
9130

31+
// Session-thread manager for validating thread affinity
32+
private static final SessionThreadManager sessionThreadManager = SessionThreadManager.getInstance();
33+
9234
/**
9335
* Static method to enhance capabilities that can be called from ASM bytecode.
94-
* This is the ACTUAL method that gets called by the agent's bytecode transformation.
36+
* This is called by RemoteWebDriverBytecodeTransformer.
9537
*/
9638
public static void enhanceCapabilities(MutableCapabilities capabilities) {
97-
// Skip if this is an internal SDK driver creation (e.g., from ChromeDriverAdvice)
39+
// Skip if this is an internal SDK driver creation
9840
if (internalDriverCreation.get()) {
9941
System.out.println("[LambdaTest SDK RemoteWebDriverAdvice] Skipping enhancement for internal driver creation");
10042
return;
@@ -114,35 +56,41 @@ public static void enhanceCapabilities(MutableCapabilities capabilities) {
11456
}
11557
processedCapabilities.add(capabilitiesHash);
11658

59+
// Log thread information for debugging
60+
long threadId = Thread.currentThread().getId();
61+
String threadName = Thread.currentThread().getName();
62+
System.out.println(String.format(
63+
"[LambdaTest SDK RemoteWebDriverAdvice] Enhancing capabilities on thread %d/%s",
64+
threadId, threadName));
65+
11766
// Set re-entrance guard
11867
isEnhancing.set(true);
11968

12069
try {
121-
12270
// Load configuration from lambdatest.yml
12371
LambdaTestConfig config = LambdaTestConfig.getInstance();
12472
Map<String, Object> sdkCapMap = config.getCapabilitiesFromYaml().asMap();
12573
Map<String, Object> userCapMap = capabilities.asMap();
12674

127-
// Add missing capabilities from SDK config
128-
for (Map.Entry<String, Object> entry : sdkCapMap.entrySet()) {
129-
String key = entry.getKey();
130-
Object value = entry.getValue();
131-
132-
if (!userCapMap.containsKey(key)) {
133-
capabilities.setCapability(key, value);
134-
}
75+
// Add missing capabilities from SDK config
76+
for (Map.Entry<String, Object> entry : sdkCapMap.entrySet()) {
77+
String key = entry.getKey();
78+
Object value = entry.getValue();
79+
80+
if (!userCapMap.containsKey(key)) {
81+
capabilities.setCapability(key, value);
82+
}
83+
}
84+
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);
13591
}
13692

137-
// Ensure lt:options contains credentials
138-
if (userCapMap.containsKey("lt:options")) {
139-
Map<String, Object> ltOptions = (Map<String, Object>) userCapMap.get("lt:options");
140-
if (sdkCapMap.containsKey("lt:options")) {
141-
Map<String, Object> sdkLtOptions = (Map<String, Object>) sdkCapMap.get("lt:options");
142-
ltOptions.putAll(sdkLtOptions);
143-
}
144-
145-
// Check if tunnel is enabled in YAML/capabilities only (no environment variable check)
93+
// Check if tunnel is enabled in capabilities
14694
if (ltOptions.containsKey("tunnel")) {
14795
Object tunnelValue = ltOptions.get("tunnel");
14896
boolean tunnelInCapabilities = false;
@@ -154,7 +102,7 @@ public static void enhanceCapabilities(MutableCapabilities capabilities) {
154102
}
155103

156104
if (tunnelInCapabilities) {
157-
// Tunnel is enabled in YAML - check if it's running and add tunnel name
105+
// Tunnel is enabled - check if it's running and add tunnel name
158106
try {
159107
com.lambdatest.selenium.tunnel.TunnelManager tunnelManager =
160108
com.lambdatest.selenium.tunnel.TunnelManager.getInstance();
@@ -165,7 +113,7 @@ public static void enhanceCapabilities(MutableCapabilities capabilities) {
165113
ltOptions.put("tunnelName", tunnelName);
166114
}
167115
} else {
168-
// Tunnel configured in YAML but not running - warn but allow test to continue
116+
// Tunnel configured but not running - warn once
169117
if (!warnedAboutMissingTunnel) {
170118
warnedAboutMissingTunnel = true;
171119
System.err.println("[LambdaTest SDK] WARNING: tunnel=true in YAML but no tunnel is running.");
@@ -177,22 +125,32 @@ public static void enhanceCapabilities(MutableCapabilities capabilities) {
177125
}
178126
}
179127
}
180-
}
128+
}
181129

182130
} catch (Exception e) {
183131
System.err.println("[LambdaTest SDK RemoteWebDriverAdvice] Error enhancing capabilities: " + e.getMessage());
184132
e.printStackTrace();
185133
} finally {
186-
// Always clear the re-entrance guard and clean up ThreadLocal
134+
// Always clear the re-entrance guard
187135
isEnhancing.set(false);
188-
// Clean up ThreadLocal to prevent memory leaks in long-running applications
189-
// Note: We don't remove internalDriverCreation here as it might still be needed
136+
137+
// Log completion with thread info
138+
System.out.println(String.format(
139+
"[LambdaTest SDK RemoteWebDriverAdvice] Capability enhancement completed on thread %d/%s",
140+
Thread.currentThread().getId(), Thread.currentThread().getName()));
190141
}
191142
}
192143

144+
/**
145+
* Set flag to indicate SDK is creating RemoteWebDriver internally.
146+
* This prevents recursive enhancement.
147+
*/
148+
public static void setInternalDriverCreation(boolean value) {
149+
internalDriverCreation.set(value);
150+
}
151+
193152
/**
194153
* Clean up all ThreadLocal variables to prevent memory leaks.
195-
* Should be called after all tests complete.
196154
*/
197155
public static void cleanupThreadLocals() {
198156
try {
@@ -202,4 +160,5 @@ public static void cleanupThreadLocals() {
202160
// Ignore cleanup errors
203161
}
204162
}
205-
}
163+
}
164+

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

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
*/
2020
public class LambdaTestConfig {
2121

22-
private static LambdaTestConfig instance;
22+
private static volatile LambdaTestConfig instance; // volatile for double-checked locking
23+
private static final Object instanceLock = new Object(); // Lock for singleton initialization
2324
private Map<String, Object> config;
2425

2526
// Static block to enable framework-level capability enhancement
@@ -36,11 +37,19 @@ private LambdaTestConfig() {
3637
loadConfig();
3738
}
3839

40+
/**
41+
* Thread-safe singleton getInstance using double-checked locking.
42+
* Critical for high-parallelism scenarios (e.g., parallel=50).
43+
*/
3944
public static LambdaTestConfig getInstance() {
40-
if (instance == null) {
41-
instance = new LambdaTestConfig();
42-
// Automatically enhance any existing MutableCapabilities objects
43-
enhanceExistingCapabilities();
45+
if (instance == null) { // First check (no locking)
46+
synchronized (instanceLock) { // Acquire lock
47+
if (instance == null) { // Second check (with locking)
48+
instance = new LambdaTestConfig();
49+
// Automatically enhance any existing MutableCapabilities objects
50+
enhanceExistingCapabilities();
51+
}
52+
}
4453
}
4554
return instance;
4655
}
@@ -124,6 +133,12 @@ public static void injectCapabilities(MutableCapabilities capabilities) {
124133
}
125134

126135
private void loadConfig() {
136+
long startTime = System.currentTimeMillis();
137+
java.util.logging.Logger logger = java.util.logging.Logger.getLogger(getClass().getName());
138+
139+
logger.fine(String.format("[Thread-%d/%s] Loading LambdaTest configuration...",
140+
Thread.currentThread().getId(), Thread.currentThread().getName()));
141+
127142
Yaml yaml = new Yaml();
128143

129144
// Try multiple locations for lambdatest.yml
@@ -162,6 +177,10 @@ private void loadConfig() {
162177
config = rawConfig;
163178
}
164179

180+
long duration = System.currentTimeMillis() - startTime;
181+
logger.fine(String.format("[Thread-%d/%s] Configuration loaded in %dms",
182+
Thread.currentThread().getId(), Thread.currentThread().getName(), duration));
183+
165184
return; // Successfully loaded, exit
166185
}
167186

@@ -172,6 +191,7 @@ private void loadConfig() {
172191

173192
// If we get here, no config file was found
174193
config = new HashMap<>();
194+
logger.fine("No configuration file found, using empty config");
175195
}
176196

177197
/**

0 commit comments

Comments
 (0)