summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/wai-aria/scripts
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/wai-aria/scripts
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/wai-aria/scripts')
-rw-r--r--testing/web-platform/tests/wai-aria/scripts/ATTAcomm.js947
-rw-r--r--testing/web-platform/tests/wai-aria/scripts/aria-utils.js196
-rw-r--r--testing/web-platform/tests/wai-aria/scripts/manual.css70
3 files changed, 1213 insertions, 0 deletions
diff --git a/testing/web-platform/tests/wai-aria/scripts/ATTAcomm.js b/testing/web-platform/tests/wai-aria/scripts/ATTAcomm.js
new file mode 100644
index 0000000000..b748233fc2
--- /dev/null
+++ b/testing/web-platform/tests/wai-aria/scripts/ATTAcomm.js
@@ -0,0 +1,947 @@
+/* globals Promise, window, done, assert_true, on_event, promise_test */
+
+/**
+ * Creates an ATTAcomm object. If the parameters are supplied
+ * it sets up event listeners to send the test data to an ATTA if one
+ * is available. If the ATTA does not respond, it will assume the test
+ * is being done manually and the results are being entered in the
+ * parent test window.
+ *
+ * @constructor
+ * @param {object} params
+ * @param {string} [params.test] - object containing JSON test definition
+ * @param {string} [params.testFile] - URI of a file with JSON test definition
+ * @param {string} params.ATTAuri - URI to use to exercise the window
+ * @event DOMContentLoaded Calls go once DOM is fully loaded
+ * @returns {object} Reference to the new object
+ *
+ */
+
+function ATTAcomm(params) {
+ 'use strict';
+
+ this.Params = null; // parameters passed in
+ this.Promise = null; // master Promise that resolves when intialization is complete
+ this.Properties = null; // testharness_properties from the opening window
+ this.Tests = null; // test object being processed
+ this.testName = ""; // name of test being run
+ this.log = ""; // a buffer to capture log information for debugging
+ this.startReponse = {}; // startTest response will go in here for debugging
+
+ this.loading = true;
+
+ this.timeout = 5000;
+
+ var pending = [] ;
+
+ // set up in case DOM finishes loading early
+ pending.push(new Promise(function(resolve) {
+ on_event(document, "DOMContentLoaded", function() {
+ resolve(true);
+ }.bind(this));
+ }.bind(this)));
+
+ // if we are under runner, then there are props in the parent window
+ //
+ // if "output" is set in that, then pause at the end of running so the output
+ // can be analyzed. @@@TODO@@@
+ if (window && window.opener && window.opener.testharness_properties) {
+ this.Properties = window.opener.testharness_properties;
+ }
+
+ this.Params = params;
+
+ if (this.Params.hasOwnProperty("ATTAuri")) {
+ this.ATTAuri = this.Params.ATTAuri;
+ } else {
+ this.ATTAuri = "http://localhost:4119";
+ }
+
+ if (this.Params.hasOwnProperty("title")) {
+ this.testName = this.Params.title;
+ }
+
+ // start by loading the test (it might be inline, but
+ // loadTest deals with that
+ pending.push(this.loadTest(params)
+ .then(function(tests) {
+ // if the test is NOT an object, turn it into one
+ if (typeof tests === 'string') {
+ tests = JSON.parse(tests) ;
+ }
+
+ this.Tests = tests;
+
+ }.bind(this)));
+
+ this.Promise = new Promise(function(resolve, reject) {
+ // once the DOM and the test is loaded... set us up
+ Promise.all(pending)
+ .then(function() {
+ // Everything is loaded
+ this.loading = false ;
+ // run the automated tests (or setup for manual testing)
+ this.go();
+ resolve(this);
+ }.bind(this))
+ .catch(function(err) {
+ // loading the components failed somehow - report the errors and mark the test failed
+ test( function() {
+ assert_true(false, "Loading of test components failed: " +JSON.stringify(err)) ;
+ }, "Loading test components");
+ this.dumpLog();
+ done() ;
+ reject("Loading of test components failed: "+JSON.stringify(err));
+ return ;
+ }.bind(this));
+ }.bind(this));
+
+ return this;
+}
+
+ATTAcomm.prototype = {
+
+ /**
+ * go sets up the connection to the ATTA
+ *
+ * If that succeeds and the tests in this test file have methods for
+ * the API supported by the ATTA, then it automatically runs those tests.
+ *
+ * Otherwise it sets up for manualt testing.
+ */
+ go: function() {
+ 'use strict';
+ // everything is ready. Let's talk to the ATTA
+ this.startTest().then(function(res) {
+
+ // start was successful - iterate over steps
+ var API = res.body.API;
+
+ var subtestsForAPI = false;
+
+ // check main and potentially nested lists of tests for
+ // tests with this API. If any step is missing this API
+ // mapping, then we need to be manual
+ this.Tests.forEach(function(subtest) {
+ if (subtest.hasOwnProperty("test") &&
+ subtest.test.hasOwnProperty(API)) {
+ // there is at least one subtest for this API so
+ // this is a test that needs to be looked at by an atta
+ subtestsForAPI = true;
+ } else if (Array.isArray(subtest)) {
+ subtest.forEach(function(st) {
+ if (st.hasOwnProperty("test") &&
+ st.test.hasOwnProperty(API)) {
+ subtestsForAPI = true;
+ }
+ });
+ }
+ });
+
+ if (subtestsForAPI) {
+ this.runTests(API, this.Tests)
+ .then(function() {
+ // the tests all ran; close it out
+ this.endTest().then(function() {
+ this.dumpLog();
+ done();
+ }.bind(this));
+ }.bind(this))
+ .catch(function(err) {
+ this.endTest().then(function() {
+ this.dumpLog();
+ done();
+ }.bind(this));
+ }.bind(this));
+ } else {
+ // we don't know this API for this test
+ // but we ARE talking to an ATTA; skip this test
+ this.dumpLog();
+ if (window.opener && window.opener.completion_callback) {
+ window.opener.completion_callback([], { status: 3, message: "No steps for AT API " + API } );
+ } else {
+ done();
+ }
+ // this.setupManualTest("Unknown AT API: " + API);
+ }
+ }.bind(this))
+ .catch(function(res) {
+ // startTest failed so just sit and wait for a manual test to occur
+ if (res.timeout || res.status === 102) {
+ this.setupManualTest("No response from ATTA at " + this.ATTAuri);
+ } else if (res.status === 200 ) {
+ this.setupManualTest(res.message);
+ } else if (res.statusText === "No response from ATTA") {
+ this.setupManualTest("");
+ } else {
+ this.setupManualTest("Error from ATTA: " + res.status + ": " + res.statusText);
+ }
+ }.bind(this));
+ },
+
+ runTests: function(API, collection) {
+ // this method returns a promise
+
+ return new Promise(function(resolve, reject) {
+ // accumulate promises; complete when done
+ var pending = [];
+ var testCount = 0;
+
+ this.sendEvents(API, collection)
+ .then(function(eventStatus) {
+
+ /* Loop strategy...
+ *
+ * If the the step is a 'test' then push it into the pending queue as a promise
+ *
+ * If the step is anything else, then if there is anything in pending, wait on it
+ * Once it resolves, clear the queue and then execute the other step.
+ *
+ */
+ collection.forEach(function(subtest) {
+ // what "type" of step in the sequence is this?
+ var theType = "test" ;
+ if (Array.isArray(subtest)) {
+ // it is a group
+ Promise.all(pending).then(function() {
+ pending = [];
+ // recursively run the tests
+ pending.push(this.runTests(API, subtest));
+ }.bind(this));
+ } else if (subtest.hasOwnProperty("type")) {
+ theType = subtest.type;
+ }
+ testCount++;
+ if (theType === "test") {
+ // this is a set of assertions that should be evaluated
+ pending.push(this.runTest(testCount, API, subtest));
+ } else if (theType === "script") {
+ Promise.all(pending).then(function() {
+ pending = [];
+ // execute the script
+ this.runScript(testCount, subtest);
+ }.bind(this));
+ } else if (theType === "attribute") {
+ Promise.all(pending).then(function() {
+ pending = [];
+ // raise the event
+ this.handleAttribute(testCount, subtest);
+ }.bind(this));
+ // } else {
+ } else if (theType === "event") {
+ Promise.all(pending).then(function() {
+ pending = [];
+ // raise the event
+ this.raiseEvent(testCount, subtest);
+ }.bind(this));
+ // } else {
+ }
+ }.bind(this));
+
+ Promise.all(pending)
+ .then(function() {
+ // this collection all ran
+ if (eventStatus !== "NOEVENTS") {
+ // there were some events at the beginning
+ this.sendStopListen().then(function() {
+ resolve(true);
+ });
+ } else {
+ resolve(true);
+ }
+ }.bind(this));
+ }.bind(this));
+ }.bind(this));
+ },
+
+ setupManualTest: function(message) {
+ // if we determine the test should run manually, then expose all of the conditions that are
+ // in the TEST data structure so that a human can to the inspection and calculate the result
+ //
+ 'use strict';
+
+ var ref = document.getElementById("manualMode");
+ if (ref) {
+ // we have a manualMode block. Populate it
+ var content = "<h2>Manual Mode Enabled</h2><p>"+message+"</p>";
+ if (this.Tests.hasOwnProperty("description")) {
+ content += "<p>" + this.Tests.description + "</p>";
+ }
+ var theTable = "<table id='steps'><tr><th>Step</th><th>Type</th><th>Element ID</th><th>Assertions</th></tr>";
+ this.Tests.forEach(function(subtest) {
+ var type = "test";
+ if (subtest.hasOwnProperty("type")) {
+ type = subtest.type;
+ }
+ var id = "" ;
+ if (subtest.hasOwnProperty("element")) {
+ id = subtest.element;
+ }
+ theTable += "<tr><td class='step'>" + subtest.title +"</td>";
+ theTable += "<td class='type'>" + type + "</td>";
+ theTable += "<td class='element'>" + id +"</td>";
+
+ // now what do we put over here? depends on the type
+ if (type === "test") {
+ // it is a test; dump the assertions
+ theTable += "<td>" + this.buildAssertionTable(subtest.test) + "</td>";
+ } else if (type === "attribute" ) {
+ if (subtest.hasOwnProperty("attribute") && subtest.hasOwnProperty("value") && subtest.hasOwnProperty("element")) {
+ if (subtest.value === "none") {
+ theTable += "<td>Remove attribute <code>" + subtest.attribute + "</code> from the element with ID <code>" + subtest.element + "</code></td>";
+ } else {
+ theTable += "<td>Set attribute <code>" + subtest.attribute + "</code> on the element with ID <code>" + subtest.element + "</code> to the value <code>" + subtest.value + "</code></td>";
+ }
+ }
+ } else if (type === "event" ) {
+ // it is some events
+ if (subtest.hasOwnProperty("event") && subtest.hasOwnProperty("element")) {
+ theTable += "<td>Send event <code>" + subtest.event + "</code> to the element with ID <code>" + subtest.element + "</code></td>";
+ }
+ } else if (type === "script" ) {
+ // it is a script fragment
+ theTable += "<td>Script: " + subtest.script + "</td>";
+ } else {
+ theTable += "<td>Unknown type: " + type + "</td>";
+ }
+ theTable += "</tr>";
+
+
+ }.bind(this));
+
+ theTable += "</table>";
+ ref.innerHTML = content + theTable ;
+ }
+ },
+
+ buildAssertionTable: function(asserts) {
+ "use strict";
+ var output = "<table class='api'><tr><th>API Name</th><th colspan='4'>Assertions</th></tr>";
+ var APIs = [] ;
+ for (var k in asserts) {
+ if (asserts.hasOwnProperty(k)) {
+ APIs.push(k);
+ }
+ }
+
+ APIs.sort().forEach(function(theAPI) {
+ var rows = asserts[theAPI] ;
+ var height = rows.length;
+ output += "<tr><td rowspan='" + height + "' class='apiName'>"+theAPI+"</td>";
+ var lastRow = rows.length - 1;
+ rows.forEach(function(theRow, index) {
+ var span = 4 - theRow.length;
+ var colspan = span ? " colspan='"+span+"'" : "";
+ theRow.forEach(function(item) {
+ output += "<td" + colspan + ">" + item + "</td>";
+ });
+ output += "</tr>";
+ if (index < lastRow) {
+ output += "<tr>";
+ }
+ });
+ });
+
+ output += "</table>";
+ return output;
+ },
+
+ // eventList - find the events for an API
+ //
+ // @param {string} API
+ // @param {array} collection - a collection of tests
+ // @returns {array} list of event names
+
+ eventList: function(API, collection) {
+ var eventHash = {};
+
+ if (!API || API === "") {
+ return [];
+ }
+
+ collection.forEach(function(subtest) {
+ if (subtest.hasOwnProperty("test") &&
+ subtest.test.hasOwnProperty(API)) {
+ // this is a subtest for this API; look at the events
+ subtest.test[API].forEach(function(assert) {
+ // look for event names
+ if (assert[0] === "event" && assert[1] === "type" && assert[2] === "is") {
+ eventHash[assert[3]] = 1;
+ }
+ });
+ }
+ });
+
+ return Object.keys(eventHash);
+ },
+
+ // handleAttribute - set or clear an attribute
+ /**
+ * @param {integer} testNum - The subtest number
+ * @param {object} subtest - attribute information to set
+ */
+ handleAttribute: function(testNum, subtest) {
+ "use strict";
+ if (subtest) {
+ if (subtest.hasOwnProperty("attribute") && subtest.hasOwnProperty("element") && subtest.hasOwnProperty("value")) {
+ // update an attribute
+ try {
+ var node = document.getElementById(subtest.element);
+ if (node) {
+ if (subtest.value === "none") {
+ // remove this attribute
+ node.removeAttribute(subtest.attribute);
+ } else if (subtest.value === '""') {
+ node.setAttribute(subtest.attribute, "");
+ } else if (subtest.value.match(/^"/) ) {
+ var v = subtest.value;
+ v = v.replace(/^"/, '');
+ v = v.replace(/"$/, '');
+ node.setAttribute(subtest.attribute, v);
+ } else {
+ node.setAttribute(subtest.attribute, subtest.value);
+ }
+ }
+ }
+ catch (e) {
+ test(function() {
+ assert_true(false, "Subtest attribute failed to update: " +e);
+ }, "Attribute subtest " + testNum);
+ }
+ } else {
+ test(function() {
+ var err = "";
+ if (!subtest.hasOwnProperty("attribute")) {
+ err += "Attribute subtest has no attribute property; ";
+ } else if (!subtest.hasOwnProperty("value")) {
+ err += "Attribute subtest has no value property; ";
+ } else if (!subtest.hasOwnProperty("element")) {
+ err += "Attribute subtest has no element property; ";
+ }
+ assert_true(false, err);
+ }, "Attribute subtest " + testNum );
+ }
+ }
+ return;
+ },
+
+
+
+ // raiseEvent - throw an event at an item
+ /**
+ * @param {integer} testNum - The subtest number
+ * @param {object} subtest - event information to throw
+ */
+ raiseEvent: function(testNum, subtest) {
+ "use strict";
+ var evt;
+ if (subtest) {
+ var kp = function(target, key) {
+ evt = document.createEvent("KeyboardEvent");
+ evt.initKeyEvent ("keypress", true, true, window,
+ 0, 0, 0, 0, 0, "e".charCodeAt(0));
+ target.dispatchEvent(evt);
+ };
+ if (subtest.hasOwnProperty("event") && subtest.hasOwnProperty("element")) {
+ // throw an event
+ try {
+ var node = document.getElementById(subtest.element);
+ if (node) {
+ if (subtest.event === "focus") {
+ node.focus();
+ } else if (subtest.event === "select") {
+ node.click();
+ } else if (subtest.event.startsWith('key:')) {
+ var key = subtest.event.replace('key:', '');
+ evt = new KeyboardEvent("keypress", { "key": key});
+ node.dispatchEvent(evt);
+ } else {
+ evt = new Event(subtest.element);
+ node.dispatchEvent(evt);
+ }
+ }
+ }
+ catch (e) {
+ test(function() {
+ assert_true(false, "Subtest event failed to dispatch: " +e);
+ }, "Event subtest " + testNum);
+ }
+ } else {
+ test(function() {
+ var err = "";
+ if (!subtest.hasOwnProperty("event")) {
+ err += "Event subtest has no event property; ";
+ } else if (!subtest.hasOwnProperty("element")) {
+ err += "Event subtest has no element property; ";
+ }
+ assert_true(false, err);
+ }, "Event subtest " + testNum );
+ }
+ }
+ return;
+ },
+
+ // runScript - run a script in the context of the window
+ /**
+ * @param {integer} testNum - The subtest number
+ * @param {object} subtest - script and related information
+ */
+ runScript: function(testNum, subtest) {
+ "use strict";
+ if (subtest) {
+ if (subtest.hasOwnProperty("script") && typeof subtest.script === "string") {
+ try {
+ /* jshint evil:true */
+ eval(subtest.script);
+ }
+ catch (e) {
+ test(function() {
+ assert_true(false, "Subtest script " + subtest.script + " failed to evaluate: " +e);
+ }, "Event subtest " + testNum);
+ }
+ } else {
+ test(function() {
+ assert_true(false, "Event subtest has no script property");
+ }, "Event subtest " + testNum );
+ }
+ }
+ return;
+ },
+
+ // runTest - process subtest
+ /**
+ * @param {integer} testNum - The subtest number
+ * @param {string} API - name of the API being tested
+ * @param {object} subtest - a subtest to run; contains 'title', 'element', and
+ * 'test array'
+ * @returns {Promise} - a Promise that resolves when the test completes
+ */
+ runTest: function(testNum, API, subtest) {
+ 'use strict';
+
+ var data = {
+ "title" : subtest.title,
+ "id" : subtest.element,
+ "data": this.normalize(subtest.test[API])
+ };
+
+ return new Promise(function(resolve) {
+ var ANNO = this;
+ if (subtest.test[API]) {
+ // we actually have a test to run
+ promise_test(function() {
+ // force a resolve of the promise regardless
+ this.add_cleanup(function() { resolve(true); });
+ return ANNO.sendTest(data)
+ .then(function(res) {
+ if (typeof res.body === "object" && res.body.hasOwnProperty("status")) {
+ // we got some sort of response
+ if (res.body.status === "OK") {
+ // the test ran - yay!
+ var messages = "";
+ var thisResult = null;
+ var theLog = "";
+ var assertionCount = 0;
+ res.body.results.forEach( function (a) {
+ if (typeof a === "object") {
+ // we have a result for this assertion
+ // first, what is the assertion?
+ var aRef = data.data[assertionCount];
+ var assertionText = '"' + aRef.join(" ") +'"';
+
+ if (a.hasOwnProperty("log") && a.log !== null && a.log !== '' ) {
+ // there is log data - save it
+ theLog += "\n--- Assertion " + assertionCount + " ---";
+ theLog += "\nAssertion: " + assertionText + "\nLog data: "+a.log ;
+ }
+
+ // is there a message?
+ var theMessage = "";
+ if (a.hasOwnProperty("message")) {
+ theMessage = a.message;
+ }
+ if (!a.hasOwnProperty("result")) {
+ messages += "ATTA did not report a result " + theMessage + "; ";
+ } else if (a.result === "ERROR") {
+ messages += "ATTA reported ERROR with message: " + theMessage + "; ";
+ } else if (a.result === "FAIL") {
+ thisResult = false;
+ messages += assertionText + " failed " + theMessage + "; ";
+ } else if (a.result === "PASS" && thisResult === null) {
+ // if we got a pass and there was no other result thus far
+ // then we are passing
+ thisResult = true;
+ }
+ }
+ assertionCount++;
+ });
+ if (theLog !== "") {
+ ANNO.saveLog("runTest", theLog, subtest);
+ }
+ if (thisResult !== null) {
+ assert_true(thisResult, messages);
+ } else {
+ assert_true(false, "ERROR: No results reported from ATTA; " + messages);
+ }
+ } else if (res.body.status === "ERROR") {
+ assert_true(false, "ATTA returned ERROR with message: " + res.body.statusText);
+ } else {
+ assert_true(false, "ATTA returned unknown status " + res.body.status + " with message: " + res.body.statusText);
+ }
+ } else {
+ // the return wasn't an object!
+ assert_true(false, "ATTA failed to return a result object: returned: "+JSON.stringify(res));
+ }
+ });
+ }, subtest.name );
+ } else {
+ // there are no test steps for this API. fake a subtest result
+ promise_test(function() {
+ // force a resolve of the promise regardless
+ this.add_cleanup(function() { resolve(true); });
+ return new Promise(function(innerResolve) {
+ innerResolve(true);
+ })
+ .then(function(res) {
+ var theLog = "\nSUBTEST NOTRUN: No assertions for API " + API + "\n";
+ if (theLog !== "") {
+ ANNO.saveLog("runTest", theLog, subtest);
+ }
+ assert_false(true, "NOTRUN: No assertion for API " + API);
+ });
+ }, subtest.name );
+ }
+ }.bind(this));
+ },
+
+ // loadTest - load a test from an external JSON file
+ //
+ // returns a promise that resolves with the contents of the
+ // test
+
+ loadTest: function(params) {
+ 'use strict';
+
+ if (params.hasOwnProperty('stepFile')) {
+ // the test is referred to by a file name
+ return this._fetch("GET", params.stepFile);
+ } // else
+ return new Promise(function(resolve, reject) {
+ if (params.hasOwnProperty('steps')) {
+ resolve(params.steps);
+ } else {
+ reject("Must supply a 'steps' or 'stepFile' parameter");
+ }
+ });
+ },
+
+ /* dumpLog - put log information into the log div on the page if it exists
+ */
+
+ dumpLog: function() {
+ 'use strict';
+ if (this.log !== "") {
+ var ref = document.getElementById("ATTAmessages");
+ if (ref) {
+ // we have a manualMode block. Populate it
+ var content = "<h2>Logging information recorded</h2>";
+ if (this.startResponse && this.startResponse.hasOwnProperty("API")) {
+ content += "<h3>ATTA Information</h3>";
+ content += "<pre>"+JSON.stringify(this.startResponse, null, " ")+"</pre>";
+ }
+ content += "<textarea rows='50' style='width:100%'>"+this.log+"</textarea>";
+ ref.innerHTML = content ;
+ }
+ }
+ },
+
+ /* saveLog - capture logging information so that it can be displayed on the page after testing is complete
+ *
+ * @param {string} caller name
+ * @param {string} log message
+ * @param {object} subtest
+ */
+
+ saveLog: function(caller, message, subtest) {
+ 'use strict';
+
+ if (typeof message === "string" && message !== "") {
+ this.log += "============================================================\n";
+ this.log += "Message from " + caller + "\n";
+ if (subtest && typeof subtest === "object") {
+ var API = this.startResponse.API;
+ this.log += "\n SUBTEST TITLE: " + subtest.title;
+ this.log += "\n SUBTEST ELEMENT: " + subtest.element;
+ this.log += "\n SUBTEST DATA: " + JSON.stringify(subtest.test[API]);
+ this.log += "\n\n";
+ }
+ this.log += message;
+ }
+ return;
+ },
+
+ // startTest - send the test start message
+ //
+ // @returns {Promise} resolves if the start is successful, or rejects with
+
+ startTest: function() {
+ 'use strict';
+
+ return new Promise(function(resolve, reject) {
+ var params = {
+ test: this.testName || window.title,
+ url: document.location.href
+ };
+
+ this._fetch("POST", this.ATTAuri + "/start", null, params)
+ .then(function(res) {
+ if (res.body.hasOwnProperty("status")) {
+ if (res.body.status === "READY") {
+ this.startResponse = res.body;
+ if (res.body.hasOwnProperty("log")) {
+ // there is some logging data - capture it
+ this.saveLog("startTest", res.body.log);
+ }
+ // the system is ready for us - is it really?
+ if (res.body.hasOwnProperty("API")) {
+ resolve(res);
+ } else {
+ res.message = "No API in response from ATTA";
+ reject(res);
+ }
+ } else {
+ // the system reported something else - fail out with the statusText as a result
+ res.message = "ATTA reported an error: " + res.body.statusText;
+ reject(res);
+ }
+ } else {
+ res.message = "ATTA did not report a status";
+ reject(res);
+ }
+ }.bind(this))
+ .catch(function(res) {
+ reject(res);
+ });
+ }.bind(this));
+ },
+
+ // sendEvents - send the list of events the ATTA needs to listen for
+ //
+ // @param {string} API
+ // @param {array} collection - a list of tests
+ // @returns {Promise} resolves if the message is successful, or rejects with
+
+ sendEvents: function(API, collection) {
+ 'use strict';
+
+ return new Promise(function(resolve, reject) {
+ var eList = this.eventList(API, collection) ;
+ if (eList && eList.length) {
+ var params = {
+ events: eList
+ };
+
+ this._fetch("POST", this.ATTAuri + "/startlisten", null, params)
+ .then(function(res) {
+ if (res.body.hasOwnProperty("status")) {
+ if (res.body.status === "READY") {
+ if (res.body.hasOwnProperty("log")) {
+ // there is some logging data - capture it
+ this.saveLog("sendEvents", res.body.log);
+ }
+ resolve(res.body.status);
+ } else {
+ // the system reported something else - fail out with the statusText as a result
+ res.message = "ATTA reported an error: " + res.body.statusText;
+ reject(res);
+ }
+ } else {
+ res.message = "ATTA did not report a status";
+ reject(res);
+ }
+ }.bind(this))
+ .catch(function(res) {
+ reject(res);
+ });
+ } else {
+ // there are no events
+ resolve("NOEVENTS");
+ }
+ }.bind(this));
+ },
+
+ sendStopListen: function() {
+ 'use strict';
+
+ return this._fetch("POST", this.ATTAuri + "/stoplisten", null, null);
+ },
+
+ // sendTest - send test data to an ATTA and wait for a response
+ //
+ // returns a promise that resolves with the results of the test
+
+ sendTest: function(testData) {
+ 'use strict';
+
+ if (typeof testData !== "string") {
+ testData = JSON.stringify(testData);
+ }
+ var ret = this._fetch("POST", this.ATTAuri + "/test", null, testData, true);
+ ret.then(function(res) {
+ if (res.body.hasOwnProperty("log")) {
+ // there is some logging data - capture it
+ this.saveLog("sendTest", res.body.log);
+ }
+ }.bind(this));
+ return ret;
+ },
+
+ endTest: function() {
+ 'use strict';
+
+ return this._fetch("GET", this.ATTAuri + "/end");
+ },
+
+ /* normalize - ensure subtest data conforms to ATTA spec
+ */
+
+ normalize: function( data ) {
+ 'use strict';
+
+ var ret = [] ;
+
+ if (data) {
+ data.forEach(function(assert) {
+ var normal = [] ;
+ // ensure if there is a value list it is compressed
+ if (Array.isArray(assert)) {
+ // we have an array
+ normal[0] = assert[0];
+ normal[1] = assert[1];
+ normal[2] = assert[2];
+ if ("string" === typeof assert[3] && assert[3].match(/^\[.*\]$/)) {
+ // it is a string and matches the valuelist pattern
+ normal[3] = assert[3].replace(/, +/, ',');
+ } else {
+ normal[3] = assert[3];
+ }
+ ret.push(normal);
+ } else {
+ ret.push(assert);
+ }
+ });
+ }
+ return ret;
+ },
+
+ // _fetch - return a promise after sending data
+ //
+ // Resolves with the returned information in a structure
+ // including:
+ //
+ // xhr - a raw xhr object
+ // headers - an array of headers sent in the request
+ // status - the status code
+ // statusText - the text of the return status
+ // text - raw returned data
+ // body - an object parsed from the returned content
+ //
+
+ _fetch: function (method, url, headers, content, parse) {
+ 'use strict';
+ if (method === null || method === undefined) {
+ method = "GET";
+ }
+ if (parse === null || parse === undefined) {
+ parse = true;
+ }
+ if (headers === null || headers === undefined) {
+ headers = [];
+ }
+
+
+ // note that this Promise always resolves - there is no reject
+ // condition
+
+ return new Promise(function (resolve, reject) {
+ var xhr = new XMLHttpRequest();
+
+ // this gets returned when the request completes
+ var resp = {
+ xhr: xhr,
+ headers: null,
+ status: 0,
+ statusText: "",
+ body: null,
+ text: ""
+ };
+
+ xhr.open(method, url);
+
+ // headers?
+ headers.forEach(function(ref) {
+ xhr.setRequestHeader(ref[0], ref[1]);
+ });
+
+ //if (this.timeout) {
+ // xhr.timeout = this.timeout;
+ //}
+
+ xhr.ontimeout = function() {
+ resp.timeout = this.timeout;
+ resolve(resp);
+ };
+
+ xhr.onerror = function() {
+ if (this.status) {
+ resp.status = this.status;
+ resp.statusText = xhr.statusText;
+ } else if (this.status === 0) {
+ resp.status = 0;
+ resp.statusText = "No response from ATTA";
+ }
+ reject(resp);
+ };
+
+ xhr.onload = function () {
+ resp.status = this.status;
+ if (this.status >= 200 && this.status < 300) {
+ var d = xhr.response;
+ // return the raw text of the response
+ resp.text = d;
+ // we have it; what is it?
+ if (parse) {
+ try {
+ d = JSON.parse(d);
+ resp.body = d;
+ }
+ catch(err) {
+ resp.body = null;
+ }
+ }
+ resolve(resp);
+ } else {
+ reject({
+ status: this.status,
+ statusText: xhr.statusText
+ });
+ }
+ };
+
+ if (content !== null && content !== undefined) {
+ if ("object" === typeof(content)) {
+ xhr.send(JSON.stringify(content));
+ } else if ("function" === typeof(content)) {
+ xhr.send(content());
+ } else if ("string" === typeof(content)) {
+ xhr.send(content);
+ }
+ } else {
+ xhr.send();
+ }
+ });
+ },
+
+};
+
+// vim: set ts=2 sw=2:
diff --git a/testing/web-platform/tests/wai-aria/scripts/aria-utils.js b/testing/web-platform/tests/wai-aria/scripts/aria-utils.js
new file mode 100644
index 0000000000..ec53464c18
--- /dev/null
+++ b/testing/web-platform/tests/wai-aria/scripts/aria-utils.js
@@ -0,0 +1,196 @@
+/* Utilities related to WAI-ARIA */
+
+const AriaUtils = {
+
+ /*
+ Tests simple role assignment: <div role="alert">x</div>
+ Not intended for nested, context-dependent, or other complex role tests.
+
+ Ex: AriaUtils.assignAndVerifyRolesByRoleNames(["group", "main", "button"])
+
+ */
+ assignAndVerifyRolesByRoleNames: function(roleNames) {
+ if (!Array.isArray(roleNames) || !roleNames.length) {
+ throw `Param roleNames of assignAndVerifyRolesByRoleNames("${roleNames}") should be an array containing at least one role string.`;
+ }
+ for (const role of roleNames) {
+ promise_test(async t => {
+ let el = document.createElement("div");
+ el.appendChild(document.createTextNode("x"));
+ el.setAttribute("role", role); // el.role not yet supported by Gecko.
+ document.body.appendChild(el);
+ const computedRole = await test_driver.get_computed_role(el);
+ assert_equals(computedRole, role, el.outerHTML);
+ }, `role: ${role}`);
+ }
+ },
+
+
+ /*
+ Tests computed ROLE of all elements matching selector
+ against the string value of their data-expectedrole attribute.
+
+ Ex: <div role="list"
+ data-testname="optional unique test name"
+ data-expectedrole="list"
+ class="ex">
+
+ AriaUtils.verifyRolesBySelector(".ex")
+
+ */
+ verifyRolesBySelector: function(selector, roleTestNamePrefix) {
+ const els = document.querySelectorAll(selector);
+ if (!els.length) {
+ throw `Selector passed in verifyRolesBySelector("${selector}") should match at least one element.`;
+ }
+ for (const el of els) {
+ let role = el.getAttribute("data-expectedrole");
+ let testName = el.getAttribute("data-testname") || role; // data-testname optional if role is unique per test file
+ if (typeof roleTestNamePrefix !== "undefined") {
+ testName = roleTestNamePrefix + testName;
+ }
+ promise_test(async t => {
+ const expectedRole = el.getAttribute("data-expectedrole");
+ const computedRole = await test_driver.get_computed_role(el);
+ assert_equals(computedRole, expectedRole, el.outerHTML);
+ }, `${testName}`);
+ }
+ },
+
+
+ /*
+ Tests computed ROLE of selected elements matching selector
+ against the string value of provided roles array.
+
+ Ex: <foo
+ data-testname="verify fooRole or barRole role on span"
+ class="ex-foo-or-bar">
+
+ AriaUtils.verifyRoleOrVariantRolesBySelector(".ex-foo-or-bar", ["fooRole", "barRole"]);
+
+ See also helper function verifyGenericRolesBySelector shorthand of the above using ["generic", "", "none"].
+
+ Note: This function should not be used to circumvent unexpected interop differences in implementations.
+ It should only be used in specific cases (like "generic") determined by ARIA WG or other spec maintainers to be acceptable for the purposes of testing.
+
+ */
+ verifyRoleOrVariantRolesBySelector: function(selector, roles) {
+ const els = document.querySelectorAll(selector);
+ if (!els.length) {
+ throw `Selector "${selector}" should match at least one element.`;
+ }
+ if (!roles.length || roles.length < 2) {
+ throw `Roles array ["${roles.join('", "')}"] should include at least two strings, a primary role and at least one acceptable implementation-specific variant. E.g. ["generic", "", "none"]…`;
+ }
+ for (const el of els) {
+ let testName = el.getAttribute("data-testname");
+ promise_test(async t => {
+ const expectedRoles = roles;
+ const computedRole = await test_driver.get_computed_role(el);
+ for (role of roles){
+ if (computedRole === role) {
+ return assert_equals(computedRole, role, `Computed Role: "${computedRole}" matches one of the acceptable role strings in ["${roles.join('", "')}"]: ${el.outerHTML}`);
+ }
+ }
+ return assert_false(true, `Computed Role: "${computedRole}" does not match any of the acceptable role strings in ["${roles.join('", "')}"]: ${el.outerHTML}`);
+ }, `${testName}`);
+ }
+ },
+
+
+ /*
+ Helper function for "generic" ROLE tests.
+
+ Ex: <span
+ data-testname="verify generic, none, or empty computed role on span"
+ class="ex-generic">
+
+ AriaUtils.verifyGenericRolesBySelector(".ex-generic");
+
+ This helper function is equivalant to AriaUtils.verifyRoleOrVariantRolesBySelector(".ex-generic", ["generic", "", "none"]);
+ See various issues and discussions linked from https://github.com/web-platform-tests/interop-accessibility/issues/48
+
+ */
+ verifyGenericRolesBySelector: function(selector) {
+ // ARIA WG determined implementation variants "none" (Chromium), and the empty string "" (WebKit), are sufficiently equivalent to "generic" for WPT test verification of HTML-AAM.
+ // See various discussions linked from https://github.com/web-platform-tests/interop-accessibility/issues/48
+ this.verifyRoleOrVariantRolesBySelector(selector, ["generic", "", "none"]);
+ },
+
+
+ /*
+ Tests computed LABEL of all elements matching selector
+ against the string value of their data-expectedlabel attribute.
+
+ Ex: <div aria-label="foo"
+ data-testname="optional unique test name"
+ data-expectedlabel="foo"
+ class="ex">
+
+ AriaUtils.verifyLabelsBySelector(".ex")
+
+ */
+ verifyLabelsBySelector: function(selector, labelTestNamePrefix) {
+ const els = document.querySelectorAll(selector);
+ if (!els.length) {
+ throw `Selector passed in verifyLabelsBySelector("${selector}") should match at least one element.`;
+ }
+ for (const el of els) {
+ let label = el.getAttribute("data-expectedlabel");
+ let testName = el.getAttribute("data-testname") || label; // data-testname optional if label is unique per test file
+ if (typeof labelTestNamePrefix !== "undefined") {
+ testName = labelTestNamePrefix + testName;
+ }
+ promise_test(async t => {
+ const expectedLabel = el.getAttribute("data-expectedlabel");
+ let computedLabel = await test_driver.get_computed_label(el);
+ assert_not_equals(computedLabel, null, `get_computed_label(el) shouldn't return null for ${el.outerHTML}`);
+
+ // See:
+ // - https://github.com/w3c/accname/pull/165
+ // - https://github.com/w3c/accname/issues/192
+ // - https://github.com/w3c/accname/issues/208
+ //
+ // AccName references HTML's definition of ASCII Whitespace
+ // https://infra.spec.whatwg.org/#ascii-whitespace
+ // which matches tab (\t), newline (\n), formfeed (\f), return (\r), and regular space (\u0020).
+ // but it does NOT match non-breaking space (\xA0,\u00A0) and others matched by \s
+ const asciiWhitespace = /[\t\n\f\r\u0020]+/g;
+ computedLabel = computedLabel.replace(asciiWhitespace, '\u0020').replace(/^\u0020|\u0020$/g, '');
+
+ assert_equals(computedLabel, expectedLabel, el.outerHTML);
+ }, `${testName}`);
+ }
+ },
+
+
+ /*
+ Tests computed LABEL and ROLE of all elements matching selector using existing
+ verifyLabelsBySelector(), verifyRolesBySelector() functions and passes a test name prefix
+ to ensure uniqueness.
+
+ Ex: <div aria-label="foo" role="button"
+ data-testname="div with role=button is labelled via aria-label"
+ data-expectedlabel="foo"
+ data-expectedrole="button"
+ class="ex-role-and-label">
+
+ AriaUtils.verifyRolesAndLabelsBySelector(".ex-role-and-label")
+
+ */
+ verifyRolesAndLabelsBySelector: function(selector) {
+ let labelTestNamePrefix = "Label: ";
+ let roleTestNamePrefix = "Role: ";
+ const els = document.querySelectorAll(selector);
+ if (!els.length) {
+ throw `Selector passed in verifyRolesAndLabelsBySelector("${selector}") should match at least one element.`;
+ }
+ for (const el of els) {
+ el.classList.add("ex-label-only");
+ el.classList.add("ex-role-only");
+ }
+ this.verifyLabelsBySelector(".ex-label-only", labelTestNamePrefix);
+ this.verifyRolesBySelector(".ex-role-only", roleTestNamePrefix);
+ },
+};
+
diff --git a/testing/web-platform/tests/wai-aria/scripts/manual.css b/testing/web-platform/tests/wai-aria/scripts/manual.css
new file mode 100644
index 0000000000..0e4f4a03e4
--- /dev/null
+++ b/testing/web-platform/tests/wai-aria/scripts/manual.css
@@ -0,0 +1,70 @@
+html {
+ font-family:DejaVu Sans, Bitstream Vera Sans, Arial, Sans;
+}
+
+table#steps {
+ border-collapse:collapse;
+ table-layout:fixed;
+ width:100%;
+}
+
+table#steps th:last-child,
+table#steps td:last-child {
+ width:70%;
+}
+
+// table#steps.assertions th:last-child,
+// table#steps.assertions td:last-child {
+// width:35%;
+// }
+
+table#steps th {
+ padding:0;
+ padding-bottom:0.25;
+ border-bottom:medium solid black;
+}
+
+table#steps td {
+ padding-top:0.25em;
+ padding-bottom:0.25em;
+ padding-left:0.5em;
+ padding-right:0.5em;
+ border-right:thin solid black;
+ border-top:thin solid black;
+ border-bottom:thin solid black;
+}
+
+table#steps td.step, table#steps td.type, table#steps td.element {
+ vertical-align:top;
+}
+table#api {
+ border-collapse:collapse;
+ table-layout:fixed;
+ width:100%;
+}
+
+// table#steps.assertions th:last-child,
+// table#steps.assertions td:last-child {
+// width:35%;
+// }
+
+table#api th {
+ padding:0;
+ padding-bottom:0.25;
+ border-bottom:medium solid black;
+}
+
+table#api td {
+ padding-top:0.25em;
+ padding-bottom:0.25em;
+ padding-left:0.5em;
+ padding-right:0.5em;
+ border-right:thin solid black;
+ border-top:thin solid black;
+ border-bottom:thin solid black;
+}
+
+table#api td.step, table#api td.type, table#api td.element {
+ vertical-align:top;
+}
+}