Feed aggregator

PanelMatic 101

java.net Articles - Thu, 01/12/2012 - 00:21
Learn about PanelMatic, a small library that allows Swing developers to quickly create common UI panels. Quickly create common UI panels with the PanelMatic library January 11, 2012 Michael Bar-Sinai

Learn about PanelMatic, a small library that allows Swing developers to quickly create common UI panels.

Every Swing developer knows this feeling: you've got the design of a UI panel. It's the 'right' design. It 'works'. It's what the user would expect. Hell, it's even what you would expect had you been the user. But it is going to be an awful lotta coding to lay it out in Swing - even before you take into consideration issue like panel re-sizing and localization.

Some developers nest numerous panels to get it working. Some try to "fake it" using the null layout. Others try to fit everything into a single panel and use the GridBagLayout - this involves quite a bit of trial-and-error, as can be seen in this documentary. Some even turn to graphical UI builders... But hey, every Swing developer knows that. Time for something new: PanelMatic.


Figure 1. Panel layout: Sketch, PanelMatic code, GUI panel

PanelMatic allows Swing developers to create common UI panels with ease. Panels are built top-to-bottom (or, more precisely, on the page axis). There is an intuitive connection between the way the code looks and the way the created panel will look. Components can be added to the panel with a label and/or an icon (lines 3-7), or alone (line 9). By default components stretch to occupy all the space they get, but this can be changed using modifiers (lines 9, 10). L_END (stands for "line end") and GROW (stands for "grow") are statically imported constants, and are in fact full-blown object that implement the BehaviorModifier interface - so you can create your own modifiers if you need 'em. Client code can add headers (lines 2, 8) and flexible spaces (not shown). The default implementation uses a pluggable component factory to create all the extra components involved (e.g. JLabels), so you can customize them when the defaults won't do.

As you've probably guessed, panels are built by builders that are obtained by invoking a static method on the PanelMatic class. PanelMatic is designed around an API/SPI approach - all the client code gets to hold is an interface, so the implementation can be changed without affecting the client code at all. Builders are pooled, so you don't have to think twice before asking for one. You can create your own implementation - just implement the service provider interfaces, and point the PanelMatic class to it. This can be done either by code or via a system property.

So this is PanelMatic. Use it to create common UI panels quickly, and use your freed up time to go to more meetings and catch up on some quality PowerPoint presentations. We could stop here, but we'd be missing half the fun.

Beyond Layout

PanelMatic's approach to building UI panels allows for some surprising improvements over normal layout-based code. Here are some of my favorites.

Customizers, or Listening to All Components

Components are passed to the builder, and eventually get added to the produced panel. But before they are, they pass through a chain of PanelMaticComponentCustomizers, or "customizers" for short. Each builder has two customizer chains - one that applies to all the panels it builds, and one for the current panel being built. The latter is disposed after the get() method is called. Customizers have a customize method, which gets and returns a JComponent. This allows the client code to apply uniform customizations to all components in the created panel, or in the application in general. These customizations can be used for, e.g.:

  • Changing the background of the focused component
  • Automatically wrap Scrollables in a JScrollPane
  • Listen to all components in a panel
Let's look into the last example. A very common requirement is to have a "save" button enabled if and only if the user changed some data on the screen. This involves listening to all components the user can change, which is a lot of boilerplate code. Instead, we can create a customizer that would add a change listener to every component added to the panel, and then add a single listener to that customizer. PanelMatic comes with such a customizer out of the box:

ChangeDetectorCustomizer cdc = new ChangeDetectorCustomizer(); panel = PanelMatic.begin( cdc ) .addHeader( H1, "Song Details") .add("Name", txfName) .add("Album",txfAlbum) .add("Artist",txfArtist) ... .add( btnSave, L_END) .get(); cdc.addListener( new ChangeDetectorCustomizer.Listener() { @Override public void changeMade(ChangeDetectorCustomizer cdc, Object o) { btnSave.setEnabled(true); }});

The ChangeDetectorCustomizer adds the appropriate listeners to all common swing components, so any change made to any component makes the save button enabled. It also recurses down the containment hierarchy, so changes made to JCheckboxes nested in some sub-sub-sub-panel are detected as well.

Localizations

