Cypress natively does not handle keyboard tests in a way that always serves our needs. Luckily, there is a library that is able to fix some of our issues called cypress-real-events. This allows us to implement a more standard response to keyboard-driven behavior in Cypress, which allows us to test both typing functionality and accessibility functionality related to using a keyboard for interactivity.

Keypress commands

cy.realPress(key)

This is a native function to the cypress-real-events library. Use this function to trigger a single key press in your test.

    cy.realPress('Tab');
CODE

This function can also accept an array with multiple keys together. So for example if you wanted to navigate backwards:

    cy.realPress(['Shift', 'Tab'])
CODE

cy.keys(array)

This is a custom function that will loop multiple cy.realPress commands. It takes an array of args that can either be single strings of one key, or arrays of multiple keys:

    cy.keys(['Tab', 'Enter']);
    
    cy.keys([['Shift', Tab], 'Enter']);
CODE

cy.repeatKey(key, multiple)

This custom function will allow us to repeat a keypress to avoid repetitive calls to cy.realPress or cy.keys:

    cy.repeatKey('Tab', 3);
    
    cy.repeatKey(['Shift', 'Tab'], 2);
CODE

Accessibility Keyboard Commands

cy.allyEvaluateSelectMenu(selectMenu, optionText, selectedOption)

This custom function checks multiple things for accessibility regarding the select menu:

  1. It verifies the select menu is visible and focused

  2. With the select focused, it uses cy.keys to type the string passed in optionText.

  3. It verifies that the selected option contains the text of what was passed into the function on selectedOption.

    cy.allyEvaluateSelectMenu('#militaryStatus', 'child', 'Child');
CODE

cy.allyEvaluateRadioButtons(selectorArray, arrowPressed, reversed = false)

This custom function verifies that once focused, a group of radio buttons can be iterated through with arrow keys. The arrowPressed argument allows you to choose which direction you want to travel, and the reversed option gives you the option to start at the end of your array of elements instead of the beginning:

    cy.allyEvaluateRadioButtons(
      [
        'input#root_onBehalfOf_0',
        'input#root_onBehalfOf_1',
        'input#root_onBehalfOf_2',
      ],
      'ArrowDown',
    );
    
    // Reversed
    cy.allyEvaluateRadioButtons(
      [
        'input#root_onBehalfOf_0',
        'input#root_onBehalfOf_1',
        'input#root_onBehalfOf_2',
      ],
      'ArrowUp',
      true,
    );
CODE

cy.allyEvaluateInput(input, inputText)

This custom function is designed to type a value into a focused input and then verify that the input holds that value appropriately.

    cy.allyEvaluateInput('#root_fullName_first', 'Benjamin');
CODE

cy.allyEvaluateCheckboxes(selectorArray)

This custom function takes a collection of checkboxes and attempts to check them all off by tabbing through them and hitting Space on each of them. It then verifies that they are checked off appropriately before moving to the next checkbox.

    cy.allyEvaluateCheckboxes(['input[type="checkbox"]']);
CODE

cy.allyEvaluateModalWindow(modalTrigger, modalElement, modalCloseElement, triggerKey = ‘Enter’)

This custom function takes a number of parameters that are defined as follows:

modalTrigger: This is the selector for what will be interacted with to activate the modal opening.

modalElement: This is the selector for the actual modal element

modalCloseElement: This is the button inside the modal that tells the modal to close

triggerKey: This is an optional parameter that allows you to set which key will be used to open and close the modal. The default is Enter.

This function goes through the keyboard-driven process of activating the modal opening, verifying that the modal opened and then closing it, verifying that the focus returns to the trigger to open it again on closing.

    cy.allyEvaluateModalWindow(
      'button[aria-label="Learn more about how the length of Montgomery GI Bill active-duty service affects your benefits"]',
      'div[role="alertdialog"]',
      'button[aria-label="Close this modal"]',
    );
CODE

cy.hasFocusableCount(selector, count)

This custom function takes a selected element and verifies how many focusable elements exist inside of it.

    cy.hasFocusableCount('div.usa-width-two-thirds form', 14);
CODE

cy.hasTabbableCount(selector, count)

This custom function takes a selected element and verifies how many tabbable elements exist inside of it.

    cy.hasTabbableCount('div.usa-width-two-thirds form', 11);
CODE

Form Testing Helpers

There are also additional form helper functions that utilize the realPress function described above. These allow tests that can navigate and fill out the form through real keyboard events.

timeoutDuration

This is the time delay between each keyboard action when using the following functions. This presents a more real-life scenario than doing each action instantly.

let timeoutDuration = 100;
CODE

cy.tabToElement(selelctor, forward)

This is a custom function that will loop multiple cy.realPress(tab) commands to navigate the users focus to the element that matches the selector passed in. It also takes a Boolean to tab forward or backward (default forward).

    cy.tabToElem('#next-page-button'); // tabs forward to button
    
    cy.tabToElem(#breadcrumb-option=1', false); // tabs backwards to a breadcrumb option
CODE

cy.chooseSelectOptionByTyping(value)

This is a custom function that selects an option from a focused select box. Also works with select options with an empty string for a value.

    cy.chooseSelectOptionByTyping('june'); // opens, navigates to, and selects the month of June
CODE

Note: Ideally, this would use the arrow keys to go through the options and select one but with the current tools using the arrow keys within a select does not work on Mac devices. This typing method is a workaround until that is fixed in the libraries used.

cy.chooseRadio(value)

This is a custom function that uses various button presses to select a radio option when a radio option is focused. It will navigate through the radio options with the same name and select the one with the matching value.

    cy.chooseRadio('hospital-3'); // navigates to, and selects hospital 3 of a radio group
CODE

cy.typeInFocused(value)

This is a custom function that will type in a focused text field or text area. Similar to the cy.type function but will have a consistent delay between keystrokes to match the delay between all of the other keyboard actions

    cy.typeInFocused('First Name Last Name'); // types each letter one at a time
CODE