explorbot 0.2.3 → 0.2.5

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 (174) hide show
  1. package/README.md +1 -1
  2. package/bin/explorbot-cli.ts +26 -8
  3. package/boat/api-tester/src/cli.ts +17 -0
  4. package/boat/api-tester/src/config.ts +4 -2
  5. package/boat/doc-collector/bin/doc-collector-cli.ts +2 -0
  6. package/boat/doc-collector/src/ai/documentarian.ts +61 -31
  7. package/boat/doc-collector/src/cli.ts +14 -1
  8. package/boat/doc-collector/src/config.ts +4 -2
  9. package/boat/prima/bin/prima-cli.ts +0 -0
  10. package/boat/prima/src/activity-line.ts +33 -0
  11. package/boat/prima/src/cli.ts +127 -86
  12. package/boat/prima/src/envelope.ts +102 -52
  13. package/boat/prima/src/prima.ts +567 -128
  14. package/boat/prima/src/pw-parser.ts +11 -1
  15. package/boat/prima/src/pw-registry.ts +4 -5
  16. package/boat/prima/src/session-log.ts +126 -0
  17. package/dist/bin/explorbot-cli.js +26 -8
  18. package/dist/boat/api-tester/bin/apibot-cli.js +2 -0
  19. package/dist/boat/api-tester/src/cli.js +17 -0
  20. package/dist/boat/api-tester/src/config.js +4 -2
  21. package/dist/boat/doc-collector/bin/doc-collector-cli.js +2 -0
  22. package/dist/boat/doc-collector/src/ai/documentarian.js +44 -19
  23. package/dist/boat/doc-collector/src/cli.js +14 -1
  24. package/dist/boat/doc-collector/src/config.js +4 -2
  25. package/dist/boat/prima/src/activity-line.js +30 -0
  26. package/dist/boat/prima/src/cli.js +109 -77
  27. package/dist/boat/prima/src/envelope.js +94 -44
  28. package/dist/boat/prima/src/prima.js +533 -119
  29. package/dist/boat/prima/src/pw-parser.js +13 -1
  30. package/dist/boat/prima/src/pw-registry.js +4 -5
  31. package/dist/boat/prima/src/session-log.js +108 -0
  32. package/dist/package.json +3 -2
  33. package/dist/rules/navigator/verification-actions.md +20 -0
  34. package/dist/src/action-result.d.ts +7 -0
  35. package/dist/src/action-result.js +4 -0
  36. package/dist/src/action.d.ts +2 -0
  37. package/dist/src/action.js +41 -2
  38. package/dist/src/ai/captain/web-mode.js +6 -3
  39. package/dist/src/ai/captain.js +2 -0
  40. package/dist/src/ai/navigator.d.ts +34 -0
  41. package/dist/src/ai/navigator.js +237 -181
  42. package/dist/src/ai/pilot.d.ts +7 -0
  43. package/dist/src/ai/pilot.js +90 -2
  44. package/dist/src/ai/provider.d.ts +2 -2
  45. package/dist/src/ai/provider.js +14 -23
  46. package/dist/src/ai/rerunner.js +2 -1
  47. package/dist/src/ai/researcher/cache.d.ts +2 -0
  48. package/dist/src/ai/researcher/cache.js +10 -2
  49. package/dist/src/ai/researcher.js +3 -2
  50. package/dist/src/ai/rules.js +17 -10
  51. package/dist/src/ai/session-analyst.js +2 -0
  52. package/dist/src/ai/task-agent.js +4 -1
  53. package/dist/src/ai/tester.d.ts +6 -3
  54. package/dist/src/ai/tester.js +50 -46
  55. package/dist/src/ai/tools.d.ts +14 -0
  56. package/dist/src/ai/tools.js +117 -37
  57. package/dist/src/commands/config-command.d.ts +51 -0
  58. package/dist/src/commands/config-command.js +117 -0
  59. package/dist/src/commands/index.js +2 -0
  60. package/dist/src/config.d.ts +9 -1
  61. package/dist/src/config.js +53 -4
  62. package/dist/src/execution-controller.d.ts +2 -0
  63. package/dist/src/execution-controller.js +6 -0
  64. package/dist/src/explorbot.d.ts +2 -1
  65. package/dist/src/explorbot.js +7 -2
  66. package/dist/src/explorer.js +2 -3
  67. package/dist/src/playwright-recorder.js +30 -0
  68. package/dist/src/remote.d.ts +55 -0
  69. package/dist/src/remote.js +235 -0
  70. package/dist/src/reporter.d.ts +1 -0
  71. package/dist/src/reporter.js +7 -1
  72. package/dist/src/state-manager.d.ts +2 -1
  73. package/dist/src/state-manager.js +3 -1
  74. package/dist/src/stats.d.ts +1 -0
  75. package/dist/src/stats.js +1 -0
  76. package/dist/src/test-plan.d.ts +3 -0
  77. package/dist/src/test-plan.js +26 -0
  78. package/dist/src/utils/aria.d.ts +2 -8
  79. package/dist/src/utils/aria.js +69 -40
  80. package/dist/src/utils/html.js +1 -0
  81. package/dist/src/utils/logger.d.ts +7 -1
  82. package/dist/src/utils/logger.js +32 -0
  83. package/dist/src/utils/page-readiness.js +18 -1
  84. package/dist/src/utils/url-matcher.js +3 -0
  85. package/dist/src/utils/web-element.d.ts +2 -0
  86. package/dist/src/utils/web-element.js +8 -0
  87. package/dist/src/utils/web-sandbox.d.ts +1 -1
  88. package/dist/src/utils/web-sandbox.js +2 -3
  89. package/docs/api-testing/basics.md +90 -0
  90. package/docs/api-testing/planning.md +57 -0
  91. package/docs/api-testing/running-tests.md +55 -0
  92. package/docs/assets/cloud-report.png +0 -0
  93. package/docs/assets/html-report.png +0 -0
  94. package/docs/assets/langfuse-trace.png +0 -0
  95. package/docs/assets/successful-explore-run.png +0 -0
  96. package/docs/basics/getting-started.md +140 -0
  97. package/docs/basics/prerequisites.md +63 -0
  98. package/docs/basics/providers.md +362 -0
  99. package/docs/basics/running.md +78 -0
  100. package/docs/contributing/ai-integration-tests.md +57 -0
  101. package/docs/contributing/contributing.md +90 -0
  102. package/docs/contributing/demo-videos.md +36 -0
  103. package/docs/contributing/npm-package.md +138 -0
  104. package/docs/contributing/observability.md +227 -0
  105. package/docs/contributing/regression-tests.md +103 -0
  106. package/docs/contributing/testing.md +95 -0
  107. package/docs/doc-collection/basics.md +128 -0
  108. package/docs/doc-collection/crawling.md +67 -0
  109. package/docs/doc-collection/interactive-mode.md +99 -0
  110. package/docs/index.json +87 -0
  111. package/docs/reference/commands.md +997 -0
  112. package/docs/reference/configuration.md +569 -0
  113. package/docs/reference/scripting.md +303 -0
  114. package/docs/reference/websocket.md +50 -0
  115. package/docs/superpowers/plans/2026-08-01-actor-boat.md +925 -0
  116. package/docs/superpowers/plans/2026-08-01-prima-boat.md +1120 -0
  117. package/docs/superpowers/reviews/2026-08-06-prima-vs-playwright-cli.md +268 -0
  118. package/docs/superpowers/specs/2026-08-01-actor-boat-design.md +204 -0
  119. package/docs/superpowers/specs/2026-08-01-prima-boat-design.md +242 -0
  120. package/docs/superpowers/specs/2026-08-03-global-config-design.md +138 -0
  121. package/docs/superpowers/specs/2026-08-07-prima-fixes-design.md +394 -0
  122. package/docs/superpowers/specs/2026-08-18-prima-false-verdicts.md +159 -0
  123. package/docs/web-testing/agents.md +158 -0
  124. package/docs/web-testing/automated-tests.md +134 -0
  125. package/docs/web-testing/basics.md +91 -0
  126. package/docs/web-testing/customization.md +131 -0
  127. package/docs/web-testing/hooks.md +238 -0
  128. package/docs/web-testing/page-interaction.md +84 -0
  129. package/docs/web-testing/planner.md +122 -0
  130. package/docs/web-testing/rerun.md +164 -0
  131. package/docs/web-testing/researcher.md +380 -0
  132. package/docs/workflow/agentic-usage.md +233 -0
  133. package/docs/workflow/application-spec.md +73 -0
  134. package/docs/workflow/ci.md +202 -0
  135. package/docs/workflow/knowledge.md +310 -0
  136. package/docs/workflow/planning-styles.md +67 -0
  137. package/docs/workflow/reporting.md +133 -0
  138. package/docs/workflow/test-plans.md +90 -0
  139. package/package.json +3 -2
  140. package/rules/navigator/verification-actions.md +20 -0
  141. package/src/action-result.ts +11 -0
  142. package/src/action.ts +43 -3
  143. package/src/ai/captain/web-mode.ts +6 -3
  144. package/src/ai/captain.ts +3 -0
  145. package/src/ai/navigator.ts +255 -186
  146. package/src/ai/pilot.ts +104 -2
  147. package/src/ai/provider.ts +14 -24
  148. package/src/ai/rerunner.ts +2 -1
  149. package/src/ai/researcher/cache.ts +12 -2
  150. package/src/ai/researcher.ts +3 -2
  151. package/src/ai/rules.ts +17 -10
  152. package/src/ai/session-analyst.ts +2 -0
  153. package/src/ai/task-agent.ts +3 -1
  154. package/src/ai/tester.ts +52 -45
  155. package/src/ai/tools.ts +136 -37
  156. package/src/commands/config-command.ts +146 -0
  157. package/src/commands/index.ts +2 -0
  158. package/src/config.ts +60 -5
  159. package/src/execution-controller.ts +8 -0
  160. package/src/explorbot.ts +7 -3
  161. package/src/explorer.ts +2 -2
  162. package/src/playwright-recorder.ts +23 -0
  163. package/src/remote.ts +244 -0
  164. package/src/reporter.ts +7 -1
  165. package/src/state-manager.ts +6 -2
  166. package/src/stats.ts +1 -0
  167. package/src/test-plan.ts +29 -0
  168. package/src/utils/aria.ts +65 -45
  169. package/src/utils/html.ts +1 -0
  170. package/src/utils/logger.ts +33 -2
  171. package/src/utils/page-readiness.ts +24 -1
  172. package/src/utils/url-matcher.ts +3 -0
  173. package/src/utils/web-element.ts +9 -0
  174. package/src/utils/web-sandbox.ts +3 -4
