Bug 1333014 - Align element interaction errors with spec; r?whimboo
This renames the ElementNotVisibleError to ElementNotInteractableError,
and adds a new ElementClickInterceptedError.
MozReview-Commit-ID: 6cjVghUCvyv
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -2573,17 +2573,18 @@ GeckoDriver.prototype.getTextFromDialog
* an element not visible error is returned.
*/
GeckoDriver.prototype.sendKeysToDialog = function (cmd, resp) {
this._checkIfAlertIsPresent();
// see toolkit/components/prompts/content/commonDialog.js
let {loginContainer, loginTextbox} = this.dialog.ui;
if (loginContainer.hidden) {
- throw new ElementNotVisibleError("This prompt does not accept text input");
+ throw new ElementNotInteractableError(
+ "This prompt does not accept text input");
}
let win = this.dialog.window ? this.dialog.window : this.getCurrentWindow();
event.sendKeysToElement(
cmd.parameters.value,
loginTextbox,
{ignoreVisibility: true},
win);
--- a/testing/marionette/error.js
+++ b/testing/marionette/error.js
@@ -2,18 +2,19 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {interfaces: Ci, utils: Cu} = Components;
const ERRORS = new Set([
+ "ElementClickInterceptedError",
"ElementNotAccessibleError",
- "ElementNotVisibleError",
+ "ElementNotInteractableError",
"InsecureCertificateError",
"InvalidArgumentError",
"InvalidElementStateError",
"InvalidSelectorError",
"InvalidSessionIDError",
"JavaScriptError",
"MoveTargetOutOfBoundsError",
"NoAlertOpenError",
@@ -199,20 +200,48 @@ class WebDriverError extends Error {
class ElementNotAccessibleError extends WebDriverError {
constructor (message) {
super(message);
this.status = "element not accessible";
}
}
-class ElementNotVisibleError extends WebDriverError {
+/**
+ * An element click could not be completed because the element receiving
+ * the events is obscuring the element that was requested clicked.
+ *
+ * @param {Element=} obscuredEl
+ * Element obscuring the element receiving the click. Providing this
+ * is not required, but will produce a nicer error message.
+ * @param {Map.<string, number>} coords
+ * Original click location. Providing this is not required, but
+ * will produce a nicer error message.
+ */
+class ElementClickInterceptedError extends WebDriverError {
+ constructor (obscuredEl = undefined, coords = undefined) {
+ let msg = "";
+ if (obscuredEl && coords) {
+ const doc = obscuredEl.ownerDocument;
+ const overlayingEl = doc.elementFromPoint(coords.x, coords.y);
+ msg = error.pprint`Element ${obscuredEl} is not clickable ` +
+ `at point (${coords.x},${coords.y}) ` +
+ error.pprint`because another element ${overlayingEl} ` +
+ `obscures it`;
+ }
+
+ super(msg);
+ this.status = "element click intercepted";
+ }
+}
+
+class ElementNotInteractableError extends WebDriverError {
constructor (message) {
super(message);
- this.status = "element not visible";
+ this.status = "element not interactable";
}
}
class InsecureCertificateError extends WebDriverError {
constructor (message) {
super(message);
this.status = "insecure certificate";
}
@@ -390,17 +419,18 @@ class UnsupportedOperationError extends
constructor (message) {
super(message);
this.status = "unsupported operation";
}
}
const STATUSES = new Map([
["element not accessible", ElementNotAccessibleError],
- ["element not visible", ElementNotVisibleError],
+ ["element not interactable", ElementNotInteractableError],
+ ["element click intercepted", ElementClickInterceptedError],
["insecure certificate", InsecureCertificateError],
["invalid argument", InvalidArgumentError],
["invalid element state", InvalidElementStateError],
["invalid selector", InvalidSelectorError],
["invalid session id", InvalidSessionIDError],
["javascript error", JavaScriptError],
["move target out of bounds", MoveTargetOutOfBoundsError],
["no alert open", NoAlertOpenError],
--- a/testing/marionette/event.js
+++ b/testing/marionette/event.js
@@ -1302,17 +1302,17 @@ event.sendKeysToElement = function (
let value = keySequence.join("");
for (let i = 0; i < value.length; i++) {
let c = value.charAt(i);
event.sendSingleKey(c, modifiers, window);
}
} else {
- throw new ElementNotVisibleError("Element is not visible");
+ throw new ElementNotInteractableError("Element is not visible");
}
};
event.sendEvent = function (eventType, el, modifiers = {}, opts = {}) {
opts.canBubble = opts.canBubble || true;
let doc = el.ownerDocument || el.document;
let ev = doc.createEvent("Event");
--- a/testing/marionette/interaction.js
+++ b/testing/marionette/interaction.js
@@ -80,35 +80,35 @@ this.interaction = {};
/**
* Interact with an element by clicking it.
*
* The element is scrolled into view before visibility- or interactability
* checks are performed.
*
* Selenium-style visibility checks will be performed if |specCompat|
- * is false (default). Otherwise pointer-interactability checks will be
- * performed. If either of these fail an {@code ElementNotVisibleError}
- * is returned.
+ * is false (default). Otherwise pointer-interactability
+ * checks will be performed. If either of these fail an
+ * {@code ElementNotInteractableError} is returned.
*
* If |strict| is enabled (defaults to disabled), further accessibility
* checks will be performed, and these may result in an {@code
* ElementNotAccessibleError} being returned.
*
* When |el| is not enabled, an {@code InvalidElementStateError}
* is returned.
*
* @param {DOMElement|XULElement} el
* Element to click.
* @param {boolean=} strict
* Enforce strict accessibility tests.
* @param {boolean=} specCompat
* Use WebDriver specification compatible interactability definition.
*
- * @throws {ElementNotVisibleError}
+ * @throws {ElementNotInteractable}
* If either Selenium-style visibility check or
* pointer-interactability check fails.
* @throws {ElementNotAccessibleError}
* If |strict| is true and element is not accessible.
* @throws {InvalidElementStateError}
* If |el| is not enabled.
*/
interaction.clickElement = function*(el, strict = false, specCompat = false) {
@@ -125,17 +125,17 @@ interaction.clickElement = function*(el,
if (!element.isPointerInteractable(visibilityCheckEl)) {
element.scrollIntoView(el);
}
interactable = element.isPointerInteractable(visibilityCheckEl);
} else {
interactable = element.isVisible(visibilityCheckEl);
}
if (!interactable) {
- throw new ElementNotVisibleError();
+ throw new ElementNotInteractableError();
}
if (!atom.isElementEnabled(el)) {
throw new InvalidElementStateError("Element is not enabled");
}
yield a11y.getAccessible(el, true).then(acc => {
a11y.assertVisible(acc, el, interactable);
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -638,17 +638,17 @@ function emitTouchEvent(type, touch) {
/**
* Function that perform a single tap
*/
function singleTap(id, corx, cory) {
let el = seenEls.get(id, curContainer);
// after this block, the element will be scrolled into view
let visible = element.isVisible(el, corx, cory);
if (!visible) {
- throw new ElementNotVisibleError("Element is not currently visible and may not be manipulated");
+ throw new ElementNotInteractableError("Element is not currently visible and may not be manipulated");
}
let a11y = accessibility.get(capabilities.get("moz:accessibilityChecks"));
return a11y.getAccessible(el, true).then(acc => {
a11y.assertVisible(acc, el, visible);
a11y.assertActionable(acc, el);
if (!curContainer.frame.document.createTouch) {
legacyactions.mouseEventsOnly = true;
--- a/testing/marionette/test_error.js
+++ b/testing/marionette/test_error.js
@@ -162,31 +162,59 @@ add_test(function test_WebDriverError()
equal("WebDriverError", err.name);
equal("foo", err.message);
equal("webdriver error", err.status);
ok(err instanceof WebDriverError);
run_next_test();
});
+add_test(function test_ElementClickInterceptedError() {
+ let otherEl = {
+ nodeType: 1,
+ localName: "a",
+ classList: [],
+ };
+ let obscuredEl = {
+ nodeType: 1,
+ localName: "b",
+ classList: [],
+ ownerDocument: {
+ elementFromPoint: function (x, y) {
+ return otherEl;
+ },
+ },
+ };
+
+ let err = new ElementClickInterceptedError(obscuredEl, {x: 1, y: 2});
+ equal("ElementClickInterceptedError", err.name);
+ equal("Element <b> is not clickable at point (1,2) " +
+ "because another element <a> obscures it",
+ err.message);
+ equal("element click intercepted", err.status);
+ ok(err instanceof WebDriverError);
+
+ run_next_test();
+});
+
add_test(function test_ElementNotAccessibleError() {
let err = new ElementNotAccessibleError("foo");
equal("ElementNotAccessibleError", err.name);
equal("foo", err.message);
equal("element not accessible", err.status);
ok(err instanceof WebDriverError);
run_next_test();
});
-add_test(function test_ElementNotVisibleError() {
- let err = new ElementNotVisibleError("foo");
- equal("ElementNotVisibleError", err.name);
+add_test(function test_ElementNotInteractableError() {
+ let err = new ElementNotInteractableError("foo");
+ equal("ElementNotInteractableError", err.name);
equal("foo", err.message);
- equal("element not visible", err.status);
+ equal("element not interactable", err.status);
ok(err instanceof WebDriverError);
run_next_test();
});
add_test(function test_InvalidArgumentError() {
let err = new InvalidArgumentError("foo");
equal("InvalidArgumentError", err.name);