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
package/dist/src/ai/tester.js
CHANGED
|
@@ -8,13 +8,13 @@ import { clearActivity, setActivity } from "../activity.js";
|
|
|
8
8
|
import { Observability } from "../observability.js";
|
|
9
9
|
import { Stats } from "../stats.js";
|
|
10
10
|
import { TestResult } from "../test-plan.js";
|
|
11
|
-
import {
|
|
11
|
+
import { compactAriaSnapshot, detectFocusArea } from "../utils/aria.js";
|
|
12
12
|
import { ErrorPageError, isErrorPage } from "../utils/error-page.js";
|
|
13
13
|
import { createDebug, tag } from "../utils/logger.js";
|
|
14
14
|
import { loop } from "../utils/loop.js";
|
|
15
15
|
import { actionRule, capabilityGroundingRule, dataProtectionRules, focusedElementRule, formRequirementsRule, locatorRule, multipleTabsRule, sectionContextRule } from "./rules.js";
|
|
16
16
|
import { TaskAgent } from "./task-agent.js";
|
|
17
|
-
import { createCodeceptJSTools, createIframeTools } from "./tools.js";
|
|
17
|
+
import { createCodeceptJSTools, createIframeTools, withdrawVisionTools } from "./tools.js";
|
|
18
18
|
const debugLog = createDebug('explorbot:tester');
|
|
19
19
|
const SAMPLE_FILES_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '../../assets/sample-files');
|
|
20
20
|
const SAMPLE_FILES = {
|
|
@@ -48,7 +48,6 @@ export class Tester extends TaskAgent {
|
|
|
48
48
|
seenUiMapUrls = new Set();
|
|
49
49
|
lastAnalyzedStateHash = null;
|
|
50
50
|
stalledIterations = 0;
|
|
51
|
-
hasSuccessfulAssertion = false;
|
|
52
51
|
MAX_STALLED_ITERATIONS = 3;
|
|
53
52
|
constructor(deps, researcher, navigator, agentTools) {
|
|
54
53
|
super(deps);
|
|
@@ -75,7 +74,7 @@ export class Tester extends TaskAgent {
|
|
|
75
74
|
getConversation() {
|
|
76
75
|
return this.currentConversation;
|
|
77
76
|
}
|
|
78
|
-
async test(task) {
|
|
77
|
+
async test(task, opts = {}) {
|
|
79
78
|
Stats.tests++;
|
|
80
79
|
const state = this.stateManager.getCurrentState();
|
|
81
80
|
if (!state)
|
|
@@ -88,14 +87,13 @@ export class Tester extends TaskAgent {
|
|
|
88
87
|
this.seenUiMapUrls.clear();
|
|
89
88
|
this.lastAnalyzedStateHash = null;
|
|
90
89
|
this.stalledIterations = 0;
|
|
91
|
-
this.hasSuccessfulAssertion = false;
|
|
92
90
|
this.stateManager.clearHistory();
|
|
93
91
|
this.resetFailureCount();
|
|
94
92
|
this.pilot?.reset();
|
|
95
93
|
const requestStore = this.requestStore;
|
|
96
94
|
requestStore.clear();
|
|
97
95
|
const offFailedRequest = requestStore.onFailedRequest((r) => {
|
|
98
|
-
task.
|
|
96
|
+
task.addObservation(`Network error: ${r.method} ${r.path} → ${r.status}`);
|
|
99
97
|
});
|
|
100
98
|
const initialState = ActionResult.fromState(state);
|
|
101
99
|
if (isErrorPage(initialState)) {
|
|
@@ -122,9 +120,9 @@ export class Tester extends TaskAgent {
|
|
|
122
120
|
startUrl: task.startUrl,
|
|
123
121
|
expected: task.expected,
|
|
124
122
|
},
|
|
125
|
-
}, async () => this.runTestSession(task, initialState, conversation, { offFailedRequest }));
|
|
123
|
+
}, async () => this.runTestSession(task, initialState, conversation, { offFailedRequest }, opts));
|
|
126
124
|
}
|
|
127
|
-
async runTestSession(task, initialState, conversation, handlers) {
|
|
125
|
+
async runTestSession(task, initialState, conversation, handlers, opts) {
|
|
128
126
|
const { offFailedRequest } = handlers;
|
|
129
127
|
if (this.pilot) {
|
|
130
128
|
try {
|
|
@@ -153,16 +151,20 @@ export class Tester extends TaskAgent {
|
|
|
153
151
|
await this.cleanupStartedTest(task);
|
|
154
152
|
return { success: task.isSuccessful };
|
|
155
153
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
await this.
|
|
165
|
-
|
|
154
|
+
if (opts.startOnCurrentPage)
|
|
155
|
+
debugLog(`Starting on the page already open at ${task.startUrl}`);
|
|
156
|
+
if (!opts.startOnCurrentPage) {
|
|
157
|
+
debugLog(`Navigating to ${task.startUrl}`);
|
|
158
|
+
try {
|
|
159
|
+
await this.explorer.visit(task.startUrl);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
const result = await this.handleLoopError(task, error);
|
|
163
|
+
if (result === 'stop') {
|
|
164
|
+
offFailedRequest?.();
|
|
165
|
+
await this.cleanupStartedTest(task);
|
|
166
|
+
return { success: task.isSuccessful };
|
|
167
|
+
}
|
|
166
168
|
}
|
|
167
169
|
}
|
|
168
170
|
const startState = this.stateManager.getCurrentState();
|
|
@@ -208,6 +210,7 @@ export class Tester extends TaskAgent {
|
|
|
208
210
|
if (currentState.isInsideIframe) {
|
|
209
211
|
Object.assign(tools, createIframeTools(this.toolDeps));
|
|
210
212
|
}
|
|
213
|
+
withdrawVisionTools(tools);
|
|
211
214
|
debugLog(`Test ${task.scenario} iteration ${iteration}`);
|
|
212
215
|
if (this.stateManager.isInDeadLoop()) {
|
|
213
216
|
task.addNote('Dead loop detected. Stopped');
|
|
@@ -268,15 +271,8 @@ export class Tester extends TaskAgent {
|
|
|
268
271
|
const allToolNames = result?.toolExecutions?.map((execution) => execution.toolName) || [];
|
|
269
272
|
const successfulToolNames = result?.toolExecutions?.filter((execution) => execution.wasSuccessful)?.map((execution) => execution.toolName) || [];
|
|
270
273
|
const actionPerformed = !!allToolNames.find((toolName) => this.ACTION_TOOLS.includes(toolName));
|
|
271
|
-
const successfulActionPerformed = !!successfulToolNames.find((toolName) => this.ACTION_TOOLS.includes(toolName));
|
|
272
274
|
assertionPerformed = !!successfulToolNames.find((toolName) => this.ASSERTION_TOOLS.includes(toolName));
|
|
273
275
|
const wasSuccessful = result?.toolExecutions?.every((execution) => execution.wasSuccessful);
|
|
274
|
-
if (successfulActionPerformed) {
|
|
275
|
-
this.hasSuccessfulAssertion = false;
|
|
276
|
-
}
|
|
277
|
-
if (assertionPerformed) {
|
|
278
|
-
this.hasSuccessfulAssertion = true;
|
|
279
|
-
}
|
|
280
276
|
this.trackToolExecutions(result?.toolExecutions || []);
|
|
281
277
|
if (this.consecutiveEmptyResults >= 5) {
|
|
282
278
|
task.addNote('AI model is not responding with actions. Stopped');
|
|
@@ -360,6 +356,7 @@ export class Tester extends TaskAgent {
|
|
|
360
356
|
if (extensions >= this.MAX_EXTENSIONS)
|
|
361
357
|
break;
|
|
362
358
|
extensions++;
|
|
359
|
+
this.stalledIterations = 0;
|
|
363
360
|
tag('info').log(`Pilot extending test (${extensions}/${this.MAX_EXTENSIONS})`);
|
|
364
361
|
conversation.cleanupTag('page_aria', '...trimmed...', 1);
|
|
365
362
|
conversation.cleanupTag('page_html', '...trimmed...', 0);
|
|
@@ -414,12 +411,7 @@ export class Tester extends TaskAgent {
|
|
|
414
411
|
this.stalledIterations++;
|
|
415
412
|
if (this.stalledIterations < this.MAX_STALLED_ITERATIONS)
|
|
416
413
|
return false;
|
|
417
|
-
|
|
418
|
-
task.addNote('No further browser progress after successful verification; requesting final review');
|
|
419
|
-
return true;
|
|
420
|
-
}
|
|
421
|
-
task.addNote('No browser progress after repeated attempts on unchanged page', TestResult.FAILED);
|
|
422
|
-
task.finish(TestResult.FAILED);
|
|
414
|
+
task.addNote('No further browser progress on unchanged page; requesting final review');
|
|
423
415
|
return true;
|
|
424
416
|
}
|
|
425
417
|
async prepareInstructionsForNextStep(task) {
|
|
@@ -437,7 +429,8 @@ export class Tester extends TaskAgent {
|
|
|
437
429
|
</rules>
|
|
438
430
|
`;
|
|
439
431
|
if (task.getPrintableNotes()) {
|
|
440
|
-
outcomeStatus
|
|
432
|
+
outcomeStatus += dedent `
|
|
433
|
+
|
|
441
434
|
Your current log:
|
|
442
435
|
<notes>
|
|
443
436
|
${task.notesToString()}
|
|
@@ -458,7 +451,7 @@ export class Tester extends TaskAgent {
|
|
|
458
451
|
this.previousStateHash = currentStateHash;
|
|
459
452
|
let context = '';
|
|
460
453
|
const focusArea = detectFocusArea(currentState.ariaSnapshot);
|
|
461
|
-
const focusedElement =
|
|
454
|
+
const focusedElement = currentState.focusedElement;
|
|
462
455
|
if (focusedElement) {
|
|
463
456
|
const isTextInput = ['textbox', 'combobox', 'searchbox'].includes(focusedElement.role);
|
|
464
457
|
context += dedent `
|
|
@@ -539,7 +532,7 @@ export class Tester extends TaskAgent {
|
|
|
539
532
|
</page>
|
|
540
533
|
|
|
541
534
|
<page_aria>
|
|
542
|
-
${
|
|
535
|
+
${await this.interactiveAriaWithRefs(currentState)}
|
|
543
536
|
</page_aria>
|
|
544
537
|
${uiMapSection}
|
|
545
538
|
|
|
@@ -574,26 +567,32 @@ export class Tester extends TaskAgent {
|
|
|
574
567
|
</page>
|
|
575
568
|
|
|
576
569
|
<page_aria>
|
|
577
|
-
${
|
|
570
|
+
${await this.interactiveAriaWithRefs(currentState)}
|
|
578
571
|
</page_aria>
|
|
579
572
|
`;
|
|
580
573
|
}
|
|
574
|
+
async interactiveAriaWithRefs(state) {
|
|
575
|
+
const withRefs = await Promise.resolve(this.explorer?.withPage?.((page) => page.locator('body').ariaSnapshot({ mode: 'ai' }))).catch(() => null);
|
|
576
|
+
if (!withRefs)
|
|
577
|
+
return state.getInteractiveARIA();
|
|
578
|
+
return compactAriaSnapshot(withRefs, false);
|
|
579
|
+
}
|
|
581
580
|
finishTest(task) {
|
|
582
|
-
if (!task.
|
|
583
|
-
task.
|
|
581
|
+
if (!task.result) {
|
|
582
|
+
if (task.hasAchievedAll())
|
|
583
|
+
task.finish(TestResult.PASSED);
|
|
584
|
+
else
|
|
585
|
+
task.finish(TestResult.FAILED);
|
|
584
586
|
}
|
|
585
587
|
if (task.isSuccessful) {
|
|
586
588
|
tag('success').log(`Successful test: ${task.scenario}`);
|
|
589
|
+
return;
|
|
587
590
|
}
|
|
588
|
-
|
|
591
|
+
if (task.isSkipped) {
|
|
589
592
|
tag('warning').log(`Skipped test: ${task.scenario}`);
|
|
593
|
+
return;
|
|
590
594
|
}
|
|
591
|
-
|
|
592
|
-
tag('error').log(`Failed test: ${task.scenario}`);
|
|
593
|
-
}
|
|
594
|
-
else {
|
|
595
|
-
tag('warning').log(`Test with no result: ${task.scenario}`);
|
|
596
|
-
}
|
|
595
|
+
tag('error').log(`Failed test: ${task.scenario}`);
|
|
597
596
|
}
|
|
598
597
|
async abortStartedTestOnErrorPage(task, actionResult) {
|
|
599
598
|
const error = new ErrorPageError(actionResult.url || task.startUrl || '', actionResult.title, actionResult.httpStatus);
|
|
@@ -734,6 +733,9 @@ export class Tester extends TaskAgent {
|
|
|
734
733
|
${task.expected.map((e) => `- ${e}`).join('\n')}
|
|
735
734
|
</expected_results>
|
|
736
735
|
|
|
736
|
+
An expected result counts as settled only when you record it back word for word as it is written above.
|
|
737
|
+
A note in your own wording is a general note and leaves that result unsettled.
|
|
738
|
+
|
|
737
739
|
Your goal is to perform actions on the web page and verify the expected outcomes.
|
|
738
740
|
Try to achieve as many goals as possible.
|
|
739
741
|
If goal is not achievable, log that and skip to next one.
|
|
@@ -968,6 +970,9 @@ export class Tester extends TaskAgent {
|
|
|
968
970
|
- You unsuccessfully tried multiple iterations and failed
|
|
969
971
|
- If the expected result was expected to fail, use status="success" instead
|
|
970
972
|
|
|
973
|
+
When a note settles one of the expected results, that note must repeat the expected result word
|
|
974
|
+
for word. Paraphrasing it leaves the expected result unsettled and it is reported as unverified.
|
|
975
|
+
|
|
971
976
|
Example:
|
|
972
977
|
- record({ notes: ["clicked login button", "login form appeared", "fill credentials"], status: "success" })
|
|
973
978
|
`,
|
|
@@ -1045,8 +1050,7 @@ export class Tester extends TaskAgent {
|
|
|
1045
1050
|
this.stalledIterations++;
|
|
1046
1051
|
if (this.stalledIterations < this.MAX_STALLED_ITERATIONS)
|
|
1047
1052
|
return false;
|
|
1048
|
-
task.addNote('No browser progress after repeated execution errors'
|
|
1049
|
-
task.finish(TestResult.FAILED);
|
|
1053
|
+
task.addNote('No browser progress after repeated execution errors; requesting final review');
|
|
1050
1054
|
return true;
|
|
1051
1055
|
}
|
|
1052
1056
|
async cleanupStartedTest(task) {
|
package/dist/src/ai/tools.d.ts
CHANGED
|
@@ -16,6 +16,10 @@ export declare function createCodeceptJSTools({ explorer, stateManager, ai }: To
|
|
|
16
16
|
commands: any;
|
|
17
17
|
explanation: any;
|
|
18
18
|
}, Record<string, any>, import("@ai-sdk/provider-utils").Context>>;
|
|
19
|
+
clickRef: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<{
|
|
20
|
+
ref: any;
|
|
21
|
+
element: any;
|
|
22
|
+
}, Record<string, any>, import("@ai-sdk/provider-utils").Context>>;
|
|
19
23
|
hover: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<{
|
|
20
24
|
commands: any;
|
|
21
25
|
explanation: any;
|
|
@@ -49,5 +53,15 @@ export declare function createLearnExperienceTool({ getExperienceTracker, getSta
|
|
|
49
53
|
error: string;
|
|
50
54
|
}, import("@ai-sdk/provider-utils").Context>>;
|
|
51
55
|
export declare function createAgentTools({ explorer, stateManager, ai, researcher, navigator, supervisor, withExperience }: AgentToolDeps): any;
|
|
56
|
+
export declare function commitNote(activeNote: any, result: TestResult, toolResult: any, action: any): Promise<void>;
|
|
57
|
+
export declare function successToolResult(action: string, data?: Record<string, any>, source?: {
|
|
58
|
+
playwrightGroupId?: string | null;
|
|
59
|
+
assertionSteps?: any[];
|
|
60
|
+
}): Record<string, any>;
|
|
52
61
|
export declare function isMajorPageChange(pageDiff: PageDiff): boolean;
|
|
62
|
+
export declare function failedToolResult(action: string, message: string, data?: Record<string, any>, error?: Error | null): Promise<Record<string, any>>;
|
|
63
|
+
export declare function withdrawVisionTools(tools: Record<string, any>): void;
|
|
64
|
+
export declare function clickFailureSuggestion(attempts: Array<{
|
|
65
|
+
error?: string;
|
|
66
|
+
}>): string;
|
|
53
67
|
export {};
|
package/dist/src/ai/tools.js
CHANGED
|
@@ -2,8 +2,9 @@ import { tool } from 'ai';
|
|
|
2
2
|
import dedent from 'dedent';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { ActionResult } from "../action-result.js";
|
|
5
|
+
import { Stats } from "../stats.js";
|
|
5
6
|
import { TestResult } from '../test-plan.js';
|
|
6
|
-
import { LARGE_ARIA_CHANGE_THRESHOLD
|
|
7
|
+
import { LARGE_ARIA_CHANGE_THRESHOLD } from "../utils/aria.js";
|
|
7
8
|
import { isFatalBrowserError } from "../utils/browser-errors.js";
|
|
8
9
|
import { createDebug, tag } from '../utils/logger.js';
|
|
9
10
|
import { pause } from '../utils/loop.js';
|
|
@@ -18,6 +19,10 @@ export function createCodeceptJSTools({ explorer, stateManager, ai }, task) {
|
|
|
18
19
|
description: dedent `
|
|
19
20
|
Click an element by trying multiple CodeceptJS commands in order until one succeeds.
|
|
20
21
|
|
|
22
|
+
Use this only for elements the page context gives you no ref for. When the element shows a ref such as [ref=e14],
|
|
23
|
+
call clickRef with that ref instead — composing a locator for an element that already has a ref is wasted work,
|
|
24
|
+
and a locator can match several elements where a ref cannot.
|
|
25
|
+
|
|
21
26
|
Follow <locator_priority> from system prompt for locator selection.
|
|
22
27
|
|
|
23
28
|
I.click(locator) - click element matching locator
|
|
@@ -36,14 +41,13 @@ export function createCodeceptJSTools({ explorer, stateManager, ai }, task) {
|
|
|
36
41
|
commands: z.array(z.string()).describe(dedent `
|
|
37
42
|
FALLBACK LOCATORS for ONE element. All commands must click the SAME element.
|
|
38
43
|
Never mix different elements — use separate click() calls instead.
|
|
44
|
+
REQUIRED: include at least one command WITHOUT a container — a wrong or stale container always fails.
|
|
39
45
|
Order by reliability:
|
|
40
|
-
1. I.click(text, container) -
|
|
46
|
+
1. I.click(text, container) - when the container is verified - e.g. I.click("Save", ".modal")
|
|
41
47
|
2. I.click(ARIA, container) - e.g. I.click({"role":"button","text":"Save"}, ".modal")
|
|
42
48
|
3. I.click(CSS, container) - e.g. I.click("#btn", ".modal")
|
|
43
49
|
4. I.click(CSS) or I.click(XPath) - when locator already includes context (ID, XPath)
|
|
44
50
|
5. I.clickXY(x, y) - coordinates fallback
|
|
45
|
-
IMPORTANT: Always include at least one command WITHOUT a container as fallback,
|
|
46
|
-
in case the element moved to a different section (e.g. I.click("Save") without container).
|
|
47
51
|
`),
|
|
48
52
|
explanation: z.string().describe('Why you are clicking this element'),
|
|
49
53
|
}),
|
|
@@ -106,14 +110,7 @@ export function createCodeceptJSTools({ explorer, stateManager, ai }, task) {
|
|
|
106
110
|
}
|
|
107
111
|
const toolResult = await ActionResult.fromState(stateManager.getCurrentState()).toToolResult(previousState, commands[0]);
|
|
108
112
|
await commitNote(activeNote, TestResult.FAILED, toolResult, action);
|
|
109
|
-
|
|
110
|
-
const lastError = attempts[attempts.length - 1]?.error || '';
|
|
111
|
-
if (lastError.includes('was not found') || lastError.includes('not found by text')) {
|
|
112
|
-
suggestion = 'Element was not found in the DOM. Use xpathCheck() to locate it, context() to refresh snapshot, or visualClick() to click by visual appearance.';
|
|
113
|
-
}
|
|
114
|
-
else if (lastError.includes('Timeout') || lastError.includes('intercept')) {
|
|
115
|
-
suggestion = 'Element exists but could not be clicked (possibly covered by overlay or not interactable). Try closing overlapping panels first, or use visualClick().';
|
|
116
|
-
}
|
|
113
|
+
const suggestion = clickFailureSuggestion(attempts);
|
|
117
114
|
return failedToolResult('click', 'All click commands failed', {
|
|
118
115
|
...toolResult,
|
|
119
116
|
attempts,
|
|
@@ -121,6 +118,38 @@ export function createCodeceptJSTools({ explorer, stateManager, ai }, task) {
|
|
|
121
118
|
}, action.lastError);
|
|
122
119
|
},
|
|
123
120
|
}),
|
|
121
|
+
clickRef: tool({
|
|
122
|
+
description: dedent `
|
|
123
|
+
Click an element by the ref the page context gave it, e.g. [ref=e14].
|
|
124
|
+
|
|
125
|
+
Prefer this over click() whenever the element you want carries a ref. A ref names one exact element, so it
|
|
126
|
+
cannot match several by mistake and never needs disambiguating — it is the fastest way to click.
|
|
127
|
+
Only pass a ref that appears in the page context you were given. Never invent or guess one.
|
|
128
|
+
If it reports the ref is gone, the page has been rebuilt: get fresh context and use the new ref.
|
|
129
|
+
`,
|
|
130
|
+
inputSchema: z.object({
|
|
131
|
+
ref: z.string().describe('The ref exactly as it appears in the page context, e.g. "e14"'),
|
|
132
|
+
element: z.string().describe('Role and name of the element you are clicking, for the record'),
|
|
133
|
+
}),
|
|
134
|
+
execute: async ({ ref, element }) => {
|
|
135
|
+
const activeNote = task.startNote(`Click ${element}`);
|
|
136
|
+
const previousState = ActionResult.fromState(stateManager.getCurrentState());
|
|
137
|
+
const action = explorer.action();
|
|
138
|
+
const named = await describeRef(explorer, ref);
|
|
139
|
+
const run = `I.usePlaywrightTo(${JSON.stringify(`click ${element}`)}, async ({ page }) => page.locator(${JSON.stringify(`aria-ref=${ref}`)}).click())`;
|
|
140
|
+
if (!(await action.attempt(run, `Click ${element}`))) {
|
|
141
|
+
activeNote.commit(TestResult.FAILED);
|
|
142
|
+
return failedToolResult('clickRef', `Ref ${ref} could not be clicked: ${errorText(action.lastError)}`, {
|
|
143
|
+
suggestion: 'The ref may belong to an older version of the page. Get fresh context and use the ref it gives, or fall back to click() with a locator.',
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
// a ref belongs to this session only, so the run is reported as the locator a later test can replay
|
|
147
|
+
const code = named ? `I.click(${JSON.stringify(named)})` : run;
|
|
148
|
+
const toolResult = await ActionResult.fromState(stateManager.getCurrentState()).toToolResult(previousState, code);
|
|
149
|
+
await commitNote(activeNote, TestResult.PASSED, toolResult, action);
|
|
150
|
+
return successToolResult('clickRef', { ...toolResult, code }, action);
|
|
151
|
+
},
|
|
152
|
+
}),
|
|
124
153
|
hover: tool({
|
|
125
154
|
description: dedent `
|
|
126
155
|
Move the mouse cursor to an element to reveal hover-only controls.
|
|
@@ -245,15 +274,11 @@ export function createCodeceptJSTools({ explorer, stateManager, ai }, task) {
|
|
|
245
274
|
}
|
|
246
275
|
const focusFreeKeys = new Set(['Escape', 'Esc', 'Tab', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12']);
|
|
247
276
|
const needsFocus = !focusFreeKeys.has(keyToUse) && !modifier;
|
|
248
|
-
if (needsFocus) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
return failedToolResult('pressKey', `No element is focused. Key '${keyToUse}' requires a focused element.`, {
|
|
254
|
-
suggestion: 'Click the target element first, then press the key.',
|
|
255
|
-
});
|
|
256
|
-
}
|
|
277
|
+
if (needsFocus && !(await hasFocusedElement(explorer))) {
|
|
278
|
+
activeNote.commit(TestResult.FAILED);
|
|
279
|
+
return failedToolResult('pressKey', `No element is focused. Key '${keyToUse}' requires a focused element.`, {
|
|
280
|
+
suggestion: 'Click the target element first, then press the key.',
|
|
281
|
+
});
|
|
257
282
|
}
|
|
258
283
|
const previousState = ActionResult.fromState(stateManager.getCurrentState());
|
|
259
284
|
const action = explorer.action();
|
|
@@ -296,6 +321,8 @@ export function createCodeceptJSTools({ explorer, stateManager, ai }, task) {
|
|
|
296
321
|
Execute raw CodeceptJS code block with multiple commands.
|
|
297
322
|
USE THIS TOOL for typing text into fields: I.fillField, I.type
|
|
298
323
|
|
|
324
|
+
Do not put a click on a ref-bearing element in here — clickRef with its ref is cheaper and cannot mis-target.
|
|
325
|
+
|
|
299
326
|
Follow <actions> from system prompt for available commands.
|
|
300
327
|
Follow <locator_priority> from system prompt for locator selection.
|
|
301
328
|
|
|
@@ -456,25 +483,25 @@ export function createLearnExperienceTool({ getExperienceTracker, getState }) {
|
|
|
456
483
|
});
|
|
457
484
|
}
|
|
458
485
|
export function createAgentTools({ explorer, stateManager, ai, researcher, navigator, supervisor, withExperience }) {
|
|
459
|
-
let visionDisabled = false;
|
|
460
486
|
const tools = {
|
|
461
487
|
see: tool({
|
|
462
488
|
description: dedent `
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
489
|
+
Answer a question about the page from a screenshot, for things its structure cannot express:
|
|
490
|
+
layout and position, what an image or canvas depicts, colour, and whether something is covered or cut off.
|
|
491
|
+
This runs a second model and is the slowest tool here, so reach for it only when the question is genuinely visual.
|
|
492
|
+
Do NOT use it to confirm an action landed — every action already reports what changed on the page.
|
|
466
493
|
Input schema has exactly one field: request. Do not pass text, reason, assertion, or other fields.
|
|
467
494
|
|
|
468
495
|
<example>
|
|
469
|
-
request: "
|
|
470
|
-
result: "
|
|
496
|
+
request: "Is the save button covered by anything, and does the chart show any plotted data?"
|
|
497
|
+
result: "The save button is partly behind a cookie banner at the bottom. The chart area is empty apart from its axes."
|
|
471
498
|
</example>
|
|
472
499
|
`,
|
|
473
500
|
inputSchema: z.object({
|
|
474
501
|
request: z.string().describe('LLM-friendly description of the page contents to look for. 1-3 sentences. No more than 100 words.'),
|
|
475
502
|
}),
|
|
476
503
|
execute: async ({ request }) => {
|
|
477
|
-
if (visionDisabled) {
|
|
504
|
+
if (Stats.visionDisabled) {
|
|
478
505
|
return failedToolResult('see', 'Vision tools are disabled for this session. Use context() to get fresh ARIA snapshot and analyze page state from ARIA data.');
|
|
479
506
|
}
|
|
480
507
|
try {
|
|
@@ -495,8 +522,7 @@ export function createAgentTools({ explorer, stateManager, ai, researcher, navig
|
|
|
495
522
|
catch (error) {
|
|
496
523
|
throwIfFatalBrowserError(error);
|
|
497
524
|
const errorMessage = errorText(error);
|
|
498
|
-
|
|
499
|
-
tag('warning').log('⚠️ Vision model is not available. Visual checks are disabled for this session.');
|
|
525
|
+
disableVision();
|
|
500
526
|
return failedToolResult('see', `See tool failed: ${errorMessage}`, {
|
|
501
527
|
suggestion: 'Vision is now disabled. Use context() to get fresh ARIA snapshot and analyze page state from ARIA data.',
|
|
502
528
|
});
|
|
@@ -576,6 +602,12 @@ export function createAgentTools({ explorer, stateManager, ai, researcher, navig
|
|
|
576
602
|
code: result.successfulCodes.join('\n'),
|
|
577
603
|
}, { assertionSteps: result.assertionSteps });
|
|
578
604
|
}
|
|
605
|
+
if (result.inexpressible) {
|
|
606
|
+
return failedToolResult('verify', `No assertion could express this claim: ${assertion}`, {
|
|
607
|
+
inexpressible: true,
|
|
608
|
+
suggestion: 'This is not evidence the page is wrong — the claim could not be turned into an assertion. Restate it in terms of what is visible or of a control state, or check it with see().',
|
|
609
|
+
});
|
|
610
|
+
}
|
|
579
611
|
return failedToolResult('verify', `Verification failed: ${assertion}`, {
|
|
580
612
|
suggestion: 'The assertion could not be verified. Check if the condition is actually present on the page or try a different assertion.',
|
|
581
613
|
});
|
|
@@ -666,7 +698,10 @@ export function createAgentTools({ explorer, stateManager, ai, researcher, navig
|
|
|
666
698
|
message: `Successfully executed: ${instruction}`,
|
|
667
699
|
});
|
|
668
700
|
}
|
|
669
|
-
|
|
701
|
+
let reason = '';
|
|
702
|
+
if (navigator.lastFailureReason)
|
|
703
|
+
reason = `: ${navigator.lastFailureReason}`;
|
|
704
|
+
return failedToolResult('interact', `Failed to execute: ${instruction}${reason}`, {
|
|
670
705
|
...toolResult,
|
|
671
706
|
suggestion: 'The action could not be completed. Try a different instruction or use more specific element descriptions.',
|
|
672
707
|
});
|
|
@@ -696,7 +731,7 @@ export function createAgentTools({ explorer, stateManager, ai, researcher, navig
|
|
|
696
731
|
context: z.string().describe('What you already tried and why it failed - helps with accurate identification'),
|
|
697
732
|
}),
|
|
698
733
|
execute: async ({ element, context }) => {
|
|
699
|
-
if (visionDisabled) {
|
|
734
|
+
if (Stats.visionDisabled) {
|
|
700
735
|
return failedToolResult('visualClick', 'Vision tools are disabled for this session. Use xpathCheck() to find the element, then click() with the discovered locator.');
|
|
701
736
|
}
|
|
702
737
|
try {
|
|
@@ -741,8 +776,7 @@ export function createAgentTools({ explorer, stateManager, ai, researcher, navig
|
|
|
741
776
|
catch (error) {
|
|
742
777
|
throwIfFatalBrowserError(error);
|
|
743
778
|
const errorMessage = errorText(error);
|
|
744
|
-
|
|
745
|
-
tag('warning').log('⚠️ Vision model is not available. Visual clicks are disabled for this session.');
|
|
779
|
+
disableVision();
|
|
746
780
|
return failedToolResult('visualClick', `visualClick tool failed: ${errorMessage}`, {
|
|
747
781
|
suggestion: 'Vision is now disabled. Use xpathCheck() to find the element, then click() with the discovered locator.',
|
|
748
782
|
});
|
|
@@ -875,6 +909,11 @@ export function createAgentTools({ explorer, stateManager, ai, researcher, navig
|
|
|
875
909
|
},
|
|
876
910
|
}),
|
|
877
911
|
};
|
|
912
|
+
const disableVision = () => {
|
|
913
|
+
Stats.visionDisabled = true;
|
|
914
|
+
withdrawVisionTools(tools);
|
|
915
|
+
tag('warning').log('⚠️ Vision model is not available. Visual tools are disabled for this session.');
|
|
916
|
+
};
|
|
878
917
|
if (withExperience !== false) {
|
|
879
918
|
tools.learnExperience = createLearnExperienceTool({
|
|
880
919
|
getExperienceTracker: () => stateManager.getExperienceTracker(),
|
|
@@ -921,6 +960,7 @@ export function createAgentTools({ explorer, stateManager, ai, researcher, navig
|
|
|
921
960
|
},
|
|
922
961
|
});
|
|
923
962
|
}
|
|
963
|
+
withdrawVisionTools(tools);
|
|
924
964
|
return tools;
|
|
925
965
|
}
|
|
926
966
|
const PAGE_DIFF_SUGGESTION = 'Analyze page diff. htmlParts shows what changed and WHERE — each part has a container selector. Use the container as context when clicking elements from the diff.';
|
|
@@ -964,13 +1004,27 @@ function errorText(error) {
|
|
|
964
1004
|
return error.toString();
|
|
965
1005
|
return 'Unknown error occurred';
|
|
966
1006
|
}
|
|
967
|
-
async function commitNote(activeNote, result, toolResult, action) {
|
|
1007
|
+
export async function commitNote(activeNote, result, toolResult, action) {
|
|
968
1008
|
if (toolResult?.pageDiff?.ariaChanges || toolResult?.pageDiff?.urlChanged) {
|
|
969
1009
|
activeNote.screenshot = await action.saveScreenshot();
|
|
970
1010
|
}
|
|
971
1011
|
activeNote.commit(result);
|
|
972
1012
|
}
|
|
973
|
-
function
|
|
1013
|
+
async function describeRef(explorer, ref) {
|
|
1014
|
+
return Promise.resolve(explorer?.withPage?.((page) => page.locator(`aria-ref=${ref}`).evaluate((el) => {
|
|
1015
|
+
const tag = el.tagName.toLowerCase();
|
|
1016
|
+
const roles = { a: 'link', button: 'button', select: 'combobox', textarea: 'textbox' };
|
|
1017
|
+
const role = el.getAttribute('role') || roles[tag] || tag;
|
|
1018
|
+
const text = (el.getAttribute('aria-label') || el.innerText || el.value || '').trim().split('\n')[0];
|
|
1019
|
+
if (!text)
|
|
1020
|
+
return null;
|
|
1021
|
+
return { role, text };
|
|
1022
|
+
}))).catch(() => null);
|
|
1023
|
+
}
|
|
1024
|
+
async function hasFocusedElement(explorer) {
|
|
1025
|
+
return explorer.withPage((page) => page.evaluate(() => !!document.activeElement && document.activeElement !== document.body)).catch(() => true);
|
|
1026
|
+
}
|
|
1027
|
+
export function successToolResult(action, data, source) {
|
|
974
1028
|
const result = { success: true, action, ...data };
|
|
975
1029
|
if (source?.playwrightGroupId) {
|
|
976
1030
|
result.playwrightGroupId = source.playwrightGroupId;
|
|
@@ -1008,7 +1062,7 @@ function hasObservablePageChange(data) {
|
|
|
1008
1062
|
return true;
|
|
1009
1063
|
return Array.isArray(data.pageDiff.htmlParts) && data.pageDiff.htmlParts.length > 0;
|
|
1010
1064
|
}
|
|
1011
|
-
async function failedToolResult(action, message, data, error) {
|
|
1065
|
+
export async function failedToolResult(action, message, data, error) {
|
|
1012
1066
|
const result = { success: false, action, message, ...data };
|
|
1013
1067
|
if (data?.pageDiff) {
|
|
1014
1068
|
result.suggestion = data.suggestion ? `${data.suggestion} ${PAGE_DIFF_SUGGESTION}` : PAGE_DIFF_SUGGESTION;
|
|
@@ -1040,6 +1094,32 @@ function getMultipleElementsSuggestion() {
|
|
|
1040
1094
|
6. Use visualClick() to click the right element by visual appearance
|
|
1041
1095
|
`;
|
|
1042
1096
|
}
|
|
1097
|
+
export function withdrawVisionTools(tools) {
|
|
1098
|
+
if (!Stats.visionDisabled)
|
|
1099
|
+
return;
|
|
1100
|
+
Reflect.deleteProperty(tools, 'see');
|
|
1101
|
+
Reflect.deleteProperty(tools, 'visualClick');
|
|
1102
|
+
}
|
|
1103
|
+
export function clickFailureSuggestion(attempts) {
|
|
1104
|
+
const errors = attempts.map((a) => a.error || '');
|
|
1105
|
+
if (errors.some((e) => e.includes('not enabled'))) {
|
|
1106
|
+
return 'Element exists but is DISABLED — clicking it again cannot work. A precondition is unmet: a required field is empty, nothing is selected, or a dialog is blocking. Satisfy it, then retry.';
|
|
1107
|
+
}
|
|
1108
|
+
if (errors.some((e) => e.includes('intercepts pointer events'))) {
|
|
1109
|
+
return 'Element exists but another element covers it. Close the overlapping panel or dialog, then retry.';
|
|
1110
|
+
}
|
|
1111
|
+
if (errors.some((e) => e.includes('is not visible'))) {
|
|
1112
|
+
return 'Element is in the DOM but not visible. Reveal it first — scroll to it, expand its section, or open the panel holding it.';
|
|
1113
|
+
}
|
|
1114
|
+
const notFound = errors.filter((e) => e.includes('was not found'));
|
|
1115
|
+
if (notFound.length && notFound.every((e) => e.includes('was not found inside element'))) {
|
|
1116
|
+
return 'Element was not found inside that container — the container is wrong or stale, and the element may exist elsewhere on the page. Retry the same locator WITHOUT a container, or verify the container with xpathCheck().';
|
|
1117
|
+
}
|
|
1118
|
+
if (notFound.length) {
|
|
1119
|
+
return 'Element was not found in the DOM. Use xpathCheck() to locate it, context() to refresh snapshot, or visualClick() to click by visual appearance.';
|
|
1120
|
+
}
|
|
1121
|
+
return "Try xpathCheck() to find the element's actual position, see() for visual analysis, or visualClick() to click by visual appearance.";
|
|
1122
|
+
}
|
|
1043
1123
|
const MAX_DISAMBIGUATE_ELEMENTS = 10;
|
|
1044
1124
|
const MULTIPLE_ELEMENTS_PATTERN = 'multiple elements';
|
|
1045
1125
|
async function extractWebElements(error) {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type AIConfig, type ReporterConfig } from '../config.js';
|
|
2
|
+
import { BaseCommand } from './base-command.js';
|
|
3
|
+
export declare class ConfigCommand extends BaseCommand {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
execute(): Promise<void>;
|
|
7
|
+
static summary(options?: {
|
|
8
|
+
config?: string;
|
|
9
|
+
path?: string;
|
|
10
|
+
url?: string;
|
|
11
|
+
json?: boolean;
|
|
12
|
+
}): Promise<string>;
|
|
13
|
+
static data(config: SummarizedConfig, options?: ConfigSummaryOptions): ConfigData;
|
|
14
|
+
static render(config: SummarizedConfig, options?: ConfigSummaryOptions): string;
|
|
15
|
+
}
|
|
16
|
+
interface ConfigSummaryOptions {
|
|
17
|
+
configPath?: string | null;
|
|
18
|
+
root?: string;
|
|
19
|
+
json?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface ConfigData {
|
|
22
|
+
config: string;
|
|
23
|
+
url: string;
|
|
24
|
+
browser: string;
|
|
25
|
+
headless: boolean;
|
|
26
|
+
dirs: Record<string, string>;
|
|
27
|
+
models: Record<string, string>;
|
|
28
|
+
providers: Record<string, string>;
|
|
29
|
+
integrations: {
|
|
30
|
+
langfuse: boolean;
|
|
31
|
+
testomatio: boolean;
|
|
32
|
+
};
|
|
33
|
+
env: Record<string, string>;
|
|
34
|
+
}
|
|
35
|
+
interface SummarizedConfig {
|
|
36
|
+
ai?: AIConfig;
|
|
37
|
+
playwright?: {
|
|
38
|
+
url?: string;
|
|
39
|
+
browser?: string;
|
|
40
|
+
show?: boolean;
|
|
41
|
+
};
|
|
42
|
+
web?: {
|
|
43
|
+
url?: string;
|
|
44
|
+
};
|
|
45
|
+
api?: {
|
|
46
|
+
baseEndpoint?: string;
|
|
47
|
+
};
|
|
48
|
+
dirs?: Record<string, string>;
|
|
49
|
+
reporter?: ReporterConfig;
|
|
50
|
+
}
|
|
51
|
+
export {};
|