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.
Files changed (72) hide show
  1. package/README.md +12 -2
  2. package/bin/explorbot-cli.ts +21 -21
  3. package/dist/bin/explorbot-cli.js +3 -3
  4. package/dist/package.json +4 -3
  5. package/dist/rules/researcher/container-rules.md +2 -0
  6. package/dist/src/action-result.js +2 -1
  7. package/dist/src/action.js +5 -10
  8. package/dist/src/ai/captain.js +0 -2
  9. package/dist/src/ai/driller.js +1108 -0
  10. package/dist/src/ai/historian/codeceptjs.js +2 -2
  11. package/dist/src/ai/historian/experience.js +1 -0
  12. package/dist/src/ai/historian/playwright.js +4 -4
  13. package/dist/src/ai/historian/screencast.js +121 -0
  14. package/dist/src/ai/historian.js +5 -3
  15. package/dist/src/ai/pilot.js +31 -22
  16. package/dist/src/ai/rules.js +3 -5
  17. package/dist/src/ai/session-analyst.js +117 -0
  18. package/dist/src/ai/tester.js +13 -2
  19. package/dist/src/commands/base-command.js +6 -6
  20. package/dist/src/commands/drill-command.js +3 -2
  21. package/dist/src/commands/exit-command.js +1 -0
  22. package/dist/src/commands/explore-command.js +20 -3
  23. package/dist/src/components/AddRule.js +1 -1
  24. package/dist/src/explorbot.js +52 -9
  25. package/dist/src/explorer.js +11 -9
  26. package/dist/src/reporter.js +68 -4
  27. package/dist/src/state-manager.js +4 -3
  28. package/dist/src/stats.js +5 -0
  29. package/dist/src/utils/aria.js +354 -529
  30. package/dist/src/utils/hooks-runner.js +2 -8
  31. package/dist/src/utils/html.js +371 -0
  32. package/dist/src/utils/strings.js +15 -0
  33. package/dist/src/utils/unique-names.js +12 -1
  34. package/dist/src/utils/url-matcher.js +6 -1
  35. package/dist/src/utils/web-element.js +27 -24
  36. package/dist/src/utils/xpath.js +1 -1
  37. package/package.json +4 -3
  38. package/rules/researcher/container-rules.md +2 -0
  39. package/src/action-result.ts +2 -1
  40. package/src/action.ts +5 -12
  41. package/src/ai/captain.ts +0 -2
  42. package/src/ai/driller.ts +1194 -0
  43. package/src/ai/historian/codeceptjs.ts +2 -2
  44. package/src/ai/historian/experience.ts +3 -2
  45. package/src/ai/historian/playwright.ts +5 -5
  46. package/src/ai/historian/screencast.ts +133 -0
  47. package/src/ai/historian.ts +7 -5
  48. package/src/ai/pilot.ts +31 -21
  49. package/src/ai/rules.ts +3 -5
  50. package/src/ai/session-analyst.ts +133 -0
  51. package/src/ai/tester.ts +15 -2
  52. package/src/commands/base-command.ts +6 -6
  53. package/src/commands/drill-command.ts +3 -2
  54. package/src/commands/exit-command.ts +1 -0
  55. package/src/commands/explore-command.ts +22 -3
  56. package/src/components/AddRule.tsx +1 -1
  57. package/src/config.ts +10 -0
  58. package/src/explorbot.ts +59 -11
  59. package/src/explorer.ts +11 -9
  60. package/src/reporter.ts +68 -4
  61. package/src/state-manager.ts +4 -3
  62. package/src/stats.ts +7 -0
  63. package/src/utils/aria.ts +367 -537
  64. package/src/utils/hooks-runner.ts +2 -6
  65. package/src/utils/html.ts +381 -0
  66. package/src/utils/strings.ts +17 -0
  67. package/src/utils/unique-names.ts +13 -0
  68. package/src/utils/url-matcher.ts +5 -1
  69. package/src/utils/web-element.ts +31 -28
  70. package/src/utils/xpath.ts +1 -1
  71. package/dist/src/ai/bosun.js +0 -456
  72. 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. Choose the framework in your config:
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: { agents: { historian: { framework: 'playwright' } } } // or 'codeceptjs' (default)
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
@@ -586,32 +586,32 @@ addCommonOptions(program.command('research <url>').description('Research a page
586
586
  }
587
587
  );
588
588
 
589
- 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', '20')).action(
590
- async (url, options) => {
591
- try {
592
- const explorBot = new ExplorBot(buildExplorBotOptions(url, options));
593
- await explorBot.start();
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
- await explorBot.visit(url);
596
+ await explorBot.visit(url);
596
597
 
597
- const plan = await explorBot.agentBosun().drill({
598
- knowledgePath: options.knowledge,
599
- maxComponents: Number.parseInt(options.max, 10),
600
- interactive: false,
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
- console.log(`\nDrill completed: ${plan.tests.length} components`);
604
- console.log(`Successful: ${plan.tests.filter((t) => t.isSuccessful).length}`);
605
- console.log(`Failed: ${plan.tests.filter((t) => t.hasFailed).length}`);
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
- await explorBot.stop();
608
- await showStatsAndExit(0);
609
- } catch (error) {
610
- console.error('Failed:', error instanceof Error ? error.message : 'Unknown error');
611
- await showStatsAndExit(1);
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', '20')).action(async (url, options) => {
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.agentBosun().drill({
539
+ const plan = await explorBot.agentDriller().drill({
540
540
  knowledgePath: options.knowledge,
541
- maxComponents: Number.parseInt(options.max, 10),
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.11",
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.6",
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.40.0",
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
@@ -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
  }
@@ -174,8 +174,6 @@ export class Captain extends CaptainBase {
174
174
  ${knowledge}
175
175
 
176
176
  ${experience}
177
-
178
- Use runCommand("/research") if you need deeper page understanding or UI element mapping.
179
177
  `;
180
178
  }
181
179
  planSummary() {