Skip to content

Instantly share code, notes, and snippets.

@WestonThayer
Created October 13, 2020 20:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save WestonThayer/f08d9c1162e4a1a68e1cfe8f24ff9837 to your computer and use it in GitHub Desktop.
Save WestonThayer/f08d9c1162e4a1a68e1cfe8f24ff9837 to your computer and use it in GitHub Desktop.
// All tests visiting a page with this HTML:
//
// <p>content before</p>
//
// <ol id="target">
// <li>
// Cats
// <ol>
// <li>Big cat</li>
// <li>Small cat</li>
// </ol>
// </li>
// <li>
// Dogs
// </li>
// <li>
// Birds
// </li>
// </ol>
// <p>content after</p>
// Approach 1 - play 100% the end user
//
// Pros - very straightforward
// Cons - brittle, will break on simple speech output changes. Each AT would
// need its own test script. Focus on speech output excludes braille.
const nvda = startNvda();
// Start Firefox — this could easily be a utility call, just playing around here
keyboard.keys.Win.hit();
nvda.speech.waitForOutput("Windows search");
keyboard.type("firefox");
nvda.speech.waitForOutput("Firefox");
keyboard.keys.Enter.hit();
nvda.speech.waitForOutput("Focus on address bar, autocomplete");
keyboard.type("https://mytestsite.com");
keyboard.keys.Enter.hit();
nvda.speech.waitForOutput("Document loaded");
keyboard.keys.ArrowDown.hit();
nvda.speech.waitForOutput("content before");
keyboard.keys.ArrowDown.hit();
nvda.speech.waitForOutput("list, 3 items, 1. Cats");
// etc, keep arrowing down
nvda.speech.waitForOutput("out of list");
// Approach 2 - play the end user, but deal in OS accessibility tree nodes
//
// Pros - abstracts enough to easily sub in different ATs without changing code
// Cons - harder to understand, mental model needs to include understanding of
// OS a11y tree, really tough questions around AT "meta output", like
// when the AT is announcing the boundaries of an a11y tree node, instead
// of just describing the node itself
const sr = startScreenReader("nvda");
const tree = getOsA11yTree();
const browser = startApp("C:\\Program Files\\Firefox\\firefox.exe");
const browserNode = tree.findNode((node) => {
if (node.role === Roles.Window && node.name.contains("Firefox")) {
return true;
}
return false;
});
// Focus address bar
sr.focus(
browserNode.findNode(
(node) =>
node.role === Roles.EditText && node.name.contains("enter address")
)
);
keyboard.type("http://mytestsite.com");
keyboard.keys.Enter.hit();
const documentNode = browserNode.findNode(
(node) => node.role === Roles.Document && node.name === "My Test Page"
);
const startNode = documentNode.findNode(
(node) => node.role === Roles.Text && node.name === "content before"
);
sr.focus(startNode);
expect(sr.describeFocusedNode().name).toBe("content before");
// Navigate through the list
sr.navigateUntil(
documentNode.findNode((node) => node.name === "content after")
);
const output = sr.getOutputCollection();
// This is where it gets really tricky — to have the same lines of code work for
// NVDA, JAWS, VoiceOver, etc, we need some sort of abstraction for speech
// output that categorizes it. In this case, BOUNDARY_EXIT is a type of output.
// Other types could include NODE_DESC for announcing a node's role, name, and
// possible value.
//
// But it'll be a lot of work to wrangle all the ATs like this. I don't know how
// they tag output under the hood
expect(output[output.length - 1]).toBe({
type: "BOUNDARY_EXIT",
boundaryNodeRole: "list",
});
// Alternatively, could just hardcode expected output from every AT
expect(output[output.length - 1]).toBeIn([
"out of list",
"exiting list",
"end of list",
]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment