@tontoko/fast-playwright-mcp 0.0.4

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.
Files changed (107) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +1047 -0
  3. package/cli.js +18 -0
  4. package/config.d.ts +124 -0
  5. package/index.d.ts +25 -0
  6. package/index.js +18 -0
  7. package/lib/actions.d.js +0 -0
  8. package/lib/batch/batch-executor.js +137 -0
  9. package/lib/browser-context-factory.js +252 -0
  10. package/lib/browser-server-backend.js +139 -0
  11. package/lib/config/constants.js +80 -0
  12. package/lib/config.js +405 -0
  13. package/lib/context.js +274 -0
  14. package/lib/diagnostics/common/diagnostic-base.js +63 -0
  15. package/lib/diagnostics/common/error-enrichment-utils.js +212 -0
  16. package/lib/diagnostics/common/index.js +56 -0
  17. package/lib/diagnostics/common/initialization-manager.js +210 -0
  18. package/lib/diagnostics/common/performance-tracker.js +132 -0
  19. package/lib/diagnostics/diagnostic-error.js +140 -0
  20. package/lib/diagnostics/diagnostic-level.js +123 -0
  21. package/lib/diagnostics/diagnostic-thresholds.js +347 -0
  22. package/lib/diagnostics/element-discovery.js +441 -0
  23. package/lib/diagnostics/enhanced-error-handler.js +376 -0
  24. package/lib/diagnostics/error-enrichment.js +157 -0
  25. package/lib/diagnostics/frame-reference-manager.js +179 -0
  26. package/lib/diagnostics/page-analyzer.js +639 -0
  27. package/lib/diagnostics/parallel-page-analyzer.js +129 -0
  28. package/lib/diagnostics/resource-manager.js +134 -0
  29. package/lib/diagnostics/smart-config.js +482 -0
  30. package/lib/diagnostics/smart-handle.js +118 -0
  31. package/lib/diagnostics/unified-system.js +717 -0
  32. package/lib/extension/cdp-relay.js +486 -0
  33. package/lib/extension/extension-context-factory.js +74 -0
  34. package/lib/extension/main.js +41 -0
  35. package/lib/file-utils.js +42 -0
  36. package/lib/generate-keys.js +75 -0
  37. package/lib/http-server.js +50 -0
  38. package/lib/in-process-client.js +64 -0
  39. package/lib/index.js +48 -0
  40. package/lib/javascript.js +90 -0
  41. package/lib/log.js +33 -0
  42. package/lib/loop/loop-claude.js +247 -0
  43. package/lib/loop/loop-open-ai.js +222 -0
  44. package/lib/loop/loop.js +174 -0
  45. package/lib/loop/main.js +46 -0
  46. package/lib/loopTools/context.js +76 -0
  47. package/lib/loopTools/main.js +65 -0
  48. package/lib/loopTools/perform.js +40 -0
  49. package/lib/loopTools/snapshot.js +37 -0
  50. package/lib/loopTools/tool.js +26 -0
  51. package/lib/manual-promise.js +125 -0
  52. package/lib/mcp/in-process-transport.js +91 -0
  53. package/lib/mcp/proxy-backend.js +127 -0
  54. package/lib/mcp/server.js +123 -0
  55. package/lib/mcp/transport.js +159 -0
  56. package/lib/package.js +28 -0
  57. package/lib/program.js +82 -0
  58. package/lib/response.js +493 -0
  59. package/lib/schemas/expectation.js +152 -0
  60. package/lib/session-log.js +210 -0
  61. package/lib/tab.js +417 -0
  62. package/lib/tools/base-tool-handler.js +141 -0
  63. package/lib/tools/batch-execute.js +150 -0
  64. package/lib/tools/common.js +65 -0
  65. package/lib/tools/console.js +60 -0
  66. package/lib/tools/diagnose/diagnose-analysis-runner.js +101 -0
  67. package/lib/tools/diagnose/diagnose-config-handler.js +130 -0
  68. package/lib/tools/diagnose/diagnose-report-builder.js +394 -0
  69. package/lib/tools/diagnose.js +147 -0
  70. package/lib/tools/dialogs.js +57 -0
  71. package/lib/tools/evaluate.js +67 -0
  72. package/lib/tools/files.js +53 -0
  73. package/lib/tools/find-elements.js +307 -0
  74. package/lib/tools/install.js +60 -0
  75. package/lib/tools/keyboard.js +93 -0
  76. package/lib/tools/mouse.js +110 -0
  77. package/lib/tools/navigate.js +82 -0
  78. package/lib/tools/network.js +50 -0
  79. package/lib/tools/pdf.js +46 -0
  80. package/lib/tools/screenshot.js +113 -0
  81. package/lib/tools/snapshot.js +158 -0
  82. package/lib/tools/tabs.js +97 -0
  83. package/lib/tools/tool.js +47 -0
  84. package/lib/tools/utils.js +131 -0
  85. package/lib/tools/wait.js +64 -0
  86. package/lib/tools.js +65 -0
  87. package/lib/types/batch.js +47 -0
  88. package/lib/types/diff.js +0 -0
  89. package/lib/types/performance.js +0 -0
  90. package/lib/types/threshold-base.js +0 -0
  91. package/lib/utils/array-utils.js +44 -0
  92. package/lib/utils/code-deduplication-utils.js +141 -0
  93. package/lib/utils/common-formatters.js +252 -0
  94. package/lib/utils/console-filter.js +64 -0
  95. package/lib/utils/diagnostic-report-utils.js +178 -0
  96. package/lib/utils/diff-formatter.js +126 -0
  97. package/lib/utils/disposable-manager.js +135 -0
  98. package/lib/utils/error-handler-middleware.js +77 -0
  99. package/lib/utils/image-processor.js +137 -0
  100. package/lib/utils/index.js +92 -0
  101. package/lib/utils/report-builder.js +189 -0
  102. package/lib/utils/request-logger.js +82 -0
  103. package/lib/utils/response-diff-detector.js +150 -0
  104. package/lib/utils/section-builder.js +62 -0
  105. package/lib/utils/tool-patterns.js +153 -0
  106. package/lib/utils.js +46 -0
  107. package/package.json +77 -0
