Path: blob/trunk/py/selenium/webdriver/firefox/firefox_binary.py
1864 views
# Licensed to the Software Freedom Conservancy (SFC) under one1# or more contributor license agreements. See the NOTICE file2# distributed with this work for additional information3# regarding copyright ownership. The SFC licenses this file4# to you under the Apache License, Version 2.0 (the5# "License"); you may not use this file except in compliance6# with the License. You may obtain a copy of the License at7#8# http://www.apache.org/licenses/LICENSE-2.09#10# Unless required by applicable law or agreed to in writing,11# software distributed under the License is distributed on an12# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY13# KIND, either express or implied. See the License for the14# specific language governing permissions and limitations15# under the License.161718import os19import sys20import time21from subprocess import DEVNULL, STDOUT, Popen2223from typing_extensions import deprecated2425from selenium.common.exceptions import WebDriverException26from selenium.webdriver.common import utils272829@deprecated("Use binary_location property in Firefox Options to set location")30class FirefoxBinary:31NO_FOCUS_LIBRARY_NAME = "x_ignore_nofocus.so"3233def __init__(self, firefox_path=None, log_file=None):34"""Creates a new instance of Firefox binary.3536:Args:37- firefox_path - Path to the Firefox executable. By default, it will be detected from the standard locations.38- log_file - A file object to redirect the firefox process output to. It can be sys.stdout.39Please note that with parallel run the output won't be synchronous.40By default, it will be redirected to /dev/null.41"""42self._start_cmd = firefox_path43# We used to default to subprocess.PIPE instead of /dev/null, but after44# a while the pipe would fill up and Firefox would freeze.45self._log_file = log_file or DEVNULL46self.command_line = None47self.platform = sys.platform48if not self._start_cmd:49self._start_cmd = self._get_firefox_start_cmd()50if not self._start_cmd.strip():51raise WebDriverException(52"Failed to find firefox binary. You can set it by specifying "53"the path to 'firefox_binary':\n\nfrom "54"selenium.webdriver.firefox.firefox_binary import "55"FirefoxBinary\n\nbinary = "56"FirefoxBinary('/path/to/binary')\ndriver = "57"webdriver.Firefox(firefox_binary=binary)"58)59# Rather than modifying the environment of the calling Python process60# copy it and modify as needed.61self._firefox_env = os.environ.copy()62self._firefox_env["MOZ_CRASHREPORTER_DISABLE"] = "1"63self._firefox_env["MOZ_NO_REMOTE"] = "1"64self._firefox_env["NO_EM_RESTART"] = "1"6566def add_command_line_options(self, *args):67self.command_line = args6869def launch_browser(self, profile, timeout=30):70"""Launches the browser for the given profile name.7172It is assumed the profile already exists.73"""74self.profile = profile7576self._start_from_profile_path(self.profile.path)77self._wait_until_connectable(timeout=timeout)7879def kill(self):80"""Kill the browser.8182This is useful when the browser is stuck.83"""84if self.process:85self.process.kill()86self.process.wait()8788def _start_from_profile_path(self, path):89self._firefox_env["XRE_PROFILE_PATH"] = path9091if self.platform == "linux":92self._modify_link_library_path()93command = [self._start_cmd, "-foreground"]94if self.command_line:95for cli in self.command_line:96command.append(cli)97self.process = Popen(command, stdout=self._log_file, stderr=STDOUT, env=self._firefox_env)9899def _wait_until_connectable(self, timeout=30):100"""Blocks until the extension is connectable in the firefox."""101count = 0102while not utils.is_connectable(self.profile.port):103if self.process.poll():104# Browser has exited105raise WebDriverException(106"The browser appears to have exited "107"before we could connect. If you specified a log_file in "108"the FirefoxBinary constructor, check it for details."109)110if count >= timeout:111self.kill()112raise WebDriverException(113"Can't load the profile. Possible firefox version mismatch. "114"You must use GeckoDriver instead for Firefox 48+. Profile "115f"Dir: {self.profile.path} If you specified a log_file in the "116"FirefoxBinary constructor, check it for details."117)118count += 1119time.sleep(1)120return True121122def _find_exe_in_registry(self):123try:124from _winreg import HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, OpenKey, QueryValue125except ImportError:126from winreg import HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, OpenKey, QueryValue127import shlex128129keys = (130r"SOFTWARE\Classes\FirefoxHTML\shell\open\command",131r"SOFTWARE\Classes\Applications\firefox.exe\shell\open\command",132)133command = ""134for path in keys:135try:136key = OpenKey(HKEY_LOCAL_MACHINE, path)137command = QueryValue(key, "")138break139except OSError:140try:141key = OpenKey(HKEY_CURRENT_USER, path)142command = QueryValue(key, "")143break144except OSError:145pass146else:147return ""148149if not command:150return ""151152return shlex.split(command)[0]153154def _get_firefox_start_cmd(self):155"""Return the command to start firefox."""156start_cmd = ""157if self.platform == "darwin": # small darwin due to lower() in self.platform158ffname = "firefox"159start_cmd = self.which(ffname)160# use hardcoded path if nothing else was found by which()161if not start_cmd:162start_cmd = "/Applications/Firefox.app/Contents/MacOS/firefox"163# fallback to homebrew installation for mac users164if not os.path.exists(start_cmd):165start_cmd = os.path.expanduser("~") + start_cmd166elif self.platform == "windows": # same167start_cmd = self._find_exe_in_registry() or self._default_windows_location()168elif self.platform == "java" and os.name == "nt":169start_cmd = self._default_windows_location()170else:171for ffname in ["firefox", "iceweasel"]:172start_cmd = self.which(ffname)173if start_cmd:174break175else:176# couldn't find firefox on the system path177raise RuntimeError(178"Could not find firefox in your system PATH."179" Please specify the firefox binary location or install firefox"180)181return start_cmd182183def _default_windows_location(self):184program_files = [185os.getenv("PROGRAMFILES", r"C:\Program Files"),186os.getenv("PROGRAMFILES(X86)", r"C:\Program Files (x86)"),187]188for path in program_files:189binary_path = os.path.join(path, r"Mozilla Firefox\firefox.exe")190if os.access(binary_path, os.X_OK):191return binary_path192return ""193194def _modify_link_library_path(self):195existing_ld_lib_path = os.environ.get("LD_LIBRARY_PATH", "")196197new_ld_lib_path = self._extract_and_check(self.profile, "x86", "amd64")198199new_ld_lib_path += existing_ld_lib_path200201self._firefox_env["LD_LIBRARY_PATH"] = new_ld_lib_path202self._firefox_env["LD_PRELOAD"] = self.NO_FOCUS_LIBRARY_NAME203204def _extract_and_check(self, profile, x86, amd64):205paths = [x86, amd64]206built_path = ""207for path in paths:208library_path = os.path.join(profile.path, path)209if not os.path.exists(library_path):210os.makedirs(library_path)211import shutil212213shutil.copy(os.path.join(os.path.dirname(__file__), path, self.NO_FOCUS_LIBRARY_NAME), library_path)214built_path += library_path + ":"215216return built_path217218def which(self, fname):219"""Returns the fully qualified path by searching Path of the given220name."""221for pe in os.environ["PATH"].split(os.pathsep):222checkname = os.path.join(pe, fname)223if os.access(checkname, os.X_OK) and not os.path.isdir(checkname):224return checkname225return None226227228