explorbot 0.1.11 → 0.1.13
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 +12 -2
- package/bin/explorbot-cli.ts +21 -21
- package/dist/bin/explorbot-cli.js +3 -3
- package/dist/package.json +4 -3
- package/dist/rules/researcher/container-rules.md +2 -0
- package/dist/src/action-result.js +2 -1
- package/dist/src/action.js +5 -10
- package/dist/src/ai/captain.js +0 -2
- package/dist/src/ai/driller.js +1108 -0
- package/dist/src/ai/historian/codeceptjs.js +2 -2
- package/dist/src/ai/historian/experience.js +1 -0
- package/dist/src/ai/historian/playwright.js +4 -4
- package/dist/src/ai/historian/screencast.js +121 -0
- package/dist/src/ai/historian.js +5 -3
- package/dist/src/ai/pilot.js +31 -22
- package/dist/src/ai/rules.js +3 -5
- package/dist/src/ai/session-analyst.js +117 -0
- package/dist/src/ai/tester.js +13 -2
- package/dist/src/commands/base-command.js +6 -6
- package/dist/src/commands/drill-command.js +3 -2
- package/dist/src/commands/exit-command.js +1 -0
- package/dist/src/commands/explore-command.js +20 -3
- package/dist/src/components/AddRule.js +1 -1
- package/dist/src/explorbot.js +52 -9
- package/dist/src/explorer.js +11 -9
- package/dist/src/reporter.js +68 -4
- package/dist/src/state-manager.js +4 -3
- package/dist/src/stats.js +5 -0
- package/dist/src/utils/aria.js +354 -529
- package/dist/src/utils/hooks-runner.js +2 -8
- package/dist/src/utils/html.js +371 -0
- package/dist/src/utils/strings.js +15 -0
- package/dist/src/utils/unique-names.js +12 -1
- package/dist/src/utils/url-matcher.js +6 -1
- package/dist/src/utils/web-element.js +27 -24
- package/dist/src/utils/xpath.js +1 -1
- package/package.json +4 -3
- package/rules/researcher/container-rules.md +2 -0
- package/src/action-result.ts +2 -1
- package/src/action.ts +5 -12
- package/src/ai/captain.ts +0 -2
- package/src/ai/driller.ts +1194 -0
- package/src/ai/historian/codeceptjs.ts +2 -2
- package/src/ai/historian/experience.ts +3 -2
- package/src/ai/historian/playwright.ts +5 -5
- package/src/ai/historian/screencast.ts +133 -0
- package/src/ai/historian.ts +7 -5
- package/src/ai/pilot.ts +31 -21
- package/src/ai/rules.ts +3 -5
- package/src/ai/session-analyst.ts +133 -0
- package/src/ai/tester.ts +15 -2
- package/src/commands/base-command.ts +6 -6
- package/src/commands/drill-command.ts +3 -2
- package/src/commands/exit-command.ts +1 -0
- package/src/commands/explore-command.ts +22 -3
- package/src/components/AddRule.tsx +1 -1
- package/src/config.ts +10 -0
- package/src/explorbot.ts +59 -11
- package/src/explorer.ts +11 -9
- package/src/reporter.ts +68 -4
- package/src/state-manager.ts +4 -3
- package/src/stats.ts +7 -0
- package/src/utils/aria.ts +367 -537
- package/src/utils/hooks-runner.ts +2 -6
- package/src/utils/html.ts +381 -0
- package/src/utils/strings.ts +17 -0
- package/src/utils/unique-names.ts +13 -0
- package/src/utils/url-matcher.ts +5 -1
- package/src/utils/web-element.ts +31 -28
- package/src/utils/xpath.ts +1 -1
- package/dist/src/ai/bosun.js +0 -456
- package/src/ai/bosun.ts +0 -571
package/README.md
CHANGED
|
@@ -173,12 +173,22 @@ See [docs/commands.md](docs/commands.md) for all commands.
|
|
|
173
173
|
| Test plans | `output/plans/*.md` | Markdown documentation of scenarios |
|
|
174
174
|
| Experience | `./experience/` | What Explorbot learned about your app |
|
|
175
175
|
|
|
176
|
-
Every run is saved as a real Playwright or CodeceptJS test you can commit and run from CI.
|
|
176
|
+
Every run is saved as a real Playwright or CodeceptJS test you can commit and run from CI. Configure the Historian to choose the output framework, record screencasts of every run, or both:
|
|
177
177
|
|
|
178
178
|
```js
|
|
179
|
-
ai: {
|
|
179
|
+
ai: {
|
|
180
|
+
agents: {
|
|
181
|
+
historian: {
|
|
182
|
+
framework: 'playwright', // or 'codeceptjs' (default)
|
|
183
|
+
screencast: true, // record .webm video per scenario, chapters labelled with each step
|
|
184
|
+
// screencast: { size: { width: 1280, height: 720 }, quality: 95 }
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
}
|
|
180
188
|
```
|
|
181
189
|
|
|
190
|
+
Screencasts land in `output/screencasts/<plan>-<n>-<scenario>.webm` and are listed alongside generated tests at the end of every run.
|
|
191
|
+
|
|
182
192
|
Playwright output uses the actual `page.locator(...)` calls executed during the run, with each action wrapped in `test.step` so failures land on a labelled step:
|
|
183
193
|
|
|
184
194
|
```ts
|
package/bin/explorbot-cli.ts
CHANGED
|
@@ -586,32 +586,32 @@ addCommonOptions(program.command('research <url>').description('Research a page
|
|
|
586
586
|
}
|
|
587
587
|
);
|
|
588
588
|
|
|
589
|
-
addCommonOptions(
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
589
|
+
addCommonOptions(
|
|
590
|
+
program.command('drill <url>').alias('driller').description('Drill all components on a page to learn interactions').option('--knowledge <path>', 'Save learned interactions to knowledge file at this URL path').option('--max-components <count>', 'Maximum number of components to drill')
|
|
591
|
+
).action(async (url, options) => {
|
|
592
|
+
try {
|
|
593
|
+
const explorBot = new ExplorBot(buildExplorBotOptions(url, options));
|
|
594
|
+
await explorBot.start();
|
|
594
595
|
|
|
595
|
-
|
|
596
|
+
await explorBot.visit(url);
|
|
596
597
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
598
|
+
const plan = await explorBot.agentDriller().drill({
|
|
599
|
+
knowledgePath: options.knowledge,
|
|
600
|
+
maxComponents: Number.parseInt(options.maxComponents || '30', 10),
|
|
601
|
+
interactive: false,
|
|
602
|
+
});
|
|
602
603
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
604
|
+
console.log(`\nDrill completed: ${plan.tests.length} components`);
|
|
605
|
+
console.log(`Successful: ${plan.tests.filter((t) => t.isSuccessful).length}`);
|
|
606
|
+
console.log(`Failed: ${plan.tests.filter((t) => t.hasFailed).length}`);
|
|
606
607
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
}
|
|
608
|
+
await explorBot.stop();
|
|
609
|
+
await showStatsAndExit(0);
|
|
610
|
+
} catch (error) {
|
|
611
|
+
console.error('Failed:', error instanceof Error ? error.message : 'Unknown error');
|
|
612
|
+
await showStatsAndExit(1);
|
|
613
613
|
}
|
|
614
|
-
);
|
|
614
|
+
});
|
|
615
615
|
|
|
616
616
|
program
|
|
617
617
|
.command('context <url>')
|
|
@@ -531,14 +531,14 @@ addCommonOptions(program.command('research <url>').description('Research a page
|
|
|
531
531
|
await showStatsAndExit(1);
|
|
532
532
|
}
|
|
533
533
|
});
|
|
534
|
-
addCommonOptions(program.command('drill <url>').description('Drill all components on a page to learn interactions').option('--knowledge <path>', 'Save learned interactions to knowledge file at this URL path').option('--max <count>', 'Maximum number of components to drill'
|
|
534
|
+
addCommonOptions(program.command('drill <url>').alias('driller').description('Drill all components on a page to learn interactions').option('--knowledge <path>', 'Save learned interactions to knowledge file at this URL path').option('--max-components <count>', 'Maximum number of components to drill')).action(async (url, options) => {
|
|
535
535
|
try {
|
|
536
536
|
const explorBot = new ExplorBot(buildExplorBotOptions(url, options));
|
|
537
537
|
await explorBot.start();
|
|
538
538
|
await explorBot.visit(url);
|
|
539
|
-
const plan = await explorBot.
|
|
539
|
+
const plan = await explorBot.agentDriller().drill({
|
|
540
540
|
knowledgePath: options.knowledge,
|
|
541
|
-
maxComponents: Number.parseInt(options.
|
|
541
|
+
maxComponents: Number.parseInt(options.maxComponents || '30', 10),
|
|
542
542
|
interactive: false,
|
|
543
543
|
});
|
|
544
544
|
console.log(`\nDrill completed: ${plan.tests.length} components`);
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "explorbot",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "CLI app built with React Ink, CodeceptJS, and Playwright",
|
|
5
5
|
"license": "Elastic-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"@opentelemetry/sdk-trace-base": "^2.2.0",
|
|
79
79
|
"@opentelemetry/semantic-conventions": "^1.38.0",
|
|
80
80
|
"@scalar/openapi-parser": "^0.25.6",
|
|
81
|
-
"@testomatio/reporter": "^2.7.
|
|
81
|
+
"@testomatio/reporter": "^2.7.9-beta.2-markdown",
|
|
82
82
|
"ai": "^6.0.6",
|
|
83
83
|
"axe-core": "^4.11.1",
|
|
84
84
|
"bash-tool": "^1.3.15",
|
|
@@ -104,11 +104,12 @@
|
|
|
104
104
|
"micromatch": "^4.0.8",
|
|
105
105
|
"ora-classic": "^5.4.2",
|
|
106
106
|
"parse5": "^8.0.0",
|
|
107
|
-
"playwright": "^1.
|
|
107
|
+
"playwright": "^1.59.0",
|
|
108
108
|
"react": "^19.1.1",
|
|
109
109
|
"strip-ansi": "^7.1.2",
|
|
110
110
|
"turndown": "^7.2.1",
|
|
111
111
|
"unique-names-generator": "^4.7.1",
|
|
112
|
+
"yaml": "^2.8.3",
|
|
112
113
|
"yargs": "^17.7.2",
|
|
113
114
|
"zod": "^4.1.8"
|
|
114
115
|
},
|
|
@@ -5,6 +5,8 @@ Container CSS must be a SINGLE semantic selector — one class, one id, or one a
|
|
|
5
5
|
- VALID: semantic class names that describe what the section IS (`.product-list`, `.sidebar-menu`, `.user-profile`, `.search-results`), semantic roles (`[role="dialog"]`), semantic ids (`#main-content`)
|
|
6
6
|
|
|
7
7
|
The container must uniquely identify a semantic wrapper, not a path through the DOM.
|
|
8
|
+
|
|
9
|
+
Iframes are not sections — list each iframe as a single row of type `iframe` inside the section that contains it. Never use an iframe selector as a section's `> Container:`.
|
|
8
10
|
</container_rules>
|
|
9
11
|
|
|
10
12
|
<css_selector_rules>
|
|
@@ -371,8 +371,9 @@ export class ActionResult {
|
|
|
371
371
|
try {
|
|
372
372
|
const urlObj = new URL(this.url);
|
|
373
373
|
const path = urlObj.pathname.replace(/\/$/, '') || '/';
|
|
374
|
+
const search = urlObj.search || '';
|
|
374
375
|
const hash = urlObj.hash || '';
|
|
375
|
-
return path + hash;
|
|
376
|
+
return path + search + hash;
|
|
376
377
|
}
|
|
377
378
|
catch {
|
|
378
379
|
// If URL parsing fails, assume it's already a relative URL
|
package/dist/src/action.js
CHANGED
|
@@ -12,6 +12,7 @@ import { ConfigParser, outputPath } from './config.js';
|
|
|
12
12
|
import { Observability } from "./observability.js";
|
|
13
13
|
import { htmlCombinedSnapshot, minifyHtml } from './utils/html.js';
|
|
14
14
|
import { createDebug, log, setStepSpanParent, tag } from './utils/logger.js';
|
|
15
|
+
import { safeFilename } from "./utils/strings.js";
|
|
15
16
|
const debugLog = createDebug('explorbot:action');
|
|
16
17
|
const FATAL_BROWSER_ERRORS = /Frame was detached|Target closed|Execution context was destroyed|Protocol error|Session closed/i;
|
|
17
18
|
class Action {
|
|
@@ -73,7 +74,7 @@ class Action {
|
|
|
73
74
|
const url = page?.url() || (await this.actor.grabCurrentUrl?.());
|
|
74
75
|
let screenshotFile = undefined;
|
|
75
76
|
if (includeScreenshot) {
|
|
76
|
-
const filename = `${stateHash}_${timestamp}.png
|
|
77
|
+
const filename = safeFilename(`${stateHash}_${timestamp}`, '.png');
|
|
77
78
|
screenshotFile = await this.actor
|
|
78
79
|
.saveScreenshot(filename)
|
|
79
80
|
.then(() => filename)
|
|
@@ -85,12 +86,12 @@ class Action {
|
|
|
85
86
|
// Save HTML to file
|
|
86
87
|
const statesDir = outputPath('states');
|
|
87
88
|
fs.mkdirSync(statesDir, { recursive: true });
|
|
88
|
-
const htmlFile = `${stateHash}_${timestamp}.html
|
|
89
|
+
const htmlFile = safeFilename(`${stateHash}_${timestamp}`, '.html');
|
|
89
90
|
const htmlPath = join(statesDir, htmlFile);
|
|
90
91
|
fs.writeFileSync(htmlPath, html, 'utf8');
|
|
91
92
|
debugLog('Captured page state');
|
|
92
93
|
// Save logs to file
|
|
93
|
-
const logFile = `${stateHash}_${timestamp}.log
|
|
94
|
+
const logFile = safeFilename(`${stateHash}_${timestamp}`, '.log');
|
|
94
95
|
const logPath = join(statesDir, logFile);
|
|
95
96
|
const formattedLogs = browserLogs.map((log) => {
|
|
96
97
|
const logTimestamp = new Date().toISOString();
|
|
@@ -112,7 +113,7 @@ class Action {
|
|
|
112
113
|
debugLog('ARIA snapshot failed:', err instanceof Error ? `${err.message}\n${err.stack}` : err);
|
|
113
114
|
}
|
|
114
115
|
if (ariaSnapshot) {
|
|
115
|
-
const ariaFileName = `${stateHash}_${timestamp}.aria.yaml
|
|
116
|
+
const ariaFileName = safeFilename(`${stateHash}_${timestamp}`, '.aria.yaml');
|
|
116
117
|
const ariaPath = join(statesDir, ariaFileName);
|
|
117
118
|
fs.writeFileSync(ariaPath, ariaSnapshot, 'utf8');
|
|
118
119
|
ariaSnapshotFile = ariaFileName;
|
|
@@ -323,12 +324,6 @@ class Action {
|
|
|
323
324
|
}
|
|
324
325
|
catch (error) {
|
|
325
326
|
this.lastError = error;
|
|
326
|
-
if (error && typeof error === 'object') {
|
|
327
|
-
const errorObj = error;
|
|
328
|
-
if (typeof errorObj.fetchDetails === 'function') {
|
|
329
|
-
await errorObj.fetchDetails();
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
327
|
debugLog(`Attempt failed: ${codeBlock}: ${errorToString(error) || this.lastError?.toString()}`);
|
|
333
328
|
return false;
|
|
334
329
|
}
|