Giter Club home page Giter Club logo

elementium's Introduction

Elementium

http://github.com/actmd/elementium

jQuery-style syntactic sugar for highly reliable automated browser testing in Python

  • Chainable methods with obvious names
  • Easy to read
  • Concise to write
  • Built-in fault tolerance

For an introduction to why you'd want to use Elementium, take a look at the following post.

Before & After

# From http://selenium-python.readthedocs.org/en/latest/getting-started.html

from selenium import webdriver
from selenium.webdriver.common.keys import Keys

driver = webdriver.Firefox()
driver.get("http://www.python.org")
assert "Python" in driver.title
elem = driver.find_element_by_name("q")
elem.send_keys("selenium")
elem.send_keys(Keys.RETURN)
driver.close()

With Elementium

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from elementium.drivers.se import SeElements

se = SeElements(webdriver.Firefox())
se.navigate("http://www.python.org").insist(lambda e: "Python" in e.title)
se.find("q").write("selenium" + Keys.RETURN)

Installation

The easy way

pip install elementium

The developer way

git clone [email protected]:actmd/elementium.git
cd elementium
python setup.py install

Compatability

Elementium has been tested for Python 2.6, 3.4, 3.5, 3.7, and pypy using Travis CI

Usage

Elementium includes by default a wrapper for the Selenium Python Bindings. As such, all of the usage examples make use of the Selenium driver.

Wrap the browser with an Elementium object

from selenium import webdriver
from elementium.drivers.se import SeElements

se = SeElements(webdriver.Firefox())

Navigating to a web page

se.navigate("http://www.google.com")

Finding DOM elements

Elementium simplifies most of Selenium's many find methods...

  • find_element_by_id
  • find_element_by_name
  • find_element_by_tag_name
  • find_element_by_class_name
  • find_element_by_css_selector
  • find_element_by_link_text
  • find_element_by_partial_link_text

...into two find() and find_link() methods:

# Find by ID
se.find("#foo")

# Find by name
se.find("[name='foo']")

# Find by tag name
se.find("input")

# Find by class name
se.find(".cssClass")

# Find by link text
se.find_link("Click me")

# Find by partial link text
se.find_link("Click", exact=False)

find() and find_link() will also return multiple elements, if present, so you can forget about all the additional find_elements_... methods, too:

<div>...</div> <div>...</div> <div>...</div>
len(se.find("div")) # == 3

Under the hood, find() returns a new SeElements object containing a list of all of the items that matched. (These individual items are also of type SeElements.)

Getting specific items

The get method lets you pull out a specific item in a chainable manner.

<button>Foo</button> <button>Bar</button> <button>Baz</button>
# Get the second button
se.find("button").get(1)

Accessing the raw object

If you would rather get the raw object (e.g. SeleniumWebElement) that is returned by the underlying driver, use items:

# Find elements on a page for a given class
buttons = se.find("button")
for button in buttons.items:
    print type(button)

The item alias will return the first raw item:

se.find("button").item

Getting values

<input value="blerg" />
se.find("input").value() # returns 'blerg'

Clicking things

<button>Click me</button>
se.find("button").click()
<input type="checkbox" value="check1">
<input type="checkbox" value="check2">
<input type="checkbox" value="check3">
# Click all three checkboxes
se.find("input[type='checkbox']").click()

Typing

<input type="text" />
se.find("input").write("If not now, when?")

Selecting

<select>
    <option value="cb">Corned Beef</option>
    <option value="ps">Pastrami</option>
</select>
# Select by visible text
se.find("select").select(text="Corned Beef")

# Select by value
se.find("select").select(value="cb")

# Select by index
se.find("select").select(i=0)

If manipulating a multiple select, you may use the deselect() method in a similar manner:

<select multiple>
    <option value="h">Hummus</option>
    <option value="t">Tahina</option>
    <option value="c">Chips</option>
    <option value="a">Amba</option>
