ES2015 Page Classes
As mentioned yesterday, I am updating my WebDriverJs and Mocha demo to use new ES2015 features.
ES2015 supports classes more elegantly than using prototype based ones in older versions of JavaScript.
For example, this old class:
var webdriver = require('selenium-webdriver'); var until = webdriver.until; var config = require('config'); RalphSaysPage = function RalphSaysPage(driver, visit) { this.driver = driver; this.url = config.get('url'); this.explicitWaitMS = config.get('explicitWaitMS'); this.quoteSelector = webdriver.By.id('quote'); if (visit === true) { this.driver.get(this.url); } this.driver.wait(until.elementLocated(this.quoteSelector), this.explicitWaitMS); }; RalphSaysPage.prototype.quoteContainerPresent = function() { var d = webdriver.promise.defer(); this.driver.isElementPresent(this.quoteSelector).then(function(present) { d.fulfill(present); }); return d.promise; }; RalphSaysPage.prototype.quoteTextDisplayed = function() { var d = webdriver.promise.defer(); this.driver.findElement(this.quoteSelector).getText().then(function(text) { d.fulfill(text); }); return d.promise; }; module.exports = RalphSaysPage;
can be better written as a ES2015 class:
import webdriver from 'selenium-webdriver'; import config from 'config'; export default class RalphSaysPage { constructor( driver, visit = false ) { this.driver = driver; this.url = config.get('ralphURL'); this.explicitWaitMS = config.get('explicitWaitMS'); this.quoteSelector = webdriver.By.id('quote'); if (visit) this.driver.get(this.url); this.driver.wait(webdriver.until.elementLocated(this.quoteSelector), this.explicitWaitMS); } quoteContainerPresent() { return this.driver.isElementPresent(this.quoteSelector); } quoteTextDisplayed() { return this.driver.findElement(this.quoteSelector).getText(); } }
You can see I have also simplified the functions quoteContainerPresent()
and quoteTextDisplayed()
to directly return the webDriver promise instead of creating our own which is unnecessary.
When I introduce another page class:
import webdriver from 'selenium-webdriver'; import config from 'config'; const by = webdriver.By; const until = webdriver.until; export default class WebDriverJsDemoPage { constructor( driver, visit = false ) { this.driver = driver; this.url = config.get('demoURL'); this.explicitWaitMS = config.get('explicitWaitMS'); this.expectedElementSelector = by.id('elementappearsparent'); if (visit) this.driver.get(this.url); this.driver.wait(webdriver.until.elementLocated(this.expectedElementSelector), this.explicitWaitMS); } waitForChildElementToAppear() { return this.driver.wait(until.elementLocated(by.id('elementappearschild')), this.explicitWaitMS, 'Could not locate the child element within the time specified'); } childElementPresent() { return this.driver.isElementPresent(by.id('elementappearschild')); } }
you can see I have duplicated some common functionality such as the navigation and page waiting across these two classes. This is where we can an ES2015 parent (or base) page class to inherit from.
Our base class might look something like:
export default class BasePage { constructor( driver, expectedElementSelector, visit = false, url = null ) { this.explicitWaitMS = config.get('explicitWaitMS'); this.driver = driver; this.expectedElementSelector = expectedElementSelector; this.url = url; if (visit) this.driver.get(this.url); this.driver.wait(webdriver.until.elementLocated(this.expectedElementSelector), this.explicitWaitMS); } }
which means our page classes are much nicer now:
export default class RalphSaysPage extends BasePage { constructor( driver, visit = false ) { const quoteSelector = webdriver.By.id('quote'); super(driver, quoteSelector, visit, config.get('ralphURL')); this.quoteSelector = quoteSelector; } quoteContainerPresent() { return this.driver.isElementPresent(this.quoteSelector); } quoteTextDisplayed() { return this.driver.findElement(this.quoteSelector).getText(); } } export default class WebDriverJsDemoPage extends BasePage { constructor( driver, visit = false ) { super(driver, by.id('elementappearsparent'), visit, config.get('demoURL')); } waitForChildElementToAppear() { return this.driver.wait(until.elementLocated(by.id('elementappearschild')), this.explicitWaitMS, 'Could not locate the child element within the time specified'); } childElementPresent() { return this.driver.isElementPresent(by.id('elementappearschild')); } }
This gives us the ability to add any common functionality across pages (such as checking the page title) quickly and easily without duplication.