mangoautomation 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mangoautomation might be problematic. Click here for more details.

Files changed (55) hide show
  1. mangoautomation/__init__.py +5 -0
  2. mangoautomation/enums/__init__.py +19 -0
  3. mangoautomation/enums/_base_enum.py +38 -0
  4. mangoautomation/enums/_ui_enum.py +110 -0
  5. mangoautomation/exceptions/__init__.py +14 -0
  6. mangoautomation/exceptions/_error_msg.py +73 -0
  7. mangoautomation/exceptions/_exceptions.py +14 -0
  8. mangoautomation/models/__init__.py +15 -0
  9. mangoautomation/models/_ui_model.py +61 -0
  10. mangoautomation/tools/__init__.py +13 -0
  11. mangoautomation/tools/_mate.py +12 -0
  12. mangoautomation/uidrive/__init__.py +20 -0
  13. mangoautomation/uidrive/_async_element.py +286 -0
  14. mangoautomation/uidrive/_base_data.py +103 -0
  15. mangoautomation/uidrive/_driver_object.py +63 -0
  16. mangoautomation/uidrive/_sync_element.py +286 -0
  17. mangoautomation/uidrive/android/__init__.py +127 -0
  18. mangoautomation/uidrive/android/_application.py +69 -0
  19. mangoautomation/uidrive/android/_assertion.py +84 -0
  20. mangoautomation/uidrive/android/_customization.py +15 -0
  21. mangoautomation/uidrive/android/_element.py +168 -0
  22. mangoautomation/uidrive/android/_equipment.py +150 -0
  23. mangoautomation/uidrive/android/_new_android.py +54 -0
  24. mangoautomation/uidrive/android/_page.py +116 -0
  25. mangoautomation/uidrive/pc/__init__.py +80 -0
  26. mangoautomation/uidrive/pc/assertion.py +5 -0
  27. mangoautomation/uidrive/pc/customization.py +10 -0
  28. mangoautomation/uidrive/pc/element.py +21 -0
  29. mangoautomation/uidrive/pc/input_device.py +14 -0
  30. mangoautomation/uidrive/pc/new_windows.py +79 -0
  31. mangoautomation/uidrive/web/__init__.py +5 -0
  32. mangoautomation/uidrive/web/async_web/__init__.py +174 -0
  33. mangoautomation/uidrive/web/async_web/_assertion.py +290 -0
  34. mangoautomation/uidrive/web/async_web/_browser.py +97 -0
  35. mangoautomation/uidrive/web/async_web/_customization.py +14 -0
  36. mangoautomation/uidrive/web/async_web/_element.py +199 -0
  37. mangoautomation/uidrive/web/async_web/_input_device.py +83 -0
  38. mangoautomation/uidrive/web/async_web/_new_browser.py +151 -0
  39. mangoautomation/uidrive/web/async_web/_page.py +62 -0
  40. mangoautomation/uidrive/web/sync_web/__init__.py +174 -0
  41. mangoautomation/uidrive/web/sync_web/_assertion.py +282 -0
  42. mangoautomation/uidrive/web/sync_web/_browser.py +96 -0
  43. mangoautomation/uidrive/web/sync_web/_customization.py +14 -0
  44. mangoautomation/uidrive/web/sync_web/_element.py +198 -0
  45. mangoautomation/uidrive/web/sync_web/_input_device.py +79 -0
  46. mangoautomation/uidrive/web/sync_web/_new_browser.py +146 -0
  47. mangoautomation/uidrive/web/sync_web/_page.py +61 -0
  48. mangoautomation-1.0.0.dist-info/LICENSE +21 -0
  49. mangoautomation-1.0.0.dist-info/METADATA +30 -0
  50. mangoautomation-1.0.0.dist-info/RECORD +55 -0
  51. mangoautomation-1.0.0.dist-info/WHEEL +5 -0
  52. mangoautomation-1.0.0.dist-info/top_level.txt +2 -0
  53. tests/__init__.py +5 -0
  54. tests/test_ui_and.py +29 -0
  55. tests/test_ui_web.py +77 -0
