Skip to content

Commit b966f72

Browse files
[py] Initial W3C Actions support
1 parent 1476750 commit b966f72

File tree

13 files changed

+510
-13
lines changed

13 files changed

+510
-13
lines changed

py/selenium/webdriver/common/action_chains.py

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from selenium.webdriver.remote.command import Command
2222

2323
from .utils import keys_to_typing
24+
from .actions.action_builder import ActionBuilder
2425

2526

2627
class ActionChains(object):
@@ -65,13 +66,25 @@ def __init__(self, driver):
6566
"""
6667
self._driver = driver
6768
self._actions = []
69+
if self._driver.w3c:
70+
self.w3c_actions = ActionBuilder(driver)
71+
6872

6973
def perform(self):
7074
"""
7175
Performs all stored actions.
7276
"""
73-
for action in self._actions:
74-
action()
77+
if self._driver.w3c:
78+
self.w3c_actions.perform()
79+
else:
80+
for action in self._actions:
81+
action()
82+
83+
def reset_actions(self):
84+
"""
85+
Clears actions that are already stored on the remote end.
86+
"""
87+
self._driver.execute(Command.W3C_CLEAR_ACTIONS)
7588

7689
def click(self, on_element=None):
7790
"""
@@ -174,9 +187,12 @@ def key_down(self, value, element=None):
174187
"""
175188
if element:
176189
self.click(element)
177-
self._actions.append(lambda: self._driver.execute(
178-
Command.SEND_KEYS_TO_ACTIVE_ELEMENT,
179-
{"value": keys_to_typing(value)}))
190+
if self._driver.w3c:
191+
self.w3c_actions.key_action.key_down(value)
192+
else:
193+
self._actions.append(lambda: self._driver.execute(
194+
Command.SEND_KEYS_TO_ACTIVE_ELEMENT,
195+
{"value": keys_to_typing(value)}))
180196
return self
181197

182198
def key_up(self, value, element=None):
@@ -195,9 +211,12 @@ def key_up(self, value, element=None):
195211
"""
196212
if element:
197213
self.click(element)
198-
self._actions.append(lambda: self._driver.execute(
199-
Command.SEND_KEYS_TO_ACTIVE_ELEMENT,
200-
{"value": keys_to_typing(value)}))
214+
if self._driver.w3c:
215+
self.w3c_actions.key_action.key_up(value)
216+
else:
217+
self._actions.append(lambda: self._driver.execute(
218+
Command.SEND_KEYS_TO_ACTIVE_ELEMENT,
219+
{"value": keys_to_typing(value)}))
201220
return self
202221

203222
def move_by_offset(self, xoffset, yoffset):
@@ -263,8 +282,11 @@ def send_keys(self, *keys_to_send):
263282
- keys_to_send: The keys to send. Modifier keys constants can be found in the
264283
'Keys' class.
265284
"""
266-
self._actions.append(lambda: self._driver.execute(
267-
Command.SEND_KEYS_TO_ACTIVE_ELEMENT, {'value': keys_to_typing(keys_to_send)}))
285+
if self._driver.w3c:
286+
self.w3c_actions.key_action.send_keys(keys_to_send)
287+
else:
288+
self._actions.append(lambda: self._driver.execute(
289+
Command.SEND_KEYS_TO_ACTIVE_ELEMENT, {'value': keys_to_typing(keys_to_send)}))
268290
return self
269291

270292
def send_keys_to_element(self, element, *keys_to_send):
@@ -276,7 +298,10 @@ def send_keys_to_element(self, element, *keys_to_send):
276298
- keys_to_send: The keys to send. Modifier keys constants can be found in the
277299
'Keys' class.
278300
"""
279-
self._actions.append(lambda: element.send_keys(*keys_to_send))
301+
if self._driver.w3c:
302+
self.w3c_actions.key_action.send_keys(keys_to_send, element=element)
303+
else:
304+
self._actions.append(lambda: element.send_keys(*keys_to_send))
280305
return self
281306

