Protractor and Dropdowns: validation

We use protractor for our end-to-end tests.  A variety of pages are suggesting that selection of dropdowns, and validation of dropdowns, is tricky.  And I think they’re right.

This post deals with a couple of code snippets that I’m using to deal with dropdown selection.

I’ve been having two problems with dropdowns.  The first is selecting them – I had been just doing “sendKeys” to the dropdown, but the behaviour of that is a bit unreliable.  The three main issues I’ve run into when using Chrome are:

  • that the dropdown menus jump to the front, so I can’t run e2e tests in the background
  • when sending text into the field, the field stops accepting once only one option matches.  The remainder of the text turns up in the next field on the page
  • verifying the text of the item that’s selected matches what I wanted to select.

I’ve written a handful of helper functions that do this for me.

Firstly, selecting an item.  The helper method below allows you to select an item if you know it’s position in the dropdown, I put this code in a common functions object that I include in all my tests so it’s formatted in pageObject format (for astrolabe):

  selectDropdownbyNum: { value: function ( element, optionNum ) {
    if (optionNum){
      var options = element.findElements(by.tagName('option'))   
        .then(function(options){
          options[optionNum].click();
        });
    }
  }}

If you wanted to just include this function directly in your code the it should look more like:

  var selectDropdownbyNum = function ( element, optionNum ) {
    if (optionNum){
      var options = element.findElements(by.tagName('option'))   
        .then(function(options){
          options[optionNum].click();
        });
    }
  };

I chose to do this by number because my app uses translations, so the text in the dropdown may not match what you are trying to set too. I also think it’s a bit fragile to build tests that rely on the exact text in a dropdown – if you change the name of one of your dropdown values the whole test will break.

Having said that, whilst I usually know the item number I want to select in my dropdown, I do sometimes want to validate that I hit the right one. When I do that, I use this code:

  expect(element(by.selectedOption('model_name')).getText()).toEqual('xxxxxx');

In newer versions of protractor, the selectedOption matcher is deprecated, you can instead use:

  expect(element(by.model('model_name')).$('option:checked').getText()).toEqual('xxxxxx');

Or if you’re using ids (as I normally do), then:

  expect(element(by.id('my_id')).$('option:checked').getText()).toEqual('xxxxxx');

You can also use regular expressions (i.e. .toMatch(/xxxx/);) if you don’t want to match the whole text.

Working in this way also avoids the Chrome dropdown hanging around issue, so now my tests run in the background successfully.

Advertisements

