Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing tab navigation order

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.

like image 365
alecxe Avatar asked Feb 27 '15 23:02

alecxe


People also ask

What should tab order?

The default keyboard navigation order must be logical and intuitive. The tab order should follow the visual flow of the page: left to right, top to bottom – header first, then main navigation, then page navigation (if present), and finally the footer.

How is tab order determined?

Control tab order is determined by the order in which controls are drawn on the screen. The first control that you draw is assigned a tab order of 1 , the next is given tab order number 2 , and so on.

How is the tab order of a control set?

The tab order is the order in which a user moves focus from one control to another by pressing the Tab key. Each form has its own tab order. By default, the tab order is the same as the order in which you created the controls.


1 Answers

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.

like image 159
P.T. Avatar answered Sep 22 '22 21:09

P.T.