PanelMatic's PanelBuilders can pass the labeling strings "as-is", like we've seen so far, or use them as keys of a ResourceBundle. Resource bundles are set using static methods of the PanelMatic class itself, and affect all builders generated afterwards. The same goes for ComponentOrientation. So it takes only two lines of code to move from an English left-to-right UI to a right-to-left UI in Urdu/Hebrew/Arabic or any of the other RTL languages (see resources). That, and someone to translate the texts.


Figure 2. Localizations can be easy

Building GUI Using Expressions - Anonymous Panels

With PanelMatic, it is often the case that panels are laid out using a single expression. This is contrary to the normal UI construction process, where one first creates the panel, then applies a layout, and then adds sub-components - each in a statement of its own. The problem with statements is that they cannot be combined - they just sit there, one after the other, as if in a shopping list.

Expressions, on the other hand, can be nested and composed with other expressions. They are "first class citizens" in java, and can appear anywhere. So, one can create a sub panel while adding it to a bigger component, rather than before:

JTabbedPane tabs = new JTabbedPane(); tabs.add( "Tab 1", PanelMatic.begin() .addHeader(HeaderLevel.H1, "Tab 1") .add("User Name", txfUserName ) .add("Password", txfPassword ) ... .get());

This is somewhat similar to Anonymous Classes (those are the classes with no names that are created, say, when you implement an ActionListener). As anonymous panels can go anywhere, the below code works:

System.out.println("A panel with two JLabels would " + "have a preferred height of " + PanelMatic.begin() .add( new JLabel("Label 1")) .add( new JLabel("Label 2")) .get().getPreferredSize().height + " pixels.");

On my mac, it says:

A panel with two JLabels would have a preferred height of 40 pixels. Advanced Customizations

PanelMatic takes the "convention over configuration" approach (thanks, guys). Everything can be customized, but you normally don't need to bother with it. When you do, there are several levels of customizations available: one could subclass a PanelBuilderFactory (easy) or implement the entire stack (builders and factories) or get to some level in between. The exact details are beyond the scope of this article, but are in the docs. Bottom line - as long as you can build your panel along the PAGE_AXIS, you can customize PanelMatic to build them. The client code should not be affected when you switch from the default implementation to yours.

Wrapping up

PanelMatic is a small library that allows Swing developers to quickly create common UI panels. It helps with localizations and customizations and makes the code creating the UI readable and intuitive. It's easy to pick up and hard to put down, though I might be slightly biased. Why not give it a go and see for yourself?

Resources (or References) Michael Bar-Sinai is a Senior Software Architect at Be'eri Print AttachmentSize PanelMatic_ltr-to-rtl.png44.83 KB PanelMatic_sketch-code-gui.png269.85 KB PanelMatic101.zip43.62 KB
Categories: Java

SWELL - An English-Like DSL for Swing Testing

java.net Articles - Fri, 11/18/2011 - 06:02
This article describes the design of SWELL, an English-Like domain-specific language for testing Swing applications An English-Like Domain-Specific Language for Swing Testing November 17, 2011 Sanjay Dasgupta

This article describes the design of SWELL, an English-Like domain-specific language for testing Swing applications.

This article describes an English-Like domain-specific language for testing Swing applications. If you are a potential SWELL user, proceed to the SWELL User Guide. But if you would like to understand the design of the tool to possibly create your own DSL-based tools, read on.

The Wikipedia's entry for domain-specific language (or DSL) explains that "a domain-specific language (DSL) is a programming language or specification language dedicated to a particular problem domain, a particular problem representation technique, and/or a particular solution technique ... Creating a domain-specific language (with software to support it) can be worthwhile if the language allows a particular type of problem or solution to be expressed more clearly than an existing languages would allow and the type of problem in question reappears sufficiently often."

The authors interpreted this as a license to create SWELL—An English-Like DSL for Swing Testing. SWELL has the following features:

  • English-like in the words used and the sentence construction
  • uses familiar Swing terms and English words in sentences typically used by human testers
  • allows Swing Components to be identified and referenced using natural language
  • can express the idea of any Swing action using a mouse, keyboard, etc. using natural language
  • allows structuring of test cases (like JUnit) to facilitate test classification and management

Specific details can be found in SWELL Language Manual and SWELL Grammar below.

SWELL Internals - Anatomy of an Interpreter details the construction of the interpreter that executes SWELL scripts. This part is also a generic blueprint for DSL processors using VisualLangLab as the parser-generator.

A periodically updated version of this article that stays compliant with the current versions of the software used will be available at SWELL Article Update.

SWELL "Hello world"

