5

In one of our tests, we need to make sure that the tab keyboard navigation inside a form is performed in the correct order.

Question: What is the conventional way to check the tab navigation order with protractor?


Currently we are solving it by repeating the following step for as many input fields existing in a form (code below):

  • check the ID of the currently focused element (using getId())
  • send TAB key to the currently focused element

Here is the example spec:

it("should navigate with tab correctly", function () {
    var regCodePage = new RegCodePage();
    browser.wait(protractor.ExpectedConditions.visibilityOf(regCodePage.title), 10000);

    // registration code field has focus by default
    expect(regCodePage.registrationCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());

    // focus moved to Remember Registration Code
    regCodePage.registrationCode.sendKeys(protractor.Key.TAB);
    expect(regCodePage.rememberRegistrationCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());

    // focus moved to Request Code
    regCodePage.rememberRegistrationCode.sendKeys(protractor.Key.TAB);
    expect(regCodePage.requestCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());

    // focus moved to Cancel
    regCodePage.requestCode.sendKeys(protractor.Key.TAB);
    expect(regCodePage.cancelButton.getId()).toEqual(browser.driver.switchTo().activeElement().getId());

    // focus moved back to the input
    regCodePage.cancelButton.sendKeys(protractor.Key.TAB);
    expect(regCodePage.registrationCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
});

where regCodePage is a Page Object:

var RegCodePage = function () {
    this.title = element(by.css("div.modal-header b.login-modal-title"));
    this.registrationCode = element(by.id("regCode"));

    this.rememberRegistrationCode = element(by.id("rememberRegCode"));
    this.requestCode = element(by.id("forgotCode"));

    this.errorMessage = element(by.css("div.auth-reg-code-block div#message"));

    this.sendRegCode = element(by.id("sendRegCode"));
    this.cancelButton = element(by.id("cancelButton"));
    this.closeButton = element(by.css("div.modal-header button.close"));
};

module.exports = RegCodePage;

It is working, but it is not really explicit and readable which makes it difficult to maintain. Also, another "smell" in the current approach is a code duplication.

If the current approach is how you would also do it, I would appreciate any insights about making it reusable.

Community
  • 1
  • 1
alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195

1 Answers1

6

I think the PageObject should define a tab order list, since that is really a direct property of the page, and should be expressible as simple data. An array of items seems like a sufficient representation, so something like:

this.tabOrder = [ this.registrationCode, this.rememberRegistrationCode, this.requestCode, this.cancelButton ];

Then you need a bit of generic code that can check a tab order.

function testTabOrder(tabOrder) {
    // Assumes TAB order hasn't been messed with and page is on default element
    tabOrder.forEach(function(el) {
       expect(el.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
       el.sendKeys(protractor.Key.TAB);
    });
}

Then your test would be something like:

it('has correct tab order', function() {
    var regCodePage = new RegCodePage();  // this should probably be in the beforeEach
    testTabOrder(regCodePage.tabOrder);
});

Of course, this assumes each element has a "getId()" method that works. (That seems like a reasonable assumption to me, but some environments may not support it.)

I think this keeps the tab-order nicely isolated on the PageObject (so its easy to keep in sync with the page content and doesn't get lost in the code that verifies the order). The testing code seem "optimistic" (I suspect the real world will introduce enough problems that you will end up expanding this code a bit).

I haven't tried any of this yet, so feel free to downvote if this doesn't work. :)

Also, I believe the forEach loop will work as-is, but I wouldn't be surprised if it needs some more explicit promise handling to make the dependencies explicit.

P.T.
  • 24,557
  • 7
  • 64
  • 95
  • Much-much cleaner and definitely reusable, thank you! I'll still need to try this out in practice - I'll edit the answer if it is not going to work as is. But, again, looks awesome! – alecxe Feb 28 '15 at 22:08
  • Working great as is. I've added testTabOrder to my helper test library and it looks very clean in the tests. We'll thank you a bit more this week :) – alecxe Mar 02 '15 at 04:12
  • With some changes to Protractor as of `2.1.0` (upgraded from `1.7.0`), one apparently has to use `browser.driver.switchTo().activeElement().then(...)`, aka the `activeElement` call is now Promise-based. – Dr1Ku Jun 11 '15 at 13:01