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.
- package/README.md +1 -1
- package/bin/explorbot-cli.ts +26 -8
- package/boat/api-tester/src/cli.ts +17 -0
- package/boat/api-tester/src/config.ts +4 -2
- package/boat/doc-collector/bin/doc-collector-cli.ts +2 -0
- package/boat/doc-collector/src/ai/documentarian.ts +61 -31
- package/boat/doc-collector/src/cli.ts +14 -1
- package/boat/doc-collector/src/config.ts +4 -2
- package/boat/prima/bin/prima-cli.ts +0 -0
- package/boat/prima/src/activity-line.ts +33 -0
- package/boat/prima/src/cli.ts +127 -86
- package/boat/prima/src/envelope.ts +102 -52
- package/boat/prima/src/prima.ts +567 -128
- package/boat/prima/src/pw-parser.ts +11 -1
- package/boat/prima/src/pw-registry.ts +4 -5
- package/boat/prima/src/session-log.ts +126 -0
- package/dist/bin/explorbot-cli.js +26 -8
- package/dist/boat/api-tester/bin/apibot-cli.js +2 -0
- package/dist/boat/api-tester/src/cli.js +17 -0
- package/dist/boat/api-tester/src/config.js +4 -2
- package/dist/boat/doc-collector/bin/doc-collector-cli.js +2 -0
- package/dist/boat/doc-collector/src/ai/documentarian.js +44 -19
- package/dist/boat/doc-collector/src/cli.js +14 -1
- package/dist/boat/doc-collector/src/config.js +4 -2
- package/dist/boat/prima/src/activity-line.js +30 -0
- package/dist/boat/prima/src/cli.js +109 -77
- package/dist/boat/prima/src/envelope.js +94 -44
- package/dist/boat/prima/src/prima.js +533 -119
- package/dist/boat/prima/src/pw-parser.js +13 -1
- package/dist/boat/prima/src/pw-registry.js +4 -5
- package/dist/boat/prima/src/session-log.js +108 -0
- package/dist/package.json +3 -2
- package/dist/rules/navigator/verification-actions.md +20 -0
- package/dist/src/action-result.d.ts +7 -0
- package/dist/src/action-result.js +4 -0
- package/dist/src/action.d.ts +2 -0
- package/dist/src/action.js +41 -2
- package/dist/src/ai/captain/web-mode.js +6 -3
- package/dist/src/ai/captain.js +2 -0
- package/dist/src/ai/navigator.d.ts +34 -0
- package/dist/src/ai/navigator.js +237 -181
- package/dist/src/ai/pilot.d.ts +7 -0
- package/dist/src/ai/pilot.js +90 -2
- package/dist/src/ai/provider.d.ts +2 -2
- package/dist/src/ai/provider.js +14 -23
- package/dist/src/ai/rerunner.js +2 -1
- package/dist/src/ai/researcher/cache.d.ts +2 -0
- package/dist/src/ai/researcher/cache.js +10 -2
- package/dist/src/ai/researcher.js +3 -2
- package/dist/src/ai/rules.js +17 -10
- package/dist/src/ai/session-analyst.js +2 -0
- package/dist/src/ai/task-agent.js +4 -1
- package/dist/src/ai/tester.d.ts +6 -3
- package/dist/src/ai/tester.js +50 -46
- package/dist/src/ai/tools.d.ts +14 -0
- package/dist/src/ai/tools.js +117 -37
- package/dist/src/commands/config-command.d.ts +51 -0
- package/dist/src/commands/config-command.js +117 -0
- package/dist/src/commands/index.js +2 -0
- package/dist/src/config.d.ts +9 -1
- package/dist/src/config.js +53 -4
- package/dist/src/execution-controller.d.ts +2 -0
- package/dist/src/execution-controller.js +6 -0
- package/dist/src/explorbot.d.ts +2 -1
- package/dist/src/explorbot.js +7 -2
- package/dist/src/explorer.js +2 -3
- package/dist/src/playwright-recorder.js +30 -0
- package/dist/src/remote.d.ts +55 -0
- package/dist/src/remote.js +235 -0
- package/dist/src/reporter.d.ts +1 -0
- package/dist/src/reporter.js +7 -1
- package/dist/src/state-manager.d.ts +2 -1
- package/dist/src/state-manager.js +3 -1
- package/dist/src/stats.d.ts +1 -0
- package/dist/src/stats.js +1 -0
- package/dist/src/test-plan.d.ts +3 -0
- package/dist/src/test-plan.js +26 -0
- package/dist/src/utils/aria.d.ts +2 -8
- package/dist/src/utils/aria.js +69 -40
- package/dist/src/utils/html.js +1 -0
- package/dist/src/utils/logger.d.ts +7 -1
- package/dist/src/utils/logger.js +32 -0
- package/dist/src/utils/page-readiness.js +18 -1
- package/dist/src/utils/url-matcher.js +3 -0
- package/dist/src/utils/web-element.d.ts +2 -0
- package/dist/src/utils/web-element.js +8 -0
- package/dist/src/utils/web-sandbox.d.ts +1 -1
- package/dist/src/utils/web-sandbox.js +2 -3
- package/docs/api-testing/basics.md +90 -0
- package/docs/api-testing/planning.md +57 -0
- package/docs/api-testing/running-tests.md +55 -0
- package/docs/assets/cloud-report.png +0 -0
- package/docs/assets/html-report.png +0 -0
- package/docs/assets/langfuse-trace.png +0 -0
- package/docs/assets/successful-explore-run.png +0 -0
- package/docs/basics/getting-started.md +140 -0
- package/docs/basics/prerequisites.md +63 -0
- package/docs/basics/providers.md +362 -0
- package/docs/basics/running.md +78 -0
- package/docs/contributing/ai-integration-tests.md +57 -0
- package/docs/contributing/contributing.md +90 -0
- package/docs/contributing/demo-videos.md +36 -0
- package/docs/contributing/npm-package.md +138 -0
- package/docs/contributing/observability.md +227 -0
- package/docs/contributing/regression-tests.md +103 -0
- package/docs/contributing/testing.md +95 -0
- package/docs/doc-collection/basics.md +128 -0
- package/docs/doc-collection/crawling.md +67 -0
- package/docs/doc-collection/interactive-mode.md +99 -0
- package/docs/index.json +87 -0
- package/docs/reference/commands.md +997 -0
- package/docs/reference/configuration.md +569 -0
- package/docs/reference/scripting.md +303 -0
- package/docs/reference/websocket.md +50 -0
- package/docs/superpowers/plans/2026-08-01-actor-boat.md +925 -0
- package/docs/superpowers/plans/2026-08-01-prima-boat.md +1120 -0
- package/docs/superpowers/reviews/2026-08-06-prima-vs-playwright-cli.md +268 -0
- package/docs/superpowers/specs/2026-08-01-actor-boat-design.md +204 -0
- package/docs/superpowers/specs/2026-08-01-prima-boat-design.md +242 -0
- package/docs/superpowers/specs/2026-08-03-global-config-design.md +138 -0
- package/docs/superpowers/specs/2026-08-07-prima-fixes-design.md +394 -0
- package/docs/superpowers/specs/2026-08-18-prima-false-verdicts.md +159 -0
- package/docs/web-testing/agents.md +158 -0
- package/docs/web-testing/automated-tests.md +134 -0
- package/docs/web-testing/basics.md +91 -0
- package/docs/web-testing/customization.md +131 -0
- package/docs/web-testing/hooks.md +238 -0
- package/docs/web-testing/page-interaction.md +84 -0
- package/docs/web-testing/planner.md +122 -0
- package/docs/web-testing/rerun.md +164 -0
- package/docs/web-testing/researcher.md +380 -0
- package/docs/workflow/agentic-usage.md +233 -0
- package/docs/workflow/application-spec.md +73 -0
- package/docs/workflow/ci.md +202 -0
- package/docs/workflow/knowledge.md +310 -0
- package/docs/workflow/planning-styles.md +67 -0
- package/docs/workflow/reporting.md +133 -0
- package/docs/workflow/test-plans.md +90 -0
- package/package.json +3 -2
- package/rules/navigator/verification-actions.md +20 -0
- package/src/action-result.ts +11 -0
- package/src/action.ts +43 -3
- package/src/ai/captain/web-mode.ts +6 -3
- package/src/ai/captain.ts +3 -0
- package/src/ai/navigator.ts +255 -186
- package/src/ai/pilot.ts +104 -2
- package/src/ai/provider.ts +14 -24
- package/src/ai/rerunner.ts +2 -1
- package/src/ai/researcher/cache.ts +12 -2
- package/src/ai/researcher.ts +3 -2
- package/src/ai/rules.ts +17 -10
- package/src/ai/session-analyst.ts +2 -0
- package/src/ai/task-agent.ts +3 -1
- package/src/ai/tester.ts +52 -45
- package/src/ai/tools.ts +136 -37
- package/src/commands/config-command.ts +146 -0
- package/src/commands/index.ts +2 -0
- package/src/config.ts +60 -5
- package/src/execution-controller.ts +8 -0
- package/src/explorbot.ts +7 -3
- package/src/explorer.ts +2 -2
- package/src/playwright-recorder.ts +23 -0
- package/src/remote.ts +244 -0
- package/src/reporter.ts +7 -1
- package/src/state-manager.ts +6 -2
- package/src/stats.ts +1 -0
- package/src/test-plan.ts +29 -0
- package/src/utils/aria.ts +65 -45
- package/src/utils/html.ts +1 -0
- package/src/utils/logger.ts +33 -2
- package/src/utils/page-readiness.ts +24 -1
- package/src/utils/url-matcher.ts +3 -0
- package/src/utils/web-element.ts +9 -0
- 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
|
+

|
|
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
|
+

|
|
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
|
+
"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.
|
|
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').
|
package/src/action-result.ts
CHANGED
|
@@ -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(() =>
|
|
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
|
-
|
|
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();
|