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.
Files changed (164) hide show
  1. misc/plist_sniffer.py +15 -15
  2. misc/remotexpc_sniffer.py +29 -28
  3. misc/understanding_idevice_protocol_layers.md +15 -10
  4. pymobiledevice3/__main__.py +317 -127
  5. pymobiledevice3/_version.py +22 -4
  6. pymobiledevice3/bonjour.py +358 -113
  7. pymobiledevice3/ca.py +253 -16
  8. pymobiledevice3/cli/activation.py +31 -23
  9. pymobiledevice3/cli/afc.py +49 -40
  10. pymobiledevice3/cli/amfi.py +16 -21
  11. pymobiledevice3/cli/apps.py +87 -42
  12. pymobiledevice3/cli/backup.py +160 -90
  13. pymobiledevice3/cli/bonjour.py +44 -40
  14. pymobiledevice3/cli/cli_common.py +204 -198
  15. pymobiledevice3/cli/companion_proxy.py +14 -14
  16. pymobiledevice3/cli/crash.py +105 -56
  17. pymobiledevice3/cli/developer/__init__.py +62 -0
  18. pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
  19. pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
  20. pymobiledevice3/cli/developer/arbitration.py +50 -0
  21. pymobiledevice3/cli/developer/condition.py +33 -0
  22. pymobiledevice3/cli/developer/core_device.py +294 -0
  23. pymobiledevice3/cli/developer/debugserver.py +244 -0
  24. pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
  25. pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
  26. pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
  27. pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
  28. pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
  29. pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
  30. pymobiledevice3/cli/developer/simulate_location.py +51 -0
  31. pymobiledevice3/cli/diagnostics/__init__.py +75 -0
  32. pymobiledevice3/cli/diagnostics/battery.py +47 -0
  33. pymobiledevice3/cli/idam.py +42 -0
  34. pymobiledevice3/cli/lockdown.py +108 -103
  35. pymobiledevice3/cli/mounter.py +158 -99
  36. pymobiledevice3/cli/notification.py +38 -26
  37. pymobiledevice3/cli/pcap.py +45 -24
  38. pymobiledevice3/cli/power_assertion.py +18 -17
  39. pymobiledevice3/cli/processes.py +17 -23
  40. pymobiledevice3/cli/profile.py +165 -109
  41. pymobiledevice3/cli/provision.py +35 -34
  42. pymobiledevice3/cli/remote.py +217 -129
  43. pymobiledevice3/cli/restore.py +159 -143
  44. pymobiledevice3/cli/springboard.py +63 -53
  45. pymobiledevice3/cli/syslog.py +193 -86
  46. pymobiledevice3/cli/usbmux.py +73 -33
  47. pymobiledevice3/cli/version.py +5 -7
  48. pymobiledevice3/cli/webinspector.py +376 -214
  49. pymobiledevice3/common.py +3 -1
  50. pymobiledevice3/exceptions.py +182 -58
  51. pymobiledevice3/irecv.py +52 -53
  52. pymobiledevice3/irecv_devices.py +1489 -464
  53. pymobiledevice3/lockdown.py +473 -275
  54. pymobiledevice3/lockdown_service_provider.py +15 -8
  55. pymobiledevice3/osu/os_utils.py +27 -9
  56. pymobiledevice3/osu/posix_util.py +34 -15
  57. pymobiledevice3/osu/win_util.py +14 -8
  58. pymobiledevice3/pair_records.py +102 -21
  59. pymobiledevice3/remote/common.py +8 -4
  60. pymobiledevice3/remote/core_device/app_service.py +94 -67
  61. pymobiledevice3/remote/core_device/core_device_service.py +17 -14
  62. pymobiledevice3/remote/core_device/device_info.py +5 -5
  63. pymobiledevice3/remote/core_device/diagnostics_service.py +19 -4
  64. pymobiledevice3/remote/core_device/file_service.py +53 -23
  65. pymobiledevice3/remote/remote_service_discovery.py +79 -45
  66. pymobiledevice3/remote/remotexpc.py +73 -44
  67. pymobiledevice3/remote/tunnel_service.py +442 -317
  68. pymobiledevice3/remote/utils.py +14 -13
  69. pymobiledevice3/remote/xpc_message.py +145 -125
  70. pymobiledevice3/resources/dsc_uuid_map.py +19 -19
  71. pymobiledevice3/resources/firmware_notifications.py +20 -16
  72. pymobiledevice3/resources/notifications.txt +144 -0
  73. pymobiledevice3/restore/asr.py +27 -27
  74. pymobiledevice3/restore/base_restore.py +110 -21
  75. pymobiledevice3/restore/consts.py +87 -66
  76. pymobiledevice3/restore/device.py +59 -12
  77. pymobiledevice3/restore/fdr.py +46 -48
  78. pymobiledevice3/restore/ftab.py +19 -19
  79. pymobiledevice3/restore/img4.py +163 -0
  80. pymobiledevice3/restore/mbn.py +587 -0
  81. pymobiledevice3/restore/recovery.py +151 -151
  82. pymobiledevice3/restore/restore.py +562 -544
  83. pymobiledevice3/restore/restore_options.py +131 -110
  84. pymobiledevice3/restore/restored_client.py +51 -31
  85. pymobiledevice3/restore/tss.py +385 -267
  86. pymobiledevice3/service_connection.py +252 -59
  87. pymobiledevice3/services/accessibilityaudit.py +202 -120
  88. pymobiledevice3/services/afc.py +962 -365
  89. pymobiledevice3/services/amfi.py +24 -30
  90. pymobiledevice3/services/companion.py +23 -19
  91. pymobiledevice3/services/crash_reports.py +71 -47
  92. pymobiledevice3/services/debugserver_applist.py +3 -3
  93. pymobiledevice3/services/device_arbitration.py +8 -8
  94. pymobiledevice3/services/device_link.py +101 -79
  95. pymobiledevice3/services/diagnostics.py +973 -967
  96. pymobiledevice3/services/dtfetchsymbols.py +8 -8
  97. pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
  98. pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
  99. pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
  100. pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
  101. pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
  102. pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
  103. pymobiledevice3/services/dvt/instruments/device_info.py +20 -11
  104. pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
  105. pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
  106. pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
  107. pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
  108. pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
  109. pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
  110. pymobiledevice3/services/dvt/instruments/process_control.py +35 -10
  111. pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
  112. pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
  113. pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
  114. pymobiledevice3/services/file_relay.py +10 -10
  115. pymobiledevice3/services/heartbeat.py +9 -8
  116. pymobiledevice3/services/house_arrest.py +16 -15
  117. pymobiledevice3/services/idam.py +20 -0
  118. pymobiledevice3/services/installation_proxy.py +173 -81
  119. pymobiledevice3/services/lockdown_service.py +20 -10
  120. pymobiledevice3/services/misagent.py +22 -19
  121. pymobiledevice3/services/mobile_activation.py +147 -64
  122. pymobiledevice3/services/mobile_config.py +331 -294
  123. pymobiledevice3/services/mobile_image_mounter.py +141 -113
  124. pymobiledevice3/services/mobilebackup2.py +203 -145
  125. pymobiledevice3/services/notification_proxy.py +11 -11
  126. pymobiledevice3/services/os_trace.py +134 -74
  127. pymobiledevice3/services/pcapd.py +314 -302
  128. pymobiledevice3/services/power_assertion.py +10 -9
  129. pymobiledevice3/services/preboard.py +4 -4
  130. pymobiledevice3/services/remote_fetch_symbols.py +21 -14
  131. pymobiledevice3/services/remote_server.py +176 -146
  132. pymobiledevice3/services/restore_service.py +16 -16
  133. pymobiledevice3/services/screenshot.py +15 -12
  134. pymobiledevice3/services/simulate_location.py +7 -7
  135. pymobiledevice3/services/springboard.py +15 -15
  136. pymobiledevice3/services/syslog.py +5 -5
  137. pymobiledevice3/services/web_protocol/alert.py +11 -11
  138. pymobiledevice3/services/web_protocol/automation_session.py +251 -239
  139. pymobiledevice3/services/web_protocol/cdp_screencast.py +46 -37
  140. pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
  141. pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
  142. pymobiledevice3/services/web_protocol/driver.py +114 -111
  143. pymobiledevice3/services/web_protocol/element.py +124 -111
  144. pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
  145. pymobiledevice3/services/web_protocol/selenium_api.py +49 -49
  146. pymobiledevice3/services/web_protocol/session_protocol.py +18 -12
  147. pymobiledevice3/services/web_protocol/switch_to.py +30 -27
  148. pymobiledevice3/services/webinspector.py +189 -155
  149. pymobiledevice3/tcp_forwarder.py +87 -69
  150. pymobiledevice3/tunneld/__init__.py +0 -0
  151. pymobiledevice3/tunneld/api.py +63 -0
  152. pymobiledevice3/tunneld/server.py +603 -0
  153. pymobiledevice3/usbmux.py +198 -147
  154. pymobiledevice3/utils.py +14 -11
  155. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +55 -28
  156. pymobiledevice3-7.0.6.dist-info/RECORD +188 -0
  157. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +1 -1
  158. pymobiledevice3/cli/developer.py +0 -1215
  159. pymobiledevice3/cli/diagnostics.py +0 -99
  160. pymobiledevice3/tunneld.py +0 -524
  161. pymobiledevice3-4.14.6.dist-info/RECORD +0 -168
  162. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
  163. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info/licenses}/LICENSE +0 -0
  164. {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 / 'enter_fullscreen.js').read_text()
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 = 'None'
22
+ sameSite: str = "None"
22
23
 
