safari-devtools-mcp 0.1.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/LICENSE +21 -0
- package/README.md +268 -0
- package/build/src/SafariDriver.d.ts +56 -0
- package/build/src/SafariDriver.d.ts.map +1 -0
- package/build/src/SafariDriver.js +475 -0
- package/build/src/SafariDriver.js.map +1 -0
- package/build/src/bin/safari-devtools-mcp.d.ts +9 -0
- package/build/src/bin/safari-devtools-mcp.d.ts.map +1 -0
- package/build/src/bin/safari-devtools-mcp.js +42 -0
- package/build/src/bin/safari-devtools-mcp.js.map +1 -0
- package/build/src/formatters/ConsoleFormatter.d.ts +7 -0
- package/build/src/formatters/ConsoleFormatter.d.ts.map +1 -0
- package/build/src/formatters/ConsoleFormatter.js +47 -0
- package/build/src/formatters/ConsoleFormatter.js.map +1 -0
- package/build/src/formatters/NetworkFormatter.d.ts +7 -0
- package/build/src/formatters/NetworkFormatter.d.ts.map +1 -0
- package/build/src/formatters/NetworkFormatter.js +70 -0
- package/build/src/formatters/NetworkFormatter.js.map +1 -0
- package/build/src/formatters/SnapshotFormatter.d.ts +6 -0
- package/build/src/formatters/SnapshotFormatter.d.ts.map +1 -0
- package/build/src/formatters/SnapshotFormatter.js +34 -0
- package/build/src/formatters/SnapshotFormatter.js.map +1 -0
- package/build/src/index.d.ts +13 -0
- package/build/src/index.d.ts.map +1 -0
- package/build/src/index.js +81 -0
- package/build/src/index.js.map +1 -0
- package/build/src/injected/console.d.ts +9 -0
- package/build/src/injected/console.d.ts.map +1 -0
- package/build/src/injected/console.js +119 -0
- package/build/src/injected/console.js.map +1 -0
- package/build/src/injected/network.d.ts +14 -0
- package/build/src/injected/network.d.ts.map +1 -0
- package/build/src/injected/network.js +273 -0
- package/build/src/injected/network.js.map +1 -0
- package/build/src/injected/snapshot.d.ts +9 -0
- package/build/src/injected/snapshot.d.ts.map +1 -0
- package/build/src/injected/snapshot.js +217 -0
- package/build/src/injected/snapshot.js.map +1 -0
- package/build/src/tools/console.d.ts +17 -0
- package/build/src/tools/console.d.ts.map +1 -0
- package/build/src/tools/console.js +76 -0
- package/build/src/tools/console.js.map +1 -0
- package/build/src/tools/input.d.ts +59 -0
- package/build/src/tools/input.d.ts.map +1 -0
- package/build/src/tools/input.js +220 -0
- package/build/src/tools/input.js.map +1 -0
- package/build/src/tools/network.d.ts +17 -0
- package/build/src/tools/network.d.ts.map +1 -0
- package/build/src/tools/network.js +74 -0
- package/build/src/tools/network.js.map +1 -0
- package/build/src/tools/pages.d.ts +36 -0
- package/build/src/tools/pages.d.ts.map +1 -0
- package/build/src/tools/pages.js +236 -0
- package/build/src/tools/pages.js.map +1 -0
- package/build/src/tools/screenshot.d.ts +15 -0
- package/build/src/tools/screenshot.d.ts.map +1 -0
- package/build/src/tools/screenshot.js +70 -0
- package/build/src/tools/screenshot.js.map +1 -0
- package/build/src/tools/script.d.ts +12 -0
- package/build/src/tools/script.d.ts.map +1 -0
- package/build/src/tools/script.js +48 -0
- package/build/src/tools/script.js.map +1 -0
- package/build/src/tools/snapshot.d.ts +17 -0
- package/build/src/tools/snapshot.d.ts.map +1 -0
- package/build/src/tools/snapshot.js +75 -0
- package/build/src/tools/snapshot.js.map +1 -0
- package/build/src/tools/types.d.ts +17 -0
- package/build/src/tools/types.d.ts.map +1 -0
- package/build/src/tools/types.js +5 -0
- package/build/src/tools/types.js.map +1 -0
- package/build/src/types.d.ts +74 -0
- package/build/src/types.d.ts.map +1 -0
- package/build/src/types.js +11 -0
- package/build/src/types.js.map +1 -0
- package/package.json +66 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injectable script for capturing network requests.
|
|
3
|
+
*
|
|
4
|
+
* Hybrid approach:
|
|
5
|
+
* 1. Performance API backfill — captures historical requests made before injection
|
|
6
|
+
* (timing, size, URL available; no headers/status)
|
|
7
|
+
* 2. Fetch interception — captures ongoing fetch() requests with full details
|
|
8
|
+
* 3. XMLHttpRequest interception — captures ongoing XHR requests with full details
|
|
9
|
+
* 4. PerformanceObserver — catches resource loads the interceptors miss
|
|
10
|
+
*
|
|
11
|
+
* Historical entries are flagged so the AI knows they have limited detail.
|
|
12
|
+
*/
|
|
13
|
+
export const NETWORK_CAPTURE_SCRIPT = `
|
|
14
|
+
(function() {
|
|
15
|
+
if (window.__safariDevToolsNetworkInitialized) return;
|
|
16
|
+
window.__safariDevToolsNetworkInitialized = true;
|
|
17
|
+
window.__safariDevToolsNetworkLogs = [];
|
|
18
|
+
window.__safariDevToolsNetworkReqId = 0;
|
|
19
|
+
|
|
20
|
+
// Helper to guess resource type from initiatorType or URL
|
|
21
|
+
function guessResourceType(initiatorType, url) {
|
|
22
|
+
if (initiatorType === 'xmlhttprequest') return 'xhr';
|
|
23
|
+
if (initiatorType === 'fetch') return 'fetch';
|
|
24
|
+
if (initiatorType === 'script') return 'script';
|
|
25
|
+
if (initiatorType === 'link' || initiatorType === 'css') return 'stylesheet';
|
|
26
|
+
if (initiatorType === 'img') return 'image';
|
|
27
|
+
if (initiatorType === 'video' || initiatorType === 'audio') return 'media';
|
|
28
|
+
|
|
29
|
+
// Guess from URL extension
|
|
30
|
+
if (url) {
|
|
31
|
+
var ext = url.split('?')[0].split('#')[0].split('.').pop().toLowerCase();
|
|
32
|
+
var typeMap = {
|
|
33
|
+
js: 'script', mjs: 'script',
|
|
34
|
+
css: 'stylesheet',
|
|
35
|
+
png: 'image', jpg: 'image', jpeg: 'image', gif: 'image', svg: 'image', webp: 'image', ico: 'image',
|
|
36
|
+
woff: 'font', woff2: 'font', ttf: 'font', otf: 'font', eot: 'font',
|
|
37
|
+
mp4: 'media', webm: 'media', mp3: 'media', ogg: 'media',
|
|
38
|
+
html: 'document', htm: 'document'
|
|
39
|
+
};
|
|
40
|
+
if (typeMap[ext]) return typeMap[ext];
|
|
41
|
+
}
|
|
42
|
+
return 'other';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Step 1: Backfill historical requests from Performance API
|
|
46
|
+
try {
|
|
47
|
+
var entries = performance.getEntriesByType('resource');
|
|
48
|
+
entries.forEach(function(entry) {
|
|
49
|
+
window.__safariDevToolsNetworkLogs.push({
|
|
50
|
+
reqid: window.__safariDevToolsNetworkReqId++,
|
|
51
|
+
url: entry.name,
|
|
52
|
+
method: 'GET',
|
|
53
|
+
status: 0,
|
|
54
|
+
statusText: '',
|
|
55
|
+
resourceType: guessResourceType(entry.initiatorType, entry.name),
|
|
56
|
+
startTime: entry.startTime,
|
|
57
|
+
duration: entry.duration,
|
|
58
|
+
transferSize: entry.transferSize || 0,
|
|
59
|
+
encodedBodySize: entry.encodedBodySize || 0,
|
|
60
|
+
decodedBodySize: entry.decodedBodySize || 0,
|
|
61
|
+
requestHeaders: {},
|
|
62
|
+
responseHeaders: {},
|
|
63
|
+
historical: true,
|
|
64
|
+
mimeType: ''
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
} catch(e) {}
|
|
68
|
+
|
|
69
|
+
// Step 2: PerformanceObserver for new resource loads
|
|
70
|
+
try {
|
|
71
|
+
var observer = new PerformanceObserver(function(list) {
|
|
72
|
+
list.getEntries().forEach(function(entry) {
|
|
73
|
+
// Skip if we already have this URL from fetch/XHR interception
|
|
74
|
+
var isDuplicate = window.__safariDevToolsNetworkLogs.some(function(log) {
|
|
75
|
+
return !log.historical && log.url === entry.name && Math.abs(log.startTime - entry.startTime) < 100;
|
|
76
|
+
});
|
|
77
|
+
if (isDuplicate) return;
|
|
78
|
+
|
|
79
|
+
// Only add if not already captured by interceptors
|
|
80
|
+
var hasIntercepted = window.__safariDevToolsNetworkLogs.some(function(log) {
|
|
81
|
+
return !log.historical && log.url === entry.name;
|
|
82
|
+
});
|
|
83
|
+
if (!hasIntercepted) {
|
|
84
|
+
window.__safariDevToolsNetworkLogs.push({
|
|
85
|
+
reqid: window.__safariDevToolsNetworkReqId++,
|
|
86
|
+
url: entry.name,
|
|
87
|
+
method: 'GET',
|
|
88
|
+
status: 0,
|
|
89
|
+
statusText: '',
|
|
90
|
+
resourceType: guessResourceType(entry.initiatorType, entry.name),
|
|
91
|
+
startTime: entry.startTime,
|
|
92
|
+
duration: entry.duration,
|
|
93
|
+
transferSize: entry.transferSize || 0,
|
|
94
|
+
encodedBodySize: entry.encodedBodySize || 0,
|
|
95
|
+
decodedBodySize: entry.decodedBodySize || 0,
|
|
96
|
+
requestHeaders: {},
|
|
97
|
+
responseHeaders: {},
|
|
98
|
+
historical: false,
|
|
99
|
+
mimeType: ''
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
observer.observe({ entryTypes: ['resource'] });
|
|
105
|
+
} catch(e) {}
|
|
106
|
+
|
|
107
|
+
// Step 3: Intercept fetch()
|
|
108
|
+
var originalFetch = window.fetch;
|
|
109
|
+
window.fetch = function() {
|
|
110
|
+
var args = arguments;
|
|
111
|
+
var request;
|
|
112
|
+
try {
|
|
113
|
+
request = new Request(args[0], args[1]);
|
|
114
|
+
} catch(e) {
|
|
115
|
+
return originalFetch.apply(this, args);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
var entry = {
|
|
119
|
+
reqid: window.__safariDevToolsNetworkReqId++,
|
|
120
|
+
url: request.url,
|
|
121
|
+
method: request.method,
|
|
122
|
+
status: 0,
|
|
123
|
+
statusText: '',
|
|
124
|
+
resourceType: 'fetch',
|
|
125
|
+
startTime: performance.now(),
|
|
126
|
+
duration: 0,
|
|
127
|
+
transferSize: 0,
|
|
128
|
+
encodedBodySize: 0,
|
|
129
|
+
decodedBodySize: 0,
|
|
130
|
+
requestHeaders: {},
|
|
131
|
+
responseHeaders: {},
|
|
132
|
+
historical: false,
|
|
133
|
+
mimeType: ''
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Capture request headers
|
|
137
|
+
try {
|
|
138
|
+
request.headers.forEach(function(value, key) {
|
|
139
|
+
entry.requestHeaders[key] = value;
|
|
140
|
+
});
|
|
141
|
+
} catch(e) {}
|
|
142
|
+
|
|
143
|
+
// Capture request body
|
|
144
|
+
try {
|
|
145
|
+
if (args[1] && args[1].body) {
|
|
146
|
+
if (typeof args[1].body === 'string') {
|
|
147
|
+
entry.requestBody = args[1].body;
|
|
148
|
+
} else {
|
|
149
|
+
entry.requestBody = '[Binary or FormData body]';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} catch(e) {}
|
|
153
|
+
|
|
154
|
+
window.__safariDevToolsNetworkLogs.push(entry);
|
|
155
|
+
|
|
156
|
+
return originalFetch.apply(this, args).then(function(response) {
|
|
157
|
+
entry.status = response.status;
|
|
158
|
+
entry.statusText = response.statusText;
|
|
159
|
+
entry.duration = performance.now() - entry.startTime;
|
|
160
|
+
entry.mimeType = response.headers.get('content-type') || '';
|
|
161
|
+
|
|
162
|
+
// Capture response headers
|
|
163
|
+
try {
|
|
164
|
+
response.headers.forEach(function(value, key) {
|
|
165
|
+
entry.responseHeaders[key] = value;
|
|
166
|
+
});
|
|
167
|
+
} catch(e) {}
|
|
168
|
+
|
|
169
|
+
return response;
|
|
170
|
+
}).catch(function(error) {
|
|
171
|
+
entry.error = error.message || String(error);
|
|
172
|
+
entry.duration = performance.now() - entry.startTime;
|
|
173
|
+
throw error;
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// Step 4: Intercept XMLHttpRequest
|
|
178
|
+
var originalXHROpen = XMLHttpRequest.prototype.open;
|
|
179
|
+
var originalXHRSend = XMLHttpRequest.prototype.send;
|
|
180
|
+
var originalXHRSetHeader = XMLHttpRequest.prototype.setRequestHeader;
|
|
181
|
+
|
|
182
|
+
XMLHttpRequest.prototype.open = function(method, url) {
|
|
183
|
+
this.__safariDevToolsMeta = {
|
|
184
|
+
method: method,
|
|
185
|
+
url: String(url),
|
|
186
|
+
requestHeaders: {}
|
|
187
|
+
};
|
|
188
|
+
return originalXHROpen.apply(this, arguments);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
XMLHttpRequest.prototype.setRequestHeader = function(name, value) {
|
|
192
|
+
if (this.__safariDevToolsMeta) {
|
|
193
|
+
this.__safariDevToolsMeta.requestHeaders[name] = value;
|
|
194
|
+
}
|
|
195
|
+
return originalXHRSetHeader.apply(this, arguments);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
XMLHttpRequest.prototype.send = function(body) {
|
|
199
|
+
var xhr = this;
|
|
200
|
+
var meta = xhr.__safariDevToolsMeta;
|
|
201
|
+
|
|
202
|
+
if (meta) {
|
|
203
|
+
var entry = {
|
|
204
|
+
reqid: window.__safariDevToolsNetworkReqId++,
|
|
205
|
+
url: meta.url,
|
|
206
|
+
method: meta.method,
|
|
207
|
+
status: 0,
|
|
208
|
+
statusText: '',
|
|
209
|
+
resourceType: 'xhr',
|
|
210
|
+
startTime: performance.now(),
|
|
211
|
+
duration: 0,
|
|
212
|
+
transferSize: 0,
|
|
213
|
+
encodedBodySize: 0,
|
|
214
|
+
decodedBodySize: 0,
|
|
215
|
+
requestHeaders: meta.requestHeaders,
|
|
216
|
+
responseHeaders: {},
|
|
217
|
+
historical: false,
|
|
218
|
+
mimeType: ''
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
if (body) {
|
|
222
|
+
try {
|
|
223
|
+
entry.requestBody = typeof body === 'string' ? body : '[Binary body]';
|
|
224
|
+
} catch(e) {}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
window.__safariDevToolsNetworkLogs.push(entry);
|
|
228
|
+
|
|
229
|
+
xhr.addEventListener('load', function() {
|
|
230
|
+
entry.status = xhr.status;
|
|
231
|
+
entry.statusText = xhr.statusText;
|
|
232
|
+
entry.duration = performance.now() - entry.startTime;
|
|
233
|
+
entry.mimeType = xhr.getResponseHeader('content-type') || '';
|
|
234
|
+
|
|
235
|
+
// Parse response headers
|
|
236
|
+
try {
|
|
237
|
+
var headerStr = xhr.getAllResponseHeaders();
|
|
238
|
+
if (headerStr) {
|
|
239
|
+
headerStr.split('\\r\\n').forEach(function(line) {
|
|
240
|
+
var parts = line.split(': ');
|
|
241
|
+
if (parts.length >= 2) {
|
|
242
|
+
entry.responseHeaders[parts[0]] = parts.slice(1).join(': ');
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
} catch(e) {}
|
|
247
|
+
|
|
248
|
+
// Estimate body size
|
|
249
|
+
try {
|
|
250
|
+
var responseText = xhr.responseText;
|
|
251
|
+
if (responseText) {
|
|
252
|
+
entry.decodedBodySize = responseText.length;
|
|
253
|
+
entry.encodedBodySize = responseText.length;
|
|
254
|
+
}
|
|
255
|
+
} catch(e) {}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
xhr.addEventListener('error', function() {
|
|
259
|
+
entry.error = 'Network error';
|
|
260
|
+
entry.duration = performance.now() - entry.startTime;
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
xhr.addEventListener('timeout', function() {
|
|
264
|
+
entry.error = 'Request timeout';
|
|
265
|
+
entry.duration = performance.now() - entry.startTime;
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return originalXHRSend.apply(this, arguments);
|
|
270
|
+
};
|
|
271
|
+
})();
|
|
272
|
+
`;
|
|
273
|
+
//# sourceMappingURL=network.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network.js","sourceRoot":"","sources":["../../../src/injected/network.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmQrC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injectable script for taking DOM snapshots with UIDs.
|
|
3
|
+
*
|
|
4
|
+
* Walks the DOM tree and builds an accessibility-like representation.
|
|
5
|
+
* Each element gets a stable data-safari-uid attribute for targeting
|
|
6
|
+
* by other tools (click, fill, screenshot, etc).
|
|
7
|
+
*/
|
|
8
|
+
export declare const SNAPSHOT_SCRIPT = "\nfunction __safariDevToolsTakeSnapshot(verbose) {\n var uidCounter = 0;\n\n function getRole(el) {\n // Explicit ARIA role\n var role = el.getAttribute && el.getAttribute('role');\n if (role) return role;\n\n // Implicit role mapping\n var tag = el.tagName.toLowerCase();\n var roleMap = {\n 'a': 'link',\n 'button': 'button',\n 'input': getInputRole(el),\n 'select': 'combobox',\n 'textarea': 'textbox',\n 'img': 'img',\n 'h1': 'heading',\n 'h2': 'heading',\n 'h3': 'heading',\n 'h4': 'heading',\n 'h5': 'heading',\n 'h6': 'heading',\n 'nav': 'navigation',\n 'main': 'main',\n 'header': 'banner',\n 'footer': 'contentinfo',\n 'aside': 'complementary',\n 'form': 'form',\n 'table': 'table',\n 'thead': 'rowgroup',\n 'tbody': 'rowgroup',\n 'tr': 'row',\n 'th': 'columnheader',\n 'td': 'cell',\n 'ul': 'list',\n 'ol': 'list',\n 'li': 'listitem',\n 'dialog': 'dialog',\n 'details': 'group',\n 'summary': 'button',\n 'progress': 'progressbar',\n 'meter': 'meter',\n 'option': 'option',\n 'section': 'region',\n 'article': 'article',\n 'video': 'video',\n 'audio': 'audio',\n 'iframe': 'document',\n 'label': 'label',\n 'fieldset': 'group',\n 'legend': 'legend'\n };\n return roleMap[tag] || 'generic';\n }\n\n function getInputRole(el) {\n var type = (el.getAttribute('type') || 'text').toLowerCase();\n var map = {\n 'text': 'textbox',\n 'email': 'textbox',\n 'password': 'textbox',\n 'search': 'searchbox',\n 'tel': 'textbox',\n 'url': 'textbox',\n 'number': 'spinbutton',\n 'range': 'slider',\n 'checkbox': 'checkbox',\n 'radio': 'radio',\n 'submit': 'button',\n 'button': 'button',\n 'reset': 'button',\n 'file': 'button',\n 'image': 'button',\n 'hidden': 'none'\n };\n return map[type] || 'textbox';\n }\n\n function getName(el) {\n // aria-label\n var label = el.getAttribute && el.getAttribute('aria-label');\n if (label) return label;\n\n // aria-labelledby\n var labelledBy = el.getAttribute && el.getAttribute('aria-labelledby');\n if (labelledBy) {\n var labelEl = document.getElementById(labelledBy);\n if (labelEl) return labelEl.textContent.trim();\n }\n\n // <label> for inputs\n if (el.id) {\n var labelFor = document.querySelector('label[for=\"' + el.id + '\"]');\n if (labelFor) return labelFor.textContent.trim();\n }\n\n // alt text for images\n if (el.tagName === 'IMG') return el.getAttribute('alt') || '';\n\n // placeholder for inputs\n if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {\n return el.getAttribute('placeholder') || '';\n }\n\n // title attribute\n var title = el.getAttribute && el.getAttribute('title');\n if (title) return title;\n\n // Direct text content (only for leaf-ish elements)\n var childElements = el.children ? el.children.length : 0;\n if (childElements === 0 && el.textContent) {\n var text = el.textContent.trim();\n if (text.length <= 100) return text;\n return text.substring(0, 97) + '...';\n }\n\n return '';\n }\n\n function getValue(el) {\n if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.tagName === 'SELECT') {\n return el.value || '';\n }\n if (el.getAttribute && el.getAttribute('aria-valuenow')) {\n return el.getAttribute('aria-valuenow');\n }\n return undefined;\n }\n\n function isVisible(el) {\n if (!el.getBoundingClientRect) return true;\n var rect = el.getBoundingClientRect();\n if (rect.width === 0 && rect.height === 0) return false;\n var style = window.getComputedStyle(el);\n if (style.display === 'none' || style.visibility === 'hidden') return false;\n if (parseFloat(style.opacity) === 0) return false;\n return true;\n }\n\n function shouldSkip(el) {\n var tag = el.tagName.toLowerCase();\n if (tag === 'script' || tag === 'style' || tag === 'noscript' || tag === 'br' || tag === 'wbr') return true;\n if (el.getAttribute && el.getAttribute('aria-hidden') === 'true') return true;\n return false;\n }\n\n function walkNode(el, depth) {\n if (!el || !el.tagName) return null;\n if (shouldSkip(el)) return null;\n if (!isVisible(el)) return null;\n\n var uid = 'e' + (uidCounter++);\n el.setAttribute('data-safari-uid', uid);\n\n var role = getRole(el);\n var name = getName(el);\n var value = getValue(el);\n\n var node = {\n uid: uid,\n role: role,\n name: name\n };\n\n if (value !== undefined && value !== '') node.value = value;\n\n if (verbose) {\n node.tagName = el.tagName.toLowerCase();\n var attrs = {};\n if (el.id) attrs.id = el.id;\n if (el.className && typeof el.className === 'string') attrs.class = el.className;\n if (el.getAttribute('href')) attrs.href = el.getAttribute('href');\n if (el.getAttribute('src')) attrs.src = el.getAttribute('src');\n if (el.getAttribute('type')) attrs.type = el.getAttribute('type');\n if (el.getAttribute('name')) attrs.name = el.getAttribute('name');\n if (el.getAttribute('disabled') !== null) attrs.disabled = 'true';\n if (el.getAttribute('checked') !== null) attrs.checked = 'true';\n if (el.getAttribute('aria-expanded')) attrs['aria-expanded'] = el.getAttribute('aria-expanded');\n if (el.getAttribute('aria-selected')) attrs['aria-selected'] = el.getAttribute('aria-selected');\n if (el.getAttribute('aria-pressed')) attrs['aria-pressed'] = el.getAttribute('aria-pressed');\n if (Object.keys(attrs).length > 0) node.attributes = attrs;\n }\n\n // Walk children\n var children = [];\n for (var i = 0; i < el.children.length; i++) {\n var childNode = walkNode(el.children[i], depth + 1);\n if (childNode) children.push(childNode);\n }\n node.children = children;\n\n // Skip generic nodes with no name, no value, and exactly one child (flatten)\n if (role === 'generic' && !name && !value && children.length === 1 && !verbose) {\n return children[0];\n }\n\n return node;\n }\n\n return walkNode(document.body || document.documentElement, 0) || {\n uid: 'root',\n role: 'document',\n name: document.title || '',\n children: []\n };\n}\n";
|
|
9
|
+
//# sourceMappingURL=snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../../src/injected/snapshot.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,eAAe,i1MAgN3B,CAAC"}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injectable script for taking DOM snapshots with UIDs.
|
|
3
|
+
*
|
|
4
|
+
* Walks the DOM tree and builds an accessibility-like representation.
|
|
5
|
+
* Each element gets a stable data-safari-uid attribute for targeting
|
|
6
|
+
* by other tools (click, fill, screenshot, etc).
|
|
7
|
+
*/
|
|
8
|
+
export const SNAPSHOT_SCRIPT = `
|
|
9
|
+
function __safariDevToolsTakeSnapshot(verbose) {
|
|
10
|
+
var uidCounter = 0;
|
|
11
|
+
|
|
12
|
+
function getRole(el) {
|
|
13
|
+
// Explicit ARIA role
|
|
14
|
+
var role = el.getAttribute && el.getAttribute('role');
|
|
15
|
+
if (role) return role;
|
|
16
|
+
|
|
17
|
+
// Implicit role mapping
|
|
18
|
+
var tag = el.tagName.toLowerCase();
|
|
19
|
+
var roleMap = {
|
|
20
|
+
'a': 'link',
|
|
21
|
+
'button': 'button',
|
|
22
|
+
'input': getInputRole(el),
|
|
23
|
+
'select': 'combobox',
|
|
24
|
+
'textarea': 'textbox',
|
|
25
|
+
'img': 'img',
|
|
26
|
+
'h1': 'heading',
|
|
27
|
+
'h2': 'heading',
|
|
28
|
+
'h3': 'heading',
|
|
29
|
+
'h4': 'heading',
|
|
30
|
+
'h5': 'heading',
|
|
31
|
+
'h6': 'heading',
|
|
32
|
+
'nav': 'navigation',
|
|
33
|
+
'main': 'main',
|
|
34
|
+
'header': 'banner',
|
|
35
|
+
'footer': 'contentinfo',
|
|
36
|
+
'aside': 'complementary',
|
|
37
|
+
'form': 'form',
|
|
38
|
+
'table': 'table',
|
|
39
|
+
'thead': 'rowgroup',
|
|
40
|
+
'tbody': 'rowgroup',
|
|
41
|
+
'tr': 'row',
|
|
42
|
+
'th': 'columnheader',
|
|
43
|
+
'td': 'cell',
|
|
44
|
+
'ul': 'list',
|
|
45
|
+
'ol': 'list',
|
|
46
|
+
'li': 'listitem',
|
|
47
|
+
'dialog': 'dialog',
|
|
48
|
+
'details': 'group',
|
|
49
|
+
'summary': 'button',
|
|
50
|
+
'progress': 'progressbar',
|
|
51
|
+
'meter': 'meter',
|
|
52
|
+
'option': 'option',
|
|
53
|
+
'section': 'region',
|
|
54
|
+
'article': 'article',
|
|
55
|
+
'video': 'video',
|
|
56
|
+
'audio': 'audio',
|
|
57
|
+
'iframe': 'document',
|
|
58
|
+
'label': 'label',
|
|
59
|
+
'fieldset': 'group',
|
|
60
|
+
'legend': 'legend'
|
|
61
|
+
};
|
|
62
|
+
return roleMap[tag] || 'generic';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getInputRole(el) {
|
|
66
|
+
var type = (el.getAttribute('type') || 'text').toLowerCase();
|
|
67
|
+
var map = {
|
|
68
|
+
'text': 'textbox',
|
|
69
|
+
'email': 'textbox',
|
|
70
|
+
'password': 'textbox',
|
|
71
|
+
'search': 'searchbox',
|
|
72
|
+
'tel': 'textbox',
|
|
73
|
+
'url': 'textbox',
|
|
74
|
+
'number': 'spinbutton',
|
|
75
|
+
'range': 'slider',
|
|
76
|
+
'checkbox': 'checkbox',
|
|
77
|
+
'radio': 'radio',
|
|
78
|
+
'submit': 'button',
|
|
79
|
+
'button': 'button',
|
|
80
|
+
'reset': 'button',
|
|
81
|
+
'file': 'button',
|
|
82
|
+
'image': 'button',
|
|
83
|
+
'hidden': 'none'
|
|
84
|
+
};
|
|
85
|
+
return map[type] || 'textbox';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getName(el) {
|
|
89
|
+
// aria-label
|
|
90
|
+
var label = el.getAttribute && el.getAttribute('aria-label');
|
|
91
|
+
if (label) return label;
|
|
92
|
+
|
|
93
|
+
// aria-labelledby
|
|
94
|
+
var labelledBy = el.getAttribute && el.getAttribute('aria-labelledby');
|
|
95
|
+
if (labelledBy) {
|
|
96
|
+
var labelEl = document.getElementById(labelledBy);
|
|
97
|
+
if (labelEl) return labelEl.textContent.trim();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// <label> for inputs
|
|
101
|
+
if (el.id) {
|
|
102
|
+
var labelFor = document.querySelector('label[for="' + el.id + '"]');
|
|
103
|
+
if (labelFor) return labelFor.textContent.trim();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// alt text for images
|
|
107
|
+
if (el.tagName === 'IMG') return el.getAttribute('alt') || '';
|
|
108
|
+
|
|
109
|
+
// placeholder for inputs
|
|
110
|
+
if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
|
|
111
|
+
return el.getAttribute('placeholder') || '';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// title attribute
|
|
115
|
+
var title = el.getAttribute && el.getAttribute('title');
|
|
116
|
+
if (title) return title;
|
|
117
|
+
|
|
118
|
+
// Direct text content (only for leaf-ish elements)
|
|
119
|
+
var childElements = el.children ? el.children.length : 0;
|
|
120
|
+
if (childElements === 0 && el.textContent) {
|
|
121
|
+
var text = el.textContent.trim();
|
|
122
|
+
if (text.length <= 100) return text;
|
|
123
|
+
return text.substring(0, 97) + '...';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return '';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function getValue(el) {
|
|
130
|
+
if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.tagName === 'SELECT') {
|
|
131
|
+
return el.value || '';
|
|
132
|
+
}
|
|
133
|
+
if (el.getAttribute && el.getAttribute('aria-valuenow')) {
|
|
134
|
+
return el.getAttribute('aria-valuenow');
|
|
135
|
+
}
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function isVisible(el) {
|
|
140
|
+
if (!el.getBoundingClientRect) return true;
|
|
141
|
+
var rect = el.getBoundingClientRect();
|
|
142
|
+
if (rect.width === 0 && rect.height === 0) return false;
|
|
143
|
+
var style = window.getComputedStyle(el);
|
|
144
|
+
if (style.display === 'none' || style.visibility === 'hidden') return false;
|
|
145
|
+
if (parseFloat(style.opacity) === 0) return false;
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function shouldSkip(el) {
|
|
150
|
+
var tag = el.tagName.toLowerCase();
|
|
151
|
+
if (tag === 'script' || tag === 'style' || tag === 'noscript' || tag === 'br' || tag === 'wbr') return true;
|
|
152
|
+
if (el.getAttribute && el.getAttribute('aria-hidden') === 'true') return true;
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function walkNode(el, depth) {
|
|
157
|
+
if (!el || !el.tagName) return null;
|
|
158
|
+
if (shouldSkip(el)) return null;
|
|
159
|
+
if (!isVisible(el)) return null;
|
|
160
|
+
|
|
161
|
+
var uid = 'e' + (uidCounter++);
|
|
162
|
+
el.setAttribute('data-safari-uid', uid);
|
|
163
|
+
|
|
164
|
+
var role = getRole(el);
|
|
165
|
+
var name = getName(el);
|
|
166
|
+
var value = getValue(el);
|
|
167
|
+
|
|
168
|
+
var node = {
|
|
169
|
+
uid: uid,
|
|
170
|
+
role: role,
|
|
171
|
+
name: name
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
if (value !== undefined && value !== '') node.value = value;
|
|
175
|
+
|
|
176
|
+
if (verbose) {
|
|
177
|
+
node.tagName = el.tagName.toLowerCase();
|
|
178
|
+
var attrs = {};
|
|
179
|
+
if (el.id) attrs.id = el.id;
|
|
180
|
+
if (el.className && typeof el.className === 'string') attrs.class = el.className;
|
|
181
|
+
if (el.getAttribute('href')) attrs.href = el.getAttribute('href');
|
|
182
|
+
if (el.getAttribute('src')) attrs.src = el.getAttribute('src');
|
|
183
|
+
if (el.getAttribute('type')) attrs.type = el.getAttribute('type');
|
|
184
|
+
if (el.getAttribute('name')) attrs.name = el.getAttribute('name');
|
|
185
|
+
if (el.getAttribute('disabled') !== null) attrs.disabled = 'true';
|
|
186
|
+
if (el.getAttribute('checked') !== null) attrs.checked = 'true';
|
|
187
|
+
if (el.getAttribute('aria-expanded')) attrs['aria-expanded'] = el.getAttribute('aria-expanded');
|
|
188
|
+
if (el.getAttribute('aria-selected')) attrs['aria-selected'] = el.getAttribute('aria-selected');
|
|
189
|
+
if (el.getAttribute('aria-pressed')) attrs['aria-pressed'] = el.getAttribute('aria-pressed');
|
|
190
|
+
if (Object.keys(attrs).length > 0) node.attributes = attrs;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Walk children
|
|
194
|
+
var children = [];
|
|
195
|
+
for (var i = 0; i < el.children.length; i++) {
|
|
196
|
+
var childNode = walkNode(el.children[i], depth + 1);
|
|
197
|
+
if (childNode) children.push(childNode);
|
|
198
|
+
}
|
|
199
|
+
node.children = children;
|
|
200
|
+
|
|
201
|
+
// Skip generic nodes with no name, no value, and exactly one child (flatten)
|
|
202
|
+
if (role === 'generic' && !name && !value && children.length === 1 && !verbose) {
|
|
203
|
+
return children[0];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return node;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return walkNode(document.body || document.documentElement, 0) || {
|
|
210
|
+
uid: 'root',
|
|
211
|
+
role: 'document',
|
|
212
|
+
name: document.title || '',
|
|
213
|
+
children: []
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
`;
|
|
217
|
+
//# sourceMappingURL=snapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../../src/injected/snapshot.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgN9B,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Console debugging tools.
|
|
3
|
+
* Mirrors chrome-devtools-mcp tool names: list_console_messages, get_console_message
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import type { ToolHandler } from './types.js';
|
|
7
|
+
export declare const listConsoleMessagesSchema: {
|
|
8
|
+
pageSize: z.ZodOptional<z.ZodNumber>;
|
|
9
|
+
pageIdx: z.ZodOptional<z.ZodNumber>;
|
|
10
|
+
types: z.ZodOptional<z.ZodArray<z.ZodEnum<["log", "debug", "info", "error", "warn", "trace", "assert", "dir", "table", "clear", "count", "timeEnd"]>, "many">>;
|
|
11
|
+
};
|
|
12
|
+
export declare const listConsoleMessages: ToolHandler;
|
|
13
|
+
export declare const getConsoleMessageSchema: {
|
|
14
|
+
msgid: z.ZodNumber;
|
|
15
|
+
};
|
|
16
|
+
export declare const getConsoleMessage: ToolHandler;
|
|
17
|
+
//# sourceMappingURL=console.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"console.d.ts","sourceRoot":"","sources":["../../../src/tools/console.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAKtB,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,YAAY,CAAC;AAiB5C,eAAO,MAAM,yBAAyB;;;;CAuBrC,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,WAgBjC,CAAC;AAEF,eAAO,MAAM,uBAAuB;;CAMnC,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,WAgB/B,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Console debugging tools.
|
|
3
|
+
* Mirrors chrome-devtools-mcp tool names: list_console_messages, get_console_message
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { formatConsoleMessage, formatConsoleMessages, } from '../formatters/ConsoleFormatter.js';
|
|
7
|
+
const FILTERABLE_LEVELS = [
|
|
8
|
+
'log',
|
|
9
|
+
'debug',
|
|
10
|
+
'info',
|
|
11
|
+
'error',
|
|
12
|
+
'warn',
|
|
13
|
+
'trace',
|
|
14
|
+
'assert',
|
|
15
|
+
'dir',
|
|
16
|
+
'table',
|
|
17
|
+
'clear',
|
|
18
|
+
'count',
|
|
19
|
+
'timeEnd',
|
|
20
|
+
];
|
|
21
|
+
export const listConsoleMessagesSchema = {
|
|
22
|
+
pageSize: z
|
|
23
|
+
.number()
|
|
24
|
+
.int()
|
|
25
|
+
.positive()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe('Maximum number of messages to return. When omitted, returns all messages.'),
|
|
28
|
+
pageIdx: z
|
|
29
|
+
.number()
|
|
30
|
+
.int()
|
|
31
|
+
.min(0)
|
|
32
|
+
.optional()
|
|
33
|
+
.describe('Page number to return (0-based). When omitted, returns the first page.'),
|
|
34
|
+
types: z
|
|
35
|
+
.array(z.enum(FILTERABLE_LEVELS))
|
|
36
|
+
.optional()
|
|
37
|
+
.describe('Filter messages to only return messages of the specified types. When omitted or empty, returns all messages.'),
|
|
38
|
+
};
|
|
39
|
+
export const listConsoleMessages = async (params, driver) => {
|
|
40
|
+
const allLogs = await driver.getConsoleLogs();
|
|
41
|
+
const filtered = await driver.getConsoleLogs({
|
|
42
|
+
types: params.types,
|
|
43
|
+
pageSize: params.pageSize,
|
|
44
|
+
pageIdx: params.pageIdx,
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
content: [
|
|
48
|
+
{
|
|
49
|
+
type: 'text',
|
|
50
|
+
text: formatConsoleMessages(filtered, allLogs.length),
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
export const getConsoleMessageSchema = {
|
|
56
|
+
msgid: z
|
|
57
|
+
.number()
|
|
58
|
+
.describe('The msgid of a console message on the page from the listed console messages'),
|
|
59
|
+
};
|
|
60
|
+
export const getConsoleMessage = async (params, driver) => {
|
|
61
|
+
const log = await driver.getConsoleMessage(params.msgid);
|
|
62
|
+
if (!log) {
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
{
|
|
66
|
+
type: 'text',
|
|
67
|
+
text: `Console message with msgid=${params.msgid} not found.`,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
content: [{ type: 'text', text: formatConsoleMessage(log) }],
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=console.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"console.js","sourceRoot":"","sources":["../../../src/tools/console.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,EACL,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,mCAAmC,CAAC;AAG3C,MAAM,iBAAiB,GAAG;IACxB,KAAK;IACL,OAAO;IACP,MAAM;IACN,OAAO;IACP,MAAM;IACN,OAAO;IACP,QAAQ;IACR,KAAK;IACL,OAAO;IACP,OAAO;IACP,OAAO;IACP,SAAS;CACD,CAAC;AAEX,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,QAAQ,EAAE;SACV,QAAQ,CACP,2EAA2E,CAC5E;IACH,OAAO,EAAE,CAAC;SACP,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,EAAE;SACV,QAAQ,CACP,wEAAwE,CACzE;IACH,KAAK,EAAE,CAAC;SACL,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;SAChC,QAAQ,EAAE;SACV,QAAQ,CACP,8GAA8G,CAC/G;CACJ,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAgB,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;IACvE,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,cAAc,EAAE,CAAC;IAC9C,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC;QAC3C,KAAK,EAAE,MAAM,CAAC,KAA6B;QAC3C,QAAQ,EAAE,MAAM,CAAC,QAA8B;QAC/C,OAAO,EAAE,MAAM,CAAC,OAA6B;KAC9C,CAAC,CAAC;IAEH,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAe;gBACrB,IAAI,EAAE,qBAAqB,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC;aACtD;SACF;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,QAAQ,CACP,6EAA6E,CAC9E;CACJ,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAgB,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;IACrE,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,MAAM,CAAC,KAAe,CAAC,CAAC;IACnE,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,8BAA8B,MAAM,CAAC,KAAK,aAAa;iBAC9D;aACF;SACF,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,EAAE,CAAC,EAAC,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,oBAAoB,CAAC,GAAG,CAAC,EAAC,CAAC;KACpE,CAAC;AACJ,CAAC,CAAC"}
|