testchimp-runner-core 0.0.33 → 0.0.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/dist/execution-service.d.ts +1 -4
  2. package/dist/execution-service.d.ts.map +1 -1
  3. package/dist/execution-service.js +155 -468
  4. package/dist/execution-service.js.map +1 -1
  5. package/dist/index.d.ts +3 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +11 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/llm-facade.d.ts.map +1 -1
  10. package/dist/llm-facade.js +7 -7
  11. package/dist/llm-facade.js.map +1 -1
  12. package/dist/llm-provider.d.ts +9 -0
  13. package/dist/llm-provider.d.ts.map +1 -1
  14. package/dist/model-constants.d.ts +16 -5
  15. package/dist/model-constants.d.ts.map +1 -1
  16. package/dist/model-constants.js +17 -6
  17. package/dist/model-constants.js.map +1 -1
  18. package/dist/orchestrator/decision-parser.d.ts +18 -0
  19. package/dist/orchestrator/decision-parser.d.ts.map +1 -0
  20. package/dist/orchestrator/decision-parser.js +127 -0
  21. package/dist/orchestrator/decision-parser.js.map +1 -0
  22. package/dist/orchestrator/index.d.ts +4 -2
  23. package/dist/orchestrator/index.d.ts.map +1 -1
  24. package/dist/orchestrator/index.js +15 -2
  25. package/dist/orchestrator/index.js.map +1 -1
  26. package/dist/orchestrator/orchestrator-agent.d.ts +17 -22
  27. package/dist/orchestrator/orchestrator-agent.d.ts.map +1 -1
  28. package/dist/orchestrator/orchestrator-agent.js +708 -577
  29. package/dist/orchestrator/orchestrator-agent.js.map +1 -1
  30. package/dist/orchestrator/orchestrator-prompts.d.ts +32 -0
  31. package/dist/orchestrator/orchestrator-prompts.d.ts.map +1 -0
  32. package/dist/orchestrator/orchestrator-prompts.js +737 -0
  33. package/dist/orchestrator/orchestrator-prompts.js.map +1 -0
  34. package/dist/orchestrator/page-som-handler.d.ts +106 -0
  35. package/dist/orchestrator/page-som-handler.d.ts.map +1 -0
  36. package/dist/orchestrator/page-som-handler.js +1353 -0
  37. package/dist/orchestrator/page-som-handler.js.map +1 -0
  38. package/dist/orchestrator/som-types.d.ts +149 -0
  39. package/dist/orchestrator/som-types.d.ts.map +1 -0
  40. package/dist/orchestrator/som-types.js +87 -0
  41. package/dist/orchestrator/som-types.js.map +1 -0
  42. package/dist/orchestrator/tool-registry.d.ts +2 -0
  43. package/dist/orchestrator/tool-registry.d.ts.map +1 -1
  44. package/dist/orchestrator/tool-registry.js.map +1 -1
  45. package/dist/orchestrator/tools/index.d.ts +5 -1
  46. package/dist/orchestrator/tools/index.d.ts.map +1 -1
  47. package/dist/orchestrator/tools/index.js +9 -2
  48. package/dist/orchestrator/tools/index.js.map +1 -1
  49. package/dist/orchestrator/tools/refresh-som-markers.d.ts +12 -0
  50. package/dist/orchestrator/tools/refresh-som-markers.d.ts.map +1 -0
  51. package/dist/orchestrator/tools/refresh-som-markers.js +64 -0
  52. package/dist/orchestrator/tools/refresh-som-markers.js.map +1 -0
  53. package/dist/orchestrator/tools/verify-action-result.d.ts +17 -0
  54. package/dist/orchestrator/tools/verify-action-result.d.ts.map +1 -0
  55. package/dist/orchestrator/tools/verify-action-result.js +140 -0
  56. package/dist/orchestrator/tools/verify-action-result.js.map +1 -0
  57. package/dist/orchestrator/tools/view-previous-screenshot.d.ts +15 -0
  58. package/dist/orchestrator/tools/view-previous-screenshot.d.ts.map +1 -0
  59. package/dist/orchestrator/tools/view-previous-screenshot.js +92 -0
  60. package/dist/orchestrator/tools/view-previous-screenshot.js.map +1 -0
  61. package/dist/orchestrator/types.d.ts +49 -1
  62. package/dist/orchestrator/types.d.ts.map +1 -1
  63. package/dist/orchestrator/types.js +11 -1
  64. package/dist/orchestrator/types.js.map +1 -1
  65. package/dist/prompts.d.ts.map +1 -1
  66. package/dist/prompts.js +40 -34
  67. package/dist/prompts.js.map +1 -1
  68. package/dist/scenario-service.d.ts +5 -0
  69. package/dist/scenario-service.d.ts.map +1 -1
  70. package/dist/scenario-service.js +17 -0
  71. package/dist/scenario-service.js.map +1 -1
  72. package/dist/scenario-worker-class.d.ts +4 -0
  73. package/dist/scenario-worker-class.d.ts.map +1 -1
  74. package/dist/scenario-worker-class.js +21 -3
  75. package/dist/scenario-worker-class.js.map +1 -1
  76. package/dist/testing/agent-tester.d.ts +35 -0
  77. package/dist/testing/agent-tester.d.ts.map +1 -0
  78. package/dist/testing/agent-tester.js +84 -0
  79. package/dist/testing/agent-tester.js.map +1 -0
  80. package/dist/testing/ref-translator-tester.d.ts +44 -0
  81. package/dist/testing/ref-translator-tester.d.ts.map +1 -0
  82. package/dist/testing/ref-translator-tester.js +104 -0
  83. package/dist/testing/ref-translator-tester.js.map +1 -0
  84. package/dist/utils/coordinate-converter.d.ts +32 -0
  85. package/dist/utils/coordinate-converter.d.ts.map +1 -0
  86. package/dist/utils/coordinate-converter.js +130 -0
  87. package/dist/utils/coordinate-converter.js.map +1 -0
  88. package/dist/utils/hierarchical-selector.d.ts +47 -0
  89. package/dist/utils/hierarchical-selector.d.ts.map +1 -0
  90. package/dist/utils/hierarchical-selector.js +212 -0
  91. package/dist/utils/hierarchical-selector.js.map +1 -0
  92. package/dist/utils/page-info-retry.d.ts +14 -0
  93. package/dist/utils/page-info-retry.d.ts.map +1 -0
  94. package/dist/utils/page-info-retry.js +60 -0
  95. package/dist/utils/page-info-retry.js.map +1 -0
  96. package/dist/utils/page-info-utils.d.ts +1 -0
  97. package/dist/utils/page-info-utils.d.ts.map +1 -1
  98. package/dist/utils/page-info-utils.js +46 -18
  99. package/dist/utils/page-info-utils.js.map +1 -1
  100. package/dist/utils/ref-attacher.d.ts +21 -0
  101. package/dist/utils/ref-attacher.d.ts.map +1 -0
  102. package/dist/utils/ref-attacher.js +149 -0
  103. package/dist/utils/ref-attacher.js.map +1 -0
  104. package/dist/utils/ref-translator.d.ts +49 -0
  105. package/dist/utils/ref-translator.d.ts.map +1 -0
  106. package/dist/utils/ref-translator.js +276 -0
  107. package/dist/utils/ref-translator.js.map +1 -0
  108. package/package.json +1 -1
  109. package/plandocs/BEFORE_AFTER_VERIFICATION.md +148 -0
  110. package/plandocs/COORDINATE_MODE_DIAGNOSIS.md +144 -0
  111. package/plandocs/IMPLEMENTATION_STATUS.md +108 -0
  112. package/plandocs/PHASE_1_COMPLETE.md +165 -0
  113. package/plandocs/PHASE_1_SUMMARY.md +184 -0
  114. package/plandocs/PROMPT_OPTIMIZATION_ANALYSIS.md +120 -0
  115. package/plandocs/PROMPT_SANITY_CHECK.md +120 -0
  116. package/plandocs/SESSION_SUMMARY_v0.0.33.md +151 -0
  117. package/plandocs/TROUBLESHOOTING_SESSION.md +72 -0
  118. package/plandocs/VISUAL_AGENT_EVOLUTION_PLAN.md +396 -0
  119. package/plandocs/WHATS_NEW_v0.0.33.md +183 -0
  120. package/plandocs/exploratory-mode-support-v2.plan.md +953 -0
  121. package/plandocs/exploratory-mode-support.plan.md +928 -0
  122. package/plandocs/journey-id-tracking-addendum.md +227 -0
  123. package/src/execution-service.ts +179 -596
  124. package/src/index.ts +10 -0
  125. package/src/llm-facade.ts +8 -8
  126. package/src/llm-provider.ts +11 -1
  127. package/src/model-constants.ts +17 -5
  128. package/src/orchestrator/decision-parser.ts +139 -0
  129. package/src/orchestrator/index.ts +27 -2
  130. package/src/orchestrator/orchestrator-agent.ts +868 -623
  131. package/src/orchestrator/orchestrator-prompts.ts +786 -0
  132. package/src/orchestrator/page-som-handler.ts +1565 -0
  133. package/src/orchestrator/som-types.ts +188 -0
  134. package/src/orchestrator/tool-registry.ts +2 -0
  135. package/src/orchestrator/tools/index.ts +5 -1
  136. package/src/orchestrator/tools/refresh-som-markers.ts +69 -0
  137. package/src/orchestrator/tools/verify-action-result.ts +159 -0
  138. package/src/orchestrator/tools/view-previous-screenshot.ts +103 -0
  139. package/src/orchestrator/types.ts +95 -4
  140. package/src/prompts.ts +40 -34
  141. package/src/scenario-service.ts +20 -0
  142. package/src/scenario-worker-class.ts +30 -4
  143. package/src/utils/coordinate-converter.ts +162 -0
  144. package/src/utils/page-info-retry.ts +65 -0
  145. package/src/utils/page-info-utils.ts +53 -18
  146. package/testchimp-runner-core-0.0.35.tgz +0 -0
  147. /package/{CREDIT_CALLBACK_ARCHITECTURE.md → plandocs/CREDIT_CALLBACK_ARCHITECTURE.md} +0 -0
  148. /package/{INTEGRATION_COMPLETE.md → plandocs/INTEGRATION_COMPLETE.md} +0 -0
  149. /package/{VISION_DIAGNOSTICS_IMPROVEMENTS.md → plandocs/VISION_DIAGNOSTICS_IMPROVEMENTS.md} +0 -0
  150. /package/{RELEASE_0.0.26.md → releasenotes/RELEASE_0.0.26.md} +0 -0
  151. /package/{RELEASE_0.0.27.md → releasenotes/RELEASE_0.0.27.md} +0 -0
  152. /package/{RELEASE_0.0.28.md → releasenotes/RELEASE_0.0.28.md} +0 -0
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ /**
3
+ * Coordinate Converter Utility
4
+ * Converts percentage-based coordinates to pixel coordinates and generates Playwright commands
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.CoordinateConverter = void 0;
8
+ class CoordinateConverter {
9
+ /**
10
+ * Convert percentage coordinates to actual pixel coordinates
11
+ */
12
+ static percentToPixels(xPercent, yPercent, viewportWidth, viewportHeight) {
13
+ return {
14
+ x: Math.round((xPercent / 100) * viewportWidth),
15
+ y: Math.round((yPercent / 100) * viewportHeight)
16
+ };
17
+ }
18
+ /**
19
+ * Get viewport dimensions from page
20
+ */
21
+ static async getViewportSize(page) {
22
+ return await page.evaluate(() => {
23
+ const win = globalThis.window;
24
+ return {
25
+ width: win.innerWidth,
26
+ height: win.innerHeight
27
+ };
28
+ });
29
+ }
30
+ /**
31
+ * Convert coordinate action with percentages to Playwright commands
32
+ * Returns array of command strings
33
+ */
34
+ static async generateCommands(action, page) {
35
+ const viewport = await this.getViewportSize(page);
36
+ const { x, y } = this.percentToPixels(action.xPercent, action.yPercent, viewport.width, viewport.height);
37
+ const commands = [];
38
+ switch (action.action) {
39
+ case 'click':
40
+ commands.push(`await page.mouse.click(${x}, ${y});`);
41
+ break;
42
+ case 'doubleClick':
43
+ commands.push(`await page.mouse.dblclick(${x}, ${y});`);
44
+ break;
45
+ case 'rightClick':
46
+ commands.push(`await page.mouse.click(${x}, ${y}, { button: 'right' });`);
47
+ break;
48
+ case 'hover':
49
+ commands.push(`await page.mouse.move(${x}, ${y});`);
50
+ break;
51
+ case 'drag':
52
+ if (action.toXPercent === undefined || action.toYPercent === undefined) {
53
+ throw new Error('Drag action requires toXPercent and toYPercent');
54
+ }
55
+ const to = this.percentToPixels(action.toXPercent, action.toYPercent, viewport.width, viewport.height);
56
+ commands.push(`await page.mouse.move(${x}, ${y});`);
57
+ commands.push(`await page.mouse.down();`);
58
+ commands.push(`await page.mouse.move(${to.x}, ${to.y});`);
59
+ commands.push(`await page.mouse.up();`);
60
+ break;
61
+ case 'fill':
62
+ if (!action.value) {
63
+ throw new Error('Fill action requires value');
64
+ }
65
+ // Click to focus, wait briefly, then type
66
+ commands.push(`await page.mouse.click(${x}, ${y});`);
67
+ commands.push(`await page.waitForTimeout(100);`);
68
+ commands.push(`await page.keyboard.type(${JSON.stringify(action.value)});`);
69
+ break;
70
+ case 'scroll':
71
+ const scrollAmount = action.scrollAmount || 100;
72
+ // Move to position, then scroll
73
+ commands.push(`await page.mouse.move(${x}, ${y});`);
74
+ commands.push(`await page.mouse.wheel(0, ${scrollAmount});`);
75
+ break;
76
+ default:
77
+ throw new Error(`Unknown coordinate action: ${action.action}`);
78
+ }
79
+ return commands;
80
+ }
81
+ /**
82
+ * Execute coordinate action directly on page
83
+ * Used during agent execution (converts and runs immediately)
84
+ */
85
+ static async executeAction(action, page) {
86
+ const viewport = await this.getViewportSize(page);
87
+ const { x, y } = this.percentToPixels(action.xPercent, action.yPercent, viewport.width, viewport.height);
88
+ switch (action.action) {
89
+ case 'click':
90
+ await page.mouse.click(x, y);
91
+ break;
92
+ case 'doubleClick':
93
+ await page.mouse.dblclick(x, y);
94
+ break;
95
+ case 'rightClick':
96
+ await page.mouse.click(x, y, { button: 'right' });
97
+ break;
98
+ case 'hover':
99
+ await page.mouse.move(x, y);
100
+ break;
101
+ case 'drag':
102
+ if (action.toXPercent === undefined || action.toYPercent === undefined) {
103
+ throw new Error('Drag requires toXPercent and toYPercent');
104
+ }
105
+ const to = this.percentToPixels(action.toXPercent, action.toYPercent, viewport.width, viewport.height);
106
+ await page.mouse.move(x, y);
107
+ await page.mouse.down();
108
+ await page.mouse.move(to.x, to.y);
109
+ await page.mouse.up();
110
+ break;
111
+ case 'fill':
112
+ if (!action.value) {
113
+ throw new Error('Fill requires value');
114
+ }
115
+ await page.mouse.click(x, y);
116
+ await page.waitForTimeout(100);
117
+ await page.keyboard.type(action.value);
118
+ break;
119
+ case 'scroll':
120
+ const scrollAmount = action.scrollAmount || 100;
121
+ await page.mouse.move(x, y);
122
+ await page.mouse.wheel(0, scrollAmount);
123
+ break;
124
+ default:
125
+ throw new Error(`Unknown coordinate action: ${action.action}`);
126
+ }
127
+ }
128
+ }
129
+ exports.CoordinateConverter = CoordinateConverter;
130
+ //# sourceMappingURL=coordinate-converter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coordinate-converter.js","sourceRoot":"","sources":["../../src/utils/coordinate-converter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Hierarchical Selector Generator
3
+ * Generates Playwright selectors using ARIA tree hierarchy for uniqueness
4
+ * Mimics Playwright's own selector generation strategy
5
+ */
6
+ export interface AriaNode {
7
+ role: string;
8
+ name?: string;
9
+ ref?: string;
10
+ children?: AriaNode[];
11
+ }
12
+ export interface AriaTreePath {
13
+ nodes: AriaNode[];
14
+ targetIndex: number;
15
+ }
16
+ export declare class HierarchicalSelectorGenerator {
17
+ private logger?;
18
+ constructor(logger?: (message: string, level?: 'log' | 'error' | 'warn') => void);
19
+ /**
20
+ * Generate Playwright selectors for a ref using ARIA tree hierarchy
21
+ * Returns selectors in priority order, considering parent context for uniqueness
22
+ */
23
+ generateSelectorsForRef(ref: string, refMap: Map<string, any>, ariaTree: any): string[];
24
+ /**
25
+ * Strategy 1: Unique attributes (most reliable)
26
+ */
27
+ private generateUniqueAttributeSelectors;
28
+ /**
29
+ * Strategy 2: Parent-scoped selectors (uses hierarchy for uniqueness)
30
+ */
31
+ private generateParentScopedSelectors;
32
+ /**
33
+ * Strategy 3: nth-match based on sibling position in ARIA tree
34
+ */
35
+ private generateNthMatchSelectors;
36
+ /**
37
+ * Strategy 4: Generic fallbacks (last resort)
38
+ */
39
+ private generateGenericFallbacks;
40
+ /**
41
+ * Walk ARIA tree to find path from root to target ref
42
+ */
43
+ private getAriaTreePath;
44
+ private walkTree;
45
+ private escape;
46
+ }
47
+ //# sourceMappingURL=hierarchical-selector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hierarchical-selector.d.ts","sourceRoot":"","sources":["../../src/utils/hierarchical-selector.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,6BAA6B;IACxC,OAAO,CAAC,MAAM,CAAC,CAA8D;gBAEjE,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,MAAM,KAAK,IAAI;IAIhF;;;OAGG;IACH,uBAAuB,CACrB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,EACxB,QAAQ,EAAE,GAAG,GACZ,MAAM,EAAE;IA+BX;;OAEG;IACH,OAAO,CAAC,gCAAgC;IAgCxC;;OAEG;IACH,OAAO,CAAC,6BAA6B;IA4CrC;;OAEG;IACH,OAAO,CAAC,yBAAyB;IA2CjC;;OAEG;IACH,OAAO,CAAC,wBAAwB;IA2BhC;;OAEG;IACH,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,QAAQ;IAyBhB,OAAO,CAAC,MAAM;CAGf"}
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ /**
3
+ * Hierarchical Selector Generator
4
+ * Generates Playwright selectors using ARIA tree hierarchy for uniqueness
5
+ * Mimics Playwright's own selector generation strategy
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.HierarchicalSelectorGenerator = void 0;
9
+ class HierarchicalSelectorGenerator {
10
+ constructor(logger) {
11
+ this.logger = logger;
12
+ }
13
+ /**
14
+ * Generate Playwright selectors for a ref using ARIA tree hierarchy
15
+ * Returns selectors in priority order, considering parent context for uniqueness
16
+ */
17
+ generateSelectorsForRef(ref, refMap, ariaTree) {
18
+ const element = refMap.get(ref);
19
+ if (!element) {
20
+ this.logger?.(`[HierarchicalSelector] Ref '${ref}' not found in refMap`, 'warn');
21
+ return [];
22
+ }
23
+ // Get ARIA tree path to understand hierarchy
24
+ const path = this.getAriaTreePath(ariaTree, ref);
25
+ const selectors = [];
26
+ // Strategy 1: BEST - Unique attribute (name, id, data-testid)
27
+ selectors.push(...this.generateUniqueAttributeSelectors(element));
28
+ // Strategy 2: GOOD - Parent-scoped semantic selectors
29
+ if (path) {
30
+ selectors.push(...this.generateParentScopedSelectors(element, path));
31
+ }
32
+ // Strategy 3: FALLBACK - nth-match based on sibling position
33
+ if (path) {
34
+ selectors.push(...this.generateNthMatchSelectors(element, path, ref));
35
+ }
36
+ // Strategy 4: LAST RESORT - Generic selectors with first()
37
+ selectors.push(...this.generateGenericFallbacks(element));
38
+ return selectors;
39
+ }
40
+ /**
41
+ * Strategy 1: Unique attributes (most reliable)
42
+ */
43
+ generateUniqueAttributeSelectors(element) {
44
+ const selectors = [];
45
+ const { tag, name, id, ariaLabel, placeholder, type } = element;
46
+ // name attribute (best for forms)
47
+ if (name && (tag === 'input' || tag === 'textarea' || tag === 'select')) {
48
+ selectors.push(`${tag}[name="${name}"]`);
49
+ }
50
+ // data-testid
51
+ if (element.dataTestId) {
52
+ selectors.push(`[data-testid="${element.dataTestId}"]`);
53
+ }
54
+ // ID (only if stable - avoid auto-generated)
55
+ if (id && !id.match(/^(rc_|:r[0-9]+:|__|«)/)) {
56
+ selectors.push(`#${id}`);
57
+ }
58
+ // aria-label (semantic)
59
+ if (ariaLabel && ariaLabel.length > 0 && ariaLabel.length < 50) {
60
+ selectors.push(`[aria-label="${this.escape(ariaLabel)}"]`);
61
+ }
62
+ // placeholder
63
+ if (placeholder && placeholder.length > 0) {
64
+ selectors.push(`[placeholder="${this.escape(placeholder)}"]`);
65
+ }
66
+ return selectors;
67
+ }
68
+ /**
69
+ * Strategy 2: Parent-scoped selectors (uses hierarchy for uniqueness)
70
+ */
71
+ generateParentScopedSelectors(element, path) {
72
+ const selectors = [];
73
+ const { role, text, tag } = element;
74
+ const nodes = path.nodes;
75
+ if (nodes.length < 2)
76
+ return selectors; // Need at least parent + target
77
+ // Get parent and grandparent for context
78
+ const parent = nodes[nodes.length - 2];
79
+ const grandparent = nodes.length >= 3 ? nodes[nodes.length - 3] : null;
80
+ // Parent has text content - use it for scoping
81
+ if (parent.name && parent.name.length > 0 && parent.name.length < 50) {
82
+ // Approach 1: Scope by parent text, then find by role
83
+ if (role) {
84
+ selectors.push(`text=${parent.name} >> [role="${role}"]`);
85
+ }
86
+ // Approach 2: Scope by parent text, then find by tag
87
+ if (tag) {
88
+ selectors.push(`text=${parent.name} >> ${tag}`);
89
+ }
90
+ // Approach 3: Use getByText parent chaining (Playwright style)
91
+ if (text && text.length > 0) {
92
+ selectors.push(`text=${parent.name} >> text=${text}`);
93
+ }
94
+ }
95
+ // Grandparent scoping (for nested structures)
96
+ if (grandparent && grandparent.name && grandparent.name.length > 0) {
97
+ if (role) {
98
+ selectors.push(`text=${grandparent.name} >> [role="${role}"]`);
99
+ }
100
+ }
101
+ // Text content with role (if element has visible text)
102
+ if (text && text.length > 0 && text.length < 30 && role) {
103
+ selectors.push(`[role="${role}"][aria-label*="${this.escape(text)}"]`);
104
+ }
105
+ return selectors;
106
+ }
107
+ /**
108
+ * Strategy 3: nth-match based on sibling position in ARIA tree
109
+ */
110
+ generateNthMatchSelectors(element, path, ref) {
111
+ const selectors = [];
112
+ const { role, tag, text } = element;
113
+ const nodes = path.nodes;
114
+ if (nodes.length < 2)
115
+ return selectors;
116
+ const parent = nodes[nodes.length - 2];
117
+ // Find position among siblings with same role
118
+ const siblingsWithSameRole = (parent.children || []).filter((child) => child.role === role);
119
+ const position = siblingsWithSameRole.findIndex((child) => child.ref === ref);
120
+ if (position >= 0) {
121
+ // nth-match by role
122
+ if (role) {
123
+ selectors.push(`[role="${role}"] >> nth=${position}`);
124
+ // Playwright's getByRole with nth
125
+ selectors.push(`getByRole('${role}').nth(${position})`);
126
+ }
127
+ // nth-match by tag
128
+ if (tag) {
129
+ selectors.push(`${tag} >> nth=${position}`);
130
+ selectors.push(`locator('${tag}').nth(${position})`);
131
+ }
132
+ // Special case: first or last
133
+ if (position === 0) {
134
+ if (role)
135
+ selectors.push(`[role="${role}"] >> first`);
136
+ if (tag)
137
+ selectors.push(`${tag} >> first`);
138
+ }
139
+ else if (position === siblingsWithSameRole.length - 1) {
140
+ if (role)
141
+ selectors.push(`[role="${role}"] >> last`);
142
+ if (tag)
143
+ selectors.push(`${tag} >> last`);
144
+ }
145
+ }
146
+ return selectors;
147
+ }
148
+ /**
149
+ * Strategy 4: Generic fallbacks (last resort)
150
+ */
151
+ generateGenericFallbacks(element) {
152
+ const selectors = [];
153
+ const { tag, role, text, type } = element;
154
+ // Text content
155
+ if (text && text.length > 0 && text.length < 30) {
156
+ selectors.push(`text=${this.escape(text)}`);
157
+ selectors.push(`getByText('${this.escape(text)}')`);
158
+ }
159
+ // Role only (use first())
160
+ if (role) {
161
+ selectors.push(`[role="${role}"] >> first`);
162
+ selectors.push(`getByRole('${role}').first()`);
163
+ }
164
+ // Type selector
165
+ if (type) {
166
+ selectors.push(`${tag}[type="${type}"] >> first`);
167
+ }
168
+ // Ultimate fallback
169
+ selectors.push(`${tag} >> first`);
170
+ return selectors;
171
+ }
172
+ /**
173
+ * Walk ARIA tree to find path from root to target ref
174
+ */
175
+ getAriaTreePath(tree, targetRef) {
176
+ const path = [];
177
+ const found = this.walkTree(tree, targetRef, path);
178
+ if (found) {
179
+ return {
180
+ nodes: path,
181
+ targetIndex: path.length - 1
182
+ };
183
+ }
184
+ return null;
185
+ }
186
+ walkTree(node, targetRef, path) {
187
+ if (!node)
188
+ return false;
189
+ // Add current node to path
190
+ path.push(node);
191
+ // Found it!
192
+ if (node.ref === targetRef) {
193
+ return true;
194
+ }
195
+ // Search children
196
+ if (node.children && Array.isArray(node.children)) {
197
+ for (const child of node.children) {
198
+ if (this.walkTree(child, targetRef, path)) {
199
+ return true; // Found in this subtree
200
+ }
201
+ }
202
+ }
203
+ // Not found in this subtree - backtrack
204
+ path.pop();
205
+ return false;
206
+ }
207
+ escape(str) {
208
+ return str.replace(/'/g, "\\'").replace(/"/g, '\\"');
209
+ }
210
+ }
211
+ exports.HierarchicalSelectorGenerator = HierarchicalSelectorGenerator;
212
+ //# sourceMappingURL=hierarchical-selector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hierarchical-selector.js","sourceRoot":"","sources":["../../src/utils/hierarchical-selector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Page Info Retry Utility
3
+ * Handles adaptive page loading with exponential backoff
4
+ */
5
+ import { PageInfo } from './page-info-utils';
6
+ export declare class PageInfoRetry {
7
+ /**
8
+ * Get page info with retry logic - waits for interactive elements to appear
9
+ * Uses exponential backoff to handle slow-loading React/Vue/Angular apps
10
+ */
11
+ static getWithRetry(page: any, maxAttempts?: number): Promise<PageInfo>;
12
+ private static calculateTotalWaitTime;
13
+ }
14
+ //# sourceMappingURL=page-info-retry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-info-retry.d.ts","sourceRoot":"","sources":["../../src/utils/page-info-retry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAuB,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElE,qBAAa,aAAa;IACxB;;;OAGG;WACU,YAAY,CAAC,IAAI,EAAE,GAAG,EAAE,WAAW,GAAE,MAAU,GAAG,OAAO,CAAC,QAAQ,CAAC;IA0ChF,OAAO,CAAC,MAAM,CAAC,sBAAsB;CAStC"}
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ /**
3
+ * Page Info Retry Utility
4
+ * Handles adaptive page loading with exponential backoff
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.PageInfoRetry = void 0;
8
+ const page_info_utils_1 = require("./page-info-utils");
9
+ class PageInfoRetry {
10
+ /**
11
+ * Get page info with retry logic - waits for interactive elements to appear
12
+ * Uses exponential backoff to handle slow-loading React/Vue/Angular apps
13
+ */
14
+ static async getWithRetry(page, maxAttempts = 6) {
15
+ // Wait for initial page load (generous timeout for slow apps)
16
+ try {
17
+ await page.waitForLoadState('domcontentloaded', { timeout: 20000 }).catch(() => { });
18
+ }
19
+ catch (waitError) {
20
+ // Continue even if wait fails
21
+ }
22
+ let attempt = 0;
23
+ let backoffMs = 1000; // Start with 1 second (adequate for most sites)
24
+ while (attempt < maxAttempts) {
25
+ attempt++;
26
+ // Try to extract page info
27
+ const pageInfo = await (0, page_info_utils_1.getEnhancedPageInfo)(page);
28
+ // If we got a reasonable number of elements, we're done
29
+ if (pageInfo.interactiveElements && pageInfo.interactiveElements.length >= 3) {
30
+ if (attempt > 1) {
31
+ console.log(`[PageInfoRetry] ✓ Page elements loaded after ${attempt} attempts`);
32
+ }
33
+ return pageInfo;
34
+ }
35
+ // If this is the last attempt, return what we have
36
+ if (attempt >= maxAttempts) {
37
+ const totalWait = this.calculateTotalWaitTime(maxAttempts);
38
+ console.log(`[PageInfoRetry] ⚠️ Only found ${pageInfo.interactiveElements?.length || 0} elements after ${maxAttempts} attempts (total wait: ~${totalWait}ms)`);
39
+ return pageInfo;
40
+ }
41
+ // Wait with exponential backoff before retrying
42
+ console.log(`[PageInfoRetry] Only ${pageInfo.interactiveElements?.length || 0} elements found (attempt ${attempt}/${maxAttempts}), waiting ${backoffMs}ms...`);
43
+ await page.waitForTimeout(backoffMs);
44
+ backoffMs = Math.min(backoffMs * 1.6, 15000); // Cap at 15 seconds per attempt
45
+ }
46
+ // Fallback (shouldn't reach here, but for type safety)
47
+ return await (0, page_info_utils_1.getEnhancedPageInfo)(page);
48
+ }
49
+ static calculateTotalWaitTime(maxAttempts) {
50
+ let total = 0;
51
+ let backoffMs = 1000;
52
+ for (let i = 1; i < maxAttempts; i++) {
53
+ total += backoffMs;
54
+ backoffMs = Math.min(backoffMs * 1.6, 15000);
55
+ }
56
+ return Math.round(total);
57
+ }
58
+ }
59
+ exports.PageInfoRetry = PageInfoRetry;
60
+ //# sourceMappingURL=page-info-retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-info-retry.js","sourceRoot":"","sources":["../../src/utils/page-info-retry.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,uDAAkE;AAElE,MAAa,aAAa;IACxB;;;OAGG;IACH,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,IAAS,EAAE,cAAsB,CAAC;QAC1D,8DAA8D;QAC9D,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACtF,CAAC;QAAC,OAAO,SAAS,EAAE,CAAC;YACnB,8BAA8B;QAChC,CAAC;QAED,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,IAAI,SAAS,GAAG,IAAI,CAAC,CAAC,gDAAgD;QAEtE,OAAO,OAAO,GAAG,WAAW,EAAE,CAAC;YAC7B,OAAO,EAAE,CAAC;YAEV,2BAA2B;YAC3B,MAAM,QAAQ,GAAG,MAAM,IAAA,qCAAmB,EAAC,IAAI,CAAC,CAAC;YAEjD,wDAAwD;YACxD,IAAI,QAAQ,CAAC,mBAAmB,IAAI,QAAQ,CAAC,mBAAmB,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;gBAC7E,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;oBAChB,OAAO,CAAC,GAAG,CAAC,gDAAgD,OAAO,WAAW,CAAC,CAAC;gBAClF,CAAC;gBACD,OAAO,QAAQ,CAAC;YAClB,CAAC;YAED,mDAAmD;YACnD,IAAI,OAAO,IAAI,WAAW,EAAE,CAAC;gBAC3B,MAAM,SAAS,GAAG,IAAI,CAAC,sBAAsB,CAAC,WAAW,CAAC,CAAC;gBAC3D,OAAO,CAAC,GAAG,CAAC,kCAAkC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,IAAI,CAAC,mBAAmB,WAAW,2BAA2B,SAAS,KAAK,CAAC,CAAC;gBAChK,OAAO,QAAQ,CAAC;YAClB,CAAC;YAED,gDAAgD;YAChD,OAAO,CAAC,GAAG,CAAC,wBAAwB,QAAQ,CAAC,mBAAmB,EAAE,MAAM,IAAI,CAAC,4BAA4B,OAAO,IAAI,WAAW,cAAc,SAAS,OAAO,CAAC,CAAC;YAC/J,MAAM,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;YACrC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,GAAG,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,gCAAgC;QAChF,CAAC;QAED,uDAAuD;QACvD,OAAO,MAAM,IAAA,qCAAmB,EAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IAEO,MAAM,CAAC,sBAAsB,CAAC,WAAmB;QACvD,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,SAAS,GAAG,IAAI,CAAC;QACrB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,KAAK,IAAI,SAAS,CAAC;YACnB,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,GAAG,GAAG,EAAE,KAAK,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;CACF;AAxDD,sCAwDC"}
@@ -7,6 +7,7 @@ export interface InteractiveElement {
7
7
  id: string | null;
8
8
  text: string;
9
9
  type: string;
10
+ name: string;
10
11
  role: string;
11
12
  ariaLabel: string;
12
13
  placeholder: string;
@@ -1 +1 @@
1
- {"version":3,"file":"page-info-utils.d.ts","sourceRoot":"","sources":["../../src/utils/page-info-utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,CAAC;IAC5D,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,GAAG,CAAC;IAClB,mBAAmB,EAAE,kBAAkB,EAAE,CAAC;IAC1C,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AACxE,wBAAsB,mBAAmB,CAAC,WAAW,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,GAAG,CAAA;CAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC"}
1
+ {"version":3,"file":"page-info-utils.d.ts","sourceRoot":"","sources":["../../src/utils/page-info-utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,CAAC;IAC5D,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,GAAG,CAAC;IAClB,mBAAmB,EAAE,kBAAkB,EAAE,CAAC;IAC1C,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AACxE,wBAAsB,mBAAmB,CAAC,WAAW,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,GAAG,CAAA;CAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC"}
@@ -13,7 +13,9 @@ async function getEnhancedPageInfo(input) {
13
13
  if ('accessibility' in input) {
14
14
  // Input is a Page object
15
15
  hasPageObject = true;
16
- const snapshot = await input.accessibility.snapshot();
16
+ // CRITICAL: Use interestingOnly: false to get FULL ARIA tree like Playwright MCP
17
+ // This includes all structural nodes (generic, paragraph, etc.) not just interactive ones
18
+ const snapshot = await input.accessibility.snapshot({ interestingOnly: false });
17
19
  const url = input.url();
18
20
  const title = await input.title();
19
21
  domSnapshot = { url, title, accessibilityTree: snapshot };
@@ -34,17 +36,34 @@ async function getEnhancedPageInfo(input) {
34
36
  '[role="textbox"]',
35
37
  '[role="checkbox"]',
36
38
  '[role="radio"]',
37
- '[onclick]'
39
+ '[onclick]',
40
+ '[type="submit"]'
38
41
  ];
39
42
  const allInteractive = new Set();
40
43
  interactiveSelectors.forEach((selector) => {
41
44
  doc.querySelectorAll(selector).forEach((el) => allInteractive.add(el));
42
45
  });
46
+ // CRITICAL: Also detect elements that are styled/behave as interactive
47
+ // (e.g., divs/spans with cursor:pointer, click handlers)
48
+ // This catches modern web apps that use non-semantic HTML
49
+ const allElements = doc.querySelectorAll('div, span, p, li, td');
50
+ allElements.forEach((el) => {
51
+ const styles = globalThis.window.getComputedStyle(el);
52
+ const hasClickHandler = el.onclick ||
53
+ el.getAttribute('onclick') ||
54
+ el.hasAttribute('data-action') ||
55
+ el.hasAttribute('data-click');
56
+ // Include if: cursor is pointer, has click handler, or has tabindex (keyboard focusable)
57
+ if (styles.cursor === 'pointer' || hasClickHandler || el.tabIndex >= 0) {
58
+ allInteractive.add(el);
59
+ }
60
+ });
43
61
  allInteractive.forEach((el) => {
44
62
  const rect = el.getBoundingClientRect();
45
63
  // Skip invisible elements
46
- if (rect.width === 0 || rect.height === 0)
64
+ if (rect.width === 0 || rect.height === 0) {
47
65
  return;
66
+ }
48
67
  const id = el.id || null;
49
68
  const tag = el.tagName.toLowerCase();
50
69
  const text = el.textContent?.trim().substring(0, 40) || '';
@@ -65,6 +84,11 @@ async function getEnhancedPageInfo(input) {
65
84
  if (text && text.length > 0 && text.length < 30) {
66
85
  selectors.push(`getByRole('${role}', {name: '${text}'})`);
67
86
  }
87
+ // 2b. BEST: getByRole for buttons with type attribute (more specific)
88
+ if (tag === 'button' && el.type) {
89
+ selectors.push(`getByRole('button', {name: '${text}'})`);
90
+ selectors.push(`button[type="${el.type}"]`);
91
+ }
68
92
  // 3. BEST: getByPlaceholder for inputs with placeholders
69
93
  if (placeholder && placeholder.length > 0 && placeholder.length < 50) {
70
94
  selectors.push(`getByPlaceholder('${placeholder}')`);
@@ -78,7 +102,19 @@ async function getEnhancedPageInfo(input) {
78
102
  el.getAttribute('data-id') || el.getAttribute('data-test');
79
103
  if (dataTestId)
80
104
  selectors.push(`getByTestId('${dataTestId}')`);
81
- // 6. LAST RESORT: ID selector (only if stable - avoid auto-generated IDs with unicode)
105
+ // 6. GOOD: name attribute (especially for form inputs - very common and stable)
106
+ const nameAttr = el.getAttribute('name');
107
+ if (nameAttr && (tag === 'input' || tag === 'textarea' || tag === 'select')) {
108
+ selectors.push(`input[name="${nameAttr}"]`);
109
+ }
110
+ // 6b. GOOD: class-based selectors for buttons (common in modern frameworks)
111
+ if (tag === 'button' && el.className) {
112
+ const classes = el.className.split(' ').filter((c) => c.length > 0 && !c.includes('userfront-'));
113
+ if (classes.length > 0) {
114
+ selectors.push(`button.${classes[0]}`);
115
+ }
116
+ }
117
+ // 7. LAST RESORT: ID selector (only if stable - avoid auto-generated IDs with unicode)
82
118
  // Skip auto-generated Ant Design IDs like «r3»-form-item or rc_select_xxx
83
119
  if (id && !id.includes('«') && !id.match(/^(rc_|:r[0-9]+:|__)/)) {
84
120
  selectors.push(`#${id}`);
@@ -88,6 +124,7 @@ async function getEnhancedPageInfo(input) {
88
124
  id,
89
125
  text,
90
126
  type,
127
+ name: nameAttr || '', // CRITICAL: Capture name attribute for form inputs
91
128
  role,
92
129
  ariaLabel,
93
130
  placeholder,
@@ -144,22 +181,13 @@ async function getEnhancedPageInfo(input) {
144
181
  }
145
182
  }
146
183
  formattedElements = selectedElements.map((e, i) => {
147
- const idInfo = e.id ? `#${e.id}` : 'NO-ID';
184
+ const idInfo = e.id ? `#${e.id}` : '';
148
185
  const typeInfo = e.type ? ` type="${e.type}"` : '';
186
+ const nameInfo = e.name ? ` name="${e.name}"` : '';
149
187
  const placeholderInfo = e.placeholder ? ` placeholder="${e.placeholder}"` : '';
150
- // Show BEST selector first, with alternatives
151
- let selectorsInfo;
152
- if (e.selectors && e.selectors.length > 0) {
153
- selectorsInfo = e.selectors[0]; // Best option
154
- if (e.selectors.length > 1) {
155
- selectorsInfo += ` (or: ${e.selectors.slice(1, 3).join(', ')})`; // Show up to 2 alternatives
156
- }
157
- }
158
- else {
159
- selectorsInfo = 'No reliable selector available';
160
- }
161
- return `${i + 1}. [${e.bbox.x},${e.bbox.y} ${e.bbox.width}x${e.bbox.height}] <${e.tag}> ${idInfo}${typeInfo}${placeholderInfo} "${e.text}"
162
- → ${selectorsInfo}`;
188
+ const roleInfo = e.role ? ` role="${e.role}"` : '';
189
+ // Compact format: show all relevant attributes for LLM to construct selectors
190
+ return `${i + 1}. [${e.bbox.x},${e.bbox.y} ${e.bbox.width}x${e.bbox.height}] <${e.tag}>${idInfo}${typeInfo}${nameInfo}${placeholderInfo}${roleInfo} "${e.text}"`;
163
191
  }).join('\n');
164
192
  // Show stats for omitted elements
165
193
  const omitted = interactiveElements.length - selectedElements.length;