dce-selenium
Version:
Selenium library to simplify testing and automatically snapshot the DOM.
791 lines (579 loc) • 22.4 kB
Markdown
# dce-selenium
Selenium library to simplify integration testing on multiple browsers.
## Table of Contents
- [Writing Tests](#writing-tests)
- [Running Tests](#running-tests)
- [Snapshots](#snapshots)
- [Environment Config](#environment-config)
- [Setting up a New Project](#setting-up-a-new-project)
- [Driver Functions:](#driver-functions)
- [Logging](#logging)
- [Navigation](#navigation)
- [Interactions](#interactions)
- [Data](#data)
- [Elements](#elements)
- [Session](#sessions)
- [Waiting](#waiting)
- [Custom Functions](#custom-functions)
- [Examples](#examples)
## Writing Tests
- Create a `test/selenium` folder for your selenium tests
- Import `dce-selenium` at the top of each test:
```js
require('dce-selenium');
...
```
- Use standard Mocha testing practices, but use `describeS` and `itS` instead of `describe` and `it`:
```js
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.
```js
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:
```js
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:
```bash
npm run selenium
```
To run your tests on specific browser(s), use:
```bash
# 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:
```bash
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`
<br>
<br>
<hr />
<br>
# 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):
```js
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:
>
> ```js
> // 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.
* `locationToWaitFor` – _optional:_ 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:
>
> ```js
> // Visit the /about page
> await driver.visit('/about');
> ```
> Example with redirect:
>
> ```js
> // Visit the /contact page which redirects to /email
> await driver.visit('/contact', '/email');
> ```
> Example with nondeterministic redirect:
>
> ```js
> // 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.
* `locationToWaitFor` – _optional:_ 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:
>
> ```js
> // 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:
>
> ```js
> // 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
* `locationToWaitFor` – _optional:_ 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // Get the current embedded metadata
> const metadata = await driver.getEmbeddedMetadata();
> ```
## Session
#### reset() – reset the browser session
> Example:
>
> ```js
> // Reset the user's session
> await driver.reset();
> ```
## Waiting
#### pause() – wait for the user to press enter in the terminal
> Example:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> // 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:
>
> ```js
> 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:
>
> ```js
> 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:
```js
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
```js
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');
});
});
```