SWTBot Essentials
Chris Aniszczykhttp://aniszczyk.org [email protected]
introduction
exercises
setting up the environment
SWTBot API
how does it work?
conclusion and Q&A
Agenda
UI Testing
It’s not easy, natural brittleness...
manual vs. automated testing
deciding what to test
UI Testing Challenges
Challenges
Identifying Controls
Challenges
Similar looking controls
Similar looking controls
Moving controls
Challenges
Sending “events” to controls
Challenges
Manage SWT Threading
Challenges
Tests to be non-blocking
Challenges
Run in a separate thread
Still manage synchronization between threads
Challenges
Internationalization (i18n) and Localization (L10n)
Challenges
Readability
Hands on exercises!
Learn by doing.
creating a java project “MyFirstProject”
create a java class
type in a program that prints “Hello, World”
execute the program
verify that the program printed “Hello, World”All exercises focus around...
Exercise 0: Setup
Test01.java
Setup SWTBot
Run a test with the SWTBot launch configuration
Objectives
Use Eclipse 3.6
Install SWTBot via update site http://www.eclipse.org/swtbot/downloads.php
Setting up the Environment
“org.eclipse.swtbot.examples”
Create a plugin project
Create a simple Test
@RunWith(SWTBotJunit4ClassRunner.class)
public class Test01 {
@Test
public void thisOneFails() throws Exception {
fail("this test fails");
}
@Test
public void thisOnePasses() throws Exception {
pass();
}
}
Run the first test
SWTBot API Introduction
Finding widgets
SWTWorkbenchBot bot = new SWTWorkbenchBot();
SWTBot[Widget] widget = bot.[widget][With][Matcher(s)]();
SWTBotText text = bot.textWithLabel("Username:");
Think of the bot as something that can
do tasks for you (e.g., find widgets, click
on them, etc.)
Finding obscure widgets
withLabel("Username:")
withMnemonic("Finish")
withText("&Finish")
inGroup("Billing Address")
widgetOfType(Button.class)
withStyle(SWT.RADIO)
withTooltip("Save All")
withRegex("Welcome, (.*)")
Use matchers and WidgetMatcherFactory !
Matcher pushButtonToProceedToNextStep = allOf(
widgetOfType(Button.class),
withStyle(SWT.PUSH),
withRegex("Proceed to step (.*) >")
);
SWTBotButton button = new SWTBotButton(
(Button)
bot.widget(pushButtonToProceedToNextStep)
);
Performing actions
button.click() // click on a button
// chain methods to make them concise
bot.menu("File").menu("New").menu("Project...").click();
tree.expand("Project").expand("src").expand("Foo.java").contextMenu("Delete").clic
k(); // delete Foo.java
list.select("Orange", "Banana", "Mango"); // make a selection in a list
list.select(2, 5, 6); // make a selection using indexes
Exercise 1: Hello SWTBot
SWTBotTest01.java
Get introduced to the SWTBot API
SWTWorkbenchBot is your friend
Tasks
beforeClass() - close the welcome view
canCreateANewProject() - create a java project
Run the test!
Ensure SWTBot logging works
Objectives
beforeClass()
@BeforeClass
public static void beforeClass() throws Exception {
bot = new SWTWorkbenchBot();
bot.viewByTitle("Welcome").close();
}
Find all widgets
Depth first traversal of UI elements
1.Find top level widgets
1.Find children of each widget
2.For each child do (1) and (2)
Finding widgets
public SWTBotTree treeWithLabelInGroup(String l, String g, int i) {
// create the matcher
Matcher matcher =
allOf(
widgetOfType(Tree.class), withLabel(l), inGroup(g)
);
// find the widget, with redundancy built in
Tree tree = (Tree) widget(matcher, index);
// create a wrapper for thread safety
// and convinience APIs
return new SWTBotTree(tree, matcher);
}
Thread Safety
Tests should run in non-ui thread
query state of a widget
change state of a widget
Thread Safety (Query state)
public class SWTBotCheckBox {
public boolean isChecked() {
// evaluate a result on the UI thread
return syncExec(new BoolResult() {
public Boolean run() {
return widget.getSelection();
}
});
}
}
Thread Safety(change state)
public class SWTBotCheckBox {
public void select() {
asyncExec(new VoidResult() {
public void run() {
widget.setSelection(true);
}
});
notifyListeners();
}
Tasks
canCreateANewJavaClass()
create a new java class: HelloWorld
Source folder: MyFirstProject/src
Package: org.eclipsecon.project
Click Finish!
Run the test!
Objectives
Tasks
canTypeInTextInAJavaClass()
open a file: HelloWorld.java
set some text in the Java file
canExecuteJavaApplication ()
Run the Java application
Run the test!
Objectives
canExecuteJavaApplication()
@Test
public void canExecuteJavaApplication() throws Exception {
bot.menu("Run").menu("Run").click();
// FIXME: verify that the program printed 'Hello World'
}
SWTBotEditor
Exercise 4: Matchers
SWTBotTest04.java
Learn about SWTBot and the Matcher API
Tasks
canExecuteJavaApplication ()
Run the Java application
Grab the Console view
Verify that “Hello World” is printed
Run the tests!
Objectives
Matchers
Under the covers, SWTBot uses Hamcrest
Hamcrest is a framework for writing ‘match’ rules
SWTBot is essentially all about match rules
bot.widget(WidgetMatcherFactory.widgetO
fType(StyledText.class),
consoleViewComposite);
WidgetMatcherFactory
Creating matchers
WidgetMatcherFactory is your friend!
withText("Finish")
withLabel("Username:")
withRegex("Proceed to step (.*)")
widgetOfType(Button.class)
withStyle(SWT.ARROW, "SWT.ARROW")
Learn about waiting and SWTBot Condition API
Tasks
afterClass()
Select the Package Explorer view
Select MyFirstProject
Right click and delete the project
Run the test!
Objectives
afterClass()
@AfterClass
public static void afterClass() throws Exception {
SWTBotView packageExplorerView = bot.viewByTitle("Package Explorer");
packageExplorerView.show();
Composite packageExplorerComposite = (Composite) packageExplorerView.getWidget();
Tree swtTree =
(Tree) bot.widget(WidgetMatcherFactory.widgetOfType(Tree.class), packageExplorerComposite);
SWTBotTree tree = new SWTBotTree(swtTree);
tree.select("MyFirstProject");
bot.menu("Edit").menu("Delete").click();
// the project deletion confirmation dialog
SWTBotShell shell = bot.shell("Delete Resources");
shell.activate();
bot.checkBox("Delete project contents on disk (cannot be undone)").select();
bot.button("OK").click();
bot.waitUntil(shellCloses(shell));
}
Handling long operations
describe a condition
poll for the condition at intervals
wait for it to evaluate to true or false
of course there’s a timeout
SWTBot has an ICondition
ICondition
Handling Waits
private void waitUntil(ICondition condition, long timeout, long interval) {
long limit = System.currentTimeMillis() + timeout;
condition.init((SWTBot) this);
while (true) {
try {
if (condition.test())
return;
} catch (Throwable e) {
// do nothing
}
sleep(interval);
if (System.currentTimeMillis() > limit)
throw new TimeoutException("Timeout after: " + timeout);
}
}
Conditions is your friend
Building Abstractions
Abstractions are good!
They simplify writing tests for QA folks
Build page objects for common “services”
Project Explorer
The Editor
The Console View
The main menu bar, tool bar
Model Capabilities
Create a project
Delete a project
Create a class
Execute a class
more...
Domain Objects
Domain Objects?
Represent the operations that can be performed on
concepts
Sample Domain Object
public class JavaProject {
public JavaProject create(String projectName){
// create a project and return it
}
public JavaProject delete(){
// delete the project and return it
}
public JavaClass createClass(String className){
// create a class and return it
}
}
Page Objects?
Represent the services offered by the page to the test
developer
Internally knows the details about how these services
are offered and the details of UI elements that offer
them
Return other page objects to model the user’s journey
through the application
Different results of the same operation modeled
Page Objects... should not
Expose details about user interface elements
Make assertions about the state of the UI
A Sample Page Object
public class LoginPage {
public HomePage loginAs(String user, String pass) {
// ... clever magic happens here
}
public LoginPage loginAsExpectingError(String user, String pass) {
// ... failed login here, maybe because one or both of
// username and password are wrong
}
public String getErrorMessage() {
// So we can verify that the correct error is shown
}
}
Using Page Objects
// the bad test
public void testMessagesAreReadOrUnread() {
Inbox inbox = new Inbox(driver);
inbox.assertMessageWithSubjectIsUnread("I like cheese");
inbox.assertMessageWithSubjectIsNotUndread("I'm not fond of tofu");
}
// the good test
public void testMessagesAreReadOrUnread() {
Inbox inbox = new Inbox(driver);
assertTrue(inbox.isMessageWithSubjectIsUnread("I like cheese"));
assertFalse(inbox.isMessageWithSubjectIsUnread("I'm not fond of tofu"));
}
LoginPage login = new LoginPage();
HomePage home = login.loginAs("username", "secret");
SearchPage search = home.searchFor("swtbot");
assertTrue(search.containsResult(" http://eclipse.org/swtbot "));
Exercise 6: Abstractions
SWTBotTest06.java
Learn about page and domain objects
Tasks
Create a NewProjectBot
Opens the new project wizard
Allows you to set the project name and click finish
Modify canCreateANewJavaProject()
Run the test!
Objectives
NewProjectBot
public class NewProjectBot {
private static SWTWorkbenchBot bot;
public NewProjectBot() {
bot.menu("File").menu("New").menu(" Project...").click();
bot.shell("New Project").activate();
bot.tree().select("Java Project");
bot.button("Next >").click();
}
public void setProjectName(String projectName) {
bot.textWithLabel("Project name:").setText(projectName);
}
public void finish() {
bot.button("Finish").click();
}
}
Modify Project Wizard Code
@Test
public void canCreateANewJavaProject() throws Exception {
// use the NewProjectBot abstraction
NewProjectBot newProjectBot = new NewProjectBot();
newProjectBot.setProjectName("MyFirstProject");
newProjectBot.finish();
assertProjectCreated();
}
Best Practices & Tips
Turn logging on always, it helps :)
http://wiki.eclipse.org/SWTBot/
FAQ#How_do_I_configure_log4j_to_turn_on
_logging_for_SWTBot.3F
Logging
Useful for debugging at times...
SWTBotPreferences.PLAYBACK_DELAY
-Dorg.eclipse.swtbot.playback.delay=20
or via code...
Slow down executions
long oldDelay = SWTBotPreferences.PLAYBACK_DELAY;
// increase the delay
SWTBotPreferences.PLAYBACK_DELAY = 10;
// do whatever
// set to the original timeout of 5 seconds
SWTBotPreferences.PLAYBACK_DELAY = oldDelay;
SWTBotPreferences.TIMEOUT
-Dorg.eclipse.swtbot.search.timeout=10000
or via code...
Changing Timeout
long oldTimeout = SWTBotPreferences.TIMEOUT;
// increase the timeout
SWTBotPreferences.TIMEOUT = 10000;
// do whatever
// set to the original timeout of 5 seconds
SWTBotPreferences.TIMEOUT = oldTimeout;
In our examples, we used labels as matchers mostly
SWTBot allows you to use IDs as matchers
IDs are less brittle than labels!
text.setData(SWTBotPreferences.DEFAULT_
KEY,”id”)
bot.textWithId(“id”)
Identifiers
Use Page Objects to simplify writing tests
Allows more people than just normal devs to write tests
Page Objects
More SWTBot?
Eclipse Forms support in HEAD
GEF support is available for graphical editor testing