chrometools-mcp 3.1.7 → 3.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +118 -0
- package/README.md +183 -117
- package/README.ru.md +352 -0
- package/chrome-extension.zip +0 -0
- package/docs/extension-developer-mode.png +0 -0
- package/docs/extension-installed.png +0 -0
- package/index.js +93 -90
- package/package.json +1 -1
- package/pom/apom-tree-converter.js +330 -46
- package/publish_output.txt +0 -0
- package/server/tool-definitions.js +24 -41
- package/server/tool-groups.js +0 -1
- package/server/tool-schemas.js +6 -11
- package/test-interactivity.html +178 -0
|
@@ -28,7 +28,7 @@ export const toolDefinitions = [
|
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
name: "click",
|
|
31
|
-
description: "
|
|
31
|
+
description: "Click element by APOM ID (preferred) or CSS selector. Handles React/Vue/Angular events, waits for navigation.",
|
|
32
32
|
inputSchema: {
|
|
33
33
|
type: "object",
|
|
34
34
|
properties: {
|
|
@@ -42,29 +42,19 @@ export const toolDefinitions = [
|
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
name: "type",
|
|
45
|
-
description: "
|
|
45
|
+
description: "Type text into input by APOM ID (preferred) or CSS selector. Updates React/Vue/Angular state automatically.",
|
|
46
46
|
inputSchema: {
|
|
47
47
|
type: "object",
|
|
48
48
|
properties: {
|
|
49
49
|
id: { type: "string", description: "APOM element ID from analyzePage (e.g., 'input_20'). Either id or selector required." },
|
|
50
50
|
selector: { type: "string", description: "CSS selector (e.g., '#email'). Either id or selector required." },
|
|
51
51
|
text: { type: "string", description: "Text to type" },
|
|
52
|
-
delay: { type: "number", description: "Keystroke delay ms (default:
|
|
52
|
+
delay: { type: "number", description: "Keystroke delay ms (default: 30)" },
|
|
53
53
|
clearFirst: { type: "boolean", description: "Clear first (default: true)" },
|
|
54
54
|
},
|
|
55
55
|
required: ["text"],
|
|
56
56
|
},
|
|
57
57
|
},
|
|
58
|
-
{
|
|
59
|
-
name: "getElement",
|
|
60
|
-
description: "Get HTML markup of element. Prefer analyzePage for better efficiency.",
|
|
61
|
-
inputSchema: {
|
|
62
|
-
type: "object",
|
|
63
|
-
properties: {
|
|
64
|
-
selector: { type: "string", description: "CSS selector (default: body)" },
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
58
|
{
|
|
69
59
|
name: "getComputedCss",
|
|
70
60
|
description: "Get computed CSS styles for element. For layout debugging and responsive design.",
|
|
@@ -102,7 +92,7 @@ export const toolDefinitions = [
|
|
|
102
92
|
},
|
|
103
93
|
{
|
|
104
94
|
name: "screenshot",
|
|
105
|
-
description: "Capture element image (15-25k tokens).
|
|
95
|
+
description: "Capture element image (15-25k tokens). Use analyzePage for form data/validation (8-10k tokens).",
|
|
106
96
|
inputSchema: {
|
|
107
97
|
type: "object",
|
|
108
98
|
properties: {
|
|
@@ -223,7 +213,7 @@ export const toolDefinitions = [
|
|
|
223
213
|
},
|
|
224
214
|
{
|
|
225
215
|
name: "hover",
|
|
226
|
-
description: "Hover over element
|
|
216
|
+
description: "Hover over element by APOM ID or CSS selector. For hover effects, tooltips, :hover states.",
|
|
227
217
|
inputSchema: {
|
|
228
218
|
type: "object",
|
|
229
219
|
properties: {
|
|
@@ -234,7 +224,7 @@ export const toolDefinitions = [
|
|
|
234
224
|
},
|
|
235
225
|
{
|
|
236
226
|
name: "selectOption",
|
|
237
|
-
description: "Select
|
|
227
|
+
description: "Select dropdown option by APOM ID or CSS selector. Specify value, text, or index.",
|
|
238
228
|
inputSchema: {
|
|
239
229
|
type: "object",
|
|
240
230
|
properties: {
|
|
@@ -248,7 +238,7 @@ export const toolDefinitions = [
|
|
|
248
238
|
},
|
|
249
239
|
{
|
|
250
240
|
name: "drag",
|
|
251
|
-
description: "Drag element
|
|
241
|
+
description: "Drag element in any direction. For maps, charts, SVG, canvas, sliders. Use scrollHorizontal for scrollbars.",
|
|
252
242
|
inputSchema: {
|
|
253
243
|
type: "object",
|
|
254
244
|
properties: {
|
|
@@ -262,7 +252,7 @@ export const toolDefinitions = [
|
|
|
262
252
|
},
|
|
263
253
|
{
|
|
264
254
|
name: "scrollHorizontal",
|
|
265
|
-
description: "Scroll element horizontally. For tables, carousels,
|
|
255
|
+
description: "Scroll element horizontally by pixels or to end. For tables, carousels, scrollable containers.",
|
|
266
256
|
inputSchema: {
|
|
267
257
|
type: "object",
|
|
268
258
|
properties: {
|
|
@@ -487,12 +477,12 @@ export const toolDefinitions = [
|
|
|
487
477
|
},
|
|
488
478
|
{
|
|
489
479
|
name: "analyzePage",
|
|
490
|
-
description: "PRIMARY tool for reading page state. Returns APOM
|
|
480
|
+
description: "PRIMARY tool for reading page state. Returns APOM tree: {tree, metadata, groups}. Compact format (default): containers as \"tag_id\":[children] keys, interactive elements as {id, tag, type, position, metadata} without selectors. Use element IDs (e.g., button_45, input_20) with click/type tools. Selectors registered internally for resolution. Use refresh:true after clicks. Efficient: 8-10k tokens vs screenshot 15-25k.",
|
|
491
481
|
inputSchema: {
|
|
492
482
|
type: "object",
|
|
493
483
|
properties: {
|
|
494
484
|
refresh: { type: "boolean", description: "Refresh cache (default: false)" },
|
|
495
|
-
includeAll: { type: "boolean", description: "Include all elements
|
|
485
|
+
includeAll: { type: "boolean", description: "Include all elements with selectors - full debug format (default: false for compact format)" },
|
|
496
486
|
useLegacyFormat: { type: "boolean", description: "Return legacy format instead of APOM (default: false - APOM is now default)" },
|
|
497
487
|
registerElements: { type: "boolean", description: "Auto-register elements in selector resolver (default: true)" },
|
|
498
488
|
groupBy: { type: "string", description: "Group elements: 'type' or 'flat' (default: 'type')", enum: ["type", "flat"] },
|
|
@@ -500,26 +490,19 @@ export const toolDefinitions = [
|
|
|
500
490
|
},
|
|
501
491
|
},
|
|
502
492
|
{
|
|
503
|
-
name: "
|
|
504
|
-
description: "Get detailed information about element by its APOM ID
|
|
493
|
+
name: "getElementDetails",
|
|
494
|
+
description: "Get detailed information about element by its APOM ID. Returns full element details including bounds, CSS selector, position, attributes, and computed styles. Can also analyze children elements tree structure. Use this when analyzePage output was simplified and you need complete information about specific element or analyze specific sections in detail.",
|
|
505
495
|
inputSchema: {
|
|
506
496
|
type: "object",
|
|
507
497
|
properties: {
|
|
508
498
|
id: { type: "string", description: "APOM element ID (e.g., 'input_20', 'button_45') from analyzePage result" },
|
|
499
|
+
analyzeChildren: { type: "boolean", description: "Analyze children elements tree structure (default: false)" },
|
|
500
|
+
includeAll: { type: "boolean", description: "When analyzing children, include all elements, not just interactive ones (default: false)" },
|
|
501
|
+
refresh: { type: "boolean", description: "Force refresh of cached analysis (default: false)" },
|
|
509
502
|
},
|
|
510
503
|
required: ["id"],
|
|
511
504
|
},
|
|
512
505
|
},
|
|
513
|
-
{
|
|
514
|
-
name: "getAllInteractiveElements",
|
|
515
|
-
description: "Get all interactive elements with selectors. For understanding available actions.",
|
|
516
|
-
inputSchema: {
|
|
517
|
-
type: "object",
|
|
518
|
-
properties: {
|
|
519
|
-
includeHidden: { type: "boolean", description: "Include hidden (default: false)" },
|
|
520
|
-
},
|
|
521
|
-
},
|
|
522
|
-
},
|
|
523
506
|
{
|
|
524
507
|
name: "findElementsByText",
|
|
525
508
|
description: "Find elements by visible text content and get their selectors. Use this INSTEAD of executeScript when you need to find elements. Returns working selectors that can be used with click/type tools. Can optionally perform actions directly.",
|
|
@@ -564,7 +547,7 @@ export const toolDefinitions = [
|
|
|
564
547
|
},
|
|
565
548
|
{
|
|
566
549
|
name: "enableRecorder",
|
|
567
|
-
description: "Check ChromeTools Extension connection
|
|
550
|
+
description: "Check ChromeTools Extension connection for scenario recording. Use Chrome Extension popup (CT icon) for recording.",
|
|
568
551
|
inputSchema: {
|
|
569
552
|
type: "object",
|
|
570
553
|
properties: {},
|
|
@@ -572,7 +555,7 @@ export const toolDefinitions = [
|
|
|
572
555
|
},
|
|
573
556
|
{
|
|
574
557
|
name: "startRecording",
|
|
575
|
-
description: "Start recording user actions
|
|
558
|
+
description: "Start recording user actions. Follows active tab automatically. Use stopRecording to finish.",
|
|
576
559
|
inputSchema: {
|
|
577
560
|
type: "object",
|
|
578
561
|
properties: {
|
|
@@ -641,7 +624,7 @@ export const toolDefinitions = [
|
|
|
641
624
|
},
|
|
642
625
|
{
|
|
643
626
|
name: "executeScenario",
|
|
644
|
-
description: "Execute
|
|
627
|
+
description: "Execute scenario by name with dependency resolution. Use projectId to disambiguate duplicate names.",
|
|
645
628
|
inputSchema: {
|
|
646
629
|
type: "object",
|
|
647
630
|
properties: {
|
|
@@ -655,7 +638,7 @@ export const toolDefinitions = [
|
|
|
655
638
|
},
|
|
656
639
|
{
|
|
657
640
|
name: "listScenarios",
|
|
658
|
-
description: "List all scenarios with metadata.
|
|
641
|
+
description: "List all scenarios with metadata.",
|
|
659
642
|
inputSchema: {
|
|
660
643
|
type: "object",
|
|
661
644
|
properties: {
|
|
@@ -665,7 +648,7 @@ export const toolDefinitions = [
|
|
|
665
648
|
},
|
|
666
649
|
{
|
|
667
650
|
name: "searchScenarios",
|
|
668
|
-
description: "Search scenarios by text or tags.
|
|
651
|
+
description: "Search scenarios by text or tags.",
|
|
669
652
|
inputSchema: {
|
|
670
653
|
type: "object",
|
|
671
654
|
properties: {
|
|
@@ -677,7 +660,7 @@ export const toolDefinitions = [
|
|
|
677
660
|
},
|
|
678
661
|
{
|
|
679
662
|
name: "getScenarioInfo",
|
|
680
|
-
description: "Get scenario details: actions, parameters, dependencies.
|
|
663
|
+
description: "Get scenario details: actions, parameters, dependencies.",
|
|
681
664
|
inputSchema: {
|
|
682
665
|
type: "object",
|
|
683
666
|
properties: {
|
|
@@ -689,7 +672,7 @@ export const toolDefinitions = [
|
|
|
689
672
|
},
|
|
690
673
|
{
|
|
691
674
|
name: "deleteScenario",
|
|
692
|
-
description: "Delete scenario and secrets.
|
|
675
|
+
description: "Delete scenario and secrets.",
|
|
693
676
|
inputSchema: {
|
|
694
677
|
type: "object",
|
|
695
678
|
properties: {
|
|
@@ -700,7 +683,7 @@ export const toolDefinitions = [
|
|
|
700
683
|
},
|
|
701
684
|
{
|
|
702
685
|
name: "exportScenarioAsCode",
|
|
703
|
-
description: "Export
|
|
686
|
+
description: "Export scenario as test code for NEW file. Cleans unstable selectors, optionally generates Page Object. Use appendScenarioToFile for existing files.",
|
|
704
687
|
inputSchema: {
|
|
705
688
|
type: "object",
|
|
706
689
|
properties: {
|
|
@@ -735,7 +718,7 @@ export const toolDefinitions = [
|
|
|
735
718
|
},
|
|
736
719
|
{
|
|
737
720
|
name: "appendScenarioToFile",
|
|
738
|
-
description: "Append
|
|
721
|
+
description: "Append scenario as test code to EXISTING file. Cleans unstable selectors, optionally generates Page Object. Use exportScenarioAsCode for new files.",
|
|
739
722
|
inputSchema: {
|
|
740
723
|
type: "object",
|
|
741
724
|
properties: {
|
package/server/tool-groups.js
CHANGED
package/server/tool-schemas.js
CHANGED
|
@@ -29,16 +29,12 @@ export const TypeSchema = z.object({
|
|
|
29
29
|
id: z.string().optional().describe("APOM element ID from analyzePage (e.g., 'input_20', 'input_33'). Mutually exclusive with selector."),
|
|
30
30
|
selector: z.string().optional().describe("CSS selector for input element. Mutually exclusive with id."),
|
|
31
31
|
text: z.string().describe("Text to type"),
|
|
32
|
-
delay: z.number().optional().describe("Delay between keystrokes in ms (default:
|
|
32
|
+
delay: z.number().optional().describe("Delay between keystrokes in ms (default: 30)"),
|
|
33
33
|
clearFirst: z.boolean().optional().describe("Clear field before typing (default: true)"),
|
|
34
34
|
}).refine(data => (data.id && !data.selector) || (!data.id && data.selector), {
|
|
35
35
|
message: "Either 'id' or 'selector' must be provided, but not both"
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
export const GetElementSchema = z.object({
|
|
39
|
-
selector: z.string().optional().describe("CSS selector (optional, defaults to body)"),
|
|
40
|
-
});
|
|
41
|
-
|
|
42
38
|
export const HoverSchema = z.object({
|
|
43
39
|
id: z.string().optional().describe("APOM element ID from analyzePage. Mutually exclusive with selector."),
|
|
44
40
|
selector: z.string().optional().describe("CSS selector for element to hover. Mutually exclusive with id."),
|
|
@@ -260,18 +256,17 @@ export const SmartFindElementSchema = z.object({
|
|
|
260
256
|
|
|
261
257
|
export const AnalyzePageSchema = z.object({
|
|
262
258
|
refresh: z.boolean().optional().describe("Force refresh of cached analysis (default: false)"),
|
|
263
|
-
includeAll: z.boolean().optional().describe("Include all elements on page, not just interactive ones (default: false).
|
|
259
|
+
includeAll: z.boolean().optional().describe("Include all elements on page, not just interactive ones (default: false). When false (default), returns compact format: containers as \"tag_id\":[children], interactive elements without selectors. When true, returns full format with selectors for debugging."),
|
|
264
260
|
useLegacyFormat: z.boolean().optional().describe("Return legacy format instead of APOM (default: false - APOM is now the default format)"),
|
|
265
261
|
registerElements: z.boolean().optional().describe("Automatically register elements in selector resolver (default: true)"),
|
|
266
262
|
groupBy: z.enum(['type', 'flat']).optional().describe("Group elements by type or return flat structure (default: 'type')"),
|
|
267
263
|
});
|
|
268
264
|
|
|
269
|
-
export const
|
|
265
|
+
export const GetElementDetailsSchema = z.object({
|
|
270
266
|
id: z.string().describe("APOM element ID (e.g., 'input_20', 'button_45') from analyzePage result"),
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
includeHidden: z.boolean().optional().describe("Include hidden elements (default: false)"),
|
|
267
|
+
analyzeChildren: z.boolean().optional().describe("Analyze children elements tree structure (default: false)"),
|
|
268
|
+
includeAll: z.boolean().optional().describe("When analyzing children, include all elements, not just interactive ones (default: false)"),
|
|
269
|
+
refresh: z.boolean().optional().describe("Force refresh of cached analysis (default: false)"),
|
|
275
270
|
});
|
|
276
271
|
|
|
277
272
|
export const FindElementsByTextSchema = z.object({
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Interactivity Detection Test</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: Arial, sans-serif;
|
|
10
|
+
padding: 20px;
|
|
11
|
+
max-width: 800px;
|
|
12
|
+
margin: 0 auto;
|
|
13
|
+
}
|
|
14
|
+
.section {
|
|
15
|
+
margin: 30px 0;
|
|
16
|
+
padding: 20px;
|
|
17
|
+
border: 1px solid #ccc;
|
|
18
|
+
border-radius: 8px;
|
|
19
|
+
}
|
|
20
|
+
h2 {
|
|
21
|
+
margin-top: 0;
|
|
22
|
+
color: #333;
|
|
23
|
+
}
|
|
24
|
+
.test-item {
|
|
25
|
+
margin: 10px 0;
|
|
26
|
+
padding: 10px;
|
|
27
|
+
background: #f0f0f0;
|
|
28
|
+
border-radius: 4px;
|
|
29
|
+
}
|
|
30
|
+
.clickable-div {
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
padding: 10px;
|
|
33
|
+
background: #4CAF50;
|
|
34
|
+
color: white;
|
|
35
|
+
border-radius: 4px;
|
|
36
|
+
display: inline-block;
|
|
37
|
+
}
|
|
38
|
+
.hover-div {
|
|
39
|
+
padding: 10px;
|
|
40
|
+
background: #2196F3;
|
|
41
|
+
color: white;
|
|
42
|
+
border-radius: 4px;
|
|
43
|
+
display: inline-block;
|
|
44
|
+
}
|
|
45
|
+
.hover-div:hover {
|
|
46
|
+
background: #0b7dda;
|
|
47
|
+
}
|
|
48
|
+
</style>
|
|
49
|
+
</head>
|
|
50
|
+
<body>
|
|
51
|
+
<h1>🧪 Interactivity Detection Test Suite</h1>
|
|
52
|
+
|
|
53
|
+
<!-- 1. Standard Interactive Elements -->
|
|
54
|
+
<div class="section">
|
|
55
|
+
<h2>1️⃣ Standard Interactive Elements</h2>
|
|
56
|
+
<div class="test-item">
|
|
57
|
+
<button id="standard-button">Standard Button</button>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="test-item">
|
|
60
|
+
<a href="#test" id="standard-link">Standard Link</a>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="test-item">
|
|
63
|
+
<input type="text" id="standard-input" placeholder="Standard Input">
|
|
64
|
+
</div>
|
|
65
|
+
<div class="test-item">
|
|
66
|
+
<select id="standard-select">
|
|
67
|
+
<option>Option 1</option>
|
|
68
|
+
<option>Option 2</option>
|
|
69
|
+
</select>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<!-- 2. JavaScript Event Listeners -->
|
|
74
|
+
<div class="section">
|
|
75
|
+
<h2>2️⃣ Elements with JS Event Listeners</h2>
|
|
76
|
+
<div class="test-item">
|
|
77
|
+
<div id="click-listener" class="clickable-div">DIV with Click Listener</div>
|
|
78
|
+
</div>
|
|
79
|
+
<div class="test-item">
|
|
80
|
+
<span id="mousedown-listener" class="clickable-div">SPAN with Mousedown Listener</span>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="test-item">
|
|
83
|
+
<p id="multiple-listeners" class="clickable-div">P with Multiple Listeners (click, mousedown, mouseup)</p>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<!-- 3. React-style Synthetic Events (inline onclick) -->
|
|
88
|
+
<div class="section">
|
|
89
|
+
<h2>3️⃣ Inline Event Handlers</h2>
|
|
90
|
+
<div class="test-item">
|
|
91
|
+
<div onclick="alert('Clicked!')" class="clickable-div">DIV with onclick attribute</div>
|
|
92
|
+
</div>
|
|
93
|
+
<div class="test-item">
|
|
94
|
+
<span onmousedown="console.log('mousedown')" class="clickable-div">SPAN with onmousedown attribute</span>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<!-- 4. Pointer Cursor Elements -->
|
|
99
|
+
<div class="section">
|
|
100
|
+
<h2>4️⃣ Elements with cursor: pointer</h2>
|
|
101
|
+
<div class="test-item">
|
|
102
|
+
<div class="clickable-div" style="cursor: pointer;">DIV with cursor:pointer (no JS)</div>
|
|
103
|
+
</div>
|
|
104
|
+
<div class="test-item">
|
|
105
|
+
<span style="cursor: pointer; padding: 5px; background: #FF9800; color: white; border-radius: 4px;">
|
|
106
|
+
SPAN with cursor:pointer (no JS)
|
|
107
|
+
</span>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<!-- 5. Hover Effects (CSS only) -->
|
|
112
|
+
<div class="section">
|
|
113
|
+
<h2>5️⃣ Elements with CSS :hover Effects</h2>
|
|
114
|
+
<div class="test-item">
|
|
115
|
+
<div class="hover-div">DIV with :hover CSS (no JS)</div>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<!-- 6. Contenteditable -->
|
|
120
|
+
<div class="section">
|
|
121
|
+
<h2>6️⃣ Contenteditable Elements</h2>
|
|
122
|
+
<div class="test-item">
|
|
123
|
+
<div contenteditable="true" style="border: 1px solid #999; padding: 10px; min-height: 50px;">
|
|
124
|
+
Editable DIV - type here!
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<!-- 7. Complex Nested Interactive Elements -->
|
|
130
|
+
<div class="section">
|
|
131
|
+
<h2>7️⃣ Nested Interactive Elements</h2>
|
|
132
|
+
<div class="test-item">
|
|
133
|
+
<div id="parent-clickable" class="clickable-div">
|
|
134
|
+
Parent DIV with click listener
|
|
135
|
+
<button id="nested-button" style="margin-left: 10px;">Nested Button</button>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<!-- 8. False Positives Check -->
|
|
141
|
+
<div class="section">
|
|
142
|
+
<h2>8️⃣ Non-Interactive Elements (Should NOT be detected)</h2>
|
|
143
|
+
<div class="test-item">
|
|
144
|
+
<p>Regular paragraph - not interactive</p>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="test-item">
|
|
147
|
+
<div style="padding: 10px; background: #ddd;">Regular DIV - not interactive</div>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="test-item">
|
|
150
|
+
<span>Regular SPAN - not interactive</span>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<script>
|
|
155
|
+
// Add event listeners to test elements
|
|
156
|
+
document.getElementById('click-listener').addEventListener('click', function() {
|
|
157
|
+
console.log('Click listener triggered');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
document.getElementById('mousedown-listener').addEventListener('mousedown', function() {
|
|
161
|
+
console.log('Mousedown listener triggered');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const multiListener = document.getElementById('multiple-listeners');
|
|
165
|
+
multiListener.addEventListener('click', () => console.log('click'));
|
|
166
|
+
multiListener.addEventListener('mousedown', () => console.log('mousedown'));
|
|
167
|
+
multiListener.addEventListener('mouseup', () => console.log('mouseup'));
|
|
168
|
+
|
|
169
|
+
document.getElementById('parent-clickable').addEventListener('click', function(e) {
|
|
170
|
+
if (e.target === this) {
|
|
171
|
+
console.log('Parent clicked');
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
console.log('✅ Test page loaded successfully');
|
|
176
|
+
</script>
|
|
177
|
+
</body>
|
|
178
|
+
</html>
|