Protractor: functions overloading

I try to extend ElementFinder library. I wondering how I can require different methods with the same names? I want to make something like:

 // spec.js
 var ef1 = require('./ef_extend1.js');
 var ef2 = require('./ef_extend2.js');

 expect(column_resizer.ef1.getWidth()).toEqual(18);
 expect(column_resizer.ef2.getWidth()).toEqual(18);

Now I have an error:

TypeError: Cannot read property 'getWidth' of undefined

My required libraries:

// ef_extend1.js
var ElementFinder = $('').constructor;
ElementFinder.prototype.getWidth = function() {
    return this.getSize().then(function(size) {
                                  return size.width + 1;
                               });
};

And the second one:

// ef_extend2.js
var ElementFinder = $('').constructor;
ElementFinder.prototype.getWidth = function() {
    return this.getSize().then(function(size) {
                                  return size.width;
                               });
};

Upvotes: 4

Views: 636

Answers (1)

Michael Radionov
Michael Radionov

Reputation: 13309

I guess you've used a solution from Protractor issue #1102, but now it can be accomplished a bit easier after PR#1633, because ElementFinder is now exposed in protractor global variable:

protractor.ElementFinder.prototype.getWidth = function () {
    return this.getSize().then(function (size) {
        return size.width;
    });
};

expect($('body').getWidth()).toBe(100);

Update:

As I said in the comment, ElementFinder can only be extended again and again. If you already had a method getWidth, and you extend ElementFinder with one more getWidth implementation, then the first one will be overriden, there should not be any conflict. But you'll have to keep them in strict order depending on when do you want to use appropriate set of methods:

require('./ef_extend1.js');

expect(column_resizer.getWidth()).toEqual(18);

require('./ef_extend2.js');

expect(column_resizer.getWidth()).toEqual(18);

Actually I've came with some alternative approach, but I do not think it will be nice to use, but anyway. Here is a sample module with extension methods:

// ef_extend1.js

// shortcut
var EF = protractor.ElementFinder;

// holds methods you want to add to ElementFinder prototype
var extend = {
    getWidth: function () {
        return this.getSize().then(function (size) {
            return size.width;
        });
    }
};

// will hold original ElementFinder methods, if they'll get overriden
// to be able to restore them back
var original = {};

// inject desired methods to prototype and also save original methods
function register() {
    Object.keys(extend).forEach(function (name) {
        original[name] = EF.prototype[name]; // save original method
        EF.prototype[name] = extend[name]; // override
    });
}

// remove injected methods and return back original ones
// to keep ElementFinder prototype clean after each execution
function unregister() {
    Object.keys(original).forEach(function (name) {
        if (typeof original[name] === 'undefined') {
            // if there was not such a method in original object
            // then get rid of meaningless property
            delete EF.prototype[name];
        } else {
            // restore back original method
            EF.prototype[name] = original[name];
        }
    });
    original = {};
}

// pass a function, which will be executed with extended ElementFinder
function execute(callback) {
    register();

    callback();

    unregister();
}

module.exports = execute;

And you will use them like that, being able to run protractor commands in "isolated" environments, where each of them has it's own set of methods for ElementFinder:

var ef1 = require('./ef_extend1.js');
var ef2 = require('./ef_extend2.js');

ef1(function () {
    expect(column_resizer.getWidth()).toEqual(18);
});

ef2(function () {
    expect(column_resizer.getWidth()).toEqual(18);
});

I'm not quire sure about it, maybe I am over-engineering here and there are solutions much easier than that.

Upvotes: 5

Related Questions