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
18"""The WebDriver implementation."""
19
20import base64
21import copy
22import warnings
23from contextlib import contextmanager
24
25from .command import Command
26from .webelement import WebElement
27from .remote_connection import RemoteConnection
28from .errorhandler import ErrorHandler
29from .switch_to import SwitchTo
30from .mobile import Mobile
31from .file_detector import FileDetector, LocalFileDetector
32from selenium.common.exceptions import (InvalidArgumentException,
33                                        WebDriverException,
34                                        NoSuchCookieException)
35from selenium.webdriver.common.by import By
36from selenium.webdriver.common.html5.application_cache import ApplicationCache
37
38try:
39    str = basestring
40except NameError:
41    pass
42
43
44_W3C_CAPABILITY_NAMES = frozenset([
45    'acceptInsecureCerts',
46    'browserName',
47    'browserVersion',
48    'platformName',
49    'pageLoadStrategy',
50    'proxy',
51    'setWindowRect',
52    'timeouts',
53    'unhandledPromptBehavior',
54])
55
56_OSS_W3C_CONVERSION = {
57    'acceptSslCerts': 'acceptInsecureCerts',
58    'version': 'browserVersion',
59    'platform': 'platformName'
60}
61
62
63def _make_w3c_caps(caps):
64    """Makes a W3C alwaysMatch capabilities object.
65
66    Filters out capability names that are not in the W3C spec. Spec-compliant
67    drivers will reject requests containing unknown capability names.
68
69    Moves the Firefox profile, if present, from the old location to the new Firefox
70    options object.
71
72    :Args:
73     - caps - A dictionary of capabilities requested by the caller.
74    """
75    caps = copy.deepcopy(caps)
76    profile = caps.get('firefox_profile')
77    always_match = {}
78    if caps.get('proxy') and caps['proxy'].get('proxyType'):
79        caps['proxy']['proxyType'] = caps['proxy']['proxyType'].lower()
80    for k, v in caps.items():
81        if v and k in _OSS_W3C_CONVERSION:
82            always_match[_OSS_W3C_CONVERSION[k]] = v.lower() if k == 'platform' else v
83        if k in _W3C_CAPABILITY_NAMES or ':' in k:
84            always_match[k] = v
85    if profile:
86        moz_opts = always_match.get('moz:firefoxOptions', {})
87        # If it's already present, assume the caller did that intentionally.
88        if 'profile' not in moz_opts:
89            # Don't mutate the original capabilities.
90            new_opts = copy.deepcopy(moz_opts)
91            new_opts['profile'] = profile
92            always_match['moz:firefoxOptions'] = new_opts
93    return {"firstMatch": [{}], "alwaysMatch": always_match}
94
95
96class WebDriver(object):
97    """
98    Controls a browser by sending commands to a remote server.
99    This server is expected to be running the WebDriver wire protocol
100    as defined at
101    https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
102
103    :Attributes:
104     - session_id - String ID of the browser session started and controlled by this WebDriver.
105     - capabilities - Dictionaty of effective capabilities of this browser session as returned
106         by the remote server. See https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities
107     - command_executor - remote_connection.RemoteConnection object used to execute commands.
108     - error_handler - errorhandler.ErrorHandler object used to handle errors.
109    """
110
111    _web_element_cls = WebElement
112
113    def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub',
114                 desired_capabilities=None, browser_profile=None, proxy=None,
115                 keep_alive=False, file_detector=None, options=None):
116        """
117        Create a new driver that will issue commands using the wire protocol.
118
119        :Args:
120         - command_executor - Either a string representing URL of the remote server or a custom
121             remote_connection.RemoteConnection object. Defaults to 'http://127.0.0.1:4444/wd/hub'.
122         - desired_capabilities - A dictionary of capabilities to request when
123             starting the browser session. Required parameter.
124         - browser_profile - A selenium.webdriver.firefox.firefox_profile.FirefoxProfile object.
125             Only used if Firefox is requested. Optional.
126         - proxy - A selenium.webdriver.common.proxy.Proxy object. The browser session will
127             be started with given proxy settings, if possible. Optional.
128         - keep_alive - Whether to configure remote_connection.RemoteConnection to use
129             HTTP keep-alive. Defaults to False.
130         - file_detector - Pass custom file detector object during instantiation. If None,
131             then default LocalFileDetector() will be used.
132         - options - instance of a driver options.Options class
133        """
134        capabilities = {}
135        if options is not None:
136            capabilities = options.to_capabilities()
137        if desired_capabilities is not None:
138            if not isinstance(desired_capabilities, dict):
139                raise WebDriverException("Desired Capabilities must be a dictionary")
140            else:
141                capabilities.update(desired_capabilities)
142        if proxy is not None:
143            warnings.warn("Please use FirefoxOptions to set proxy",
144                          DeprecationWarning, stacklevel=2)
145            proxy.add_to_capabilities(capabilities)
146        self.command_executor = command_executor
147        if type(self.command_executor) is bytes or isinstance(self.command_executor, str):
148            self.command_executor = RemoteConnection(command_executor, keep_alive=keep_alive)
149        self._is_remote = True
150        self.session_id = None
151        self.capabilities = {}
152        self.error_handler = ErrorHandler()
153        self.start_client()
154        if browser_profile is not None:
155            warnings.warn("Please use FirefoxOptions to set browser profile",
156                          DeprecationWarning, stacklevel=2)
157        self.start_session(capabilities, browser_profile)
158        self._switch_to = SwitchTo(self)
159        self._mobile = Mobile(self)
160        self.file_detector = file_detector or LocalFileDetector()
161
162    def __repr__(self):
163        return '<{0.__module__}.{0.__name__} (session="{1}")>'.format(
164            type(self), self.session_id)
165
166    def __enter__(self):
167        return self
168
169    def __exit__(self, *args):
170        self.quit()
171
172    @contextmanager
173    def file_detector_context(self, file_detector_class, *args, **kwargs):
174        """
175        Overrides the current file detector (if necessary) in limited context.
176        Ensures the original file detector is set afterwards.
177
178        Example:
179
180        with webdriver.file_detector_context(UselessFileDetector):
181            someinput.send_keys('/etc/hosts')
182
183        :Args:
184         - file_detector_class - Class of the desired file detector. If the class is different
185             from the current file_detector, then the class is instantiated with args and kwargs
186             and used as a file detector during the duration of the context manager.
187         - args - Optional arguments that get passed to the file detector class during
188             instantiation.
189         - kwargs - Keyword arguments, passed the same way as args.
190        """
191        last_detector = None
192        if not isinstance(self.file_detector, file_detector_class):
193            last_detector = self.file_detector
194            self.file_detector = file_detector_class(*args, **kwargs)
195        try:
196            yield
197        finally:
198            if last_detector is not None:
199                self.file_detector = last_detector
200
201    @property
202    def mobile(self):
203        return self._mobile
204
205    @property
206    def name(self):
207        """Returns the name of the underlying browser for this instance.
208
209        :Usage:
210            name = driver.name
211        """
212        if 'browserName' in self.capabilities:
213            return self.capabilities['browserName']
214        else:
215            raise KeyError('browserName not specified in session capabilities')
216
217    def start_client(self):
218        """
219        Called before starting a new session. This method may be overridden
220        to define custom startup behavior.
221        """
222        pass
223
224    def stop_client(self):
225        """
226        Called after executing a quit command. This method may be overridden
227        to define custom shutdown behavior.
228        """
229        pass
230
231    def start_session(self, capabilities, browser_profile=None):
232        """
233        Creates a new session with the desired capabilities.
234
235        :Args:
236         - browser_name - The name of the browser to request.
237         - version - Which browser version to request.
238         - platform - Which platform to request the browser on.
239         - javascript_enabled - Whether the new session should support JavaScript.
240         - browser_profile - A selenium.webdriver.firefox.firefox_profile.FirefoxProfile object. Only used if Firefox is requested.
241        """
242        if not isinstance(capabilities, dict):
243            raise InvalidArgumentException("Capabilities must be a dictionary")
244        if browser_profile:
245            if "moz:firefoxOptions" in capabilities:
246                capabilities["moz:firefoxOptions"]["profile"] = browser_profile.encoded
247            else:
248                capabilities.update({'firefox_profile': browser_profile.encoded})
249        w3c_caps = _make_w3c_caps(capabilities)
250        parameters = {"capabilities": w3c_caps,
251                      "desiredCapabilities": capabilities}
252        response = self.execute(Command.NEW_SESSION, parameters)
253        if 'sessionId' not in response:
254            response = response['value']
255        self.session_id = response['sessionId']
256        self.capabilities = response.get('value')
257
258        # if capabilities is none we are probably speaking to
259        # a W3C endpoint
260        if self.capabilities is None:
261            self.capabilities = response.get('capabilities')
262
263        # Double check to see if we have a W3C Compliant browser
264        self.w3c = response.get('status') is None
265        self.command_executor.w3c = self.w3c
266
267    def _wrap_value(self, value):
268        if isinstance(value, dict):
269            converted = {}
270            for key, val in value.items():
271                converted[key] = self._wrap_value(val)
272            return converted
273        elif isinstance(value, self._web_element_cls):
274            return {'ELEMENT': value.id, 'element-6066-11e4-a52e-4f735466cecf': value.id}
275        elif isinstance(value, list):
276            return list(self._wrap_value(item) for item in value)
277        else:
278            return value
279
280    def create_web_element(self, element_id):
281        """Creates a web element with the specified `element_id`."""
282        return self._web_element_cls(self, element_id, w3c=self.w3c)
283
284    def _unwrap_value(self, value):
285        if isinstance(value, dict):
286            if 'ELEMENT' in value or 'element-6066-11e4-a52e-4f735466cecf' in value:
287                wrapped_id = value.get('ELEMENT', None)
288                if wrapped_id:
289                    return self.create_web_element(value['ELEMENT'])
290                else:
291                    return self.create_web_element(value['element-6066-11e4-a52e-4f735466cecf'])
292            else:
293                for key, val in value.items():
294                    value[key] = self._unwrap_value(val)
295                return value
296        elif isinstance(value, list):
297            return list(self._unwrap_value(item) for item in value)
298        else:
299            return value
300
301    def execute(self, driver_command, params=None):
302        """
303        Sends a command to be executed by a command.CommandExecutor.
304
305        :Args:
306         - driver_command: The name of the command to execute as a string.
307         - params: A dictionary of named parameters to send with the command.
308
309        :Returns:
310          The command's JSON response loaded into a dictionary object.
311        """
312        if self.session_id is not None:
313            if not params:
314                params = {'sessionId': self.session_id}
315            elif 'sessionId' not in params:
316                params['sessionId'] = self.session_id
317
318        params = self._wrap_value(params)
319        response = self.command_executor.execute(driver_command, params)
320        if response:
321            self.error_handler.check_response(response)
322            response['value'] = self._unwrap_value(
323                response.get('value', None))
324            return response
325        # If the server doesn't send a response, assume the command was
326        # a success
327        return {'success': 0, 'value': None, 'sessionId': self.session_id}
328
329    def get(self, url):
330        """
331        Loads a web page in the current browser session.
332        """
333        self.execute(Command.GET, {'url': url})
334
335    @property
336    def title(self):
337        """Returns the title of the current page.
338
339        :Usage:
340            title = driver.title
341        """
342        resp = self.execute(Command.GET_TITLE)
343        return resp['value'] if resp['value'] is not None else ""
344
345    def find_element_by_id(self, id_):
346        """Finds an element by id.
347
348        :Args:
349         - id\_ - The id of the element to be found.
350
351        :Returns:
352         - WebElement - the element if it was found
353
354        :Raises:
355         - NoSuchElementException - if the element wasn't found
356
357        :Usage:
358            element = driver.find_element_by_id('foo')
359        """
360        return self.find_element(by=By.ID, value=id_)
361
362    def find_elements_by_id(self, id_):
363        """
364        Finds multiple elements by id.
365
366        :Args:
367         - id\_ - The id of the elements to be found.
368
369        :Returns:
370         - list of WebElement - a list with elements if any was found.  An
371           empty list if not
372
373        :Usage:
374            elements = driver.find_elements_by_id('foo')
375        """
376        return self.find_elements(by=By.ID, value=id_)
377
378    def find_element_by_xpath(self, xpath):
379        """
380        Finds an element by xpath.
381
382        :Args:
383         - xpath - The xpath locator of the element to find.
384
385        :Returns:
386         - WebElement - the element if it was found
387
388        :Raises:
389         - NoSuchElementException - if the element wasn't found
390
391        :Usage:
392            element = driver.find_element_by_xpath('//div/td[1]')
393        """
394        return self.find_element(by=By.XPATH, value=xpath)
395
396    def find_elements_by_xpath(self, xpath):
397        """
398        Finds multiple elements by xpath.
399
400        :Args:
401         - xpath - The xpath locator of the elements to be found.
402
403        :Returns:
404         - list of WebElement - a list with elements if any was found.  An
405           empty list if not
406
407        :Usage:
408            elements = driver.find_elements_by_xpath("//div[contains(@class, 'foo')]")
409        """
410        return self.find_elements(by=By.XPATH, value=xpath)
411
412    def find_element_by_link_text(self, link_text):
413        """
414        Finds an element by link text.
415
416        :Args:
417         - link_text: The text of the element to be found.
418
419        :Returns:
420         - WebElement - the element if it was found
421
422        :Raises:
423         - NoSuchElementException - if the element wasn't found
424
425        :Usage:
426            element = driver.find_element_by_link_text('Sign In')
427        """
428        return self.find_element(by=By.LINK_TEXT, value=link_text)
429
430    def find_elements_by_link_text(self, text):
431        """
432        Finds elements by link text.
433
434        :Args:
435         - link_text: The text of the elements to be found.
436
437        :Returns:
438         - list of webelement - a list with elements if any was found.  an
439           empty list if not
440
441        :Usage:
442            elements = driver.find_elements_by_link_text('Sign In')
443        """
444        return self.find_elements(by=By.LINK_TEXT, value=text)
445
446    def find_element_by_partial_link_text(self, link_text):
447        """
448        Finds an element by a partial match of its link text.
449
450        :Args:
451         - link_text: The text of the element to partially match on.
452
453        :Returns:
454         - WebElement - the element if it was found
455
456        :Raises:
457         - NoSuchElementException - if the element wasn't found
458
459        :Usage:
460            element = driver.find_element_by_partial_link_text('Sign')
461        """
462        return self.find_element(by=By.PARTIAL_LINK_TEXT, value=link_text)
463
464    def find_elements_by_partial_link_text(self, link_text):
465        """
466        Finds elements by a partial match of their link text.
467
468        :Args:
469         - link_text: The text of the element to partial match on.
470
471        :Returns:
472         - list of webelement - a list with elements if any was found.  an
473           empty list if not
474
475        :Usage:
476            elements = driver.find_elements_by_partial_link_text('Sign')
477        """
478        return self.find_elements(by=By.PARTIAL_LINK_TEXT, value=link_text)
479
480    def find_element_by_name(self, name):
481        """
482        Finds an element by name.
483
484        :Args:
485         - name: The name of the element to find.
486
487        :Returns:
488         - WebElement - the element if it was found
489
490        :Raises:
491         - NoSuchElementException - if the element wasn't found
492
493        :Usage:
494            element = driver.find_element_by_name('foo')
495        """
496        return self.find_element(by=By.NAME, value=name)
497
498    def find_elements_by_name(self, name):
499        """
500        Finds elements by name.
501
502        :Args:
503         - name: The name of the elements to find.
504
505        :Returns:
506         - list of webelement - a list with elements if any was found.  an
507           empty list if not
508
509        :Usage:
510            elements = driver.find_elements_by_name('foo')
511        """
512        return self.find_elements(by=By.NAME, value=name)
513
514    def find_element_by_tag_name(self, name):
515        """
516        Finds an element by tag name.
517
518        :Args:
519         - name - name of html tag (eg: h1, a, span)
520
521        :Returns:
522         - WebElement - the element if it was found
523
524        :Raises:
525         - NoSuchElementException - if the element wasn't found
526
527        :Usage:
528            element = driver.find_element_by_tag_name('h1')
529        """
530        return self.find_element(by=By.TAG_NAME, value=name)
531
532    def find_elements_by_tag_name(self, name):
533        """
534        Finds elements by tag name.
535
536        :Args:
537         - name - name of html tag (eg: h1, a, span)
538
539        :Returns:
540         - list of WebElement - a list with elements if any was found.  An
541           empty list if not
542
543        :Usage:
544            elements = driver.find_elements_by_tag_name('h1')
545        """
546        return self.find_elements(by=By.TAG_NAME, value=name)
547
548    def find_element_by_class_name(self, name):
549        """
550        Finds an element by class name.
551
552        :Args:
553         - name: The class name of the element to find.
554
555        :Returns:
556         - WebElement - the element if it was found
557
558        :Raises:
559         - NoSuchElementException - if the element wasn't found
560
561        :Usage:
562            element = driver.find_element_by_class_name('foo')
563        """
564        return self.find_element(by=By.CLASS_NAME, value=name)
565
566    def find_elements_by_class_name(self, name):
567        """
568        Finds elements by class name.
569
570        :Args:
571         - name: The class name of the elements to find.
572
573        :Returns:
574         - list of WebElement - a list with elements if any was found.  An
575           empty list if not
576
577        :Usage:
578            elements = driver.find_elements_by_class_name('foo')
579        """
580        return self.find_elements(by=By.CLASS_NAME, value=name)
581
582    def find_element_by_css_selector(self, css_selector):
583        """
584        Finds an element by css selector.
585
586        :Args:
587         - css_selector - CSS selector string, ex: 'a.nav#home'
588
589        :Returns:
590         - WebElement - the element if it was found
591
592        :Raises:
593         - NoSuchElementException - if the element wasn't found
594
595        :Usage:
596            element = driver.find_element_by_css_selector('#foo')
597        """
598        return self.find_element(by=By.CSS_SELECTOR, value=css_selector)
599
600    def find_elements_by_css_selector(self, css_selector):
601        """
602        Finds elements by css selector.
603
604        :Args:
605         - css_selector - CSS selector string, ex: 'a.nav#home'
606
607        :Returns:
608         - list of WebElement - a list with elements if any was found.  An
609           empty list if not
610
611        :Usage:
612            elements = driver.find_elements_by_css_selector('.foo')
613        """
614        return self.find_elements(by=By.CSS_SELECTOR, value=css_selector)
615
616    def execute_script(self, script, *args):
617        """
618        Synchronously Executes JavaScript in the current window/frame.
619
620        :Args:
621         - script: The JavaScript to execute.
622         - \*args: Any applicable arguments for your JavaScript.
623
624        :Usage:
625            driver.execute_script('return document.title;')
626        """
627        converted_args = list(args)
628        command = None
629        if self.w3c:
630            command = Command.W3C_EXECUTE_SCRIPT
631        else:
632            command = Command.EXECUTE_SCRIPT
633
634        return self.execute(command, {
635            'script': script,
636            'args': converted_args})['value']
637
638    def execute_async_script(self, script, *args):
639        """
640        Asynchronously Executes JavaScript in the current window/frame.
641
642        :Args:
643         - script: The JavaScript to execute.
644         - \*args: Any applicable arguments for your JavaScript.
645
646        :Usage:
647            script = "var callback = arguments[arguments.length - 1]; " \
648                     "window.setTimeout(function(){ callback('timeout') }, 3000);"
649            driver.execute_async_script(script)
650        """
651        converted_args = list(args)
652        if self.w3c:
653            command = Command.W3C_EXECUTE_SCRIPT_ASYNC
654        else:
655            command = Command.EXECUTE_ASYNC_SCRIPT
656
657        return self.execute(command, {
658            'script': script,
659            'args': converted_args})['value']
660
661    @property
662    def current_url(self):
663        """
664        Gets the URL of the current page.
665
666        :Usage:
667            driver.current_url
668        """
669        return self.execute(Command.GET_CURRENT_URL)['value']
670
671    @property
672    def page_source(self):
673        """
674        Gets the source of the current page.
675
676        :Usage:
677            driver.page_source
678        """
679        return self.execute(Command.GET_PAGE_SOURCE)['value']
680
681    def close(self):
682        """
683        Closes the current window.
684
685        :Usage:
686            driver.close()
687        """
688        self.execute(Command.CLOSE)
689
690    def quit(self):
691        """
692        Quits the driver and closes every associated window.
693
694        :Usage:
695            driver.quit()
696        """
697        try:
698            self.execute(Command.QUIT)
699        finally:
700            self.stop_client()
701
702    @property
703    def current_window_handle(self):
704        """
705        Returns the handle of the current window.
706
707        :Usage:
708            driver.current_window_handle
709        """
710        if self.w3c:
711            return self.execute(Command.W3C_GET_CURRENT_WINDOW_HANDLE)['value']
712        else:
713            return self.execute(Command.GET_CURRENT_WINDOW_HANDLE)['value']
714
715    @property
716    def window_handles(self):
717        """
718        Returns the handles of all windows within the current session.
719
720        :Usage:
721            driver.window_handles
722        """
723        if self.w3c:
724            return self.execute(Command.W3C_GET_WINDOW_HANDLES)['value']
725        else:
726            return self.execute(Command.GET_WINDOW_HANDLES)['value']
727
728    def maximize_window(self):
729        """
730        Maximizes the current window that webdriver is using
731        """
732        params = None
733        command = Command.W3C_MAXIMIZE_WINDOW
734        if not self.w3c:
735            command = Command.MAXIMIZE_WINDOW
736            params = {'windowHandle': 'current'}
737        self.execute(command, params)
738
739    def fullscreen_window(self):
740        """
741        Invokes the window manager-specific 'full screen' operation
742        """
743        self.execute(Command.FULLSCREEN_WINDOW)
744
745    def minimize_window(self):
746        """
747        Invokes the window manager-specific 'minimize' operation
748        """
749        self.execute(Command.MINIMIZE_WINDOW)
750
751    @property
752    def switch_to(self):
753        """
754        :Returns:
755            - SwitchTo: an object containing all options to switch focus into
756
757        :Usage:
758            element = driver.switch_to.active_element
759            alert = driver.switch_to.alert
760            driver.switch_to.default_content()
761            driver.switch_to.frame('frame_name')
762            driver.switch_to.frame(1)
763            driver.switch_to.frame(driver.find_elements_by_tag_name("iframe")[0])
764            driver.switch_to.parent_frame()
765            driver.switch_to.window('main')
766        """
767        return self._switch_to
768
769    # Target Locators
770    def switch_to_active_element(self):
771        """ Deprecated use driver.switch_to.active_element
772        """
773        warnings.warn("use driver.switch_to.active_element instead",
774                      DeprecationWarning, stacklevel=2)
775        return self._switch_to.active_element
776
777    def switch_to_window(self, window_name):
778        """ Deprecated use driver.switch_to.window
779        """
780        warnings.warn("use driver.switch_to.window instead",
781                      DeprecationWarning, stacklevel=2)
782        self._switch_to.window(window_name)
783
784    def switch_to_frame(self, frame_reference):
785        """ Deprecated use driver.switch_to.frame
786        """
787        warnings.warn("use driver.switch_to.frame instead",
788                      DeprecationWarning, stacklevel=2)
789        self._switch_to.frame(frame_reference)
790
791    def switch_to_default_content(self):
792        """ Deprecated use driver.switch_to.default_content
793        """
794        warnings.warn("use driver.switch_to.default_content instead",
795                      DeprecationWarning, stacklevel=2)
796        self._switch_to.default_content()
797
798    def switch_to_alert(self):
799        """ Deprecated use driver.switch_to.alert
800        """
801        warnings.warn("use driver.switch_to.alert instead",
802                      DeprecationWarning, stacklevel=2)
803        return self._switch_to.alert
804
805    # Navigation
806    def back(self):
807        """
808        Goes one step backward in the browser history.
809
810        :Usage:
811            driver.back()
812        """
813        self.execute(Command.GO_BACK)
814
815    def forward(self):
816        """
817        Goes one step forward in the browser history.
818
819        :Usage:
820            driver.forward()
821        """
822        self.execute(Command.GO_FORWARD)
823
824    def refresh(self):
825        """
826        Refreshes the current page.
827
828        :Usage:
829            driver.refresh()
830        """
831        self.execute(Command.REFRESH)
832
833    # Options
834    def get_cookies(self):
835        """
836        Returns a set of dictionaries, corresponding to cookies visible in the current session.
837
838        :Usage:
839            driver.get_cookies()
840        """
841        return self.execute(Command.GET_ALL_COOKIES)['value']
842
843    def get_cookie(self, name):
844        """
845        Get a single cookie by name. Returns the cookie if found, None if not.
846
847        :Usage:
848            driver.get_cookie('my_cookie')
849        """
850        if self.w3c:
851            try:
852                return self.execute(Command.GET_COOKIE, {'name': name})['value']
853            except NoSuchCookieException:
854                return None
855        else:
856            cookies = self.get_cookies()
857            for cookie in cookies:
858                if cookie['name'] == name:
859                    return cookie
860            return None
861
862    def delete_cookie(self, name):
863        """
864        Deletes a single cookie with the given name.
865
866        :Usage:
867            driver.delete_cookie('my_cookie')
868        """
869        self.execute(Command.DELETE_COOKIE, {'name': name})
870
871    def delete_all_cookies(self):
872        """
873        Delete all cookies in the scope of the session.
874
875        :Usage:
876            driver.delete_all_cookies()
877        """
878        self.execute(Command.DELETE_ALL_COOKIES)
879
880    def add_cookie(self, cookie_dict):
881        """
882        Adds a cookie to your current session.
883
884        :Args:
885         - cookie_dict: A dictionary object, with required keys - "name" and "value";
886            optional keys - "path", "domain", "secure", "expiry"
887
888        Usage:
889            driver.add_cookie({'name' : 'foo', 'value' : 'bar'})
890            driver.add_cookie({'name' : 'foo', 'value' : 'bar', 'path' : '/'})
891            driver.add_cookie({'name' : 'foo', 'value' : 'bar', 'path' : '/', 'secure':True})
892
893        """
894        self.execute(Command.ADD_COOKIE, {'cookie': cookie_dict})
895
896    # Timeouts
897    def implicitly_wait(self, time_to_wait):
898        """
899        Sets a sticky timeout to implicitly wait for an element to be found,
900           or a command to complete. This method only needs to be called one
901           time per session. To set the timeout for calls to
902           execute_async_script, see set_script_timeout.
903
904        :Args:
905         - time_to_wait: Amount of time to wait (in seconds)
906
907        :Usage:
908            driver.implicitly_wait(30)
909        """
910        if self.w3c:
911            self.execute(Command.SET_TIMEOUTS, {
912                'implicit': int(float(time_to_wait) * 1000)})
913        else:
914            self.execute(Command.IMPLICIT_WAIT, {
915                'ms': float(time_to_wait) * 1000})
916
917    def set_script_timeout(self, time_to_wait):
918        """
919        Set the amount of time that the script should wait during an
920           execute_async_script call before throwing an error.
921
922        :Args:
923         - time_to_wait: The amount of time to wait (in seconds)
924
925        :Usage:
926            driver.set_script_timeout(30)
927        """
928        if self.w3c:
929            self.execute(Command.SET_TIMEOUTS, {
930                'script': int(float(time_to_wait) * 1000)})
931        else:
932            self.execute(Command.SET_SCRIPT_TIMEOUT, {
933                'ms': float(time_to_wait) * 1000})
934
935    def set_page_load_timeout(self, time_to_wait):
936        """
937        Set the amount of time to wait for a page load to complete
938           before throwing an error.
939
940        :Args:
941         - time_to_wait: The amount of time to wait
942
943        :Usage:
944            driver.set_page_load_timeout(30)
945        """
946        try:
947            self.execute(Command.SET_TIMEOUTS, {
948                'pageLoad': int(float(time_to_wait) * 1000)})
949        except WebDriverException:
950            self.execute(Command.SET_TIMEOUTS, {
951                'ms': float(time_to_wait) * 1000,
952                'type': 'page load'})
953
954    def find_element(self, by=By.ID, value=None):
955        """
956        Find an element given a By strategy and locator. Prefer the find_element_by_* methods when
957        possible.
958
959        :Usage:
960            element = driver.find_element(By.ID, 'foo')
961
962        :rtype: WebElement
963        """
964        if self.w3c:
965            if by == By.ID:
966                by = By.CSS_SELECTOR
967                value = '[id="%s"]' % value
968            elif by == By.TAG_NAME:
969                by = By.CSS_SELECTOR
970            elif by == By.CLASS_NAME:
971                by = By.CSS_SELECTOR
972                value = ".%s" % value
973            elif by == By.NAME:
974                by = By.CSS_SELECTOR
975                value = '[name="%s"]' % value
976        return self.execute(Command.FIND_ELEMENT, {
977            'using': by,
978            'value': value})['value']
979
980    def find_elements(self, by=By.ID, value=None):
981        """
982        Find elements given a By strategy and locator. Prefer the find_elements_by_* methods when
983        possible.
984
985        :Usage:
986            elements = driver.find_elements(By.CLASS_NAME, 'foo')
987
988        :rtype: list of WebElement
989        """
990        if self.w3c:
991            if by == By.ID:
992                by = By.CSS_SELECTOR
993                value = '[id="%s"]' % value
994            elif by == By.TAG_NAME:
995                by = By.CSS_SELECTOR
996            elif by == By.CLASS_NAME:
997                by = By.CSS_SELECTOR
998                value = ".%s" % value
999            elif by == By.NAME:
1000                by = By.CSS_SELECTOR
1001                value = '[name="%s"]' % value
1002
1003        # Return empty list if driver returns null
1004        # See https://github.com/SeleniumHQ/selenium/issues/4555
1005        return self.execute(Command.FIND_ELEMENTS, {
1006            'using': by,
1007            'value': value})['value'] or []
1008
1009    @property
1010    def desired_capabilities(self):
1011        """
1012        returns the drivers current desired capabilities being used
1013        """
1014        return self.capabilities
1015
1016    def get_screenshot_as_file(self, filename):
1017        """
1018        Saves a screenshot of the current window to a PNG image file. Returns
1019           False if there is any IOError, else returns True. Use full paths in
1020           your filename.
1021
1022        :Args:
1023         - filename: The full path you wish to save your screenshot to. This
1024           should end with a `.png` extension.
1025
1026        :Usage:
1027            driver.get_screenshot_as_file('/Screenshots/foo.png')
1028        """
1029        if not filename.lower().endswith('.png'):
1030            warnings.warn("name used for saved screenshot does not match file "
1031                          "type. It should end with a `.png` extension", UserWarning)
1032        png = self.get_screenshot_as_png()
1033        try:
1034            with open(filename, 'wb') as f:
1035                f.write(png)
1036        except IOError:
1037            return False
1038        finally:
1039            del png
1040        return True
1041
1042    def save_screenshot(self, filename):
1043        """
1044        Saves a screenshot of the current window to a PNG image file. Returns
1045           False if there is any IOError, else returns True. Use full paths in
1046           your filename.
1047
1048        :Args:
1049         - filename: The full path you wish to save your screenshot to. This
1050           should end with a `.png` extension.
1051
1052        :Usage:
1053            driver.save_screenshot('/Screenshots/foo.png')
1054        """
1055        return self.get_screenshot_as_file(filename)
1056
1057    def get_screenshot_as_png(self):
1058        """
1059        Gets the screenshot of the current window as a binary data.
1060
1061        :Usage:
1062            driver.get_screenshot_as_png()
1063        """
1064        return base64.b64decode(self.get_screenshot_as_base64().encode('ascii'))
1065
1066    def get_screenshot_as_base64(self):
1067        """
1068        Gets the screenshot of the current window as a base64 encoded string
1069           which is useful in embedded images in HTML.
1070
1071        :Usage:
1072            driver.get_screenshot_as_base64()
1073        """
1074        return self.execute(Command.SCREENSHOT)['value']
1075
1076    def set_window_size(self, width, height, windowHandle='current'):
1077        """
1078        Sets the width and height of the current window. (window.resizeTo)
1079
1080        :Args:
1081         - width: the width in pixels to set the window to
1082         - height: the height in pixels to set the window to
1083
1084        :Usage:
1085            driver.set_window_size(800,600)
1086        """
1087        if self.w3c:
1088            if windowHandle != 'current':
1089                warnings.warn("Only 'current' window is supported for W3C compatibile browsers.")
1090            self.set_window_rect(width=int(width), height=int(height))
1091        else:
1092            self.execute(Command.SET_WINDOW_SIZE, {
1093                'width': int(width),
1094                'height': int(height),
1095                'windowHandle': windowHandle})
1096
1097    def get_window_size(self, windowHandle='current'):
1098        """
1099        Gets the width and height of the current window.
1100
1101        :Usage:
1102            driver.get_window_size()
1103        """
1104        command = Command.GET_WINDOW_SIZE
1105        if self.w3c:
1106            if windowHandle != 'current':
1107                warnings.warn("Only 'current' window is supported for W3C compatibile browsers.")
1108            size = self.get_window_rect()
1109        else:
1110            size = self.execute(command, {'windowHandle': windowHandle})
1111
1112        if size.get('value', None) is not None:
1113            size = size['value']
1114
1115        return {k: size[k] for k in ('width', 'height')}
1116
1117    def set_window_position(self, x, y, windowHandle='current'):
1118        """
1119        Sets the x,y position of the current window. (window.moveTo)
1120
1121        :Args:
1122         - x: the x-coordinate in pixels to set the window position
1123         - y: the y-coordinate in pixels to set the window position
1124
1125        :Usage:
1126            driver.set_window_position(0,0)
1127        """
1128        if self.w3c:
1129            if windowHandle != 'current':
1130                warnings.warn("Only 'current' window is supported for W3C compatibile browsers.")
1131            return self.set_window_rect(x=int(x), y=int(y))
1132        else:
1133            self.execute(Command.SET_WINDOW_POSITION,
1134                         {
1135                             'x': int(x),
1136                             'y': int(y),
1137                             'windowHandle': windowHandle
1138                         })
1139
1140    def get_window_position(self, windowHandle='current'):
1141        """
1142        Gets the x,y position of the current window.
1143
1144        :Usage:
1145            driver.get_window_position()
1146        """
1147        if self.w3c:
1148            if windowHandle != 'current':
1149                warnings.warn("Only 'current' window is supported for W3C compatibile browsers.")
1150            position = self.get_window_rect()
1151        else:
1152            position = self.execute(Command.GET_WINDOW_POSITION,
1153                                    {'windowHandle': windowHandle})['value']
1154
1155        return {k: position[k] for k in ('x', 'y')}
1156
1157    def get_window_rect(self):
1158        """
1159        Gets the x, y coordinates of the window as well as height and width of
1160        the current window.
1161
1162        :Usage:
1163            driver.get_window_rect()
1164        """
1165        return self.execute(Command.GET_WINDOW_RECT)['value']
1166
1167    def set_window_rect(self, x=None, y=None, width=None, height=None):
1168        """
1169        Sets the x, y coordinates of the window as well as height and width of
1170        the current window.
1171
1172        :Usage:
1173            driver.set_window_rect(x=10, y=10)
1174            driver.set_window_rect(width=100, height=200)
1175            driver.set_window_rect(x=10, y=10, width=100, height=200)
1176        """
1177        if (x is None and y is None) and (height is None and width is None):
1178            raise InvalidArgumentException("x and y or height and width need values")
1179
1180        return self.execute(Command.SET_WINDOW_RECT, {"x": x, "y": y,
1181                                                      "width": width,
1182                                                      "height": height})['value']
1183
1184    @property
1185    def file_detector(self):
1186        return self._file_detector
1187
1188    @file_detector.setter
1189    def file_detector(self, detector):
1190        """
1191        Set the file detector to be used when sending keyboard input.
1192        By default, this is set to a file detector that does nothing.
1193
1194        see FileDetector
1195        see LocalFileDetector
1196        see UselessFileDetector
1197
1198        :Args:
1199         - detector: The detector to use. Must not be None.
1200        """
1201        if detector is None:
1202            raise WebDriverException("You may not set a file detector that is null")
1203        if not isinstance(detector, FileDetector):
1204            raise WebDriverException("Detector has to be instance of FileDetector")
1205        self._file_detector = detector
1206
1207    @property
1208    def orientation(self):
1209        """
1210        Gets the current orientation of the device
1211
1212        :Usage:
1213            orientation = driver.orientation
1214        """
1215        return self.execute(Command.GET_SCREEN_ORIENTATION)['value']
1216
1217    @orientation.setter
1218    def orientation(self, value):
1219        """
1220        Sets the current orientation of the device
1221
1222        :Args:
1223         - value: orientation to set it to.
1224
1225        :Usage:
1226            driver.orientation = 'landscape'
1227        """
1228        allowed_values = ['LANDSCAPE', 'PORTRAIT']
1229        if value.upper() in allowed_values:
1230            self.execute(Command.SET_SCREEN_ORIENTATION, {'orientation': value})
1231        else:
1232            raise WebDriverException("You can only set the orientation to 'LANDSCAPE' and 'PORTRAIT'")
1233
1234    @property
1235    def application_cache(self):
1236        """ Returns a ApplicationCache Object to interact with the browser app cache"""
1237        return ApplicationCache(self)
1238
1239    @property
1240    def log_types(self):
1241        """
1242        Gets a list of the available log types
1243
1244        :Usage:
1245            driver.log_types
1246        """
1247        return self.execute(Command.GET_AVAILABLE_LOG_TYPES)['value']
1248
1249    def get_log(self, log_type):
1250        """
1251        Gets the log for a given log type
1252
1253        :Args:
1254         - log_type: type of log that which will be returned
1255
1256        :Usage:
1257            driver.get_log('browser')
1258            driver.get_log('driver')
1259            driver.get_log('client')
1260            driver.get_log('server')
1261        """
1262        return self.execute(Command.GET_LOG, {'type': log_type})['value']
1263