【问题标题】:How to Prevent Selenium 3.0 (Geckodriver) from Creating Temporary Firefox Profiles?如何防止 Selenium 3.0 (Geckodriver) 创建临时 Firefox 配置文件?
【发布时间】:2021-08-06 05:33:27
【问题描述】:

我正在使用 Geckodriver 运行最新版本的 Selenium WebDriver。我想阻止 Selenium 在启动 WebDriver 的新实例时在临时文件目录中创建临时 Firefox 配置文件。相反,我想直接使用原始的 Firefox 配置文件。这有双重好处。首先,它节省了时间(将配置文件复制到临时目录需要大量时间)。其次,它确保在会话期间创建的 cookie 被保存到原始配置文件中。在 Selenium 开始依赖 Geckodriver 之前,我可以通过编辑 SeleniumHQ 中的 FirefoxProfile.class 类来 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 配置文件的创建(和启动)由 Geckodriver.exe 控制(使用 Rust 语言编写)。如何使用 Geckodriver 实现相同的目标?我不介意编辑源代码。我正在使用 Java。

谢谢

重要更新:

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

我真的不知道为什么 4 个答案中有 3 个会重复完全相同的答案而出现完全相同的错误。他们读过这个问题吗?甚至尝试解决手头问题的唯一答案是@Life的答案很复杂,但它不完整。谢谢。

【问题讨论】:

  • 在对 Geckodriver、 FirefoxSelenium 进行了大量代码和问题审查之后。我已经确定,努力去做你需要的事情是困难的。您可以编辑源代码 capabilities.rs 以删除 .temp_dir 调用,但似乎您可能还必须更改 Firefox 中的代码防止创建 .temp_dir*。我还注意到 Selenium 正在远离复制和使用版本 4 中的配置文件,该版本处于测试阶段。因此,当新版本发布时,修复当前版本中的问题可能需要更多的努力。
  • 我还注意到,多个用户向 Selenium 投诉了您要解决的问题。 Selenium 的监督者要么告诉用户联系 Mozilla 以获得支持,要么告诉他们尝试不使用配置文件的版本 4。我发现在 Selenium 下为 FirefoxChromeEdge 设置首选项需要更好的文档。
  • 我最好的建议是迁移到不使用配置文件的 Selenium v​​er 4 (BETA)。 (我不确定该代码库中的 temp_dir 内容)或降级您的 GeckodriverFirefoxSelenium升级之前的工作方式。
  • @Lifeiscomplex 非常感谢您的努力! Selenium 版本 4 的问题在于,我认为它自 2018 年以来一直处于测试阶段,所以我会担心我们是否会在不久的将来看到正式发布。关于在 Selenium 4 中贬值的配置文件很有趣,我很想了解更多。我找不到有关此特定功能的官方文档以及 v4 中是否存在原始配置文件的任何副本。降级 Selenium 不是一种选择,因为大部分代码都是围绕版本 3 编写的,如果我降级它将导致更多问题。谢谢!
  • 所以我认为您的问题是在您升级 GeckodriverFirefox 时发生的。我还假设您不能将这些应用程序中的任何一个降级到以前的状态。关于 Selenium 4,最新版本的发行说明有这个 - (1) 仅在选项中使用 Profile 时才给出弃用警告 (2) 在选项中弃用使用 Firefox 配置文件。我在发行说明中找不到有关创建 temp_dir 的任何内容。

标签: java selenium selenium-webdriver geckodriver


【解决方案1】:

感谢link! 中 Life is complex 的回答中提供的源代码。我有机会浏览 geckodriver 源码。

解释

我相信您在源代码中找不到任何 rust_tmp 的原因是因为它是由 Profile::new() 函数随机生成的。

当我深入查看代码结构时,我发现 browser.rs 是实际加载浏览器的地方,它是通过 ma​​rionette.rs 调用的。如果您仔细注意,每当初始化新会话时都会调用 LocalBrowser::new 方法,并且配置文件也会在该状态下加载。然后通过检查 browser.rs 文件,将有一个块代码行 60 - 70 用于实际生成新会话实例的配置文件。现在,需要做的是修改此路径以加载您的自定义配置文件。

简短回答

下载geckodriver-0.30.0的zip文件,用你喜欢的zip程序解压:P

查看 src/browser.rs 的 geckodriver 源代码,第 60 - 70 行,希望您会看到如下内容:

        let is_custom_profile = options.profile.is_some();

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

将其更改为您喜欢的文件夹(希望您知道一些 rust 代码),例如:

        /*
        let mut profile = match options.profile {
            Some(x) => x,
            None => Profile::new()?,
        };
        */
        let path = std::path::Path::new("path-to-profile");
        let mut profile = Profile::new_from_path(path)?;

用prefer rust编译器重新编译,例子:

Cargo build

注意

