applib-ui 0.0.1__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.
- applib_ui/__init__.py +13 -0
- applib_ui/case.py +79 -0
- applib_ui/cli.py +75 -0
- applib_ui/core/__init__.py +0 -0
- applib_ui/core/android/__init__.py +16 -0
- applib_ui/core/android/case.py +8 -0
- applib_ui/core/android/driver.py +191 -0
- applib_ui/core/android/element.py +291 -0
- applib_ui/core/android/page.py +6 -0
- applib_ui/core/android/recorder.py +171 -0
- applib_ui/core/android/remote_adb.py +69 -0
- applib_ui/core/android/remote_driver.py +176 -0
- applib_ui/core/img/__init__.py +4 -0
- applib_ui/core/img/driver.py +43 -0
- applib_ui/core/img/element.py +73 -0
- applib_ui/core/ios/__init__.py +16 -0
- applib_ui/core/ios/case.py +8 -0
- applib_ui/core/ios/driver.py +290 -0
- applib_ui/core/ios/elem.py +222 -0
- applib_ui/core/ios/local_wda.py +576 -0
- applib_ui/core/ios/page.py +6 -0
- applib_ui/core/ios/recorder.py +202 -0
- applib_ui/core/ios/remote_driver.py +220 -0
- applib_ui/core/ios/remote_wda.py +54 -0
- applib_ui/core/ocr/__init__.py +5 -0
- applib_ui/core/ocr/driver.py +185 -0
- applib_ui/core/ocr/element.py +65 -0
- applib_ui/device.py +53 -0
- applib_ui/element.py +210 -0
- applib_ui/page.py +6 -0
- applib_ui/pytest_plugin.py +57 -0
- applib_ui/running/__init__.py +0 -0
- applib_ui/running/conf.py +14 -0
- applib_ui/running/conf.yml +19 -0
- applib_ui/running/runner.py +33 -0
- applib_ui/utils/__init__.py +1 -0
- applib_ui/utils/aes_util.py +47 -0
- applib_ui/utils/allure_util.py +213 -0
- applib_ui/utils/base64_util.py +21 -0
- applib_ui/utils/common.py +84 -0
- applib_ui/utils/config.py +135 -0
- applib_ui/utils/cv_util.py +110 -0
- applib_ui/utils/dingtalk.py +56 -0
- applib_ui/utils/excel_util.py +101 -0
- applib_ui/utils/log.py +12 -0
- applib_ui/utils/mail.py +47 -0
- applib_ui/utils/mysql_util.py +74 -0
- applib_ui/utils/nacos_util.py +52 -0
- applib_ui/utils/pytest_util.py +89 -0
- applib_ui/utils/swagger_util.py +195 -0
- applib_ui-0.0.1.dist-info/METADATA +170 -0
- applib_ui-0.0.1.dist-info/RECORD +55 -0
- applib_ui-0.0.1.dist-info/WHEEL +5 -0
- applib_ui-0.0.1.dist-info/entry_points.txt +5 -0
- applib_ui-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import allure
|
|
2
|
+
import wda
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
import subprocess
|
|
6
|
+
|
|
7
|
+
from .local_wda import LocalWdaInit
|
|
8
|
+
|
|
9
|
+
from applib_ui.utils.log import logger
|
|
10
|
+
from applib_ui.utils.common import general_file_path, general_video_path
|
|
11
|
+
from .recorder import WdaMjpegRecorder
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Driver(object):
|
|
15
|
+
"""
|
|
16
|
+
https://github.com/openatx/facebook-wda
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
udid: str,
|
|
22
|
+
bundle_id: str = None,
|
|
23
|
+
):
|
|
24
|
+
"""
|
|
25
|
+
手机udid
|
|
26
|
+
@param udid: tidevice list获取
|
|
27
|
+
@param bundle_id: tidevice applist获取
|
|
28
|
+
"""
|
|
29
|
+
self.udid = udid
|
|
30
|
+
self.bundle_id = bundle_id
|
|
31
|
+
logger.info(f"初始化本地wda: {udid}")
|
|
32
|
+
# tunnel-info 端口由框架自动分配(每台设备自动错开),无需传入
|
|
33
|
+
self._wda = LocalWdaInit(udid)
|
|
34
|
+
self.p1, self.p2, self.p3, self.wda_url = self._wda.run()
|
|
35
|
+
self.tunnel_info_port = self._wda.tunnel_info_port
|
|
36
|
+
print(self.wda_url)
|
|
37
|
+
self._recorder = None
|
|
38
|
+
self._new_client()
|
|
39
|
+
if self.d.is_ready():
|
|
40
|
+
logger.info('wda已就绪')
|
|
41
|
+
else:
|
|
42
|
+
logger.info('wda未就绪, 请检查wda服务')
|
|
43
|
+
|
|
44
|
+
def _new_client(self):
|
|
45
|
+
"""
|
|
46
|
+
新建一个 wda 客户端
|
|
47
|
+
- 端口转发每次重建都会换本机端口,所以重连后必须换客户端,否则一直打到已经死掉的端口
|
|
48
|
+
- facebook-wda 的 HTTP 请求超时是模块级 HTTP_TIMEOUT(默认 180s):
|
|
49
|
+
链路断开后一次元素查询能卡 3 分钟,这里收紧,让断链尽快抛出来交给上层恢复
|
|
50
|
+
(可用 APPLIB_UI_WDA_TIMEOUT 调整,注意 App 冷启动等慢请求也要留够时间)
|
|
51
|
+
"""
|
|
52
|
+
if hasattr(wda, "HTTP_TIMEOUT"):
|
|
53
|
+
wda.HTTP_TIMEOUT = float(os.environ.get("APPLIB_UI_WDA_TIMEOUT", "60"))
|
|
54
|
+
self.d = wda.Client(self.wda_url)
|
|
55
|
+
return self.d
|
|
56
|
+
|
|
57
|
+
def uninstall_app(self, bundle_id: str = None):
|
|
58
|
+
"""
|
|
59
|
+
卸载应用
|
|
60
|
+
@return:
|
|
61
|
+
"""
|
|
62
|
+
if self.bundle_id is None:
|
|
63
|
+
if bundle_id is None:
|
|
64
|
+
raise KeyError('bundle_id不能为空')
|
|
65
|
+
else:
|
|
66
|
+
self.bundle_id = bundle_id
|
|
67
|
+
|
|
68
|
+
cmd = f"tidevice -u {self.udid} uninstall {self.bundle_id}"
|
|
69
|
+
print(cmd)
|
|
70
|
+
output = subprocess.getoutput(cmd)
|
|
71
|
+
if "Complete" in output.split()[-1]:
|
|
72
|
+
logger.info(f"{self.udid} 卸载应用成功")
|
|
73
|
+
return
|
|
74
|
+
else:
|
|
75
|
+
logger.info(f"{self.udid} 卸载应用成功")
|
|
76
|
+
|
|
77
|
+
def install_app(self, ipa_url, bundle_id: str = None):
|
|
78
|
+
"""
|
|
79
|
+
安装应用
|
|
80
|
+
@param ipa_url:
|
|
81
|
+
@param bundle_id
|
|
82
|
+
@return:
|
|
83
|
+
"""
|
|
84
|
+
if self.bundle_id is None:
|
|
85
|
+
if bundle_id is None:
|
|
86
|
+
raise KeyError('bundle_id不能为空')
|
|
87
|
+
else:
|
|
88
|
+
self.bundle_id = bundle_id
|
|
89
|
+
self.uninstall_app(self.bundle_id)
|
|
90
|
+
|
|
91
|
+
cmd = f"tidevice -u {self.udid} install {ipa_url}"
|
|
92
|
+
print(cmd)
|
|
93
|
+
output = subprocess.getoutput(cmd)
|
|
94
|
+
if "Complete" in output.split()[-1]:
|
|
95
|
+
logger.info(f"{self.udid} 安装应用成功")
|
|
96
|
+
return
|
|
97
|
+
else:
|
|
98
|
+
logger.info(f"{self.udid} 安装应用失败")
|
|
99
|
+
|
|
100
|
+
def start_app(self, bundle_id: str = None):
|
|
101
|
+
"""
|
|
102
|
+
启动应用
|
|
103
|
+
@return:
|
|
104
|
+
"""
|
|
105
|
+
if self.bundle_id is None:
|
|
106
|
+
if bundle_id is None:
|
|
107
|
+
raise KeyError('bundle_id不能为空')
|
|
108
|
+
else:
|
|
109
|
+
self.bundle_id = bundle_id
|
|
110
|
+
logger.info(f"启动应用: {self.bundle_id}")
|
|
111
|
+
try:
|
|
112
|
+
self.d.app_start(self.bundle_id)
|
|
113
|
+
except Exception as e:
|
|
114
|
+
logger.error(str(e))
|
|
115
|
+
self.close()
|
|
116
|
+
|
|
117
|
+
def stop_app(self, bundle_id: str = None):
|
|
118
|
+
"""
|
|
119
|
+
停止应用
|
|
120
|
+
@return:
|
|
121
|
+
"""
|
|
122
|
+
if self.bundle_id is None:
|
|
123
|
+
if bundle_id is None:
|
|
124
|
+
raise KeyError('bundle_id不能为空')
|
|
125
|
+
else:
|
|
126
|
+
self.bundle_id = bundle_id
|
|
127
|
+
logger.info(f"停止应用: {self.bundle_id}")
|
|
128
|
+
self.d.app_stop(self.bundle_id)
|
|
129
|
+
|
|
130
|
+
def close(self):
|
|
131
|
+
"""
|
|
132
|
+
清除tunner、wda、relay进程
|
|
133
|
+
@return:
|
|
134
|
+
"""
|
|
135
|
+
# try:
|
|
136
|
+
# self.stop_record()
|
|
137
|
+
# except Exception:
|
|
138
|
+
# pass
|
|
139
|
+
try:
|
|
140
|
+
self.p1.kill()
|
|
141
|
+
self.p2.kill()
|
|
142
|
+
self.p3.kill()
|
|
143
|
+
except Exception as e:
|
|
144
|
+
print(str(e))
|
|
145
|
+
|
|
146
|
+
def reconnect(self):
|
|
147
|
+
"""断链后重建 tunnel/wda/端口转发,并换掉指向旧端口的 wda 客户端"""
|
|
148
|
+
logger.info(f"reconnect wda for device: {self.udid}")
|
|
149
|
+
self.close()
|
|
150
|
+
# 重新拉起时端口由框架自动重新分配;tunnel 与 runwda 始终用同一个端口
|
|
151
|
+
self._wda = LocalWdaInit(self.udid)
|
|
152
|
+
self.p1, self.p2, self.p3, self.wda_url = self._wda.run()
|
|
153
|
+
self.tunnel_info_port = self._wda.tunnel_info_port
|
|
154
|
+
self._new_client()
|
|
155
|
+
if self.d.is_ready():
|
|
156
|
+
logger.info(f"{self.udid} wda 已重新就绪")
|
|
157
|
+
return self.wda_url
|
|
158
|
+
|
|
159
|
+
def is_alive(self, timeout: float = 3) -> bool:
|
|
160
|
+
"""
|
|
161
|
+
tunnel + WDA 是否都还活着(轻量探针,不用等元素查询卡到超时才发现断链)
|
|
162
|
+
@param timeout: tunnel 探针超时
|
|
163
|
+
"""
|
|
164
|
+
if not self._wda.tunnel_alive(timeout):
|
|
165
|
+
logger.warning(f"{self.udid} 的 tunnel 已断开")
|
|
166
|
+
return False
|
|
167
|
+
if not self.d.is_ready():
|
|
168
|
+
logger.warning(f"{self.udid} 的 wda 无响应")
|
|
169
|
+
return False
|
|
170
|
+
return True
|
|
171
|
+
|
|
172
|
+
def ensure_alive(self) -> bool:
|
|
173
|
+
"""
|
|
174
|
+
断链检测 + 重建,用例开始前调一次即可(批次级重启的挂载点)
|
|
175
|
+
@return: 链路是否可用;重建失败会记录日志并返回 False,不抛异常
|
|
176
|
+
"""
|
|
177
|
+
if self.is_alive():
|
|
178
|
+
return True
|
|
179
|
+
logger.warning(f"{self.udid} 的 wda 链路不可用,重建 tunnel/wda/端口转发")
|
|
180
|
+
try:
|
|
181
|
+
self.reconnect()
|
|
182
|
+
except Exception as e:
|
|
183
|
+
logger.error(f"{self.udid} 重建失败:{str(e).splitlines()[0]}")
|
|
184
|
+
return False
|
|
185
|
+
return self.d.is_ready()
|
|
186
|
+
|
|
187
|
+
def input(self, text: str):
|
|
188
|
+
self.d.send_keys(text)
|
|
189
|
+
|
|
190
|
+
def click(self, x, y):
|
|
191
|
+
logger.info(f"点击坐标:{x}, {y}")
|
|
192
|
+
self.d.click(x, y)
|
|
193
|
+
|
|
194
|
+
def shot(self, file_name=None):
|
|
195
|
+
file_path = general_file_path(file_name, self.udid)
|
|
196
|
+
logger.info(f"截图保存至: {file_path}")
|
|
197
|
+
self.d.screenshot(file_path)
|
|
198
|
+
|
|
199
|
+
logger.info("截图上传allure报告")
|
|
200
|
+
allure.attach.file(
|
|
201
|
+
file_path,
|
|
202
|
+
attachment_type=allure.attachment_type.PNG,
|
|
203
|
+
name=f"{file_path}",
|
|
204
|
+
)
|
|
205
|
+
return file_path
|
|
206
|
+
|
|
207
|
+
def scale(self):
|
|
208
|
+
return self.d.scale
|
|
209
|
+
|
|
210
|
+
def unlock(self):
|
|
211
|
+
self.d.unlock()
|
|
212
|
+
|
|
213
|
+
def _match(self, text):
|
|
214
|
+
"""
|
|
215
|
+
判断元素是否存在当前页面
|
|
216
|
+
@return:
|
|
217
|
+
"""
|
|
218
|
+
_element = self.d(text=text)
|
|
219
|
+
result = text if _element.wait(timeout=0.1,
|
|
220
|
+
raise_error=False) else None
|
|
221
|
+
return result
|
|
222
|
+
|
|
223
|
+
def pop_check(self, watch_list: list, timeout=3):
|
|
224
|
+
logger.info(f"开始弹窗检测: {watch_list}")
|
|
225
|
+
# 多线程match,如果match到,获取第一个非None内容,进行点击
|
|
226
|
+
# match完休息1s,如果休息3s也没有match到,就停止(定义一个flag,match到就清零)
|
|
227
|
+
# 如果3s内仍然能match到就继续(如果flag大于3就停止)
|
|
228
|
+
_build_info = ["允许", "使用App时允许", "始终允许"]
|
|
229
|
+
if watch_list is True:
|
|
230
|
+
loc_list = _build_info
|
|
231
|
+
else:
|
|
232
|
+
loc_list = list(set(_build_info + watch_list))
|
|
233
|
+
flag = timeout
|
|
234
|
+
while flag > 0:
|
|
235
|
+
import concurrent.futures
|
|
236
|
+
|
|
237
|
+
logger.info(f"匹配: {loc_list}")
|
|
238
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
239
|
+
results = executor.map(self._match, loc_list)
|
|
240
|
+
results = [item for item in results if item is not None]
|
|
241
|
+
|
|
242
|
+
if results:
|
|
243
|
+
logger.info(f"匹配到: {results}")
|
|
244
|
+
try:
|
|
245
|
+
self.d(text=results[0]).click_exists(timeout=1)
|
|
246
|
+
except:
|
|
247
|
+
pass
|
|
248
|
+
logger.info("点击成功")
|
|
249
|
+
flag = timeout
|
|
250
|
+
else:
|
|
251
|
+
logger.info("匹配失败")
|
|
252
|
+
|
|
253
|
+
flag -= 1
|
|
254
|
+
time.sleep(1)
|
|
255
|
+
logger.info("结束检测")
|
|
256
|
+
|
|
257
|
+
def back(self):
|
|
258
|
+
logger.info("返回上一页")
|
|
259
|
+
self.d.swipe(0.01, 0.3, 0.9, 0.3, duration=1)
|
|
260
|
+
|
|
261
|
+
def start_record(self, file_name: str = "record"):
|
|
262
|
+
"""
|
|
263
|
+
开始录屏
|
|
264
|
+
@param file_name: 录屏文件名
|
|
265
|
+
@return: 录像路径;录制没起来返回 None(最终产物以 stop_record 的返回值为准)
|
|
266
|
+
"""
|
|
267
|
+
if self._recorder is not None:
|
|
268
|
+
self.stop_record()
|
|
269
|
+
try:
|
|
270
|
+
self.d.appium_settings({"mjpegServerFramerate": 10})
|
|
271
|
+
except Exception as e:
|
|
272
|
+
logger.warning(f"设置 mjpeg 帧率失败: {e}")
|
|
273
|
+
file_path = general_video_path(file_name, self.udid)
|
|
274
|
+
self._recorder = WdaMjpegRecorder(self.udid)
|
|
275
|
+
if not self._recorder.start(file_path):
|
|
276
|
+
# 没起来:原因已由 recorder 打成 ERROR,这里返回 None,别让调用方以为正在录
|
|
277
|
+
self._recorder = None
|
|
278
|
+
return None
|
|
279
|
+
return file_path
|
|
280
|
+
|
|
281
|
+
def stop_record(self):
|
|
282
|
+
"""
|
|
283
|
+
停止录屏并上传allure
|
|
284
|
+
@return:
|
|
285
|
+
"""
|
|
286
|
+
if self._recorder is None:
|
|
287
|
+
return None
|
|
288
|
+
file_path = self._recorder.stop()
|
|
289
|
+
self._recorder = None
|
|
290
|
+
return file_path
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import threading
|
|
3
|
+
|
|
4
|
+
from applib_ui.utils.log import logger
|
|
5
|
+
from .driver import Driver
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
lock = threading.Lock()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# 链式调用临时存储定位方式
|
|
12
|
+
class ChildLocator:
|
|
13
|
+
|
|
14
|
+
def __init__(self, *args, **kwargs):
|
|
15
|
+
self.args = args
|
|
16
|
+
self.kwargs = kwargs
|
|
17
|
+
self.type = 'child'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Elem(object):
|
|
21
|
+
"""
|
|
22
|
+
IOS原生元素定义
|
|
23
|
+
https://github.com/openatx/facebook-wda
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self,
|
|
27
|
+
tag=None
|
|
28
|
+
):
|
|
29
|
+
"""
|
|
30
|
+
@param tag: 元素名称,方便后续维护
|
|
31
|
+
"""
|
|
32
|
+
self._driver: Driver = None
|
|
33
|
+
self.tag = tag
|
|
34
|
+
if not self.tag:
|
|
35
|
+
raise KeyError('tag不能为空')
|
|
36
|
+
self._first_locator = None
|
|
37
|
+
self._locators = []
|
|
38
|
+
|
|
39
|
+
def __get__(self, instance, owner):
|
|
40
|
+
"""po模式中element初始化不需要带driver的关键"""
|
|
41
|
+
if instance is None:
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
self._driver = instance.driver
|
|
45
|
+
return self
|
|
46
|
+
|
|
47
|
+
# 链式调用方法
|
|
48
|
+
def loc(self, **kwargs):
|
|
49
|
+
if not kwargs:
|
|
50
|
+
raise KeyError('定位方式不能为空')
|
|
51
|
+
if 'xpath' in kwargs:
|
|
52
|
+
self._first_locator = kwargs.pop('xpath')
|
|
53
|
+
else:
|
|
54
|
+
self._first_locator = kwargs
|
|
55
|
+
return self
|
|
56
|
+
|
|
57
|
+
def child(self, *args, **kwargs):
|
|
58
|
+
self._locators.append(ChildLocator(*args, **kwargs))
|
|
59
|
+
return self
|
|
60
|
+
|
|
61
|
+
# def _match(self, text):
|
|
62
|
+
# """
|
|
63
|
+
# 判断元素是否存在当前页面
|
|
64
|
+
# @return:
|
|
65
|
+
# """
|
|
66
|
+
# _element = self._driver.d(text=text)
|
|
67
|
+
# result = text if _element.wait(timeout=0.1,
|
|
68
|
+
# raise_error=False) else None
|
|
69
|
+
# return result
|
|
70
|
+
#
|
|
71
|
+
# def pop_check(self, timeout=3):
|
|
72
|
+
# logger.info(f"开始弹窗检测: {self._watch}")
|
|
73
|
+
# # 多线程match,如果match到,获取第一个非None内容,进行点击
|
|
74
|
+
# # match完休息1s,如果休息3s也没有match到,就停止(定义一个flag,match到就清零)
|
|
75
|
+
# # 如果3s内仍然能match到就继续(如果flag大于3就停止)
|
|
76
|
+
# _build_info = ["允许", "使用App时允许", "始终允许", "同意"]
|
|
77
|
+
# if self._watch is True:
|
|
78
|
+
# loc_list = _build_info
|
|
79
|
+
# else:
|
|
80
|
+
# loc_list = list(set(_build_info + self._watch))
|
|
81
|
+
# flag = timeout
|
|
82
|
+
# while flag > 0:
|
|
83
|
+
# import concurrent.futures
|
|
84
|
+
#
|
|
85
|
+
# logger.info(f"匹配: {loc_list}")
|
|
86
|
+
# with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
87
|
+
# results = executor.map(self._match, loc_list)
|
|
88
|
+
# results = [item for item in results if item is not None]
|
|
89
|
+
#
|
|
90
|
+
# if results:
|
|
91
|
+
# logger.info(f"匹配到: {results}")
|
|
92
|
+
# self._driver.d(text=results[0]).click()
|
|
93
|
+
# logger.info("点击成功")
|
|
94
|
+
# flag = timeout
|
|
95
|
+
# else:
|
|
96
|
+
# logger.info("匹配失败")
|
|
97
|
+
#
|
|
98
|
+
# flag -= 1
|
|
99
|
+
# time.sleep(1)
|
|
100
|
+
# logger.info("结束检测")
|
|
101
|
+
|
|
102
|
+
def find(self, timeout=5):
|
|
103
|
+
"""
|
|
104
|
+
针对元素定位失败的情况,抛出KError异常
|
|
105
|
+
@param timeout:
|
|
106
|
+
@param n: 重试次数
|
|
107
|
+
["允许", "同意"]
|
|
108
|
+
@return:
|
|
109
|
+
"""
|
|
110
|
+
logger.info(f"查找: {self.tag}")
|
|
111
|
+
# 第一个定位
|
|
112
|
+
if isinstance(self._first_locator, dict):
|
|
113
|
+
_element = self._driver.d(**self._first_locator)
|
|
114
|
+
else:
|
|
115
|
+
_element = self._driver.d.xpath(self._first_locator)
|
|
116
|
+
|
|
117
|
+
# 后面的定位
|
|
118
|
+
if self._locators:
|
|
119
|
+
for loc_obj in self._locators:
|
|
120
|
+
if loc_obj.type == "child":
|
|
121
|
+
_element = _element.child(*loc_obj.args, **loc_obj.kwargs)
|
|
122
|
+
else:
|
|
123
|
+
raise KeyError('不支持的子调用')
|
|
124
|
+
|
|
125
|
+
if _element.wait(timeout=timeout):
|
|
126
|
+
logger.info(f"查找成功")
|
|
127
|
+
return _element
|
|
128
|
+
else:
|
|
129
|
+
logger.info("查找失败")
|
|
130
|
+
self._driver.shot(f"{self.tag}_查找失败")
|
|
131
|
+
raise Exception(f"{self.tag}, 查找失败")
|
|
132
|
+
|
|
133
|
+
def get_text(self, timeout=5):
|
|
134
|
+
logger.info(f"获取{self.tag}的文本")
|
|
135
|
+
return self.find(timeout=timeout).text
|
|
136
|
+
|
|
137
|
+
def exists(self, timeout=5):
|
|
138
|
+
"""
|
|
139
|
+
判断元素是否存在当前页面
|
|
140
|
+
@param timeout:
|
|
141
|
+
@return:
|
|
142
|
+
"""
|
|
143
|
+
logger.info(f"判断{self.tag}是否存在")
|
|
144
|
+
result = False
|
|
145
|
+
try:
|
|
146
|
+
_element = self.find(timeout=timeout)
|
|
147
|
+
result = True
|
|
148
|
+
except:
|
|
149
|
+
result = False
|
|
150
|
+
finally:
|
|
151
|
+
logger.info(result)
|
|
152
|
+
return result
|
|
153
|
+
|
|
154
|
+
def _adapt_center(self, timeout=5):
|
|
155
|
+
"""
|
|
156
|
+
修正控件中心坐标
|
|
157
|
+
"""
|
|
158
|
+
bounds = self.find(timeout=timeout).bounds
|
|
159
|
+
left_top_x, left_top_y, width, height = \
|
|
160
|
+
bounds.x, bounds.y, bounds.width, bounds.height
|
|
161
|
+
center_x = int(left_top_x + width/2)
|
|
162
|
+
center_y = int(left_top_y + height/2)
|
|
163
|
+
logger.info(f'{center_x}, {center_y}')
|
|
164
|
+
return center_x, center_y
|
|
165
|
+
|
|
166
|
+
def click_exists(self, timeout=5):
|
|
167
|
+
"""
|
|
168
|
+
存在才点击
|
|
169
|
+
@param: retry,重试次数
|
|
170
|
+
@param: timeout,每次重试超时时间
|
|
171
|
+
"""
|
|
172
|
+
logger.info(f'{self.tag}存在才点击')
|
|
173
|
+
try:
|
|
174
|
+
x, y = self._adapt_center(timeout=timeout)
|
|
175
|
+
self._driver.d.appium_settings({"snapshotMaxDepth": 0})
|
|
176
|
+
self._driver.click(x, y)
|
|
177
|
+
self._driver.d.appium_settings({"snapshotMaxDepth": 50})
|
|
178
|
+
logger.info("点击完成")
|
|
179
|
+
except:
|
|
180
|
+
logger.info("不存在,不进行操作")
|
|
181
|
+
|
|
182
|
+
def click(self, timeout=5):
|
|
183
|
+
"""
|
|
184
|
+
单击
|
|
185
|
+
@param: retry,重试次数
|
|
186
|
+
@param: timeout,每次重试超时时间
|
|
187
|
+
"""
|
|
188
|
+
logger.info(f'点击{self.tag}')
|
|
189
|
+
x, y = self._adapt_center(timeout=timeout)
|
|
190
|
+
logger.error('{x}, {y}')
|
|
191
|
+
self._driver.d.appium_settings({"snapshotMaxDepth": 0})
|
|
192
|
+
self._driver.click(x, y)
|
|
193
|
+
self._driver.d.appium_settings({"snapshotMaxDepth": 50})
|
|
194
|
+
logger.info("点击完成")
|
|
195
|
+
|
|
196
|
+
def input(self, text, timeout=5, clear=False):
|
|
197
|
+
"""输入内容"""
|
|
198
|
+
logger.info(f"定位到{self.tag}并输入: {text}")
|
|
199
|
+
element = self.find(timeout=timeout)
|
|
200
|
+
if clear is True:
|
|
201
|
+
element.clear_text()
|
|
202
|
+
element.set_text(text)
|
|
203
|
+
logger.info("输入完成")
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
|