@winstonfassett/webdev-gateway 0.1.0-alpha.0
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/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +78 -0
- package/dist/adapter-helpers.d.ts +64 -0
- package/dist/adapter-helpers.d.ts.map +1 -0
- package/dist/adapter-helpers.js +297 -0
- package/dist/adapter-helpers.js.map +1 -0
- package/dist/admin/assets/index-DEDI8OIx.css +2 -0
- package/dist/admin/assets/index-DaI40ww1.js +70 -0
- package/dist/admin/assets/tinykeys.module-CjuTRcEz.js +1 -0
- package/dist/admin/index.html +13 -0
- package/dist/admin-rpc.d.ts +27 -0
- package/dist/admin-rpc.d.ts.map +1 -0
- package/dist/admin-rpc.js +147 -0
- package/dist/admin-rpc.js.map +1 -0
- package/dist/admin.d.ts +10 -0
- package/dist/admin.d.ts.map +1 -0
- package/dist/admin.js +202 -0
- package/dist/admin.js.map +1 -0
- package/dist/auto-register.d.ts +10 -0
- package/dist/auto-register.d.ts.map +1 -0
- package/dist/auto-register.js +145 -0
- package/dist/auto-register.js.map +1 -0
- package/dist/cdp-relay.d.ts +110 -0
- package/dist/cdp-relay.d.ts.map +1 -0
- package/dist/cdp-relay.js +616 -0
- package/dist/cdp-relay.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +95 -0
- package/dist/cli.js.map +1 -0
- package/dist/doctor.d.ts +6 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +149 -0
- package/dist/doctor.js.map +1 -0
- package/dist/element-grab-client.js +305 -0
- package/dist/element-grab.d.ts +15 -0
- package/dist/element-grab.d.ts.map +1 -0
- package/dist/element-grab.js +102 -0
- package/dist/element-grab.js.map +1 -0
- package/dist/gateway.d.ts +5 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +534 -0
- package/dist/gateway.js.map +1 -0
- package/dist/installer.d.ts +48 -0
- package/dist/installer.d.ts.map +1 -0
- package/dist/installer.js +637 -0
- package/dist/installer.js.map +1 -0
- package/dist/libs/element-source.js +35 -0
- package/dist/libs/modern-screenshot.js +14 -0
- package/dist/log-reader.d.ts +30 -0
- package/dist/log-reader.d.ts.map +1 -0
- package/dist/log-reader.js +174 -0
- package/dist/log-reader.js.map +1 -0
- package/dist/mcp-server.d.ts +22 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +115 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/mcp-tools-core.d.ts +30 -0
- package/dist/mcp-tools-core.d.ts.map +1 -0
- package/dist/mcp-tools-core.js +375 -0
- package/dist/mcp-tools-core.js.map +1 -0
- package/dist/mcp-tools-full.d.ts +4 -0
- package/dist/mcp-tools-full.d.ts.map +1 -0
- package/dist/mcp-tools-full.js +141 -0
- package/dist/mcp-tools-full.js.map +1 -0
- package/dist/playwright-commands.d.ts +33 -0
- package/dist/playwright-commands.d.ts.map +1 -0
- package/dist/playwright-commands.js +356 -0
- package/dist/playwright-commands.js.map +1 -0
- package/dist/registry.d.ts +83 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +205 -0
- package/dist/registry.js.map +1 -0
- package/dist/rpc-server.d.ts +54 -0
- package/dist/rpc-server.d.ts.map +1 -0
- package/dist/rpc-server.js +207 -0
- package/dist/rpc-server.js.map +1 -0
- package/dist/session.d.ts +13 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +61 -0
- package/dist/session.js.map +1 -0
- package/dist/types.d.ts +76 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/webdev-client.js +20 -0
- package/dist/writers/base.d.ts +24 -0
- package/dist/writers/base.d.ts.map +1 -0
- package/dist/writers/base.js +98 -0
- package/dist/writers/base.js.map +1 -0
- package/dist/writers/console.d.ts +8 -0
- package/dist/writers/console.d.ts.map +1 -0
- package/dist/writers/console.js +14 -0
- package/dist/writers/console.js.map +1 -0
- package/dist/writers/dev-events.d.ts +28 -0
- package/dist/writers/dev-events.d.ts.map +1 -0
- package/dist/writers/dev-events.js +53 -0
- package/dist/writers/dev-events.js.map +1 -0
- package/dist/writers/errors.d.ts +8 -0
- package/dist/writers/errors.d.ts.map +1 -0
- package/dist/writers/errors.js +14 -0
- package/dist/writers/errors.js.map +1 -0
- package/dist/writers/network.d.ts +9 -0
- package/dist/writers/network.d.ts.map +1 -0
- package/dist/writers/network.js +17 -0
- package/dist/writers/network.js.map +1 -0
- package/dist/writers/server-console.d.ts +8 -0
- package/dist/writers/server-console.d.ts.map +1 -0
- package/dist/writers/server-console.js +14 -0
- package/dist/writers/server-console.js.map +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playwright command implementations — used when Chrome extension CDP is available.
|
|
3
|
+
* Falls back to injected client RPC commands when extension is not connected.
|
|
4
|
+
*
|
|
5
|
+
* Each function maps an MCP tool command to the equivalent Playwright API call.
|
|
6
|
+
* Based on @xmorse/playwright-core with getExistingCDPSession pattern from Playwriter.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Try to execute a browser command via Playwright.
|
|
10
|
+
* Returns the result if CDP is available, or null to signal fallback to RPC.
|
|
11
|
+
*
|
|
12
|
+
* @param serverPort - dev server port to match against page URLs for multi-tab targeting
|
|
13
|
+
*/
|
|
14
|
+
export async function tryPlaywrightCommand(relay, method, params, serverPort) {
|
|
15
|
+
if (!relay)
|
|
16
|
+
return null;
|
|
17
|
+
// Auto-activate debugging on first CDP tool use (passive mode)
|
|
18
|
+
if (!relay.isAvailable) {
|
|
19
|
+
if (!relay.canActivate)
|
|
20
|
+
return null;
|
|
21
|
+
const activated = await relay.ensureDebugging();
|
|
22
|
+
if (!activated)
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
// Reset idle timer on every tool call
|
|
27
|
+
relay.ensureDebugging();
|
|
28
|
+
}
|
|
29
|
+
const page = await relay.getPage(serverPort);
|
|
30
|
+
if (!page)
|
|
31
|
+
return null;
|
|
32
|
+
try {
|
|
33
|
+
switch (method) {
|
|
34
|
+
case 'screenshot':
|
|
35
|
+
return await pwScreenshot(page, params);
|
|
36
|
+
case 'click':
|
|
37
|
+
return await pwClick(page, params, relay);
|
|
38
|
+
case 'fill':
|
|
39
|
+
return await pwFill(page, params, relay);
|
|
40
|
+
case 'hover':
|
|
41
|
+
return await pwHover(page, params, relay);
|
|
42
|
+
case 'selectOption':
|
|
43
|
+
return await pwSelectOption(page, params);
|
|
44
|
+
case 'pressKey':
|
|
45
|
+
return await pwPressKey(page, params);
|
|
46
|
+
case 'scroll':
|
|
47
|
+
return await pwScroll(page, params);
|
|
48
|
+
case 'navigate':
|
|
49
|
+
return await pwNavigate(page, params);
|
|
50
|
+
case 'goBack':
|
|
51
|
+
await page.goBack();
|
|
52
|
+
return { url: page.url() };
|
|
53
|
+
case 'goForward':
|
|
54
|
+
await page.goForward();
|
|
55
|
+
return { url: page.url() };
|
|
56
|
+
case 'queryDom':
|
|
57
|
+
return await pwQueryDom(relay, page, params);
|
|
58
|
+
case 'a11ySnapshot':
|
|
59
|
+
return await pwA11ySnapshot(relay, page, params);
|
|
60
|
+
case 'getVisibleText':
|
|
61
|
+
return await pwGetVisibleText(page, params);
|
|
62
|
+
default:
|
|
63
|
+
// Not a command we handle via Playwright — fall back to RPC
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
// If Playwright fails, fall back to RPC rather than erroring
|
|
69
|
+
console.log(`[playwright] ${method} failed, falling back to RPC: ${e.message}`);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// ---- Command implementations ----
|
|
74
|
+
async function pwScreenshot(page, params) {
|
|
75
|
+
const opts = { type: params?.format === 'png' ? 'png' : 'jpeg' };
|
|
76
|
+
if (params?.format === 'jpeg' || !params?.format) {
|
|
77
|
+
opts.quality = params?.quality ?? 80;
|
|
78
|
+
}
|
|
79
|
+
if (params?.selector) {
|
|
80
|
+
const el = page.locator(params.selector).first();
|
|
81
|
+
const buf = await el.screenshot(opts);
|
|
82
|
+
const box = await el.boundingBox();
|
|
83
|
+
return {
|
|
84
|
+
data: `data:image/${opts.type};base64,${buf.toString('base64')}`,
|
|
85
|
+
width: Math.round(box?.width ?? 0),
|
|
86
|
+
height: Math.round(box?.height ?? 0),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (params?.preset === 'full') {
|
|
90
|
+
opts.fullPage = true;
|
|
91
|
+
}
|
|
92
|
+
const buf = await page.screenshot(opts);
|
|
93
|
+
const viewport = page.viewportSize() || { width: 1280, height: 720 };
|
|
94
|
+
return {
|
|
95
|
+
data: `data:image/${opts.type};base64,${buf.toString('base64')}`,
|
|
96
|
+
width: viewport.width,
|
|
97
|
+
height: params?.preset === 'full' ? undefined : viewport.height,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/** Resolve a selector — supports CSS, text=, and ref= prefixes */
|
|
101
|
+
async function resolveLocator(page, relay, selector) {
|
|
102
|
+
if (selector.startsWith('ref=')) {
|
|
103
|
+
if (!relay)
|
|
104
|
+
throw new Error('ref= requires CDP extension');
|
|
105
|
+
const locator = await resolveRef(relay, page, selector.slice(4));
|
|
106
|
+
if (locator.error)
|
|
107
|
+
throw new Error(locator.error);
|
|
108
|
+
return locator;
|
|
109
|
+
}
|
|
110
|
+
if (selector.startsWith('text=')) {
|
|
111
|
+
return page.getByText(selector.slice(5)).first();
|
|
112
|
+
}
|
|
113
|
+
return page.locator(selector).first();
|
|
114
|
+
}
|
|
115
|
+
async function pwClick(page, params, relay) {
|
|
116
|
+
const selector = params?.selector;
|
|
117
|
+
if (!selector)
|
|
118
|
+
throw new Error('selector required');
|
|
119
|
+
const locator = await resolveLocator(page, relay || null, selector);
|
|
120
|
+
await locator.click();
|
|
121
|
+
return { clicked: selector };
|
|
122
|
+
}
|
|
123
|
+
async function pwFill(page, params, relay) {
|
|
124
|
+
const { selector, value } = params || {};
|
|
125
|
+
if (!selector || value === undefined)
|
|
126
|
+
throw new Error('selector and value required');
|
|
127
|
+
const locator = await resolveLocator(page, relay || null, selector);
|
|
128
|
+
await locator.fill(String(value));
|
|
129
|
+
return { filled: selector, value };
|
|
130
|
+
}
|
|
131
|
+
async function pwHover(page, params, relay) {
|
|
132
|
+
const selector = params?.selector;
|
|
133
|
+
if (!selector)
|
|
134
|
+
throw new Error('selector required');
|
|
135
|
+
const locator = await resolveLocator(page, relay || null, selector);
|
|
136
|
+
await locator.hover();
|
|
137
|
+
return { hovered: selector };
|
|
138
|
+
}
|
|
139
|
+
async function pwSelectOption(page, params) {
|
|
140
|
+
const { selector, value } = params || {};
|
|
141
|
+
await page.locator(selector).first().selectOption(value);
|
|
142
|
+
return { selected: selector, value };
|
|
143
|
+
}
|
|
144
|
+
async function pwPressKey(page, params) {
|
|
145
|
+
const { key, modifiers } = params || {};
|
|
146
|
+
let keyCombo = key;
|
|
147
|
+
if (modifiers) {
|
|
148
|
+
const mods = Array.isArray(modifiers) ? modifiers : [modifiers];
|
|
149
|
+
keyCombo = [...mods, key].join('+');
|
|
150
|
+
}
|
|
151
|
+
await page.keyboard.press(keyCombo);
|
|
152
|
+
return { pressed: keyCombo };
|
|
153
|
+
}
|
|
154
|
+
async function pwScroll(page, params) {
|
|
155
|
+
if (params?.selector) {
|
|
156
|
+
await page.locator(params.selector).first().scrollIntoViewIfNeeded();
|
|
157
|
+
return { scrolled: params.selector };
|
|
158
|
+
}
|
|
159
|
+
const x = params?.x ?? 0;
|
|
160
|
+
const y = params?.y ?? 0;
|
|
161
|
+
await page.evaluate(([sx, sy]) => window.scrollTo(sx, sy), [x, y]);
|
|
162
|
+
return { scrolled: { x, y } };
|
|
163
|
+
}
|
|
164
|
+
async function pwNavigate(page, params) {
|
|
165
|
+
await page.goto(params?.url, { waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
166
|
+
return { url: page.url(), title: await page.title() };
|
|
167
|
+
}
|
|
168
|
+
async function pwQueryDom(relay, page, params) {
|
|
169
|
+
// Use CDP Accessibility.getFullAXTree for richer DOM inspection
|
|
170
|
+
try {
|
|
171
|
+
const cdp = await relay.getCDPSession(page);
|
|
172
|
+
const { nodes } = await cdp.send('Accessibility.getFullAXTree');
|
|
173
|
+
// Build a simplified a11y tree
|
|
174
|
+
const interactive = nodes.filter((n) => n.role?.value && !['none', 'generic', 'InlineTextBox', 'StaticText'].includes(n.role.value));
|
|
175
|
+
const lines = [];
|
|
176
|
+
for (const node of interactive.slice(0, 100)) {
|
|
177
|
+
const role = node.role?.value || '';
|
|
178
|
+
const name = node.name?.value || '';
|
|
179
|
+
const value = node.value?.value || '';
|
|
180
|
+
lines.push(`${role}: ${name}${value ? ` = "${value}"` : ''}`);
|
|
181
|
+
}
|
|
182
|
+
return { html: lines.join('\n'), nodeCount: nodes.length };
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
// Fall back to null — will use RPC
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// ---- A11y Snapshot with Refs ----
|
|
190
|
+
/** Roles that are structural containers — keep for tree context but don't assign refs */
|
|
191
|
+
const STRUCTURAL_ROLES = new Set([
|
|
192
|
+
'banner', 'complementary', 'contentinfo', 'form', 'main', 'navigation',
|
|
193
|
+
'region', 'search', 'list', 'listitem', 'table', 'row', 'cell',
|
|
194
|
+
'heading', 'group', 'toolbar', 'tablist', 'tabpanel', 'menu', 'menubar',
|
|
195
|
+
'tree', 'treegrid', 'grid', 'dialog', 'alertdialog', 'article', 'figure',
|
|
196
|
+
]);
|
|
197
|
+
/** Roles that are interactive — assign refs to these */
|
|
198
|
+
const INTERACTIVE_ROLES = new Set([
|
|
199
|
+
'button', 'link', 'textbox', 'checkbox', 'radio', 'combobox', 'switch',
|
|
200
|
+
'slider', 'spinbutton', 'searchbox', 'menuitem', 'menuitemcheckbox',
|
|
201
|
+
'menuitemradio', 'option', 'tab', 'treeitem', 'gridcell', 'scrollbar',
|
|
202
|
+
'progressbar',
|
|
203
|
+
]);
|
|
204
|
+
/** Roles to skip entirely — these are noise */
|
|
205
|
+
const SKIP_ROLES = new Set([
|
|
206
|
+
'none', 'presentation', 'generic', 'InlineTextBox', 'StaticText',
|
|
207
|
+
'LineBreak', 'paragraph', 'Section',
|
|
208
|
+
]);
|
|
209
|
+
/**
|
|
210
|
+
* Build a filtered a11y tree from CDP AX nodes.
|
|
211
|
+
* Assigns ref IDs to interactive elements.
|
|
212
|
+
*/
|
|
213
|
+
function buildA11yTree(nodes) {
|
|
214
|
+
if (!nodes.length)
|
|
215
|
+
return { tree: [], refs: new Map() };
|
|
216
|
+
// Build lookup: nodeId → AX node
|
|
217
|
+
const nodeMap = new Map();
|
|
218
|
+
for (const node of nodes) {
|
|
219
|
+
nodeMap.set(node.nodeId, node);
|
|
220
|
+
}
|
|
221
|
+
let refCounter = 0;
|
|
222
|
+
const refs = new Map();
|
|
223
|
+
function processNode(axNode) {
|
|
224
|
+
const role = axNode.role?.value || '';
|
|
225
|
+
const name = axNode.name?.value || '';
|
|
226
|
+
const value = axNode.value?.value;
|
|
227
|
+
// Skip noise roles
|
|
228
|
+
if (SKIP_ROLES.has(role)) {
|
|
229
|
+
// But process children — they might be meaningful
|
|
230
|
+
const promotedChildren = [];
|
|
231
|
+
for (const childId of axNode.childIds || []) {
|
|
232
|
+
const child = nodeMap.get(childId);
|
|
233
|
+
if (child) {
|
|
234
|
+
const processed = processNode(child);
|
|
235
|
+
if (processed)
|
|
236
|
+
promotedChildren.push(processed);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// If skipped node has exactly one child, promote it directly
|
|
240
|
+
if (promotedChildren.length === 1)
|
|
241
|
+
return promotedChildren[0];
|
|
242
|
+
// If multiple children, skip this level but return children
|
|
243
|
+
if (promotedChildren.length > 1) {
|
|
244
|
+
return { role: '', name: '', children: promotedChildren };
|
|
245
|
+
}
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
// Ignore elements with no name and no children (empty containers)
|
|
249
|
+
const childIds = axNode.childIds || [];
|
|
250
|
+
// Process children
|
|
251
|
+
const children = [];
|
|
252
|
+
for (const childId of childIds) {
|
|
253
|
+
const child = nodeMap.get(childId);
|
|
254
|
+
if (child) {
|
|
255
|
+
const processed = processNode(child);
|
|
256
|
+
if (processed)
|
|
257
|
+
children.push(processed);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// Skip empty structural nodes with no name
|
|
261
|
+
if (!name && children.length === 0 && !INTERACTIVE_ROLES.has(role)) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
const treeNode = { role, name, children };
|
|
265
|
+
if (value)
|
|
266
|
+
treeNode.value = value;
|
|
267
|
+
if (axNode.backendDOMNodeId)
|
|
268
|
+
treeNode.backendDOMNodeId = axNode.backendDOMNodeId;
|
|
269
|
+
// Assign ref to interactive elements
|
|
270
|
+
if (INTERACTIVE_ROLES.has(role)) {
|
|
271
|
+
// Check for stable IDs from common test attributes
|
|
272
|
+
const props = axNode.properties || [];
|
|
273
|
+
// Build ref: prefer data-testid from description, fall back to sequential
|
|
274
|
+
const ref = `e${refCounter++}`;
|
|
275
|
+
treeNode.ref = ref;
|
|
276
|
+
refs.set(ref, {
|
|
277
|
+
role,
|
|
278
|
+
name,
|
|
279
|
+
backendDOMNodeId: axNode.backendDOMNodeId,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
return treeNode;
|
|
283
|
+
}
|
|
284
|
+
// Start from root (first node)
|
|
285
|
+
const root = processNode(nodes[0]);
|
|
286
|
+
const tree = root ? (root.role ? [root] : root.children) : [];
|
|
287
|
+
return { tree, refs };
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Render a11y tree as indented text.
|
|
291
|
+
* Interactive elements get [ref=eN] annotations.
|
|
292
|
+
*/
|
|
293
|
+
function renderA11yTree(nodes, indent = 0) {
|
|
294
|
+
const lines = [];
|
|
295
|
+
const pad = ' '.repeat(indent);
|
|
296
|
+
for (const node of nodes) {
|
|
297
|
+
// Skip wrapper nodes with empty role (promoted from skipped parents)
|
|
298
|
+
if (!node.role && node.children.length > 0) {
|
|
299
|
+
lines.push(renderA11yTree(node.children, indent));
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
let line = `${pad}- ${node.role}`;
|
|
303
|
+
if (node.name)
|
|
304
|
+
line += ` "${node.name}"`;
|
|
305
|
+
if (node.value)
|
|
306
|
+
line += ` = "${node.value}"`;
|
|
307
|
+
if (node.ref)
|
|
308
|
+
line += ` [ref=${node.ref}]`;
|
|
309
|
+
lines.push(line);
|
|
310
|
+
if (node.children.length > 0) {
|
|
311
|
+
lines.push(renderA11yTree(node.children, indent + 1));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return lines.join('\n');
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Take an a11y snapshot with ref IDs for interactive elements.
|
|
318
|
+
* Caches refs on the relay for subsequent ref-based actions.
|
|
319
|
+
*/
|
|
320
|
+
export async function pwA11ySnapshot(relay, page, params) {
|
|
321
|
+
try {
|
|
322
|
+
const cdp = await relay.getCDPSession(page);
|
|
323
|
+
const { nodes } = await cdp.send('Accessibility.getFullAXTree');
|
|
324
|
+
const { tree, refs } = buildA11yTree(nodes);
|
|
325
|
+
const snapshot = renderA11yTree(tree);
|
|
326
|
+
// Cache refs on relay for ref-based actions
|
|
327
|
+
relay.refCache = refs;
|
|
328
|
+
return {
|
|
329
|
+
snapshot,
|
|
330
|
+
refCount: refs.size,
|
|
331
|
+
nodeCount: nodes.length,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
catch (e) {
|
|
335
|
+
return { error: `a11y snapshot failed: ${e.message}` };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Resolve a ref (e.g. "e3") to a Playwright locator action.
|
|
340
|
+
* Uses cached refs from the last a11y_snapshot call.
|
|
341
|
+
*/
|
|
342
|
+
export async function resolveRef(relay, page, ref) {
|
|
343
|
+
const entry = relay.refCache?.get(ref);
|
|
344
|
+
if (!entry) {
|
|
345
|
+
return { error: `Unknown ref "${ref}". Call a11y_snapshot first to assign refs.` };
|
|
346
|
+
}
|
|
347
|
+
// Resolve via role + name (most reliable Playwright locator)
|
|
348
|
+
const { role, name } = entry;
|
|
349
|
+
return page.getByRole(role, name ? { name } : undefined).first();
|
|
350
|
+
}
|
|
351
|
+
async function pwGetVisibleText(page, params) {
|
|
352
|
+
const selector = params?.selector || 'body';
|
|
353
|
+
const text = await page.locator(selector).first().innerText();
|
|
354
|
+
return { text };
|
|
355
|
+
}
|
|
356
|
+
//# sourceMappingURL=playwright-commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playwright-commands.js","sourceRoot":"","sources":["../src/playwright-commands.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Registry - tracks dev servers registered with the gateway
|
|
3
|
+
*
|
|
4
|
+
* Identity model:
|
|
5
|
+
* Project = directory path (persistent scope)
|
|
6
|
+
* Server = projectId:type or projectId:key (stable, survives restarts)
|
|
7
|
+
* Endpoint = port on a server (can have multiple per server)
|
|
8
|
+
* Process = PID serving an endpoint (ephemeral, rotates transparently)
|
|
9
|
+
* Browser = affiliated with a server, not an endpoint or process
|
|
10
|
+
*/
|
|
11
|
+
/** Stable project short ID: basename-hash4 (e.g. "nextjs-turbopack-a3f7") */
|
|
12
|
+
export declare function makeProjectId(directory: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Stable server identity: projectId:type (default) or projectId:key (user override).
|
|
15
|
+
* This is the join key between browsers and servers — survives restarts.
|
|
16
|
+
*/
|
|
17
|
+
export declare function makeServerId(directory: string, type: string, key?: string): string;
|
|
18
|
+
export interface Endpoint {
|
|
19
|
+
port: number;
|
|
20
|
+
pid: number;
|
|
21
|
+
registeredAt: number;
|
|
22
|
+
}
|
|
23
|
+
export interface RegisteredServer {
|
|
24
|
+
id: string;
|
|
25
|
+
projectId: string;
|
|
26
|
+
directory: string;
|
|
27
|
+
type: 'vite' | 'nextjs' | 'storybook' | 'astro' | 'generic';
|
|
28
|
+
key?: string;
|
|
29
|
+
name?: string;
|
|
30
|
+
endpoints: Endpoint[];
|
|
31
|
+
logPaths: Record<string, string>;
|
|
32
|
+
logDir: string;
|
|
33
|
+
}
|
|
34
|
+
/** Create per-project log directory and return channel file paths */
|
|
35
|
+
export declare function initProjectLogDir(directory: string, channels?: string[]): {
|
|
36
|
+
logDir: string;
|
|
37
|
+
logPaths: Record<string, string>;
|
|
38
|
+
};
|
|
39
|
+
export type ServerEvent = 'register' | 'deregister';
|
|
40
|
+
export type ServerEventCallback = (event: ServerEvent, data: {
|
|
41
|
+
serverId: string;
|
|
42
|
+
server?: RegisteredServer;
|
|
43
|
+
}) => void;
|
|
44
|
+
export declare function onServerEvent(cb: ServerEventCallback): () => void;
|
|
45
|
+
export declare class ServerRegistry {
|
|
46
|
+
private servers;
|
|
47
|
+
private projectIdIndex;
|
|
48
|
+
private connectionOrder;
|
|
49
|
+
/**
|
|
50
|
+
* Register an endpoint on a server. Creates the server if it doesn't exist.
|
|
51
|
+
* Same server + same port = update PID (restart on same port).
|
|
52
|
+
* Same server + new port = add endpoint (additional instance).
|
|
53
|
+
*/
|
|
54
|
+
addEndpoint(serverId: string, serverInfo: Omit<RegisteredServer, 'endpoints'>, endpoint: Endpoint): RegisteredServer;
|
|
55
|
+
/**
|
|
56
|
+
* Remove a specific endpoint from a server.
|
|
57
|
+
* If the server has no more endpoints, the server entry stays (browsers still affiliated).
|
|
58
|
+
*/
|
|
59
|
+
removeEndpoint(serverId: string, port: number): void;
|
|
60
|
+
/** Remove a server entirely (explicit unregister) */
|
|
61
|
+
remove(id: string): void;
|
|
62
|
+
get(id: string): RegisteredServer | undefined;
|
|
63
|
+
getByProjectId(projectId: string): RegisteredServer[];
|
|
64
|
+
getByDirectory(directory: string): RegisteredServer[];
|
|
65
|
+
getAll(): RegisteredServer[];
|
|
66
|
+
getByType(type: RegisteredServer['type']): RegisteredServer[];
|
|
67
|
+
/** Find a server that has an endpoint on this port */
|
|
68
|
+
getByPort(port: number): RegisteredServer | undefined;
|
|
69
|
+
getLatest(): RegisteredServer | undefined;
|
|
70
|
+
has(id: string): boolean;
|
|
71
|
+
size(): number;
|
|
72
|
+
/** List all registered project directories */
|
|
73
|
+
directories(): string[];
|
|
74
|
+
/** Check if any endpoint on any server has a live process */
|
|
75
|
+
hasLiveEndpoints(serverId: string): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Remove endpoints whose processes are no longer running.
|
|
78
|
+
* Skips endpoints registered within the last 30s (grace period for process forks).
|
|
79
|
+
* Server entries are never removed — only endpoints are cleaned up.
|
|
80
|
+
*/
|
|
81
|
+
cleanupDeadEndpoints(): string[];
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAIvD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAElF;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,OAAO,GAAG,SAAS,CAAA;IAC3D,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,QAAQ,EAAE,CAAA;IACrB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,MAAM,EAAE,MAAM,CAAA;CACf;AAED,qEAAqE;AACrE,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,QAAQ,GAAE,MAAM,EAA0D,GACzE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAatD;AAED,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,YAAY,CAAA;AACnD,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,gBAAgB,CAAA;CAAE,KAAK,IAAI,CAAA;AAIrH,wBAAgB,aAAa,CAAC,EAAE,EAAE,mBAAmB,GAAG,MAAM,IAAI,CAGjE;AAMD,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,cAAc,CAAiC;IACvD,OAAO,CAAC,eAAe,CAAe;IAEtC;;;;OAIG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,gBAAgB,EAAE,WAAW,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAG,gBAAgB;IA+CpH;;;OAGG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAWpD,qDAAqD;IACrD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAoBxB,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAI7C,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EAAE;IAMrD,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EAAE;IAIrD,MAAM,IAAI,gBAAgB,EAAE;IAI5B,SAAS,CAAC,IAAI,EAAE,gBAAgB,CAAC,MAAM,CAAC,GAAG,gBAAgB,EAAE;IAI7D,sDAAsD;IACtD,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAIrD,SAAS,IAAI,gBAAgB,GAAG,SAAS;IAMzC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAIxB,IAAI,IAAI,MAAM;IAId,8CAA8C;IAC9C,WAAW,IAAI,MAAM,EAAE;IAIvB,6DAA6D;IAC7D,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAM3C;;;;OAIG;IACH,oBAAoB,IAAI,MAAM,EAAE;CAgBjC"}
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Registry - tracks dev servers registered with the gateway
|
|
3
|
+
*
|
|
4
|
+
* Identity model:
|
|
5
|
+
* Project = directory path (persistent scope)
|
|
6
|
+
* Server = projectId:type or projectId:key (stable, survives restarts)
|
|
7
|
+
* Endpoint = port on a server (can have multiple per server)
|
|
8
|
+
* Process = PID serving an endpoint (ephemeral, rotates transparently)
|
|
9
|
+
* Browser = affiliated with a server, not an endpoint or process
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { join, basename } from 'node:path';
|
|
14
|
+
/** Stable project short ID: basename-hash4 (e.g. "nextjs-turbopack-a3f7") */
|
|
15
|
+
export function makeProjectId(directory) {
|
|
16
|
+
const name = basename(directory);
|
|
17
|
+
const hash = createHash('sha256').update(directory).digest('hex').slice(0, 4);
|
|
18
|
+
return `${name}-${hash}`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Stable server identity: projectId:type (default) or projectId:key (user override).
|
|
22
|
+
* This is the join key between browsers and servers — survives restarts.
|
|
23
|
+
*/
|
|
24
|
+
export function makeServerId(directory, type, key) {
|
|
25
|
+
return `${makeProjectId(directory)}:${key || type}`;
|
|
26
|
+
}
|
|
27
|
+
/** Create per-project log directory and return channel file paths */
|
|
28
|
+
export function initProjectLogDir(directory, channels = ['console', 'errors', 'dev-events', 'server-console']) {
|
|
29
|
+
const logDir = join(directory, '.webdev');
|
|
30
|
+
mkdirSync(logDir, { recursive: true });
|
|
31
|
+
const logPaths = {};
|
|
32
|
+
for (const ch of channels) {
|
|
33
|
+
const filePath = join(logDir, `${ch}.ndjson`);
|
|
34
|
+
logPaths[ch] = filePath;
|
|
35
|
+
// Truncate on registration (fresh session)
|
|
36
|
+
writeFileSync(filePath, '');
|
|
37
|
+
}
|
|
38
|
+
return { logDir, logPaths };
|
|
39
|
+
}
|
|
40
|
+
const serverEventListeners = new Set();
|
|
41
|
+
export function onServerEvent(cb) {
|
|
42
|
+
serverEventListeners.add(cb);
|
|
43
|
+
return () => serverEventListeners.delete(cb);
|
|
44
|
+
}
|
|
45
|
+
function emitServerEvent(event, data) {
|
|
46
|
+
for (const cb of serverEventListeners)
|
|
47
|
+
cb(event, data);
|
|
48
|
+
}
|
|
49
|
+
export class ServerRegistry {
|
|
50
|
+
servers = new Map(); // server ID → server
|
|
51
|
+
projectIdIndex = new Map(); // projectId → set of server IDs
|
|
52
|
+
connectionOrder = [];
|
|
53
|
+
/**
|
|
54
|
+
* Register an endpoint on a server. Creates the server if it doesn't exist.
|
|
55
|
+
* Same server + same port = update PID (restart on same port).
|
|
56
|
+
* Same server + new port = add endpoint (additional instance).
|
|
57
|
+
*/
|
|
58
|
+
addEndpoint(serverId, serverInfo, endpoint) {
|
|
59
|
+
let server = this.servers.get(serverId);
|
|
60
|
+
if (server) {
|
|
61
|
+
// Existing server — update or add endpoint
|
|
62
|
+
const existing = server.endpoints.find(e => e.port === endpoint.port);
|
|
63
|
+
if (existing) {
|
|
64
|
+
// Same port, new PID (restart)
|
|
65
|
+
existing.pid = endpoint.pid;
|
|
66
|
+
existing.registeredAt = endpoint.registeredAt;
|
|
67
|
+
console.log(`[registry] Endpoint updated: ${serverId} port=${endpoint.port} pid=${endpoint.pid}`);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// New port (additional instance)
|
|
71
|
+
server.endpoints.push(endpoint);
|
|
72
|
+
console.log(`[registry] Endpoint added: ${serverId} port=${endpoint.port} pid=${endpoint.pid}`);
|
|
73
|
+
}
|
|
74
|
+
// Update log paths (may change if re-registered with different channels)
|
|
75
|
+
server.logPaths = serverInfo.logPaths;
|
|
76
|
+
server.logDir = serverInfo.logDir;
|
|
77
|
+
if (serverInfo.name)
|
|
78
|
+
server.name = serverInfo.name;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
// New server
|
|
82
|
+
server = { ...serverInfo, endpoints: [endpoint] };
|
|
83
|
+
this.servers.set(serverId, server);
|
|
84
|
+
// Update project index
|
|
85
|
+
let projectServers = this.projectIdIndex.get(server.projectId);
|
|
86
|
+
if (!projectServers) {
|
|
87
|
+
projectServers = new Set();
|
|
88
|
+
this.projectIdIndex.set(server.projectId, projectServers);
|
|
89
|
+
}
|
|
90
|
+
projectServers.add(serverId);
|
|
91
|
+
console.log(`[registry] Server registered: ${serverId} (${server.type}) dir=${server.directory} port=${endpoint.port}`);
|
|
92
|
+
}
|
|
93
|
+
// Track connection order (most recent registration at end)
|
|
94
|
+
const index = this.connectionOrder.indexOf(serverId);
|
|
95
|
+
if (index !== -1) {
|
|
96
|
+
this.connectionOrder.splice(index, 1);
|
|
97
|
+
}
|
|
98
|
+
this.connectionOrder.push(serverId);
|
|
99
|
+
emitServerEvent('register', { serverId, server });
|
|
100
|
+
return server;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Remove a specific endpoint from a server.
|
|
104
|
+
* If the server has no more endpoints, the server entry stays (browsers still affiliated).
|
|
105
|
+
*/
|
|
106
|
+
removeEndpoint(serverId, port) {
|
|
107
|
+
const server = this.servers.get(serverId);
|
|
108
|
+
if (!server)
|
|
109
|
+
return;
|
|
110
|
+
server.endpoints = server.endpoints.filter(e => e.port !== port);
|
|
111
|
+
console.log(`[registry] Endpoint removed: ${serverId} port=${port} (${server.endpoints.length} remaining)`);
|
|
112
|
+
if (server.endpoints.length === 0) {
|
|
113
|
+
emitServerEvent('deregister', { serverId, server });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** Remove a server entirely (explicit unregister) */
|
|
117
|
+
remove(id) {
|
|
118
|
+
const server = this.servers.get(id);
|
|
119
|
+
if (server) {
|
|
120
|
+
this.servers.delete(id);
|
|
121
|
+
const projectServers = this.projectIdIndex.get(server.projectId);
|
|
122
|
+
if (projectServers) {
|
|
123
|
+
projectServers.delete(id);
|
|
124
|
+
if (projectServers.size === 0) {
|
|
125
|
+
this.projectIdIndex.delete(server.projectId);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const index = this.connectionOrder.indexOf(id);
|
|
129
|
+
if (index !== -1) {
|
|
130
|
+
this.connectionOrder.splice(index, 1);
|
|
131
|
+
}
|
|
132
|
+
console.log(`[registry] Server removed: ${id}`);
|
|
133
|
+
emitServerEvent('deregister', { serverId: id, server });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
get(id) {
|
|
137
|
+
return this.servers.get(id);
|
|
138
|
+
}
|
|
139
|
+
getByProjectId(projectId) {
|
|
140
|
+
const serverIds = this.projectIdIndex.get(projectId);
|
|
141
|
+
if (!serverIds)
|
|
142
|
+
return [];
|
|
143
|
+
return [...serverIds].map(id => this.servers.get(id)).filter(Boolean);
|
|
144
|
+
}
|
|
145
|
+
getByDirectory(directory) {
|
|
146
|
+
return this.getAll().filter(s => s.directory === directory);
|
|
147
|
+
}
|
|
148
|
+
getAll() {
|
|
149
|
+
return Array.from(this.servers.values());
|
|
150
|
+
}
|
|
151
|
+
getByType(type) {
|
|
152
|
+
return this.getAll().filter(s => s.type === type);
|
|
153
|
+
}
|
|
154
|
+
/** Find a server that has an endpoint on this port */
|
|
155
|
+
getByPort(port) {
|
|
156
|
+
return this.getAll().find(s => s.endpoints.some(e => e.port === port));
|
|
157
|
+
}
|
|
158
|
+
getLatest() {
|
|
159
|
+
if (this.connectionOrder.length === 0)
|
|
160
|
+
return undefined;
|
|
161
|
+
const latestId = this.connectionOrder[this.connectionOrder.length - 1];
|
|
162
|
+
return this.servers.get(latestId);
|
|
163
|
+
}
|
|
164
|
+
has(id) {
|
|
165
|
+
return this.servers.has(id);
|
|
166
|
+
}
|
|
167
|
+
size() {
|
|
168
|
+
return this.servers.size;
|
|
169
|
+
}
|
|
170
|
+
/** List all registered project directories */
|
|
171
|
+
directories() {
|
|
172
|
+
return [...new Set(this.getAll().map(s => s.directory))];
|
|
173
|
+
}
|
|
174
|
+
/** Check if any endpoint on any server has a live process */
|
|
175
|
+
hasLiveEndpoints(serverId) {
|
|
176
|
+
const server = this.servers.get(serverId);
|
|
177
|
+
if (!server)
|
|
178
|
+
return false;
|
|
179
|
+
return server.endpoints.length > 0;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Remove endpoints whose processes are no longer running.
|
|
183
|
+
* Skips endpoints registered within the last 30s (grace period for process forks).
|
|
184
|
+
* Server entries are never removed — only endpoints are cleaned up.
|
|
185
|
+
*/
|
|
186
|
+
cleanupDeadEndpoints() {
|
|
187
|
+
const cleaned = [];
|
|
188
|
+
const now = Date.now();
|
|
189
|
+
for (const server of this.getAll()) {
|
|
190
|
+
for (const endpoint of [...server.endpoints]) {
|
|
191
|
+
if (now - endpoint.registeredAt < 30_000)
|
|
192
|
+
continue;
|
|
193
|
+
try {
|
|
194
|
+
process.kill(endpoint.pid, 0);
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
this.removeEndpoint(server.id, endpoint.port);
|
|
198
|
+
cleaned.push(`${server.id}:${endpoint.port}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return cleaned;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=registry.js.map
|