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

15

我正在使用最新版本的Selenium WebDriverGeckodriver。我想要防止在启动新的WebDriver实例时,Selenium创建临时的Firefox配置文件,并将其保存到临时文件目录中。相反,我希望直接使用原始的Firefox配置文件。这样做有双重好处。首先,它可以节省时间(将配置文件复制到临时目录需要花费大量时间)。其次,它确保会话期间创建的Cookie被保存到原始配置文件中。在Selenium开始依赖于Geckodriver之前,我曾通过编辑以下位置的FirefoxProfile.class类来解决这个问题

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配置文件。

然而,现在由Geckodriver控制Selenium,这个解决方案不再适用,因为Firefox配置文件的创建和启动是由Geckodriver.exe(使用Rust语言编写)控制的。我如何通过Geckodriver实现相同的目标?我不介意编辑源代码。我正在使用Java。

谢谢

重要更新:

我想感谢每个人抽出时间回答这个问题。然而,正如一些评论中所述,前三个答案根本没有回答问题,原因有两个。首先,使用现有的Firefox配置文件不会防止Geckodriver将原始配置文件复制到临时目录(如OP所示并由一个或多个评论员明确说明)。其次,即使它可以,它也与Selenium 3.0不兼容。

我真的不确定为什么4个答案中有3个答案重复了完全相同的答案,并犯了完全相同的错误。他们读懂了问题吗?唯一试图回答问题的答案是@Life is complex的答案,但它并不完整。谢谢。


2
经过对 GeckodriverFirefoxSelenium 进行大量代码和问题审查后,我确定实现您所需的功能是困难的。您可以编辑源代码 capabilities.rs 以删除 .temp_dir 调用,但似乎您还需要更改 Firefox 中的代码以防止创建 .temp_dir。我还注意到 Selenium 在 beta 版本中已经停止复制和使用配置文件,因此在当前版本中修复问题可能需要更多的努力,当新版本发布时也会如此。 - Life is complex
1
我还注意到有多个用户向Selenium抱怨了您正在尝试解决的问题。在Selenium的监管者要么告诉用户联系Mozilla获取支持,要么告诉他们尝试不使用配置文件的版本4。我发现需要更好地记录如何在Selenium中设置FirefoxChromeEdge的偏好设置。 - Life is complex
1
我最好的建议是要么升级到Selenium ver 4 (BETA),它不使用配置文件。(我对该代码库中的temp_dir问题不确定) 要么将您的GeckodriverFirefoxSelenium降级到您升级之前的版本。 - Life is complex
@Lifeiscomplex 非常感谢你的努力!关于Selenium 4版本的事情,我认为它自2018年以来一直处于测试版状态,所以我担心我们是否会在不久的将来看到官方发布。关于Selenium 4中个人资料被弃用的问题很有趣,我很想了解更多。我没有找到关于这个特定功能的官方文档,也不知道v4中是否有原始个人资料的复制。降级Selenium不是一个选项,因为代码的大部分都是围绕版本3编写的,如果我降级,会引起更多的问题。谢谢! - alpha1
所以我假设您的问题出现在升级GeckodriverFirefox时。我还假设您无法将这些应用程序降级到其先前的状态。关于Selenium 4,最新版本的发布说明如下:(1)仅在选项中使用Profile时发出弃用警告(2)弃用在选项中使用Firefox配置文件。我在发布说明中找不到有关创建temp_dir的任何信息。 - Life is complex
6个回答

8

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);
    }
  }

理论上来说,您应该能够修改capabilities.rsgeckodriver源代码中。该文件包含了temp_dir

正如我之前所述,这只是一种理论,因为当我查看Firefox源代码时,其中遍布着temp_dir

原始帖子 2021年5月26日


我不确定你能否阻止Selenium创建临时的Firefox配置文件。

根据gecko文档

