This post covers the tests defined in https://github.com/britishlibrary/UniversalViewerTests
Recently, I have been writing browser tests for a Javascript application. The tests were invoked via Protractor running under Node.js locally on a Windows laptop. The Automate service from BrowserStack was used for cross-browser capability checking, and to be honest I can't recommend that service enough. It's *very* clever and straightforward.
The tests use Cucumber to enable BDD-style spec statements for features and scenarios. This differs from the usual uses of Protractor tests which generally use Jasmine - a library that represents test scenarios programmatically. A lack of documentation surrounding the use of Cucumber in Protractor has been a major headache during the project.
The tests as received originally used Selenium's webdriverjs to drive the browser session. Before the previous developer left, we converted many of the test features over to using a Page Object which offers an abstracted set of functions that allow access to elements on the page whilst hiding the details of how the elements are found; the concerns of the test functionality are therefore separated from the concerns of how to locate an element.
Further, since we are using Protractor to test a website that does not use AngularJS, the synchronisation that Protractor will do under the hood is not available. This means that communication with Selenium webdriver must be done using a Promise - a callback-like way of scheduling functionality to occur after a call into a sub-system with handling for success and failure of operations. Although ostensibly a means to cut-down on code, using Promises has the same effect as callbacks in that *everything* becomes a callback and you can end up very far across your screen for even minor functionality.
A Page Object doesn't have to be very smart. All it has to do is hide the implementation of locating elements for the tests that call it.
I generalised three methods - find, findAll and sleep:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
this.find = function(css) { | |
if(that.showdebug) { console.log('finding ' + css); } | |
return element(protractor.By.css(css)); | |
}; | |
this.findAll = function(css) { | |
if(that.showdebug) { console.log('finding all ' + css); } | |
return element.all(protractor.By.css(css)); | |
}; | |
this.sleep = function(ms) { | |
if(that.showdebug) { console.log('sleeping for ' + ms + 'ms'); } | |
return browser.sleep(ms); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
this.contentsPanelThumbnailIncreaseSizeButton = function() { | |
return this.find('.leftPanel .galleryView .btn.size-up'); | |
}; | |
this.contentsPanelIndexTabSubTrees = function() { | |
return this.findAll('.treeView .tree li ul'); | |
}; |
So far, so good. The tests work really well in Chrome and Firefox. Problems occur, as ever, when Internet Explorer gets involved. The general advice you should heed when testing with Internet Explorer is to NOT TOUCH ANYTHING while the test is running. IE is effectively an embedded component of Windows and it really doesn't let you forget it. Changing focus to another window could be enough for it to lose its place and invalidate your test run. Further, when the thing being tested uses an HTML IFrame, IE will lose its place after EVERY SINGLE TIME you try to find anything in the DOM. This led to a complete re-write of the browser tests in order to maintain cross-browser compatibility whilst also trying to be robust enough for IE's behaviour.
The test rig HTML IFrame we use for the viewer is the only one on the page. Even with that fact, the following code is necessary to reset which frame Internet Explorer is considering before it attempts to locate an element:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
this.resetFrame = function(callback) { | |
if(that.showdebug) { console.log('switching to viewer frame'); } | |
ptor.switchTo().defaultContent().then( | |
function() { | |
that.sleep(that.frameSwitchDelay).then( | |
function() { | |
if (that.showdebug) { console.log('switching to frame[0]'); } | |
ptor.switchTo().frame(0).then( | |
function() { | |
if (that.showdebug) { console.log('switched'); } | |
if(typeof(callback) == "function") { | |
callback(); | |
} | |
}); | |
}); | |
}); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var vp = new ViewerPage(); | |
this.When(/^they click in the Thumbnails tab$/, function (callback) { | |
if(showsteps) { console.log("When they click in the Thumbnails tab"); } | |
var that = this; | |
vp.resetFrame( | |
function() { | |
vp.contentsPanelExpandThumbnailsButton().then( | |
function(contentsPanelExpandThumbnailsButton) { | |
contentsPanelExpandThumbnailsButton.click().then( | |
callback, | |
function () { | |
callback.fail('clicking on thumbnails expand button did not work') | |
}); | |
}, function() { | |
callback.fail('expand thumbnails button not found'); | |
}); | |
}); | |
}); |
If I decide to do a refactoring job on this again, I would probably wrap the resetFrame call into the find/findAll functionality. This would mean passing the behaviour needed by the test into a callback function and passing it into the element locator function, frankly a bit like how the calls to resetFrame work now, but transparent to the feature files.
Okay, that's all for now. I wanted to record how to make these tests cross-browser compatible with the use of resetting the frame - this is just in case someone out there finds it useful in the future ... (HI, FUTURE ME!)
No comments:
Post a Comment