Best JavaScript code snippet using playwright-internal
TargetRegistry.js
Source:TargetRegistry.js  
1/* This Source Code Form is subject to the terms of the Mozilla Public2 * License, v. 2.0. If a copy of the MPL was not distributed with this3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */4const {EventEmitter} = ChromeUtils.import('resource://gre/modules/EventEmitter.jsm');5const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');6const {SimpleChannel} = ChromeUtils.import('chrome://juggler/content/SimpleChannel.js');7const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");8const {Preferences} = ChromeUtils.import("resource://gre/modules/Preferences.jsm");9const {ContextualIdentityService} = ChromeUtils.import("resource://gre/modules/ContextualIdentityService.jsm");10const {NetUtil} = ChromeUtils.import('resource://gre/modules/NetUtil.jsm');11const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");12const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");13const helper = new Helper();14const IDENTITY_NAME = 'JUGGLER ';15const HUNDRED_YEARS = 60 * 60 * 24 * 365 * 100;16const ALL_PERMISSIONS = [17  'geo',18  'desktop-notification',19];20class DownloadInterceptor {21  constructor(registry) {22    this._registry = registry23    this._handlerToUuid = new Map();24  }25  //26  // nsIDownloadInterceptor implementation.27  //28  interceptDownloadRequest(externalAppHandler, request, browsingContext, outFile) {29    if (!(request instanceof Ci.nsIChannel))30      return false;31    const channel = request.QueryInterface(Ci.nsIChannel);32    let pageTarget = this._registry._browserBrowsingContextToTarget.get(channel.loadInfo.browsingContext);33    if (!pageTarget)34      return false;35    const browserContext = pageTarget.browserContext();36    const options = browserContext.downloadOptions;37    if (!options)38      return false;39    const uuid = helper.generateId();40    let file = null;41    if (options.behavior === 'saveToDisk') {42      file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);43      file.initWithPath(options.downloadsDir);44      file.append(uuid);45      try {46        file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);47      } catch (e) {48        dump(`interceptDownloadRequest failed to create file: ${e}\n`);49        return false;50      }51    }52    outFile.value = file;53    this._handlerToUuid.set(externalAppHandler, uuid);54    const downloadInfo = {55      uuid,56      browserContextId: browserContext.browserContextId,57      pageTargetId: pageTarget.id(),58      url: request.name,59      suggestedFileName: externalAppHandler.suggestedFileName,60    };61    this._registry.emit(TargetRegistry.Events.DownloadCreated, downloadInfo);62    return true;63  }64  onDownloadComplete(externalAppHandler, canceled, errorName) {65    const uuid = this._handlerToUuid.get(externalAppHandler);66    if (!uuid)67      return;68    this._handlerToUuid.delete(externalAppHandler);69    const downloadInfo = {70      uuid,71    };72    if (errorName === 'NS_BINDING_ABORTED') {73      downloadInfo.canceled = true;74    } else {75      downloadInfo.error = errorName;76    }77    this._registry.emit(TargetRegistry.Events.DownloadFinished, downloadInfo);78  }79}80class TargetRegistry {81  constructor() {82    EventEmitter.decorate(this);83    this._browserContextIdToBrowserContext = new Map();84    this._userContextIdToBrowserContext = new Map();85    this._browserToTarget = new Map();86    this._browserBrowsingContextToTarget = new Map();87    this._browserProxy = null;88    // Cleanup containers from previous runs (if any)89    for (const identity of ContextualIdentityService.getPublicIdentities()) {90      if (identity.name && identity.name.startsWith(IDENTITY_NAME)) {91        ContextualIdentityService.remove(identity.userContextId);92        ContextualIdentityService.closeContainerTabs(identity.userContextId);93      }94    }95    this._defaultContext = new BrowserContext(this, undefined, undefined);96    Services.obs.addObserver({97      observe: (subject, topic, data) => {98        const browser = subject.ownerElement;99        if (!browser)100          return;101        const target = this._browserToTarget.get(browser);102        if (!target)103          return;104        target.emit(PageTarget.Events.Crashed);105        target.dispose();106      }107    }, 'oop-frameloader-crashed');108    Services.mm.addMessageListener('juggler:content-ready', {109      receiveMessage: message => {110        const linkedBrowser = message.target;111        const target = this._browserToTarget.get(linkedBrowser);112        if (!target)113          return;114        return {115          scriptsToEvaluateOnNewDocument: target.browserContext().scriptsToEvaluateOnNewDocument,116          bindings: target.browserContext().bindings,117          settings: target.browserContext().settings,118        };119      },120    });121    const onTabOpenListener = (appWindow, window, event) => {122      const tab = event.target;123      const userContextId = tab.userContextId;124      const browserContext = this._userContextIdToBrowserContext.get(userContextId);125      const hasExplicitSize = appWindow && (appWindow.chromeFlags & Ci.nsIWebBrowserChrome.JUGGLER_WINDOW_EXPLICIT_SIZE) !== 0;126      const openerContext = tab.linkedBrowser.browsingContext.opener;127      let openerTarget;128      if (openerContext) {129        // Popups usually have opener context.130        openerTarget = this._browserBrowsingContextToTarget.get(openerContext);131      } else if (tab.openerTab) {132        // Noopener popups from the same window have opener tab instead.133        openerTarget = this._browserToTarget.get(tab.openerTab.linkedBrowser);134      }135      if (!browserContext)136        throw new Error(`Internal error: cannot find context for userContextId=${userContextId}`);137      const target = new PageTarget(this, window, tab, browserContext, openerTarget);138      target.updateUserAgent();139      target.updateTouchOverride();140      target.updateColorSchemeOverride();141      if (!hasExplicitSize)142        target.updateViewportSize();143      if (browserContext.screencastOptions)144        target._startVideoRecording(browserContext.screencastOptions);145    };146    const onTabCloseListener = event => {147      const tab = event.target;148      const linkedBrowser = tab.linkedBrowser;149      const target = this._browserToTarget.get(linkedBrowser);150      if (target)151          target.dispose();152    };153    const domWindowTabListeners = new Map();154    const onOpenWindow = async (appWindow) => {155      let domWindow;156      if (appWindow instanceof Ci.nsIAppWindow) {157        domWindow = appWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);158      } else {159        domWindow = appWindow;160        appWindow = null;161      }162      if (!(domWindow instanceof Ci.nsIDOMChromeWindow))163        return;164      // In persistent mode, window might be opened long ago and might be165      // already initialized.166      //167      // In this case, we want to keep this callback synchronous so that we will call168      // `onTabOpenListener` synchronously and before the sync IPc message `juggler:content-ready`.169      if (domWindow.document.readyState === 'uninitialized' || domWindow.document.readyState === 'loading') {170        // For non-initialized windows, DOMContentLoaded initializes gBrowser171        // and starts tab loading (see //browser/base/content/browser.js), so we172        // are guaranteed to call `onTabOpenListener` before the sync IPC message173        // `juggler:content-ready`.174        await helper.awaitEvent(domWindow, 'DOMContentLoaded');175      }176      if (!domWindow.gBrowser)177        return;178      const tabContainer = domWindow.gBrowser.tabContainer;179      domWindowTabListeners.set(domWindow, [180        helper.addEventListener(tabContainer, 'TabOpen', event => onTabOpenListener(appWindow, domWindow, event)),181        helper.addEventListener(tabContainer, 'TabClose', onTabCloseListener),182      ]);183      for (const tab of domWindow.gBrowser.tabs)184        onTabOpenListener(appWindow, domWindow, { target: tab });185    };186    const onCloseWindow = window => {187      const domWindow = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);188      if (!(domWindow instanceof Ci.nsIDOMChromeWindow))189        return;190      if (!domWindow.gBrowser)191        return;192      const listeners = domWindowTabListeners.get(domWindow) || [];193      domWindowTabListeners.delete(domWindow);194      helper.removeListeners(listeners);195      for (const tab of domWindow.gBrowser.tabs)196        onTabCloseListener({ target: tab });197    };198    const extHelperAppSvc = Cc["@mozilla.org/uriloader/external-helper-app-service;1"].getService(Ci.nsIExternalHelperAppService);199    extHelperAppSvc.setDownloadInterceptor(new DownloadInterceptor(this));200    Services.wm.addListener({ onOpenWindow, onCloseWindow });201    for (const win of Services.wm.getEnumerator(null))202      onOpenWindow(win);203  }204  setBrowserProxy(proxy) {205    this._browserProxy = proxy;206  }207  getProxyInfo(channel) {208    const originAttributes = channel.loadInfo && channel.loadInfo.originAttributes;209    const browserContext = originAttributes ? this.browserContextForUserContextId(originAttributes.userContextId) : null;210    // Prefer context proxy and fallback to browser-level proxy.211    const proxyInfo = (browserContext && browserContext._proxy) || this._browserProxy;212    if (!proxyInfo || proxyInfo.bypass.some(domainSuffix => channel.URI.host.endsWith(domainSuffix)))213      return null;214    return proxyInfo;215  }216  defaultContext() {217    return this._defaultContext;218  }219  createBrowserContext(removeOnDetach) {220    return new BrowserContext(this, helper.generateId(), removeOnDetach);221  }222  browserContextForId(browserContextId) {223    return this._browserContextIdToBrowserContext.get(browserContextId);224  }225  browserContextForUserContextId(userContextId) {226    return this._userContextIdToBrowserContext.get(userContextId);227  }228  async newPage({browserContextId}) {229    const browserContext = this.browserContextForId(browserContextId);230    const features = "chrome,dialog=no,all";231    // See _callWithURIToLoad in browser.js for the structure of window.arguments232    // window.arguments[1]: unused (bug 871161)233    //                 [2]: referrerInfo (nsIReferrerInfo)234    //                 [3]: postData (nsIInputStream)235    //                 [4]: allowThirdPartyFixup (bool)236    //                 [5]: userContextId (int)237    //                 [6]: originPrincipal (nsIPrincipal)238    //                 [7]: originStoragePrincipal (nsIPrincipal)239    //                 [8]: triggeringPrincipal (nsIPrincipal)240    //                 [9]: allowInheritPrincipal (bool)241    //                 [10]: csp (nsIContentSecurityPolicy)242    //                 [11]: nsOpenWindowInfo243    const args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);244    const urlSupports = Cc["@mozilla.org/supports-string;1"].createInstance(245      Ci.nsISupportsString246    );247    urlSupports.data = 'about:blank';248    args.appendElement(urlSupports); // 0249    args.appendElement(undefined); // 1250    args.appendElement(undefined); // 2251    args.appendElement(undefined); // 3252    args.appendElement(undefined); // 4253    const userContextIdSupports = Cc[254      "@mozilla.org/supports-PRUint32;1"255    ].createInstance(Ci.nsISupportsPRUint32);256    userContextIdSupports.data = browserContext.userContextId;257    args.appendElement(userContextIdSupports); // 5258    args.appendElement(undefined); // 6259    args.appendElement(undefined); // 7260    args.appendElement(Services.scriptSecurityManager.getSystemPrincipal()); // 8261    const window = Services.ww.openWindow(null, AppConstants.BROWSER_CHROME_URL, '_blank', features, args);262    await waitForWindowReady(window);263    if (window.gBrowser.browsers.length !== 1)264      throw new Error(`Unexpected number of tabs in the new window: ${window.gBrowser.browsers.length}`);265    const browser = window.gBrowser.browsers[0];266    const target = this._browserToTarget.get(browser);267    browser.focus();268    if (browserContext.settings.timezoneId) {269      if (await target.hasFailedToOverrideTimezone())270        throw new Error('Failed to override timezone');271    }272    return target.id();273  }274  targets() {275    return Array.from(this._browserToTarget.values());276  }277  targetForBrowser(browser) {278    return this._browserToTarget.get(browser);279  }280}281class PageTarget {282  constructor(registry, win, tab, browserContext, opener) {283    EventEmitter.decorate(this);284    this._targetId = helper.generateId();285    this._registry = registry;286    this._window = win;287    this._gBrowser = win.gBrowser;288    this._tab = tab;289    this._linkedBrowser = tab.linkedBrowser;290    this._browserContext = browserContext;291    this._viewportSize = undefined;292    this._initialDPPX = this._linkedBrowser.browsingContext.overrideDPPX;293    this._url = 'about:blank';294    this._openerId = opener ? opener.id() : undefined;295    this._channel = SimpleChannel.createForMessageManager(`browser::page[${this._targetId}]`, this._linkedBrowser.messageManager);296    this._screencastInfo = undefined;297    this._dialogs = new Map();298    const navigationListener = {299      QueryInterface: ChromeUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]),300      onLocationChange: (aWebProgress, aRequest, aLocation) => this._onNavigated(aLocation),301    };302    this._eventListeners = [303      helper.addObserver(this._updateModalDialogs.bind(this), 'tabmodal-dialog-loaded'),304      helper.addProgressListener(tab.linkedBrowser, navigationListener, Ci.nsIWebProgress.NOTIFY_LOCATION),305      helper.addEventListener(this._linkedBrowser, 'DOMModalDialogClosed', event => this._updateModalDialogs()),306    ];307    this._disposed = false;308    browserContext.pages.add(this);309    this._registry._browserToTarget.set(this._linkedBrowser, this);310    this._registry._browserBrowsingContextToTarget.set(this._linkedBrowser.browsingContext, this);311    this._registry.emit(TargetRegistry.Events.TargetCreated, this);312  }313  dialog(dialogId) {314    return this._dialogs.get(dialogId);315  }316  dialogs() {317    return [...this._dialogs.values()];318  }319  async windowReady() {320    await waitForWindowReady(this._window);321  }322  linkedBrowser() {323    return this._linkedBrowser;324  }325  browserContext() {326    return this._browserContext;327  }328  updateTouchOverride() {329    this._linkedBrowser.browsingContext.touchEventsOverride = this._browserContext.touchOverride ? 'enabled' : 'none';330  }331  updateUserAgent() {332    this._linkedBrowser.browsingContext.customUserAgent = this._browserContext.defaultUserAgent;333  }334  _updateModalDialogs() {335    const prompts = new Set(this._linkedBrowser.tabModalPromptBox ? this._linkedBrowser.tabModalPromptBox.listPrompts() : []);336    for (const dialog of this._dialogs.values()) {337      if (!prompts.has(dialog.prompt())) {338        this._dialogs.delete(dialog.id());339        this.emit(PageTarget.Events.DialogClosed, dialog);340      } else {341        prompts.delete(dialog.prompt());342      }343    }344    for (const prompt of prompts) {345      const dialog = Dialog.createIfSupported(prompt);346      if (!dialog)347        continue;348      this._dialogs.set(dialog.id(), dialog);349      this.emit(PageTarget.Events.DialogOpened, dialog);350    }351  }352  async updateViewportSize() {353    // Viewport size is defined by three arguments:354    // 1. default size. Could be explicit if set as part of `window.open` call, e.g.355    //   `window.open(url, title, 'width=400,height=400')`356    // 2. page viewport size357    // 3. browserContext viewport size358    //359    // The "default size" (1) is only respected when the page is opened.360    // Otherwise, explicitly set page viewport prevales over browser context361    // default viewport.362    const viewportSize = this._viewportSize || this._browserContext.defaultViewportSize;363    const actualSize = await setViewportSizeForBrowser(viewportSize, this._linkedBrowser, this._window);364    this._linkedBrowser.browsingContext.overrideDPPX = this._browserContext.deviceScaleFactor || this._initialDPPX;365    await this._channel.connect('').send('awaitViewportDimensions', {366      width: actualSize.width,367      height: actualSize.height,368      deviceSizeIsPageSize: !!this._browserContext.deviceScaleFactor,369    });370  }371  setEmulatedMedia(mediumOverride) {372    this._linkedBrowser.browsingContext.mediumOverride = mediumOverride || '';373  }374  setColorScheme(colorScheme) {375    this.colorScheme = fromProtocolColorScheme(colorScheme);376    this.updateColorSchemeOverride();377  }378  updateColorSchemeOverride() {379    this._linkedBrowser.browsingContext.prefersColorSchemeOverride = this.colorScheme || this._browserContext.colorScheme || 'none';380  }381  async setViewportSize(viewportSize) {382    this._viewportSize = viewportSize;383    await this.updateViewportSize();384  }385  close(runBeforeUnload = false) {386    this._gBrowser.removeTab(this._tab, {387      skipPermitUnload: !runBeforeUnload,388    });389  }390  channel() {391    return this._channel;392  }393  id() {394    return this._targetId;395  }396  info() {397    return {398      targetId: this.id(),399      type: 'page',400      browserContextId: this._browserContext.browserContextId,401      openerId: this._openerId,402    };403  }404  _onNavigated(aLocation) {405    this._url = aLocation.spec;406    this._browserContext.grantPermissionsToOrigin(this._url);407  }408  async ensurePermissions() {409    await this._channel.connect('').send('ensurePermissions', {}).catch(e => void e);410  }411  async addScriptToEvaluateOnNewDocument(script) {412    await this._channel.connect('').send('addScriptToEvaluateOnNewDocument', script).catch(e => void e);413  }414  async addBinding(name, script) {415    await this._channel.connect('').send('addBinding', { name, script }).catch(e => void e);416  }417  async applyContextSetting(name, value) {418    await this._channel.connect('').send('applyContextSetting', { name, value }).catch(e => void e);419  }420  async hasFailedToOverrideTimezone() {421    return await this._channel.connect('').send('hasFailedToOverrideTimezone').catch(e => true);422  }423  async _startVideoRecording({width, height, scale, dir}) {424    // On Mac the window may not yet be visible when TargetCreated and its425    // NSWindow.windowNumber may be -1, so we wait until the window is known426    // to be initialized and visible.427    await this.windowReady();428    const file = OS.Path.join(dir, helper.generateId() + '.webm');429    if (width < 10 || width > 10000 || height < 10 || height > 10000)430      throw new Error("Invalid size");431    if (scale && (scale <= 0 || scale > 1))432      throw new Error("Unsupported scale");433    const screencast = Cc['@mozilla.org/juggler/screencast;1'].getService(Ci.nsIScreencastService);434    const docShell = this._gBrowser.ownerGlobal.docShell;435    // Exclude address bar and navigation control from the video.436    const rect = this.linkedBrowser().getBoundingClientRect();437    const devicePixelRatio = this._window.devicePixelRatio;438    const videoSessionId = screencast.startVideoRecording(docShell, file, width, height, scale || 0, devicePixelRatio * rect.top);439    this._screencastInfo = { videoSessionId, file };440    this.emit(PageTarget.Events.ScreencastStarted);441  }442  async _stopVideoRecording() {443    if (!this._screencastInfo)444      throw new Error('No video recording in progress');445    const screencastInfo = this._screencastInfo;446    this._screencastInfo = undefined;447    const screencast = Cc['@mozilla.org/juggler/screencast;1'].getService(Ci.nsIScreencastService);448    const result = new Promise(resolve =>449      Services.obs.addObserver(function onStopped(subject, topic, data) {450        if (screencastInfo.videoSessionId != data)451          return;452        Services.obs.removeObserver(onStopped, 'juggler-screencast-stopped');453        resolve();454      }, 'juggler-screencast-stopped')455    );456    screencast.stopVideoRecording(screencastInfo.videoSessionId);457    return result;458  }459  screencastInfo() {460    return this._screencastInfo;461  }462  dispose() {463    this._disposed = true;464    if (this._screencastInfo)465      this._stopVideoRecording().catch(e => dump(`stopVideoRecording failed:\n${e}\n`));466    this._browserContext.pages.delete(this);467    this._registry._browserToTarget.delete(this._linkedBrowser);468    this._registry._browserBrowsingContextToTarget.delete(this._linkedBrowser.browsingContext);469    try {470      helper.removeListeners(this._eventListeners);471    } catch (e) {472      // In some cases, removing listeners from this._linkedBrowser fails473      // because it is already half-destroyed.474      if (e)475        dump(e.message + '\n' + e.stack + '\n');476    }477    this._registry.emit(TargetRegistry.Events.TargetDestroyed, this);478  }479}480PageTarget.Events = {481  ScreencastStarted: Symbol('PageTarget.ScreencastStarted'),482  Crashed: Symbol('PageTarget.Crashed'),483  DialogOpened: Symbol('PageTarget.DialogOpened'),484  DialogClosed: Symbol('PageTarget.DialogClosed'),485};486function fromProtocolColorScheme(colorScheme) {487  if (colorScheme === 'light' || colorScheme === 'dark')488    return colorScheme;489  if (colorScheme === null || colorScheme === 'no-preference')490    return undefined;491  throw new Error('Unknown color scheme: ' + colorScheme);492}493class BrowserContext {494  constructor(registry, browserContextId, removeOnDetach) {495    this._registry = registry;496    this.browserContextId = browserContextId;497    // Default context has userContextId === 0, but we pass undefined to many APIs just in case.498    this.userContextId = 0;499    if (browserContextId !== undefined) {500      const identity = ContextualIdentityService.create(IDENTITY_NAME + browserContextId);501      this.userContextId = identity.userContextId;502    }503    this._principals = [];504    // Maps origins to the permission lists.505    this._permissions = new Map();506    this._registry._browserContextIdToBrowserContext.set(this.browserContextId, this);507    this._registry._userContextIdToBrowserContext.set(this.userContextId, this);508    this._proxy = null;509    this.removeOnDetach = removeOnDetach;510    this.extraHTTPHeaders = undefined;511    this.httpCredentials = undefined;512    this.requestInterceptionEnabled = undefined;513    this.ignoreHTTPSErrors = undefined;514    this.downloadOptions = undefined;515    this.defaultViewportSize = undefined;516    this.deviceScaleFactor = undefined;517    this.defaultUserAgent = null;518    this.touchOverride = false;519    this.colorScheme = 'none';520    this.screencastOptions = undefined;521    this.scriptsToEvaluateOnNewDocument = [];522    this.bindings = [];523    this.settings = {};524    this.pages = new Set();525  }526  setColorScheme(colorScheme) {527    this.colorScheme = fromProtocolColorScheme(colorScheme);528    for (const page of this.pages)529      page.updateColorSchemeOverride();530  }531  async destroy() {532    if (this.userContextId !== 0) {533      ContextualIdentityService.remove(this.userContextId);534      for (const page of this.pages)535        page.close();536      if (this.pages.size) {537        await new Promise(f => {538          const listener = helper.on(this._registry, TargetRegistry.Events.TargetDestroyed, () => {539            if (!this.pages.size) {540              helper.removeListeners([listener]);541              f();542            }543          });544        });545      }546    }547    this._registry._browserContextIdToBrowserContext.delete(this.browserContextId);548    this._registry._userContextIdToBrowserContext.delete(this.userContextId);549  }550  setProxy(proxy) {551    // Clear AuthCache.552    Services.obs.notifyObservers(null, "net:clear-active-logins");553    this._proxy = proxy;554  }555  setIgnoreHTTPSErrors(ignoreHTTPSErrors) {556    if (this.ignoreHTTPSErrors === ignoreHTTPSErrors)557      return;558    this.ignoreHTTPSErrors = ignoreHTTPSErrors;559    const certOverrideService = Cc[560      "@mozilla.org/security/certoverride;1"561    ].getService(Ci.nsICertOverrideService);562    if (ignoreHTTPSErrors) {563      Preferences.set("network.stricttransportsecurity.preloadlist", false);564      Preferences.set("security.cert_pinning.enforcement_level", 0);565      certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(true, this.userContextId);566    } else {567      certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(false, this.userContextId);568    }569  }570  async setDefaultUserAgent(userAgent) {571    this.defaultUserAgent = userAgent;572    for (const page of this.pages)573      page.updateUserAgent();574  }575  setTouchOverride(touchOverride) {576    this.touchOverride = touchOverride;577    for (const page of this.pages)578      page.updateTouchOverride();579  }580  async setDefaultViewport(viewport) {581    this.defaultViewportSize = viewport ? viewport.viewportSize : undefined;582    this.deviceScaleFactor = viewport ? viewport.deviceScaleFactor : undefined;583    await Promise.all(Array.from(this.pages).map(page => page.updateViewportSize()));584  }585  async addScriptToEvaluateOnNewDocument(script) {586    this.scriptsToEvaluateOnNewDocument.push(script);587    await Promise.all(Array.from(this.pages).map(page => page.addScriptToEvaluateOnNewDocument(script)));588  }589  async addBinding(name, script) {590    this.bindings.push({ name, script });591    await Promise.all(Array.from(this.pages).map(page => page.addBinding(name, script)));592  }593  async applySetting(name, value) {594    this.settings[name] = value;595    await Promise.all(Array.from(this.pages).map(page => page.applyContextSetting(name, value)));596  }597  async grantPermissions(origin, permissions) {598    this._permissions.set(origin, permissions);599    const promises = [];600    for (const page of this.pages) {601      if (origin === '*' || page._url.startsWith(origin)) {602        this.grantPermissionsToOrigin(page._url);603        promises.push(page.ensurePermissions());604      }605    }606    await Promise.all(promises);607  }608  resetPermissions() {609    for (const principal of this._principals) {610      for (const permission of ALL_PERMISSIONS)611        Services.perms.removeFromPrincipal(principal, permission);612    }613    this._principals = [];614    this._permissions.clear();615  }616  grantPermissionsToOrigin(url) {617    let origin = Array.from(this._permissions.keys()).find(key => url.startsWith(key));618    if (!origin)619      origin = '*';620    const permissions = this._permissions.get(origin);621    if (!permissions)622      return;623    const attrs = { userContextId: this.userContextId || undefined };624    const principal = Services.scriptSecurityManager.createContentPrincipal(NetUtil.newURI(url), attrs);625    this._principals.push(principal);626    for (const permission of ALL_PERMISSIONS) {627      const action = permissions.includes(permission) ? Ci.nsIPermissionManager.ALLOW_ACTION : Ci.nsIPermissionManager.DENY_ACTION;628      Services.perms.addFromPrincipal(principal, permission, action, Ci.nsIPermissionManager.EXPIRE_NEVER, 0 /* expireTime */);629    }630  }631  setCookies(cookies) {632    const protocolToSameSite = {633      [undefined]: Ci.nsICookie.SAMESITE_NONE,634      'Lax': Ci.nsICookie.SAMESITE_LAX,635      'Strict': Ci.nsICookie.SAMESITE_STRICT,636    };637    for (const cookie of cookies) {638      const uri = cookie.url ? NetUtil.newURI(cookie.url) : null;639      let domain = cookie.domain;640      if (!domain) {641        if (!uri)642          throw new Error('At least one of the url and domain needs to be specified');643        domain = uri.host;644      }645      let path = cookie.path;646      if (!path)647        path = uri ? dirPath(uri.filePath) : '/';648      let secure = false;649      if (cookie.secure !== undefined)650        secure = cookie.secure;651      else if (uri && uri.scheme === 'https')652        secure = true;653      Services.cookies.add(654        domain,655        path,656        cookie.name,657        cookie.value,658        secure,659        cookie.httpOnly || false,660        cookie.expires === undefined || cookie.expires === -1 /* isSession */,661        cookie.expires === undefined ? Date.now() + HUNDRED_YEARS : cookie.expires,662        { userContextId: this.userContextId || undefined } /* originAttributes */,663        protocolToSameSite[cookie.sameSite],664        Ci.nsICookie.SCHEME_UNSET665      );666    }667  }668  clearCookies() {669    Services.cookies.removeCookiesWithOriginAttributes(JSON.stringify({ userContextId: this.userContextId || undefined }));670  }671  getCookies() {672    const result = [];673    const sameSiteToProtocol = {674      [Ci.nsICookie.SAMESITE_NONE]: 'None',675      [Ci.nsICookie.SAMESITE_LAX]: 'Lax',676      [Ci.nsICookie.SAMESITE_STRICT]: 'Strict',677    };678    for (let cookie of Services.cookies.cookies) {679      if (cookie.originAttributes.userContextId !== this.userContextId)680        continue;681      if (cookie.host === 'addons.mozilla.org')682        continue;683      result.push({684        name: cookie.name,685        value: cookie.value,686        domain: cookie.host,687        path: cookie.path,688        expires: cookie.isSession ? -1 : cookie.expiry,689        size: cookie.name.length + cookie.value.length,690        httpOnly: cookie.isHttpOnly,691        secure: cookie.isSecure,692        session: cookie.isSession,693        sameSite: sameSiteToProtocol[cookie.sameSite],694      });695    }696    return result;697  }698  async setScreencastOptions(options) {699    this.screencastOptions = options;700    if (!options)701      return;702    const promises = [];703    for (const page of this.pages)704      promises.push(page._startVideoRecording(options));705    await Promise.all(promises);706  }707}708class Dialog {709  static createIfSupported(prompt) {710    const type = prompt.args.promptType;711    switch (type) {712      case 'alert':713      case 'alertCheck':714        return new Dialog(prompt, 'alert');715      case 'prompt':716        return new Dialog(prompt, 'prompt');717      case 'confirm':718      case 'confirmCheck':719        return new Dialog(prompt, 'confirm');720      case 'confirmEx':721        return new Dialog(prompt, 'beforeunload');722      default:723        return null;724    };725  }726  constructor(prompt, type) {727    this._id = helper.generateId();728    this._type = type;729    this._prompt = prompt;730  }731  id() {732    return this._id;733  }734  message() {735    return this._prompt.ui.infoBody.textContent;736  }737  type() {738    return this._type;739  }740  prompt() {741    return this._prompt;742  }743  dismiss() {744    if (this._prompt.ui.button1)745      this._prompt.ui.button1.click();746    else747      this._prompt.ui.button0.click();748  }749  defaultValue() {750    return this._prompt.ui.loginTextbox.value;751  }752  accept(promptValue) {753    if (typeof promptValue === 'string' && this._type === 'prompt')754      this._prompt.ui.loginTextbox.value = promptValue;755    this._prompt.ui.button0.click();756  }757}758function dirPath(path) {759  return path.substring(0, path.lastIndexOf('/') + 1);760}761async function waitForWindowReady(window) {762  if (window.delayedStartupPromise) {763    await window.delayedStartupPromise;764  } else {765    await new Promise((resolve => {766      Services.obs.addObserver(function observer(aSubject, aTopic) {767        if (window == aSubject) {768          Services.obs.removeObserver(observer, aTopic);769          resolve();770        }771      }, "browser-delayed-startup-finished");772    }));773  }774  if (window.document.readyState !== 'complete')775    await helper.awaitEvent(window, 'load');776}777async function setViewportSizeForBrowser(viewportSize, browser, window) {778  await waitForWindowReady(window);779  if (viewportSize) {780    const {width, height} = viewportSize;781    const rect = browser.getBoundingClientRect();782    window.resizeBy(width - rect.width, height - rect.height);783    browser.style.setProperty('min-width', width + 'px');784    browser.style.setProperty('min-height', height + 'px');785    browser.style.setProperty('max-width', width + 'px');786    browser.style.setProperty('max-height', height + 'px');787  } else {788    browser.style.removeProperty('min-width');789    browser.style.removeProperty('min-height');790    browser.style.removeProperty('max-width');791    browser.style.removeProperty('max-height');792  }793  const rect = browser.getBoundingClientRect();794  return { width: rect.width, height: rect.height };795}796TargetRegistry.Events = {797  TargetCreated: Symbol('TargetRegistry.Events.TargetCreated'),798  TargetDestroyed: Symbol('TargetRegistry.Events.TargetDestroyed'),799  DownloadCreated: Symbol('TargetRegistry.Events.DownloadCreated'),800  DownloadFinished: Symbol('TargetRegistry.Events.DownloadFinished'),801};802var EXPORTED_SYMBOLS = ['TargetRegistry', 'PageTarget'];803this.TargetRegistry = TargetRegistry;...PageAgent.js
Source:PageAgent.js  
...18    const docShell = frameTree.mainFrame().docShell();19    this._initialDPPX = docShell.contentViewer.overrideDPPX;20    this._customScrollbars = null;21  }22  async awaitViewportDimensions({width, height}) {23    const win = this._frameTree.mainFrame().domWindow();24    if (win.innerWidth === width && win.innerHeight === height)25      return;26    await new Promise(resolve => {27      const listener = helper.addEventListener(win, 'resize', () => {28        if (win.innerWidth === width && win.innerHeight === height) {29          helper.removeListeners([listener]);30          resolve();31        }32      });33    });34  }35  async setViewport({deviceScaleFactor, isMobile, hasTouch}) {36    const docShell = this._frameTree.mainFrame().docShell();...PageHandler.jsm
Source:PageHandler.jsm  
1"use strict";2const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');3const Cc = Components.classes;4const Ci = Components.interfaces;5const Cu = Components.utils;6const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';7const FRAME_SCRIPT = "chrome://juggler/content/content/ContentSession.js";8const helper = new Helper();9class PageHandler {10  constructor(chromeSession, tab) {11    this._pageId = helper.generateId();12    this._chromeSession = chromeSession;13    this._tab = tab;14    this._browser = tab.linkedBrowser;15    this._enabled = false;16    this.QueryInterface = ChromeUtils.generateQI([17      Ci.nsIWebProgressListener,18      Ci.nsISupportsWeakReference,19    ]);20    this._browser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);21    this._dialogs = new Map();22    // First navigation always happens to about:blank - do not report it.23    this._skipNextNavigation = true;24  }25  async setViewport({viewport}) {26    if (viewport) {27      const {width, height} = viewport;28      this._browser.style.setProperty('min-width', width + 'px');29      this._browser.style.setProperty('min-height', height + 'px');30      this._browser.style.setProperty('max-width', width + 'px');31      this._browser.style.setProperty('max-height', height + 'px');32    } else {33      this._browser.style.removeProperty('min-width');34      this._browser.style.removeProperty('min-height');35      this._browser.style.removeProperty('max-width');36      this._browser.style.removeProperty('max-height');37    }38    const dimensions = this._browser.getBoundingClientRect();39    await Promise.all([40      this._contentSession.send('setViewport', {41        deviceScaleFactor: viewport ? viewport.deviceScaleFactor : 0,42        isMobile: viewport && viewport.isMobile,43        hasTouch: viewport && viewport.hasTouch,44      }),45      this._contentSession.send('awaitViewportDimensions', {46        width: dimensions.width,47        height: dimensions.height48      }),49    ]);50  }51  _initializeDialogEvents() {52    this._browser.addEventListener('DOMWillOpenModalDialog', async (event) => {53      // wait for the dialog to be actually added to DOM.54      await Promise.resolve();55      this._updateModalDialogs();56    });57    this._browser.addEventListener('DOMModalDialogClosed', (event) => {58      this._updateModalDialogs();59    });60    this._updateModalDialogs();61  }62  _updateModalDialogs() {63    const elements = new Set(this._browser.parentNode.getElementsByTagNameNS(XUL_NS, "tabmodalprompt"));64    for (const dialog of this._dialogs.values()) {65      if (!elements.has(dialog.element())) {66        this._dialogs.delete(dialog.id());67        this._chromeSession.emitEvent('Page.dialogClosed', {68          pageId: this._pageId,69          dialogId: dialog.id(),70        });71      } else {72        elements.delete(dialog.element());73      }74    }75    for (const element of elements) {76      const dialog = Dialog.createIfSupported(element);77      if (!dialog)78        continue;79      this._dialogs.set(dialog.id(), dialog);80      this._chromeSession.emitEvent('Page.dialogOpened', {81        pageId: this._pageId,82        dialogId: dialog.id(),83        type: dialog.type(),84        message: dialog.message(),85        defaultValue: dialog.defaultValue(),86      });87    }88  }89  onLocationChange(aWebProgress, aRequest, aLocation) {90    if (this._skipNextNavigation) {91      this._skipNextNavigation = false;92      return;93    }94    this._chromeSession.emitEvent('Browser.tabNavigated', {95      pageId: this._pageId,96      url: aLocation.spec97    });98  }99  url() {100    return this._browser.currentURI.spec;101  }102  tab() {103    return this._tab;104  }105  id() {106    return this._pageId;107  }108  async enable() {109    if (this._enabled)110      return;111    this._enabled = true;112    this._initializeDialogEvents();113    this._contentSession = new ContentSession(this._chromeSession, this._browser, this._pageId);114    await this._contentSession.send('enable');115  }116  async screenshot(options) {117    return await this._contentSession.send('screenshot', options);118  }119  async getBoundingBox(options) {120    return await this._contentSession.send('getBoundingBox', options);121  }122  async getContentQuads(options) {123    return await this._contentSession.send('getContentQuads', options);124  }125  /**126   * @param {{frameId: string, url: string}} options127   */128  async navigate(options) {129    return await this._contentSession.send('navigate', options);130  }131  /**132   * @param {{frameId: string, url: string}} options133   */134  async goBack(options) {135    return await this._contentSession.send('goBack', options);136  }137  /**138   * @param {{frameId: string, url: string}} options139   */140  async goForward(options) {141    return await this._contentSession.send('goForward', options);142  }143  /**144   * @param {{frameId: string, url: string}} options145   */146  async reload(options) {147    return await this._contentSession.send('reload', options);148  }149  /**150   * @param {{functionText: String, frameId: String}} options151   * @return {!Promise<*>}152   */153  async evaluate(options) {154    return await this._contentSession.send('evaluate', options);155  }156  async getObjectProperties(options) {157    return await this._contentSession.send('getObjectProperties', options);158  }159  async addScriptToEvaluateOnNewDocument(options) {160    return await this._contentSession.send('addScriptToEvaluateOnNewDocument', options);161  }162  async removeScriptToEvaluateOnNewDocument(options) {163    return await this._contentSession.send('removeScriptToEvaluateOnNewDocument', options);164  }165  async disposeObject(options) {166    return await this._contentSession.send('disposeObject', options);167  }168  async dispatchKeyEvent(options) {169    return await this._contentSession.send('dispatchKeyEvent', options);170  }171  async dispatchMouseEvent(options) {172    return await this._contentSession.send('dispatchMouseEvent', options);173  }174  async insertText(options) {175    return await this._contentSession.send('insertText', options);176  }177  async handleDialog({dialogId, accept, promptText}) {178    const dialog = this._dialogs.get(dialogId);179    if (!dialog)180      throw new Error('Failed to find dialog with id = ' + dialogId);181    if (accept)182      dialog.accept(promptText);183    else184      dialog.dismiss();185  }186  dispose() {187    this._browser.removeProgressListener(this);188    if (this._contentSession) {189      this._contentSession.dispose();190      this._contentSession = null;191    }192  }193}194class ContentSession {195  constructor(chromeSession, browser, pageId) {196    this._chromeSession = chromeSession;197    this._browser = browser;198    this._pageId = pageId;199    this._messageId = 0;200    this._pendingMessages = new Map();201    this._sessionId = helper.generateId();202    this._browser.messageManager.sendAsyncMessage('juggler:create-content-session', this._sessionId);203    this._eventListeners = [204      helper.addMessageListener(this._browser.messageManager, this._sessionId, {205        receiveMessage: message => this._onMessage(message)206      }),207    ];208  }209  dispose() {210    helper.removeListeners(this._eventListeners);211    for (const {resolve, reject} of this._pendingMessages.values())212      reject(new Error('Page closed.'));213    this._pendingMessages.clear();214  }215  /**216   * @param {string} methodName217   * @param {*} params218   * @return {!Promise<*>}219   */220  send(methodName, params) {221    const id = ++this._messageId;222    const promise = new Promise((resolve, reject) => {223      this._pendingMessages.set(id, {resolve, reject});224    });225    this._browser.messageManager.sendAsyncMessage(this._sessionId, {id, methodName, params});226    return promise;227  }228  _onMessage({data}) {229    if (data.id) {230      let id = data.id;231      const {resolve, reject} = this._pendingMessages.get(data.id);232      this._pendingMessages.delete(data.id);233      if (data.error)234        reject(new Error(data.error));235      else236        resolve(data.result);237    } else {238      const {239        eventName,240        params = {}241      } = data;242      params.pageId = this._pageId;243      this._chromeSession.emitEvent(eventName, params);244    }245  }246}247class Dialog {248  static createIfSupported(element) {249    const type = element.Dialog.args.promptType;250    switch (type) {251      case 'alert':252      case 'prompt':253      case 'confirm':254        return new Dialog(element, type);255      case 'confirmEx':256        return new Dialog(element, 'beforeunload');257      default:258        return null;259    };260  }261  constructor(element, type) {262    this._id = helper.generateId();263    this._type = type;264    this._element = element;265  }266  id() {267    return this._id;268  }269  message() {270    return this._element.ui.infoBody.textContent;271  }272  type() {273    return this._type;274  }275  element() {276    return this._element;277  }278  dismiss() {279    if (this._element.ui.button1)280      this._element.ui.button1.click();281    else282      this._element.ui.button0.click();283  }284  defaultValue() {285    return this._element.ui.loginTextbox.value;286  }287  accept(promptValue) {288    if (typeof promptValue === 'string' && this._type === 'prompt')289      this._element.ui.loginTextbox.value = promptValue;290    this._element.ui.button0.click();291  }292}293var EXPORTED_SYMBOLS = ['PageHandler'];...main.js
Source:main.js  
...95    },96    hasFailedToOverrideTimezone() {97      return failedToOverrideTimezone;98    },99    async awaitViewportDimensions({width, height, deviceSizeIsPageSize}) {100      docShell.deviceSizeIsPageSize = deviceSizeIsPageSize;101      const win = docShell.domWindow;102      if (win.innerWidth === width && win.innerHeight === height)103        return;104      await new Promise(resolve => {105        const listener = helper.addEventListener(win, 'resize', () => {106          if (win.innerWidth === width && win.innerHeight === height) {107            helper.removeListeners([listener]);108            resolve();109          }110        });111      });112    },113    dispose() {...Using AI Code Generation
1const playwright = require('playwright');2(async () => {3  for (const browserType of ['chromium', 'firefox', 'webkit']) {4    const browser = await playwright[browserType].launch();5    const context = await browser.newContext();6    const page = await context.newPage();7    await page.waitForSelector('h1');8    const dimensions = await page._client.send('Emulation.getVisibleSize');9    console.log(dimensions);10    await browser.close();11  }12})();13{ width: 800, height: 600 }14{ width: 800, height: 600 }15{ width: 800, height: 600 }Using AI Code Generation
1const playwright = require('playwright');2(async () => {3  const browser = await playwright['chromium'].launch();4  const context = await browser.newContext();5  const page = await context.newPage();6  await page.waitForLoadState('networkidle');7  const dimensions = await page.evaluate(() => {8    return {9    };10  });11  console.log('Dimensions:', dimensions);12  await browser.close();13})();14const playwright = require('playwright');15(async () => {16  const browser = await playwright['chromium'].launch();17  const context = await browser.newContext();18  const page = await context.newPage();19  await page.waitForLoadState('networkidle');20  await page.waitForViewportDimensions({21  });22  const dimensions = await page.evaluate(() => {23    return {24    };25  });26  console.log('Dimensions:', dimensions);27  await browser.close();28})();29const playwright = require('playwright');30(async () => {31  const browser = await playwright['chromium'].launch();32  const context = await browser.newContext();33  const page = await context.newPage();34  await page.waitForLoadState('networkidle');35  await page.waitForViewportDimensions({36  });37  const dimensions = await page.evaluate(() => {38    return {39    };40  });41  console.log('Dimensions:', dimensions);42  await browser.close();43})();44const playwright = require('playwright');45(async () => {46  const browser = await playwright['chromium'].launch();47  const context = await browser.newContext();Using AI Code Generation
1const { chromium } = require('playwright');2(async () => {3  const browser = await chromium.launch();4  const context = await browser.newContext();5  const page = await context.newPage();6  const { width, height } = await page._delegate.awaitViewportDimensions();7  console.log(width, height);8  await browser.close();9})();Using AI Code Generation
1const playwright = require('playwright');2(async () => {3  const browser = await playwright.chromium.launch();4  const context = await browser.newContext();5  const page = await context.newPage();6  const dimensions = await page._delegate._mainFrameSession._client.send('Emulation.getVisibleSize');7  console.log(dimensions);8  await browser.close();9})();10Output: { width: 800, height: 600 }Using AI Code Generation
1const { chromium } = require('playwright');2(async () => {3  const browser = await chromium.launch();4  const page = await browser.newPage();5  await page.setViewportSize({ width: 800, height: 600 });6  await page.screenshot({ path: 'example.png' });7  await browser.close();8})();9import { PlaywrightTestConfig } from '@playwright/test';10const config: PlaywrightTestConfig = {11  use: {12    viewport: { width: 800, height: 600 },13  },14};15export default config;16const { chromium } = require('playwright');17(async () => {18  const browser = await chromium.launch();19  const page = await browser.newPage();20  await page.setViewportSize({ width: 800, height: 600 });21  await page.screenshot({ path: 'example.png' });22  await browser.close();23})();24import { PlaywrightTestConfig } from '@playwright/test';25const config: PlaywrightTestConfig = {26  use: {27    viewport: { width: 800, height: 600 },28  },29};30export default config;31import { PlaywrightTestConfig } from '@playwright/test';32const config: PlaywrightTestConfig = {33  use: {34    viewport: { width: 800, height: 600 },Using AI Code Generation
1const { awaitViewportDimensions } = require('playwright/lib/server/browserContext');2const { chromium } = require('playwright');3(async () => {4  const browser = await chromium.launch();5  const context = await browser.newContext();6  const page = await context.newPage();7  const [width, height] = await awaitViewportDimensions.call(page);8  console.log(width, height);9  await browser.close();10})();Using AI Code Generation
1const { test, expect } = require('@playwright/test');2test('test', async ({ page }) => {3  await page.setViewportSize({ width: 1280, height: 720 });4  await page.waitForLoadState();5  const dimensions = await page._delegate._mainFrameSession._client.send('Emulation.getVisibleSize');6  expect(dimensions).toEqual({ width: 1280, height: 720 });7});8const { test, expect } = require('@playwright/test');9test('test', async ({ page }) => {10  await page.setViewportSize({ width: 1280, height: 720 });11  await page.waitForLoadState();12  const dimensions = await page.viewportSize();13  expect(dimensions).toEqual({ width: 1280, height: 720 });14});Using AI Code Generation
1const playwright = require("playwright");2const { getViewportDimensions } = require("playwright/lib/server/supplements/recorder/recorderSupplement.js");3(async () => {4  const browser = await playwright["chromium"].launch();5  const context = await browser.newContext();6  const page = await context.newPage();7  const dimensions = await getViewportDimensions(page);8  console.log(dimensions);9  await browser.close();10})();11{ width: 1280, height: 800 }12const playwright = require("playwright");13const { getViewportDimensions } = require("playwright/lib/server/supplements/recorder/recorderSupplement.js");14(async () => {15  const browser = await playwright["chromium"].launch();16  const context = await browser.newContext();17  const page = await context.newPage();18  const dimensions = await getViewportDimensions(page);19  console.log(dimensions);20  await browser.close();21})();22const playwright = require("playwright");23const { getViewportDimensions } = require("playwright/lib/server/supplements/recorder/recorderSupplement.js");24(async () => {25  const browser = await playwright["chromium"].launch();26  const context = await browser.newContext();27  const page = await context.newPage();28  const dimensions = await getViewportDimensions(page);29  console.log(dimensions);30  await page.close();31  await browser.close();32})();33const playwright = require("playwright");34const { getViewportDimensions } = require("playwright/lib/server/supplements/recorder/recorderSupplement.js");35(async () => {36  const browser = await playwright["chromium"].launch();37  const context = await browser.newContext();38  const page = await context.newPage();39  await page.setViewportSize({ width: 1600, height: 900 });40  const dimensions = await getViewportDimensions(page);41  console.log(dimensions);Using AI Code Generation
1const playwright = require('playwright');2const { chromium } = playwright;3const { devices } = require('playwright');4const iPhone = devices['iPhone 6'];5(async () => {6  const browser = await chromium.launch();7  const context = await browser.newContext({8    viewport: { width: 800, height: 600 },9  });10  const page = await context.newPage();11  await page.waitForTimeout(3000);12  const dimensions = await page.mainFrame().awaitViewportDimensions();13  console.log(dimensions);14  await browser.close();15})();Using AI Code Generation
1import { Page } from 'playwright';2import { InternalAPI } from 'playwright-core/lib/internal';3export async function getViewportDimensions(page: Page) {4  const internalAPI = new InternalAPI(page);5  const { width, height } = await internalAPI.awaitViewportDimensions();6  return { width, height };7}8import { getViewportDimensions } from './test.js';9const { width, height } = await getViewportDimensions(page);10import { Page } from 'playwright';11import { InternalAPI } from 'playwright-core/lib/internal';12export async function getViewportDimensions(page: Page) {13  const internalAPI = new InternalAPI(page);14  const { width, height } = await internalAPI.awaitViewportDimensions();15  return { width, height };16}17import { getViewportDimensions } from './test.js';18const { width, height } = await getViewportDimensions(page);19import { Page } from 'playwright';20import { InternalAPI } from 'playwright-core/lib/internal';21export async function getViewportDimensions(page: Page) {22  const internalAPI = new InternalAPI(page);23  const { width, height } = await internalAPI.awaitViewportDimensions();24  return { width, height };25}26import { getViewportDimensions } from './test.js';27const { width, height } = await getViewportDimensions(page);28import { Page } from 'playwright';29import { InternalAPI } from 'playwright-core/lib/internal';30export async function getViewportDimensions(page: Page) {31  const internalAPI = new InternalAPI(page);32  const { width, height } = await internalAPI.awaitViewportDimensions();33  return { width, height };34}35import { getViewportDimensions } from './test.js';36const { width, height } = await getViewportDimensions(page);LambdaTest’s Playwright tutorial will give you a broader idea about the Playwright automation framework, its unique features, and use cases with examples to exceed your understanding of Playwright testing. This tutorial will give A to Z guidance, from installing the Playwright framework to some best practices and advanced concepts.
Get 100 minutes of automation test minutes FREE!!
