1# Copyright 2013 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import command_executor
6from command_executor import Command
7from webelement import WebElement
8
9
10class ChromeDriverException(Exception):
11  pass
12class NoSuchElement(ChromeDriverException):
13  pass
14class NoSuchFrame(ChromeDriverException):
15  pass
16class UnknownCommand(ChromeDriverException):
17  pass
18class StaleElementReference(ChromeDriverException):
19  pass
20class UnknownError(ChromeDriverException):
21  pass
22class JavaScriptError(ChromeDriverException):
23  pass
24class XPathLookupError(ChromeDriverException):
25  pass
26class NoSuchWindow(ChromeDriverException):
27  pass
28class InvalidCookieDomain(ChromeDriverException):
29  pass
30class ScriptTimeout(ChromeDriverException):
31  pass
32class InvalidSelector(ChromeDriverException):
33  pass
34class SessionNotCreatedException(ChromeDriverException):
35  pass
36class NoSuchSession(ChromeDriverException):
37  pass
38
39def _ExceptionForResponse(response):
40  exception_class_map = {
41    6: NoSuchSession,
42    7: NoSuchElement,
43    8: NoSuchFrame,
44    9: UnknownCommand,
45    10: StaleElementReference,
46    13: UnknownError,
47    17: JavaScriptError,
48    19: XPathLookupError,
49    23: NoSuchWindow,
50    24: InvalidCookieDomain,
51    28: ScriptTimeout,
52    32: InvalidSelector,
53    33: SessionNotCreatedException
54  }
55  status = response['status']
56  msg = response['value']['message']
57  return exception_class_map.get(status, ChromeDriverException)(msg)
58
59
60class ChromeDriver(object):
61  """Starts and controls a single Chrome instance on this machine."""
62
63  def __init__(self, server_url, chrome_binary=None, android_package=None,
64               android_activity=None, android_process=None,
65               android_use_running_app=None, chrome_switches=None,
66               chrome_extensions=None, chrome_log_path=None,
67               debugger_address=None, browser_log_level=None,
68               performance_log_level=None, mobile_emulation=None,
69               experimental_options=None):
70    self._executor = command_executor.CommandExecutor(server_url)
71
72    options = {}
73
74    if experimental_options:
75      assert isinstance(experimental_options, dict)
76      options = experimental_options.copy()
77
78    if android_package:
79      options['androidPackage'] = android_package
80      if android_activity:
81        options['androidActivity'] = android_activity
82      if android_process:
83        options['androidProcess'] = android_process
84      if android_use_running_app:
85        options['androidUseRunningApp'] = android_use_running_app
86    elif chrome_binary:
87      options['binary'] = chrome_binary
88
89    if chrome_switches:
90      assert type(chrome_switches) is list
91      options['args'] = chrome_switches
92
93    if mobile_emulation:
94      assert type(mobile_emulation) is dict
95      options['mobileEmulation'] = mobile_emulation
96
97    if chrome_extensions:
98      assert type(chrome_extensions) is list
99      options['extensions'] = chrome_extensions
100
101    if chrome_log_path:
102      assert type(chrome_log_path) is str
103      options['logPath'] = chrome_log_path
104
105    if debugger_address:
106      assert type(debugger_address) is str
107      options['debuggerAddress'] = debugger_address
108
109    logging_prefs = {}
110    log_levels = ['ALL', 'DEBUG', 'INFO', 'WARNING', 'SEVERE', 'OFF']
111    if browser_log_level:
112      assert browser_log_level in log_levels
113      logging_prefs['browser'] = browser_log_level
114    if performance_log_level:
115      assert performance_log_level in log_levels
116      logging_prefs['performance'] = performance_log_level
117
118    params = {
119      'desiredCapabilities': {
120        'chromeOptions': options,
121        'loggingPrefs': logging_prefs
122      }
123    }
124
125    response = self._ExecuteCommand(Command.NEW_SESSION, params)
126    self._session_id = response['sessionId']
127    self.capabilities = self._UnwrapValue(response['value'])
128
129  def _WrapValue(self, value):
130    """Wrap value from client side for chromedriver side."""
131    if isinstance(value, dict):
132      converted = {}
133      for key, val in value.items():
134        converted[key] = self._WrapValue(val)
135      return converted
136    elif isinstance(value, WebElement):
137      return {'ELEMENT': value._id}
138    elif isinstance(value, list):
139      return list(self._WrapValue(item) for item in value)
140    else:
141      return value
142
143  def _UnwrapValue(self, value):
144    """Unwrap value from chromedriver side for client side."""
145    if isinstance(value, dict):
146      if (len(value) == 1 and 'ELEMENT' in value
147          and isinstance(value['ELEMENT'], basestring)):
148        return WebElement(self, value['ELEMENT'])
149      else:
150        unwraped = {}
151        for key, val in value.items():
152          unwraped[key] = self._UnwrapValue(val)
153        return unwraped
154    elif isinstance(value, list):
155      return list(self._UnwrapValue(item) for item in value)
156    else:
157      return value
158
159  def _ExecuteCommand(self, command, params={}):
160    params = self._WrapValue(params)
161    response = self._executor.Execute(command, params)
162    if response['status'] != 0:
163      raise _ExceptionForResponse(response)
164    return response
165
166  def ExecuteCommand(self, command, params={}):
167    params['sessionId'] = self._session_id
168    response = self._ExecuteCommand(command, params)
169    return self._UnwrapValue(response['value'])
170
171  def GetWindowHandles(self):
172    return self.ExecuteCommand(Command.GET_WINDOW_HANDLES)
173
174  def SwitchToWindow(self, handle_or_name):
175    self.ExecuteCommand(Command.SWITCH_TO_WINDOW, {'name': handle_or_name})
176
177  def GetCurrentWindowHandle(self):
178    return self.ExecuteCommand(Command.GET_CURRENT_WINDOW_HANDLE)
179
180  def CloseWindow(self):
181    self.ExecuteCommand(Command.CLOSE)
182
183  def Load(self, url):
184    self.ExecuteCommand(Command.GET, {'url': url})
185
186  def LaunchApp(self, app_id):
187    self.ExecuteCommand(Command.LAUNCH_APP, {'id': app_id})
188
189  def ExecuteScript(self, script, *args):
190    converted_args = list(args)
191    return self.ExecuteCommand(
192        Command.EXECUTE_SCRIPT, {'script': script, 'args': converted_args})
193
194  def ExecuteAsyncScript(self, script, *args):
195    converted_args = list(args)
196    return self.ExecuteCommand(
197        Command.EXECUTE_ASYNC_SCRIPT,
198        {'script': script, 'args': converted_args})
199
200  def SwitchToFrame(self, id_or_name):
201    self.ExecuteCommand(Command.SWITCH_TO_FRAME, {'id': id_or_name})
202
203  def SwitchToFrameByIndex(self, index):
204    self.SwitchToFrame(index)
205
206  def SwitchToMainFrame(self):
207    self.SwitchToFrame(None)
208
209  def SwitchToParentFrame(self):
210    self.ExecuteCommand(Command.SWITCH_TO_PARENT_FRAME)
211
212  def GetTitle(self):
213    return self.ExecuteCommand(Command.GET_TITLE)
214
215  def GetPageSource(self):
216    return self.ExecuteCommand(Command.GET_PAGE_SOURCE)
217
218  def FindElement(self, strategy, target):
219    return self.ExecuteCommand(
220        Command.FIND_ELEMENT, {'using': strategy, 'value': target})
221
222  def FindElements(self, strategy, target):
223    return self.ExecuteCommand(
224        Command.FIND_ELEMENTS, {'using': strategy, 'value': target})
225
226  def SetTimeout(self, type, timeout):
227    return self.ExecuteCommand(
228        Command.SET_TIMEOUT, {'type' : type, 'ms': timeout})
229
230  def GetCurrentUrl(self):
231    return self.ExecuteCommand(Command.GET_CURRENT_URL)
232
233  def GoBack(self):
234    return self.ExecuteCommand(Command.GO_BACK)
235
236  def GoForward(self):
237    return self.ExecuteCommand(Command.GO_FORWARD)
238
239  def Refresh(self):
240    return self.ExecuteCommand(Command.REFRESH)
241
242  def MouseMoveTo(self, element=None, x_offset=None, y_offset=None):
243    params = {}
244    if element is not None:
245      params['element'] = element._id
246    if x_offset is not None:
247      params['xoffset'] = x_offset
248    if y_offset is not None:
249      params['yoffset'] = y_offset
250    self.ExecuteCommand(Command.MOUSE_MOVE_TO, params)
251
252  def MouseClick(self, button=0):
253    self.ExecuteCommand(Command.MOUSE_CLICK, {'button': button})
254
255  def MouseButtonDown(self, button=0):
256    self.ExecuteCommand(Command.MOUSE_BUTTON_DOWN, {'button': button})
257
258  def MouseButtonUp(self, button=0):
259    self.ExecuteCommand(Command.MOUSE_BUTTON_UP, {'button': button})
260
261  def MouseDoubleClick(self, button=0):
262    self.ExecuteCommand(Command.MOUSE_DOUBLE_CLICK, {'button': button})
263
264  def TouchDown(self, x, y):
265    self.ExecuteCommand(Command.TOUCH_DOWN, {'x': x, 'y': y})
266
267  def TouchUp(self, x, y):
268    self.ExecuteCommand(Command.TOUCH_UP, {'x': x, 'y': y})
269
270  def TouchMove(self, x, y):
271    self.ExecuteCommand(Command.TOUCH_MOVE, {'x': x, 'y': y})
272
273  def TouchFlick(self, element, xoffset, yoffset, speed):
274    params = {
275        'element': element._id,
276        'xoffset': xoffset,
277        'yoffset': yoffset,
278        'speed': speed
279    }
280    self.ExecuteCommand(Command.TOUCH_FLICK, params)
281
282  def GetCookies(self):
283    return self.ExecuteCommand(Command.GET_COOKIES)
284
285  def AddCookie(self, cookie):
286    self.ExecuteCommand(Command.ADD_COOKIE, {'cookie': cookie})
287
288  def DeleteCookie(self, name):
289    self.ExecuteCommand(Command.DELETE_COOKIE, {'name': name})
290
291  def DeleteAllCookies(self):
292    self.ExecuteCommand(Command.DELETE_ALL_COOKIES)
293
294  def IsAlertOpen(self):
295    return self.ExecuteCommand(Command.GET_ALERT)
296
297  def GetAlertMessage(self):
298    return self.ExecuteCommand(Command.GET_ALERT_TEXT)
299
300  def HandleAlert(self, accept, prompt_text=''):
301    if prompt_text:
302      self.ExecuteCommand(Command.SET_ALERT_VALUE, {'text': prompt_text})
303    if accept:
304      cmd = Command.ACCEPT_ALERT
305    else:
306      cmd = Command.DISMISS_ALERT
307    self.ExecuteCommand(cmd)
308
309  def IsLoading(self):
310    return self.ExecuteCommand(Command.IS_LOADING)
311
312  def GetWindowPosition(self):
313    position = self.ExecuteCommand(Command.GET_WINDOW_POSITION,
314                                   {'windowHandle': 'current'})
315    return [position['x'], position['y']]
316
317  def SetWindowPosition(self, x, y):
318    self.ExecuteCommand(Command.SET_WINDOW_POSITION,
319                        {'windowHandle': 'current', 'x': x, 'y': y})
320
321  def GetWindowSize(self):
322    size = self.ExecuteCommand(Command.GET_WINDOW_SIZE,
323                               {'windowHandle': 'current'})
324    return [size['width'], size['height']]
325
326  def SetWindowSize(self, width, height):
327    self.ExecuteCommand(
328        Command.SET_WINDOW_SIZE,
329        {'windowHandle': 'current', 'width': width, 'height': height})
330
331  def MaximizeWindow(self):
332    self.ExecuteCommand(Command.MAXIMIZE_WINDOW, {'windowHandle': 'current'})
333
334  def Quit(self):
335    """Quits the browser and ends the session."""
336    self.ExecuteCommand(Command.QUIT)
337
338  def GetLog(self, type):
339    return self.ExecuteCommand(Command.GET_LOG, {'type': type})
340
341  def GetAvailableLogTypes(self):
342    return self.ExecuteCommand(Command.GET_AVAILABLE_LOG_TYPES)
343
344  def IsAutoReporting(self):
345    return self.ExecuteCommand(Command.IS_AUTO_REPORTING)
346
347  def SetAutoReporting(self, enabled):
348    self.ExecuteCommand(Command.SET_AUTO_REPORTING, {'enabled': enabled})
349