Everyone's reaction to Selenium is the same. First, it's a sense of awe for how it can navigate around a website, enter text, and click on buttons. This is soon followed by frustration when finding out that unlike most code that has been written to develop the application, testing in browsers is particularly fickle; widgets don't load in the same number of seconds in different runs, and elements become stale when the DOM updates. This leads to a state of despair and generally results in brittle testing code that is littered with
time.sleep()
calls and with ugly retry logic. This was exactly my progression through the journey of automated browser testing. Fortunately, it didn't end there.Before I continue, let me be very clear in saying that I am eternally grateful to the folks that created and maintain Selenium - I could not do any of the testing that I do without it. It is a fantastic library that does a fantastic job. It just happens to have been created in an era when dynamic pages littered with AJAX calls was not the norm. Any of this work or potential "criticisms" are meant to be taken with this in mind -- without Selenium, we wouldn't even be this far.
If one takes a look at what people try to accomplish with automated browser testing it is:
- Select browser elements, and perform actions based on selected elements
- Assert to insist that particular elements exist
- Waiting for an element to appear on the page
- Handling
StaleElementExceptions
spin_assert
that spins and waits until the element appears. Here is a modified version of what they present (but the basic idea is exactly the same):
def spin_assert_equal(element, assertion):
for i in xrange(60):
try:
# Re-get the element from the page via the lambda
# and assert they are equal
assert element() = assertion
return
except Exception, e:
pass
sleep(1)
# If we get here, give it one more try, or make it raise an
# AssertionError
assert element() = assertion
# Create a lambda that finds the element
element = lambda: selenium.find_element_by_id('foo').text
# Try the assertion
spin_assert(element, 'FOO')
For full details on their method, check out their post here. Does the above method work? Of course! But let me ask you this. Which of the following do you think is easier to read, understand, and maintain?
# Option 1: Pure selenium
element = lambda: selenium.find_element_by_id('foo').text
spin_assert(element, 'FOO')
# Option 2: Elementium
elements.find('#foo').insist(lambda e: e.text() == 'FOO')
I'm hoping that you say Option 2. Taking cues from jQuery, Elementium allows you to chain commands and handles all of the automatic retrying for you. For example, you could do something like this if you really wanted to:
elements.\
find('.foo').\
filter(lambda e: e.text().startswith('a')).\
until(lambda e: len(e) == 2).\
foreach(lamba e: e.click())
This here will find all elements with the CSS class 'foo', filter it down to the elements that have text starting with 'a', insist that there are exactly 2 of them, and then click on them. Yes, all of that in 1 line of code. Oh, and did I mention that this handles all of the retry logic automatically for you? (Btw, you can use the click()
method on a list of elements as well elements.find('.foo').click()
)How about if you want to wait until there are exactly 3 elements of this type on the page (because, for example, you have made some AJAX calls that creates three notifications)
elements.find('.notification').until(lambda e: len(e) == 3)
That's it. This will retry (for 20 seconds by default) and wait until there are three elements with the CSS class 'nofitication'."How does it do all this magic," you ask. I won't go into too much detail here, but under the hood, each selector (e.g.
'.foo'
or '#foo'
) is stored as a callback function (similar to the lambda: selenium.find_element_by_id('foo')
of the first example. 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!
Ok, and now to a "full" example that shows you exactly how to set everything up so that you can start using this today.
from selenium import webdriver from elementium.drivers.se import SeElements # Initialize the elements wrapper elements = SeElements(webdriver.Firefox()) # Do cool stuff elements.find('#foo')It's as simple as that!
Although this library is still under development, you can get this library and make use of it now by getting it from this public GitHub repo: https://github.com/actmd/elementium. There you will find more usage examples, the code, etc. We have been using it at ACT.md for the past 3 months and it has reduced our testing code, made it more stable, and made it much more legible. I'd say that's a win, win, win situation!
Don't hesitate to let me know if you have any questions, suggestions, etc. Happy testing.
Can you show some complete and working example of Elementium and Behave, esepcialy with page objects?
ReplyDeleteSure -- thanks for the suggestion. I'll whip something up in the next week or so. I'll be sure to update the post with it and have it as part of the project under tutorials in the github repo.
DeleteI've put together a simple Behave example and it's now posted under examples/ in the GitHub repo. I didn't include the PageObject pattern as not everyone uses it. However, it should be fairly trivial to modify the example provided by Selenium (http://selenium-python.readthedocs.org/en/latest/page-objects.html) to pass in the Elementium object instead of the driver.
Delete(Note, you may have to switch to the develop branch in the repo as the next release isn't finalized yet.)
Hi, he tried to say that you can implement elementium with python only but you can test any webapp with it :)
ReplyDeleteHi Patrick, does elementium works with IE browser?
ReplyDeleteThanks
As Elementium is a wrapper around Selenium, it should work with any browser that Selenium has a driver for. I haven’t tested this, but here is the link to the IE driver: https://github.com/SeleniumHQ/selenium/wiki/InternetExplorerDriver once you have that downloaded and setup, the rest should be pretty much the same as above. Hope that helps!
DeleteThanks for your quick response. I came from using splinter. As you know, it is a wrapper too, but a bit different.
DeleteThey wrap also how the driver is called, and they do not support IE. So to use IE I mush edit a splinter file and convert it to call IE. It may work but is not official, so any problem will not be taking in consideration.
I see Elementium uses: "se = SeElements(webdriver.Firefox())", like inheriting directly from webdriver, pretty cool indeed :)
Where is the best place to talk about elementium development/news?
Glad to hear that you are liking it!
DeleteGreat question regarding where to talk about Elementium. I just set up a google group forum for this sort of thing.
https://groups.google.com/d/forum/elementiumlib
Hello, I am using Elementium with Python and behave. I am trying to close the browser after testing and it says "AttributeError: 'SeElements' object has no attribute 'close'". I tried with quit but it's not working either. How can I close the browser automatically?
ReplyDelete