【问题标题】:Selenium webdriver: firefox headless inject javascript to modify browser propertySelenium webdriver:firefox无头注入javascript来修改浏览器属性
【发布时间】:2018-12-28 13:53:51
【问题描述】:

我试图弄清楚如何使用 selenium webdriver 和 python 或 java 来注入 javascript 以修改浏览器属性/属性。 我的最终目标是使用 selenium 和 firefox 获得类似于 this 的内容,因为它是一个更开放和灵活的选择。

木偶和铬文件test.js

const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch({
    args: ["--no-sandbox"],
    headless: true,
  });
  const page = await browser.newPage();
  const fs = require("fs");

  // In your puppeteer script, assuming the javascriptChromium.js file is in same folder of our script
  const preloadFile = fs.readFileSync("./javascriptChromium.js", "utf8");
  await page.evaluateOnNewDocument(preloadFile);

  const testUrl="https://intoli.com/blog/not-possible-to-block-chrome-headless/chrome-headless-test.html";

  await page.goto(testUrl);

  // save screenshot
  await page.screenshot({path: "puppeteer-chromium-async-script-test.png"});
  await browser.close()
})();

Javascript 文件javascriptChromium.js

// overwrite the `languages` property to use a custom getter
Object.defineProperty(navigator, "languages", {
  get: function() {
    return ["en-US", "en", "es"];
  }
});

// Overwrite the `plugins` property to use a custom getter.
Object.defineProperty(navigator, 'plugins', {
  get: () => [1, 2, 3, 4, 5],
});

// Pass the Webdriver test
Object.defineProperty(navigator, 'webdriver', {
  get: () => false,
});

这段代码运行良好,我通过test Web site检查了属性是否已更改。

现在,硒和火狐:

import os
from selenium import webdriver

def readJSFile(scriptFile):
    with open(scriptFile, 'r') as fileHandle:  
        script=fileHandle.read()
    return script
injectedJavascript=readJSFile("./javascriptFirefox.js")

options=webdriver.FirefoxOptions()
options.set_headless(True)
driver=webdriver.Firefox(options=options)
driver.set_script_timeout(3)

# inject JavaScript
try:
    driver.execute_async_script(injectedJavascript)
except:
    print("Timeout")

# solution found here https://stackoverflow.com/questions/17385779/how-do-i-load-a-javascript-file-into-the-dom-using-selenium
driver.execute_script("var s=window.document.createElement('script'); s.src='javascriptFirefox.js';window.document.head.appendChild(s);")
testUrl="https://intoli.com/blog/not-possible-to-block-chrome-headless/chrome-headless-test.html";
driver.get(testUrl)

# example sync script
time=driver.execute_script("return performance.timing.loadEventEnd - performance.timing.navigationStart;")
print(time)
# example async script
time=driver.execute_async_script("var callback = arguments[arguments.length-1]; const time = () => { total=performance.timing.loadEventEnd - performance.timing.navigationStart; callback(total); }; time();")
print(time)

file="selenium-firefox-async-script-test.png"
driver.save_screenshot(file)

driver.quit()

Javascript 文件javascriptFirefox.js

// overwrite the `languages` property to use a custom getter
const setProperty = () => {
    Object.defineProperty(navigator, "languages", {
        get: function() {
            return ["en-US", "en", "es"];
        }
    });

    // Overwrite the `plugins` property to use a custom getter.
    Object.defineProperty(navigator, 'plugins', {
        get: () => [1, 2, 3, 4, 5],
    });

    // Pass the Webdriver test
    Object.defineProperty(navigator, 'webdriver', {
      get: () => false,
    });
    callback();
};
setProperty();

我是 javascript 新手,但是这两种方法(puppeteer 和 selenium)之间的不同之处在于它们如何管理当前选项卡/页面。前者通过页面类和方法page.evaluateOnNewDocument 而对于后者我没有找到和等效的方法。 我也尝试过使用greasemonkey或暴力猴子注入javascript但没有成功。

你有什么建议吗?

谢谢

【问题讨论】:

    标签: javascript python firefox selenium-webdriver firefox-addon


    【解决方案1】:

    我通过关注post 找到了问题的解决方案。 简而言之,通过使用扩展,也可以使用 firefox 将 javascript 代码注入 Web 页面。 为了避免浪费其他用户的时间,主要文件有:

    Python 文件: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()
    

    扩展文件:manifest.js 和 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 文件: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);
    };
    

    这适用于图形模式下的所有测试,而无头模式下的所有测试除了 WebGL 测试似乎对bug 有影响。

    【讨论】:

      猜你喜欢
      • 2017-07-26
      • 1970-01-01
      • 2014-08-29
      • 2021-09-15
      • 2018-11-15
      • 2011-11-26
      • 2013-06-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多