"配置文件会被创建在系统的临时文件夹中。当提供了配置文件时,编码的配置文件也会被提取到这个位置。默认情况下,geckodriver会在这个位置创建一个新的配置文件。"

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

我正在查看源代码。这些文件可能是正确的,但我需要进一步查看源代码:

以下是需要仔细查看的其他文件:

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


这看起来很有前途。

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

geckodriver使用[配置文件]来操纵Firefox的行为。用户通常会依赖geckodriver生成一个临时的、一次性的配置文件。这些配置文件在WebDriver会话过期时被删除。
在用户需要使用自定义的预先准备好的配置文件的情况下,geckodriver会对配置文件进行修改以确保正确的行为。请参阅下面关于用户定义偏好设置的优先级的[自动化偏好设置]。
可以通过两种不同的方式提供自定义配置文件:
1. 通过将`--profile /some/location`附加到[args capability],这将指示geckodriver在原地使用该配置文件;
我在尝试这样做时找到了一个相关问题:如何在Selenium Webdriver中原地使用现有配置文件? 此外,这是一个在Selenium的Github上提出的关于临时目录的问题: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
1
@alpha1 我还在寻找正确的文件。Mozilla Central 中有大量的测试代码需要与基础代码分离。我会继续寻找,但可能无法在此赏金问题到期之前发布任何内容。这是我目前正在查看的位置 https://searchfox.org/mozilla-central/source/browser/app - Life is complex
感谢更新。一旦我们能够确定需要编辑的文件,就会接受作为答案。然而,我正在尝试理解的一件事是:您指向的文件是否是Mozilla Firefox的一部分?但是我的印象是,我们需要修改来源:https://github.com/mozilla/geckodriver/releases/tag/v0.29.1(但这不包含任何“Profile”类),我错了吗?谢谢! - alpha1
1
@alpha1 我现在正在查看那个版本的源代码。我相信Profile类位于 mozprofile::profile::Profile下面。我认为mozprofile::profile::Profile来自于火狐(Firefox),而不是geckodriver。 - Life is complex
非常感谢您的努力和全面的回复!我将查看您提到的源代码,看看是否可以使用Selenium V3使其正常工作。有一件事我不确定:您提到复制可能来自Firefox而不是Geckodriver。但如果是这种情况,为什么FF在没有WebDriver的情况下每次启动时都不会将复制的永久配置文件复制到临时目录中呢?据我了解,只有当我们通过WebDriver / Selenium启动FF时才会发生复制。谢谢! - alpha1
1
@Lifeiscomplex 感谢您的出色回答。我为这个问题添加了50点赏金。如果可能的话,能否修改响应以包括工作的Rust代码,在运行时接受任何配置文件路径作为参数(因此不是硬编码),其中WebDriver不会创建原始配置文件的临时副本,而是直接使用原始配置文件。 - Bradford Griggs

3

感谢这个链接中的答案提供了源代码!我有机会查看geckodriver的源代码。

解释

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

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

简短回答

下载geckodriver-0.30.0的zip文件,并使用您喜欢的zip程序进行解压:P

查看geckodriver源代码中的src/browser.rs,在第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)?;

使用首选的 Rust 编译器重新编译,例如:

Cargo build

注意

希望这些信息能对你有所帮助。虽然不是非常全面,但希望它足够成为一个好的提示。例如,可以编写一些额外的代码从环境中加载配置文件或者通过参数传递。虽然我不是 Rust 开发人员,懒得在这里提供代码。

以上解决方案对我来说很有效,我可以直接加载并使用我的配置文件。顺便说一下,我在 Archlinux 上工作,使用的是 rust 信息: cargo 1.57.0

说实话,这是我第一次在 stackoverflow 发表评论,如果我错了或者回答不清楚,请随时纠正我 :P

