如何防止 Selenium 3.0 (Geckodriver) 创建临时 Firefox 配置文件?

时间:2021-05-16 20:53:03

标签: java selenium selenium-webdriver geckodriver

我正在使用 Geckodriver 运行最新版本的 Selenium WebDriver。我想阻止 Selenium 在启动 WebDriver 的新实例时在临时文件目录中创建临时 Firefox 配置文件。相反,我想直接使用原始的 Firefox 配置文件。这有双重好处。首先,它节省了时间(将配置文件复制到临时目录需要花费大量时间)。其次,它确保在会话期间创建的 cookie 保存到原始配置文件中。在 Selenium 开始依赖 Geckodriver 之前,我能够通过编辑 FirefoxProfile.class 中的 SeleniumHQ 类来solve this problem,如下所示:

public File layoutOnDisk() {

 File profileDir;

 if (this.disableTempProfileCreation) {
  profileDir = this.model;
  return profileDir;

  } else {

   try {
    profileDir = TemporaryFilesystem.getDefaultTmpFS().createTempDir("ABC", "XYZ");
    File userPrefs = new File(profileDir, "user.js");
    this.copyModel(this.model, profileDir);
    this.installExtensions(profileDir);
    this.deleteLockFiles(profileDir);
    this.deleteExtensionsCacheIfItExists(profileDir);
    this.updateUserPrefs(userPrefs);
    return profileDir;
    } catch (IOException var3) {
   throw new UnableToCreateProfileException(var3);
  }
 }
}

当参数 disableTempProfileCreation 设置为 true 时,这将阻止 Selenium 创建临时 Firefox 配置文件。

但是,现在 Selenium 由 Geckodriver 控制,此解决方案不再有效,因为 Firefox Profile 的创建(和启动)由 Geckodriver.exe(写在 {{ 1}} 语言)。如何使用 Geckodriver 实现相同的目标?我不介意编辑源代码。我正在使用 Java。

谢谢

重要更新:

感谢大家抽出时间回答这个问题。但是,正如一些评论中所述,前 3 个答案根本没有解决问题 - 有两个原因。首先,使用现有的 Firefox 配置文件不会阻止 Rust 将原始配置文件复制到临时目录(如 OP 中所示并由以下一位或多位评论员明确说明)。其次,即使这样做了,它也不兼容 Selenium 3.0。

我真的不知道为什么 4 个答案中有 3 个会以完全相同的错误重复完全相同的答案。他们是否阅读了问题?即使试图解决手头问题的唯一答案是@Life 的答案很复杂,但它并不完整。谢谢。

3 个答案:

答案 0 :(得分:3)

更新帖子 05-30-2021


这是我在 Stack Overflow 上尝试回答的最难的问题。因为它涉及用多种语言(Java、Rust 和 C++)编写的多个代码库的交互。这种复杂性使问题可能无法解决。

我对这个可能无法解决的问题的最后一次破解:

在您问题中的代码中,您正在修改文件 user.js 该文件仍由 Selenium 使用。

public FirefoxProfile() {
    this(null);
  }

  /**
   * Constructs a firefox profile from an existing profile directory.
   * <p>
   * Users who need this functionality should consider using a named profile.
   *
   * @param profileDir The profile directory to use as a model.
   */
  public FirefoxProfile(File profileDir) {
    this(null, profileDir);
  }

  @Beta
  protected FirefoxProfile(Reader defaultsReader, File profileDir) {
    if (defaultsReader == null) {
      defaultsReader = onlyOverrideThisIfYouKnowWhatYouAreDoing();
    }

    additionalPrefs = new Preferences(defaultsReader);

    model = profileDir;
    verifyModel(model);

    File prefsInModel = new File(model, "user.js");
    if (prefsInModel.exists()) {
      StringReader reader = new StringReader("{\"frozen\": {}, \"mutable\": {}}");
      Preferences existingPrefs = new Preferences(reader, prefsInModel);
      acceptUntrustedCerts = getBooleanPreference(existingPrefs, ACCEPT_UNTRUSTED_CERTS_PREF, true);
      untrustedCertIssuer = getBooleanPreference(existingPrefs, ASSUME_UNTRUSTED_ISSUER_PREF, true);
      existingPrefs.addTo(additionalPrefs);
    } else {
      acceptUntrustedCerts = true;
      untrustedCertIssuer = true;
    }

    // This is not entirely correct but this is not stored in the profile
    // so for now will always be set to false.
    loadNoFocusLib = false;

    try {
      defaultsReader.close();
    } catch (IOException e) {
      throw new WebDriverException(e);
    }
  }

