testronaut 1.0.8 → 1.0.9

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/core/agent.js CHANGED
@@ -2,33 +2,6 @@ import 'dotenv/config';
2
2
  import { ChromeBrowser } from '../tools/chromeBrowser.js';
3
3
  import { turnLoop } from './turnLoop.js';
4
4
 
5
-
6
- // export async function runAgent(goal, maxTurns = 10) {
7
- // console.log(`\nšŸš€ Running Agent with goal: ${goal}`);
8
- // const browser = new ChromeBrowser();
9
- // await browser.start();
10
-
11
- // const messages = [
12
- // {
13
- // role: 'system',
14
- // content: `
15
- // You are an autonomous web agent. Use function calls to complete the user's goal.
16
- // If you are unsure of the selectors for inputs or buttons, call 'get_dom' to retrieve page HTML,
17
- // analyze it, then make your best guess based on labels, names, types, and placeholder values.
18
- // After completing the goal, respond with a final plain-text message starting with SUCCESS or FAILURE.
19
- // `.trim(),
20
- // },
21
- // { role: 'user', content: goal },
22
- // ];
23
-
24
- // try {
25
- // const result = await turnLoop(browser, messages, maxTurns);
26
- // !result && console.log('šŸ›‘ Agent ran out of turns.')
27
- // } finally {
28
- // await browser.close();
29
- // }
30
- // }
31
-
32
5
  export async function runAgent(goals, missionName, maxTurns = 20, ) {
33
6
  const browser = new ChromeBrowser();
34
7
  await browser.start();
@@ -86,13 +59,3 @@ export async function runAgent(goals, missionName, maxTurns = 20, ) {
86
59
  await browser.close();
87
60
  }
88
61
  }
89
-
90
-
91
- // (async () => {
92
- // await runAgent(`
93
- // Visit ${process.env.URL}.
94
- // Log in using ${process.env.USERNAME} and password ${process.env.PASSWORD}.
95
- // After clicking the login button, use 'check_text' to verify that "${process.env.AFTER_LOGIN_CHECK}" appears on the page.
96
- // If found, report SUCCESS. Otherwise, report FAILURE.
97
- // `);
98
- // })();
package/core/turnLoop.js CHANGED
@@ -18,6 +18,7 @@ const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
18
18
 
19
19
  let totalTokensUsed = 0;
20
20
  let turnTimestamps = [];
21
+ let shouldBackoff;
21
22
 
