appium-mcp 1.6.1 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.releaserc +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/tools/app-management/list-apps.js +5 -1
- package/dist/tools/app-management/list-apps.js.map +1 -1
- package/dist/tools/context/get-contexts.js +5 -1
- package/dist/tools/context/get-contexts.js.map +1 -1
- package/dist/tools/interactions/get-page-source.js +5 -1
- package/dist/tools/interactions/get-page-source.js.map +1 -1
- package/dist/tools/interactions/screenshot.js +5 -1
- package/dist/tools/interactions/screenshot.js.map +1 -1
- package/dist/tools/session/create-session.js +18 -3
- package/dist/tools/session/create-session.js.map +1 -1
- package/dist/tools/session/select-device.js +9 -2
- package/dist/tools/session/select-device.js.map +1 -1
- package/dist/tools/test-generation/locators.js +5 -1
- package/dist/tools/test-generation/locators.js.map +1 -1
- package/dist/ui/mcp-ui-utils.d.ts +111 -0
- package/dist/ui/mcp-ui-utils.js +1542 -0
- package/dist/ui/mcp-ui-utils.js.map +1 -0
- package/package.json +7 -6
- package/scripts/sync-version.mjs +15 -0
- package/server.json +3 -3
- package/src/tools/app-management/list-apps.ts +14 -1
- package/src/tools/context/get-contexts.ts +14 -1
- package/src/tools/interactions/get-page-source.ts +14 -1
- package/src/tools/interactions/screenshot.ts +14 -1
- package/src/tools/session/create-session.ts +29 -3
- package/src/tools/session/select-device.ts +23 -2
- package/src/tools/test-generation/locators.ts +15 -1
- package/src/ui/mcp-ui-utils.ts +1630 -0
|
@@ -0,0 +1,1542 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP-UI Utility Functions
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities for creating UI resources using MCP-UI protocol.
|
|
5
|
+
* It enables interactive UI components to be returned alongside text responses.
|
|
6
|
+
*
|
|
7
|
+
* Reference: https://mcpui.dev/guide/introduction
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Creates a UIResource object following MCP-UI protocol
|
|
11
|
+
* @param uri - Unique identifier using ui:// scheme (e.g., 'ui://appium-mcp/device-picker')
|
|
12
|
+
* @param htmlContent - HTML string to render in the iframe
|
|
13
|
+
* @returns UIResource object ready to be included in MCP response content
|
|
14
|
+
*/
|
|
15
|
+
export function createUIResource(uri, htmlContent) {
|
|
16
|
+
return {
|
|
17
|
+
type: 'resource',
|
|
18
|
+
resource: {
|
|
19
|
+
uri,
|
|
20
|
+
mimeType: 'text/html',
|
|
21
|
+
text: htmlContent,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Creates a device picker UI component
|
|
27
|
+
* @param devices - Array of device objects with name, udid, state, etc.
|
|
28
|
+
* @param platform - 'android' or 'ios'
|
|
29
|
+
* @param deviceType - 'simulator' or 'real' for iOS
|
|
30
|
+
* @returns HTML string for device picker UI
|
|
31
|
+
*/
|
|
32
|
+
export function createDevicePickerUI(devices, platform, deviceType) {
|
|
33
|
+
const deviceTypeLabel = platform === 'ios' && deviceType
|
|
34
|
+
? deviceType === 'simulator'
|
|
35
|
+
? 'iOS Simulators'
|
|
36
|
+
: 'iOS Devices'
|
|
37
|
+
: 'Android Devices';
|
|
38
|
+
const deviceCards = devices
|
|
39
|
+
.map((device, index) => `
|
|
40
|
+
<div class="device-card" data-udid="${device.udid}" data-index="${index}">
|
|
41
|
+
<div class="device-header">
|
|
42
|
+
<h3>${device.name || device.udid}</h3>
|
|
43
|
+
${device.state ? `<span class="device-state ${device.state.toLowerCase()}">${device.state}</span>` : ''}
|
|
44
|
+
</div>
|
|
45
|
+
<div class="device-details">
|
|
46
|
+
<p><strong>UDID:</strong> <code>${device.udid}</code></p>
|
|
47
|
+
${device.type ? `<p><strong>Type:</strong> ${device.type}</p>` : ''}
|
|
48
|
+
</div>
|
|
49
|
+
<button class="select-device-btn" onclick="selectDevice('${device.udid}')">
|
|
50
|
+
Select Device
|
|
51
|
+
</button>
|
|
52
|
+
</div>
|
|
53
|
+
`)
|
|
54
|
+
.join('');
|
|
55
|
+
return `
|
|
56
|
+
<!DOCTYPE html>
|
|
57
|
+
<html lang="en">
|
|
58
|
+
<head>
|
|
59
|
+
<meta charset="UTF-8">
|
|
60
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
61
|
+
<title>Device Picker - ${deviceTypeLabel}</title>
|
|
62
|
+
<style>
|
|
63
|
+
* {
|
|
64
|
+
margin: 0;
|
|
65
|
+
padding: 0;
|
|
66
|
+
box-sizing: border-box;
|
|
67
|
+
}
|
|
68
|
+
body {
|
|
69
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
70
|
+
padding: 20px;
|
|
71
|
+
background: #f5f5f5;
|
|
72
|
+
color: #333;
|
|
73
|
+
}
|
|
74
|
+
.container {
|
|
75
|
+
max-width: 1200px;
|
|
76
|
+
margin: 0 auto;
|
|
77
|
+
}
|
|
78
|
+
.header {
|
|
79
|
+
margin-bottom: 24px;
|
|
80
|
+
}
|
|
81
|
+
.header h1 {
|
|
82
|
+
font-size: 24px;
|
|
83
|
+
margin-bottom: 8px;
|
|
84
|
+
color: #1a1a1a;
|
|
85
|
+
}
|
|
86
|
+
.header p {
|
|
87
|
+
color: #666;
|
|
88
|
+
font-size: 14px;
|
|
89
|
+
}
|
|
90
|
+
.devices-grid {
|
|
91
|
+
display: grid;
|
|
92
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
93
|
+
gap: 16px;
|
|
94
|
+
}
|
|
95
|
+
.device-card {
|
|
96
|
+
background: white;
|
|
97
|
+
border: 1px solid #e0e0e0;
|
|
98
|
+
border-radius: 8px;
|
|
99
|
+
padding: 16px;
|
|
100
|
+
transition: all 0.2s;
|
|
101
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
102
|
+
}
|
|
103
|
+
.device-card:hover {
|
|
104
|
+
border-color: #007AFF;
|
|
105
|
+
box-shadow: 0 4px 12px rgba(0,122,255,0.15);
|
|
106
|
+
transform: translateY(-2px);
|
|
107
|
+
}
|
|
108
|
+
.device-header {
|
|
109
|
+
display: flex;
|
|
110
|
+
justify-content: space-between;
|
|
111
|
+
align-items: center;
|
|
112
|
+
margin-bottom: 12px;
|
|
113
|
+
}
|
|
114
|
+
.device-header h3 {
|
|
115
|
+
font-size: 16px;
|
|
116
|
+
font-weight: 600;
|
|
117
|
+
color: #1a1a1a;
|
|
118
|
+
}
|
|
119
|
+
.device-state {
|
|
120
|
+
padding: 4px 8px;
|
|
121
|
+
border-radius: 4px;
|
|
122
|
+
font-size: 12px;
|
|
123
|
+
font-weight: 500;
|
|
124
|
+
text-transform: uppercase;
|
|
125
|
+
}
|
|
126
|
+
.device-state.booted {
|
|
127
|
+
background: #d4edda;
|
|
128
|
+
color: #155724;
|
|
129
|
+
}
|
|
130
|
+
.device-state.shutdown {
|
|
131
|
+
background: #f8d7da;
|
|
132
|
+
color: #721c24;
|
|
133
|
+
}
|
|
134
|
+
.device-details {
|
|
135
|
+
margin-bottom: 12px;
|
|
136
|
+
font-size: 13px;
|
|
137
|
+
}
|
|
138
|
+
.device-details p {
|
|
139
|
+
margin-bottom: 6px;
|
|
140
|
+
color: #666;
|
|
141
|
+
}
|
|
142
|
+
.device-details code {
|
|
143
|
+
background: #f5f5f5;
|
|
144
|
+
padding: 2px 6px;
|
|
145
|
+
border-radius: 3px;
|
|
146
|
+
font-size: 12px;
|
|
147
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
148
|
+
}
|
|
149
|
+
.select-device-btn {
|
|
150
|
+
width: 100%;
|
|
151
|
+
padding: 10px 16px;
|
|
152
|
+
background: #007AFF;
|
|
153
|
+
color: white;
|
|
154
|
+
border: none;
|
|
155
|
+
border-radius: 6px;
|
|
156
|
+
font-size: 14px;
|
|
157
|
+
font-weight: 500;
|
|
158
|
+
cursor: pointer;
|
|
159
|
+
transition: background 0.2s;
|
|
160
|
+
}
|
|
161
|
+
.select-device-btn:hover {
|
|
162
|
+
background: #0056b3;
|
|
163
|
+
}
|
|
164
|
+
.select-device-btn:active {
|
|
165
|
+
transform: scale(0.98);
|
|
166
|
+
}
|
|
167
|
+
.empty-state {
|
|
168
|
+
text-align: center;
|
|
169
|
+
padding: 40px;
|
|
170
|
+
color: #999;
|
|
171
|
+
}
|
|
172
|
+
</style>
|
|
173
|
+
</head>
|
|
174
|
+
<body>
|
|
175
|
+
<div class="container">
|
|
176
|
+
<div class="header">
|
|
177
|
+
<h1>📱 Select ${deviceTypeLabel}</h1>
|
|
178
|
+
<p>Found ${devices.length} device${devices.length !== 1 ? 's' : ''}. Click on a device to select it.</p>
|
|
179
|
+
</div>
|
|
180
|
+
<div class="devices-grid">
|
|
181
|
+
${devices.length > 0 ? deviceCards : '<div class="empty-state">No devices found</div>'}
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
<script>
|
|
185
|
+
function selectDevice(udid) {
|
|
186
|
+
// Send intent message to parent window
|
|
187
|
+
window.parent.postMessage({
|
|
188
|
+
type: 'intent',
|
|
189
|
+
payload: {
|
|
190
|
+
intent: 'select-device',
|
|
191
|
+
params: {
|
|
192
|
+
platform: '${platform}',
|
|
193
|
+
${deviceType ? `deviceType: '${deviceType}',` : ''}
|
|
194
|
+
deviceUdid: udid
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}, '*');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Add click handlers for better UX
|
|
201
|
+
document.querySelectorAll('.device-card').forEach(card => {
|
|
202
|
+
card.addEventListener('click', (e) => {
|
|
203
|
+
if (e.target.classList.contains('select-device-btn')) return;
|
|
204
|
+
const udid = card.dataset.udid;
|
|
205
|
+
selectDevice(udid);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
</script>
|
|
209
|
+
</body>
|
|
210
|
+
</html>
|
|
211
|
+
`;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Creates a screenshot viewer UI component
|
|
215
|
+
* @param screenshotBase64 - Base64 encoded PNG image
|
|
216
|
+
* @param filepath - Path where screenshot was saved
|
|
217
|
+
* @returns HTML string for screenshot viewer
|
|
218
|
+
*/
|
|
219
|
+
export function createScreenshotViewerUI(screenshotBase64, filepath) {
|
|
220
|
+
return `
|
|
221
|
+
<!DOCTYPE html>
|
|
222
|
+
<html lang="en">
|
|
223
|
+
<head>
|
|
224
|
+
<meta charset="UTF-8">
|
|
225
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
226
|
+
<title>Screenshot Viewer</title>
|
|
227
|
+
<style>
|
|
228
|
+
* {
|
|
229
|
+
margin: 0;
|
|
230
|
+
padding: 0;
|
|
231
|
+
box-sizing: border-box;
|
|
232
|
+
}
|
|
233
|
+
body {
|
|
234
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
235
|
+
background: #1a1a1a;
|
|
236
|
+
color: #fff;
|
|
237
|
+
padding: 20px;
|
|
238
|
+
overflow: hidden;
|
|
239
|
+
}
|
|
240
|
+
.viewer-container {
|
|
241
|
+
display: flex;
|
|
242
|
+
flex-direction: column;
|
|
243
|
+
height: 100vh;
|
|
244
|
+
max-width: 100%;
|
|
245
|
+
}
|
|
246
|
+
.toolbar {
|
|
247
|
+
display: flex;
|
|
248
|
+
justify-content: space-between;
|
|
249
|
+
align-items: center;
|
|
250
|
+
padding: 12px 16px;
|
|
251
|
+
background: #2a2a2a;
|
|
252
|
+
border-radius: 8px 8px 0 0;
|
|
253
|
+
margin-bottom: 1px;
|
|
254
|
+
}
|
|
255
|
+
.toolbar-left {
|
|
256
|
+
display: flex;
|
|
257
|
+
gap: 8px;
|
|
258
|
+
align-items: center;
|
|
259
|
+
}
|
|
260
|
+
.toolbar-right {
|
|
261
|
+
display: flex;
|
|
262
|
+
gap: 8px;
|
|
263
|
+
}
|
|
264
|
+
.btn {
|
|
265
|
+
padding: 6px 12px;
|
|
266
|
+
background: #007AFF;
|
|
267
|
+
color: white;
|
|
268
|
+
border: none;
|
|
269
|
+
border-radius: 4px;
|
|
270
|
+
font-size: 13px;
|
|
271
|
+
cursor: pointer;
|
|
272
|
+
transition: background 0.2s;
|
|
273
|
+
}
|
|
274
|
+
.btn:hover {
|
|
275
|
+
background: #0056b3;
|
|
276
|
+
}
|
|
277
|
+
.btn-secondary {
|
|
278
|
+
background: #444;
|
|
279
|
+
}
|
|
280
|
+
.btn-secondary:hover {
|
|
281
|
+
background: #555;
|
|
282
|
+
}
|
|
283
|
+
.filepath {
|
|
284
|
+
font-size: 12px;
|
|
285
|
+
color: #999;
|
|
286
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
287
|
+
}
|
|
288
|
+
.image-container {
|
|
289
|
+
flex: 1;
|
|
290
|
+
overflow: auto;
|
|
291
|
+
background: #1a1a1a;
|
|
292
|
+
display: flex;
|
|
293
|
+
align-items: center;
|
|
294
|
+
justify-content: center;
|
|
295
|
+
padding: 20px;
|
|
296
|
+
}
|
|
297
|
+
.screenshot-img {
|
|
298
|
+
max-width: 100%;
|
|
299
|
+
max-height: 100%;
|
|
300
|
+
object-fit: contain;
|
|
301
|
+
border-radius: 4px;
|
|
302
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
|
|
303
|
+
cursor: zoom-in;
|
|
304
|
+
}
|
|
305
|
+
.screenshot-img.zoomed {
|
|
306
|
+
cursor: zoom-out;
|
|
307
|
+
transform: scale(2);
|
|
308
|
+
transition: transform 0.3s;
|
|
309
|
+
}
|
|
310
|
+
.zoom-controls {
|
|
311
|
+
position: absolute;
|
|
312
|
+
bottom: 20px;
|
|
313
|
+
right: 20px;
|
|
314
|
+
display: flex;
|
|
315
|
+
gap: 8px;
|
|
316
|
+
background: rgba(42, 42, 42, 0.9);
|
|
317
|
+
padding: 8px;
|
|
318
|
+
border-radius: 6px;
|
|
319
|
+
}
|
|
320
|
+
.zoom-btn {
|
|
321
|
+
width: 32px;
|
|
322
|
+
height: 32px;
|
|
323
|
+
background: #007AFF;
|
|
324
|
+
color: white;
|
|
325
|
+
border: none;
|
|
326
|
+
border-radius: 4px;
|
|
327
|
+
cursor: pointer;
|
|
328
|
+
font-size: 16px;
|
|
329
|
+
display: flex;
|
|
330
|
+
align-items: center;
|
|
331
|
+
justify-content: center;
|
|
332
|
+
}
|
|
333
|
+
.zoom-btn:hover {
|
|
334
|
+
background: #0056b3;
|
|
335
|
+
}
|
|
336
|
+
</style>
|
|
337
|
+
</head>
|
|
338
|
+
<body>
|
|
339
|
+
<div class="viewer-container">
|
|
340
|
+
<div class="toolbar">
|
|
341
|
+
<div class="toolbar-left">
|
|
342
|
+
<span style="font-size: 14px; font-weight: 500;">📸 Screenshot</span>
|
|
343
|
+
<span class="filepath">${filepath}</span>
|
|
344
|
+
</div>
|
|
345
|
+
<div class="toolbar-right">
|
|
346
|
+
<button class="btn btn-secondary" onclick="downloadScreenshot()">Download</button>
|
|
347
|
+
<button class="btn" onclick="takeNewScreenshot()">Take New</button>
|
|
348
|
+
</div>
|
|
349
|
+
</div>
|
|
350
|
+
<div class="image-container" id="imageContainer">
|
|
351
|
+
<img src="data:image/png;base64,${screenshotBase64}"
|
|
352
|
+
alt="Screenshot"
|
|
353
|
+
class="screenshot-img"
|
|
354
|
+
id="screenshotImg"
|
|
355
|
+
onclick="toggleZoom()">
|
|
356
|
+
</div>
|
|
357
|
+
<div class="zoom-controls">
|
|
358
|
+
<button class="zoom-btn" onclick="zoomIn()">+</button>
|
|
359
|
+
<button class="zoom-btn" onclick="zoomOut()">−</button>
|
|
360
|
+
<button class="zoom-btn" onclick="resetZoom()">⌂</button>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
<script>
|
|
364
|
+
let currentZoom = 1;
|
|
365
|
+
const img = document.getElementById('screenshotImg');
|
|
366
|
+
|
|
367
|
+
function toggleZoom() {
|
|
368
|
+
if (currentZoom === 1) {
|
|
369
|
+
zoomIn();
|
|
370
|
+
} else {
|
|
371
|
+
resetZoom();
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function zoomIn() {
|
|
376
|
+
currentZoom = Math.min(currentZoom + 0.5, 4);
|
|
377
|
+
img.style.transform = \`scale(\${currentZoom})\`;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function zoomOut() {
|
|
381
|
+
currentZoom = Math.max(currentZoom - 0.5, 0.5);
|
|
382
|
+
img.style.transform = \`scale(\${currentZoom})\`;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function resetZoom() {
|
|
386
|
+
currentZoom = 1;
|
|
387
|
+
img.style.transform = 'scale(1)';
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function downloadScreenshot() {
|
|
391
|
+
const link = document.createElement('a');
|
|
392
|
+
link.href = img.src;
|
|
393
|
+
link.download = '${filepath.split('/').pop() || 'screenshot.png'}';
|
|
394
|
+
link.click();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function takeNewScreenshot() {
|
|
398
|
+
window.parent.postMessage({
|
|
399
|
+
type: 'tool',
|
|
400
|
+
payload: {
|
|
401
|
+
toolName: 'appium_screenshot',
|
|
402
|
+
params: {}
|
|
403
|
+
}
|
|
404
|
+
}, '*');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Keyboard shortcuts
|
|
408
|
+
document.addEventListener('keydown', (e) => {
|
|
409
|
+
if (e.key === '+' || e.key === '=') zoomIn();
|
|
410
|
+
if (e.key === '-') zoomOut();
|
|
411
|
+
if (e.key === '0') resetZoom();
|
|
412
|
+
});
|
|
413
|
+
</script>
|
|
414
|
+
</body>
|
|
415
|
+
</html>
|
|
416
|
+
`;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Creates a session dashboard UI component
|
|
420
|
+
* @param sessionInfo - Session information object
|
|
421
|
+
* @returns HTML string for session dashboard
|
|
422
|
+
*/
|
|
423
|
+
export function createSessionDashboardUI(sessionInfo) {
|
|
424
|
+
// Safely convert sessionId to string
|
|
425
|
+
const sessionIdStr = typeof sessionInfo.sessionId === 'string'
|
|
426
|
+
? sessionInfo.sessionId
|
|
427
|
+
: String(sessionInfo.sessionId || 'Unknown');
|
|
428
|
+
// Get first 8 characters for display, or full string if shorter
|
|
429
|
+
const sessionIdDisplay = sessionIdStr.length > 8
|
|
430
|
+
? `${sessionIdStr.substring(0, 8)}...`
|
|
431
|
+
: sessionIdStr;
|
|
432
|
+
return `
|
|
433
|
+
<!DOCTYPE html>
|
|
434
|
+
<html lang="en">
|
|
435
|
+
<head>
|
|
436
|
+
<meta charset="UTF-8">
|
|
437
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
438
|
+
<title>Session Dashboard</title>
|
|
439
|
+
<style>
|
|
440
|
+
* {
|
|
441
|
+
margin: 0;
|
|
442
|
+
padding: 0;
|
|
443
|
+
box-sizing: border-box;
|
|
444
|
+
}
|
|
445
|
+
body {
|
|
446
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
447
|
+
background: #f5f5f5;
|
|
448
|
+
padding: 20px;
|
|
449
|
+
}
|
|
450
|
+
.dashboard {
|
|
451
|
+
max-width: 800px;
|
|
452
|
+
margin: 0 auto;
|
|
453
|
+
background: white;
|
|
454
|
+
border-radius: 12px;
|
|
455
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
456
|
+
overflow: hidden;
|
|
457
|
+
}
|
|
458
|
+
.header {
|
|
459
|
+
background: linear-gradient(135deg, #007AFF 0%, #0056b3 100%);
|
|
460
|
+
color: white;
|
|
461
|
+
padding: 24px;
|
|
462
|
+
}
|
|
463
|
+
.header h1 {
|
|
464
|
+
font-size: 24px;
|
|
465
|
+
margin-bottom: 8px;
|
|
466
|
+
}
|
|
467
|
+
.header .status {
|
|
468
|
+
display: inline-block;
|
|
469
|
+
padding: 4px 12px;
|
|
470
|
+
background: rgba(255,255,255,0.2);
|
|
471
|
+
border-radius: 12px;
|
|
472
|
+
font-size: 12px;
|
|
473
|
+
font-weight: 500;
|
|
474
|
+
}
|
|
475
|
+
.content {
|
|
476
|
+
padding: 24px;
|
|
477
|
+
}
|
|
478
|
+
.info-grid {
|
|
479
|
+
display: grid;
|
|
480
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
481
|
+
gap: 16px;
|
|
482
|
+
margin-bottom: 24px;
|
|
483
|
+
}
|
|
484
|
+
.info-card {
|
|
485
|
+
padding: 16px;
|
|
486
|
+
background: #f8f9fa;
|
|
487
|
+
border-radius: 8px;
|
|
488
|
+
border-left: 3px solid #007AFF;
|
|
489
|
+
}
|
|
490
|
+
.info-card label {
|
|
491
|
+
display: block;
|
|
492
|
+
font-size: 12px;
|
|
493
|
+
color: #666;
|
|
494
|
+
margin-bottom: 4px;
|
|
495
|
+
text-transform: uppercase;
|
|
496
|
+
font-weight: 500;
|
|
497
|
+
}
|
|
498
|
+
.info-card value {
|
|
499
|
+
display: block;
|
|
500
|
+
font-size: 16px;
|
|
501
|
+
color: #1a1a1a;
|
|
502
|
+
font-weight: 600;
|
|
503
|
+
}
|
|
504
|
+
.actions {
|
|
505
|
+
display: flex;
|
|
506
|
+
gap: 12px;
|
|
507
|
+
flex-wrap: wrap;
|
|
508
|
+
}
|
|
509
|
+
.btn {
|
|
510
|
+
padding: 10px 20px;
|
|
511
|
+
border: none;
|
|
512
|
+
border-radius: 6px;
|
|
513
|
+
font-size: 14px;
|
|
514
|
+
font-weight: 500;
|
|
515
|
+
cursor: pointer;
|
|
516
|
+
transition: all 0.2s;
|
|
517
|
+
}
|
|
518
|
+
.btn-primary {
|
|
519
|
+
background: #007AFF;
|
|
520
|
+
color: white;
|
|
521
|
+
}
|
|
522
|
+
.btn-primary:hover {
|
|
523
|
+
background: #0056b3;
|
|
524
|
+
}
|
|
525
|
+
.btn-secondary {
|
|
526
|
+
background: #f0f0f0;
|
|
527
|
+
color: #333;
|
|
528
|
+
}
|
|
529
|
+
.btn-secondary:hover {
|
|
530
|
+
background: #e0e0e0;
|
|
531
|
+
}
|
|
532
|
+
.btn-danger {
|
|
533
|
+
background: #dc3545;
|
|
534
|
+
color: white;
|
|
535
|
+
}
|
|
536
|
+
.btn-danger:hover {
|
|
537
|
+
background: #c82333;
|
|
538
|
+
}
|
|
539
|
+
</style>
|
|
540
|
+
</head>
|
|
541
|
+
<body>
|
|
542
|
+
<div class="dashboard">
|
|
543
|
+
<div class="header">
|
|
544
|
+
<h1>📱 Appium Session Dashboard</h1>
|
|
545
|
+
<span class="status">● Active</span>
|
|
546
|
+
</div>
|
|
547
|
+
<div class="content">
|
|
548
|
+
<div class="info-grid">
|
|
549
|
+
<div class="info-card">
|
|
550
|
+
<label>Session ID</label>
|
|
551
|
+
<value>${sessionIdDisplay}</value>
|
|
552
|
+
</div>
|
|
553
|
+
<div class="info-card">
|
|
554
|
+
<label>Platform</label>
|
|
555
|
+
<value>${sessionInfo.platform}</value>
|
|
556
|
+
</div>
|
|
557
|
+
<div class="info-card">
|
|
558
|
+
<label>Automation</label>
|
|
559
|
+
<value>${sessionInfo.automationName}</value>
|
|
560
|
+
</div>
|
|
561
|
+
${sessionInfo.deviceName
|
|
562
|
+
? `
|
|
563
|
+
<div class="info-card">
|
|
564
|
+
<label>Device</label>
|
|
565
|
+
<value>${sessionInfo.deviceName}</value>
|
|
566
|
+
</div>
|
|
567
|
+
`
|
|
568
|
+
: ''}
|
|
569
|
+
${sessionInfo.platformVersion
|
|
570
|
+
? `
|
|
571
|
+
<div class="info-card">
|
|
572
|
+
<label>Platform Version</label>
|
|
573
|
+
<value>${sessionInfo.platformVersion}</value>
|
|
574
|
+
</div>
|
|
575
|
+
`
|
|
576
|
+
: ''}
|
|
577
|
+
${sessionInfo.udid
|
|
578
|
+
? `
|
|
579
|
+
<div class="info-card">
|
|
580
|
+
<label>UDID</label>
|
|
581
|
+
<value><code style="font-size: 12px;">${sessionInfo.udid}</code></value>
|
|
582
|
+
</div>
|
|
583
|
+
`
|
|
584
|
+
: ''}
|
|
585
|
+
</div>
|
|
586
|
+
<div class="actions">
|
|
587
|
+
<button class="btn btn-primary" onclick="takeScreenshot()">📸 Screenshot</button>
|
|
588
|
+
<button class="btn btn-primary" onclick="getPageSource()">📄 Page Source</button>
|
|
589
|
+
<button class="btn btn-primary" onclick="generateLocators()">🔍 Generate Locators</button>
|
|
590
|
+
<button class="btn btn-secondary" onclick="getContexts()">🌐 Contexts</button>
|
|
591
|
+
<button class="btn btn-danger" onclick="deleteSession()">🗑️ End Session</button>
|
|
592
|
+
</div>
|
|
593
|
+
</div>
|
|
594
|
+
</div>
|
|
595
|
+
<script>
|
|
596
|
+
function takeScreenshot() {
|
|
597
|
+
window.parent.postMessage({
|
|
598
|
+
type: 'tool',
|
|
599
|
+
payload: {
|
|
600
|
+
toolName: 'appium_screenshot',
|
|
601
|
+
params: {}
|
|
602
|
+
}
|
|
603
|
+
}, '*');
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function getPageSource() {
|
|
607
|
+
window.parent.postMessage({
|
|
608
|
+
type: 'tool',
|
|
609
|
+
payload: {
|
|
610
|
+
toolName: 'appium_get_page_source',
|
|
611
|
+
params: {}
|
|
612
|
+
}
|
|
613
|
+
}, '*');
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function generateLocators() {
|
|
617
|
+
window.parent.postMessage({
|
|
618
|
+
type: 'tool',
|
|
619
|
+
payload: {
|
|
620
|
+
toolName: 'generate_locators',
|
|
621
|
+
params: {}
|
|
622
|
+
}
|
|
623
|
+
}, '*');
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function getContexts() {
|
|
627
|
+
window.parent.postMessage({
|
|
628
|
+
type: 'tool',
|
|
629
|
+
payload: {
|
|
630
|
+
toolName: 'appium_get_contexts',
|
|
631
|
+
params: {}
|
|
632
|
+
}
|
|
633
|
+
}, '*');
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function deleteSession() {
|
|
637
|
+
if (confirm('Are you sure you want to end this session?')) {
|
|
638
|
+
window.parent.postMessage({
|
|
639
|
+
type: 'tool',
|
|
640
|
+
payload: {
|
|
641
|
+
toolName: 'delete_session',
|
|
642
|
+
params: {}
|
|
643
|
+
}
|
|
644
|
+
}, '*');
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
</script>
|
|
648
|
+
</body>
|
|
649
|
+
</html>
|
|
650
|
+
`;
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Creates a locator generator UI component
|
|
654
|
+
* @param locators - Array of elements with locators
|
|
655
|
+
* @returns HTML string for locator generator UI
|
|
656
|
+
*/
|
|
657
|
+
export function createLocatorGeneratorUI(locators) {
|
|
658
|
+
const locatorCards = locators
|
|
659
|
+
.map((element, index) => `
|
|
660
|
+
<div class="locator-card" data-index="${index}">
|
|
661
|
+
<div class="locator-header">
|
|
662
|
+
<h3>${element.tagName}</h3>
|
|
663
|
+
<div class="badges">
|
|
664
|
+
${element.clickable ? '<span class="badge badge-clickable">Clickable</span>' : ''}
|
|
665
|
+
${element.enabled ? '<span class="badge badge-enabled">Enabled</span>' : ''}
|
|
666
|
+
${element.displayed ? '<span class="badge badge-displayed">Displayed</span>' : ''}
|
|
667
|
+
</div>
|
|
668
|
+
</div>
|
|
669
|
+
${element.text ? `<p class="element-text"><strong>Text:</strong> ${element.text}</p>` : ''}
|
|
670
|
+
${element.contentDesc ? `<p class="element-text"><strong>Content Desc:</strong> ${element.contentDesc}</p>` : ''}
|
|
671
|
+
${element.resourceId ? `<p class="element-text"><strong>Resource ID:</strong> <code>${element.resourceId}</code></p>` : ''}
|
|
672
|
+
<div class="locators-list">
|
|
673
|
+
${Object.entries(element.locators)
|
|
674
|
+
.map(([strategy, selector]) => `
|
|
675
|
+
<div class="locator-item">
|
|
676
|
+
<span class="strategy">${strategy}</span>
|
|
677
|
+
<code class="selector">${selector}</code>
|
|
678
|
+
<button class="test-btn" onclick="testLocator('${strategy}', \`${selector.replace(/`/g, '\\`')}\`)">Test</button>
|
|
679
|
+
</div>
|
|
680
|
+
`)
|
|
681
|
+
.join('')}
|
|
682
|
+
</div>
|
|
683
|
+
</div>
|
|
684
|
+
`)
|
|
685
|
+
.join('');
|
|
686
|
+
return `
|
|
687
|
+
<!DOCTYPE html>
|
|
688
|
+
<html lang="en">
|
|
689
|
+
<head>
|
|
690
|
+
<meta charset="UTF-8">
|
|
691
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
692
|
+
<title>Locator Generator</title>
|
|
693
|
+
<style>
|
|
694
|
+
* {
|
|
695
|
+
margin: 0;
|
|
696
|
+
padding: 0;
|
|
697
|
+
box-sizing: border-box;
|
|
698
|
+
}
|
|
699
|
+
body {
|
|
700
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
701
|
+
background: #f5f5f5;
|
|
702
|
+
padding: 20px;
|
|
703
|
+
}
|
|
704
|
+
.container {
|
|
705
|
+
max-width: 1200px;
|
|
706
|
+
margin: 0 auto;
|
|
707
|
+
}
|
|
708
|
+
.header {
|
|
709
|
+
margin-bottom: 24px;
|
|
710
|
+
}
|
|
711
|
+
.header h1 {
|
|
712
|
+
font-size: 24px;
|
|
713
|
+
margin-bottom: 8px;
|
|
714
|
+
}
|
|
715
|
+
.locators-grid {
|
|
716
|
+
display: grid;
|
|
717
|
+
gap: 16px;
|
|
718
|
+
}
|
|
719
|
+
.locator-card {
|
|
720
|
+
background: white;
|
|
721
|
+
border: 1px solid #e0e0e0;
|
|
722
|
+
border-radius: 8px;
|
|
723
|
+
padding: 16px;
|
|
724
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
725
|
+
}
|
|
726
|
+
.locator-header {
|
|
727
|
+
display: flex;
|
|
728
|
+
justify-content: space-between;
|
|
729
|
+
align-items: center;
|
|
730
|
+
margin-bottom: 12px;
|
|
731
|
+
}
|
|
732
|
+
.locator-header h3 {
|
|
733
|
+
font-size: 16px;
|
|
734
|
+
font-weight: 600;
|
|
735
|
+
}
|
|
736
|
+
.badges {
|
|
737
|
+
display: flex;
|
|
738
|
+
gap: 6px;
|
|
739
|
+
}
|
|
740
|
+
.badge {
|
|
741
|
+
padding: 2px 8px;
|
|
742
|
+
border-radius: 4px;
|
|
743
|
+
font-size: 11px;
|
|
744
|
+
font-weight: 500;
|
|
745
|
+
}
|
|
746
|
+
.badge-clickable {
|
|
747
|
+
background: #d4edda;
|
|
748
|
+
color: #155724;
|
|
749
|
+
}
|
|
750
|
+
.badge-enabled {
|
|
751
|
+
background: #d1ecf1;
|
|
752
|
+
color: #0c5460;
|
|
753
|
+
}
|
|
754
|
+
.badge-displayed {
|
|
755
|
+
background: #fff3cd;
|
|
756
|
+
color: #856404;
|
|
757
|
+
}
|
|
758
|
+
.element-text {
|
|
759
|
+
font-size: 13px;
|
|
760
|
+
color: #666;
|
|
761
|
+
margin-bottom: 8px;
|
|
762
|
+
}
|
|
763
|
+
.element-text code {
|
|
764
|
+
background: #f5f5f5;
|
|
765
|
+
padding: 2px 6px;
|
|
766
|
+
border-radius: 3px;
|
|
767
|
+
font-size: 12px;
|
|
768
|
+
}
|
|
769
|
+
.locators-list {
|
|
770
|
+
margin-top: 12px;
|
|
771
|
+
}
|
|
772
|
+
.locator-item {
|
|
773
|
+
display: flex;
|
|
774
|
+
align-items: center;
|
|
775
|
+
gap: 12px;
|
|
776
|
+
padding: 8px;
|
|
777
|
+
background: #f8f9fa;
|
|
778
|
+
border-radius: 4px;
|
|
779
|
+
margin-bottom: 6px;
|
|
780
|
+
}
|
|
781
|
+
.strategy {
|
|
782
|
+
font-size: 12px;
|
|
783
|
+
font-weight: 600;
|
|
784
|
+
color: #007AFF;
|
|
785
|
+
min-width: 120px;
|
|
786
|
+
}
|
|
787
|
+
.selector {
|
|
788
|
+
flex: 1;
|
|
789
|
+
font-size: 12px;
|
|
790
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
791
|
+
background: white;
|
|
792
|
+
padding: 4px 8px;
|
|
793
|
+
border-radius: 3px;
|
|
794
|
+
overflow-x: auto;
|
|
795
|
+
}
|
|
796
|
+
.test-btn {
|
|
797
|
+
padding: 4px 12px;
|
|
798
|
+
background: #007AFF;
|
|
799
|
+
color: white;
|
|
800
|
+
border: none;
|
|
801
|
+
border-radius: 4px;
|
|
802
|
+
font-size: 12px;
|
|
803
|
+
cursor: pointer;
|
|
804
|
+
}
|
|
805
|
+
.test-btn:hover {
|
|
806
|
+
background: #0056b3;
|
|
807
|
+
}
|
|
808
|
+
</style>
|
|
809
|
+
</head>
|
|
810
|
+
<body>
|
|
811
|
+
<div class="container">
|
|
812
|
+
<div class="header">
|
|
813
|
+
<h1>🔍 Generated Locators</h1>
|
|
814
|
+
<p>Found ${locators.length} interactable element${locators.length !== 1 ? 's' : ''}</p>
|
|
815
|
+
</div>
|
|
816
|
+
<div class="locators-grid">
|
|
817
|
+
${locators.length > 0 ? locatorCards : '<p>No locators found</p>'}
|
|
818
|
+
</div>
|
|
819
|
+
</div>
|
|
820
|
+
<script>
|
|
821
|
+
function testLocator(strategy, selector) {
|
|
822
|
+
window.parent.postMessage({
|
|
823
|
+
type: 'tool',
|
|
824
|
+
payload: {
|
|
825
|
+
toolName: 'appium_find_element',
|
|
826
|
+
params: {
|
|
827
|
+
strategy: strategy,
|
|
828
|
+
selector: selector
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}, '*');
|
|
832
|
+
}
|
|
833
|
+
</script>
|
|
834
|
+
</body>
|
|
835
|
+
</html>
|
|
836
|
+
`;
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Creates a page source inspector UI component
|
|
840
|
+
* @param pageSource - XML page source string
|
|
841
|
+
* @returns HTML string for page source inspector
|
|
842
|
+
*/
|
|
843
|
+
export function createPageSourceInspectorUI(pageSource) {
|
|
844
|
+
// Escape HTML for safe display
|
|
845
|
+
const escapedSource = pageSource
|
|
846
|
+
.replace(/&/g, '&')
|
|
847
|
+
.replace(/</g, '<')
|
|
848
|
+
.replace(/>/g, '>')
|
|
849
|
+
.replace(/"/g, '"')
|
|
850
|
+
.replace(/'/g, ''');
|
|
851
|
+
return `
|
|
852
|
+
<!DOCTYPE html>
|
|
853
|
+
<html lang="en">
|
|
854
|
+
<head>
|
|
855
|
+
<meta charset="UTF-8">
|
|
856
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
857
|
+
<title>Page Source Inspector</title>
|
|
858
|
+
<style>
|
|
859
|
+
* {
|
|
860
|
+
margin: 0;
|
|
861
|
+
padding: 0;
|
|
862
|
+
box-sizing: border-box;
|
|
863
|
+
}
|
|
864
|
+
body {
|
|
865
|
+
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
|
|
866
|
+
background: #1e1e1e;
|
|
867
|
+
color: #d4d4d4;
|
|
868
|
+
padding: 0;
|
|
869
|
+
overflow: hidden;
|
|
870
|
+
}
|
|
871
|
+
.toolbar {
|
|
872
|
+
display: flex;
|
|
873
|
+
justify-content: space-between;
|
|
874
|
+
align-items: center;
|
|
875
|
+
padding: 12px 16px;
|
|
876
|
+
background: #2d2d2d;
|
|
877
|
+
border-bottom: 1px solid #3e3e3e;
|
|
878
|
+
}
|
|
879
|
+
.toolbar-left {
|
|
880
|
+
display: flex;
|
|
881
|
+
gap: 8px;
|
|
882
|
+
align-items: center;
|
|
883
|
+
}
|
|
884
|
+
.toolbar-right {
|
|
885
|
+
display: flex;
|
|
886
|
+
gap: 8px;
|
|
887
|
+
}
|
|
888
|
+
.btn {
|
|
889
|
+
padding: 6px 12px;
|
|
890
|
+
background: #007AFF;
|
|
891
|
+
color: white;
|
|
892
|
+
border: none;
|
|
893
|
+
border-radius: 4px;
|
|
894
|
+
font-size: 13px;
|
|
895
|
+
cursor: pointer;
|
|
896
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
897
|
+
}
|
|
898
|
+
.btn:hover {
|
|
899
|
+
background: #0056b3;
|
|
900
|
+
}
|
|
901
|
+
.btn-secondary {
|
|
902
|
+
background: #444;
|
|
903
|
+
}
|
|
904
|
+
.btn-secondary:hover {
|
|
905
|
+
background: #555;
|
|
906
|
+
}
|
|
907
|
+
.info {
|
|
908
|
+
font-size: 12px;
|
|
909
|
+
color: #999;
|
|
910
|
+
}
|
|
911
|
+
.viewer {
|
|
912
|
+
height: calc(100vh - 50px);
|
|
913
|
+
overflow: auto;
|
|
914
|
+
padding: 16px;
|
|
915
|
+
}
|
|
916
|
+
.xml-content {
|
|
917
|
+
background: #1e1e1e;
|
|
918
|
+
color: #d4d4d4;
|
|
919
|
+
white-space: pre;
|
|
920
|
+
font-size: 13px;
|
|
921
|
+
line-height: 1.6;
|
|
922
|
+
}
|
|
923
|
+
.xml-tag {
|
|
924
|
+
color: #569cd6;
|
|
925
|
+
}
|
|
926
|
+
.xml-attr {
|
|
927
|
+
color: #9cdcfe;
|
|
928
|
+
}
|
|
929
|
+
.xml-value {
|
|
930
|
+
color: #ce9178;
|
|
931
|
+
}
|
|
932
|
+
.search-box {
|
|
933
|
+
padding: 6px 12px;
|
|
934
|
+
background: #3e3e3e;
|
|
935
|
+
border: 1px solid #555;
|
|
936
|
+
border-radius: 4px;
|
|
937
|
+
color: #d4d4d4;
|
|
938
|
+
font-size: 13px;
|
|
939
|
+
width: 200px;
|
|
940
|
+
}
|
|
941
|
+
.search-box:focus {
|
|
942
|
+
outline: none;
|
|
943
|
+
border-color: #007AFF;
|
|
944
|
+
}
|
|
945
|
+
</style>
|
|
946
|
+
</head>
|
|
947
|
+
<body>
|
|
948
|
+
<div class="toolbar">
|
|
949
|
+
<div class="toolbar-left">
|
|
950
|
+
<span style="font-size: 14px; font-weight: 500;">📄 Page Source Inspector</span>
|
|
951
|
+
<span class="info">${pageSource.length} characters</span>
|
|
952
|
+
</div>
|
|
953
|
+
<div class="toolbar-right">
|
|
954
|
+
<input type="text" class="search-box" id="searchBox" placeholder="Search...">
|
|
955
|
+
<button class="btn btn-secondary" onclick="copyToClipboard()">Copy</button>
|
|
956
|
+
<button class="btn btn-secondary" onclick="formatXML()">Format</button>
|
|
957
|
+
<button class="btn" onclick="generateLocators()">Generate Locators</button>
|
|
958
|
+
</div>
|
|
959
|
+
</div>
|
|
960
|
+
<div class="viewer">
|
|
961
|
+
<pre class="xml-content" id="xmlContent">${escapedSource}</pre>
|
|
962
|
+
</div>
|
|
963
|
+
<script>
|
|
964
|
+
function copyToClipboard() {
|
|
965
|
+
const text = document.getElementById('xmlContent').textContent;
|
|
966
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
967
|
+
alert('Copied to clipboard!');
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
function formatXML() {
|
|
972
|
+
const content = document.getElementById('xmlContent').textContent;
|
|
973
|
+
try {
|
|
974
|
+
const parser = new DOMParser();
|
|
975
|
+
const xmlDoc = parser.parseFromString(content, 'text/xml');
|
|
976
|
+
const serializer = new XMLSerializer();
|
|
977
|
+
const formatted = serializer.serializeToString(xmlDoc);
|
|
978
|
+
document.getElementById('xmlContent').textContent = formatted;
|
|
979
|
+
} catch (e) {
|
|
980
|
+
alert('Failed to format XML');
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
function generateLocators() {
|
|
985
|
+
window.parent.postMessage({
|
|
986
|
+
type: 'tool',
|
|
987
|
+
payload: {
|
|
988
|
+
toolName: 'generate_locators',
|
|
989
|
+
params: {}
|
|
990
|
+
}
|
|
991
|
+
}, '*');
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// Search functionality
|
|
995
|
+
document.getElementById('searchBox').addEventListener('input', (e) => {
|
|
996
|
+
const searchTerm = e.target.value.toLowerCase();
|
|
997
|
+
const content = document.getElementById('xmlContent');
|
|
998
|
+
if (!searchTerm) {
|
|
999
|
+
content.innerHTML = \`${escapedSource}\`;
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
const highlighted = content.textContent.replace(
|
|
1003
|
+
new RegExp(\`(\${searchTerm})\`, 'gi'),
|
|
1004
|
+
'<mark style="background: #ffd700; color: #000;">$1</mark>'
|
|
1005
|
+
);
|
|
1006
|
+
content.innerHTML = highlighted;
|
|
1007
|
+
});
|
|
1008
|
+
</script>
|
|
1009
|
+
</body>
|
|
1010
|
+
</html>
|
|
1011
|
+
`;
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Creates a context switcher UI component
|
|
1015
|
+
* @param contexts - Array of context names
|
|
1016
|
+
* @param currentContext - Currently active context name
|
|
1017
|
+
* @returns HTML string for context switcher
|
|
1018
|
+
*/
|
|
1019
|
+
export function createContextSwitcherUI(contexts, currentContext) {
|
|
1020
|
+
const contextCards = contexts
|
|
1021
|
+
.map(context => `
|
|
1022
|
+
<div class="context-card ${context === currentContext ? 'active' : ''}"
|
|
1023
|
+
onclick="switchContext('${context}')">
|
|
1024
|
+
<div class="context-header">
|
|
1025
|
+
<h3>${context}</h3>
|
|
1026
|
+
${context === currentContext ? '<span class="badge-active">Active</span>' : ''}
|
|
1027
|
+
</div>
|
|
1028
|
+
<div class="context-type">
|
|
1029
|
+
${context === 'NATIVE_APP' ? '📱 Native App' : '🌐 WebView'}
|
|
1030
|
+
</div>
|
|
1031
|
+
<button class="switch-btn" onclick="event.stopPropagation(); switchContext('${context}')">
|
|
1032
|
+
${context === currentContext ? 'Current' : 'Switch'}
|
|
1033
|
+
</button>
|
|
1034
|
+
</div>
|
|
1035
|
+
`)
|
|
1036
|
+
.join('');
|
|
1037
|
+
return `
|
|
1038
|
+
<!DOCTYPE html>
|
|
1039
|
+
<html lang="en">
|
|
1040
|
+
<head>
|
|
1041
|
+
<meta charset="UTF-8">
|
|
1042
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1043
|
+
<title>Context Switcher</title>
|
|
1044
|
+
<style>
|
|
1045
|
+
* {
|
|
1046
|
+
margin: 0;
|
|
1047
|
+
padding: 0;
|
|
1048
|
+
box-sizing: border-box;
|
|
1049
|
+
}
|
|
1050
|
+
body {
|
|
1051
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
1052
|
+
background: #f5f5f5;
|
|
1053
|
+
padding: 20px;
|
|
1054
|
+
}
|
|
1055
|
+
.container {
|
|
1056
|
+
max-width: 800px;
|
|
1057
|
+
margin: 0 auto;
|
|
1058
|
+
}
|
|
1059
|
+
.header {
|
|
1060
|
+
margin-bottom: 24px;
|
|
1061
|
+
}
|
|
1062
|
+
.header h1 {
|
|
1063
|
+
font-size: 24px;
|
|
1064
|
+
margin-bottom: 8px;
|
|
1065
|
+
}
|
|
1066
|
+
.header p {
|
|
1067
|
+
color: #666;
|
|
1068
|
+
font-size: 14px;
|
|
1069
|
+
}
|
|
1070
|
+
.contexts-grid {
|
|
1071
|
+
display: grid;
|
|
1072
|
+
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
|
1073
|
+
gap: 16px;
|
|
1074
|
+
}
|
|
1075
|
+
.context-card {
|
|
1076
|
+
background: white;
|
|
1077
|
+
border: 2px solid #e0e0e0;
|
|
1078
|
+
border-radius: 8px;
|
|
1079
|
+
padding: 16px;
|
|
1080
|
+
cursor: pointer;
|
|
1081
|
+
transition: all 0.2s;
|
|
1082
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
1083
|
+
}
|
|
1084
|
+
.context-card:hover {
|
|
1085
|
+
border-color: #007AFF;
|
|
1086
|
+
box-shadow: 0 4px 12px rgba(0,122,255,0.15);
|
|
1087
|
+
transform: translateY(-2px);
|
|
1088
|
+
}
|
|
1089
|
+
.context-card.active {
|
|
1090
|
+
border-color: #007AFF;
|
|
1091
|
+
background: #f0f7ff;
|
|
1092
|
+
}
|
|
1093
|
+
.context-header {
|
|
1094
|
+
display: flex;
|
|
1095
|
+
justify-content: space-between;
|
|
1096
|
+
align-items: center;
|
|
1097
|
+
margin-bottom: 12px;
|
|
1098
|
+
}
|
|
1099
|
+
.context-header h3 {
|
|
1100
|
+
font-size: 16px;
|
|
1101
|
+
font-weight: 600;
|
|
1102
|
+
color: #1a1a1a;
|
|
1103
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
1104
|
+
}
|
|
1105
|
+
.badge-active {
|
|
1106
|
+
padding: 4px 8px;
|
|
1107
|
+
background: #d4edda;
|
|
1108
|
+
color: #155724;
|
|
1109
|
+
border-radius: 4px;
|
|
1110
|
+
font-size: 11px;
|
|
1111
|
+
font-weight: 500;
|
|
1112
|
+
}
|
|
1113
|
+
.context-type {
|
|
1114
|
+
font-size: 13px;
|
|
1115
|
+
color: #666;
|
|
1116
|
+
margin-bottom: 12px;
|
|
1117
|
+
}
|
|
1118
|
+
.switch-btn {
|
|
1119
|
+
width: 100%;
|
|
1120
|
+
padding: 8px 16px;
|
|
1121
|
+
background: #007AFF;
|
|
1122
|
+
color: white;
|
|
1123
|
+
border: none;
|
|
1124
|
+
border-radius: 6px;
|
|
1125
|
+
font-size: 14px;
|
|
1126
|
+
font-weight: 500;
|
|
1127
|
+
cursor: pointer;
|
|
1128
|
+
transition: background 0.2s;
|
|
1129
|
+
}
|
|
1130
|
+
.switch-btn:hover {
|
|
1131
|
+
background: #0056b3;
|
|
1132
|
+
}
|
|
1133
|
+
.context-card.active .switch-btn {
|
|
1134
|
+
background: #6c757d;
|
|
1135
|
+
cursor: default;
|
|
1136
|
+
}
|
|
1137
|
+
</style>
|
|
1138
|
+
</head>
|
|
1139
|
+
<body>
|
|
1140
|
+
<div class="container">
|
|
1141
|
+
<div class="header">
|
|
1142
|
+
<h1>🌐 Context Switcher</h1>
|
|
1143
|
+
<p>Found ${contexts.length} context${contexts.length !== 1 ? 's' : ''}. Click to switch.</p>
|
|
1144
|
+
</div>
|
|
1145
|
+
<div class="contexts-grid">
|
|
1146
|
+
${contexts.length > 0 ? contextCards : '<p>No contexts available</p>'}
|
|
1147
|
+
</div>
|
|
1148
|
+
</div>
|
|
1149
|
+
<script>
|
|
1150
|
+
function switchContext(contextName) {
|
|
1151
|
+
window.parent.postMessage({
|
|
1152
|
+
type: 'tool',
|
|
1153
|
+
payload: {
|
|
1154
|
+
toolName: 'appium_switch_context',
|
|
1155
|
+
params: {
|
|
1156
|
+
context: contextName
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
}, '*');
|
|
1160
|
+
}
|
|
1161
|
+
</script>
|
|
1162
|
+
</body>
|
|
1163
|
+
</html>
|
|
1164
|
+
`;
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Creates an app list UI component
|
|
1168
|
+
* @param apps - Array of app objects with packageName and appName
|
|
1169
|
+
* @returns HTML string for app list
|
|
1170
|
+
*/
|
|
1171
|
+
export function createAppListUI(apps) {
|
|
1172
|
+
const appCards = apps
|
|
1173
|
+
.map(app => `
|
|
1174
|
+
<div class="app-card" data-package="${app.packageName}">
|
|
1175
|
+
<div class="app-header">
|
|
1176
|
+
<h3>${app.appName || app.packageName}</h3>
|
|
1177
|
+
</div>
|
|
1178
|
+
<div class="app-details">
|
|
1179
|
+
<p><strong>Package:</strong> <code>${app.packageName}</code></p>
|
|
1180
|
+
</div>
|
|
1181
|
+
<div class="app-actions">
|
|
1182
|
+
<button class="btn btn-primary" onclick="activateApp('${app.packageName}')">Activate</button>
|
|
1183
|
+
<button class="btn btn-secondary" onclick="terminateApp('${app.packageName}')">Terminate</button>
|
|
1184
|
+
<button class="btn btn-danger" onclick="uninstallApp('${app.packageName}')">Uninstall</button>
|
|
1185
|
+
</div>
|
|
1186
|
+
</div>
|
|
1187
|
+
`)
|
|
1188
|
+
.join('');
|
|
1189
|
+
return `
|
|
1190
|
+
<!DOCTYPE html>
|
|
1191
|
+
<html lang="en">
|
|
1192
|
+
<head>
|
|
1193
|
+
<meta charset="UTF-8">
|
|
1194
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1195
|
+
<title>Installed Apps</title>
|
|
1196
|
+
<style>
|
|
1197
|
+
* {
|
|
1198
|
+
margin: 0;
|
|
1199
|
+
padding: 0;
|
|
1200
|
+
box-sizing: border-box;
|
|
1201
|
+
}
|
|
1202
|
+
body {
|
|
1203
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
1204
|
+
background: #f5f5f5;
|
|
1205
|
+
padding: 20px;
|
|
1206
|
+
}
|
|
1207
|
+
.container {
|
|
1208
|
+
max-width: 1200px;
|
|
1209
|
+
margin: 0 auto;
|
|
1210
|
+
}
|
|
1211
|
+
.header {
|
|
1212
|
+
margin-bottom: 24px;
|
|
1213
|
+
display: flex;
|
|
1214
|
+
justify-content: space-between;
|
|
1215
|
+
align-items: center;
|
|
1216
|
+
}
|
|
1217
|
+
.header h1 {
|
|
1218
|
+
font-size: 24px;
|
|
1219
|
+
}
|
|
1220
|
+
.header p {
|
|
1221
|
+
color: #666;
|
|
1222
|
+
font-size: 14px;
|
|
1223
|
+
}
|
|
1224
|
+
.search-box {
|
|
1225
|
+
padding: 8px 12px;
|
|
1226
|
+
border: 1px solid #ddd;
|
|
1227
|
+
border-radius: 6px;
|
|
1228
|
+
font-size: 14px;
|
|
1229
|
+
width: 300px;
|
|
1230
|
+
}
|
|
1231
|
+
.apps-grid {
|
|
1232
|
+
display: grid;
|
|
1233
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
1234
|
+
gap: 16px;
|
|
1235
|
+
}
|
|
1236
|
+
.app-card {
|
|
1237
|
+
background: white;
|
|
1238
|
+
border: 1px solid #e0e0e0;
|
|
1239
|
+
border-radius: 8px;
|
|
1240
|
+
padding: 16px;
|
|
1241
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
1242
|
+
transition: all 0.2s;
|
|
1243
|
+
}
|
|
1244
|
+
.app-card:hover {
|
|
1245
|
+
border-color: #007AFF;
|
|
1246
|
+
box-shadow: 0 4px 12px rgba(0,122,255,0.15);
|
|
1247
|
+
}
|
|
1248
|
+
.app-header h3 {
|
|
1249
|
+
font-size: 16px;
|
|
1250
|
+
font-weight: 600;
|
|
1251
|
+
margin-bottom: 12px;
|
|
1252
|
+
color: #1a1a1a;
|
|
1253
|
+
}
|
|
1254
|
+
.app-details {
|
|
1255
|
+
margin-bottom: 12px;
|
|
1256
|
+
font-size: 13px;
|
|
1257
|
+
}
|
|
1258
|
+
.app-details code {
|
|
1259
|
+
background: #f5f5f5;
|
|
1260
|
+
padding: 2px 6px;
|
|
1261
|
+
border-radius: 3px;
|
|
1262
|
+
font-size: 12px;
|
|
1263
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
1264
|
+
}
|
|
1265
|
+
.app-actions {
|
|
1266
|
+
display: flex;
|
|
1267
|
+
gap: 8px;
|
|
1268
|
+
flex-wrap: wrap;
|
|
1269
|
+
}
|
|
1270
|
+
.btn {
|
|
1271
|
+
padding: 6px 12px;
|
|
1272
|
+
border: none;
|
|
1273
|
+
border-radius: 4px;
|
|
1274
|
+
font-size: 12px;
|
|
1275
|
+
font-weight: 500;
|
|
1276
|
+
cursor: pointer;
|
|
1277
|
+
transition: background 0.2s;
|
|
1278
|
+
}
|
|
1279
|
+
.btn-primary {
|
|
1280
|
+
background: #007AFF;
|
|
1281
|
+
color: white;
|
|
1282
|
+
}
|
|
1283
|
+
.btn-primary:hover {
|
|
1284
|
+
background: #0056b3;
|
|
1285
|
+
}
|
|
1286
|
+
.btn-secondary {
|
|
1287
|
+
background: #6c757d;
|
|
1288
|
+
color: white;
|
|
1289
|
+
}
|
|
1290
|
+
.btn-secondary:hover {
|
|
1291
|
+
background: #5a6268;
|
|
1292
|
+
}
|
|
1293
|
+
.btn-danger {
|
|
1294
|
+
background: #dc3545;
|
|
1295
|
+
color: white;
|
|
1296
|
+
}
|
|
1297
|
+
.btn-danger:hover {
|
|
1298
|
+
background: #c82333;
|
|
1299
|
+
}
|
|
1300
|
+
.hidden {
|
|
1301
|
+
display: none;
|
|
1302
|
+
}
|
|
1303
|
+
</style>
|
|
1304
|
+
</head>
|
|
1305
|
+
<body>
|
|
1306
|
+
<div class="container">
|
|
1307
|
+
<div class="header">
|
|
1308
|
+
<div>
|
|
1309
|
+
<h1>📱 Installed Apps</h1>
|
|
1310
|
+
<p>Found ${apps.length} app${apps.length !== 1 ? 's' : ''}</p>
|
|
1311
|
+
</div>
|
|
1312
|
+
<input type="text" class="search-box" id="searchBox" placeholder="Search apps...">
|
|
1313
|
+
</div>
|
|
1314
|
+
<div class="apps-grid" id="appsGrid">
|
|
1315
|
+
${apps.length > 0 ? appCards : '<p>No apps found</p>'}
|
|
1316
|
+
</div>
|
|
1317
|
+
</div>
|
|
1318
|
+
<script>
|
|
1319
|
+
function activateApp(packageName) {
|
|
1320
|
+
window.parent.postMessage({
|
|
1321
|
+
type: 'tool',
|
|
1322
|
+
payload: {
|
|
1323
|
+
toolName: 'appium_activate_app',
|
|
1324
|
+
params: {
|
|
1325
|
+
id: packageName
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}, '*');
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
function terminateApp(packageName) {
|
|
1332
|
+
if (confirm('Are you sure you want to terminate this app?')) {
|
|
1333
|
+
window.parent.postMessage({
|
|
1334
|
+
type: 'tool',
|
|
1335
|
+
payload: {
|
|
1336
|
+
toolName: 'appium_terminate_app',
|
|
1337
|
+
params: {
|
|
1338
|
+
id: packageName
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}, '*');
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
function uninstallApp(packageName) {
|
|
1346
|
+
if (confirm('Are you sure you want to uninstall this app? This action cannot be undone.')) {
|
|
1347
|
+
window.parent.postMessage({
|
|
1348
|
+
type: 'tool',
|
|
1349
|
+
payload: {
|
|
1350
|
+
toolName: 'appium_uninstall_app',
|
|
1351
|
+
params: {
|
|
1352
|
+
id: packageName
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}, '*');
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
// Search functionality
|
|
1360
|
+
document.getElementById('searchBox').addEventListener('input', (e) => {
|
|
1361
|
+
const searchTerm = e.target.value.toLowerCase();
|
|
1362
|
+
const cards = document.querySelectorAll('.app-card');
|
|
1363
|
+
cards.forEach(card => {
|
|
1364
|
+
const text = card.textContent.toLowerCase();
|
|
1365
|
+
if (text.includes(searchTerm)) {
|
|
1366
|
+
card.classList.remove('hidden');
|
|
1367
|
+
} else {
|
|
1368
|
+
card.classList.add('hidden');
|
|
1369
|
+
}
|
|
1370
|
+
});
|
|
1371
|
+
});
|
|
1372
|
+
</script>
|
|
1373
|
+
</body>
|
|
1374
|
+
</html>
|
|
1375
|
+
`;
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Creates a test code viewer UI component
|
|
1379
|
+
* @param code - Generated test code string
|
|
1380
|
+
* @param language - Code language (java, javascript, etc.)
|
|
1381
|
+
* @returns HTML string for test code viewer
|
|
1382
|
+
*/
|
|
1383
|
+
export function createTestCodeViewerUI(code, language = 'java') {
|
|
1384
|
+
// Escape HTML for safe display
|
|
1385
|
+
const escapedCode = code
|
|
1386
|
+
.replace(/&/g, '&')
|
|
1387
|
+
.replace(/</g, '<')
|
|
1388
|
+
.replace(/>/g, '>')
|
|
1389
|
+
.replace(/"/g, '"')
|
|
1390
|
+
.replace(/'/g, ''');
|
|
1391
|
+
return `
|
|
1392
|
+
<!DOCTYPE html>
|
|
1393
|
+
<html lang="en">
|
|
1394
|
+
<head>
|
|
1395
|
+
<meta charset="UTF-8">
|
|
1396
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1397
|
+
<title>Test Code Viewer</title>
|
|
1398
|
+
<style>
|
|
1399
|
+
* {
|
|
1400
|
+
margin: 0;
|
|
1401
|
+
padding: 0;
|
|
1402
|
+
box-sizing: border-box;
|
|
1403
|
+
}
|
|
1404
|
+
body {
|
|
1405
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
1406
|
+
background: #1e1e1e;
|
|
1407
|
+
color: #d4d4d4;
|
|
1408
|
+
overflow: hidden;
|
|
1409
|
+
}
|
|
1410
|
+
.toolbar {
|
|
1411
|
+
display: flex;
|
|
1412
|
+
justify-content: space-between;
|
|
1413
|
+
align-items: center;
|
|
1414
|
+
padding: 12px 16px;
|
|
1415
|
+
background: #2d2d2d;
|
|
1416
|
+
border-bottom: 1px solid #3e3e3e;
|
|
1417
|
+
}
|
|
1418
|
+
.toolbar-left {
|
|
1419
|
+
display: flex;
|
|
1420
|
+
gap: 12px;
|
|
1421
|
+
align-items: center;
|
|
1422
|
+
}
|
|
1423
|
+
.toolbar-right {
|
|
1424
|
+
display: flex;
|
|
1425
|
+
gap: 8px;
|
|
1426
|
+
}
|
|
1427
|
+
.btn {
|
|
1428
|
+
padding: 6px 12px;
|
|
1429
|
+
background: #007AFF;
|
|
1430
|
+
color: white;
|
|
1431
|
+
border: none;
|
|
1432
|
+
border-radius: 4px;
|
|
1433
|
+
font-size: 13px;
|
|
1434
|
+
cursor: pointer;
|
|
1435
|
+
transition: background 0.2s;
|
|
1436
|
+
}
|
|
1437
|
+
.btn:hover {
|
|
1438
|
+
background: #0056b3;
|
|
1439
|
+
}
|
|
1440
|
+
.btn-secondary {
|
|
1441
|
+
background: #444;
|
|
1442
|
+
}
|
|
1443
|
+
.btn-secondary:hover {
|
|
1444
|
+
background: #555;
|
|
1445
|
+
}
|
|
1446
|
+
.language-badge {
|
|
1447
|
+
padding: 4px 8px;
|
|
1448
|
+
background: #007AFF;
|
|
1449
|
+
color: white;
|
|
1450
|
+
border-radius: 4px;
|
|
1451
|
+
font-size: 11px;
|
|
1452
|
+
font-weight: 500;
|
|
1453
|
+
text-transform: uppercase;
|
|
1454
|
+
}
|
|
1455
|
+
.viewer {
|
|
1456
|
+
height: calc(100vh - 50px);
|
|
1457
|
+
overflow: auto;
|
|
1458
|
+
padding: 16px;
|
|
1459
|
+
}
|
|
1460
|
+
.code-content {
|
|
1461
|
+
background: #1e1e1e;
|
|
1462
|
+
color: #d4d4d4;
|
|
1463
|
+
white-space: pre;
|
|
1464
|
+
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
|
|
1465
|
+
font-size: 13px;
|
|
1466
|
+
line-height: 1.6;
|
|
1467
|
+
margin: 0;
|
|
1468
|
+
}
|
|
1469
|
+
.line-numbers {
|
|
1470
|
+
display: inline-block;
|
|
1471
|
+
padding-right: 16px;
|
|
1472
|
+
color: #858585;
|
|
1473
|
+
user-select: none;
|
|
1474
|
+
text-align: right;
|
|
1475
|
+
min-width: 50px;
|
|
1476
|
+
}
|
|
1477
|
+
</style>
|
|
1478
|
+
</head>
|
|
1479
|
+
<body>
|
|
1480
|
+
<div class="toolbar">
|
|
1481
|
+
<div class="toolbar-left">
|
|
1482
|
+
<span style="font-size: 14px; font-weight: 500;">💻 Test Code Viewer</span>
|
|
1483
|
+
<span class="language-badge">${language}</span>
|
|
1484
|
+
<span style="font-size: 12px; color: #999;">${code.length} characters, ${code.split('\\n').length} lines</span>
|
|
1485
|
+
</div>
|
|
1486
|
+
<div class="toolbar-right">
|
|
1487
|
+
<button class="btn btn-secondary" onclick="copyToClipboard()">Copy Code</button>
|
|
1488
|
+
<button class="btn btn-secondary" onclick="downloadCode()">Download</button>
|
|
1489
|
+
<button class="btn" onclick="formatCode()">Format</button>
|
|
1490
|
+
</div>
|
|
1491
|
+
</div>
|
|
1492
|
+
<div class="viewer">
|
|
1493
|
+
<pre class="code-content" id="codeContent">${escapedCode}</pre>
|
|
1494
|
+
</div>
|
|
1495
|
+
<script>
|
|
1496
|
+
function copyToClipboard() {
|
|
1497
|
+
const text = document.getElementById('codeContent').textContent;
|
|
1498
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
1499
|
+
alert('Code copied to clipboard!');
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
function downloadCode() {
|
|
1504
|
+
const text = document.getElementById('codeContent').textContent;
|
|
1505
|
+
const blob = new Blob([text], { type: 'text/plain' });
|
|
1506
|
+
const url = URL.createObjectURL(blob);
|
|
1507
|
+
const a = document.createElement('a');
|
|
1508
|
+
a.href = url;
|
|
1509
|
+
a.download = 'TestCode.${language === 'java' ? 'java' : 'js'}';
|
|
1510
|
+
a.click();
|
|
1511
|
+
URL.revokeObjectURL(url);
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
function formatCode() {
|
|
1515
|
+
// Basic formatting - could be enhanced with a proper formatter
|
|
1516
|
+
const content = document.getElementById('codeContent');
|
|
1517
|
+
const text = content.textContent;
|
|
1518
|
+
// Add line numbers
|
|
1519
|
+
const lines = text.split('\\n');
|
|
1520
|
+
const formatted = lines.map((line, i) =>
|
|
1521
|
+
\`<span class="line-numbers">\${i + 1}</span>\${line}\`
|
|
1522
|
+
).join('\\n');
|
|
1523
|
+
content.innerHTML = formatted;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
// Initial format
|
|
1527
|
+
formatCode();
|
|
1528
|
+
</script>
|
|
1529
|
+
</body>
|
|
1530
|
+
</html>
|
|
1531
|
+
`;
|
|
1532
|
+
}
|
|
1533
|
+
/**
|
|
1534
|
+
* Helper function to add UI resource to response content
|
|
1535
|
+
* Returns both text and UI resource for backward compatibility
|
|
1536
|
+
*/
|
|
1537
|
+
export function addUIResourceToResponse(response, uiResource) {
|
|
1538
|
+
return {
|
|
1539
|
+
content: [...response.content, uiResource],
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
//# sourceMappingURL=mcp-ui-utils.js.map
|