Javascript, Protractor, Tests, WebDriver

Using angular.element with WebDriver’s executeScript to perform actions which aren’t accessible from UI

A while ago i had the following problem:
I needed to write UI test of a canvas based component, which received array of objects, for the sake of the post let’s assume those were cars objects, and draw DOM elements on the canvas to represent those cars. It also had functionality to display car information in a popup when a user clicks on a car or a model in scope is updated (let’s call it vm.selectedCar).

I needed to test the information popup functionality.
The problem was that i didn’t have anything to work with, because:

  1. I didn’t have any element to select, because there were no DOM elements on the canvas.
  2. I couldn’t rely on mouse actions because elements position on the canvas was inconsistent by design.

So basically i couldn’t do anything on a UI level to trigger the appereance of the information popup.


As you may know, you can execute javascript by using WebDriver’s executeScript, so if we combine it with angular.element ability to retrieve the scope of the currently focused element, we can basically perform actions on the scope that we can’t do by interacting with the UI. Let’s see some code:

var openCarInfoTroughConsole = function(args) {
        var carsCanvasScope = angular.element($("#"+args.canvasId)).scope();
        carsCanvasScope.vm.selectedCar = args.carId;

var mockCar = {
        canvasId: 'canvasElmId',
        selectedCar: 'carId33'

    .executeScript(openCarInfoTroughConsole, mockCar)
    .then(performTests, handleError);

As you can see here, the first argument passed to executeScript, is a function that will be executed in the browser console scope (you can also pass a string).
The second param is arguments that we can pass from the test scope to the function scope when it’ll be executed, in this case we use those arguments to pass our car mock Id.

The openCarInfoTroughConsole itself using the angular.element($0).scope() / isolateScope() trick to get the scope of the selected element. But in this case, instead of using $0 we locate our element by id.

Important to note – depending on your angular version, debugInfoEnabled may be set to false. The app must run with debug info enabled, otherwise scope() or isolateScope() will be undefined. Since in most cases you’ll run tests in development enviroment, it shouldn’t to be a problem.

After we got the scope of our component, we are updating selectedCar model with the id of our mock car and then triggering $apply to notify angular that we changed the model. After this, the information popup will be displayed and you’ll be able to run tests on it. That’s it.