因此理论上您应该能够修改 geckodriver 源代码中的 capabilities.rs。该文件包含 temp_dir

正如我在理论中所说的那样,因为当我查看 Firefox 源代码时,它temp_dir 遍布整个代码库。

原始帖子 05-26-2021


我不确定您是否可以阻止 Selenium 创建临时的 Firefox 配置文件。

来自gecko documents

“配置文件在系统临时文件夹中创建。这也是提供配置文件时提取编码配置文件的位置。默认情况下,geckodriver 将在此位置创建新配置文件。”

我目前看到的唯一解决方案是需要您修改 Geckodriver 源文件以防止创建临时文件夹/配置文件。

我目前正在查看源代码。这些文件可能是正确的,但我需要更多地查看源代码:

这里还有一些其他需要梳理的文件:

https://searchfox.org/mozilla-central/search?q=tempfile&path=


这看起来很有希望:

https://searchfox.org/mozilla-central/source/testing/geckodriver/doc/Profiles.md

"geckodriver 使用 [profiles] 来检测 Firefox 的行为。 用户通常会依赖 geckodriver 来生成一个临时的, 一次性配置文件。当 WebDriver 删除这些配置文件时 会话过期。

如果用户需要使用自定义的、准备好的配置文件, geckodriver 将修改配置文件以确保 正确的行为。请参阅下面的 [自动化首选项] 在这种情况下,用户定义的首选项优先。

自定义配置文件可以通过两种不同的方式提供:

1.通过将 --profile /some/location 附加到 [args 功能], 这将指示 geckodriver 使用配置文件就地;

我在尝试这样做时发现了这个问题:how do I use an existing profile in-place with Selenium Webdriver?

还有一个在 Github 上的 selenium 中提出的关于临时目录的问题。 https://github.com/SeleniumHQ/selenium/issues/8645


查看 geckodriver v0.29.1 的源代码,我发现了一个加载配置文件的文件。

来源:capabilities.rs

   fn load_profile(options: &Capabilities) -> WebDriverResult<Option<Profile>> {
        if let Some(profile_json) = options.get("profile") {
            let profile_base64 = profile_json.as_str().ok_or_else(|| {
                WebDriverError::new(ErrorStatus::InvalidArgument, "Profile is not a string")
            })?;
            let profile_zip = &*base64::decode(profile_base64)?;

            // Create an emtpy profile directory
            let profile = Profile::new()?;
            unzip_buffer(
                profile_zip,
                profile
                    .temp_dir
                    .as_ref()
                    .expect("Profile doesn't have a path")
                    .path(),
            )?;

            Ok(Some(profile))
        } else {
            Ok(None)
        }
    }