23
24
  @classmethod
24
25
  def from_automation(cls, d):
25
- d.pop('size')
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
- """ Adds a cookie to your current session. """
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
- """ Goes one step backward in the browser history. """
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
- """ Closes the current window. """
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
- """ Gets the URL of the current page. """
56
- self.session.wait_for_navigation_to_complete()
57
- return self.session.get_browsing_context()['url']
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
- """ Returns the handle of the current window. """
62
- return self.session.get_browsing_context()['handle']
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
- """ Delete all cookies in the scope of the session. """
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
- """ Deletes a single cookie with the given name. """
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
- """ Find an element given a By strategy and locator. """
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
- """ Find elements given a By strategy and locator. """
95
- elements = self.session.find_elements(by, value, single=False)
96
- return list(map(lambda elem: WebElement(self.session, elem), elements))
97
-
98
- def forward(self):
99
- """ Goes one step forward in the browser history. """
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
- """ Invokes the window manager-specific 'full screen' operation. """
106
- self.session.evaluate_js_function(ENTER_FULLSCREEN, implicit_callback=True)
107
-
108
- def get(self, url: str):
109
- """ Loads a web page in the current browser session. """
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
- """ Get a single cookie by name. Returns the cookie if found, None if not. """
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
- """ Returns cookies visible in the current session. """
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
- """ Gets the screenshot of the current window as a base64 encoded string. """
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
- """ Gets the x,y position of the current window. """
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
- """ Gets the x, y coordinates of the window as well as height and width of the current window. """
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['windowOrigin']['x'], context['windowOrigin']['y'],
139
- context['windowSize']['width'], context['windowSize']['height']
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
- """ Gets the width and height of the current window. """
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
- """ Sets a sticky timeout to implicitly wait for an element to be found, or a command to complete. """
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
- """ Maximizes the current window. """
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
- """ Invokes the window manager-specific 'minimize' operation. """
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
- """ Gets the source of the current page. """
162
- return self.session.evaluate_js_function('function() { return document.documentElement.outerHTML; }')
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
- """ Refreshes the current page. """
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
- """ Sets the x,y position of the current window. """
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
- """ Sets the x, y coordinates of the window as well as height and width of the current window. """
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
- """ Sets the width and height of the current window. """
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
- """ Creates a new session. """
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
- """ Returns the title of the current page. """
189
- self.session.wait_for_navigation_to_complete()
190
- return self.session.evaluate_js_function('function() { return document.title; }')
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
- """ Returns the handles of all windows within the current session. """
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 MODIFIER_TO_KEY, RESOURCES, VIRTUAL_KEYS, \
3
- KeyboardInteractionType, MouseButton, MouseInteraction, Point, Rect, Size
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 / 'is_editable.js').read_text()
7
- ELEMENT_CLEAR = (RESOURCES / 'element_clear.js').read_text()
8
- GET_ATTRIBUTE = (RESOURCES / 'get_attribute.js').read_text()
9
- ELEMENT_ATTRIBUTE = (RESOURCES / 'element_attribute.js').read_text()
10
- IS_DISPLAYED = (RESOURCES / 'is_displayed.js').read_text()
11
- IS_ENABLED = (RESOURCES / 'is_enabled.js').read_text()
12
- FOCUS = (RESOURCES / 'focus.js').read_text()
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'session-node-{self.session.id_}']
32
+ self.node_id = id_[f"session-node-{self.session.id_}"]
24
33
 
