kuto
Advanced tools
@@ -32,4 +32,3 @@ import kuto | ||
| kuto.main( | ||
| platform='android', | ||
| device_id="UJK0220521066836", | ||
| devices=["85WKMBHQFEK76T8D"], | ||
| pkg_name='com.qizhidao.clientapp', | ||
@@ -36,0 +35,0 @@ ) |
@@ -45,5 +45,3 @@ import kuto | ||
| """仅执行本模块""" | ||
| kuto.main( | ||
| platform="api", | ||
| host='https://app-test.qizhidao.com' | ||
| ) | ||
| kuto.main(host='https://app-test.qizhidao.com') | ||
@@ -29,4 +29,3 @@ """ | ||
| kuto.main( | ||
| platform='android', | ||
| device_id='UJK0220521066836', | ||
| devices=['UJK0220521066836'], | ||
| pkg_name='com.tencent.mm' | ||
@@ -33,0 +32,0 @@ ) |
@@ -16,2 +16,3 @@ import kuto | ||
| def test_go_setting(self): | ||
| self.index_page.adBtn.click_exists(timeout=5) | ||
| self.index_page.myTab.click() | ||
@@ -24,5 +25,6 @@ self.my_page.settingBtn.click() | ||
| """仅执行本模块""" | ||
| from kuto.ios.common import get_connected | ||
| kuto.main( | ||
| platform="ios", | ||
| device_id="00008101-000E646A3C29003A", | ||
| devices=get_connected(), | ||
| pkg_name='com.qizhidao.company' | ||
@@ -29,0 +31,0 @@ ) |
@@ -27,5 +27,2 @@ """ | ||
| """仅执行本模块""" | ||
| kuto.main( | ||
| platform='mac', | ||
| pkg_name='Calculator', | ||
| ) | ||
| kuto.main(pkg_name='Calculator') |
@@ -28,4 +28,3 @@ """ | ||
| kuto.main( | ||
| platform='ios', | ||
| device_id='00008101-000E646A3C29003A', | ||
| devices=['00008101-000E646A3C29003A'], | ||
| pkg_name='com.tencent.xin' | ||
@@ -32,0 +31,0 @@ ) |
@@ -26,7 +26,4 @@ """ | ||
| """仅执行本模块""" | ||
| kuto.main( | ||
| platform="web", | ||
| brow="chrome", | ||
| host="https://www-test.qizhidao.com" | ||
| ) | ||
| kuto.main(host="https://www-test.qizhidao.com") | ||
@@ -27,5 +27,3 @@ """ | ||
| """仅执行本模块""" | ||
| kuto.main( | ||
| platform='win', | ||
| pkg_name='calc.exe', | ||
| ) | ||
| kuto.main(pkg_name='calc.exe') | ||
| Metadata-Version: 2.1 | ||
| Name: kuto | ||
| Version: 0.0.64 | ||
| Version: 0.0.65 | ||
| Summary: 全平台自动化测试框架 | ||
@@ -5,0 +5,0 @@ Home-page: https://gitee.com/bluepang2021/kuto |
@@ -13,2 +13,3 @@ requests-toolbelt==0.10.1 | ||
| psutil==5.9.5 | ||
| PyYAML~=6.0 | ||
@@ -39,3 +40,2 @@ [android] | ||
| XlsxWriter==3.0.2 | ||
| PyYAML~=6.0 | ||
@@ -42,0 +42,0 @@ [web] |
@@ -66,3 +66,2 @@ README.md | ||
| kuto/running/conf.yml | ||
| kuto/running/free_devices.yml | ||
| kuto/running/runner.py | ||
@@ -69,0 +68,0 @@ kuto/testdata/__init__.py |
+1
-1
@@ -13,3 +13,3 @@ from allure import * | ||
| __version__ = "0.0.64" | ||
| __version__ = "0.0.65" | ||
| __description__ = "全平台自动化测试框架" |
@@ -9,3 +9,3 @@ import time | ||
| from kuto.utils.config import config, free_config | ||
| from kuto.utils.config import config | ||
| from kuto.utils.log import logger | ||
@@ -66,6 +66,6 @@ from kuto.utils.exceptions import KError | ||
| with FileLock("session.lock"): | ||
| device_id = free_config.get_random_device() | ||
| device_id = config.get_random_device() | ||
| if device_id: | ||
| logger.info(f"获取空闲设备成功: {device_id}") | ||
| logger.info(f"剩余空闲设备列表: {free_config.get_all_device()}") | ||
| logger.info(f"剩余空闲设备列表: {config.get_all_device()}") | ||
| break | ||
@@ -77,3 +77,3 @@ logger.info("未找到空闲设备,休息3秒") | ||
| logger.info(f"获取空闲设备失败!!!") | ||
| logger.info(f"剩余空闲设备列表: {free_config.get_all_device()}") | ||
| logger.info(f"剩余空闲设备列表: {config.get_all_device()}") | ||
| raise KError("获取空闲设备失败!!!") | ||
@@ -97,6 +97,6 @@ | ||
| with FileLock("session.lock"): | ||
| devices = free_config.get('devices') | ||
| devices = config.get_app('devices') | ||
| if device_id not in devices: | ||
| free_config.add_devices([self.driver.device_id]) | ||
| logger.info(f"剩余空闲设备列表: {free_config.get_all_device()}") | ||
| config.add_devices([self.driver.device_id]) | ||
| logger.info(f"剩余空闲设备列表: {config.get_all_device()}") | ||
@@ -103,0 +103,0 @@ take_time = time.time() - self.start_time |
@@ -11,3 +11,3 @@ """ | ||
| def get_connected_adr_devices(): | ||
| def get_connected(): | ||
| """获取当前连接的手机列表""" | ||
@@ -14,0 +14,0 @@ cmd = 'adb devices' |
@@ -12,3 +12,2 @@ import os | ||
| from kuto.utils.exceptions import KError | ||
| from kuto.android.common import get_connected_adr_devices | ||
@@ -25,4 +24,3 @@ | ||
| if not self.device_id: | ||
| # 未传入设备id时,获取已连接设备 | ||
| self.device_id = get_connected_adr_devices()[0] | ||
| raise KError("设备id不能为空") | ||
@@ -29,0 +27,0 @@ self.d = u2.connect(self.device_id) |
@@ -9,3 +9,4 @@ import typing | ||
| from kuto.utils.log import logger | ||
| from kuto.utils.common import calculate_time, draw_red_by_coordinate | ||
| from kuto.utils.common import calculate_time, \ | ||
| draw_red_by_coordinate | ||
@@ -12,0 +13,0 @@ |
+10
-14
@@ -41,3 +41,3 @@ # @Time : 2022/2/22 9:35 | ||
| # 从配置文件中读取域名 | ||
| host = config.get_api("base_url") | ||
| host = config.get_common("base_url") | ||
| # 如果接口路径不以http开头,把域名写到key为url的位置参数中或者第一个参数中 | ||
@@ -62,12 +62,8 @@ if "url" in kwargs: | ||
| # 请求头处理,写入登录态 | ||
| # 从配置文件获取登录用户和游客的请求头 | ||
| if kwargs.get("login", True): | ||
| login_header: dict = config.get_api("login") | ||
| else: | ||
| login_header: dict = config.get_api("visit") | ||
| # 把用例脚本中设置的请求头加进来 | ||
| default_headers = config.get_common("headers") | ||
| if default_headers: | ||
| kwargs["headers"] = default_headers | ||
| header_user_set = kwargs.pop("headers", {}) | ||
| login_header.update(header_user_set) | ||
| # 把组装好的请求头装回到kwargs当中 | ||
| kwargs["headers"] = login_header | ||
| if header_user_set: | ||
| kwargs["headers"] = header_user_set | ||
@@ -140,11 +136,11 @@ # 更新超时时间 | ||
| @request | ||
| def get(self, url, params=None, verify=False, login=True, **kwargs): | ||
| def get(self, url, params=None, verify=False, **kwargs): | ||
| return requests.get(url, params=params, verify=verify, **kwargs) | ||
| @request | ||
| def post(self, url, data=None, json=None, verify=False, login=True, **kwargs): | ||
| def post(self, url, data=None, json=None, verify=False, **kwargs): | ||
| return requests.post(url, data=data, json=json, verify=verify, **kwargs) | ||
| @request | ||
| def put(self, url, data=None, json=None, verify=False, login=True, **kwargs): | ||
| def put(self, url, data=None, json=None, verify=False, **kwargs): | ||
| if json is not None: | ||
@@ -155,3 +151,3 @@ data = json_util.dumps(json) | ||
| @request | ||
| def delete(self, url, verify=False, login=True, **kwargs): | ||
| def delete(self, url, verify=False, **kwargs): | ||
| return requests.delete(url, verify=verify, **kwargs) | ||
@@ -158,0 +154,0 @@ |
+7
-7
@@ -8,3 +8,3 @@ import time | ||
| from kuto.ios.driver import Driver | ||
| from kuto.utils.config import config, free_config | ||
| from kuto.utils.config import config | ||
| from kuto.utils.log import logger | ||
@@ -65,6 +65,6 @@ from kuto.utils.exceptions import KError | ||
| with FileLock("session.lock"): | ||
| device_id = free_config.get_random_device() | ||
| device_id = config.get_random_device() | ||
| if device_id: | ||
| logger.info(f"获取空闲设备成功: {device_id}") | ||
| logger.info(f"剩余空闲设备列表: {free_config.get_all_device()}") | ||
| logger.info(f"剩余空闲设备列表: {config.get_all_device()}") | ||
| break | ||
@@ -76,3 +76,3 @@ logger.info("未找到空闲设备,休息3秒") | ||
| logger.info(f"获取空闲设备失败!!!") | ||
| logger.info(f"剩余空闲设备列表: {free_config.get_all_device()}") | ||
| logger.info(f"剩余空闲设备列表: {config.get_all_device()}") | ||
| raise KError("获取空闲设备失败!!!") | ||
@@ -96,6 +96,6 @@ | ||
| with FileLock("session.lock"): | ||
| devices = free_config.get('devices') | ||
| devices = config.get_app('devices') | ||
| if device_id not in devices: | ||
| free_config.add_devices([self.driver.device_id]) | ||
| logger.info(f"剩余空闲设备列表: {free_config.get_all_device()}") | ||
| config.add_devices([self.driver.device_id]) | ||
| logger.info(f"剩余空闲设备列表: {config.get_all_device()}") | ||
@@ -102,0 +102,0 @@ take_time = time.time() - self.start_time |
@@ -8,3 +8,3 @@ import os | ||
| def get_connected_ios_devices(): | ||
| def get_connected(): | ||
| """获取当前连接的设备列表""" | ||
@@ -11,0 +11,0 @@ cmd = 'tidevice list' |
+10
-12
@@ -9,3 +9,3 @@ import shutil | ||
| from kuto.utils.common import screenshot_util | ||
| from kuto.ios.common import TideviceUtil, get_connected_ios_devices | ||
| from kuto.ios.common import TideviceUtil | ||
@@ -33,17 +33,15 @@ | ||
| logger.info("初始化ios驱动") | ||
| if not pkg_name: | ||
| self.pkg_name = pkg_name | ||
| if not self.pkg_name: | ||
| raise KError('应用包名不能为空') | ||
| self.pkg_name = pkg_name | ||
| if device_id is None: | ||
| self.device_id = get_connected_ios_devices()[0] | ||
| self.device_id = device_id | ||
| if not self.device_id: | ||
| raise KError("设备id不能为空") | ||
| if 'http' in self.device_id: | ||
| self.wda_url = self.device_id | ||
| else: | ||
| self.port = self.device_id.split("-")[0][-4:] | ||
| self.wda_url = f"http://localhost:{self.port}" | ||
| else: | ||
| self.device_id = device_id | ||
| if 'http' in self.device_id: | ||
| self.wda_url = self.device_id | ||
| else: | ||
| self.port = self.device_id.split("-")[0][-4:] | ||
| self.wda_url = f"http://localhost:{self.port}" | ||
@@ -50,0 +48,0 @@ self.d = wda.Client(self.wda_url) |
@@ -1,14 +0,10 @@ | ||
| api: | ||
| base_url: null | ||
| login: {} | ||
| visit: {} | ||
| app: | ||
| auto_start: false | ||
| device_id: null | ||
| devices: [] | ||
| pkg_name: null | ||
| common: | ||
| base_url: null | ||
| headers: {} | ||
| web: | ||
| base_url: null | ||
| browser_name: chrome | ||
| cookies: null | ||
| browser_name: null | ||
| headless: false | ||
| state: null |
+38
-98
| import inspect | ||
| import json | ||
| import os | ||
| import pytest | ||
| import psutil | ||
| from kuto.utils.config import config, free_config | ||
| from kuto.utils.config import config | ||
| from kuto.utils.log import logger | ||
| from kuto.android.common import get_connected_adr_devices | ||
| from kuto.ios.common import get_connected_ios_devices | ||
@@ -18,82 +14,46 @@ | ||
| def __init__(self, | ||
| platform: str = None, | ||
| device_id=None, | ||
| case_path: str = None, | ||
| host: str = None, | ||
| headers: dict = None, | ||
| rerun: int = 0, | ||
| xdist: bool = False, | ||
| devices: list = None, | ||
| pkg_name: str = None, | ||
| start: bool = True, | ||
| brow: str = None, | ||
| headless: bool = False, | ||
| path: str = None, | ||
| rerun: int = 0, | ||
| xdist: bool = False, | ||
| host: str = None, | ||
| headers: dict = None, | ||
| state: dict = None, | ||
| cookies: list = None | ||
| brow_name: str = None, | ||
| headless: bool = False | ||
| ): | ||
| """ | ||
| @param platform: 测试平台,android、ios、web、api、mac、win | ||
| @param device_id: 设备id,针对安卓和ios,可以是str和list, | ||
| @param devices: 设备id,针对安卓和ios, | ||
| 对安卓和ios来说也可以是远程服务 | ||
| @param pkg_name: 应用包名,针对安卓和ios | ||
| @param pkg_name: 应用包名,针对安卓、ios、mac、win | ||
| @param start: 是否默认启动应用,针对安卓和ios | ||
| @param brow: 浏览器类型,chrome、firefox、webkit | ||
| @param path: 用例目录,None默认代表当前文件 | ||
| @param brow_name: 浏览器类型,chrome、firefox、webkit | ||
| @param case_path: 用例目录,None默认代表当前文件 | ||
| @param rerun: 失败重试次数 | ||
| @param xdist: 是否使用多进程执行 | ||
| @param host: 域名,针对接口和web | ||
| @param headers: { | ||
| "login": {}, | ||
| "visit": {} | ||
| } | ||
| @param state: 通过playwright的storage_state方法获取 | ||
| @param cookies: | ||
| @param headers: {"token": "xxx"} | ||
| @param headless: 是否使用无头模式 | ||
| """ | ||
| # api参数保存 | ||
| config.set_api("base_url", host) | ||
| if headers: | ||
| if 'login' not in headers.keys(): | ||
| raise KeyError("without login key!!!") | ||
| login_ = headers.pop('login', {}) | ||
| config.set_api('login', login_) | ||
| visit_ = headers.pop('visit', {}) | ||
| config.set_api('visit', visit_) | ||
| # 公共参数保存 | ||
| config.set_common("base_url", host) | ||
| config.set_common("headers", headers) | ||
| # app参数保存 | ||
| # 增加一段逻辑支持多进程以及设备调度 | ||
| # 把所有的设备id加入到空闲设备列表中(用文件保存) | ||
| free_devices = [] | ||
| if device_id is None: | ||
| # 获取当前连接的手机列表 | ||
| if platform == "android": | ||
| free_devices = get_connected_adr_devices() | ||
| elif platform == "ios": | ||
| free_devices = get_connected_ios_devices() | ||
| free_config.add_devices(free_devices) | ||
| else: | ||
| if isinstance(device_id, str): | ||
| free_devices = [device_id] | ||
| free_config.add_devices(free_devices) | ||
| if isinstance(device_id, list): | ||
| if xdist is True: | ||
| # 如果需要并发,才把所有设备放入空闲列表 | ||
| free_devices = device_id | ||
| free_config.add_devices(free_devices) | ||
| else: | ||
| # 如果不需要并发,把第一个设备放入空闲列表 | ||
| free_devices = device_id[0:1] | ||
| free_config.add_devices(free_devices) | ||
| config.set_app("devices", devices) | ||
| config.set_app("pkg_name", pkg_name) | ||
| config.set_app("auto_start", start) | ||
| # web参数保存 | ||
| config.set_web("base_url", host) | ||
| config.set_web("browser_name", brow) | ||
| config.set_web("browser_name", brow_name) | ||
| config.set_web("headless", headless) | ||
| if state: | ||
| config.set_web("state", json.dumps(state)) | ||
| if cookies: | ||
| config.set_web("cookies", json.dumps(cookies)) | ||
| # 执行用例 | ||
| # logger.info('执行用例') | ||
| if path is None: | ||
| cmd_list = [ | ||
| '-sv', | ||
| '--reruns', str(rerun), | ||
| '--alluredir', 'report', '--clean-alluredir' | ||
| ] | ||
| if case_path is None: | ||
| stack_t = inspect.stack() | ||
@@ -109,23 +69,10 @@ ins = inspect.getframeinfo(stack_t[1][0]) | ||
| this_file = file_path | ||
| path = os.path.join(file_dir, this_file) | ||
| cmd_list = [ | ||
| '-sv', | ||
| '--reruns', str(rerun), | ||
| '--alluredir', 'report', '--clean-alluredir' | ||
| ] | ||
| if path: | ||
| cmd_list.insert(0, path) | ||
| case_path = os.path.join(file_dir, this_file) | ||
| cmd_list.insert(0, case_path) | ||
| if xdist: | ||
| if platform in ["android", "ios"]: | ||
| if len(free_devices) > 1: | ||
| # 设备数大于1才开启多进程 | ||
| n = len(free_devices) | ||
| cpu_count = psutil.cpu_count() | ||
| if n > cpu_count: | ||
| n = cpu_count | ||
| cmd_list.insert(1, '-n') | ||
| cmd_list.insert(2, str(n)) | ||
| else: | ||
| cmd_list.insert(1, '-n') | ||
| cmd_list.insert(2, 'auto') | ||
| cmd_list.insert(1, '-n') | ||
| cmd_list.insert(2, 'auto') | ||
| logger.info(cmd_list) | ||
@@ -135,10 +82,6 @@ pytest.main(cmd_list) | ||
| # api参数保存 | ||
| config.set_api("base_url", None) | ||
| config.set_api('login', {}) | ||
| config.set_api('visit', {}) | ||
| config.set_common("base_url", None) | ||
| config.set_common('headers', {}) | ||
| # app参数保存 | ||
| # 增加一段逻辑支持多进程以及设备调度 | ||
| # 清空空闲设备列表 | ||
| free_config.clear_devices() | ||
| config.set_app("device_id", None) | ||
| config.set_app("devices", []) | ||
| config.set_app("pkg_name", None) | ||
@@ -148,7 +91,4 @@ config.set_app("auto_start", False) | ||
| # web参数保存 | ||
| config.set_web("base_url", None) | ||
| config.set_web("browser_name", "chrome") | ||
| config.set_web("browser_name", None) | ||
| config.set_web("headless", False) | ||
| config.set_web("state", None) | ||
| config.set_web("cookies", None) | ||
@@ -155,0 +95,0 @@ |
+30
-0
@@ -19,2 +19,5 @@ import os | ||
| def get_common(self, key): | ||
| return self.get('common', key) | ||
| def get_api(self, key): | ||
@@ -36,2 +39,5 @@ return self.get('api', key) | ||
| def set_common(self, key, value): | ||
| self.set('common', key, value) | ||
| def set_api(self, key, value): | ||
@@ -46,3 +52,27 @@ self.set('api', key, value) | ||
| def add_devices(self, devices: list): | ||
| old_devices = self.get_app("devices") | ||
| new_devices = old_devices + devices | ||
| self.set_app("devices", new_devices) | ||
| def set_devices(self, devices: list): | ||
| self.set_app("devices", devices) | ||
| def get_all_device(self): | ||
| return self.get_app("devices") | ||
| def get_random_device(self): | ||
| devices: list = self.get_all_device() | ||
| if devices: | ||
| device = random.choice(devices) | ||
| devices.remove(device) | ||
| self.set_devices(devices) | ||
| return device | ||
| return [] | ||
| def clear_devices(self): | ||
| new_devices = [] | ||
| self.set_app("devices", new_devices) | ||
| class FreeConfig: | ||
@@ -49,0 +79,0 @@ def __init__(self): |
+12
-12
@@ -65,8 +65,8 @@ import json | ||
| headless = config.get_web("headless") | ||
| state = config.get_web("state") | ||
| if state: | ||
| state_json = json.loads(state) | ||
| self.driver = Driver(browserName=browserName, headless=headless, state=state_json) | ||
| else: | ||
| self.driver = Driver(browserName=browserName, headless=headless) | ||
| # state = config.get_web("state") | ||
| # if state: | ||
| # state_json = json.loads(state) | ||
| # self.driver = Driver(browserName=browserName, headless=headless, state=state_json) | ||
| # else: | ||
| self.driver = Driver(browserName=browserName, headless=headless) | ||
@@ -89,7 +89,7 @@ self.start() | ||
| def open_url(self, url=None): | ||
| def open_url(self, url=None, cookies=None): | ||
| """浏览器打开页面""" | ||
| # 拼接域名 | ||
| if url is None: | ||
| base_url = config.get_web("base_url") | ||
| base_url = config.get_common("base_url") | ||
| if not base_url: | ||
@@ -100,3 +100,3 @@ raise KError('base_url is null') | ||
| if "http" not in url: | ||
| base_url = config.get_web("base_url") | ||
| base_url = config.get_common("base_url") | ||
| if not base_url: | ||
@@ -107,4 +107,4 @@ raise KError('base_url is null') | ||
| self.driver.open_url(url) | ||
| # 设置cookies | ||
| cookies = config.get_web("cookies") | ||
| if cookies: | ||
@@ -130,3 +130,3 @@ self.driver.set_cookies(cookies) | ||
| if url is None: | ||
| base_url = config.get_web("base_url") | ||
| base_url = config.get_common("base_url") | ||
| if not base_url: | ||
@@ -137,3 +137,3 @@ raise KError('base_url is null') | ||
| if "http" not in url: | ||
| base_url = config.get_web("base_url") | ||
| base_url = config.get_common("base_url") | ||
| if not base_url: | ||
@@ -140,0 +140,0 @@ raise KError('base_url is null') |
@@ -82,6 +82,4 @@ """ | ||
| if __name__ == '__main__': | ||
| driver1 = PlayWrightDriver(browserName="chrome") | ||
| driver1.open_url("https://patents.qizhidao.com/search/simple-result?from=simple&searchBtntype=searchBtn&businessSource=PC%E6%9F%A5%E4%B8%93%E5%88%A9&statement=%E4%BC%81%E7%9F%A5%E9%81%93") | ||
| time.sleep(5) | ||
| print(driver1.page.url) | ||
| pass | ||
+1
-1
| Metadata-Version: 2.1 | ||
| Name: kuto | ||
| Version: 0.0.64 | ||
| Version: 0.0.65 | ||
| Summary: 全平台自动化测试框架 | ||
@@ -5,0 +5,0 @@ Home-page: https://gitee.com/bluepang2021/kuto |
+3
-2
@@ -41,6 +41,7 @@ # -*- coding: utf-8 -*- | ||
| 'filelock==3.12.2', | ||
| 'psutil==5.9.5' | ||
| 'psutil==5.9.5', | ||
| 'PyYAML~=6.0' | ||
| ], | ||
| extras_require={ | ||
| "reader": ['pandas==1.3.4', 'openpyxl==3.0.9', 'XlsxWriter==3.0.2', 'PyYAML~=6.0'], | ||
| "reader": ['pandas==1.3.4', 'openpyxl==3.0.9', 'XlsxWriter==3.0.2'], | ||
| "encrypt": ["pycryptodome==3.14.1"], | ||
@@ -47,0 +48,0 @@ "android": ['uiautomator2==2.16.23', 'opencv-python==4.6.0.66'], |
| devices: [] |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
222362
-1.38%94
-1.05%5364
-1.23%