@@ -0,0 +1,286 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Project: 芒果测试平台
3
+ # @Description:
4
+ # @Time : 2025-04-12 15:53
5
+ # @Author : 毛鹏
6
+ import asyncio
7
+ import os
8
+ import traceback
9
+
10
+ from playwright._impl._errors import TargetClosedError, Error, TimeoutError
11
+
12
+ from mangotools.assertion import PublicAssertion
13
+ from mangotools.decorator import async_retry
14
+ from mangotools.enums import StatusEnum
15
+ from ..enums import ElementOperationEnum, DriveTypeEnum
16
+ from ..exceptions import MangoAutomationError
17
+ from ..exceptions._error_msg import *
18
+ from ..models import ElementResultModel, ElementModel
19
+ from ..uidrive.android import AndroidDriver
20
+ from ..uidrive.web.async_web import AsyncWebDevice, AsyncWebAssertion
21
+
22
+
23
+ class AsyncElement(AsyncWebDevice, AndroidDriver):
24
+
25
+ def __init__(self, base_data, element_model: ElementModel, drive_type: int, element_data: dict | None = None):
26
+ super().__init__(base_data)
27
+ self.element_data = element_data
28
+ self.element_model = element_model
29
+ self.drive_type = drive_type
30
+ self.ope_name = element_model.name if element_model.name else element_model.ope_key
31
+ self.element_result_model = ElementResultModel(
32
+ id=self.element_model.id,
33
+ name=self.element_model.name,
34
+ loc=self.element_model.loc,
35
+ exp=self.element_model.exp,
36
+ sub=self.element_model.sub,
37
+ sleep=self.element_model.sleep,
38
+
39
+ type=self.element_model.type,
40
+ ope_key=self.element_model.ope_key,
41
+ sql=self.element_model.sql,
42
+ key_list=self.element_model.key_list,
43
+ key=self.element_model.key,
44
+ value=self.element_model.value,
45
+
46
+ status=StatusEnum.FAIL.value,
47
+ )
48
+
49
+ async def open_device(self, is_open: bool = False):
50
+ if self.drive_type == DriveTypeEnum.WEB.value:
51
+ await self.open_url(is_open)
52
+ elif self.drive_type == DriveTypeEnum.ANDROID.value:
53
+ self.open_app()
54
+ elif self.drive_type == DriveTypeEnum.DESKTOP.value:
55
+ pass
56
+ else:
57
+ self.base_data.log.debug(f'不存在这个类型,如果是非管理员看到这种提示,请联系管理员')
58
+ raise Exception('不存在的设备类型')
59
+
60
+ async def clean_data(self):
61
+ try:
62
+ for field_name, field_value in self.element_model:
63
+ if field_value is None:
64
+ continue
65
+ if field_name == "ope_value":
66
+ for method_model in field_value:
67
+ for method_field, method_val in method_model:
68
+ if isinstance(method_val, str):
69
+ setattr(method_model, method_field, self.base_data.test_data.replace(method_val))
70
+ elif isinstance(field_value, str):
71
+ setattr(self.element_model, field_name, self.base_data.test_data.replace(field_value))
72
+ except MangoAutomationError as error:
73
+ self.base_data.log.debug(f'操作元素解析数据失败,类型:{type(error)}, 详情:{error}')
74
+ raise MangoAutomationError(error.code, error.msg)
75
+
76
+ async def element_main(self) -> ElementResultModel:
77
+ try:
78
+ await self.__main()
79
+ if self.element_model.sleep:
80
+ await asyncio.sleep(self.element_model.sleep)
81
+ self.element_result_model.status = StatusEnum.SUCCESS.value
82
+ self.element_result_model.error_message = None
83
+ except TargetClosedError as error:
84
+ self.base_data.setup()
85
+ self.base_data.log.debug(
86
+ f'浏览器关闭异常,类型:{type(error)},失败详情:{error},失败明细:{traceback.format_exc()}')
87
+ self.element_result_model.status = StatusEnum.FAIL.value
88
+ self.element_result_model.error_message = '浏览器被关闭,请不要认关闭浏览器,非认为管理请联系管理员解决!'
89
+ except MangoAutomationError as error:
90
+ await self.__error(error.msg)
91
+ self.base_data.log.debug(f'已知异常,类型:{type(error)},失败详情:{error}')
92
+ except Error as error:
93
+ await self.__error(f'未知错误失败,请检查测试数据,如果需要明确的提示请联系管理员,提示:{error.message}')
94
+ self.base_data.log.error(
95
+ f'未知错误捕获-1,类型:{type(error)},失败详情:{error},失败明细:{traceback.format_exc()}')
96
+ except Exception as error:
97
+ error_msg = f'未知错误失败,请检查测试数据,如果需要明确的提示请联系管理员,提示:{error.args}'
98
+ if hasattr(error, 'msg'):
99
+ error_msg = error.msg
100
+ await self.__error(error_msg)
101
+ self.base_data.log.error(
102
+ f'未知错误捕获-2,类型:{type(error)},失败详情:{error},失败明细:{traceback.format_exc()}')
103
+ return self.element_result_model
104
+
105
+ @async_retry()
106
+ async def __main(self):
107
+ await self.clean_data()
108
+ if self.element_model.type == ElementOperationEnum.OPE.value:
109
+ await self.__ope()
110
+ elif self.element_model.type == ElementOperationEnum.ASS.value:
111
+ await self.__ass()
112
+ elif self.element_model.type == ElementOperationEnum.SQL.value:
113
+ await self.__sql()
114
+ elif self.element_model.type == ElementOperationEnum.CUSTOM.value:
115
+ await self.__custom()
116
+ else:
117
+ raise MangoAutomationError(*ERROR_MSG_0015)
118
+
119
+ async def __ope(self):
120
+ method_name = getattr(self.element_model, 'ope_key', None)
121
+ if not method_name:
122
+ self.base_data.log.debug('操作失败-1,ope_key 不存在或为空')
123
+ raise MangoAutomationError(*ERROR_MSG_0048)
124
+ if not hasattr(self, method_name):
125
+ self.base_data.log.debug(f'操作失败-2,方法不存在: {method_name}')
126
+ raise MangoAutomationError(*ERROR_MSG_0048)
127
+ if not callable(getattr(self, method_name)):
128
+ self.base_data.log.debug(f'操作失败-3,属性不可调用: {method_name}')
129
+ raise MangoAutomationError(*ERROR_MSG_0048)
130
+ if self.element_model.ope_value is None:
131
+ raise MangoAutomationError(*ERROR_MSG_0054)
132
+
133
+ await self.__ope_value()
134
+ if self.drive_type == DriveTypeEnum.WEB.value:
135
+ await self.web_action_element(
136
+ self.element_model.name,
137
+ self.element_model.ope_key,
138
+ {i.f: i.v for i in self.element_model.ope_value}
139
+ )
140
+ elif self.drive_type == DriveTypeEnum.ANDROID.value:
141
+ self.a_action_element(
142
+ self.element_model.name,
143
+ self.element_model.ope_key,
144
+ {i.f: i.v for i in self.element_model.ope_value}
145
+ )
146
+ else:
147
+ pass
148
+ for i in self.element_model.ope_value:
149
+ if i.d:
150
+ self.element_result_model.ope_value[i.f] = i.v
151
+
152
+ async def __ass(self):
153
+ if self.element_model.ope_value is None:
154
+ raise MangoAutomationError(*ERROR_MSG_0053)
155
+ await self.__ope_value(True)
156
+ if self.drive_type == DriveTypeEnum.WEB.value:
157
+ await self.web_assertion_element(
158
+ self.element_model.name,
159
+ self.element_model.ope_key,
160
+ {i.f: i.v for i in self.element_model.ope_value}
161
+ )
162
+ elif self.drive_type == DriveTypeEnum.ANDROID.value:
163
+ self.a_assertion_element(
164
+ self.element_model.name,
165
+ self.element_model.ope_key,
166
+ {i.f: i.v for i in self.element_model.ope_value}
167
+ )
168
+ else:
169
+ pass
170
+ for i in self.element_model.ope_value:
171
+ if i.d:
172
+ self.element_result_model.ope_value[i.f] = i.v
173
+
174
+ async def __sql(self):
175
+ if not self.element_data:
176
+ sql = self.base_data.test_data.replace(self.element_model.sql)
177
+ key_list = self.element_model.key_list
178
+ else:
179
+ sql = self.base_data.test_data.replace(self.element_data.get('sql'))
180
+ key_list = self.element_data.get('key_list')
181
+ if self.base_data.mysql_connect:
182
+ result_list: list[dict] = self.base_data.mysql_connect.condition_execute(sql)
183
+ if isinstance(result_list, list):
184
+ for result in result_list:
185
+ try:
186
+ for value, key in zip(result, key_list):
187
+ self.base_data.test_data.set_cache(key, result.get(value))
188
+ except SyntaxError as error:
189
+ self.base_data.log.debug(
190
+ f'SQL执行失败-1,类型:{type(error)},失败详情:{error},失败明细:{traceback.format_exc()}')
191
+ raise MangoAutomationError(*ERROR_MSG_0038)
192
+
193
+ if not result_list:
194
+ raise MangoAutomationError(*ERROR_MSG_0036, value=(self.element_model.sql,))
195
+
196
+ async def __custom(self):
197
+ if not self.element_data:
198
+ key = self.element_model.key
199
+ value = self.element_model.value
200
+ else:
201
+ key = self.element_data.get('key')
202
+ value = self.element_data.get('value')
203
+ self.base_data.test_data.set_cache(key, self.base_data.test_data.replace(value))
204
+
205
+ async def __ope_value(self, is_ass: bool = False):
206
+ try:
207
+ ope_key = 'actual' if is_ass else 'locating'
208
+ for i in self.element_model.ope_value:
209
+ if i.f == ope_key and self.element_model.loc:
210
+ find_params = {
211
+ 'name': self.element_model.name,
212
+ '_type': self.element_model.type,
213
+ 'exp': self.element_model.exp,
214
+ 'loc': self.element_model.loc,
215
+ 'sub': self.element_model.sub
216
+ }
217
+ if self.drive_type == DriveTypeEnum.WEB.value:
218
+ loc, \
219
+ self.element_result_model.ele_quantity, \
220
+ self.element_result_model.element_text \
221
+ = await self.web_find_ele(**find_params, is_iframe=self.element_model.is_iframe)
222
+ elif self.drive_type == DriveTypeEnum.ANDROID.value:
223
+ loc, \
224
+ self.element_result_model.ele_quantity, \
225
+ self.element_result_model.element_text \
226
+ = self.a_find_ele(**find_params)
227
+ else:
228
+ loc, \
229
+ self.element_result_model.ele_quantity, \
230
+ self.element_result_model.element_text \
231
+ = None, 0, None
232
+
233
+ if is_ass:
234
+ if callable(getattr(AsyncWebAssertion, self.element_model.ope_key, None)):
235
+ i.v = loc
236
+ elif callable(getattr(PublicAssertion, self.element_model.ope_key, None)):
237
+ i.v = self.element_result_model.element_text
238
+ else:
239
+ i.v = loc
240
+ else:
241
+ if self.element_data:
242
+ for ele_name, case_data in self.element_data.items():
243
+ if ele_name == i.f:
244
+ value = case_data
245
+ i.v = self.base_data.test_data.replace(value)
246
+
247
+ except AttributeError as error:
248
+ self.base_data.log.debug(
249
+ f'获取操作值失败-1,类型:{type(error)},失败详情:{error},失败明细:{traceback.format_exc()}')
250
+ raise MangoAutomationError(*ERROR_MSG_0027)
251
+
252
+ async def __error(self, msg: str):
253
+ self.element_result_model.status = StatusEnum.FAIL.value
254
+ self.element_result_model.error_message = msg
255
+ self.base_data.log.debug(
256
+ f"""
257
+ 元素操作失败----->
258
+ 元 素 对 象:{self.element_model.model_dump() if self.element_model else self.element_model}
259
+ 元素测试结果:{
260
+ self.element_result_model.model_dump() if self.element_result_model else self.element_result_model}
261
+ """
262
+ )
263
+ file_name = f'失败截图-{self.element_model.name}{self.base_data.test_data.get_time_for_min()}.jpg'
264
+ file_path = os.path.join(self.base_data.screenshot_path, file_name)
265
+ self.element_result_model.picture_path = file_path
266
+ self.element_result_model.picture_name = file_name
267
+ await self.__error_screenshot(file_path)
268
+
269
+ async def __error_screenshot(self, file_path):
270
+ if self.drive_type == DriveTypeEnum.WEB.value:
271
+ try:
272
+ await self.w_screenshot(file_path)
273
+ except (TargetClosedError, TimeoutError) as error:
274
+ self.base_data.log.debug(
275
+ f'截图出现异常失败-1,类型:{type(error)},失败详情:{error},失败明细:{traceback.format_exc()}')
276
+ self.base_data.setup()
277
+ raise MangoAutomationError(*ERROR_MSG_0010)
278
+ except AttributeError as error:
279
+ self.base_data.log.debug(
280
+ f'截图出现异常失败-2,类型:{type(error)},失败详情:{error},失败明细:{traceback.format_exc()}')
281
+ self.base_data.setup()
282
+ raise MangoAutomationError(*ERROR_MSG_0010)
283
+ elif self.drive_type == DriveTypeEnum.ANDROID.value:
284
+ self.a_screenshot(file_path)
285
+ else:
286
+ pass
@@ -0,0 +1,103 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Project: 芒果测试平台
3
+ # @Description:
4
+ # @Time : 2024-02-04 10:43
5
+ # @Author : 毛鹏
6
+ from typing import Optional
7
+ from unittest.mock import MagicMock
8
+
9
+ import sys
10
+ from uiautomator2 import Device
11
+
12
+ from mangotools.data_processor import DataProcessor
13
+ from mangotools.database import MysqlConnect
14
+ from mangotools.enums import StatusEnum
15
+ from mangotools.models import MysqlConingModel
16
+
17
+ if not sys.platform.startswith('win32'):
18
+ WindowControl = MagicMock()
19
+ print("警告: uiautomation 仅支持 Windows,当前环境已自动跳过")
20
+ else:
21
+ from uiautomation import WindowControl
22
+
23
+
24
+ class BaseData:
25
+
26
+ def __init__(self, test_data: DataProcessor, log):
27
+ super().__init__()
28
+ self.test_data = test_data
29
+ self.log = log
30
+ self.download_path: Optional[str | None] = None
31
+ self.screenshot_path: Optional[str | None] = None
32
+
33
+ self.mysql_config: Optional[MysqlConingModel | None] = None
34
+ self.mysql_connect: Optional[MysqlConnect | None] = None
35
+
36
+ self.url: Optional[str | None] = None
37
+ self.is_open_url = False
38
+ self.page = None
39
+ self.context = None
40
+
41
+ self.package_name: Optional[str | None] = None
42
+ self.android: Optional[Device | None] = None
43
+ self.is_open_app = False
44
+
45
+ self.window: Optional[None | WindowControl] = None
46
+
47
+ def set_file_path(self, download_path, screenshot_path):
48
+ self.download_path = download_path
49
+ self.screenshot_path = screenshot_path
50
+ return self
51
+
52
+ def set_url(self, url: str):
53
+ self.url = url
54
+ return self
55
+
56
+ def set_page_context(self, page, context):
57
+ self.page = page
58
+ self.context = context
59
+ return self
60
+
61
+ def set_package_name(self, package_name: str):
62
+ self.package_name = package_name
63
+ return self
64
+
65
+ def set_android(self, android: Device):
66
+ self.android = android
67
+ return self
68
+
69
+ def setup(self) -> None:
70
+ self.url = None
71
+ self.page = None
72
+ self.context = None
73
+
74
+ self.package_name = None
75
+ self.android = None
76
+ self.mysql_connect = None
77
+ self.mysql_config = None
78
+
79
+ async def async_base_close(self):
80
+ if self.context:
81
+ await self.context.close()
82
+ if self.page:
83
+ await self.page.close()
84
+ if self.mysql_connect:
85
+ self.mysql_connect.close()
86
+ self.setup()
87
+
88
+ def sync_base_close(self):
89
+ if self.context:
90
+ self.context.close()
91
+ if self.page:
92
+ self.page.close()
93
+ if self.mysql_connect:
94
+ self.mysql_connect.close()
95
+ self.setup()
96
+
97
+ def set_mysql(self, db_c_status, db_rud_status, mysql_config: MysqlConingModel):
98
+ self.mysql_config = mysql_config
99
+ if StatusEnum.SUCCESS.value in [db_c_status, db_rud_status]:
100
+ self.mysql_connect = MysqlConnect(mysql_config,
101
+ bool(db_c_status),
102
+ bool(db_rud_status))
103
+ return self
@@ -0,0 +1,63 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Project: 芒果测试平台
3
+ # @Description:
4
+ # @Time : 2024-04-24 10:43
5
+ # @Author : 毛鹏
6
+
7
+ from typing import Optional
8
+
9
+ from ..uidrive.android._new_android import NewAndroid
10
+ from ..uidrive.pc.new_windows import NewWindows
11
+ from ..uidrive.web.async_web._new_browser import AsyncWebNewBrowser
12
+ from ..uidrive.web.sync_web._new_browser import SyncWebNewBrowser
13
+
14
+
15
+ class DriverObject:
16
+
17
+ def __init__(self, is_async=False):
18
+ self.is_async = is_async
19
+ self.web: Optional[AsyncWebNewBrowser | SyncWebNewBrowser] = None
20
+ self.android: Optional[NewAndroid] = None
21
+ self.windows: Optional[NewWindows] = None
22
+
23
+ def set_web(self,
24
+ web_type: int,
25
+ web_path: str | None = None,
26
+ web_max=False,
27
+ web_headers=False,
28
+ web_recording=False,
29
+ web_h5=None,
30
+ is_header_intercept=False,
31
+ web_is_default=False,
32
+ videos_path=None
33
+ ):
34
+ if self.is_async:
35
+ self.web = AsyncWebNewBrowser(
36
+ web_type,
37
+ web_path,
38
+ web_max,
39
+ web_headers,
40
+ web_recording,
41
+ web_h5,
42
+ is_header_intercept,
43
+ web_is_default,
44
+ videos_path
45
+ )
46
+ else:
47
+ self.web = SyncWebNewBrowser(
48
+ web_type,
49
+ web_path,
50
+ web_max,
51
+ web_headers,
52
+ web_recording,
53
+ web_h5,
54
+ is_header_intercept,
55
+ web_is_default,
56
+ videos_path
57
+ )
58
+
59
+ def set_android(self, and_equipment: str):
60
+ self.android = NewAndroid(and_equipment)
61
+
62
+ def set_windows(self, win_path: str, win_title: str):
63
+ self.windows = NewWindows(win_path, win_title)