Bug 1365886 - [marionette] Allow performActions to operate on chrome elements r=marionette-reviewers,jdescottes,whimboo

Change error handling and initialization on the server side.

On the Marionette Python client side, add a `kind` attribute to HTMLElement
to distinguish chrome elements from content elements in the action sequence
sent to the server. This change is necessary for `performActions` in contrast to
other command implementations because of the extra parsing step done by
`actions.Chain.fromJson` on the server side.

Note that for the time being Marionette's ReferenceStore does not
distinguish chrome and content elements (Bug 1672788), so this client-side
change is correct but not strictly necessary.

Differential Revision: https://phabricator.services.mozilla.com/D93778
This commit is contained in:
Maja Frydrychowicz 2020-10-23 15:32:08 +00:00
Родитель 0d4b979717
Коммит c0bcd174a4
5 изменённых файлов: 86 добавлений и 17 удалений

Просмотреть файл

@ -367,7 +367,7 @@ action.PointerOrigin.get = function(obj) {
let name = capitalize(obj);
assert.in(name, this, pprint`Unknown pointer-move origin: ${obj}`);
origin = this[name];
} else if (!element.isDOMElement(obj)) {
} else if (!element.isElement(obj)) {
throw new error.InvalidArgumentError(
"Expected 'origin' to be undefined, " +
'"viewport", "pointer", ' +
@ -408,18 +408,18 @@ action.PointerType.get = function(str) {
* input ID and the device state for that input source, with one entry
* for each active input source.
*
* Initialized in listener.js.
* Re-initialized in listener.js.
*/
action.inputStateMap = undefined;
action.inputStateMap = new Map();
/**
* List of {@link action.Action} associated with current session. Used to
* manage dispatching events when resetting the state of the input sources.
* Reset operations are assumed to be idempotent.
*
* Initialized in listener.js
* Re-initialized in listener.js
*/
action.inputsToCancel = undefined;
action.inputsToCancel = [];
/**
* Represents device state for an input source.
@ -1496,7 +1496,7 @@ function inViewPort(x, y, win) {
}
function getElementCenter(el, win) {
if (element.isDOMElement(el)) {
if (element.isElement(el)) {
if (action.specCompatPointerOrigin) {
return element.getInViewCentrePoint(el.getClientRects()[0], win);
}

Просмотреть файл

@ -103,7 +103,7 @@ class ActionSequence(object):
action["duration"] = duration
if origin is not None:
if isinstance(origin, HTMLElement):
action["origin"] = {WEB_ELEMENT_KEY: origin.id}
action["origin"] = {origin.kind: origin.id}
else:
action["origin"] = origin
self._actions.append(action)
@ -198,10 +198,11 @@ class HTMLElement(object):
identifiers = (CHROME_ELEMENT_KEY, FRAME_KEY, WINDOW_KEY, WEB_ELEMENT_KEY)
def __init__(self, marionette, id):
def __init__(self, marionette, id, kind=WEB_ELEMENT_KEY):
self.marionette = marionette
assert(id is not None)
self.id = id
self.kind = kind
def __str__(self):
return self.id
@ -347,13 +348,13 @@ class HTMLElement(object):
def _from_json(cls, json, marionette):
if isinstance(json, dict):
if WEB_ELEMENT_KEY in json:
return cls(marionette, json[WEB_ELEMENT_KEY])
return cls(marionette, json[WEB_ELEMENT_KEY], WEB_ELEMENT_KEY)
elif CHROME_ELEMENT_KEY in json:
return cls(marionette, json[CHROME_ELEMENT_KEY])
return cls(marionette, json[CHROME_ELEMENT_KEY], CHROME_ELEMENT_KEY)
elif FRAME_KEY in json:
return cls(marionette, json[FRAME_KEY])
return cls(marionette, json[FRAME_KEY], FRAME_KEY)
elif WINDOW_KEY in json:
return cls(marionette, json[WINDOW_KEY])
return cls(marionette, json[WINDOW_KEY], WINDOW_KEY)
raise ValueError("Unrecognised web element")

Просмотреть файл

@ -1930,10 +1930,6 @@ GeckoDriver.prototype.singleTap = async function(cmd) {
* Not yet available in current context.
*/
GeckoDriver.prototype.performActions = async function(cmd) {
assert.content(
this.context,
"Command 'performActions' is not yet available in chrome context"
);
assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
@ -1944,6 +1940,11 @@ GeckoDriver.prototype.performActions = async function(cmd) {
return;
}
assert.content(
this.context,
"Command 'performActions' is not yet available in chrome context"
);
await this.listener.performActions({ actions }, this.capabilities);
};
@ -1958,7 +1959,6 @@ GeckoDriver.prototype.performActions = async function(cmd) {
* Not available in current context.
*/
GeckoDriver.prototype.releaseActions = async function() {
assert.content(this.context);
assert.open(this.getBrowsingContext());
await this._handleUserPrompts();
@ -1967,6 +1967,10 @@ GeckoDriver.prototype.releaseActions = async function() {
return;
}
assert.content(
this.context,
"Command 'releaseActions' is not yet available in chrome context"
);
await this.listener.releaseActions();
};

Просмотреть файл

@ -0,0 +1,63 @@
# 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/.
from __future__ import absolute_import
from marionette_driver import By, errors
from marionette_driver.keys import Keys
from marionette_harness import MarionetteTestCase, WindowManagerMixin
class TestPointerActions(WindowManagerMixin, MarionetteTestCase):
def setUp(self):
super(TestPointerActions, self).setUp()
self.actors_enabled = self.marionette.get_pref("marionette.actors.enabled")
self.mouse_chain = self.marionette.actions.sequence(
"pointer", "pointer_id", {"pointerType": "mouse"})
self.key_chain = self.marionette.actions.sequence(
"key", "keyboard_id")
if self.marionette.session_capabilities["platformName"] == "mac":
self.mod_key = Keys.META
else:
self.mod_key = Keys.CONTROL
self.marionette.set_context("chrome")
self.win = self.open_chrome_window("chrome://marionette/content/test.xhtml")
self.marionette.switch_to_window(self.win)
def tearDown(self):
if self.actors_enabled:
self.marionette.actions.release()
self.close_all_windows()
super(TestPointerActions, self).tearDown()
def test_click_action(self):
box = self.marionette.find_element(By.ID, "testBox")
box.get_property("localName")
if self.actors_enabled:
self.assertFalse(self.marionette.execute_script(
"return document.getElementById('testBox').checked"))
self.mouse_chain.click(element=box).perform()
self.assertTrue(self.marionette.execute_script(
"return document.getElementById('testBox').checked"))
else:
with self.assertRaises(errors.UnsupportedOperationException):
self.mouse_chain.click(element=box).perform()
def test_key_action(self):
self.marionette.find_element(By.ID, "textInput").click()
if self.actors_enabled:
self.key_chain.send_keys("x").perform()
self.assertEqual(self.marionette.execute_script(
"return document.getElementById('textInput').value"), "testx")
else:
with self.assertRaises(errors.UnsupportedOperationException):
self.key_chain.send_keys("x").perform()

Просмотреть файл

@ -80,6 +80,7 @@ skip-if = manage_instance == false
[test_key_actions.py]
[test_mouse_action.py]
[test_chrome_action.py]
[test_teardown_context_preserved.py]
[test_file_upload.py]