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
|
@@ -8,15 +8,15 @@ from pymobiledevice3.exceptions import InspectorEvaluateError
|
|
|
8
8
|
from pymobiledevice3.services.web_protocol.session_protocol import SessionProtocol
|
|
9
9
|
|
|
10
10
|
logger = logging.getLogger(__name__)
|
|
11
|
-
console_logger = logging.getLogger(
|
|
12
|
-
heap_logger = logging.getLogger(
|
|
11
|
+
console_logger = logging.getLogger("webinspector.console")
|
|
12
|
+
heap_logger = logging.getLogger("webinspector.heap")
|
|
13
13
|
|
|
14
14
|
webinspector_logger_handlers = {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
"log": console_logger.info,
|
|
16
|
+
"info": console_logger.info,
|
|
17
|
+
"error": console_logger.error,
|
|
18
|
+
"debug": console_logger.debug,
|
|
19
|
+
"warning": console_logger.warning,
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
|
|
@@ -24,8 +24,8 @@ class JSObjectPreview(UserDict):
|
|
|
24
24
|
def __init__(self, properties: list[dict]):
|
|
25
25
|
super().__init__()
|
|
26
26
|
for p in properties:
|
|
27
|
-
name = p[
|
|
28
|
-
value = p[
|
|
27
|
+
name = p["name"]
|
|
28
|
+
value = p["value"]
|
|
29
29
|
self.data[name] = value
|
|
30
30
|
|
|
31
31
|
|
|
@@ -33,26 +33,25 @@ class JSObjectProperties(UserDict):
|
|
|
33
33
|
def __init__(self, properties: list[dict]):
|
|
34
34
|
super().__init__()
|
|
35
35
|
for p in properties:
|
|
36
|
-
name = p[
|
|
37
|
-
if name ==
|
|
38
|
-
self.class_name = p[
|
|
36
|
+
name = p["name"]
|
|
37
|
+
if name == "__proto__":
|
|
38
|
+
self.class_name = p["value"]["className"]
|
|
39
39
|
continue
|
|
40
40
|
# test if a getter/setter first
|
|
41
|
-
value = p.get(
|
|
41
|
+
value = p.get("get", p.get("set", p.get("value")))
|
|
42
42
|
if value is None:
|
|
43
43
|
continue
|
|
44
|
-
preview = value.get(
|
|
44
|
+
preview = value.get("preview")
|
|
45
45
|
if preview is not None:
|
|
46
|
-
value = JSObjectPreview(preview[
|
|
47
|
-
elif value.get(
|
|
48
|
-
value = value[
|
|
46
|
+
value = JSObjectPreview(preview["properties"])
|
|
47
|
+
elif value.get("className") == "Function":
|
|
48
|
+
value = value["description"]
|
|
49
49
|
else:
|
|
50
|
-
value = value.get(
|
|
50
|
+
value = value.get("value")
|
|
51
51
|
self.data[name] = value
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
class InspectorSession:
|
|
55
|
-
|
|
56
55
|
def __init__(self, protocol: SessionProtocol, target_id: Optional[str] = None):
|
|
57
56
|
"""
|
|
58
57
|
:param pymobiledevice3.services.web_protocol.session_protocol.SessionProtocol protocol: Session protocol.
|
|
@@ -64,14 +63,14 @@ class InspectorSession:
|
|
|
64
63
|
self._dispatch_message_responses = {}
|
|
65
64
|
|
|
66
65
|
self.response_methods = {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
66
|
+
"Target.targetCreated": self._target_created,
|
|
67
|
+
"Target.targetDestroyed": self._target_destroyed,
|
|
68
|
+
"Target.dispatchMessageFromTarget": self._target_dispatch_message_from_target,
|
|
69
|
+
"Target.didCommitProvisionalTarget": self._target_did_commit_provisional_target,
|
|
70
|
+
"Console.messageAdded": self._console_message_added,
|
|
71
|
+
"Console.messagesCleared": lambda _: _,
|
|
72
|
+
"Console.messageRepeatCountUpdated": self._console_message_repeated_count_updated,
|
|
73
|
+
"Heap.garbageCollected": self._heap_garbage_collected,
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
self._receive_task = asyncio.create_task(self._receive_loop())
|
|
@@ -88,64 +87,65 @@ class InspectorSession:
|
|
|
88
87
|
while not protocol.inspector.wir_events:
|
|
89
88
|
await asyncio.sleep(0)
|
|
90
89
|
created = protocol.inspector.wir_events.pop(0)
|
|
91
|
-
while
|
|
90
|
+
while "targetInfo" not in created["params"]:
|
|
92
91
|
created = protocol.inspector.wir_events.pop(0)
|
|
93
|
-
target_id = created[
|
|
94
|
-
logger.info(f
|
|
92
|
+
target_id = created["params"]["targetInfo"]["targetId"]
|
|
93
|
+
logger.info(f"Created: {target_id}")
|
|
95
94
|
target = cls(protocol, target_id)
|
|
96
95
|
return target
|
|
97
96
|
|
|
98
97
|
def set_target_id(self, target_id):
|
|
99
98
|
self.target_id = target_id
|
|
100
|
-
logger.info(f
|
|
99
|
+
logger.info(f"Changed to: {target_id}")
|
|
101
100
|
|
|
102
101
|
async def heap_gc(self):
|
|
103
|
-
return await self.send_command(
|
|
102
|
+
return await self.send_command("Heap.gc")
|
|
104
103
|
|
|
105
104
|
async def heap_snapshot(self):
|
|
106
|
-
snapshot = await self.send_command(
|
|
105
|
+
snapshot = await self.send_command("Heap.snapshot")
|
|
107
106
|
if self.target_id is not None:
|
|
108
|
-
snapshot = json.loads(snapshot[
|
|
109
|
-
snapshot = json.loads(snapshot)[
|
|
107
|
+
snapshot = json.loads(snapshot["params"]["message"])
|
|
108
|
+
snapshot = json.loads(snapshot)["result"]["snapshotData"]
|
|
110
109
|
return snapshot
|
|
111
110
|
|
|
112
111
|
async def heap_enable(self):
|
|
113
|
-
return await self.send_command(
|
|
112
|
+
return await self.send_command("Heap.enable")
|
|
114
113
|
|
|
115
114
|
async def console_enable(self):
|
|
116
|
-
return await self.send_command(
|
|
115
|
+
return await self.send_command("Console.enable")
|
|
117
116
|
|
|
118
117
|
async def runtime_enable(self):
|
|
119
|
-
return await self.send_command(
|
|
118
|
+
return await self.send_command("Runtime.enable")
|
|
120
119
|
|
|
121
120
|
async def send_command(self, method: str, **kwargs):
|
|
122
121
|
if self.target_id is None:
|
|
123
122
|
return await self.protocol.send_receive(method, **kwargs)
|
|
124
123
|
else:
|
|
125
|
-
return await self.send_and_receive({
|
|
124
|
+
return await self.send_and_receive({"method": method, "params": kwargs})
|
|
126
125
|
|
|
127
126
|
async def runtime_evaluate(self, exp: str, return_by_value: bool = False):
|
|
128
127
|
# if the expression is dict, it's needed to be in ()
|
|
129
128
|
exp = exp.strip()
|
|
130
|
-
if exp:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
129
|
+
if exp and exp[0] == "{" and exp[-1] == "}":
|
|
130
|
+
exp = f"({exp})"
|
|
131
|
+
|
|
132
|
+
response = await self.send_and_receive({
|
|
133
|
+
"method": "Runtime.evaluate",
|
|
134
|
+
"params": {
|
|
135
|
+
"expression": exp,
|
|
136
|
+
"objectGroup": "console",
|
|
137
|
+
"includeCommandLineAPI": True,
|
|
138
|
+
"doNotPauseOnExceptionsAndMuteConsole": False,
|
|
139
|
+
"silent": False,
|
|
140
|
+
"returnByValue": return_by_value,
|
|
141
|
+
"generatePreview": True,
|
|
142
|
+
"userGesture": True,
|
|
143
|
+
"awaitPromise": False,
|
|
144
|
+
"replMode": True,
|
|
145
|
+
"allowUnsafeEvalBlockedByCSP": False,
|
|
146
|
+
"uniqueContextId": "0.1",
|
|
147
|
+
},
|
|
148
|
+
})
|
|
149
149
|
|
|
150
150
|
return await self._parse_runtime_evaluate(response)
|
|
151
151
|
|
|
@@ -154,18 +154,19 @@ class InspectorSession:
|
|
|
154
154
|
|
|
155
155
|
async def send_and_receive(self, message: dict) -> dict:
|
|
156
156
|
if self.target_id is None:
|
|
157
|
-
message_id = await self.protocol.send_command(message[
|
|
157
|
+
message_id = await self.protocol.send_command(message["method"], **message.get("params", {}))
|
|
158
158
|
return await self.protocol.wait_for_message(message_id)
|
|
159
159
|
else:
|
|
160
160
|
message_id = await self.send_message_to_target(message)
|
|
161
161
|
return await self.receive_response_by_id(message_id)
|
|
162
162
|
|
|
163
163
|
async def send_message_to_target(self, message: dict) -> int:
|
|
164
|
-
message[
|
|
164
|
+
message["id"] = self.message_id
|
|
165
165
|
self.message_id += 1
|
|
166
|
-
await self.protocol.send_command(
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
await self.protocol.send_command(
|
|
167
|
+
"Target.sendMessageToTarget", targetId=self.target_id, message=json.dumps(message)
|
|
168
|
+
)
|
|
169
|
+
return message["id"]
|
|
169
170
|
|
|
170
171
|
async def _receive_loop(self):
|
|
171
172
|
while True:
|
|
@@ -173,11 +174,11 @@ class InspectorSession:
|
|
|
173
174
|
await asyncio.sleep(0)
|
|
174
175
|
|
|
175
176
|
response = self.protocol.inspector.wir_events.pop(0)
|
|
176
|
-
response_method = response[
|
|
177
|
+
response_method = response["method"]
|
|
177
178
|
if response_method in self.response_methods:
|
|
178
179
|
self.response_methods[response_method](response)
|
|
179
180
|
else:
|
|
180
|
-
logger.error(f
|
|
181
|
+
logger.error(f"Unknown response: {response}")
|
|
181
182
|
|
|
182
183
|
async def receive_response_by_id(self, message_id: int) -> dict:
|
|
183
184
|
while True:
|
|
@@ -187,64 +188,67 @@ class InspectorSession:
|
|
|
187
188
|
|
|
188
189
|
async def get_properties(self, object_id: str) -> JSObjectProperties:
|
|
189
190
|
message = await self.send_command(
|
|
190
|
-
|
|
191
|
+
"Runtime.getProperties", objectId=object_id, ownProperties=True, generatePreview=True
|
|
192
|
+
)
|
|
191
193
|
if self.target_id is not None:
|
|
192
|
-
message = json.loads(message[
|
|
193
|
-
return JSObjectProperties(message[
|
|
194
|
+
message = json.loads(message["params"]["message"])["result"]
|
|
195
|
+
return JSObjectProperties(message["properties"])
|
|
194
196
|
|
|
195
197
|
async def _parse_runtime_evaluate(self, response: dict):
|
|
196
|
-
if self.target_id is None
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
elif result[
|
|
198
|
+
message = response if self.target_id is None else json.loads(response["params"]["message"])
|
|
199
|
+
result = message["result"]["result"]
|
|
200
|
+
if result.get("subtype", "") == "error":
|
|
201
|
+
properties = await self.get_properties(result["objectId"])
|
|
202
|
+
raise InspectorEvaluateError(
|
|
203
|
+
properties.class_name,
|
|
204
|
+
properties["message"],
|
|
205
|
+
properties.get("line"),
|
|
206
|
+
properties.get("column"),
|
|
207
|
+
properties.get("stack", "").split("\n"),
|
|
208
|
+
)
|
|
209
|
+
elif result["type"] == "bigint":
|
|
210
|
+
return result["description"]
|
|
211
|
+
elif result["type"] == "undefined":
|
|
208
212
|
pass
|
|
209
|
-
elif result[
|
|
210
|
-
value = result.get(
|
|
213
|
+
elif result["type"] == "object":
|
|
214
|
+
value = result.get("value")
|
|
211
215
|
if value is not None:
|
|
212
216
|
return value
|
|
213
217
|
|
|
214
218
|
# TODO: JSObjectProperties()
|
|
215
|
-
preview = result[
|
|
216
|
-
preview_buf =
|
|
217
|
-
for p in result[
|
|
218
|
-
value = p.get(
|
|
219
|
-
preview_buf += f
|
|
220
|
-
if preview.get(
|
|
221
|
-
preview_buf +=
|
|
222
|
-
preview_buf +=
|
|
223
|
-
return f
|
|
224
|
-
elif result[
|
|
225
|
-
return result[
|
|
219
|
+
preview = result["preview"]
|
|
220
|
+
preview_buf = "{\n"
|
|
221
|
+
for p in result["preview"]["properties"]:
|
|
222
|
+
value = p.get("value", "NOT_SUPPORTED_FOR_PREVIEW")
|
|
223
|
+
preview_buf += f"\t{p['name']}: {value}, // {p['type']}\n"
|
|
224
|
+
if preview.get("overflow"):
|
|
225
|
+
preview_buf += "\t// ...\n"
|
|
226
|
+
preview_buf += "}"
|
|
227
|
+
return f"[object {result['className']}]\n{preview_buf}"
|
|
228
|
+
elif result["type"] == "function":
|
|
229
|
+
return result["description"]
|
|
226
230
|
else:
|
|
227
|
-
return result[
|
|
231
|
+
return result["value"]
|
|
228
232
|
|
|
229
233
|
# response methods
|
|
230
234
|
def _target_dispatch_message_from_target(self, response: dict):
|
|
231
|
-
target_message = json.loads(response[
|
|
232
|
-
receive_message_id = target_message.get(
|
|
235
|
+
target_message = json.loads(response["params"]["message"])
|
|
236
|
+
receive_message_id = target_message.get("id")
|
|
233
237
|
if receive_message_id is None:
|
|
234
238
|
self._missing_id_in_message(target_message)
|
|
235
239
|
return
|
|
236
240
|
self._dispatch_message_responses[receive_message_id] = response
|
|
237
241
|
|
|
238
242
|
def _missing_id_in_message(self, message: dict):
|
|
239
|
-
handler = self.response_methods.get(message[
|
|
243
|
+
handler = self.response_methods.get(message["method"])
|
|
240
244
|
if handler is not None:
|
|
241
245
|
handler(message)
|
|
242
246
|
else:
|
|
243
|
-
logger.critical(f
|
|
247
|
+
logger.critical(f"unhandled message: {message}")
|
|
244
248
|
|
|
245
249
|
def _console_message_added(self, message: dict):
|
|
246
|
-
log_level = message[
|
|
247
|
-
text = message[
|
|
250
|
+
log_level = message["params"]["message"]["level"]
|
|
251
|
+
text = message["params"]["message"]["text"]
|
|
248
252
|
self._last_console_message = message
|
|
249
253
|
webinspector_logger_handlers[log_level](text)
|
|
250
254
|
|
|
@@ -252,7 +256,7 @@ class InspectorSession:
|
|
|
252
256
|
self._console_message_added(self._last_console_message)
|
|
253
257
|
|
|
254
258
|
def _heap_garbage_collected(self, message: dict):
|
|
255
|
-
heap_logger.debug(message[
|
|
259
|
+
heap_logger.debug(message["params"])
|
|
256
260
|
|
|
257
261
|
def _target_created(self, response: dict):
|
|
258
262
|
pass
|
|
@@ -261,4 +265,4 @@ class InspectorSession:
|
|
|
261
265
|
pass
|
|
262
266
|
|
|
263
267
|
def _target_did_commit_provisional_target(self, response: dict):
|
|
264
|
-
self.set_target_id(response[
|
|
268
|
+
self.set_target_id(response["params"]["newTargetId"])
|
|
@@ -6,86 +6,86 @@ from pymobiledevice3.services.web_protocol.automation_session import By
|
|
|
6
6
|
|
|
7
7
|
class SeleniumApi(ABC):
|
|
8
8
|
@abstractmethod
|
|
9
|
-
def find_element(self, by=By.ID, value=None):
|
|
9
|
+
async def find_element(self, by=By.ID, value=None):
|
|
10
10
|
pass
|
|
11
11
|
|
|
12
12
|
@abstractmethod
|
|
13
|
-
def find_elements(self, by=By.ID, value=None):
|
|
13
|
+
async def find_elements(self, by=By.ID, value=None):
|
|
14
14
|
pass
|
|
15
15
|
|
|
16
16
|
@property
|
|
17
17
|
@abstractmethod
|
|
18
|
-
def screenshot_as_base64(self):
|
|
18
|
+
async def screenshot_as_base64(self) -> str:
|
|
19
19
|
pass
|
|
20
20
|
|
|
21
|
-
def find_element_by_class_name(self, name):
|
|
22
|
-
return self.find_element(By.CLASS_NAME, name)
|
|
21
|
+
async def find_element_by_class_name(self, name):
|
|
22
|
+
return await self.find_element(By.CLASS_NAME, name)
|
|
23
23
|
|
|
24
|
-
def find_element_by_css_selector(self, css_selector):
|
|
25
|
-
return self.find_element(By.CSS_SELECTOR, css_selector)
|
|
24
|
+
async def find_element_by_css_selector(self, css_selector):
|
|
25
|
+
return await self.find_element(By.CSS_SELECTOR, css_selector)
|
|
26
26
|
|
|
27
|
-
def find_element_by_id(self, id_):
|
|
28
|
-
return self.find_element(value=id_)
|
|
27
|
+
async def find_element_by_id(self, id_):
|
|
28
|
+
return await self.find_element(value=id_)
|
|
29
29
|
|
|
30
|
-
def find_element_by_link_text(self, link_text):
|
|
31
|
-
return self.find_element(By.LINK_TEXT, link_text)
|
|
30
|
+
async def find_element_by_link_text(self, link_text):
|
|
31
|
+
return await self.find_element(By.LINK_TEXT, link_text)
|
|
32
32
|
|
|
33
|
-
def find_element_by_name(self, name):
|
|
34
|
-
return self.find_element(By.NAME, name)
|
|
33
|
+
async def find_element_by_name(self, name):
|
|
34
|
+
return await self.find_element(By.NAME, name)
|
|
35
35
|
|
|
36
|
-
def find_element_by_partial_link_text(self, link_text):
|
|
37
|
-
return self.find_element(By.PARTIAL_LINK_TEXT, link_text)
|
|
36
|
+
async def find_element_by_partial_link_text(self, link_text):
|
|
37
|
+
return await self.find_element(By.PARTIAL_LINK_TEXT, link_text)
|
|
38
38
|
|
|
39
|
-
def find_element_by_tag_name(self, name):
|
|
40
|
-
return self.find_element(By.TAG_NAME, name)
|
|
39
|
+
async def find_element_by_tag_name(self, name):
|
|
40
|
+
return await self.find_element(By.TAG_NAME, name)
|
|
41
41
|
|
|
42
|
-
def find_element_by_xpath(self, xpath):
|
|
43
|
-
return self.find_element(By.XPATH, xpath)
|
|
42
|
+
async def find_element_by_xpath(self, xpath):
|
|
43
|
+
return await self.find_element(By.XPATH, xpath)
|
|
44
44
|
|
|
45
|
-
def find_elements_by_class_name(self, name):
|
|
46
|
-
return self.find_elements(By.CLASS_NAME, name)
|
|
45
|
+
async def find_elements_by_class_name(self, name):
|
|
46
|
+
return await self.find_elements(By.CLASS_NAME, name)
|
|
47
47
|
|
|
48
|
-
def find_elements_by_css_selector(self, css_selector):
|
|
49
|
-
return self.find_elements(By.CSS_SELECTOR, css_selector)
|
|
48
|
+
async def find_elements_by_css_selector(self, css_selector):
|
|
49
|
+
return await self.find_elements(By.CSS_SELECTOR, css_selector)
|
|
50
50
|
|
|
51
|
-
def find_elements_by_id(self, id_):
|
|
52
|
-
return self.find_elements(value=id_)
|
|
51
|
+
async def find_elements_by_id(self, id_):
|
|
52
|
+
return await self.find_elements(value=id_)
|
|
53
53
|
|
|
54
|
-
def find_elements_by_link_text(self, link_text):
|
|
55
|
-
return self.find_elements(By.LINK_TEXT, link_text)
|
|
54
|
+
async def find_elements_by_link_text(self, link_text):
|
|
55
|
+
return await self.find_elements(By.LINK_TEXT, link_text)
|
|
56
56
|
|
|
57
|
-
def find_elements_by_name(self, name):
|
|
58
|
-
return self.find_elements(By.NAME, name)
|
|
57
|
+
async def find_elements_by_name(self, name):
|
|
58
|
+
return await self.find_elements(By.NAME, name)
|
|
59
59
|
|
|
60
|
-
def find_elements_by_partial_link_text(self, link_text):
|
|
61
|
-
return self.find_elements(By.PARTIAL_LINK_TEXT, link_text)
|
|
60
|
+
async def find_elements_by_partial_link_text(self, link_text):
|
|
61
|
+
return await self.find_elements(By.PARTIAL_LINK_TEXT, link_text)
|
|
62
62
|
|
|
63
|
-
def find_elements_by_tag_name(self, name):
|
|
64
|
-
return self.find_elements(By.TAG_NAME, name)
|
|
63
|
+
async def find_elements_by_tag_name(self, name):
|
|
64
|
+
return await self.find_elements(By.TAG_NAME, name)
|
|
65
65
|
|
|
66
|
-
def find_elements_by_xpath(self, xpath):
|
|
67
|
-
return self.find_elements(By.XPATH, xpath)
|
|
66
|
+
async def find_elements_by_xpath(self, xpath):
|
|
67
|
+
return await self.find_elements(By.XPATH, xpath)
|
|
68
68
|
|
|
69
|
-
def screenshot(self, filename):
|
|
70
|
-
png = self.screenshot_as_png()
|
|
69
|
+
async def screenshot(self, filename):
|
|
70
|
+
png = await self.screenshot_as_png()
|
|
71
71
|
try:
|
|
72
|
-
with open(filename,
|
|
72
|
+
with open(filename, "wb") as f:
|
|
73
73
|
f.write(png)
|
|
74
|
-
except
|
|
74
|
+
except OSError:
|
|
75
75
|
return False
|
|
76
76
|
return True
|
|
77
77
|
|
|
78
|
-
def screenshot_as_png(self):
|
|
79
|
-
return b64decode(self.screenshot_as_base64.encode(
|
|
78
|
+
async def screenshot_as_png(self) -> bytes:
|
|
79
|
+
return b64decode((await self.screenshot_as_base64).encode("ascii"))
|
|
80
80
|
|
|
81
|
-
def get_screenshot_as_base64(self):
|
|
82
|
-
return self.screenshot_as_base64
|
|
81
|
+
async def get_screenshot_as_base64(self) -> str:
|
|
82
|
+
return await self.screenshot_as_base64
|
|
83
83
|
|
|
84
|
-
def get_screenshot_as_file(self, filename):
|
|
85
|
-
return self.screenshot(filename)
|
|
84
|
+
async def get_screenshot_as_file(self, filename):
|
|
85
|
+
return await self.screenshot(filename)
|
|
86
86
|
|
|
87
|
-
def get_screenshot_as_png(self):
|
|
88
|
-
return self.screenshot_as_png()
|
|
87
|
+
async def get_screenshot_as_png(self):
|
|
88
|
+
return await self.screenshot_as_png()
|
|
89
89
|
|
|
90
|
-
def save_screenshot(self, filename) -> bool:
|
|
91
|
-
return self.screenshot(filename)
|
|
90
|
+
async def save_screenshot(self, filename) -> bool:
|
|
91
|
+
return await self.screenshot(filename)
|
|
@@ -5,7 +5,7 @@ from pymobiledevice3.exceptions import WirError
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class SessionProtocol:
|
|
8
|
-
def __init__(self, inspector, id_, app, page, method_prefix=
|
|
8
|
+
def __init__(self, inspector, id_, app, page, method_prefix="Automation"):
|
|
9
9
|
"""
|
|
10
10
|
:param pymobiledevice3.services.webinspector.WebinspectorService inspector:
|
|
11
11
|
"""
|
|
@@ -19,19 +19,25 @@ class SessionProtocol:
|
|
|
19
19
|
async def send_command(self, method, **kwargs):
|
|
20
20
|
wir_id = self._wir_messages_id
|
|
21
21
|
self._wir_messages_id += 1
|
|
22
|
-
await self.inspector.send_socket_data(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
await self.inspector.send_socket_data(
|
|
23
|
+
self.id_,
|
|
24
|
+
self.app.id_,
|
|
25
|
+
self.page.id_,
|
|
26
|
+
{
|
|
27
|
+
"method": f"{self.method_prefix}.{method}" if self.method_prefix else method,
|
|
28
|
+
"params": kwargs,
|
|
29
|
+
"id": wir_id,
|
|
30
|
+
},
|
|
31
|
+
)
|
|
27
32
|
return wir_id
|
|
28
33
|
|
|
29
34
|
async def get_response(self, wir_id):
|
|
30
35
|
response = await self.wait_for_message(wir_id)
|
|
31
|
-
if
|
|
32
|
-
return response[
|
|
33
|
-
elif
|
|
34
|
-
raise WirError(response[
|
|
36
|
+
if "result" in response:
|
|
37
|
+
return response["result"]
|
|
38
|
+
elif "error" in response:
|
|
39
|
+
raise WirError(response["error"]["message"])
|
|
40
|
+
raise WirError(f"Unknown response: {response}")
|
|
35
41
|
|
|
36
42
|
async def send_receive(self, method, wait_for_response=True, **kwargs):
|
|
37
43
|
wir_id = await self.send_command(method, **kwargs)
|
|
@@ -45,8 +51,8 @@ class SessionProtocol:
|
|
|
45
51
|
await asyncio.sleep(0)
|
|
46
52
|
return self.inspector.wir_message_results.pop(id_)
|
|
47
53
|
|
|
48
|
-
def sync_send_receive(self, method, wait_for_response=True, **kwargs):
|
|
49
|
-
return self.
|
|
54
|
+
async def sync_send_receive(self, method, wait_for_response=True, **kwargs):
|
|
55
|
+
return await self.send_receive(method, wait_for_response, **kwargs)
|
|
50
56
|
|
|
51
57
|
def __getattr__(self, item):
|
|
52
58
|
return partial(self.sync_send_receive, method=item)
|
|
@@ -11,57 +11,60 @@ class SwitchTo:
|
|
|
11
11
|
self.session = session
|
|
12
12
|
|
|
13
13
|
@property
|
|
14
|
-
def active_element(self) -> WebElement:
|
|
15
|
-
"""
|
|
16
|
-
self.session.wait_for_navigation_to_complete()
|
|
17
|
-
elem = self.session.evaluate_js_function(
|
|
14
|
+
async def active_element(self) -> WebElement:
|
|
15
|
+
"""Returns the element with focus, or BODY if nothing has focus."""
|
|
16
|
+
await self.session.wait_for_navigation_to_complete()
|
|
17
|
+
elem = await self.session.evaluate_js_function(
|
|
18
|
+
"function() { return document.activeElement; }", include_frame=False
|
|
19
|
+
)
|
|
18
20
|
return WebElement(self.session, elem)
|
|
19
21
|
|
|
20
22
|
@property
|
|
21
23
|
def alert(self) -> Alert:
|
|
22
|
-
"""
|
|
24
|
+
"""Switches focus to an alert on the page."""
|
|
23
25
|
return Alert(self.session)
|
|
24
26
|
|
|
25
|
-
def default_content(self):
|
|
26
|
-
"""
|
|
27
|
-
self.session.switch_to_browsing_context(
|
|
27
|
+
async def default_content(self):
|
|
28
|
+
"""Switch focus to the default frame."""
|
|
29
|
+
await self.session.switch_to_browsing_context("")
|
|
28
30
|
|
|
29
|
-
def frame(self, frame_reference):
|
|
31
|
+
async def frame(self, frame_reference):
|
|
30
32
|
"""
|
|
31
33
|
Switches focus to the specified frame, by index, name, or web element.
|
|
32
34
|
:param frame_reference: The name of the window to switch to, an integer representing the index,
|
|
33
35
|
or a web element that is an (i)frame to switch to.
|
|
34
36
|
"""
|
|
35
|
-
if isinstance(frame_reference, int
|
|
37
|
+
if isinstance(frame_reference, (int, WebElement)):
|
|
36
38
|
frame = frame_reference
|
|
37
39
|
elif isinstance(frame_reference, str):
|
|
38
|
-
elem = self.session.find_elements(By.ID, frame_reference)
|
|
40
|
+
elem = await self.session.find_elements(By.ID, frame_reference)
|
|
39
41
|
if elem is None:
|
|
40
|
-
elem = self.session.find_elements(By.NAME, frame_reference)
|
|
42
|
+
elem = await self.session.find_elements(By.NAME, frame_reference)
|
|
41
43
|
frame = WebElement(self.session, elem)
|
|
42
44
|
else:
|
|
43
|
-
raise
|
|
45
|
+
raise TypeError()
|
|
44
46
|
|
|
45
|
-
self.session.wait_for_navigation_to_complete()
|
|
47
|
+
await self.session.wait_for_navigation_to_complete()
|
|
46
48
|
if isinstance(frame, int):
|
|
47
|
-
self.session.switch_to_frame(frame_ordinal=frame)
|
|
49
|
+
await self.session.switch_to_frame(frame_ordinal=frame)
|
|
48
50
|
else:
|
|
49
|
-
self.session.switch_to_frame(frame_handle=frame)
|
|
51
|
+
await self.session.switch_to_frame(frame_handle=frame)
|
|
50
52
|
|
|
51
|
-
def new_window(self, type_=
|
|
52
|
-
"""
|
|
53
|
-
self.session.switch_to_window(self.session.create_window(type_))
|
|
53
|
+
async def new_window(self, type_=""):
|
|
54
|
+
"""Switches to a new top-level browsing context."""
|
|
55
|
+
await self.session.switch_to_window(self.session.create_window(type_))
|
|
54
56
|
|
|
55
|
-
def parent_frame(self):
|
|
57
|
+
async def parent_frame(self):
|
|
56
58
|
"""
|
|
57
59
|
Switches focus to the parent context. If the current context is the top
|
|
58
60
|
level browsing context, the context remains unchanged.
|
|
59
61
|
"""
|
|
60
|
-
self.session.wait_for_navigation_to_complete()
|
|
61
|
-
self.session.switch_to_browsing_context_frame(
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
await self.session.wait_for_navigation_to_complete()
|
|
63
|
+
await self.session.switch_to_browsing_context_frame(
|
|
64
|
+
self.session.top_level_handle, self.session.current_parent_handle
|
|
65
|
+
)
|
|
66
|
+
await self.session.switch_to_browsing_context(self.session.current_parent_handle)
|
|
64
67
|
|
|
65
|
-
def window(self, window_name):
|
|
66
|
-
"""
|
|
67
|
-
self.session.switch_to_window(window_name)
|
|
68
|
+
async def window(self, window_name):
|
|
69
|
+
"""Switches focus to the specified window."""
|
|
70
|
+
await self.session.switch_to_window(window_name)
|