0

I am trying to create a login function that I can use with mocha testing and selenium-webdriver for unit testing, since I have a bunch of things to do that start with "user logs in and..."

Below is my best shot, but when I run it, the console just logs

Already registered user logs in and sees homepage
  1) should work (it's red text)

Login an existing user

and then nothing happens and I have to control-C out of the process. My code was working before I went for this modular login implementation, so I'm sure that's where my problem is. Is there something I am missing here? I am also open to general critiques of how I went about making the login function, since I don't have that much experience with webdriver.

Below is my code. It's in one js file.

var test = require('selenium-webdriver/testing'),
chai = require('chai');
chai.use(require('chai-string'));
chai.use(require('chai-as-promised'));
var expect = chai.expect,
webdriver = require('selenium-webdriver'),
By = webdriver.By;

// I want this to be a modular login function I can use multiple places
function login(driver) {
    test.describe('Login an existing user', function() {
        test.it('should work', function() {
            this.timeout(0);

            var email = 'myemail@test.com';
            var password = 'password';

            driver.get('http://localhost:9000');

            driver.getTitle().then(function(title) {
                expect(title).to.equal('my title');
            })
            .then(function() {
                // do login stuff and click login button
            });
            .then(function() {
                return driver;
            });
        });
    });
}

//an example of when I would use the login function
test.describe('Already registered user logs in and sees homepage', function() {
  test.it('should work', function() {

    this.timeout(0);
    var driver = new webdriver.Builder().
    withCapabilities(webdriver.Capabilities.chrome()).
    build();

    driver = login(driver)
    .then(function() {
        driver.findElement(By.xpath("relevant xpath")).click();
    })
  })
})
swagrov
  • 1,510
  • 3
  • 22
  • 38

1 Answers1

1

By calling login from inside test.it, you are effectively calling describe inside it, which is not allowed. Mocha simply does not support calling describe or it from inside it. Unfortunately, it does not try to detect such occurrence and scream about it. It just goes ahead and do whatever it does. I all cases it results in undefined behavior, like the behavior you observed. In most cases, the undefined behavior is erratic. I very rare cases, it happens to match the developer's expectations but that's due to chance, not design.

In your case, I'd just remove the calls to test.describe and test.it from inside login, and make sure to return a promise. You can keep the expect there. You won't be able to call this.timeout(0) inside login, however. Something like this:

function login(driver) {
    var email = 'myemail@test.com';
    var password = 'password';

    driver.get('http://localhost:9000');

    return driver.getTitle().then(function(title) {
        expect(title).to.equal('my title');
    })
    .then(function() {
        // do login stuff and click login button
    })
    .then(function () {
        return driver;
    });
}

And modify your calling code to:

var promise = login(driver)
.then(function(driver) {
    driver.findElement(By.xpath("relevant xpath")).click();
});

You can do whatever you want to the promise, or nothing at all. However, driver is not itself a promise so setting driver to that value won't work.

Louis
  • 146,715
  • 28
  • 274
  • 320
  • Thanks for clarifying the `describe` behavior! If I have `driver=login(driver)` it works fine. But it I add `driver.then(etc)` after that, I get `TypeError: Cannot read property 'then' of undefined`. I thought webdrivers were promiseable? – swagrov Aug 02 '17 at 17:43
  • Looks like you deleted your earlier comment just as I was posting mine. Anyhow, I've edited with an example. You should not get the error you report if you actually return a value from your function. See the `return` I added before `driver.getTitle()...`. – Louis Aug 02 '17 at 17:44
  • Also `this.timeout` is not a function. If you remove describe, then what is `this`? – swagrov Aug 02 '17 at 17:44
  • True, it is not longer possible to call `this.timeout` from inside `login`. You could do `login.call(this, driver)` but it not particularly meaningful to set timeout to 0 and then set it to something else later. If you want to have a special timeout only for `login` you could call `login` from a `beforeEach` or `before` hook. – Louis Aug 02 '17 at 17:50
  • Adding that extra `return` fixed the then issue, but then after using the function with `driver=login(driver).then(function() {driver.findElement...` I have `driver.findElement` is not a function! How can I maintain the driver object? – swagrov Aug 02 '17 at 17:57
  • I've edited again. `driver` is not a promise so setting it to a promise value won't work. You should review the documentation of Selenium. – Louis Aug 02 '17 at 18:02
  • Alright thank you, I get it now. Also I finally found a related question, in case anyone is interested https://stackoverflow.com/questions/32565577/how-to-return-a-promise-in-a-function-from-the-last-promise-in-a-chain-of-then – swagrov Aug 02 '17 at 18:10