chrome-devtools-mcp 0.17.2 → 0.18.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.
Files changed (265) hide show
  1. package/README.md +27 -2
  2. package/build/src/DevToolsConnectionAdapter.js +69 -0
  3. package/build/src/DevtoolsUtils.js +340 -0
  4. package/build/src/McpContext.js +703 -0
  5. package/build/src/McpResponse.js +491 -0
  6. package/build/src/Mutex.js +37 -0
  7. package/build/src/PageCollector.js +309 -0
  8. package/build/src/SlimMcpResponse.js +18 -0
  9. package/build/src/WaitForHelper.js +139 -0
  10. package/build/src/browser.js +202 -0
  11. package/build/src/cli.js +307 -0
  12. package/build/src/daemon/daemon.js +167 -0
  13. package/build/src/daemon/utils.js +67 -0
  14. package/build/src/formatters/ConsoleFormatter.js +241 -0
  15. package/build/src/formatters/IssueFormatter.js +192 -0
  16. package/build/src/formatters/NetworkFormatter.js +218 -0
  17. package/build/src/formatters/SnapshotFormatter.js +134 -0
  18. package/build/src/index.js +21 -0
  19. package/build/src/issue-descriptions.js +39 -0
  20. package/build/src/logger.js +36 -0
  21. package/build/src/main.js +201 -0
  22. package/build/src/polyfill.js +7 -0
  23. package/build/src/telemetry/ClearcutLogger.js +102 -0
  24. package/build/src/telemetry/WatchdogClient.js +60 -0
  25. package/build/src/telemetry/flagUtils.js +45 -0
  26. package/build/src/telemetry/metricUtils.js +14 -0
  27. package/build/src/telemetry/persistence.js +53 -0
  28. package/build/src/telemetry/types.js +33 -0
  29. package/build/src/telemetry/watchdog/ClearcutSender.js +201 -0
  30. package/build/src/telemetry/watchdog/main.js +127 -0
  31. package/build/src/third_party/THIRD_PARTY_NOTICES +2011 -0
  32. package/build/src/third_party/bundled-packages.json +8 -0
  33. package/build/src/third_party/devtools-formatter-worker.js +15449 -0
  34. package/build/src/third_party/index.js +172870 -0
  35. package/build/src/third_party/issue-descriptions/CoepCoopSandboxedIframeCannotNavigateToCoopPage.md +4 -0
  36. package/build/src/third_party/issue-descriptions/CoepCorpNotSameOrigin.md +8 -0
  37. package/build/src/third_party/issue-descriptions/CoepCorpNotSameOriginAfterDefaultedToSameOriginByCoep.md +18 -0
  38. package/build/src/third_party/issue-descriptions/CoepCorpNotSameSite.md +7 -0
  39. package/build/src/third_party/issue-descriptions/CoepFrameResourceNeedsCoepHeader.md +10 -0
  40. package/build/src/third_party/issue-descriptions/CompatibilityModeQuirks.md +5 -0
  41. package/build/src/third_party/issue-descriptions/CookieAttributeValueExceedsMaxSize.md +5 -0
  42. package/build/src/third_party/issue-descriptions/LowTextContrast.md +5 -0
  43. package/build/src/third_party/issue-descriptions/SameSiteExcludeContextDowngradeRead.md +8 -0
  44. package/build/src/third_party/issue-descriptions/SameSiteExcludeContextDowngradeSet.md +8 -0
  45. package/build/src/third_party/issue-descriptions/SameSiteExcludeNavigationContextDowngrade.md +8 -0
  46. package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureErrorRead.md +8 -0
  47. package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureErrorSet.md +8 -0
  48. package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureWarnRead.md +8 -0
  49. package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureWarnSet.md +8 -0
  50. package/build/src/third_party/issue-descriptions/SameSiteUnspecifiedLaxAllowUnsafeRead.md +9 -0
  51. package/build/src/third_party/issue-descriptions/SameSiteUnspecifiedLaxAllowUnsafeSet.md +9 -0
  52. package/build/src/third_party/issue-descriptions/SameSiteWarnCrossDowngradeRead.md +8 -0
  53. package/build/src/third_party/issue-descriptions/SameSiteWarnCrossDowngradeSet.md +8 -0
  54. package/build/src/third_party/issue-descriptions/SameSiteWarnStrictLaxDowngradeStrict.md +8 -0
  55. package/build/src/third_party/issue-descriptions/arInsecureContext.md +7 -0
  56. package/build/src/third_party/issue-descriptions/arInvalidInfoHeader.md +5 -0
  57. package/build/src/third_party/issue-descriptions/arInvalidRegisterOsSourceHeader.md +5 -0
  58. package/build/src/third_party/issue-descriptions/arInvalidRegisterOsTriggerHeader.md +5 -0
  59. package/build/src/third_party/issue-descriptions/arInvalidRegisterSourceHeader.md +5 -0
  60. package/build/src/third_party/issue-descriptions/arInvalidRegisterTriggerHeader.md +5 -0
  61. package/build/src/third_party/issue-descriptions/arNavigationRegistrationUniqueScopeAlreadySet.md +5 -0
  62. package/build/src/third_party/issue-descriptions/arNavigationRegistrationWithoutTransientUserActivation.md +6 -0
  63. package/build/src/third_party/issue-descriptions/arNoRegisterOsSourceHeader.md +5 -0
  64. package/build/src/third_party/issue-descriptions/arNoRegisterOsTriggerHeader.md +5 -0
  65. package/build/src/third_party/issue-descriptions/arNoRegisterSourceHeader.md +5 -0
  66. package/build/src/third_party/issue-descriptions/arNoRegisterTriggerHeader.md +5 -0
  67. package/build/src/third_party/issue-descriptions/arNoWebOrOsSupport.md +4 -0
  68. package/build/src/third_party/issue-descriptions/arOsSourceIgnored.md +18 -0
  69. package/build/src/third_party/issue-descriptions/arOsTriggerIgnored.md +19 -0
  70. package/build/src/third_party/issue-descriptions/arPermissionPolicyDisabled.md +8 -0
  71. package/build/src/third_party/issue-descriptions/arSourceAndTriggerHeaders.md +9 -0
  72. package/build/src/third_party/issue-descriptions/arSourceIgnored.md +13 -0
  73. package/build/src/third_party/issue-descriptions/arTriggerIgnored.md +12 -0
  74. package/build/src/third_party/issue-descriptions/arUntrustworthyReportingOrigin.md +10 -0
  75. package/build/src/third_party/issue-descriptions/arWebAndOsHeaders.md +11 -0
  76. package/build/src/third_party/issue-descriptions/bounceTrackingMitigations.md +3 -0
  77. package/build/src/third_party/issue-descriptions/clientHintMetaTagAllowListInvalidOrigin.md +4 -0
  78. package/build/src/third_party/issue-descriptions/clientHintMetaTagModifiedHTML.md +4 -0
  79. package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidAllowlistItemType.md +12 -0
  80. package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidHeader.md +12 -0
  81. package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidUrlPattern.md +8 -0
  82. package/build/src/third_party/issue-descriptions/connectionAllowlistItemNotInnerList.md +12 -0
  83. package/build/src/third_party/issue-descriptions/connectionAllowlistMoreThanOneList.md +7 -0
  84. package/build/src/third_party/issue-descriptions/connectionAllowlistReportingEndpointNotToken.md +10 -0
  85. package/build/src/third_party/issue-descriptions/cookieCrossSiteRedirectDowngrade.md +12 -0
  86. package/build/src/third_party/issue-descriptions/cookieExcludeBlockedWithinRelatedWebsiteSet.md +4 -0
  87. package/build/src/third_party/issue-descriptions/cookieExcludeDomainNonAscii.md +11 -0
  88. package/build/src/third_party/issue-descriptions/cookieExcludePortMismatch.md +8 -0
  89. package/build/src/third_party/issue-descriptions/cookieExcludeSchemeMismatch.md +7 -0
  90. package/build/src/third_party/issue-descriptions/cookieExcludeThirdPartyPhaseoutRead.md +6 -0
  91. package/build/src/third_party/issue-descriptions/cookieExcludeThirdPartyPhaseoutSet.md +6 -0
  92. package/build/src/third_party/issue-descriptions/cookieWarnDomainNonAscii.md +11 -0
  93. package/build/src/third_party/issue-descriptions/cookieWarnMetadataGrantRead.md +4 -0
  94. package/build/src/third_party/issue-descriptions/cookieWarnMetadataGrantSet.md +4 -0
  95. package/build/src/third_party/issue-descriptions/cookieWarnThirdPartyPhaseoutRead.md +6 -0
  96. package/build/src/third_party/issue-descriptions/cookieWarnThirdPartyPhaseoutSet.md +6 -0
  97. package/build/src/third_party/issue-descriptions/corsAllowCredentialsRequired.md +6 -0
  98. package/build/src/third_party/issue-descriptions/corsDisabledScheme.md +7 -0
  99. package/build/src/third_party/issue-descriptions/corsDisallowedByMode.md +7 -0
  100. package/build/src/third_party/issue-descriptions/corsHeaderDisallowedByPreflightResponse.md +5 -0
  101. package/build/src/third_party/issue-descriptions/corsInvalidHeaderValues.md +7 -0
  102. package/build/src/third_party/issue-descriptions/corsLocalNetworkAccessPermissionDenied.md +19 -0
  103. package/build/src/third_party/issue-descriptions/corsMethodDisallowedByPreflightResponse.md +5 -0
  104. package/build/src/third_party/issue-descriptions/corsNoCorsRedirectModeNotFollow.md +5 -0
  105. package/build/src/third_party/issue-descriptions/corsOriginMismatch.md +6 -0
  106. package/build/src/third_party/issue-descriptions/corsPreflightResponseInvalid.md +5 -0
  107. package/build/src/third_party/issue-descriptions/corsRedirectContainsCredentials.md +5 -0
  108. package/build/src/third_party/issue-descriptions/corsWildcardOriginNotAllowed.md +8 -0
  109. package/build/src/third_party/issue-descriptions/cspEvalViolation.md +9 -0
  110. package/build/src/third_party/issue-descriptions/cspInlineViolation.md +10 -0
  111. package/build/src/third_party/issue-descriptions/cspTrustedTypesPolicyViolation.md +5 -0
  112. package/build/src/third_party/issue-descriptions/cspTrustedTypesSinkViolation.md +8 -0
  113. package/build/src/third_party/issue-descriptions/cspURLViolation.md +10 -0
  114. package/build/src/third_party/issue-descriptions/deprecation.md +3 -0
  115. package/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsHttpNotFound.md +1 -0
  116. package/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsInvalidResponse.md +1 -0
  117. package/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsNoResponse.md +1 -0
  118. package/build/src/third_party/issue-descriptions/federatedAuthRequestApprovalDeclined.md +1 -0
  119. package/build/src/third_party/issue-descriptions/federatedAuthRequestCanceled.md +1 -0
  120. package/build/src/third_party/issue-descriptions/federatedAuthRequestErrorFetchingSignin.md +1 -0
  121. package/build/src/third_party/issue-descriptions/federatedAuthRequestErrorIdToken.md +1 -0
  122. package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenHttpNotFound.md +1 -0
  123. package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenInvalidRequest.md +1 -0
  124. package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenInvalidResponse.md +1 -0
  125. package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenNoResponse.md +1 -0
  126. package/build/src/third_party/issue-descriptions/federatedAuthRequestInvalidSigninResponse.md +1 -0
  127. package/build/src/third_party/issue-descriptions/federatedAuthRequestManifestHttpNotFound.md +1 -0
  128. package/build/src/third_party/issue-descriptions/federatedAuthRequestManifestInvalidResponse.md +1 -0
  129. package/build/src/third_party/issue-descriptions/federatedAuthRequestManifestNoResponse.md +1 -0
  130. package/build/src/third_party/issue-descriptions/federatedAuthRequestTooManyRequests.md +1 -0
  131. package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestInvalidAccountsResponse.md +1 -0
  132. package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestInvalidConfigOrWellKnown.md +1 -0
  133. package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoAccountSharingPermission.md +1 -0
  134. package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoApiPermission.md +1 -0
  135. package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoReturningUserFromFetchedAccounts.md +1 -0
  136. package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotIframe.md +1 -0
  137. package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotPotentiallyTrustworthy.md +1 -0
  138. package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotSameOrigin.md +1 -0
  139. package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotSignedInWithIdp.md +1 -0
  140. package/build/src/third_party/issue-descriptions/fetchingPartitionedBlobURL.md +7 -0
  141. package/build/src/third_party/issue-descriptions/genericFormAriaLabelledByToNonExistingIdError.md +8 -0
  142. package/build/src/third_party/issue-descriptions/genericFormAutocompleteAttributeEmptyError.md +5 -0
  143. package/build/src/third_party/issue-descriptions/genericFormDuplicateIdForInputError.md +5 -0
  144. package/build/src/third_party/issue-descriptions/genericFormEmptyIdAndNameAttributesForInputError.md +5 -0
  145. package/build/src/third_party/issue-descriptions/genericFormInputAssignedAutocompleteValueToIdOrNameAttributeError.md +5 -0
  146. package/build/src/third_party/issue-descriptions/genericFormInputHasWrongButWellIntendedAutocompleteValueError.md +5 -0
  147. package/build/src/third_party/issue-descriptions/genericFormInputWithNoLabelError.md +5 -0
  148. package/build/src/third_party/issue-descriptions/genericFormLabelForMatchesNonExistingIdError.md +5 -0
  149. package/build/src/third_party/issue-descriptions/genericFormLabelForNameError.md +5 -0
  150. package/build/src/third_party/issue-descriptions/genericFormLabelHasNeitherForNorNestedInputError.md +5 -0
  151. package/build/src/third_party/issue-descriptions/genericNavigationEntryMarkedSkippable.md +7 -0
  152. package/build/src/third_party/issue-descriptions/genericResponseWasBlockedByORB.md +4 -0
  153. package/build/src/third_party/issue-descriptions/heavyAd.md +10 -0
  154. package/build/src/third_party/issue-descriptions/mixedContent.md +5 -0
  155. package/build/src/third_party/issue-descriptions/navigatingPartitionedBlobURL.md +5 -0
  156. package/build/src/third_party/issue-descriptions/permissionElementActivationDisabled.md +7 -0
  157. package/build/src/third_party/issue-descriptions/permissionElementActivationDisabledWithOccluder.md +9 -0
  158. package/build/src/third_party/issue-descriptions/permissionElementActivationDisabledWithOccluderParent.md +9 -0
  159. package/build/src/third_party/issue-descriptions/permissionElementCspFrameAncestorsMissing.md +5 -0
  160. package/build/src/third_party/issue-descriptions/permissionElementFencedFrameDisallowed.md +5 -0
  161. package/build/src/third_party/issue-descriptions/permissionElementFontSizeTooLarge.md +5 -0
  162. package/build/src/third_party/issue-descriptions/permissionElementFontSizeTooSmall.md +5 -0
  163. package/build/src/third_party/issue-descriptions/permissionElementGeolocationDeprecated.md +5 -0
  164. package/build/src/third_party/issue-descriptions/permissionElementInsetBoxShadowUnsupported.md +5 -0
  165. package/build/src/third_party/issue-descriptions/permissionElementInvalidDisplayStyle.md +5 -0
  166. package/build/src/third_party/issue-descriptions/permissionElementInvalidSizeValue.md +5 -0
  167. package/build/src/third_party/issue-descriptions/permissionElementInvalidType.md +5 -0
  168. package/build/src/third_party/issue-descriptions/permissionElementInvalidTypeActivation.md +5 -0
  169. package/build/src/third_party/issue-descriptions/permissionElementLowContrast.md +5 -0
  170. package/build/src/third_party/issue-descriptions/permissionElementNonOpaqueColor.md +5 -0
  171. package/build/src/third_party/issue-descriptions/permissionElementPaddingBottomUnsupported.md +6 -0
  172. package/build/src/third_party/issue-descriptions/permissionElementPaddingRightUnsupported.md +6 -0
  173. package/build/src/third_party/issue-descriptions/permissionElementPermissionsPolicyBlocked.md +5 -0
  174. package/build/src/third_party/issue-descriptions/permissionElementRegistrationFailed.md +5 -0
  175. package/build/src/third_party/issue-descriptions/permissionElementRequestInProgress.md +5 -0
  176. package/build/src/third_party/issue-descriptions/permissionElementSecurityChecksFailed.md +5 -0
  177. package/build/src/third_party/issue-descriptions/permissionElementTypeNotSupported.md +5 -0
  178. package/build/src/third_party/issue-descriptions/permissionElementUntrustedEvent.md +7 -0
  179. package/build/src/third_party/issue-descriptions/placeholderDescriptionForInvisibleIssues.md +3 -0
  180. package/build/src/third_party/issue-descriptions/propertyRuleInvalidNameIssue.md +3 -0
  181. package/build/src/third_party/issue-descriptions/propertyRuleIssue.md +7 -0
  182. package/build/src/third_party/issue-descriptions/selectElementAccessibilityDisallowedOptGroupChild.md +7 -0
  183. package/build/src/third_party/issue-descriptions/selectElementAccessibilityDisallowedSelectChild.md +7 -0
  184. package/build/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentAttributesSelectDescendant.md +3 -0
  185. package/build/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentLegendChild.md +3 -0
  186. package/build/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentOptionChild.md +3 -0
  187. package/build/src/third_party/issue-descriptions/selectElementAccessibilityNonPhrasingContentOptionChild.md +3 -0
  188. package/build/src/third_party/issue-descriptions/sharedArrayBuffer.md +7 -0
  189. package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorCrossOriginNoCorsRequest.md +1 -0
  190. package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorDictionaryLoadFailure.md +3 -0
  191. package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorMatchingDictionaryNotUsed.md +3 -0
  192. package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorUnexpectedContentDictionaryHeader.md +1 -0
  193. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorCossOriginNoCorsRequest.md +1 -0
  194. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorDisallowedBySettings.md +1 -0
  195. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorExpiredResponse.md +3 -0
  196. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorFeatureDisabled.md +3 -0
  197. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInsufficientResources.md +1 -0
  198. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInvalidMatchField.md +1 -0
  199. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInvalidStructuredHeader.md +1 -0
  200. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInvalidTTLField.md +1 -0
  201. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNavigationRequest.md +3 -0
  202. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNoMatchField.md +1 -0
  203. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonIntegerTTLField.md +1 -0
  204. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonListMatchDestField.md +1 -0
  205. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonSecureContext.md +3 -0
  206. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonStringIdField.md +1 -0
  207. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonStringInMatchDestList.md +1 -0
  208. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonStringMatchField.md +1 -0
  209. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonTokenTypeField.md +1 -0
  210. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorRequestAborted.md +1 -0
  211. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorShuttingDown.md +1 -0
  212. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorTooLongIdField.md +3 -0
  213. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorUnsupportedType.md +3 -0
  214. package/build/src/third_party/issue-descriptions/sriInvalidSignatureHeader.md +14 -0
  215. package/build/src/third_party/issue-descriptions/sriInvalidSignatureInputHeader.md +15 -0
  216. package/build/src/third_party/issue-descriptions/sriMissingSignatureHeader.md +8 -0
  217. package/build/src/third_party/issue-descriptions/sriMissingSignatureInputHeader.md +7 -0
  218. package/build/src/third_party/issue-descriptions/sriSignatureHeaderValueIsIncorrectLength.md +11 -0
  219. package/build/src/third_party/issue-descriptions/sriSignatureHeaderValueIsNotByteSequence.md +14 -0
  220. package/build/src/third_party/issue-descriptions/sriSignatureHeaderValueIsParameterized.md +15 -0
  221. package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidComponentName.md +8 -0
  222. package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidComponentType.md +13 -0
  223. package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidDerivedComponentParameter.md +4 -0
  224. package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidHeaderComponentParameter.md +5 -0
  225. package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidParameter.md +11 -0
  226. package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderKeyIdLength.md +12 -0
  227. package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderMissingLabel.md +6 -0
  228. package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderMissingRequiredParameters.md +8 -0
  229. package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderValueMissingComponents.md +11 -0
  230. package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderValueNotInnerList.md +11 -0
  231. package/build/src/third_party/issue-descriptions/sriValidationFailedIntegrityMismatch.md +10 -0
  232. package/build/src/third_party/issue-descriptions/sriValidationFailedInvalidLength.md +5 -0
  233. package/build/src/third_party/issue-descriptions/sriValidationFailedSignatureExpired.md +6 -0
  234. package/build/src/third_party/issue-descriptions/sriValidationFailedSignatureMismatch.md +11 -0
  235. package/build/src/third_party/issue-descriptions/stylesheetLateImport.md +4 -0
  236. package/build/src/third_party/issue-descriptions/stylesheetRequestFailed.md +3 -0
  237. package/build/src/third_party/issue-descriptions/summaryElementAccessibilityInteractiveContentSummaryDescendant.md +3 -0
  238. package/build/src/third_party/issue-descriptions/unencodedDigestIncorrectDigestLength.md +12 -0
  239. package/build/src/third_party/issue-descriptions/unencodedDigestIncorrectDigestType.md +17 -0
  240. package/build/src/third_party/issue-descriptions/unencodedDigestMalformedDictionary.md +14 -0
  241. package/build/src/third_party/issue-descriptions/unencodedDigestUnknownAlgorithm.md +15 -0
  242. package/build/src/tools/ToolDefinition.js +20 -0
  243. package/build/src/tools/categories.js +24 -0
  244. package/build/src/tools/console.js +85 -0
  245. package/build/src/tools/emulation.js +87 -0
  246. package/build/src/tools/extensions.js +79 -0
  247. package/build/src/tools/input.js +374 -0
  248. package/build/src/tools/memory.js +29 -0
  249. package/build/src/tools/network.js +120 -0
  250. package/build/src/tools/pages.js +329 -0
  251. package/build/src/tools/performance.js +188 -0
  252. package/build/src/tools/screencast.js +79 -0
  253. package/build/src/tools/screenshot.js +84 -0
  254. package/build/src/tools/script.js +71 -0
  255. package/build/src/tools/slim/tools.js +81 -0
  256. package/build/src/tools/snapshot.js +55 -0
  257. package/build/src/tools/tools.js +50 -0
  258. package/build/src/trace-processing/parse.js +84 -0
  259. package/build/src/utils/ExtensionRegistry.js +35 -0
  260. package/build/src/utils/keyboard.js +296 -0
  261. package/build/src/utils/pagination.js +49 -0
  262. package/build/src/utils/string.js +36 -0
  263. package/build/src/utils/types.js +6 -0
  264. package/build/src/version.js +9 -0
  265. package/package.json +7 -7