@@ -0,0 +1,639 @@
1
+ import { createRequire } from "node:module";
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __toESM = (mod, isNodeMode, target) => {
8
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
9
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
+ for (let key of __getOwnPropNames(mod))
11
+ if (!__hasOwnProp.call(to, key))
12
+ __defProp(to, key, {
13
+ get: () => mod[key],
14
+ enumerable: true
15
+ });
16
+ return to;
17
+ };
18
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
+
20
+ // src/diagnostics/page-analyzer.ts
21
+ import { DiagnosticBase } from "./common/diagnostic-base.js";
22
+ import { getCurrentThresholds } from "./diagnostic-thresholds.js";
23
+ import { FrameReferenceManager } from "./frame-reference-manager.js";
24
+ import { ParallelPageAnalyzer } from "./parallel-page-analyzer.js";
25
+
26
+ class PageAnalyzer extends DiagnosticBase {
27
+ metricsThresholds;
28
+ frameRefs = new Set;
29
+ frameManager;
30
+ constructor(page) {
31
+ super(page, "PageAnalyzer");
32
+ this.frameManager = new FrameReferenceManager;
33
+ this.metricsThresholds = getCurrentThresholds().getMetricsThresholds();
34
+ }
35
+ async performDispose() {
36
+ try {
37
+ await this.frameManager.dispose();
38
+ } catch {}
39
+ this.frameRefs.clear();
40
+ }
41
+ async analyzePageStructure() {
42
+ this.getPage();
43
+ const [iframes, modalStates, elements] = await Promise.all([
44
+ this.analyzeIframes(),
45
+ this.analyzeModalStates(),
46
+ this.analyzeElements()
47
+ ]);
48
+ return {
49
+ iframes,
50
+ modalStates,
51
+ elements
52
+ };
53
+ }
54
+ async analyzeIframes() {
55
+ const page = this.getPage();
56
+ const iframes = await page.$$("iframe");
57
+ const detected = iframes.length > 0;
58
+ const accessible = [];
59
+ const inaccessible = [];
60
+ try {
61
+ await this.processIframes(iframes, accessible, inaccessible);
62
+ await this.frameManager.cleanupDetachedFrames();
63
+ } catch (error) {
64
+ await this.cleanupIframesOnError(iframes);
65
+ throw error;
66
+ }
67
+ return {
68
+ detected,
69
+ count: iframes.length,
70
+ accessible,
71
+ inaccessible
72
+ };
73
+ }
74
+ async processIframes(iframes, accessible, inaccessible) {
75
+ await this.processIframesRecursive(iframes, 0, accessible, inaccessible);
76
+ }
77
+ async processIframesRecursive(iframes, index, accessible, inaccessible) {
78
+ if (index >= iframes.length) {
79
+ return;
80
+ }
81
+ const iframe = iframes[index];
82
+ const src = await iframe.getAttribute("src") ?? "about:blank";
83
+ try {
84
+ await this.processIndividualIframe(iframe, src, accessible, inaccessible);
85
+ } catch (error) {
86
+ this.addInaccessibleIframe(inaccessible, src, error);
87
+ } finally {
88
+ await this.disposeIframeElement(iframe);
89
+ }
90
+ await this.processIframesRecursive(iframes, index + 1, accessible, inaccessible);
91
+ }
92
+ async processIndividualIframe(iframe, src, accessible, inaccessible) {
93
+ const frame = await iframe.contentFrame();
94
+ if (!frame) {
95
+ inaccessible.push({
96
+ src,
97
+ reason: "Content frame not available"
98
+ });
99
+ return;
100
+ }
101
+ this.frameManager.trackFrame(frame);
102
+ this.frameRefs.add(frame);
103
+ try {
104
+ await this.verifyFrameAccessibility(frame);
105
+ accessible.push({ src, accessible: true });
106
+ await this.updateFrameMetadata(frame);
107
+ } catch {
108
+ inaccessible.push({
109
+ src,
110
+ reason: "Frame content not accessible - cross-origin or blocked"
111
+ });
112
+ }
113
+ }
114
+ async verifyFrameAccessibility(frame) {
115
+ await Promise.race([
116
+ frame.url(),
117
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 1000))
118
+ ]);
119
+ }
120
+ async updateFrameMetadata(frame) {
121
+ try {
122
+ const elementCount = await frame.$$eval("*", (elements) => elements.length);
123
+ this.frameManager.updateElementCount(frame, elementCount);
124
+ } catch {}
125
+ }
126
+ addInaccessibleIframe(inaccessible, src, error) {
127
+ inaccessible.push({
128
+ src,
129
+ reason: error instanceof Error ? error.message : "Access denied"
130
+ });
131
+ }
132
+ async disposeIframeElement(iframe) {
133
+ try {
134
+ await iframe.dispose();
135
+ } catch {}
136
+ }
137
+ async cleanupIframesOnError(iframes) {
138
+ await Promise.all(iframes.map(async (iframe) => {
139
+ try {
140
+ await iframe.dispose();
141
+ } catch {}
142
+ }));
143
+ }
144
+ async analyzeModalStates() {
145
+ const page = this.getPage();
146
+ const blockedBy = [];
147
+ let hasDialog = false;
148
+ let hasFileChooser = false;
149
+ try {
150
+ hasDialog = await page.evaluate(() => {
151
+ const modals = document.querySelectorAll('[role="dialog"], .modal, .dialog, .popup');
152
+ const overlays = document.querySelectorAll(".overlay, .modal-backdrop, .dialog-backdrop");
153
+ return modals.length > 0 || overlays.length > 0;
154
+ });
155
+ hasFileChooser = await page.evaluate(() => {
156
+ const fileInputs = document.querySelectorAll('input[type="file"]');
157
+ return Array.from(fileInputs).some((input) => {
158
+ const style = window.getComputedStyle(input);
159
+ return style.display !== "none" && style.visibility !== "hidden";
160
+ });
161
+ });
162
+ } catch {
163
+ hasDialog = false;
164
+ hasFileChooser = false;
165
+ }
166
+ if (hasDialog) {
167
+ blockedBy.push("dialog");
168
+ }
169
+ if (hasFileChooser) {
170
+ blockedBy.push("fileChooser");
171
+ }
172
+ return {
173
+ hasDialog,
174
+ hasFileChooser,
175
+ blockedBy
176
+ };
177
+ }
178
+ async analyzeElements() {
179
+ const page = this.getPage();
180
+ const elementStats = await page.evaluate(() => {
181
+ const allElements = Array.from(document.querySelectorAll("*"));
182
+ let totalVisible = 0;
183
+ let totalInteractable = 0;
184
+ let missingAria = 0;
185
+ const isElementVisible = (element) => {
186
+ const style = window.getComputedStyle(element);
187
+ return style.display !== "none" && style.visibility !== "hidden";
188
+ };
189
+ const isElementInteractable = (element) => {
190
+ const tagName = element.tagName.toLowerCase();
191
+ return ["button", "input", "select", "textarea", "a"].includes(tagName) || element.hasAttribute("onclick") || element.hasAttribute("role");
192
+ };
193
+ const hasMissingAriaAttributes = (element) => {
194
+ return !(element.hasAttribute("aria-label") || element.hasAttribute("aria-labelledby") || element.textContent?.trim());
195
+ };
196
+ for (const element of allElements) {
197
+ if (isElementVisible(element)) {
198
+ totalVisible++;
199
+ if (isElementInteractable(element)) {
200
+ totalInteractable++;
201
+ if (hasMissingAriaAttributes(element)) {
202
+ missingAria++;
203
+ }
204
+ }
205
+ }
206
+ }
207
+ return { totalVisible, totalInteractable, missingAria };
208
+ });
209
+ return elementStats;
210
+ }
211
+ async analyzePerformanceMetrics() {
212
+ const startTime = Date.now();
213
+ const page = this.getPage();
214
+ try {
215
+ const metricsData = await page.evaluate(() => {
216
+ const getAllElementsWithTreeWalker = () => {
217
+ const elements = [];
218
+ const walker = document.createTreeWalker(document.documentElement, NodeFilter.SHOW_ELEMENT, null);
219
+ let node = walker.nextNode();
220
+ while (node !== null) {
221
+ elements.push(node);
222
+ node = walker.nextNode();
223
+ }
224
+ return elements;
225
+ };
226
+ const getMaxDepth = (element, currentDepth = 0) => {
227
+ let maxChildDepth = currentDepth;
228
+ for (const child of Array.from(element.children)) {
229
+ const childDepth = getMaxDepth(child, currentDepth + 1);
230
+ maxChildDepth = Math.max(maxChildDepth, childDepth);
231
+ }
232
+ return maxChildDepth;
233
+ };
234
+ const countDescendants = (rootElement) => {
235
+ const walker = document.createTreeWalker(rootElement, NodeFilter.SHOW_ELEMENT, null);
236
+ let count = 0;
237
+ while (walker.nextNode()) {
238
+ count++;
239
+ }
240
+ return count - 1;
241
+ };
242
+ const getSubtreeDescription = (tagName, element) => {
243
+ if (tagName === "ul" || tagName === "ol") {
244
+ return "Large list structure";
245
+ }
246
+ if (tagName === "table") {
247
+ return "Large table structure";
248
+ }
249
+ if (tagName === "div" && (element.className.includes("container") || element.className.includes("wrapper"))) {
250
+ return "Large container element";
251
+ }
252
+ return "Large subtree";
253
+ };
254
+ const buildElementSelector = (element) => {
255
+ const tagName = element.tagName.toLowerCase();
256
+ const id = element.id ? `#${element.id}` : "";
257
+ const className = element.className ? `.${element.className.split(" ")[0]}` : "";
258
+ return `${tagName}${id}${className}`;
259
+ };
260
+ const analyzeSubtree = (element, selector, subtreeArray) => {
261
+ const descendantCount = countDescendants(element);
262
+ if (descendantCount >= 500) {
263
+ const tagName = element.tagName.toLowerCase();
264
+ const fullSelector = buildElementSelector(element);
265
+ const description = getSubtreeDescription(tagName, element);
266
+ subtreeArray.push({
267
+ selector: fullSelector || selector,
268
+ elementCount: descendantCount,
269
+ description
270
+ });
271
+ }
272
+ };
273
+ const analyzeLargeSubtrees = () => {
274
+ const largeSubtrees2 = [];
275
+ if (document.body) {
276
+ analyzeSubtree(document.body, "body", largeSubtrees2);
277
+ const containers = Array.from(document.body.querySelectorAll("div, section, main, article, aside"));
278
+ for (let index = 0;index < containers.length; index++) {
279
+ const container = containers[index];
280
+ analyzeSubtree(container, `container-${index}`, largeSubtrees2);
281
+ }
282
+ }
283
+ return largeSubtrees2;
284
+ };
285
+ const isClickableElement = (element, tagName, type) => {
286
+ return tagName === "button" || tagName === "input" && ["button", "submit", "reset"].includes(type ?? "") || tagName === "a" && element.hasAttribute("href") || element.hasAttribute("onclick") || element.getAttribute("role") === "button" || element.getAttribute("role") === "link" || element.hasAttribute("tabindex") && element.getAttribute("tabindex") !== "-1";
287
+ };
288
+ const isFormElement = (tagName, type) => {
289
+ return ["input", "select", "textarea"].includes(tagName) || tagName === "button" && type === "submit";
290
+ };
291
+ const isDisabledElement = (element) => {
292
+ return element.hasAttribute("disabled") || element.getAttribute("aria-disabled") === "true";
293
+ };
294
+ const analyzeInteractionElements = (elements) => {
295
+ let clickableElements = 0;
296
+ let formElements = 0;
297
+ let disabledElements = 0;
298
+ for (const element of elements) {
299
+ const tagName = element.tagName.toLowerCase();
300
+ const type = element.type?.toLowerCase();
301
+ if (isClickableElement(element, tagName, type)) {
302
+ clickableElements++;
303
+ }
304
+ if (isFormElement(tagName, type)) {
305
+ formElements++;
306
+ }
307
+ if (isDisabledElement(element)) {
308
+ disabledElements++;
309
+ }
310
+ }
311
+ const iframes = document.querySelectorAll("iframe").length;
312
+ return { clickableElements, formElements, disabledElements, iframes };
313
+ };
314
+ const analyzeResourceMetrics = () => {
315
+ const images = document.querySelectorAll("img");
316
+ const imageCount = images.length;
317
+ let sizeDescription = "Small (estimated)";
318
+ if (imageCount > 0) {
319
+ const estimatedImageSize = imageCount * 50;
320
+ if (estimatedImageSize > 1000) {
321
+ sizeDescription = "Large (>1MB estimated)";
322
+ } else if (estimatedImageSize > 500) {
323
+ sizeDescription = "Medium (>500KB estimated)";
324
+ }
325
+ }
326
+ const scriptTags = document.querySelectorAll("script").length;
327
+ const inlineScripts = document.querySelectorAll("script:not([src])").length;
328
+ const externalScripts = scriptTags - inlineScripts;
329
+ const stylesheetCount = document.querySelectorAll('link[rel="stylesheet"], style').length;
330
+ return {
331
+ totalRequests: 0,
332
+ totalSize: 0,
333
+ loadTime: 0,
334
+ imageCount,
335
+ estimatedImageSize: sizeDescription,
336
+ scriptTags,
337
+ inlineScripts,
338
+ externalScripts,
339
+ stylesheetCount
340
+ };
341
+ };
342
+ const getFixedElementPurpose = (tagName, element) => {
343
+ const className = element.className.toLowerCase();
344
+ if (isNavigationElement(tagName, element, className)) {
345
+ return "Fixed navigation element";
346
+ }
347
+ if (isHeaderElement(tagName, className)) {
348
+ return "Fixed header element";
349
+ }
350
+ if (isModalElement(className)) {
351
+ return "Modal or dialog overlay";
352
+ }
353
+ if (isToolbarElement(className)) {
354
+ return "Fixed toolbar or controls";
355
+ }
356
+ return "Unknown fixed element";
357
+ };
358
+ const isNavigationElement = (tagName, element, className) => {
359
+ return tagName === "nav" || element.getAttribute("role") === "navigation" || className.includes("nav");
360
+ };
361
+ const isHeaderElement = (tagName, className) => {
362
+ return tagName === "header" || className.includes("header");
363
+ };
364
+ const isModalElement = (className) => {
365
+ return className.includes("modal") || className.includes("dialog");
366
+ };
367
+ const isToolbarElement = (className) => {
368
+ return className.includes("toolbar") || className.includes("controls");
369
+ };
370
+ const getZIndexDescription = (zIndex, element) => {
371
+ if (zIndex >= 9999) {
372
+ return "Extremely high z-index (potential issue)";
373
+ }
374
+ if (element.className.toLowerCase().includes("modal")) {
375
+ return "Modal with high z-index";
376
+ }
377
+ if (element.className.toLowerCase().includes("tooltip")) {
378
+ return "Tooltip with high z-index";
379
+ }
380
+ return "High z-index element";
381
+ };
382
+ const analyzeLayoutElements = (elements) => {
383
+ const results = {
384
+ viewportWidth: window.innerWidth,
385
+ viewportHeight: window.innerHeight,
386
+ scrollHeight: document.documentElement.scrollHeight,
387
+ fixedElements: [],
388
+ highZIndexElements: [],
389
+ overflowHiddenElements: 0
390
+ };
391
+ for (let index = 0;index < elements.length; index++) {
392
+ const element = elements[index];
393
+ const style = window.getComputedStyle(element);
394
+ processElementLayout(element, style, index, results);
395
+ }
396
+ return results;
397
+ };
398
+ const processElementLayout = (element, style, index, results) => {
399
+ const position = style.position;
400
+ const zIndex = Number.parseInt(style.zIndex ?? "0", 10);
401
+ const tagName = element.tagName.toLowerCase();
402
+ if (position === "fixed") {
403
+ processFixedElement(element, tagName, zIndex, index, results.fixedElements);
404
+ }
405
+ if (zIndex >= 1000) {
406
+ processHighZIndexElement(element, zIndex, index, results.highZIndexElements);
407
+ }
408
+ if (style.overflow === "hidden") {
409
+ results.overflowHiddenElements++;
410
+ }
411
+ };
412
+ const processFixedElement = (element, tagName, zIndex, index, fixedElements) => {
413
+ const purpose = getFixedElementPurpose(tagName, element);
414
+ const selector = generateElementSelector(element, tagName, index);
415
+ fixedElements.push({ selector, purpose, zIndex });
416
+ };
417
+ const processHighZIndexElement = (element, zIndex, index, highZIndexElements) => {
418
+ const description = getZIndexDescription(zIndex, element);
419
+ const selector = generateElementSelector(element, element.tagName.toLowerCase(), index);
420
+ highZIndexElements.push({ selector, zIndex, description });
421
+ };
422
+ const generateElementSelector = (element, tagName, index) => {
423
+ return element.id ? `#${element.id}` : `${tagName}:nth-child(${index + 1})`;
424
+ };
425
+ const allElements = getAllElementsWithTreeWalker();
426
+ const totalElements = allElements.length;
427
+ const maxDepth = getMaxDepth(document.documentElement);
428
+ const largeSubtrees = analyzeLargeSubtrees();
429
+ const interaction = analyzeInteractionElements(allElements);
430
+ const resource = analyzeResourceMetrics();
431
+ const layout = analyzeLayoutElements(allElements);
432
+ return {
433
+ dom: {
434
+ totalElements,
435
+ maxDepth,
436
+ largeSubtrees
437
+ },
438
+ interaction,
439
+ resource,
440
+ layout
441
+ };
442
+ });
443
+ const warnings = [];
444
+ if (metricsData.dom.totalElements >= this.metricsThresholds.dom.elementsDanger) {
445
+ warnings.push({
446
+ type: "dom_complexity",
447
+ level: "danger",
448
+ message: `Very high DOM complexity: ${metricsData.dom.totalElements} elements (threshold: ${this.metricsThresholds.dom.elementsDanger})`
449
+ });
450
+ } else if (metricsData.dom.totalElements >= this.metricsThresholds.dom.elementsWarning) {
451
+ warnings.push({
452
+ type: "dom_complexity",
453
+ level: "warning",
454
+ message: `High DOM complexity: ${metricsData.dom.totalElements} elements (threshold: ${this.metricsThresholds.dom.elementsWarning})`
455
+ });
456
+ }
457
+ if (metricsData.dom.maxDepth >= this.metricsThresholds.dom.depthDanger) {
458
+ warnings.push({
459
+ type: "dom_complexity",
460
+ level: "danger",
461
+ message: `Very deep DOM structure: ${metricsData.dom.maxDepth} levels (threshold: ${this.metricsThresholds.dom.depthDanger})`
462
+ });
463
+ } else if (metricsData.dom.maxDepth >= this.metricsThresholds.dom.depthWarning) {
464
+ warnings.push({
465
+ type: "dom_complexity",
466
+ level: "warning",
467
+ message: `Deep DOM structure: ${metricsData.dom.maxDepth} levels (threshold: ${this.metricsThresholds.dom.depthWarning})`
468
+ });
469
+ }
470
+ if (metricsData.interaction.clickableElements >= this.metricsThresholds.interaction.clickableHigh) {
471
+ warnings.push({
472
+ type: "interaction_overload",
473
+ level: "warning",
474
+ message: `High number of clickable elements: ${metricsData.interaction.clickableElements} (threshold: ${this.metricsThresholds.interaction.clickableHigh})`
475
+ });
476
+ }
477
+ if (metricsData.layout.highZIndexElements.some((el) => el.zIndex >= this.metricsThresholds.layout.excessiveZIndexThreshold)) {
478
+ warnings.push({
479
+ type: "layout_issue",
480
+ level: "warning",
481
+ message: `Elements with excessive z-index values detected (>=${this.metricsThresholds.layout.excessiveZIndexThreshold})`
482
+ });
483
+ }
484
+ if (metricsData.resource.imageCount > 20) {
485
+ warnings.push({
486
+ type: "resource_heavy",
487
+ level: "warning",
488
+ message: `High number of images: ${metricsData.resource.imageCount} (may impact loading performance)`
489
+ });
490
+ }
491
+ const executionTime = Date.now() - startTime;
492
+ return {
493
+ executionTime,
494
+ memoryUsage: process.memoryUsage().heapUsed,
495
+ operationCount: 1,
496
+ errorCount: 0,
497
+ successRate: 1,
498
+ domMetrics: metricsData.dom,
499
+ interactionMetrics: metricsData.interaction,
500
+ resourceMetrics: metricsData.resource,
501
+ layoutMetrics: metricsData.layout,
502
+ warnings
503
+ };
504
+ } catch (error) {
505
+ const executionTime = Date.now() - startTime;
506
+ return {
507
+ executionTime,
508
+ memoryUsage: process.memoryUsage().heapUsed,
509
+ operationCount: 1,
510
+ errorCount: 1,
511
+ successRate: 0,
512
+ domMetrics: {
513
+ totalElements: 0,
514
+ maxDepth: 0,
515
+ largeSubtrees: []
516
+ },
517
+ interactionMetrics: {
518
+ clickableElements: 0,
519
+ formElements: 0,
520
+ disabledElements: 0,
521
+ iframes: 0
522
+ },
523
+ resourceMetrics: {
524
+ totalRequests: 0,
525
+ totalSize: 0,
526
+ loadTime: 0,
527
+ imageCount: 0,
528
+ estimatedImageSize: "Unknown",
529
+ scriptTags: 0,
530
+ inlineScripts: 0,
531
+ externalScripts: 0,
532
+ stylesheetCount: 0
533
+ },
534
+ layoutMetrics: {
535
+ viewportWidth: 0,
536
+ viewportHeight: 0,
537
+ scrollHeight: 0,
538
+ fixedElements: [],
539
+ highZIndexElements: [],
540
+ overflowHiddenElements: 0
541
+ },
542
+ warnings: [
543
+ {
544
+ type: "dom_complexity",
545
+ level: "danger",
546
+ message: `Performance analysis failed: ${error instanceof Error ? error.message : "Unknown error"}`
547
+ }
548
+ ]
549
+ };
550
+ }
551
+ }
552
+ getFrameStats() {
553
+ if (this.disposed) {
554
+ return {
555
+ frameStats: {
556
+ activeCount: 0,
557
+ totalTracked: 0,
558
+ detachedCount: 0,
559
+ averageElementCount: 0
560
+ },
561
+ performanceIssues: {
562
+ largeFrames: [],
563
+ oldFrames: []
564
+ },
565
+ isDisposed: this.disposed
566
+ };
567
+ }
568
+ const frameStats = this.frameManager.getStatistics();
569
+ const performanceIssues = this.frameManager.findPerformanceIssues();
570
+ return {
571
+ frameStats,
572
+ performanceIssues,
573
+ isDisposed: false
574
+ };
575
+ }
576
+ async cleanupFrames() {
577
+ if (this.disposed) {
578
+ return;
579
+ }
580
+ await this.frameManager.cleanupDetachedFrames();
581
+ }
582
+ async runParallelAnalysis() {
583
+ const page = this.getPage();
584
+ const parallelAnalyzer = new ParallelPageAnalyzer(page);
585
+ try {
586
+ return await parallelAnalyzer.runParallelAnalysis();
587
+ } catch (error) {
588
+ throw new Error(`Parallel analysis failed: ${error instanceof Error ? error.message : "Unknown error"}`);
589
+ }
590
+ }
591
+ async getEnhancedDiagnostics() {
592
+ const [parallelAnalysis, frameStats] = await Promise.all([
593
+ this.runParallelAnalysis(),
594
+ Promise.resolve(this.getFrameStats())
595
+ ]);
596
+ return {
597
+ parallelAnalysis,
598
+ frameStats,
599
+ timestamp: Date.now()
600
+ };
601
+ }
602
+ async shouldUseParallelAnalysis() {
603
+ const page = this.getPage();
604
+ try {
605
+ const elementCount = await page.evaluate(() => document.querySelectorAll("*").length);
606
+ const iframeCount = await page.evaluate(() => document.querySelectorAll("iframe").length);
607
+ const formElements = await page.evaluate(() => document.querySelectorAll("input, button, select, textarea").length);
608
+ const complexity = elementCount + iframeCount * 100 + formElements * 10;
609
+ if (complexity > 2000) {
610
+ return {
611
+ recommended: true,
612
+ reason: `High page complexity detected (elements: ${elementCount}, iframes: ${iframeCount})`,
613
+ estimatedBenefit: "Expected 40-60% performance improvement"
614
+ };
615
+ }
616
+ if (complexity > 1000) {
617
+ return {
618
+ recommended: true,
619
+ reason: "Moderate complexity - parallel analysis will provide better resource monitoring",
620
+ estimatedBenefit: "Expected 20-40% performance improvement"
621
+ };
622
+ }
623
+ return {
624
+ recommended: false,
625
+ reason: "Low complexity page - sequential analysis sufficient",
626
+ estimatedBenefit: "Minimal performance difference expected"
627
+ };
628
+ } catch {
629
+ return {
630
+ recommended: true,
631
+ reason: "Unable to assess complexity - using parallel analysis as fallback",
632
+ estimatedBenefit: "Resource monitoring and error handling benefits"
633
+ };
634
+ }
635
+ }
636
+ }
637
+ export {
638
+ PageAnalyzer
639
+ };