dce-selenium

Selenium library to simplify testing and automatically snapshot the DOM.

Usage no npm install needed!

<script type="module">
  import dceSelenium from 'https://cdn.skypack.dev/dce-selenium';
</script>

README

dce-selenium

Selenium library to simplify integration testing on multiple browsers.

Table of Contents

Writing Tests

  • Create a test/selenium folder for your selenium tests

  • Import dce-selenium at the top of each test:

    require('dce-selenium');
    
    ...
    
  • Use standard Mocha testing practices, but use describeS and itS instead of describe and it:

      describeS('My App', function () {
        itS('Opens', async function (driver) {
          await driver.visit('https://www.website.com/');
          ...
        });
      });
    
  • To add a timeout to a test, call itS like this: itS(title, timeout, testFunction). Default timeout is 45s.

      describeS('My App', function () {  
        // Test with 5s timeout:
        itS('Logs in', 5000, async function (driver) {
          await driver.visit('https://www.website.com/login');
          ...
        });
      });
    
  • To add code that runs before or after each test, add arguments to the describe handler:

      describeS('My App', function (beforeEachS, afterEachS) {
        beforeEachS(async function (driver) {
          // Code to run before each test
        });
    
        afterEachS(async function (driver) {
          // Code to run after each test
        });
      });
    
  • See "Driver Functions" below for a list of actions the driver can perform

Running Tests

To run your tests on all non-headless browsers (Chrome, Firefox, and Safari if on Mac), use:

npm run selenium

To run your tests on specific browser(s), use:

# run on chrome
npm run selenium --chrome
# run on firefox
npm run selenium --firefox
# run on safari
npm run selenium --safari
# run on safari technology preview
npm run selenium --safari-stp
# run on headless chrome
npm run selenium --headless-chrome
# run on chrome then safari
npm run selenium --chrome --safari

To run your tests with less printing to the terminal, use the --silent parameter. Example:

npm run selenium --chrome --silent

Snapshots

When you run your tests, a test/snapshots folder will be created. Check it out!

Environment Config

Add config parameters to your test/selenium/config.json file to customize how dce-selenium works.

Name Description Default
defaultHost a default hostname to use when driver.visit is passed a path, not a full url none
dontUseHTTPS if true, when driver.visit is passed a path. not a full url, we will visit the host using HTTP instead of HTTPS false
noSnapshots if true, no snapshots are taken false

Setting up a New Project

To add dce-selenium to your NPM project, use our initializer:

npm init dce-selenium

Notes:

  • Your npm project must already be initialized with npm init or npm init caccl
  • This initializer automatically installs dependencies, creates a /test folder, and adds the npm run selenium script to package.json




Driver Functions

This is the list of built-in functions the driver can perform. Every function is asynchronous, so you must use the await command to wait for it to complete.

To call any driver function, just call driver.functionName (driver is a global variable):

await driver.visit('https://www.my-website.com');
await driver.click('#login-button');
await driver.typeInto('#username-field', 'divardo');
await driver.typeInto('#password-field', '142509');

Custom functions: to create your own functions, see the info at the end of this section.

Logging

log(object) - print to the log

Arguments:

  • object - a string or any object to print to the terminal

Note: use this function instead of console.log for better log formatting.

Example:

// Print the status
driver.log(`The current status is ${status}`);

Navigation

visit(location, [locationToWaitFor]) – visit a webpage and wait for it to load

Arguments:

  • location – either full url or just path. If location is a path, defaultHost and dontUseHTTPS are used to determine the host and protocol for the url.
  • locationToWaitForoptional: the location or list of locations to wait for before resolving. If the location we're visiting redirects the user elsewhere, we need to wait for that location instead. Put that final location as this locationToWaitFor. If there are more than one possible final locations, include an array. Defaults to same value as location.

Example:

// Visit the /about page
await driver.visit('/about');

Example with redirect:

// Visit the /contact page which redirects to /email
await driver.visit('/contact', '/email');

Example with nondeterministic redirect:

// Visit the /contact page which redirects to /email or /phone
await driver.visit('/contact', ['/email', '/phone']);

post(location, body, [finalLocation]) – visit a webpage and wait for it to load

Arguments:

  • location – either full url or just path. If location is a path, defaultHost and dontUseHTTPS are used to determine the host and protocol for the url.
  • body – the POST request body to send.
  • locationToWaitForoptional: the location or list of locations to wait for before resolving. If the location we're visiting redirects the user elsewhere, we need to wait for that location instead. Put that final location as this locationToWaitFor. If there are more than one possible final locations, include an array. Defaults to same value as location.

