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