testdriverai 6.0.18 → 6.0.19
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/events.js +1 -0
- package/agent/index.js +111 -0
- package/package.json +2 -1
- package/testdriver/examples/chrome-extension/lifecycle/provision.yaml +70 -48
- package/testdriver/examples/desktop/lifecycle/provision.yaml +55 -15
- package/testdriver/extended/dashcam-chrome.yaml +0 -8
- package/testdriver/extended/exec-pwsh-multiline.yaml +0 -10
- package/testdriver/extended/prompt-in-middle.yaml +0 -23
- package/testdriver/extended/prompt-nested.yaml +0 -6
package/agent/events.js
CHANGED
package/agent/index.js
CHANGED
|
@@ -15,6 +15,7 @@ const path = require("path");
|
|
|
15
15
|
const yaml = require("js-yaml");
|
|
16
16
|
const sanitizeFilename = require("sanitize-filename");
|
|
17
17
|
const { EventEmitter2 } = require("eventemitter2");
|
|
18
|
+
const diff = require("diff");
|
|
18
19
|
|
|
19
20
|
// global utilities
|
|
20
21
|
const generator = require("./lib/generator.js");
|
|
@@ -1117,11 +1118,110 @@ ${yml}
|
|
|
1117
1118
|
return;
|
|
1118
1119
|
}
|
|
1119
1120
|
|
|
1121
|
+
// Read existing file content for diff comparison
|
|
1122
|
+
let existingContent = "";
|
|
1123
|
+
let fileExists = false;
|
|
1124
|
+
try {
|
|
1125
|
+
if (fs.existsSync(filepath)) {
|
|
1126
|
+
existingContent = fs.readFileSync(filepath, "utf8");
|
|
1127
|
+
fileExists = true;
|
|
1128
|
+
}
|
|
1129
|
+
} catch {
|
|
1130
|
+
// File doesn't exist or can't be read, treat as empty
|
|
1131
|
+
existingContent = "";
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1120
1134
|
// write reply to /tmp/testdriver-summary.md
|
|
1121
1135
|
let regression = await generator.dumpToYML(
|
|
1122
1136
|
this.executionHistory,
|
|
1123
1137
|
this.session,
|
|
1124
1138
|
);
|
|
1139
|
+
|
|
1140
|
+
// Create diff if file exists and content has changed
|
|
1141
|
+
let diffResult = null;
|
|
1142
|
+
console.log("Checking for diff. File exists:", fileExists);
|
|
1143
|
+
console.log(
|
|
1144
|
+
"Content changed:",
|
|
1145
|
+
fileExists && existingContent !== regression,
|
|
1146
|
+
);
|
|
1147
|
+
if (fileExists) {
|
|
1148
|
+
console.log(
|
|
1149
|
+
"Existing content preview:",
|
|
1150
|
+
existingContent.substring(0, 100),
|
|
1151
|
+
);
|
|
1152
|
+
console.log("New content preview:", regression.substring(0, 100));
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
if (fileExists && existingContent !== regression) {
|
|
1156
|
+
console.log("Creating diff - content has changed");
|
|
1157
|
+
const patches = diff.structuredPatch(
|
|
1158
|
+
filepath,
|
|
1159
|
+
filepath,
|
|
1160
|
+
existingContent,
|
|
1161
|
+
regression,
|
|
1162
|
+
`${new Date().toISOString()} (before)`,
|
|
1163
|
+
`${new Date().toISOString()} (after)`,
|
|
1164
|
+
);
|
|
1165
|
+
|
|
1166
|
+
// Create source map-like information for VS Code
|
|
1167
|
+
const diffLines = diff.diffLines(existingContent, regression);
|
|
1168
|
+
const sourceMaps = [];
|
|
1169
|
+
let oldLineNumber = 1;
|
|
1170
|
+
let newLineNumber = 1;
|
|
1171
|
+
|
|
1172
|
+
diffLines.forEach((part) => {
|
|
1173
|
+
const lineCount = part.value.split("\n").length - 1;
|
|
1174
|
+
if (part.added) {
|
|
1175
|
+
sourceMaps.push({
|
|
1176
|
+
type: "addition",
|
|
1177
|
+
oldStart: oldLineNumber,
|
|
1178
|
+
oldEnd: oldLineNumber,
|
|
1179
|
+
newStart: newLineNumber,
|
|
1180
|
+
newEnd: newLineNumber + lineCount,
|
|
1181
|
+
content: part.value,
|
|
1182
|
+
lines: lineCount,
|
|
1183
|
+
});
|
|
1184
|
+
newLineNumber += lineCount;
|
|
1185
|
+
} else if (part.removed) {
|
|
1186
|
+
sourceMaps.push({
|
|
1187
|
+
type: "deletion",
|
|
1188
|
+
oldStart: oldLineNumber,
|
|
1189
|
+
oldEnd: oldLineNumber + lineCount,
|
|
1190
|
+
newStart: newLineNumber,
|
|
1191
|
+
newEnd: newLineNumber,
|
|
1192
|
+
content: part.value,
|
|
1193
|
+
lines: lineCount,
|
|
1194
|
+
});
|
|
1195
|
+
oldLineNumber += lineCount;
|
|
1196
|
+
} else {
|
|
1197
|
+
// unchanged
|
|
1198
|
+
sourceMaps.push({
|
|
1199
|
+
type: "unchanged",
|
|
1200
|
+
oldStart: oldLineNumber,
|
|
1201
|
+
oldEnd: oldLineNumber + lineCount,
|
|
1202
|
+
newStart: newLineNumber,
|
|
1203
|
+
newEnd: newLineNumber + lineCount,
|
|
1204
|
+
content: part.value,
|
|
1205
|
+
lines: lineCount,
|
|
1206
|
+
});
|
|
1207
|
+
oldLineNumber += lineCount;
|
|
1208
|
+
newLineNumber += lineCount;
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
|
|
1212
|
+
diffResult = {
|
|
1213
|
+
patches,
|
|
1214
|
+
sourceMaps,
|
|
1215
|
+
summary: {
|
|
1216
|
+
additions: diffLines.filter((part) => part.added).length,
|
|
1217
|
+
deletions: diffLines.filter((part) => part.removed).length,
|
|
1218
|
+
modifications: diffLines.filter(
|
|
1219
|
+
(part) => !part.added && !part.removed,
|
|
1220
|
+
).length,
|
|
1221
|
+
},
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1125
1225
|
try {
|
|
1126
1226
|
fs.writeFileSync(filepath, regression);
|
|
1127
1227
|
|
|
@@ -1134,6 +1234,17 @@ ${yml}
|
|
|
1134
1234
|
timestamp: endTime,
|
|
1135
1235
|
});
|
|
1136
1236
|
|
|
1237
|
+
// Emit diff event if there were changes
|
|
1238
|
+
if (diffResult) {
|
|
1239
|
+
this.emitter.emit(events.file.diff, {
|
|
1240
|
+
filePath: filepath,
|
|
1241
|
+
diff: diffResult,
|
|
1242
|
+
timestamp: endTime,
|
|
1243
|
+
});
|
|
1244
|
+
} else {
|
|
1245
|
+
console.log("No diff result to emit");
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1137
1248
|
// Emit file save completion event
|
|
1138
1249
|
this.emitter.emit(events.file.stop, {
|
|
1139
1250
|
operation: "save",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testdriverai",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.19",
|
|
4
4
|
"description": "Next generation autonomous AI agent for end-to-end testing of web & desktop",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"chalk": "^4.1.2",
|
|
42
42
|
"cli-progress": "^3.12.0",
|
|
43
43
|
"datadog-winston": "^1.6.0",
|
|
44
|
+
"diff": "^8.0.2",
|
|
44
45
|
"dotenv": "^16.4.5",
|
|
45
46
|
"eventemitter2": "^6.4.9",
|
|
46
47
|
"jimp": "^0.22.12",
|
|
@@ -1,52 +1,74 @@
|
|
|
1
1
|
version: 6.0.0
|
|
2
2
|
session: 67f00511acbd9ccac373edf7
|
|
3
3
|
steps:
|
|
4
|
-
- prompt:
|
|
4
|
+
- prompt: launch chrome
|
|
5
5
|
commands:
|
|
6
|
-
- command:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
6
|
+
- command: exec
|
|
7
|
+
lang: pwsh
|
|
8
|
+
code: |
|
|
9
|
+
Write-Host "Cloning Chrome extension samples..."
|
|
10
|
+
$repoUrl = "https://github.com/GoogleChrome/chrome-extensions-samples.git"
|
|
11
|
+
$clonePath = Join-Path $env:TEMP "chrome-extensions-samples"
|
|
12
|
+
if (!(Test-Path $clonePath)) {
|
|
13
|
+
git clone $repoUrl $clonePath
|
|
14
|
+
} else {
|
|
15
|
+
Write-Host "Repo already cloned at $clonePath"
|
|
16
|
+
}
|
|
17
|
+
Write-Host "Repo ready at $clonePath"
|
|
18
|
+
|
|
19
|
+
Write-Host "Initializing new npm project..."
|
|
20
|
+
cd $env:TEMP
|
|
21
|
+
Write-Host "Changed directory to TEMP: $env:TEMP"
|
|
22
|
+
|
|
23
|
+
Write-Host "Running 'npm init -y'..."
|
|
24
|
+
npm init -y
|
|
25
|
+
|
|
26
|
+
Write-Host "Installing dependencies: @puppeteer/browsers and dashcam-chrome..."
|
|
27
|
+
npm install @puppeteer/browsers dashcam-chrome
|
|
28
|
+
|
|
29
|
+
Write-Host "Installing Chromium via '@puppeteer/browsers'..."
|
|
30
|
+
npx @puppeteer/browsers install chrome
|
|
31
|
+
|
|
32
|
+
# Define paths - redefining variables for this script
|
|
33
|
+
$extensionPath = Join-Path (Join-Path $env:TEMP "chrome-extensions-samples") "functional-samples/tutorial.hello-world"
|
|
34
|
+
$profilePath = Join-Path $env:TEMP "chrome-profile-$(Get-Random)"
|
|
35
|
+
$defaultDir = Join-Path $profilePath "Default"
|
|
36
|
+
$prefsPath = Join-Path $defaultDir "Preferences"
|
|
37
|
+
|
|
38
|
+
Write-Host "Extension path (hello-world): $extensionPath"
|
|
39
|
+
Write-Host "Chrome user data dir: $profilePath"
|
|
40
|
+
|
|
41
|
+
# Create a clean profile + Preferences to disable password manager & autofill
|
|
42
|
+
Remove-Item $profilePath -Recurse -Force -ErrorAction SilentlyContinue
|
|
43
|
+
New-Item $defaultDir -ItemType Directory -Force | Out-Null
|
|
44
|
+
|
|
45
|
+
$prefs = @{
|
|
46
|
+
"credentials_enable_service" = $false
|
|
47
|
+
"profile" = @{
|
|
48
|
+
"password_manager_enabled" = $false
|
|
49
|
+
}
|
|
50
|
+
"autofill" = @{
|
|
51
|
+
"profile_enabled" = $false
|
|
52
|
+
"address_enabled" = $false
|
|
53
|
+
"credit_card_enabled" = $false
|
|
54
|
+
}
|
|
55
|
+
} | ConvertTo-Json -Depth 6
|
|
56
|
+
|
|
57
|
+
# Write Preferences before Chrome starts (ASCII/UTF8 is fine)
|
|
58
|
+
$prefs | Set-Content -Path $prefsPath -Encoding ASCII
|
|
59
|
+
|
|
60
|
+
# Build args - load only hello-world extension
|
|
61
|
+
$chromeArgs = @(
|
|
62
|
+
"--start-maximized",
|
|
63
|
+
"--load-extension=$extensionPath",
|
|
64
|
+
"--user-data-dir=$profilePath",
|
|
65
|
+
"--no-first-run",
|
|
66
|
+
"--no-default-browser-check",
|
|
67
|
+
"--disable-infobars"
|
|
68
|
+
"https://testdriver.ai"
|
|
69
|
+
) -join ' '
|
|
70
|
+
|
|
71
|
+
Start-Process "cmd.exe" -ArgumentList "/c", "npx @puppeteer/browsers launch chrome -- $chromeArgs"
|
|
72
|
+
|
|
73
|
+
Write-Host "Script complete."
|
|
74
|
+
exit
|
|
@@ -1,24 +1,64 @@
|
|
|
1
1
|
version: 6.0.0
|
|
2
2
|
session: 67f00511acbd9ccac373edf7
|
|
3
3
|
steps:
|
|
4
|
-
- prompt: download
|
|
4
|
+
- prompt: download and install Spotify
|
|
5
5
|
commands:
|
|
6
6
|
- command: exec
|
|
7
7
|
lang: pwsh
|
|
8
8
|
code: |
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
$
|
|
14
|
-
|
|
15
|
-
Write-Host "
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
9
|
+
$spotifyUrl = "https://download.scdn.co/SpotifySetup.exe"
|
|
10
|
+
$installerPath = "$env:TEMP\SpotifySetup.exe"
|
|
11
|
+
|
|
12
|
+
Write-Host "Downloading Spotify installer..."
|
|
13
|
+
Invoke-WebRequest -Uri $spotifyUrl -OutFile $installerPath -UseBasicParsing
|
|
14
|
+
|
|
15
|
+
Write-Host "Downloaded to $installerPath"
|
|
16
|
+
- command: exec
|
|
17
|
+
lang: pwsh
|
|
18
|
+
code: |
|
|
19
|
+
$installerPath = "$env:TEMP\SpotifySetup.exe"
|
|
20
|
+
|
|
21
|
+
if (-Not (Test-Path $installerPath)) {
|
|
22
|
+
Write-Error "Spotify installer not found at $installerPath"
|
|
23
|
+
exit 1
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Write-Host "Installing Spotify silently..."
|
|
27
|
+
$process = Start-Process -FilePath $installerPath -ArgumentList "/silent" -Wait -PassThru
|
|
28
|
+
|
|
29
|
+
if ($process.ExitCode -eq 0) {
|
|
30
|
+
Write-Host "Spotify installed successfully."
|
|
31
|
+
Remove-Item $installerPath -Force
|
|
32
|
+
exit 0
|
|
33
|
+
} else {
|
|
34
|
+
Write-Error "Installation failed with exit code $($process.ExitCode)"
|
|
35
|
+
exit $process.ExitCode
|
|
36
|
+
}
|
|
37
|
+
- command: exec
|
|
38
|
+
lang: pwsh
|
|
39
|
+
code: |
|
|
40
|
+
# patch-path.ps1
|
|
41
|
+
|
|
42
|
+
$spotifyPath = "$env:APPDATA\Spotify"
|
|
43
|
+
|
|
44
|
+
if (-not ($env:PATH -like "*$spotifyPath*")) {
|
|
45
|
+
Write-Host "Temporarily appending Spotify to PATH for current session."
|
|
46
|
+
$env:PATH += ";$spotifyPath"
|
|
47
|
+
} else {
|
|
48
|
+
Write-Host "Spotify path already in PATH."
|
|
49
|
+
}
|
|
50
|
+
- command: exec
|
|
51
|
+
lang: pwsh
|
|
52
|
+
code: |
|
|
53
|
+
# launch-spotify.ps1
|
|
54
|
+
|
|
55
|
+
Write-Host "Launching Spotify..."
|
|
56
|
+
$spotifyExe = "$env:APPDATA\Spotify\Spotify.exe"
|
|
57
|
+
|
|
58
|
+
if (Test-Path $spotifyExe) {
|
|
59
|
+
Start-Process $spotifyExe
|
|
60
|
+
Write-Host "Spotify launched successfully."
|
|
22
61
|
} else {
|
|
23
|
-
Write-
|
|
62
|
+
Write-Error "Spotify executable not found at $spotifyExe"
|
|
63
|
+
exit 1
|
|
24
64
|
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
version: 6.0.0
|
|
2
|
-
steps:
|
|
3
|
-
- commands:
|
|
4
|
-
- command: exec
|
|
5
|
-
lang: pwsh
|
|
6
|
-
code: |
|
|
7
|
-
npm init
|
|
8
|
-
Start-Process "C:\Program Files\Google\Chrome\Application\chrome.exe" -ArgumentList "--start-maximized --disable-infobars --disable-fre --no-default-browser-check --no-first-run --guest --load-extension=$(pwd)/node_modules/dashcam-chrome/build", "${TD_WEBSITE}"
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
version: 5.1.1
|
|
2
|
-
session: 67f00511acbd9ccac373edf7
|
|
3
|
-
steps:
|
|
4
|
-
- prompt: execute powershell multiline
|
|
5
|
-
commands:
|
|
6
|
-
- command: exec
|
|
7
|
-
lang: pwsh
|
|
8
|
-
code: |
|
|
9
|
-
Start-Process "C:/Program Files/Google/Chrome/Application/chrome.exe" -ArgumentList "--start-maximized", "--guest", "${TD_WEBSITE}"
|
|
10
|
-
exit
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
version: 5.7.7
|
|
2
|
-
session: 682f6071811bd5a322c0e6dd
|
|
3
|
-
steps:
|
|
4
|
-
- prompt: focus chrome
|
|
5
|
-
commands:
|
|
6
|
-
- command: focus-application
|
|
7
|
-
name: Google Chrome
|
|
8
|
-
- prompt: enter a username
|
|
9
|
-
commands:
|
|
10
|
-
- command: hover-text
|
|
11
|
-
text: Username
|
|
12
|
-
description: username input field
|
|
13
|
-
action: click
|
|
14
|
-
- command: type
|
|
15
|
-
text: standard_user
|
|
16
|
-
- prompt: >-
|
|
17
|
-
enter a valid password
|
|
18
|
-
- prompt: click sign in
|
|
19
|
-
commands:
|
|
20
|
-
- command: hover-text
|
|
21
|
-
text: Sign in
|
|
22
|
-
description: sign in button
|
|
23
|
-
action: click
|