testronaut 1.0.4 β 1.0.6
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/README.md +16 -5
- package/bin/cli.js +31 -0
- package/bin/createWelcomeMission.js +35 -0
- package/bin/init.js +77 -0
- package/core/turnLoop.js +3 -3
- package/package.json +3 -2
- package/tools/chromeBrowser.js +3 -3
package/README.md
CHANGED
|
@@ -20,9 +20,13 @@
|
|
|
20
20
|
```
|
|
21
21
|
npm install -g testronaut
|
|
22
22
|
```
|
|
23
|
-
|
|
23
|
+
Then initialize with npx, following the instructions:
|
|
24
24
|
```
|
|
25
|
-
npx testronaut
|
|
25
|
+
npx testronaut --init
|
|
26
|
+
```
|
|
27
|
+
run the welcome mission:
|
|
28
|
+
```
|
|
29
|
+
npx testronaut welcome.mission.js
|
|
26
30
|
```
|
|
27
31
|
π Directory Structure
|
|
28
32
|
Your project should include a missions/ folder with mission files like:
|
|
@@ -42,7 +46,7 @@ Create a file in your missions/ directory, e.g. missions/login.mission.js:
|
|
|
42
46
|
import { runMissions } from 'testronaut';
|
|
43
47
|
|
|
44
48
|
export const loginMission = `
|
|
45
|
-
|
|
49
|
+
Visit ${process.env.URL}.
|
|
46
50
|
Fill in the username field with ${process.env.USERNAME} and password field with ${process.env.PASSWORD}.
|
|
47
51
|
Submit the form.
|
|
48
52
|
Wait for the dashboard to appear.
|
|
@@ -56,7 +60,14 @@ export async function executeMission() {
|
|
|
56
60
|
}
|
|
57
61
|
```
|
|
58
62
|
|
|
59
|
-
Pass in credentials using an .env file
|
|
63
|
+
Pass in credentials using an .env file. The .env file should also include your Open AI API key (permissions to the completions endpoint required):
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
OPENAI_API_KEY=sk-proj-############
|
|
67
|
+
URL=https://example.com/login
|
|
68
|
+
USERNAME=example@example.com
|
|
69
|
+
PASSWORD=********
|
|
70
|
+
```
|
|
60
71
|
|
|
61
72
|
You can chain multiple phases:
|
|
62
73
|
|
|
@@ -91,7 +102,7 @@ testronaut
|
|
|
91
102
|
Run a specific file:
|
|
92
103
|
|
|
93
104
|
```
|
|
94
|
-
testronaut
|
|
105
|
+
testronaut login.mission.js
|
|
95
106
|
```
|
|
96
107
|
|
|
97
108
|
π§° Available Helpers
|
package/bin/cli.js
CHANGED
|
@@ -2,12 +2,43 @@
|
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
|
+
import { initializeTestronautProject } from './init.js';
|
|
6
|
+
import { createWelcomeMission } from './createWelcomeMission.js';
|
|
5
7
|
|
|
6
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
9
|
|
|
8
10
|
const args = process.argv.slice(2);
|
|
9
11
|
const missionsDir = path.resolve(process.cwd(), 'missions');
|
|
10
12
|
|
|
13
|
+
const HELP_TEXT = `
|
|
14
|
+
π§βπ testronaut - Autonomous Agent Mission Runner
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
npx testronaut Run all missions in the ./missions directory
|
|
18
|
+
npx testronaut <file> Run a specific mission file (e.g., login.mission.js)
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
--init Scaffold project folders and a welcome mission
|
|
22
|
+
--help Show this help message
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
npx testronaut
|
|
26
|
+
npx testronaut login.mission.js
|
|
27
|
+
npx testronaut --init
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if (args.includes('--init')) {
|
|
32
|
+
await initializeTestronautProject();
|
|
33
|
+
await createWelcomeMission();
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (args.includes('--help')) {
|
|
38
|
+
console.log(HELP_TEXT);
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
|
|
11
42
|
if (!fs.existsSync(missionsDir)) {
|
|
12
43
|
console.error('β No `missions` directory found.');
|
|
13
44
|
process.exit(1);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export const createWelcomeMission = async () => {
|
|
5
|
+
const missionsDir = path.join(process.cwd(), 'missions');
|
|
6
|
+
const welcomePath = path.join(missionsDir, 'welcome.mission.js');
|
|
7
|
+
|
|
8
|
+
if (fs.existsSync(welcomePath)) {
|
|
9
|
+
console.log('β
welcome.mission.js already exists');
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const content = `import { runMissions } from 'testronaut';
|
|
14
|
+
|
|
15
|
+
const welcomeGoal = \`
|
|
16
|
+
Welcome to Testronaut!
|
|
17
|
+
|
|
18
|
+
This is your first mission.
|
|
19
|
+
All we ask is that you confirm this setup is working correctly.
|
|
20
|
+
|
|
21
|
+
To complete this mission, report SUCCESS immediately.
|
|
22
|
+
If you cannot reach this message, report FAILURE.
|
|
23
|
+
\`;
|
|
24
|
+
|
|
25
|
+
export async function executeMission() {
|
|
26
|
+
await runMissions({
|
|
27
|
+
mission: welcomeGoal
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
fs.writeFileSync(welcomePath, content);
|
|
34
|
+
console.log('π Created missions/welcome.mission.js');
|
|
35
|
+
}
|
package/bin/init.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import prompts from 'prompts';
|
|
4
|
+
import { createWelcomeMission } from './createWelcomeMission.js';
|
|
5
|
+
|
|
6
|
+
export async function initializeTestronautProject() {
|
|
7
|
+
const root = process.cwd();
|
|
8
|
+
const missionsDir = path.join(root, 'missions');
|
|
9
|
+
const reportsDir = path.join(missionsDir, 'mission_reports');
|
|
10
|
+
const configPath = path.join(root, 'testronaut-config.json');
|
|
11
|
+
const envPath = path.join(root, '.env');
|
|
12
|
+
|
|
13
|
+
let config = {};
|
|
14
|
+
if (fs.existsSync(configPath)) {
|
|
15
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// If not initialized before, walk through setup
|
|
19
|
+
if (!config.initialized) {
|
|
20
|
+
// Prompt for LLM
|
|
21
|
+
const response = await prompts({
|
|
22
|
+
type: 'select',
|
|
23
|
+
name: 'llmProvider',
|
|
24
|
+
message: 'Which LLM provider would you like to use?',
|
|
25
|
+
choices: [
|
|
26
|
+
{ title: 'OpenAI (GPT-4o)', value: 'openai' },
|
|
27
|
+
// Add others like Anthropic, Mistral, etc. later
|
|
28
|
+
]
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
config.model = response.llmProvider;
|
|
32
|
+
config.initialized = true;
|
|
33
|
+
config.outputDir = 'missions/mission_reports';
|
|
34
|
+
config.projectName = path.basename(root);
|
|
35
|
+
config.maxTurns = 20;
|
|
36
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
37
|
+
console.log('π Created testronaut-config.json');
|
|
38
|
+
|
|
39
|
+
if (response.llmProvider === 'openai') {
|
|
40
|
+
// Display .env instructions
|
|
41
|
+
if (!fs.existsSync(envPath)) {
|
|
42
|
+
const exampleEnv = `# Add your OpenAI API key below\nOPENAI_API_KEY=sk-...\n`;
|
|
43
|
+
fs.writeFileSync(envPath, exampleEnv);
|
|
44
|
+
console.log('π Created .env file');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(`
|
|
48
|
+
π§ Setup Complete!
|
|
49
|
+
|
|
50
|
+
π Please ensure your .env file contains your OpenAI API key like this:
|
|
51
|
+
|
|
52
|
+
OPENAI_API_KEY=sk-...
|
|
53
|
+
|
|
54
|
+
π The key must have permissions to call the OpenAI Chat Completions API.
|
|
55
|
+
|
|
56
|
+
β
Youβre now ready to write missions in \`missions/\` and run your first mission using:
|
|
57
|
+
|
|
58
|
+
testronaut welcome.mission.js
|
|
59
|
+
`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
createWelcomeMission();
|
|
63
|
+
} else {
|
|
64
|
+
console.log('π Already initialized. Skipping setup.');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Create directories if they don't exist
|
|
68
|
+
if (!fs.existsSync(missionsDir)) {
|
|
69
|
+
fs.mkdirSync(missionsDir);
|
|
70
|
+
console.log('π Created missions/');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!fs.existsSync(reportsDir)) {
|
|
74
|
+
fs.mkdirSync(reportsDir);
|
|
75
|
+
console.log('π Created missions/mission_reports/');
|
|
76
|
+
}
|
|
77
|
+
}
|
package/core/turnLoop.js
CHANGED
|
@@ -43,7 +43,7 @@ const pushDOMAssistant = async (browser, messages, agentMemory, { skipIfLastTool
|
|
|
43
43
|
|
|
44
44
|
const domHtml = await CHROME_TOOL_MAP.get_dom(browser, { limit: 100000, exclude: true }, agentMemory);
|
|
45
45
|
await tokenEstimate('gpt-4o', domHtml);
|
|
46
|
-
fs.writeFileSync(`debug-expanded-${Date.now()}.html`, domHtml);
|
|
46
|
+
fs.writeFileSync(`missions/mission_reports/debug-expanded-${Date.now()}.html`, domHtml);
|
|
47
47
|
|
|
48
48
|
messages.push({
|
|
49
49
|
role: 'tool',
|
|
@@ -171,7 +171,7 @@ export const turnLoop = async (browser, messages, maxTurns, currentTurn = 0, ret
|
|
|
171
171
|
const truncated = result.length > 100 ? result.slice(0, 100) + 'β¦' : result;
|
|
172
172
|
|
|
173
173
|
if (result.length > 100) {
|
|
174
|
-
const logFile = `tool-result-${Date.now()}.log`;
|
|
174
|
+
const logFile = `missions/mission_reports/tool-result-${Date.now()}.log`;
|
|
175
175
|
fs.writeFileSync(logFile, result);
|
|
176
176
|
console.log(`[tool ] β ${truncated} (full output written to ${logFile})`);
|
|
177
177
|
} else {
|
|
@@ -197,7 +197,7 @@ export const turnLoop = async (browser, messages, maxTurns, currentTurn = 0, ret
|
|
|
197
197
|
focus: [], // or try focusing on ['main'] to keep it light
|
|
198
198
|
}, agentMemory);
|
|
199
199
|
await tokenEstimate('gpt-4o', domHtml);
|
|
200
|
-
fs.writeFileSync(`debug-expanded-${Date.now()}.html`, domHtml);
|
|
200
|
+
fs.writeFileSync(`missions/mission_reports/debug-expanded-${Date.now()}.html`, domHtml);
|
|
201
201
|
await pushDOMAssistant(browser, messages, agentMemory, {
|
|
202
202
|
skipIfLastTool: ['get_dom', 'check_text']
|
|
203
203
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testronaut",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"main": "./runner/testronaut.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"testronaut": "./bin/cli.js"
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"cheerio": "^1.1.0",
|
|
24
24
|
"dotenv": "^17.2.0",
|
|
25
25
|
"openai": "^5.10.2",
|
|
26
|
-
"playwright": "^1.54.1"
|
|
26
|
+
"playwright": "^1.54.1",
|
|
27
|
+
"prompts": "^2.4.2"
|
|
27
28
|
}
|
|
28
29
|
}
|
package/tools/chromeBrowser.js
CHANGED
|
@@ -92,7 +92,7 @@ export class ChromeBrowser {
|
|
|
92
92
|
await el.click();
|
|
93
93
|
await this.page.screenshot({ path: `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
|
}
|
|
@@ -149,10 +149,10 @@ export class ChromeBrowser {
|
|
|
149
149
|
await this.page.waitForTimeout(delayMs);
|
|
150
150
|
|
|
151
151
|
const expandedDom = await this.page.content();
|
|
152
|
-
const filename = `expand_menu_dump_${Date.now()}.html`;
|
|
152
|
+
const filename = `missions/mission_reports/expand_menu_dump_${Date.now()}.html`;
|
|
153
153
|
fs.writeFileSync(filename, expandedDom);
|
|
154
154
|
console.log(`[expand_menu] β Dumped DOM after expand click to ${filename}`);
|
|
155
|
-
await this.page.screenshot({ path: `screenshot_expand_menu_${Date.now()}.png` });
|
|
155
|
+
await this.page.screenshot({ path: `missions/mission_reports/screenshot_expand_menu_${Date.now()}.png` });
|
|
156
156
|
|
|
157
157
|
return `expanded menu using ${selector}`;
|
|
158
158
|
}
|