firefox-devtools-mcp 0.2.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 +126 -0
- package/dist/index.js +15826 -0
- package/dist/snapshot.injected.global.js +1 -0
- package/package.json +102 -0
- package/scripts/_helpers/page-loader.js +121 -0
- package/scripts/demo-server.js +325 -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-input-tools.js +233 -0
- package/scripts/test-lifecycle-hooks.js +124 -0
- package/scripts/test-screenshot.js +190 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Standalone test for console message handling
|
|
5
|
+
* Tests console.log, console.warn, console.error capturing
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { FirefoxDevTools } from '../dist/index.js';
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
console.log('๐ Testing Console Message Handling...\n');
|
|
12
|
+
|
|
13
|
+
const firefox = new FirefoxDevTools({
|
|
14
|
+
firefoxPath: undefined,
|
|
15
|
+
headless: false,
|
|
16
|
+
viewport: { width: 1024, height: 768 },
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
// Connect
|
|
21
|
+
console.log('๐ก Connecting to Firefox...');
|
|
22
|
+
await firefox.connect();
|
|
23
|
+
console.log('โ
Connected!\n');
|
|
24
|
+
|
|
25
|
+
// Navigate to blank page
|
|
26
|
+
console.log('๐ Navigating to about:blank...');
|
|
27
|
+
await firefox.navigate('about:blank');
|
|
28
|
+
console.log('โ
Navigation complete\n');
|
|
29
|
+
|
|
30
|
+
// Clear existing console messages
|
|
31
|
+
console.log('๐งน Clearing existing console messages...');
|
|
32
|
+
firefox.clearConsoleMessages();
|
|
33
|
+
const afterClear = await firefox.getConsoleMessages();
|
|
34
|
+
console.log(`โ
Console cleared (${afterClear.length} messages)\n`);
|
|
35
|
+
|
|
36
|
+
// Test 1: Generate different log levels
|
|
37
|
+
console.log('1๏ธโฃ Generating console messages of different levels...');
|
|
38
|
+
await firefox.evaluate(`
|
|
39
|
+
console.log('This is a log message');
|
|
40
|
+
console.info('This is an info message');
|
|
41
|
+
console.warn('This is a warning message');
|
|
42
|
+
console.error('This is an error message');
|
|
43
|
+
console.debug('This is a debug message');
|
|
44
|
+
`);
|
|
45
|
+
|
|
46
|
+
// Wait a bit for messages to be captured
|
|
47
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
48
|
+
|
|
49
|
+
const allMessages = await firefox.getConsoleMessages();
|
|
50
|
+
console.log(` โ
Captured ${allMessages.length} console messages\n`);
|
|
51
|
+
|
|
52
|
+
// Display all messages
|
|
53
|
+
console.log('๐ All console messages:');
|
|
54
|
+
for (const msg of allMessages) {
|
|
55
|
+
const emoji = {
|
|
56
|
+
log: '๐',
|
|
57
|
+
info: 'โน๏ธ',
|
|
58
|
+
warn: 'โ ๏ธ',
|
|
59
|
+
error: 'โ',
|
|
60
|
+
debug: '๐',
|
|
61
|
+
}[msg.level.toLowerCase()] || '๐';
|
|
62
|
+
console.log(` ${emoji} [${msg.level}] ${msg.text}`);
|
|
63
|
+
}
|
|
64
|
+
console.log();
|
|
65
|
+
|
|
66
|
+
// Test 2: Filter by level
|
|
67
|
+
console.log('2๏ธโฃ Testing level filtering...');
|
|
68
|
+
const errors = allMessages.filter((msg) => msg.level.toLowerCase() === 'error');
|
|
69
|
+
const warnings = allMessages.filter((msg) => msg.level.toLowerCase() === 'warn');
|
|
70
|
+
console.log(` โ
Found ${errors.length} error(s)`);
|
|
71
|
+
console.log(` โ
Found ${warnings.length} warning(s)\n`);
|
|
72
|
+
|
|
73
|
+
// Test 3: Generate many messages and test limit
|
|
74
|
+
console.log('3๏ธโฃ Testing message limit (generating 100 messages)...');
|
|
75
|
+
firefox.clearConsoleMessages();
|
|
76
|
+
await firefox.evaluate(`
|
|
77
|
+
for (let i = 0; i < 100; i++) {
|
|
78
|
+
console.log('Message ' + i);
|
|
79
|
+
}
|
|
80
|
+
`);
|
|
81
|
+
|
|
82
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
83
|
+
const manyMessages = await firefox.getConsoleMessages();
|
|
84
|
+
console.log(` โ
Captured ${manyMessages.length} messages\n`);
|
|
85
|
+
|
|
86
|
+
// Test 4: Clear messages
|
|
87
|
+
console.log('4๏ธโฃ Testing clear console messages...');
|
|
88
|
+
firefox.clearConsoleMessages();
|
|
89
|
+
const afterSecondClear = await firefox.getConsoleMessages();
|
|
90
|
+
console.log(` โ
Console cleared (${afterSecondClear.length} messages remaining)\n`);
|
|
91
|
+
|
|
92
|
+
// Test 5: Test timestamps
|
|
93
|
+
console.log('5๏ธโฃ Testing message timestamps...');
|
|
94
|
+
firefox.clearConsoleMessages();
|
|
95
|
+
const startTime = Date.now();
|
|
96
|
+
|
|
97
|
+
await firefox.evaluate(`console.log('Message at T=0')`);
|
|
98
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
99
|
+
await firefox.evaluate(`console.log('Message at T=500ms')`);
|
|
100
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
101
|
+
await firefox.evaluate(`console.log('Message at T=1000ms')`);
|
|
102
|
+
|
|
103
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
104
|
+
const timedMessages = await firefox.getConsoleMessages();
|
|
105
|
+
console.log(` โ
Captured ${timedMessages.length} timed messages`);
|
|
106
|
+
|
|
107
|
+
for (const msg of timedMessages) {
|
|
108
|
+
const elapsed = msg.timestamp ? msg.timestamp - startTime : 'N/A';
|
|
109
|
+
console.log(` ๐ "${msg.text}" at +${elapsed}ms`);
|
|
110
|
+
}
|
|
111
|
+
console.log();
|
|
112
|
+
|
|
113
|
+
// Test 6: Test with objects and complex data
|
|
114
|
+
console.log('6๏ธโฃ Testing console with objects and arrays...');
|
|
115
|
+
firefox.clearConsoleMessages();
|
|
116
|
+
await firefox.evaluate(`
|
|
117
|
+
console.log('Simple string');
|
|
118
|
+
console.log('Number:', 42);
|
|
119
|
+
console.log('Object:', { name: 'Test', value: 123 });
|
|
120
|
+
console.log('Array:', [1, 2, 3, 4, 5]);
|
|
121
|
+
`);
|
|
122
|
+
|
|
123
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
124
|
+
const complexMessages = await firefox.getConsoleMessages();
|
|
125
|
+
console.log(` โ
Captured ${complexMessages.length} messages with complex data`);
|
|
126
|
+
for (const msg of complexMessages) {
|
|
127
|
+
console.log(` ๐ ${msg.text}`);
|
|
128
|
+
}
|
|
129
|
+
console.log();
|
|
130
|
+
|
|
131
|
+
console.log('โ
All console tests passed! ๐\n');
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error('โ Test failed:', error.message);
|
|
134
|
+
if (error.stack) {
|
|
135
|
+
console.error(error.stack);
|
|
136
|
+
}
|
|
137
|
+
process.exit(1);
|
|
138
|
+
} finally {
|
|
139
|
+
console.log('๐งน Closing Firefox...');
|
|
140
|
+
await firefox.close();
|
|
141
|
+
console.log('โ
Done!');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
main().catch((error) => {
|
|
146
|
+
console.error('๐ฅ Fatal error:', error);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Standalone test for dialog handling (Task 23)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { FirefoxDevTools } from '../dist/index.js';
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
console.log('๐ฌ Testing Dialog Handling (Task 23)...\n');
|
|
11
|
+
|
|
12
|
+
const firefox = new FirefoxDevTools({
|
|
13
|
+
firefoxPath: undefined,
|
|
14
|
+
headless: false,
|
|
15
|
+
viewport: { width: 1024, height: 768 },
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// Connect
|
|
20
|
+
console.log('๐ก Connecting to Firefox...');
|
|
21
|
+
await firefox.connect();
|
|
22
|
+
console.log('โ
Connected!\n');
|
|
23
|
+
|
|
24
|
+
// Test 1: Alert dialog
|
|
25
|
+
console.log('1๏ธโฃ Testing alert() dialog...');
|
|
26
|
+
await firefox.navigate('about:blank');
|
|
27
|
+
await firefox.evaluate('setTimeout(() => alert("This is a test alert!"), 100)');
|
|
28
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
29
|
+
await firefox.acceptDialog();
|
|
30
|
+
console.log(' โ
Alert accepted\n');
|
|
31
|
+
|
|
32
|
+
// Test 2: Confirm dialog - accept
|
|
33
|
+
console.log('2๏ธโฃ Testing confirm() dialog - accept...');
|
|
34
|
+
await firefox.evaluate(
|
|
35
|
+
'setTimeout(() => { window.confirmResult = confirm("Click OK to accept"); }, 100)'
|
|
36
|
+
);
|
|
37
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
38
|
+
await firefox.acceptDialog();
|
|
39
|
+
const confirmAccepted = await firefox.evaluate('return window.confirmResult');
|
|
40
|
+
console.log(` Result: ${confirmAccepted}`);
|
|
41
|
+
console.log(` ${confirmAccepted ? 'โ
' : 'โ'} Confirm was accepted\n`);
|
|
42
|
+
|
|
43
|
+
// Test 3: Confirm dialog - dismiss
|
|
44
|
+
console.log('3๏ธโฃ Testing confirm() dialog - dismiss...');
|
|
45
|
+
await firefox.evaluate(
|
|
46
|
+
'setTimeout(() => { window.confirmResult2 = confirm("Click Cancel to dismiss"); }, 100)'
|
|
47
|
+
);
|
|
48
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
49
|
+
await firefox.dismissDialog();
|
|
50
|
+
const confirmDismissed = await firefox.evaluate('return window.confirmResult2');
|
|
51
|
+
console.log(` Result: ${confirmDismissed}`);
|
|
52
|
+
console.log(` ${!confirmDismissed ? 'โ
' : 'โ'} Confirm was dismissed\n`);
|
|
53
|
+
|
|
54
|
+
// Test 4: Prompt dialog with text
|
|
55
|
+
console.log('4๏ธโฃ Testing prompt() dialog with custom text...');
|
|
56
|
+
await firefox.evaluate(
|
|
57
|
+
'setTimeout(() => { window.promptResult = prompt("Enter your favorite color:"); }, 100)'
|
|
58
|
+
);
|
|
59
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
60
|
+
await firefox.acceptDialog('Blue');
|
|
61
|
+
const promptResult = await firefox.evaluate('return window.promptResult');
|
|
62
|
+
console.log(` Result: ${promptResult}`);
|
|
63
|
+
console.log(` ${promptResult === 'Blue' ? 'โ
' : 'โ'} Prompt returned: "${promptResult}"\n`);
|
|
64
|
+
|
|
65
|
+
// Test 5: Prompt dialog dismissed
|
|
66
|
+
console.log('5๏ธโฃ Testing prompt() dialog - dismiss...');
|
|
67
|
+
await firefox.evaluate(
|
|
68
|
+
'setTimeout(() => { window.promptResult2 = prompt("This will be dismissed"); }, 100)'
|
|
69
|
+
);
|
|
70
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
71
|
+
await firefox.dismissDialog();
|
|
72
|
+
const promptDismissed = await firefox.evaluate('return window.promptResult2');
|
|
73
|
+
console.log(` Result: ${promptDismissed}`);
|
|
74
|
+
console.log(` ${promptDismissed === null ? 'โ
' : 'โ'} Prompt was dismissed (null)\n`);
|
|
75
|
+
|
|
76
|
+
// Test 6: Error handling - no dialog present
|
|
77
|
+
console.log('6๏ธโฃ Testing error handling (no dialog present)...');
|
|
78
|
+
try {
|
|
79
|
+
await firefox.acceptDialog();
|
|
80
|
+
console.log(' โ Should have thrown error');
|
|
81
|
+
} catch (e) {
|
|
82
|
+
console.log(` โ
Error caught: ${e.message}\n`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
console.log('โ
All dialog tests passed! ๐\n');
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error('โ Test failed:', error.message);
|
|
88
|
+
if (error.stack) {
|
|
89
|
+
console.error(error.stack);
|
|
90
|
+
}
|
|
91
|
+
process.exit(1);
|
|
92
|
+
} finally {
|
|
93
|
+
console.log('๐งน Closing Firefox...');
|
|
94
|
+
await firefox.close();
|
|
95
|
+
console.log('โ
Done!');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
main().catch((error) => {
|
|
100
|
+
console.error('๐ฅ Fatal error:', error);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
});
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test script for UID-based input tools (Task 21)
|
|
5
|
+
* Tests: clickByUid, fillByUid, hoverByUid, dragByUidToUid, fillFormByUid, uploadFileByUid
|
|
6
|
+
*
|
|
7
|
+
* Note: Uses innerHTML injection to avoid Firefox data: URL parsing issues
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { FirefoxDevTools } from '../dist/index.js';
|
|
11
|
+
import { writeFile, mkdtemp, rm } from 'node:fs/promises';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { tmpdir } from 'node:os';
|
|
14
|
+
import { loadHTML, waitShort } from './_helpers/page-loader.js';
|
|
15
|
+
|
|
16
|
+
async function main() {
|
|
17
|
+
console.log('๐งช Testing UID-based input tools...\n');
|
|
18
|
+
|
|
19
|
+
const firefox = new FirefoxDevTools({
|
|
20
|
+
firefoxPath: undefined,
|
|
21
|
+
headless: false,
|
|
22
|
+
startUrl: 'about:blank',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
console.log('๐ก Connecting to Firefox...');
|
|
27
|
+
await firefox.connect();
|
|
28
|
+
console.log('โ
Connected!\n');
|
|
29
|
+
|
|
30
|
+
// Test 1: Click
|
|
31
|
+
console.log('๐ฑ๏ธ Test 1: Click By UID');
|
|
32
|
+
await loadHTML(firefox, `
|
|
33
|
+
<head><title>Test</title></head>
|
|
34
|
+
<body>
|
|
35
|
+
<button id="btn">Click Me</button>
|
|
36
|
+
<script>
|
|
37
|
+
document.getElementById('btn').addEventListener('click', () => {
|
|
38
|
+
document.body.setAttribute('data-result', 'clicked');
|
|
39
|
+
});
|
|
40
|
+
</script>
|
|
41
|
+
</body>
|
|
42
|
+
`);
|
|
43
|
+
let snapshot = await firefox.takeSnapshot();
|
|
44
|
+
const btnUid = snapshot.json.root.children.find(n => n.tag === 'button')?.uid;
|
|
45
|
+
if (btnUid) {
|
|
46
|
+
await firefox.clickByUid(btnUid);
|
|
47
|
+
await waitShort();
|
|
48
|
+
const result = await firefox.evaluate("return document.body.getAttribute('data-result')");
|
|
49
|
+
console.log(` ${result === 'clicked' ? 'โ
' : 'โ'} Click: ${result}\n`);
|
|
50
|
+
} else {
|
|
51
|
+
console.log(' โ Button UID not found\n');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Test 2: Fill
|
|
55
|
+
console.log('โ๏ธ Test 2: Fill By UID');
|
|
56
|
+
await loadHTML(firefox, `
|
|
57
|
+
<head><title>Test</title></head>
|
|
58
|
+
<body>
|
|
59
|
+
<input id="inp" type="text">
|
|
60
|
+
<script>
|
|
61
|
+
document.getElementById('inp').addEventListener('input', (e) => {
|
|
62
|
+
document.body.setAttribute('data-value', e.target.value);
|
|
63
|
+
});
|
|
64
|
+
</script>
|
|
65
|
+
</body>
|
|
66
|
+
`);
|
|
67
|
+
snapshot = await firefox.takeSnapshot();
|
|
68
|
+
const inpUid = snapshot.json.root.children.find(n => n.tag === 'input')?.uid;
|
|
69
|
+
if (inpUid) {
|
|
70
|
+
await firefox.fillByUid(inpUid, 'Hello Test');
|
|
71
|
+
await waitShort();
|
|
72
|
+
const value = await firefox.evaluate("return document.body.getAttribute('data-value')");
|
|
73
|
+
console.log(` ${value === 'Hello Test' ? 'โ
' : 'โ'} Fill: ${value}\n`);
|
|
74
|
+
} else {
|
|
75
|
+
console.log(' โ Input UID not found\n');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Test 3: Hover
|
|
79
|
+
console.log('๐ฏ Test 3: Hover By UID');
|
|
80
|
+
await loadHTML(firefox, `
|
|
81
|
+
<head><title>Test</title></head>
|
|
82
|
+
<body>
|
|
83
|
+
<div id="hover">Hover Me</div>
|
|
84
|
+
<script>
|
|
85
|
+
document.getElementById('hover').addEventListener('mouseenter', () => {
|
|
86
|
+
document.body.setAttribute('data-hovered', '1');
|
|
87
|
+
});
|
|
88
|
+
</script>
|
|
89
|
+
</body>
|
|
90
|
+
`);
|
|
91
|
+
snapshot = await firefox.takeSnapshot();
|
|
92
|
+
const hoverUid = snapshot.json.root.children.find(n => n.tag === 'div')?.uid;
|
|
93
|
+
if (hoverUid) {
|
|
94
|
+
await firefox.hoverByUid(hoverUid);
|
|
95
|
+
await waitShort();
|
|
96
|
+
const hovered = await firefox.evaluate("return document.body.getAttribute('data-hovered')");
|
|
97
|
+
console.log(` ${hovered === '1' ? 'โ
' : 'โ'} Hover: ${hovered}\n`);
|
|
98
|
+
} else {
|
|
99
|
+
console.log(' โ Div UID not found\n');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Test 4: Fill Form
|
|
103
|
+
console.log('๐ Test 4: Fill Form By UID');
|
|
104
|
+
await loadHTML(firefox, `
|
|
105
|
+
<head><title>Test</title></head>
|
|
106
|
+
<body>
|
|
107
|
+
<input id="first" type="text" name="firstName">
|
|
108
|
+
<input id="last" type="text" name="lastName">
|
|
109
|
+
<script>
|
|
110
|
+
let values = {};
|
|
111
|
+
['first', 'last'].forEach(id => {
|
|
112
|
+
document.getElementById(id).addEventListener('input', (e) => {
|
|
113
|
+
values[id] = e.target.value;
|
|
114
|
+
if (Object.keys(values).length === 2) {
|
|
115
|
+
document.body.setAttribute('data-form', JSON.stringify(values));
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
</script>
|
|
120
|
+
</body>
|
|
121
|
+
`);
|
|
122
|
+
snapshot = await firefox.takeSnapshot();
|
|
123
|
+
const inputs = snapshot.json.root.children.filter(n => n.tag === 'input');
|
|
124
|
+
if (inputs.length === 2) {
|
|
125
|
+
await firefox.fillFormByUid([
|
|
126
|
+
{ uid: inputs[0].uid, value: 'John' },
|
|
127
|
+
{ uid: inputs[1].uid, value: 'Doe' },
|
|
128
|
+
]);
|
|
129
|
+
await waitShort(500);
|
|
130
|
+
const formData = await firefox.evaluate("return document.body.getAttribute('data-form')");
|
|
131
|
+
const parsed = JSON.parse(formData || '{}');
|
|
132
|
+
const ok = parsed.first === 'John' && parsed.last === 'Doe';
|
|
133
|
+
console.log(` ${ok ? 'โ
' : 'โ'} Fill Form: ${formData}\n`);
|
|
134
|
+
} else {
|
|
135
|
+
console.log(` โ Expected 2 inputs, found ${inputs.length}\n`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Test 5: Upload File
|
|
139
|
+
console.log('๐ Test 5: Upload File By UID');
|
|
140
|
+
const tmpDir = await mkdtemp(join(tmpdir(), 'test-'));
|
|
141
|
+
const filePath = join(tmpDir, 'test.txt');
|
|
142
|
+
await writeFile(filePath, 'test content');
|
|
143
|
+
|
|
144
|
+
await loadHTML(firefox, `
|
|
145
|
+
<head><title>Test</title></head>
|
|
146
|
+
<body>
|
|
147
|
+
<input id="file" type="file" style="display:none">
|
|
148
|
+
<script>
|
|
149
|
+
document.getElementById('file').addEventListener('change', (e) => {
|
|
150
|
+
if (e.target.files[0]) {
|
|
151
|
+
document.body.setAttribute('data-filename', e.target.files[0].name);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
</script>
|
|
155
|
+
</body>
|
|
156
|
+
`);
|
|
157
|
+
snapshot = await firefox.takeSnapshot();
|
|
158
|
+
const fileUid = snapshot.json.root.children.find(n => n.tag === 'input')?.uid;
|
|
159
|
+
if (fileUid) {
|
|
160
|
+
await firefox.uploadFileByUid(fileUid, filePath);
|
|
161
|
+
await waitShort();
|
|
162
|
+
const filename = await firefox.evaluate("return document.body.getAttribute('data-filename')");
|
|
163
|
+
console.log(` ${filename === 'test.txt' ? 'โ
' : 'โ'} Upload: ${filename}\n`);
|
|
164
|
+
} else {
|
|
165
|
+
console.log(' โ File input UID not found\n');
|
|
166
|
+
}
|
|
167
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
168
|
+
|
|
169
|
+
// Test 6: Drag & Drop
|
|
170
|
+
console.log('๐งฒ Test 6: Drag & Drop By UID');
|
|
171
|
+
await loadHTML(firefox, `
|
|
172
|
+
<head><title>Test</title></head>
|
|
173
|
+
<body>
|
|
174
|
+
<div id="drag" draggable="true">Drag</div>
|
|
175
|
+
<div id="drop">Drop</div>
|
|
176
|
+
<script>
|
|
177
|
+
const drop = document.getElementById('drop');
|
|
178
|
+
drop.addEventListener('drop', (e) => {
|
|
179
|
+
e.preventDefault();
|
|
180
|
+
document.body.setAttribute('data-dropped', '1');
|
|
181
|
+
});
|
|
182
|
+
drop.addEventListener('dragover', (e) => e.preventDefault());
|
|
183
|
+
</script>
|
|
184
|
+
</body>
|
|
185
|
+
`);
|
|
186
|
+
snapshot = await firefox.takeSnapshot();
|
|
187
|
+
const divs = snapshot.json.root.children.filter(n => n.tag === 'div');
|
|
188
|
+
if (divs.length === 2) {
|
|
189
|
+
await firefox.dragByUidToUid(divs[0].uid, divs[1].uid);
|
|
190
|
+
await waitShort();
|
|
191
|
+
const dropped = await firefox.evaluate("return document.body.getAttribute('data-dropped')");
|
|
192
|
+
console.log(` ${dropped === '1' ? 'โ
' : 'โ'} Drag & Drop: ${dropped}\n`);
|
|
193
|
+
} else {
|
|
194
|
+
console.log(` โ Expected 2 divs, found ${divs.length}\n`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Test 7: Double Click
|
|
198
|
+
console.log('๐ฑ๏ธ๐ฑ๏ธ Test 7: Double Click By UID');
|
|
199
|
+
await loadHTML(firefox, `
|
|
200
|
+
<head><title>Test</title></head>
|
|
201
|
+
<body>
|
|
202
|
+
<button id="dblBtn">Double Click</button>
|
|
203
|
+
<script>
|
|
204
|
+
document.getElementById('dblBtn').addEventListener('dblclick', () => {
|
|
205
|
+
document.body.setAttribute('data-dblclick', '1');
|
|
206
|
+
});
|
|
207
|
+
</script>
|
|
208
|
+
</body>
|
|
209
|
+
`);
|
|
210
|
+
snapshot = await firefox.takeSnapshot();
|
|
211
|
+
const dblBtnUid = snapshot.json.root.children.find(n => n.tag === 'button')?.uid;
|
|
212
|
+
if (dblBtnUid) {
|
|
213
|
+
await firefox.clickByUid(dblBtnUid, true);
|
|
214
|
+
await waitShort();
|
|
215
|
+
const dblClicked = await firefox.evaluate("return document.body.getAttribute('data-dblclick')");
|
|
216
|
+
console.log(` ${dblClicked === '1' ? 'โ
' : 'โ'} Double Click: ${dblClicked}\n`);
|
|
217
|
+
} else {
|
|
218
|
+
console.log(' โ Button UID not found\n');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
console.log('โ
All tests completed! ๐\n');
|
|
222
|
+
} catch (error) {
|
|
223
|
+
console.error('โ Test failed:', error.message);
|
|
224
|
+
if (error.stack) console.error(error.stack);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
} finally {
|
|
227
|
+
console.log('๐งน Closing...');
|
|
228
|
+
await firefox.close();
|
|
229
|
+
console.log('โ
Done');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test lifecycle hooks and automatic cleanup on navigation
|
|
3
|
+
*/
|
|
4
|
+
import { FirefoxDevTools } from '../dist/index.js';
|
|
5
|
+
|
|
6
|
+
async function testLifecycleHooks() {
|
|
7
|
+
console.log('๐งช Testing lifecycle hooks...\n');
|
|
8
|
+
|
|
9
|
+
const client = new FirefoxDevTools({
|
|
10
|
+
headless: true,
|
|
11
|
+
args: ['--width=1280', '--height=720'],
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
await client.connect();
|
|
16
|
+
console.log('โ
Connected to Firefox\n');
|
|
17
|
+
|
|
18
|
+
// Navigate to example.com
|
|
19
|
+
console.log('๐ Navigating to example.com...');
|
|
20
|
+
await client.navigate('https://example.com');
|
|
21
|
+
console.log('โ
Page loaded\n');
|
|
22
|
+
|
|
23
|
+
// Take snapshot and get a UID
|
|
24
|
+
console.log('๐ธ Taking snapshot...');
|
|
25
|
+
const snapshot1 = await client.takeSnapshot();
|
|
26
|
+
const uid1 = snapshot1.json.root.uid;
|
|
27
|
+
console.log(`โ
Snapshot 1 taken, root UID: ${uid1}\n`);
|
|
28
|
+
|
|
29
|
+
// Verify UID works
|
|
30
|
+
console.log('๐งช Testing UID resolution before navigation...');
|
|
31
|
+
const selector1 = client.resolveUidToSelector(uid1);
|
|
32
|
+
console.log(`โ
UID ${uid1} resolves to: ${selector1}\n`);
|
|
33
|
+
|
|
34
|
+
// Start network monitoring
|
|
35
|
+
console.log('๐ Starting network monitoring...');
|
|
36
|
+
await client.startNetworkMonitoring();
|
|
37
|
+
console.log('โ
Network monitoring started\n');
|
|
38
|
+
|
|
39
|
+
// Make some network requests by navigating
|
|
40
|
+
console.log('๐ Navigating to mozilla.org (triggers lifecycle hooks)...');
|
|
41
|
+
await client.navigate('https://www.mozilla.org');
|
|
42
|
+
console.log('โ
Navigation completed\n');
|
|
43
|
+
|
|
44
|
+
// Wait a bit for network requests
|
|
45
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
46
|
+
|
|
47
|
+
// Check network requests (should have some from mozilla.org)
|
|
48
|
+
const requests1 = await client.getNetworkRequests();
|
|
49
|
+
console.log(`๐ Network requests captured: ${requests1.length}`);
|
|
50
|
+
if (requests1.length > 0) {
|
|
51
|
+
console.log(` First request: ${requests1[0].method} ${requests1[0].url}\n`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Test staleness: old UID should fail
|
|
55
|
+
console.log('๐งช Testing staleness detection (old UID should fail)...');
|
|
56
|
+
try {
|
|
57
|
+
client.resolveUidToSelector(uid1);
|
|
58
|
+
console.log('โ FAIL: Old UID should have been invalidated!\n');
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.log(`โ
PASS: Old UID correctly rejected: ${err.message}\n`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Take new snapshot
|
|
64
|
+
console.log('๐ธ Taking new snapshot...');
|
|
65
|
+
const snapshot2 = await client.takeSnapshot();
|
|
66
|
+
const uid2 = snapshot2.json.root.uid;
|
|
67
|
+
console.log(`โ
Snapshot 2 taken, root UID: ${uid2}\n`);
|
|
68
|
+
|
|
69
|
+
// Navigate again
|
|
70
|
+
console.log('๐ Navigating to example.com again (triggers lifecycle hooks)...');
|
|
71
|
+
await client.navigate('https://example.com');
|
|
72
|
+
console.log('โ
Navigation completed\n');
|
|
73
|
+
|
|
74
|
+
// Wait for page to settle
|
|
75
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
76
|
+
|
|
77
|
+
// Check network requests after navigation (should be cleared automatically)
|
|
78
|
+
const requests2 = await client.getNetworkRequests();
|
|
79
|
+
console.log(`๐ Network requests after 2nd navigation: ${requests2.length}`);
|
|
80
|
+
console.log(
|
|
81
|
+
requests2.length === 0
|
|
82
|
+
? 'โ
PASS: Network requests auto-cleared on navigation\n'
|
|
83
|
+
: 'โ ๏ธ WARN: Network requests not cleared (check lifecycle hook)\n'
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// Test console messages
|
|
87
|
+
console.log('๐ Checking console messages...');
|
|
88
|
+
const consoleMessages1 = await client.getConsoleMessages();
|
|
89
|
+
console.log(` Console messages: ${consoleMessages1.length}\n`);
|
|
90
|
+
|
|
91
|
+
// Trigger console message
|
|
92
|
+
await client.evaluate(`console.log('Test message from lifecycle hook test')`);
|
|
93
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
94
|
+
|
|
95
|
+
const consoleMessages2 = await client.getConsoleMessages();
|
|
96
|
+
console.log(` Console messages after log: ${consoleMessages2.length}`);
|
|
97
|
+
console.log(
|
|
98
|
+
` Last message: ${consoleMessages2[consoleMessages2.length - 1]?.text || 'none'}\n`
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// Navigate again and check if console was cleared
|
|
102
|
+
console.log('๐ Navigating one more time to check console clearing...');
|
|
103
|
+
await client.navigate('https://example.com');
|
|
104
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
105
|
+
|
|
106
|
+
const consoleMessages3 = await client.getConsoleMessages();
|
|
107
|
+
console.log(`๐ Console messages after 3rd navigation: ${consoleMessages3.length}`);
|
|
108
|
+
console.log(
|
|
109
|
+
consoleMessages3.length === 0
|
|
110
|
+
? 'โ
PASS: Console auto-cleared on navigation\n'
|
|
111
|
+
: 'โ ๏ธ INFO: Console has messages (from new page)\n'
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
console.log('โ
All lifecycle hook tests completed! ๐\n');
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error('โ Test failed:', error);
|
|
117
|
+
} finally {
|
|
118
|
+
console.log('๐งน Closing connection...');
|
|
119
|
+
await client.close();
|
|
120
|
+
console.log('โ
Done');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
testLifecycleHooks();
|