来源:marionette.rs

    fn start_browser(&mut self, port: u16, options: FirefoxOptions) -> WebDriverResult<()> {
        let binary = options.binary.ok_or_else(|| {
            WebDriverError::new(
                ErrorStatus::SessionNotCreated,
                "Expected browser binary location, but unable to find \
             binary in default location, no \
             'moz:firefoxOptions.binary' capability provided, and \
             no binary flag set on the command line",
            )
        })?;

        let is_custom_profile = options.profile.is_some();

        let mut profile = match options.profile {
            Some(x) => x,
            None => Profile::new()?,
        };

        self.set_prefs(port, &mut profile, is_custom_profile, options.prefs)
            .map_err(|e| {
                WebDriverError::new(
                    ErrorStatus::SessionNotCreated,
                    format!("Failed to set preferences: {}", e),
                )
            })?;

        let mut runner = FirefoxRunner::new(&binary, profile);

        runner.arg("--marionette");
        if self.settings.jsdebugger {
            runner.arg("--jsdebugger");
        }
        if let Some(args) = options.args.as_ref() {
            runner.args(args);
        }

        // https://developer.mozilla.org/docs/Environment_variables_affecting_crash_reporting
        runner
            .env("MOZ_CRASHREPORTER", "1")
            .env("MOZ_CRASHREPORTER_NO_REPORT", "1")
            .env("MOZ_CRASHREPORTER_SHUTDOWN", "1");

        let browser_proc = runner.start().map_err(|e| {
            WebDriverError::new(
                ErrorStatus::SessionNotCreated,
                format!("Failed to start browser {}: {}", binary.display(), e),
            )
        })?;
        self.browser = Some(Browser::Host(browser_proc));

        Ok(())
    }

    pub fn set_prefs(
        &self,
        port: u16,
        profile: &mut Profile,
        custom_profile: bool,
        extra_prefs: Vec<(String, Pref)>,
    ) -> WebDriverResult<()> {
        let prefs = profile.user_prefs().map_err(|_| {
            WebDriverError::new(
                ErrorStatus::UnknownError,
                "Unable to read profile preferences file",
            )
        })?;

        for &(ref name, ref value) in prefs::DEFAULT.iter() {
            if !custom_profile || !prefs.contains_key(name) {
                prefs.insert((*name).to_string(), (*value).clone());
            }
        }

        prefs.insert_slice(&extra_prefs[..]);

        if self.settings.jsdebugger {
            prefs.insert("devtools.browsertoolbox.panel", Pref::new("jsdebugger"));
            prefs.insert("devtools.debugger.remote-enabled", Pref::new(true));
            prefs.insert("devtools.chrome.enabled", Pref::new(true));
            prefs.insert("devtools.debugger.prompt-connection", Pref::new(false));
        }

        prefs.insert("marionette.log.level", logging::max_level().into());
        prefs.insert("marionette.port", Pref::new(port));

        prefs.write().map_err(|e| {
            WebDriverError::new(
                ErrorStatus::UnknownError,
                format!("Unable to write Firefox profile: {}", e),
            )
        })
    }
}

在查看 Gecko 源代码后,看起来 mozprofile::profile::Profile 来自 FireFox 而不是 geckodriver


当您迁移到 Selenium 4 时,您的配置文件似乎有问题。

参考:https://github.com/SeleniumHQ/selenium/issues/9417

对于 Selenium 4,我们已弃用配置文件,因为我们可以采取其他机制来加快启动速度。 请使用 Options 类来设置您需要的首选项,如果您需要使用插件,请使用 driver.install_addon("path/to/addon") 您可以通过 pip install selenium --pre

安装测试版 selenium 4

我在您的代码中注意到您正在写入 user.js,这是 FireFox 的自定义文件。您是否考虑过在 Gecko 之外手动创建这些文件?

你也看过mozprofile吗?

答案 1 :(得分:0)

如果未设置任何选项,默认情况下新驱动程序会创建一个新配置文件。要使用现有配置文件,一种方法是在创建 firefox 驱动程序之前设置系统属性 webdriver.firefox.profile。一个可以创建 firefox 驱动程序的小代码片段(假设您有 geckodriver 的位置和 firefox 配置文件):

System.setProperty("webdriver.gecko.driver","path_to_gecko_driver");
System.setProperty("webdriver.firefox.profile", "path_to_firefox_profile");
WebDriver driver = new FirefoxDriver();

您甚至可以使用 env 设置这些系统属性。变量并跳过在任何地方定义它们。

另一种方法是使用 FirefoxOptions 类,它允许您配置很多选项。首先,看看org.openqa.selenium.firefox.FirefoxDriverorg.openqa.selenium.firefox.FirefoxOptions。一个小例子:

FirefoxOptions options = new FirefoxOptions();
options.setProfile(new FirefoxProfile(new File("path_to_your_profile")));
WebDriver driver = new FirefoxDriver(options);

希望对您有所帮助。

答案 2 :(得分:0)

您可以创建一个干净的 firefox profile 并将其命名为 SELENIUM

因此在初始化 Webdriver 时获取您已经通过代码创建的配置文件,这样它就不会一直创建任何新的临时配置文件。

ProfilesIni allProfiles = new ProfilesIni();
 FirefoxProfile desiredProfile = allProfiles.getProfile("SELENIUM");
 WebDriver driver = new FirefoxDriver(desiredProfile);

这样,您就可以确保在任何时候进行测试时都会使用此配置文件。

-阿琼