webdriver.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. # Licensed to the Software Freedom Conservancy (SFC) under one
  2. # or more contributor license agreements. See the NOTICE file
  3. # distributed with this work for additional information
  4. # regarding copyright ownership. The SFC licenses this file
  5. # to you under the Apache License, Version 2.0 (the
  6. # "License"); you may not use this file except in compliance
  7. # with the License. You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing,
  12. # software distributed under the License is distributed on an
  13. # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  14. # KIND, either express or implied. See the License for the
  15. # specific language governing permissions and limitations
  16. # under the License.
  17. try:
  18. import http.client as http_client
  19. except ImportError:
  20. import httplib as http_client
  21. try:
  22. basestring
  23. except NameError: # Python 3.x
  24. basestring = str
  25. import shutil
  26. import socket
  27. import sys
  28. from contextlib import contextmanager
  29. from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
  30. from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
  31. from .extension_connection import ExtensionConnection
  32. from .firefox_binary import FirefoxBinary
  33. from .firefox_profile import FirefoxProfile
  34. from .options import Options
  35. from .remote_connection import FirefoxRemoteConnection
  36. from .service import Service
  37. from .webelement import FirefoxWebElement
  38. class WebDriver(RemoteWebDriver):
  39. # There is no native event support on Mac
  40. NATIVE_EVENTS_ALLOWED = sys.platform != "darwin"
  41. CONTEXT_CHROME = "chrome"
  42. CONTEXT_CONTENT = "content"
  43. _web_element_cls = FirefoxWebElement
  44. def __init__(self, firefox_profile=None, firefox_binary=None,
  45. timeout=30, capabilities=None, proxy=None,
  46. executable_path="geckodriver", firefox_options=None,
  47. log_path="geckodriver.log"):
  48. """Starts a new local session of Firefox.
  49. Based on the combination and specificity of the various keyword
  50. arguments, a capabilities dictionary will be constructed that
  51. is passed to the remote end.
  52. The keyword arguments given to this constructor are helpers to
  53. more easily allow Firefox WebDriver sessions to be customised
  54. with different options. They are mapped on to a capabilities
  55. dictionary that is passed on to the remote end.
  56. As some of the options, such as `firefox_profile` and
  57. `firefox_options.profile` are mutually exclusive, precedence is
  58. given from how specific the setting is. `capabilities` is the
  59. least specific keyword argument, followed by `firefox_options`,
  60. followed by `firefox_binary` and `firefox_profile`.
  61. In practice this means that if `firefox_profile` and
  62. `firefox_options.profile` are both set, the selected profile
  63. instance will always come from the most specific variable.
  64. In this case that would be `firefox_profile`. This will result in
  65. `firefox_options.profile` to be ignored because it is considered
  66. a less specific setting than the top-level `firefox_profile`
  67. keyword argument. Similarily, if you had specified a
  68. `capabilities["firefoxOptions"]["profile"]` Base64 string,
  69. this would rank below `firefox_options.profile`.
  70. :param firefox_profile: Instance of ``FirefoxProfile`` object
  71. or a string. If undefined, a fresh profile will be created
  72. in a temporary location on the system.
  73. :param firefox_binary: Instance of ``FirefoxBinary`` or full
  74. path to the Firefox binary. If undefined, the system default
  75. Firefox installation will be used.
  76. :param timeout: Time to wait for Firefox to launch when using
  77. the extension connection.
  78. :param capabilities: Dictionary of desired capabilities.
  79. :param proxy: The proxy settings to us when communicating with
  80. Firefox via the extension connection.
  81. :param executable_path: Full path to override which geckodriver
  82. binary to use for Firefox 47.0.1 and greater, which
  83. defaults to picking up the binary from the system path.
  84. :param firefox_options: Instance of ``options.Options``.
  85. :param log_path: Where to log information from the driver.
  86. """
  87. self.binary = None
  88. self.profile = None
  89. self.service = None
  90. if capabilities is None:
  91. capabilities = DesiredCapabilities.FIREFOX.copy()
  92. if firefox_options is None:
  93. firefox_options = Options()
  94. if capabilities.get("binary"):
  95. self.binary = capabilities["binary"]
  96. # firefox_options overrides capabilities
  97. if firefox_options is not None:
  98. if firefox_options.binary is not None:
  99. self.binary = firefox_options.binary
  100. if firefox_options.profile is not None:
  101. self.profile = firefox_options.profile
  102. # firefox_binary and firefox_profile
  103. # override firefox_options
  104. if firefox_binary is not None:
  105. if isinstance(firefox_binary, basestring):
  106. firefox_binary = FirefoxBinary(firefox_binary)
  107. self.binary = firefox_binary
  108. firefox_options.binary = firefox_binary
  109. if firefox_profile is not None:
  110. if isinstance(firefox_profile, basestring):
  111. firefox_profile = FirefoxProfile(firefox_profile)
  112. self.profile = firefox_profile
  113. firefox_options.profile = firefox_profile
  114. # W3C remote
  115. # TODO(ato): Perform conformance negotiation
  116. if capabilities.get("marionette"):
  117. capabilities.pop("marionette")
  118. self.service = Service(executable_path, log_path=log_path)
  119. self.service.start()
  120. capabilities.update(firefox_options.to_capabilities())
  121. executor = FirefoxRemoteConnection(
  122. remote_server_addr=self.service.service_url)
  123. RemoteWebDriver.__init__(
  124. self,
  125. command_executor=executor,
  126. desired_capabilities=capabilities,
  127. keep_alive=True)
  128. # Selenium remote
  129. else:
  130. if self.binary is None:
  131. self.binary = FirefoxBinary()
  132. if self.profile is None:
  133. self.profile = FirefoxProfile()
  134. # disable native events if globally disabled
  135. self.profile.native_events_enabled = (
  136. self.NATIVE_EVENTS_ALLOWED and self.profile.native_events_enabled)
  137. if proxy is not None:
  138. proxy.add_to_capabilities(capabilities)
  139. executor = ExtensionConnection("127.0.0.1", self.profile,
  140. self.binary, timeout)
  141. RemoteWebDriver.__init__(
  142. self,
  143. command_executor=executor,
  144. desired_capabilities=capabilities,
  145. keep_alive=True)
  146. self._is_remote = False
  147. def quit(self):
  148. """Quits the driver and close every associated window."""
  149. try:
  150. RemoteWebDriver.quit(self)
  151. except (http_client.BadStatusLine, socket.error):
  152. # Happens if Firefox shutsdown before we've read the response from
  153. # the socket.
  154. pass
  155. if self.w3c:
  156. self.service.stop()
  157. else:
  158. self.binary.kill()
  159. if self.profile is not None:
  160. try:
  161. shutil.rmtree(self.profile.path)
  162. if self.profile.tempfolder is not None:
  163. shutil.rmtree(self.profile.tempfolder)
  164. except Exception as e:
  165. print(str(e))
  166. @property
  167. def firefox_profile(self):
  168. return self.profile
  169. # Extension commands:
  170. def set_context(self, context):
  171. self.execute("SET_CONTEXT", {"context": context})
  172. @contextmanager
  173. def context(self, context):
  174. """Sets the context that Selenium commands are running in using
  175. a `with` statement. The state of the context on the server is
  176. saved before entering the block, and restored upon exiting it.
  177. :param context: Context, may be one of the class properties
  178. `CONTEXT_CHROME` or `CONTEXT_CONTENT`.
  179. Usage example::
  180. with selenium.context(selenium.CONTEXT_CHROME):
  181. # chrome scope
  182. ... do stuff ...
  183. """
  184. initial_context = self.execute('GET_CONTEXT').pop('value')
  185. self.set_context(context)
  186. try:
  187. yield
  188. finally:
  189. self.set_context(initial_context)