282307
# Context manager so ActionChains can be used in a 'with .. as' statements.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Licensed to the Software Freedom Conservancy (SFC) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The SFC licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Licensed to the Software Freedom Conservancy (SFC) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The SFC licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
import interaction
19+
20+
from .key_input import KeyInput
21+
from .key_actions import KeyActions
22+
from .pointer_input import PointerInput
23+
from .pointer_actions import PointerActions
24+
25+
from selenium.webdriver.remote.command import Command
26+
27+
28+
class ActionBuilder(object):
29+
30+
def __init__(self, driver, mouse=None, keyboard=None):
31+
if mouse is None:
32+
mouse = PointerInput("mouse", "mouse")
33+
if keyboard is None:
34+
keyboard = KeyInput("keyboard")
35+
self.devices = [keyboard]
36+
self._key_action = KeyActions(keyboard)
37+
self._pointer_action = PointerActions(mouse)
38+
self.driver = driver
39+
40+
def get_device_with(self, name):
41+
try:
42+
idx = self.devices.index(name)
43+
return self.devices[idx]
44+
except:
45+
pass
46+
47+
@property
48+
def pointer_inputs(self):
49+
return [device for device in self.devices if device.type == interaction.POINTER]
50+
51+
@property
52+
def key_inputs(self):
53+
return [device for device in self.devices if device.type == interaction.KEY]
54+
55+
@property
56+
def key_action(self):
57+
return self._key_action
58+
59+
@property
60+
def pointer_action(self):
61+
return self._pointer_action
62+
63+
def add_key_input(self, name):
64+
new_input = KeyInput(name)
65+
self._add_input(new_input)
66+
return new_input
67+
68+
def add_pointer_input(self, type_, name):
69+
new_input = PointerInput(type_, name)
70+
self._add_input(new_input)
71+
return new_input
72+
73+
def perform(self):
74+
enc ={"actions": []}
75+
for device in self.devices:
76+
enc["actions"].append(device.encode())
77+
self.driver.execute(Command.W3C_ACTIONS, enc)
78+
79+
def clear_actions(self):
80+
self.driver.execute(Command.W3C_CLEAR_ACTIONS)
81+
82+
def _add_input(self, input):
83+
self.devices.append(input)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Licensed to the Software Freedom Conservancy (SFC) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The SFC licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
import uuid
19+
20+
21+
class InputDevice(object):
22+
"""
23+
Describes the input device being used for the action.
24+
"""
25+
def __init__(self, name=None):
26+
if name is None:
27+
self.name = uuid.uuid4()
28+
else:
29+
self.name = name
30+
31+
self.actions = []
32+
33+
def add_action(self, action):
34+
"""
35+
36+
"""
37+
self.actions.append(action)
38+
39+
def clear_actions(self):
40+
self.actions = []
41+
42+
def create_pause(self, duraton=0):
43+
pass
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Licensed to the Software Freedom Conservancy (SFC) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The SFC licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
19+
KEY = "key"
20+
POINTER = "pointer"
21+
NONE = "none"
22+
SOURCE_TYPES = set([KEY, POINTER, NONE])
23+
24+
25+
class Interaction(object):
26+
27+
PAUSE = "pause"
28+
29+
def __init__(self, source):
30+
self.source = source
31+
32+
33+
class Pause(Interaction):
34+
35+
def __init__(self, source, duration=0):
36+
super(Interaction, self).__init__()
37+
self.source = source
38+
self.duration = duration
39+
40+
def encode(self):
41+
output = {"type": self.PAUSE}
42+
output["duration"] = self.duration * 1000
43+
return output
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Licensed to the Software Freedom Conservancy (SFC) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The SFC licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
from ..utils import keys_to_typing
18+
19+
from .interaction import Interaction
20+
from .key_input import KeyInput
21+
22+
23+
class KeyActions(Interaction):
24+
25+
def __init__(self, source=None):
26+
if source is None:
27+
source = KeyInput()
28+
self.source = source
29+
super(KeyActions, self).__init__(source)
30+
31+
def key_down(self, letter, element=None):
32+
return self._key_action("create_key_down",
33+
letter, element)
34+
35+
def key_up(self, letter, element=None):
36+
return self._key_action("create_key_up",
37+
letter, element)
38+
39+
def pause(self, duration=0):
40+
return self._key_action("create_pause", duration)
41+
42+
def send_keys(self, text, element=None):
43+
if not isinstance(text, list):
44+
text = keys_to_typing(text)
45+
for letter in text:
46+
self.key_down(letter, element)
47+
self.key_up(letter, element)
48+
return self
49+
50+
def _key_action(self, action, letter, element=None):
51+
meth = getattr(self.source, action)
52+
meth(letter)
53+
return self
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Licensed to the Software Freedom Conservancy (SFC) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The SFC licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
import interaction
18+
19+
from input_device import InputDevice
20+
from interaction import (Interaction,
21+
Pause)
22+
23+
24+
class KeyInput(InputDevice):
25+
def __init__(self, name):
26+
super(KeyInput, self).__init__()
27+
self.name = name
28+
self.type = interaction.KEY
29+
30+
def encode(self):
31+
return {"type": self.type, "id": self.name, "actions": [acts.encode() for acts in self.actions]}
32+
33+
def create_key_down(self, key):
34+
self.add_action(TypingInteraction(self, "keyDown", key))
35+
36+
def create_key_up(self, key):
37+
self.add_action(TypingInteraction(self, "keyUp", key))
38+
39+
def create_pause(self, pause_duration=0):
40+
self.add_action(Pause(self, pause_duration))
41+
42+
43+
class TypingInteraction(Interaction):
44+
45+
def __init__(self, source, type_, key):
46+
super(TypingInteraction, self).__init__(source)
47+
self.type = type_
48+
self.key = key
49+
50+
def encode(self):
51+
return {"type": self.type, "value": self.key}

0 commit comments

Comments
 (0)