pymobiledevice3 5.0.0__py3-none-any.whl → 5.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pymobiledevice3 might be problematic. Click here for more details.
- misc/plist_sniffer.py +15 -15
- misc/remotexpc_sniffer.py +29 -28
- pymobiledevice3/__main__.py +128 -102
- pymobiledevice3/_version.py +2 -2
- pymobiledevice3/bonjour.py +26 -49
- pymobiledevice3/ca.py +32 -24
- pymobiledevice3/cli/activation.py +7 -7
- pymobiledevice3/cli/afc.py +19 -19
- pymobiledevice3/cli/amfi.py +4 -4
- pymobiledevice3/cli/apps.py +51 -39
- pymobiledevice3/cli/backup.py +58 -32
- pymobiledevice3/cli/bonjour.py +25 -18
- pymobiledevice3/cli/cli_common.py +112 -81
- pymobiledevice3/cli/companion_proxy.py +4 -4
- pymobiledevice3/cli/completions.py +10 -10
- pymobiledevice3/cli/crash.py +37 -31
- pymobiledevice3/cli/developer.py +602 -520
- pymobiledevice3/cli/diagnostics.py +38 -33
- pymobiledevice3/cli/lockdown.py +79 -74
- pymobiledevice3/cli/mounter.py +85 -68
- pymobiledevice3/cli/notification.py +10 -10
- pymobiledevice3/cli/pcap.py +19 -14
- pymobiledevice3/cli/power_assertion.py +12 -10
- pymobiledevice3/cli/processes.py +10 -10
- pymobiledevice3/cli/profile.py +88 -77
- pymobiledevice3/cli/provision.py +17 -17
- pymobiledevice3/cli/remote.py +186 -110
- pymobiledevice3/cli/restore.py +43 -40
- pymobiledevice3/cli/springboard.py +30 -28
- pymobiledevice3/cli/syslog.py +85 -58
- pymobiledevice3/cli/usbmux.py +21 -20
- pymobiledevice3/cli/version.py +3 -2
- pymobiledevice3/cli/webinspector.py +157 -79
- pymobiledevice3/common.py +1 -1
- pymobiledevice3/exceptions.py +154 -60
- pymobiledevice3/irecv.py +49 -53
- pymobiledevice3/irecv_devices.py +1489 -492
- pymobiledevice3/lockdown.py +394 -241
- pymobiledevice3/lockdown_service_provider.py +5 -7
- pymobiledevice3/osu/os_utils.py +18 -9
- pymobiledevice3/osu/posix_util.py +28 -15
- pymobiledevice3/osu/win_util.py +14 -8
- pymobiledevice3/pair_records.py +19 -19
- pymobiledevice3/remote/common.py +4 -4
- pymobiledevice3/remote/core_device/app_service.py +94 -67
- pymobiledevice3/remote/core_device/core_device_service.py +17 -14
- pymobiledevice3/remote/core_device/device_info.py +5 -5
- pymobiledevice3/remote/core_device/diagnostics_service.py +10 -8
- pymobiledevice3/remote/core_device/file_service.py +47 -33
- pymobiledevice3/remote/remote_service_discovery.py +53 -35
- pymobiledevice3/remote/remotexpc.py +62 -41
- pymobiledevice3/remote/tunnel_service.py +371 -293
- pymobiledevice3/remote/utils.py +12 -11
- pymobiledevice3/remote/xpc_message.py +145 -125
- pymobiledevice3/resources/dsc_uuid_map.py +19 -19
- pymobiledevice3/resources/firmware_notifications.py +16 -16
- pymobiledevice3/restore/asr.py +27 -27
- pymobiledevice3/restore/base_restore.py +90 -47
- pymobiledevice3/restore/consts.py +87 -66
- pymobiledevice3/restore/device.py +11 -11
- pymobiledevice3/restore/fdr.py +46 -46
- pymobiledevice3/restore/ftab.py +19 -19
- pymobiledevice3/restore/img4.py +130 -133
- pymobiledevice3/restore/mbn.py +35 -54
- pymobiledevice3/restore/recovery.py +125 -135
- pymobiledevice3/restore/restore.py +524 -523
- pymobiledevice3/restore/restore_options.py +122 -115
- pymobiledevice3/restore/restored_client.py +25 -22
- pymobiledevice3/restore/tss.py +378 -270
- pymobiledevice3/service_connection.py +50 -46
- pymobiledevice3/services/accessibilityaudit.py +136 -126
- pymobiledevice3/services/afc.py +350 -291
- pymobiledevice3/services/amfi.py +21 -18
- pymobiledevice3/services/companion.py +23 -19
- pymobiledevice3/services/crash_reports.py +60 -46
- pymobiledevice3/services/debugserver_applist.py +3 -3
- pymobiledevice3/services/device_arbitration.py +8 -8
- pymobiledevice3/services/device_link.py +55 -47
- pymobiledevice3/services/diagnostics.py +971 -968
- pymobiledevice3/services/dtfetchsymbols.py +8 -8
- pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
- pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
- pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
- pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
- pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
- pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
- pymobiledevice3/services/dvt/instruments/device_info.py +11 -11
- pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
- pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
- pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
- pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
- pymobiledevice3/services/dvt/instruments/process_control.py +25 -10
- pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
- pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
- pymobiledevice3/services/dvt/testmanaged/xcuitest.py +40 -50
- pymobiledevice3/services/file_relay.py +10 -10
- pymobiledevice3/services/heartbeat.py +8 -7
- pymobiledevice3/services/house_arrest.py +12 -15
- pymobiledevice3/services/installation_proxy.py +119 -100
- pymobiledevice3/services/lockdown_service.py +12 -5
- pymobiledevice3/services/misagent.py +22 -19
- pymobiledevice3/services/mobile_activation.py +84 -72
- pymobiledevice3/services/mobile_config.py +330 -301
- pymobiledevice3/services/mobile_image_mounter.py +137 -116
- pymobiledevice3/services/mobilebackup2.py +188 -150
- pymobiledevice3/services/notification_proxy.py +11 -11
- pymobiledevice3/services/os_trace.py +69 -51
- pymobiledevice3/services/pcapd.py +306 -306
- pymobiledevice3/services/power_assertion.py +10 -9
- pymobiledevice3/services/preboard.py +4 -4
- pymobiledevice3/services/remote_fetch_symbols.py +16 -14
- pymobiledevice3/services/remote_server.py +176 -146
- pymobiledevice3/services/restore_service.py +16 -16
- pymobiledevice3/services/screenshot.py +13 -10
- pymobiledevice3/services/simulate_location.py +7 -7
- pymobiledevice3/services/springboard.py +15 -15
- pymobiledevice3/services/syslog.py +5 -5
- pymobiledevice3/services/web_protocol/alert.py +3 -3
- pymobiledevice3/services/web_protocol/automation_session.py +180 -176
- pymobiledevice3/services/web_protocol/cdp_screencast.py +44 -36
- pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
- pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
- pymobiledevice3/services/web_protocol/driver.py +47 -45
- pymobiledevice3/services/web_protocol/element.py +74 -63
- pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
- pymobiledevice3/services/web_protocol/selenium_api.py +2 -2
- pymobiledevice3/services/web_protocol/session_protocol.py +15 -10
- pymobiledevice3/services/web_protocol/switch_to.py +11 -12
- pymobiledevice3/services/webinspector.py +127 -116
- pymobiledevice3/tcp_forwarder.py +35 -22
- pymobiledevice3/tunneld/api.py +20 -15
- pymobiledevice3/tunneld/server.py +212 -133
- pymobiledevice3/usbmux.py +183 -138
- pymobiledevice3/utils.py +14 -11
- {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/METADATA +1 -1
- pymobiledevice3-5.0.2.dist-info/RECORD +173 -0
- pymobiledevice3-5.0.0.dist-info/RECORD +0 -173
- {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/WHEEL +0 -0
- {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/top_level.txt +0 -0
|
@@ -5,24 +5,24 @@ from pymobiledevice3.services.web_protocol.element import WebElement
|
|
|
5
5
|
from pymobiledevice3.services.web_protocol.selenium_api import By, SeleniumApi
|
|
6
6
|
from pymobiledevice3.services.web_protocol.switch_to import SwitchTo
|
|
7
7
|
|
|
8
|
-
ENTER_FULLSCREEN = (RESOURCES /
|
|
8
|
+
ENTER_FULLSCREEN = (RESOURCES / "enter_fullscreen.js").read_text()
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
@dataclass
|
|
12
12
|
class Cookie:
|
|
13
13
|
name: str
|
|
14
14
|
value: str
|
|
15
|
-
domain: str =
|
|
16
|
-
path: str =
|
|
15
|
+
domain: str = ""
|
|
16
|
+
path: str = "/"
|
|
17
17
|
expires: int = 0
|
|
18
18
|
httpOnly: bool = False
|
|
19
19
|
secure: bool = False
|
|
20
20
|
session: bool = True
|
|
21
|
-
sameSite: str =
|
|
21
|
+
sameSite: str = "None"
|
|
22
22
|
|
|
23
23
|
@classmethod
|
|
24
24
|
def from_automation(cls, d):
|
|
25
|
-
d.pop(
|
|
25
|
+
d.pop("size")
|
|
26
26
|
return cls(**d)
|
|
27
27
|
|
|
28
28
|
|
|
@@ -35,38 +35,38 @@ class WebDriver(SeleniumApi):
|
|
|
35
35
|
self.switch_to = SwitchTo(session)
|
|
36
36
|
|
|
37
37
|
def add_cookie(self, cookie: Cookie):
|
|
38
|
-
"""
|
|
38
|
+
"""Adds a cookie to your current session."""
|
|
39
39
|
if isinstance(cookie, Cookie):
|
|
40
40
|
cookie = asdict(cookie)
|
|
41
41
|
self.session.add_single_cookie(cookie)
|
|
42
42
|
|
|
43
43
|
def back(self):
|
|
44
|
-
"""
|
|
44
|
+
"""Goes one step backward in the browser history."""
|
|
45
45
|
self.session.wait_for_navigation_to_complete()
|
|
46
46
|
self.session.go_back_in_browsing_context()
|
|
47
|
-
self.session.switch_to_browsing_context(
|
|
47
|
+
self.session.switch_to_browsing_context("")
|
|
48
48
|
|
|
49
49
|
def close(self):
|
|
50
|
-
"""
|
|
50
|
+
"""Closes the current window."""
|
|
51
51
|
self.session.close_window()
|
|
52
52
|
|
|
53
53
|
@property
|
|
54
54
|
def current_url(self):
|
|
55
|
-
"""
|
|
55
|
+
"""Gets the URL of the current page."""
|
|
56
56
|
self.session.wait_for_navigation_to_complete()
|
|
57
|
-
return self.session.get_browsing_context()[
|
|
57
|
+
return self.session.get_browsing_context()["url"]
|
|
58
58
|
|
|
59
59
|
@property
|
|
60
60
|
def current_window_handle(self):
|
|
61
|
-
"""
|
|
62
|
-
return self.session.get_browsing_context()[
|
|
61
|
+
"""Returns the handle of the current window."""
|
|
62
|
+
return self.session.get_browsing_context()["handle"]
|
|
63
63
|
|
|
64
64
|
def delete_all_cookies(self):
|
|
65
|
-
"""
|
|
65
|
+
"""Delete all cookies in the scope of the session."""
|
|
66
66
|
self.session.delete_all_cookies()
|
|
67
67
|
|
|
68
68
|
def delete_cookie(self, name: str):
|
|
69
|
-
"""
|
|
69
|
+
"""Deletes a single cookie with the given name."""
|
|
70
70
|
self.session.delete_single_cookie(name)
|
|
71
71
|
|
|
72
72
|
def execute_async_script(self, script: str, *args):
|
|
@@ -86,110 +86,112 @@ class WebDriver(SeleniumApi):
|
|
|
86
86
|
return self.session.execute_script(script, args)
|
|
87
87
|
|
|
88
88
|
def find_element(self, by=By.ID, value=None) -> WebElement:
|
|
89
|
-
"""
|
|
89
|
+
"""Find an element given a By strategy and locator."""
|
|
90
90
|
elem = self.session.find_elements(by, value)
|
|
91
91
|
return None if elem is None else WebElement(self.session, elem)
|
|
92
92
|
|
|
93
93
|
def find_elements(self, by=By.ID, value=None) -> list[WebElement]:
|
|
94
|
-
"""
|
|
94
|
+
"""Find elements given a By strategy and locator."""
|
|
95
95
|
elements = self.session.find_elements(by, value, single=False)
|
|
96
|
-
return
|
|
96
|
+
return [WebElement(self.session, elem) for elem in elements]
|
|
97
97
|
|
|
98
98
|
def forward(self):
|
|
99
|
-
"""
|
|
99
|
+
"""Goes one step forward in the browser history."""
|
|
100
100
|
self.session.wait_for_navigation_to_complete()
|
|
101
101
|
self.session.go_forward_in_browsing_context()
|
|
102
|
-
self.session.switch_to_browsing_context(
|
|
102
|
+
self.session.switch_to_browsing_context("")
|
|
103
103
|
|
|
104
104
|
def fullscreen_window(self):
|
|
105
|
-
"""
|
|
105
|
+
"""Invokes the window manager-specific 'full screen' operation."""
|
|
106
106
|
self.session.evaluate_js_function(ENTER_FULLSCREEN, implicit_callback=True)
|
|
107
107
|
|
|
108
108
|
def get(self, url: str):
|
|
109
|
-
"""
|
|
109
|
+
"""Loads a web page in the current browser session."""
|
|
110
110
|
self.session.wait_for_navigation_to_complete()
|
|
111
111
|
self.session.navigate_broswing_context(url)
|
|
112
|
-
self.session.switch_to_browsing_context(
|
|
112
|
+
self.session.switch_to_browsing_context("")
|
|
113
113
|
|
|
114
114
|
def get_cookie(self, name: str) -> Cookie:
|
|
115
|
-
"""
|
|
115
|
+
"""Get a single cookie by name. Returns the cookie if found, None if not."""
|
|
116
116
|
for cookie in self.get_cookies():
|
|
117
117
|
if cookie.name == name:
|
|
118
118
|
return cookie
|
|
119
119
|
|
|
120
120
|
def get_cookies(self) -> list[Cookie]:
|
|
121
|
-
"""
|
|
121
|
+
"""Returns cookies visible in the current session."""
|
|
122
122
|
return list(map(Cookie.from_automation, self.session.get_all_cookies()))
|
|
123
123
|
|
|
124
124
|
@property
|
|
125
125
|
def screenshot_as_base64(self) -> str:
|
|
126
|
-
"""
|
|
126
|
+
"""Gets the screenshot of the current window as a base64 encoded string."""
|
|
127
127
|
return self.session.screenshot_as_base64()
|
|
128
128
|
|
|
129
129
|
def get_window_position(self) -> Point:
|
|
130
|
-
"""
|
|
130
|
+
"""Gets the x,y position of the current window."""
|
|
131
131
|
rect = self.get_window_rect()
|
|
132
132
|
return Point(x=rect.x, y=rect.y)
|
|
133
133
|
|
|
134
134
|
def get_window_rect(self) -> Rect:
|
|
135
|
-
"""
|
|
135
|
+
"""Gets the x, y coordinates of the window as well as height and width of the current window."""
|
|
136
136
|
context = self.session.get_browsing_context()
|
|
137
137
|
return Rect(
|
|
138
|
-
context[
|
|
139
|
-
context[
|
|
138
|
+
context["windowOrigin"]["x"],
|
|
139
|
+
context["windowOrigin"]["y"],
|
|
140
|
+
context["windowSize"]["width"],
|
|
141
|
+
context["windowSize"]["height"],
|
|
140
142
|
)
|
|
141
143
|
|
|
142
144
|
def get_window_size(self) -> Size:
|
|
143
|
-
"""
|
|
145
|
+
"""Gets the width and height of the current window."""
|
|
144
146
|
rect = self.get_window_rect()
|
|
145
147
|
return Size(height=rect.height, width=rect.width)
|
|
146
148
|
|
|
147
149
|
def implicitly_wait(self, time_to_wait):
|
|
148
|
-
"""
|
|
150
|
+
"""Sets a sticky timeout to implicitly wait for an element to be found, or a command to complete."""
|
|
149
151
|
self.session.implicit_wait_timeout = time_to_wait * 1000
|
|
150
152
|
|
|
151
153
|
def maximize_window(self):
|
|
152
|
-
"""
|
|
154
|
+
"""Maximizes the current window."""
|
|
153
155
|
self.session.maximize_window()
|
|
154
156
|
|
|
155
157
|
def minimize_window(self):
|
|
156
|
-
"""
|
|
158
|
+
"""Invokes the window manager-specific 'minimize' operation."""
|
|
157
159
|
self.session.hide_window()
|
|
158
160
|
|
|
159
161
|
@property
|
|
160
162
|
def page_source(self) -> str:
|
|
161
|
-
"""
|
|
162
|
-
return self.session.evaluate_js_function(
|
|
163
|
+
"""Gets the source of the current page."""
|
|
164
|
+
return self.session.evaluate_js_function("function() { return document.documentElement.outerHTML; }")
|
|
163
165
|
|
|
164
166
|
def refresh(self):
|
|
165
|
-
"""
|
|
167
|
+
"""Refreshes the current page."""
|
|
166
168
|
self.session.wait_for_navigation_to_complete()
|
|
167
169
|
self.session.reload_browsing_context()
|
|
168
|
-
self.session.switch_to_browsing_context(
|
|
170
|
+
self.session.switch_to_browsing_context("")
|
|
169
171
|
|
|
170
172
|
def set_window_position(self, x, y):
|
|
171
|
-
"""
|
|
173
|
+
"""Sets the x,y position of the current window."""
|
|
172
174
|
self.set_window_rect(x=int(x, 0), y=int(y, 0))
|
|
173
175
|
|
|
174
176
|
def set_window_rect(self, x=None, y=None, width=None, height=None):
|
|
175
|
-
"""
|
|
177
|
+
"""Sets the x, y coordinates of the window as well as height and width of the current window."""
|
|
176
178
|
self.session.set_window_frame(x, y, width, height)
|
|
177
179
|
|
|
178
180
|
def set_window_size(self, width, height):
|
|
179
|
-
"""
|
|
181
|
+
"""Sets the width and height of the current window."""
|
|
180
182
|
self.set_window_rect(width=int(width, 0), height=int(height, 0))
|
|
181
183
|
|
|
182
184
|
def start_session(self):
|
|
183
|
-
"""
|
|
185
|
+
"""Creates a new session."""
|
|
184
186
|
self.session.start_session()
|
|
185
187
|
|
|
186
188
|
@property
|
|
187
189
|
def title(self) -> str:
|
|
188
|
-
"""
|
|
190
|
+
"""Returns the title of the current page."""
|
|
189
191
|
self.session.wait_for_navigation_to_complete()
|
|
190
|
-
return self.session.evaluate_js_function(
|
|
192
|
+
return self.session.evaluate_js_function("function() { return document.title; }")
|
|
191
193
|
|
|
192
194
|
@property
|
|
193
195
|
def window_handles(self) -> list[str]:
|
|
194
|
-
"""
|
|
196
|
+
"""Returns the handles of all windows within the current session."""
|
|
195
197
|
return self.session.get_window_handles()
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
from pymobiledevice3.exceptions import WirError
|
|
2
|
-
from pymobiledevice3.services.web_protocol.automation_session import
|
|
3
|
-
|
|
2
|
+
from pymobiledevice3.services.web_protocol.automation_session import (
|
|
3
|
+
MODIFIER_TO_KEY,
|
|
4
|
+
RESOURCES,
|
|
5
|
+
VIRTUAL_KEYS,
|
|
6
|
+
KeyboardInteractionType,
|
|
7
|
+
MouseButton,
|
|
8
|
+
MouseInteraction,
|
|
9
|
+
Point,
|
|
10
|
+
Rect,
|
|
11
|
+
Size,
|
|
12
|
+
)
|
|
4
13
|
from pymobiledevice3.services.web_protocol.selenium_api import By, SeleniumApi
|
|
5
14
|
|
|
6
|
-
IS_EDITABLE = (RESOURCES /
|
|
7
|
-
ELEMENT_CLEAR = (RESOURCES /
|
|
8
|
-
GET_ATTRIBUTE = (RESOURCES /
|
|
9
|
-
ELEMENT_ATTRIBUTE = (RESOURCES /
|
|
10
|
-
IS_DISPLAYED = (RESOURCES /
|
|
11
|
-
IS_ENABLED = (RESOURCES /
|
|
12
|
-
FOCUS = (RESOURCES /
|
|
15
|
+
IS_EDITABLE = (RESOURCES / "is_editable.js").read_text()
|
|
16
|
+
ELEMENT_CLEAR = (RESOURCES / "element_clear.js").read_text()
|
|
17
|
+
GET_ATTRIBUTE = (RESOURCES / "get_attribute.js").read_text()
|
|
18
|
+
ELEMENT_ATTRIBUTE = (RESOURCES / "element_attribute.js").read_text()
|
|
19
|
+
IS_DISPLAYED = (RESOURCES / "is_displayed.js").read_text()
|
|
20
|
+
IS_ENABLED = (RESOURCES / "is_enabled.js").read_text()
|
|
21
|
+
FOCUS = (RESOURCES / "focus.js").read_text()
|
|
13
22
|
|
|
14
23
|
|
|
15
24
|
class WebElement(SeleniumApi):
|
|
@@ -20,83 +29,83 @@ class WebElement(SeleniumApi):
|
|
|
20
29
|
"""
|
|
21
30
|
self.session = session
|
|
22
31
|
self.id_ = id_
|
|
23
|
-
self.node_id = id_[f
|
|
32
|
+
self.node_id = id_[f"session-node-{self.session.id_}"]
|
|
24
33
|
|
|
25
34
|
def clear(self):
|
|
26
|
-
"""
|
|
35
|
+
"""Clears the text if it's a text entry element."""
|
|
27
36
|
if not self.is_editable():
|
|
28
37
|
return
|
|
29
|
-
rect, center,
|
|
38
|
+
rect, center, _is_obscured = self._compute_layout()
|
|
30
39
|
if rect is None or center is None:
|
|
31
40
|
return
|
|
32
41
|
self._evaluate_js_function(ELEMENT_CLEAR)
|
|
33
42
|
|
|
34
43
|
def click(self):
|
|
35
|
-
"""
|
|
44
|
+
"""Clicks the element."""
|
|
36
45
|
rect, center, is_obscured = self._compute_layout(use_viewport=True)
|
|
37
46
|
if rect is None or is_obscured or center is None:
|
|
38
47
|
return
|
|
39
|
-
if self.tag_name ==
|
|
48
|
+
if self.tag_name == "option":
|
|
40
49
|
self._select_option_element()
|
|
41
50
|
else:
|
|
42
51
|
self.session.perform_mouse_interaction(center.x, center.y, MouseButton.LEFT, MouseInteraction.SINGLE_CLICK)
|
|
43
52
|
|
|
44
53
|
def find_element(self, by=By.ID, value=None):
|
|
45
|
-
"""
|
|
54
|
+
"""Find an element given a By strategy and locator."""
|
|
46
55
|
elem = self.session.find_elements(by, value, root=self.id_)
|
|
47
56
|
return None if elem is None else WebElement(self.session, elem)
|
|
48
57
|
|
|
49
58
|
def find_elements(self, by=By.ID, value=None):
|
|
50
|
-
"""
|
|
59
|
+
"""Find elements given a By strategy and locator."""
|
|
51
60
|
elements = self.session.find_elements(by, value, single=False, root=self.id_)
|
|
52
|
-
return
|
|
61
|
+
return [WebElement(self.session, elem) for elem in elements]
|
|
53
62
|
|
|
54
63
|
def get_attribute(self, name: str) -> str:
|
|
55
|
-
"""
|
|
56
|
-
return self.session.execute_script(f
|
|
64
|
+
"""Gets the given attribute or property of the element."""
|
|
65
|
+
return self.session.execute_script(f"return ({GET_ATTRIBUTE}).apply(null, arguments);", self.id_, name)
|
|
57
66
|
|
|
58
67
|
def get_dom_attribute(self, name: str) -> str:
|
|
59
|
-
"""
|
|
68
|
+
"""Gets the given attribute of the element."""
|
|
60
69
|
return self._evaluate_js_function(ELEMENT_ATTRIBUTE, name)
|
|
61
70
|
|
|
62
71
|
def get_property(self, name: str) -> str:
|
|
63
|
-
"""
|
|
64
|
-
return self._evaluate_js_function(f
|
|
72
|
+
"""Gets the given property of the element."""
|
|
73
|
+
return self._evaluate_js_function(f"function(element) {{ return element.{name}; }}")
|
|
65
74
|
|
|
66
75
|
def is_displayed(self) -> bool:
|
|
67
|
-
"""
|
|
68
|
-
return self.session.execute_script(f
|
|
76
|
+
"""Whether the element is visible to a user."""
|
|
77
|
+
return self.session.execute_script(f"return ({IS_DISPLAYED}).apply(null, arguments);", self.id_)
|
|
69
78
|
|
|
70
79
|
def is_enabled(self) -> bool:
|
|
71
|
-
"""
|
|
80
|
+
"""Returns whether the element is enabled."""
|
|
72
81
|
return self._evaluate_js_function(IS_ENABLED)
|
|
73
82
|
|
|
74
83
|
def is_selected(self) -> bool:
|
|
75
|
-
"""
|
|
76
|
-
return bool(self.get_dom_attribute(
|
|
84
|
+
"""Returns whether the element is selected. Can be used to check if a checkbox or radio button is selected."""
|
|
85
|
+
return bool(self.get_dom_attribute("selected"))
|
|
77
86
|
|
|
78
87
|
@property
|
|
79
88
|
def location(self) -> Point:
|
|
80
|
-
"""
|
|
89
|
+
"""The location of the element in the renderable canvas."""
|
|
81
90
|
rect = self.rect
|
|
82
91
|
return Point(x=rect.x, y=rect.y)
|
|
83
92
|
|
|
84
93
|
@property
|
|
85
94
|
def location_once_scrolled_into_view(self) -> Point:
|
|
86
|
-
"""
|
|
95
|
+
"""Returns the top lefthand corner location on the screen, or ``None`` if the element is not visible."""
|
|
87
96
|
rect = self.session.execute_script(
|
|
88
|
-
|
|
97
|
+
"arguments[0].scrollIntoView(true); return arguments[0].getBoundingClientRect(); ", self.id_
|
|
89
98
|
)
|
|
90
|
-
return Point(x=round(rect[
|
|
99
|
+
return Point(x=round(rect["x"]), y=round(rect["y"]))
|
|
91
100
|
|
|
92
101
|
@property
|
|
93
102
|
def rect(self) -> Rect:
|
|
94
|
-
"""
|
|
103
|
+
"""The size and location of the element."""
|
|
95
104
|
return self._compute_layout(scroll_if_needed=False)[0]
|
|
96
105
|
|
|
97
106
|
@property
|
|
98
107
|
def screenshot_as_base64(self) -> str:
|
|
99
|
-
"""
|
|
108
|
+
"""Gets the screenshot of the current element as a base64 encoded string."""
|
|
100
109
|
return self.session.screenshot_as_base64(scroll=True, node_id=self.node_id)
|
|
101
110
|
|
|
102
111
|
def send_keys(self, value):
|
|
@@ -110,80 +119,82 @@ class WebElement(SeleniumApi):
|
|
|
110
119
|
for key in value:
|
|
111
120
|
if key in VIRTUAL_KEYS:
|
|
112
121
|
virtual_key, modifier = VIRTUAL_KEYS[key]
|
|
113
|
-
interaction = {
|
|
122
|
+
interaction = {"type": KeyboardInteractionType.INSERT_BY_KEY, "key": virtual_key}
|
|
114
123
|
if modifier is not None:
|
|
115
124
|
sticky_modifier ^= {modifier}
|
|
116
|
-
interaction[
|
|
117
|
-
|
|
125
|
+
interaction["type"] = (
|
|
126
|
+
KeyboardInteractionType.KEY_PRESS
|
|
127
|
+
if modifier in sticky_modifier
|
|
128
|
+
else KeyboardInteractionType.KEY_RELEASE
|
|
129
|
+
)
|
|
118
130
|
interactions.append(interaction)
|
|
119
131
|
else:
|
|
120
|
-
interactions.append({
|
|
132
|
+
interactions.append({"type": KeyboardInteractionType.INSERT_BY_KEY, "text": key})
|
|
121
133
|
for modifier in sticky_modifier:
|
|
122
|
-
interactions.append({
|
|
134
|
+
interactions.append({"type": KeyboardInteractionType.KEY_RELEASE, "key": MODIFIER_TO_KEY[modifier]})
|
|
123
135
|
self.session.perform_keyboard_interactions(interactions)
|
|
124
136
|
|
|
125
137
|
@property
|
|
126
138
|
def size(self) -> Size:
|
|
127
|
-
"""
|
|
139
|
+
"""The size of the element."""
|
|
128
140
|
rect = self.rect
|
|
129
141
|
return Size(height=rect.height, width=rect.width)
|
|
130
142
|
|
|
131
143
|
def submit(self):
|
|
132
|
-
"""
|
|
144
|
+
"""Submits a form."""
|
|
133
145
|
form = self.find_element(By.XPATH, "./ancestor-or-self::form")
|
|
134
146
|
submit_code = (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
147
|
+
"var e = arguments[0].ownerDocument.createEvent('Event');"
|
|
148
|
+
"e.initEvent('submit', true, true);"
|
|
149
|
+
"if (arguments[0].dispatchEvent(e)) { arguments[0].submit() }"
|
|
138
150
|
)
|
|
139
151
|
self.session.execute_script(submit_code, form.id_)
|
|
140
152
|
|
|
141
153
|
@property
|
|
142
154
|
def tag_name(self) -> str:
|
|
143
|
-
"""
|
|
144
|
-
return self._evaluate_js_function(
|
|
155
|
+
"""This element's ``tagName`` property."""
|
|
156
|
+
return self._evaluate_js_function("function(element) { return element.tagName.toLowerCase() }")
|
|
145
157
|
|
|
146
158
|
@property
|
|
147
159
|
def text(self) -> str:
|
|
148
|
-
"""
|
|
160
|
+
"""The text of the element."""
|
|
149
161
|
return self._evaluate_js_function(
|
|
150
162
|
'function(element) { return element.innerText.replace(/^[^\\S\\xa0]+|[^\\S\\xa0]+$/g, "") }'
|
|
151
163
|
)
|
|
152
164
|
|
|
153
165
|
def touch(self):
|
|
154
|
-
"""
|
|
155
|
-
|
|
166
|
+
"""Simulate touch interaction on the element."""
|
|
167
|
+
_rect, center, _is_obscured = self._compute_layout(use_viewport=True)
|
|
156
168
|
self.session.perform_interaction_sequence(
|
|
157
|
-
[{
|
|
158
|
-
[{
|
|
159
|
-
{'sourceId': self.session.id_, 'location': {'x': center.x, 'y': center.y}}
|
|
160
|
-
]}]
|
|
169
|
+
[{"sourceId": self.session.id_, "sourceType": "Touch"}],
|
|
170
|
+
[{"states": [{"sourceId": self.session.id_, "location": {"x": center.x, "y": center.y}}]}],
|
|
161
171
|
)
|
|
162
172
|
|
|
163
173
|
def value_of_css_property(self, property_name) -> str:
|
|
164
|
-
"""
|
|
174
|
+
"""The value of a CSS property."""
|
|
165
175
|
return self._evaluate_js_function(
|
|
166
|
-
|
|
176
|
+
"function(element) {"
|
|
167
177
|
f' return document.defaultView.getComputedStyle(element).getPropertyValue("{property_name}"); '
|
|
168
|
-
|
|
178
|
+
"}"
|
|
169
179
|
)
|
|
170
180
|
|
|
171
181
|
def is_editable(self) -> bool:
|
|
172
|
-
"""
|
|
182
|
+
"""Returns whether the element is editable."""
|
|
173
183
|
return self._evaluate_js_function(IS_EDITABLE)
|
|
174
184
|
|
|
175
185
|
def _compute_layout(self, scroll_if_needed=True, use_viewport=False):
|
|
176
186
|
try:
|
|
177
|
-
result = self.session.compute_element_layout(
|
|
178
|
-
|
|
187
|
+
result = self.session.compute_element_layout(
|
|
188
|
+
self.node_id, scroll_if_needed, "LayoutViewport" if use_viewport else "Page"
|
|
189
|
+
)
|
|
179
190
|
except WirError:
|
|
180
191
|
return
|
|
181
192
|
|
|
182
|
-
origin = result[
|
|
183
|
-
size = result[
|
|
184
|
-
rect = Rect(x=round(origin[
|
|
185
|
-
center = Point(x=result[
|
|
186
|
-
return rect, center, result[
|
|
193
|
+
origin = result["rect"]["origin"]
|
|
194
|
+
size = result["rect"]["size"]
|
|
195
|
+
rect = Rect(x=round(origin["x"]), y=round(origin["y"]), width=size["width"], height=size["height"])
|
|
196
|
+
center = Point(x=result["inViewCenterPoint"]["x"], y=result["inViewCenterPoint"]["y"])
|
|
197
|
+
return rect, center, result["isObscured"]
|
|
187
198
|
|
|
188
199
|
def _select_option_element(self):
|
|
189
200
|
self.session.select_option_element(self.node_id)
|