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.
Files changed (55) hide show
  1. applib_ui/__init__.py +13 -0
  2. applib_ui/case.py +79 -0
  3. applib_ui/cli.py +75 -0
  4. applib_ui/core/__init__.py +0 -0
  5. applib_ui/core/android/__init__.py +16 -0
  6. applib_ui/core/android/case.py +8 -0
  7. applib_ui/core/android/driver.py +191 -0
  8. applib_ui/core/android/element.py +291 -0
  9. applib_ui/core/android/page.py +6 -0
  10. applib_ui/core/android/recorder.py +171 -0
  11. applib_ui/core/android/remote_adb.py +69 -0
  12. applib_ui/core/android/remote_driver.py +176 -0
  13. applib_ui/core/img/__init__.py +4 -0
  14. applib_ui/core/img/driver.py +43 -0
  15. applib_ui/core/img/element.py +73 -0
  16. applib_ui/core/ios/__init__.py +16 -0
  17. applib_ui/core/ios/case.py +8 -0
  18. applib_ui/core/ios/driver.py +290 -0
  19. applib_ui/core/ios/elem.py +222 -0
  20. applib_ui/core/ios/local_wda.py +576 -0
  21. applib_ui/core/ios/page.py +6 -0
  22. applib_ui/core/ios/recorder.py +202 -0
  23. applib_ui/core/ios/remote_driver.py +220 -0
  24. applib_ui/core/ios/remote_wda.py +54 -0
  25. applib_ui/core/ocr/__init__.py +5 -0
  26. applib_ui/core/ocr/driver.py +185 -0
  27. applib_ui/core/ocr/element.py +65 -0
  28. applib_ui/device.py +53 -0
  29. applib_ui/element.py +210 -0
  30. applib_ui/page.py +6 -0
  31. applib_ui/pytest_plugin.py +57 -0
  32. applib_ui/running/__init__.py +0 -0
  33. applib_ui/running/conf.py +14 -0
  34. applib_ui/running/conf.yml +19 -0
  35. applib_ui/running/runner.py +33 -0
  36. applib_ui/utils/__init__.py +1 -0
  37. applib_ui/utils/aes_util.py +47 -0
  38. applib_ui/utils/allure_util.py +213 -0
  39. applib_ui/utils/base64_util.py +21 -0
  40. applib_ui/utils/common.py +84 -0
  41. applib_ui/utils/config.py +135 -0
  42. applib_ui/utils/cv_util.py +110 -0
  43. applib_ui/utils/dingtalk.py +56 -0
  44. applib_ui/utils/excel_util.py +101 -0
  45. applib_ui/utils/log.py +12 -0
  46. applib_ui/utils/mail.py +47 -0
  47. applib_ui/utils/mysql_util.py +74 -0
  48. applib_ui/utils/nacos_util.py +52 -0
  49. applib_ui/utils/pytest_util.py +89 -0
  50. applib_ui/utils/swagger_util.py +195 -0
  51. applib_ui-0.0.1.dist-info/METADATA +170 -0
  52. applib_ui-0.0.1.dist-info/RECORD +55 -0
  53. applib_ui-0.0.1.dist-info/WHEEL +5 -0
  54. applib_ui-0.0.1.dist-info/entry_points.txt +5 -0
  55. applib_ui-0.0.1.dist-info/top_level.txt +1 -0
