Reputation: 1515
I am using Selenium + Mocha (in Node.js) to do some UI testing on a site. After each test, I want to capture a screen shot so that I can review/share the visible results.
However, after the basic UI changes are made (i.e. insert text and hit submit), there is an AJAX call + JavaScript animation that triggers after that I would like to be part of my screen shot.
The screenshots I'm getting appear to be in taken after the next step has already started. (So, the first test runs > trigger the screenshot > second test starts > screenshot is actually captured)
I've tried .sleep()
and timeouts -- however my use could have been wrong.
function takeScreenshot(name='out'){
driver.takeScreenshot().then(function(data){
var base64Data = data.replace(/^data:image\/png;base64,/,"")
fs.writeFile(`screenshots/${name}.png`, base64Data, 'base64', function(err) {
if(err) console.log(err);
});
});
}
describe('UI Tests', async function() {
before(async function() {
driver = await new Builder().forBrowser('chrome').build();
await driver.get('http://localhost:8000').then( async function(){ await getAssets(); });
/* search | submit | reset are declared in getAssets() */
});
after(async function() {
driver.quit();
});
afterEach(async function(){
step++;
driver.sleep(3000).then(
takeScreenshot(step)
);
});
describe('User Types/Modifies Input and Submits Query', async function() {
it('type in search + click submit', async function(){
search.sendKeys("Puppies and Kittens");
submit.click();
});
it('click the X icon to clear search', async function(){
reset.click();
});
});
});
In the above snippet, the screenshot for the first test ('type in search + click submit'
) would show the input field already cleared from the second test.
Upvotes: 1
Views: 1495
Reputation: 74620
The commands being sent to selenium are asynchronous, meaning a function will schedule the work to run at some point in the future but return immediately so the code moves on.
In selenium-webdrivers case, the functions return a "promise" that will resolve at some point in the future. Mocha can use a promise to wait for the result.
Mocha uses the return value of a test to determine if it should wait for a promise to resolve. You can control promise flow with await
or .then
.
If Promise A+ code is being used, then you will always have return someFormOfPromise
in the test.
it(waits, function(){
return Promise.resolve(true)
.then(res => expect(res).to.be.true)
})
If async
is being used, that guarantees a promise is returned but you must await
anything asynchronous or the tests will continue on
it(waits, async function(){
let res = await Promise.resolve(true)
expect(res).to.be.true
})
Callbacks are similar, but they use a done
function to signal the tests to move on.
it(waits, function(done){
someTrueCallback((err, res) => {
if (err) return done(err)
expect(res).to.be.true
done()
})
})
There's a lot of mixed Promise A+ and async
in the posted code, there's even a callback thrown in. Pick one way and stick with it where possible, I'll use async
/await
.
Any asynchronous callbacks can be converted to promises, so starting with the fs.writeFile
callback function, turn it into a promise:
function writeFile(path, data, format){
return new Promise((resolve, reject)=>{
fs.writeFile(path, data, format, function(err){
if (err) return reject(err)
resolve(true)
})
})
}
Luckily this is pretty standard and Node.js has a helper for this in util.promisify
so the helpers can become:
const writeFile = util.promisify(fs.writeFile)
function async takeScreenshot(name='out'){
let data = await driver.takeScreenshot()
var base64Data = data.replace(/^data:image\/png;base64,/,"")
return await writeFile(`screenshots/${name}.png`, base64Data, 'base64')
}
There's no need to use .then
when await
is available.
Whenever a function returning a promise is called, you probably need an await
or let res = await...
if you want to use the resolved value.
describe('UI Tests', function() {
before(async function() {
driver = await new Builder().forBrowser('chrome').build();
await driver.get('http://localhost:8000')
await getAssets()
/* search | submit | reset are declared in getAssets() */
});
after(async function() {
await driver.quit();
});
afterEach(async function(){
step++;
await takeScreenshot(step)
});
describe('User Types/Modifies Input and Submits Query', function() {
it('type in search + click submit', async function(){
await search.sendKeys("Puppies and Kittens");
await submit.click();
});
it('click the X icon to clear search', async function(){
await reset.click();
});
});
});
As a side note these tests aren't actually checking anything. Normally an assertion library like chai would be used to ensure things are the way you expect.
Upvotes: 1