testdriverai 6.1.10 → 6.2.0
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/.github/workflows/acceptance-tests.yml +0 -2
- package/.github/workflows/acceptance-v6.yml +0 -2
- package/.github/workflows/lint.yml +1 -4
- package/.github/workflows/publish-canary.yml +0 -2
- package/.github/workflows/publish-latest.yml +0 -1
- package/.prettierignore +0 -1
- package/.vscode/settings.json +1 -4
- package/agent/events.js +10 -1
- package/agent/index.js +76 -104
- package/agent/interface.js +6 -43
- package/agent/lib/censorship.js +10 -15
- package/agent/lib/commander.js +18 -31
- package/agent/lib/commands.js +63 -81
- package/agent/lib/debugger-server.js +5 -0
- package/agent/lib/generator.js +2 -2
- package/agent/lib/redraw.js +1 -0
- package/agent/lib/sandbox.js +2 -0
- package/agent/lib/sdk.js +1 -2
- package/agent/lib/source-mapper.js +1 -1
- package/agent/lib/system.js +6 -1
- package/docs/account/enterprise.mdx +12 -8
- package/docs/account/pricing.mdx +2 -2
- package/docs/account/projects.mdx +0 -5
- package/docs/cli/overview.mdx +6 -6
- package/docs/commands/assert.mdx +0 -1
- package/docs/commands/hover-text.mdx +1 -3
- package/docs/commands/match-image.mdx +4 -5
- package/docs/commands/press-keys.mdx +8 -6
- package/docs/commands/scroll-until-image.mdx +7 -8
- package/docs/commands/scroll-until-text.mdx +6 -7
- package/docs/commands/wait-for-image.mdx +4 -5
- package/docs/commands/wait-for-text.mdx +5 -6
- package/docs/docs.json +40 -42
- package/docs/getting-started/vscode.mdx +56 -67
- package/docs/guide/environment-variables.mdx +5 -5
- package/docs/overview/comparison.mdx +39 -22
- package/docs/overview/quickstart.mdx +32 -84
- package/docs/styles.css +1 -10
- package/interfaces/cli/lib/base.js +6 -27
- package/interfaces/cli/utils/factory.js +4 -17
- package/interfaces/logger.js +5 -4
- package/interfaces/readline.js +1 -1
- package/package.json +3 -3
- package/schema.json +2 -22
- package/testdriver/acceptance/hover-text.yaml +1 -2
- package/testdriver/acceptance/prompt.yaml +1 -4
- package/testdriver/acceptance/scroll-until-image.yaml +0 -5
- package/testdriver/{lifecycle/prerun.yaml → examples/web/lifecycle/provision.yaml} +0 -6
- package/testdriver/lifecycle/provision.yaml +20 -0
- package/.github/workflows/self-hosted.yml +0 -102
- package/docs/apps/tauri-apps.mdx +0 -361
- package/docs/getting-started/playwright.mdx +0 -342
- package/docs/getting-started/self-hosting.mdx +0 -370
- package/docs/guide/dashcam.mdx +0 -118
- package/docs/images/content/self-hosted/launchtemplateid.png +0 -0
- package/docs/images/content/vscode/ide-full.png +0 -0
- package/docs/images/content/vscode/running.png +0 -0
- package/interfaces/cli/commands/generate.js +0 -3
- package/setup/aws/cloudformation.yaml +0 -463
- package/setup/aws/spawn-runner.sh +0 -190
- package/testdriver/edge-cases/js-exception.yaml +0 -8
- package/testdriver/edge-cases/js-promise.yaml +0 -19
- package/testdriver/edge-cases/lifecycle/postrun.yaml +0 -10
- package/testdriver/edge-cases/success-test.yaml +0 -9
- package/testdriver/examples/web/lifecycle/postrun.yaml +0 -7
- package/testdriver/examples/web/lifecycle/prerun.yaml +0 -17
- package/testdriver/lifecycle/postrun.yaml +0 -7
package/agent/lib/commander.js
CHANGED
|
@@ -80,21 +80,21 @@ commands:
|
|
|
80
80
|
// this will actually interpret the command and execute it
|
|
81
81
|
switch (object.command) {
|
|
82
82
|
case "type":
|
|
83
|
-
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
84
83
|
emitter.emit(events.log.narration, `typing ${object.text}`);
|
|
84
|
+
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
85
85
|
response = await commands.type(object.text, object.delay);
|
|
86
86
|
break;
|
|
87
87
|
case "press-keys":
|
|
88
|
-
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
89
88
|
emitter.emit(
|
|
90
89
|
events.log.narration,
|
|
91
|
-
`pressing keys
|
|
90
|
+
`pressing keys ${object.keys.join(",")}`,
|
|
92
91
|
);
|
|
92
|
+
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
93
93
|
response = await commands["press-keys"](object.keys);
|
|
94
94
|
break;
|
|
95
95
|
case "scroll":
|
|
96
|
-
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
97
96
|
emitter.emit(events.log.narration, `scrolling ${object.direction}`);
|
|
97
|
+
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
98
98
|
response = await commands.scroll(
|
|
99
99
|
object.direction,
|
|
100
100
|
object.amount,
|
|
@@ -102,21 +102,21 @@ commands:
|
|
|
102
102
|
);
|
|
103
103
|
break;
|
|
104
104
|
case "wait":
|
|
105
|
-
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
106
105
|
emitter.emit(
|
|
107
106
|
events.log.narration,
|
|
108
107
|
`waiting ${object.timeout} seconds`,
|
|
109
108
|
);
|
|
109
|
+
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
110
110
|
response = await commands.wait(object.timeout);
|
|
111
111
|
break;
|
|
112
112
|
case "click":
|
|
113
|
-
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
114
113
|
emitter.emit(events.log.narration, `${object.action}`);
|
|
114
|
+
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
115
115
|
response = await commands["click"](object.x, object.y, object.action);
|
|
116
116
|
break;
|
|
117
117
|
case "hover":
|
|
118
|
-
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
119
118
|
emitter.emit(events.log.narration, `moving mouse`);
|
|
119
|
+
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
120
120
|
response = await commands["hover"](object.x, object.y);
|
|
121
121
|
break;
|
|
122
122
|
case "drag":
|
|
@@ -124,25 +124,24 @@ commands:
|
|
|
124
124
|
response = await commands["drag"](object.x, object.y);
|
|
125
125
|
break;
|
|
126
126
|
case "hover-text":
|
|
127
|
-
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
128
127
|
emitter.emit(
|
|
129
128
|
events.log.narration,
|
|
130
129
|
`searching for ${object.description}`,
|
|
131
130
|
);
|
|
131
|
+
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
132
132
|
response = await commands["hover-text"](
|
|
133
133
|
object.text,
|
|
134
134
|
object.description,
|
|
135
135
|
object.action,
|
|
136
136
|
object.method,
|
|
137
|
-
object.timeout,
|
|
138
137
|
);
|
|
139
138
|
break;
|
|
140
139
|
case "hover-image":
|
|
141
|
-
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
142
140
|
emitter.emit(
|
|
143
141
|
events.log.narration,
|
|
144
142
|
`searching for image of ${object.description}`,
|
|
145
143
|
);
|
|
144
|
+
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
146
145
|
response = await commands["hover-image"](
|
|
147
146
|
object.description,
|
|
148
147
|
object.action,
|
|
@@ -154,38 +153,32 @@ commands:
|
|
|
154
153
|
events.log.narration,
|
|
155
154
|
`${object.action} image ${object.path}`,
|
|
156
155
|
);
|
|
157
|
-
response = await commands["match-image"](
|
|
158
|
-
object.path,
|
|
159
|
-
object.action,
|
|
160
|
-
object.invert,
|
|
161
|
-
);
|
|
156
|
+
response = await commands["match-image"](object.path, object.action);
|
|
162
157
|
break;
|
|
163
158
|
case "wait-for-image":
|
|
164
|
-
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
165
159
|
emitter.emit(
|
|
166
160
|
events.log.narration,
|
|
167
161
|
`waiting for ${object.description}`,
|
|
168
162
|
);
|
|
163
|
+
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
169
164
|
response = await commands["wait-for-image"](
|
|
170
165
|
object.description,
|
|
171
166
|
object.timeout,
|
|
172
|
-
object.invert,
|
|
173
167
|
);
|
|
174
168
|
break;
|
|
175
169
|
case "wait-for-text":
|
|
176
|
-
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
177
170
|
emitter.emit(events.log.narration, `waiting for ${object.text}`);
|
|
171
|
+
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
178
172
|
copy.text = "*****";
|
|
179
173
|
response = await commands["wait-for-text"](
|
|
180
174
|
object.text,
|
|
181
175
|
object.timeout,
|
|
182
176
|
object.method,
|
|
183
|
-
object.invert,
|
|
184
177
|
);
|
|
185
178
|
break;
|
|
186
179
|
case "scroll-until-text":
|
|
187
|
-
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
188
180
|
emitter.emit(events.log.narration, `scrolling until ${object.text}`);
|
|
181
|
+
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
189
182
|
copy.text = "*****";
|
|
190
183
|
response = await commands["scroll-until-text"](
|
|
191
184
|
object.text,
|
|
@@ -193,26 +186,24 @@ commands:
|
|
|
193
186
|
object.distance,
|
|
194
187
|
object.textMatchMethod,
|
|
195
188
|
object.method,
|
|
196
|
-
object.invert,
|
|
197
189
|
);
|
|
198
190
|
break;
|
|
199
191
|
case "scroll-until-image": {
|
|
200
192
|
const needle = object.description || object.path;
|
|
201
|
-
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
202
193
|
emitter.emit(events.log.narration, `scrolling until ${needle}`);
|
|
194
|
+
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
203
195
|
response = await commands["scroll-until-image"](
|
|
204
196
|
object.description,
|
|
205
197
|
object.direction,
|
|
206
198
|
object.distance,
|
|
207
199
|
object.method,
|
|
208
200
|
object.path,
|
|
209
|
-
object.invert,
|
|
210
201
|
);
|
|
211
202
|
break;
|
|
212
203
|
}
|
|
213
204
|
case "focus-application":
|
|
214
|
-
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
215
205
|
emitter.emit(events.log.narration, `focusing ${object.name}`);
|
|
206
|
+
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
216
207
|
response = await commands["focus-application"](object.name);
|
|
217
208
|
break;
|
|
218
209
|
case "remember": {
|
|
@@ -223,16 +214,12 @@ commands:
|
|
|
223
214
|
break;
|
|
224
215
|
}
|
|
225
216
|
case "assert":
|
|
226
|
-
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
227
217
|
emitter.emit(events.log.narration, `asserting ${object.expect}`);
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
object.async,
|
|
231
|
-
object.invert,
|
|
232
|
-
);
|
|
233
|
-
|
|
218
|
+
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
219
|
+
response = await commands.assert(object.expect, object.async);
|
|
234
220
|
break;
|
|
235
221
|
case "exec":
|
|
222
|
+
emitter.emit(events.log.narration, `exec`);
|
|
236
223
|
emitter.emit(
|
|
237
224
|
events.log.log,
|
|
238
225
|
generator.jsonToManual({
|
package/agent/lib/commands.js
CHANGED
|
@@ -170,34 +170,20 @@ const createCommands = (
|
|
|
170
170
|
return result;
|
|
171
171
|
};
|
|
172
172
|
|
|
173
|
-
const assert = async (
|
|
174
|
-
assertion,
|
|
175
|
-
shouldThrow = false,
|
|
176
|
-
async = false,
|
|
177
|
-
invert = false,
|
|
178
|
-
) => {
|
|
173
|
+
const assert = async (assertion, shouldThrow = false, async = false) => {
|
|
179
174
|
if (async) {
|
|
180
175
|
shouldThrow = true;
|
|
181
176
|
}
|
|
182
177
|
|
|
183
178
|
const handleAssertResponse = (response) => {
|
|
184
|
-
emitter.emit(events.log.
|
|
185
|
-
|
|
186
|
-
let valid = response.indexOf("The task passed") > -1;
|
|
187
|
-
|
|
188
|
-
if (invert) {
|
|
189
|
-
valid = !valid;
|
|
190
|
-
}
|
|
179
|
+
emitter.emit(events.log.markdown.static, response);
|
|
191
180
|
|
|
192
|
-
if (
|
|
181
|
+
if (response.indexOf("The task passed") > -1) {
|
|
193
182
|
return true;
|
|
194
183
|
} else {
|
|
195
184
|
if (shouldThrow) {
|
|
196
185
|
// Is fatal, othewise it just changes the assertion to be true
|
|
197
|
-
throw new MatchError(
|
|
198
|
-
`AI Assertion failed ${invert && "(Inverted)"}`,
|
|
199
|
-
true,
|
|
200
|
-
);
|
|
186
|
+
throw new MatchError(`AI Assertion failed`, true);
|
|
201
187
|
} else {
|
|
202
188
|
return false;
|
|
203
189
|
}
|
|
@@ -241,17 +227,31 @@ const createCommands = (
|
|
|
241
227
|
switch (direction) {
|
|
242
228
|
case "up":
|
|
243
229
|
if (method === "mouse") {
|
|
244
|
-
await sandbox.send({
|
|
230
|
+
await sandbox.send({
|
|
231
|
+
os: "linux",
|
|
232
|
+
type: "scroll",
|
|
233
|
+
amount,
|
|
234
|
+
direction,
|
|
235
|
+
});
|
|
245
236
|
} else {
|
|
246
|
-
await sandbox.send({ type: "press", keys: ["pageup"] });
|
|
237
|
+
await sandbox.send({ os: "linux", type: "press", keys: ["pageup"] });
|
|
247
238
|
}
|
|
248
239
|
await redraw.wait(2500);
|
|
249
240
|
break;
|
|
250
241
|
case "down":
|
|
251
242
|
if (method === "mouse") {
|
|
252
|
-
await sandbox.send({
|
|
243
|
+
await sandbox.send({
|
|
244
|
+
os: "linux",
|
|
245
|
+
type: "scroll",
|
|
246
|
+
amount,
|
|
247
|
+
direction,
|
|
248
|
+
});
|
|
253
249
|
} else {
|
|
254
|
-
await sandbox.send({
|
|
250
|
+
await sandbox.send({
|
|
251
|
+
os: "linux",
|
|
252
|
+
type: "press",
|
|
253
|
+
keys: ["pagedown"],
|
|
254
|
+
});
|
|
255
255
|
}
|
|
256
256
|
await redraw.wait(2500);
|
|
257
257
|
break;
|
|
@@ -298,7 +298,7 @@ const createCommands = (
|
|
|
298
298
|
x = parseInt(x);
|
|
299
299
|
y = parseInt(y);
|
|
300
300
|
|
|
301
|
-
await sandbox.send({ type: "moveMouse", x, y });
|
|
301
|
+
await sandbox.send({ os: "linux", type: "moveMouse", x, y });
|
|
302
302
|
|
|
303
303
|
emitter.emit(events.mouseMove, { x, y });
|
|
304
304
|
|
|
@@ -306,17 +306,21 @@ const createCommands = (
|
|
|
306
306
|
|
|
307
307
|
if (action !== "hover") {
|
|
308
308
|
if (action === "click" || action === "left-click") {
|
|
309
|
-
await sandbox.send({ type: "leftClick" });
|
|
309
|
+
await sandbox.send({ os: "linux", type: "leftClick" });
|
|
310
310
|
} else if (action === "right-click") {
|
|
311
|
-
await sandbox.send({ type: "rightClick" });
|
|
311
|
+
await sandbox.send({ os: "linux", type: "rightClick" });
|
|
312
312
|
} else if (action === "middle-click") {
|
|
313
|
-
await sandbox.send({ type: "middleClick" });
|
|
313
|
+
await sandbox.send({ os: "linux", type: "middleClick" });
|
|
314
314
|
} else if (action === "double-click") {
|
|
315
|
-
await sandbox.send({ type: "doubleClick" });
|
|
315
|
+
await sandbox.send({ os: "linux", type: "doubleClick" });
|
|
316
316
|
} else if (action === "drag-start") {
|
|
317
|
-
await sandbox.send({ type: "mousePress", button: "left" });
|
|
317
|
+
await sandbox.send({ os: "linux", type: "mousePress", button: "left" });
|
|
318
318
|
} else if (action === "drag-end") {
|
|
319
|
-
await sandbox.send({
|
|
319
|
+
await sandbox.send({
|
|
320
|
+
os: "linux",
|
|
321
|
+
type: "mouseRelease",
|
|
322
|
+
button: "left",
|
|
323
|
+
});
|
|
320
324
|
}
|
|
321
325
|
|
|
322
326
|
emitter.emit(events.mouseClick, { x, y, button, click, double });
|
|
@@ -333,7 +337,7 @@ const createCommands = (
|
|
|
333
337
|
x = parseInt(x);
|
|
334
338
|
y = parseInt(y);
|
|
335
339
|
|
|
336
|
-
await sandbox.send({ type: "moveMouse", x, y });
|
|
340
|
+
await sandbox.send({ os: "linux", type: "moveMouse", x, y });
|
|
337
341
|
|
|
338
342
|
await redraw.wait(2500);
|
|
339
343
|
|
|
@@ -353,12 +357,11 @@ const createCommands = (
|
|
|
353
357
|
description = null,
|
|
354
358
|
action = "click",
|
|
355
359
|
method = "turbo",
|
|
356
|
-
timeout = 5000, // we pass this to the subsequent wait-for-text block
|
|
357
360
|
) => {
|
|
358
361
|
text = text ? text.toString() : null;
|
|
359
362
|
|
|
360
363
|
// wait for the text to appear on screen
|
|
361
|
-
await commands["wait-for-text"](text,
|
|
364
|
+
await commands["wait-for-text"](text, 5000);
|
|
362
365
|
|
|
363
366
|
description = description ? description.toString() : null;
|
|
364
367
|
|
|
@@ -413,7 +416,7 @@ const createCommands = (
|
|
|
413
416
|
return response.data;
|
|
414
417
|
}
|
|
415
418
|
},
|
|
416
|
-
"match-image": async (relativePath, action = "click"
|
|
419
|
+
"match-image": async (relativePath, action = "click") => {
|
|
417
420
|
// Resolve the image path relative to the current file
|
|
418
421
|
const resolvedPath = resolveRelativePath(relativePath);
|
|
419
422
|
|
|
@@ -421,10 +424,6 @@ const createCommands = (
|
|
|
421
424
|
|
|
422
425
|
let result = await findImageOnScreen(resolvedPath, image);
|
|
423
426
|
|
|
424
|
-
if (invert) {
|
|
425
|
-
result = !result;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
427
|
if (!result) {
|
|
429
428
|
throw new CommandError(`Image not found: ${resolvedPath}`);
|
|
430
429
|
} else {
|
|
@@ -438,12 +437,13 @@ const createCommands = (
|
|
|
438
437
|
return true;
|
|
439
438
|
},
|
|
440
439
|
// type a string
|
|
440
|
+
os: "linux",
|
|
441
441
|
type: async (string, delay = 250) => {
|
|
442
442
|
await redraw.start();
|
|
443
443
|
|
|
444
444
|
string = string.toString();
|
|
445
445
|
|
|
446
|
-
await sandbox.send({ type: "write", text: string, delay });
|
|
446
|
+
await sandbox.send({ os: "linux", type: "write", text: string, delay });
|
|
447
447
|
await redraw.wait(5000);
|
|
448
448
|
return;
|
|
449
449
|
},
|
|
@@ -453,7 +453,7 @@ const createCommands = (
|
|
|
453
453
|
await redraw.start();
|
|
454
454
|
|
|
455
455
|
// finally, press the keys
|
|
456
|
-
await sandbox.send({ type: "press", keys: inputKeys });
|
|
456
|
+
await sandbox.send({ os: "linux", type: "press", keys: inputKeys });
|
|
457
457
|
|
|
458
458
|
await redraw.wait(5000);
|
|
459
459
|
|
|
@@ -463,7 +463,7 @@ const createCommands = (
|
|
|
463
463
|
wait: async (timeout = 3000) => {
|
|
464
464
|
return await delay(timeout);
|
|
465
465
|
},
|
|
466
|
-
"wait-for-image": async (description, timeout = 10000
|
|
466
|
+
"wait-for-image": async (description, timeout = 10000) => {
|
|
467
467
|
emitter.emit(
|
|
468
468
|
events.log.narration,
|
|
469
469
|
theme.dim(
|
|
@@ -481,7 +481,6 @@ const createCommands = (
|
|
|
481
481
|
`An image matching the description "${description}" appears on screen.`,
|
|
482
482
|
false,
|
|
483
483
|
false,
|
|
484
|
-
invert,
|
|
485
484
|
);
|
|
486
485
|
|
|
487
486
|
durationPassed = new Date().getTime() - startTime;
|
|
@@ -512,12 +511,7 @@ const createCommands = (
|
|
|
512
511
|
);
|
|
513
512
|
}
|
|
514
513
|
},
|
|
515
|
-
"wait-for-text": async (
|
|
516
|
-
text,
|
|
517
|
-
timeout = 5000,
|
|
518
|
-
method = "turbo",
|
|
519
|
-
invert = false,
|
|
520
|
-
) => {
|
|
514
|
+
"wait-for-text": async (text, timeout = 5000, method = "turbo") => {
|
|
521
515
|
await redraw.start();
|
|
522
516
|
|
|
523
517
|
emitter.emit(
|
|
@@ -547,12 +541,7 @@ const createCommands = (
|
|
|
547
541
|
);
|
|
548
542
|
|
|
549
543
|
passed = response.data;
|
|
550
|
-
|
|
551
|
-
if (invert) {
|
|
552
|
-
passed = !passed;
|
|
553
|
-
}
|
|
554
544
|
durationPassed = new Date().getTime() - startTime;
|
|
555
|
-
|
|
556
545
|
if (!passed) {
|
|
557
546
|
emitter.emit(
|
|
558
547
|
events.log.narration,
|
|
@@ -580,7 +569,6 @@ const createCommands = (
|
|
|
580
569
|
maxDistance = 10000,
|
|
581
570
|
textMatchMethod = "turbo",
|
|
582
571
|
method = "keyboard",
|
|
583
|
-
invert = false,
|
|
584
572
|
) => {
|
|
585
573
|
await redraw.start();
|
|
586
574
|
|
|
@@ -592,11 +580,15 @@ const createCommands = (
|
|
|
592
580
|
|
|
593
581
|
if (method === "keyboard") {
|
|
594
582
|
try {
|
|
595
|
-
await sandbox.send({
|
|
583
|
+
await sandbox.send({
|
|
584
|
+
os: "linux",
|
|
585
|
+
type: "press",
|
|
586
|
+
keys: ["f", "ctrl"],
|
|
587
|
+
});
|
|
596
588
|
await delay(1000);
|
|
597
|
-
await sandbox.send({ type: "write", text });
|
|
589
|
+
await sandbox.send({ os: "linux", type: "write", text });
|
|
598
590
|
await redraw.wait(5000);
|
|
599
|
-
await sandbox.send({ type: "press", keys: ["escape"] });
|
|
591
|
+
await sandbox.send({ os: "linux", type: "press", keys: ["escape"] });
|
|
600
592
|
} catch {
|
|
601
593
|
throw new MatchError(
|
|
602
594
|
"Could not find element using browser text search",
|
|
@@ -624,11 +616,6 @@ const createCommands = (
|
|
|
624
616
|
);
|
|
625
617
|
|
|
626
618
|
passed = response.data;
|
|
627
|
-
|
|
628
|
-
if (invert) {
|
|
629
|
-
passed = !passed;
|
|
630
|
-
}
|
|
631
|
-
|
|
632
619
|
if (!passed) {
|
|
633
620
|
emitter.emit(
|
|
634
621
|
events.log.narration,
|
|
@@ -657,7 +644,6 @@ const createCommands = (
|
|
|
657
644
|
maxDistance = 10000,
|
|
658
645
|
method = "keyboard",
|
|
659
646
|
path,
|
|
660
|
-
invert = false,
|
|
661
647
|
) => {
|
|
662
648
|
const needle = description || path;
|
|
663
649
|
|
|
@@ -687,7 +673,6 @@ const createCommands = (
|
|
|
687
673
|
`An image matching the description "${description}" appears on screen.`,
|
|
688
674
|
false,
|
|
689
675
|
false,
|
|
690
|
-
invert,
|
|
691
676
|
);
|
|
692
677
|
}
|
|
693
678
|
|
|
@@ -727,6 +712,7 @@ const createCommands = (
|
|
|
727
712
|
await redraw.start();
|
|
728
713
|
|
|
729
714
|
await sandbox.send({
|
|
715
|
+
os: "linux",
|
|
730
716
|
type: "commands.focus-application",
|
|
731
717
|
name,
|
|
732
718
|
});
|
|
@@ -740,40 +726,41 @@ const createCommands = (
|
|
|
740
726
|
});
|
|
741
727
|
return result.data;
|
|
742
728
|
},
|
|
743
|
-
assert: async (assertion, async = false
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
return response;
|
|
729
|
+
assert: async (assertion, async = false) => {
|
|
730
|
+
return await assert(assertion, true, async);
|
|
747
731
|
},
|
|
748
|
-
exec: async (language, code, timeout, silent = false) => {
|
|
732
|
+
exec: async (language = "pwsh", code, timeout, silent = false) => {
|
|
749
733
|
emitter.emit(events.log.narration, theme.dim(`calling exec...`), true);
|
|
750
734
|
|
|
751
735
|
emitter.emit(events.log.log, code);
|
|
752
736
|
|
|
753
737
|
let plat = system.platform();
|
|
754
738
|
|
|
755
|
-
if (language == "pwsh") {
|
|
739
|
+
if (language == "pwsh" || language == "sh") {
|
|
756
740
|
let result = null;
|
|
757
741
|
|
|
758
742
|
result = await sandbox.send({
|
|
743
|
+
os: "linux",
|
|
759
744
|
type: "commands.run",
|
|
760
745
|
command: code,
|
|
761
746
|
timeout,
|
|
762
747
|
});
|
|
763
748
|
|
|
749
|
+
console.log("Exec result:", result);
|
|
750
|
+
|
|
764
751
|
if (result.out && result.out.returncode !== 0) {
|
|
765
752
|
throw new MatchError(
|
|
766
753
|
`Command failed with exit code ${result.out.returncode}: ${result.out.stderr}`,
|
|
767
754
|
);
|
|
768
755
|
} else {
|
|
769
|
-
if (!silent
|
|
770
|
-
emitter.emit(events.log.log, theme.dim(`stdout:`), true);
|
|
756
|
+
if (!silent) {
|
|
757
|
+
emitter.emit(events.log.log, theme.dim(`Command stdout:`), true);
|
|
771
758
|
emitter.emit(events.log.log, `${result.out.stdout}`, true);
|
|
772
|
-
}
|
|
773
759
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
760
|
+
if (result.out.stderr) {
|
|
761
|
+
emitter.emit(events.log.log, theme.dim(`Command stderr:`), true);
|
|
762
|
+
emitter.emit(events.log.log, `${result.out.stderr}`, true);
|
|
763
|
+
}
|
|
777
764
|
}
|
|
778
765
|
|
|
779
766
|
return result.out?.stdout?.trim();
|
|
@@ -805,12 +792,7 @@ const createCommands = (
|
|
|
805
792
|
try {
|
|
806
793
|
await script.runInNewContext(context);
|
|
807
794
|
} catch (e) {
|
|
808
|
-
|
|
809
|
-
emitter.emit(
|
|
810
|
-
events.log.debug,
|
|
811
|
-
`JavaScript execution error: ${e.message}`,
|
|
812
|
-
);
|
|
813
|
-
// Wait a tick to allow any promise rejections to be handled
|
|
795
|
+
console.error(e);
|
|
814
796
|
throw new CommandError(`Error running script: ${e.message}`);
|
|
815
797
|
}
|
|
816
798
|
|
|
@@ -65,6 +65,11 @@ function createDebuggerServer(config = {}) {
|
|
|
65
65
|
|
|
66
66
|
ws.on("close", () => {
|
|
67
67
|
clients.delete(ws);
|
|
68
|
+
|
|
69
|
+
// If no clients connected, we can optionally shut down
|
|
70
|
+
if (clients.size === 0) {
|
|
71
|
+
console.log("No clients connected, keeping server alive");
|
|
72
|
+
}
|
|
68
73
|
});
|
|
69
74
|
|
|
70
75
|
ws.on("error", (error) => {
|
package/agent/lib/generator.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// parses markdown content to find code blocks, and then extracts yaml from those code blocks
|
|
2
2
|
const yaml = require("js-yaml");
|
|
3
|
-
const
|
|
3
|
+
const package = require("../../package.json");
|
|
4
4
|
const session = require("./session");
|
|
5
5
|
const theme = require("./theme");
|
|
6
6
|
// do the actual parsing
|
|
@@ -56,7 +56,7 @@ const jsonToManual = function (json, colors = true) {
|
|
|
56
56
|
const dumpToYML = async function (inputArray, sessionInstance = null) {
|
|
57
57
|
// use yml dump to convert json to yml
|
|
58
58
|
let yml = await yaml.dump({
|
|
59
|
-
version:
|
|
59
|
+
version: package.version,
|
|
60
60
|
session: sessionInstance ? sessionInstance.get() : session.get(),
|
|
61
61
|
steps: inputArray,
|
|
62
62
|
});
|
package/agent/lib/redraw.js
CHANGED
package/agent/lib/sandbox.js
CHANGED
|
@@ -51,6 +51,7 @@ const createSandbox = (emitter, analytics) => {
|
|
|
51
51
|
async auth(apiKey) {
|
|
52
52
|
let reply = await this.send({
|
|
53
53
|
type: "authenticate",
|
|
54
|
+
os: "linux",
|
|
54
55
|
apiKey,
|
|
55
56
|
});
|
|
56
57
|
|
|
@@ -64,6 +65,7 @@ const createSandbox = (emitter, analytics) => {
|
|
|
64
65
|
async connect(sandboxId, persist = false) {
|
|
65
66
|
let reply = await this.send({
|
|
66
67
|
type: "connect",
|
|
68
|
+
os: "linux",
|
|
67
69
|
persist,
|
|
68
70
|
sandboxId,
|
|
69
71
|
});
|
package/agent/lib/sdk.js
CHANGED
|
@@ -95,7 +95,7 @@ const createSDK = (emitter, config, sessionInstance) => {
|
|
|
95
95
|
return token;
|
|
96
96
|
} catch (error) {
|
|
97
97
|
outputError(error);
|
|
98
|
-
|
|
98
|
+
return;
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
};
|
|
@@ -194,7 +194,6 @@ const createSDK = (emitter, config, sessionInstance) => {
|
|
|
194
194
|
return value;
|
|
195
195
|
} catch (error) {
|
|
196
196
|
outputError(error);
|
|
197
|
-
throw error; // Re-throw the error so calling code can handle it properly
|
|
198
197
|
}
|
|
199
198
|
};
|
|
200
199
|
|
|
@@ -263,7 +263,7 @@ class SourceMapper {
|
|
|
263
263
|
let description = `${fileName}:${(sourcePosition.step.startLine || 0) + 1}`;
|
|
264
264
|
|
|
265
265
|
if (sourcePosition.command) {
|
|
266
|
-
description += `:${(sourcePosition.command.startLine || 0) + 1}`;
|
|
266
|
+
description += `:${(sourcePosition.command.startLine || 0) + 1} (${sourcePosition.command.command || "unknown command"})`;
|
|
267
267
|
} else {
|
|
268
268
|
description += ` (step ${sourcePosition.step.stepIndex + 1})`;
|
|
269
269
|
}
|
package/agent/lib/system.js
CHANGED
|
@@ -7,7 +7,10 @@ const { events } = require("../events.js");
|
|
|
7
7
|
|
|
8
8
|
const createSystem = (emitter, sandbox, config) => {
|
|
9
9
|
const screenshot = async (options) => {
|
|
10
|
-
let { base64 } = await sandbox.send({
|
|
10
|
+
let { base64 } = await sandbox.send({
|
|
11
|
+
os: "linux",
|
|
12
|
+
type: "system.screenshot",
|
|
13
|
+
});
|
|
11
14
|
|
|
12
15
|
if (!base64) {
|
|
13
16
|
console.error("Failed to take screenshot");
|
|
@@ -110,6 +113,7 @@ const createSystem = (emitter, sandbox, config) => {
|
|
|
110
113
|
const activeWin = async () => {
|
|
111
114
|
// Get Mouse Position from command line
|
|
112
115
|
let result = await sandbox.send({
|
|
116
|
+
os: "linux",
|
|
113
117
|
type: "system.get-active-window",
|
|
114
118
|
});
|
|
115
119
|
|
|
@@ -119,6 +123,7 @@ const createSystem = (emitter, sandbox, config) => {
|
|
|
119
123
|
const getMousePosition = async () => {
|
|
120
124
|
// Get Mouse Position from command line
|
|
121
125
|
let result = await sandbox.send({
|
|
126
|
+
os: "linux",
|
|
122
127
|
type: "system.get-mouse-position",
|
|
123
128
|
});
|
|
124
129
|
|