From 47268649ef72b22b018adf8b67d5ca1570077364 Mon Sep 17 00:00:00 2001 From: Yura Zenevich Date: Thu, 10 Sep 2015 11:45:33 -0400 Subject: [PATCH] Bug 1201595 - improving a11y checks reliability and error messaging. r=ato --- .../tests/unit/test_accessibility.py | 4 +- testing/marionette/driver.js | 2 +- testing/marionette/elements.js | 9 +- testing/marionette/listener.js | 85 ++++++++++--------- 4 files changed, 53 insertions(+), 47 deletions(-) diff --git a/testing/marionette/client/marionette/tests/unit/test_accessibility.py b/testing/marionette/client/marionette/tests/unit/test_accessibility.py index ebd5c154418d..ea9544d57d42 100644 --- a/testing/marionette/client/marionette/tests/unit/test_accessibility.py +++ b/testing/marionette/client/marionette/tests/unit/test_accessibility.py @@ -97,7 +97,7 @@ class TestAccessibility(MarionetteTestCase): lambda button: self.assertRaises(ElementNotAccessibleException, button.tap)) self.run_element_test(self.falsy_elements, - lambda button: self.assertRaises(ElementNotAccessibleException, + lambda button: self.assertRaises(ElementNotVisibleException, button.tap)) def test_single_tap_raises_no_exceptions(self): @@ -120,7 +120,7 @@ class TestAccessibility(MarionetteTestCase): lambda button: self.assertRaises(ElementNotAccessibleException, button.click)) self.run_element_test(self.falsy_elements, - lambda button: self.assertRaises(ElementNotAccessibleException, + lambda button: self.assertRaises(ElementNotVisibleException, button.click)) def test_click_raises_no_exceptions(self): diff --git a/testing/marionette/driver.js b/testing/marionette/driver.js index ae458e2e54ae..ede82cc07740 100644 --- a/testing/marionette/driver.js +++ b/testing/marionette/driver.js @@ -1803,7 +1803,7 @@ GeckoDriver.prototype.singleTap = function(cmd, resp) { case Context.CONTENT: this.addFrameCloseListener("tap"); - yield this.listener.singleTap({id: id, corx: x, cory: y}); + yield this.listener.singleTap(id, x, y); break; } }; diff --git a/testing/marionette/elements.js b/testing/marionette/elements.js index bc35cbbe2a6f..c3779ff82b9e 100644 --- a/testing/marionette/elements.js +++ b/testing/marionette/elements.js @@ -98,7 +98,8 @@ Accessibility.prototype = { getAccessibleObject(element, mustHaveAccessible = false) { let acc = this.accessibleRetrieval.getAccessibleFor(element); if (!acc && mustHaveAccessible) { - this.handleErrorMessage('Element does not have an accessible object'); + this.handleErrorMessage('Element does not have an accessible object', + element); } return acc; }, @@ -178,11 +179,15 @@ Accessibility.prototype = { /** * Send an error message or log the error message in the log * @param String message + * @param DOMElement element that caused an error */ - handleErrorMessage(message) { + handleErrorMessage(message, element) { if (!message) { return; } + if (element) { + message += ` -> id: ${element.id}, tagName: ${element.tagName}, className: ${element.className}\n`; + } if (this.strict) { throw new ElementNotAccessibleError(message); } diff --git a/testing/marionette/listener.js b/testing/marionette/listener.js index 2401c62723c5..c3d6fed20b31 100644 --- a/testing/marionette/listener.js +++ b/testing/marionette/listener.js @@ -215,6 +215,7 @@ let isElementDisplayedFn = dispatch(isElementDisplayed); let getElementValueOfCssPropertyFn = dispatch(getElementValueOfCssProperty); let switchToShadowRootFn = dispatch(switchToShadowRoot); let getCookiesFn = dispatch(getCookies); +let singleTapFn = dispatch(singleTap); /** * Start all message listeners @@ -225,7 +226,7 @@ function startListeners() { addMessageListenerId("Marionette:executeScript", executeScript); addMessageListenerId("Marionette:executeAsyncScript", executeAsyncScript); addMessageListenerId("Marionette:executeJSScript", executeJSScript); - addMessageListenerId("Marionette:singleTap", singleTap); + addMessageListenerId("Marionette:singleTap", singleTapFn); addMessageListenerId("Marionette:actionChain", actionChain); addMessageListenerId("Marionette:multiAction", multiAction); addMessageListenerId("Marionette:get", get); @@ -329,7 +330,7 @@ function deleteSession(msg) { removeMessageListenerId("Marionette:executeScript", executeScript); removeMessageListenerId("Marionette:executeAsyncScript", executeAsyncScript); removeMessageListenerId("Marionette:executeJSScript", executeJSScript); - removeMessageListenerId("Marionette:singleTap", singleTap); + removeMessageListenerId("Marionette:singleTap", singleTapFn); removeMessageListenerId("Marionette:actionChain", actionChain); removeMessageListenerId("Marionette:multiAction", multiAction); removeMessageListenerId("Marionette:get", get); @@ -914,34 +915,27 @@ function checkVisible(el, x, y) { /** * Function that perform a single tap */ -function singleTap(msg) { - let command_id = msg.json.command_id; - try { - let el = elementManager.getKnownElement(msg.json.id, curContainer); - let acc = accessibility.getAccessibleObject(el, true); - // after this block, the element will be scrolled into view - let visible = checkVisible(el, msg.json.corx, msg.json.cory); - checkVisibleAccessibility(acc, visible); - if (!visible) { - sendError(new ElementNotVisibleError("Element is not currently visible and may not be manipulated"), command_id); - return; - } - checkActionableAccessibility(acc); - if (!curContainer.frame.document.createTouch) { - actions.mouseEventsOnly = true; - } - let c = coordinates(el, msg.json.corx, msg.json.cory); - if (!actions.mouseEventsOnly) { - let touchId = actions.nextTouchId++; - let touch = createATouch(el, c.x, c.y, touchId); - emitTouchEvent('touchstart', touch); - emitTouchEvent('touchend', touch); - } - actions.mouseTap(el.ownerDocument, c.x, c.y); - sendOk(command_id); - } catch (e) { - sendError(e, command_id); +function singleTap(id, corx, cory) { + let el = elementManager.getKnownElement(id, curContainer); + // after this block, the element will be scrolled into view + let visible = checkVisible(el, corx, cory); + if (!visible) { + throw new ElementNotVisibleError("Element is not currently visible and may not be manipulated"); } + let acc = accessibility.getAccessibleObject(el, true); + checkVisibleAccessibility(acc, el, visible); + checkActionableAccessibility(acc, el); + if (!curContainer.frame.document.createTouch) { + actions.mouseEventsOnly = true; + } + let c = coordinates(el, corx, cory); + if (!actions.mouseEventsOnly) { + let touchId = actions.nextTouchId++; + let touch = createATouch(el, c.x, c.y, touchId); + emitTouchEvent('touchstart', touch); + emitTouchEvent('touchend', touch); + } + actions.mouseTap(el.ownerDocument, c.x, c.y); } /** @@ -969,16 +963,17 @@ function checkEnabledAccessibility(accesible, element, enabled) { } else if (!enabled && !disabledAccessibility) { message = 'Element is disabled but enabled via the accessibility API'; } - accessibility.handleErrorMessage(message); + accessibility.handleErrorMessage(message, element); } /** * Check if the element's visible state corresponds to its accessibility API * visibility * @param nsIAccessible object + * @param WebElement corresponding to nsIAccessible object * @param Boolean visible element's visibility state */ -function checkVisibleAccessibility(accesible, visible) { +function checkVisibleAccessibility(accesible, element, visible) { if (!accesible) { return; } @@ -991,14 +986,15 @@ function checkVisibleAccessibility(accesible, visible) { message = 'Element is currently only visible via the accessibility API ' + 'and can be manipulated by it'; } - accessibility.handleErrorMessage(message); + accessibility.handleErrorMessage(message, element); } /** * Check if it is possible to activate an element with the accessibility API * @param nsIAccessible object + * @param WebElement corresponding to nsIAccessible object */ -function checkActionableAccessibility(accesible) { +function checkActionableAccessibility(accesible, element) { if (!accesible) { return; } @@ -1013,16 +1009,17 @@ function checkActionableAccessibility(accesible) { } else if (!accessibility.matchState(accesible, 'STATE_FOCUSABLE')) { message = 'Element is not focusable via the accessibility API'; } - accessibility.handleErrorMessage(message); + accessibility.handleErrorMessage(message, element); } /** * Check if element's selected state corresponds to its accessibility API * selected state. * @param nsIAccessible object + * @param WebElement corresponding to nsIAccessible object * @param Boolean selected element's selected state */ -function checkSelectedAccessibility(accessible, selected) { +function checkSelectedAccessibility(accessible, element, selected) { if (!accessible) { return; } @@ -1039,7 +1036,7 @@ function checkSelectedAccessibility(accessible, selected) { } else if (!selected && selectedAccessibility) { message = 'Element is not selected but selected via the accessibility API'; } - accessibility.handleErrorMessage(message); + accessibility.handleErrorMessage(message, element); } @@ -1448,15 +1445,16 @@ function getActiveElement() { */ function clickElement(id) { let el = elementManager.getKnownElement(id, curContainer); - let acc = accessibility.getAccessibleObject(el, true); let visible = checkVisible(el); - checkVisibleAccessibility(acc, visible); if (!visible) { throw new ElementNotVisibleError("Element is not visible"); } - checkActionableAccessibility(acc); + let acc = accessibility.getAccessibleObject(el, true); + checkVisibleAccessibility(acc, el, visible); + if (utils.isElementEnabled(el)) { checkEnabledAccessibility(acc, el, true); + checkActionableAccessibility(acc, el); utils.synthesizeMouseAtCenter(el, {}, el.ownerDocument.defaultView); } else { throw new InvalidElementStateError("Element is not Enabled"); @@ -1516,7 +1514,8 @@ function getElementTagName(id) { function isElementDisplayed(id) { let el = elementManager.getKnownElement(id, curContainer); let displayed = utils.isElementDisplayed(el); - checkVisibleAccessibility(accessibility.getAccessibleObject(el), displayed); + checkVisibleAccessibility( + accessibility.getAccessibleObject(el), el, displayed); return displayed; } @@ -1584,7 +1583,8 @@ function isElementEnabled(id) { function isElementSelected(id) { let el = elementManager.getKnownElement(id, curContainer); let selected = utils.isElementSelected(el); - checkSelectedAccessibility(accessibility.getAccessibleObject(el), selected); + checkSelectedAccessibility( + accessibility.getAccessibleObject(el), el, selected); return selected; } @@ -1599,7 +1599,8 @@ function sendKeysToElement(msg) { let el = elementManager.getKnownElement(msg.json.id, curContainer); // Element should be actionable from the accessibility standpoint to be able // to send keys to it. - checkActionableAccessibility(accessibility.getAccessibleObject(el, true)); + checkActionableAccessibility( + accessibility.getAccessibleObject(el, true), el); if (el.type == "file") { let p = val.join(""); fileInputElement = el;