希望这些信息对您有所帮助。这并不全面,但希望它对您来说足够好,例如可以编写一些额外的代码来从 env 加载配置文件或从参数传递,这是可能的,但我不是 rust 开发人员,所以懒得在这里提供一个.

上述解决方案对我来说很好,我可以从中直接加载和使用我的个人资料。顺便说一句,我在 Archlinux 上使用 rust 信息:cargo 1.57.0

TBH,这是我第一次在 stackoverflow 上发表评论,如果我错了或给出不清楚的答案,请随时纠正我:P

更新

  1. 我在 geckodriver 0.30.0 工作过,这与 Life is complex 提到的 geckodriver 0.29.1 不同。但是两个版本之间的变化只是拆分动作,所以 0.29.1 版本中类似的修改路径将包含在文件 src/marionette.rs 的方法 MarionetteHandler::start_browser 中>.

  2. 由于我的出发点是生活很复杂 answer,请浏览它以获取更多信息。

【讨论】:

    【解决方案2】:

    2021 年 5 月 30 日更新后


    这是我在 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 分布在整个代码库中。

    2021 年 5 月 26 日原帖


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

    来自gecko documents

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

    我目前看到的唯一解决方案是要求您修改 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吗?

    【讨论】:

    • 感谢您的回复。您实际上是解决该问题的唯一答案。但是,它仍然不完整,因为我想必须编辑哪些文件才能实现既定目标。谢谢!
    • @alpha1 我仍在寻找正确的文件。 Mozilla Central 中有大量测试代码,我必须将它们与基本代码分开。我会继续寻找,但在这个赏金问题到期之前可能不会发布任何内容。这是我目前正在寻找的地方searchfox.org/mozilla-central/source/browser/app
    • 感谢您的更新。一旦我们确定需要编辑的文件将被接受为答案。但是,我想了解的一件事是:您指向的文件是 Mozilla Firefox 的一部分?但我的印象是我们需要修改来源:github.com/mozilla/geckodriver/releases/tag/v0.29.1(但这不包含任何“配置文件”类)我错了吗?谢谢!
    • @alpha1 我现在正在查看该版本的源代码。我相信 Profile 类在 mozprofile::profile::Profile; 下。我认为 mozprofile::profile::Profile 来自 FireFox 而不是 geckodriver。
    • 再次感谢您的努力和全面的回复!我将查看您提到的源代码,看看是否可以使用 Selenium V3。我不确定的一件事:您提到复制可能来自 Firefox 而不是 Geckdodriver。但如果是这种情况,为什么每次启动 FF(没有 WebDriver)时 FF 不将永久配置文件复制到临时目录?据我了解,只有当我们通过 WebDriver / Selenium 启动 FF 时才会发生复制。谢谢!
    【解决方案3】:

    您可以使用 --

        FirefoxProfile profile = new FirefoxProfile(new File("D:\\Selenium Profile..."));                  
    
        WebDriver driver = new FirefoxDriver(profile);
    

    还有一个选项,但它继承了以前使用配置文件的所有 cookie、缓存内容等,让我们看看它会如何--

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

    希望这能简短地回答您的问题。

    【讨论】:

    • 您有机会检查此解决方案吗?或者您正在寻找其他方法来解决这个问题?
    • 感谢您的回复。但是,正如 OP 中所述,这并没有解决这个问题。请在 OP 中查看更新。谢谢。
    【解决方案4】:

    您可以创建 Firefox profile,它将是干净的并将其命名为 SELENIUM

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

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

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

    -阿琼

    【讨论】:

    • 这不会阻止 Selenium 创建临时 Firefox 配置文件。 Selenium 总是将配置文件复制到临时目录,即使提供了自定义配置文件也是如此。请在回答之前阅读问题。除此之外,我认为 ProfilesIni 与 Selenium 3.0 不兼容。
    【解决方案5】:

    如果未设置任何选项,默认情况下新驱动程序会创建一个新配置文件。要使用现有配置文件,一种方法是在创建 firefox 驱动程序之前设置系统属性webdriver.firefox.profile。一个可以创建 firefox 驱动程序的小代码 sn-p(假设您有 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);
    

    希望这有帮助。

    【讨论】:

    • 不幸的是,这并没有解决 OPs 问题。 OP 知道如何使用特定配置文件启动 Firefox。问题是 FF 仍然将原始配置文件复制到一个临时文件夹,这 1)增加了启动 FF 所需的时间,2)导致一旦 WebDriver 关闭,cookie 就会被删除。因此,答案既没有解决 OP 中的两个问题。
    • 防止 Selenium (Geckodriver) 创建临时 Firefox 配置文件需要对源文件进行某种编辑。
    猜你喜欢
    • 2018-01-13
    • 2011-03-24
    • 2010-10-19
    • 1970-01-01
    • 1970-01-01
    • 2012-03-07
    • 2017-10-08
    • 2013-10-16
    • 2017-03-24
    相关资源
    最近更新 更多