How to use ensureCachedData method in Playwright Internal

Best JavaScript code snippet using playwright-internal

Run Playwright Internal automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

snapshotterInjected.js

Source: snapshotterInjected.js Github

copy
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4  value: true
5});
6exports.frameSnapshotStreamer = frameSnapshotStreamer;
7
8/**
9 * Copyright (c) Microsoft Corporation.
10 *
11 * Licensed under the Apache License, Version 2.0 (the "License");
12 * you may not use this file except in compliance with the License.
13 * You may obtain a copy of the License at
14 *
15 *     http://www.apache.org/licenses/LICENSE-2.0
16 *
17 * Unless required by applicable law or agreed to in writing, software
18 * distributed under the License is distributed on an "AS IS" BASIS,
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 * See the License for the specific language governing permissions and
21 * limitations under the License.
22 */
23function frameSnapshotStreamer(snapshotStreamer) {
24  // Communication with Playwright.
25  if (window[snapshotStreamer]) return; // Attributes present in the snapshot.
26
27  const kShadowAttribute = '__playwright_shadow_root_';
28  const kScrollTopAttribute = '__playwright_scroll_top_';
29  const kScrollLeftAttribute = '__playwright_scroll_left_';
30  const kStyleSheetAttribute = '__playwright_style_sheet_'; // Symbols for our own info on Nodes/StyleSheets.
31
32  const kSnapshotFrameId = Symbol('__playwright_snapshot_frameid_');
33  const kCachedData = Symbol('__playwright_snapshot_cache_');
34  const kEndOfList = Symbol('__playwright_end_of_list_');
35
36  function resetCachedData(obj) {
37    delete obj[kCachedData];
38  }
39
40  function ensureCachedData(obj) {
41    if (!obj[kCachedData]) obj[kCachedData] = {};
42    return obj[kCachedData];
43  }
44
45  function removeHash(url) {
46    try {
47      const u = new URL(url);
48      u.hash = '';
49      return u.toString();
50    } catch (e) {
51      return url;
52    }
53  }
54
55  class Streamer {
56    // To avoid invalidating due to our own reads.
57    constructor() {
58      this._removeNoScript = true;
59      this._lastSnapshotNumber = 0;
60      this._staleStyleSheets = new Set();
61      this._readingStyleSheet = false;
62      this._fakeBase = void 0;
63      this._observer = void 0;
64
65      this._interceptNativeMethod(window.CSSStyleSheet.prototype, 'insertRule', sheet => this._invalidateStyleSheet(sheet));
66
67      this._interceptNativeMethod(window.CSSStyleSheet.prototype, 'deleteRule', sheet => this._invalidateStyleSheet(sheet));
68
69      this._interceptNativeMethod(window.CSSStyleSheet.prototype, 'addRule', sheet => this._invalidateStyleSheet(sheet));
70
71      this._interceptNativeMethod(window.CSSStyleSheet.prototype, 'removeRule', sheet => this._invalidateStyleSheet(sheet));
72
73      this._interceptNativeGetter(window.CSSStyleSheet.prototype, 'rules', sheet => this._invalidateStyleSheet(sheet));
74
75      this._interceptNativeGetter(window.CSSStyleSheet.prototype, 'cssRules', sheet => this._invalidateStyleSheet(sheet));
76
77      this._fakeBase = document.createElement('base');
78      this._observer = new MutationObserver(list => this._handleMutations(list));
79      const observerConfig = {
80        attributes: true,
81        subtree: true
82      };
83
84      this._observer.observe(document, observerConfig);
85    }
86
87    _interceptNativeMethod(obj, method, cb) {
88      const native = obj[method];
89      if (!native) return;
90
91      obj[method] = function (...args) {
92        const result = native.call(this, ...args);
93        cb(this, result);
94        return result;
95      };
96    }
97
98    _interceptNativeGetter(obj, prop, cb) {
99      const descriptor = Object.getOwnPropertyDescriptor(obj, prop);
100      Object.defineProperty(obj, prop, { ...descriptor,
101        get: function () {
102          const result = descriptor.get.call(this);
103          cb(this, result);
104          return result;
105        }
106      });
107    }
108
109    _handleMutations(list) {
110      for (const mutation of list) ensureCachedData(mutation.target).attributesCached = undefined;
111    }
112
113    _invalidateStyleSheet(sheet) {
114      if (this._readingStyleSheet) return;
115
116      this._staleStyleSheets.add(sheet);
117    }
118
119    _updateStyleElementStyleSheetTextIfNeeded(sheet) {
120      const data = ensureCachedData(sheet);
121
122      if (this._staleStyleSheets.has(sheet)) {
123        this._staleStyleSheets.delete(sheet);
124
125        try {
126          data.cssText = this._getSheetText(sheet);
127        } catch (e) {// Sometimes we cannot access cross-origin stylesheets.
128        }
129      }
130
131      return data.cssText;
132    } // Returns either content, ref, or no override.
133
134
135    _updateLinkStyleSheetTextIfNeeded(sheet, snapshotNumber) {
136      const data = ensureCachedData(sheet);
137
138      if (this._staleStyleSheets.has(sheet)) {
139        this._staleStyleSheets.delete(sheet);
140
141        try {
142          data.cssText = this._getSheetText(sheet);
143          data.cssRef = snapshotNumber;
144          return data.cssText;
145        } catch (e) {// Sometimes we cannot access cross-origin stylesheets.
146        }
147      }
148
149      return data.cssRef === undefined ? undefined : snapshotNumber - data.cssRef;
150    }
151
152    markIframe(iframeElement, frameId) {
153      iframeElement[kSnapshotFrameId] = frameId;
154    }
155
156    reset() {
157      this._staleStyleSheets.clear();
158
159      const visitNode = node => {
160        resetCachedData(node);
161
162        if (node.nodeType === Node.ELEMENT_NODE) {
163          const element = node;
164          if (element.shadowRoot) visitNode(element.shadowRoot);
165        }
166
167        for (let child = node.firstChild; child; child = child.nextSibling) visitNode(child);
168      };
169
170      visitNode(document.documentElement);
171      visitNode(this._fakeBase);
172    }
173
174    _sanitizeUrl(url) {
175      if (url.startsWith('javascript:')) return '';
176      return url;
177    }
178
179    _sanitizeSrcSet(srcset) {
180      return srcset.split(',').map(src => {
181        src = src.trim();
182        const spaceIndex = src.lastIndexOf(' ');
183        if (spaceIndex === -1) return this._sanitizeUrl(src);
184        return this._sanitizeUrl(src.substring(0, spaceIndex).trim()) + src.substring(spaceIndex);
185      }).join(', ');
186    }
187
188    _resolveUrl(base, url) {
189      if (url === '') return '';
190
191      try {
192        return new URL(url, base).href;
193      } catch (e) {
194        return url;
195      }
196    }
197
198    _getSheetBase(sheet) {
199      let rootSheet = sheet;
200
201      while (rootSheet.parentStyleSheet) rootSheet = rootSheet.parentStyleSheet;
202
203      if (rootSheet.ownerNode) return rootSheet.ownerNode.baseURI;
204      return document.baseURI;
205    }
206
207    _getSheetText(sheet) {
208      this._readingStyleSheet = true;
209
210      try {
211        const rules = [];
212
213        for (const rule of sheet.cssRules) rules.push(rule.cssText);
214
215        return rules.join('\n');
216      } finally {
217        this._readingStyleSheet = false;
218      }
219    }
220
221    captureSnapshot() {
222      const timestamp = performance.now();
223      const snapshotNumber = ++this._lastSnapshotNumber;
224      let nodeCounter = 0;
225      let shadowDomNesting = 0; // Ensure we are up to date.
226
227      this._handleMutations(this._observer.takeRecords());
228
229      const visitNode = node => {
230        const nodeType = node.nodeType;
231        const nodeName = nodeType === Node.DOCUMENT_FRAGMENT_NODE ? 'template' : node.nodeName;
232        if (nodeType !== Node.ELEMENT_NODE && nodeType !== Node.DOCUMENT_FRAGMENT_NODE && nodeType !== Node.TEXT_NODE) return;
233        if (nodeName === 'SCRIPT') return;
234        if (this._removeNoScript && nodeName === 'NOSCRIPT') return;
235        const data = ensureCachedData(node);
236        const values = [];
237        let equals = !!data.cached;
238        let extraNodes = 0;
239
240        const expectValue = value => {
241          equals = equals && data.cached[values.length] === value;
242          values.push(value);
243        };
244
245        const checkAndReturn = n => {
246          data.attributesCached = true;
247          if (equals) return {
248            equals: true,
249            n: [[snapshotNumber - data.ref[0], data.ref[1]]]
250          };
251          nodeCounter += extraNodes;
252          data.ref = [snapshotNumber, nodeCounter++];
253          data.cached = values;
254          return {
255            equals: false,
256            n
257          };
258        };
259
260        if (nodeType === Node.TEXT_NODE) {
261          const value = node.nodeValue || '';
262          expectValue(value);
263          return checkAndReturn(value);
264        }
265
266        if (nodeName === 'STYLE') {
267          const sheet = node.sheet;
268          let cssText;
269          if (sheet) cssText = this._updateStyleElementStyleSheetTextIfNeeded(sheet);
270          cssText = cssText || node.textContent || '';
271          expectValue(cssText); // Compensate for the extra 'cssText' text node.
272
273          extraNodes++;
274          return checkAndReturn(['style', {}, cssText]);
275        }
276
277        const attrs = {};
278        const result = [nodeName, attrs];
279
280        const visitChild = child => {
281          const snapshot = visitNode(child);
282
283          if (snapshot) {
284            result.push(snapshot.n);
285            expectValue(child);
286            equals = equals && snapshot.equals;
287          }
288        };
289
290        const visitChildStyleSheet = child => {
291          const snapshot = visitStyleSheet(child);
292
293          if (snapshot) {
294            result.push(snapshot.n);
295            expectValue(child);
296            equals = equals && snapshot.equals;
297          }
298        };
299
300        if (nodeType === Node.DOCUMENT_FRAGMENT_NODE) attrs[kShadowAttribute] = 'open';
301
302        if (nodeType === Node.ELEMENT_NODE) {
303          const element = node;
304
305          if (nodeName === 'INPUT') {
306            const value = element.value;
307            expectValue('value');
308            expectValue(value);
309            attrs['value'] = value;
310
311            if (element.checked) {
312              expectValue('checked');
313              attrs['checked'] = '';
314            }
315          }
316
317          if (element.scrollTop) {
318            expectValue(kScrollTopAttribute);
319            expectValue(element.scrollTop);
320            attrs[kScrollTopAttribute] = '' + element.scrollTop;
321          }
322
323          if (element.scrollLeft) {
324            expectValue(kScrollLeftAttribute);
325            expectValue(element.scrollLeft);
326            attrs[kScrollLeftAttribute] = '' + element.scrollLeft;
327          }
328
329          if (element.shadowRoot) {
330            ++shadowDomNesting;
331            visitChild(element.shadowRoot);
332            --shadowDomNesting;
333          }
334        }
335
336        if (nodeName === 'TEXTAREA') {
337          const value = node.value;
338          expectValue(value);
339          extraNodes++; // Compensate for the extra text node.
340
341          result.push(value);
342        } else {
343          if (nodeName === 'HEAD') {
344            // Insert fake <base> first, to ensure all <link> elements use the proper base uri.
345            this._fakeBase.setAttribute('href', document.baseURI);
346
347            visitChild(this._fakeBase);
348          }
349
350          for (let child = node.firstChild; child; child = child.nextSibling) visitChild(child);
351
352          expectValue(kEndOfList);
353          let documentOrShadowRoot = null;
354          if (node.ownerDocument.documentElement === node) documentOrShadowRoot = node.ownerDocument;else if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) documentOrShadowRoot = node;
355
356          if (documentOrShadowRoot) {
357            for (const sheet of documentOrShadowRoot.adoptedStyleSheets || []) visitChildStyleSheet(sheet);
358
359            expectValue(kEndOfList);
360          }
361        } // Process iframe src attribute before bailing out since it depends on a symbol, not the DOM.
362
363
364        if (nodeName === 'IFRAME' || nodeName === 'FRAME') {
365          const element = node;
366          const frameId = element[kSnapshotFrameId];
367          const name = 'src';
368          const value = frameId ? `/snapshot/${frameId}` : '';
369          expectValue(name);
370          expectValue(value);
371          attrs[name] = value;
372        } // We can skip attributes comparison because nothing else has changed,
373        // and mutation observer didn't tell us about the attributes.
374
375
376        if (equals && data.attributesCached && !shadowDomNesting) return checkAndReturn(result);
377
378        if (nodeType === Node.ELEMENT_NODE) {
379          const element = node;
380
381          for (let i = 0; i < element.attributes.length; i++) {
382            const name = element.attributes[i].name;
383            if (name === 'value' && (nodeName === 'INPUT' || nodeName === 'TEXTAREA')) continue;
384            if (nodeName === 'LINK' && name === 'integrity') continue;
385            if (nodeName === 'IFRAME' && name === 'src') continue;
386            let value = element.attributes[i].value;
387            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 = '';
388            expectValue(name);
389            expectValue(value);
390            attrs[name] = value;
391          }
392
393          expectValue(kEndOfList);
394        }
395
396        if (result.length === 2 && !Object.keys(attrs).length) result.pop(); // Remove empty attrs when there are no children.
397
398        return checkAndReturn(result);
399      };
400
401      const visitStyleSheet = sheet => {
402        const data = ensureCachedData(sheet);
403        const oldCSSText = data.cssText;
404        const cssText = this._updateStyleElementStyleSheetTextIfNeeded(sheet) || '';
405        if (cssText === oldCSSText) return {
406          equals: true,
407          n: [[snapshotNumber - data.ref[0], data.ref[1]]]
408        };
409        data.ref = [snapshotNumber, nodeCounter++];
410        return {
411          equals: false,
412          n: ['template', {
413            [kStyleSheetAttribute]: cssText
414          }]
415        };
416      };
417
418      let html;
419
420      if (document.documentElement) {
421        const {
422          n
423        } = visitNode(document.documentElement);
424        html = n;
425      } else {
426        html = ['html'];
427      }
428
429      const result = {
430        html,
431        doctype: document.doctype ? document.doctype.name : undefined,
432        resourceOverrides: [],
433        viewport: {
434          width: window.innerWidth,
435          height: window.innerHeight
436        },
437        url: location.href,
438        timestamp,
439        collectionTime: 0
440      };
441
442      for (const sheet of this._staleStyleSheets) {
443        if (sheet.href === null) continue;
444
445        const content = this._updateLinkStyleSheetTextIfNeeded(sheet, snapshotNumber);
446
447        if (content === undefined) {
448          // Unable to capture stylesheet contents.
449          continue;
450        }
451
452        const base = this._getSheetBase(sheet);
453
454        const url = removeHash(this._resolveUrl(base, sheet.href));
455        result.resourceOverrides.push({
456          url,
457          content,
458          contentType: 'text/css'
459        });
460      }
461
462      result.collectionTime = performance.now() - result.timestamp;
463      return result;
464    }
465
466  }
467
468  window[snapshotStreamer] = new Streamer();
469}
Full Screen

Accelerate Your Automation Test Cycles With LambdaTest

Leverage LambdaTest’s cloud-based platform to execute your automation tests in parallel and trim down your test execution time significantly. Your first 100 automation testing minutes are on us.

Try LambdaTest

Run JavaScript Tests on LambdaTest Cloud Grid

Execute automation tests with Playwright Internal on a cloud-based Grid of 3000+ real browsers and operating systems for both web and mobile applications.

Test now for Free
LambdaTestX

We use cookies to give you the best experience. Cookies help to provide a more personalized experience and relevant advertising for you, and web analytics for us. Learn More in our Cookies policy, Privacy & Terms of service

Allow Cookie
Sarah

I hope you find the best code examples for your project.

If you want to accelerate automated browser testing, try LambdaTest. Your first 100 automation testing minutes are FREE.

Sarah Elson (Product & Growth Lead)