summaryrefslogtreecommitdiffstats
path: root/remote/shared/webdriver/test/xpcshell/test_Actions.js
diff options
context:
space:
mode:
Diffstat (limited to 'remote/shared/webdriver/test/xpcshell/test_Actions.js')
-rw-r--r--remote/shared/webdriver/test/xpcshell/test_Actions.js758
1 files changed, 758 insertions, 0 deletions
diff --git a/remote/shared/webdriver/test/xpcshell/test_Actions.js b/remote/shared/webdriver/test/xpcshell/test_Actions.js
new file mode 100644
index 0000000000..24eac2e09d
--- /dev/null
+++ b/remote/shared/webdriver/test/xpcshell/test_Actions.js
@@ -0,0 +1,758 @@
+/* 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 { action, CLICK_INTERVAL, ClickTracker } = ChromeUtils.importESModule(
+ "chrome://remote/content/shared/webdriver/Actions.sys.mjs"
+);
+
+const { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+const XHTMLNS = "http://www.w3.org/1999/xhtml";
+
+const domEl = {
+ nodeType: 1,
+ ELEMENT_NODE: 1,
+ namespaceURI: XHTMLNS,
+};
+
+add_task(function test_createInputState() {
+ for (let type of ["none", "key", "pointer" /*"wheel"*/]) {
+ const state = new action.State();
+ const id = "device";
+ const actionSequence = {
+ type,
+ id,
+ actions: [],
+ };
+ action.Chain.fromJSON(state, [actionSequence]);
+ equal(state.inputStateMap.size, 1);
+ equal(state.inputStateMap.get(id).constructor.type, type);
+ }
+});
+
+add_task(function test_defaultPointerParameters() {
+ let state = new action.State();
+ const inputTickActions = [
+ { type: "pointer", subtype: "pointerDown", button: 0 },
+ ];
+ const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
+ const pointerAction = chain[0][0];
+ equal(
+ state.getInputSource(pointerAction.id).pointer.constructor.type,
+ "mouse"
+ );
+});
+
+add_task(function test_processPointerParameters() {
+ for (let subtype of ["pointerDown", "pointerUp"]) {
+ for (let pointerType of [2, true, {}, []]) {
+ const inputTickActions = [
+ {
+ type: "pointer",
+ parameters: { pointerType },
+ subtype,
+ button: 0,
+ },
+ ];
+ let message = `Action sequence with parameters: {pointerType: ${pointerType} subtype: ${subtype}}`;
+ checkFromJSONErrors(
+ inputTickActions,
+ /Expected "pointerType" to be a string/,
+ message
+ );
+ }
+
+ for (let pointerType of ["", "foo"]) {
+ const inputTickActions = [
+ {
+ type: "pointer",
+ parameters: { pointerType },
+ subtype,
+ button: 0,
+ },
+ ];
+ let message = `Action sequence with parameters: {pointerType: ${pointerType} subtype: ${subtype}}`;
+ checkFromJSONErrors(
+ inputTickActions,
+ /Expected "pointerType" to be one of/,
+ message
+ );
+ }
+ }
+
+ for (let pointerType of ["mouse" /*"touch"*/]) {
+ let state = new action.State();
+ const inputTickActions = [
+ {
+ type: "pointer",
+ parameters: { pointerType },
+ subtype: "pointerDown",
+ button: 0,
+ },
+ ];
+ const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
+ const pointerAction = chain[0][0];
+ equal(
+ state.getInputSource(pointerAction.id).pointer.constructor.type,
+ pointerType
+ );
+ }
+});
+
+add_task(function test_processPointerDownAction() {
+ for (let button of [-1, "a"]) {
+ const inputTickActions = [
+ { type: "pointer", subtype: "pointerDown", button },
+ ];
+ checkFromJSONErrors(
+ inputTickActions,
+ /Expected "button" to be a positive integer/,
+ `pointerDown with {button: ${button}}`
+ );
+ }
+ let state = new action.State();
+ const inputTickActions = [
+ { type: "pointer", subtype: "pointerDown", button: 5 },
+ ];
+ const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
+ equal(chain[0][0].button, 5);
+});
+
+add_task(function test_validateActionDurationAndCoordinates() {
+ for (let [type, subtype] of [
+ ["none", "pause"],
+ ["pointer", "pointerMove"],
+ ]) {
+ for (let duration of [-1, "a"]) {
+ const inputTickActions = [{ type, subtype, duration }];
+ checkFromJSONErrors(
+ inputTickActions,
+ /Expected "duration" to be a positive integer/,
+ `{subtype} with {duration: ${duration}}`
+ );
+ }
+ }
+ for (let name of ["x", "y"]) {
+ const actionItem = {
+ type: "pointer",
+ subtype: "pointerMove",
+ duration: 5000,
+ };
+ actionItem[name] = "a";
+ checkFromJSONErrors(
+ [actionItem],
+ /Expected ".*" to be an integer/,
+ `${name}: "a", subtype: pointerMove`
+ );
+ }
+});
+
+add_task(function test_processPointerMoveActionOriginValidation() {
+ for (let origin of [-1, { a: "blah" }, []]) {
+ const inputTickActions = [
+ { type: "pointer", duration: 5000, subtype: "pointerMove", origin },
+ ];
+ checkFromJSONErrors(
+ inputTickActions,
+ /Expected "origin" to be undefined, "viewport", "pointer", or an element/,
+ `actionItem.origin: (${getTypeString(origin)})`
+ );
+ }
+});
+
+add_task(function test_processPointerMoveActionOriginStringValidation() {
+ for (let origin of ["", "viewports", "pointers"]) {
+ const inputTickActions = [
+ { type: "pointer", duration: 5000, subtype: "pointerMove", origin },
+ ];
+ checkFromJSONErrors(
+ inputTickActions,
+ /Expected "origin" to be undefined, "viewport", "pointer", or an element/,
+ `actionItem.origin: ${origin}`
+ );
+ }
+});
+
+add_task(function test_processPointerMoveActionElementOrigin() {
+ let state = new action.State();
+ const inputTickActions = [
+ {
+ type: "pointer",
+ duration: 5000,
+ subtype: "pointerMove",
+ origin: domEl,
+ x: 0,
+ y: 0,
+ },
+ ];
+ const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
+ deepEqual(chain[0][0].origin.element, domEl);
+});
+
+add_task(function test_processPointerMoveActionDefaultOrigin() {
+ let state = new action.State();
+ const inputTickActions = [
+ { type: "pointer", duration: 5000, subtype: "pointerMove", x: 0, y: 0 },
+ ];
+ const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
+ // The default is viewport coordinates which have an origin at [0,0] and don't depend on inputSource
+ deepEqual(chain[0][0].origin.getOriginCoordinates(null, null), {
+ x: 0,
+ y: 0,
+ });
+});
+
+add_task(function test_processPointerMoveAction() {
+ let state = new action.State();
+ const actionItems = [
+ {
+ duration: 5000,
+ type: "pointerMove",
+ origin: undefined,
+ x: 0,
+ y: 0,
+ },
+ {
+ duration: undefined,
+ type: "pointerMove",
+ origin: domEl,
+ x: 0,
+ y: 0,
+ },
+ {
+ duration: 5000,
+ type: "pointerMove",
+ x: 1,
+ y: 2,
+ origin: undefined,
+ },
+ ];
+ const actionSequence = {
+ id: "some_id",
+ type: "pointer",
+ actions: actionItems,
+ };
+ let chain = action.Chain.fromJSON(state, [actionSequence]);
+ equal(chain.length, actionItems.length);
+ for (let i = 0; i < actionItems.length; i++) {
+ let actual = chain[i][0];
+ let expected = actionItems[i];
+ equal(actual.duration, expected.duration);
+ equal(actual.x, expected.x);
+ equal(actual.y, expected.y);
+
+ let originClass;
+ if (expected.origin === undefined || expected.origin == "viewport") {
+ originClass = "ViewportOrigin";
+ } else if (expected.origin === "pointer") {
+ originClass = "PointerOrigin";
+ } else {
+ originClass = "ElementOrigin";
+ }
+ deepEqual(actual.origin.constructor.name, originClass);
+ }
+});
+
+add_task(function test_computePointerDestinationViewport() {
+ const state = new action.State();
+ const inputTickActions = [
+ {
+ type: "pointer",
+ subtype: "pointerMove",
+ x: 100,
+ y: 200,
+ origin: "viewport",
+ },
+ ];
+ const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
+ const actionItem = chain[0][0];
+ const inputSource = state.getInputSource(actionItem.id);
+ // these values should not affect the outcome
+ inputSource.x = "99";
+ inputSource.y = "10";
+ const target = actionItem.origin.getTargetCoordinates(
+ inputSource,
+ [actionItem.x, actionItem.y],
+ null
+ );
+ equal(actionItem.x, target[0]);
+ equal(actionItem.y, target[1]);
+});
+
+add_task(function test_computePointerDestinationPointer() {
+ const state = new action.State();
+ const inputTickActions = [
+ {
+ type: "pointer",
+ subtype: "pointerMove",
+ x: 100,
+ y: 200,
+ origin: "pointer",
+ },
+ ];
+ const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
+ const actionItem = chain[0][0];
+ const inputSource = state.getInputSource(actionItem.id);
+ inputSource.x = 10;
+ inputSource.y = 99;
+ const target = actionItem.origin.getTargetCoordinates(
+ inputSource,
+ [actionItem.x, actionItem.y],
+ null
+ );
+ equal(actionItem.x + inputSource.x, target[0]);
+ equal(actionItem.y + inputSource.y, target[1]);
+});
+
+add_task(function test_processPointerAction() {
+ for (let pointerType of ["mouse", "touch"]) {
+ const actionItems = [
+ {
+ duration: 2000,
+ type: "pause",
+ },
+ {
+ type: "pointerMove",
+ duration: 2000,
+ x: 0,
+ y: 0,
+ },
+ {
+ type: "pointerUp",
+ button: 1,
+ },
+ ];
+ let actionSequence = {
+ type: "pointer",
+ id: "some_id",
+ parameters: {
+ pointerType,
+ },
+ actions: actionItems,
+ };
+ const state = new action.State();
+ const chain = action.Chain.fromJSON(state, [actionSequence]);
+ equal(chain.length, actionItems.length);
+ for (let i = 0; i < actionItems.length; i++) {
+ const actual = chain[i][0];
+ const expected = actionItems[i];
+ equal(actual.type, expected.type === "pause" ? "none" : "pointer");
+ equal(actual.subtype, expected.type);
+ equal(actual.id, actionSequence.id);
+ if (expected.type === "pointerUp") {
+ equal(actual.button, expected.button);
+ } else {
+ equal(actual.duration, expected.duration);
+ }
+ if (expected.type !== "pause") {
+ equal(
+ state.getInputSource(actual.id).pointer.constructor.type,
+ pointerType
+ );
+ }
+ }
+ }
+});
+
+add_task(function test_processPauseAction() {
+ for (let type of ["none", "key", "pointer"]) {
+ const state = new action.State();
+ const actionSequence = {
+ type,
+ id: "some_id",
+ actions: [{ type: "pause", duration: 5000 }],
+ };
+ const actionItem = action.Chain.fromJSON(state, [actionSequence])[0][0];
+ equal(actionItem.type, "none");
+ equal(actionItem.subtype, "pause");
+ equal(actionItem.id, "some_id");
+ equal(actionItem.duration, 5000);
+ }
+ const state = new action.State();
+ const actionSequence = {
+ type: "none",
+ id: "some_id",
+ actions: [{ type: "pause" }],
+ };
+ const actionItem = action.Chain.fromJSON(state, [actionSequence])[0][0];
+ equal(actionItem.duration, undefined);
+});
+
+add_task(function test_processActionSubtypeValidation() {
+ for (let type of ["none", "key", "pointer"]) {
+ const message = `type: ${type}, subtype: dancing`;
+ const inputTickActions = [{ type, subtype: "dancing" }];
+ checkFromJSONErrors(
+ inputTickActions,
+ new RegExp(`Expected known subtype for type`),
+ message
+ );
+ }
+});
+
+add_task(function test_processKeyActionDown() {
+ for (let value of [-1, undefined, [], ["a"], { length: 1 }, null]) {
+ const inputTickActions = [{ type: "key", subtype: "keyDown", value }];
+ const message = `actionItem.value: (${getTypeString(value)})`;
+ checkFromJSONErrors(
+ inputTickActions,
+ /Expected "value" to be a string that represents single code point/,
+ message
+ );
+ }
+
+ const state = new action.State();
+ const actionSequence = {
+ type: "key",
+ id: "keyboard",
+ actions: [{ type: "keyDown", value: "\uE004" }],
+ };
+ const actionItem = action.Chain.fromJSON(state, [actionSequence])[0][0];
+
+ equal(actionItem.type, "key");
+ equal(actionItem.id, "keyboard");
+ equal(actionItem.subtype, "keyDown");
+ equal(actionItem.value, "\ue004");
+});
+
+add_task(function test_processInputSourceActionSequenceValidation() {
+ checkFromJSONErrors(
+ [{ type: "swim", subtype: "pause", id: "some id" }],
+ /Expected known action type/,
+ "actionSequence type: swim"
+ );
+
+ checkFromJSONErrors(
+ [{ type: "none", subtype: "pause", id: -1 }],
+ /Expected "id" to be a string/,
+ "actionSequence id: -1"
+ );
+
+ checkFromJSONErrors(
+ [{ type: "none", subtype: "pause", id: undefined }],
+ /Expected "id" to be a string/,
+ "actionSequence id: undefined"
+ );
+
+ const state = new action.State();
+ const actionSequence = [
+ { type: "none", subtype: "pause", id: "some_id", actions: -1 },
+ ];
+ const errorRegex = /Expected "actionSequence.actions" to be an array/;
+ const message = "actionSequence actions: -1";
+
+ Assert.throws(
+ () => action.Chain.fromJSON(state, actionSequence),
+ /InvalidArgumentError/,
+ message
+ );
+ Assert.throws(
+ () => action.Chain.fromJSON(state, actionSequence),
+ errorRegex,
+ message
+ );
+});
+
+add_task(function test_processInputSourceActionSequence() {
+ const state = new action.State();
+ const actionItem = { type: "pause", duration: 5 };
+ const actionSequence = {
+ type: "none",
+ id: "some id",
+ actions: [actionItem],
+ };
+ const chain = action.Chain.fromJSON(state, [actionSequence]);
+ equal(chain.length, 1);
+ const tickActions = chain[0];
+ equal(tickActions.length, 1);
+ equal(tickActions[0].type, "none");
+ equal(tickActions[0].subtype, "pause");
+ equal(tickActions[0].duration, 5);
+ equal(tickActions[0].id, "some id");
+});
+
+add_task(function test_processInputSourceActionSequencePointer() {
+ const state = new action.State();
+ const actionItem = { type: "pointerDown", button: 1 };
+ const actionSequence = {
+ type: "pointer",
+ id: "9",
+ actions: [actionItem],
+ parameters: {
+ pointerType: "mouse", // TODO "pen"
+ },
+ };
+ const chain = action.Chain.fromJSON(state, [actionSequence]);
+ equal(chain.length, 1);
+ const tickActions = chain[0];
+ equal(tickActions.length, 1);
+ equal(tickActions[0].type, "pointer");
+ equal(tickActions[0].subtype, "pointerDown");
+ equal(tickActions[0].button, 1);
+ equal(tickActions[0].id, "9");
+ const inputSource = state.getInputSource(tickActions[0].id);
+ equal(inputSource.constructor.type, "pointer");
+ equal(inputSource.pointer.constructor.type, "mouse");
+});
+
+add_task(function test_processInputSourceActionSequenceKey() {
+ const state = new action.State();
+ const actionItem = { type: "keyUp", value: "a" };
+ const actionSequence = {
+ type: "key",
+ id: "9",
+ actions: [actionItem],
+ };
+ const chain = action.Chain.fromJSON(state, [actionSequence]);
+ equal(chain.length, 1);
+ const tickActions = chain[0];
+ equal(tickActions.length, 1);
+ equal(tickActions[0].type, "key");
+ equal(tickActions[0].subtype, "keyUp");
+ equal(tickActions[0].value, "a");
+ equal(tickActions[0].id, "9");
+});
+
+add_task(function test_processInputSourceActionSequenceInputStateMap() {
+ const state = new action.State();
+ const id = "1";
+ const actionItem = { type: "pause", duration: 5000 };
+ const actionSequence = {
+ type: "key",
+ id,
+ actions: [actionItem],
+ };
+ action.Chain.fromJSON(state, [actionSequence]);
+ equal(state.inputStateMap.size, 1);
+ equal(state.inputStateMap.get(id).constructor.type, "key");
+
+ // Construct a different state with the same input id
+ const state1 = new action.State();
+ const actionItem1 = { type: "pointerDown", button: 0 };
+ const actionSequence1 = {
+ type: "pointer",
+ id,
+ actions: [actionItem1],
+ };
+ action.Chain.fromJSON(state1, [actionSequence1]);
+ equal(state1.inputStateMap.size, 1);
+
+ // Overwrite the state in the initial map with one of a different type
+ state.inputStateMap.set(id, state1.inputStateMap.get(id));
+ equal(state.inputStateMap.get(id).constructor.type, "pointer");
+
+ const message = "Wrong state for input id type";
+ Assert.throws(
+ () => action.Chain.fromJSON(state, [actionSequence]),
+ /InvalidArgumentError/,
+ message
+ );
+ Assert.throws(
+ () => action.Chain.fromJSON(state, [actionSequence]),
+ /Expected input source \[object String\] "1" to be type pointer/,
+ message
+ );
+});
+
+add_task(function test_extractActionChainValidation() {
+ for (let actions of [-1, "a", undefined, null]) {
+ const state = new action.State();
+ let message = `actions: ${getTypeString(actions)}`;
+ Assert.throws(
+ () => action.Chain.fromJSON(state, actions),
+ /InvalidArgumentError/,
+ message
+ );
+ Assert.throws(
+ () => action.Chain.fromJSON(state, actions),
+ /Expected "actions" to be an array/,
+ message
+ );
+ }
+});
+
+add_task(function test_extractActionChainEmpty() {
+ const state = new action.State();
+ deepEqual(action.Chain.fromJSON(state, []), []);
+});
+
+add_task(function test_extractActionChain_oneTickOneInput() {
+ const state = new action.State();
+ const actionItem = { type: "pause", duration: 5000 };
+ const actionSequence = {
+ type: "none",
+ id: "some id",
+ actions: [actionItem],
+ };
+ const actionsByTick = action.Chain.fromJSON(state, [actionSequence]);
+ equal(1, actionsByTick.length);
+ equal(1, actionsByTick[0].length);
+ equal(actionsByTick[0][0].id, actionSequence.id);
+ equal(actionsByTick[0][0].type, "none");
+ equal(actionsByTick[0][0].subtype, "pause");
+ equal(actionsByTick[0][0].duration, actionItem.duration);
+});
+
+add_task(function test_extractActionChain_twoAndThreeTicks() {
+ const state = new action.State();
+ const mouseActionItems = [
+ {
+ type: "pointerDown",
+ button: 2,
+ },
+ {
+ type: "pointerUp",
+ button: 2,
+ },
+ ];
+ const mouseActionSequence = {
+ type: "pointer",
+ id: "7",
+ actions: mouseActionItems,
+ parameters: {
+ pointerType: "mouse",
+ },
+ };
+ const keyActionItems = [
+ {
+ type: "keyDown",
+ value: "a",
+ },
+ {
+ type: "pause",
+ duration: 4,
+ },
+ {
+ type: "keyUp",
+ value: "a",
+ },
+ ];
+ let keyActionSequence = {
+ type: "key",
+ id: "1",
+ actions: keyActionItems,
+ };
+ let actionsByTick = action.Chain.fromJSON(state, [
+ keyActionSequence,
+ mouseActionSequence,
+ ]);
+ // number of ticks is same as longest action sequence
+ equal(keyActionItems.length, actionsByTick.length);
+ equal(2, actionsByTick[0].length);
+ equal(2, actionsByTick[1].length);
+ equal(1, actionsByTick[2].length);
+
+ equal(actionsByTick[2][0].id, keyActionSequence.id);
+ equal(actionsByTick[2][0].type, "key");
+ equal(actionsByTick[2][0].subtype, "keyUp");
+});
+
+add_task(function test_computeTickDuration() {
+ const state = new action.State();
+ const expected = 8000;
+ const inputTickActions = [
+ { type: "none", subtype: "pause", duration: 5000 },
+ { type: "key", subtype: "pause", duration: 1000 },
+ { type: "pointer", subtype: "pointerMove", duration: 6000, x: 0, y: 0 },
+ // invalid because keyDown should not have duration, so duration should be ignored.
+ { type: "key", subtype: "keyDown", duration: 100000, value: "a" },
+ { type: "pointer", subtype: "pause", duration: expected },
+ { type: "pointer", subtype: "pointerUp", button: 0 },
+ ];
+ const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
+ equal(1, chain.length);
+ const tickActions = chain[0];
+ equal(expected, tickActions.getDuration());
+});
+
+add_task(function test_computeTickDuration_noDurations() {
+ const state = new action.State();
+ const inputTickActions = [
+ // invalid because keyDown should not have duration, so duration should be ignored.
+ { type: "key", subtype: "keyDown", duration: 100000, value: "a" },
+ // undefined duration permitted
+ { type: "none", subtype: "pause" },
+ { type: "pointer", subtype: "pointerMove", button: 0, x: 0, y: 0 },
+ { type: "pointer", subtype: "pointerDown", button: 0 },
+ { type: "key", subtype: "keyUp", value: "a" },
+ ];
+ const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
+ equal(0, chain[0].getDuration());
+});
+
+add_task(function test_ClickTracker_setClick() {
+ const clickTracker = new ClickTracker();
+ const button1 = 1;
+ const button2 = 2;
+
+ clickTracker.setClick(button1);
+ equal(1, clickTracker.count);
+
+ // Make sure that clicking different mouse buttons doesn't increase the count.
+ clickTracker.setClick(button2);
+ equal(1, clickTracker.count);
+
+ clickTracker.setClick(button2);
+ equal(2, clickTracker.count);
+
+ clickTracker.reset();
+ equal(0, clickTracker.count);
+});
+
+add_task(function test_ClickTracker_reset_after_timeout() {
+ const clickTracker = new ClickTracker();
+
+ clickTracker.setClick(1);
+ equal(1, clickTracker.count);
+
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(() => equal(0, clickTracker.count), CLICK_INTERVAL + 10);
+});
+
+// helpers
+function getTypeString(obj) {
+ return Object.prototype.toString.call(obj);
+}
+
+function checkFromJSONErrors(inputTickActions, regex, message) {
+ const state = new action.State();
+
+ if (typeof message == "undefined") {
+ message = `fromJSON`;
+ }
+ Assert.throws(
+ () => action.Chain.fromJSON(state, chainForTick(inputTickActions)),
+ /InvalidArgumentError/,
+ message
+ );
+ Assert.throws(
+ () => action.Chain.fromJSON(state, chainForTick(inputTickActions)),
+ regex,
+ message
+ );
+}
+
+function chainForTick(tickActions) {
+ const actions = [];
+ let lastId = 0;
+ for (let { type, subtype, parameters, ...props } of tickActions) {
+ let id;
+ if (!props.hasOwnProperty("id")) {
+ id = `${type}_${lastId++}`;
+ } else {
+ id = props.id;
+ delete props.id;
+ }
+ const inputAction = { type, id, actions: [{ type: subtype, ...props }] };
+ if (parameters !== undefined) {
+ inputAction.parameters = parameters;
+ }
+ actions.push(inputAction);
+ }
+ return actions;
+}