@zibby/core 0.1.21 → 0.1.23
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/dist/agents/base.js +17 -0
- package/dist/backend-client.js +1 -0
- package/dist/constants/tool-names.js +1 -0
- package/dist/constants/zibby-scratch.js +1 -0
- package/dist/constants.js +1 -0
- package/dist/enrichment/base.js +1 -0
- package/dist/enrichment/enrichers/accessibility-enricher.js +1 -0
- package/dist/enrichment/enrichers/dom-enricher.js +1 -0
- package/dist/enrichment/enrichers/page-state-enricher.js +1 -0
- package/dist/enrichment/enrichers/position-enricher.js +1 -0
- package/dist/enrichment/index.js +1 -0
- package/dist/enrichment/mcp-integration.js +1 -0
- package/dist/enrichment/mcp-ref-enricher.js +1 -0
- package/dist/enrichment/pipeline.js +3 -0
- package/dist/enrichment/trace-text-enricher.js +1 -0
- package/dist/framework/agents/assistant-strategy.js +5 -0
- package/dist/framework/agents/base.js +1 -0
- package/dist/framework/agents/claude-strategy.js +4 -0
- package/dist/framework/agents/codex-strategy.js +4 -0
- package/dist/framework/agents/cursor-strategy.js +32 -0
- package/dist/framework/agents/gemini-strategy.js +11 -0
- package/dist/framework/agents/index.js +13 -0
- package/dist/framework/agents/middleware/assistant-round-pipeline.js +3 -0
- package/dist/framework/agents/providers/base.js +1 -0
- package/dist/framework/agents/providers/index.js +1 -0
- package/dist/framework/agents/providers/openai-transport.js +2 -0
- package/dist/framework/agents/providers/openai.js +1 -0
- package/dist/framework/agents/providers/transport-base.js +1 -0
- package/dist/framework/agents/utils/auth-resolver.js +1 -0
- package/dist/framework/agents/utils/cursor-output-formatter.js +1 -0
- package/dist/framework/agents/utils/openai-proxy-formatter.js +9 -0
- package/dist/framework/agents/utils/payload-budget.js +3 -0
- package/dist/framework/agents/utils/structured-output-formatter.js +21 -0
- package/dist/framework/code-generator.js +10 -0
- package/dist/framework/constants.js +1 -0
- package/dist/framework/context-loader.js +5 -0
- package/dist/framework/function-bridge.js +2 -0
- package/dist/framework/function-skill-registry.js +1 -0
- package/dist/framework/graph-compiler.js +1 -0
- package/dist/framework/graph.js +5 -0
- package/dist/framework/index.js +1 -0
- package/dist/framework/mcp-client.js +2 -0
- package/dist/framework/node-registry.js +9 -0
- package/dist/framework/node.js +5 -0
- package/dist/framework/output-parser.js +3 -0
- package/dist/framework/skill-registry.js +1 -0
- package/dist/framework/state-utils.js +1 -0
- package/dist/framework/state.js +1 -0
- package/dist/framework/tool-resolver.js +1 -0
- package/dist/index.js +8 -0
- package/dist/runtime/generation/base.js +1 -0
- package/dist/runtime/generation/index.js +3 -0
- package/dist/runtime/generation/mcp-ref-strategy.js +41 -0
- package/dist/runtime/generation/stable-id-strategy.js +16 -0
- package/dist/runtime/stable-id-runtime.js +1 -0
- package/dist/runtime/verification/base.js +1 -0
- package/dist/runtime/verification/index.js +3 -0
- package/dist/runtime/verification/playwright-json-strategy.js +1 -0
- package/dist/runtime/zibby-runtime.js +1 -0
- package/dist/sync/index.js +1 -0
- package/dist/sync/uploader.js +1 -0
- package/dist/tools/run-playwright-test.js +5 -0
- package/dist/utils/adf-converter.js +7 -0
- package/dist/utils/ast-utils.js +1 -0
- package/dist/utils/ci-setup.js +5 -0
- package/dist/utils/cursor-mcp-isolated-home.js +1 -0
- package/dist/utils/cursor-utils.js +18 -0
- package/dist/utils/live-frame-discovery.js +1 -0
- package/dist/utils/logger.js +1 -0
- package/dist/utils/mcp-config-writer.js +10 -0
- package/dist/utils/mission-control-from-run-states.js +1 -0
- package/dist/utils/node-schema-parser.js +1 -0
- package/dist/utils/parallel-config.js +1 -0
- package/dist/utils/post-process-events.js +1 -0
- package/dist/utils/result-handler.js +1 -0
- package/{src → dist}/utils/ripple-effect.js +3 -12
- package/dist/utils/run-capacity-coordinator.js +1 -0
- package/dist/utils/run-capacity-queue.js +2 -0
- package/dist/utils/run-index-merge.js +1 -0
- package/dist/utils/run-index-post-cli.js +1 -0
- package/dist/utils/run-registry.js +3 -0
- package/dist/utils/run-state-session.js +2 -0
- package/dist/utils/selector-generator.js +4 -0
- package/dist/utils/session-state-constants.js +1 -0
- package/dist/utils/session-state-live-runs.js +1 -0
- package/dist/utils/streaming-parser.js +4 -0
- package/dist/utils/test-post-processor.js +18 -0
- package/dist/utils/timeline.js +14 -0
- package/dist/utils/trace-parser.js +2 -0
- package/dist/utils/video-organizer.js +3 -0
- package/package.json +49 -35
- package/templates/browser-test-automation/README.md +29 -7
- package/templates/browser-test-automation/chat.mjs +36 -0
- package/templates/browser-test-automation/graph.mjs +5 -9
- package/templates/browser-test-automation/nodes/execute-live.mjs +30 -58
- package/templates/browser-test-automation/nodes/generate-script.mjs +32 -12
- package/templates/browser-test-automation/nodes/utils.mjs +153 -10
- package/templates/browser-test-automation/pipeline-ids.js +12 -0
- package/templates/browser-test-automation/result-handler.mjs +78 -2
- package/templates/browser-test-automation/run-index.mjs +418 -0
- package/scripts/export-default-workflows.js +0 -51
- package/scripts/patch-cursor-mcp.js +0 -174
- package/scripts/setup-ci.sh +0 -115
- package/scripts/setup-official-playwright-mcp.sh +0 -226
- package/scripts/test-with-video.sh +0 -49
- package/src/agents/base.js +0 -361
- package/src/constants.js +0 -47
- package/src/enrichment/base.js +0 -49
- package/src/enrichment/enrichers/accessibility-enricher.js +0 -197
- package/src/enrichment/enrichers/dom-enricher.js +0 -171
- package/src/enrichment/enrichers/page-state-enricher.js +0 -129
- package/src/enrichment/enrichers/position-enricher.js +0 -67
- package/src/enrichment/index.js +0 -96
- package/src/enrichment/mcp-integration.js +0 -149
- package/src/enrichment/mcp-ref-enricher.js +0 -78
- package/src/enrichment/pipeline.js +0 -192
- package/src/enrichment/trace-text-enricher.js +0 -115
- package/src/framework/AGENTS.md +0 -98
- package/src/framework/agents/base.js +0 -72
- package/src/framework/agents/claude-strategy.js +0 -278
- package/src/framework/agents/cursor-strategy.js +0 -544
- package/src/framework/agents/index.js +0 -105
- package/src/framework/agents/utils/cursor-output-formatter.js +0 -67
- package/src/framework/agents/utils/openai-proxy-formatter.js +0 -249
- package/src/framework/code-generator.js +0 -301
- package/src/framework/constants.js +0 -33
- package/src/framework/context-loader.js +0 -101
- package/src/framework/function-bridge.js +0 -78
- package/src/framework/function-skill-registry.js +0 -20
- package/src/framework/graph-compiler.js +0 -342
- package/src/framework/graph.js +0 -610
- package/src/framework/index.js +0 -28
- package/src/framework/node-registry.js +0 -163
- package/src/framework/node.js +0 -259
- package/src/framework/output-parser.js +0 -71
- package/src/framework/skill-registry.js +0 -55
- package/src/framework/state-utils.js +0 -52
- package/src/framework/state.js +0 -67
- package/src/framework/tool-resolver.js +0 -65
- package/src/index.js +0 -345
- package/src/runtime/generation/base.js +0 -46
- package/src/runtime/generation/index.js +0 -70
- package/src/runtime/generation/mcp-ref-strategy.js +0 -197
- package/src/runtime/generation/stable-id-strategy.js +0 -170
- package/src/runtime/stable-id-runtime.js +0 -248
- package/src/runtime/verification/base.js +0 -44
- package/src/runtime/verification/index.js +0 -67
- package/src/runtime/verification/playwright-json-strategy.js +0 -119
- package/src/runtime/zibby-runtime.js +0 -299
- package/src/sync/index.js +0 -2
- package/src/sync/uploader.js +0 -29
- package/src/tools/run-playwright-test.js +0 -158
- package/src/utils/adf-converter.js +0 -68
- package/src/utils/ast-utils.js +0 -37
- package/src/utils/ci-setup.js +0 -124
- package/src/utils/cursor-utils.js +0 -71
- package/src/utils/logger.js +0 -144
- package/src/utils/mcp-config-writer.js +0 -115
- package/src/utils/node-schema-parser.js +0 -522
- package/src/utils/post-process-events.js +0 -55
- package/src/utils/result-handler.js +0 -102
- package/src/utils/selector-generator.js +0 -239
- package/src/utils/streaming-parser.js +0 -387
- package/src/utils/test-post-processor.js +0 -211
- package/src/utils/timeline.js +0 -217
- package/src/utils/trace-parser.js +0 -325
- package/src/utils/video-organizer.js +0 -91
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DOM Enricher - Captures DOM path, XPath, and element attributes
|
|
3
|
-
* This file contains browser code executed via element.evaluate() where browser globals are available
|
|
4
|
-
*/
|
|
5
|
-
/* global document, window */
|
|
6
|
-
import { EventEnricher } from '../base.js';
|
|
7
|
-
|
|
8
|
-
export class DOMEnricher extends EventEnricher {
|
|
9
|
-
getName() {
|
|
10
|
-
return 'DOMEnricher';
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
getPriority() {
|
|
14
|
-
return 85; // High priority - structural data
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
canEnrich(context) {
|
|
18
|
-
if (!this.enabled) return false;
|
|
19
|
-
if (!context.element) return false;
|
|
20
|
-
if (!context.event) return false;
|
|
21
|
-
|
|
22
|
-
return ['click', 'fill', 'type', 'selectOption', 'hover'].includes(context.event.type);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async enrich(event, context) {
|
|
26
|
-
try {
|
|
27
|
-
const { element } = context;
|
|
28
|
-
|
|
29
|
-
// Get DOM path, XPath, and attributes
|
|
30
|
-
const domData = await element.evaluate(el => {
|
|
31
|
-
// Build CSS path
|
|
32
|
-
const getCssPath = (innerEl) => {
|
|
33
|
-
const path = [];
|
|
34
|
-
let current = innerEl;
|
|
35
|
-
|
|
36
|
-
while (current && current !== document.body) {
|
|
37
|
-
let selector = current.tagName.toLowerCase();
|
|
38
|
-
|
|
39
|
-
// Add nth-child if there are siblings of same type
|
|
40
|
-
const parent = current.parentElement;
|
|
41
|
-
if (parent) {
|
|
42
|
-
const siblings = Array.from(parent.children).filter(
|
|
43
|
-
child => child.tagName === current.tagName
|
|
44
|
-
);
|
|
45
|
-
if (siblings.length > 1) {
|
|
46
|
-
const index = siblings.indexOf(current) + 1;
|
|
47
|
-
selector += `:nth-child(${index})`;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
path.unshift(selector);
|
|
52
|
-
current = current.parentElement;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return `body > ${path.join(' > ')}`;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
// Build XPath
|
|
59
|
-
const getXPath = (innerEl) => {
|
|
60
|
-
const path = [];
|
|
61
|
-
let current = innerEl;
|
|
62
|
-
|
|
63
|
-
while (current && current !== document.body) {
|
|
64
|
-
let index = 1;
|
|
65
|
-
let sibling = current.previousSibling;
|
|
66
|
-
|
|
67
|
-
while (sibling) {
|
|
68
|
-
if (sibling.nodeType === 1 && sibling.tagName === current.tagName) {
|
|
69
|
-
index++;
|
|
70
|
-
}
|
|
71
|
-
sibling = sibling.previousSibling;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const tag = current.tagName.toLowerCase();
|
|
75
|
-
path.unshift(`${tag}[${index}]`);
|
|
76
|
-
current = current.parentElement;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return `/html/body/${path.join('/')}`;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
// Get all attributes
|
|
83
|
-
const attributes = {};
|
|
84
|
-
for (const attr of el.attributes) {
|
|
85
|
-
attributes[attr.name] = attr.value;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Get computed style (only essential properties)
|
|
89
|
-
const computed = window.getComputedStyle(el);
|
|
90
|
-
const state = {
|
|
91
|
-
display: computed.display,
|
|
92
|
-
visibility: computed.visibility,
|
|
93
|
-
opacity: computed.opacity,
|
|
94
|
-
pointerEvents: computed.pointerEvents
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// Calculate depth
|
|
98
|
-
let depth = 0;
|
|
99
|
-
let parent = el.parentElement;
|
|
100
|
-
while (parent) {
|
|
101
|
-
depth++;
|
|
102
|
-
parent = parent.parentElement;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
path: getCssPath(el),
|
|
107
|
-
xpath: getXPath(el),
|
|
108
|
-
depth,
|
|
109
|
-
parent: el.parentElement ? el.parentElement.tagName.toLowerCase() : null,
|
|
110
|
-
tagName: el.tagName.toLowerCase(),
|
|
111
|
-
attributes,
|
|
112
|
-
state: {
|
|
113
|
-
visible: computed.display !== 'none' && computed.visibility !== 'hidden',
|
|
114
|
-
enabled: !el.disabled,
|
|
115
|
-
focused: document.activeElement === el,
|
|
116
|
-
...state
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
return {
|
|
122
|
-
dom: {
|
|
123
|
-
path: domData.path,
|
|
124
|
-
xpath: domData.xpath,
|
|
125
|
-
depth: domData.depth,
|
|
126
|
-
parent: domData.parent,
|
|
127
|
-
selector: this.buildSmartSelector(domData)
|
|
128
|
-
},
|
|
129
|
-
attributes: domData.attributes,
|
|
130
|
-
state: domData.state
|
|
131
|
-
};
|
|
132
|
-
} catch (error) {
|
|
133
|
-
return this.handleError(error, event);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Build a smart CSS selector from DOM data
|
|
139
|
-
*/
|
|
140
|
-
buildSmartSelector(domData) {
|
|
141
|
-
let selector = domData.tagName;
|
|
142
|
-
|
|
143
|
-
// Add ID if available
|
|
144
|
-
if (domData.attributes.id) {
|
|
145
|
-
return `#${domData.attributes.id}`;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Add data-test-id if available
|
|
149
|
-
if (domData.attributes['data-test-id']) {
|
|
150
|
-
return `[data-test-id="${domData.attributes['data-test-id']}"]`;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Add class if available
|
|
154
|
-
if (domData.attributes.class) {
|
|
155
|
-
const classes = domData.attributes.class.split(' ')
|
|
156
|
-
.filter(c => c && !c.match(/^(active|focus|hover|disabled)/)); // Skip state classes
|
|
157
|
-
if (classes.length > 0) {
|
|
158
|
-
selector += `.${classes.slice(0, 2).join('.')}`;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Add parent context
|
|
163
|
-
if (domData.parent) {
|
|
164
|
-
selector = `${domData.parent} > ${selector}`;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return selector;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export default DOMEnricher;
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Page State Enricher - Captures network and page stability state
|
|
3
|
-
* This file contains browser code executed via page.evaluate() where browser globals are available
|
|
4
|
-
*/
|
|
5
|
-
/* global document, MutationObserver */
|
|
6
|
-
import { EventEnricher } from '../base.js';
|
|
7
|
-
|
|
8
|
-
export class PageStateEnricher extends EventEnricher {
|
|
9
|
-
constructor(config = {}) {
|
|
10
|
-
super(config);
|
|
11
|
-
this.pendingRequests = new Set();
|
|
12
|
-
this.setupNetworkTracking = false;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
getName() {
|
|
16
|
-
return 'PageStateEnricher';
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
getPriority() {
|
|
20
|
-
return 95; // Very high priority - critical for timing
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
canEnrich(context) {
|
|
24
|
-
return this.enabled && context.page;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Setup network request tracking (call once per page)
|
|
29
|
-
*/
|
|
30
|
-
async setupTracking(page) {
|
|
31
|
-
if (this.setupNetworkTracking) return;
|
|
32
|
-
|
|
33
|
-
page.on('request', (request) => {
|
|
34
|
-
if (['document', 'xhr', 'fetch'].includes(request.resourceType())) {
|
|
35
|
-
this.pendingRequests.add(request.url());
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
page.on('requestfinished', (request) => {
|
|
40
|
-
this.pendingRequests.delete(request.url());
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
page.on('requestfailed', (request) => {
|
|
44
|
-
this.pendingRequests.delete(request.url());
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
this.setupNetworkTracking = true;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async enrich(event, context) {
|
|
51
|
-
try {
|
|
52
|
-
const { page } = context;
|
|
53
|
-
|
|
54
|
-
// Setup tracking if not done
|
|
55
|
-
await this.setupTracking(page);
|
|
56
|
-
|
|
57
|
-
// Get page load state
|
|
58
|
-
const pageState = await page.evaluate(() => ({
|
|
59
|
-
readyState: document.readyState,
|
|
60
|
-
domContentLoaded: document.readyState !== 'loading',
|
|
61
|
-
loadComplete: document.readyState === 'complete',
|
|
62
|
-
url: document.location.href
|
|
63
|
-
}));
|
|
64
|
-
|
|
65
|
-
// Check if DOM is stable (no mutations for 500ms)
|
|
66
|
-
const domStable = await this.checkDOMStability(page);
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
page: {
|
|
70
|
-
networkIdle: this.pendingRequests.size === 0,
|
|
71
|
-
pendingRequests: this.pendingRequests.size,
|
|
72
|
-
domStable,
|
|
73
|
-
...pageState
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
} catch (error) {
|
|
77
|
-
return this.handleError(error, event);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Check if DOM hasn't mutated for 500ms
|
|
83
|
-
*/
|
|
84
|
-
async checkDOMStability(page, timeoutMs = 500) {
|
|
85
|
-
try {
|
|
86
|
-
const stable = await page.evaluate((timeout) => {
|
|
87
|
-
return new Promise((resolve) => {
|
|
88
|
-
let timer;
|
|
89
|
-
let mutations = 0;
|
|
90
|
-
|
|
91
|
-
const observer = new MutationObserver(() => {
|
|
92
|
-
mutations++;
|
|
93
|
-
clearTimeout(timer);
|
|
94
|
-
timer = setTimeout(() => {
|
|
95
|
-
observer.disconnect();
|
|
96
|
-
resolve(mutations === 0);
|
|
97
|
-
}, timeout);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
observer.observe(document.body, {
|
|
101
|
-
childList: true,
|
|
102
|
-
subtree: true,
|
|
103
|
-
attributes: true
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// Initial timer
|
|
107
|
-
timer = setTimeout(() => {
|
|
108
|
-
observer.disconnect();
|
|
109
|
-
resolve(true);
|
|
110
|
-
}, timeout);
|
|
111
|
-
});
|
|
112
|
-
}, timeoutMs);
|
|
113
|
-
|
|
114
|
-
return stable;
|
|
115
|
-
} catch (_error) {
|
|
116
|
-
return false; // Assume not stable on error
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Reset tracking for new page navigation
|
|
122
|
-
*/
|
|
123
|
-
reset() {
|
|
124
|
-
this.pendingRequests.clear();
|
|
125
|
-
this.setupNetworkTracking = false;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export default PageStateEnricher;
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Position Enricher - Captures element bounding box and viewport state
|
|
3
|
-
* This file contains browser code executed via element.evaluate() where browser globals are available
|
|
4
|
-
*/
|
|
5
|
-
/* global window */
|
|
6
|
-
import { EventEnricher } from '../base.js';
|
|
7
|
-
|
|
8
|
-
export class PositionEnricher extends EventEnricher {
|
|
9
|
-
getName() {
|
|
10
|
-
return 'PositionEnricher';
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
getPriority() {
|
|
14
|
-
return 90; // High priority - cheap and useful
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
canEnrich(context) {
|
|
18
|
-
// Only enrich interactive actions that have an element
|
|
19
|
-
if (!this.enabled) return false;
|
|
20
|
-
if (!context.element) return false;
|
|
21
|
-
if (!context.event) return false;
|
|
22
|
-
|
|
23
|
-
return ['click', 'fill', 'type', 'selectOption', 'hover'].includes(context.event.type);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async enrich(event, context) {
|
|
27
|
-
try {
|
|
28
|
-
const { page, element } = context;
|
|
29
|
-
|
|
30
|
-
// Get bounding box
|
|
31
|
-
const box = await element.boundingBox();
|
|
32
|
-
if (!box) return null; // Element not visible
|
|
33
|
-
|
|
34
|
-
// Get viewport state
|
|
35
|
-
const viewport = await page.evaluate(() => ({
|
|
36
|
-
scrollX: window.scrollX,
|
|
37
|
-
scrollY: window.scrollY,
|
|
38
|
-
width: window.innerWidth,
|
|
39
|
-
height: window.innerHeight
|
|
40
|
-
}));
|
|
41
|
-
|
|
42
|
-
// Check if in viewport
|
|
43
|
-
const inViewport = (
|
|
44
|
-
box.y >= viewport.scrollY &&
|
|
45
|
-
box.y + box.height <= viewport.scrollY + viewport.height &&
|
|
46
|
-
box.x >= 0 &&
|
|
47
|
-
box.x + box.width <= viewport.width
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
position: {
|
|
52
|
-
boundingBox: box,
|
|
53
|
-
viewport,
|
|
54
|
-
inViewport,
|
|
55
|
-
centerPoint: {
|
|
56
|
-
x: Math.round(box.x + box.width / 2),
|
|
57
|
-
y: Math.round(box.y + box.height / 2)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
} catch (error) {
|
|
62
|
-
return this.handleError(error, event);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export default PositionEnricher;
|
package/src/enrichment/index.js
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Event Enrichment System
|
|
3
|
-
* Modular pipeline for adding data to MCP events
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export { EventEnricher } from './base.js';
|
|
7
|
-
export { EnrichmentPipeline } from './pipeline.js';
|
|
8
|
-
|
|
9
|
-
// Enrichers
|
|
10
|
-
export { PositionEnricher } from './enrichers/position-enricher.js';
|
|
11
|
-
export { AccessibilityEnricher } from './enrichers/accessibility-enricher.js';
|
|
12
|
-
export { PageStateEnricher } from './enrichers/page-state-enricher.js';
|
|
13
|
-
export { DOMEnricher } from './enrichers/dom-enricher.js';
|
|
14
|
-
export { MCPRefEnricher } from './mcp-ref-enricher.js';
|
|
15
|
-
export { TraceTextEnricher } from './trace-text-enricher.js';
|
|
16
|
-
|
|
17
|
-
import { EnrichmentPipeline } from './pipeline.js';
|
|
18
|
-
import { PositionEnricher } from './enrichers/position-enricher.js';
|
|
19
|
-
import { AccessibilityEnricher } from './enrichers/accessibility-enricher.js';
|
|
20
|
-
import { PageStateEnricher } from './enrichers/page-state-enricher.js';
|
|
21
|
-
import { DOMEnricher } from './enrichers/dom-enricher.js';
|
|
22
|
-
import { MCPRefEnricher } from './mcp-ref-enricher.js';
|
|
23
|
-
import { TraceTextEnricher } from './trace-text-enricher.js';
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Create a default enrichment pipeline with all enrichers
|
|
27
|
-
* @param {Object} config - Configuration options
|
|
28
|
-
* @param {boolean} config.enablePosition - Enable position enricher (default: true)
|
|
29
|
-
* @param {boolean} config.enableAccessibility - Enable accessibility enricher (default: true)
|
|
30
|
-
* @param {boolean} config.enablePageState - Enable page state enricher (default: true)
|
|
31
|
-
* @param {boolean} config.enableDOM - Enable DOM enricher (default: true)
|
|
32
|
-
* @returns {EnrichmentPipeline}
|
|
33
|
-
*/
|
|
34
|
-
export function createDefaultPipeline(config = {}) {
|
|
35
|
-
const pipeline = new EnrichmentPipeline(config);
|
|
36
|
-
|
|
37
|
-
// Register enrichers (they'll auto-sort by priority)
|
|
38
|
-
// MCPRefEnricher has highest priority (200) - captures exact MCP refs
|
|
39
|
-
if (config.enableMCPRef !== false) {
|
|
40
|
-
pipeline.register(new MCPRefEnricher(config));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// TraceTextEnricher (190) - extracts ACTUAL text from trace (for Chinese/multi-language)
|
|
44
|
-
if (config.enableTraceText !== false) {
|
|
45
|
-
pipeline.register(new TraceTextEnricher(config));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (config.enableAccessibility !== false) {
|
|
49
|
-
pipeline.register(new AccessibilityEnricher(config));
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (config.enablePageState !== false) {
|
|
53
|
-
pipeline.register(new PageStateEnricher(config));
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (config.enablePosition !== false) {
|
|
57
|
-
pipeline.register(new PositionEnricher(config));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (config.enableDOM !== false) {
|
|
61
|
-
pipeline.register(new DOMEnricher(config));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return pipeline;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Create a minimal pipeline (only critical enrichers)
|
|
69
|
-
* @param {Object} config
|
|
70
|
-
* @returns {EnrichmentPipeline}
|
|
71
|
-
*/
|
|
72
|
-
export function createMinimalPipeline(config = {}) {
|
|
73
|
-
const pipeline = new EnrichmentPipeline(config);
|
|
74
|
-
|
|
75
|
-
// Only most critical enrichers
|
|
76
|
-
pipeline.register(new AccessibilityEnricher(config));
|
|
77
|
-
pipeline.register(new PageStateEnricher(config));
|
|
78
|
-
|
|
79
|
-
return pipeline;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Create a custom pipeline
|
|
84
|
-
* @param {Array} enrichers - Array of enricher instances
|
|
85
|
-
* @param {Object} config
|
|
86
|
-
* @returns {EnrichmentPipeline}
|
|
87
|
-
*/
|
|
88
|
-
export function createCustomPipeline(enrichers, config = {}) {
|
|
89
|
-
const pipeline = new EnrichmentPipeline(config);
|
|
90
|
-
|
|
91
|
-
for (const enricher of enrichers) {
|
|
92
|
-
pipeline.register(enricher);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return pipeline;
|
|
96
|
-
}
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP Recorder Integration with Enrichment Pipeline
|
|
3
|
-
*
|
|
4
|
-
* This module provides utilities to enrich MCP-recorded events
|
|
5
|
-
* after test execution (when we have access to trace data)
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { createDefaultPipeline, createMinimalPipeline } from './index.js';
|
|
9
|
-
import { readFileSync, writeFileSync } from 'fs';
|
|
10
|
-
import { join } from 'path';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Enrich recorded events.json file with data from Playwright trace
|
|
14
|
-
*
|
|
15
|
-
* @param {string} sessionPath - Path to session directory
|
|
16
|
-
* @param {Object} config - Enrichment configuration
|
|
17
|
-
* @returns {Promise<Object>} - { enrichedCount, skippedCount, errors }
|
|
18
|
-
*/
|
|
19
|
-
export async function enrichRecordedEvents(sessionPath, _config = {}) {
|
|
20
|
-
const eventsPath = join(sessionPath, 'events.json');
|
|
21
|
-
const _tracePath = join(sessionPath, 'trace.zip');
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
// Read events
|
|
25
|
-
const events = JSON.parse(readFileSync(eventsPath, 'utf-8'));
|
|
26
|
-
|
|
27
|
-
// For now, we add placeholder enrichment metadata
|
|
28
|
-
// Full enrichment requires re-playing with Playwright access
|
|
29
|
-
const enriched = events.map(event => ({
|
|
30
|
-
...event,
|
|
31
|
-
_enrichmentNote: 'Full enrichment requires live Playwright access. Use EnrichmentPipeline during test execution.'
|
|
32
|
-
}));
|
|
33
|
-
|
|
34
|
-
// Save enriched events
|
|
35
|
-
const backupPath = `${eventsPath }.backup`;
|
|
36
|
-
writeFileSync(backupPath, JSON.stringify(events, null, 2));
|
|
37
|
-
writeFileSync(eventsPath, JSON.stringify(enriched, null, 2));
|
|
38
|
-
|
|
39
|
-
return {
|
|
40
|
-
enrichedCount: enriched.length,
|
|
41
|
-
skippedCount: 0,
|
|
42
|
-
errors: []
|
|
43
|
-
};
|
|
44
|
-
} catch (error) {
|
|
45
|
-
console.error('[EnrichmentIntegration] Failed to enrich events:', error.message);
|
|
46
|
-
return {
|
|
47
|
-
enrichedCount: 0,
|
|
48
|
-
skippedCount: 0,
|
|
49
|
-
errors: [error.message]
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Example: How to use enrichment pipeline during live test execution
|
|
56
|
-
*
|
|
57
|
-
* This is a reference implementation for integrating enrichment
|
|
58
|
-
* into your test execution flow where you have Playwright access.
|
|
59
|
-
*/
|
|
60
|
-
export class LiveEnrichmentRecorder {
|
|
61
|
-
constructor(config = {}) {
|
|
62
|
-
// Create enrichment pipeline
|
|
63
|
-
this.pipeline = config.minimal
|
|
64
|
-
? createMinimalPipeline(config)
|
|
65
|
-
: createDefaultPipeline(config);
|
|
66
|
-
|
|
67
|
-
this.events = [];
|
|
68
|
-
this.config = config;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Record and enrich an event during test execution
|
|
73
|
-
*
|
|
74
|
-
* @param {string} type - Event type (click, fill, etc)
|
|
75
|
-
* @param {Object} data - Event data
|
|
76
|
-
* @param {Object} context - { page, element, ref }
|
|
77
|
-
* @returns {Promise<Object>} - Enriched event
|
|
78
|
-
*/
|
|
79
|
-
async recordEvent(type, data, context) {
|
|
80
|
-
// Create base event (MCP format)
|
|
81
|
-
const baseEvent = {
|
|
82
|
-
id: this.events.length,
|
|
83
|
-
type,
|
|
84
|
-
timestamp: new Date().toISOString(),
|
|
85
|
-
data
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
// Enrich with pipeline
|
|
89
|
-
const enriched = await this.pipeline.enrich(baseEvent, {
|
|
90
|
-
...context,
|
|
91
|
-
event: baseEvent
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
this.events.push(enriched);
|
|
95
|
-
return enriched;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Save enriched events to file
|
|
100
|
-
* @param {string} filePath
|
|
101
|
-
*/
|
|
102
|
-
saveEvents(filePath) {
|
|
103
|
-
writeFileSync(filePath, JSON.stringify(this.events, null, 2));
|
|
104
|
-
console.log(`[LiveEnrichment] Saved ${this.events.length} enriched events to ${filePath}`);
|
|
105
|
-
|
|
106
|
-
// Log statistics
|
|
107
|
-
this.pipeline.logStatus();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Get pipeline statistics
|
|
112
|
-
*/
|
|
113
|
-
getStats() {
|
|
114
|
-
return this.pipeline.getStats();
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Example integration with Cursor Agent workflow
|
|
120
|
-
*
|
|
121
|
-
* Usage in execute_live node:
|
|
122
|
-
*
|
|
123
|
-
* ```javascript
|
|
124
|
-
* import { LiveEnrichmentRecorder } from '@zibby/core';
|
|
125
|
-
*
|
|
126
|
-
* const recorder = new LiveEnrichmentRecorder({ minimal: true });
|
|
127
|
-
*
|
|
128
|
-
* // During test execution (when you have page/element access):
|
|
129
|
-
* const page = await browser.newPage();
|
|
130
|
-
* const element = await page.locator('button');
|
|
131
|
-
*
|
|
132
|
-
* // Record enriched event
|
|
133
|
-
* await recorder.recordEvent('click', {
|
|
134
|
-
* tool: 'browser_click',
|
|
135
|
-
* params: { element: 'Submit button', ref: 'k123' }
|
|
136
|
-
* }, {
|
|
137
|
-
* page,
|
|
138
|
-
* element
|
|
139
|
-
* });
|
|
140
|
-
*
|
|
141
|
-
* // Save at end
|
|
142
|
-
* recorder.saveEvents('enriched-events.json');
|
|
143
|
-
* ```
|
|
144
|
-
*/
|
|
145
|
-
|
|
146
|
-
export default {
|
|
147
|
-
enrichRecordedEvents,
|
|
148
|
-
LiveEnrichmentRecorder
|
|
149
|
-
};
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { EventEnricher } from './base.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* MCPRefEnricher - Captures ACTUAL element text from trace DOM snapshots
|
|
5
|
-
*
|
|
6
|
-
* Problem: AI might say "Login button" but actual button text is "登录" (Chinese)
|
|
7
|
-
* Solution: Extract the REAL text from trace DOM snapshot at action time
|
|
8
|
-
*
|
|
9
|
-
* Priority order:
|
|
10
|
-
* 1. Extract actual text/label from trace DOM snapshot (handles Chinese/any language)
|
|
11
|
-
* 2. Fall back to MCP element description (AI's description)
|
|
12
|
-
*
|
|
13
|
-
* This ensures Chinese, Arabic, Japanese, etc. apps work perfectly!
|
|
14
|
-
*/
|
|
15
|
-
export class MCPRefEnricher extends EventEnricher {
|
|
16
|
-
constructor(config = {}) {
|
|
17
|
-
super(config);
|
|
18
|
-
this.priority = 200; // Highest priority
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
getName() {
|
|
22
|
-
return 'MCPRef';
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
getPriority() {
|
|
26
|
-
return this.priority;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async enrich(event, context) {
|
|
30
|
-
const ref = event.data?.params?.ref;
|
|
31
|
-
const mcpElement = event.data?.params?.element; // What AI said
|
|
32
|
-
|
|
33
|
-
if (!ref && !mcpElement) {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Try to extract ACTUAL element properties from the DOM (if we have element access)
|
|
38
|
-
let actualText = null;
|
|
39
|
-
let actualRole = null;
|
|
40
|
-
let actualLabel = null;
|
|
41
|
-
|
|
42
|
-
if (context?.element) {
|
|
43
|
-
try {
|
|
44
|
-
// Extract the REAL text that user sees (not what AI said)
|
|
45
|
-
const elementData = await context.element.evaluate(el => {
|
|
46
|
-
return {
|
|
47
|
-
text: el.textContent?.trim() || '',
|
|
48
|
-
innerText: el.innerText?.trim() || '',
|
|
49
|
-
value: el.value || '',
|
|
50
|
-
label: el.getAttribute('aria-label') || el.getAttribute('label') || '',
|
|
51
|
-
role: el.getAttribute('role') || el.tagName.toLowerCase(),
|
|
52
|
-
placeholder: el.getAttribute('placeholder') || '',
|
|
53
|
-
title: el.getAttribute('title') || ''
|
|
54
|
-
};
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
// Use the most specific text we can find
|
|
58
|
-
actualText = elementData.text || elementData.innerText || elementData.value || elementData.placeholder;
|
|
59
|
-
actualRole = elementData.role;
|
|
60
|
-
actualLabel = elementData.label || elementData.title;
|
|
61
|
-
|
|
62
|
-
console.log(`[MCPRefEnricher] ✅ Captured actual text: "${actualText}" (AI said: "${mcpElement}")`);
|
|
63
|
-
} catch (e) {
|
|
64
|
-
console.log(`[MCPRefEnricher] ⚠️ Could not extract actual text: ${e.message}`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
mcpRef: ref,
|
|
70
|
-
mcpElement, // What AI said (might be translated)
|
|
71
|
-
actualText, // ✅ REAL text from DOM (Chinese/any language)
|
|
72
|
-
actualRole, // ✅ REAL role (button, link, textbox)
|
|
73
|
-
actualLabel, // ✅ REAL aria-label
|
|
74
|
-
// Use actual text if available, otherwise fall back to AI's description
|
|
75
|
-
recordedSelector: actualText || mcpElement
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
}
|