Extending the core test vocabulary
Frankenstein is an event-driven framework. It is possible to extend the core event vocabulary by creating new event classes.
Custom events need to implement the FrankensteinEvent
interface. (For most custom events, it will be sufficient to
extend from AbstractFrankensteinEvent
). You'll need to explicitly register a custom event with Frankenstein.
Compound events
Compound events can help decouple Actions (Clicks, RightClicks, Double Clicks, Drag , Drop, etc.) from the components where the action is executed.
Here is an example of a compound event:
package com.thoughtworks.frankenstein.events;
import com.thoughtworks.frankenstein.events.actions.Action;
import javax.swing.*;
/**
* Understands radio button actions
*/
public class RadioButtonEvent extends AbstractCompoundEvent {
private String radioButtonName;
/**
* Compound events need to declare a constructor that takes in a script line and an action.
*/
public RadioButtonEvent(String scriptLine, Action action) {
super(action);
this.radioButtonName = scriptLine;
}
public String toString() {
return "RadioButtonEvent: " + radioButtonName;
}
public String target() {
return radioButtonName;
}
public void run() {
JRadioButton radioButton = (JRadioButton) finder.findComponent(context, radioButtonName);
action.execute(center(radioButton), radioButton, finder, context);
}
}
Adding custom events to the Ruby driver
Custom events can be added to the Frankenstein driver by adding functions to the FrankensteinDriver
module. Please refer
to the Ruby Driver documentation for more information about Ruby.
#Include the Frankenstein driver module.
require 'frankenstein_driver'
#Add custom actions to the driver
module FrankensteinDriver
def custom_event(param1,param2)
append_to_script("custom_event \"#{param1}\",\"#{param2}\")
end
end
Custom components
If required, custom recorders can be written for custom components.
Most component recorders work in the following manner:
- Attach a component specific listener when a component is shown (for example, the CheckBoxRecorder attaches an ActionListener)
-
- Record a custom event when the component specific listener is triggered.
- Detach the listener when the component is hidden.It is critical to ensure that your listeners get detached at the right time to avoid memory leaks
Here's an example recorder: the CheckBoxRecorder (this recorder is part of the core framework).
/**
* Records interactions with check boxes.
*/
public class CheckBoxRecorder extends AbstractComponentRecorder implements ActionListener {
public CheckBoxRecorder(EventRecorder recorder, NamingStrategy namingStrategy) {
super(recorder, namingStrategy, JCheckBox.class);
}
public void componentShown(Component component) {
checkBox(component).addActionListener(this);
}
public void componentHidden(Component component) {
checkBox(component).removeActionListener(this);
}
private JCheckBox checkBox(Component component) {
return (JCheckBox) component;
}
public void actionPerformed(ActionEvent e) {
JCheckBox checkBox = (JCheckBox) e.getSource();
recorder.record(new ClickCheckboxEvent(componentName(checkBox), checkBox.isSelected()));
}
}
What to record
It would be advisable to record high level events that express the user's actions, rather than low level mouse and keyboard events.
For example: let's look at what we'd like to record when the user interacts with a Map widget which allows locations can be selected on a map.
Recording raw mouse events would typically result in a script that looks something like this:
Click map 100,100
Clearly, it's next to impossible to determine which location the user had selected
An example of a higher level event would be one that records the location, latitude, and longitude that the user selected.
Select location Bangalore, 12 58 N , 77 35 E
Transforming low level actions into high level events:
- Allows tests to be readable and meaningful
- The tests tend to be relatively stable. Coordinate based testing is notorious for creating fragile tests.