All of our examples will target VisualLangLab (a convenient Swing application we will be using for parser development anyway). So, if you are not at all familiar with VisualLangLab, take a quick look at the Grammar without Tears article. The following code is a SWELL script for testing two of VisualLangLab's built-in sample grammars: ArithExpr and SimpleJSON.

Suite SampleGrammars begins with - Launch vll.gui.VllGui.main() - Wait for frame "VisualLangLab/S" - clearLogButton is the button with ToolTipText = "Clear log" - parseInputButton is the button with ToolTipText = "Parse input" - parserInputArea is the 3rd textcomponent - logTextPane is the 4th textcomponent Before each test
- clear text in ${logTextPane}
- clear text in ${parserInputArea}
After each test
- delay 500 ms
Test ArithExpr "Check ArithExpr" is - select "Help" -> "Sample grammars" -> "ArithExpr" from the menubar - Wait for frame "VisualLangLab sample.*" - delay 1 second - click the "OK" button of the "VisualLangLab sample.*" dialog - key "3 * 5" into ${parserInputArea} - click ${parseInputButton} - fail the test with message "Bad parse result" unless ${logTextPane} contains "Array(Array(Pair(0, 3), List(Pair(0, Pair(0, 5)))), List())" within 1 s Test SimpleJSON "Check SimpleJSON" is - select "Help" -> "Sample grammars" -> "SimpleJSON" from the menubar - Wait for frame "VisualLangLab sample.*" - delay 1 second - click the "OK" button of the "VisualLangLab sample.*" dialog - key "{\"name\":\"Centurion\"}" into ${parserInputArea} - click ${parseInputButton} - fail the test with message "Bad parse result" unless ${logTextPane} contains "Pair(0, Array({, List(Array(\"name\", :, Pair(2, \"Centurion\"))), }))" within 2 seconds Suite SampleGrammars ends with - pause "We're done! Please click the \"OK\" button to exit."

The colored (blue, cyan, and red) lines are the structuring constructs. Every SWELL script contains a single test suite delimited by a test header and trailer (the blue lines). The header and trailer include the suite's name (SampleGrammars), which exists for documentation purposes only. The header and trailer can both be optionally followed by one or more SWELL statements that are executed at the start and end of the suite respectively.

A suite contains one or more tests, each starting with a test header (the red lines). Each test header (the red lines) includes the test's name (ArithExpr, SimpleJSON, etc.) and a string containing a more verbose description of the test. Each test-header is followed by one or more SWELL statements that are executed as part of the test. Like JUnit (and other test frameworks) SWELL also has ways of reporting the outcome of a test (e.g. the fail the test ... in these examples).

Optional before and after sections (headed by the cyan lines) list actions to be performed before and after each test respectively.

Figure 1 below shows this script in action. It loads the two sample grammars one by one, provides grammar-specific test input for each (in the Parser Test Input area), runs the parser and verifies that the expected output appears (in the Parser Log area) within one second. A failure is reported if the verification fails.


Figure 1. SWELL in action!

Running the Examples below has instructions on how to run this script. Most readers should have no problem deciphering this SWELL script, but if you need help use the SWELL Language Manual below.

SWELL User Guide

This brief User Guide introduces the available resources, and explains how they can be used to run the examples in this article or to run your own SWELL scripts. All of the resources required to support the descriptions in this article can be found in the swell-article-resources.zip file listed in the Resources section at the bottom of this article.

Prerequisites

Apart from the contents of the swell-article-resources.zip file, you will need to get the latest download from the Abbot Java GUI Test Framework, and to have a 6.0+ JRE installed. Both, SWELL itself and VisualLangLab, are written in Scala, but you do not need a Scala installation to use SWELL or VisualLangLab as described here.

Running the Examples