Example:

// Send a POST request to the login page
await driver.post('/login', {
  user: 'admin',
  password: 'Pa$w0rD',
});

Interactions

click(cssSelectorOrElem) – click an item on the page

Arguments:

  • cssSelectorOrElem – the css selector or the WebElement that identifies the item to click

Example:

// Click the support button
await driver.click('#support-button');

openAnchorInSameTab(cssSelectorOrElem, [locationToWaitFor]) - open an anchor tab in the current tab even if it was made to open in another tab

Arguments:

  • cssSelectorOrElem – the css selector or the WebElement that identifies the anchor tag
  • locationToWaitForoptional: the location or list of locations to wait for before resolving. If the location we're visiting redirects the user elsewhere, we need to wait for that location instead. Put that final location as this locationToWaitFor. If there are more than one possible final locations, include an array. Defaults to same value as location.

Example:

// Click the contact button but force it to open in the same tab
await driver.openAnchorInSameTab('#contact-button');

Example where button leads to /contact which redirects to /email:

// Click the contact button but force it to open in the same tab
await driver.openAnchorInSameTab('#contact-button', '/email');

Example where button leads to /contact which either redirects to /email or /phone:

// Click the contact button but force it to open in the same tab
await driver.openAnchorInSameTab('#contact-button', ['/email', '/phone']);

clickByContents(contents, [cssSelector]) – click an item (find using its contents)

Find an item containing contents in its body and click it.

Arguments:

  • contents – the text contents of the item to click (may be a substring)
  • cssSelector – the css selector that further limits the items in question

Example:

// The log in button doesn't have any identifying characteristics
//   other than its text, so click it by contents
await driver.clickByContents('Log In');

typeInto(cssSelectorOrElem, text) – type in an html element

Arguments:

  • cssSelectorOrElem – the css selector or the WebElement that identifies the item to type into
  • text – the text to type

Example:

// Log in
await driver.typeInto('#username', 'admin');
await driver.typeInto('#password', 'Pa$w0rD');
await driver.clickByContents('Log In');

Note: to type a backspace, use the driver.BACKSPACE character.

Example:

// Type query
await driver.typeInto('#search', 'dogs');
// Remove the "s"
await driver.typeInto('#search', driver.BACKSPACE);

simulateKeystrokesIntoDocument(text, [finishWithEnter]) – simulate keystroke into the page document

Arguments:

  • text – the text to type
  • finishWithEnter – if true, the enter key is pressed after the text is typed

Example:

// Simulate typing onto page itself then press enter
await driver.simulateKeystrokesIntoDocument('test', true);

scrollTo(cssSelectorOrElem) – scroll to an element

Arguments:

  • cssSelectorOrElem – the css selector or the WebElement that identifies the item to scroll to

Example:

// Scroll down to the footer
await driver.scrollTo('#footer');

chooseSelectItem(cssSelectorOrElem, item) – select an item from a select-based dropdown based on its human-visible text

Arguments:

  • cssSelectorOrElem – the css selector or the WebElement that identifies the select item to interact with
  • item – the human-readable text of the option to choose

Example:

// Choose 'MasterCard Debit'
await driver.chooseSelectValue('#card-types', 'MasterCard Debit');

chooseSelectValue(cssSelectorOrElem, value) – select an item from a select-based dropdown based on the item's value attribute

Arguments:

  • cssSelectorOrElem – the css selector or the WebElement that identifies the select item to interact with
  • value – the value attribute of the option to select

Example:

// Choose 'MasterCard Debit' by its select value ('mc-debit')
await driver.chooseSelectValue('#card-types', 'mc-debit');

chooseFile(cssSelectorOrElem, filePath) – choose a file for a file input field

Arguments:

  • cssSelectorOrElem – the css selector or the WebElement that identifies the file chooser element
  • filePath – the path of the file with respect to the project directory (the directory from which you started the selenium tests)

Example:

const path = require('path');
...
// Upload new profile image
await driver.chooseFile('#file-upload', '/test/resources/images/profile.png');
await driver.click('#upload');

Data

getTitle() – gets the title of the page

Returns the title of the current page.

Example:

// Make sure the title of the page is "Divardo's Profile"
assert.equal(await driver.getTitle(), 'Divard\'s Profile', 'Page title is wrong');

getSource() – gets the source of the page

