@mozilla/firefox-devtools-mcp-moz 0.9.3
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/README.md +219 -0
- package/dist.moz/index.js +33246 -0
- package/dist.moz/snapshot.injected.global.js +1 -0
- package/package.json +79 -0
- package/plugins/claude/firefox-devtools/.claude-plugin/plugin.json +11 -0
- package/plugins/claude/firefox-devtools/.mcp.json +8 -0
- package/plugins/claude/firefox-devtools/README.md +82 -0
- package/plugins/claude/firefox-devtools/agents/e2e-tester.md +36 -0
- package/plugins/claude/firefox-devtools/agents/web-scraper.md +35 -0
- package/plugins/claude/firefox-devtools/commands/debug.md +32 -0
- package/plugins/claude/firefox-devtools/commands/navigate.md +31 -0
- package/plugins/claude/firefox-devtools/commands/screenshot.md +30 -0
- package/plugins/claude/firefox-devtools/skills/browser-automation/SKILL.md +65 -0
- package/scripts/_helpers/page-loader.js +121 -0
- package/scripts/demo-server.js +325 -0
- package/scripts/generate-moz-package.mjs +35 -0
- package/scripts/publish-moz-package.mjs +52 -0
- package/scripts/run-integration-tests-windows.mjs +188 -0
- package/scripts/setup-mcp-config.js +260 -0
- package/scripts/test-bidi-devtools.js +380 -0
- package/scripts/test-console.js +148 -0
- package/scripts/test-dialog.js +102 -0
- package/scripts/test-firefox-temp.js +21 -0
- package/scripts/test-input-tools.js +233 -0
- package/scripts/test-lifecycle-hooks.js +125 -0
- package/scripts/test-mozlog.js +112 -0
- package/scripts/test-navigation.js +57 -0
- package/scripts/test-privileged-context.js +99 -0
- package/scripts/test-screenshot.js +190 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Simple demo server for testing MCP tools
|
|
5
|
+
* Serves a page with console logs, dialogs, and interactive elements
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import http from 'http';
|
|
9
|
+
|
|
10
|
+
const PORT = 3456;
|
|
11
|
+
|
|
12
|
+
const HTML_PAGE = `
|
|
13
|
+
<!DOCTYPE html>
|
|
14
|
+
<html lang="en">
|
|
15
|
+
<head>
|
|
16
|
+
<meta charset="UTF-8">
|
|
17
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
18
|
+
<title>MCP - Demo Page</title>
|
|
19
|
+
<style>
|
|
20
|
+
body {
|
|
21
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
22
|
+
max-width: 800px;
|
|
23
|
+
margin: 50px auto;
|
|
24
|
+
padding: 20px;
|
|
25
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
26
|
+
color: white;
|
|
27
|
+
}
|
|
28
|
+
.container {
|
|
29
|
+
background: rgba(255, 255, 255, 0.1);
|
|
30
|
+
backdrop-filter: blur(10px);
|
|
31
|
+
border-radius: 16px;
|
|
32
|
+
padding: 30px;
|
|
33
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
|
34
|
+
}
|
|
35
|
+
h1 {
|
|
36
|
+
margin-top: 0;
|
|
37
|
+
font-size: 2.5rem;
|
|
38
|
+
}
|
|
39
|
+
.section {
|
|
40
|
+
margin: 30px 0;
|
|
41
|
+
padding: 20px;
|
|
42
|
+
background: rgba(255, 255, 255, 0.1);
|
|
43
|
+
border-radius: 12px;
|
|
44
|
+
}
|
|
45
|
+
button {
|
|
46
|
+
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
47
|
+
border: none;
|
|
48
|
+
color: white;
|
|
49
|
+
padding: 12px 24px;
|
|
50
|
+
font-size: 16px;
|
|
51
|
+
border-radius: 8px;
|
|
52
|
+
cursor: pointer;
|
|
53
|
+
margin: 5px;
|
|
54
|
+
transition: transform 0.2s;
|
|
55
|
+
font-weight: 600;
|
|
56
|
+
}
|
|
57
|
+
button:hover {
|
|
58
|
+
transform: scale(1.05);
|
|
59
|
+
}
|
|
60
|
+
button:active {
|
|
61
|
+
transform: scale(0.95);
|
|
62
|
+
}
|
|
63
|
+
input {
|
|
64
|
+
padding: 10px;
|
|
65
|
+
border-radius: 6px;
|
|
66
|
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
67
|
+
background: rgba(255, 255, 255, 0.2);
|
|
68
|
+
color: white;
|
|
69
|
+
font-size: 16px;
|
|
70
|
+
margin: 5px;
|
|
71
|
+
}
|
|
72
|
+
input::placeholder {
|
|
73
|
+
color: rgba(255, 255, 255, 0.6);
|
|
74
|
+
}
|
|
75
|
+
.console-output {
|
|
76
|
+
background: rgba(0, 0, 0, 0.3);
|
|
77
|
+
padding: 15px;
|
|
78
|
+
border-radius: 8px;
|
|
79
|
+
font-family: 'Monaco', 'Consolas', monospace;
|
|
80
|
+
font-size: 14px;
|
|
81
|
+
margin-top: 10px;
|
|
82
|
+
max-height: 200px;
|
|
83
|
+
overflow-y: auto;
|
|
84
|
+
}
|
|
85
|
+
.log { color: #4ade80; }
|
|
86
|
+
.info { color: #60a5fa; }
|
|
87
|
+
.warn { color: #fbbf24; }
|
|
88
|
+
.error { color: #f87171; }
|
|
89
|
+
.debug { color: #a78bfa; }
|
|
90
|
+
</style>
|
|
91
|
+
</head>
|
|
92
|
+
<body>
|
|
93
|
+
<div class="container">
|
|
94
|
+
<h1>š¦ Firefox DevTools MCP</h1>
|
|
95
|
+
<p><strong>Demo Page for Testing MCP Tools</strong></p>
|
|
96
|
+
<p>Use this page with the MCP Inspector to test various tools!</p>
|
|
97
|
+
|
|
98
|
+
<div class="section">
|
|
99
|
+
<h2>š Console Logs</h2>
|
|
100
|
+
<button onclick="generateLogs()">Generate All Log Types</button>
|
|
101
|
+
<button onclick="generateManyLogs()">Generate 50 Logs</button>
|
|
102
|
+
<button onclick="logObject()">Log Object</button>
|
|
103
|
+
<button onclick="logArray()">Log Array</button>
|
|
104
|
+
<button onclick="console.clear()">Clear Console</button>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div class="section">
|
|
108
|
+
<h2>š¬ Dialogs</h2>
|
|
109
|
+
<button onclick="testAlert()">Show Alert</button>
|
|
110
|
+
<button onclick="testConfirm()">Show Confirm</button>
|
|
111
|
+
<button onclick="testPrompt()">Show Prompt</button>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<div class="section">
|
|
115
|
+
<h2>āØļø Input Elements</h2>
|
|
116
|
+
<input type="text" id="testInput" placeholder="Type something here...">
|
|
117
|
+
<button onclick="showInputValue()">Show Input Value</button>
|
|
118
|
+
<button onclick="clearInput()">Clear Input</button>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<div class="section">
|
|
122
|
+
<h2>š Page Actions</h2>
|
|
123
|
+
<button onclick="location.reload()">Reload Page</button>
|
|
124
|
+
<button onclick="window.open('about:blank', '_blank')">Open New Tab</button>
|
|
125
|
+
<button onclick="navigateToExample()">Navigate to Example.com</button>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<div class="section">
|
|
129
|
+
<h2>šÆ Clickable Elements</h2>
|
|
130
|
+
<button id="clickCounter" onclick="incrementCounter()">Click Counter: 0</button>
|
|
131
|
+
<button onclick="changeBackground()">Change Background</button>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<div class="section">
|
|
135
|
+
<h2>š Live Console Output</h2>
|
|
136
|
+
<div class="console-output" id="consoleOutput">
|
|
137
|
+
Console logs will appear here...
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<script>
|
|
143
|
+
let clickCount = 0;
|
|
144
|
+
const colors = ['#667eea', '#764ba2', '#f093fb', '#f5576c', '#4ade80', '#60a5fa'];
|
|
145
|
+
let colorIndex = 0;
|
|
146
|
+
|
|
147
|
+
// Intercept console methods to show in page
|
|
148
|
+
const originalLog = console.log;
|
|
149
|
+
const originalInfo = console.info;
|
|
150
|
+
const originalWarn = console.warn;
|
|
151
|
+
const originalError = console.error;
|
|
152
|
+
const originalDebug = console.debug;
|
|
153
|
+
|
|
154
|
+
function addToOutput(message, type) {
|
|
155
|
+
const output = document.getElementById('consoleOutput');
|
|
156
|
+
const line = document.createElement('div');
|
|
157
|
+
line.className = type;
|
|
158
|
+
line.textContent = '[' + type.toUpperCase() + '] ' + message;
|
|
159
|
+
output.appendChild(line);
|
|
160
|
+
output.scrollTop = output.scrollHeight;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
console.log = function(...args) {
|
|
164
|
+
originalLog.apply(console, args);
|
|
165
|
+
addToOutput(args.join(' '), 'log');
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
console.info = function(...args) {
|
|
169
|
+
originalInfo.apply(console, args);
|
|
170
|
+
addToOutput(args.join(' '), 'info');
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
console.warn = function(...args) {
|
|
174
|
+
originalWarn.apply(console, args);
|
|
175
|
+
addToOutput(args.join(' '), 'warn');
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
console.error = function(...args) {
|
|
179
|
+
originalError.apply(console, args);
|
|
180
|
+
addToOutput(args.join(' '), 'error');
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
console.debug = function(...args) {
|
|
184
|
+
originalDebug.apply(console, args);
|
|
185
|
+
addToOutput(args.join(' '), 'debug');
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// Console log functions
|
|
189
|
+
function generateLogs() {
|
|
190
|
+
console.log('This is a log message');
|
|
191
|
+
console.info('This is an info message');
|
|
192
|
+
console.warn('This is a warning message');
|
|
193
|
+
console.error('This is an error message');
|
|
194
|
+
console.debug('This is a debug message');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function generateManyLogs() {
|
|
198
|
+
for (let i = 0; i < 50; i++) {
|
|
199
|
+
const types = ['log', 'info', 'warn', 'error', 'debug'];
|
|
200
|
+
const type = types[i % types.length];
|
|
201
|
+
console[type]('Message #' + (i + 1) + ' - ' + type);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function logObject() {
|
|
206
|
+
console.log('Object:', {
|
|
207
|
+
name: 'Firefox DevTools MCP',
|
|
208
|
+
version: '0.1.0',
|
|
209
|
+
features: ['console', 'dialog', 'input', 'screenshot'],
|
|
210
|
+
config: { headless: false, timeout: 5000 }
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function logArray() {
|
|
215
|
+
console.log('Array:', [1, 2, 3, 'four', { five: 5 }, [6, 7, 8]]);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Dialog functions
|
|
219
|
+
function testAlert() {
|
|
220
|
+
alert('This is an alert dialog! šØ');
|
|
221
|
+
console.info('Alert dialog was shown');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function testConfirm() {
|
|
225
|
+
const result = confirm('Do you want to continue? š¤');
|
|
226
|
+
console.log('Confirm result:', result);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function testPrompt() {
|
|
230
|
+
const result = prompt('What is your favorite color? šØ');
|
|
231
|
+
console.log('Prompt result:', result);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Input functions
|
|
235
|
+
function showInputValue() {
|
|
236
|
+
const value = document.getElementById('testInput').value;
|
|
237
|
+
console.log('Input value:', value);
|
|
238
|
+
alert('Input value: ' + value);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function clearInput() {
|
|
242
|
+
document.getElementById('testInput').value = '';
|
|
243
|
+
console.info('Input cleared');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Page actions
|
|
247
|
+
function navigateToExample() {
|
|
248
|
+
console.info('Navigating to example.com...');
|
|
249
|
+
window.location.href = 'https://example.com';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Clickable elements
|
|
253
|
+
function incrementCounter() {
|
|
254
|
+
clickCount++;
|
|
255
|
+
document.getElementById('clickCounter').textContent = 'Click Counter: ' + clickCount;
|
|
256
|
+
console.log('Button clicked! Count:', clickCount);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function changeBackground() {
|
|
260
|
+
colorIndex = (colorIndex + 1) % colors.length;
|
|
261
|
+
document.body.style.background = 'linear-gradient(135deg, ' + colors[colorIndex] + ' 0%, ' + colors[(colorIndex + 1) % colors.length] + ' 100%)';
|
|
262
|
+
console.log('Background changed to:', colors[colorIndex]);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Initial log
|
|
266
|
+
console.log('š¦ Firefox DevTools MCP Demo Page Loaded!');
|
|
267
|
+
console.info('Server running on http://localhost:3456');
|
|
268
|
+
console.info('Use MCP Inspector to test tools: list_console_messages, list_pages, etc.');
|
|
269
|
+
</script>
|
|
270
|
+
</body>
|
|
271
|
+
</html>
|
|
272
|
+
`;
|
|
273
|
+
|
|
274
|
+
const server = http.createServer((req, res) => {
|
|
275
|
+
if (req.url === '/') {
|
|
276
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
277
|
+
res.end(HTML_PAGE);
|
|
278
|
+
} else if (req.url === '/health') {
|
|
279
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
280
|
+
res.end(JSON.stringify({ status: 'ok', timestamp: new Date().toISOString() }));
|
|
281
|
+
} else {
|
|
282
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
283
|
+
res.end('Not Found');
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
server.listen(PORT, () => {
|
|
288
|
+
console.log('');
|
|
289
|
+
console.log('š¦ Firefox DevTools MCP - Demo Server');
|
|
290
|
+
console.log('=====================================');
|
|
291
|
+
console.log('');
|
|
292
|
+
console.log(`š Server running at: http://localhost:${PORT}`);
|
|
293
|
+
console.log('');
|
|
294
|
+
console.log('š Available endpoints:');
|
|
295
|
+
console.log(` http://localhost:${PORT}/ - Demo page`);
|
|
296
|
+
console.log(` http://localhost:${PORT}/health - Health check`);
|
|
297
|
+
console.log('');
|
|
298
|
+
console.log('š” Usage with MCP Inspector:');
|
|
299
|
+
console.log(' 1. Start this server (already running)');
|
|
300
|
+
console.log(` 2. Open MCP Inspector: npm run inspector`);
|
|
301
|
+
console.log(` 3. Use tool: new_page with url "http://localhost:${PORT}"`);
|
|
302
|
+
console.log(' 4. Test tools: list_console_messages, list_pages, etc.');
|
|
303
|
+
console.log('');
|
|
304
|
+
console.log('Press Ctrl+C to stop the server');
|
|
305
|
+
console.log('');
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
server.on('error', (error) => {
|
|
309
|
+
if (error.code === 'EADDRINUSE') {
|
|
310
|
+
console.error(`ā Port ${PORT} is already in use!\n`);
|
|
311
|
+
console.error(' Try stopping other servers or change the PORT in demo-server.js\n');
|
|
312
|
+
} else {
|
|
313
|
+
console.error('ā Server error:', error.message);
|
|
314
|
+
}
|
|
315
|
+
process.exit(1);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Graceful shutdown
|
|
319
|
+
process.on('SIGINT', () => {
|
|
320
|
+
console.log('\n\nš Shutting down demo server...');
|
|
321
|
+
server.close(() => {
|
|
322
|
+
console.log('ā
Server closed');
|
|
323
|
+
process.exit(0);
|
|
324
|
+
});
|
|
325
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generates package.moz.json from package.json by applying the overrides
|
|
4
|
+
* needed for the @mozilla/firefox-devtools-mcp-moz npm package.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { resolve, dirname } from 'node:path';
|
|
10
|
+
|
|
11
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
12
|
+
const pkg = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8'));
|
|
13
|
+
|
|
14
|
+
const moz = {
|
|
15
|
+
...pkg,
|
|
16
|
+
name: '@mozilla/firefox-devtools-mcp-moz',
|
|
17
|
+
description:
|
|
18
|
+
pkg.description + ' (moz build with privileged context support)',
|
|
19
|
+
main: 'dist.moz/index.js',
|
|
20
|
+
types: 'dist.moz/index.d.ts',
|
|
21
|
+
bin: {
|
|
22
|
+
'firefox-devtools-mcp-moz': './dist.moz/index.js',
|
|
23
|
+
},
|
|
24
|
+
files: ['dist.moz', 'README.md', 'LICENSE', 'scripts', 'plugins'],
|
|
25
|
+
publishConfig: {
|
|
26
|
+
access: 'public',
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Remove scripts that don't apply to the moz package
|
|
31
|
+
delete moz.scripts;
|
|
32
|
+
|
|
33
|
+
const outPath = resolve(root, 'package.moz.json');
|
|
34
|
+
writeFileSync(outPath, JSON.stringify(moz, null, 2) + '\n');
|
|
35
|
+
console.log(`Written ${outPath}`);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Builds and publishes the firefox-devtools-mcp-moz package.
|
|
4
|
+
* Creates a staging directory with dist.moz/ and package.moz.json as package.json,
|
|
5
|
+
* then runs npm publish from there.
|
|
6
|
+
*
|
|
7
|
+
* Any extra arguments are forwarded to npm publish, e.g.:
|
|
8
|
+
* node publish-moz-package.mjs --access public --provenance
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
readFileSync,
|
|
13
|
+
existsSync,
|
|
14
|
+
cpSync,
|
|
15
|
+
copyFileSync,
|
|
16
|
+
mkdirSync,
|
|
17
|
+
mkdtempSync,
|
|
18
|
+
rmSync,
|
|
19
|
+
} from 'node:fs';
|
|
20
|
+
import { execSync } from 'node:child_process';
|
|
21
|
+
import { resolve, dirname } from 'node:path';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
23
|
+
import { tmpdir } from 'node:os';
|
|
24
|
+
|
|
25
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
26
|
+
|
|
27
|
+
console.log('Building moz package...');
|
|
28
|
+
execSync('npm run build:moz', { cwd: root, stdio: 'inherit' });
|
|
29
|
+
|
|
30
|
+
const mozPkg = JSON.parse(readFileSync(resolve(root, 'package.moz.json'), 'utf8'));
|
|
31
|
+
const stagingDir = mkdtempSync(resolve(tmpdir(), 'firefox-devtools-mcp-moz-'));
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
for (const entry of mozPkg.files) {
|
|
35
|
+
const src = resolve(root, entry);
|
|
36
|
+
const dst = resolve(stagingDir, entry);
|
|
37
|
+
if (!existsSync(src)) {
|
|
38
|
+
console.warn(`Warning: ${entry} not found, skipping`);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
mkdirSync(dirname(dst), { recursive: true });
|
|
42
|
+
cpSync(src, dst, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
copyFileSync(resolve(root, 'package.moz.json'), resolve(stagingDir, 'package.json'));
|
|
46
|
+
|
|
47
|
+
const extraArgs = process.argv.slice(2).join(' ');
|
|
48
|
+
console.log(`Publishing ${stagingDir} ${mozPkg.name}@${mozPkg.version}...`);
|
|
49
|
+
// execSync(`npm publish ${extraArgs}`, { cwd: stagingDir, stdio: 'inherit' });
|
|
50
|
+
} finally {
|
|
51
|
+
// rmSync(stagingDir, { recursive: true, force: true });
|
|
52
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Windows Integration Tests Runner
|
|
4
|
+
*
|
|
5
|
+
* Runs integration tests directly via node to avoid vitest fork issues on Windows.
|
|
6
|
+
* See: https://github.com/mozilla/firefox-devtools-mcp/issues/33
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { FirefoxDevTools } from '../dist/index.js';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { dirname, resolve } from 'node:path';
|
|
12
|
+
import assert from 'node:assert';
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const fixturesPath = resolve(__dirname, '../tests/fixtures');
|
|
16
|
+
|
|
17
|
+
let firefox = null;
|
|
18
|
+
let passed = 0;
|
|
19
|
+
let failed = 0;
|
|
20
|
+
|
|
21
|
+
async function test(name, fn) {
|
|
22
|
+
process.stdout.write(` ${name}... `);
|
|
23
|
+
try {
|
|
24
|
+
await fn();
|
|
25
|
+
console.log('\x1b[32mā\x1b[0m');
|
|
26
|
+
passed++;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.log('\x1b[31mā\x1b[0m');
|
|
29
|
+
console.error(` Error: ${error.message}`);
|
|
30
|
+
failed++;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function setup() {
|
|
35
|
+
console.log('\nš Starting Firefox...');
|
|
36
|
+
firefox = new FirefoxDevTools({
|
|
37
|
+
headless: true,
|
|
38
|
+
enableBidiLogging: false,
|
|
39
|
+
width: 1280,
|
|
40
|
+
height: 720,
|
|
41
|
+
});
|
|
42
|
+
await firefox.connect();
|
|
43
|
+
console.log('ā
Firefox connected\n');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function teardown() {
|
|
47
|
+
if (firefox) {
|
|
48
|
+
await firefox.close();
|
|
49
|
+
console.log('\nā
Firefox closed');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// Tab Management Tests
|
|
55
|
+
// ============================================================================
|
|
56
|
+
async function tabTests() {
|
|
57
|
+
console.log('š Tab Management Tests:');
|
|
58
|
+
|
|
59
|
+
await test('should list tabs', async () => {
|
|
60
|
+
const fixturePath = `file://${fixturesPath}/simple.html`;
|
|
61
|
+
await firefox.navigate(fixturePath);
|
|
62
|
+
await firefox.refreshTabs();
|
|
63
|
+
const tabs = firefox.getTabs();
|
|
64
|
+
assert(Array.isArray(tabs), 'tabs should be an array');
|
|
65
|
+
assert(tabs.length > 0, 'should have at least one tab');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await test('should create new tab', async () => {
|
|
69
|
+
await firefox.refreshTabs();
|
|
70
|
+
const initialTabs = firefox.getTabs();
|
|
71
|
+
const initialCount = initialTabs.length;
|
|
72
|
+
|
|
73
|
+
const fixturePath = `file://${fixturesPath}/simple.html`;
|
|
74
|
+
await firefox.createNewPage(fixturePath);
|
|
75
|
+
|
|
76
|
+
await firefox.refreshTabs();
|
|
77
|
+
const updatedTabs = firefox.getTabs();
|
|
78
|
+
assert(updatedTabs.length === initialCount + 1, 'should have one more tab');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
await test('should get selected tab index', async () => {
|
|
82
|
+
const idx = firefox.getSelectedTabIdx();
|
|
83
|
+
assert(typeof idx === 'number', 'index should be a number');
|
|
84
|
+
assert(idx >= 0, 'index should be non-negative');
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ============================================================================
|
|
89
|
+
// Snapshot Tests
|
|
90
|
+
// ============================================================================
|
|
91
|
+
async function snapshotTests() {
|
|
92
|
+
console.log('\nšø Snapshot Tests:');
|
|
93
|
+
|
|
94
|
+
await test('should take snapshot', async () => {
|
|
95
|
+
const fixturePath = `file://${fixturesPath}/simple.html`;
|
|
96
|
+
await firefox.navigate(fixturePath);
|
|
97
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
98
|
+
|
|
99
|
+
const snapshot = await firefox.takeSnapshot();
|
|
100
|
+
assert(snapshot, 'snapshot should exist');
|
|
101
|
+
assert(snapshot.text || snapshot.markdown, 'snapshot should have text or markdown');
|
|
102
|
+
assert(snapshot.json, 'snapshot should have json');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
await test('should have uidMap in snapshot', async () => {
|
|
106
|
+
const snapshot = await firefox.takeSnapshot();
|
|
107
|
+
assert(Array.isArray(snapshot.json.uidMap), 'uidMap should be an array');
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ============================================================================
|
|
112
|
+
// Console Tests
|
|
113
|
+
// ============================================================================
|
|
114
|
+
async function consoleTests() {
|
|
115
|
+
console.log('\nš¬ Console Tests:');
|
|
116
|
+
|
|
117
|
+
await test('should get console messages', async () => {
|
|
118
|
+
const messages = await firefox.getConsoleMessages();
|
|
119
|
+
assert(Array.isArray(messages), 'messages should be an array');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
await test('should clear console messages', async () => {
|
|
123
|
+
firefox.clearConsoleMessages();
|
|
124
|
+
const messages = await firefox.getConsoleMessages();
|
|
125
|
+
assert(messages.length === 0, 'messages should be empty after clear');
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ============================================================================
|
|
130
|
+
// Network Tests
|
|
131
|
+
// ============================================================================
|
|
132
|
+
async function networkTests() {
|
|
133
|
+
console.log('\nš Network Tests:');
|
|
134
|
+
|
|
135
|
+
await test('should start network monitoring', async () => {
|
|
136
|
+
await firefox.startNetworkMonitoring();
|
|
137
|
+
// No error means success
|
|
138
|
+
assert(true);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
await test('should get network requests', async () => {
|
|
142
|
+
const requests = await firefox.getNetworkRequests();
|
|
143
|
+
assert(Array.isArray(requests), 'requests should be an array');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
await test('should clear network requests', async () => {
|
|
147
|
+
firefox.clearNetworkRequests();
|
|
148
|
+
const requests = await firefox.getNetworkRequests();
|
|
149
|
+
assert(requests.length === 0, 'requests should be empty after clear');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
await test('should stop network monitoring', async () => {
|
|
153
|
+
await firefox.stopNetworkMonitoring();
|
|
154
|
+
assert(true);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ============================================================================
|
|
159
|
+
// Main
|
|
160
|
+
// ============================================================================
|
|
161
|
+
async function main() {
|
|
162
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
163
|
+
console.log(' Windows Integration Tests (direct node runner)');
|
|
164
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
await setup();
|
|
168
|
+
|
|
169
|
+
await tabTests();
|
|
170
|
+
await snapshotTests();
|
|
171
|
+
await consoleTests();
|
|
172
|
+
await networkTests();
|
|
173
|
+
|
|
174
|
+
await teardown();
|
|
175
|
+
|
|
176
|
+
console.log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
177
|
+
console.log(` Results: \x1b[32m${passed} passed\x1b[0m, \x1b[31m${failed} failed\x1b[0m`);
|
|
178
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
|
|
179
|
+
|
|
180
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.error('\nā Fatal error:', error.message);
|
|
183
|
+
await teardown();
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
main();
|