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.
- mangoautomation/__init__.py +5 -0
- mangoautomation/enums/__init__.py +19 -0
- mangoautomation/enums/_base_enum.py +38 -0
- mangoautomation/enums/_ui_enum.py +110 -0
- mangoautomation/exceptions/__init__.py +14 -0
- mangoautomation/exceptions/_error_msg.py +73 -0
- mangoautomation/exceptions/_exceptions.py +14 -0
- mangoautomation/models/__init__.py +15 -0
- mangoautomation/models/_ui_model.py +61 -0
- mangoautomation/tools/__init__.py +13 -0
- mangoautomation/tools/_mate.py +12 -0
- mangoautomation/uidrive/__init__.py +20 -0
- mangoautomation/uidrive/_async_element.py +286 -0
- mangoautomation/uidrive/_base_data.py +103 -0
- mangoautomation/uidrive/_driver_object.py +63 -0
- mangoautomation/uidrive/_sync_element.py +286 -0
- mangoautomation/uidrive/android/__init__.py +127 -0
- mangoautomation/uidrive/android/_application.py +69 -0
- mangoautomation/uidrive/android/_assertion.py +84 -0
- mangoautomation/uidrive/android/_customization.py +15 -0
- mangoautomation/uidrive/android/_element.py +168 -0
- mangoautomation/uidrive/android/_equipment.py +150 -0
- mangoautomation/uidrive/android/_new_android.py +54 -0
- mangoautomation/uidrive/android/_page.py +116 -0
- mangoautomation/uidrive/pc/__init__.py +80 -0
- mangoautomation/uidrive/pc/assertion.py +5 -0
- mangoautomation/uidrive/pc/customization.py +10 -0
- mangoautomation/uidrive/pc/element.py +21 -0
- mangoautomation/uidrive/pc/input_device.py +14 -0
- mangoautomation/uidrive/pc/new_windows.py +79 -0
- mangoautomation/uidrive/web/__init__.py +5 -0
- mangoautomation/uidrive/web/async_web/__init__.py +174 -0
- mangoautomation/uidrive/web/async_web/_assertion.py +290 -0
- mangoautomation/uidrive/web/async_web/_browser.py +97 -0
- mangoautomation/uidrive/web/async_web/_customization.py +14 -0
- mangoautomation/uidrive/web/async_web/_element.py +199 -0
- mangoautomation/uidrive/web/async_web/_input_device.py +83 -0
- mangoautomation/uidrive/web/async_web/_new_browser.py +151 -0
- mangoautomation/uidrive/web/async_web/_page.py +62 -0
- mangoautomation/uidrive/web/sync_web/__init__.py +174 -0
- mangoautomation/uidrive/web/sync_web/_assertion.py +282 -0
- mangoautomation/uidrive/web/sync_web/_browser.py +96 -0
- mangoautomation/uidrive/web/sync_web/_customization.py +14 -0
- mangoautomation/uidrive/web/sync_web/_element.py +198 -0
- mangoautomation/uidrive/web/sync_web/_input_device.py +79 -0
- mangoautomation/uidrive/web/sync_web/_new_browser.py +146 -0
- mangoautomation/uidrive/web/sync_web/_page.py +61 -0
- mangoautomation-1.0.0.dist-info/LICENSE +21 -0
- mangoautomation-1.0.0.dist-info/METADATA +30 -0
- mangoautomation-1.0.0.dist-info/RECORD +55 -0
- mangoautomation-1.0.0.dist-info/WHEEL +5 -0
- mangoautomation-1.0.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +5 -0
- tests/test_ui_and.py +29 -0
- tests/test_ui_web.py +77 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# @Project: 芒果测试平台
|
|
3
|
+
# @Description: # @Time : 2023-04-26 22:22
|
|
4
|
+
# @Author : 毛鹏
|
|
5
|
+
import asyncio
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
from playwright.async_api import Locator, Error
|
|
10
|
+
|
|
11
|
+
from mangotools.decorator import async_method_callback
|
|
12
|
+
from mangotools.models import MethodModel
|
|
13
|
+
from ..._base_data import BaseData
|
|
14
|
+
from ....exceptions import MangoAutomationError
|
|
15
|
+
from ....exceptions._error_msg import ERROR_MSG_0024, ERROR_MSG_0056
|
|
16
|
+
from ....tools import Meta
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AsyncWebElement(metaclass=Meta):
|
|
20
|
+
"""元素操作"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, base_data: BaseData):
|
|
23
|
+
self.base_data = base_data
|
|
24
|
+
|
|
25
|
+
@async_method_callback('web', '元素操作', 0, [MethodModel(f='locating')])
|
|
26
|
+
async def w_click(self, locating: Locator):
|
|
27
|
+
"""元素单击"""
|
|
28
|
+
await locating.click()
|
|
29
|
+
|
|
30
|
+
@async_method_callback('web', '元素操作', 1, [MethodModel(f='locating')])
|
|
31
|
+
async def w_dblclick(self, locating: Locator):
|
|
32
|
+
"""元素双击"""
|
|
33
|
+
await locating.dblclick()
|
|
34
|
+
|
|
35
|
+
@async_method_callback('web', '元素操作', 2, [MethodModel(f='locating')])
|
|
36
|
+
async def w_force_click(self, locating: Locator):
|
|
37
|
+
"""强制单击"""
|
|
38
|
+
await locating.evaluate('element => element.click()')
|
|
39
|
+
|
|
40
|
+
@async_method_callback('web', '元素操作', 3, [
|
|
41
|
+
MethodModel(f='locating'),
|
|
42
|
+
MethodModel(f='input_value', p='请输入输入内容', d=True)])
|
|
43
|
+
async def w_input(self, locating: Locator, input_value: str):
|
|
44
|
+
"""元素输入"""
|
|
45
|
+
await locating.fill(str(input_value))
|
|
46
|
+
|
|
47
|
+
@async_method_callback('web', '元素操作', 4, [MethodModel(f='locating')])
|
|
48
|
+
async def w_hover(self, locating: Locator):
|
|
49
|
+
"""鼠标悬停"""
|
|
50
|
+
await locating.hover()
|
|
51
|
+
await asyncio.sleep(1)
|
|
52
|
+
|
|
53
|
+
@async_method_callback('web', '元素操作', 5, [
|
|
54
|
+
MethodModel(f='locating'),
|
|
55
|
+
MethodModel(f='set_cache_key', p='请输入获取元素文本后存储的key', d=True)])
|
|
56
|
+
async def w_get_text(self, locating: Locator, set_cache_key=None):
|
|
57
|
+
"""获取元素文本"""
|
|
58
|
+
value = await locating.inner_text()
|
|
59
|
+
if set_cache_key:
|
|
60
|
+
self.base_data.test_data.set_cache(key=set_cache_key, value=value)
|
|
61
|
+
|
|
62
|
+
return value
|
|
63
|
+
|
|
64
|
+
@async_method_callback('web', '元素操作', 6, [
|
|
65
|
+
MethodModel(f='locating'),
|
|
66
|
+
MethodModel(f='file_path', p='请输入文件路径,参照帮助文档', d=True)])
|
|
67
|
+
async def w_upload_files(self, locating: Locator, file_path: str | list):
|
|
68
|
+
"""拖拽文件上传"""
|
|
69
|
+
try:
|
|
70
|
+
if isinstance(file_path, str):
|
|
71
|
+
await locating.set_input_files(file_path)
|
|
72
|
+
else:
|
|
73
|
+
for file in file_path:
|
|
74
|
+
await locating.set_input_files(file)
|
|
75
|
+
except Error:
|
|
76
|
+
raise MangoAutomationError(*ERROR_MSG_0024)
|
|
77
|
+
|
|
78
|
+
@async_method_callback('web', '元素操作', 7, [
|
|
79
|
+
MethodModel(f='locating'),
|
|
80
|
+
MethodModel(f='file_path', p='请输入文件路径,参照帮助文档', d=True)])
|
|
81
|
+
async def w_click_upload_files(self, locating: Locator, file_path: str | list):
|
|
82
|
+
"""点击并选择文件上传"""
|
|
83
|
+
async with self.base_data.page.expect_file_chooser() as fc_info:
|
|
84
|
+
await locating.click()
|
|
85
|
+
file_chooser = await fc_info.value
|
|
86
|
+
await file_chooser.set_files(file_path)
|
|
87
|
+
|
|
88
|
+
@async_method_callback('web', '元素操作', 8, [
|
|
89
|
+
MethodModel(f='locating'),
|
|
90
|
+
MethodModel(f='file_key', p='请输入文件存储路径的key,后续通过key获取文件保存的绝对路径', d=True)])
|
|
91
|
+
async def w_download(self, locating: Locator, file_key: str):
|
|
92
|
+
"""下载文件"""
|
|
93
|
+
async with self.base_data.page.expect_download() as download_info:
|
|
94
|
+
await locating.click()
|
|
95
|
+
download = await download_info.value
|
|
96
|
+
file_name = download.suggested_filename
|
|
97
|
+
save_path = os.path.join(self.base_data.download_path, file_name)
|
|
98
|
+
await download.save_as(save_path)
|
|
99
|
+
self.base_data.test_data.set_cache(file_key, file_name)
|
|
100
|
+
|
|
101
|
+
@async_method_callback('web', '元素操作', 9, [
|
|
102
|
+
MethodModel(f='locating1'),
|
|
103
|
+
MethodModel(f='locating2')])
|
|
104
|
+
async def w_drag_to(self, locating1: Locator, locating2: Locator):
|
|
105
|
+
"""拖动A元素到达B"""
|
|
106
|
+
await locating1.drag_to(locating2)
|
|
107
|
+
|
|
108
|
+
@async_method_callback('web', '元素操作', 10, [
|
|
109
|
+
MethodModel(f='locating'),
|
|
110
|
+
MethodModel(f='n', p='请输入循环点击的时间', d=True)])
|
|
111
|
+
async def w_time_click(self, locating: Locator, n: int):
|
|
112
|
+
"""循环点击N秒"""
|
|
113
|
+
try:
|
|
114
|
+
n = int(n)
|
|
115
|
+
except ValueError:
|
|
116
|
+
raise MangoAutomationError(*ERROR_MSG_0056)
|
|
117
|
+
s = time.time()
|
|
118
|
+
while True:
|
|
119
|
+
await locating.click()
|
|
120
|
+
if time.time() - s > n:
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
@async_method_callback('web', '元素操作', 11, [
|
|
124
|
+
MethodModel(f='locating'),
|
|
125
|
+
MethodModel(f='n', p='请输入向上像素', d=True)])
|
|
126
|
+
async def w_drag_up_pixel(self, locating: Locator, n: int):
|
|
127
|
+
"""往上拖动N个像素"""
|
|
128
|
+
try:
|
|
129
|
+
n = int(n)
|
|
130
|
+
except ValueError:
|
|
131
|
+
raise MangoAutomationError(*ERROR_MSG_0056)
|
|
132
|
+
|
|
133
|
+
box = await locating.bounding_box()
|
|
134
|
+
|
|
135
|
+
if box:
|
|
136
|
+
await self.base_data.page.mouse.move(box['x'] + box['width'] / 2, box['y'] + box['height'] / 2)
|
|
137
|
+
await self.base_data.page.mouse.down()
|
|
138
|
+
await self.base_data.page.mouse.move(box['x'] + box['width'] / 2, box['y'] + box['height'] / 2 - n)
|
|
139
|
+
await self.base_data.page.mouse.up()
|
|
140
|
+
|
|
141
|
+
@async_method_callback('web', '元素操作', 12, [
|
|
142
|
+
MethodModel(f='locating'),
|
|
143
|
+
MethodModel(f='n', p='请输入向下像素', d=True)])
|
|
144
|
+
async def w_drag_down_pixel(self, locating: Locator, n: int):
|
|
145
|
+
"""往下拖动N个像素"""
|
|
146
|
+
try:
|
|
147
|
+
n = int(n)
|
|
148
|
+
except ValueError:
|
|
149
|
+
raise MangoAutomationError(*ERROR_MSG_0056)
|
|
150
|
+
|
|
151
|
+
box = await locating.bounding_box()
|
|
152
|
+
|
|
153
|
+
if box:
|
|
154
|
+
await self.base_data.page.mouse.move(box['x'] + box['width'] / 2, box['y'] + box['height'] / 2)
|
|
155
|
+
await self.base_data.page.mouse.down()
|
|
156
|
+
await self.base_data.page.mouse.move(box['x'] + box['width'] / 2, box['y'] + box['height'] / 2 + n)
|
|
157
|
+
await self.base_data.page.mouse.up()
|
|
158
|
+
|
|
159
|
+
@async_method_callback('web', '元素操作', 13, [
|
|
160
|
+
MethodModel(f='locating'),
|
|
161
|
+
MethodModel(f='n', p='请输入向左像素', d=True)])
|
|
162
|
+
async def w_drag_left_pixel(self, locating: Locator, n: int):
|
|
163
|
+
"""往左拖动N个像素"""
|
|
164
|
+
try:
|
|
165
|
+
n = int(n)
|
|
166
|
+
except ValueError:
|
|
167
|
+
raise MangoAutomationError(*ERROR_MSG_0056)
|
|
168
|
+
|
|
169
|
+
box = await locating.bounding_box()
|
|
170
|
+
|
|
171
|
+
if box:
|
|
172
|
+
await self.base_data.page.mouse.move(box['x'] + box['width'] / 2, box['y'] + box['height'] / 2)
|
|
173
|
+
await self.base_data.page.mouse.down()
|
|
174
|
+
await self.base_data.page.mouse.move(box['x'] + box['width'] / 2 - n, box['y'] + box['height'] / 2)
|
|
175
|
+
await self.base_data.page.mouse.up()
|
|
176
|
+
|
|
177
|
+
@async_method_callback('web', '元素操作', 14, [
|
|
178
|
+
MethodModel(f='locating'),
|
|
179
|
+
MethodModel(f='n', p='请输入向右像素', d=True)])
|
|
180
|
+
async def w_drag_right_pixel(self, locating: Locator, n: int):
|
|
181
|
+
"""往右拖动N个像素"""
|
|
182
|
+
try:
|
|
183
|
+
n = int(n)
|
|
184
|
+
except ValueError:
|
|
185
|
+
raise MangoAutomationError(*ERROR_MSG_0056)
|
|
186
|
+
box = await locating.bounding_box()
|
|
187
|
+
|
|
188
|
+
if box:
|
|
189
|
+
await self.base_data.page.mouse.move(box['x'] + box['width'] / 2, box['y'] + box['height'] / 2)
|
|
190
|
+
await self.base_data.page.mouse.down()
|
|
191
|
+
await self.base_data.page.mouse.move(box['x'] + box['width'] / 2 + n, box['y'] + box['height'] / 2)
|
|
192
|
+
await self.base_data.page.mouse.up()
|
|
193
|
+
|
|
194
|
+
@async_method_callback('web', '元素操作', 15, [
|
|
195
|
+
MethodModel(f='locating'),
|
|
196
|
+
MethodModel(f='path', p='请输入截图保存路径', d=True)])
|
|
197
|
+
async def w_ele_screenshot(self, locating: Locator, path: str):
|
|
198
|
+
"""元素截图"""
|
|
199
|
+
await locating.screenshot(path=path)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# @Project: 芒果测试平台
|
|
3
|
+
# @Description: # @Time : 2023-04-29 12:11
|
|
4
|
+
# @Author : 毛鹏
|
|
5
|
+
from playwright.async_api import Locator
|
|
6
|
+
|
|
7
|
+
from mangotools.decorator import async_method_callback
|
|
8
|
+
from mangotools.models import MethodModel
|
|
9
|
+
from ....tools import Meta
|
|
10
|
+
from ....uidrive._base_data import BaseData
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AsyncWebDeviceInput(metaclass=Meta):
|
|
14
|
+
"""输入设备"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, base_data: BaseData):
|
|
17
|
+
self.base_data = base_data
|
|
18
|
+
|
|
19
|
+
@async_method_callback('web', '输入设备', 0, [
|
|
20
|
+
MethodModel(f='keyboard', p='请输入键盘名称,首字母大写', d=True)])
|
|
21
|
+
async def w_keys(self, keyboard: str):
|
|
22
|
+
"""模拟按下指定的键"""
|
|
23
|
+
await self.base_data.page.keyboard.press(str(keyboard))
|
|
24
|
+
|
|
25
|
+
async def w_element_wheel(self, locating: Locator):
|
|
26
|
+
"""滚动到元素位置"""
|
|
27
|
+
await locating.scroll_into_view_if_needed()
|
|
28
|
+
|
|
29
|
+
@async_method_callback('web', '输入设备', 1, [
|
|
30
|
+
MethodModel(f='y', p='请输入向上滚动像素', d=True)])
|
|
31
|
+
async def w_wheel(self, y):
|
|
32
|
+
"""鼠标上下滚动像素,负数代表向上"""
|
|
33
|
+
await self.base_data.page.mouse.wheel(0, int(y))
|
|
34
|
+
|
|
35
|
+
@async_method_callback('web', '输入设备', 2, [
|
|
36
|
+
MethodModel(f='x', p='请输入点击的x轴', d=True), MethodModel(f='y', p='请输入点击的y轴', d=True)])
|
|
37
|
+
async def w_mouse_click(self, x: float, y: float):
|
|
38
|
+
"""鼠标点击坐标"""
|
|
39
|
+
await self.base_data.page.mouse.click(float(x), float(y))
|
|
40
|
+
|
|
41
|
+
@async_method_callback('web', '输入设备', 3)
|
|
42
|
+
async def w_mouse_center(self):
|
|
43
|
+
"""鼠标移动到中间"""
|
|
44
|
+
|
|
45
|
+
viewport_size = await self.base_data.page.evaluate('''() => {
|
|
46
|
+
return {
|
|
47
|
+
width: window.innerWidth,
|
|
48
|
+
height: window.innerHeight
|
|
49
|
+
}
|
|
50
|
+
}''')
|
|
51
|
+
center_x = viewport_size['width'] / 2
|
|
52
|
+
center_y = viewport_size['height'] / 2
|
|
53
|
+
await self.base_data.page.mouse.move(center_x, center_y)
|
|
54
|
+
|
|
55
|
+
@async_method_callback('web', '输入设备', 4)
|
|
56
|
+
async def w_mouse_center(self):
|
|
57
|
+
"""鼠标移动到中间并点击"""
|
|
58
|
+
|
|
59
|
+
viewport_size = await self.base_data.page.evaluate('''() => {
|
|
60
|
+
return {
|
|
61
|
+
width: window.innerWidth,
|
|
62
|
+
height: window.innerHeight
|
|
63
|
+
}
|
|
64
|
+
}''')
|
|
65
|
+
center_x = viewport_size['width'] / 2
|
|
66
|
+
center_y = viewport_size['height'] / 2
|
|
67
|
+
await self.base_data.page.mouse.click(center_x, center_y)
|
|
68
|
+
|
|
69
|
+
@async_method_callback('web', '输入设备', 5, [MethodModel(f='text', p='请输入键盘输入的内容', d=True)])
|
|
70
|
+
async def w_keyboard_type_text(self, text: str):
|
|
71
|
+
"""模拟人工输入文字"""
|
|
72
|
+
await self.base_data.page.keyboard.type(str(text))
|
|
73
|
+
|
|
74
|
+
@async_method_callback('web', '输入设备', 6, [MethodModel(f='text', p='请输入键盘输入的内容', d=True)])
|
|
75
|
+
async def w_keyboard_insert_text(self, text: str):
|
|
76
|
+
"""直接输入文字"""
|
|
77
|
+
await self.base_data.page.keyboard.insert_text(str(text))
|
|
78
|
+
|
|
79
|
+
@async_method_callback('web', '输入设备', 7, [MethodModel(f='count', p='请输入要删除字符串的个数', d=True)])
|
|
80
|
+
async def w_keyboard_delete_text(self, count: int):
|
|
81
|
+
"""删除光标左侧的字符"""
|
|
82
|
+
for _ in range(0, int(count) + 1):
|
|
83
|
+
await self.base_data.page.keyboard.press("Backspace")
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# @Project: 芒果测试平台
|
|
3
|
+
# @Description:
|
|
4
|
+
# @Time : 2024-05-23 15:04
|
|
5
|
+
# @Author : 毛鹏
|
|
6
|
+
# -*- coding: utf-8 -*-
|
|
7
|
+
# @Project: 芒果测试平台
|
|
8
|
+
# @Description:
|
|
9
|
+
# @Time : 2024-04-24 10:43
|
|
10
|
+
# @Author : 毛鹏
|
|
11
|
+
import asyncio
|
|
12
|
+
import ctypes
|
|
13
|
+
import os
|
|
14
|
+
import string
|
|
15
|
+
import traceback
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
from playwright._impl._errors import Error
|
|
19
|
+
from playwright.async_api import async_playwright, Page, BrowserContext, Browser, Playwright, Request, Route
|
|
20
|
+
|
|
21
|
+
from ....enums import BrowserTypeEnum
|
|
22
|
+
from ....exceptions import MangoAutomationError
|
|
23
|
+
from ....exceptions._error_msg import ERROR_MSG_0057, ERROR_MSG_0008, ERROR_MSG_0062, ERROR_MSG_0009, ERROR_MSG_0055
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
python -m uiautomator2 init
|
|
27
|
+
python -m weditor
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AsyncWebNewBrowser:
|
|
33
|
+
|
|
34
|
+
def __init__(self,
|
|
35
|
+
web_type: int,
|
|
36
|
+
web_path: str | None = None,
|
|
37
|
+
web_max=False,
|
|
38
|
+
web_headers=False,
|
|
39
|
+
web_recording=False,
|
|
40
|
+
web_h5=None,
|
|
41
|
+
is_header_intercept=False,
|
|
42
|
+
web_is_default=False,
|
|
43
|
+
videos_path=None
|
|
44
|
+
):
|
|
45
|
+
self.lock = asyncio.Lock()
|
|
46
|
+
self.web_type = web_type
|
|
47
|
+
self.web_path = web_path
|
|
48
|
+
self.web_max = web_max
|
|
49
|
+
self.web_headers = web_headers
|
|
50
|
+
self.web_recording = web_recording
|
|
51
|
+
self.web_h5 = web_h5
|
|
52
|
+
self.web_is_default = web_is_default
|
|
53
|
+
self.is_header_intercept = is_header_intercept
|
|
54
|
+
self.videos_path = videos_path
|
|
55
|
+
self.browser_path = ['chrome.exe', 'msedge.exe', 'firefox.exe', '苹果', '360se.exe']
|
|
56
|
+
self.browser: Optional[None | Browser] = None
|
|
57
|
+
self.playwright: Optional[None | Playwright] = None
|
|
58
|
+
|
|
59
|
+
async def new_web_page(self, count=0) -> tuple[BrowserContext, Page]:
|
|
60
|
+
if self.browser is None:
|
|
61
|
+
async with self.lock:
|
|
62
|
+
if self.browser is None:
|
|
63
|
+
self.browser = await self.new_browser()
|
|
64
|
+
await asyncio.sleep(1)
|
|
65
|
+
try:
|
|
66
|
+
context = await self.new_context()
|
|
67
|
+
page = await self.new_page(context)
|
|
68
|
+
return context, page
|
|
69
|
+
except Exception as error:
|
|
70
|
+
self.browser = None
|
|
71
|
+
traceback.format_exc()
|
|
72
|
+
if count >= 3:
|
|
73
|
+
raise MangoAutomationError(*ERROR_MSG_0057)
|
|
74
|
+
else:
|
|
75
|
+
return await self.new_web_page(count=count + 1)
|
|
76
|
+
|
|
77
|
+
async def new_browser(self) -> Browser:
|
|
78
|
+
self.playwright = await async_playwright().start()
|
|
79
|
+
if self.web_type == BrowserTypeEnum.CHROMIUM.value or self.web_type == BrowserTypeEnum.EDGE.value:
|
|
80
|
+
browser = self.playwright.chromium
|
|
81
|
+
elif self.web_type == BrowserTypeEnum.FIREFOX.value:
|
|
82
|
+
browser = self.playwright.firefox
|
|
83
|
+
elif self.web_type == BrowserTypeEnum.WEBKIT.value:
|
|
84
|
+
browser = self.playwright.webkit
|
|
85
|
+
else:
|
|
86
|
+
raise MangoAutomationError(*ERROR_MSG_0008)
|
|
87
|
+
if self.web_is_default:
|
|
88
|
+
try:
|
|
89
|
+
return await browser.launch()
|
|
90
|
+
except Error:
|
|
91
|
+
raise MangoAutomationError(*ERROR_MSG_0062)
|
|
92
|
+
else:
|
|
93
|
+
try:
|
|
94
|
+
if self.web_max:
|
|
95
|
+
return await browser.launch(
|
|
96
|
+
headless=self.web_headers,
|
|
97
|
+
executable_path=self.web_path if self.web_path else self.__search_path(),
|
|
98
|
+
args=['--start-maximized']
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
return await browser.launch(
|
|
102
|
+
headless=self.web_headers,
|
|
103
|
+
executable_path=self.web_path if self.web_path else self.__search_path()
|
|
104
|
+
)
|
|
105
|
+
except Error:
|
|
106
|
+
raise MangoAutomationError(*ERROR_MSG_0009, value=(self.web_path,))
|
|
107
|
+
|
|
108
|
+
async def new_context(self) -> BrowserContext:
|
|
109
|
+
args_dict = {
|
|
110
|
+
'no_viewport': True,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if self.web_recording and self.videos_path:
|
|
114
|
+
args_dict['record_video_dir'] = self.videos_path
|
|
115
|
+
|
|
116
|
+
if self.web_h5:
|
|
117
|
+
del args_dict['no_viewport']
|
|
118
|
+
args_dict.update(self.playwright.devices[self.web_h5])
|
|
119
|
+
return await self.browser.new_context(**args_dict)
|
|
120
|
+
|
|
121
|
+
async def new_page(self, context: BrowserContext) -> Page:
|
|
122
|
+
try:
|
|
123
|
+
page = await context.new_page()
|
|
124
|
+
if self.is_header_intercept:
|
|
125
|
+
await page.route("**/*", self.wen_intercept_request) # 应用拦截函数到页面的所有请求
|
|
126
|
+
return page
|
|
127
|
+
except Error:
|
|
128
|
+
raise MangoAutomationError(*ERROR_MSG_0009, value=(self.web_path,))
|
|
129
|
+
|
|
130
|
+
async def close(self):
|
|
131
|
+
if self.browser:
|
|
132
|
+
await self.browser.close()
|
|
133
|
+
|
|
134
|
+
def __search_path(self, ):
|
|
135
|
+
drives = []
|
|
136
|
+
for letter in string.ascii_uppercase:
|
|
137
|
+
drive = f"{letter}:\\"
|
|
138
|
+
if ctypes.windll.kernel32.GetDriveTypeW(drive) == 3:
|
|
139
|
+
drives.append(drive)
|
|
140
|
+
for i in drives:
|
|
141
|
+
for root, dirs, files in os.walk(i):
|
|
142
|
+
if self.browser_path[self.web_type] in files:
|
|
143
|
+
return os.path.join(root, self.browser_path[self.web_type])
|
|
144
|
+
|
|
145
|
+
raise MangoAutomationError(*ERROR_MSG_0055)
|
|
146
|
+
|
|
147
|
+
async def wen_intercept_request(self, route: Route, request: Request):
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
async def wen_recording_api(self, request: Request, project_product: int):
|
|
151
|
+
pass
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# @Project: 芒果测试平台
|
|
3
|
+
# @Description: # @Time : 2023-04-25 22:33
|
|
4
|
+
# @Author : 毛鹏
|
|
5
|
+
import asyncio
|
|
6
|
+
|
|
7
|
+
from playwright.async_api import Locator
|
|
8
|
+
|
|
9
|
+
from mangotools.decorator import async_method_callback
|
|
10
|
+
from mangotools.models import MethodModel
|
|
11
|
+
from ....tools import Meta
|
|
12
|
+
from ....uidrive._base_data import BaseData
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AsyncWebPage(metaclass=Meta):
|
|
16
|
+
"""页面操作"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, base_data: BaseData):
|
|
19
|
+
self.base_data = base_data
|
|
20
|
+
|
|
21
|
+
@async_method_callback('web', '页面操作', 0, [MethodModel(f='individual', p='请输入页签下标,从1开始数', d=True)])
|
|
22
|
+
async def w_switch_tabs(self, individual: int):
|
|
23
|
+
"""切换页签"""
|
|
24
|
+
pages = self.base_data.context.pages
|
|
25
|
+
await pages[int(individual) + 1].bring_to_front()
|
|
26
|
+
self.base_data.page = pages[int(individual) + 1]
|
|
27
|
+
await asyncio.sleep(1)
|
|
28
|
+
|
|
29
|
+
@async_method_callback('web', '页面操作', 1)
|
|
30
|
+
async def w_close_current_tab(self):
|
|
31
|
+
"""关闭当前页签"""
|
|
32
|
+
await asyncio.sleep(2)
|
|
33
|
+
pages = self.base_data.context.pages
|
|
34
|
+
await pages[-1].close()
|
|
35
|
+
self.base_data.page = pages[0]
|
|
36
|
+
|
|
37
|
+
@async_method_callback('web', '页面操作', 2, [MethodModel(f='locating')])
|
|
38
|
+
async def w_open_new_tab_and_switch(self, locating: Locator):
|
|
39
|
+
"""点击并打开新页签"""
|
|
40
|
+
|
|
41
|
+
await locating.click()
|
|
42
|
+
await asyncio.sleep(2)
|
|
43
|
+
pages = self.base_data.context.pages
|
|
44
|
+
new_page = pages[-1]
|
|
45
|
+
await new_page.bring_to_front()
|
|
46
|
+
self.base_data.page = new_page
|
|
47
|
+
await asyncio.sleep(1)
|
|
48
|
+
|
|
49
|
+
@async_method_callback('web', '页面操作', 3)
|
|
50
|
+
async def w_refresh(self):
|
|
51
|
+
"""刷新页面"""
|
|
52
|
+
await self.base_data.page.reload()
|
|
53
|
+
|
|
54
|
+
@async_method_callback('web', '页面操作', 4)
|
|
55
|
+
async def w_go_back(self):
|
|
56
|
+
"""返回上一页"""
|
|
57
|
+
await self.base_data.page.go_back()
|
|
58
|
+
|
|
59
|
+
@async_method_callback('web', '页面操作', 5)
|
|
60
|
+
async def w_go_forward(self):
|
|
61
|
+
"""前进到下一页"""
|
|
62
|
+
await self.base_data.page.go_forward()
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# @Project: 芒果测试平台
|
|
3
|
+
# @Description:
|
|
4
|
+
# @Time : 2025-04-04 21:42
|
|
5
|
+
# @Author : 毛鹏
|
|
6
|
+
import re
|
|
7
|
+
import traceback
|
|
8
|
+
|
|
9
|
+
from playwright._impl._errors import TimeoutError, Error, TargetClosedError
|
|
10
|
+
from playwright.sync_api._generated import Locator
|
|
11
|
+
|
|
12
|
+
from mangotools.assertion import PublicAssertion, SqlAssertion
|
|
13
|
+
from mangotools.mangos import Mango
|
|
14
|
+
from mangotools.enums import StatusEnum
|
|
15
|
+
from ....enums import ElementExpEnum
|
|
16
|
+
from ....exceptions import MangoAutomationError
|
|
17
|
+
from ....exceptions._error_msg import *
|
|
18
|
+
from ....uidrive._base_data import BaseData
|
|
19
|
+
from ....uidrive.web.sync_web._assertion import SyncWebAssertion
|
|
20
|
+
from ....uidrive.web.sync_web._browser import SyncWebBrowser
|
|
21
|
+
from ....uidrive.web.sync_web._customization import SyncWebCustomization
|
|
22
|
+
from ....uidrive.web.sync_web._element import SyncWebElement
|
|
23
|
+
from ....uidrive.web.sync_web._input_device import SyncWebDeviceInput
|
|
24
|
+
from ....uidrive.web.sync_web._page import SyncWebPage
|
|
25
|
+
|
|
26
|
+
re = re
|
|
27
|
+
__all__ = [
|
|
28
|
+
'SyncWebAssertion',
|
|
29
|
+
'SyncWebBrowser',
|
|
30
|
+
'SyncWebCustomization',
|
|
31
|
+
'SyncWebDeviceInput',
|
|
32
|
+
'SyncWebElement',
|
|
33
|
+
'SyncWebPage',
|
|
34
|
+
'SyncWebDevice',
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SyncWebDevice(SyncWebBrowser,
|
|
39
|
+
SyncWebPage,
|
|
40
|
+
SyncWebElement,
|
|
41
|
+
SyncWebDeviceInput,
|
|
42
|
+
SyncWebCustomization):
|
|
43
|
+
|
|
44
|
+
def __init__(self, base_data: BaseData):
|
|
45
|
+
super().__init__(base_data)
|
|
46
|
+
|
|
47
|
+
def open_url(self, is_open: bool = False):
|
|
48
|
+
if not self.base_data.is_open_url or is_open:
|
|
49
|
+
self.base_data.log.debug(f'打开url,is_open_url:{self.base_data.is_open_url},url:{self.base_data.url}')
|
|
50
|
+
self.w_goto(self.base_data.url)
|
|
51
|
+
self.base_data.is_open_url = True
|
|
52
|
+
|
|
53
|
+
def web_action_element(self, name, ope_key, ope_value, ):
|
|
54
|
+
self.base_data.log.debug(f'操作元素,名称:{name},key:{ope_key},value:{ope_value}')
|
|
55
|
+
try:
|
|
56
|
+
Mango.s_e(self, ope_key, ope_value)
|
|
57
|
+
except TimeoutError as error:
|
|
58
|
+
self.base_data.log.error(f'WEB自动化操作失败-1,类型:{type(error)},失败详情:{error}')
|
|
59
|
+
raise MangoAutomationError(*ERROR_MSG_0011, value=(name,))
|
|
60
|
+
except TargetClosedError:
|
|
61
|
+
self.base_data.setup()
|
|
62
|
+
raise MangoAutomationError(*ERROR_MSG_0010)
|
|
63
|
+
except Error as error:
|
|
64
|
+
self.base_data.log.error(f'WEB自动化操作失败-2,类型:{type(error)},失败详情:{error}')
|
|
65
|
+
raise MangoAutomationError(*ERROR_MSG_0032, value=(name,))
|
|
66
|
+
except ValueError as error:
|
|
67
|
+
self.base_data.log.error(f'WEB自动化操作失败-3,类型:{type(error)},失败详情:{error}')
|
|
68
|
+
raise MangoAutomationError(*ERROR_MSG_0012)
|
|
69
|
+
|
|
70
|
+
def web_assertion_element(self, name, ope_key, ope_value):
|
|
71
|
+
self.base_data.log.debug(f'断言元素,名称:{name},key:{ope_key},value:{ope_value}')
|
|
72
|
+
is_method = callable(getattr(SyncWebAssertion, ope_key, None))
|
|
73
|
+
is_method_public = callable(getattr(PublicAssertion, ope_key, None))
|
|
74
|
+
try:
|
|
75
|
+
if is_method:
|
|
76
|
+
if ope_value.get('actual', None) is None:
|
|
77
|
+
raise MangoAutomationError(*ERROR_MSG_0031, value=(name,))
|
|
78
|
+
self.base_data.log.debug(f'开始断言-1,方法:{ope_key},断言值:{ope_value}')
|
|
79
|
+
Mango.s_e(SyncWebAssertion(self.base_data), ope_key, ope_value)
|
|
80
|
+
elif is_method_public:
|
|
81
|
+
self.base_data.log.debug(f'开始断言-2,方法:{ope_key},断言值:{ope_value}')
|
|
82
|
+
Mango.s_e(PublicAssertion, ope_key, ope_value)
|
|
83
|
+
else:
|
|
84
|
+
if self.base_data.mysql_connect is not None:
|
|
85
|
+
SqlAssertion.mysql_obj = self.base_data.mysql_connect
|
|
86
|
+
self.base_data.log.debug(f'开始断言-3,方法:sql相等端游,实际值:{ope_value}')
|
|
87
|
+
SqlAssertion.sql_is_equal(**ope_value)
|
|
88
|
+
else:
|
|
89
|
+
raise MangoAutomationError(*ERROR_MSG_0019)
|
|
90
|
+
except AssertionError as error:
|
|
91
|
+
self.base_data.log.debug(f'WEB自动化断言失败-1,类型:{type(error)},失败详情:{error}')
|
|
92
|
+
raise MangoAutomationError(*ERROR_MSG_0017, value=error.args)
|
|
93
|
+
except AttributeError as error:
|
|
94
|
+
self.base_data.log.error(f'WEB自动化断言失败-2,类型:{type(error)},失败详情:{error}')
|
|
95
|
+
raise MangoAutomationError(*ERROR_MSG_0048)
|
|
96
|
+
except ValueError as error:
|
|
97
|
+
self.base_data.log.error(f'WEB自动化断言失败-3,类型:{type(error)},失败详情:{error}')
|
|
98
|
+
raise MangoAutomationError(*ERROR_MSG_0005)
|
|
99
|
+
except TargetClosedError:
|
|
100
|
+
self.base_data.setup()
|
|
101
|
+
raise MangoAutomationError(*ERROR_MSG_0010)
|
|
102
|
+
except Error as error:
|
|
103
|
+
self.base_data.log.error(f'WEB自动化断言失败-4,类型:{type(error)},失败详情:{error}')
|
|
104
|
+
raise MangoAutomationError(*ERROR_MSG_0052, value=(name,), )
|
|
105
|
+
|
|
106
|
+
def web_find_ele(self, name, _type, exp, loc, sub, is_iframe) \
|
|
107
|
+
-> tuple[Locator, int, str] | tuple[list[Locator], int, str]:
|
|
108
|
+
self.base_data.log.debug(
|
|
109
|
+
f'查找元素,名称:{name},_type:{_type},exp:{exp},loc:{loc},sub:{sub},is_iframe:{is_iframe}')
|
|
110
|
+
if is_iframe != StatusEnum.SUCCESS.value:
|
|
111
|
+
locator: Locator = self.__find_ele(self.base_data.page, exp, loc)
|
|
112
|
+
try:
|
|
113
|
+
count = locator.count()
|
|
114
|
+
loc = locator.nth(sub - 1) if sub else locator
|
|
115
|
+
try:
|
|
116
|
+
text = self.w_get_text(loc)
|
|
117
|
+
except Exception:
|
|
118
|
+
text = None
|
|
119
|
+
return loc, count, text
|
|
120
|
+
except Error as error:
|
|
121
|
+
self.base_data.log.error(
|
|
122
|
+
f'WEB自动化查找元素失败-1,类型:{type(error)},失败详情:{error},失败明细:{traceback.format_exc()}')
|
|
123
|
+
raise MangoAutomationError(*ERROR_MSG_0041, )
|
|
124
|
+
else:
|
|
125
|
+
ele_list: list[Locator] = []
|
|
126
|
+
for i in self.base_data.page.frames:
|
|
127
|
+
locator: Locator = self.__find_ele(i, exp, loc)
|
|
128
|
+
try:
|
|
129
|
+
count = locator.count()
|
|
130
|
+
except Error as error:
|
|
131
|
+
self.base_data.log.error(
|
|
132
|
+
f'WEB自动化查找元素失败-2,类型:{type(error)},失败详情:{error},失败明细:{traceback.format_exc()}')
|
|
133
|
+
raise MangoAutomationError(*ERROR_MSG_0041, )
|
|
134
|
+
if count > 0:
|
|
135
|
+
for nth in range(0, count):
|
|
136
|
+
ele_list.append(locator.nth(nth))
|
|
137
|
+
else:
|
|
138
|
+
raise MangoAutomationError(*ERROR_MSG_0023)
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
count = len(ele_list)
|
|
142
|
+
loc = ele_list[sub - 1] if sub else ele_list[0]
|
|
143
|
+
try:
|
|
144
|
+
text = self.w_get_text(loc)
|
|
145
|
+
except Exception:
|
|
146
|
+
text = None
|
|
147
|
+
return loc, count, text
|
|
148
|
+
except IndexError:
|
|
149
|
+
raise MangoAutomationError(*ERROR_MSG_0025, value=(len(ele_list),))
|
|
150
|
+
|
|
151
|
+
def __find_ele(self, page, exp, loc) -> Locator:
|
|
152
|
+
match exp:
|
|
153
|
+
case ElementExpEnum.LOCATOR.value:
|
|
154
|
+
try:
|
|
155
|
+
return eval(f"page.{loc}")
|
|
156
|
+
except SyntaxError:
|
|
157
|
+
try:
|
|
158
|
+
return eval(f"await page.{loc}")
|
|
159
|
+
except SyntaxError as error:
|
|
160
|
+
self.base_data.log.error(f'WEB自动化查找元素失败-3,类型:{type(error)},失败详情:{error}')
|
|
161
|
+
raise MangoAutomationError(*ERROR_MSG_0022)
|
|
162
|
+
except NameError as error:
|
|
163
|
+
self.base_data.log.error(f'WEB自动化查找元素失败-4,类型:{type(error)},失败详情:{error}')
|
|
164
|
+
raise MangoAutomationError(*ERROR_MSG_0060)
|
|
165
|
+
case ElementExpEnum.XPATH.value:
|
|
166
|
+
return page.locator(f'xpath={loc}')
|
|
167
|
+
case ElementExpEnum.CSS.value:
|
|
168
|
+
return page.locator(loc)
|
|
169
|
+
case ElementExpEnum.TEXT.value:
|
|
170
|
+
return page.get_by_text(loc, exact=True)
|
|
171
|
+
case ElementExpEnum.PLACEHOLDER.value:
|
|
172
|
+
return page.get_by_placeholder(loc)
|
|
173
|
+
case _:
|
|
174
|
+
raise MangoAutomationError(*ERROR_MSG_0020)
|