@@ -0,0 +1,133 @@
1
+ # Test Reporting
2
+
3
+ Explorbot generates test reports with [@testomatio/reporter](https://github.com/testomatio/reporter). Reports include test steps, screenshots, and result messages for every run. The [API tester](../api-testing/basics.md) reports through the same reporter, so the HTML and Testomat.io settings below apply to API runs too.
4
+
5
+ The [Testomat.io cloud reporter](#testomatio-cloud-report) is the recommended way to review runs, especially on CI: pair it with the [Analyst](#session-analysis) and [screencasts](#screencasts) and each run opens with a written overview you can drill from down to a video of any single test. The [HTML](#html-report-local) and [markdown](#markdown-report-local) reports are the local alternatives. For the full CI setup, see [Continuous Integration](./ci.md#reporting-on-ci).
6
+
7
+ ## Session analysis
8
+
9
+ After `/explore` and `/freesail` runs, the [Analyst agent](../web-testing/agents.md#analyst-agent) writes a summary that clusters findings by root cause.
10
+
11
+ The same markdown is printed to the console, written to disk, and set as the run description on Testomat.io when the cloud reporter is enabled.
12
+
13
+ **On disk:** `output/reports/<mode>-<sessionName>.md` — for example `explore-WiseFox42.md` or `freesail-CleverOwl91.md`. Each run gets a unique name, so nothing is overwritten.
14
+
15
+ **On Testomat.io:** the markdown becomes the run description, so the analysis sits next to the test list in the cloud dashboard with no extra setup.
16
+
17
+ See [Analyst Agent](../web-testing/agents.md#analyst-agent) for the report format and configuration options.
18
+
19
+ ## Screencasts
20
+
21
+ Explorbot can record a video of every test instead of leaving you a trail of screenshots. Enable it on the Historian agent:
22
+
23
+ ```js
24
+ export default {
25
+ ai: {
26
+ agents: {
27
+ historian: {
28
+ screencast: true, // or { size: { width: 1280, height: 720 }, quality: 95 }
29
+ },
30
+ },
31
+ },
32
+ };
33
+ ```
34
+
35
+ Each test gets one video in `output/screencasts/`, with the executed actions overlaid in the corner and the current scenario step shown as a chapter title — the video narrates itself. The file is attached to the test as an artifact, so with [artifact storage configured](#artifacts-in-cloud-reports) it appears on the test in Testomat.io, ready to watch next to the failure message.
36
+
37
+ ## HTML report (local)
38
+
39
+ With `html: true` under `reporter` in `explorbot.config.js`, Explorbot creates an HTML report after each run in `output/reports/<mode>-<sessionName>.html`, for example `explore-WiseFox42.html`. Each session gets its own file, so nothing is overwritten. Open it in a browser to review results. Configs generated by `explorbot init` enable it, so new projects get HTML reports out of the box.
40
+
41
+ ![HTML report](https://github.com/testomatio/explorbot/blob/main/docs/assets/html-report.png)
42
+
43
+ For cloud reporting with history and team features, see [Testomat.io cloud report](#testomatio-cloud-report).
44
+
45
+ ## Markdown report (local)
46
+
47
+ Explorbot can write a markdown report next to the HTML one. It's plain text, so you can paste it into a PR description, a chat thread, or a CI summary.
48
+
49
+ Opt in via `explorbot.config.js`:
50
+
51
+ ```js
52
+ export default {
53
+ reporter: {
54
+ enabled: true,
55
+ html: true,
56
+ markdown: true,
57
+ },
58
+ };
59
+ ```
60
+
61
+ Output: `output/reports/<mode>-<sessionName>-tests.md`, for example `explore-WiseFox42-tests.md`. Like the HTML report, the filename is session-scoped, so successive runs don't overwrite each other. The `-tests` suffix keeps it distinct from the [Analyst report](#session-analysis), which writes `<mode>-<sessionName>.md` in the same folder.
62
+
63
+ The markdown report isn't generated unless `markdown: true` is set. Configs generated by `explorbot init` enable it as well.
64
+
65
+ ## Testomat.io cloud report
66
+
67
+ [Testomat.io](https://testomat.io) provides a cloud dashboard with test history, analytics, and team collaboration. It's free with unlimited test runs.
68
+
69
+ ![Testomat.io cloud report](https://github.com/testomatio/explorbot/blob/main/docs/assets/cloud-report.png)
70
+
71
+ ### Setup
72
+
73
+ 1. Register at [app.testomat.io](https://app.testomat.io).
74
+ 2. Create an empty project.
75
+ 3. Copy the project API key.
76
+
77
+ ### Run with the key
78
+
79
+ ```bash
80
+ TESTOMATIO=tstmt_your_key_here npx explorbot explore /
81
+ ```
82
+
83
+ Set the key in your shell profile or CI environment so it's always active.
84
+
85
+ ### Options
86
+
87
+ | Variable | Description |
88
+ |----------|-------------|
89
+ | `TESTOMATIO` | Project API key (required) |
90
+ | `TESTOMATIO_TITLE` | Custom name for the test run |
91
+ | `TESTOMATIO_ENV` | Environment label (e.g. `staging`, `production`) |
92
+ | `TESTOMATIO_SHARED_RUN` | Merge parallel executions into one run |
93
+ | `TESTOMATIO_RUNGROUP_TITLE` | Group successive runs under one heading (overrides `reporter.runGroup`) |
94
+
95
+ See the [@testomatio/reporter docs](https://github.com/testomatio/reporter/blob/2.x/docs/pipes/testomatio.md) for the full list.
96
+
97
+ ### Run group
98
+
99
+ Set `reporter.runGroup` to file successive runs under one heading on Testomat.io. There is no built-in default, but configs generated by `explorbot init` set it to today's date (`YYYY-MM-DD`), so all sessions from one day appear together in the dashboard.
100
+
101
+ Change or remove it via `explorbot.config.js`:
102
+
103
+ ```js
104
+ export default {
105
+ reporter: {
106
+ enabled: true,
107
+ runGroup: 'Smoke Suite', // any string; remove to disable grouping
108
+ },
109
+ };
110
+ ```
111
+
112
+ If set, `TESTOMATIO_RUNGROUP_TITLE` from the environment takes precedence over the config.
113
+
114
+ ## Artifacts in cloud reports
115
+
116
+ Explorbot attaches screenshots to test steps and, when [screencasts](#screencasts) are enabled, a video to every test. To see them in Testomat.io, configure an S3-compatible storage provider under Settings > Artifacts in Testomat.io. Without it, screenshots and screencasts won't appear in cloud reports.
117
+
118
+ Set these environment variables:
119
+
120
+ ```bash
121
+ S3_ACCESS_KEY_ID=your_access_key
122
+ S3_SECRET_ACCESS_KEY=your_secret_key
123
+ S3_BUCKET=your-bucket-name
124
+ S3_REGION=us-east-1
125
+ ```
126
+
127
+ For non-AWS providers (DigitalOcean Spaces, Cloudflare R2, Google Cloud Storage, Minio), also set:
128
+
129
+ ```bash
130
+ S3_ENDPOINT=https://your-provider-endpoint.com
131
+ ```
132
+
133
+ See the [@testomatio/reporter artifacts docs](https://github.com/testomatio/reporter/blob/2.x/docs/artifacts.md) for provider-specific examples.
@@ -0,0 +1,90 @@
1
+ # Test Plans
2
+
3
+ A test plan is a markdown file with a suite of scenarios for the Tester to run. The [Planner](../web-testing/planner.md) generates plans, but the format is plain markdown. You can write plans by hand, edit generated ones, or check them into version control.
4
+
5
+ Explorbot saves plans to `output/plans/` by default. The same parser loads them whether they were generated or written by hand. [API testing](../api-testing/basics.md) saves and loads its plans in this same format.
6
+
7
+ The format is a dialect of the [Testomat.io classical markdown format](https://docs.testomat.io/project/import-export/export-tests/classical-tests-markdown-format/). It adds a `### Prerequisite` block that tells Explorbot which page to open before each test.
8
+
9
+ ## Format
10
+
11
+ ```markdown
12
+ <!-- suite -->
13
+ # Plan Title
14
+
15
+ ### Prerequisite
16
+
17
+ * URL: /relative-path
18
+
19
+ <!-- test
20
+ priority: critical
21
+ -->
22
+ # Scenario written as a user-facing sentence
23
+
24
+ ## Requirements
25
+ /relative-path
26
+
27
+ ## Steps
28
+ * First step in plain language
29
+ * Second step
30
+
31
+ ## Expected
32
+ * First expected outcome
33
+ * Second expected outcome
34
+ ```
35
+
36
+ One file can hold several suites. Each begins with its own `<!-- suite -->` marker and parses as an independent plan.
37
+
38
+ ## Elements
39
+
40
+ ### `<!-- suite -->`
41
+
42
+ Marks the start of a plan. The `#` heading on the next line becomes the plan's title. This follows the Testomat.io convention of HTML-comment metadata blocks.
43
+
44
+ ### `### Prerequisite`
45
+
46
+ Holds the suite-level URL as a single bullet:
47
+
48
+ ```
49
+ * URL: /relative-path
50
+ ```
51
+
52
+ The URL is required. Without it, the suite's tests do not run. Make it relative to the configured base URL (start with `/`), so the same plan runs against staging, production, or a local dev server without edits.
53
+
54
+ Tests without their own `## Requirements` URL use this URL as their start page. Explorbot navigates to it before each scenario.
55
+
56
+ ### `<!-- test priority: … -->`
57
+
58
+ Opens a test block. Valid priorities: `critical`, `important`, `high`, `normal`, `low`. Omit it and the priority defaults to `normal`. See [Test Priorities](../web-testing/planner.md#test-priorities) for what each level means.
59
+
60
+ ### `#` Scenario heading
61
+
62
+ A single `#` heading inside a test block is the scenario description. Write it as a business outcome, not a click path.
63
+
64
+ ### `## Requirements`
65
+
66
+ A per-test start URL, written as a plain line (no bullet) right under the heading. It overrides the suite Prerequisite URL for this test. The Planner writes it for every test; when authoring plans by hand, give each test its own Requirements URL.
67
+
68
+ ### `## Steps`
69
+
70
+ A bulleted list (`* `) of planned actions in plain language. The Tester treats these as guidance, not a strict script, and may adapt them to what it sees on the page. To span a step across lines, indent continuation lines with 2 spaces.
71
+
72
+ The Testomat.io classical format inlines `*Expected*:` inside each step. Explorbot splits actions and outcomes into separate `## Steps` and `## Expected` sections.
73
+
74
+ ### `## Expected`
75
+
76
+ A bulleted list (`* `) of expected outcomes. Each outcome should describe a verifiable change: a data change, a state change, or a UI change with a side effect. See the Planner's [outcome-strength guidance](../web-testing/planner.md#built-in-styles) for what counts.
77
+
78
+ The Tester passes a test only when it has verified every expected outcome.
79
+
80
+ ## Reusing saved plans
81
+
82
+ Saved plans are reusable. Run `npx explorbot explore --configure="new:25%"` to load the matching plan, re-run a subset of its tests, and let the planner generate a few new ones to fill the budget. Filter old picks by priority or style, and order them by priority, file index, or random shuffle. See the [`--configure` reference in commands.md](../reference/commands.md#explore) for the full key list and examples.
83
+
84
+ ## See Also
85
+
86
+ - [Automated Tests](../web-testing/automated-tests.md) — the runnable Playwright or CodeceptJS files Explorbot writes after executing a plan
87
+ - [Planner](../web-testing/planner.md) — how plans are generated
88
+ - [Planning Styles](./planning-styles.md) — how planning styles shape scenarios
89
+ - [Commands](../reference/commands.md) — `/plan`, `/explore`, `npx explorbot plan`
90
+ - [Rerun](../web-testing/rerun.md) — re-executing generated tests
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "explorbot",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "CLI app built with React Ink, CodeceptJS, and Playwright",
5
5
  "license": "Elastic-2.0",
6
6
  "type": "module",
@@ -19,6 +19,7 @@
19
19
  },
20
20
  "files": [
21
21
  "dist/",
22
+ "docs/",
22
23
  "src/**/*.ts",
23
24
  "src/**/*.tsx",
24
25
  "bin/**/*.ts",
@@ -119,7 +120,7 @@
119
120
  "ora-classic": "^5.4.2",
120
121
  "parse5": "^8.0.0",
121
122
  "pixelmatch": "^7.2.0",
122
- "playwright": "^1.60",
123
+ "playwright": "^1.62",
123
124
  "pngjs": "^7.0.0",
124
125
  "react": "^19.1.1",
125
126
  "sambanova-ai-provider": "^1.2.2",
@@ -105,9 +105,29 @@ Checks that page source does NOT contain expected text.
105
105
  I.dontSeeInSource('error-class');
106
106
  </example>
107
107
 
108
+ ### Asserting the state of a control
109
+
110
+ State means disabled, checked, readonly, required, selected, expanded. A presence assertion says
111
+ nothing about it, so assert it with an attribute selector:
112
+
113
+ <example>
114
+ I.seeElement('button[aria-label="Submit"][disabled]');
115
+ I.dontSeeElement('button[aria-label="Submit"][disabled]');
116
+ I.seeElement('input[name="accept"][checked]');
117
+ I.seeElement('button[aria-label="Details"][aria-expanded="true"]');
118
+ </example>
119
+
120
+ Use I.seeElement for the state you expect and I.dontSeeElement for the state you expect to be
121
+ absent — that pair expresses both directions.
122
+
123
+ I.seeAttributesOnElements(<locator>, { disabled: true }) also exists, but only takes a state that
124
+ must be PRESENT and does not resolve reliably against a role/text locator. Prefer the selector form above.
125
+
108
126
  <verification_rules>
109
127
  Be strict in assertions to avoid false positives.
110
128
  Prefer I.seeElement() with ARIA locators - most reliable.
129
+ For a claim about a control's state, assert it with an attribute selector — presence of the element is not evidence of its state.
130
+ If no assertion above can express the claim, say so instead of proposing an assertion that checks something weaker.
111
131
  I.see() and I.dontSee() MUST include context parameter.
112
132
  For input field values, ALWAYS use I.seeInField() — never check value via CSS attribute selectors or I.seeInSource.
113
133
  Prefer text locators (label, name, placeholder) for form fields: I.seeInField('Search', 'value') over I.seeInField('input[name="search"]', 'value').
@@ -30,6 +30,7 @@ interface ActionResultData extends WebPageState {
30
30
  iframeSnapshots?: Array<{ src: string; html: string; id?: string }>;
31
31
  ariaSnapshot?: string | null;
32
32
  ariaSnapshotFile?: string;
33
+ focusedElement?: FocusedElement | null;
33
34
  iframeURL?: string;
34
35
  links?: Link[];
35
36
  }
@@ -74,6 +75,7 @@ export class ActionResult implements ActionResultData {
74
75
  private snapshotCache = new TTLCache<string>();
75
76
  readonly logFile: string | undefined = undefined;
76
77
  readonly ariaSnapshotFile: string | undefined = undefined;
78
+ readonly focusedElement: FocusedElement | null = null;
77
79
  private _ariaSnapshot: string | null | undefined = undefined;
78
80
  private _lastExtractedHtml: string | undefined = undefined;
79
81
  notes: string[] = [];
@@ -107,6 +109,9 @@ export class ActionResult implements ActionResultData {
107
109
  if (data.ariaSnapshotFile !== undefined) {
108
110
  this.ariaSnapshotFile = data.ariaSnapshotFile;
109
111
  }
112
+ if (data.focusedElement !== undefined) {
113
+ this.focusedElement = data.focusedElement;
114
+ }
110
115
 
111
116
  // Store HTML in a private property if provided
112
117
  if (data.html !== undefined) {
@@ -643,3 +648,9 @@ export class Diff {
643
648
  this._ariaChangeCount = ariaDiff.count;
644
649
  }
645
650
  }
651
+
652
+ export interface FocusedElement {
653
+ role: string;
654
+ name: string;
655
+ value?: string;
656
+ }
package/src/action.ts CHANGED
@@ -3,7 +3,7 @@ import { join } from 'node:path';
3
3
  import { context, trace } from '@opentelemetry/api';
4
4
  import { container, recorder } from 'codeceptjs';
5
5
  import * as codeceptjs from 'codeceptjs';
6
- import { ActionResult } from './action-result.js';
6
+ import { ActionResult, type FocusedElement } from './action-result.js';
7
7
  import { clearActivity, setActivity } from './activity.ts';
8
8
  import { ConfigParser, outputPath } from './config.js';
9
9
  import type { ExplorbotConfig } from './config.js';
@@ -19,6 +19,8 @@ import { codeceptJSSandbox, hasPlaywrightCommands, playwrightSandbox, sanitizeCo
19
19
 
20
20
  const debugLog = createDebug('explorbot:action');
21
21
  const CAPTURE_NAVIGATION_TRANSITION_ATTEMPTS = 3;
22
+ const DEFAULT_ACTION_TIMEOUT = 3000;
23
+ const DEFAULT_PAGE_TIMEOUT = 3000;
22
24
 
23
25
  class Action {
24
26
  private actor: CodeceptJS.I;
@@ -32,6 +34,7 @@ class Action {
32
34
  public playwrightHelper: any;
33
35
  public playwrightGroupId: string | null = null;
34
36
  public assertionSteps: Array<{ name: string; args: any[] }> = [];
37
+ public lastValue: unknown;
35
38
  private recorder?: PlaywrightRecorder;
36
39
  private recovery: RecoveryRunner;
37
40
  private mainDocumentStatus: number | undefined = undefined;
@@ -54,6 +57,7 @@ class Action {
54
57
  try {
55
58
  await (this.actor as any).saveScreenshot(filename);
56
59
  if (currentState) currentState.screenshotFile = filename;
60
+ tag('data').log('screenshot', { path: outputPath('states', filename) });
57
61
  return filename;
58
62
  } catch (err) {
59
63
  debugLog('Screenshot failed:', err);
@@ -102,7 +106,10 @@ class Action {
102
106
  const screenshotPath = join(statesDir, filename);
103
107
  screenshotFile = await page
104
108
  ?.screenshot({ path: screenshotPath, fullPage: true })
105
- .then(() => filename)
109
+ .then(() => {
110
+ tag('data').log('screenshot', { path: screenshotPath });
111
+ return filename;
112
+ })
106
113
  .catch((err: Error) => {
107
114
  debugLog('Screenshot failed, continuing without it:', err);
108
115
  return undefined;
@@ -133,10 +140,12 @@ class Action {
133
140
 
134
141
  let ariaSnapshot: string | null = null;
135
142
  let ariaSnapshotFile: string | undefined = undefined;
143
+ let focusedElement: FocusedElement | null = null;
136
144
 
137
145
  try {
138
146
  const page = this.playwrightHelper.page;
139
147
  ariaSnapshot = await page.locator('body').ariaSnapshot();
148
+ focusedElement = await page.evaluate(readFocusedElement);
140
149
  } catch (err) {
141
150
  debugLog('ARIA snapshot failed:', err instanceof Error ? `${err.message}\n${err.stack}` : err);
142
151
  }
@@ -160,6 +169,7 @@ class Action {
160
169
  iframeSnapshots,
161
170
  ariaSnapshot,
162
171
  ariaSnapshotFile,
172
+ focusedElement,
163
173
  iframeURL: frame ? frame.url?.() || 'iframe' : undefined,
164
174
  });
165
175
  this.stateManager.updateState(result, codeBlock);
@@ -297,16 +307,21 @@ class Action {
297
307
  throw new Error('No valid I.* or page.* commands found in code block');
298
308
  }
299
309
 
310
+ this.playwrightHelper?.page?.setDefaultTimeout(this.config.action?.timeout ?? DEFAULT_ACTION_TIMEOUT);
311
+
300
312
  if (isPlaywright) {
301
313
  const page = this.playwrightHelper.page;
302
314
  await playwrightSandbox(page, sanitizedCode);
303
315
  await sleep(this.config.action?.delay || 500);
304
316
  } else {
305
- codeceptJSSandbox(this.actor, sanitizedCode);
317
+ const returned = codeceptJSSandbox(this.actor, sanitizedCode);
306
318
  await recorder.add(() => sleep(this.config.action?.delay || 500));
307
319
  await recorder.promise();
320
+ this.lastValue = await returned;
308
321
  }
309
322
 
323
+ this.restorePageTimeout();
324
+
310
325
  if (executedSteps.length > 0) {
311
326
  codeString = executedSteps.join('\n');
312
327
  }
@@ -325,6 +340,7 @@ class Action {
325
340
  this.assertionSteps = [];
326
341
  throw err;
327
342
  } finally {
343
+ this.restorePageTimeout();
328
344
  detachMainDocumentResponse();
329
345
  if (groupId) await this.recorder!.endAction();
330
346
  detachStepLogger(stepListener);
@@ -374,6 +390,10 @@ class Action {
374
390
  return this.actionResult;
375
391
  }
376
392
 
393
+ private restorePageTimeout(): void {
394
+ this.playwrightHelper?.page?.setDefaultTimeout(this.config.playwright.timeout ?? DEFAULT_PAGE_TIMEOUT);
395
+ }
396
+
377
397
  private async waitForPageReadiness(page: any): Promise<void> {
378
398
  await waitForPageReadiness(page, {
379
399
  timeout: this.config.playwright.waitForTimeout,
@@ -442,3 +462,23 @@ const detachStepLogger = (listener: StepListener) => {
442
462
  codeceptjs.event.dispatcher.off(codeceptjs.event.step.passed, listener);
443
463
  codeceptjs.event.dispatcher.off(codeceptjs.event.step.failed, listener);
444
464
  };
465
+
466
+ const readFocusedElement = () => {
467
+ const el = document.activeElement as any;
468
+ if (!el || el === document.body) return null;
469
+
470
+ const tag = el.tagName.toLowerCase();
471
+ const textish = new Set(['text', 'search', 'email', 'password', 'url', 'tel', 'number']);
472
+ let role = el.getAttribute('role') || tag;
473
+ if (tag === 'textarea' || el.isContentEditable) role = 'textbox';
474
+ if (tag === 'input' && textish.has(el.type)) role = 'textbox';
475
+ if (tag === 'select') role = 'combobox';
476
+ if (tag === 'a') role = 'link';
477
+
478
+ const label = el.getAttribute('aria-label') || el.getAttribute('placeholder') || el.labels?.[0]?.textContent || el.textContent || '';
479
+ const focused: { role: string; name: string; value?: string } = { role, name: label.trim().slice(0, 80) };
480
+
481
+ const value = el.value ?? el.textContent;
482
+ if (typeof value === 'string' && value) focused.value = value.slice(0, 200);
483
+ return focused;
484
+ };
@@ -18,7 +18,7 @@ export function WithWebMode<T extends Constructor>(Base: T) {
18
18
  });
19
19
  const { see, context, visualClick, learnExperience } = agentTools;
20
20
 
21
- return {
21
+ const tools: Record<string, any> = {
22
22
  navigate: tool({
23
23
  description: 'Navigate to a URL or page description using AI-powered navigation.',
24
24
  inputSchema: z.object({
@@ -123,11 +123,14 @@ export function WithWebMode<T extends Constructor>(Base: T) {
123
123
  }),
124
124
 
125
125
  ...codeceptTools,
126
- see,
127
126
  context,
128
- visualClick,
129
127
  learnExperience,
130
128
  };
129
+
130
+ if (see) tools.see = see;
131
+ if (visualClick) tools.visualClick = visualClick;
132
+
133
+ return tools;
131
134
  }
132
135
 
133
136
  webModePrompt(): string {
package/src/ai/captain.ts CHANGED
@@ -21,6 +21,7 @@ import type { Navigator } from './navigator.ts';
21
21
  import type { Provider } from './provider.ts';
22
22
  import { Researcher } from './researcher.ts';
23
23
  import { TaskAgent } from './task-agent.ts';
24
+ import { withdrawVisionTools } from './tools.ts';
24
25
 
25
26
  const MAX_STEPS = 15;
26
27
 
@@ -437,6 +438,8 @@ export class Captain extends CaptainBase implements Agent {
437
438
  return;
438
439
  }
439
440
 
441
+ withdrawVisionTools(tools);
442
+
440
443
  const currentState = stateManager.getCurrentState();
441
444
  if (!currentState && this.getMode() !== 'idle') {
442
445
  stop();