DCE Selenium Library
https://github.com/harvard-edtech/dce-selenium
- Our selenium library meant to simplify writing selenium tests.
- Selenium library with wrappers for common driver functions.
- Runs with Chrome, Safari, and Firefox web drivers.
- Takes screenshots accessible in your testing folder after a test runs.
selenium-webdriver documentation
https://seleniumhq.github.io/selenium/docs/api/javascript/index.html
- If the functionality you need is not possible with the dce-selenium functions, you can also use the JavaScript selenium-webdriver.
- If you find you are needing to do something frequently using selenium-webdriver, you should contribute a wrapper function to dce-selenium!
CSS Selector reference https://www.w3schools.com/cssref/css_selectors.asp
Mocha testing uses describe
and it
dce-selenium is built on mocha and uses describeS
instead of describe
and itS
instead of it
describeS
is for grouping
itS
are test cases
describeS
can be nested...
describeS('galaxy', function() {
describeS('earth', function() {
describeS('cambridge', function() {
itS('birds should fly', function() {
...test code...
});
});
});
});
To call a driver function, call await driver.functionName().
All dce-selenium driver functions are asynchronous. Never call driver function without await ! |
---|
Visit a website:
await driver.visit('www.website.com');
Fill out a form:
await driver.typeInto('#name', 'Mallory');
Click on an element by css selector:
await driver.click('#button');
Or alternatively, click on an element by contents:
await driver.click('Submit');
Write code that identifies the indended elements consistently.
Many of dce-selenium's driver functions use CSS selectors to identify HTML elements.
Selector | Example | Description |
---|---|---|
element | 'button' | Selects all button elements. |
.class | '.results' | Elements with class results , table.results for table with class results. |
#id | 'button#name' | Selects <button id=name/> |
[attribute=value] | 'div[role="navigation"]' | Selects <div role="navigation"/> |
- Multiple selectors can be chained together in the format
element.class1.class2#id[key="value"]
- More syntax for CSS selectors: https://www.w3schools.com/cssref/css_selectors.asp
Driver functions:
// Returns a element object
await driver.getElement('button');
// Returns a bool
await driver.elementExists('button');
await driver.elementAbsent('button');
- If multiple elements match your criteria, the first one will be used so make sure your CSS selectors include enough information to uniquely identify your target element!
// Returns an object
await driver.getElementByContents('Submit');
await driver.getElementByContents('Submit', 'button');
// Returns a bool
await driver.elementWithContentsExists('Submit', 'button');
await driver.elementWithContentsAbsent('Submit', 'button');
Problem: You want to click the button in line with "Test Assignment 2" but the button itself has no unique identifiers
await driver.clickByContents('Submit');
will click the wrong button, it will click the first button element it finds which will be the "Test Assignment 1" button or another button on the same page
<div>
<div class="row>
<div class="col">
<h4 class="item-title">Test Assignment 1</h4>
</div>
<div class="col item-buttons">
<button>Submit</button>
</div>
</div>
<div class="row>
<div class="col">
<h4 class="item-title">Test Assignment 2</h4>
</div>
<div class="col item-buttons">
<button>Submit</button>
</div>
</div>
</div>
Solution:
// Find the h4 element with contents "Test Assignment 2"
const titleEl = await driver.getElementByContents("Test Assignment 2");
// Use parentOf to find the row div
const col = await driver.parentOf(titleEl);
const row = await driver.parentOf(col);
// Use descendantOf to find the button in this row
const button = await driver.descendantOf(row, 'button');
Clicking
await driver.click('button');
await driver.clickByContents('Submit', 'button');
Typing
await driver.typeInto('input#username', 'Mallory');
or
const usernameField = await driver.getElement('input#username');
await driver.typeInto(usernameField, driver.BACKSPACE);
Scrolling
await driver.scrollTo('div#footer');
or
const footer = await driver.getElement('div#footer');
await driver.scrollTo(footer);
This finicky and one of the biggest challenges when writing selenium tests!
Different browsers render elements in slightly different orders so you will find timing errors as you run your tests in different browsers.
Avoid the case where you receive an error that an element doesn't exist or is not interactable when in fact it just has not rendered yet.
- If a click causes a page transition...
- Can you check the URL? Use
await driver.waitForLocation(location);
- If the URL is difficult to check or the app is a single page app (React/CACCL!), you need to cleverly check if elements have rendered.
- Can you check the URL? Use
Many ways to wait:
// Not recommended to wait for arbitrary units of time
// But sometimes this is useful for debugging
await driver.wait([timeout]);
// Wait for specific elements to render
await driver.waitForElementVisible(cssSelector, [timeout]);
await driver.waitForElementWithContentsVisible(contents, [cssSelector], [timeout]);
// Wait for url to match
await driver.waitForLocation(location);
- Use
await driver.pause();
for debugging. Either at the end of your test or right before your error. Don't forget to remove it later. - Use
Date.now()
to generate a unique identifier for something you create during your test. This makes it easier to check if your test generated the item. - Try to get your test working in one browser first.
- Use the snapshots (automatically generated and placed in
test/selenium/snapshots/
). - Insert
.only
afterdescribeS
oritS
to test one test at a time. - Log
await driver.getElementInnerHTML(await driver.parentOf(element));
-
Update to the latest version with
npm i -g npm
. -
Create a folder for selenium training and cd into it.
-
Create a NPM project:
npm init
, use the defaults for everything (when prompted to enter a package name, etc) -
Install dce-selenium:
npm install dce-selenium --save-dev
-
Add dce-selenium to your NPM project:
npm init dce-selenium
-
You should now have a
test
folder, create the foldertest/selenium
and cd into it. -
Create a file
test/selenium/example.js
In example.js
Import dce-selenium require('dce-selenium');
describeS('Test google', function () {
itS('Visits google', async function (driver) {
await driver.visit('https://www.google.com');
});
});
npm run selenium
to run tests on Chrome, Firefox, and safari
Run your test on a specific browser
npm run selenium --chrome
npm run selenium --firefox
npm run selenium --safari
Run your test on headless Chrome
npm run selenium --headless-chrome
After you run your test, look in your test folder, you should see a new folder called "snapshots" with screenshots from while your test was executing.
-
Set a default host
Create a
config.json
{ "defaultHost": "www.google.com" }
-
Navigate to google
-
Search for something
-
Verify that you reached the results page for that search
-
Navigate to a link in the search results or another page in the search results and verify that your reach your intended destination
In your test/selenium
folder create a file called commands.js
.
Here you can write custom functions that can be used by multiple tests.
Inside commands.js
, instead of calling await driver.func()
use await this.func()
.
module.exports = {
async searchFor(term) {
await this.visit(...);
// your code here
},
async anotherCustomFunction() {
},
}
In example.js
describeS('Test google', function () {
itS('Navigates in google search results', async function (driver) {
await driver.searchFor('cats');
// more testing code
});
});
Shared course-admin-tools/test/selenium
folder?
Shared config config.json
and custom functions commands.js
?
Please suggest or contribute new functions! Driver functions are defined in classes/Driver.js
in the harvard-edtech/dce-selenium
repo.