How to make Selenium tests reliable, scalable, and maintainable
lucidchart.com
lucidchart.com
The test suite they wrote has about ~600 tests and while they're slower than I'd like (2~3 minutes) they've been bulletproof since we got my dev environment configured properly. It includes some fairly complicated interactions, most relevantly around our calendar interface.
1. How does it compare with phantomJS?
2. What's the current webkit version?
3. How often does the javaFX webkit update?
2. Current WebKit version depends on the JRE used. Oracle Java 1.8.0_45 has WebKit version 537.44.
3. Java maintainers will update WebKit periodically, including within a major version. E.g., here they update WebKit for the 1.8.0_60 JRE: http://openjdk.java.net/jeps/239 ... Other than that I'm not sure.
I've found this is true for a lot of projects and it seems like restrictive licenses prevent projects from going mainstream.
Works very well. I can run the 100+ test cases in all IE/FF/Chrome/Safari, ios/android browser without change one line of JS/Test code. Runs fine with desktop with wire connect to cellphone browser on cell connection.
It tests out all the app backend db logic also. The time/pass/fail info are submitted back to the test backend db.
Can you give an example of how it works? Say navigate to a page, fill in a form, click submit and verify that some text is present after submitting the form
If you're going to write tests, I think it makes an insane amount of sense to emulate real world conditions as much as feasibly possible (making judgement calls on things that don't matter like speed of the mouse).
We have compiled a few tips we learned along the way in our blog post - http://novoit.eu/blog/05-5-tips-when-writing-Selenium-browse...
What was actually going wrong during that 10%?
I get something closer to 100% reliability, so I'm feeling a little perplexed by all of this.
Do you make heavy use of sleeps?
This might be exacerbated by the fact that we use the remote Browserstack Selenium hosting service so that the tests can be executed automatically as a part of our deployment process.
This is pretty good actually. It sucks if you're relying on Selenium testing for verifying your code as you're writing it, but before and after deploys to staging and production? This isn't bad at all.
http://lhorie.github.io/mithril/mithril.deps.html
You can cover a lot of ground with that approach and make an extremely fast test suite that is suitable for a save-refresh-test workflow and then you can put trickier tests in a secondary test suite that you only run once in a while (e.g. before a commit)
The extent of the testing I'm currently interested in is "load a page, does the JS on that page run without error"? It won't execute from a CLI and everyone I talked to pointed me at Selenium.
The entire suite runs in around 10 minutes on CircleCI, using 8 parallel threads (each running an instance of the Firefox Selenium driver), and it is rock solid stable.
It took us a while to get to this point, though.
The hard part is handling timing due to Javascript race conditions on the front-end. I had to write my own helper methods like "wait_for_ajax" that I sprinkle in various page object methods to wait for any jQuery AJAX requests to complete. I also use a "wait_until_true" method that can evaluate a block of code over and over until a time limit has been reached before throwing an exception. Once you figure out ways to solve those types of issues, testing things with Selenium becomes a lot more stable and easy.
I have also used the exact same techniques (page objects, custom waiter methods for race conditions, etc) to test mobile apps on iOS and Android with Selenium.
It can be a challenge, but once you have a system down and you know what you are doing, it's not so bad.
The approach in the blog post (and I think elsewhere ... not sure) is to poll the DOM with a timeout.
Is there a better solution to be add with something like `executeScript`? You could run `requestAnimationFrame`, and then poll for an indicator that the click, etc. handler has indeed finished. That way if it fails, you know about it pretty soon, without the need for long timeouts. This is all just a guess though.
Yes. And it's pretty simple:
WebDriver driver = new FirefoxDriver();
driver.get("http://somedomain/url_that_delays_loading");
WebElement myDynamicElement = (new WebDriverWait(driver, 10))
.until(ExpectedConditions.presenceOfElementLocated(By.id("myDynamicElement")));
From : http://docs.seleniumhq.org/docs/04_webdriver_advanced.jspYou can make the timeout shorter when running the test on a dev environment, though, so you get quicker feedback about errors.
click_link('bar')
expect(page).to have_content('baz')
and it will work even if the baz element is injected into the page by an Ajax request to the server triggered by clicking on bar. I've been using it for many years but I didn't check how they implement it. Maybe a callback from a MutationObserver? https://developer.mozilla.org/en-US/docs/Web/API/MutationObs...Documentation at https://github.com/jnicklas/capybara#asynchronous-javascript...
> One developer designed a way to take a screenshot of our main drawing canvas and store it in Amazon’s S3 service. This was then integrated with a screenshot comparison tool to do image comparison tests.
I would also take a look at Applitools https://applitools.com/ — they have Selenium webdriver-compatible libraries that do this screenshot taking/upload and offer a nice interface for comparing screenshot differences (and for adding ignore areas). Way fewer false failures than typical pdiff/imagemagick comparisons.
cv2.imdecode(
numpy.asarray(
bytearray(base64.decodestring(driver.get_screenshot_as_base64())),
dtype=numpy.uint8),
cv2.CV_LOAD_IMAGE_UNCHANGED)
(where `driver` is your WebDriver object, e.g. `WebDriver.Chrome()`).Then to match that frame against a previously-captured "template" image, you can use stb-tester's[1] "match" function[2] which allows you to specify things like the region to ignore and tweak the matching sensitivity.
[1] http://stb-tester.com [2] http://stb-tester.com/stb-tester-one/rev2015.1/python-api#st...
Does anyone know of such a project?
The tests were also flaky as hell but that was more to do with poor environment management. That, admittedly, was also easier to fix in python.
It provides a "page object model" implementation on top of Capybara, so you can define a model for each page you want to test, which stores the page's relative URL, and has references to all the elements on the page you care about, and methods for all the interactions you want to do with that page.
So for example, you might have a "LoginPage" model, which contains the following:
class LoginPage < SitePrism::Page
set_url "/login"
element :username_input, '.username-input'
element :password_input, '.password-input'
element :submit, '.submit-button'
def login(username, password)
load # Load the page URL in the Selenium instance
username_input.set(username) # Fill in username
password_input.set(password) # Fill in password
submit.click # Click submit
end
end
Then whenever you want to login from one of your steps, you can just do: login_page = LoginPage.new
login_page.login('whoever', 'what3v3r')
I think it's a nice abstraction as it allows more experienced test automation developers to build the page model while less experienced ones can write steps just calling the methods. You still have to pay a lot of attention to things like appropriate use of "wait for element to appear" rather than "sleep", and ensuring tests use isolated data, to get it working reliably, but we've got it working pretty well at my current place.I should write up how we have it set up at some point as we have our own app-specific framework on top of SitePrism which provides some useful abstractions to make it quicker to develop tests.
The one downside is that the developers only seem to tag official releases once in a blue moon; despite the github repo being well updated, the last push to Maven was more than half a year ago, and so depends on a rather old version of Selenium.
This one is a rough test automation, mostly used for filling in forms etc during development http://kopy.io/LMBKt (old one but to hand) handy to be able to open, login and fill in a form in a few seconds that by hand would take minutes.
I find that way works as the abstraction is only one level removed and I can just throw in methods that relate to that project.
I used Geb on a recent project, and I actually felt that the tests I built demonstrated a passable level of engineering discipline. However, Geb was really hard to learn (partly the error messages were really confusing/missing) and you're still on top of Selenium so you still get wacky exceptions and edge cases.
Nope. Integration tests. But integration tests that start a Firefox instance from scratch and have to be rerun multiple times to pass due to non-determinism are slow.
Maybe the Scala ecosystem is still immature on the side of integration testing. They could implement them in Ruby if they are familiar with the language. I don't feel OK about using two languages but at least it could enforce strict separation between integration testing and the application.
Disclaimer: I work for https://testingbot.com : at my work we offer our customers automatic retries when a test fails. Writing a Selenium test does take its time, but once you run it in parallel across hundreds of browser and os combinations, it's worth it.
This is usually a sign of either a buggy test or buggy code.
> getWithRetry takes a function with a return value
>
> def numberOfChildren(implicit user: LucidUser): Int = {
> getWithRetry() {
> user.driver.getCssElement(visibleCss).children.size
> }
> }
>
> predicateWithRetry takes function that returns a boolean and will retry on any false values
>
> def onPage(implicit user: LucidUser): Boolean = {
> predicateWithRetry() {
> user.driver.getCurrentUrl.contains(pageUrl)
> }
> }
At first I didn't get the difference between `getWithRetry` and
`predicateWithRetry`, but then I noticed that the former throws an
exception whereas the latter returns false. I infer that `getWithRetry`
will handle exceptions thrown by the retried function.In stb-tester[1] (a UI tool/framework targeted more at consumer electronics devices where the only access you have to the system-under-test is an HDMI output) after a few years we've settled on a `wait_until` function, which waits until the retried function returns a "truthy" value. `wait_until` returns whatever the retried function returns:
def miniguide_is_up():
return match("miniguide.png")
press(Key.INFO)
assert wait_until(miniguide_is_up)
# or:
if wait_until(miniguide_is_up): ...
(This is Python code.)Since we use `assert` instead of throwing exceptions in our retried function, `wait_until` seems to fill both the roles of `getWithRetry` and `predicateWithRetry`. I suppose that you've chosen to go with 2 separate functions because so many of the APIs provided by Selenium throw exceptions instead of returning true/false.
> doWithRetry takes a function with no return type
>
> def clickFillColorWell(implicit user: LucidUser) {
> doWithRetry() {
> user.clickElementByCss("#fill-colorwell-color-well-wrapper")
> }
Unlike Selenium, when testing the UI of an external device we have no
way of noticing whether an action failed, other than by checking the
device's video output. For example we have `press` to send an infrared
signal ("press a button on the remote control"), but that will never
throw unless you've forgotten to plug in your infrared emitter. I
haven't come up with a really natural way of specifying the retry of
actions. We have `press_until_match`, but that's not very general. The
best I have come up with is `do_until`, which takes two functions: The
action to do, and the predicate to say whether the action succeeded. do_until(
lambda: press(Key.INFO),
miniguide_is_up)
It's not ideal, given the limitations around Python's lambdas (anonymous
functions). Using Python's normal looping constructs is also not ideal: # Could get into an infinite loop if the system-under-test fails
while not miniguide_is_up():
press(Key.INFO)
# This is very verbose, and it uses an obscure Python feature: `for...else`[2]
for _ in range(10):
press(Key.INFO)
if miniguide_is_up():
break
else:
assert False, "Miniguide didn't appear after pressing INFO 10 times"
Thanks for the article, I enjoyed it and it has reminded me to write up
more of my experiences with UI testing. I take it that the article's
sample code is Scala? I like its syntax for anonymous functions.[1] http://stb-tester.com [2] https://docs.python.org/2/reference/compound_stmts.html#the-...
And you are correct, we are using Scala. There are some really cool things about the language, case classes, pattern matching, first order functions, and traits just to name a few.
Yes, I've been bitten by that too -- it's too easy to forget the "assert". This morning it occurred to me that I could write a pylint (static analysis) checker to catch that, so I've done just that: https://github.com/stb-tester/stb-tester/commit/5e5bdbb
And you are correct, we are using Scala. There are some really cool things about the language, case classes, pattern matching, first order functions, and traits just to name a few.