25
- def clear(self):
26
- """ Clears the text if it's a text entry element. """
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, is_obscured = self._compute_layout()
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
- """ Clicks the element. """
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 == 'option':
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(center.x, center.y, MouseButton.LEFT, MouseInteraction.SINGLE_CLICK)
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
- """ Find an element given a By strategy and locator. """
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
- """ Find elements given a By strategy and locator. """
51
- elements = self.session.find_elements(by, value, single=False, root=self.id_)
52
- return list(map(lambda elem: WebElement(self.session, elem), elements))
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
- """ Gets the given attribute or property of the element. """
56
- return self.session.execute_script(f'return ({GET_ATTRIBUTE}).apply(null, arguments);', self.id_, name)
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
- """ Gets the given attribute of the element. """
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
- """ Gets the given property of the element. """
64
- return self._evaluate_js_function(f'function(element) {{ return element.{name}; }}')
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
- """ Whether the element is visible to a user. """
68
- return self.session.execute_script(f'return ({IS_DISPLAYED}).apply(null, arguments);', self.id_)
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
- """ Returns whether the element is enabled. """
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
- """ Returns whether the element is selected. Can be used to check if a checkbox or radio button is selected. """
76
- return bool(self.get_dom_attribute('selected'))
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
- """ The location of the element in the renderable canvas. """
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
- """ Returns the top lefthand corner location on the screen, or ``None`` if the element is not visible. """
87
- rect = self.session.execute_script(
88
- 'arguments[0].scrollIntoView(true); return arguments[0].getBoundingClientRect(); ', self.id_
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['x']), y=round(rect['y']))
101
+ return Point(x=round(rect["x"]), y=round(rect["y"]))
91
102
 
