ZK Testing with Selenium IDE"
Robertwenzel (talk | contribs) |
Robertwenzel (talk | contribs) |
||
Line 315: | Line 315: | ||
Wouldn't it be nice to avoid having to adapt test cases that often? Read on... | Wouldn't it be nice to avoid having to adapt test cases that often? Read on... | ||
− | == | + | ==Improve robustness and maintainability of Test Scripts == |
+ | |||
+ | To remove the hard coded running numbers of the component IDs from our test cases Selenium offers e.g. XPath or CSS locators. | ||
+ | |||
+ | Using xpath is it possible to select a node by its ID-prefix: | ||
+ | |||
+ | //input[starts-with(@id, 'username_')] | ||
+ | |||
+ | This will work as long as the prefix "username_" is unique on the page. So it is a good idea for this scenario to give widgets good names (i.e. IDs). And it will also improve the readability of source code. So now we can change the testcase to use this kind of XPath selector. | ||
+ | |||
+ | In more complex cases one can select nested components e.g. if the "street" component appears several times on the page use this: | ||
+ | |||
+ | //panel[starts-with(@id, 'deliveryAddress_')]//input[starts-with(@id, 'street_')] | ||
+ | //panel[starts-with(@id, 'billingAddress_')]//input[starts-with(@id, 'street_')] | ||
<table cellpadding="1" cellspacing="1" border="1"> | <table cellpadding="1" cellspacing="1" border="1"> | ||
Line 337: | Line 350: | ||
<td>clickAndWait</td> | <td>clickAndWait</td> | ||
<td>//button[starts-with(@id, 'login_')]</td> | <td>//button[starts-with(@id, 'login_')]</td> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
<td></td> | <td></td> | ||
</tr> | </tr> | ||
</table> | </table> | ||
+ | |||
+ | |||
+ | This [https://www.simple-talk.com/dotnet/.net-framework/xpath,-css,-dom-and-selenium-the-rosetta-stone/ sheet] provided by Michael Sorens is an excellent reference with many helpful examples how to select nodes in various situations. | ||
+ | |||
explain new locator extension | explain new locator extension | ||
Line 369: | Line 381: | ||
<td>clickAndWait</td> | <td>clickAndWait</td> | ||
<td>zktest=button#login_</td> | <td>zktest=button#login_</td> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
<td></td> | <td></td> | ||
</tr> | </tr> |
Revision as of 08:26, 21 June 2013
Robert Wenzel, Engineer, Potix Corporation
July 2013
ZK 6.5 (or later)
Introduction
About this article
nice application, how to test ... refer to zats... some things need to be tested in a real browser, so selenium comes into play... how to create tests
programmatically... link to older Small Talk?? or use Selenium-IDE to record/tweak/maintain!! and replay/debug -- this article :D
Environment
- Firefox (used with 21.0)
- Selenium IDE 2.0.0
- JDK 6/7
- ZK 6.5.3 6/7
- Maven 3.0
Steps
Initialize your testing environment
- download Selenium IDE 2.0.0 link
- Maven 3.0
- download link
- setup maven
- download link (test project)
- maven: mvn jetty:run
- localhost:8080
(- war file ) ???
Initial Situation
- Launch Selenium IDE CTRL+ALT+S
- open example app localhost:8080
- Record login-test and replay it
New Test | ||
open | /selenium_testing/login.zul | |
type | id=jXDW5 | test |
type | id=jXDW8 | test |
clickAndWait | id=jXDWb |
looks hard to read / maintain and doesn't even work :'(
Looking at the recorded commands and comparing with page source we notice the IDs are generated and changing with every request. So no chance to record / replay this way.
A way to make the IDs predictable is shown in the next Section.
Custom Id Generator
As mentioned in other Small Talks about testing one strategy is to use a custom IdGenerator implementation to create predictable, readable (with a business meaning) and easily selectable (by selenium) component ids.
public class TestingIdGenerator implements IdGenerator {
public String nextComponentUuid(Desktop desktop, Component comp,
ComponentInfo compInfo) {
int i = Integer.parseInt(desktop.getAttribute("Id_Num").toString());
i++;// Start from 1
StringBuilder uuid = new StringBuilder("");
desktop.setAttribute("Id_Num", String.valueOf(i));
if(compInfo != null) {
String id = getId(compInfo);
if(id != null) {
uuid.append(id).append("_");
}
String tag = compInfo.getTag();
if(tag != null) {
uuid.append(tag).append("_");
}
}
return uuid.length() == 0 ? "zkcomp_" + i : uuid.append(i).toString();
}
The IDs will look like this:
- {id}_{tag}_{##} (for components with a given id)
- {tag}_{##} (for components without a given id - e.g. listitems in a listbox)
- {zkcomp}_{##} (for other cases, just to make them unique)
e.g. a button in zul-file
<button id="login" label="login" />
will become something like this in HTML in browser (number can vary):
<button id="login_button_12" class="z-button-os" type="button">login</button>
In order to separate production from test configuration we can include an additional config file at startup to enable the TestingIdGenerator only for testing. In ZK this is possible by setting the library property org.zkoss.zk.config.path (also refer to our Testing Tips)
- e.g. via VM argument -Dorg.zkoss.zk.config.path=/WEB-INF/zk-test.xml
Windows:
set MAVEN_OPTS=-Dorg.zkoss.zk.config.path=/WEB-INF/zk-test.xml mvn jetty:run
Linux:
export MAVEN_OPTS=-Dorg.zkoss.zk.config.path=/WEB-INF/zk-test.xml mvn jetty:run
TODO: improve using maven profiles...
Now we get nicer and deterministic IDs when recording a test case.
After recording login-test again and we get this:
login test | ||
---|---|---|
open | /selenium_testing/login.zul | |
type | id=username_textbox_6 | test |
type | id=password_textbox_9 | test |
clickAndWait | id=login_button_12 |
This already looks nice, and self explaining - Unfortunately still fails to replay :(
Why this happens and how to fix it? See next section ...
ZK specific details
ZK heavily uses JavaScript and Selenium IDE does not record all events by default.
in our case here the "blur" events of the input fields have not been recorded, but ZK relies on them to determine updated fields. So we need to manually add selenium commands "fireEvent target blur" unfortunately Selenium does not offer a nice equivalent like "focus" action.
login test | ||
---|---|---|
open | /selenium_testing/login.zul | |
type | id=username_textbox_6 | test |
fireEvent | id=username_textbox_6 | blur |
type | id=password_textbox_9 | test |
fireEvent | id=password_textbox_9 | blur |
clickAndWait | id=login_button_12 | |
assertLocation | glob:*/selenium_testing/index.zul | |
verifyTextPresent | Welcome test | |
verifyTextPresent | Feedback Overview |
Replaying the script finally works :D, and we see the overview page (also added a few verifications for that).
Selenium Extensions
If you find the syntax of "fireEvent" ugly or think it is too inconvenient to add an additional line just to update an input field there help using a Selenium Core extension.
This snippet shows 2 simple custom actions
- blur
- convenience action to avoid "fireEvent locator blur"
- typeAndBlur
- combines the type with a blur event, to make ZK aware of the input change automatically
Selenium.prototype.doBlur = function(locator) {
// All locator-strategies are automatically handled by "findElement"
var element = this.page().findElement(locator);
// Fire the "blur" event
triggerEvent(element, "blur", false);
};
Selenium.prototype.doTypeAndBlur = function(locator, text) {
// All locator-strategies are automatically handled by "findElement"
var element = this.page().findElement(locator);
// Replace the element text with the new text
this.page().replaceText(element, text);
// Fire the "blur" event
triggerEvent(element, "blur", false);
};
user-extensions.js DOWNLOADable (file contains another extension ... more about that later in "making tests more robust and maintainable to UI updates")
add to Selenium IDE Options/Options... [General Tab] Selenium Core extensions
login test | ||
---|---|---|
open | /selenium_testing/login.zul | |
type | id=username_textbox_6 | test |
blur | id=username_textbox_6 | |
type | id=password_textbox_9 | test |
blur | id=password_textbox_9 | |
clickAndWait | id=login_button_12 |
login test | ||
---|---|---|
open | /selenium_testing/login.zul | |
typeAndBlur | id=username_textbox_6 | test |
typeAndBlur | id=password_textbox_9 | test |
clickAndWait | id=login_button_12 |
It is not required to use either of these extensions, but saving 1 line for each input is my personal preference.
Another thing to keep in mind is robustness and maintenance effort of test-cases when changes in the UI happen ... the generated numbers in the end of each ID are still an obstacle on this way as they are prone to change everytime a component is added or removed above that component.
Wouldn't it be nice to avoid having to adapt test cases that often? Read on...
Improve robustness and maintainability of Test Scripts
To remove the hard coded running numbers of the component IDs from our test cases Selenium offers e.g. XPath or CSS locators.
Using xpath is it possible to select a node by its ID-prefix:
//input[starts-with(@id, 'username_')]
This will work as long as the prefix "username_" is unique on the page. So it is a good idea for this scenario to give widgets good names (i.e. IDs). And it will also improve the readability of source code. So now we can change the testcase to use this kind of XPath selector.
In more complex cases one can select nested components e.g. if the "street" component appears several times on the page use this:
//panel[starts-with(@id, 'deliveryAddress_')]//input[starts-with(@id, 'street_')] //panel[starts-with(@id, 'billingAddress_')]//input[starts-with(@id, 'street_')]
login test | ||
---|---|---|
open | /selenium_testing/login.zul | |
typeAndBlur | //input[starts-with(@id, 'username_')] | test |
typeAndBlur | //input[starts-with(@id, 'password_')] | test |
clickAndWait | //button[starts-with(@id, 'login_')] |
This sheet provided by Michael Sorens is an excellent reference with many helpful examples how to select nodes in various situations.
explain new locator extension
import IDE extension...
New Test | ||
---|---|---|
open | /selenium_testing/login.zul | |
typeAndBlur | zktest=input#username_ | test |
typeAndBlur | zktest=input#password_ | test |
clickAndWait | zktest=button#login_ |
bigger test
Let's record a test case for edit and save an existing "feedback item" in the list, and verify the results, and a logout test.