22
23
  const pushDOMAssistant = async (browser, messages, agentMemory, { skipIfLastTool } = {}) => {
23
24
  if (skipIfLastTool && skipIfLastTool.includes(messages.at(-1)?.name)) {
@@ -61,48 +62,22 @@ export const turnLoop = async (browser, messages, maxTurns, currentTurn = 0, ret
61
62
 
62
63
  for (let turn = currentTurn; turn < maxTurns; turn++) {
63
64
  console.log(`\nšŸ”„ Turn ${turn + 1}/${maxTurns}`);
64
- // console.log("current tools", toolsSchema);
65
-
66
- // const now = Date.now();
67
- // turnTimestamps = turnTimestamps.filter(ts => now - ts < 60000);
65
+ let response;
66
+ try {
68
67
 
69
- // Recalculate the rolling window before checking cooldown
70
- const pruned = pruneOldTokenUsage(turnTimestamps);
71
- turnTimestamps = pruned.turnTimestamps;
72
- totalTokensUsed = pruned.totalTokensUsed;
68
+ // Recalculate the rolling window before checking cooldown
69
+ ({ turnTimestamps, totalTokensUsed } = pruneOldTokenUsage(turnTimestamps));
73
70
 
74
- // Cool off check
75
- const cooldownResult = await tokenUseCoolOff(totalTokensUsed, turnTimestamps);
76
- totalTokensUsed = cooldownResult.totalTokensUsed;
77
- turnTimestamps = cooldownResult.turnTimestamps;
78
- if (cooldownResult.shouldBackoff) {
79
- return await turnLoop(browser, messages, maxTurns, turn);
80
- }
81
- // if (await tokenUseCoolOff(totalTokensUsed, turnTimestamps)) {return await turnLoop(browser, messages, maxTurns, turn)}
71
+ // Cool off check
72
+ ({ totalTokensUsed, turnTimestamps, shouldBackoff } = await tokenUseCoolOff(totalTokensUsed, turnTimestamps));
73
+ if(shouldBackoff) return await turnLoop(browser, messages, maxTurns, turn);
82
74
 
83
- const isValid = validateAndInsertMissingToolResponses(messages, {
84
- insertPlaceholders: true, // safe fallback behavior
85
- });
86
-
87
- if (!isValid) {
88
- console.error('āŒ Tool call structure invalid and no placeholders inserted.');
89
- return false;
90
- }
91
- // console.log(JSON.stringify(messages, null, 2));
75
+ //Tool Validator
76
+ if (!validateAndInsertMissingToolResponses(messages, { insertPlaceholders: true })) {
77
+ console.error('āŒ Tool call structure invalid and no placeholders inserted.');
78
+ return false;
79
+ }
92
80
 
93
- let response;
94
- try {
95
- // console.log(
96
- // 'šŸ” Verifying message structure before OpenAI call:\n',
97
- // messages.map(m => ({
98
- // role: m.role,
99
- // name: m.name,
100
- // tool_call_id: m.tool_call_id,
101
- // tool_calls: m.tool_calls?.map(t => t.id),
102
- // content: m.content?.slice?.(0, 100), // truncate for readability
103
- // }))
104
- // );
105
-
106
81
  const unrespondedCalls = messages.filter(
107
82
  (msg, i) =>
108
83
  msg.role === 'assistant' &&
@@ -140,8 +115,8 @@ export const turnLoop = async (browser, messages, maxTurns, currentTurn = 0, ret
140
115
  return await turnLoop(browser, messages, maxTurns, turn, retryCount + 1);
141
116
  } else if (err.status === 400) {
142
117
  console.error('āŒ Bad request:', err.message);
143
- console.log("current tools", toolsSchema);
144
- console.log("messages: ", messages)
118
+ // console.log("current tools", toolsSchema);
119
+ // console.log("messages: ", messages)
145
120
  return false;
146
121
  } else {
147
122
  throw err;
@@ -149,40 +124,23 @@ export const turnLoop = async (browser, messages, maxTurns, currentTurn = 0, ret
149
124
  }
150
125
  console.log('Response received from OpenAI');
151
126
  const usage = response.usage;
152
- // if (usage) {
153
- // console.log(`šŸ“Š Token Usage This Turn → Prompt: ${usage.prompt_tokens}, Completion: ${usage.completion_tokens}, Total: ${usage.total_tokens}`);
154
- // totalTokensUsed += usage?.total_tokens || 0;
155
- // turnTimestamps.push(now);
156
- // console.log(`šŸ“ˆ Running Total Tokens Used: ${totalTokensUsed}`);
157
- // }
158
127
  if (usage) {
159
128
  const tokensUsed = usage.total_tokens || 0;
160
129
  console.log(`šŸ“Š Token Usage This Turn → Total: ${tokensUsed}`);
161
130
  recordTokenUsage(turnTimestamps, tokensUsed);
162
- const pruned = pruneOldTokenUsage(turnTimestamps);
163
- turnTimestamps = pruned.turnTimestamps;
164
- totalTokensUsed = pruned.totalTokensUsed;
131
+ ({ turnTimestamps, totalTokensUsed } = pruneOldTokenUsage(turnTimestamps));
132
+
165
133
  console.log(`šŸ“ˆ Running Total Tokens Used (Rolling 60s): ${totalTokensUsed}`);
166
134
  }
167
135
 
168
136
  const msg = response.choices[0].message;
169
137
 
170
- // if (!msg.tool_calls?.length) {
171
- // console.error('āŒ Assistant message had empty or missing tool_calls array');
172
- // return false;
173
- // }
174
-
175
138
  if (msg.tool_calls?.length) {
176
139
  console.log('Processing tool calls...');
177
- // messages.push(msg);
178
140
  const toolResponses = [];
179
141
  let lastToolName = null;
180
142
 
181
143
  for (const call of msg.tool_calls) {
182
- const toolCallId = call.id;
183
- // const hasResponse = messages.some(m =>
184
- // m.role === 'tool' && m.tool_call_id === toolCallId
185
- // );
186
144
  const fnName = call.function.name;
187
145
  lastToolName = fnName;
188
146
  const args = JSON.parse(call.function.arguments || '{}');
@@ -194,15 +152,6 @@ export const turnLoop = async (browser, messages, maxTurns, currentTurn = 0, ret
194
152
  // console.log("agentMemory: ", agentMemory);
195
153
  result = await CHROME_TOOL_MAP[fnName](browser, args, agentMemory);
196
154
 
197
- // Smart DOM-ready wait after major actions like click
198
- // if (['click', 'click_text', 'fill'].includes(fnName)) {
199
- // try {
200
- // await browser.page.waitForLoadState?.('domcontentloaded', { timeout: 5000 });
201
- // await browser.page.waitForTimeout?.(2000); // allow animations/navigation
202
- // } catch (err) {
203
- // console.warn(`āš ļø DOM readiness wait failed after ${fnName}: ${err.message}`);
204
- // }
205
- // }
206
155
  if (typeof result !== 'string') {
207
156
  result = JSON.stringify(result ?? '');
208
157
  }
@@ -211,26 +160,9 @@ export const turnLoop = async (browser, messages, maxTurns, currentTurn = 0, ret
211
160
  result = errorMessage;
212
161
  }
213
162
 
214
- // šŸ” Diagnostic: log whether this tool call succeeded or failed
215
163
  console.log(`[tool ] ← ${fnName} result:`, errorMessage ? 'āŒ Failed' : 'āœ… Success');
216
-
217
164
  const truncated = result.length > 100 ? result.slice(0, 100) + '…' : result;
218
-
219
- // if (result.length > 100) {
220
- // // const logFile = `missions/mission_reports/tool-result-${Date.now()}.log`;
221
- // // fs.writeFileSync(logFile, result);
222
- // console.log(`[tool ] ← ${truncated} (full output written to ${logFile})`);
223
- // } else {
224
- console.log(`[tool ] ← ${truncated}`);
225
- // }
226
-
227
- // messages.push({
228
- // role: 'tool',
229
- // tool_call_id: call.id,
230
- // name: fnName,
231
- // type: 'function',
232
- // content: result,
233
- // });
165
+ console.log(`[tool ] ← ${truncated}`);
234
166
 
235
167
  toolResponses.push({
236
168
  role: 'tool',
@@ -249,7 +181,7 @@ export const turnLoop = async (browser, messages, maxTurns, currentTurn = 0, ret
249
181
  const domHtml = await CHROME_TOOL_MAP.get_dom(browser, {
250
182
  limit: 100000,
251
183
  exclude: true,
252
- focus: [], // or try focusing on ['main'] to keep it light
184
+ focus: [],
253
185
  }, agentMemory);
254
186
  await tokenEstimate('gpt-4o', domHtml);
255
187
  // fs.writeFileSync(`missions/mission_reports/debug-expanded-${Date.now()}.html`, domHtml);
@@ -258,18 +190,9 @@ export const turnLoop = async (browser, messages, maxTurns, currentTurn = 0, ret
258
190
  });
259
191
  console.log(`[auto] → DOM size after ${fnName}: ${domHtml.length} chars`);
260
192
  }
261
-
262
-
263
-
264
- // if (!hasResponse) {
265
- // console.error(`āŒ Tool call ${toolCallId} for ${call.function.name} was not followed by a response`);
266
- // return false;
267
- // }
268
- }
269
193
 
270
- // Only after ALL tool responses are ready
194
+ }
271
195
  messages.push(msg, ...toolResponses);
272
-
273
196
  continue;
274
197
  }
275
198
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testronaut",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "main": "./runner/testronaut.js",
5
5
  "bin": {
6
6
  "testronaut": "./bin/cli.js"
@@ -25,5 +25,12 @@
25
25
  "openai": "^5.10.2",
26
26
  "playwright": "^1.54.1",
27
27
  "prompts": "^2.4.2"
28
- }
28
+ },
29
+ "keywords": [
30
+ "cli",
31
+ "testing",
32
+ "automation",
33
+ "node",
34
+ "qa"
35
+ ]
29
36
  }
@@ -6,4 +6,4 @@ mission('Collegium Dashboard Login Test', () => {
6
6
  objective('should land on dashboard after login', dashboardWorkflow);
7
7
  });
8
8
 
9
- await launch(loginWorkflow); // Setup: login
9
+ await launch(loginWorkflow);
@@ -16,38 +16,6 @@ export async function launch(preMissionSetup) {
16
16
  await runSuite(objectives, preMissionSetup);
17
17
  }
18
18
 
19
-
20
- // export async function runMissions({ preMission, mission, postMission }) {
21
- // const goals = [];
22
-
23
- // if (preMission) {
24
- // console.log('\n🧭 Running pre-mission setup:', preMission);
25
- // goals.push({ goal: preMission, label: 'pre-mission' });
26
- // }
27
-
28
- // if (mission) {
29
- // console.log('\n🧭 Running main mission:', mission);
30
- // goals.push({ goal: mission, label: 'mission' });
31
- // }
32
-
33
- // if (postMission) {
34
- // console.log('\n🧭 Running post-mission cleanup:', postMission);
35
- // goals.push({ goal: postMission, label: 'post-mission' });
36
- // }
37
-
38
- // console.log('\n🧭 Running mission flow:\n', goals);
39
-
40
- // // for (const { goal, label } of goals) {
41
- // // console.log(`\n🧭 Running ${label}:\n`);
42
- // const success = await runAgent(goals);
43
- // if (!success) {
44
- // console.log(`āŒ Aborting after failed goal.`);
45
- // return;
46
- // }
47
- // // }
48
- // console.log('\nāœ… Mission flow complete.');
49
- // }
50
-
51
19
  export async function runMissions({ preMission, mission, postMission }, missionName) {
52
20
  const normalizeToArray = (x) => (Array.isArray(x) ? x : x ? [x] : []);
53
21
 
@@ -58,17 +26,14 @@ export async function runMissions({ preMission, mission, postMission }, missionN
58
26
  const goals = [];
59
27
 
60
28
  if (pre.length) {
61
- // console.log('\n🧭 Running pre-mission setup:', pre);
62
29
  goals.push(...pre.map(fn => ({ goal: fn, label: 'pre-mission' })));
63
30
  }
64
31
 
65
32
  if (main.length) {
66
- // console.log('\n🧭 Running main mission(s):', main);
67
33
  goals.push(...main.map(fn => ({ goal: fn, label: 'mission' })));
68
34
  }
69
35
 
70
36
  if (post.length) {
71
- // console.log('\n🧭 Running post-mission cleanup:', post);
72
37
  goals.push(...post.map(fn => ({ goal: fn, label: 'post-mission' })));
73
38
  }
74
39
 
@@ -84,5 +49,4 @@ export async function runMissions({ preMission, mission, postMission }, missionN
84
49
  }
85
50
  console.log('\nāœ… Mission flow complete.');
86
51
  return success;
87
-
88
52
  }
@@ -92,7 +92,7 @@ export class ChromeBrowser {
92
92
  await el.click();
93
93
  await this.page.screenshot({ path: `missions/mission_reports/screenshot_after_click_${Date.now()}.png` });
94
94
  await this.page.waitForTimeout(5000);
95
- // await this.page.screenshot({ path: `screenshot_after_waiting_click_${Date.now()}.png` });
95
+ await this.page.screenshot({ path: `screenshot_after_waiting_click_${Date.now()}.png` });
96
96
  return `clicked text "${text}"`;
97
97
 
98
98
  }
@@ -147,11 +147,6 @@ export class ChromeBrowser {
147
147
  await this.page.waitForSelector(selector, { timeout: 30_000 });
148
148
  await this.page.click(selector);
149
149
  await this.page.waitForTimeout(delayMs);
150
-
151
- const expandedDom = await this.page.content();
152
- // const filename = `missions/mission_reports/expand_menu_dump_${Date.now()}.html`;
153
- // fs.writeFileSync(filename, expandedDom);
154
- // console.log(`[expand_menu] → Dumped DOM after expand click to ${filename}`);
155
150
  await this.page.screenshot({ path: `missions/mission_reports/screenshot_expand_menu_${Date.now()}.png` });
156
151
 
157
152
  return `expanded menu using ${selector}`;