UNPKG

playwright-core

Version:

A high-level API to automate web browsers

562 lines (561 loc) • 22.1 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var snapshotterInjected_exports = {}; __export(snapshotterInjected_exports, { frameSnapshotStreamer: () => frameSnapshotStreamer }); module.exports = __toCommonJS(snapshotterInjected_exports); function frameSnapshotStreamer(snapshotStreamer, removeNoScript) { if (window[snapshotStreamer]) return; const kShadowAttribute = "__playwright_shadow_root_"; const kValueAttribute = "__playwright_value_"; const kCheckedAttribute = "__playwright_checked_"; const kSelectedAttribute = "__playwright_selected_"; const kScrollTopAttribute = "__playwright_scroll_top_"; const kScrollLeftAttribute = "__playwright_scroll_left_"; const kStyleSheetAttribute = "__playwright_style_sheet_"; const kTargetAttribute = "__playwright_target__"; const kCustomElementsAttribute = "__playwright_custom_elements__"; const kCurrentSrcAttribute = "__playwright_current_src__"; const kBoundingRectAttribute = "__playwright_bounding_rect__"; const kPopoverOpenAttribute = "__playwright_popover_open_"; const kDialogOpenAttribute = "__playwright_dialog_open_"; const kSnapshotFrameId = Symbol("__playwright_snapshot_frameid_"); const kCachedData = Symbol("__playwright_snapshot_cache_"); const kEndOfList = Symbol("__playwright_end_of_list_"); function resetCachedData(obj) { delete obj[kCachedData]; } function ensureCachedData(obj) { if (!obj[kCachedData]) obj[kCachedData] = {}; return obj[kCachedData]; } function removeHash(url) { try { const u = new URL(url); u.hash = ""; return u.toString(); } catch (e) { return url; } } class Streamer { constructor() { this._lastSnapshotNumber = 0; this._staleStyleSheets = /* @__PURE__ */ new Set(); this._modifiedStyleSheets = /* @__PURE__ */ new Set(); this._readingStyleSheet = false; const invalidateCSSGroupingRule = (rule) => { if (rule.parentStyleSheet) this._invalidateStyleSheet(rule.parentStyleSheet); }; this._interceptNativeMethod(window.CSSStyleSheet.prototype, "insertRule", (sheet) => this._invalidateStyleSheet(sheet)); this._interceptNativeMethod(window.CSSStyleSheet.prototype, "deleteRule", (sheet) => this._invalidateStyleSheet(sheet)); this._interceptNativeMethod(window.CSSStyleSheet.prototype, "addRule", (sheet) => this._invalidateStyleSheet(sheet)); this._interceptNativeMethod(window.CSSStyleSheet.prototype, "removeRule", (sheet) => this._invalidateStyleSheet(sheet)); this._interceptNativeGetter(window.CSSStyleSheet.prototype, "rules", (sheet) => this._invalidateStyleSheet(sheet)); this._interceptNativeGetter(window.CSSStyleSheet.prototype, "cssRules", (sheet) => this._invalidateStyleSheet(sheet)); this._interceptNativeMethod(window.CSSStyleSheet.prototype, "replaceSync", (sheet) => this._invalidateStyleSheet(sheet)); this._interceptNativeMethod(window.CSSGroupingRule.prototype, "insertRule", invalidateCSSGroupingRule); this._interceptNativeMethod(window.CSSGroupingRule.prototype, "deleteRule", invalidateCSSGroupingRule); this._interceptNativeGetter(window.CSSGroupingRule.prototype, "cssRules", invalidateCSSGroupingRule); this._interceptNativeSetter(window.StyleSheet.prototype, "disabled", (sheet) => { if (sheet instanceof CSSStyleSheet) this._invalidateStyleSheet(sheet); }); this._interceptNativeAsyncMethod(window.CSSStyleSheet.prototype, "replace", (sheet) => this._invalidateStyleSheet(sheet)); this._fakeBase = document.createElement("base"); this._observer = new MutationObserver((list) => this._handleMutations(list)); const observerConfig = { attributes: true, subtree: true }; this._observer.observe(document, observerConfig); this._refreshListenersWhenNeeded(); } _refreshListenersWhenNeeded() { this._refreshListeners(); const customEventName = "__playwright_snapshotter_global_listeners_check__"; let seenEvent = false; const handleCustomEvent = () => seenEvent = true; window.addEventListener(customEventName, handleCustomEvent); const observer = new MutationObserver((entries) => { const newDocumentElement = entries.some((entry) => Array.from(entry.addedNodes).includes(document.documentElement)); if (newDocumentElement) { seenEvent = false; window.dispatchEvent(new CustomEvent(customEventName)); if (!seenEvent) { window.addEventListener(customEventName, handleCustomEvent); this._refreshListeners(); } } }); observer.observe(document, { childList: true }); } _refreshListeners() { document.addEventListener("__playwright_mark_target__", (event) => { if (!event.detail) return; const callId = event.detail; event.composedPath()[0].__playwright_target__ = callId; }); document.addEventListener("__playwright_unmark_target__", (event) => { if (!event.detail) return; const callId = event.detail; if (event.composedPath()[0].__playwright_target__ === callId) delete event.composedPath()[0].__playwright_target__; }); } _interceptNativeMethod(obj, method, cb) { const native = obj[method]; if (!native) return; obj[method] = function(...args) { const result = native.call(this, ...args); cb(this, result); return result; }; } _interceptNativeAsyncMethod(obj, method, cb) { const native = obj[method]; if (!native) return; obj[method] = async function(...args) { const result = await native.call(this, ...args); cb(this, result); return result; }; } _interceptNativeGetter(obj, prop, cb) { const descriptor = Object.getOwnPropertyDescriptor(obj, prop); Object.defineProperty(obj, prop, { ...descriptor, get: function() { const result = descriptor.get.call(this); cb(this, result); return result; } }); } _interceptNativeSetter(obj, prop, cb) { const descriptor = Object.getOwnPropertyDescriptor(obj, prop); Object.defineProperty(obj, prop, { ...descriptor, set: function(value) { const result = descriptor.set.call(this, value); cb(this, value); return result; } }); } _handleMutations(list) { for (const mutation of list) ensureCachedData(mutation.target).attributesCached = void 0; } _invalidateStyleSheet(sheet) { if (this._readingStyleSheet) return; this._staleStyleSheets.add(sheet); if (sheet.href !== null) this._modifiedStyleSheets.add(sheet); } _updateStyleElementStyleSheetTextIfNeeded(sheet, forceText) { const data = ensureCachedData(sheet); if (this._staleStyleSheets.has(sheet) || forceText && data.cssText === void 0) { this._staleStyleSheets.delete(sheet); try { data.cssText = this._getSheetText(sheet); } catch (e) { data.cssText = ""; } } return data.cssText; } // Returns either content, ref, or no override. _updateLinkStyleSheetTextIfNeeded(sheet, snapshotNumber) { const data = ensureCachedData(sheet); if (this._staleStyleSheets.has(sheet)) { this._staleStyleSheets.delete(sheet); try { data.cssText = this._getSheetText(sheet); data.cssRef = snapshotNumber; return data.cssText; } catch (e) { } } return data.cssRef === void 0 ? void 0 : snapshotNumber - data.cssRef; } markIframe(iframeElement, frameId) { iframeElement[kSnapshotFrameId] = frameId; } reset() { this._staleStyleSheets.clear(); const visitNode = (node) => { resetCachedData(node); if (node.nodeType === Node.ELEMENT_NODE) { const element = node; if (element.shadowRoot) visitNode(element.shadowRoot); } for (let child = node.firstChild; child; child = child.nextSibling) visitNode(child); }; visitNode(document.documentElement); visitNode(this._fakeBase); } __sanitizeMetaAttribute(name, value, httpEquiv) { if (name === "charset") return "utf-8"; if (httpEquiv.toLowerCase() !== "content-type" || name !== "content") return value; const [type, ...params] = value.split(";"); if (type !== "text/html" || params.length <= 0) return value; const charsetParamIdx = params.findIndex((param) => param.trim().startsWith("charset=")); if (charsetParamIdx > -1) params[charsetParamIdx] = "charset=utf-8"; return `${type}; ${params.join("; ")}`; } _sanitizeUrl(url) { if (url.startsWith("javascript:") || url.startsWith("vbscript:")) return ""; return url; } _sanitizeSrcSet(srcset) { return srcset.split(",").map((src) => { src = src.trim(); const spaceIndex = src.lastIndexOf(" "); if (spaceIndex === -1) return this._sanitizeUrl(src); return this._sanitizeUrl(src.substring(0, spaceIndex).trim()) + src.substring(spaceIndex); }).join(", "); } _resolveUrl(base, url) { if (url === "") return ""; try { return new URL(url, base).href; } catch (e) { return url; } } _getSheetBase(sheet) { let rootSheet = sheet; while (rootSheet.parentStyleSheet) rootSheet = rootSheet.parentStyleSheet; if (rootSheet.ownerNode) return rootSheet.ownerNode.baseURI; return document.baseURI; } _getSheetText(sheet) { this._readingStyleSheet = true; try { if (sheet.disabled) return ""; const rules = []; for (const rule of sheet.cssRules) rules.push(rule.cssText); return rules.join("\n"); } finally { this._readingStyleSheet = false; } } captureSnapshot(needsReset) { const timestamp = performance.now(); const snapshotNumber = ++this._lastSnapshotNumber; if (needsReset) this.reset(); let nodeCounter = 0; let shadowDomNesting = 0; let headNesting = 0; this._handleMutations(this._observer.takeRecords()); const definedCustomElements = /* @__PURE__ */ new Set(); const visitNode = (node) => { const nodeType = node.nodeType; const nodeName = nodeType === Node.DOCUMENT_FRAGMENT_NODE ? "template" : node.nodeName; if (nodeType !== Node.ELEMENT_NODE && nodeType !== Node.DOCUMENT_FRAGMENT_NODE && nodeType !== Node.TEXT_NODE) return; if (nodeName === "SCRIPT") return; if (nodeName === "LINK" && nodeType === Node.ELEMENT_NODE) { const rel = node.getAttribute("rel")?.toLowerCase(); if (rel === "preload" || rel === "prefetch") return; } if (removeNoScript && nodeName === "NOSCRIPT") return; if (nodeName === "META" && node.httpEquiv.toLowerCase() === "content-security-policy") return; if ((nodeName === "IFRAME" || nodeName === "FRAME") && headNesting) return; const data = ensureCachedData(node); const values = []; let equals = !!data.cached; let extraNodes = 0; const expectValue = (value) => { equals = equals && data.cached[values.length] === value; values.push(value); }; const checkAndReturn = (n) => { data.attributesCached = true; if (equals) return { equals: true, n: [[snapshotNumber - data.ref[0], data.ref[1]]] }; nodeCounter += extraNodes; data.ref = [snapshotNumber, nodeCounter++]; data.cached = values; return { equals: false, n }; }; if (nodeType === Node.TEXT_NODE) { const value = node.nodeValue || ""; expectValue(value); return checkAndReturn(value); } if (nodeName === "STYLE") { const sheet = node.sheet; let cssText; if (sheet) cssText = this._updateStyleElementStyleSheetTextIfNeeded(sheet); cssText = cssText || node.textContent || ""; expectValue(cssText); extraNodes++; return checkAndReturn([nodeName, {}, cssText]); } const attrs = {}; const result2 = [nodeName, attrs]; const visitChild = (child) => { const snapshot = visitNode(child); if (snapshot) { result2.push(snapshot.n); expectValue(child); equals = equals && snapshot.equals; } }; const visitChildStyleSheet = (child) => { const snapshot = visitStyleSheet(child); if (snapshot) { result2.push(snapshot.n); expectValue(child); equals = equals && snapshot.equals; } }; if (nodeType === Node.DOCUMENT_FRAGMENT_NODE) attrs[kShadowAttribute] = "open"; if (nodeType === Node.ELEMENT_NODE) { const element = node; if (element.localName.includes("-") && window.customElements?.get(element.localName)) definedCustomElements.add(element.localName); if (nodeName === "INPUT" || nodeName === "TEXTAREA") { const value = element.value; expectValue(kValueAttribute); expectValue(value); attrs[kValueAttribute] = value; } if (nodeName === "INPUT" && ["checkbox", "radio"].includes(element.type)) { const value = element.checked ? "true" : "false"; expectValue(kCheckedAttribute); expectValue(value); attrs[kCheckedAttribute] = value; } if (nodeName === "OPTION") { const value = element.selected ? "true" : "false"; expectValue(kSelectedAttribute); expectValue(value); attrs[kSelectedAttribute] = value; } if (nodeName === "CANVAS" || nodeName === "IFRAME" || nodeName === "FRAME") { const boundingRect = element.getBoundingClientRect(); const value = JSON.stringify({ left: boundingRect.left, top: boundingRect.top, right: boundingRect.right, bottom: boundingRect.bottom }); expectValue(kBoundingRectAttribute); expectValue(value); attrs[kBoundingRectAttribute] = value; } if (element.popover && element.matches && element.matches(":popover-open")) { const value = "true"; expectValue(kPopoverOpenAttribute); expectValue(value); attrs[kPopoverOpenAttribute] = value; } if (nodeName === "DIALOG" && element.open) { const value = element.matches(":modal") ? "modal" : "true"; expectValue(kDialogOpenAttribute); expectValue(value); attrs[kDialogOpenAttribute] = value; } if (element.scrollTop) { expectValue(kScrollTopAttribute); expectValue(element.scrollTop); attrs[kScrollTopAttribute] = "" + element.scrollTop; } if (element.scrollLeft) { expectValue(kScrollLeftAttribute); expectValue(element.scrollLeft); attrs[kScrollLeftAttribute] = "" + element.scrollLeft; } if (element.shadowRoot) { ++shadowDomNesting; visitChild(element.shadowRoot); --shadowDomNesting; } if ("__playwright_target__" in element) { expectValue(kTargetAttribute); expectValue(element["__playwright_target__"]); attrs[kTargetAttribute] = element["__playwright_target__"]; } } if (nodeName === "HEAD") { ++headNesting; this._fakeBase.setAttribute("href", document.baseURI); visitChild(this._fakeBase); } for (let child = node.firstChild; child; child = child.nextSibling) visitChild(child); if (nodeName === "HEAD") --headNesting; expectValue(kEndOfList); let documentOrShadowRoot = null; if (node.ownerDocument.documentElement === node) documentOrShadowRoot = node.ownerDocument; else if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) documentOrShadowRoot = node; if (documentOrShadowRoot) { for (const sheet of documentOrShadowRoot.adoptedStyleSheets || []) visitChildStyleSheet(sheet); expectValue(kEndOfList); } if (nodeName === "IFRAME" || nodeName === "FRAME") { const element = node; const frameId = element[kSnapshotFrameId]; const name = "src"; const value = frameId ? `/snapshot/${frameId}` : ""; expectValue(name); expectValue(value); attrs[name] = value; } if (nodeName === "BODY" && definedCustomElements.size) { const value = [...definedCustomElements].join(","); expectValue(kCustomElementsAttribute); expectValue(value); attrs[kCustomElementsAttribute] = value; } if (nodeName === "IMG" || nodeName === "PICTURE") { const value = nodeName === "PICTURE" ? "" : this._sanitizeUrl(node.currentSrc); expectValue(kCurrentSrcAttribute); expectValue(value); attrs[kCurrentSrcAttribute] = value; } if (equals && data.attributesCached && !shadowDomNesting) return checkAndReturn(result2); if (nodeType === Node.ELEMENT_NODE) { const element = node; for (let i = 0; i < element.attributes.length; i++) { const name = element.attributes[i].name; if (nodeName === "LINK" && name === "integrity") continue; if (nodeName === "IFRAME" && (name === "src" || name === "srcdoc" || name === "sandbox")) continue; if (nodeName === "FRAME" && name === "src") continue; if (nodeName === "DIALOG" && name === "open") continue; let value = element.attributes[i].value; if (nodeName === "META") value = this.__sanitizeMetaAttribute(name, value, node.httpEquiv); else if (name === "src" && nodeName === "IMG") value = this._sanitizeUrl(value); else if (name === "srcset" && nodeName === "IMG") value = this._sanitizeSrcSet(value); else if (name === "srcset" && nodeName === "SOURCE") value = this._sanitizeSrcSet(value); else if (name === "href" && nodeName === "LINK") value = this._sanitizeUrl(value); else if (name.startsWith("on")) value = ""; expectValue(name); expectValue(value); attrs[name] = value; } expectValue(kEndOfList); } if (result2.length === 2 && !Object.keys(attrs).length) result2.pop(); return checkAndReturn(result2); }; const visitStyleSheet = (sheet) => { const data = ensureCachedData(sheet); const oldCSSText = data.cssText; const cssText = this._updateStyleElementStyleSheetTextIfNeeded( sheet, true /* forceText */ ); if (cssText === oldCSSText) return { equals: true, n: [[snapshotNumber - data.ref[0], data.ref[1]]] }; data.ref = [snapshotNumber, nodeCounter++]; return { equals: false, n: ["template", { [kStyleSheetAttribute]: cssText }] }; }; let html; if (document.documentElement) { const { n } = visitNode(document.documentElement); html = n; } else { html = ["html"]; } const result = { html, doctype: document.doctype ? document.doctype.name : void 0, resourceOverrides: [], viewport: { width: window.innerWidth, height: window.innerHeight }, url: location.href, wallTime: Date.now(), collectionTime: 0 }; for (const sheet of this._modifiedStyleSheets) { if (sheet.href === null) continue; const content = this._updateLinkStyleSheetTextIfNeeded(sheet, snapshotNumber); if (content === void 0) { continue; } const base = this._getSheetBase(sheet); const url = removeHash(this._resolveUrl(base, sheet.href)); result.resourceOverrides.push({ url, content, contentType: "text/css" }); } result.collectionTime = performance.now() - timestamp; return result; } } window[snapshotStreamer] = new Streamer(); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { frameSnapshotStreamer });