Skip to content

Commit 261ff54

Browse files
Move action and keyboard helpers to mixin classes (#307)
1 parent df1537c commit 261ff54

File tree

3 files changed

+308
-272
lines changed

3 files changed

+308
-272
lines changed
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
#!/usr/bin/env python
2+
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from selenium import webdriver
16+
17+
from appium.webdriver.common.multi_action import MultiAction
18+
from appium.webdriver.common.touch_action import TouchAction
19+
20+
21+
class ActionHelpers(webdriver.Remote):
22+
23+
def scroll(self, origin_el, destination_el, duration=None):
24+
"""Scrolls from one element to another
25+
26+
:Args:
27+
- originalEl - the element from which to being scrolling
28+
- destinationEl - the element to scroll to
29+
- duration - a duration after pressing originalEl and move the element to destinationEl.
30+
Default is 600 ms for W3C spec. Zero for MJSONWP.
31+
32+
:Usage:
33+
driver.scroll(el1, el2)
34+
"""
35+
36+
# XCUITest x W3C spec has no duration by default in server side
37+
if self.w3c and duration is None:
38+
duration = 600
39+
40+
action = TouchAction(self)
41+
if duration is None:
42+
action.press(origin_el).move_to(destination_el).release().perform()
43+
else:
44+
action.press(origin_el).wait(duration).move_to(destination_el).release().perform()
45+
return self
46+
47+
def drag_and_drop(self, origin_el, destination_el):
48+
"""Drag the origin element to the destination element
49+
50+
:Args:
51+
- originEl - the element to drag
52+
- destinationEl - the element to drag to
53+
"""
54+
action = TouchAction(self)
55+
action.long_press(origin_el).move_to(destination_el).release().perform()
56+
return self
57+
58+
def tap(self, positions, duration=None):
59+
"""Taps on an particular place with up to five fingers, holding for a
60+
certain time
61+
62+
:Args:
63+
- positions - an array of tuples representing the x/y coordinates of
64+
the fingers to tap. Length can be up to five.
65+
- duration - (optional) length of time to tap, in ms
66+
67+
:Usage:
68+
driver.tap([(100, 20), (100, 60), (100, 100)], 500)
69+
"""
70+
if len(positions) == 1:
71+
action = TouchAction(self)
72+
x = positions[0][0]
73+
y = positions[0][1]
74+
if duration:
75+
action.long_press(x=x, y=y, duration=duration).release()
76+
else:
77+
action.tap(x=x, y=y)
78+
action.perform()
79+
else:
80+
ma = MultiAction(self)
81+
for position in positions:
82+
x = position[0]
83+
y = position[1]
84+
action = TouchAction(self)
85+
if duration:
86+
action.long_press(x=x, y=y, duration=duration).release()
87+
else:
88+
action.press(x=x, y=y).release()
89+
ma.add(action)
90+
91+
ma.perform()
92+
return self
93+
94+
def swipe(self, start_x, start_y, end_x, end_y, duration=None):
95+
"""Swipe from one point to another point, for an optional duration.
96+
97+
:Args:
98+
- start_x - x-coordinate at which to start
99+
- start_y - y-coordinate at which to start
100+
- end_x - x-coordinate at which to stop
101+
- end_y - y-coordinate at which to stop
102+
- duration - (optional) time to take the swipe, in ms.
103+
104+
:Usage:
105+
driver.swipe(100, 100, 100, 400)
106+
"""
107+
# `swipe` is something like press-wait-move_to-release, which the server
108+
# will translate into the correct action
109+
action = TouchAction(self)
110+
action \
111+
.press(x=start_x, y=start_y) \
112+
.wait(ms=duration) \
113+
.move_to(x=end_x, y=end_y) \
114+
.release()
115+
action.perform()
116+
return self
117+
118+
def flick(self, start_x, start_y, end_x, end_y):
119+
"""Flick from one point to another point.
120+
121+
:Args:
122+
- start_x - x-coordinate at which to start
123+
- start_y - y-coordinate at which to start
124+
- end_x - x-coordinate at which to stop
125+
- end_y - y-coordinate at which to stop
126+
127+
:Usage:
128+
driver.flick(100, 100, 100, 400)
129+
"""
130+
action = TouchAction(self)
131+
action \
132+
.press(x=start_x, y=start_y) \
133+
.move_to(x=end_x, y=end_y) \
134+
.release()
135+
action.perform()
136+
return self
137+
138+
def pinch(self, element=None, percent=200, steps=50):
139+
"""Pinch on an element a certain amount
140+
141+
:Args:
142+
- element - the element to pinch
143+
- percent - (optional) amount to pinch. Defaults to 200%
144+
- steps - (optional) number of steps in the pinch action
145+
146+
:Usage:
147+
driver.pinch(element)
148+
"""
149+
if element:
150+
element = element.id
151+
152+
opts = {
153+
'element': element,
154+
'percent': percent,
155+
'steps': steps,
156+
}
157+
self.execute_script('mobile: pinchClose', opts)
158+
return self
159+
160+
def zoom(self, element=None, percent=200, steps=50):
161+
"""Zooms in on an element a certain amount
162+
163+
:Args:
164+
- element - the element to zoom
165+
- percent - (optional) amount to zoom. Defaults to 200%
166+
- steps - (optional) number of steps in the zoom action
167+
168+
:Usage:
169+
driver.zoom(element)
170+
"""
171+
if element:
172+
element = element.id
173+
174+
opts = {
175+
'element': element,
176+
'percent': percent,
177+
'steps': steps,
178+
}
179+
self.execute_script('mobile: pinchOpen', opts)
180+
return self
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!/usr/bin/env python
2+
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from selenium import webdriver
16+
from ..mobilecommand import MobileCommand as Command
17+
18+
19+
class Keyboard(webdriver.Remote):
20+
21+
def hide_keyboard(self, key_name=None, key=None, strategy=None):
22+
"""Hides the software keyboard on the device. In iOS, use `key_name` to press
23+
a particular key, or `strategy`. In Android, no parameters are used.
24+
25+
:Args:
26+
- key_name - key to press
27+
- strategy - strategy for closing the keyboard (e.g., `tapOutside`)
28+
"""
29+
data = {}
30+
if key_name is not None:
31+
data['keyName'] = key_name
32+
elif key is not None:
33+
data['key'] = key
34+
elif strategy is None:
35+
strategy = 'tapOutside'
36+
data['strategy'] = strategy
37+
self.execute(Command.HIDE_KEYBOARD, data)
38+
return self
39+
40+
def is_keyboard_shown(self):
41+
"""Attempts to detect whether a software keyboard is present"""
42+
return self.execute(Command.IS_KEYBOARD_SHOWN)['value']
43+
44+
def keyevent(self, keycode, metastate=None):
45+
"""Sends a keycode to the device. Android only. Possible keycodes can be
46+
found in http://developer.android.com/reference/android/view/KeyEvent.html.
47+
48+
:Args:
49+
- keycode - the keycode to be sent to the device
50+
- metastate - meta information about the keycode being sent
51+
"""
52+
data = {
53+
'keycode': keycode,
54+
}
55+
if metastate is not None:
56+
data['metastate'] = metastate
57+
self.execute(Command.KEY_EVENT, data)
58+
return self
59+
60+
def press_keycode(self, keycode, metastate=None, flags=None):
61+
"""Sends a keycode to the device. Android only. Possible keycodes can be
62+
found in http://developer.android.com/reference/android/view/KeyEvent.html.
63+
64+
:Args:
65+
- keycode - the keycode to be sent to the device
66+
- metastate - meta information about the keycode being sent
67+
- flags - the set of key event flags
68+
"""
69+
data = {
70+
'keycode': keycode,
71+
}
72+
if metastate is not None:
73+
data['metastate'] = metastate
74+
if flags is not None:
75+
data['flags'] = flags
76+
self.execute(Command.PRESS_KEYCODE, data)
77+
return self
78+
79+
def long_press_keycode(self, keycode, metastate=None, flags=None):
80+
"""Sends a long press of keycode to the device. Android only. Possible keycodes can be
81+
found in http://developer.android.com/reference/android/view/KeyEvent.html.
82+
83+
:Args:
84+
- keycode - the keycode to be sent to the device
85+
- metastate - meta information about the keycode being sent
86+
- flags - the set of key event flags
87+
"""
88+
data = {
89+
'keycode': keycode
90+
}
91+
if metastate is not None:
92+
data['metastate'] = metastate
93+
if flags is not None:
94+
data['flags'] = flags
95+
self.execute(Command.LONG_PRESS_KEYCODE, data)
96+
return self
97+
98+
# pylint: disable=protected-access
99+
100+
def _addCommands(self):
101+
self.command_executor._commands[Command.HIDE_KEYBOARD] = \
102+
('POST', '/session/$sessionId/appium/device/hide_keyboard')
103+
self.command_executor._commands[Command.IS_KEYBOARD_SHOWN] = \
104+
('GET', '/session/$sessionId/appium/device/is_keyboard_shown')
105+
self.command_executor._commands[Command.KEY_EVENT] = \
106+
('POST', '/session/$sessionId/appium/device/keyevent')
107+
self.command_executor._commands[Command.PRESS_KEYCODE] = \
108+
('POST', '/session/$sessionId/appium/device/press_keycode')
109+
self.command_executor._commands[Command.LONG_PRESS_KEYCODE] = \
110+
('POST', '/session/$sessionId/appium/device/long_press_keycode')

0 commit comments

Comments
 (0)