From 89729a754ee3b625c05b41b1ae01b56e2344cce7 Mon Sep 17 00:00:00 2001 From: Yiming Yang Date: Fri, 26 Apr 2013 11:14:50 -0700 Subject: [PATCH] Bug 845925 - Add scroll/pinch actions using touch action chains to the python touch layer, r=mdas --- .../marionette/client/marionette/gestures.py | 71 +++++++++++++++++++ .../client/marionette/marionette.py | 16 +++++ .../client/marionette/marionette_touch.py | 12 ++-- .../marionette/tests/unit/test_gesture.py | 23 ++++++ .../tests/unit/test_single_finger.py | 12 ++++ .../marionette/tests/unit/unit-tests.ini | 16 +++-- .../client/marionette/www/testAction.html | 36 +++++++++- testing/marionette/marionette-listener.js | 2 +- 8 files changed, 177 insertions(+), 11 deletions(-) create mode 100644 testing/marionette/client/marionette/gestures.py create mode 100644 testing/marionette/client/marionette/tests/unit/test_gesture.py diff --git a/testing/marionette/client/marionette/gestures.py b/testing/marionette/client/marionette/gestures.py new file mode 100644 index 000000000000..a6cad63b04bf --- /dev/null +++ b/testing/marionette/client/marionette/gestures.py @@ -0,0 +1,71 @@ +# 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 marionette import MultiActions, Actions + +#axis is y or x +#direction is 0 for positive, and -1 for negative +#length is the total length we want to scroll +#increments is how much we want to move per scrolling +#wait_period is the seconds we wait between scrolling +#scroll_back is whether we want to scroll back to original view +def smooth_scroll(marionette_session, start_element, axis, direction, length, increments=None, wait_period=None, scroll_back=None): + if axis not in ["x", "y"]: + raise Exception("Axis must be either 'x' or 'y'") + if direction not in [-1, 0]: + raise Exception("Direction must either be -1 negative or 0 positive") + increments = increments or 100 + wait_period = wait_period or 0.05 + scroll_back = scroll_back or False + current = 0 + if axis is "x": + if direction is -1: + offset = [-increments, 0] + else: + offset = [increments, 0] + else: + if direction is -1: + offset = [0, -increments] + else: + offset = [0, increments] + action = Actions(marionette_session) + action.press(start_element) + while (current < length): + current += increments + action.move_by_offset(*offset).wait(wait_period) + if scroll_back: + offset = [-value for value in offset] + while (current > 0): + current -= increments + action.move_by_offset(*offset).wait(wait_period) + action.release() + action.perform() + +#element is the target +#x1,x2 are 1st finger starting position relative to the target +#x3,y3 are 1st finger ending position relative to the target +#x2,y2 are 2nd finger starting position relative to the target +#x4,y4 are 2nd finger ending position relative to the target +#duration is the amount of time in milliseconds we want to complete the pinch. +def pinch(marionette_session, element, x1, y1, x2, y2, x3, y3, x4, y4, duration=200): + time = 0 + time_increment = 10 + if time_increment >= duration: + time_increment = duration + move_x1 = time_increment*1.0/duration * (x3 - x1) + move_y1 = time_increment*1.0/duration * (y3 - y1) + move_x2 = time_increment*1.0/duration * (x4 - x2) + move_y2 = time_increment*1.0/duration * (y4 - y2) + multiAction = MultiActions(marionette_session) + action1 = Actions(marionette_session) + action2 = Actions(marionette_session) + action1.press(element, x1, y1) + action2.press(element, x2, y2) + while (time < duration): + time += time_increment + action1.move_by_offset(move_x1, move_y1).wait(time_increment/1000) + action2.move_by_offset(move_x2, move_y2).wait(time_increment/1000) + action1.release() + action2.release() + multiAction.add(action1).add(action2).perform() diff --git a/testing/marionette/client/marionette/marionette.py b/testing/marionette/client/marionette/marionette.py index 1b8b2e0962e0..cf5bd8d8bba6 100644 --- a/testing/marionette/client/marionette/marionette.py +++ b/testing/marionette/client/marionette/marionette.py @@ -138,6 +138,22 @@ class Actions(object): self.action_chain.append(['cancel']) return self + def flick(self, element, x1, y1, x2, y2, duration=200): + element = element.id + time = 0 + time_increment = 10 + if time_increment >= duration: + time_increment = duration + move_x = time_increment*1.0/duration * (x2 - x1) + move_y = time_increment*1.0/duration * (y2 - y1) + self.action_chain.append(['press', element, x1, y1]) + while (time < duration): + time += time_increment + self.action_chain.append(['moveByOffset', move_x, move_y]) + self.action_chain.append(['wait', time_increment/1000]) + self.action_chain.append(['release']) + return self + def long_press(self, element, time_in_seconds): element = element.id self.action_chain.append(['press', element]) diff --git a/testing/marionette/client/marionette/marionette_touch.py b/testing/marionette/client/marionette/marionette_touch.py index 445ce4be9368..7d616a75a658 100644 --- a/testing/marionette/client/marionette/marionette_touch.py +++ b/testing/marionette/client/marionette/marionette_touch.py @@ -4,7 +4,8 @@ import os from errors import ElementNotVisibleException - +from marionette import Actions +from gestures import pinch """ Adds touch support in Marionette """ @@ -35,8 +36,9 @@ class MarionetteTouchMixin(object): def flick(self, element, x1, y1, x2, y2, duration=200): self.check_element(element) - # there's 'flick' which is pixels per second, but I'd rather have the library support it than piece it together here. - self.execute_script("%s.swipe.apply(this, arguments);" % self.library_name, [element, x1, y1, x2, y2, duration]) + action = Actions(self.marionette) + action.flick(element, x1, y1, x2, y2, duration).perform() - def pinch(self, *args, **kwargs): - raise Exception("Pinch is unsupported") + def pinch(self, element, x1, y1, x2, y2, x3, y3, x4, y4, duration = 200): + self.check_element(element) + pinch(element, x1, y1, x2, y2, x3, y3, x4, y4, duration) diff --git a/testing/marionette/client/marionette/tests/unit/test_gesture.py b/testing/marionette/client/marionette/tests/unit/test_gesture.py new file mode 100644 index 000000000000..2c45868ea8a3 --- /dev/null +++ b/testing/marionette/client/marionette/tests/unit/test_gesture.py @@ -0,0 +1,23 @@ +# 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/. + +import time +from marionette_test import MarionetteTestCase +from gestures import smooth_scroll, pinch + +class testGestures(MarionetteTestCase): + def test_smooth_scroll(self): + testTouch = self.marionette.absolute_url("testAction.html") + self.marionette.navigate(testTouch) + button = self.marionette.find_element("id", "mozLinkScrollStart") + smooth_scroll(self.marionette, button, "y", -1, 250) + time.sleep(15) + self.assertEqual("End", self.marionette.execute_script("return document.getElementById('mozLinkScroll').innerHTML;")) + + def test_pinch(self): + testTouch = self.marionette.absolute_url("testAction.html") + self.marionette.navigate(testTouch) + button = self.marionette.find_element("id", "mozLinkScrollStart") + pinch(self.marionette, button, 0, 0, 0, 0, 0, -50, 0, 50) + time.sleep(15) diff --git a/testing/marionette/client/marionette/tests/unit/test_single_finger.py b/testing/marionette/client/marionette/tests/unit/test_single_finger.py index 35ea9030aed4..13e492326f25 100644 --- a/testing/marionette/client/marionette/tests/unit/test_single_finger.py +++ b/testing/marionette/client/marionette/tests/unit/test_single_finger.py @@ -6,6 +6,7 @@ import time from marionette_test import MarionetteTestCase from marionette import Actions from errors import NoSuchElementException, MarionetteException +from unittest import skip class testSingleFinger(MarionetteTestCase): def test_wait(self): @@ -131,6 +132,7 @@ class testSingleFinger(MarionetteTestCase): time.sleep(10) self.assertEqual("ContextEnd", self.marionette.execute_script("return document.getElementById('mozLinkCopy').innerHTML;")) + @skip("Skipping due to Bug 865334") def test_long_press_fail(self): testTouch = self.marionette.absolute_url("testAction.html") self.marionette.navigate(testTouch) @@ -144,3 +146,13 @@ class testSingleFinger(MarionetteTestCase): self.marionette.navigate(testTouch) self.assertRaises(MarionetteException, self.marionette.send_mouse_event, "boolean") + def test_chain_flick(self): + testTouch = self.marionette.absolute_url("testAction.html") + self.marionette.navigate(testTouch) + button = self.marionette.find_element("id", "mozLinkScrollStart") + action = Actions(self.marionette) + action.flick(button, 0, 0, 0, -250).perform() + time.sleep(15) + self.assertEqual("End", self.marionette.execute_script("return document.getElementById('mozLinkScroll').innerHTML;")) + self.assertEqual("Start", self.marionette.execute_script("return document.getElementById('mozLinkScrollStart').innerHTML;")) + diff --git a/testing/marionette/client/marionette/tests/unit/unit-tests.ini b/testing/marionette/client/marionette/tests/unit/unit-tests.ini index c8f1dc275403..852e092b30f3 100644 --- a/testing/marionette/client/marionette/tests/unit/unit-tests.ini +++ b/testing/marionette/client/marionette/tests/unit/unit-tests.ini @@ -11,6 +11,9 @@ b2g = true ; true if the test should be skipped skip = false +; true if the test requires unagi +unagi = false + [test_getstatus.py] [test_import_script.py] [test_import_script_content.py.py] @@ -48,14 +51,15 @@ b2g = false b2g = true browser = false -[test_cancel.py] -b2g = true -browser = false - [test_press_release.py] b2g = true browser = false +[test_gesture.py] +b2g = true +browser = false +unagi = true + [test_single_finger.py] b2g = true browser = false @@ -68,6 +72,10 @@ browser = false b2g = true browser = false +[test_cancel.py] +b2g = true +browser = false + [test_simpletest_pass.js] [test_simpletest_sanity.py] [test_simpletest_chrome.js] diff --git a/testing/marionette/client/marionette/www/testAction.html b/testing/marionette/client/marionette/www/testAction.html index 62c3c9e2e8aa..4bceb7071ef6 100644 --- a/testing/marionette/client/marionette/www/testAction.html +++ b/testing/marionette/client/marionette/www/testAction.html @@ -15,6 +15,10 @@ + + + + @@ -32,6 +36,7 @@ var fourth = document.getElementById("mozLinkCopy2"); var fifth = document.getElementById("mozLinkCancel"); var sixth = document.getElementById("mozMouse"); + var seventh = document.getElementById("mozLinkScrollStart"); // touchmove and touchend must be performed on the same element as touchstart // here is press for vertical move press.addEventListener("touchstart", function(){changePressText("mozLink")}, false); @@ -59,6 +64,9 @@ sixth.addEventListener("mousedown", function(){changeMouseText("MouseDown")}, false); sixth.addEventListener("mouseup", function(){changeMouseText("MouseUp")}, false); sixth.addEventListener("click", function(){changeMouseText("MouseClick")}, false); + // here is seventh for a scroll + seventh.addEventListener("touchstart", function(){changePressText("mozLinkScrollStart")}, false); + seventh.addEventListener("touchend", function(){changeScrollText("mozLinkScroll")}, false); function changeMouseText(strId) { var mouse = document.getElementById("mozMouse"); switch(strId) { @@ -193,13 +201,23 @@ } } + function changeScrollText(strId) { + var seventh = document.getElementById(strId); + if (elementInViewport(seventh)) { + seventh.innerHTML = "End"; + } + else { + seventh.innerHTML = "Error"; + } + } + function changeTimePress() { var fourth = document.getElementById("mozLinkCopy2"); var d = new Date(); fourth.innerHTML = d.getTime(); var newButton = document.createElement("button"); newButton.id = "delayed"; - newButton.setAttribute("style", "position:absolute;left:80px;top:105px;"); + newButton.setAttribute("style", "position:absolute;left:220px;top:455px;"); var content = document.createTextNode("Button6"); newButton.appendChild(content); document.body.appendChild(newButton); @@ -224,6 +242,22 @@ var context = document.getElementById("mozLinkCopy"); context.innerHTML = "Context"; } + + function elementInViewport(el) { + var top = el.offsetTop; + var left = el.offsetLeft; + var width = el.offsetWidth; + var height = el.offsetHeight; + while(el.offsetParent) { + el = el.offsetParent; + top += el.offsetTop; + left += el.offsetLeft; + } + return (top >= window.pageYOffset && + left >= window.pageXOffset && + (top + height) <= (window.pageYOffset + window.innerHeight) && + (left + width) <= (window.pageXOffset + window.innerWidth)); + } diff --git a/testing/marionette/marionette-listener.js b/testing/marionette/marionette-listener.js index b7e2c94de57a..c8876f81db9e 100644 --- a/testing/marionette/marionette-listener.js +++ b/testing/marionette/marionette-listener.js @@ -747,7 +747,7 @@ function touch(target, duration, xt, yt, then) { /** * This function generates the coordinates of the element - * @param 'x0', 'y0', 'x1', and 'y1' are the relative to the viewport. + * @param 'x0', 'y0', 'x1', and 'y1' are the relative to the target. * If they are not specified, then the center of the target is used. */ function coordinates(target, x0, y0, x1, y1) {