Returns the source of the current page.

Example:

// Make sure the source of the page includes a jQuery import
const body = await driver.getSource();
assert(body.includes('src="https://code.jquery.com'), 'No jQuery embed');

getJSON() – gets the JSON of the page

Returns the JSON object of the current page (only valid if the current url points to a JSON object).

Example:

// Visit the server's user profile endpoint that sends a json response
await driver.visit('/api/user/profile');
const profile = await driver.getJSON();
// Log the user's name
driver.log(`The current user's name is: ${profile.name}`);

getBody() - gets the html body of the page

Return the html body of the page.

Example:

// Make sure the body of the page includes at least 3 "@" characters
const body = await driver.getBody();
const numAtSymbols = (body.split('@').length - 1);
assert(numAtSymbols >= 3, `Not enough "@" symbols: only found ${numAtSymbols}.`);

getURL() - gets the url of the page

Return the url of the page.

Example:

// Check that the url ends with '/about'
const url = await driver.getURL();
assert(url.endsWith('/about'), 'URL does not end with /about');

getQuery() - gets the query parameters of the page

Return the query parameters of the page in the form { name: value }.

Example:

// Get the 'email' and 'name' query parameter
const queryParams = await driver.getQuery();
const { name, email } = queryParams;

Elements

elementExists(cssSelector) - checks if an element exists

Returns true if the element exists.

Arguments:

  • cssSelector – the css selector identifying the item

Example:

// Make sure there is a search box
assert(await driver.elementExists('#search-box'), 'No search box');

elementAbsent(cssSelector) - checks if an element does not exist

Returns true if the element does not exist.

Arguments:

  • cssSelector – the css selector identifying the item

Example:

// Make sure there is no warning messgae
assert(await driver.elementAbsent('#warning-message'), 'Warning message found');

elementWithContentsExists(contents, [cssSelector]) – check if an element with specific contents exists

Return true if the element exists.

Arguments:

  • contents – the text contents of the element we're looking for
  • cssSelector – the css selector that further limits the search

Example:

// Click the submit button if it's there
// The submit button has no identifying characteristics other than
//   its contents, so we need to identify using contents
const submitVisible = await driver.elementWithContentsExists('Submit');
if (submitVisible) {
  await driver.clickByContents('Submit');
}

elementWithContentsAbsent(contents, [cssSelector]) – check if an element with specific contents does not exist

Returns true if the element does not exist.

Arguments:

  • contents – the text contents of the element we're looking for
  • cssSelector – the css selector that further limits the search

Example:

// Expand the description if it isn't already visible
const needToExpand = elementWithContentsAbsent('This is part of the body of the description');
if (needToExpand) {
  await driver.click('#expand-description');
}

getElement(cssSelector) - find an element by css selector

Arguments:

  • cssSelector - the css selector

Example:

// Get a table with the id "cities"
const searchButtonElement = await driver.getElement('table#cities');

getElements(cssSelector) - find all elements that match a css selector

Arguments:

  • cssSelector - the css selector

Example:

// Make sure there are 3 images on the page
const images = await driver.getElements('img');
assert.equal(
  images.length,
  3,
  'The wrong number of images were found in the page'
);

getElementByContents(contents, [cssSelector]) – find an element by its contents

Arguments:

  • contents – the text contents of the element we're looking for
  • cssSelector – the css selector that further limits the search

Example:

// The search button doesn't have any identifying characteristics,
//   so let's find it by its contents
const searchButtonElement = await driver.getElementByContents('Search');

parentOf(cssSelectorOrElem) – given a selector or an element, finds the parent of the element

Arguments:

  • cssSelectorOrElem – the css selector or the WebElement that identifies the child element

Example:

// Each user's profile has a picture and name inside it:
// |"""""""| |"""""""| |"""""""| |"""""""| |"""""""|
// |  \O/  | |  \O/  | |  \O/  | |  \O/  | |  \O/  |
// | Sarah | | Jenny | | David | | Caleb | | Alexi |
//  """""""   """""""   """""""   """""""   """""""
// To click Sarah's profile, find her name,
//   get the parent (the profile) and click it
const nameElement = await driver.getElementByContents('Sarah');
const profileElement = await driver.parentOf(nameElement);
await driver.click(profileElement);

descendantOf(element, cssSelector) - given an element and a cssSelector, finds the first descendant of the element that matches the css selector

Arguments:

  • element - the parent element
  • cssSelector - the css selector that identifies the desired descendant

Example:

