From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- testing/marionette/interaction.js | 455 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 455 insertions(+) create mode 100644 testing/marionette/interaction.js (limited to 'testing/marionette/interaction.js') diff --git a/testing/marionette/interaction.js b/testing/marionette/interaction.js new file mode 100644 index 000000000..c8275665d --- /dev/null +++ b/testing/marionette/interaction.js @@ -0,0 +1,455 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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 {utils: Cu} = Components; + +Cu.import("chrome://marionette/content/accessibility.js"); +Cu.import("chrome://marionette/content/atom.js"); +Cu.import("chrome://marionette/content/error.js"); +Cu.import("chrome://marionette/content/element.js"); +Cu.import("chrome://marionette/content/event.js"); + +Cu.importGlobalProperties(["File"]); + +this.EXPORTED_SYMBOLS = ["interaction"]; + +/** + * XUL elements that support disabled attribute. + */ +const DISABLED_ATTRIBUTE_SUPPORTED_XUL = new Set([ + "ARROWSCROLLBOX", + "BUTTON", + "CHECKBOX", + "COLORPICKER", + "COMMAND", + "DATEPICKER", + "DESCRIPTION", + "KEY", + "KEYSET", + "LABEL", + "LISTBOX", + "LISTCELL", + "LISTHEAD", + "LISTHEADER", + "LISTITEM", + "MENU", + "MENUITEM", + "MENULIST", + "MENUSEPARATOR", + "PREFERENCE", + "RADIO", + "RADIOGROUP", + "RICHLISTBOX", + "RICHLISTITEM", + "SCALE", + "TAB", + "TABS", + "TEXTBOX", + "TIMEPICKER", + "TOOLBARBUTTON", + "TREE", +]); + +/** + * XUL elements that support checked property. + */ +const CHECKED_PROPERTY_SUPPORTED_XUL = new Set([ + "BUTTON", + "CHECKBOX", + "LISTITEM", + "TOOLBARBUTTON", +]); + +/** + * XUL elements that support selected property. + */ +const SELECTED_PROPERTY_SUPPORTED_XUL = new Set([ + "LISTITEM", + "MENU", + "MENUITEM", + "MENUSEPARATOR", + "RADIO", + "RICHLISTITEM", + "TAB", +]); + +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 ElementNotInteractableError} is thrown. + * + * 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 {ElementNotInteractableError} + * If either Selenium-style visibility check or + * pointer-interactability check fails. + * @throws {ElementClickInterceptedError} + * If |el| is obscured by another element and a click would not hit, + * in |specCompat| mode. + * @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) { + const a11y = accessibility.get(strict); + if (specCompat) { + yield webdriverClickElement(el, a11y); + } else { + yield seleniumClickElement(el, a11y); + } +}; + +function* webdriverClickElement (el, a11y) { + const win = getWindow(el); + const doc = win.document; + + // step 3 + if (el.localName == "input" && el.type == "file") { + throw new InvalidArgumentError( + "Cannot click elements"); + } + + let containerEl = element.getContainer(el); + + // step 4 + if (!element.isInView(containerEl)) { + element.scrollIntoView(containerEl); + } + + // step 5 + // TODO(ato): wait for containerEl to be in view + + // step 6 + // if we cannot bring the container element into the viewport + // there is no point in checking if it is pointer-interactable + if (!element.isInView(containerEl)) { + throw new ElementNotInteractableError( + error.pprint`Element ${el} could not be scrolled into view`); + } + + // step 7 + let rects = containerEl.getClientRects(); + let clickPoint = element.getInViewCentrePoint(rects[0], win); + + if (!element.isPointerInteractable(containerEl)) { + throw new ElementClickInterceptedError(containerEl, clickPoint); + } + + yield a11y.getAccessible(el, true).then(acc => { + a11y.assertVisible(acc, el, true); + a11y.assertEnabled(acc, el, true); + a11y.assertActionable(acc, el); + }); + + // step 8 + + // chrome elements + if (element.isXULElement(el)) { + if (el.localName == "option") { + interaction.selectOption(el); + } else { + el.click(); + } + + // content elements + } else { + if (el.localName == "option") { + interaction.selectOption(el); + } else { + event.synthesizeMouseAtPoint(clickPoint.x, clickPoint.y, {}, win); + } + } + + // step 9 + yield interaction.flushEventLoop(win); + + // step 10 + // TODO(ato): if the click causes navigation, + // run post-navigation checks +} + +function* seleniumClickElement (el, a11y) { + let win = getWindow(el); + + let visibilityCheckEl = el; + if (el.localName == "option") { + visibilityCheckEl = element.getContainer(el); + } + + if (!element.isVisible(visibilityCheckEl)) { + 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, true); + a11y.assertEnabled(acc, el, true); + a11y.assertActionable(acc, el); + }); + + // chrome elements + if (element.isXULElement(el)) { + if (el.localName == "option") { + interaction.selectOption(el); + } else { + el.click(); + } + + // content elements + } else { + if (el.localName == "option") { + interaction.selectOption(el); + } else { + let rects = el.getClientRects(); + let centre = element.getInViewCentrePoint(rects[0], win); + let opts = {}; + event.synthesizeMouseAtPoint(centre.x, centre.y, opts, win); + } + } +}; + +/** + * Select