</select>
# Deselect by visible text
se.find("select").deselect(text="Chips")

# Deelect by value
se.find("select").deselect(value="c")

# Deselect by index
se.find("select").deselect(i=2)

# Deselect all
se.find("select").deselect()

Waiting

So far, we haven't taken any huge leaps from off-the-shelf Selenium, though we're certainly typing less!

One of the big issues with Selenium is waiting for pages to load completely and all of the retry logic that may have to be used to have tests that work well. A common solution is to wrap your code with "retry" functions.

For example, a naive way of retrying might have been:

browser = webdriver.Firefox()
els = None
while not els:
    els = browser.find_element_by_tag_name('button')
    time.sleep(0.5)

With Elementium, just tell find() to wait:

# Retry until we find a button on the page (up to 20 seconds by default)
se.find('button', wait=True)

Have a more complex success condition? Use until():

# Retry until we find 3 buttons on the page (up to 20 seconds by default)
se.find('button').until(lambda e: len(e) == 3)

# Retry for 60 seconds
se.find('button').until(lambda e: len(e) == 3, ttl=60)

Both of the above methods will raise a elementium.elements.TimeoutError if the element is not found in the specified period of time.

Basically all methods that are part of the SeElements object will be automatically retried for you. Under the hood, each selector (e.g. '.foo' or '#foo') is stored as a callback function (similar to something like lambda: selenium.find_element_by_id('foo')). This way, when any of the calls to any of the methods of an element has an expected error (StaleElementException, etc.) it will recall this function. If you perform chaining, this will actually propagate that refresh (called update()) up the entire chain to ensure that all parts of the call are valid. Cool!

(Look at the code for more detail.)

Making assertions

se.find('input').insist(lambda e: e.value() == 'Pilkington')

This works exactly like until() above, only it raises an AssertionError instead.

Other useful methods

See the full Elementium documentation for more details on the following methods.

  • filter()
  • scroll()
  • scroll_top()
  • scroll_bottom()

The following are simply more reliable versions of their Selenium counterparts. Some have been renamed for ease of use.

  • is_displayed()
  • is_enabled()
  • is_selected()
  • text()
  • tag_name()
  • attribute()
  • clear()
  • parent()
  • xpath()
  • source()
  • refresh()
  • current_url()
  • execute_script()
  • get_window_size()
  • set_window_size()
  • switch_to_active_element()

Examples

There are a couple examples in the aptly named examples directory. Take a look there for fully functioning source code.

Running the Tests

Simple

First, make sure to install the testing requirements

pip install -r requirements-tests.txt

Then run the tests via nose

nosetests

Running the tests across multiple python versions in parallel

If you don't trust our Travis CI badge above, you can run all of the tests across multiple python versions by using pyenv and detox. A good writeup for what you need to do to set this up can be found here. If you are using OS X and installed pyenv with brew, make sure to follow these instructions as well.

# Assuming OS X with Homebrew installed
brew update
brew install pyenv
# Install tox and detox
pip install tox
pip install detox

You'll want to make sure that you have all of the different python versions are installed so that they can be tested:

# Install the pyenv versions
pyenv install 2.7.13
pyenv install 3.4.7
pyenv install 3.5.4
pyenv install 3.6.0
pyenv install 3.7.3

# Set all these to be global versions
pyenv global system 2.7.13 3.4.7 3.5.4 3.6.0 3.7.3

# Make sure that they are all there (they should all have a * next to them)
pyenv versions

Note, if you are running in to issues installing python due to a zlib issue, then take a look at the solution in this thread which can be summarized by saying that you should install your pyenv versions as follows

CFLAGS="-I$(xcrun --show-sdk-path)/usr/include" before your pyenv install ...

Once you get everything installed, you can run the tests across the different versions as follows.

detox

Note this assumes that you have detox installed globally.

Assuming all goes well, you should see a result akin to

