Selenium webdriver: firefox headless inject javascript to modify browser property

后端 未结 1 1215
离开以前
离开以前 2021-02-02 02:59

I\'m trying to figure out how is possible to use selenium webdriver with python or java to inject javascript in order to modify browser property/attribute. My final object is to

1条回答
  •  误落风尘
    2021-02-02 03:40

    I found the solution to the problem by following this post. In few words, by using an extensions it is possible to inject javascript code into the Web page also with firefox. In order to avoid a waste of time for the other users, the main files are:

    Python file: selenium+firefox

    import json
    import os
    import sys
    
    from selenium import webdriver
    from selenium.common.exceptions import NoSuchElementException
    from selenium.webdriver.firefox.firefox_profile import AddonFormatError
    
    # Patch in support for WebExtensions in Firefox.
    # See: https://intoli.com/blog/firefox-extensions-with-selenium/
    class FirefoxProfileWithWebExtensionSupport(webdriver.FirefoxProfile):
        def _addon_details(self, addon_path):
            try:
                return super()._addon_details(addon_path)
            except AddonFormatError:
                try:
                    with open(os.path.join(addon_path, "manifest.json"), "r") as f:
                        manifest = json.load(f)
                        return {
                            "id": manifest["applications"]["gecko"]["id"],
                            "version": manifest["version"],
                            "name": manifest["name"],
                            "unpack": False,
                        }
                except (IOError, KeyError) as e:
                    raise AddonFormatError(str(e), sys.exc_info()[2])
    
    profile_folder="profile_path"
    profile=FirefoxProfileWithWebExtensionSupport(profile_folder)
    extension_directory="extension"
    profile.add_extension(extension_directory)
    # firefox dev it is necessary for custom profile, not for standard one
    firefox_binary="/usr/bin/firefox-dev"
    options=webdriver.FirefoxOptions()
    # firefox 56+ headless mode https://developer.mozilla.org/en-US/Firefox/Headless_mode
    options.set_headless(True)
    driver=webdriver.Firefox(options=options, firefox_profile=profile, firefox_binary=firefox_binary)
    
    test_url="https://intoli.com/blog/not-possible-to-block-chrome-headless/chrome-headless-test.html";
    driver.get(test_url)
    
    file="selenium-firefox-extension-profile-script-second-test.png"
    driver.save_screenshot(file)
    
    test_url="https://intoli.com/blog/making-chrome-headless-undetectable/chrome-headless-test.html";
    driver.get(test_url)
    
    file="selenium-firefox-extension-profile-script-first-test.png"
    driver.save_screenshot(file)
    
    driver.quit()
    

    Extensions files: manifest.js and content.js

    {
      "manifest_version": 2,
      "name": "Smart Extension",
      "version": "1.0.0",
      "applications": {
        "gecko": {
          "id": "user@protonmail.com"
        }
      },
      "content_scripts": [
        {
          "matches": ["*://*/*"],
          "js": ["content.js"],
          "run_at": "document_start"
        }
      ]
    }
    
    var script=document.createElement("script");
    script.src=browser.extension.getURL("myscript.js");
    script.async=false;
    document.documentElement.appendChild(script);
    

    Javascript file: myscript.js

    // overwrite the `languages` property to use a custom getter
    Object.defineProperty(navigator, "languages", {
      get: function() {
        return ["en", "es"];
      }
    });
    
    // Overwrite the `plugins` property to use a custom getter.
    Object.defineProperty(navigator, "plugins", {
      get: () => new Array(Math.floor(Math.random() * 6) + 1),
    });
    
    // Pass the Webdriver test
    Object.defineProperty(navigator, "webdriver", {
      get: () => false,
    });
    
    // hairline: store the existing descriptor
    const elementDescriptor=Object.getOwnPropertyDescriptor(HTMLElement.prototype, "offsetHeight");
    
    // redefine the property with a patched descriptor
    Object.defineProperty(HTMLDivElement.prototype, "offsetHeight", {
        ...elementDescriptor,
      get: function() {
        if (this.id === "modernizr") {
          return 1;
        }
        return elementDescriptor.get.apply(this);
      },
    });
    
    ["height", "width"].forEach(property => {
      // store the existing descriptor
      const imageDescriptor=Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, property);
    
      // redefine the property with a patched descriptor
      Object.defineProperty(HTMLImageElement.prototype, property, {
        ...imageDescriptor,
        get: function() {
          // return an arbitrary non-zero dimension if the image failed to load
          if (this.complete && this.naturalHeight == 0) {
            return 24;
          }
          // otherwise, return the actual dimension
          return imageDescriptor.get.apply(this);
        },
      });
    });
    
    const getParameter=WebGLRenderingContext.getParameter;
    WebGLRenderingContext.prototype.getParameter=function(parameter) {
      // UNMASKED_VENDOR_WEBGL WebGLRenderingContext.prototype.VENDOR
      if (parameter === 37445) {
        return "Intel Open Source Technology Center";
      }
      // UNMASKED_RENDERER_WEBGL WebGLRenderingContext.prototype.RENDERER
      if (parameter === 37446) { 
        return "Mesa DRI Intel(R) Ivybridge Mobile";
      }
      return getParameter(parameter);
    };
    

    This works well for all tests in graphical mode while in headless mode all tests except WebGL test which seems affect to a bug.

    0 讨论(0)
提交回复
热议问题