From 36edd705bfa36f1443ac03149063a2729d02090d Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Thu, 2 Apr 2026 14:13:00 +0300 Subject: [PATCH 1/2] Fixed nullish error for openAI models, actualized config generator, added Makefile --- Makefile | 6 +++++ bin/explorbot-cli.ts | 63 +++++++++++++++++++++++++++++++++----------- bun.lock | 6 ++++- src/ai/pilot.ts | 24 ++++++++++++----- src/ai/planner.ts | 16 ++++++++--- 5 files changed, 89 insertions(+), 26 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..aeb7bbc --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +start: + ./bin/explorbot-cli.ts start + +init: + ./bin/explorbot-cli.ts init + \ No newline at end of file diff --git a/bin/explorbot-cli.ts b/bin/explorbot-cli.ts index 8dc75fa..f5c575e 100755 --- a/bin/explorbot-cli.ts +++ b/bin/explorbot-cli.ts @@ -144,7 +144,10 @@ addCommonOptions(program.command('plan [feature]').description('Generate } } - await explorBot.plan(feature || undefined, { fresh: !options.append, style: options.style }); + await explorBot.plan(feature || undefined, { + fresh: !options.append, + style: options.style, + }); const plan = explorBot.getCurrentPlan(); if (!plan?.tests.length) { @@ -320,7 +323,13 @@ program log(`Working in directory: ${resolvedPath}`); } - const defaultConfig = `import { } from 'ai'; + const defaultConfig = `import { '' } from ''; + +// This example uses OpenRouter (one API key, many providers). Any Vercel AI SDK provider works; see +// https://github.com/testomatio/explorbot/blob/main/docs/providers.md +const openrouter = createOpenRouter({ + apiKey: process.env.OPENROUTER_API_KEY, +}); const config = { playwright: { @@ -330,9 +339,9 @@ const config = { }, ai: { - provider: , model: '', - apiKey: '', + visionModel: '', + agenticModel: '', }, reporter: { @@ -470,7 +479,9 @@ program .option('-p, --path ', 'Working directory path') .action(async (url, description, options) => { try { - await ConfigParser.getInstance().loadConfig({ path: options.path || process.cwd() }); + await ConfigParser.getInstance().loadConfig({ + path: options.path || process.cwd(), + }); if (url && description) { const { KnowledgeTracker } = await import('../src/knowledge-tracker.js'); @@ -498,7 +509,9 @@ program .option('-p, --path ', 'Working directory path') .action(async (url, options) => { try { - await ConfigParser.getInstance().loadConfig({ path: options.path || process.cwd() }); + await ConfigParser.getInstance().loadConfig({ + path: options.path || process.cwd(), + }); const { KnowsCommand } = await import('../src/commands/knows-command.js'); const explorBot = new ExplorBot({ path: options.path }); const command = new KnowsCommand(explorBot); @@ -648,14 +661,20 @@ browserCmd .option('-p, --path ', 'Working directory path') .action(async (options) => { const { launchServer, removeEndpointFile } = await import('../src/browser-server.js'); - await ConfigParser.getInstance().loadConfig({ config: options.config, path: options.path }); + await ConfigParser.getInstance().loadConfig({ + config: options.config, + path: options.path, + }); const config = ConfigParser.getInstance().getConfig(); let show = config.playwright.show || false; if (options.show !== undefined) show = true; if (options.headless !== undefined) show = false; - const server = await launchServer({ browser: config.playwright.browser, show }); + const server = await launchServer({ + browser: config.playwright.browser, + show, + }); console.log('Browser server is running. Press Ctrl+C to stop.'); @@ -677,7 +696,10 @@ browserCmd .option('-p, --path ', 'Working directory path') .action(async (options) => { const { getAliveEndpoint, removeEndpointFile } = await import('../src/browser-server.js'); - await ConfigParser.getInstance().loadConfig({ config: options.config, path: options.path }); + await ConfigParser.getInstance().loadConfig({ + config: options.config, + path: options.path, + }); const endpoint = await getAliveEndpoint(); if (!endpoint) { @@ -702,7 +724,10 @@ browserCmd .option('-p, --path ', 'Working directory path') .action(async (options) => { const { getAliveEndpoint } = await import('../src/browser-server.js'); - await ConfigParser.getInstance().loadConfig({ config: options.config, path: options.path }); + await ConfigParser.getInstance().loadConfig({ + config: options.config, + path: options.path, + }); const endpoint = await getAliveEndpoint(); if (endpoint) { @@ -743,15 +768,23 @@ program if (agent && name) { const { AddRuleCommand } = await import('../src/commands/add-rule-command.js'); - const result = AddRuleCommand.createRuleFile(agent, name, { urlPattern: options.url }); + const result = AddRuleCommand.createRuleFile(agent, name, { + urlPattern: options.url, + }); process.exit(result ? 0 : 1); } const AddRule = (await import('../src/components/AddRule.js')).default; - render(React.createElement(AddRule, { initialAgent: agent || '', initialName: name || '' }), { - exitOnCtrlC: false, - patchConsole: false, - }); + render( + React.createElement(AddRule, { + initialAgent: agent || '', + initialName: name || '', + }), + { + exitOnCtrlC: false, + patchConsole: false, + } + ); }); import { createApiCommands } from '../boat/api-tester/src/cli.ts'; diff --git a/bun.lock b/bun.lock index 7c83b2a..fc675d5 100644 --- a/bun.lock +++ b/bun.lock @@ -83,7 +83,7 @@ "@ai-sdk/groq": ["@ai-sdk/groq@3.0.2", "", { "dependencies": { "@ai-sdk/provider": "3.0.1", "@ai-sdk/provider-utils": "4.0.2" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Gs7Ir9cUSYlbDIArNMt3+0Ql+OrEKELQhYfji5CCxQ8MdcJGbhbyPf9AQralu9PMxq/QEy2JSOgYW5zOnHDd2g=="], - "@ai-sdk/openai": ["@ai-sdk/openai@3.0.2", "", { "dependencies": { "@ai-sdk/provider": "3.0.1", "@ai-sdk/provider-utils": "4.0.2" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-GONwavgSWtcWO+t9+GpGK8l7nIYh+zNtCL/NYDSeHxHiw6ksQS9XMRWrZyE5NpJ0EXNxSAWCHIDmb1WvTqhq9Q=="], + "@ai-sdk/openai": ["@ai-sdk/openai@3.0.49", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-U2f0pCyNn/jQH3wjgxr8o9VvCkuDFTtXbIhbFFtgXqCzMbed6rBnvzQcAMEK0/Pa44byL9zfcvCOFOflvkRA8w=="], "@ai-sdk/provider": ["@ai-sdk/provider@3.0.1", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-2lR4w7mr9XrydzxBSjir4N6YMGdXD+Np1Sh0RXABh7tWdNFFwIeRI1Q+SaYZMbfL8Pg8RRLcrxQm51yxTLhokg=="], @@ -2653,6 +2653,10 @@ "zone.js": ["zone.js@0.15.1", "", {}, "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w=="], + "@ai-sdk/openai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="], + + "@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + "@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], diff --git a/src/ai/pilot.ts b/src/ai/pilot.ts index 8f815a2..c223767 100644 --- a/src/ai/pilot.ts +++ b/src/ai/pilot.ts @@ -85,7 +85,7 @@ export class Pilot implements Agent { const schema = z.object({ decision: z.enum(['pass', 'fail', 'continue', 'skipped']).describe('pass = test succeeded, fail = test failed, continue = tester should keep going, skipped = scenario is irrelevant OR systematic execution failures prevented testing'), reason: z.string().describe('What happened and why (1-2 sentences). Do NOT repeat the decision status (e.g. "scenario goal achieved/not achieved") — just explain the evidence. For continue: explain why rejected and suggest alternatives.'), - guidance: z.string().nullish().describe('Required for "continue": specific actionable instruction for the tester — what exactly to verify, retry differently, or complete next. Be concrete.'), + guidance: z.string().nullable().describe('Required for "continue": specific actionable instruction for the tester — what exactly to verify, retry differently, or complete next. Be concrete.'), }); const userContent = dedent` @@ -117,12 +117,18 @@ export class Pilot implements Agent { `; const messages = [ - { role: 'system' as const, content: this.buildVerdictSystemPrompt(type, task) }, + { + role: 'system' as const, + content: this.buildVerdictSystemPrompt(type, task), + }, { role: 'user' as const, content: userContent }, ]; try { - const response = await this.provider.generateObject(messages, schema, this.provider.getAgenticModel('pilot'), { agentName: 'pilot', experimental_telemetry: { functionId: 'pilot.reviewVerdict' } }); + const response = await this.provider.generateObject(messages, schema, this.provider.getAgenticModel('pilot'), { + agentName: 'pilot', + experimental_telemetry: { functionId: 'pilot.reviewVerdict' }, + }); const result = response?.object; if (!result) { @@ -222,7 +228,9 @@ export class Pilot implements Agent { async planTest(task: Test, currentState: ActionResult): Promise { tag('substep').log('Pilot planning test...'); - const pageSummary = await this.researcher.summary(currentState, { allowNewResearch: false }); + const pageSummary = await this.researcher.summary(currentState, { + allowNewResearch: false, + }); const agenticModel = this.provider.getAgenticModel('pilot'); this.conversation = this.provider.startConversation(this.getSystemPrompt(task, currentState, pageSummary), 'pilot', agenticModel); @@ -257,7 +265,9 @@ export class Pilot implements Agent { tag('substep').log('Pilot reviewing new page...'); - const pageSummary = await this.researcher.summary(currentState, { allowNewResearch: false }); + const pageSummary = await this.researcher.summary(currentState, { + allowNewResearch: false, + }); if (!pageSummary) return ''; const stateContext = this.buildStateContext(currentState); @@ -289,7 +299,9 @@ export class Pilot implements Agent { tag('substep').log('Pilot analyzing progress...'); if (!this.conversation) { - const pageSummary = await this.researcher.summary(currentState, { allowNewResearch: false }); + const pageSummary = await this.researcher.summary(currentState, { + allowNewResearch: false, + }); const agenticModel = this.provider.getAgenticModel('pilot'); this.conversation = this.provider.startConversation(this.getSystemPrompt(task, currentState, pageSummary), 'pilot', agenticModel); } diff --git a/src/ai/planner.ts b/src/ai/planner.ts index 78f92e4..db912fd 100644 --- a/src/ai/planner.ts +++ b/src/ai/planner.ts @@ -32,7 +32,7 @@ const TasksSchema = z.object({ z.object({ scenario: z.string().describe('A single sentence describing what to test'), priority: z.enum(['critical', 'important', 'high', 'normal', 'low']).describe('Priority of the task based on business importance'), - startUrl: z.string().optional().describe('Start URL for the test if different from plan URL (only for tests on visited subpages)'), + startUrl: z.string().nullable().describe('Start URL for the test if different from plan URL (only for tests on visited subpages)'), steps: z.array(z.string()).describe('List of steps to perform for this scenario. Each step should be a specific action (e.g., "Click on Login button", "Enter username in email field", "Submit the form"). Keep steps atomic and actionable.'), expectedOutcomes: z .array(z.string()) @@ -325,13 +325,18 @@ export class Planner extends PlannerBase implements Agent { conversation.addUserText(planningPrompt); const currentState = this.stateManager.getCurrentState(); - const research = await this.researcher.research(currentState || state, { deep: true }); + const research = await this.researcher.research(currentState || state, { + deep: true, + }); let plannerResearch = mdq(research).query('code').replace(''); for (const table of mdq(plannerResearch).query('table').each()) { const rawTable = table.text(); const rows = table.toJson(); if (rows.length === 0 || !rows[0].Element) continue; - const elementWithType = rows.map((r) => ({ Element: r.Element, Type: r.Type || '' })); + const elementWithType = rows.map((r) => ({ + Element: r.Element, + Type: r.Type || '', + })); plannerResearch = plannerResearch.replace(rawTable, jsonToTable(elementWithType, ['Element', 'Type'])); } @@ -349,7 +354,10 @@ export class Planner extends PlannerBase implements Agent { `); - const rawFlows = this.experienceTracker.getSuccessfulExperience(state, { includeDescendants: true, stripCode: true }); + const rawFlows = this.experienceTracker.getSuccessfulExperience(state, { + includeDescendants: true, + stripCode: true, + }); const flows = rawFlows.map((f) => this.cleanExperienceFlows(f)).filter(Boolean) as string[]; if (flows.length > 0) { conversation.addUserText(dedent` From a218ec5576179ddded350fc7db028c2e926972b6 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Sun, 5 Apr 2026 15:00:15 +0300 Subject: [PATCH 2/2] Update docs according to CHANGELOG.md --- bun.lock | 6 +- docs/agents.md | 25 ++++- docs/commands.md | 239 ++++++++++++++++++++++++++++++++++++------ docs/configuration.md | 220 ++++++++++++++++++++++++++------------ docs/planner.md | 64 ++++++----- docs/researcher.md | 95 ++++++++++------- package.json | 12 ++- 7 files changed, 487 insertions(+), 174 deletions(-) diff --git a/bun.lock b/bun.lock index fc675d5..e9b4567 100644 --- a/bun.lock +++ b/bun.lock @@ -7,7 +7,7 @@ "dependencies": { "@ai-sdk/anthropic": "^3.0", "@ai-sdk/groq": "^3.0", - "@ai-sdk/openai": "^3.0", + "@ai-sdk/openai": "^3.0.49", "@axe-core/playwright": "^4.11.0", "@inkjs/ui": "^2.0.0", "@langfuse/otel": "^4.5.1", @@ -20,7 +20,7 @@ "@opentelemetry/sdk-trace-base": "^2.2.0", "@opentelemetry/semantic-conventions": "^1.38.0", "@scalar/openapi-parser": "^0.25.6", - "@testomatio/reporter": "2.7.3", + "@testomatio/reporter": "2.7.6", "ai": "^6.0.6", "axe-core": "^4.11.1", "bash-tool": "^1.3.15", @@ -919,7 +919,7 @@ "@testing-library/react": ["@testing-library/react@16.3.0", "", { "dependencies": { "@babel/runtime": "^7.12.5" }, "peerDependencies": { "@testing-library/dom": "^10.0.0", "@types/react": "^18.0.0 || ^19.0.0", "@types/react-dom": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw=="], - "@testomatio/reporter": ["@testomatio/reporter@2.7.3", "", { "dependencies": { "@aws-sdk/client-s3": "^3.279.0", "@aws-sdk/lib-storage": "^3.279.0", "@cucumber/cucumber": "^10.9.0", "@octokit/rest": "^21.1.1", "aws-sdk": "^2.1072.0", "callsite-record": "^4.1.4", "commander": "^12", "cross-spawn": "^7.0.3", "csv-writer": "^1.6.0", "debug": "4.3.4", "dotenv": "^16.0.1", "fast-xml-parser": "^5.3.4", "file-url": "3.0.0", "filesize": "^10.1.6", "gaxios": ">=6.0 || >=7.0.0-rc.4 || <8", "glob": "^10.3", "handlebars": "^4.7.8", "has-flag": "^5.0.1", "humanize-duration": "^3.27.3", "is-valid-path": "^0.1.1", "js-yaml": "^4.1.1", "json-cycle": "^1.3.0", "lodash.memoize": "^4.1.2", "lodash.merge": "^4.6.2", "minimatch": "^10.2.4", "picocolors": "^1.0.1", "pretty-ms": "^7.0.1", "promise-retry": "^2.0.1", "strip-ansi": "7.1.0", "uuid": "^9.0.0" }, "bin": { "report-xml": "src/bin/reportXml.js", "start-test-run": "src/bin/startTest.js", "upload-artifacts": "src/bin/uploadArtifacts.js", "reporter": "src/bin/cli.js" } }, "sha512-ytrKPVPeQ7PkA/BfTkkkNMs4QlUQ/ZwFZhSsrxIN8SAph6M9GG1gjjUuft7hrwAgN2aWZkDyp8CL0eYe6czHYg=="], + "@testomatio/reporter": ["@testomatio/reporter@2.7.6", "", { "dependencies": { "@aws-sdk/client-s3": "^3.279.0", "@aws-sdk/lib-storage": "^3.279.0", "@cucumber/cucumber": "^10.9.0", "@octokit/rest": "^21.1.1", "aws-sdk": "^2.1072.0", "callsite-record": "^4.1.4", "commander": "^12", "cross-spawn": "^7.0.3", "csv-writer": "^1.6.0", "debug": "4.3.4", "dotenv": "^16.0.1", "fast-xml-parser": "^5.3.4", "file-url": "3.0.0", "filesize": "^10.1.6", "gaxios": ">=6.0 || >=7.0.0-rc.4 || <8", "glob": "^10.3", "handlebars": "^4.7.8", "has-flag": "^5.0.1", "humanize-duration": "^3.27.3", "is-valid-path": "^0.1.1", "js-yaml": "^4.1.1", "json-cycle": "^1.3.0", "lodash.memoize": "^4.1.2", "lodash.merge": "^4.6.2", "minimatch": "^10.2.4", "picocolors": "^1.0.1", "pretty-ms": "^7.0.1", "promise-retry": "^2.0.1", "strip-ansi": "7.1.0", "uuid": "^9.0.0" }, "bin": { "report-xml": "src/bin/reportXml.js", "start-test-run": "src/bin/startTest.js", "upload-artifacts": "src/bin/uploadArtifacts.js", "reporter": "src/bin/cli.js" } }, "sha512-Q7onX+TBqXGfeANSRTEfPUl/T4l7kJTjSfOWBKEJvD4aVRAaCH1L+psnsHkQr8i9IzGtHowoGJlBeCzGNfiMCg=="], "@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="], diff --git a/docs/agents.md b/docs/agents.md index b2aa7ed..ae18af8 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -19,17 +19,20 @@ flowchart LR **Purpose:** Handles all browser interactions — clicks, form fills, navigation. **What it does:** + - Executes CodeceptJS commands in the browser - Tries multiple locator strategies when selectors fail - Automatically resolves failed interactions without stopping - Remembers what worked (and what didn't) for next time **Why you'll love it:** + - No more `ElementNotFound` exceptions killing your test runs - Self-healing when your UI changes - Learns optimal selectors for your specific app **Commands that use Navigator:** + - `/navigate ` - `I.click()`, `I.fillField()`, `I.amOnPage()`, etc. @@ -38,6 +41,7 @@ flowchart LR **Purpose:** Analyzes pages to understand what's actually there. **What it does:** + - Discovers all interactive UI elements - Expands hidden content (accordions, dropdowns, modals) - Maps navigation paths and form structures @@ -45,12 +49,14 @@ flowchart LR - Filters out irrelevant elements (cookie banners, ads) **Why you'll love it:** + - Discovers UI elements you forgot existed - Gives you a complete picture of what's testable - Documents forms with all their validation rules - Configurable filtering to focus on what matters **Commands that use Researcher:** + - `explorbot research /path` (CLI) - `/research [path]` (TUI) - `/research --deep` — expand hidden elements @@ -63,6 +69,7 @@ See [Researcher Agent](./researcher.md) for detailed configuration and usage. **Purpose:** Generates test scenarios from research findings. **What it does:** + - Creates business-focused test scenarios - Assigns priority levels (critical/important/high/normal/low) - Generates expected outcomes for verification @@ -71,12 +78,14 @@ See [Researcher Agent](./researcher.md) for detailed configuration and usage. - Cycles through planning styles (normal, psycho, curious) for comprehensive coverage **Why you'll love it:** + - Creates tests that matter, not just "click stuff" - Prioritizes by risk (critical flows first) - Different styles ensure broad coverage over multiple iterations - Fully customizable — add your own styles and page-specific rules **Commands that use Planner:** + - `/plan [feature]` - `/explore` @@ -87,6 +96,7 @@ See [Planner Agent](./planner.md) for detailed documentation on planning styles, **Purpose:** Executes the planned scenarios. **What it does:** + - Runs test scenarios step by step - Adapts when things don't go as expected - Tracks state changes during execution @@ -94,11 +104,13 @@ See [Planner Agent](./planner.md) for detailed documentation on planning styles, - Uses research context for smart decisions **Why you'll love it:** + - Handles unexpected modals and popups - Recovers from minor failures automatically - Produces detailed execution logs **Commands that use Tester:** + - `/test [scenario]` - `/explore` @@ -107,31 +119,37 @@ See [Planner Agent](./planner.md) for detailed documentation on planning styles, **Purpose:** Supervises Tester and intervenes when tests get stuck. **What it does:** + - Maintains separate conversation to track test progress over time - Detects stuck patterns (loops, repeated failures, no page changes) - Decides what context Tester needs (HTML, ARIA, UI map) - Asks user for help when automated recovery fails **Why you'll love it:** + - Catches when Tester is spinning wheels on the same failure - Requests user input before giving up on a test - Can use smarter models without token cost explosion (only sees tool summaries, not raw HTML) **When Pilot intervenes:** + - Actions succeed but page doesn't change (wrong element) - Same action repeated multiple times (loop) - Same locator keeps failing (need alternative approach) - Only research/context calls, no action tools (not progressing) -## Captain Agent *(coming soon)* +## Captain Agent -**Purpose:** Orchestrates the whole testing session. +**Purpose:** Orchestrates the whole testing session and handles user commands in TUI. **What it does:** -- Coordinates all agents intelligently + - Responds to user commands in real-time - Adjusts strategy based on discoveries - Manages conversation context efficiently +- Runs in idle mode with access to diagnostic tools +- Inspects test sessions (logs, tool calls, ARIA states, pilot analysis) +- Reads/writes files, evaluates browser JS, manages tabs ## Per-Agent Model Configuration @@ -157,6 +175,7 @@ export default { ``` **Typical optimization:** + - Navigator needs fast responses for real-time interaction - Researcher benefits from vision capabilities - Planner can use a slightly larger model for better test design diff --git a/docs/commands.md b/docs/commands.md index d37b158..3ab04ee 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -9,13 +9,16 @@ Explorbot has two types of commands: Some commands work in both modes. Where a CLI equivalent exists, it is noted below. -| TUI Command | CLI Equivalent | -|-------------|---------------| -| `/explore [url]` | `explorbot explore [path]` | -| `/research [url]` | `explorbot research ` | -| `/plan [feature]` | `explorbot plan [feature]` | -| `/drill` | `explorbot drill ` | -| `/know [note]` | `explorbot knows:add [url] [description]` | +| TUI Command | CLI Equivalent | +| ------------------ | ----------------------------------------- | +| `/explore [url]` | `explorbot explore [path]` | +| `/research [url]` | `explorbot research ` | +| `/plan [feature]` | `explorbot plan [feature]` | +| `/drill` | `explorbot drill ` | +| `/know [note]` | `explorbot knows:add [url] [description]` | +| `/test [scenario]` | `explorbot test [index]` | +| `/freesail` | `explorbot freesail [startUrl]` | +| `/rules:add` | `explorbot add-rule [agent] [name]` | CLI commands run headless by default, execute the task, and exit. TUI commands run inside an interactive session where you can chain multiple actions. @@ -23,16 +26,16 @@ CLI commands run headless by default, execute the task, and exit. TUI commands r These options are available on all CLI commands (`start`, `explore`, `plan`, `drill`, `research`, `context`): -| Option | Description | -|--------|-------------| -| `-v, --verbose` | Enable verbose logging | -| `--debug` | Enable debug logging (same as `--verbose`) | -| `-c, --config ` | Path to configuration file | -| `-p, --path ` | Working directory path | -| `-s, --show` | Show browser window | -| `--headless` | Run browser in headless mode | -| `--incognito` | Run without recording experiences | -| `--session [file]` | Save/restore browser session (cookies, localStorage) from file | +| Option | Description | +| --------------------- | -------------------------------------------------------------- | +| `-v, --verbose` | Enable verbose logging | +| `--debug` | Enable debug logging (same as `--verbose`) | +| `-c, --config ` | Path to configuration file | +| `-p, --path ` | Working directory path | +| `-s, --show` | Show browser window | +| `--headless` | Run browser in headless mode | +| `--incognito` | Run without recording experiences | +| `--session [file]` | Save/restore browser session (cookies, localStorage) from file | ### `--session` @@ -97,12 +100,87 @@ explorbot start /dashboard explorbot browser stop ``` -| Option | Description | -|--------|-------------| -| `-s, --show` | Launch browser in headed mode (visible window) | -| `--headless` | Launch browser in headless mode | -| `-c, --config ` | Path to configuration file | -| `-p, --path ` | Working directory path | +| Option | Description | +| --------------------- | ---------------------------------------------- | +| `-s, --show` | Launch browser in headed mode (visible window) | +| `--headless` | Launch browser in headless mode | +| `-c, --config ` | Path to configuration file | +| `-p, --path ` | Working directory path | + +## Test Execution + +### `explorbot test [index]` + +Run tests from a saved plan file without launching TUI. + +```bash +explorbot test plan.md 1 # run first test +explorbot test plan.md 1-3 # run tests 1 to 3 +explorbot test plan.md 1,3,5 # run specific tests +explorbot test plan.md * # run all pending tests +explorbot test plan.md all # same as * +``` + +| Option | Description | +| ------------------ | -------------------------- | +| `--grep ` | Run tests matching pattern | + +### `explorbot shell ` + +Navigate to a URL, execute a single CodeceptJS command, and exit. Useful for quick one-off browser interactions. + +```bash +explorbot shell /login "I.see('Welcome')" +explorbot shell /dashboard "I.click('Settings')" +``` + +## Autonomous Exploration + +### `explorbot freesail [startUrl]` + +Continuously explore and test pages autonomously. Explorbot navigates to new pages, researches them, runs tests, then moves on — indefinitely. + +```bash +explorbot freesail /admin # start exploring from /admin +explorbot freesail /dashboard --deep # depth-first: explore nearby pages first +explorbot freesail /app --shallow # breadth-first: spread across many pages +explorbot freesail /app --scope /admin # only explore pages under /admin +explorbot freesail /app --max-tests 20 # stop after 20 tests +``` + +| Option | Description | +| --------------------- | ----------------------------------------------------------------------- | +| `--deep` | Depth-first: prioritize newly discovered pages close to the current URL | +| `--shallow` | Breadth-first: pick the globally least-visited page next | +| `--scope ` | Restrict to URLs starting with the given prefix | +| `--max-tests ` | Stop after the specified number of tests | + +## API Testing + +### `explorbot api` + +AI-powered API testing. Generate and run test plans for API endpoints. + +```bash +explorbot api init # initialize API testing project +explorbot api plan /users # generate test plan for endpoint +explorbot api plan /users --style curious # use a specific planning style +explorbot api test plan.md # run tests from plan +explorbot api test plan.md 1-3 # run specific tests +explorbot api know /users "CRUD endpoint" # add API knowledge +``` + +## Rules Management + +### `explorbot add-rule [agent] [name]` + +Create a rule file for an agent. Opens an interactive TUI form when called without arguments. + +```bash +explorbot add-rule researcher check-tooltips +explorbot add-rule tester wait-for-toasts --url '/admin/*' +explorbot add-rule # interactive mode +``` ## Exploration Commands @@ -113,12 +191,17 @@ Start full exploration cycle: research → plan → test. ``` /explore /explore /dashboard +/explore --max-tests 5 ``` If a URL is provided, navigates there first. After completion, use `/navigate` or `/explore` again to continue. **CLI equivalent:** `explorbot explore [path]` — runs the full cycle and exits. +| Option | Description | +| --------------------- | ---------------------------------------- | +| `--max-tests ` | Stop after the specified number of tests | + ### `/research [url] [--data]` Analyze the current page using the Researcher agent. @@ -127,13 +210,20 @@ Analyze the current page using the Researcher agent. /research /research /settings /research --data +/research --no-fix # skip locator validation/fix cycle ``` - If URL provided, navigates there first - `--data` flag extracts structured data from the page +- `--no-fix` skips the locator validation and fix cycle **CLI equivalent:** `explorbot research ` — researches the page and exits. +```bash +explorbot research /dashboard --no-fix # skip locator fix +explorbot research /dashboard --incognito # without experience files +``` + ### `/plan [feature]` Generate test scenarios for the current page using the Planner agent. @@ -142,12 +232,38 @@ Generate test scenarios for the current page using the Planner agent. /plan /plan login /plan checkout flow +/plan --style curious # use a specific planning style +/plan --clear # clear current plan and create new one +/plan --fresh # re-plan from scratch, discarding existing plan ``` Optional feature focus narrows the scope of generated tests. **CLI equivalent:** `explorbot plan [feature]` — generates a plan and exits. +Options: + +| Option | Description | +| ----------------- | ------------------------------------------------------------- | +| `--style