Scripted browser with chained select field options

I’m looking at writing a scripted browser test for a page on a website which uses a series of chained select menus in a form on the page.

The first select menu to begin with that has populated options on page load, when selecting an option from this select field, it then triggers the next select menu to be visible in the DOM and loads in the option data from a JSON endpoint, the same happens with the next chained field once this select field has been interacted with.

Because this data is dynamic, I am grabbing the select menu options and then selecting a random index position, rather than using SendKeys as the data is likely to change and therefore static values will likely fail at some point. This is working for the first select menu as it’s visible on page load, however for the next chained select, it is not grabbing the options, the element is visible, but it is as if the chained select options that get loaded are not in the DOM. I can see this on the screenshot.

I’ve wrote a function within my scripted browser test, as it will be repeated a few times for a total of three select fields, this is what I have so far:

function getSelect(element) {
  return $browser.waitForAndFindElement($driver.By.id(element), 10000)
  .then(function() {
    return $browser.findElements($driver.By.css('#' + element + ' > option'))
      .then(function(options) {
        var optionsCount = options.length;
        // Random index position, but never return 0
        var randomIndex = Math.floor(Math.random() * optionsCount) + 1;
        console.log(element + ' options available: ' + optionsCount);
        assert.ok(optionsCount > 1, 'Expected total options for ' + element + 'to be greater than 1.')
        console.log(element + ' option index position selected: ' + randomIndex);
        return options[randomIndex].click();
      });
  });
}

The index position, should be between 1 and the total options returned. If working correctly each select menu will have at least two options at all times. The first one will always be the default “Please select” value, the others actual values. This means I need to make sure the randomIndex value is never 0, as selecting this will not trigger the next select field.

The part that is currently failing is the second select field (first chained field) select option count is 1, which suggests my function hasn’t grabbed or can see the loaded options other than the default. The chained behaviour is working fine if I check the actual page now, but under a scripted browser it’s not seeing them. I’m wondering if my CSS selector is not actually querying the field at the right time to grab these?

I can see the JSON request in the waterfall when testing for the chained select, so it’s definitely executed the JSON request to grab the option data for the next select field.

Any pointers for improving my getSelect logic to make this work?

Might have found the issue. There are two issues currently:

The randomIndex logic is flawed because by always adding 1 every time you could actually go beyond the actual index values returned, which would then lead to an undefined error for the click(). Instead I’ve adjusted this to be a min and max range and always start at 1, so this will mean a valid index position is always provided now. e.g.

var randomIndex = Math.floor(Math.random() * (max - min) + min);

Max being the options.length value and min being always 1.

I believe the issue might be because the JSON request to get the option value data isn’t completing before the script tries to move to the chained select to select any option, adding in a $browser.wait(5000) before the script uses the CSS selector of #element > option seems to make it work more reliably when doing some validation tests. Occasionally, the script did work, but I think it was purely by chance as the execution of the JSON completed quick enough, but it isn’t able to do this consistently without forcing a delay.

$browser.sleep will essentially hard code a fixed delay all the time, it looks like $browser.waitForPendingRequests is a better option as it is specifically designed to wait for pending requests. The JSON data for getting the option data to be populated will be a request that the scripted browser will wait for, using this compared to adding a fixed delay. The advantage being if the request completes before the timeout, it will proceed, rather than waiting longer than it might need to.