applib_ui/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ from .running.runner import main
2
+ from .running.conf import App
3
+ from .utils.config import FileConfig
4
+ from .utils.pytest_util import *
5
+ from .utils.allure_util import *
6
+ from .utils.log import logger
7
+ from .page import Page
8
+ from .element import Elem
9
+ from .case import TestCase
10
+
11
+
12
+ __version__ = "0.0.1"
13
+ __description__ = "Android / iOS 通用 UI 自动化测试框架"
applib_ui/case.py ADDED
@@ -0,0 +1,79 @@
1
+ import time
2
+
3
+ import pytest
4
+
5
+ from .running.conf import App
6
+ from .utils.log import logger
7
+
8
+
9
+ class TestCase:
10
+ """pytest 测试基类;也可以直接在测试函数中使用 app_driver fixture。"""
11
+
12
+ dr = None
13
+
14
+ def start_class(self):
15
+ pass
16
+
17
+ def end_class(self):
18
+ pass
19
+
20
+ @classmethod
21
+ def setup_class(cls):
22
+ cls().start_class()
23
+
24
+ @classmethod
25
+ def teardown_class(cls):
26
+ cls().end_class()
27
+
28
+ def start(self):
29
+ pass
30
+
31
+ def end(self):
32
+ pass
33
+
34
+ @pytest.fixture(autouse=True)
35
+ def _app_lifecycle(self, app_driver):
36
+ self.dr = app_driver
37
+ try:
38
+ if getattr(self, '_platform', None) is not None:
39
+ self.dr.unlock()
40
+ self.start()
41
+ yield
42
+ finally:
43
+ self.end()
44
+
45
+ @staticmethod
46
+ def sleep(n: float):
47
+ logger.info(f"暂停: {n}s")
48
+ time.sleep(n)
49
+
50
+ def shot(self, name: str, delay=0):
51
+ if delay:
52
+ self.sleep(delay)
53
+ return self.dr.shot(name)
54
+
55
+ def start_record(self, video_name='record'):
56
+ """开始录屏"""
57
+ return self.dr.start_record(video_name)
58
+
59
+ def stop_record(self):
60
+ """结束录屏并上传 allure"""
61
+ return self.dr.stop_record()
62
+
63
+ def start_app(self, force=True, activity=None, use_monkey=False):
64
+ if force:
65
+ self.stop_app()
66
+ if App.platform == 'ios':
67
+ self.dr.start_app()
68
+ elif activity is not None:
69
+ self.dr.start_app(activity=activity)
70
+ elif use_monkey:
71
+ self.dr.start_app(use_monkey=True)
72
+ else:
73
+ self.dr.start_app()
74
+
75
+ def stop_app(self):
76
+ self.dr.stop_app()
77
+
78
+ def back(self):
79
+ self.dr.back()
applib_ui/cli.py ADDED
@@ -0,0 +1,75 @@
1
+ import click
2
+ from . import __version__
3
+
4
+ @click.group()
5
+ @click.version_option(version=__version__, help="Show version.")
6
+ def main():
7
+ """applib-ui 移动端自动化工具。"""
8
+
9
+
10
+ @main.command(context_settings={'ignore_unknown_options': True})
11
+ @click.argument('pytest_args', nargs=-1, type=click.UNPROCESSED)
12
+ def run(pytest_args):
13
+ """运行 pytest,透传路径、筛选条件和报告等参数。"""
14
+ import pytest
15
+
16
+ raise SystemExit(pytest.main(list(pytest_args)))
17
+
18
+
19
+ @main.command()
20
+ @click.option('-u', '--udid', help="Specify the udid.")
21
+ def restart_wda(udid):
22
+ """
23
+ 重启wda
24
+ @param udid: IOS设备udid
25
+ @return:
26
+ """
27
+ from .core.ios.local_wda import LocalWdaInit
28
+
29
+ LocalWdaInit(udid=udid).restart_wda()
30
+
31
+ @main.command()
32
+ @click.option('-u', '--udid', help="Specify the udid.")
33
+ def start_wda(udid):
34
+ """
35
+ 启动wda
36
+ @param udid: IOS设备udid
37
+ @return:
38
+ """
39
+ from .core.ios.local_wda import LocalWdaInit
40
+
41
+ LocalWdaInit(udid=udid).run()
42
+
43
+ @main.command()
44
+ @click.option('-u', '--udid', help="Specify the udid.")
45
+ def stop_wda(udid):
46
+ """
47
+ 杀掉wda
48
+ @param udid: IOS设备udid
49
+ @return:
50
+ """
51
+ from .core.ios.local_wda import LocalWdaInit
52
+
53
+ LocalWdaInit(udid=udid).kill_wda_server()
54
+
55
+
56
+ # @main.command()
57
+ # @click.option('-p', '--platform', help="Specify the platform.")
58
+ # @click.option('-u', '--url', help="Specify the url.")
59
+ # def inspector(platform, url):
60
+ # """
61
+ # 获取元素定位信息
62
+ # @param platform: 平台,android、ios、web
63
+ # @param url:针对web,需要录制的页面的url
64
+ # @return:
65
+ # """
66
+ # import os
67
+ # if platform in ['android', 'ios', 'web']:
68
+ # os.system('weditor')
69
+ # elif platform == 'web':
70
+ # if not url:
71
+ # raise KeyError('url不能为空')
72
+ # os.system(f'playwright codegen {url}')
73
+ # else:
74
+ # raise KeyError('只支持android、ios、web')
75
+
File without changes
@@ -0,0 +1,16 @@
1
+ """android 平台接口,按需加载驱动依赖。"""
2
+
3
+ from importlib import import_module
4
+
5
+ __all__ = ['Elem', 'Page', 'Driver', 'RemoteDriver', 'TestCase']
6
+
7
+ _MODULES = {'Elem': 'element', 'Page': 'page', 'Driver': 'driver',
8
+ 'RemoteDriver': 'remote_driver', 'TestCase': 'case'}
9
+
10
+
11
+ def __getattr__(name):
12
+ if name not in _MODULES:
13
+ raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
14
+ value = getattr(import_module(f'.{_MODULES[name]}', __name__), name)
15
+ globals()[name] = value
16
+ return value
@@ -0,0 +1,8 @@
1
+ from ...case import TestCase as BaseTestCase
2
+
3
+
4
+ class TestCase(BaseTestCase):
5
+ """Android pytest 测试基类。"""
6
+
7
+ _platform = 'android'
8
+
@@ -0,0 +1,191 @@
1
+ import allure
2
+ import uiautomator2 as u2
3
+
4
+ from applib_ui.utils.log import logger
5
+ from applib_ui.utils.common import general_file_path, general_video_path
6
+ from .recorder import ScrcpyRecorder
7
+
8
+
9
+ class Driver:
10
+ """
11
+ u2库代码地址:https://github.com/openatx/uiautomator2
12
+ """
13
+
14
+ def __init__(self, serial: str, package: str = None):
15
+ """
16
+ @param serial: 设备序列号,通过adb devices获取,或者通过关于手机-序列号获取
17
+ @param package: 应用包名,通过adb shell pm list packages获取
18
+ """
19
+ logger.info(f"初始化本地设备连接: {serial}")
20
+ self.serial = serial
21
+ self.package = package
22
+ self.d = u2.connect(serial)
23
+ self._recorder = None
24
+
25
+ def uninstall_app(self, package: str = None):
26
+ """
27
+ 卸载应用
28
+ @param package: 应用包名
29
+ @return:
30
+ """
31
+ if self.package is None:
32
+ if package is None:
33
+ raise KeyError('package不能为空')
34
+ else:
35
+ self.package = package
36
+ logger.info(f"卸载应用: {self.package}")
37
+ self.d.app_uninstall(self.package)
38
+
39
+ def install_app(self, apk_url, package: str = None):
40
+ """
41
+ 安装应用
42
+ @param apk_url: apk链接
43
+ @param package: 应用包名
44
+ @return:
45
+ """
46
+ if self.package is None:
47
+ if package is None:
48
+ raise KeyError('package不能为空')
49
+ else:
50
+ self.package = package
51
+ self.d.app_uninstall(self.package)
52
+ logger.info(f"安装应用: {apk_url}")
53
+ self.d.app_install(apk_url)
54
+
55
+ def start_app(self, package: str = None, activity: str = None, use_monkey: bool = False):
56
+ """
57
+ 启动应用
58
+ @param package: 应用包名
59
+ @return:
60
+ """
61
+ if self.package is None:
62
+ if package is None:
63
+ raise KeyError('package不能为空')
64
+ else:
65
+ self.package = package
66
+ logger.info(f"启动应用: {self.package}")
67
+ if activity is not None:
68
+ self.d.app_start(package_name=self.package, activity=activity)
69
+ else:
70
+ self.d.app_start(package_name=self.package, use_monkey=use_monkey)
71
+
72
+ def stop_app(self, package: str = None):
73
+ """
74
+ 停止应用
75
+ @param package: 应用包名
76
+ @return:
77
+ """
78
+ if self.package is None:
79
+ if package is None:
80
+ raise KeyError('package不能为空')
81
+ else:
82
+ self.package = package
83
+ logger.info(f"停止应用: {self.package}")
84
+ self.d.app_stop(self.package)
85
+
86
+ def shot(self, file_name: str = None):
87
+ """
88
+ 截图
89
+ @param file_name: 截图保存后的文件名
90
+ @return:
91
+ """
92
+ file_path = general_file_path(file_name, self.serial)
93
+ logger.info(f"截图保存至: {file_path}")
94
+ self.d.screenshot(file_path)
95
+
96
+ logger.info("截图上传allure报告")
97
+ allure.attach.file(
98
+ file_path,
99
+ attachment_type=allure.attachment_type.PNG,
100
+ name=f"{file_path}",
101
+ )
102
+ return file_path
103
+
104
+ def click(self, x, y):
105
+ """
106
+ 点击坐标
107
+ @param x: 横坐标
108
+ @param y: 纵坐标
109
+ @return:
110
+ """
111
+ self.d.click(x, y)
112
+
113
+ def input(self, *args, **kwargs):
114
+ """
115
+ 输入
116
+ @param args:
117
+ @param kwargs:
118
+ @return:
119
+ """
120
+ self.d.send_keys(*args, **kwargs)
121
+
122
+ def unlock(self):
123
+ """
124
+ 设备解锁
125
+ @return:
126
+ """
127
+ self.d.unlock()
128
+
129
+ def shell(self, *args, **kwargs):
130
+ """
131
+ 执行adb shell
132
+ @param args:
133
+ @param kwargs:
134
+ @return:
135
+ """
136
+ self.d.shell(*args, **kwargs)
137
+
138
+
139
+ def back(self):
140
+ logger.info("返回上一页")
141
+ self.d.press('back')
142
+
143
+ def start_record(self, file_name: str = "record"):
144
+ """
145
+ 开始录屏
146
+ @param file_name: 录屏文件名
147
+ @return: 录像路径;录制没起来返回 None(最终产物以 stop_record 的返回值为准)
148
+ """
149
+ if self._recorder is not None:
150
+ self.stop_record()
151
+ file_path = general_video_path(file_name, self.serial)
152
+ self._recorder = ScrcpyRecorder(self.serial)
153
+ if not self._recorder.start(file_path):
154
+ # 没起来:原因已由 recorder 打成 ERROR,这里返回 None,别让调用方以为正在录
155
+ self._recorder = None
156
+ return None
157
+ return file_path
158
+
159
+ def stop_record(self):
160
+ """
161
+ 停止录屏并上传allure
162
+ @return:
163
+ """
164
+ if self._recorder is None:
165
+ return None
166
+ file_path = self._recorder.stop()
167
+ self._recorder = None
168
+ return file_path
169
+
170
+
171
+
172
+
173
+
174
+
175
+
176
+
177
+
178
+
179
+
180
+
181
+
182
+
183
+
184
+
185
+
186
+
187
+
188
+
189
+
190
+
191
+
@@ -0,0 +1,291 @@
1
+ import typing
2
+ from .driver import Driver
3
+
4
+ from applib_ui.utils.log import logger
5
+ from uiautomator2 import UiObject
6
+ from uiautomator2.xpath import XPathSelector
7
+
8
+
9
+ # 链式调用临时存储定位方式
10
+ # class BaseLocator:
11
+ #
12
+ # def __init__(self, **kwargs):
13
+ # self.rid = kwargs.get('rid', None)
14
+ # self.text = kwargs.get('text', None)
15
+ # self.xpath = kwargs.get('xpath', None)
16
+ # self.type = 'current'
17
+
18
+
19
+ class ChildLocator:
20
+
21
+ def __init__(self, *args, **kwargs):
22
+ self.args = args
23
+ self.kwargs = kwargs
24
+ self.type = 'child'
25
+
26
+
27
+ class SiblingLocator:
28
+
29
+ def __init__(self, *args, **kwargs):
30
+ self.args = args
31
+ self.kwargs = kwargs
32
+ self.type = 'sibling'
33
+
34
+
35
+ class LeftLocator:
36
+
37
+ def __init__(self, *args, **kwargs):
38
+ self.args = args
39
+ self.kwargs = kwargs
40
+ self.type = 'left'
41
+
42
+
43
+ class RightLocator:
44
+
45
+ def __init__(self, *args, **kwargs):
46
+ self.args = args
47
+ self.kwargs = kwargs
48
+ self.type = 'right'
49
+
50
+
51
+ class UpLocator:
52
+
53
+ def __init__(self, *args, **kwargs):
54
+ self.args = args
55
+ self.kwargs = kwargs
56
+ self.type = 'up'
57
+
58
+
59
+ class DownLocator:
60
+
61
+ def __init__(self, *args, **kwargs):
62
+ self.args = args
63
+ self.kwargs = kwargs
64
+ self.type = 'down'
65
+
66
+
67
+ class Elem(object):
68
+ """
69
+ 安卓控件定义
70
+ https://github.com/openatx/uiautomator2
71
+ """
72
+
73
+ def __init__(self,
74
+ tag=None,
75
+ ):
76
+ """
77
+ @param tag: 元素名称,方面后续维护
78
+ """
79
+ self.tag = tag
80
+ # self._kwargs = kwargs
81
+ self._driver: Driver = None
82
+ # self._xpath = kwargs.get('xpath', None)
83
+ # self._watch = watch
84
+ self._first_locator = None
85
+ self._locators = []
86
+ # if 'rid' in self._kwargs:
87
+ # self._kwargs['resourceId'] = self._kwargs.pop('rid')
88
+ # self._pkg = App.pkg
89
+ # if 'resourceId' in self._kwargs:
90
+ # if not self._kwargs['resourceId'].startswith(self._pkg):
91
+ # if not self._pkg:
92
+ # raise KeyError('应用包名不能为空')
93
+ # else:
94
+ # self._kwargs['resourceId'] = self._pkg + ":" + self._kwargs['resourceId']
95
+
96
+ def __get__(self, instance, owner):
97
+ """po模式中element初始化不需要带driver的关键"""
98
+ if instance is None:
99
+ return None
100
+
101
+ self._driver = instance.driver
102
+ return self
103
+
104
+ # 链式调用方法
105
+ # def get_by_rid(self, rid):
106
+ # self.locators.append(BaseLocator(rid=rid))
107
+ # return self
108
+ #
109
+ # def get_by_text(self, text):
110
+ # self.locators.append(BaseLocator(text=text))
111
+ # return self
112
+ #
113
+ # def get_by_xpath(self, xpath):
114
+ # self.locators.append(BaseLocator(xpath=xpath))
115
+ # return self
116
+
117
+ def loc(self, **kwargs):
118
+ if not kwargs:
119
+ raise KeyError('定位方式不能为空')
120
+ if 'xpath' in kwargs:
121
+ self._first_locator = kwargs.pop('xpath')
122
+ else:
123
+ self._first_locator = kwargs
124
+ return self
125
+
126
+ def child(self, *args, **kwargs):
127
+ self._locators.append(ChildLocator(*args, **kwargs))
128
+ return self
129
+
130
+ def sibling(self, *args, **kwargs):
131
+ self._locators.append(SiblingLocator(*args, **kwargs))
132
+ return self
133
+
134
+ def left(self, *args, **kwargs):
135
+ self._locators.append(LeftLocator(*args, **kwargs))
136
+ return self
137
+
138
+ def right(self, *args, **kwargs):
139
+ self._locators.append(RightLocator(*args, **kwargs))
140
+ return self
141
+
142
+ def up(self, *args, **kwargs):
143
+ self._locators.append(UpLocator(*args, **kwargs))
144
+ return self
145
+
146
+ def down(self, *args, **kwargs):
147
+ self._locators.append(DownLocator(*args, **kwargs))
148
+ return self
149
+
150
+ # # 公共方法
151
+ # def watch_handler(self):
152
+ # """
153
+ # 异常弹窗处理
154
+ # @return:
155
+ # """
156
+ # logger.info(f"开始弹窗检测: {self._watch}")
157
+ # ctx = self._driver.d.watch_context()
158
+ # for loc in self._watch:
159
+ # ctx.when(loc).click()
160
+ # ctx.wait_stable()
161
+ # ctx.close()
162
+ # logger.info("检测结束")
163
+
164
+ def find(self, timeout=5):
165
+ """
166
+ 增加截图的方法
167
+ @param timeout: 每次查找时间
168
+ @return:
169
+ """
170
+ logger.info(f"查找: {self.tag}")
171
+ # 第一个定位
172
+ if isinstance(self._first_locator, dict):
173
+ _element = self._driver.d(**self._first_locator)
174
+ else:
175
+ _element = self._driver.d.xpath(self._first_locator)
176
+
177
+ # 后面的定位
178
+ if self._locators:
179
+ for loc_obj in self._locators:
180
+ if loc_obj.type == "child":
181
+ _element = _element.child(*loc_obj.args, **loc_obj.kwargs)
182
+ elif loc_obj.type == 'sibling':
183
+ _element = _element.sibling(*loc_obj.args, **loc_obj.kwargs)
184
+ elif loc_obj.type == 'left':
185
+ _element = _element.left(*loc_obj.args, **loc_obj.kwargs)
186
+ elif loc_obj.type == 'right':
187
+ _element = _element.right(*loc_obj.args, **loc_obj.kwargs)
188
+ elif loc_obj.type == 'up':
189
+ _element = _element.up(*loc_obj.args, **loc_obj.kwargs)
190
+ elif loc_obj.type == 'down':
191
+ _element = _element.down(*loc_obj.args, **loc_obj.kwargs)
192
+
193
+ if _element.wait(timeout=timeout):
194
+ logger.info(f"查找成功")
195
+ return _element
196
+ else:
197
+ logger.info("查找失败")
198
+ self._driver.shot(f"{self.tag}_查找失败")
199
+ raise Exception(f"{self.tag}, 查找失败")
200
+
201
+ # 属性获取
202
+ def get_text(self, timeout=5):
203
+ logger.info(f"获取{self.tag}的文本")
204
+ _elem = self.find(timeout=timeout)
205
+ text = _elem.get_text()
206
+ logger.info(f"获取成功: {text}")
207
+ return text
208
+
209
+ def get_texts(self, timeout=5):
210
+ logger.info(f"获取{self.tag}的文本")
211
+ _elem = self.find(timeout=timeout)
212
+ texts = []
213
+ if isinstance(_elem, XPathSelector):
214
+ elems = _elem.all()
215
+ for elem in elems:
216
+ texts.append(elem.text)
217
+ else:
218
+ elems = list(_elem)
219
+ for elem in elems:
220
+ texts.append(elem.get_text())
221
+ logger.info(f"获取成功: {texts}")
222
+ return texts
223
+
224
+ def exists(self, timeout=5):
225
+ logger.info(f"判断{self.tag}是否存在")
226
+ result = False
227
+ try:
228
+ _element = self.find(timeout=timeout)
229
+ result = True
230
+ except:
231
+ result = False
232
+ finally:
233
+ logger.info(result)
234
+ return result
235
+
236
+ def center(self, timeout=5, *args, **kwargs):
237
+ return self.find(timeout=timeout).center(*args, **kwargs)
238
+
239
+ @staticmethod
240
+ def _adapt_center(e: typing.Union[UiObject, XPathSelector],
241
+ offset=(0.5, 0.5)):
242
+ """
243
+ 修正控件中心坐标
244
+ """
245
+ if isinstance(e, UiObject):
246
+ return e.center(offset=offset)
247
+ else:
248
+ return e.offset(offset[0], offset[1])
249
+
250
+ # 操作
251
+ def click_exists(self, timeout=5):
252
+ logger.info(f"{self.tag}存在才点击")
253
+ try:
254
+ element = self.find(timeout=timeout)
255
+ x, y = self._adapt_center(element)
256
+ self._driver.click(x, y)
257
+ logger.info("点击完成")
258
+ except:
259
+ logger.info("不存在,不进行操作")
260
+
261
+ def click(self, timeout=5):
262
+ logger.info(f"{self.tag}点击")
263
+ element = self.find(timeout=timeout)
264
+ x, y = self._adapt_center(element)
265
+ self._driver.click(x, y)
266
+ logger.info("点击完成")
267
+
268
+ def input(self, text, timeout=5, clear=False, pwd_check=False):
269
+ logger.info(f"{self.tag}输入: {text}")
270
+ element = self.find(timeout=timeout)
271
+ # 清空输入框
272
+ if clear is True:
273
+ element.clear_text()
274
+ # 点击输入框
275
+ x, y = self._adapt_center(element)
276
+ self._driver.click(x, y)
277
+ # 输入
278
+ if pwd_check is True:
279
+ self._driver.d(focused=True).set_text(text)
280
+ else:
281
+ self._driver.input(text)
282
+ logger.info("输入完成")
283
+
284
+
285
+
286
+
287
+
288
+
289
+
290
+
291
+
@@ -0,0 +1,6 @@
1
+ class Page(object):
2
+ """页面基类,用于pom模式封装"""
3
+
4
+ def __init__(self, driver):
5
+ self.driver = driver
6
+