JavaScript Tests Guide

Since version 0.11.1 Galen Framework migrated to JavaScript test runners. This means that you can now have a better support for your functional tests.
You should store all your javascript-based tests in .test.js files.

Defining tests

The tests are defined using test function where the first argument represents a test name and second is test callback

test("Simplest test", function () { // here goes a test code });

Creating driver

While in basic test suites you didn’t have to control the driver creation here in JavaScript test suites you have to do it yourself. Therefore you have the complete control over driver instantiation. Still there is a good API for creating a driver that mimics the behavior from basic test suites.

The function for creating a driver is createDriver. By default it creates a firefox driver. That behavior can also be overridden in config file so later you can easily switch to Selenium Grid.

test("Home page test on mobile device", function () { var driver = createDriver("http://galenframework.com", "640x480"); });

You can also provide a browser type

test("Home page test on mobile device", function () { var driver = createDriver("http://galenframework.com", "640x480", "firefox"); });

Creating driver in Selenium Grid

To get the driver instance in Selenium Grid you should use createGridDriver

test("Home page test on mobile device", function () { var driver = createGridDriver("http://mygrid/wd/hub", { browser: "firefox", browserVersion: "24", platform: "LINUX", size: "1024x768" }); });

You can also provide custom desired capabilities. This might be very handy if you want to run your tests on SauceLabs Grid as there you have to choose a specific browser through desiredCapabilities property

test("Home page test on mobile device", function () { var driver = createGridDriver("http://mygrid/wd/hub", { browser: "iphone", desiredCapabilities: { platform: "OS X 10.8", version: "6.0", deviceName: "iPhone", "device-orientation": "portrait" } }); });

Checking layout

To check the layout of the page you should use checkLayout function. It takes the following arguments:

test("Home page", function() { var driver = createDriver("http://galenframework.com", "1024x768"); checkLayout(driver, "homePage.gspec", ["all", "desktop"]); });

Parameterizing tests

In basic test suites you used to have Tables and Parameterized constructions. In JavaScript test suites you can use the forAll function for parameterizing your tests. There are two parameterization ways available: by 2-dimensional arrays and by structures

Using 2-dimensional arrays

In case of array-based parameterization you define the amount of tests by rows and arguments by columns in array.
The following example will run 3 times for devices: mobile, tablet and desktop.

forAll([ ["mobile", "400x700"], ["tablet", "600x800"], ["desktop", "1024x768"] ], function (deviceName, size){ test("Home page on " + deviceName + " device", function (deviceName, size){ var driver = createDriver("http://galenframework.com", size); }); });

Using structures

A more convenient way for parameterization would be using structures. Galen will iterate over each element in a structure and pass its value to the test.
With this it also allows to use basic templating expression in test name

// Lets create a separate variable describing all devices // as it might be used by other tests as well this.devices = { mobile: { deviceName: "mobile", size: "400x700" }, tablet: { deviceName: "tablet", size: "600x800" }, desktop: { deviceName: "desktop", size: "1024x768" } }; forAll(devices, function (device) { test("Home page on ${deviceName}", function (device){ var driver = createDriver("http://galenframework.com", device.size); }); });

Multi-level parameterization

What if you want to use multiple levels of parameterization? You can do it in the following way:

var operatingSystems = { linux: { osName: "Linux" }, windows: { osName: "Windows" } }; var browsers = { firefox: { browserName: "Firefox" }, chrome: { browserName: "Chrome" } }; forAll(operatingSystems, function () { forAll(browsers, function () { test("Test on ", function (os, browser) { // some test code }); }); });

As you can see in the example above you don’t need to declare all argument in all callbacks as all these arguments are passed implicitly to the test

Reusing parameterization element to run only once

Sometimes you need to run specific tests for specific parameters. You can do so through forOnly function:

this.devices = { mobile: { deviceName: "mobile", size: "400x700" }, tablet: { deviceName: "tablet", size: "600x800" }, desktop: { deviceName: "desktop", size: "1024x768" } }; forOnly(devices.mobile, function () { test("Home page on ${deviceName}", function (device){ // test code goes here }); });

GalenPages: interacting with page elements

In case you want to perform functional testing and you need to interact with elements on page it is best to use GalenPages. It is a small lightweight framework for working with page object model.

This is how you declare a page

this.LoginPage = function (driver) { GalenPages.extendPage(this, driver, { emailTextfield: "input.email", // css locator passwordTextfield: "xpath: //input[@class='password']", // xpath locator submitButton: "id: submit" // id locator }); };

This is how you can use your page in the test

var loginPage = new LoginPage(driver); loginPage.emailTextfield.typeText("[email protected]"); loginPage.passwordTextfield.typeText("P@ssw0rd"); loginPage.submitButton.click();

But you can also provide functions together with page declaration

this.LoginPage = function (driver) { GalenPages.extendPage(this, driver, { emailTextfield: "input.email", // css locator passwordTextfield: "xpath: //input[@class='password']", // xpath locator submitButton: "id: submit", // id locator loginAs: function (userName, password) { this.emailTextfield.typeText(userName); this.passwordTextfield.typeText(password); this.submitButton.click(); } }); }; // now you can do it like this var loginPage = new LoginPage(driver); loginPage.loginAs("[email protected]", "password");

Each page has an in-built waitForIt method that waits for 10 seconds until all defined elements are displayed on page. In case you have some fields defined that are not always shown on page you can define them in next argument

this.LoginPage = function (driver) { GalenPages.extendPage(this, driver, { emailTextfield: "input.email", // css locator passwordTextfield: "xpath: //input[@class='password']", // xpath locator submitButton: "id: submit" // id locator }, { // here are defined elements that will // not be used in waitForIt function tooltip: "div.tooltip" }); }; var loginPage = new LoginPage(driver); loginPage.waitForIt();

For more information about GalenPages please read GalenPages JavaScript API

Events

You can assign custom handlers for different test events like:

Before test suite

Called once all the tests are prepared but not yet executed

beforeTestSuite(function () { // here goes the code });

After test suite

Called once all the tests are finished

afterTestSuite(function () { // here goes the code });

Before test

Called before a test execution. Passes a TestInfo object as an argument

beforeTest(function (testInfo) { // here goes the code });

A TestInfo object has the following fields.

  • name - a name of the test
  • startedAt - Java Date object representing time when the test has started
  • endedAt - Java Date object representing time when the test has finished
  • isFailed - a Boolean function return the state of test (true if failed, false if passed)
  • report - an instance of test report

After test

Called once a test is finished. Passes a TestInfo object as an argument

afterTest(function (testInfo) { // here goes the code });

Reporting

Each test has a report field once it is instantiated. This is how you can use it:

test("Some basic test", function () { this.report.info("Checked something"); this.report.error("Validation failed"); });

Reports in Galen Framework are hierarchical which means that you can design the structure of reports yourself. Here is another interesting trick with logged function

test("Hierarchical reporting test", function () { logged("Doing some restful request", function (report) { report.info("POST http://example.com"); // some code here logged("Got 200 response", function () { // ... some more code here }); }); });

This is what the resulting report would look like

You can also add details to the report entry

test("Hierarchical reporting test", function () { this.report.info("This text will be shown") .withDetails("This text will be hidden inside"); });

You can also attach files to report

test("Some basic test", function () { this.report.info("Take a look at these attachments") .withAttachment("page.html", "/path/to/file/page.html"); });

Advanced reporting

You can create functions that will be automatically logged in the HTML report with the passed arguments based on the provided log expression. Just use loggedFunction instead of function

this.LoginPage = $page("Login page", { email: ".email", password: ".password", submitButton: "#submit", loginAs: loggedFunction("Login as ${_1.email} with password ${_2.password}", function (user) { this.email.typeText(user.email); this.password.typeText(user.password); this.submitButton.click(); }) });

Test Session

Each test has a session in which it stores its data. This way it is easier to share data across all your custom components.
This is how you can put your data in session:

test("My simple test", function () { session.put("myObject", { getName: function () { return "John"; } }); });

Somewhere later in your code you can retrieve the stored object

var myObject = session.get("myObject"); myObject.getName();

Data Providers

It is also possible to create your own custom data providers for your tests. This could be used in case you want to create custom generic test functions.
To create a test data provider you should use createTestDataProvider function

this.featureSwitched = createTestDataProvider("featureSwitches");

Now we can use it like this

featureSwitched("Feature1", function () { test("Home page test", function () { console.log("All my features are here " + this.data.featureSwitches); }); });

In the example above the output will be [“Feature1”]

Data providers also support multi-level definition and array values. In the end they are flatten in array

featureSwitched(["Feature1", "Feature2"], function () { featureSwitched("Feature3", function () { test("Home page test", function () { console.log("All my features are here " + this.data.featureSwitches); }); }); });

In the example above the output will be [“Feature1”, “Feature2”, “Feature3”]

This doesn’t really look useful unless you have some defined some kind of basic test template and then reusing it everywhere. Lets check out this example

this.featureSwitched = createTestDataProvider("featureSwitches"); this.myBaseTest = function (testName, myCallback) { test(testName, function () { var driver = createDriver("http://example.com", "1024x768"); if (this.data.featureSwitches != undefined) { // Do something with the 'featureSwitches' data } myCallback.call(this, driver); driver.quit(); }); }; // And now defining tests featureSwitched("Feature1", function () { myBaseTest("Home page", function (driver) { // do something in the test // This test will be with feature switch // but in this place you don't have to care about it // as it is handled already in your base test template above }); }); myBaseTest("Other page", function (driver) { // This test will be without feature switch });

Properties

Sometimes you need to load a property file so later it could be picked up in your spec. This is very useful in case of internationalization testing
To load properties you can use loadProperties function

var properties = loadProperties("mycustom.properties");

Reading file

To read file content into variable you can use readFile function

var fileContent = readFile("myfile.txt");

Retrying unstable code

Sometimes you need to retry some unstable action a couple of times until it passes. This might be if you use Selenium Grid and you have errors with instantiating a WebDriver. This can be done with retry function

var driver = retry(3, function () { return createGridDriver("http://mygrid/wd/hub", { browser: "firefox" }); });

The first argument – is the amount of retries and the second argument is the callback.

Taking screenshot

To create a screenshot you can use the takeScreenshot function. It returns the Java File type

var screenshotFile = takeScreenshot(driver);

You can also use it together with the reports

test("Some basic test", function () { // ... this.report.error("Something went wrong on the page") .withAttachment("Screenshot", takeScreenshot(driver)); });

Injecting client-side javascript

Sometimes you need to inject a custom client-side javascript on the test page. This can done with inject function

test("Some basic test", function () { var driver = createDriver("http://example.com"); inject(driver, "document.getElementById('mybutton').innerHTML = 'my button';"); });

Setting cookies

If you need to provide custom cookie on the test page you can use cookie function

test("Some basic test", function () { var driver = createDriver("http://example.com"); cookie(driver, "UserName=testuser; path=/"); });

Test Filters

It is also possible to change the sequence of tests that will be executed by Galen. By using the test filters you can exclude the test by some specific properties or reorder the test sequence. Just use testFilter function

testFilter(function (tests) { var newTests = []; for (var i = 0; i < tests.length; i++) { if (tests[i].name != "Some test") { newTests.push(tests[i]); } } return newTests; });

Retrying a failed test

If you want to retry a failed test you can create your own handler which checks whether a certain test should be retried or not. For that you should use testRetry function. With this function you can provide a callback which will be used for checking if a specific failed test should be retried. The callback is a Boolean function and takes two parameters: test and retryCount:

  • test - an instance of a test
  • retryCount - a number representing the amount of times this test was already retried.
testRetry(function (test, retryCount) { if (retryCount < 3) { return true; } else { return false; } });

Test Groups

Since version 1.6 you can group your tests. Each test can belong to multiple groups. In JavaScript tests you can use a function grouped for grouping specific tests. Here is an example:

grouped(["mobile", "homepage"], function () { test("Home page on mobile device", function () { // ... }); grouped(["temp"], function () { test("Home page on mobile device 2", function() { // ... }); }); }); $$

Comments

We have moved all the discussions to Google Groups. From this moment, if you have problems with your test code or some issues with installation, please ask your questions in https://groups.google.com/forum/#!forum/galen-framework.