To run the examples shown, proceed as follows (this runs the Hello world script above):

  • download the swell-article-resources.zip file, and unzip the contents into a directory (called swell-article-resources)
  • obtain the latest download from the Abbot Java GUI Test Framework, and copy all its jar files into the jars subdirectory of the swell-article-resources directory
  • on Windows: open a CMD window, cd into the swell-article-resources directory, and run the command "launch.bat test-SampleGrammars.txt"
  • on Linux/Mac OS/*NIX: at a shell prompt, cd into the swell-article-resources directory, and run the command "chmod +x launch". Then run the command "launch test-SampleGrammars.txt"
  • after completion of the OS-specific actions described in the previous two points, there is a delay of 10-30 seconds before any activity is visible. Do not touch the mouse till the end of the tests
  • at the end of the test, a dialog is displayed (as in Figure 1 above), click the "OK" button to exit the test environment
  • a detailed test log can be found in the file named test-SampleGrammars.log
Running your own SWELL Tests

The swell-article-resources.zip file contains everything you need to use SWELL for your own test. To create a new SWELL test script proceed as follows:

  • download and unzip swell-article-resources.zip into the swell-article-resources directory, and add jar files from the Abbot Java GUI Test Framework as usual (see above)
  • modify the launch.bat or launch script (depending on your host OS) to add directories and jar/zip files required for the system-under-test to the classpath. Save the modified launcher script with a different name (e.g. launch-test.bat or launch-test)
  • using a text editor program, create one or more SWELL script files (equivalent to the test-SampleGrammars.txt file). Use the SWELL Language Manual for help with SWELL statements
  • run the test by using the new launcher script using the OS-specific directions provided in the previous section. For example, if a new SWELL script file is named edit-rates.txt, you can start it by entering the command "launch-test edit-rates.txt"

Users should be aware that the parser's error-reporting capability is currently rudimentary, and the error messages are sometimes not very helpful. However we expect to improve this aspect progressively, and improved resources and SWELL documentation will be made available at the The SWELL Project.

The SWELL Project

The resources in the swell-article-resources.zip file as well as additional documentation are being published as an open-source project hosted at http://swell.java.net/. The project is expected to be available online by the time (or soon after) this article is published.

SWELL Grammar

We won't be reinventing the wheel here, so just extract the SWELL grammar file swell.vll from the swell-article-resources.zip file listed in the Resources section. You will need the grammar-file to follow the descriptions and examples below.

Introducing VisualLangLab

Our DSL interpreter will need a parser to analyze SWELL scripts, and since the SWELL grammar is quite elaborate we must use a proper parser-generator. However, we would like to avoid the theory and formalisms (as much as possible) and use the simplest tools and approach. So, while there are many good parser generators, our choice for the job is the simple, user-friendly, and easy to learn VisualLangLab. To save space, we won't say much about the tool here, but refer interested readers to the Grammar without Tears article.

Open Grammar in VisualLangLab

The SWELL grammar was developed using VisualLangLab, and you can use the same tool to inspect and review the grammar. You can also test-run the grammar by feeding it snippets of SWELL. Within VisualLangLab, grammars are like gentle herbivores in an open zoo, and they don't bite your hand if you offer them the wrong kind of leaf or straw. You can also modify the grammar, and see how that changes its behavior, and the AST produced.

If you haven't downloaded VisualLangLab yet, get the VLLS-All.jar file now. Start VisualLangLab by double-clicking this file (Linux, Mac OS, and UNIX users will need to chmod +x VisualLangLab.jar). Select File -> Open from the VisualLangLab main menu, choose script.vll in the file-chooser dialog, and click the "Open" button. You should see the grammar-tree for the top-level parser-rule, Suite, displayed as in Figure 2 below.


Figure 2. Initial grammar view

The GUI's menus, toolbars, and displays, and the grammar-tree's icons and annotations are explained in the Grammar without Tears article, but if you need additional help take a look at The GUI. And if you need help with the icons or any other part of the grammar-tree, take a look at Editing the Grammar Tree. A somewhat more complex (but unfortunately essential) concept is the VisualLangLab AST. The rest of the article requires a good understanding of these concepts, so you should revisit the resources listed above if required.

Grammar Structure

A VisualLangLab grammar contains many separate grammar-trees (or parser-rules). You can display any grammar-rule by selecting its name in the toolbar's drop-down list (in the box near the red "A" in Figure 2 above). All these apparently independent grammar-trees actually constitute one large tree rooted at the top-level rule (Suite in this case). You can navigate up any branch of the tree by right-clicking any Reference node and selecting Go to from the grammar-tree's context-menu as shown in Figure 3 below.


Figure 3. Navigating up the singleTest branch

This process (right-clicking a Reference node and selecting Go to) can be repeated till you reach a grammar-tree that has no Reference nodes. The grammar-trees in Figure 4 below trace the path from Suite (the root) through singleTest, runStatements, stmtEnterText, and swingQuery to treePath.


Figure 4. Navigating grammar-tree references

Later in the article, when we discuss SWELL Internals, the grammar-trees in Figure 4 will be used to illustrate how application code (in the SWELL interpreter) is interfaced to the parser. To follow that discussion an understanding of each grammar-tree's AST will be required. The AST is displayed in the text area to the right of the grammar tree (see red box marked with a "B" in Figure 2 above). The information shown is the AST of the currently selected node, so to see the AST of the entire grammar-tree select (click on) the grammar-tree's root node. Also, the radio button marked with a "§" (for Depth, near the red "C" in Figure 2 above) should be selected. More details can be found in AST Structure.

Test-Running a Grammar-Tree

Actually running a parser-rule with different inputs gives you greater insight into the grammar, and we recommend trying to run some of the grammar-trees in Figure 4 above. Figure 5 below shows the simple steps required to test-run the selected grammar tree. Type the input text into the Parser Test Input area (the red box marked "A"), click the Parse input button (near the red "B"), then validate the parser's output (after the words result follows: in the red box marked "C"). If you see any error messages in red in place of the parser's result, the input did not match the grammar.


Figure 5. Testing the treePath grammar-tree

Figure 6 below shows the stmtEnterText parser-rule (middle of Figure 4 above) being tested. Observe that to parse the input provided, stmtEnterText needs to invoke swingQuery as well, but does not require treePath.


Figure 6. Testing the stmtEnterText grammar-tree

Testing Parsers has more detailed information about approaches to testing within the VisualLangLab GUI.

SWELL Internals - Anatomy of an Interpreter

The code described below is obviously SWELL-specific, but the overall organization is a generic blueprint (see Figure 7 below) for any DSL interpreter that uses VisualLangLab.


Figure 7. Interpreter blueprint

Disclaimer: Figure 7 just describes the dependencies between the various components. It does not imply that SWELL embeds any of the other components in any way.

As Figure 7 shows, the internal structure is very simple, just bundle your own code together with any required libraries and the VisualLangLab API. In the case of SWELL, it depends on the ABBOT (http://abbot.sourceforge.net/) framework for Swing GUI testing, so those jars are required. All interpreters using the VisualLangLab API must perform the following steps:

  • Locate the grammar (XML) file
  • Regenerate the parser
  • Parse the given input, and obtain AST
  • Interpret the AST

The following sections show and explain the essential code that handles these functions. The first three steps (up to obtaining the AST) are handled in one block of code, and are explained under Locate Grammar ... Obtain the AST below. Interpretation of the AST is explained under Application-Code AST Interaction.

Locate Grammar ... Obtain AST

A parser developed with VisualLangLab is saved as XML in a grammar-file with a .vll type. A program that uses such a parser (like the SWELL interpreter) uses the VisualLangLab API to turn this XML into an instance of the Scala Parsers type. But it must first obtain the contents of the grammar file. In the case of the SWELL interpreter, the grammar file is included as a resource in the jar file, and is retrieved using the system classloader's getResourceAsStream(String) method. This approach is convenient for programs that use large grammar-files. Smaller grammar-files can be embedded as a string within the interpreter program itself. To examine the code that performs these functions, look around the def main(...) {...} function.

Application-Code AST Interaction

The SWELL interpreter is written in Scala, and exploits Scala's pattern matching capability to analyze and unravel the AST. The description below uses snippets of code from SWELL that show how the AST is unraveled in steps strongly reminiscent of the oft-quoted onion skin metaphor. The following function marks the beginning of the onion's odyssey. The expression res.get returns the AST (the entire onion), which is passed to the Suite_handler().

Those interested in reviewing the full code should visit the src directory of the swell-article-resources.zip file.

def main(args: Array[String]) {
... lines removed for clarity ... val res = parser(input) if (res.successful) { ... lines removed for clarity ... Suite_handler(res.get) ... lines removed for clarity ... }

The function Suite_handler() shown below processes the AST from the top-level parser-rule Suite, (the entire onion). The structure of Suite's AST is shown on the left below, and notice how the code uses pattern matching to separate the onion's layers. The part of the onion containing the tests is passed to the function handleTests().

AST Handler Code Array(
| @stmtSuitBegins,
| List(@groupDefinition),
| Option(@stmtBeforeEachTest),
| Option(@stmtAfterEachTest),
| List(
| | Choice(
| | | Pair(0, @singleTest),
| | | Pair(1, @runCommand)
| | )
| ),
| @stmtSuitEnds
) private def Suite_handler(pTree: Any) {
pTree match {
case Array(suiteBegins, groups, beforeEachTest,
afterEachTest, tests, suiteEnds) =>
... lines removed for clarity ... handleTests(tests)
... lines removed for clarity ... }

The structure of the part of the AST passed to handleTests() is shown below left, and a part of handleTests()'s code is shown on the right. Notice the use of a cast and pattern-matching to locate the list of statements in each test. Each statement is passed off (one at a time) to handleRunStatements().

AST Handler Code Array(
| @testHeader,
| List(@runStatements) ) private def handleTests(tests: Any) {
tests.asInstanceOf[List[_]].foreach(_ match {
case Pair(0, Array(Array(testHeader: String, descrExprRes),
statements: List[_])) =>
... lines removed for clarity ... handleRunStatements(statements)
}

An abbreviated version of handleRunStatements()'s AST and code are shown below. Notice how pattern-matching is used to recognize each statement, and then a part of the sub-onion received is passed off to the appropriate handler function.

AST Handler Code Choice(
| Pair(0, @stmtClearText),
... lines removed ... | Pair(6, @stmtEnterText),
... lines removed ... | Pair(18, @stmtMoveMouse),
... lines removed ... ) private def handleRunStatements(stmts: List[_]) {
... lines removed for clarity ... sz._1 match { case Pair(0, c2) => currentStatementName = "Clear Text"; gd(); as(traceAll); stmtClearText(c2)
... lines removed for clarity ... case Pair(6, txt) => currentStatementName = "Enter Text"; gd(); as(traceAll); stmtEnterText(txt)
... lines removed for clarity ... case Pair(18, pos) => currentStatementName = "Move Mouse"; gd(); as(traceAll); stmtMoveMouse(pos)
}
... lines removed for clarity ... }

The code below illustrates how the enter text statement is executed. The code seems to rely on java.awt.Components having an actionEnterText(String) method! To understand this, check out SWELL Document Object Model below.

An important part of the SWELL grammar is the SwingQuery, a construct that enables any GUI component to be referred using natural-English phrases.

AST Handler Code Array(
| Choice(
| | Pair(0, [STRING]),
| | Pair(1, [VAR])
| ),
| @swingQuery
) private def stmtEnterText(txt: Any) = {
txt match {
case Array(value: Pair[Int, _], compChoice) =>
Utils.swingQuery(compChoice) match {
case c: Component => value match {
case Pair(0, strStr: String) =>
c.actionEnterText(Utils.unEscape(Utils.unQuote(strStr)))
... lines removed for clarity ... } case x => throw new NotAwtComponent(Utils.scalaTypeOf(x)) } } }

Because Swing components are central to the purpose of SWELL, its parser-rules are peppered with references to swingQuery (see Swing-Query Syntax below). The article will not go into the details of the SwingQuery grammar, but readers are encouraged to analyze and test the grammar within the VisualLangLab IDE.

AST Handler Code List(
| Choice(
| | Pair(0, @treePath),
| | Pair(1,
| | | Array(
| | | | [INT],
| | | | [INT]
| | | )
| | ),
| | Pair(2, @uniqueComponentSelector),
| | Pair(3, @methodCall),
| | Pair(4, [INLINE_CODE]),
| | Pair(5, @componentSelector),
| | Pair(6, [VAR])
| )
) def swingQuery(mc: Any) = mc match {
case tPath: List[Pair[Int, _]] =>
var expr: Any = null
tPath.reverse.foreach(x => x match {
case Pair(0, tp: List[_]) => expr match {
case jt: JTree => expr = treePath(jt, tp).last
... lines removed for clarity ... } ... lines removed for clarity ... case Pair(1, Array(r, c)) => ... lines removed for clarity ... case Pair(2, ucs) => ... lines removed for clarity ... }) expr }

The AST and code below illustrate the last step in the rule chain in Figure 4 above.

AST Handler Code List(
| Choice(
| | Pair(0, [STRING]),
| | Pair(1, [INT]),
| | Pair(2, [INLINE_CODE])
| )
) def treePath(jt: JTree, path: List[_]) = {
path.foreach(p => p match {
case Pair(0, strStr: String) =>
... lines removed for clarity ... case Pair(1, strInt: String) => ... lines removed for clarity ... case Pair(2, code: String) => ... lines removed for clarity ... }) ... lines removed for clarity ... } SWELL has DOM and SQL (Swing Query Language) Too!

The term DOM is used here in a sense similar to the web-browser usage. The SWELL DOM is a conceptual tree that contains all of the GUI's visual java.awt.Components. The DOM is implemented by the RichComponent class in the SWELL sources (see the src directory inside swell-article-resources.zip). RichComponent and its companion object leverage pimp my library (also see this paper from SigBovik 2010) to magically endow the lowly java.awt.Component with interesting methods and properties.

As explained above, a SwingQuery is a parser-rule (see Figure 8 below) that enables SWELL scripts to refer any GUI component using natural-English (Swing Query Language or SQL) phrases. SWELL's SQL is, in every sense, the heart of SWELL. The English-like character of SWELL scripts comes entirely from SQL. The power of the DOM would be completely wasted if the SWELL user wasn't able to identify a GUI element using an English-like phrase.


Figure 8. The Swing-Query syntax

The SQL grammar is based on the premise that the DOM is rooted at the top-level window. Any GUI Component can be uniquely identified by any one of the following:

  • a set of attribute values that uniquely identify the component in the local context
  • a path from the root to the Component of interest, where each intermediate step is a uniquely identified component

We give a few real examples here to clarify the concepts described above. The graphics in these examples are slightly modified versions of screens captured during tests on the CIIMS messaging-framework for airports from Unisys.

The following SWELL statements illustrate the use of SQL to work with the login dialog panel shown in Figure 9 below. In each SWELL statement, the underlined part is the SQL.

- key "USER01" into the first textfield of the "Login" dialog - key "pass123" into the passwordfield of the "Login" dialog - key "FATS01" into the 2nd textfield of the "Login" dialog # the following 3 lines are equivalents, and any one may be used - click the 1st button of the "Login" dialog - click the first button of the "Login" dialog - click the "Login" button of the "Login" dialog - click the button with text = "Login" of the "Login" dialog

In all of the above SWELL statements, [... of the "Login" dialog] would have worked as well since there is only one visible dialog, and the type (dialog) itself identifies the component uniquely.


Figure 9. Locator syntax for GUI Components

SQL can also be used with more complex GUI structures. The following two examples show how the syntax is used to access nodes of a JTree and cells of a JTable respectively.


Figure 10. Locator syntax for JTree elements

Figure 10 above shows a JFrame main window containing a JInternalFrame that in turn contains a JTree. Parts of the figure are annotated with alphabetic markers to facilitate referencing from the following explanation.

  1. the JInternalFrame itself (A) is located with:
    the "Flow" internalframe
  2. the JTree (B) is located with:
    the tree of the "Flow" internalframe
  3. the "Events" node (C) of the JTree is located with:
    node / "Events" of the tree of the "Flow" internalframe
  4. the "TESTEVENT" node (D) of the JTree is located with:
    node / "Events" / "TESTEVENT" of the tree of the "Flow" internalframe
  5. the "TESTEVENT" node may also be located with:
    node / 2 / "TESTEVENT" of the tree of the "Flow" internalframe, or
    node / "Events" / 0 of the tree of the "Flow" internalframe, or
    node / 2 / 0 of the tree of the "Flow" internalframe

The JFrame main window in Figure 11 below contains a JInternalFrame that in turn contains a JTable. The alphabetic markers facilitate referencing from the following explanation.


Figure 11. Locator syntax for JTable elements

  1. the JInternalFrame itself (A) is located with:
    the "Statistics" internalframe
  2. the Start button (B) is located with:
    the "Start" button of the "Statistics" internalframe
  3. the JTable (C) is located with:
    the table of the "Statistics" internalframe
  4. the outlined cell (D) of the JTable is located with:
    cell 17 1 of the table of the "Statistics" internalframe
SWELL Language Manual

This is a short guide to the SWELL language. More details can be found at the SWELL project site.

SWELL Statements

The following table lists all SWELL statements.

Statement Description Grammar-Tree Name After-Each-Test Introduces a list of statements that are executed after the completion of each and every test (irrespective of the test outcome) stmtAfterEachTest Before-Each-Test Introduces a list of statements that are executed before each stmtBeforeEachTest Clear-Text Clears the text in the specified Component stmtClearText Click Performs a simple mouse-click over the middle of the specified Component. Left Click and Right Click are also available. stmtClick Collapse Collapses the specified tree node stmtCollapse Delay Causes the test execution to be delayed by the specified duration stmtDelay Display Displays the specified string in the test log (same as print) stmtDisplay End Ends either the current test or the entire test suite and prints the specified message to the test log stmtEnd Enter-Text Enters the supplied string into the specified Component stmtEnterText Exec Exectues the specified SWELL file as a sub-process stmtExec Expand Recursively expands the specified tree node stmtExpand Fail-If Specifies a condition (and associated message) for a negative test outcome stmtFailIf Focus Moves focus to the specified Component stmtFocus If A conditional control flow statement stmtIf Key-Text Keys the supplied text into the specified Component stmtKeyText Launch-GUI Launches the specified GUI program stmtLaunchGui List-Components Lists (on the test log) one or more Components selected stmtListComponents Locate-Components Helps to locate one or more Components by coloring them stmtLocate Maximize Maximizes the specified internal frame stmtMaximize Minimize Minimizes the specified internal frame stmtMinimize Move-Mouse Moves the mouse over the middle of the specified component stmtMoveMouse Pause Pops up a blocking dialog with the specified message stmtPause Perform Executes a previously declared group or some embedded-code stmtPerform Print Prints the supplied string (with variable substitutions) to the test log stmtPrint Reference (or Assignment) Assigns a value to a variable stmtReference Select Executes a GUI operation involving one or more selections from menus or lists stmtSelect Set Obsolete, do not use stmtSet Suite-Begins The suite header line stmtSuitBegins Suite-Ends The suite trailer line stmtSuitEnds Trace Starts or ends tracing stmtTrace Unless The unless statement is an inverse if statement stmtUnless Wait Causes execution to be delayed till the specified event occurs stmtWait WarnIf Causes a warning to be issued to the test log if the specified condition occurs stmtWarnIf While The usual procedural control flow statement stmtWhile Embedded Code

Several SWELL statements allow the use of embedded Scala code. Embedded Scala code must be enclosed within doubled curly-braces, like this: {{your code here}}. Check the token INLINE_CODE for details.

Durations

Time durations in SWELL may be expressed either as seconds or milli-seconds. The unit specification (which follows the magnitude) may be any of the following: s, second, seconds, milli second, milli seconds, ms. Review the parser-rule timeDuration for details.

Variables

Several SWELL statements require (or optionally permit) the use of variables. Variable names are user-chosen following the usual conventions of contemporary programming languages. Variable references are given using the form ${variable name}. Check the token called VAR

Conclusion

The article introduces the SWELL domain-specific language for testing Swing GUIs. The SWELL interpreter uses the completely visual parser-generator VisualLangLab, so the design of SWELL described in the article is also a generic framework for DSLs that use VisualLangLab as their parser-generator.

The SWELL interpreter (being developed at the SWELL Project) is already usable and reliable enough to be valuable in real-world development projects. The authors plan to continue improving upon this early code base to improve the tool further.

Acknowledgements

We would like to thank the following folks at TwoPiRadian Infotech (2PiRad): Prodipta Golder, Debopam Ghoshal, and Ramanendu Chatterjee for the numerous discussions and great ideas that helped to shape the DSL, and Jishnu Ray for editorial comments and guidance on the article contents. The DSL was analyzed, reviewed and refined in lively discussions with many others, and the world's population of DSLs would be one less without 2PiRad's amazing people and environment.

TwoPiRadian Infotech is a boutique software solutions developer, specializing in developing dynamic web applications, rich internet applications, collaboration and content management systems. They have in-depth experience in the new generation software technology platforms covering key business domains (airports, airlines, travel & hospitality, and financial services)

References

swell-article-resources.zip

References Sanjay Dasgupta has been using Java for telecom applications since 1996 (after many years of using many different languages in many industries). Chirantan Kundu is Analytics Principal at TwoPiRadian Infotech (http://www.2pirad.com/). He has been designing and developing software, and managing software projects for over 15 years AttachmentSize SWELL_FirstVLLTest.png33.12 KB SWELL_initial-grammar-view.png26.07 KB SWELL_locators-dialog.png10.54 KB SWELL_locators-table.png33.8 KB SWELL_locators-tree.png22.15 KB SWELL_navigatingGrammarCompressed.png24.82 KB SWELL_singleTest-goto.png22.58 KB SWELL_SWELL-Structure.png7.36 KB SWELL_SwingQuery.png29.56 KB SWELL_testing-stmtEnterText.png33.18 KB SWELL_testing-treePath.png27.25 KB
Categories: Java

Recent comments

Syndicate content