Action-Command Scripting

Action-Command Scripting

Action-Command Scripting is a simple paradigm that helps structure your testing frameworks. You can think of it as a half-step toward Given-When-Then syntax. It can work for any time of test automation, though it’s particularly handy for front-end automation where GWT tends to fall short on its own.

I borrowed this idea from my early days as a Doom modder. Action Code Scripting was a scripting engine added to Doom in the Hexen mod pack. The idea was to separate the Action of the game engine from your scripted Code: one line of code could potentially bundle many actions within the game engine, allowing a greater amount of flexibility for scripted events.

That degree of separation and code reuse is extremely advantageous in your AQA codebase: let your tests Command and your framework take Action. ACS is an idea of separation between your Commands and Actions.

(Note: this is essentially the same concept as Page-Object model. I like to call it ACS as Page-Object makes it sound linked to Front-End automation where in reality we can apply this same concept to many form of AQA or even development in general. In fact the first project I applied this to was SOAP API testing).

Commands vs Actions

A Command is an order to do something.

An Action is the series of events required to carry out the Command.

Consider when you press the accelerator on your car. You are giving a Command: car, go faster. Your car, through a series of mechanical and chemical events, carries out the Action of applying more torque to the tires. You have no direct control of how that Action is carried out. You likely even have little knowledge of how exactly the Action is carried out. You likely don't even care how the Action is carried out. All you know is the Command: car, go faster. The Command is simple and clean.

The Action that happens as a result is carried out by the engine and related subsystems. These systems come in many forms, have many features and, most importantly, can be repaired, changed, and even completely replaced while keeping the Command the same. Actions can be messy and complicated - but they only need to be written once, and can be reused in a number of instances: it doesn't matter who is driving the car, pressing the accelerator causes the car to go faster.

When writing tests, the most time-consuming portion is figuring out the logic of how to do something - just as building an internal combustion engine is no easy task. In API testing, this is how to create a REST or SOAP request, package it, and send it - along with all the authentication etc. - and then parsing the response. In front-end testing, this is finding all your selectors and sending the appropriate event (or series of events). And of course the logging and error-handling that comes with those steps. If we bundle these as Actions, we can streamline our actual test code and invite a lot of reuse and maintainability.

Your actual test code should make a Command: send this JSON. Click this button. Set this field. Your framework should then have methods that handles the necessary Action of finding the field, checking it’s status, sending the keys, verifying the field was set, handling exceptions, and logging every step of the way.

Action-Command Scripting: Data Model

Consider this Selenium script:

driver.findElement(By.css(“#username”)).sendKeys(“xXx_bob_XxX”)
driver.findElement(By.css(“#password”)).sendKeys(“bobRulz123”)
driver.findElement(By.css(“#submit”)).click()
Assert.assertEquals(driver.findElement(By.css(“#message”)).getText(), “Welcome, xXx_bob_XxX”)
driver.findElement(By.css(“#profile”)).click()
driver.findElement(By.css(“#change-password”)).click()
driver.findElement(By.css(“#current-password”)).sendKeys(“bobRulz123”)
driver.findElement(By.css(“#new-password”)).sendKeys(“bobIsAwesome123”)
driver.findElement(By.css(“#confirm-new-password”)).sendKeys(“bobIsAwesome123”)
driver.findElement(By.css(“#update-password”)).click()
Assert.assertEquals(driver.findElement(By.css(“#message”)).getText(), “Password Updated”)
driver.findElement(By.css(“#logout”)).click()
driver.findElement(By.css(“#username”)).sendKeys(“xXx_bob_XxX”)
driver.findElement(By.css(“#password”)).sendKeys(“bobRulz123”)
driver.findElement(By.css(“#submit”)).click();
Assert.assertEquals(driver.findElement(By.css(“#message”)).getText(), “Invalid Login”)
driver.findElement(By.css(“#username”)).sendKeys(“xXx_bob_XxX”)
driver.findElement(By.css(“#password”)).sendKeys(“bobIsAwesome123”)
driver.findElement(By.css(“#submit”)).click();
Assert.assertEquals(driver.findElement(By.css(“#message”)).getText(), “Welcome, xXx_bob_XxX”)

This script is ok, it works (... probably). But it leave a lot to be desired. The first thing that comes to mind is if this breaks mid-stream it’s difficult to debug by just reading the logs - because there aren't any. So let's fix that first.

driver.findElement(By.css(“#username”)).sendKeys(“xXx_bob_XxX”)
System.out.println(“Set username to xXx_bob_XxX”)
driver.findElement(By.css(“#password”)).sendKeys(“bobRulz123”)
System.out.println(“Set password to bobRulz123”)
System.out.println(“Attempting to log in”)
driver.findElement(By.css(“#submit”)).click()
System.out.println(“Submitted Form”)
Assert.assertEquals(driver.findElement(By.css(“#message”)).getText(), “Welcome, xXx_bob_XxX”)
System.out.println(“Login successful”)
driver.findElement(By.css(“#profile”)).click()
System.out.println(“Navigated to Profile”)
driver.findElement(By.css(“#change-password”)).click()
System.out.println(“Navigated to Change Password”)
driver.findElement(By.css(“#current-password”)).sendKeys(“bobRulz123”)
System.out.println(“Set Current Password To bobRulz123”)
driver.findElement(By.css(“#new-password”)).sendKeys(“bobIsAwesome123”)
System.out.println(“Set New Password To bobIsAwesome123”)
driver.findElement(By.css(“#confirm-new-password”)).sendKeys(“bobIsAwesome123”)
System.out.println(“Set Confirm New Password To bobIsAwesome123”)
driver.findElement(By.css(“#update-password”)).click()
System.out.println(“Updated Password”)
Assert.assertEquals(driver.findElement(By.css(“#message”)).getText(), “Password Updated”)
driver.findElement(By.css(“#logout”)).click()
driver.findElement(By.css(“#username”)).sendKeys(“xXx_bob_XxX”)
System.out.println(“Set username to xXx_bob_XxX”)
driver.findElement(By.css(“#password”)).sendKeys(“bobRulz123”)
System.out.println(“Set password to bobRulz123”)
System.out.println(“Attempting to log in with old password”)
driver.findElement(By.css(“#submit”)).click();
System.out.println(“Submitted Form”);
Assert.assertEquals(driver.findElement(By.css(“#message”)).getText(), “Invalid Login”)
System.out.println(“Login unsuccessful”)
driver.findElement(By.css(“#username”)).sendKeys(“xXx_bob_XxX”)
System.out.println(“Set username to xXx_bob_XxX”)
driver.findElement(By.css(“#password”)).sendKeys(“bobIsAwesome123”)
System.out.println(“Set password to bobIsAwesome123”)
System.out.println(“Attempting to log in with new password”)
driver.findElement(By.css(“#submit”)).click();
System.out.println(“Submitted Form”);
Assert.assertEquals(driver.findElement(By.css(“#message”)).getText(), “Welcome, xXx_bob_XxX”)
System.out.println(“Login unsuccessful”)

Ok, now we’ve almost doubled our code, but we have logs. But now we’re also only verifying the messages, if something fails we only know that a message didn't appear - or possibly a message did appear when it shouldn't have. So let's add some verification:

driver.findElement(By.css(“#username”)).sendKeys(“xXx_bob_XxX”)
Assert.assertEquals(driver.findElement(By.css(“#username”).getValue(), “xXx_bob_XxX”)
System.out.println(“Set username to xXx_bob_XxX”)
driver.findElement(By.css(“#password”)).sendKeys(“bobRulz123”)
Assert.assertEquals(driver.findElement(By.css(“#password”).getValue(), “bobRulz123”)
System.out.println(“Set password to bobRulz123”)
....

Phew, well, you get the idea. Even though this works, and does adhere generally to best practices, there are many problems with this code. For one, it’s very difficult to read. Second there’s very little reuse (when we revisit the login page, for example). Finally it is a royal pain to write - you’re not going to remember to do every single check and log with every single line of code across 30 test cases.

This is where ACS comes in. We need to divide our labor between classes: our tests Command and our UI takes Action. First we build two classes: Test and UI.

Our test class will handle what we want to do:

class ID00001_ChangePassword {
    @Test changeUserPasswordToValid(){
        login_ui.setUsername(“xXx_bob_XxX”)
        login_ui.setPassword(“bobRulz123”)
        login_ui.clickSubmit()
        Assert.assertEquals(home_ui.getMessage(), “Welcome, xXx_bob_XxX”)
        home_ui.clickProfile()
        profile_ui.clickChangePassword()
        profile_ui.setCurrentPassword(“bobRulz123”)
        profile_ui.setNewPassword(“bobIsAwesome123”)
        profile_ui.setConfirmNewPassword(“bobIsAwesome123”)
        navigation_ui.clickLogout()
        ...
    }
}

Next, we need our UI class that takes Action:

class Login_UI {
    @FindElement(css=”#username”)
    public WebElement username_Text_UI
    @FindElement(css=”#password”)
    public WebElement password_Text_UI
    @FindElement(css=”#submit”)
    public WebElement submit_Button_UI

    public void setUsername(String value){
        utility.setTextField(username_Text_UI, “username”, value)
    }

    public void setPassword(String value){
        utility.setTextField(password_Text_UI, “password”, value)
    }


    public void clickSubmit(){
        utility.clickButton(submit_Button_UI, “submit”)
    }
}

And, we can take it one step further and wrap certain common functionality, like setting fields, into a utility class:

class Utility {
    public void setTextField(WebElement e, String name, String value)   {
   Instant startSet = Instant.now();
   System.out.println("Setting Field:"+fieldName +" To:" + value);
   int catchStaleElement = 0;
   do{
       try{
           wait.waitElementIsVisible(e);
           e.clear();
           wait.waitUntilClear(e);
           try {
               Thread.sleep(500);
           } catch (InterruptedException e1) {
               e1.printStackTrace();
           }
           e.sendKeys(value);
           Assert.assertEquals(e.getAttribute("value"),value);
           System.out.println("Set to:"+value,);
           break;
           } catch (StaleElementReferenceException ex){
               System.out.println.log("Stale element. Retrying.");
               catchStaleElement++;
           }
    } while(catchStaleElement < 5);
    System.out.println("Duration    (millis):"+Duration.between(startSet, Instant.now()).toMillis());
    }
}

Look at all that cool stuff we can do! We can add logging, assert the value before and after it’s set, we can put the thread to sleep, we can track our timing on how long it takes to set certain fields, we can wait for elements to appear, and we can do error handling. The best part is we only have to do this once, and in one place, and all our tests will benefit from using these methods.

Alternative to Cucumber

I love Cucumber as a paradigm of writing tests, but in my five years of AQA I’ve never been able to actually implement it as a process. Cucumber is very difficult to retrofit into an existing codebase, and sometimes can be difficult to implement from scratch if the rest of the development team isn't on board or doesn't have the buy-in to AQA.

I’ve found ACS to be a good middle-ground. It’s pretty easy to refactor most codebases to be ACS and it’ll greatly increase the maintainability and stability of your codebase. It has many of the same benefits and hallmarks of Cucumber without some of the more restrictive sections.

Data-Driven Automation

Data-Driven Automation

Performance Testing: Benchmarking Your Product

Performance Testing: Benchmarking Your Product