@@ -0,0 +1,703 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import fs from 'node:fs/promises';
7
+ import os from 'node:os';
8
+ import path from 'node:path';
9
+ import { extractUrlLikeFromDevToolsTitle, UniverseManager, urlsEqual, } from './DevtoolsUtils.js';
10
+ import { NetworkCollector, ConsoleCollector } from './PageCollector.js';
11
+ import { Locator } from './third_party/index.js';
12
+ import { PredefinedNetworkConditions } from './third_party/index.js';
13
+ import { listPages } from './tools/pages.js';
14
+ import { takeSnapshot } from './tools/snapshot.js';
15
+ import { CLOSE_PAGE_ERROR } from './tools/ToolDefinition.js';
16
+ import { ExtensionRegistry, } from './utils/ExtensionRegistry.js';
17
+ import { WaitForHelper } from './WaitForHelper.js';
18
+ const DEFAULT_TIMEOUT = 5_000;
19
+ const NAVIGATION_TIMEOUT = 10_000;
20
+ function getNetworkMultiplierFromString(condition) {
21
+ const puppeteerCondition = condition;
22
+ switch (puppeteerCondition) {
23
+ case 'Fast 4G':
24
+ return 1;
25
+ case 'Slow 4G':
26
+ return 2.5;
27
+ case 'Fast 3G':
28
+ return 5;
29
+ case 'Slow 3G':
30
+ return 10;
31
+ }
32
+ return 1;
33
+ }
34
+ function getExtensionFromMimeType(mimeType) {
35
+ switch (mimeType) {
36
+ case 'image/png':
37
+ return 'png';
38
+ case 'image/jpeg':
39
+ return 'jpeg';
40
+ case 'image/webp':
41
+ return 'webp';
42
+ }
43
+ throw new Error(`No mapping for Mime type ${mimeType}.`);
44
+ }
45
+ export class McpContext {
46
+ browser;
47
+ logger;
48
+ // Maps LLM-provided isolatedContext name → Puppeteer BrowserContext.
49
+ #isolatedContexts = new Map();
50
+ // Reverse lookup: Page → isolatedContext name (for snapshot labeling).
51
+ // WeakMap so closed pages are garbage-collected automatically.
52
+ #pageToIsolatedContextName = new WeakMap();
53
+ // Auto-generated name counter for when no name is provided.
54
+ #nextIsolatedContextId = 1;
55
+ #pages = [];
56
+ #pageToDevToolsPage = new Map();
57
+ #selectedPage;
58
+ #textSnapshot = null;
59
+ #networkCollector;
60
+ #consoleCollector;
61
+ #devtoolsUniverseManager;
62
+ #extensionRegistry = new ExtensionRegistry();
63
+ #isRunningTrace = false;
64
+ #screenRecorderData = null;
65
+ #emulationSettingsMap = new WeakMap();
66
+ #dialog;
67
+ #pageIdMap = new WeakMap();
68
+ #nextPageId = 1;
69
+ #nextSnapshotId = 1;
70
+ #traceResults = [];
71
+ #locatorClass;
72
+ #options;
73
+ #uniqueBackendNodeIdToMcpId = new Map();
74
+ constructor(browser, logger, options, locatorClass) {
75
+ this.browser = browser;
76
+ this.logger = logger;
77
+ this.#locatorClass = locatorClass;
78
+ this.#options = options;
79
+ this.#networkCollector = new NetworkCollector(this.browser);
80
+ this.#consoleCollector = new ConsoleCollector(this.browser, collect => {
81
+ return {
82
+ console: event => {
83
+ collect(event);
84
+ },
85
+ uncaughtError: event => {
86
+ collect(event);
87
+ },
88
+ issue: event => {
89
+ collect(event);
90
+ },
91
+ };
92
+ });
93
+ this.#devtoolsUniverseManager = new UniverseManager(this.browser);
94
+ }
95
+ async #init() {
96
+ const pages = await this.createPagesSnapshot();
97
+ await this.#networkCollector.init(pages);
98
+ await this.#consoleCollector.init(pages);
99
+ await this.#devtoolsUniverseManager.init(pages);
100
+ }
101
+ dispose() {
102
+ this.#networkCollector.dispose();
103
+ this.#consoleCollector.dispose();
104
+ this.#devtoolsUniverseManager.dispose();
105
+ // Isolated contexts are intentionally not closed here.
106
+ // Either the entire browser will be closed or we disconnect
107
+ // without destroying browser state.
108
+ this.#isolatedContexts.clear();
109
+ }
110
+ static async from(browser, logger, opts,
111
+ /* Let tests use unbundled Locator class to avoid overly strict checks within puppeteer that fail when mixing bundled and unbundled class instances */
112
+ locatorClass = Locator) {
113
+ const context = new McpContext(browser, logger, opts, locatorClass);
114
+ await context.#init();
115
+ return context;
116
+ }
117
+ resolveCdpRequestId(cdpRequestId) {
118
+ const selectedPage = this.getSelectedPage();
119
+ if (!cdpRequestId) {
120
+ this.logger('no network request');
121
+ return;
122
+ }
123
+ const request = this.#networkCollector.find(selectedPage, request => {
124
+ // @ts-expect-error id is internal.
125
+ return request.id === cdpRequestId;
126
+ });
127
+ if (!request) {
128
+ this.logger('no network request for ' + cdpRequestId);
129
+ return;
130
+ }
131
+ return this.#networkCollector.getIdForResource(request);
132
+ }
133
+ resolveCdpElementId(cdpBackendNodeId) {
134
+ if (!cdpBackendNodeId) {
135
+ this.logger('no cdpBackendNodeId');
136
+ return;
137
+ }
138
+ if (this.#textSnapshot === null) {
139
+ this.logger('no text snapshot');
140
+ return;
141
+ }
142
+ // TODO: index by backendNodeId instead.
143
+ const queue = [this.#textSnapshot.root];
144
+ while (queue.length) {
145
+ const current = queue.pop();
146
+ if (current.backendNodeId === cdpBackendNodeId) {
147
+ return current.id;
148
+ }
149
+ for (const child of current.children) {
150
+ queue.push(child);
151
+ }
152
+ }
153
+ return;
154
+ }
155
+ getNetworkRequests(includePreservedRequests) {
156
+ const page = this.getSelectedPage();
157
+ return this.#networkCollector.getData(page, includePreservedRequests);
158
+ }
159
+ getConsoleData(includePreservedMessages) {
160
+ const page = this.getSelectedPage();
161
+ return this.#consoleCollector.getData(page, includePreservedMessages);
162
+ }
163
+ getDevToolsUniverse() {
164
+ return this.#devtoolsUniverseManager.get(this.getSelectedPage());
165
+ }
166
+ getConsoleMessageStableId(message) {
167
+ return this.#consoleCollector.getIdForResource(message);
168
+ }
169
+ getConsoleMessageById(id) {
170
+ return this.#consoleCollector.getById(this.getSelectedPage(), id);
171
+ }
172
+ async newPage(background, isolatedContextName) {
173
+ let page;
174
+ if (isolatedContextName !== undefined) {
175
+ let ctx = this.#isolatedContexts.get(isolatedContextName);
176
+ if (!ctx) {
177
+ ctx = await this.browser.createBrowserContext();
178
+ this.#isolatedContexts.set(isolatedContextName, ctx);
179
+ }
180
+ page = await ctx.newPage();
181
+ this.#pageToIsolatedContextName.set(page, isolatedContextName);
182
+ }
183
+ else {
184
+ page = await this.browser.newPage({ background });
185
+ }
186
+ await this.createPagesSnapshot();
187
+ this.selectPage(page);
188
+ this.#networkCollector.addPage(page);
189
+ this.#consoleCollector.addPage(page);
190
+ return page;
191
+ }
192
+ async closePage(pageId) {
193
+ if (this.#pages.length === 1) {
194
+ throw new Error(CLOSE_PAGE_ERROR);
195
+ }
196
+ const page = this.getPageById(pageId);
197
+ await page.close({ runBeforeUnload: false });
198
+ this.#pageToIsolatedContextName.delete(page);
199
+ }
200
+ getNetworkRequestById(reqid) {
201
+ return this.#networkCollector.getById(this.getSelectedPage(), reqid);
202
+ }
203
+ async emulate(options) {
204
+ const page = this.getSelectedPage();
205
+ const currentSettings = this.#emulationSettingsMap.get(page) ?? {};
206
+ const newSettings = { ...currentSettings };
207
+ let timeoutsNeedUpdate = false;
208
+ if (options.networkConditions !== undefined) {
209
+ timeoutsNeedUpdate = true;
210
+ if (options.networkConditions === null ||
211
+ options.networkConditions === 'No emulation') {
212
+ await page.emulateNetworkConditions(null);
213
+ delete newSettings.networkConditions;
214
+ }
215
+ else if (options.networkConditions === 'Offline') {
216
+ await page.emulateNetworkConditions({
217
+ offline: true,
218
+ download: 0,
219
+ upload: 0,
220
+ latency: 0,
221
+ });
222
+ newSettings.networkConditions = 'Offline';
223
+ }
224
+ else if (options.networkConditions in PredefinedNetworkConditions) {
225
+ const networkCondition = PredefinedNetworkConditions[options.networkConditions];
226
+ await page.emulateNetworkConditions(networkCondition);
227
+ newSettings.networkConditions = options.networkConditions;
228
+ }
229
+ }
230
+ if (options.cpuThrottlingRate !== undefined) {
231
+ timeoutsNeedUpdate = true;
232
+ if (options.cpuThrottlingRate === null) {
233
+ await page.emulateCPUThrottling(1);
234
+ delete newSettings.cpuThrottlingRate;
235
+ }
236
+ else {
237
+ await page.emulateCPUThrottling(options.cpuThrottlingRate);
238
+ newSettings.cpuThrottlingRate = options.cpuThrottlingRate;
239
+ }
240
+ }
241
+ if (options.geolocation !== undefined) {
242
+ if (options.geolocation === null) {
243
+ await page.setGeolocation({ latitude: 0, longitude: 0 });
244
+ delete newSettings.geolocation;
245
+ }
246
+ else {
247
+ await page.setGeolocation(options.geolocation);
248
+ newSettings.geolocation = options.geolocation;
249
+ }
250
+ }
251
+ if (options.userAgent !== undefined) {
252
+ if (options.userAgent === null) {
253
+ await page.setUserAgent({ userAgent: undefined });
254
+ delete newSettings.userAgent;
255
+ }
256
+ else {
257
+ await page.setUserAgent({ userAgent: options.userAgent });
258
+ newSettings.userAgent = options.userAgent;
259
+ }
260
+ }
261
+ if (options.colorScheme !== undefined) {
262
+ if (options.colorScheme === null || options.colorScheme === 'auto') {
263
+ await page.emulateMediaFeatures([
264
+ { name: 'prefers-color-scheme', value: '' },
265
+ ]);
266
+ delete newSettings.colorScheme;
267
+ }
268
+ else {
269
+ await page.emulateMediaFeatures([
270
+ { name: 'prefers-color-scheme', value: options.colorScheme },
271
+ ]);
272
+ newSettings.colorScheme = options.colorScheme;
273
+ }
274
+ }
275
+ if (options.viewport !== undefined) {
276
+ if (options.viewport === null) {
277
+ await page.setViewport(null);
278
+ delete newSettings.viewport;
279
+ }
280
+ else {
281
+ const defaults = {
282
+ deviceScaleFactor: 1,
283
+ isMobile: false,
284
+ hasTouch: false,
285
+ isLandscape: false,
286
+ };
287
+ const viewport = { ...defaults, ...options.viewport };
288
+ await page.setViewport(viewport);
289
+ newSettings.viewport = viewport;
290
+ }
291
+ }
292
+ if (Object.keys(newSettings).length) {
293
+ this.#emulationSettingsMap.set(page, newSettings);
294
+ }
295
+ else {
296
+ this.#emulationSettingsMap.delete(page);
297
+ }
298
+ if (timeoutsNeedUpdate) {
299
+ this.#updateSelectedPageTimeouts();
300
+ }
301
+ }
302
+ getNetworkConditions() {
303
+ const page = this.getSelectedPage();
304
+ return this.#emulationSettingsMap.get(page)?.networkConditions ?? null;
305
+ }
306
+ getCpuThrottlingRate() {
307
+ const page = this.getSelectedPage();
308
+ return this.#emulationSettingsMap.get(page)?.cpuThrottlingRate ?? 1;
309
+ }
310
+ getGeolocation() {
311
+ const page = this.getSelectedPage();
312
+ return this.#emulationSettingsMap.get(page)?.geolocation ?? null;
313
+ }
314
+ getViewport() {
315
+ const page = this.getSelectedPage();
316
+ return this.#emulationSettingsMap.get(page)?.viewport ?? null;
317
+ }
318
+ getUserAgent() {
319
+ const page = this.getSelectedPage();
320
+ return this.#emulationSettingsMap.get(page)?.userAgent ?? null;
321
+ }
322
+ getColorScheme() {
323
+ const page = this.getSelectedPage();
324
+ return this.#emulationSettingsMap.get(page)?.colorScheme ?? null;
325
+ }
326
+ setIsRunningPerformanceTrace(x) {
327
+ this.#isRunningTrace = x;
328
+ }
329
+ isRunningPerformanceTrace() {
330
+ return this.#isRunningTrace;
331
+ }
332
+ getScreenRecorder() {
333
+ return this.#screenRecorderData;
334
+ }
335
+ setScreenRecorder(data) {
336
+ this.#screenRecorderData = data;
337
+ }
338
+ isCruxEnabled() {
339
+ return this.#options.performanceCrux;
340
+ }
341
+ getDialog() {
342
+ return this.#dialog;
343
+ }
344
+ clearDialog() {
345
+ this.#dialog = undefined;
346
+ }
347
+ getSelectedPage() {
348
+ const page = this.#selectedPage;
349
+ if (!page) {
350
+ throw new Error('No page selected');
351
+ }
352
+ if (page.isClosed()) {
353
+ throw new Error(`The selected page has been closed. Call ${listPages.name} to see open pages.`);
354
+ }
355
+ return page;
356
+ }
357
+ getPageById(pageId) {
358
+ const page = this.#pages.find(p => this.#pageIdMap.get(p) === pageId);
359
+ if (!page) {
360
+ throw new Error('No page found');
361
+ }
362
+ return page;
363
+ }
364
+ getPageId(page) {
365
+ return this.#pageIdMap.get(page);
366
+ }
367
+ #dialogHandler = (dialog) => {
368
+ this.#dialog = dialog;
369
+ };
370
+ isPageSelected(page) {
371
+ return this.#selectedPage === page;
372
+ }
373
+ selectPage(newPage) {
374
+ const oldPage = this.#selectedPage;
375
+ if (oldPage) {
376
+ oldPage.off('dialog', this.#dialogHandler);
377
+ void oldPage.emulateFocusedPage(false).catch(error => {
378
+ this.logger('Error turning off focused page emulation', error);
379
+ });
380
+ }
381
+ this.#selectedPage = newPage;
382
+ newPage.on('dialog', this.#dialogHandler);
383
+ this.#updateSelectedPageTimeouts();
384
+ void newPage.emulateFocusedPage(true).catch(error => {
385
+ this.logger('Error turning on focused page emulation', error);
386
+ });
387
+ }
388
+ #updateSelectedPageTimeouts() {
389
+ const page = this.getSelectedPage();
390
+ // For waiters 5sec timeout should be sufficient.
391
+ // Increased in case we throttle the CPU
392
+ const cpuMultiplier = this.getCpuThrottlingRate();
393
+ page.setDefaultTimeout(DEFAULT_TIMEOUT * cpuMultiplier);
394
+ // 10sec should be enough for the load event to be emitted during
395
+ // navigations.
396
+ // Increased in case we throttle the network requests
397
+ const networkMultiplier = getNetworkMultiplierFromString(this.getNetworkConditions());
398
+ page.setDefaultNavigationTimeout(NAVIGATION_TIMEOUT * networkMultiplier);
399
+ }
400
+ getNavigationTimeout() {
401
+ const page = this.getSelectedPage();
402
+ return page.getDefaultNavigationTimeout();
403
+ }
404
+ getAXNodeByUid(uid) {
405
+ return this.#textSnapshot?.idToNode.get(uid);
406
+ }
407
+ async getElementByUid(uid) {
408
+ if (!this.#textSnapshot?.idToNode.size) {
409
+ throw new Error(`No snapshot found. Use ${takeSnapshot.name} to capture one.`);
410
+ }
411
+ const node = this.#textSnapshot?.idToNode.get(uid);
412
+ if (!node) {
413
+ throw new Error('No such element found in the snapshot.');
414
+ }
415
+ const message = `Element with uid ${uid} no longer exists on the page.`;
416
+ try {
417
+ const handle = await node.elementHandle();
418
+ if (!handle) {
419
+ throw new Error(message);
420
+ }
421
+ return handle;
422
+ }
423
+ catch (error) {
424
+ throw new Error(message, {
425
+ cause: error,
426
+ });
427
+ }
428
+ }
429
+ async createPagesSnapshot() {
430
+ const allPages = await this.#getAllPages();
431
+ for (const page of allPages) {
432
+ if (!this.#pageIdMap.has(page)) {
433
+ this.#pageIdMap.set(page, this.#nextPageId++);
434
+ }
435
+ }
436
+ this.#pages = allPages.filter(page => {
437
+ return (this.#options.experimentalDevToolsDebugging ||
438
+ !page.url().startsWith('devtools://'));
439
+ });
440
+ if ((!this.#selectedPage || this.#pages.indexOf(this.#selectedPage) === -1) &&
441
+ this.#pages[0]) {
442
+ this.selectPage(this.#pages[0]);
443
+ }
444
+ await this.detectOpenDevToolsWindows();
445
+ return this.#pages;
446
+ }
447
+ async #getAllPages() {
448
+ const defaultCtx = this.browser.defaultBrowserContext();
449
+ const allPages = await this.browser.pages(this.#options.experimentalIncludeAllPages);
450
+ // Build a reverse lookup from BrowserContext instance → name.
451
+ const contextToName = new Map();
452
+ for (const [name, ctx] of this.#isolatedContexts) {
453
+ contextToName.set(ctx, name);
454
+ }
455
+ // Auto-discover BrowserContexts not in our mapping (e.g., externally
456
+ // created incognito contexts) and assign generated names.
457
+ const knownContexts = new Set(this.#isolatedContexts.values());
458
+ for (const ctx of this.browser.browserContexts()) {
459
+ if (ctx !== defaultCtx && !ctx.closed && !knownContexts.has(ctx)) {
460
+ const name = `isolated-context-${this.#nextIsolatedContextId++}`;
461
+ this.#isolatedContexts.set(name, ctx);
462
+ contextToName.set(ctx, name);
463
+ }
464
+ }
465
+ // Use page.browserContext() to determine each page's context membership.
466
+ for (const page of allPages) {
467
+ const ctx = page.browserContext();
468
+ const name = contextToName.get(ctx);
469
+ if (name) {
470
+ this.#pageToIsolatedContextName.set(page, name);
471
+ }
472
+ }
473
+ return allPages;
474
+ }
475
+ async detectOpenDevToolsWindows() {
476
+ this.logger('Detecting open DevTools windows');
477
+ const pages = await this.#getAllPages();
478
+ this.#pageToDevToolsPage = new Map();
479
+ for (const devToolsPage of pages) {
480
+ if (devToolsPage.url().startsWith('devtools://')) {
481
+ try {
482
+ this.logger('Calling getTargetInfo for ' + devToolsPage.url());
483
+ const data = await devToolsPage
484
+ // @ts-expect-error no types for _client().
485
+ ._client()
486
+ .send('Target.getTargetInfo');
487
+ const devtoolsPageTitle = data.targetInfo.title;
488
+ const urlLike = extractUrlLikeFromDevToolsTitle(devtoolsPageTitle);
489
+ if (!urlLike) {
490
+ continue;
491
+ }
492
+ // TODO: lookup without a loop.
493
+ for (const page of this.#pages) {
494
+ if (urlsEqual(page.url(), urlLike)) {
495
+ this.#pageToDevToolsPage.set(page, devToolsPage);
496
+ }
497
+ }
498
+ }
499
+ catch (error) {
500
+ this.logger('Issue occurred while trying to find DevTools', error);
501
+ }
502
+ }
503
+ }
504
+ }
505
+ getPages() {
506
+ return this.#pages;
507
+ }
508
+ getIsolatedContextName(page) {
509
+ return this.#pageToIsolatedContextName.get(page);
510
+ }
511
+ getDevToolsPage(page) {
512
+ return this.#pageToDevToolsPage.get(page);
513
+ }
514
+ async getDevToolsData() {
515
+ try {
516
+ this.logger('Getting DevTools UI data');
517
+ const selectedPage = this.getSelectedPage();
518
+ const devtoolsPage = this.getDevToolsPage(selectedPage);
519
+ if (!devtoolsPage) {
520
+ this.logger('No DevTools page detected');
521
+ return {};
522
+ }
523
+ const { cdpRequestId, cdpBackendNodeId } = await devtoolsPage.evaluate(async () => {
524
+ // @ts-expect-error no types
525
+ const UI = await import('/bundled/ui/legacy/legacy.js');
526
+ // @ts-expect-error no types
527
+ const SDK = await import('/bundled/core/sdk/sdk.js');
528
+ const request = UI.Context.Context.instance().flavor(SDK.NetworkRequest.NetworkRequest);
529
+ const node = UI.Context.Context.instance().flavor(SDK.DOMModel.DOMNode);
530
+ return {
531
+ cdpRequestId: request?.requestId(),
532
+ cdpBackendNodeId: node?.backendNodeId(),
533
+ };
534
+ });
535
+ return { cdpBackendNodeId, cdpRequestId };
536
+ }
537
+ catch (err) {
538
+ this.logger('error getting devtools data', err);
539
+ }
540
+ return {};
541
+ }
542
+ /**
543
+ * Creates a text snapshot of a page.
544
+ */
545
+ async createTextSnapshot(verbose = false, devtoolsData = undefined) {
546
+ const page = this.getSelectedPage();
547
+ const rootNode = await page.accessibility.snapshot({
548
+ includeIframes: true,
549
+ interestingOnly: !verbose,
550
+ });
551
+ if (!rootNode) {
552
+ return;
553
+ }
554
+ const snapshotId = this.#nextSnapshotId++;
555
+ // Iterate through the whole accessibility node tree and assign node ids that
556
+ // will be used for the tree serialization and mapping ids back to nodes.
557
+ let idCounter = 0;
558
+ const idToNode = new Map();
559
+ const seenUniqueIds = new Set();
560
+ const assignIds = (node) => {
561
+ let id = '';
562
+ // @ts-expect-error untyped loaderId & backendNodeId.
563
+ const uniqueBackendId = `${node.loaderId}_${node.backendNodeId}`;
564
+ if (this.#uniqueBackendNodeIdToMcpId.has(uniqueBackendId)) {
565
+ // Re-use MCP exposed ID if the uniqueId is the same.
566
+ id = this.#uniqueBackendNodeIdToMcpId.get(uniqueBackendId);
567
+ }
568
+ else {
569
+ // Only generate a new ID if we have not seen the node before.
570
+ id = `${snapshotId}_${idCounter++}`;
571
+ this.#uniqueBackendNodeIdToMcpId.set(uniqueBackendId, id);
572
+ }
573
+ seenUniqueIds.add(uniqueBackendId);
574
+ const nodeWithId = {
575
+ ...node,
576
+ id,
577
+ children: node.children
578
+ ? node.children.map(child => assignIds(child))
579
+ : [],
580
+ };
581
+ // The AXNode for an option doesn't contain its `value`.
582
+ // Therefore, set text content of the option as value.
583
+ if (node.role === 'option') {
584
+ const optionText = node.name;
585
+ if (optionText) {
586
+ nodeWithId.value = optionText.toString();
587
+ }
588
+ }
589
+ idToNode.set(nodeWithId.id, nodeWithId);
590
+ return nodeWithId;
591
+ };
592
+ const rootNodeWithId = assignIds(rootNode);
593
+ this.#textSnapshot = {
594
+ root: rootNodeWithId,
595
+ snapshotId: String(snapshotId),
596
+ idToNode,
597
+ hasSelectedElement: false,
598
+ verbose,
599
+ };
600
+ const data = devtoolsData ?? (await this.getDevToolsData());
601
+ if (data?.cdpBackendNodeId) {
602
+ this.#textSnapshot.hasSelectedElement = true;
603
+ this.#textSnapshot.selectedElementUid = this.resolveCdpElementId(data?.cdpBackendNodeId);
604
+ }
605
+ // Clean up unique IDs that we did not see anymore.
606
+ for (const key of this.#uniqueBackendNodeIdToMcpId.keys()) {
607
+ if (!seenUniqueIds.has(key)) {
608
+ this.#uniqueBackendNodeIdToMcpId.delete(key);
609
+ }
610
+ }
611
+ }
612
+ getTextSnapshot() {
613
+ return this.#textSnapshot;
614
+ }
615
+ async saveTemporaryFile(data, mimeType) {
616
+ try {
617
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'chrome-devtools-mcp-'));
618
+ const filename = path.join(dir, `screenshot.${getExtensionFromMimeType(mimeType)}`);
619
+ await fs.writeFile(filename, data);
620
+ return { filename };
621
+ }
622
+ catch (err) {
623
+ this.logger(err);
624
+ throw new Error('Could not save a screenshot to a file', { cause: err });
625
+ }
626
+ }
627
+ async saveFile(data, filename) {
628
+ try {
629
+ const filePath = path.resolve(filename);
630
+ await fs.writeFile(filePath, data);
631
+ return { filename };
632
+ }
633
+ catch (err) {
634
+ this.logger(err);
635
+ throw new Error('Could not save a screenshot to a file', { cause: err });
636
+ }
637
+ }
638
+ storeTraceRecording(result) {
639
+ // Clear the trace results because we only consume the latest trace currently.
640
+ this.#traceResults = [];
641
+ this.#traceResults.push(result);
642
+ }
643
+ recordedTraces() {
644
+ return this.#traceResults;
645
+ }
646
+ getWaitForHelper(page, cpuMultiplier, networkMultiplier) {
647
+ return new WaitForHelper(page, cpuMultiplier, networkMultiplier);
648
+ }
649
+ waitForEventsAfterAction(action, options) {
650
+ const page = this.getSelectedPage();
651
+ const cpuMultiplier = this.getCpuThrottlingRate();
652
+ const networkMultiplier = getNetworkMultiplierFromString(this.getNetworkConditions());
653
+ const waitForHelper = this.getWaitForHelper(page, cpuMultiplier, networkMultiplier);
654
+ return waitForHelper.waitForEventsAfterAction(action, options);
655
+ }
656
+ getNetworkRequestStableId(request) {
657
+ return this.#networkCollector.getIdForResource(request);
658
+ }
659
+ waitForTextOnPage(text, timeout) {
660
+ const page = this.getSelectedPage();
661
+ const frames = page.frames();
662
+ let locator = this.#locatorClass.race(frames.flatMap(frame => text.flatMap(value => [
663
+ frame.locator(`aria/${value}`),
664
+ frame.locator(`text/${value}`),
665
+ ])));
666
+ if (timeout) {
667
+ locator = locator.setTimeout(timeout);
668
+ }
669
+ return locator.wait();
670
+ }
671
+ /**
672
+ * We need to ignore favicon request as they make our test flaky
673
+ */
674
+ async setUpNetworkCollectorForTesting() {
675
+ this.#networkCollector = new NetworkCollector(this.browser, collect => {
676
+ return {
677
+ request: req => {
678
+ if (req.url().includes('favicon.ico')) {
679
+ return;
680
+ }
681
+ collect(req);
682
+ },
683
+ };
684
+ });
685
+ const pages = await this.#getAllPages();
686
+ await this.#networkCollector.init(pages);
687
+ }
688
+ async installExtension(extensionPath) {
689
+ const id = await this.browser.installExtension(extensionPath);
690
+ await this.#extensionRegistry.registerExtension(id, extensionPath);
691
+ return id;
692
+ }
693
+ async uninstallExtension(id) {
694
+ await this.browser.uninstallExtension(id);
695
+ this.#extensionRegistry.remove(id);
696
+ }
697
+ listExtensions() {
698
+ return this.#extensionRegistry.list();
699
+ }
700
+ getExtension(id) {
701
+ return this.#extensionRegistry.getById(id);
702
+ }
703
+ }