testdriverai 7.2.91 → 7.2.92
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/index.js +118 -50
- package/agent/lib/redraw.js +3 -14
- package/package.json +1 -1
package/agent/index.js
CHANGED
|
@@ -214,9 +214,6 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
214
214
|
// Ignore sandbox close errors during exit
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
|
-
|
|
218
|
-
// Clean up IDE session file
|
|
219
|
-
this.cleanupIdeSessionFile();
|
|
220
217
|
|
|
221
218
|
shouldRunPostrun =
|
|
222
219
|
!this.hasRunPostrun &&
|
|
@@ -2042,8 +2039,8 @@ ${regression}
|
|
|
2042
2039
|
const previewMode = this.config.TD_PREVIEW || "browser";
|
|
2043
2040
|
|
|
2044
2041
|
if (previewMode === "ide") {
|
|
2045
|
-
//
|
|
2046
|
-
this.
|
|
2042
|
+
// Send session to VS Code extension via HTTP
|
|
2043
|
+
this.sendIdeSessionNotification(urlToOpen, data);
|
|
2047
2044
|
} else if (previewMode !== "none") {
|
|
2048
2045
|
// Open in browser (default behavior)
|
|
2049
2046
|
this.emitter.emit(events.showWindow, urlToOpen);
|
|
@@ -2052,64 +2049,135 @@ ${regression}
|
|
|
2052
2049
|
}
|
|
2053
2050
|
}
|
|
2054
2051
|
|
|
2055
|
-
//
|
|
2056
|
-
|
|
2052
|
+
// Find the VS Code instance that contains the test file
|
|
2053
|
+
findTargetIdeInstance(testFilePath) {
|
|
2057
2054
|
const fs = require("fs");
|
|
2058
2055
|
const os = require("os");
|
|
2059
2056
|
const path = require("path");
|
|
2060
2057
|
|
|
2061
|
-
const
|
|
2062
|
-
const sessionsDir = path.join(sessionDir, "ide-sessions");
|
|
2058
|
+
const instancesDir = path.join(os.homedir(), ".testdriver", "ide-instances");
|
|
2063
2059
|
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
.replace(/\.[^/.]+$/, ""); // Remove file extension
|
|
2068
|
-
const sessionId = `${testFileName}-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
2069
|
-
const sessionFile = path.join(sessionsDir, `${sessionId}.json`);
|
|
2070
|
-
|
|
2071
|
-
try {
|
|
2072
|
-
// Ensure directories exist
|
|
2073
|
-
if (!fs.existsSync(sessionDir)) {
|
|
2074
|
-
fs.mkdirSync(sessionDir, { recursive: true });
|
|
2075
|
-
}
|
|
2076
|
-
if (!fs.existsSync(sessionsDir)) {
|
|
2077
|
-
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
2078
|
-
}
|
|
2060
|
+
if (!fs.existsSync(instancesDir)) {
|
|
2061
|
+
return null;
|
|
2062
|
+
}
|
|
2079
2063
|
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
os: data.os || this.sandboxOs || "linux",
|
|
2086
|
-
timestamp: Date.now(),
|
|
2087
|
-
};
|
|
2064
|
+
const files = fs.readdirSync(instancesDir);
|
|
2065
|
+
const normalizedTestPath = testFilePath ? path.normalize(testFilePath) : null;
|
|
2066
|
+
|
|
2067
|
+
let matchingInstance = null;
|
|
2068
|
+
let longestMatchLength = 0;
|
|
2088
2069
|
|
|
2089
|
-
|
|
2090
|
-
|
|
2070
|
+
for (const file of files) {
|
|
2071
|
+
if (!file.endsWith('.json')) continue;
|
|
2091
2072
|
|
|
2092
|
-
// Store session file path for cleanup on exit
|
|
2093
|
-
this._ideSessionFile = sessionFile;
|
|
2094
|
-
} catch (error) {
|
|
2095
|
-
logger.warn(`Failed to write IDE session file: ${error.message}`);
|
|
2096
|
-
}
|
|
2097
|
-
}
|
|
2098
|
-
|
|
2099
|
-
// Clean up IDE session file when test completes
|
|
2100
|
-
cleanupIdeSessionFile() {
|
|
2101
|
-
if (this._ideSessionFile) {
|
|
2102
|
-
const fs = require("fs");
|
|
2103
2073
|
try {
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2074
|
+
const registrationPath = path.join(instancesDir, file);
|
|
2075
|
+
const registration = JSON.parse(fs.readFileSync(registrationPath, 'utf-8'));
|
|
2076
|
+
|
|
2077
|
+
// Check if this instance is still alive (registration within last 60 seconds or process exists)
|
|
2078
|
+
const isRecent = Date.now() - registration.timestamp < 60000;
|
|
2079
|
+
|
|
2080
|
+
// Skip stale registrations
|
|
2081
|
+
if (!isRecent) {
|
|
2082
|
+
// Try to clean up stale file
|
|
2083
|
+
try { fs.unlinkSync(registrationPath); } catch {}
|
|
2084
|
+
continue;
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
// If we have a test file path, find the best matching workspace
|
|
2088
|
+
if (normalizedTestPath && registration.workspacePaths) {
|
|
2089
|
+
for (const workspacePath of registration.workspacePaths) {
|
|
2090
|
+
const normalizedWorkspace = path.normalize(workspacePath);
|
|
2091
|
+
if (normalizedTestPath.startsWith(normalizedWorkspace + path.sep) ||
|
|
2092
|
+
normalizedTestPath === normalizedWorkspace) {
|
|
2093
|
+
// Prefer longest match (most specific workspace)
|
|
2094
|
+
if (normalizedWorkspace.length > longestMatchLength) {
|
|
2095
|
+
longestMatchLength = normalizedWorkspace.length;
|
|
2096
|
+
matchingInstance = registration;
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
} else if (!matchingInstance) {
|
|
2101
|
+
// If no test file path, just use the first available instance
|
|
2102
|
+
matchingInstance = registration;
|
|
2107
2103
|
}
|
|
2108
2104
|
} catch (error) {
|
|
2109
|
-
// Ignore
|
|
2105
|
+
// Ignore malformed registration files
|
|
2110
2106
|
}
|
|
2111
|
-
this._ideSessionFile = null;
|
|
2112
2107
|
}
|
|
2108
|
+
|
|
2109
|
+
return matchingInstance;
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
// Send session notification to VS Code extension via HTTP
|
|
2113
|
+
sendIdeSessionNotification(debuggerUrl, data) {
|
|
2114
|
+
const http = require("http");
|
|
2115
|
+
const path = require("path");
|
|
2116
|
+
|
|
2117
|
+
const testFilePath = data.testFile || this.thisFile;
|
|
2118
|
+
const targetInstance = this.findTargetIdeInstance(testFilePath);
|
|
2119
|
+
|
|
2120
|
+
if (!targetInstance) {
|
|
2121
|
+
logger.warn("No VS Code instance found for IDE preview. Make sure VS Code with TestDriver extension is open.");
|
|
2122
|
+
// Fall back to browser
|
|
2123
|
+
this.emitter.emit(events.showWindow, debuggerUrl);
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
// Generate a unique session ID
|
|
2128
|
+
const testFileName = (testFilePath || "test")
|
|
2129
|
+
.split(path.sep).pop()
|
|
2130
|
+
.replace(/\.[^/.]+$/, "");
|
|
2131
|
+
const sessionId = `${testFileName}-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
2132
|
+
|
|
2133
|
+
const sessionData = {
|
|
2134
|
+
sessionId: sessionId,
|
|
2135
|
+
debuggerUrl: debuggerUrl,
|
|
2136
|
+
resolution: data.resolution || this.config.TD_RESOLUTION,
|
|
2137
|
+
testFile: testFilePath,
|
|
2138
|
+
os: data.os || this.sandboxOs || "linux",
|
|
2139
|
+
timestamp: Date.now(),
|
|
2140
|
+
};
|
|
2141
|
+
|
|
2142
|
+
const postData = JSON.stringify(sessionData);
|
|
2143
|
+
|
|
2144
|
+
const options = {
|
|
2145
|
+
hostname: '127.0.0.1',
|
|
2146
|
+
port: targetInstance.port,
|
|
2147
|
+
path: '/session',
|
|
2148
|
+
method: 'POST',
|
|
2149
|
+
headers: {
|
|
2150
|
+
'Content-Type': 'application/json',
|
|
2151
|
+
'Content-Length': Buffer.byteLength(postData)
|
|
2152
|
+
},
|
|
2153
|
+
timeout: 5000
|
|
2154
|
+
};
|
|
2155
|
+
|
|
2156
|
+
const req = http.request(options, (res) => {
|
|
2157
|
+
if (res.statusCode === 200) {
|
|
2158
|
+
logger.log(`IDE session notification sent to port ${targetInstance.port}`);
|
|
2159
|
+
} else {
|
|
2160
|
+
logger.warn(`IDE session notification failed with status ${res.statusCode}`);
|
|
2161
|
+
// Fall back to browser on failure
|
|
2162
|
+
this.emitter.emit(events.showWindow, debuggerUrl);
|
|
2163
|
+
}
|
|
2164
|
+
});
|
|
2165
|
+
|
|
2166
|
+
req.on('error', (error) => {
|
|
2167
|
+
logger.warn(`Failed to send IDE session notification: ${error.message}`);
|
|
2168
|
+
// Fall back to browser on error
|
|
2169
|
+
this.emitter.emit(events.showWindow, debuggerUrl);
|
|
2170
|
+
});
|
|
2171
|
+
|
|
2172
|
+
req.on('timeout', () => {
|
|
2173
|
+
req.destroy();
|
|
2174
|
+
logger.warn('IDE session notification timed out');
|
|
2175
|
+
// Fall back to browser on timeout
|
|
2176
|
+
this.emitter.emit(events.showWindow, debuggerUrl);
|
|
2177
|
+
});
|
|
2178
|
+
|
|
2179
|
+
req.write(postData);
|
|
2180
|
+
req.end();
|
|
2113
2181
|
}
|
|
2114
2182
|
|
|
2115
2183
|
async connectToSandboxService() {
|
package/agent/lib/redraw.js
CHANGED
|
@@ -8,7 +8,6 @@ const DEFAULT_REDRAW_OPTIONS = {
|
|
|
8
8
|
enabled: true, // Master switch to enable/disable redraw detection
|
|
9
9
|
screenRedraw: true, // Enable screen redraw detection
|
|
10
10
|
networkMonitor: false, // Enable network activity monitoring
|
|
11
|
-
noChangeTimeoutMs: 1500, // Exit early if no screen change detected after this time
|
|
12
11
|
};
|
|
13
12
|
|
|
14
13
|
// Factory function that creates redraw functionality with the provided system instance
|
|
@@ -242,7 +241,7 @@ const createRedraw = (
|
|
|
242
241
|
}
|
|
243
242
|
|
|
244
243
|
async function checkCondition(resolve, startTime, timeoutMs, options) {
|
|
245
|
-
const { enabled, screenRedraw, networkMonitor
|
|
244
|
+
const { enabled, screenRedraw, networkMonitor } = options;
|
|
246
245
|
|
|
247
246
|
// If redraw is disabled, resolve immediately
|
|
248
247
|
if (!enabled) {
|
|
@@ -260,9 +259,6 @@ const createRedraw = (
|
|
|
260
259
|
let diffFromInitial = 0;
|
|
261
260
|
let diffFromLast = 0;
|
|
262
261
|
let isTimeout = timeElapsed > timeoutMs;
|
|
263
|
-
|
|
264
|
-
// Early exit: if no screen change detected after noChangeTimeoutMs, assume action had no visual effect
|
|
265
|
-
const noChangeTimeout = screenRedraw && !hasChangedFromInitial && timeElapsed > noChangeTimeoutMs;
|
|
266
262
|
|
|
267
263
|
// Screen stability detection:
|
|
268
264
|
// 1. Check if screen has changed from initial (detect transition)
|
|
@@ -291,14 +287,8 @@ const createRedraw = (
|
|
|
291
287
|
lastScreenImage = nowImage;
|
|
292
288
|
}
|
|
293
289
|
|
|
294
|
-
// Screen is settled when
|
|
295
|
-
|
|
296
|
-
// 2. No change was detected after noChangeTimeoutMs (action had no visual effect)
|
|
297
|
-
const screenSettled = (hasChangedFromInitial && consecutiveFramesStable) || noChangeTimeout;
|
|
298
|
-
|
|
299
|
-
if (noChangeTimeout && !hasChangedFromInitial) {
|
|
300
|
-
emitter.emit(events.log.debug, `[redraw] No screen change detected after ${noChangeTimeoutMs}ms, settling early`);
|
|
301
|
-
}
|
|
290
|
+
// Screen is settled when it has changed from initial AND consecutive frames are now stable
|
|
291
|
+
const screenSettled = hasChangedFromInitial && consecutiveFramesStable;
|
|
302
292
|
|
|
303
293
|
// If screen redraw is disabled, consider it as "settled"
|
|
304
294
|
const effectiveScreenSettled = screenRedraw ? screenSettled : true;
|
|
@@ -355,7 +345,6 @@ const createRedraw = (
|
|
|
355
345
|
networkSettled: effectiveNetworkSettled,
|
|
356
346
|
isTimeout,
|
|
357
347
|
timeElapsed,
|
|
358
|
-
noChangeTimeout,
|
|
359
348
|
});
|
|
360
349
|
resolve("true");
|
|
361
350
|
} else {
|