Introduction
The last three months I had to work with Selenium 2.0 (WebDriver).
In this article I will describe my impressions, thoughts and experiences that I gained.
I will also describe the main actions that most often cause problems and show the most successful solutions that I was able to implement for them. Perhaps there are more correct approaches - I will be glad if you leave them in the comments.
Briefly about Selenium
The Selenium library allows testing graphical web interfaces. Its principle is to simulate the user's activity as accurately as possible. In essence, this is the writing of a bot that runs through the pages of the site, performs actions and checks the expected result. Selenium 2.0 implements messages with browsers through special drivers. Unlike Selenium 1.0, it does not use JavaScript, but communicates directly with the browser API.
What I managed to implement
It turned out to write tests based on JUnit and Selenium 2.0, combined into one application. This application can run on a Selenium Grid - a network, led by Selenium Hub, which accepts and distributes incoming testing tasks to its Selenium Nodes. On various Selenium Nodes any required browsers can be configured. Drivers used are native drivers for each browser.
')
Part one. Impressions
Different behavior in different browsers
By browsers, I mean the main ones: Firefox, Google Chrome, Opera, Safari, IE8, IE9
For the same code to work equally well in different browsers, you need to spend a huge amount of time. Sometimes an iron will is needed in order not to give up this bad job. In this regard, the most compliant browsers are Firefox and Google Chrome. In my personal experience, it is vital that the test can change behavior in the right places depending on which browser is currently being used. Those. he must have information about the environment in which he is passing.
Tip:
Try not to use the webDriver object directly in tests! Create wrapper methods around the basic methods you need. It is easier to change the behavior in one place than everywhere in the code of all tests.Selenium 2.0 - raw product
Reading a lot of posts on Stackoverflow in search of best practices or just a solution to a problem, you constantly come across workarounds. There are several reasons: differences in browser drivers, non-fulfillment of the required functionality by the drivers, presence of errors in the versions, direct dependence of the browser version on the driver version. Sometimes he can just drop a test from a bare place (from the user's API point of view) - there is an element, but he does not see it. In my experience there are a lot of floating errors that are intentionally reproduced only once, under absolutely similar conditions and actions. With Firefox, a fever sometimes starts and the browser can simply close with something like this:
Error communicating with the remote browser. It may have died . It is extremely difficult to find the reason, if it is at all available to the user of Selenium. Therefore, sometimes the situation is helpless - the functionality just doesn't work.
Tip:
This kind of regrettable business makes us change the behavior of the test case. Fortunately, the same things in GUI clients can often be done either in a different sequence or in a different way. If you could not find a solution by googling - try to choose another behavior that will be successfully worked out. Do not lock on a specific action, if there is no special need.Selenium tests - dependent tests
This means that if you do not take extra care, the actions of one test can affect the result of another test. It is quite obvious, including the user changes the data during their activity. By testing this functionality, you will have to change the initial data. If other tests depend on it and you did not return the data to its original state - or could not do it due to the fact that the test was interrupted by an error - another test may also break. A sort of domino principle. When you first realize this, it becomes very painful. Hands down ...
Tip:
If it is possible to reproduce the test conditions independently, i.e. there is direct access to the application under test and there are no barriers to deploying the initial test data - you are lucky, isolate your tests in a similar way - by preparing the data before the test and clearing them after. For example, the Liquibase tool can help in recovering data in a database.
Most likely this is not possible. In this case, there is only one way out - in addition to the actions being tested, to describe with the help of Selenium the actions for their “rollback”. Those. if the user has deleted an entity, at the end of the test it must be re-created or loaded.
This is a sinful way. Since such actions are also vulnerable and can also terminate with an error without fulfilling their purpose.Selenium Tests - Slow Tests
You need to be prepared that the sequential run of a large set of tests for all browsers can take a large amount of time, measured in hours (from 30 minutes to 2-3 hours). It does everything that I described above as tragedies and at times looks like a mockery. The reason is that the tests are very saturated with various expectations, the search for elements and other slow actions.
Tip:
Test only what you really need to test. Of all the possible working options for the implementation of the same action - choose the fastest.Selenium IDE is not an assistant
Selenium IDE is a special plugin for Firefox, which is able to record all the actions performed by the user in the form of scripts. You can also export compiled scripts in different languages ​​and in two formats: Selenium 1.0 (RC) and Selenium 2.0 (WebDriver).
In most cases, useless thing.
Problems:
- generated code - do not read
- the generated code is not working, in the case of a complex interface, due to all the above features
- if the id elements (div, table, span, input) are automatically generated - the XPath pointers offered for selection do not fit
- A large number of tests (5 tests are already enough) will force you to get on the right path of the Jedi and create your own implementation of frequently performed actions - and then use them as an inherited method. Once described and honed. As soon as such a set of methods is formed, the benefits of IDE drop sharply. She cannot be told to use her own methods - the development environment will generate her own idle non-ideal templates. View then generated code and replace all the necessary places over time is reduced to a complete rewriting of this code. The same idea can be continued with a single “reference book” - a list of all XPath locators of key elements. As soon as all such locators are rendered in constants or in a separate directory - it becomes easier to use them than to check again - what the development environment got there
Benefit:
The only benefit that I constantly feel is the ability to check the XPath pointer. A very convenient function - if the pointer is valid and such an element exists - it is highlighted by a frame.
Tip:
Play with IDE, understand the essence of Selenium, you can even pee tests with it. But as soon as you feel that the benefits are less than the costs - start making your own blanks. Accumulate them in a general abstract ancestor class or in a utility class. Starting from a certain point, your tests can turn into just a listing of such methods, diluted with checks of the result and the current state.Part two. Practical solutions to emerging problems (Java)
The solutions described below are not beautiful, ideal, they can cause rejection, but they are workers. In my experience, they eliminate problems. I hope they will benefit and get rid of lost hours and days.
Getting an item (findElement)
Problem:
WebDriver provides a mechanism for searching and retrieving an entity WebElement:
webDriver.findElement(By.id("elementId"));
Theoretically, the behavior of this method is influenced by the parameter
'implicit wait', which can be specified when building the webdriver itself. For example, like this:
webDriver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
Again, theoretically, this should obviously force the webdriver to search for an element for a specified time and wait either for the desired element to appear or for the specified timeout to expire. By the way, this timeout seems to be set only once.
In practice, something strange happens. The pause is maintained, but there is an internal feeling that the search, if it follows the DOM model, does not update this DOM model. For some browsers, a different situation is obtained - the element is already in the DOM model, but has not yet been drawn or partially drawn (Google Chrome). WebDriver returns the found half-drawn element and the click event falls into the still not drawn coordinates. The isDisplayed () method does not help in such cases. In any case, the result is always the same for me - the element is visually guaranteed to appear, and webDriver still does not detect it.
Decision:
Make a rough pause. In order not to multiply the number of lines of code by half, I recommend making your own implementation of the findElement () method;
As I wrote above, in order to work more efficiently, the test should know which browser is currently running. For my Firefox, according to my observations, such a delay is not required.
You can also use the tool WebDriverWait. I will not describe such an option here, since I decided to stop at the hibernation of the stream, that's enough for me - so there is no proven option. But everything is pretty simple there.
In the future, use only this method in all tests and not use webDriver.findElement () directly.Code example:
protected WebElement findElement(By elementLocatorToFind) { if(isSafari() || isChrome() || isIE()) {
Getting items (findElements)
The problem and solution are similar to finding one item.
Check for the existence of an element
If it is necessary to check that the element is missing, it is recommended to use the construction:
findElements(elementLocatorToFind).isEmpty();
Here is the recommendation from JavaDocs:
it is not necessary to use the elements for the non-present elements, use the findElements (By) and assert zero length response instead.
Downloading a picture or file
Problem:
There is a desire to test downloading a file, that the downloaded file is as expected, and if this is a picture, it is really available at the indicated link.
Reasonings:
In 99% of cases you do not need it. Once again ask yourself, what do you want to test? I’m pretty sure that you only need to know that the download is available. That the link is active, the download button is enabled and the response status after the start of the download is 200. You have no task to test the browser and its download process.
Also, if the tests pass on Selenium Grid, then you will not be able to download the file and check its location after that. The file is downloaded to Selenium Node, and you will check it on Selenium Hub. These are different hosts, at least in common practice.
Decision:
The solution is to perform an ordinary HTTP request on the link leading to the file on the server, or on the link on which the server should return such a file. If the status of the response received from the server 200 is correct, the file exists. All other options I see as the inaccessibility of downloading the file. Since requests often must have authorized cookies with you, such cookies must be imported from webDriver.
If one status is not enough, nothing prevents you from counting the entire InputStream from HttpEntity and then comparing its contents with the reference one, be it MD5 sum or some other method.Code example:
Clearing the input field value
UPD: below, in the comments, you can find a discussion. As a result, the method seems to work correctly, and the reason was hidden in the difference in browser versions and in another conflict related to it. But I decided not to delete the description of this problem, since perhaps for someone such methods will also be useful, as for me in due time.
Problem:
Occasionally, you need to clear the value of an input type field. For example, you need to replace the old value with a new one.
WebDriver provides a special method for this:
webElement.clear();
In my experience, this method does not work, moreover, throws an error and breaks the test.
It is required to find another way to clear the field value.
Decision:
There are several basic ways.
The first way is to simulate the action “select all” and immediately after that send a new value:
inputElement.sendKeys(Keys.chord(Keys.CONTROL, "a") + Keys.DELETE + newValue);
But this solution does not work for me on all browsers and not always.
The second way is to send the number of characters backspace equal to the length of the old value. This solution is ugly, but it is efficient and guaranteed to work in all browsers.
Below I publish the version that I use myself. It has a separate consideration of the situation when the browser is IE, and input with the file type.
This is a special situation. When executing the sendKeys command to such an element, IE replaces the old value with the new one, and does not add it to the end. Therefore, it makes no sense to clean up such a field. Moreover, such an attempt will lead to an error. Either because of a non-existent file (as it will attempt to find the file on an empty path), or because of an attempt to find the file by a path whose string value is equal to the backspace symbol.Code example:
protected void clearInput(WebElement webElement) {
Upload file to server
Problem:
You must upload the file to the server using standard HTML elements:
<input name="uploadFile" type="file"> <input name="doUpload" value="Upload" type="button"/>
Decision:
I recommend simply taking it and putting it into a separate universal method and using it every time you need to load something through this form.An exception:
Safari Driver does not fully support file download, as as far as I understood, it is javascript-based. The window with the choice of the file stumps it. Such scenarios should either be avoided or another result achieved — create your own HTTP request or enclose the data directly on the server side, if there is such a possibility.
Code example:
protected void uploadFile(By uploadInput, By uploadButton, String filePath) { clearInput(uploadInput); findElement(uploadInput).sendKeys(filePath); findElement(uploadButton).click(); }
Actions with elements inside the iframe
Problem:
If the required element is inside an iframe, it is not accessible from the default context. You cannot detect it in the DOM model, and webDriver will throw a NoSuchElementException exception.
Decision:
Before interacting with this element, you must switch webDriver to the iframe context of the element. As I understand it, this is due to the fact that the page context and the iframe context on this page are two different DOM models.Code example:
webDriver.switchTo().frame(findElement(By.id("id_of_your_iframe")));
IE8. XPath issue
Problem:
IE8, in its eccentric manner, can misinterpret element pointers (By.id, By.xpath, and others).
I had a situation when he ignored the clarification for the search element pointing to his class attribute.
For example, IEDriver refused to distinguish between two different elements found on such locators and derived elements suitable for both options:
findElement(By.xpath("//div[@id='elementContainer']/div[@class='someProcessInProgress']")); findElement(By.xpath("//div[@id='elementContainer']/div[@class='someProcessFinished']"));
Understand in what situations he had problems I could not.
Absolutely identical situation occurred with a direct indication of the id element. WebDriver pretends that it does not exist.
Decision:
If IEDriver has a hallucinogenic delusion in the search for an item (but not in other drivers and browsers), the best way out is to change the XPath. The benefit of the possible options due to the flexibility of XPath is always a lot.IE8. item not available to click
Problem:
IE8, unlike other browsers, is not always able to independently scroll to an element if you click on an element outside the visible part of the container (layer, table, etc.). As a result, this behavior leads to an error.
Decision:
Need to scroll. The only working way found by me is to use javascript. In fact, WebDriver has a special mechanism to help scroll to the desired element: new Actions(webDriver).moveToElement(elementToScrollTo).perform();
But it will not work in the case of IE8.Code example:
((JavascriptExecutor) webDriver).executeScript("container.scrollLeft=1000;");
Where container is the id of the item you want to scroll. Those. in our case, a div or table within which the element is located. As you can see, this script will scroll horizontally.
Firefox may die
Problem:
Firefox Driver could be an example for other drivers, but it has one very unpleasant flaw. As you can see from the comments to different versions of WebDriver, this flaw either disappears or reappears from version to version.
The bottom line is that sometimes Firefox finds a demon and it suddenly, without any external, as it seems, impacts and changes, begins to fall on level ground.
It looks like this: you observe how a test that has already been debugged to shine is successfully executed in a browser window. And here on an absolutely petty step or action the browser window simply disappears. In the logs you find the following entry: Error communicating with the remote browser. It may have died. All, no more information you will not find.
Decision:
This is a regressive error and there can be no guaranteed medication. It lies in the fact that between the browser and the driver there is a misunderstanding. For example, due to the fact that your browser has been updated, you did not pay attention to it and continue to use the old WebDriver. There is a dependency between Firefox and its driver, as I felt it. It is not absolute, i.e. not every time Firefox is updated, you need to run to update the web driver too. But the first thing I advise you to do is google it, which version of the webdriver is most suitable for your version of Firefox.
In the case of Firefox 19, I was helped to update the stand-alone-server Selenium to version 2.30.0.Conclusion
I am grateful for this experience and for the opportunity to work with this framework. Over the past months, XPath has become like a native language for me, I can probably correspond with it soon. Apparently, I gained a lot of knowledge on how to use Selenium and how to do it effectively.
But nevertheless ... I would not like to face such tasks in the future. This is extremely tiring, debugging is like a torment, sometimes makes you write bad code, but most of all it’s scary that the web client being tested will be modified. I am guaranteed to know that it will be changed. And this is another painful moment.
Therefore, if you decide to write serious tests on this platform - prepare yourself psychologically.