19 thoughts on “Protractor and Dropdowns: validation

  1. Good stuff. Ran into same issue on the select control today.
    Was using xpath to locate the item by text, and it turned but not reliable as well, lol.

  2. Hi, Paul!
    Could you please give an example of code how could you use your selectDropdownbyNum function (the latter of two)?

  3. Andrey,

    My logic assumes I know the position of the value in the dropdown. On the registration page, I require people to nominate a locale. The common function that does dropdown selection looks like this:

    selectDropdownbyNum: { value: function ( element, optionNum ) {
    if (optionNum){
    var options = element.findElements(by.tagName(‘option’))
    .then(function(options){
    options[optionNum].click();
    });
    }
    }},

    This then gets wrapped in another function, which I use to enter arbitrary data into the page. The subset of that function that deals with dropdowns looks like this:
    this.selectDropdownbyNum(element(by.id(page.entityName() + ‘.select_’ + key)), fieldValues[key].num);
    if(fieldValues[key].text) {
    if(page.selects()[key] == ‘system’){
    expect(element(by.id(page.entityName() + ‘.select_’ + key)).findElement(by.selectedOption(“$parent[$parent.objectName][fieldName]”)).getText()).toEqual(fieldValues[key].text);
    } else {
    expect(element(by.id(page.entityName() + ‘.select_’ + key)).findElement(by.selectedOption(“$parent[$parent.objectName][fieldName + ‘_id’]”)).getText()).toEqual(fieldValues[key].text);
    }
    }

    Basically I set the value in the dropdown using a number, then I check that the selected text matches what I expect it to be. Sort of convoluted, but works for what I want at the moment.

    Finally, when I call it, it looks like this:
    fieldHash = { ‘name’: “Test Tester”, ’email’: “test@test.com”, ‘password’: “Password”, ‘password_confirmation’: “Password”,
    ‘account_name’: “Test Account”, ‘locale’: { num: 1, text: “English / Australia” }};

    appPage.enterDataHash(this, fieldHash);

    The bit that selects the locale dropdown is the hash under locale. Hope that is sort of useful to you.

    Paul

  4. var selectDropdownbyNum = function ( element, optionNum ) {
    if (optionNum){
    var options = element.findElements(by.tagName(‘option’))
    .then(function(options){
    options[optionNum].click();
    });
    }
    };

    In tnis code, what is “element” parameter for you? To be exact, when you call this function, what you forward as parameter for element?

  5. Hi, I’m newbie with some JavaScript syntax and I would like to answer you something. I’m want to check you’re code for one test of mine and I don’t know what change for what.

    var selectDropdownbyNum = function ( element, optionNum ) {
    if (optionNum){
    var options = element.findElements(by.tagName(‘option’))
    .then(function(options){
    options[optionNum].click();
    });
    }
    };

    If I understood it well, the only thing I should change it’s label ‘option’, shouldn’t I?

  6. The function should probably work as is – you pass in the element that you want to select, and the number of the option you want to select.

    The tag ‘option’ should be present on every dropdown – if you inspect element (in Chrome, some other command in other browsers) then you can see that your dropdown has a list of values, each inside the tag ‘option’.

    It’s worth also looking at the select by text that was posted on stackoverflow, it’s only possible in later versions of protractor, but it’s much more intuitive than having to guess the option number. In my app I’m doing multilingual, so in some cases it’s better to select by number, but that’s probably not true for everyone.

  7. Hey Paul,

    I am new to automation and protractor, could you please tell me how can I call the method written by you.

    It seems I am passing some wrong parameters thats why could not get through.

    Here it is, how I am calling:

    expect(selectDropdownbyNum((ptor.findElement(protractor.By.id(‘efc-control-input-subType’))), 2)).toEqual(‘New’);

  8. Shobha,

    In my code I call it without the ptor. I’ve never explicitly defined a ptor in my code, and it’s never given me a problem, notwithstanding that every example I’ve ever seen has one. No idea why that is. So for me, it looks:

    selectDropdownbyNum( element( by.id( ‘efc-control-input-subType’ )), 2 );

    So this is just a method, rather than an expect. If you wanted to validate the option that was selected was as you wanted, you’d go with:

    expect( element( by.id( ‘efc-control-input-subType’ )).$(‘option:checked’).getText() ).toEqual( ‘new’ );

    I also have recently moved to the new example given in the stackoverflow question – the latest versions of protractor (anything newer than 0.22 I think) allow you to select an option by text instead of by number, which is much easier. That looks like:

    setSelect = function( dropdownId, selectValue ) {
    if( value ) {
    dropdown = element( by.id( dropdownId ));
    dropdown.element(by.cssContainingText(‘option’, selectValue)).click();
    }
    };

  9. Thank you Paul for your timely help, I was able to click on option in drop down using below:

    element(by.xpath(‘//*[@id=”xyz”]/option[‘+(2)+’]’)).click();

    expect(element(by.id(‘xyz’)).$(‘option:checked’).getText()).toEqual(‘abc’);

    I have another question:

    I want to verify that particular text field should accept only numbers and if any other input other than numbers is entered then it will through an error. Would be great help if you can help in this.

  10. You need to address based on the behaviour of your application when a non-numeric value is entered into the field. That is to say, you need to enter a non-numeric value into the field, then expect that an error occurs. The expectation depends on what should happen when a non-numeric value is entered – so you’ll either get an error message appear somewhere on the screen (and expect that), or you’ll just get the save fail.

  11. Paul, I wanted to do some regex expression matches, is there any way that we can match the entered value with regex expression in protractor ?

  12. Yes, I do this when I’m checking translations. All my translation keys start with an upper case code (e.g. LABEL_name), so when I check everything is translated I do it by verifying that the first few letters are not entirely upper case using a regex.

    The logic goes something like:
    expect( element(by.id( ‘mylabel’ )).getText()).toMatch(/^(?!LABEL).*$/);

    The bit between the / / is the regex. There is documentation at: https://github.com/pivotal/jasmine/wiki/Matchers or http://stackoverflow.com/questions/11185543/jasmine-regexp-using-tocontain-function

    The key when googling this stuff is probably to look for Jasmine rather than Protractor – Jasmine provides a lot of the expectations functionality.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s