testdriverai 6.0.16-canary.e337e67.0 → 6.0.16-canary.f0eefe0.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/agent/events.js +1 -1
- package/agent/index.js +61 -32
- package/agent/lib/commander.js +25 -16
- package/agent/lib/commands.js +23 -19
- package/agent/lib/sandbox.js +0 -4
- package/docs/docs.json +2 -2
- package/docs/guide/protips.mdx +3 -2
- package/package.json +1 -1
- package/curl.sh +0 -13
- package/debug-ast.js +0 -38
package/agent/events.js
CHANGED
|
@@ -33,7 +33,6 @@ const events = {
|
|
|
33
33
|
vm: {
|
|
34
34
|
show: "vm:show",
|
|
35
35
|
},
|
|
36
|
-
narration: "narration",
|
|
37
36
|
status: "status",
|
|
38
37
|
log: {
|
|
39
38
|
markdown: {
|
|
@@ -45,6 +44,7 @@ const events = {
|
|
|
45
44
|
log: "log:log",
|
|
46
45
|
warn: "log:warn",
|
|
47
46
|
debug: "log:debug",
|
|
47
|
+
narration: "log:narration",
|
|
48
48
|
},
|
|
49
49
|
command: {
|
|
50
50
|
start: "command:start",
|
package/agent/index.js
CHANGED
|
@@ -158,7 +158,7 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
158
158
|
// single function to handle all program exits
|
|
159
159
|
// allows us to save the current state, run lifecycle hooks, and track analytics
|
|
160
160
|
async exit(failed = true, shouldSave = false, shouldRunPostrun = false) {
|
|
161
|
-
this.emitter.emit(events.log.
|
|
161
|
+
this.emitter.emit(events.log.narration, theme.dim("exiting..."), true);
|
|
162
162
|
|
|
163
163
|
// Clean up redraw interval
|
|
164
164
|
if (this.redraw && this.redraw.cleanup) {
|
|
@@ -192,7 +192,7 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
192
192
|
|
|
193
193
|
// fatal errors always exit the program
|
|
194
194
|
// this ensure we log the error, summarize it, and exit cleanly
|
|
195
|
-
async dieOnFatal(error) {
|
|
195
|
+
async dieOnFatal(error, skipPostrun = false) {
|
|
196
196
|
// Show error with source context if available
|
|
197
197
|
const errorContext = this.sourceMapper.getErrorWithSourceContext(error);
|
|
198
198
|
if (errorContext) {
|
|
@@ -204,9 +204,13 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
204
204
|
);
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
207
|
+
if (skipPostrun) {
|
|
208
|
+
this.exit(true);
|
|
209
|
+
} else {
|
|
210
|
+
await this.summarize(error.message);
|
|
211
|
+
// Always run postrun lifecycle script, even for fatal errors
|
|
212
|
+
return await this.exit(true, false, true);
|
|
213
|
+
}
|
|
210
214
|
}
|
|
211
215
|
|
|
212
216
|
// creates a new "thread" in which the AI is given an error
|
|
@@ -286,7 +290,7 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
286
290
|
image = null;
|
|
287
291
|
}
|
|
288
292
|
|
|
289
|
-
this.emitter.emit(events.log.
|
|
293
|
+
this.emitter.emit(events.log.narration, theme.dim("thinking..."), true);
|
|
290
294
|
|
|
291
295
|
const streamId = `error-${Date.now()}`;
|
|
292
296
|
this.emitter.emit(events.log.markdown.start, streamId);
|
|
@@ -328,14 +332,14 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
328
332
|
|
|
329
333
|
if (this.checkCount >= this.checkLimit) {
|
|
330
334
|
this.emitter.emit(
|
|
331
|
-
events.log.
|
|
335
|
+
events.log.narration,
|
|
332
336
|
theme.red("Exploratory loop detected. Exiting."),
|
|
333
337
|
);
|
|
334
338
|
await this.summarize("Check loop detected.");
|
|
335
339
|
return await this.exit(true);
|
|
336
340
|
}
|
|
337
341
|
|
|
338
|
-
this.emitter.emit(events.log.
|
|
342
|
+
this.emitter.emit(events.log.narration, theme.dim("checking..."));
|
|
339
343
|
|
|
340
344
|
// check asks the ai if the task is complete
|
|
341
345
|
let thisScreenshot = await this.system.captureScreenBase64(1, false, true);
|
|
@@ -579,8 +583,6 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
579
583
|
"check thinks more needs to be done",
|
|
580
584
|
);
|
|
581
585
|
|
|
582
|
-
this.emitter.emit(events.log.log, theme.dim("not done yet!"));
|
|
583
|
-
|
|
584
586
|
return await this.aiExecute(response, validateAndLoop);
|
|
585
587
|
} else {
|
|
586
588
|
this.emitter.emit(events.log.debug, "seems complete, returning");
|
|
@@ -715,7 +717,7 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
715
717
|
}
|
|
716
718
|
}
|
|
717
719
|
|
|
718
|
-
this.emitter.emit(events.log.
|
|
720
|
+
this.emitter.emit(events.log.narration, theme.dim("thinking..."), true);
|
|
719
721
|
|
|
720
722
|
let response = `\`\`\`yaml
|
|
721
723
|
commands:
|
|
@@ -743,7 +745,7 @@ commands:
|
|
|
743
745
|
|
|
744
746
|
this.tasks.push(currentTask);
|
|
745
747
|
|
|
746
|
-
this.emitter.emit(events.log.
|
|
748
|
+
this.emitter.emit(events.log.narration, theme.dim("thinking..."), true);
|
|
747
749
|
|
|
748
750
|
this.lastScreenshot = await this.system.captureScreenBase64();
|
|
749
751
|
|
|
@@ -785,7 +787,7 @@ commands:
|
|
|
785
787
|
async generate(type, count, baseYaml, skipYaml = false) {
|
|
786
788
|
this.emitter.emit(events.log.debug, "generate called, %s", type);
|
|
787
789
|
|
|
788
|
-
this.emitter.emit(events.log.
|
|
790
|
+
this.emitter.emit(events.log.narration, theme.dim("thinking..."), true);
|
|
789
791
|
|
|
790
792
|
if (baseYaml && !skipYaml) {
|
|
791
793
|
await this.runLifecycle("prerun");
|
|
@@ -862,7 +864,7 @@ commands:
|
|
|
862
864
|
|
|
863
865
|
// this is the functinoality for "undo"
|
|
864
866
|
async popFromHistory(fullStep) {
|
|
865
|
-
this.emitter.emit(events.log.
|
|
867
|
+
this.emitter.emit(events.log.narration, theme.dim("undoing..."), true);
|
|
866
868
|
|
|
867
869
|
if (this.executionHistory.length) {
|
|
868
870
|
if (fullStep) {
|
|
@@ -942,12 +944,16 @@ ${yml}
|
|
|
942
944
|
async summarize(error = null) {
|
|
943
945
|
this.analytics.track("summarize");
|
|
944
946
|
|
|
945
|
-
this.emitter.emit(
|
|
947
|
+
this.emitter.emit(
|
|
948
|
+
events.log.narration,
|
|
949
|
+
theme.dim("reviewing test..."),
|
|
950
|
+
true,
|
|
951
|
+
);
|
|
946
952
|
|
|
947
953
|
// let text = prompts.summarize(tasks, error);
|
|
948
954
|
let image = await this.system.captureScreenBase64();
|
|
949
955
|
|
|
950
|
-
this.emitter.emit(events.log.
|
|
956
|
+
this.emitter.emit(events.log.narration, theme.dim("summarizing..."), true);
|
|
951
957
|
|
|
952
958
|
const streamId = `summarize-${Date.now()}`;
|
|
953
959
|
this.emitter.emit(events.log.markdown.start, streamId);
|
|
@@ -1120,7 +1126,7 @@ ${regression}
|
|
|
1120
1126
|
timestamp: fileStartTime,
|
|
1121
1127
|
});
|
|
1122
1128
|
|
|
1123
|
-
this.emitter.emit(events.log.
|
|
1129
|
+
this.emitter.emit(events.log.narration, theme.cyan(`running ${file}...`));
|
|
1124
1130
|
|
|
1125
1131
|
let ymlObj = await this.loadYML(file);
|
|
1126
1132
|
|
|
@@ -1512,8 +1518,8 @@ ${regression}
|
|
|
1512
1518
|
if (this.sandboxId && !this.config.CI && !createNew) {
|
|
1513
1519
|
// Attempt to connect to known instance
|
|
1514
1520
|
this.emitter.emit(
|
|
1515
|
-
events.log.
|
|
1516
|
-
theme.dim(
|
|
1521
|
+
events.log.narration,
|
|
1522
|
+
theme.dim(`connecting to sandbox ${this.sandboxId}...`),
|
|
1517
1523
|
);
|
|
1518
1524
|
|
|
1519
1525
|
try {
|
|
@@ -1535,17 +1541,20 @@ ${regression}
|
|
|
1535
1541
|
}
|
|
1536
1542
|
}
|
|
1537
1543
|
|
|
1538
|
-
this.emitter.emit(
|
|
1544
|
+
this.emitter.emit(
|
|
1545
|
+
events.log.narration,
|
|
1546
|
+
theme.dim(`creating new sandbox...`),
|
|
1547
|
+
);
|
|
1539
1548
|
this.emitter.emit(
|
|
1540
1549
|
events.log.log,
|
|
1541
|
-
theme.dim(`
|
|
1550
|
+
theme.dim(`this can take between 10 - 240 seconds`),
|
|
1542
1551
|
);
|
|
1543
1552
|
// We don't have resiliency/retries baked in, so let's at least give it 1 attempt
|
|
1544
1553
|
// to see if that fixes the issue.
|
|
1545
1554
|
let newSandbox = await this.createNewSandbox().catch(() => {
|
|
1546
1555
|
this.emitter.emit(
|
|
1547
|
-
events.log.
|
|
1548
|
-
theme.dim(`
|
|
1556
|
+
events.log.narration,
|
|
1557
|
+
theme.dim(`double-checking sandbox availability`),
|
|
1549
1558
|
);
|
|
1550
1559
|
|
|
1551
1560
|
return this.createNewSandbox();
|
|
@@ -1572,7 +1581,7 @@ ${regression}
|
|
|
1572
1581
|
if (!debuggerStarted) {
|
|
1573
1582
|
debuggerStarted = true; // Prevent multiple starts, especially when running test in parallel
|
|
1574
1583
|
this.emitter.emit(
|
|
1575
|
-
events.log.
|
|
1584
|
+
events.log.narration,
|
|
1576
1585
|
theme.green(`Starting debugger server...`),
|
|
1577
1586
|
);
|
|
1578
1587
|
debuggerProcess = await createDebuggerProcess(
|
|
@@ -1584,9 +1593,12 @@ ${regression}
|
|
|
1584
1593
|
this.emitter.emit(events.log.log, `This is beta software!`);
|
|
1585
1594
|
this.emitter.emit(
|
|
1586
1595
|
events.log.log,
|
|
1587
|
-
theme.yellow(`Join our
|
|
1596
|
+
theme.yellow(`Join our Discord for help`),
|
|
1597
|
+
);
|
|
1598
|
+
this.emitter.emit(
|
|
1599
|
+
events.log.log,
|
|
1600
|
+
`https://discord.com/invite/cWDFW8DzPm`,
|
|
1588
1601
|
);
|
|
1589
|
-
this.emitter.emit(events.log.log, `https://forums.testdriver.ai`);
|
|
1590
1602
|
|
|
1591
1603
|
// make testdriver directory if it doesn't exist
|
|
1592
1604
|
let testdriverFolder = path.join(this.workingDir);
|
|
@@ -1690,16 +1702,33 @@ ${regression}
|
|
|
1690
1702
|
|
|
1691
1703
|
async connectToSandboxService() {
|
|
1692
1704
|
this.emitter.emit(
|
|
1693
|
-
events.log.
|
|
1694
|
-
theme.
|
|
1705
|
+
events.log.narration,
|
|
1706
|
+
theme.dim(`establishing connection...`),
|
|
1695
1707
|
);
|
|
1696
|
-
await this.sandbox.boot(this.config.TD_API_ROOT);
|
|
1697
|
-
|
|
1698
|
-
|
|
1708
|
+
let ableToBoot = await this.sandbox.boot(this.config.TD_API_ROOT);
|
|
1709
|
+
|
|
1710
|
+
if (!ableToBoot) {
|
|
1711
|
+
return await this.dieOnFatal(
|
|
1712
|
+
`Unable to connect to TestDriver sandbox service at ${this.config.TD_API_ROOT}.
|
|
1713
|
+
Please check your network connection, TD_API_KEY, or the service status.`,
|
|
1714
|
+
true,
|
|
1715
|
+
);
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
this.emitter.emit(events.log.narration, theme.dim(`authenticating...`));
|
|
1719
|
+
let ableToAuth = await this.sandbox.auth(this.config.TD_API_KEY);
|
|
1720
|
+
|
|
1721
|
+
if (!ableToAuth) {
|
|
1722
|
+
return await this.dieOnFatal(
|
|
1723
|
+
`Unable to authorize with TestDriver sandbox service at ${this.config.TD_API_ROOT}.
|
|
1724
|
+
Please check your network connection, TD_API_KEY, or the service status.`,
|
|
1725
|
+
true,
|
|
1726
|
+
);
|
|
1727
|
+
}
|
|
1699
1728
|
}
|
|
1700
1729
|
|
|
1701
1730
|
async connectToSandboxDirect(sandboxId, persist = false) {
|
|
1702
|
-
this.emitter.emit(events.log.
|
|
1731
|
+
this.emitter.emit(events.log.narration, theme.dim(`connecting...`));
|
|
1703
1732
|
let instance = await this.sandbox.connect(sandboxId, persist);
|
|
1704
1733
|
return instance;
|
|
1705
1734
|
}
|
package/agent/lib/commander.js
CHANGED
|
@@ -80,20 +80,20 @@ 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.narration, `typing ${object.text}`);
|
|
83
|
+
emitter.emit(events.log.narration, `typing ${object.text}`);
|
|
84
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
88
|
emitter.emit(
|
|
89
|
-
events.narration,
|
|
89
|
+
events.log.narration,
|
|
90
90
|
`pressing keys ${object.keys.join(",")}`,
|
|
91
91
|
);
|
|
92
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.narration, `scrolling ${object.direction}`);
|
|
96
|
+
emitter.emit(events.log.narration, `scrolling ${object.direction}`);
|
|
97
97
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
98
98
|
response = await commands.scroll(
|
|
99
99
|
object.direction,
|
|
@@ -102,17 +102,20 @@ commands:
|
|
|
102
102
|
);
|
|
103
103
|
break;
|
|
104
104
|
case "wait":
|
|
105
|
-
emitter.emit(
|
|
105
|
+
emitter.emit(
|
|
106
|
+
events.log.narration,
|
|
107
|
+
`waiting ${object.timeout} seconds`,
|
|
108
|
+
);
|
|
106
109
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
107
110
|
response = await commands.wait(object.timeout);
|
|
108
111
|
break;
|
|
109
112
|
case "click":
|
|
110
|
-
emitter.emit(events.narration, `${object.action}`);
|
|
113
|
+
emitter.emit(events.log.narration, `${object.action}`);
|
|
111
114
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
112
115
|
response = await commands["click"](object.x, object.y, object.action);
|
|
113
116
|
break;
|
|
114
117
|
case "hover":
|
|
115
|
-
emitter.emit(events.narration, `moving mouse`);
|
|
118
|
+
emitter.emit(events.log.narration, `moving mouse`);
|
|
116
119
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
117
120
|
response = await commands["hover"](object.x, object.y);
|
|
118
121
|
break;
|
|
@@ -121,7 +124,10 @@ commands:
|
|
|
121
124
|
response = await commands["drag"](object.x, object.y);
|
|
122
125
|
break;
|
|
123
126
|
case "hover-text":
|
|
124
|
-
emitter.emit(
|
|
127
|
+
emitter.emit(
|
|
128
|
+
events.log.narration,
|
|
129
|
+
`searching for ${object.description}`,
|
|
130
|
+
);
|
|
125
131
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
126
132
|
response = await commands["hover-text"](
|
|
127
133
|
object.text,
|
|
@@ -132,7 +138,7 @@ commands:
|
|
|
132
138
|
break;
|
|
133
139
|
case "hover-image":
|
|
134
140
|
emitter.emit(
|
|
135
|
-
events.narration,
|
|
141
|
+
events.log.narration,
|
|
136
142
|
`searching for image of ${object.description}`,
|
|
137
143
|
);
|
|
138
144
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
@@ -144,13 +150,16 @@ commands:
|
|
|
144
150
|
case "match-image":
|
|
145
151
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
146
152
|
emitter.emit(
|
|
147
|
-
events.narration,
|
|
153
|
+
events.log.narration,
|
|
148
154
|
`${object.action} image ${object.path}`,
|
|
149
155
|
);
|
|
150
156
|
response = await commands["match-image"](object.path, object.action);
|
|
151
157
|
break;
|
|
152
158
|
case "wait-for-image":
|
|
153
|
-
emitter.emit(
|
|
159
|
+
emitter.emit(
|
|
160
|
+
events.log.narration,
|
|
161
|
+
`waiting for ${object.description}`,
|
|
162
|
+
);
|
|
154
163
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
155
164
|
response = await commands["wait-for-image"](
|
|
156
165
|
object.description,
|
|
@@ -158,7 +167,7 @@ commands:
|
|
|
158
167
|
);
|
|
159
168
|
break;
|
|
160
169
|
case "wait-for-text":
|
|
161
|
-
emitter.emit(events.narration, `waiting for ${object.text}`);
|
|
170
|
+
emitter.emit(events.log.narration, `waiting for ${object.text}`);
|
|
162
171
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
163
172
|
copy.text = "*****";
|
|
164
173
|
response = await commands["wait-for-text"](
|
|
@@ -168,7 +177,7 @@ commands:
|
|
|
168
177
|
);
|
|
169
178
|
break;
|
|
170
179
|
case "scroll-until-text":
|
|
171
|
-
emitter.emit(events.narration, `scrolling until ${object.text}`);
|
|
180
|
+
emitter.emit(events.log.narration, `scrolling until ${object.text}`);
|
|
172
181
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
173
182
|
copy.text = "*****";
|
|
174
183
|
response = await commands["scroll-until-text"](
|
|
@@ -181,7 +190,7 @@ commands:
|
|
|
181
190
|
break;
|
|
182
191
|
case "scroll-until-image": {
|
|
183
192
|
const needle = object.description || object.path;
|
|
184
|
-
emitter.emit(events.narration, `scrolling until ${needle}`);
|
|
193
|
+
emitter.emit(events.log.narration, `scrolling until ${needle}`);
|
|
185
194
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
186
195
|
response = await commands["scroll-until-image"](
|
|
187
196
|
object.description,
|
|
@@ -193,7 +202,7 @@ commands:
|
|
|
193
202
|
break;
|
|
194
203
|
}
|
|
195
204
|
case "focus-application":
|
|
196
|
-
emitter.emit(events.narration, `focusing ${object.name}`);
|
|
205
|
+
emitter.emit(events.log.narration, `focusing ${object.name}`);
|
|
197
206
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
198
207
|
response = await commands["focus-application"](object.name);
|
|
199
208
|
break;
|
|
@@ -205,12 +214,12 @@ commands:
|
|
|
205
214
|
break;
|
|
206
215
|
}
|
|
207
216
|
case "assert":
|
|
208
|
-
emitter.emit(events.narration, `asserting ${object.expect}`);
|
|
217
|
+
emitter.emit(events.log.narration, `asserting ${object.expect}`);
|
|
209
218
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
210
219
|
response = await commands.assert(object.expect, object.async);
|
|
211
220
|
break;
|
|
212
221
|
case "exec":
|
|
213
|
-
emitter.emit(events.narration, `exec`);
|
|
222
|
+
emitter.emit(events.log.narration, `exec`);
|
|
214
223
|
emitter.emit(
|
|
215
224
|
events.log.log,
|
|
216
225
|
generator.jsonToManual({
|
package/agent/lib/commands.js
CHANGED
|
@@ -190,7 +190,7 @@ const createCommands = (
|
|
|
190
190
|
}
|
|
191
191
|
};
|
|
192
192
|
|
|
193
|
-
emitter.emit(events.log.
|
|
193
|
+
emitter.emit(events.log.narration, `thinking...`);
|
|
194
194
|
|
|
195
195
|
if (async) {
|
|
196
196
|
await sdk
|
|
@@ -276,7 +276,7 @@ const createCommands = (
|
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
emitter.emit(
|
|
279
|
-
|
|
279
|
+
events.log.narration,
|
|
280
280
|
theme.dim(`${action} ${button} clicking at ${x}, ${y}...`),
|
|
281
281
|
true,
|
|
282
282
|
);
|
|
@@ -347,7 +347,7 @@ const createCommands = (
|
|
|
347
347
|
|
|
348
348
|
description = description ? description.toString() : null;
|
|
349
349
|
|
|
350
|
-
emitter.emit(events.log.
|
|
350
|
+
emitter.emit(events.log.narration, theme.dim("thinking..."), true);
|
|
351
351
|
|
|
352
352
|
let response = await sdk.req(
|
|
353
353
|
"hover/text",
|
|
@@ -375,7 +375,7 @@ const createCommands = (
|
|
|
375
375
|
// uses our api to find all images on screen
|
|
376
376
|
"hover-image": async (description, action = "click") => {
|
|
377
377
|
// take a screenshot
|
|
378
|
-
emitter.emit(events.log.
|
|
378
|
+
emitter.emit(events.log.narration, theme.dim("thinking..."), true);
|
|
379
379
|
|
|
380
380
|
let response = await sdk.req(
|
|
381
381
|
"hover/image",
|
|
@@ -446,7 +446,7 @@ const createCommands = (
|
|
|
446
446
|
},
|
|
447
447
|
"wait-for-image": async (description, timeout = 10000) => {
|
|
448
448
|
emitter.emit(
|
|
449
|
-
events.log.
|
|
449
|
+
events.log.narration,
|
|
450
450
|
theme.dim(
|
|
451
451
|
`waiting for an image matching description "${description}"...`,
|
|
452
452
|
),
|
|
@@ -467,7 +467,7 @@ const createCommands = (
|
|
|
467
467
|
durationPassed = new Date().getTime() - startTime;
|
|
468
468
|
if (!passed) {
|
|
469
469
|
emitter.emit(
|
|
470
|
-
events.log.
|
|
470
|
+
events.log.narration,
|
|
471
471
|
theme.dim(
|
|
472
472
|
`${niceSeconds(durationPassed)} seconds have passed without finding an image matching the description "${description}"`,
|
|
473
473
|
),
|
|
@@ -479,7 +479,7 @@ const createCommands = (
|
|
|
479
479
|
|
|
480
480
|
if (passed) {
|
|
481
481
|
emitter.emit(
|
|
482
|
-
events.log.
|
|
482
|
+
events.log.narration,
|
|
483
483
|
theme.dim(
|
|
484
484
|
`An image matching the description "${description}" found!`,
|
|
485
485
|
),
|
|
@@ -496,7 +496,7 @@ const createCommands = (
|
|
|
496
496
|
await redraw.start();
|
|
497
497
|
|
|
498
498
|
emitter.emit(
|
|
499
|
-
events.log.
|
|
499
|
+
events.log.narration,
|
|
500
500
|
theme.dim(`waiting for text: "${text}"...`),
|
|
501
501
|
true,
|
|
502
502
|
);
|
|
@@ -525,7 +525,7 @@ const createCommands = (
|
|
|
525
525
|
durationPassed = new Date().getTime() - startTime;
|
|
526
526
|
if (!passed) {
|
|
527
527
|
emitter.emit(
|
|
528
|
-
events.log.
|
|
528
|
+
events.log.narration,
|
|
529
529
|
theme.dim(
|
|
530
530
|
`${niceSeconds(durationPassed)} seconds have passed without finding "${text}"`,
|
|
531
531
|
),
|
|
@@ -536,7 +536,7 @@ const createCommands = (
|
|
|
536
536
|
}
|
|
537
537
|
|
|
538
538
|
if (passed) {
|
|
539
|
-
emitter.emit(events.log.
|
|
539
|
+
emitter.emit(events.log.narration, theme.dim(`"${text}" found!`), true);
|
|
540
540
|
return;
|
|
541
541
|
} else {
|
|
542
542
|
throw new MatchError(
|
|
@@ -554,7 +554,7 @@ const createCommands = (
|
|
|
554
554
|
await redraw.start();
|
|
555
555
|
|
|
556
556
|
emitter.emit(
|
|
557
|
-
events.log.
|
|
557
|
+
events.log.narration,
|
|
558
558
|
theme.dim(`scrolling for text: "${text}"...`),
|
|
559
559
|
true,
|
|
560
560
|
);
|
|
@@ -598,7 +598,7 @@ const createCommands = (
|
|
|
598
598
|
passed = response.data;
|
|
599
599
|
if (!passed) {
|
|
600
600
|
emitter.emit(
|
|
601
|
-
events.log.
|
|
601
|
+
events.log.narration,
|
|
602
602
|
theme.dim(
|
|
603
603
|
`scrolling ${direction} ${incrementDistance}px. ${scrollDistance + incrementDistance}/${maxDistance}px scrolled...`,
|
|
604
604
|
),
|
|
@@ -610,7 +610,7 @@ const createCommands = (
|
|
|
610
610
|
}
|
|
611
611
|
|
|
612
612
|
if (passed) {
|
|
613
|
-
emitter.emit(events.log.
|
|
613
|
+
emitter.emit(events.log.narration, theme.dim(`"${text}" found!`), true);
|
|
614
614
|
return;
|
|
615
615
|
} else {
|
|
616
616
|
throw new MatchError(
|
|
@@ -638,7 +638,7 @@ const createCommands = (
|
|
|
638
638
|
}
|
|
639
639
|
|
|
640
640
|
emitter.emit(
|
|
641
|
-
events.log.
|
|
641
|
+
events.log.narration,
|
|
642
642
|
theme.dim(`scrolling for an image matching "${needle}"...`),
|
|
643
643
|
true,
|
|
644
644
|
);
|
|
@@ -665,7 +665,7 @@ const createCommands = (
|
|
|
665
665
|
|
|
666
666
|
if (!passed) {
|
|
667
667
|
emitter.emit(
|
|
668
|
-
events.log.
|
|
668
|
+
events.log.narration,
|
|
669
669
|
theme.dim(`scrolling ${direction} ${incrementDistance} pixels...`),
|
|
670
670
|
true,
|
|
671
671
|
);
|
|
@@ -675,7 +675,11 @@ const createCommands = (
|
|
|
675
675
|
}
|
|
676
676
|
|
|
677
677
|
if (passed) {
|
|
678
|
-
emitter.emit(
|
|
678
|
+
emitter.emit(
|
|
679
|
+
events.log.narration,
|
|
680
|
+
theme.dim(`"${needle}" found!`),
|
|
681
|
+
true,
|
|
682
|
+
);
|
|
679
683
|
return;
|
|
680
684
|
} else {
|
|
681
685
|
throw new CommandError(
|
|
@@ -705,7 +709,7 @@ const createCommands = (
|
|
|
705
709
|
return await assert(assertion, true, async);
|
|
706
710
|
},
|
|
707
711
|
exec: async (language, code, timeout, silent = false) => {
|
|
708
|
-
emitter.emit(events.log.
|
|
712
|
+
emitter.emit(events.log.narration, theme.dim(`calling exec...`), true);
|
|
709
713
|
|
|
710
714
|
emitter.emit(events.log.log, code);
|
|
711
715
|
|
|
@@ -738,10 +742,10 @@ const createCommands = (
|
|
|
738
742
|
return result.out?.stdout?.trim();
|
|
739
743
|
}
|
|
740
744
|
} else if (language == "js") {
|
|
741
|
-
emitter.emit(events.log.
|
|
745
|
+
emitter.emit(events.log.narration, theme.dim(`running js...`), true);
|
|
742
746
|
|
|
743
747
|
emitter.emit(
|
|
744
|
-
events.log.
|
|
748
|
+
events.log.narration,
|
|
745
749
|
theme.dim(`running value of \`${plat}\` in local JS vm...`),
|
|
746
750
|
true,
|
|
747
751
|
);
|
package/agent/lib/sandbox.js
CHANGED
|
@@ -84,10 +84,6 @@ const createSandbox = (emitter, analytics) => {
|
|
|
84
84
|
this.socket.on("close", () => {
|
|
85
85
|
clearInterval(this.heartbeat);
|
|
86
86
|
// Emit a clear error event for API key issues
|
|
87
|
-
emitter.emit(events.error.fatal, {
|
|
88
|
-
message: "Socket closed. Check your API KEY (TD_API_KEY)",
|
|
89
|
-
code: "API_KEY_MISSING_OR_INVALID",
|
|
90
|
-
});
|
|
91
87
|
reject();
|
|
92
88
|
this.apiSocketConnected = false;
|
|
93
89
|
});
|
package/docs/docs.json
CHANGED
|
@@ -220,8 +220,8 @@
|
|
|
220
220
|
"href": "https://github.com/testdriverai/testdriverai"
|
|
221
221
|
},
|
|
222
222
|
{
|
|
223
|
-
"label": "
|
|
224
|
-
"href": "https://
|
|
223
|
+
"label": "Discord",
|
|
224
|
+
"href": "https://discord.com/invite/cWDFW8DzPm"
|
|
225
225
|
},
|
|
226
226
|
{
|
|
227
227
|
"label": "Report an Issue",
|
package/docs/guide/protips.mdx
CHANGED
|
@@ -13,6 +13,7 @@ When executing the `hover-image` and `hover-text` command, TestDriver may use th
|
|
|
13
13
|
|
|
14
14
|
TestDriver may not accurately locate matches in certain cases. This can lead to unexpected behavior when using the `hover-image` or `hover-text` command. For example, if you specify a description that's too generic or similar to other images on the screen, TestDriver may not be able to identify the correct image to interact with. To improve accuracy, use specific and unique descriptions for images and text.
|
|
15
15
|
When executing the `hover-image` and `hover-text` commands, TestDriver uses the provided description to identify the target image or text. If the description is too generic or matches multiple elements on the screen, TestDriver may not be able to determine which one to interact with, leading to unexpected behavior. To improve accuracy, always use specific and unique descriptions for both images and text.
|
|
16
|
+
|
|
16
17
|
### Avoid using `hover-text` for single characters (ex: `1`, `>`, `|`)
|
|
17
18
|
|
|
18
19
|
To improve accuracy of our model, we do not include single characters or symbols in the matching data. This means that `hover-text` may not work as expected for these cases. If you need to interact with single characters or symbols, consider using the `match-image` command with a screenshot of the target element instead.
|
|
@@ -29,14 +30,14 @@ Assertions use the current state of the page to determine if the step passes or
|
|
|
29
30
|
|
|
30
31
|
## Reporting issues
|
|
31
32
|
|
|
32
|
-
If you encounter any issues while using TestDriver, please report them to us. You can do this by creating a new issue in the [
|
|
33
|
+
If you encounter any issues while using TestDriver, please report them to us. You can do this by creating a new issue in the [discord](https://discord.com/invite/cWDFW8DzPm). We appreciate your feedback and will work to resolve any problems as quickly as possible.
|
|
33
34
|
|
|
34
35
|
<Card
|
|
35
36
|
title="Report an Issue"
|
|
36
37
|
icon="arrow-turn-down-right"
|
|
37
38
|
iconType="duotone"
|
|
38
39
|
horizontal
|
|
39
|
-
href="https://
|
|
40
|
+
href="https://discord.com/invite/cWDFW8DzPm"
|
|
40
41
|
>
|
|
41
42
|
We'd love to hear from you!
|
|
42
43
|
</Card>
|
package/package.json
CHANGED
package/curl.sh
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
curl -X POST "https://forums.testdriver.ai/users" \
|
|
2
|
-
-H "Content-Type: application/json" \
|
|
3
|
-
-H "Api-Key: YOUR_ADMIN_API_KEY" \
|
|
4
|
-
-H "Api-Username: admin" \
|
|
5
|
-
-d '{
|
|
6
|
-
"name": "Jane Doe",
|
|
7
|
-
"email": "jane.doe@example.com",
|
|
8
|
-
"username": "janedoe",
|
|
9
|
-
"active": false,
|
|
10
|
-
"approved": true,
|
|
11
|
-
"email_verified": false,
|
|
12
|
-
"staged": false
|
|
13
|
-
}'
|
package/debug-ast.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const yamlAst = require("@stoplight/yaml-ast-parser");
|
|
4
|
-
const yaml = require("js-yaml");
|
|
5
|
-
const fs = require("fs");
|
|
6
|
-
|
|
7
|
-
function debugAST() {
|
|
8
|
-
const testFile =
|
|
9
|
-
"/Users/ianjennings/Development/testdriverai/test-source-mapping.yaml";
|
|
10
|
-
const yamlContent = fs.readFileSync(testFile, "utf-8");
|
|
11
|
-
|
|
12
|
-
console.log("YAML Content:");
|
|
13
|
-
console.log(yamlContent);
|
|
14
|
-
console.log("\n" + "=".repeat(50) + "\n");
|
|
15
|
-
|
|
16
|
-
const ast = yamlAst.load(yamlContent);
|
|
17
|
-
|
|
18
|
-
console.log("AST Structure:");
|
|
19
|
-
console.log(
|
|
20
|
-
JSON.stringify(
|
|
21
|
-
ast,
|
|
22
|
-
(key, value) => {
|
|
23
|
-
if (key === "parent") return "[parent]"; // Avoid circular reference
|
|
24
|
-
return value;
|
|
25
|
-
},
|
|
26
|
-
2,
|
|
27
|
-
),
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
console.log("\n" + "=".repeat(50) + "\n");
|
|
31
|
-
|
|
32
|
-
// Test with js-yaml for comparison
|
|
33
|
-
const yamlObj = yaml.load(yamlContent);
|
|
34
|
-
console.log("js-yaml result:");
|
|
35
|
-
console.log(JSON.stringify(yamlObj, null, 2));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
debugAST();
|