Handling JavaScript alerts when leaving a page with WebDriver

You’ve most probably seen the sometimes-useful-but-often-annoying browser alerts when navigating away from a page:JavaScript onbeforeunload alert

How do we deal with these using WebDriver?

Dismissing these as expected alerts

I set up a sample page that shows such an alert when navigating away, and we can test this behaviour fairly easily:

test.it( 'can check for an alert when leaving the page', function() {
  let page = new WebDriverJsLeavePage( driver, true );
  page.navHome();
} );

And our page object looks like this:

import { By as by } from 'selenium-webdriver';
import config from 'config';
import BasePage from './base-page.js';

export default class WebDriverJsLeavePage extends BasePage {
	constructor( driver, visit = false ) {
		super( driver, by.css( '#leavepage' ), visit, `${config.get( 'demoURL' )}/leave` );
	}
	navHome() {
		this.driver.findElement( by.css( '#homelink' ) ).click();
		return this.driver.switchTo().alert().then( function( alert ) {
			return alert.accept();
		} );
	}
}

which is all well and good when everything works.

Dismissing unexpected alerts

But what happens if our test fails before successfully navigating away by accepting the alert?

import assert from 'assert';
import webdriver from 'selenium-webdriver';
import test from 'selenium-webdriver/testing';
import config from 'config';
import WebDriverJsDemoPage from '../lib/webdriver-js-demo-page.js';
import WebDriverJsErrorPage from '../lib/webdriver-js-error-page.js';
import WebDriverJsLeavePage from '../lib/webdriver-js-leave-page.js';

let driver = null;

const mochaTimeoutMS = config.get( 'mochaTimeoutMS' );

test.before( function() {
	this.timeout( mochaTimeoutMS );
	let pref = new webdriver.logging.Preferences();
	pref.setLevel( 'browser', webdriver.logging.Level.SEVERE );
	driver = new webdriver.Builder().forBrowser( 'chrome' ).setLoggingPrefs( pref ).build();
} );

test.describe( 'WebDriverJsDemo', function() {
	this.timeout( mochaTimeoutMS );

	test.it( 'can wait for an element to appear', function() {
		let page = new WebDriverJsDemoPage( driver, true );
		page.waitForChildElementToAppear();
		page.childElementPresent().then( function( present ) {
			assert.equal( present, true, 'The child element is not present' );
		} );
	} );

	test.it( 'can check for an alert when leaving the page', function() {
		let page = new WebDriverJsLeavePage( driver, true );
		assert( false, 'An unexpected error!' );
		page.navHome();
	} );

	test.it( 'can check for errors when there should be none', function() {
		let page = new WebDriverJsDemoPage( driver, true );
		page.consoleErrors().then( ( errors ) => {
			assert.deepEqual( errors, [] );
		} );
	} );

	test.it( 'can check for errors when there are present', function() {
		let page = new WebDriverJsErrorPage( driver, true );
		page.consoleErrors().then( ( errors ) => {
			assert.deepEqual( errors, [ 'http://webdriverjsdemo.github.io/scripts/error.js 1:1 Uncaught Purple Monkey Dishwasher Error' ] );
		} );
	} );
} );

test.afterEach( () => driver.manage().deleteAllCookies() );

test.after( () => driver.quit() );

Well, our test failed as expected, but the alert was never seen/dismissed during that test, and, since we’re using the same browser across different tests, all subsequent tests have now failed with an error UnexpectedAlertOpenError: unexpected alert open: {Alert text : Are you sure you want to leave?}.

That’s not great at all 😱

We can’t really check before running a test whether that alert will be shown as it’s only shown by the browser navigating somewhere else during the test, and we don’t want to check every time we do some form of navigation whether an alert appears since this will be in so many potential places (and we can forget to check).

What we need to do is ensure that when a test fails we need to check whether that test has left an upcoming alert, and get rid of it so our next test runs cleanly!

Lucky we’re using Mocha and we can use hooks.

Using Mocha hooks to check for unexpected alerts

We can expand our afterEach hook to on failure to attempt to get the alert to show and if it shows, dismiss it:

test.afterEach( function() {
	if ( this.currentTest.state === 'failed' ) {
		driver.get( 'data:,' );
		driver.switchTo().alert().then( ( alert ) => {
			alert.accept();
		}, () => {} );
	}
	driver.manage().deleteAllCookies()
} );

The way this works is it tells the browser to navigate to an empty page (data URI) and it tries to switch to an alert when doing so. You need to make sure there’s a second function passed to driver.switchTo().alert() which is the function that receives an error callback for when there isn’t an alert shown (and ignores it), otherwise you will see NoSuchAlertError errors.

As this works, we now only have a single failing test which dismisses its own alert in a mocha afterEach hook. No more UnexpectedAlertOpenError errors 😊

Do you apps have such alerts? How do you deal with them?

Author: Alister Scott

Alister is an Excellence Wrangler for Automattic.

2 thoughts on “Handling JavaScript alerts when leaving a page with WebDriver”

Comments are closed.