explorbot 0.1.32-beta.1 → 0.2.1
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 +35 -19
- package/bin/explorbot-cli.ts +33 -10
- package/boat/api-tester/src/config.ts +45 -3
- package/boat/doc-collector/src/ai/documentarian.ts +26 -28
- package/boat/doc-collector/src/ai/tools.ts +74 -16
- package/boat/doc-collector/src/cli.ts +1 -6
- package/boat/doc-collector/src/docbot.ts +30 -9
- package/boat/doc-collector/src/docs-renderer.ts +19 -24
- package/boat/doc-collector/src/screenshots.ts +34 -2
- package/boat/doc-collector/src/state-diagram.ts +281 -0
- package/dist/bin/explorbot-cli.js +29 -9
- package/dist/boat/api-tester/src/config.js +43 -4
- package/dist/boat/doc-collector/src/ai/documentarian.js +19 -7
- package/dist/boat/doc-collector/src/ai/tools.js +53 -16
- package/dist/boat/doc-collector/src/cli.js +1 -5
- package/dist/boat/doc-collector/src/docbot.js +27 -8
- package/dist/boat/doc-collector/src/docs-renderer.js +18 -3
- package/dist/boat/doc-collector/src/screenshots.js +30 -1
- package/dist/boat/doc-collector/src/state-diagram.js +200 -0
- package/dist/models.json +30 -0
- package/dist/package.json +23 -3
- package/dist/src/action-result.d.ts +8 -10
- package/dist/src/action-result.js +72 -164
- package/dist/src/action.d.ts +13 -12
- package/dist/src/action.js +37 -113
- package/dist/src/ai/agent.d.ts +17 -0
- package/dist/src/ai/captain/idle-mode.js +1 -1
- package/dist/src/ai/captain/test-mode.js +1 -1
- package/dist/src/ai/captain/web-mode.js +18 -29
- package/dist/src/ai/captain.d.ts +1 -3
- package/dist/src/ai/captain.js +17 -45
- package/dist/src/ai/conversation.d.ts +2 -5
- package/dist/src/ai/conversation.js +9 -28
- package/dist/src/ai/driller.d.ts +2 -17
- package/dist/src/ai/driller.js +26 -50
- package/dist/src/ai/experience-compactor.d.ts +2 -2
- package/dist/src/ai/experience-compactor.js +16 -33
- package/dist/src/ai/fisherman-tools.js +1 -1
- package/dist/src/ai/fisherman.d.ts +0 -1
- package/dist/src/ai/fisherman.js +0 -3
- package/dist/src/ai/historian/codeceptjs.d.ts +2 -0
- package/dist/src/ai/historian/codeceptjs.js +4 -3
- package/dist/src/ai/historian/playwright.d.ts +4 -1
- package/dist/src/ai/historian/playwright.js +4 -3
- package/dist/src/ai/historian/screencast.d.ts +2 -1
- package/dist/src/ai/historian/screencast.js +2 -2
- package/dist/src/ai/historian/utils.d.ts +0 -1
- package/dist/src/ai/historian/utils.js +0 -1
- package/dist/src/ai/historian.d.ts +10 -8
- package/dist/src/ai/historian.js +1 -5
- package/dist/src/ai/navigator.d.ts +8 -6
- package/dist/src/ai/navigator.js +34 -65
- package/dist/src/ai/pilot.d.ts +13 -8
- package/dist/src/ai/pilot.js +53 -59
- package/dist/src/ai/planner.d.ts +5 -4
- package/dist/src/ai/planner.js +66 -52
- package/dist/src/ai/provider.d.ts +4 -1
- package/dist/src/ai/provider.js +77 -137
- package/dist/src/ai/quartermaster.d.ts +22 -22
- package/dist/src/ai/quartermaster.js +8 -14
- package/dist/src/ai/rerunner.d.ts +3 -13
- package/dist/src/ai/rerunner.js +23 -44
- package/dist/src/ai/researcher/cache.js +11 -18
- package/dist/src/ai/researcher/coordinates.js +4 -3
- package/dist/src/ai/researcher/deep-analysis.d.ts +2 -0
- package/dist/src/ai/researcher/deep-analysis.js +8 -11
- package/dist/src/ai/researcher/locators.d.ts +9 -2
- package/dist/src/ai/researcher/locators.js +65 -8
- package/dist/src/ai/researcher/parser.js +1 -1
- package/dist/src/ai/researcher/research-result.d.ts +0 -1
- package/dist/src/ai/researcher/research-result.js +0 -19
- package/dist/src/ai/researcher/sections.d.ts +2 -0
- package/dist/src/ai/researcher/sections.js +7 -3
- package/dist/src/ai/researcher.js +40 -106
- package/dist/src/ai/rules.d.ts +1 -0
- package/dist/src/ai/rules.js +22 -15
- package/dist/src/ai/session-analyst.js +8 -5
- package/dist/src/ai/task-agent.d.ts +19 -6
- package/dist/src/ai/task-agent.js +37 -33
- package/dist/src/ai/tester.d.ts +6 -14
- package/dist/src/ai/tester.js +46 -111
- package/dist/src/ai/tools.d.ts +26 -14
- package/dist/src/ai/tools.js +138 -132
- package/dist/src/command-handler.js +1 -1
- package/dist/src/commands/compact-command.d.ts +1 -1
- package/dist/src/commands/compact-command.js +6 -6
- package/dist/src/commands/context-aria-command.js +1 -1
- package/dist/src/commands/context-command.js +8 -6
- package/dist/src/commands/context-data-command.js +2 -2
- package/dist/src/commands/context-experience-command.js +2 -2
- package/dist/src/commands/context-html-command.js +2 -2
- package/dist/src/commands/context-knowledge-command.js +2 -2
- package/dist/src/commands/drill-command.js +1 -1
- package/dist/src/commands/experience-command.js +1 -1
- package/dist/src/commands/explore-command.js +4 -3
- package/dist/src/commands/freesail-command.js +2 -2
- package/dist/src/commands/knows-command.js +1 -1
- package/dist/src/commands/learn-command.js +5 -3
- package/dist/src/commands/path-command.js +1 -1
- package/dist/src/commands/research-command.js +1 -1
- package/dist/src/commands/test-command.js +1 -1
- package/dist/src/components/AddKnowledge.d.ts +2 -0
- package/dist/src/components/AddKnowledge.js +3 -7
- package/dist/src/components/App.js +3 -3
- package/dist/src/config.d.ts +19 -0
- package/dist/src/config.js +156 -10
- package/dist/src/experience-tracker.d.ts +11 -6
- package/dist/src/experience-tracker.js +82 -155
- package/dist/src/explorbot.d.ts +21 -13
- package/dist/src/explorbot.js +84 -80
- package/dist/src/explorer.d.ts +66 -102
- package/dist/src/explorer.js +332 -623
- package/dist/src/knowledge-tracker.d.ts +2 -2
- package/dist/src/knowledge-tracker.js +38 -45
- package/dist/src/state-manager.d.ts +10 -30
- package/dist/src/state-manager.js +11 -129
- package/dist/src/utils/aria.d.ts +6 -1
- package/dist/src/utils/aria.js +4 -1
- package/dist/src/utils/cache.d.ts +15 -0
- package/dist/src/utils/cache.js +34 -0
- package/dist/src/utils/context-formatter.d.ts +6 -0
- package/dist/src/utils/context-formatter.js +15 -27
- package/dist/src/utils/hooks-runner.js +2 -4
- package/dist/src/utils/html.d.ts +5 -0
- package/dist/src/utils/html.js +71 -0
- package/dist/src/utils/markdown-files.d.ts +10 -0
- package/dist/src/utils/markdown-files.js +21 -0
- package/dist/src/utils/markdown-query.d.ts +6 -0
- package/dist/src/utils/markdown-query.js +14 -1
- package/dist/src/utils/page-readiness.d.ts +1 -0
- package/dist/src/utils/page-readiness.js +1 -1
- package/dist/src/utils/secrets.js +2 -2
- package/dist/src/utils/strings.d.ts +2 -0
- package/dist/src/utils/strings.js +10 -0
- package/dist/src/utils/test-files.js +1 -1
- package/dist/src/utils/web-annotate.d.ts +5 -0
- package/dist/src/utils/web-annotate.js +58 -0
- package/dist/src/utils/web-eidx.d.ts +2 -0
- package/dist/src/utils/web-eidx.js +20 -0
- package/dist/src/utils/web-element.d.ts +3 -1
- package/dist/src/utils/web-element.js +32 -0
- package/dist/src/utils/web-sandbox.d.ts +4 -0
- package/dist/src/utils/web-sandbox.js +37 -0
- package/models.json +30 -0
- package/package.json +23 -3
- package/src/action-result.ts +70 -173
- package/src/action.ts +38 -132
- package/src/ai/agent.ts +20 -0
- package/src/ai/captain/idle-mode.ts +1 -1
- package/src/ai/captain/test-mode.ts +1 -1
- package/src/ai/captain/web-mode.ts +18 -30
- package/src/ai/captain.ts +17 -42
- package/src/ai/conversation.ts +9 -32
- package/src/ai/driller.ts +53 -83
- package/src/ai/experience-compactor.ts +16 -35
- package/src/ai/fisherman-tools.ts +1 -1
- package/src/ai/fisherman.ts +0 -4
- package/src/ai/historian/codeceptjs.ts +5 -3
- package/src/ai/historian/playwright.ts +7 -4
- package/src/ai/historian/screencast.ts +4 -3
- package/src/ai/historian/utils.ts +0 -1
- package/src/ai/historian.ts +6 -8
- package/src/ai/navigator.ts +39 -70
- package/src/ai/pilot.ts +59 -61
- package/src/ai/planner.ts +76 -62
- package/src/ai/provider.ts +85 -146
- package/src/ai/quartermaster.ts +39 -45
- package/src/ai/rerunner.ts +27 -50
- package/src/ai/researcher/cache.ts +10 -16
- package/src/ai/researcher/coordinates.ts +4 -3
- package/src/ai/researcher/deep-analysis.ts +10 -11
- package/src/ai/researcher/locators.ts +66 -9
- package/src/ai/researcher/parser.ts +1 -1
- package/src/ai/researcher/research-result.ts +0 -18
- package/src/ai/researcher/sections.ts +9 -3
- package/src/ai/researcher.ts +42 -119
- package/src/ai/rules.ts +24 -13
- package/src/ai/session-analyst.ts +8 -5
- package/src/ai/task-agent.ts +45 -38
- package/src/ai/tester.ts +51 -129
- package/src/ai/tools.ts +145 -153
- package/src/command-handler.ts +1 -1
- package/src/commands/compact-command.ts +7 -8
- package/src/commands/context-aria-command.ts +1 -1
- package/src/commands/context-command.ts +8 -6
- package/src/commands/context-data-command.ts +2 -2
- package/src/commands/context-experience-command.ts +2 -2
- package/src/commands/context-html-command.ts +2 -2
- package/src/commands/context-knowledge-command.ts +2 -2
- package/src/commands/drill-command.ts +1 -1
- package/src/commands/experience-command.ts +1 -1
- package/src/commands/explore-command.ts +4 -3
- package/src/commands/freesail-command.ts +2 -2
- package/src/commands/knows-command.ts +1 -1
- package/src/commands/learn-command.ts +5 -3
- package/src/commands/path-command.ts +1 -1
- package/src/commands/research-command.ts +1 -1
- package/src/commands/test-command.ts +1 -1
- package/src/components/AddKnowledge.tsx +5 -7
- package/src/components/App.tsx +3 -3
- package/src/config.ts +185 -11
- package/src/experience-tracker.ts +89 -152
- package/src/explorbot.ts +88 -83
- package/src/explorer.ts +402 -693
- package/src/knowledge-tracker.ts +42 -49
- package/src/state-manager.ts +20 -155
- package/src/utils/aria.ts +11 -2
- package/src/utils/cache.ts +40 -0
- package/src/utils/context-formatter.ts +12 -15
- package/src/utils/hooks-runner.ts +2 -4
- package/src/utils/html.ts +79 -0
- package/src/utils/markdown-files.ts +30 -0
- package/src/utils/markdown-query.ts +15 -1
- package/src/utils/page-readiness.ts +1 -1
- package/src/utils/secrets.ts +2 -3
- package/src/utils/strings.ts +12 -0
- package/src/utils/test-files.ts +1 -1
- package/src/utils/web-annotate.ts +64 -0
- package/src/utils/web-eidx.ts +21 -0
- package/src/utils/web-element.ts +34 -0
- package/src/utils/web-sandbox.ts +43 -0
- package/dist/boat/api-tester/example/apibot.config.js +0 -30
package/src/ai/quartermaster.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import type { ActionResult } from '../action-result.ts';
|
|
5
|
-
import {
|
|
5
|
+
import { outputPath } from '../config.ts';
|
|
6
|
+
import type Explorer from '../explorer.ts';
|
|
6
7
|
import type { StateManager, StateTransition, WebPageState } from '../state-manager.ts';
|
|
7
8
|
import type { Task } from '../test-plan.ts';
|
|
8
9
|
import { createDebug, tag } from '../utils/logger.ts';
|
|
@@ -12,56 +13,25 @@ import type { Provider } from './provider.ts';
|
|
|
12
13
|
|
|
13
14
|
const debugLog = createDebug('explorbot:quartermaster');
|
|
14
15
|
|
|
15
|
-
interface AxeViolation {
|
|
16
|
-
id: string;
|
|
17
|
-
impact: 'critical' | 'serious' | 'moderate' | 'minor';
|
|
18
|
-
description: string;
|
|
19
|
-
helpUrl: string;
|
|
20
|
-
nodes: Array<{ html: string; target: string[] }>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface PageAnalysis {
|
|
24
|
-
url: string;
|
|
25
|
-
stateHash: string;
|
|
26
|
-
timestamp: string;
|
|
27
|
-
axeViolations: AxeViolation[];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface SemanticIssue {
|
|
31
|
-
type: 'unclear_intention' | 'confusing_naming' | 'hard_to_interact';
|
|
32
|
-
element: string;
|
|
33
|
-
issue: string;
|
|
34
|
-
suggestion: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface AnalysisReport {
|
|
38
|
-
scenario: string;
|
|
39
|
-
timestamp: string;
|
|
40
|
-
url: string;
|
|
41
|
-
axeViolations: AxeViolation[];
|
|
42
|
-
semanticIssues: SemanticIssue[];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
16
|
export class Quartermaster {
|
|
46
17
|
private provider: Provider;
|
|
47
18
|
private model?: any;
|
|
48
19
|
private outputDir: string;
|
|
49
20
|
private pageAnalyses: Map<string, PageAnalysis> = new Map();
|
|
50
21
|
private unsubscribe: (() => void) | null = null;
|
|
51
|
-
private
|
|
22
|
+
private explorer: Explorer | null = null;
|
|
52
23
|
private pendingAnalyses: Promise<void>[] = [];
|
|
53
24
|
|
|
54
25
|
constructor(provider: Provider, options?: { model?: any }) {
|
|
55
26
|
this.provider = provider;
|
|
56
27
|
this.model = options?.model;
|
|
57
28
|
|
|
58
|
-
|
|
59
|
-
this.outputDir = join(configParser.getOutputDir(), 'a11y');
|
|
29
|
+
this.outputDir = outputPath('a11y');
|
|
60
30
|
}
|
|
61
31
|
|
|
62
|
-
start(
|
|
63
|
-
this.
|
|
64
|
-
this.
|
|
32
|
+
start(explorer: Explorer, stateManager: StateManager): void {
|
|
33
|
+
this.explorer = explorer;
|
|
34
|
+
mkdirSync(this.outputDir, { recursive: true });
|
|
65
35
|
|
|
66
36
|
this.unsubscribe = stateManager.onStateChange((event) => {
|
|
67
37
|
this.onPageChange(event);
|
|
@@ -78,12 +48,6 @@ export class Quartermaster {
|
|
|
78
48
|
debugLog('Quartermaster stopped');
|
|
79
49
|
}
|
|
80
50
|
|
|
81
|
-
private ensureDirectory(): void {
|
|
82
|
-
if (!existsSync(this.outputDir)) {
|
|
83
|
-
mkdirSync(this.outputDir, { recursive: true });
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
51
|
private onPageChange(event: StateTransition): void {
|
|
88
52
|
const state = event.toState;
|
|
89
53
|
if (!state?.hash) return;
|
|
@@ -97,7 +61,7 @@ export class Quartermaster {
|
|
|
97
61
|
}
|
|
98
62
|
|
|
99
63
|
private async runAxeAnalysis(state: WebPageState): Promise<void> {
|
|
100
|
-
const page = this.
|
|
64
|
+
const page = this.explorer?.page;
|
|
101
65
|
if (!page || !state.hash) {
|
|
102
66
|
debugLog('No page available for axe analysis');
|
|
103
67
|
return;
|
|
@@ -284,3 +248,33 @@ Focus on what would confuse a real user or caused the agent to make mistakes.`;
|
|
|
284
248
|
return content;
|
|
285
249
|
}
|
|
286
250
|
}
|
|
251
|
+
|
|
252
|
+
interface AxeViolation {
|
|
253
|
+
id: string;
|
|
254
|
+
impact: 'critical' | 'serious' | 'moderate' | 'minor';
|
|
255
|
+
description: string;
|
|
256
|
+
helpUrl: string;
|
|
257
|
+
nodes: Array<{ html: string; target: string[] }>;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
interface PageAnalysis {
|
|
261
|
+
url: string;
|
|
262
|
+
stateHash: string;
|
|
263
|
+
timestamp: string;
|
|
264
|
+
axeViolations: AxeViolation[];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
interface SemanticIssue {
|
|
268
|
+
type: 'unclear_intention' | 'confusing_naming' | 'hard_to_interact';
|
|
269
|
+
element: string;
|
|
270
|
+
issue: string;
|
|
271
|
+
suggestion: string;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
interface AnalysisReport {
|
|
275
|
+
scenario: string;
|
|
276
|
+
timestamp: string;
|
|
277
|
+
url: string;
|
|
278
|
+
axeViolations: AxeViolation[];
|
|
279
|
+
semanticIssues: SemanticIssue[];
|
|
280
|
+
}
|
package/src/ai/rerunner.ts
CHANGED
|
@@ -12,16 +12,13 @@ import figureSet from 'figures';
|
|
|
12
12
|
import { z } from 'zod';
|
|
13
13
|
import { ActionResult } from '../action-result.ts';
|
|
14
14
|
import { setActivity } from '../activity.ts';
|
|
15
|
-
import type { ExperienceTracker } from '../experience-tracker.ts';
|
|
16
|
-
import type Explorer from '../explorer.ts';
|
|
17
|
-
import type { KnowledgeTracker } from '../knowledge-tracker.ts';
|
|
18
15
|
import { Stats } from '../stats.ts';
|
|
19
16
|
import { Task, Test, TestResult } from '../test-plan.ts';
|
|
17
|
+
import { formatHeadings } from '../utils/context-formatter.ts';
|
|
20
18
|
import { createDebug, tag } from '../utils/logger.ts';
|
|
21
19
|
import { loop } from '../utils/loop.ts';
|
|
22
20
|
import { RulesLoader } from '../utils/rules-loader.ts';
|
|
23
|
-
import {
|
|
24
|
-
import type { Agent } from './agent.ts';
|
|
21
|
+
import type { Agent, AgentDeps } from './agent.ts';
|
|
25
22
|
import { toolExecutionLabel } from './conversation.ts';
|
|
26
23
|
import type { Navigator } from './navigator.ts';
|
|
27
24
|
import { Provider } from './provider.ts';
|
|
@@ -35,16 +32,13 @@ export class Rerunner extends TaskAgent implements Agent {
|
|
|
35
32
|
protected readonly ACTION_TOOLS = ['click', 'pressKey', 'form'];
|
|
36
33
|
emoji = '🔄';
|
|
37
34
|
|
|
38
|
-
private explorer: Explorer;
|
|
39
|
-
private provider: Provider;
|
|
40
35
|
private agentTools: any;
|
|
41
|
-
private healedSteps: Array<{
|
|
36
|
+
private healedSteps: Array<{ original: string; healed: string }> = [];
|
|
42
37
|
private traceDir = '';
|
|
38
|
+
private static pluginsWired = false;
|
|
43
39
|
|
|
44
|
-
constructor(
|
|
45
|
-
super();
|
|
46
|
-
this.explorer = explorer;
|
|
47
|
-
this.provider = provider;
|
|
40
|
+
constructor(deps: AgentDeps, agentTools?: any) {
|
|
41
|
+
super(deps);
|
|
48
42
|
this.agentTools = agentTools;
|
|
49
43
|
}
|
|
50
44
|
|
|
@@ -52,20 +46,8 @@ export class Rerunner extends TaskAgent implements Agent {
|
|
|
52
46
|
throw new Error('Rerunner does not use Navigator');
|
|
53
47
|
}
|
|
54
48
|
|
|
55
|
-
protected getExperienceTracker(): ExperienceTracker {
|
|
56
|
-
return this.explorer.getStateManager().getExperienceTracker();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
protected getKnowledgeTracker(): KnowledgeTracker {
|
|
60
|
-
return this.explorer.getKnowledgeTracker();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
protected getProvider(): Provider {
|
|
64
|
-
return this.provider;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
49
|
private get rerunnerConfig(): Record<string, any> {
|
|
68
|
-
return (this.
|
|
50
|
+
return (this.config.ai?.agents?.rerunner as any) || {};
|
|
69
51
|
}
|
|
70
52
|
|
|
71
53
|
private get healLimit(): number {
|
|
@@ -76,10 +58,6 @@ export class Rerunner extends TaskAgent implements Agent {
|
|
|
76
58
|
return this.rerunnerConfig.healMaxIterations ?? 3;
|
|
77
59
|
}
|
|
78
60
|
|
|
79
|
-
listTests(testsDir: string): void {
|
|
80
|
-
printTestList(loadTestSuites(testsDir));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
61
|
async rerun(filePath: string, options?: { testIndices?: number[] }): Promise<RerunResult> {
|
|
84
62
|
const absPath = resolve(filePath);
|
|
85
63
|
if (!existsSync(absPath)) {
|
|
@@ -219,7 +197,6 @@ export class Rerunner extends TaskAgent implements Agent {
|
|
|
219
197
|
|
|
220
198
|
private setupPlugins(): void {
|
|
221
199
|
const healMod = heal.default || heal;
|
|
222
|
-
healMod.connectToEvents();
|
|
223
200
|
|
|
224
201
|
healMod.addRecipe('explorbot-ai-healer', {
|
|
225
202
|
priority: 10,
|
|
@@ -233,21 +210,29 @@ export class Rerunner extends TaskAgent implements Agent {
|
|
|
233
210
|
healMod.addRecipe(name, recipe);
|
|
234
211
|
}
|
|
235
212
|
|
|
213
|
+
const outputDir = (global as any).output_dir || 'output';
|
|
214
|
+
this.traceDir = `${outputDir}/rerun-traces`;
|
|
215
|
+
|
|
216
|
+
if (Rerunner.pluginsWired) return;
|
|
217
|
+
Rerunner.pluginsWired = true;
|
|
218
|
+
|
|
219
|
+
healMod.connectToEvents();
|
|
220
|
+
|
|
236
221
|
let currentTest: any = null;
|
|
237
222
|
let healTries = 0;
|
|
238
223
|
let isHealing = false;
|
|
239
224
|
let caughtError: any = null;
|
|
240
|
-
const healLimit = this.healLimit;
|
|
241
225
|
|
|
242
226
|
codeceptjs.event.dispatcher.on('test.before', (test: any) => {
|
|
243
227
|
currentTest = test;
|
|
244
228
|
healTries = 0;
|
|
245
229
|
caughtError = null;
|
|
230
|
+
isHealing = false;
|
|
246
231
|
});
|
|
247
232
|
|
|
248
233
|
codeceptjs.event.dispatcher.on('step.after', (step: any) => {
|
|
249
234
|
if (isHealing) return;
|
|
250
|
-
if (healTries >= healLimit) return;
|
|
235
|
+
if (healTries >= this.healLimit) return;
|
|
251
236
|
if (!healMod.hasCorrespondingRecipes(step)) return;
|
|
252
237
|
|
|
253
238
|
codeceptjs.recorder.catchWithoutStop(async (err: any) => {
|
|
@@ -286,10 +271,6 @@ export class Rerunner extends TaskAgent implements Agent {
|
|
|
286
271
|
factor: 1.5,
|
|
287
272
|
});
|
|
288
273
|
|
|
289
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
290
|
-
const outputDir = (global as any).output_dir || 'output';
|
|
291
|
-
this.traceDir = `${outputDir}/rerun_${timestamp}`;
|
|
292
|
-
|
|
293
274
|
const aiTrace = aiTracePlugin.default || aiTracePlugin;
|
|
294
275
|
aiTrace(this.rerunnerConfig.aiTrace || { output: this.traceDir });
|
|
295
276
|
|
|
@@ -303,9 +284,9 @@ export class Rerunner extends TaskAgent implements Agent {
|
|
|
303
284
|
|
|
304
285
|
private teardownHealing(): void {
|
|
305
286
|
const healMod = heal.default || heal;
|
|
306
|
-
healMod.recipes
|
|
287
|
+
Reflect.deleteProperty(healMod.recipes, 'explorbot-ai-healer');
|
|
307
288
|
for (const name of Object.keys(this.rerunnerConfig.recipes || {})) {
|
|
308
|
-
healMod.recipes
|
|
289
|
+
Reflect.deleteProperty(healMod.recipes, name);
|
|
309
290
|
}
|
|
310
291
|
}
|
|
311
292
|
|
|
@@ -325,7 +306,7 @@ export class Rerunner extends TaskAgent implements Agent {
|
|
|
325
306
|
});
|
|
326
307
|
|
|
327
308
|
const healTask = new Task(`Heal: ${failedCode}`);
|
|
328
|
-
const codeceptTools = createCodeceptJSTools(this.
|
|
309
|
+
const codeceptTools = createCodeceptJSTools(this.toolDeps, healTask);
|
|
329
310
|
|
|
330
311
|
let healed = false;
|
|
331
312
|
let healedCommand = '';
|
|
@@ -345,9 +326,9 @@ export class Rerunner extends TaskAgent implements Agent {
|
|
|
345
326
|
healTask.addNote(note);
|
|
346
327
|
tag('substep').log(note);
|
|
347
328
|
}
|
|
348
|
-
const action = this.explorer.
|
|
329
|
+
const action = this.explorer.action();
|
|
349
330
|
await action.execute(`I.wait(${seconds})`);
|
|
350
|
-
const state = this.
|
|
331
|
+
const state = this.stateManager.getCurrentState();
|
|
351
332
|
const ar = state ? ActionResult.fromState(state) : null;
|
|
352
333
|
return {
|
|
353
334
|
success: true,
|
|
@@ -432,14 +413,14 @@ export class Rerunner extends TaskAgent implements Agent {
|
|
|
432
413
|
throw new Error(`Could not heal: ${failedCode}`);
|
|
433
414
|
}
|
|
434
415
|
|
|
435
|
-
this.healedSteps.push({
|
|
416
|
+
this.healedSteps.push({ original: failedCode, healed: healedCommand });
|
|
436
417
|
console.log(chalk.green(` ${figureSet.tick} Healed: ${healedCommand}`));
|
|
437
418
|
};
|
|
438
419
|
}
|
|
439
420
|
|
|
440
421
|
private getHealSystemPrompt(): string {
|
|
441
|
-
const customRules = this.provider.getSystemPromptForAgent('rerunner', this.
|
|
442
|
-
const currentUrl = this.
|
|
422
|
+
const customRules = this.provider.getSystemPromptForAgent('rerunner', this.stateManager.getCurrentState()?.url) || '';
|
|
423
|
+
const currentUrl = this.stateManager.getCurrentState()?.url || '';
|
|
443
424
|
const approach = RulesLoader.loadRules('rerunner', ['healing-approach'], currentUrl);
|
|
444
425
|
|
|
445
426
|
return dedent`
|
|
@@ -474,14 +455,10 @@ export class Rerunner extends TaskAgent implements Agent {
|
|
|
474
455
|
}
|
|
475
456
|
|
|
476
457
|
private getHealUserPrompt(failedCode: string, error: Error): string {
|
|
477
|
-
const state = this.
|
|
458
|
+
const state = this.stateManager.getCurrentState();
|
|
478
459
|
const actionResult = state ? ActionResult.fromState(state) : null;
|
|
479
460
|
|
|
480
|
-
const headings
|
|
481
|
-
if (state?.h1) headings.push(`H1: ${state.h1}`);
|
|
482
|
-
if (state?.h2) headings.push(`H2: ${state.h2}`);
|
|
483
|
-
if (state?.h3) headings.push(`H3: ${state.h3}`);
|
|
484
|
-
if (state?.h4) headings.push(`H4: ${state.h4}`);
|
|
461
|
+
const headings = formatHeadings(state || {});
|
|
485
462
|
|
|
486
463
|
return dedent`
|
|
487
464
|
A test step failed and needs healing.
|
|
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'no
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { Worker } from 'node:worker_threads';
|
|
4
4
|
import { outputPath } from '../../config.ts';
|
|
5
|
+
import { TTLCache } from '../../utils/cache.ts';
|
|
5
6
|
import { computeHtmlFingerprint } from '../../utils/html-diff.ts';
|
|
6
7
|
import { debugLog } from './mixin.ts';
|
|
7
8
|
|
|
@@ -10,8 +11,7 @@ const FINGERPRINT_MAX_AGE_MS = 60 * 60 * 1000; // 1 hour
|
|
|
10
11
|
const FINGERPRINT_WORKER_TIMEOUT_MS = 10_000;
|
|
11
12
|
const SIMILARITY_THRESHOLD = 90;
|
|
12
13
|
|
|
13
|
-
const memoryCache
|
|
14
|
-
const memoryCacheTimestamps: Record<string, number> = {};
|
|
14
|
+
const memoryCache = new TTLCache<string>(CACHE_TTL_MS);
|
|
15
15
|
|
|
16
16
|
let fingerprintWorker: Worker | null = null;
|
|
17
17
|
|
|
@@ -28,25 +28,20 @@ function getFingerprintWorker(): Worker {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export function clearResearchCache(): void {
|
|
31
|
-
|
|
32
|
-
for (const key of Object.keys(memoryCacheTimestamps)) delete memoryCacheTimestamps[key];
|
|
31
|
+
memoryCache.clear();
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
export function getCachedResearch(hash: string): string {
|
|
36
35
|
if (!hash) return '';
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
if (timestamp && now - timestamp <= CACHE_TTL_MS) {
|
|
40
|
-
return memoryCache[hash] || '';
|
|
41
|
-
}
|
|
36
|
+
const cached = memoryCache.get(hash);
|
|
37
|
+
if (cached !== undefined) return cached;
|
|
42
38
|
const researchFile = outputPath('research', `${hash}.md`);
|
|
43
39
|
if (!existsSync(researchFile)) return '';
|
|
44
40
|
const stats = statSync(researchFile);
|
|
45
|
-
if (now - stats.mtimeMs > CACHE_TTL_MS) return '';
|
|
46
|
-
const
|
|
47
|
-
memoryCache
|
|
48
|
-
|
|
49
|
-
return cached;
|
|
41
|
+
if (Date.now() - stats.mtimeMs > CACHE_TTL_MS) return '';
|
|
42
|
+
const fromDisk = readFileSync(researchFile, 'utf8');
|
|
43
|
+
memoryCache.set(hash, fromDisk);
|
|
44
|
+
return fromDisk;
|
|
50
45
|
}
|
|
51
46
|
|
|
52
47
|
export function getPreviousResearch(hash: string): string {
|
|
@@ -61,8 +56,7 @@ export function saveResearch(hash: string, text: string, combinedHtml?: string):
|
|
|
61
56
|
const researchFile = join(researchDir, `${hash}.md`);
|
|
62
57
|
if (!existsSync(researchDir)) mkdirSync(researchDir, { recursive: true });
|
|
63
58
|
writeFileSync(researchFile, text);
|
|
64
|
-
memoryCache
|
|
65
|
-
memoryCacheTimestamps[hash] = Date.now();
|
|
59
|
+
memoryCache.set(hash, text);
|
|
66
60
|
debugLog(`Research saved to ${researchFile}`);
|
|
67
61
|
|
|
68
62
|
if (combinedHtml) {
|
|
@@ -4,6 +4,7 @@ import type { ActionResult } from '../../action-result.js';
|
|
|
4
4
|
import type Explorer from '../../explorer.ts';
|
|
5
5
|
import { tag } from '../../utils/logger.js';
|
|
6
6
|
import { mdq } from '../../utils/markdown-query.ts';
|
|
7
|
+
import { eidxByLocator } from '../../utils/web-eidx.ts';
|
|
7
8
|
import { WebElement } from '../../utils/web-element.ts';
|
|
8
9
|
import type { Provider } from '../provider.js';
|
|
9
10
|
import { type Constructor, debugLog } from './mixin.ts';
|
|
@@ -81,7 +82,7 @@ export function WithCoordinates<T extends Constructor>(Base: T) {
|
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
async visuallyAnnotateElements(opts?: { containers?: Array<{ css: string; label: string }> }): Promise<number> {
|
|
84
|
-
return this.explorer.
|
|
85
|
+
return this.explorer.withPage((page) => visuallyAnnotateContainers(page, opts?.containers || []));
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
private async _analyzeScreenshotForVisualProps(): Promise<VisualAnalysisResult> {
|
|
@@ -177,7 +178,7 @@ export function WithCoordinates<T extends Constructor>(Base: T) {
|
|
|
177
178
|
let eidx = el.eidx || null;
|
|
178
179
|
if (!eidx) {
|
|
179
180
|
const locator = el.css || el.xpath || (el.aria ? `role=${el.aria.role}[name="${el.aria.text}"]` : null);
|
|
180
|
-
if (locator) eidx = await this.explorer.
|
|
181
|
+
if (locator) eidx = await this.explorer.withPage((page) => eidxByLocator(page, locator, section.containerCss));
|
|
181
182
|
}
|
|
182
183
|
if (!eidx) continue;
|
|
183
184
|
|
|
@@ -202,7 +203,7 @@ export function WithCoordinates<T extends Constructor>(Base: T) {
|
|
|
202
203
|
}
|
|
203
204
|
if (eidxWithoutCoords.length === 0) return;
|
|
204
205
|
|
|
205
|
-
const webElements = await this.explorer.
|
|
206
|
+
const webElements = await this.explorer.withPage((page) => WebElement.fromEidxList(page, eidxWithoutCoords));
|
|
206
207
|
if (webElements.length === 0) return;
|
|
207
208
|
|
|
208
209
|
const rectMap = new Map(webElements.map((w) => [w.eidx!, w]));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import dedent from 'dedent';
|
|
2
2
|
import { ActionResult, type Diff } from '../../action-result.js';
|
|
3
|
+
import type { ExplorbotConfig } from '../../config.ts';
|
|
3
4
|
import { executionController } from '../../execution-controller.ts';
|
|
4
5
|
import type Explorer from '../../explorer.ts';
|
|
5
6
|
import type { StateManager } from '../../state-manager.js';
|
|
@@ -21,13 +22,14 @@ export function WithDeepAnalysis<T extends Constructor>(Base: T) {
|
|
|
21
22
|
declare explorer: Explorer;
|
|
22
23
|
declare provider: Provider;
|
|
23
24
|
declare stateManager: StateManager;
|
|
25
|
+
declare config: ExplorbotConfig;
|
|
24
26
|
declare actionResult: ActionResult | undefined;
|
|
25
27
|
|
|
26
28
|
async performDeepAnalysis(state: WebPageState, result: ResearchResult): Promise<void> {
|
|
27
29
|
tag('info').log('Starting deep analysis of expandable elements');
|
|
28
30
|
await (this as any).navigateTo(state.fullUrl || state.url);
|
|
29
31
|
|
|
30
|
-
const maxClicks = (this.
|
|
32
|
+
const maxClicks = (this.config.ai?.agents?.researcher as any)?.maxExpandableClicks ?? DEFAULT_MAX_EXPANDABLE_CLICKS;
|
|
31
33
|
|
|
32
34
|
const expandedSections: string[] = [];
|
|
33
35
|
const navigationLinks: Array<{ code: string; url: string }> = [];
|
|
@@ -98,7 +100,6 @@ export function WithDeepAnalysis<T extends Constructor>(Base: T) {
|
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
const diff = await current.diff(previous);
|
|
101
|
-
await diff.calculate();
|
|
102
103
|
|
|
103
104
|
if (!diff.ariaChanged && diff.htmlParts.length === 0) {
|
|
104
105
|
debugLog(`No diff between current and previous state for overlay "${focusArea.name}"`);
|
|
@@ -359,13 +360,12 @@ export function WithDeepAnalysis<T extends Constructor>(Base: T) {
|
|
|
359
360
|
const isCoordinateClick = el.commands[0].startsWith('I.clickXY(');
|
|
360
361
|
if (!isCoordinateClick) {
|
|
361
362
|
const hoverCmd = el.commands[0].replace('I.click(', 'I.moveCursorTo(');
|
|
362
|
-
await this.explorer.
|
|
363
|
+
await this.explorer.action().attempt(hoverCmd);
|
|
363
364
|
await new Promise((r) => setTimeout(r, 500));
|
|
364
365
|
|
|
365
|
-
await this.explorer.
|
|
366
|
+
await this.explorer.capture();
|
|
366
367
|
const hoverAR = ActionResult.fromState(this.stateManager.getCurrentState()!);
|
|
367
368
|
const hoverDiff = await hoverAR.diff(previousState);
|
|
368
|
-
await hoverDiff.calculate();
|
|
369
369
|
const hoverHtmlSize = hoverDiff.htmlParts.reduce((sum, p) => sum + p.subtree.length, 0);
|
|
370
370
|
const hoverRevealed = hoverDiff.ariaChanged && hoverHtmlSize > 500;
|
|
371
371
|
|
|
@@ -403,9 +403,9 @@ export function WithDeepAnalysis<T extends Constructor>(Base: T) {
|
|
|
403
403
|
const previousState = ActionResult.fromState(this.stateManager.getCurrentState()!);
|
|
404
404
|
|
|
405
405
|
let clickCode: string | null = null;
|
|
406
|
-
const action = this.explorer.
|
|
406
|
+
const action = this.explorer.action();
|
|
407
407
|
for (const cmd of commands) {
|
|
408
|
-
if (await action.attempt(cmd, undefined
|
|
408
|
+
if (await action.attempt(cmd, undefined)) {
|
|
409
409
|
clickCode = cmd;
|
|
410
410
|
break;
|
|
411
411
|
}
|
|
@@ -419,10 +419,9 @@ export function WithDeepAnalysis<T extends Constructor>(Base: T) {
|
|
|
419
419
|
|
|
420
420
|
let diff: Diff;
|
|
421
421
|
try {
|
|
422
|
-
await this.explorer.
|
|
422
|
+
await this.explorer.capture();
|
|
423
423
|
const currAR = ActionResult.fromState(this.stateManager.getCurrentState()!);
|
|
424
424
|
diff = await currAR.diff(previousState);
|
|
425
|
-
await diff.calculate();
|
|
426
425
|
} catch (err) {
|
|
427
426
|
tag('warning').log(`State capture failed after click: ${err instanceof Error ? err.message : err}`);
|
|
428
427
|
await this._restorePageState(state.url, originalAria);
|
|
@@ -451,9 +450,9 @@ export function WithDeepAnalysis<T extends Constructor>(Base: T) {
|
|
|
451
450
|
private async _restorePageState(url: string, originalAria: string): Promise<void> {
|
|
452
451
|
try {
|
|
453
452
|
await (this as any).cancelInUi();
|
|
454
|
-
await this.explorer.
|
|
453
|
+
await this.explorer.capture();
|
|
455
454
|
const currentAria = this.stateManager.getCurrentState()?.ariaSnapshot || '';
|
|
456
|
-
if (!diffAriaSnapshots(originalAria, currentAria)) return;
|
|
455
|
+
if (!diffAriaSnapshots(originalAria, currentAria).text) return;
|
|
457
456
|
} catch (err) {
|
|
458
457
|
tag('warning').log(`State capture failed after cancelInUi: ${err instanceof Error ? err.message : err}`);
|
|
459
458
|
}
|
|
@@ -6,6 +6,7 @@ import { parseAriaLocator } from '../../utils/aria.ts';
|
|
|
6
6
|
import { tag } from '../../utils/logger.js';
|
|
7
7
|
import { mdq } from '../../utils/markdown-query.ts';
|
|
8
8
|
import { WebElement } from '../../utils/web-element.ts';
|
|
9
|
+
import { isDynamicId } from '../../utils/xpath.ts';
|
|
9
10
|
import type { Conversation } from '../conversation.ts';
|
|
10
11
|
import type { Provider } from '../provider.js';
|
|
11
12
|
import { locatorRule as generalLocatorRuleText } from '../rules.js';
|
|
@@ -38,7 +39,7 @@ export function WithLocators<T extends Constructor>(Base: T) {
|
|
|
38
39
|
declare provider: Provider;
|
|
39
40
|
declare actionResult: ActionResult | undefined;
|
|
40
41
|
|
|
41
|
-
async testLocators(locators: Locator[]): Promise<void> {
|
|
42
|
+
async testLocators(locators: Locator[], opts: { scope?: boolean } = {}): Promise<void> {
|
|
42
43
|
let broken = 0;
|
|
43
44
|
for (const loc of locators) {
|
|
44
45
|
if (executionController.isInterrupted()) break;
|
|
@@ -51,20 +52,21 @@ export function WithLocators<T extends Constructor>(Base: T) {
|
|
|
51
52
|
continue;
|
|
52
53
|
}
|
|
53
54
|
try {
|
|
54
|
-
const count = await this.explorer.
|
|
55
|
+
const count = await this.explorer.withPage((page) => {
|
|
55
56
|
const base = loc.container ? page.locator(loc.container) : page;
|
|
56
57
|
if (loc.type === 'aria') {
|
|
57
58
|
const parsed = parseAriaLocator(loc.locator);
|
|
58
|
-
if (!parsed) return page.locator('__invalid__');
|
|
59
|
-
return base.getByRole(parsed.role as any, { name: parsed.text });
|
|
59
|
+
if (!parsed) return page.locator('__invalid__').count();
|
|
60
|
+
return base.getByRole(parsed.role as any, { name: parsed.text }).count();
|
|
60
61
|
}
|
|
61
62
|
const converted = loc.locator.replace(/:contains\(/g, ':has-text(');
|
|
62
63
|
if (converted !== loc.locator) {
|
|
63
64
|
loc.locator = converted;
|
|
64
65
|
}
|
|
65
|
-
return base.locator(loc.locator);
|
|
66
|
+
return base.locator(loc.locator).count();
|
|
66
67
|
});
|
|
67
68
|
loc.valid = count === 1;
|
|
69
|
+
if (opts.scope && count > 1) loc.valid = true;
|
|
68
70
|
loc.pwLocator = buildPwLocatorString(loc);
|
|
69
71
|
if (!loc.valid) {
|
|
70
72
|
loc.error = count === 0 ? '0 elements' : `${count} elements`;
|
|
@@ -194,7 +196,7 @@ export function WithLocators<T extends Constructor>(Base: T) {
|
|
|
194
196
|
}
|
|
195
197
|
|
|
196
198
|
if (needsXpath.length > 0) {
|
|
197
|
-
const webElements = await this.explorer.
|
|
199
|
+
const webElements = await this.explorer.withPage((page) => WebElement.fromEidxList(page, needsXpath));
|
|
198
200
|
const changedSections = new Set<(typeof sections)[0]>();
|
|
199
201
|
for (const w of webElements) {
|
|
200
202
|
const entry = needsXpathEls.get(w.eidx!);
|
|
@@ -209,6 +211,60 @@ export function WithLocators<T extends Constructor>(Base: T) {
|
|
|
209
211
|
await this.validateContainers(result);
|
|
210
212
|
}
|
|
211
213
|
|
|
214
|
+
async resolveContainers(result: ResearchResult): Promise<Locator[]> {
|
|
215
|
+
const containerLocs = result.containerLocators;
|
|
216
|
+
await this.testLocators(containerLocs, { scope: true });
|
|
217
|
+
for (const loc of containerLocs.filter((l) => l.valid === false)) {
|
|
218
|
+
if (await this.recoverContainerFromChildren(result, loc.locator)) loc.valid = true;
|
|
219
|
+
}
|
|
220
|
+
return containerLocs.filter((l) => l.valid === false);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private async recoverContainerFromChildren(result: ResearchResult, css: string): Promise<boolean> {
|
|
224
|
+
const sections = parseResearchSections(result.text).filter((s) => s.containerCss === css);
|
|
225
|
+
let recovered = 0;
|
|
226
|
+
|
|
227
|
+
for (const section of sections) {
|
|
228
|
+
const eidxList = section.elements.map((el) => el.eidx).filter(Boolean) as string[];
|
|
229
|
+
if (eidxList.length < 2) continue;
|
|
230
|
+
|
|
231
|
+
const ancestor = await this.explorer.runWithBrowserRecovery('recoverContainerFromChildren', () => WebElement.commonAncestor(this.explorer.playwrightHelper.page, eidxList));
|
|
232
|
+
if (!ancestor) continue;
|
|
233
|
+
|
|
234
|
+
const candidates: string[] = [];
|
|
235
|
+
const id = ancestor.attrs.id;
|
|
236
|
+
if (id && !isDynamicId(id)) candidates.push(`#${id}`);
|
|
237
|
+
for (const cls of ancestor.filteredClasses) {
|
|
238
|
+
if (!/^[\w-]+$/.test(cls)) continue;
|
|
239
|
+
candidates.push(`${ancestor.tag}.${cls}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let unique: string | null = null;
|
|
243
|
+
let multiple: string | null = null;
|
|
244
|
+
for (const candidate of candidates) {
|
|
245
|
+
let count = 0;
|
|
246
|
+
try {
|
|
247
|
+
count = await this.explorer.playwrightLocatorCount((page) => page.locator(candidate));
|
|
248
|
+
} catch {}
|
|
249
|
+
if (count === 1) {
|
|
250
|
+
unique = candidate;
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
if (count > 1 && !multiple) multiple = candidate;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const newCss = unique || multiple;
|
|
257
|
+
if (!newCss) continue;
|
|
258
|
+
|
|
259
|
+
debugLog(`Recovered container: '${css}' → '${newCss}' in "${section.name}"`);
|
|
260
|
+
this.updateSectionContainer(result, section, newCss);
|
|
261
|
+
recovered++;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (recovered > 0 && recovered < sections.length) debugLog(`Container '${css}' recovered in ${recovered}/${sections.length} sections, still broken for the rest`);
|
|
265
|
+
return recovered > 0 && recovered === sections.length;
|
|
266
|
+
}
|
|
267
|
+
|
|
212
268
|
private async validateContainers(result: ResearchResult): Promise<void> {
|
|
213
269
|
const sections = parseResearchSections(result.text);
|
|
214
270
|
|
|
@@ -217,7 +273,7 @@ export function WithLocators<T extends Constructor>(Base: T) {
|
|
|
217
273
|
|
|
218
274
|
let count = 0;
|
|
219
275
|
try {
|
|
220
|
-
count = await this.explorer.
|
|
276
|
+
count = await this.explorer.withPage((page) => page.locator(section.containerCss!).count());
|
|
221
277
|
} catch {}
|
|
222
278
|
|
|
223
279
|
if (count >= 1) continue;
|
|
@@ -226,7 +282,7 @@ export function WithLocators<T extends Constructor>(Base: T) {
|
|
|
226
282
|
if (simplified) {
|
|
227
283
|
let simplifiedCount = 0;
|
|
228
284
|
try {
|
|
229
|
-
simplifiedCount = await this.explorer.
|
|
285
|
+
simplifiedCount = await this.explorer.withPage((page) => page.locator(simplified).count());
|
|
230
286
|
} catch {}
|
|
231
287
|
|
|
232
288
|
if (simplifiedCount >= 1) {
|
|
@@ -275,7 +331,8 @@ export interface Locator {
|
|
|
275
331
|
}
|
|
276
332
|
|
|
277
333
|
export interface LocatorMethods {
|
|
278
|
-
testLocators(locators: Locator[]): Promise<void>;
|
|
334
|
+
testLocators(locators: Locator[], opts?: { scope?: boolean }): Promise<void>;
|
|
279
335
|
fixBrokenSections(result: ResearchResult, conversation: Conversation): Promise<void>;
|
|
280
336
|
backfillBrokenLocators(result: ResearchResult): Promise<void>;
|
|
337
|
+
resolveContainers(result: ResearchResult): Promise<Locator[]>;
|
|
281
338
|
}
|
|
@@ -94,7 +94,7 @@ export function extractContainerFromBlockquote(sectionMarkdown: string): string
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
export function parseResearchSections(markdown: string): ResearchSection[] {
|
|
97
|
-
const hasExtendedResearch = markdown.
|
|
97
|
+
const hasExtendedResearch = mdq(markdown).query('section1(~"Extended Research")').count() > 0;
|
|
98
98
|
|
|
99
99
|
return parseSections(markdown)
|
|
100
100
|
.filter((s) => !SKIP_SECTIONS.has(s.name.toLowerCase()) && !s.name.toLowerCase().includes('data:'))
|
|
@@ -46,24 +46,6 @@ export class ResearchResult {
|
|
|
46
46
|
}));
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
updateSection(sectionName: string, locators: Locator[]): void {
|
|
50
|
-
const sections = parseResearchSections(this.text);
|
|
51
|
-
const section = sections.find((s) => s.name === sectionName);
|
|
52
|
-
if (!section) return;
|
|
53
|
-
|
|
54
|
-
for (const el of section.elements) {
|
|
55
|
-
const elLocators = locators.filter((l) => l.element === el.name);
|
|
56
|
-
for (const loc of elLocators) {
|
|
57
|
-
const value = loc.valid === false ? null : loc.locator || null;
|
|
58
|
-
if (loc.type === 'css') el.css = value;
|
|
59
|
-
if (loc.type === 'xpath') el.xpath = value;
|
|
60
|
-
if (loc.type === 'aria') el.aria = value ? parseAriaLocator(value) : null;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
this.rebuildSectionInText(section);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
49
|
rebuildSectionInText(section: ResearchSection): void {
|
|
68
50
|
if (section.elements.length === 0) return;
|
|
69
51
|
const newTable = rebuildSectionMarkdown(section);
|