Example Automated Test with Node.js, Selenium, and Jasmine¶
To illustrate the basic concepts of automated testing, the example below uses the following tooling to perform a test within Skuid:
- Node.js: A Javascript runtime environment, commonly used for server-side logic. It can also be run from the command line on a local machine. In this case, Node provides the runtime environment to run the test suite—allowing the test to be written in JavaScript.
- Selenium: A cross-platform, cross-browser suite of automated testing tools. More specifically, this example uses WebDriverJS to work in a Node.js environment. Selenium provides the automated point-and-click functionality used to open and test web pages, while WebDriverJS’s bindings determine the language used to write the automated actions of the test.
- Selenium also requires a driver for the browser that will run the tests. The example below uses the ChromeDriver.
- Jasmine: A behavior-driven JavaScript testing framework, used in conjunction with Selenium to process test results. The testing framework determines the syntax used to write the expectations of tests.
Any of the the above may be substituted with tooling of your choice, but the samples below provide a starting point from which to build your own custom tests and implementations.
Note
This example assumes that you have installed the software noted above and are working on a Linux or macOS system.
Prerequisites¶
The tests below use an example page that must exist within your Skuid site.
Copy and paste this XML
into a Skuid page named SeleniumTest in your chosen environment.The sections below also discuss the construction of the
example-spec
JS file. You may combine the two code samples below and save the file yourself, ordownload the file here and follow along.
You’ll also need to install the software listed above. You may do so manually at each of the above linked sites, or you may run the following commands from your terminal.
# Install node Node.js from their download page: https://nodejs.org/en/download/ # Or via a package manager: https://nodejs.org/en/download/package-manager/ # For example, with Homebrew: brew install node # Next, install jasmine globally npm install -g jasmine # Finally, navigate to the directory with example-spec.js # And install these node modules there so they are available # for the tests cd /path/to/example-spec-file/ npm install --save chromedriver npm install --save selenium-webdriver
Accessing Skuid through automation software¶
Before any testing can begin, first write the logic to log in to a Skuid site. After writing this logic in your chosen language, you can reuse it for all future tests.
The example below, written in nodeJS, does the following:
- Imports the required modules used by Selenium for automated testing.
- Looks to the environment variables of the current runtime for Skuid credentials.
- Establishes which HTML elements to target for entering credentials and logging in.
- Checks whether SSO has been enabled for the Skuid site, navigating to the basic authentication page if so.
- Enters the username and password
- Clicks the Log In button element to log in.
See the emphasized lines below for this logic:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | // Require modules used in the logic below
const {Builder, By, Key, until} = require('selenium-webdriver');
// You can use a remote Selenium Hub, but we are not doing that here
require('chromedriver');
const driver = new Builder()
.forBrowser('chrome')
.build();
// Get environment variables for Skuid site credentials
const baseUrl = process.env.SKUID_HOST
const username = process.env.SKUID_UN
const password = process.env.SKUID_PW
// Logic to login to a Skuid site using the above credentials
// Note: This function does not check if already logged in.
var login = async function() {
// Define login elements
let loginContainer = By.css('.public-main-wrapper');
let inpUsername = By.xpath('//*[@id="username-input-field"]//input');
let inpPassword = By.xpath('//*[@id="password-input-field"]//input');
let btnLoginWithoutSso = By.id('login-without-sso-button');
let btnLogin = By.id('userpass-login-button');
var enterCredentialsAndLogin = async function() {
console.log('Entering credentials...')
// Wait until an input element appears
await driver.wait(until.elementLocated(inpUsername), 10 * 1000);
// Enter credentials and log in
await driver.findElement(inpUsername).sendKeys(username);
await driver.findElement(inpPassword).sendKeys(password);
await driver.findElement(btnLogin).click();
}
// Load the login page
await driver.get(baseUrl + '/ui/login');
// Wait until the page is loaded
await driver.wait(until.elementLocated(loginContainer), 10 * 1000);
console.log('Login screen loaded.')
// Check to see if SSO has been enabled for this site.
// If it has, click through to log in with credentials instead.
await driver.findElement(btnLoginWithoutSso).then(async function(){
console.log ('SSO enabled. Logging in with username and password instead.')
await driver.findElement(btnLoginWithoutSso).click()
enterCredentialsAndLogin()
}, async function(){
await enterCredentialsAndLogin()
})
// Wait to be logged in, assuming it was was successful
// once the Log in button has gone "stale."
await driver.wait(until.stalenessOf(driver.findElement(btnLogin)));
console.log('Logged in.')
}
// Configure Jasmine's timeout value to account for longer tests.
// Adjust this value if you find our tests failing due to timeouts.
jasmine.DEFAULT_TIMEOUT_INTERVAL = 20 * 1000;
// Define a category of tests using test framework, in this case Jasmine
describe("Basic element tests", function() {
// Before every test, open a browser and login
// using the logic written above.
beforeEach(async function() {
await login();
console.log('Test beginning.')
});
// After each test, close the browser.
afterAll(async function() {
await driver.quit();
});
// Specify a test
it("Click the button, Verify Correct UI Block Message", async function() {
// Provide basic data used to evaluate the test.
// This test should pass.
var testData = {
pageName: 'SeleniumTest',
button: By.css('#test-button'),
blockMessage: By.css('div.blockUI.blockMsg')
}
console.log('Running test...')
// Preview the test page
await driver.get(baseUrl + '/ui/page/preview/' + testData.pageName);
// Wait for button
await driver.wait(until.elementLocated(testData.button), 10 * 1000);
// Verify button is present
expect(await driver.findElement(testData.button).isDisplayed()).toBe(true);
// Click button
await driver.findElement(testData.button).click();
// Wait for the blocked UI message to appear
await driver.wait(until.elementLocated(testData.blockMessage), 10 * 1000);
// Verify the text of the message, which should match the example page XML
expect(await driver.findElement(testData.blockMessage).getText()).toBe('The button renders and is clickable.');
});
// Specify a second test
it("Click the button, Verify Incorrect UI Block Message", async function() {
// Provide basic data used to evaluate the test.
// This test should fail.
var testData = {
pageName: 'SeleniumTest',
button: By.css('#test-button'),
blockMessage: By.css('div.blockUI.blockMsg')
}
// Preview the test page
await driver.get(baseUrl + '/ui/page/preview/' + testData.pageName);
// Wait for button
await driver.wait(until.elementLocated(testData.button), 10 * 1000);
// Verify button is present
expect(await driver.findElement(testData.button).isDisplayed()).toBe(true);
// Click button
await driver.findElement(testData.button).click();
// Wait for and Verify Correct UI Block Message
await driver.wait(until.elementLocated(testData.blockMessage), 10 * 1000);
// Verify the text of the message, which should *not* match the example page XML
expect(await driver.findElement(testData.blockMessage).getText()).toBe('These aren\'t the Droids you\'re looking for..');
});
});
|
Designing tests¶
For the rest of the example-spec
JS file, we detail two specs. Both will click the button on the example page and then attempt to verify the text within its Show Message and Block UI logic. One test will succeed, and the next test will fail.
See the emphasized lines below, beginning at line 56, for this logic:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | // Require modules used in the logic below
const {Builder, By, Key, until} = require('selenium-webdriver');
// You can use a remote Selenium Hub, but we are not doing that here
require('chromedriver');
const driver = new Builder()
.forBrowser('chrome')
.build();
// Get environment variables for Skuid site credentials
const baseUrl = process.env.SKUID_HOST
const username = process.env.SKUID_UN
const password = process.env.SKUID_PW
// Logic to login to a Skuid site using the above credentials
// Note: This function does not check if already logged in.
var login = async function() {
// Define login elements
let loginContainer = By.css('.public-main-wrapper');
let inpUsername = By.xpath('//*[@id="username-input-field"]//input');
let inpPassword = By.xpath('//*[@id="password-input-field"]//input');
let btnLoginWithoutSso = By.id('login-without-sso-button');
let btnLogin = By.id('userpass-login-button');
var enterCredentialsAndLogin = async function() {
console.log('Entering credentials...')
// Wait until an input element appears
await driver.wait(until.elementLocated(inpUsername), 10 * 1000);
// Enter credentials and log in
await driver.findElement(inpUsername).sendKeys(username);
await driver.findElement(inpPassword).sendKeys(password);
await driver.findElement(btnLogin).click();
}
// Load the login page
await driver.get(baseUrl + '/ui/login');
// Wait until the page is loaded
await driver.wait(until.elementLocated(loginContainer), 10 * 1000);
console.log('Login screen loaded.')
// Check to see if SSO has been enabled for this site.
// If it has, click through to log in with credentials instead.
await driver.findElement(btnLoginWithoutSso).then(async function(){
console.log ('SSO enabled. Logging in with username and password instead.')
await driver.findElement(btnLoginWithoutSso).click()
enterCredentialsAndLogin()
}, async function(){
await enterCredentialsAndLogin()
})
// Wait to be logged in, assuming it was was successful
// once the Log in button has gone "stale."
await driver.wait(until.stalenessOf(driver.findElement(btnLogin)));
console.log('Logged in.')
}
// Configure Jasmine's timeout value to account for longer tests.
// Adjust this value if you find our tests failing due to timeouts.
jasmine.DEFAULT_TIMEOUT_INTERVAL = 20 * 1000;
// Define a category of tests using test framework, in this case Jasmine
describe("Basic element tests", function() {
// Before every test, open a browser and login
// using the logic written above.
beforeEach(async function() {
await login();
console.log('Test beginning.')
});
// After each test, close the browser.
afterAll(async function() {
await driver.quit();
});
// Specify a test
it("Click the button, Verify Correct UI Block Message", async function() {
// Provide basic data used to evaluate the test.
// This test should pass.
var testData = {
pageName: 'SeleniumTest',
button: By.css('#test-button'),
blockMessage: By.css('div.blockUI.blockMsg')
}
console.log('Running test...')
// Preview the test page
await driver.get(baseUrl + '/ui/page/preview/' + testData.pageName);
// Wait for button
await driver.wait(until.elementLocated(testData.button), 10 * 1000);
// Verify button is present
expect(await driver.findElement(testData.button).isDisplayed()).toBe(true);
// Click button
await driver.findElement(testData.button).click();
// Wait for the blocked UI message to appear
await driver.wait(until.elementLocated(testData.blockMessage), 10 * 1000);
// Verify the text of the message, which should match the example page XML
expect(await driver.findElement(testData.blockMessage).getText()).toBe('The button renders and is clickable.');
});
// Specify a second test
it("Click the button, Verify Incorrect UI Block Message", async function() {
// Provide basic data used to evaluate the test.
// This test should fail.
var testData = {
pageName: 'SeleniumTest',
button: By.css('#test-button'),
blockMessage: By.css('div.blockUI.blockMsg')
}
// Preview the test page
await driver.get(baseUrl + '/ui/page/preview/' + testData.pageName);
// Wait for button
await driver.wait(until.elementLocated(testData.button), 10 * 1000);
// Verify button is present
expect(await driver.findElement(testData.button).isDisplayed()).toBe(true);
// Click button
await driver.findElement(testData.button).click();
// Wait for and Verify Correct UI Block Message
await driver.wait(until.elementLocated(testData.blockMessage), 10 * 1000);
// Verify the text of the message, which should *not* match the example page XML
expect(await driver.findElement(testData.blockMessage).getText()).toBe('These aren\'t the Droids you\'re looking for..');
});
});
|
With the code samples of the last two sections combined into one file
, you have a complete test suite for a basic Skuid page.
Now run these tests locally to see how they pass or fail.
Running tests locally¶
First, environment variables must be set. Hardcoding credentials within scripts is not recommended for security purposes. Enter these credentials within the terminal, or source them from a file, before running the test:
export SKUID_UN=username
export SKUID_PW=password
export SKUID_HOST=https://example.skuidsite.com
# Or if stored within a file
source /path/to/skuid-creds/.site-credentials
With those variables set, initiate the test.
In the command line:
Navigate to the directory containing the test file,
example-spec.js
.Initiate the test using the jasmine command:
jasmine example-spec.js
After running this command, you should see the following:
- A Chrome window appears, navigating to the login page of the Skuid site.
- Selenium enters the credentials set within the environment variables.
- Selenium then clicks the login button.
- Next, Selenium performs both tests:
- Click the button, Verify Correct UI Block Message
- Selenium opens the SeleniumTest page within the site.
- Selenium clicks the
#test-button
element. - Selenium reads the block UI message.
- Selenium determines that the message within that div does match the expected value.
- The test passes.
- Click the button, Verify Incorrect UI Block Message
- Selenium opens the SeleniumTest page within the site.
- Selenium clicks the
#test-button
element. - Selenium reads the block UI message.
- Selenium determines that the message within that div does not match the expected value.
- The test fails.
- Click the button, Verify Correct UI Block Message
The command line then reads out the results, noting which tests passed and which failed. And with that, you’ve successfully run a basic automated test!