// Find a button within a specific div
const elem = await driver.getElement('div#main');
const button = await driver.descendantOf(elem, 'button');

listSelectItems(cssSelectorOrElem) - lists the human-readable text for the items in a select-based dropdown

Arguments:

  • cssSelectorOrElem – the css selector or the WebElement that identifies the select element

Example:

// List the human-readable options for supported credit card types
const cards = await listSelectItems('#card-types');
> ['MasterCard Credit', 'MasterCard Debit', 'Visa', 'Discover']

listSelectValues(cssSelectorOrElem) - lists the values for the items in a select-based dropdown

Arguments:

  • cssSelectorOrElem – the css selector or the WebElement that identifies the select element

Example:

// Find the types of supported credit cards
const cards = await listSelectValues('#card-types');
> ['mc-credit', 'mc-debit', 'visa', 'discover']

getElementInnerHTML(cssSelectorOrElement) - gets the innerHTML of an element

Arguments:

  • cssSelectorOrElem – the css selector or the WebElement that identifies the item to get contents of

Example:

// Get the body of the message on the page
const html = await getElementInnerHTML('#message');

getEmbeddedMetadata() - Searches the page for a #embedded-metadata element and parses the stringified json content inside it

Example:

// Get the current embedded metadata
const metadata = await driver.getEmbeddedMetadata();

Session

reset() – reset the browser session

Example:

// Reset the user's session
await driver.reset();

Waiting

pause() – wait for the user to press enter in the terminal

Example:

// Wait until user hits enter
await driver.pause();

wait(ms) – wait for a specific number of milliseconds

Arguments:

  • ms – number of milliseconds to wait

Example:

// Wait 2 seconds
await driver.wait(2000);

waitForElementVisible(cssSelector, timeout) – wait for an element to be visible

Arguments:

  • cssSelector – the css selector of the element to wait for
  • [timeout=10000] – number of ms to wait before timing out

Example:

// Wait for the title bar to be visible
await driver.waitForElementVisible('#title-bar');

waitForElementWithContentsVisible([see below]) – wait for an element with contents to be visible

Either use separated arguments:

  • contents – the text contents to search for
  • [cssSelector] – an optional css selector to further limit the search
  • [timeout=10000] – the timeout of the wait in ms

Example:

await waitForElementWithContentsVisible('About Me', 'h3');

...or use a single-argument options object:

  • options.contents – the text contents to search for
  • [options.cssSelector] – an optional css selector to further limit the search
  • [options.timeout=10000] – the timeout of the wait in ms

Example:

await waitForElementWithContentsVisible({
  contents: 'About Me',
  timeout: 7000,
});

waitForLocation(location) – wait for a location to load

Note: location waiting happens automatically after visit(...). There is no need to call this function right after calling visit(...).

Arguments:

  • location – either full url or just path. If location is a path, defaultHost and dontUseHTTPS are used to determine the host and protocol for the url. The query part of the path is not included in the match. If you want to check the query, use getQuery() (see above)

waitForLocations(locations) – wait for any location in a list to load

Note: location waiting happens automatically after visit(...). There is no need to call this function right after calling visit(...).

Arguments:

  • locations – either an array of or a single url/path to wait for. If a location is a path, defaultHost and dontUseHTTPS are used to determine the host and protocol for the url. The query part of the path is not included in the match. If you want to check the query, use getQuery() (see above)

Custom Functions

To create your own functions, add a file test/selenium/commands.js and export your custom commands:

module.exports = {
  async visitGoogle() {
    await this.visit('https://www.google.com');
  },

  async searchYoutube(query) {
    await this.visit(`https://www.youtube.com/results?search_query=${query}`);
  },
};

The context of the function is the driver. Thus, this.visit calls driver.visit. To directly access the Selenium webdriver, use this.webdriver.

Examples

Visit google and type into the search box

require('dce-selenium');
const assert = require('assert');

describeS('Google', function () {
    itS('Displays Search Results', async function (driver) {
        // Load Google
        await driver.visit('https://www.google.com');

        // Type a query
        await driver.typeInto('.gLFyf', 'Puppy');

        // Click the "Search" button
        await driver.clickByContents('Google Search', 'input');

        // Wait for results to be visible
        await driver.waitForElementVisible('#resultStats');

        // Check for the usual top result
        const wikiExists = await driver.elementWithContentsExists('Puppy - Wikipedia');
        assert(wikiExists, 'Could not find expected result: Wikipedia');
    });
});