92
103
  @property
93
- def rect(self) -> Rect:
94
- """ The size and location of the element. """
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
- """ Gets the screenshot of the current element as a base64 encoded string. """
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 = {'type': KeyboardInteractionType.INSERT_BY_KEY, 'key': virtual_key}
124
+ interaction = {"type": KeyboardInteractionType.INSERT_BY_KEY, "key": virtual_key}
114
125
  if modifier is not None:
115
126
  sticky_modifier ^= {modifier}
116
- interaction['type'] = (KeyboardInteractionType.KEY_PRESS if modifier in sticky_modifier
117
- else KeyboardInteractionType.KEY_RELEASE)
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({'type': KeyboardInteractionType.INSERT_BY_KEY, 'text': key})
134
+ interactions.append({"type": KeyboardInteractionType.INSERT_BY_KEY, "text": key})
121
135
  for modifier in sticky_modifier:
122
- interactions.append({'type': KeyboardInteractionType.KEY_RELEASE, 'key': MODIFIER_TO_KEY[modifier]})
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
- """ The size of the element. """
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
- """ Submits a form. """
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
- 'var e = arguments[0].ownerDocument.createEvent(\'Event\');'
136
- 'e.initEvent(\'submit\', true, true);'
137
- 'if (arguments[0].dispatchEvent(e)) { arguments[0].submit() }'
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
- """ This element's ``tagName`` property. """
144
- return self._evaluate_js_function('function(element) { return element.tagName.toLowerCase() }')
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
- """ The text of the element. """
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
- """ Simulate touch interaction on the element. """
155
- rect, center, is_obscured = self._compute_layout(use_viewport=True)
156
- self.session.perform_interaction_sequence(
157
- [{'sourceId': self.session.id_, 'sourceType': 'Touch'}],
158
- [{'states': [
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
- """ The value of a CSS property. """
165
- return self._evaluate_js_function(
166
- 'function(element) {'
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
- """ Returns whether the element is editable. """
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(self.node_id, scroll_if_needed,
178
- 'LayoutViewport' if use_viewport else 'Page')
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['rect']['origin']
183
- size = result['rect']['size']
184
- rect = Rect(x=round(origin['x']), y=round(origin['y']), width=size['width'], height=size['height'])
185
- center = Point(x=result['inViewCenterPoint']['x'], y=result['inViewCenterPoint']['y'])
186
- return rect, center, result['isObscured']
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_