123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550 |
- # Licensed to the Software Freedom Conservancy (SFC) under one
- # or more contributor license agreements. See the NOTICE file
- # distributed with this work for additional information
- # regarding copyright ownership. The SFC licenses this file
- # to you under the Apache License, Version 2.0 (the
- # "License"); you may not use this file except in compliance
- # with the License. You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing,
- # software distributed under the License is distributed on an
- # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- # KIND, either express or implied. See the License for the
- # specific language governing permissions and limitations
- # under the License.
- import base64
- import hashlib
- import pkgutil
- import os
- import zipfile
- from .command import Command
- from selenium.common.exceptions import WebDriverException
- from selenium.webdriver.common.by import By
- from selenium.webdriver.common.utils import keys_to_typing
- # Python 3 imports
- try:
- str = basestring
- except NameError:
- pass
- try:
- from StringIO import StringIO as IOStream
- except ImportError: # 3+
- from io import BytesIO as IOStream
- getAttribute_js = pkgutil.get_data(__package__, 'getAttribute.js').decode('utf8')
- isDisplayed_js = pkgutil.get_data(__package__, 'isDisplayed.js').decode('utf8')
- class WebElement(object):
- """Represents a DOM element.
- Generally, all interesting operations that interact with a document will be
- performed through this interface.
- All method calls will do a freshness check to ensure that the element
- reference is still valid. This essentially determines whether or not the
- element is still attached to the DOM. If this test fails, then an
- ``StaleElementReferenceException`` is thrown, and all future calls to this
- instance will fail."""
- def __init__(self, parent, id_, w3c=False):
- self._parent = parent
- self._id = id_
- self._w3c = w3c
- def __repr__(self):
- return '<{0.__module__}.{0.__name__} (session="{1}", element="{2}")>'.format(
- type(self), self._parent.session_id, self._id)
- @property
- def tag_name(self):
- """This element's ``tagName`` property."""
- return self._execute(Command.GET_ELEMENT_TAG_NAME)['value']
- @property
- def text(self):
- """The text of the element."""
- return self._execute(Command.GET_ELEMENT_TEXT)['value']
- def click(self):
- """Clicks the element."""
- self._execute(Command.CLICK_ELEMENT)
- def submit(self):
- """Submits a form."""
- if self._w3c:
- form = self.find_element(By.XPATH, "./ancestor-or-self::form")
- self._parent.execute_script(
- "var e = arguments[0].ownerDocument.createEvent('Event');"
- "e.initEvent('submit', true, true);"
- "if (arguments[0].dispatchEvent(e)) { arguments[0].submit() }", form)
- else:
- self._execute(Command.SUBMIT_ELEMENT)
- def clear(self):
- """Clears the text if it's a text entry element."""
- self._execute(Command.CLEAR_ELEMENT)
- def get_property(self, name):
- """
- Gets the given property of the element.
- :Args:
- - name - Name of the property to retrieve.
- Example::
- # Check if the "active" CSS class is applied to an element.
- text_length = target_element.get_property("text_length")
- """
- try:
- return self._execute(Command.GET_ELEMENT_PROPERTY, {"name": name})["value"]
- except WebDriverException:
- # if we hit an end point that doesnt understand getElementProperty lets fake it
- return self.parent.execute_script('return arguments[0][arguments[1]]', self, name)
- def get_attribute(self, name):
- """Gets the given attribute or property of the element.
- This method will first try to return the value of a property with the
- given name. If a property with that name doesn't exist, it returns the
- value of the attribute with the same name. If there's no attribute with
- that name, ``None`` is returned.
- Values which are considered truthy, that is equals "true" or "false",
- are returned as booleans. All other non-``None`` values are returned
- as strings. For attributes or properties which do not exist, ``None``
- is returned.
- :Args:
- - name - Name of the attribute/property to retrieve.
- Example::
- # Check if the "active" CSS class is applied to an element.
- is_active = "active" in target_element.get_attribute("class")
- """
- attributeValue = ''
- if self._w3c:
- attributeValue = self.parent.execute_script(
- "return (%s).apply(null, arguments);" % getAttribute_js,
- self, name)
- else:
- resp = self._execute(Command.GET_ELEMENT_ATTRIBUTE, {'name': name})
- attributeValue = resp.get('value')
- if attributeValue is not None:
- if name != 'value' and attributeValue.lower() in ('true', 'false'):
- attributeValue = attributeValue.lower()
- return attributeValue
- def is_selected(self):
- """Returns whether the element is selected.
- Can be used to check if a checkbox or radio button is selected.
- """
- return self._execute(Command.IS_ELEMENT_SELECTED)['value']
- def is_enabled(self):
- """Returns whether the element is enabled."""
- return self._execute(Command.IS_ELEMENT_ENABLED)['value']
- def find_element_by_id(self, id_):
- """Finds element within this element's children by ID.
- :Args:
- - id\_ - ID of child element to locate.
- """
- return self.find_element(by=By.ID, value=id_)
- def find_elements_by_id(self, id_):
- """Finds a list of elements within this element's children by ID.
- :Args:
- - id\_ - Id of child element to find.
- """
- return self.find_elements(by=By.ID, value=id_)
- def find_element_by_name(self, name):
- """Finds element within this element's children by name.
- :Args:
- - name - name property of the element to find.
- """
- return self.find_element(by=By.NAME, value=name)
- def find_elements_by_name(self, name):
- """Finds a list of elements within this element's children by name.
- :Args:
- - name - name property to search for.
- """
- return self.find_elements(by=By.NAME, value=name)
- def find_element_by_link_text(self, link_text):
- """Finds element within this element's children by visible link text.
- :Args:
- - link_text - Link text string to search for.
- """
- return self.find_element(by=By.LINK_TEXT, value=link_text)
- def find_elements_by_link_text(self, link_text):
- """Finds a list of elements within this element's children by visible link text.
- :Args:
- - link_text - Link text string to search for.
- """
- return self.find_elements(by=By.LINK_TEXT, value=link_text)
- def find_element_by_partial_link_text(self, link_text):
- """Finds element within this element's children by partially visible link text.
- :Args:
- - link_text - Link text string to search for.
- """
- return self.find_element(by=By.PARTIAL_LINK_TEXT, value=link_text)
- def find_elements_by_partial_link_text(self, link_text):
- """Finds a list of elements within this element's children by link text.
- :Args:
- - link_text - Link text string to search for.
- """
- return self.find_elements(by=By.PARTIAL_LINK_TEXT, value=link_text)
- def find_element_by_tag_name(self, name):
- """Finds element within this element's children by tag name.
- :Args:
- - name - name of html tag (eg: h1, a, span)
- """
- return self.find_element(by=By.TAG_NAME, value=name)
- def find_elements_by_tag_name(self, name):
- """Finds a list of elements within this element's children by tag name.
- :Args:
- - name - name of html tag (eg: h1, a, span)
- """
- return self.find_elements(by=By.TAG_NAME, value=name)
- def find_element_by_xpath(self, xpath):
- """Finds element by xpath.
- :Args:
- xpath - xpath of element to locate. "//input[@class='myelement']"
- Note: The base path will be relative to this element's location.
- This will select the first link under this element.
- ::
- myelement.find_elements_by_xpath(".//a")
- However, this will select the first link on the page.
- ::
- myelement.find_elements_by_xpath("//a")
- """
- return self.find_element(by=By.XPATH, value=xpath)
- def find_elements_by_xpath(self, xpath):
- """Finds elements within the element by xpath.
- :Args:
- - xpath - xpath locator string.
- Note: The base path will be relative to this element's location.
- This will select all links under this element.
- ::
- myelement.find_elements_by_xpath(".//a")
- However, this will select all links in the page itself.
- ::
- myelement.find_elements_by_xpath("//a")
- """
- return self.find_elements(by=By.XPATH, value=xpath)
- def find_element_by_class_name(self, name):
- """Finds element within this element's children by class name.
- :Args:
- - name - class name to search for.
- """
- return self.find_element(by=By.CLASS_NAME, value=name)
- def find_elements_by_class_name(self, name):
- """Finds a list of elements within this element's children by class name.
- :Args:
- - name - class name to search for.
- """
- return self.find_elements(by=By.CLASS_NAME, value=name)
- def find_element_by_css_selector(self, css_selector):
- """Finds element within this element's children by CSS selector.
- :Args:
- - css_selector - CSS selctor string, ex: 'a.nav#home'
- """
- return self.find_element(by=By.CSS_SELECTOR, value=css_selector)
- def find_elements_by_css_selector(self, css_selector):
- """Finds a list of elements within this element's children by CSS selector.
- :Args:
- - css_selector - CSS selctor string, ex: 'a.nav#home'
- """
- return self.find_elements(by=By.CSS_SELECTOR, value=css_selector)
- def send_keys(self, *value):
- """Simulates typing into the element.
- :Args:
- - value - A string for typing, or setting form fields. For setting
- file inputs, this could be a local file path.
- Use this to send simple key events or to fill out form fields::
- form_textfield = driver.find_element_by_name('username')
- form_textfield.send_keys("admin")
- This can also be used to set file inputs.
- ::
- file_input = driver.find_element_by_name('profilePic')
- file_input.send_keys("path/to/profilepic.gif")
- # Generally it's better to wrap the file path in one of the methods
- # in os.path to return the actual path to support cross OS testing.
- # file_input.send_keys(os.path.abspath("path/to/profilepic.gif"))
- """
- # transfer file to another machine only if remote driver is used
- # the same behaviour as for java binding
- if self.parent._is_remote:
- local_file = self.parent.file_detector.is_local_file(*value)
- if local_file is not None:
- value = self._upload(local_file)
- self._execute(Command.SEND_KEYS_TO_ELEMENT,
- {'text': "".join(keys_to_typing(value)),
- 'value': keys_to_typing(value)})
- # RenderedWebElement Items
- def is_displayed(self):
- """Whether the element is visible to a user."""
- # Only go into this conditional for browsers that don't use the atom themselves
- if self._w3c and self.parent.capabilities['browserName'] == 'safari':
- return self.parent.execute_script(
- "return (%s).apply(null, arguments);" % isDisplayed_js,
- self)
- else:
- return self._execute(Command.IS_ELEMENT_DISPLAYED)['value']
- @property
- def location_once_scrolled_into_view(self):
- """THIS PROPERTY MAY CHANGE WITHOUT WARNING. Use this to discover
- where on the screen an element is so that we can click it. This method
- should cause the element to be scrolled into view.
- Returns the top lefthand corner location on the screen, or ``None`` if
- the element is not visible.
- """
- if self._w3c:
- old_loc = self._execute(Command.W3C_EXECUTE_SCRIPT, {
- 'script': "arguments[0].scrollIntoView(true); return arguments[0].getBoundingClientRect()",
- 'args': [self]})['value']
- return {"x": round(old_loc['x']),
- "y": round(old_loc['y'])}
- else:
- return self._execute(Command.GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW)['value']
- @property
- def size(self):
- """The size of the element."""
- size = {}
- if self._w3c:
- size = self._execute(Command.GET_ELEMENT_RECT)['value']
- else:
- size = self._execute(Command.GET_ELEMENT_SIZE)['value']
- new_size = {"height": size["height"],
- "width": size["width"]}
- return new_size
- def value_of_css_property(self, property_name):
- """The value of a CSS property."""
- return self._execute(Command.GET_ELEMENT_VALUE_OF_CSS_PROPERTY, {
- 'propertyName': property_name})['value']
- @property
- def location(self):
- """The location of the element in the renderable canvas."""
- if self._w3c:
- old_loc = self._execute(Command.GET_ELEMENT_RECT)['value']
- else:
- old_loc = self._execute(Command.GET_ELEMENT_LOCATION)['value']
- new_loc = {"x": round(old_loc['x']),
- "y": round(old_loc['y'])}
- return new_loc
- @property
- def rect(self):
- """A dictionary with the size and location of the element."""
- return self._execute(Command.GET_ELEMENT_RECT)['value']
- @property
- def screenshot_as_base64(self):
- """
- Gets the screenshot of the current element as a base64 encoded string.
- :Usage:
- img_b64 = element.screenshot_as_base64
- """
- return self._execute(Command.ELEMENT_SCREENSHOT)['value']
- @property
- def screenshot_as_png(self):
- """
- Gets the screenshot of the current element as a binary data.
- :Usage:
- element_png = element.screenshot_as_png
- """
- return base64.b64decode(self.screenshot_as_base64.encode('ascii'))
- def screenshot(self, filename):
- """
- Gets the screenshot of the current element. Returns False if there is
- any IOError, else returns True. Use full paths in your filename.
- :Args:
- - filename: The full path you wish to save your screenshot to.
- :Usage:
- element.screenshot('/Screenshots/foo.png')
- """
- png = self.screenshot_as_png
- try:
- with open(filename, 'wb') as f:
- f.write(png)
- except IOError:
- return False
- finally:
- del png
- return True
- @property
- def parent(self):
- """Internal reference to the WebDriver instance this element was found from."""
- return self._parent
- @property
- def id(self):
- """Internal ID used by selenium.
- This is mainly for internal use. Simple use cases such as checking if 2
- webelements refer to the same element, can be done using ``==``::
- if element1 == element2:
- print("These 2 are equal")
- """
- return self._id
- def __eq__(self, element):
- return hasattr(element, 'id') and self._id == element.id
- def __ne__(self, element):
- return not self.__eq__(element)
- # Private Methods
- def _execute(self, command, params=None):
- """Executes a command against the underlying HTML element.
- Args:
- command: The name of the command to _execute as a string.
- params: A dictionary of named parameters to send with the command.
- Returns:
- The command's JSON response loaded into a dictionary object.
- """
- if not params:
- params = {}
- params['id'] = self._id
- return self._parent.execute(command, params)
- def find_element(self, by=By.ID, value=None):
- if self._w3c:
- if by == By.ID:
- by = By.CSS_SELECTOR
- value = '[id="%s"]' % value
- elif by == By.TAG_NAME:
- by = By.CSS_SELECTOR
- elif by == By.CLASS_NAME:
- by = By.CSS_SELECTOR
- value = ".%s" % value
- elif by == By.NAME:
- by = By.CSS_SELECTOR
- value = '[name="%s"]' % value
- return self._execute(Command.FIND_CHILD_ELEMENT,
- {"using": by, "value": value})['value']
- def find_elements(self, by=By.ID, value=None):
- if self._w3c:
- if by == By.ID:
- by = By.CSS_SELECTOR
- value = '[id="%s"]' % value
- elif by == By.TAG_NAME:
- by = By.CSS_SELECTOR
- elif by == By.CLASS_NAME:
- by = By.CSS_SELECTOR
- value = ".%s" % value
- elif by == By.NAME:
- by = By.CSS_SELECTOR
- value = '[name="%s"]' % value
- return self._execute(Command.FIND_CHILD_ELEMENTS,
- {"using": by, "value": value})['value']
- def __hash__(self):
- return int(hashlib.md5(self._id.encode('utf-8')).hexdigest(), 16)
- def _upload(self, filename):
- fp = IOStream()
- zipped = zipfile.ZipFile(fp, 'w', zipfile.ZIP_DEFLATED)
- zipped.write(filename, os.path.split(filename)[1])
- zipped.close()
- content = base64.encodestring(fp.getvalue())
- if not isinstance(content, str):
- content = content.decode('utf-8')
- try:
- return self._execute(Command.UPLOAD_FILE, {'file': content})['value']
- except WebDriverException as e:
- if "Unrecognized command: POST" in e.__str__():
- return filename
- elif "Command not found: POST " in e.__str__():
- return filename
- elif '{"status":405,"value":["GET","HEAD","DELETE"]}' in e.__str__():
- return filename
- else:
- raise e
|