更新

  1. 我使用的是 geckodriver 0.30.0,与 Life is complex 提到的 geckodriver 0.29.1 不同。但是两个版本之间的更改只是拆分操作,因此在版本 0.29.1 中类似的修改路径将包含在文件 src/marionette.rsMarionetteHandler::start_browser 方法中。

  2. 由于我的起点是 Life is complex回答,所以请查看该回答获取更多信息。


感谢您的出色回答。我已经为这个问题添加了50点赏金。如果可以修改回答,包括工作的Rust代码,在运行时接受任何配置文件路径作为参数(因此不是硬编码),其中WebDriver不会创建原始配置文件的临时副本,而是直接使用原始配置文件。 - Bradford Griggs
@BradfordGriggs,请查看我在此处发布的帖子,其中包含您的解决方案 https://dev59.com/nlEG5IYBdhLWcg3wV67A#75116150 - Young

1
我想出了一个解决方案,它可以与Selenium 4.7.0一起使用--但我不认为它不能与3.x一起使用;允许用户通过环境变量动态传递现有的Firefox配置文件--如果这个环境变量不存在,则只是正常地工作;如果您不想要一个临时的配置文件目录副本,只需不将源配置文件目录传递给Selenium。
我下载了Geckodriver 0.32.0,使得你只需要通过环境变量FIREFOX_PROFILE_DIR提供Firefox配置文件目录。例如,在C#中,在创建FirefoxDriver之前调用:
Environment.SetEnvironmentVariable("FIREFOX_PROFILE_DIR", myProfileDir);

Rust语言中的变化出现在browser.rs文件的第88行,将其替换为:
    let mut profile = match options.profile {
        ProfileType::Named => None,
        ProfileType::Path(x) => Some(x),
        ProfileType::Temporary => Some(Profile::new(profile_root)?),
    };

使用:

    let mut profile = if let Ok(profile_dir) = std::env::var("FIREFOX_PROFILE_DIR") {
        Some(Profile::new_from_path(Path::new(&profile_dir))?)
    } else {
        match options.profile {
            ProfileType::Named => None,
            ProfileType::Path(x) => Some(x),
            ProfileType::Temporary => Some(Profile::new(profile_root)?),
        }
    };

您可以参考我的Git提交,查看与原始Geckodriver代码的差异。

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();

你甚至可以使用环境变量设置这些系统属性,并跳过在任何地方定义它们的步骤。

另一种方法是使用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);

希望这对你有所帮助。


1
不幸的是,这并没有解决OP的问题。 OP知道如何使用特定配置文件启动Firefox。 问题是FF仍将原始配置文件复制到临时文件夹中,这会增加启动FF所需的时间,并导致WebDriver关闭后Cookie被删除。 因此,答案不解决OP中的这两个问题。 - S.O.S
防止Selenium(Geckodriver)创建临时Firefox配置文件需要对源文件进行某种编辑。 - S.O.S

0
你可以创建一个干净的火狐浏览器配置文件,并将其命名为SELENIUM
因此,在初始化Webdriver时,通过代码获取已经创建的配置文件,这样就不会一直创建新的临时配置文件。
ProfilesIni allProfiles = new ProfilesIni();
FirefoxProfile desiredProfile = allProfiles.getProfile("SELENIUM");
WebDriver driver = new FirefoxDriver(desiredProfile);

这样,您确保每次进行测试时都会使用此配置文件。

-阿尔琼


3
这不会阻止Selenium创建临时的Firefox配置文件。即使提供了自定义配置文件,Selenium仍会将其复制到临时目录中。请在回答前仔细阅读问题。另外我认为ProfilesIni与Selenium 3.0不兼容。 - Alan Cook

0

你可以通过使用--来处理这个问题

    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(...);

希望这个简短的回答解决了你的问题。


你有没有机会检查这个解决方案?或者你正在寻找其他方法来解决这个问题? - Vaibhav Atray
1
感谢您的回复。然而,正如在原帖中所述,这并没有解决问题。请查看原帖中的更新。谢谢。 - alpha1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接