py27-1.7: commands succeeded
py27-1.8: commands succeeded
py27-1.9: commands succeeded
py27-master: commands succeeded
py34-1.7: commands succeeded
py34-1.8: commands succeeded
py34-1.9: commands succeeded
py34-master: commands succeeded
py35-1.8: commands succeeded
py35-1.9: commands succeeded
py35-master: commands succeeded
congratulations :)

If you run in to an issue with running detox, make sure that you have the latest version of pip as there are some issues with pyenv and older versions of pip.

The Future

There are several features planned for the future to improve Elementium and they will be rolled out as they pass through our internal scrutiny. If you have great ideas, you can be part of Elementium's future as well!

Contributing

If you would like to contribute to this project, you will need to use git flow. This way, any and all changes happen on the development branch and not on the master branch. As such, after you have git-flow-ified your elementium git repo, create a pull request for your branch, and we'll take it from there.

Acknowledgements

Elementium has been a collaborative effort of ACT.md.

elementium's People

Contributors

ekostadinov avatar prschmid avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

elementium's Issues

Feature: Lazy load items

Instead of evaluating the function passed to Elements on __init__, evaluate it on the first time items are accessed.

Configurable Selenium version

Currently the Selenium version is locked to 2.48, but some users would like to integrate Elementium to their own setup and versions. In my case:

elementium 1.1.10 has requirement selenium==2.48.0, but you'll have selenium 3.0.2 which is incompatible.

I would be happy to try and implement the needed changes, after a discussion @prschmid.

Async is a keyword in Python 3.7.*

Hello,

within elements.py and se.py files, there is an 'async' parameter used which is a keyword in python3.7.* version. It could be renamed to -> asynchronous.

Regards,
Kamil

execute_script() should have an element option to run on

Hi,

Fist: I want to thank you for your hard work on this lib, it is awesome.

Regarding the issue:
execute_script() should have an element option in order to run code on that specific element. For example if I want to highlight the element during execution for debugging purposes using the default execute_script I can but using elementium it is harder.

Sample code (context.driver is just the driver created in the before_feature of the behave script):

browser = SeElements(context.driver)
browser.navigate("https://www.toolsqa.com/automation-practice-form/").insist(lambda e: "Demo Form" in e.title())
first_name = browser.find("input[name='firstname']", wait=True, ttl=context.timeout)
first_name.write("First Name")
script = "arguments[0].style.border='5px solid #6bf442'"

se_first_name = context.driver.find_element_by_css_selector("input[name='firstname']")
context.driver.execute_script(script, se_first_name)

first_name.execute_script(script=script)

Looking into the implementation of execute_script in se.py it is called directly on self.browser

Issue with Elementium and six 1.11

Copied from an email bug report.

I do the following steps:

  1. pip install elementium

from selenium import webdriver
from elementium.drivers.se import SeElements
  1. it crashes with the following errors:
Traceback (most recent call last):
  File "fb_fill.py", line 15, in <module>
    import elementium.drivers.se
  File "/home/great/myapp/myappenv/local/lib/python2.7/site-packages/elementium/__init__.py", line 4, in <module>
    from . import elements
  File "/home/great/myapp/myappenv/local/lib/python2.7/site-packages/elementium/elements.py", line 14, in <module>
    from elementium.waiters import ConditionElementsWaiter
  File "/home/great/myapp/myappenv/local/lib/python2.7/site-packages/elementium/waiters.py", line 22, in <module>
    class Waiter(six.with_metaclass(abc.ABCMeta, object)):
  File "/home/great/myapp/myappenv/lib/python2.7/abc.py", line 87, in __new__
    cls = super(ABCMeta, mcls).__new__(mcls, name, bases, namespace)
TypeError: Error when calling the metaclass bases
    metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

if I downgrade to six 1.10, everything seems to be OK.

Python 3.*

Is there any chance for update elementium to Python 3.5?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.