Search code examples
jqueryangularjsblurend-to-end

Forcing 'blur' on input field in angular e2e test


I have ng-blur functionality that I am trying to test in my angularjs e2e tests. Basically, when a field is blurred, data in another field will (depending on the circumstance) change as well.

The following is my e2e test ([selector] and [selector2] are JQuery selectors for readability. The actual selectors work in my code is working; I know this because they pass in other e2e tests):

it("updates sister fields", function () {
    //try to cause a blur using an angular scenario thing
    element(<selector>).val("Tommy");
    applyFunction(<selector>, function(element) {
        element.trigger("blur");
    });

    //try again to cause a blur with query()
    element(<selector>).query(function(element, done) {
        element.blur();
        done();
    });

    //try a third time to blur
    element(<selector>).query(function(element, done) {
        var event = new CustomEven('blur');
        $element.dispatchEven(event);
        done();
    });

    //if the blur has triggered this other element should have expected text
    expect(element(<selector2>).val()).toEqual("expected text");
});

The applyFunction matcher is created in another file with the following code

/*global angular*/
(function () {
    "use strict";
    angular.scenario.dsl("applyFunction", function () {
        return function(selector, fn) {
            return this.addFutureAction('element ' + selector, function($window,    $document, done) {
                fn.call(this, $window.angular.element(selector));
                done();
            });
        };
    });
}());

These three ideas were found from this answer but have not worked. No errors are thrown in the first two but the expect fails because the data in the field never changes to the expected text. So I am pretty sure that the field is never being blurred. I have validated the functionality in the app over and over. It works, I just can't get the blur to trigger in the e2e tests. The third fails because it cannot find 'CustomEvent'.


Before working on these two options I had also tried these, but they all threw undefined errors, which is when I began searching.

element(<selector>).trigger("blur");
element(<selector>).triggerHandler("blur");
element(<selector>).blur();

EDIT 1: After much debugging I found that the blur was being triggered correctly (method 3 above, which caused the error, never worked, but the other two methods seemed fine). In reality, my ng-model was not actually being updated correctly, so the function in my ng-blur() was not firing in a manner that would update any other fields.

The issue I was experiencing was because I was accessing several form fields that were inside an ng-repeat. The repeat caused the ng-models to be named the same for similar fields so standard e2e testing

input([ng-model]).enter([val]) 

was not usable, since it would select all the fields with ng-model and update their fields. Instead I used

element().val()

syntax. This (in the scenario runner I am using, the old version) did not cause the ng-model to update. So, if you need to access a single field in an ng-repeat, use the 'using' call. Eg

using('.example-parent-class').input('ng-model').enter('text').

Solution

  • Thanks to Nobita for letting me know how to properly close out a question. Most of this is repeated from my first edit in the question.

    After much debugging I found that the blur was being triggered correctly (method 3 above, which caused the error, never worked, but the other two methods seemed fine). In reality, my ng-model was not actually being updated correctly, so the function in my ng-blur() was not firing in a manner that would update any other fields.

    The issue I was experiencing was because I was accessing several form fields that were inside an ng-repeat. The repeat caused the ng-models to be named the same for similar fields so standard e2e testing

    input([ng-model]).enter([val])
    

    was not usable, since it would select all the fields with ng-model and update their fields. Instead I used

    element().val()
    

    syntax. This (in the scenario runner I am using, the old version) did not cause the ng-model to update. So, if you need to access a single field in an ng-repeat, use the 'using' call. Eg

    using('.example-parent-class').input('ng-model').enter('text').
    

    So, I didn't elaborate on the 'using' much in my edit, so I'll try to lay out my new understanding here. The using call simply returns a lesser scope for a following selection. In my case I needed to limit the scope to a certain div inside an ng-repeat so I could access the correct input by its ng-model.