├── .gitignore ├── README.md ├── appium_wework ├── __init__.py ├── datas │ ├── __init__.py │ └── contact.yml ├── page │ ├── __init__.py │ ├── addresslist_page.py │ ├── app.py │ ├── base_page.py │ ├── contact_add.py │ ├── main.py │ └── member_invit.py ├── steps │ └── __init__.py ├── test_d.py └── testcases │ ├── run.sh │ ├── run_cases.sh │ ├── test_contact1.py │ ├── test_search.py │ ├── test_xueqiu1.py │ ├── test_xueqiu2.py │ ├── test_xueqiu3.py │ └── test_xueqiu4.py ├── appium_xueqiu ├── __init__.py ├── page │ ├── __init__.py │ ├── app.py │ ├── base_page.py │ ├── main.py │ ├── main.yaml │ ├── market.py │ ├── search.py │ ├── search.yaml │ └── wrapper.py └── testcase │ ├── __init__.py │ ├── conftest.py │ ├── test_search.py │ ├── test_search.yaml │ ├── test_stack.py │ ├── test_vedio.py │ ├── test_wrapper.py │ ├── test_yaml_load.py │ ├── tmp.mp4 │ └── tmp.png ├── mitmproxy ├── __init__.py └── maplocal.py ├── performance_xueqiu ├── __init__.py └── test_xueqiu.py ├── requests_wework ├── __init__.py ├── action │ ├── __init__.py │ └── api_action.py ├── api │ ├── __init__.py │ ├── address.py │ ├── base_api.py │ ├── update.yaml │ └── wework.py ├── core │ ├── __init__.py │ ├── base_action.py │ ├── content.py │ └── exception.py ├── pytest.ini ├── test_partment.py ├── test_requests.py └── testcase │ ├── __init__.py │ ├── test_a.py │ ├── test_address.py │ ├── test_send_api.py │ └── work.yaml ├── selenium_cookies ├── __init__.py └── test_demo.py ├── selenium_wework_login ├── __init__.py ├── index.py ├── login.py ├── register.py └── test_register.py ├── selenium_wework_main ├── __init__.py ├── __pycache__ │ └── __init__.cpython-37.pyc ├── page │ ├── __init__.py │ ├── __pycache__ │ │ └── __init__.cpython-37.pyc │ ├── add_member.py │ ├── base_page.py │ └── main.py └── test_case │ ├── __init__.py │ └── test_add_member.py ├── tmp.xml └── venv └── pyvenv.cfg /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | __*__/ 3 | venv/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HogwartsSDET12 2 | 霍格沃兹测试学院测试开发python班12期演练 3 | -------------------------------------------------------------------------------- /appium_wework/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | -------------------------------------------------------------------------------- /appium_wework/datas/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | -------------------------------------------------------------------------------- /appium_wework/datas/contact.yml: -------------------------------------------------------------------------------- 1 | add: 2 | - ['霍格沃兹用户001','男','13100000001'] 3 | - ['霍格沃兹用户002','女','13100000002'] 4 | - ['霍格沃兹用户003','男','13100000003'] 5 | 6 | del: 7 | - ['霍格沃兹用户001'] 8 | - ['霍格沃兹用户002'] 9 | - ['霍格沃兹用户003'] -------------------------------------------------------------------------------- /appium_wework/page/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceshiren/HogwartsSDET12/8a8a71a996344735cb9a891eb9df679a6c059eb2/appium_wework/page/__init__.py -------------------------------------------------------------------------------- /appium_wework/page/addresslist_page.py: -------------------------------------------------------------------------------- 1 | from appium.webdriver.common.mobileby import MobileBy 2 | 3 | from appium_wework.page.base_page import BasePage 4 | from appium_wework.page.member_invit import MemberInvite 5 | 6 | 7 | class AddressList(BasePage): 8 | def add_member(self): 9 | self.find(MobileBy.XPATH, "//*[@text='添加成员']").click() 10 | return MemberInvite(self._driver) 11 | -------------------------------------------------------------------------------- /appium_wework/page/app.py: -------------------------------------------------------------------------------- 1 | from appium import webdriver 2 | 3 | from appium_wework.page.base_page import BasePage 4 | from appium_wework.page.main import Main 5 | 6 | 7 | class App(BasePage): 8 | def start(self): 9 | if self._driver == None: 10 | caps = {} 11 | caps["platformName"] = "Android" 12 | caps["deviceName"] = "192.168.56.103:5555" 13 | caps["appPackage"] = "com.tencent.wework" 14 | caps["appActivity"] = ".launch.WwMainActivity" 15 | caps['noReset'] = "true" 16 | caps['skipServerInstallation'] = True 17 | caps['skipDeviceInitialization'] = True 18 | self._driver = webdriver.Remote("http://localhost:4723/wd/hub", caps) 19 | else: 20 | self._driver.launch_app() 21 | 22 | self._driver.implicitly_wait(10) 23 | 24 | return self 25 | 26 | def restart(self): 27 | pass 28 | 29 | def stop(self): 30 | self._driver.quit() 31 | 32 | def main(self) -> Main: 33 | return Main(self._driver) 34 | -------------------------------------------------------------------------------- /appium_wework/page/base_page.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from appium.webdriver import WebElement 4 | from appium.webdriver.webdriver import WebDriver 5 | from selenium.webdriver.common.by import By 6 | 7 | 8 | class BasePage: 9 | # 弹框 处理的定位列表 10 | logging.basicConfig(level=logging.INFO) 11 | _black_list = [ 12 | (By.XPATH, "//*[@text='确认']"), 13 | (By.XPATH, "//*[@text='下次再说']"), 14 | (By.XPATH, "//*[@text='确定']"), 15 | ] 16 | _max_num = 3 17 | _error_num = 0 18 | 19 | def __init__(self, driver: WebDriver = None): 20 | self._driver = driver 21 | 22 | def back(self, num=1): 23 | for i in range(num): 24 | self._driver.back() 25 | 26 | def find(self, locator, value: str = None): 27 | logging.info(locator) 28 | logging.info(value) 29 | element: WebElement 30 | try: 31 | element = self._driver.find_element(*locator) if isinstance(locator, tuple) else self._driver.find_element( 32 | locator, value) 33 | # if isinstance(locator, tuple): 34 | # element = self._driver.find_element(*locator) 35 | # else: 36 | # element = self._driver.find_element(locator,value) 37 | # 找到之前 _error_num 归0 38 | self._error_num = 0 39 | # 隐式等待回复原来的等待, 40 | self._driver.implicitly_wait(10) 41 | return element 42 | except Exception as e: 43 | # 出现异常, 将隐式等待设置小一点,快速的处理弹框 44 | self._driver.implicitly_wait(1) 45 | # 判断异常处理次数 46 | if self._error_num > self._max_num: 47 | raise e 48 | self._error_num += 1 49 | # 处理黑名单里面的弹框 50 | for ele in self._black_list: 51 | logging.info(ele) 52 | elelist = self._driver.find_elements(*ele) 53 | if len(elelist) > 0: 54 | elelist[0].click() 55 | # 处理完弹框,再将去查找目标元素 56 | return self.find(locator, value) 57 | 58 | raise e 59 | 60 | def find_and_get_text(self, locator, value: str = None): 61 | element: WebElement 62 | try: 63 | element_text = self._driver.find_element(*locator).text if isinstance(locator, 64 | tuple) else self._driver.find_element( 65 | locator, value).text 66 | # if isinstance(locator, tuple): 67 | # element = self._driver.find_element(*locator) 68 | # else: 69 | # element = self._driver.find_element(locator,value) 70 | # 找到之前 _error_num 归0 71 | self._error_num = 0 72 | # 隐式等待回复原来的等待, 73 | self._driver.implicitly_wait(10) 74 | return element_text 75 | except Exception as e: 76 | # 出现异常, 将隐式等待设置小一点,快速的处理弹框 77 | self._driver.implicitly_wait(1) 78 | # 判断异常处理次数 79 | if self._error_num > self._max_num: 80 | raise e 81 | self._error_num += 1 82 | # 处理黑名单里面的弹框 83 | for ele in self._black_list: 84 | elelist = self._driver.find_elements(*ele) 85 | if len(elelist) > 0: 86 | elelist[0].click() 87 | # 处理完弹框,再将去查找目标元素 88 | return self.find_and_get_text(locator, value) 89 | 90 | raise e 91 | -------------------------------------------------------------------------------- /appium_wework/page/contact_add.py: -------------------------------------------------------------------------------- 1 | from appium.webdriver.common.mobileby import MobileBy 2 | 3 | from appium_wework.page.base_page import BasePage 4 | 5 | 6 | class ContactAdd(BasePage): 7 | 8 | def input_name(self, username): 9 | nameelement = self.find(MobileBy.XPATH, 10 | "//*[@text='姓名 ']/..//*[@class='android.widget.EditText']") 11 | nameelement.send_keys(username) 12 | 13 | return self 14 | 15 | def set_gender(self, gender): 16 | self.find(MobileBy.XPATH, 17 | "//*[@text='性别']/..//*[contains(@class, 'TextView') and @text='男']").click() 18 | if gender == '女': 19 | self.find(MobileBy.XPATH, "//*[@text='女']").click() 20 | else: 21 | self.find(MobileBy.XPATH, "//*[@text='男']").click() 22 | 23 | return self 24 | 25 | def input_phonenum(self, phonenum): 26 | phonenum_element = self.find(MobileBy.XPATH, 27 | "//*[@text='手机 ']/..//*[contains(@class, 'EditText')]") 28 | phonenum_element.send_keys(phonenum) 29 | 30 | return self 31 | 32 | def click_save(self): 33 | from appium_wework.page.member_invit import MemberInvite 34 | self.find(MobileBy.ID, "com.tencent.wework:id/gur").click() 35 | 36 | return MemberInvite(self._driver) 37 | -------------------------------------------------------------------------------- /appium_wework/page/main.py: -------------------------------------------------------------------------------- 1 | from appium.webdriver.common.mobileby import MobileBy 2 | 3 | from appium_wework.page.addresslist_page import AddressList 4 | from appium_wework.page.base_page import BasePage 5 | 6 | 7 | class Main(BasePage): 8 | 9 | def goto_message(self): 10 | pass 11 | 12 | def goto_addresslist(self): 13 | self._driver.find_element(MobileBy.XPATH, "//*[@text='通讯录']").click() 14 | return AddressList(self._driver) 15 | 16 | def goto_workbench(self): 17 | pass 18 | 19 | def goto_profile(self): 20 | pass 21 | -------------------------------------------------------------------------------- /appium_wework/page/member_invit.py: -------------------------------------------------------------------------------- 1 | from appium.webdriver.common.mobileby import MobileBy 2 | 3 | from appium_wework.page.base_page import BasePage 4 | 5 | 6 | class MemberInvite(BasePage): 7 | 8 | def addmember_by_manul(self): 9 | from appium_wework.page.contact_add import ContactAdd 10 | self.find(MobileBy.XPATH, "//*[@text='手动输入添加']").click() 11 | 12 | return ContactAdd(self._driver) 13 | 14 | def get_toast(self): 15 | return self.find(MobileBy.XPATH, "//*[@class='android.widget.Toast']").text 16 | # return "toast" 17 | -------------------------------------------------------------------------------- /appium_wework/steps/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | -------------------------------------------------------------------------------- /appium_wework/test_d.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from time import sleep 4 | 5 | import pytest 6 | from appium_wework import webdriver 7 | from appium_wework.webdriver.common.mobileby import MobileBy 8 | 9 | 10 | class TestTransaction(): 11 | def setup(self): 12 | desire_cap = { 13 | "platformName": "Android", 14 | "deviceName": "7XBNW19910007839", 15 | "appPackage": "com.bkt.exchange", 16 | "appActivity": ".activity.StartPageActivity", 17 | "autoGrantPermissions" : True, 18 | # 支持中文输入 19 | "unicodeKeyBoard": "true", 20 | "resetKeyBoard": "true", 21 | # 绕过弹窗 22 | "noReset": True, 23 | # 不需要重启,直接按照上次停留的页面继续操作(提升运行速度) 24 | # "dontStopAppOnReset": True, 25 | # 跳过安装,权限等设置等操作(提升运行速度) 26 | "skipDeviceInitialization": True 27 | } 28 | self.driver = webdriver.Remote("http://localhost:4723/wd/hub", desire_cap) 29 | self.driver.implicitly_wait(10) 30 | 31 | def teardown(self): 32 | sleep(2) 33 | self.driver.quit() 34 | pass 35 | 36 | def test_search(self): 37 | print("这是一个搜索案例") 38 | sleep(3) 39 | self.driver.find_element(MobileBy.XPATH, 40 | f"//*[@resource-id='com.bkt.exchange:id/tv_indicator'and @text='交易']").click() 41 | self.driver.find_element(MobileBy.CLASS_NAME, "android.widget.ImageView").click() 42 | 43 | @pytest.mark.parametrize("cointype,result",[ 44 | ('BKK',"BKK/USDT"), 45 | ('BNB',"BNB/USDT") 46 | ]) 47 | def test_bkk(self,cointype,result): 48 | # self.driver.find_element(MobileBy.ID, "com.bkt.exchange:id/stv_search").click() 49 | self.driver.find_element(MobileBy.XPATH, "//*[@text='交易']").click() 50 | self.driver.find_element(MobileBy.XPATH, "//*[@resource-id='com.bkt.exchange:id/rightContainLin']/android.widget.ImageView").click() 51 | self.driver.find_element(MobileBy.ID, "com.bkt.exchange:id/coin_edit").send_keys(cointype) 52 | self.driver.find_element(MobileBy.XPATH, f"//*[@resource-id='com.bkt.exchange:id/pair' and @text='{result}']").click() 53 | 54 | # self.driver.find_element(MobileBy.ID, "com.bkt.exchange:id/coin_edit").send_keys(cointype) 55 | # sleep(3) 56 | # # @pytest.mark.parametrize("transtype",['BKK/USDT','BNB/USDT','ETH/SUDT']) 57 | # # def test_response(self,transtype): 58 | # self.driver.find_element(MobileBy.XPATH, 59 | # f"//*[@resource-id='com.bkt.exchange:id/pair] and @text='{cointype}").click() 60 | # sleep(2) 61 | # # 返回到上一页面 62 | # self.driver.back() 63 | # self.driver.back() 64 | # 65 | # def test_add_favorites(self): 66 | # pass 67 | if __name__ == '__main__': 68 | pytest.main() -------------------------------------------------------------------------------- /appium_wework/testcases/run.sh: -------------------------------------------------------------------------------- 1 | for i in `adb devices|grep 'device$'|awk '{print $1}'` 2 | do 3 | echo $i 4 | udid=$i pytest test_xueqiu1.py --alluredir ./result_$i & 5 | done -------------------------------------------------------------------------------- /appium_wework/testcases/run_cases.sh: -------------------------------------------------------------------------------- 1 | [ -e /tmp/fifo_3 ] || mkfifo /tmp/fifo_3 2 | exec 3<> /tmp/fifo_3 3 | rm -rf /tmp/fifo_3 4 | 5 | adb devices | grep "device$" | awk '{print $1}' >&3 6 | 7 | find . -name "test_xueqiu*.py" | { 8 | while read file; do 9 | read udid <&3 && { 10 | echo udid=$udid 11 | udid=$udid pytest $file 12 | echo $udid >&3 13 | } & # & 放入后台执行 14 | 15 | done 16 | wait 17 | } 18 | 19 | exec 3<&- 20 | exec 3<&- -------------------------------------------------------------------------------- /appium_wework/testcases/test_contact1.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import yaml 3 | 4 | from appium_wework.page.app import App 5 | 6 | with open('../datas/contact.yml') as f: 7 | addlists = yaml.safe_load(f)['add'] 8 | 9 | 10 | class TestContact: 11 | def setup(self): 12 | self.app = App() 13 | self.main = self.app.start().main() 14 | 15 | @pytest.mark.parametrize('username, gender,phonenum', addlists) 16 | def test_addcontact(self, username, gender, phonenum): 17 | # invitpage = self.main.goto_addresslist().add_member(). \ 18 | # addmember_by_manul().input_name(username).set_gender(gender) \ 19 | # .input_phonenum(phonenum).click_save() 20 | print(addlists) 21 | 22 | # assert '成功' in invitpage.get_toast() 23 | 24 | def teardown(self): 25 | self.app.stop() 26 | -------------------------------------------------------------------------------- /appium_wework/testcases/test_search.py: -------------------------------------------------------------------------------- 1 | # This sample code uses the Appium python client 2 | # pip install Appium-Python-Client 3 | # Then you can paste this into a file and simply run with Python 4 | 5 | 6 | """ 7 | 改造1: pytest模式 8 | 改造2: 改造成可维护的代码形态,绝对不允许有绝对路径的存在 9 | 改造3: 将自动生成的find_element_by_** 改造成find_element(MobileBy.) 10 | 改造4: 添加断言 11 | 改造5: 合理使用 setup_class, setup 方法 12 | """ 13 | import pytest 14 | from appium import webdriver 15 | from appium.webdriver.common.mobileby import MobileBy 16 | 17 | 18 | class TestXueQiu: 19 | def setup_class(self): 20 | print("setup_class") 21 | caps = {} 22 | caps["platformName"] = "Android" 23 | caps["deviceName"] = "127.0.0.1:7555" 24 | caps["appPackage"] = "com.xueqiu.android" 25 | caps["appActivity"] = ".view.WelcomeActivityAlias" 26 | caps['noReset'] = "true" 27 | caps['skipServerInstallation'] = True 28 | caps['skipDeviceInitialization'] = True 29 | 30 | self.driver = webdriver.Remote("http://localhost:4724/wd/hub", caps) 31 | self.driver.implicitly_wait(10) 32 | 33 | def teardown_class(self): 34 | print("teardown_class") 35 | self.driver.quit() 36 | 37 | def setup(self): 38 | print("setup") 39 | 40 | def teardown(self): 41 | print("teardown") 42 | self.driver.find_element(MobileBy.ID, 'com.xueqiu.android:id/action_close').click() 43 | 44 | @pytest.mark.parametrize('searchkey,searchresult', [ 45 | ("alibaba", "阿里巴巴"), 46 | ("jd", "京东") 47 | ]) 48 | def test_search(self, searchkey, searchresult): 49 | print("search") 50 | el1 = self.driver.find_element(MobileBy.ID, "com.xueqiu.android:id/tv_search") 51 | el1.click() 52 | 53 | el2 = self.driver.find_element(MobileBy.ID, "com.xueqiu.android:id/search_input_text") 54 | el2.send_keys(searchkey) 55 | 56 | el3 = self.driver.find_element(MobileBy.XPATH, f"//*[@text='{searchresult}']") 57 | el3.click() 58 | 59 | el4 = self.driver.find_elements(MobileBy.XPATH, 60 | f"//*[@text='{searchresult}']/../..//*[@text='加自选']") 61 | if len(el4) > 0: 62 | el4[0].click() 63 | self.driver.find_element(MobileBy.XPATH, 64 | f"//*[@text='{searchresult}']/../..//*[@text='已添加']") 65 | else: 66 | print("已加自选") 67 | 68 | -------------------------------------------------------------------------------- /appium_wework/testcases/test_xueqiu1.py: -------------------------------------------------------------------------------- 1 | import os 2 | from time import sleep 3 | 4 | from appium import webdriver 5 | from appium.webdriver.common.mobileby import MobileBy 6 | 7 | 8 | class TestWeixin: 9 | def setup(self): 10 | caps = {} 11 | caps["platformName"] = "Android" 12 | caps["deviceName"] = "abc" 13 | caps['udid'] = os.getenv("udid", None) 14 | # caps["appPackage"] = "com.tencent.wework" 15 | # caps["appActivity"] = ".launch.WwMainActivity" 16 | caps["appPackage"] = "com.xueqiu.android" 17 | caps["appActivity"] = ".view.WelcomeActivityAlias" 18 | caps['noReset'] = "true" 19 | # caps['skipServerInstallation'] = True 20 | # caps['skipDeviceInitialization'] = True 21 | # caps['dontStopAppOnReset'] = True 22 | self.driver = webdriver.Remote("http://192.168.56.1:4444/wd/hub", caps) 23 | self.driver.implicitly_wait(10) 24 | 25 | def teardown(self): 26 | self.driver.quit() 27 | 28 | def test_search(self): 29 | self.driver.find_element(MobileBy.ID, "com.xueqiu.android:id/tv_search") 30 | 31 | def test_search1(self): 32 | self.driver.find_element(MobileBy.ID, "com.xueqiu.android:id/tv_search") 33 | 34 | def test_search2(self): 35 | self.driver.find_element(MobileBy.ID, "com.xueqiu.android:id/tv_search") 36 | 37 | def test_search3(self): 38 | self.driver.find_element(MobileBy.ID, "com.xueqiu.android:id/tv_search") 39 | -------------------------------------------------------------------------------- /appium_wework/testcases/test_xueqiu2.py: -------------------------------------------------------------------------------- 1 | import os 2 | from time import sleep 3 | 4 | from appium import webdriver 5 | from appium.webdriver.common.mobileby import MobileBy 6 | 7 | 8 | class TestWeixin: 9 | def setup(self): 10 | caps = {} 11 | caps["platformName"] = "Android" 12 | caps["deviceName"] = "abc" 13 | caps['udid'] = os.getenv("udid", None) 14 | # caps["appPackage"] = "com.tencent.wework" 15 | # caps["appActivity"] = ".launch.WwMainActivity" 16 | caps["appPackage"] = "com.xueqiu.android" 17 | caps["appActivity"] = ".view.WelcomeActivityAlias" 18 | caps['noReset'] = "true" 19 | # caps['skipServerInstallation'] = True 20 | # caps['skipDeviceInitialization'] = True 21 | # caps['dontStopAppOnReset'] = True 22 | self.driver = webdriver.Remote("http://192.168.56.1:4444/wd/hub", caps) 23 | self.driver.implicitly_wait(10) 24 | 25 | def teardown(self): 26 | self.driver.quit() 27 | 28 | def test_case1(self): 29 | self.driver.find_element(MobileBy.ID, "com.xueqiu.android:id/tv_search") 30 | 31 | def test_case2(self): 32 | self.driver.find_element(MobileBy.ID, "com.xueqiu.android:id/tv_search") 33 | 34 | def test_case3(self): 35 | self.driver.find_element(MobileBy.ID, "com.xueqiu.android:id/tv_search") 36 | -------------------------------------------------------------------------------- /appium_wework/testcases/test_xueqiu3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | from time import sleep 5 | 6 | from appium import webdriver 7 | from appium.webdriver.common.mobileby import MobileBy 8 | 9 | 10 | class TestWeixin: 11 | def setup(self): 12 | caps = {} 13 | caps["platformName"] = "Android" 14 | caps["deviceName"] = "abc" 15 | caps['udid'] = os.getenv("udid", None) 16 | # caps["appPackage"] = "com.tencent.wework" 17 | # caps["appActivity"] = ".launch.WwMainActivity" 18 | caps["appPackage"] = "com.xueqiu.android" 19 | caps["appActivity"] = ".view.WelcomeActivityAlias" 20 | caps['noReset'] = "true" 21 | # caps['skipServerInstallation'] = True 22 | # caps['skipDeviceInitialization'] = True 23 | # caps['dontStopAppOnReset'] = True 24 | self.driver = webdriver.Remote("http://192.168.56.1:4444/wd/hub", caps) 25 | self.driver.implicitly_wait(10) 26 | 27 | def teardown(self): 28 | self.driver.quit() 29 | 30 | def test_case4(self): 31 | self.driver.find_element(MobileBy.ID, "com.xueqiu.android:id/tv_search") 32 | 33 | def test_case5(self): 34 | self.driver.find_element(MobileBy.ID, "com.xueqiu.android:id/tv_search") 35 | 36 | def test_case6(self): 37 | self.driver.find_element(MobileBy.ID, "com.xueqiu.android:id/tv_search") 38 | -------------------------------------------------------------------------------- /appium_wework/testcases/test_xueqiu4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | from time import sleep 5 | 6 | from appium import webdriver 7 | from appium.webdriver.common.mobileby import MobileBy 8 | 9 | 10 | class TestWeixin: 11 | def setup(self): 12 | caps = {} 13 | caps["platformName"] = "Android" 14 | caps["deviceName"] = "abc" 15 | caps['udid'] = os.getenv("udid", None) 16 | # caps["appPackage"] = "com.tencent.wework" 17 | # caps["appActivity"] = ".launch.WwMainActivity" 18 | caps["appPackage"] = "com.xueqiu.android" 19 | caps["appActivity"] = ".view.WelcomeActivityAlias" 20 | caps['noReset'] = "true" 21 | # caps['skipServerInstallation'] = True 22 | # caps['skipDeviceInitialization'] = True 23 | # caps['dontStopAppOnReset'] = True 24 | self.driver = webdriver.Remote("http://192.168.56.1:4444/wd/hub", caps) 25 | self.driver.implicitly_wait(10) 26 | 27 | def teardown(self): 28 | self.driver.quit() 29 | 30 | def test_case7(self): 31 | self.driver.find_element(MobileBy.ID, "com.xueqiu.android:id/tv_search") 32 | 33 | def test_case8(self): 34 | self.driver.find_element(MobileBy.ID, "com.xueqiu.android:id/tv_search") 35 | 36 | def test_case9(self): 37 | self.driver.find_element(MobileBy.ID, "com.xueqiu.android:id/tv_search") 38 | -------------------------------------------------------------------------------- /appium_xueqiu/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceshiren/HogwartsSDET12/8a8a71a996344735cb9a891eb9df679a6c059eb2/appium_xueqiu/__init__.py -------------------------------------------------------------------------------- /appium_xueqiu/page/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceshiren/HogwartsSDET12/8a8a71a996344735cb9a891eb9df679a6c059eb2/appium_xueqiu/page/__init__.py -------------------------------------------------------------------------------- /appium_xueqiu/page/app.py: -------------------------------------------------------------------------------- 1 | from appium import webdriver 2 | 3 | from appium_wework.page.base_page import BasePage 4 | from appium_xueqiu.page.main import Main 5 | 6 | 7 | class App(BasePage): 8 | def start(self): 9 | if self._driver == None: 10 | caps = {} 11 | caps["platformName"] = "Android" 12 | caps["deviceName"] = "192.168.56.102:5555" 13 | caps["appPackage"] = "com.xueqiu.android" 14 | caps["appActivity"] = ".view.WelcomeActivityAlias" 15 | caps['noReset'] = "true" 16 | caps['skipServerInstallation'] = True 17 | caps['skipDeviceInitialization'] = True 18 | self._driver = webdriver.Remote("http://localhost:4723/wd/hub", caps) 19 | else: 20 | self._driver.launch_app() 21 | 22 | self._driver.implicitly_wait(15) 23 | 24 | return self 25 | 26 | def restart(self): 27 | self._driver.close() 28 | self._driver.launch_app() 29 | 30 | def stop(self): 31 | self._driver.quit() 32 | 33 | def main(self) -> Main: 34 | return Main(self._driver) 35 | -------------------------------------------------------------------------------- /appium_xueqiu/page/base_page.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import json 3 | import yaml 4 | from appium.webdriver import WebElement 5 | from appium.webdriver.webdriver import WebDriver 6 | 7 | from appium_xueqiu.page.wrapper import handle_black 8 | 9 | 10 | class BasePage: 11 | _params = {} 12 | 13 | def __init__(self, driver: WebDriver = None): 14 | self._driver = driver 15 | 16 | def set_implicitly(self, time): 17 | self._driver.implicitly_wait(time) 18 | 19 | def screenshot(self, name): 20 | self._driver.save_screenshot(name) 21 | 22 | def finds(self, locator, value: str = None): 23 | elements: list 24 | if isinstance(locator, tuple): 25 | elements = self._driver.find_elements(*locator) 26 | else: 27 | elements = self._driver.find_elements(locator, value) 28 | return elements 29 | 30 | @handle_black 31 | def find(self, locator, value: str = None): 32 | element: WebElement 33 | if isinstance(locator, tuple): 34 | element = self._driver.find_element(*locator) 35 | else: 36 | element = self._driver.find_element(locator, value) 37 | return element 38 | 39 | @handle_black 40 | def find_and_get_text(self, locator, value: str = None): 41 | element: WebElement 42 | if isinstance(locator, tuple): 43 | element_text = self._driver.find_element(*locator).text 44 | else: 45 | element_text = self._driver.find_element(locator, value).text 46 | return element_text 47 | 48 | def get_screenshot(self, filename): 49 | self._driver.get_screenshot_as_file(filename) 50 | 51 | def steps(self, path): 52 | with open(path, encoding="utf-8") as f: 53 | name = inspect.stack()[1].function 54 | steps = yaml.safe_load(f)[name] 55 | raw = json.dumps(steps) 56 | for key, value in self._params.items(): 57 | raw = raw.replace('${' + key + '}', value) 58 | steps = json.loads(raw) 59 | for step in steps: 60 | if "action" in step.keys(): 61 | action = step["action"] 62 | if "click" == action: 63 | self.find(step["by"], step["locator"]).click() 64 | if "send" == action: 65 | self.find(step["by"], step["locator"]).send_keys(step["value"]) 66 | if "len > 0" == action: 67 | eles = self.finds(step["by"], step["locator"]) 68 | return len(eles) > 0 69 | 70 | def back(self): 71 | self._driver.back() 72 | -------------------------------------------------------------------------------- /appium_xueqiu/page/main.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | from selenium.webdriver.common.by import By 3 | 4 | from appium_xueqiu.page.base_page import BasePage 5 | from appium_xueqiu.page.market import Market 6 | 7 | 8 | class Main(BasePage): 9 | def goto_market(self): 10 | self.set_implicitly(10) 11 | self.steps("../page/main.yaml") 12 | self.set_implicitly(3) 13 | return Market(self._driver) 14 | -------------------------------------------------------------------------------- /appium_xueqiu/page/main.yaml: -------------------------------------------------------------------------------- 1 | goto_market: 2 | - by: xpath 3 | locator: "//*[@resource-id='android:id/tabs']//*[@text='行情']" 4 | action: click -------------------------------------------------------------------------------- /appium_xueqiu/page/market.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.common.by import By 2 | 3 | from appium_xueqiu.page.base_page import BasePage 4 | from appium_xueqiu.page.search import Search 5 | 6 | 7 | class Market(BasePage): 8 | def goto_search(self): 9 | # self.find(By.XPATH, "//*[@resource-id='com.xueqiu.android:id/action_search']").click() 10 | return Search(self._driver) -------------------------------------------------------------------------------- /appium_xueqiu/page/search.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | from selenium.webdriver.common.by import By 3 | 4 | from appium_xueqiu.page.base_page import BasePage 5 | 6 | 7 | class Search(BasePage): 8 | def search(self, name): 9 | self._params["name"]=name 10 | self.steps("../page/search.yaml") 11 | 12 | def add(self,name): 13 | self._params["name"] = name 14 | self.steps("../page/search.yaml") 15 | 16 | def is_choose(self, name): 17 | self._params["name"] = name 18 | return self.steps("../page/search.yaml") 19 | 20 | def reset(self, name): 21 | self._params["name"] = name 22 | return self.steps("../page/search.yaml") 23 | -------------------------------------------------------------------------------- /appium_xueqiu/page/search.yaml: -------------------------------------------------------------------------------- 1 | search: 2 | - by: id 3 | locator: 'com.xueqiu.android:id/action_search' 4 | action: click 5 | - by: id 6 | # locator: '//*[@resource-id="com.xueqiu.android:id/search_input_text"]' 7 | locator: 'com.xueqiu.android:id/search_input_text' 8 | action: send 9 | value: alibaba 10 | - by: xpath 11 | locator: '//*[@text="BABA"]' 12 | action: click 13 | 14 | add: 15 | - by: xpath 16 | locator: '//*[contains(@resource-id,"stock_item_container")]//*[@text="${name}"]/../..//*[@text="加自选"]' 17 | action: click 18 | 19 | is_choose: 20 | - by: xpath 21 | locator: '//*[contains(@resource-id,"stock_item_container")]//*[@text="${name}"]/../..//*[@text="已添加"]' 22 | action: len > 0 23 | 24 | reset: 25 | - by: xpath 26 | locator: '//*[contains(@resource-id,"stock_item_container")]//*[@text="${name}"]/../..//*[@text="已添加"]' 27 | action: click -------------------------------------------------------------------------------- /appium_xueqiu/page/wrapper.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import allure 4 | from selenium.webdriver.common.by import By 5 | 6 | 7 | def handle_black(func): 8 | logging.basicConfig(level=logging.INFO) 9 | 10 | def wrapper(*args, **kwargs): 11 | from appium_xueqiu.page.base_page import BasePage 12 | _black_list = [ 13 | (By.XPATH, "//*[@resource-id='com.xueqiu.android:id/action_search']"), 14 | (By.XPATH, "//*[@text='确认']"), 15 | (By.XPATH, "//*[@text='下次再说']"), 16 | (By.XPATH, "//*[@text='确定']"), 17 | ] 18 | _max_num = 3 19 | _error_num = 0 20 | instance: BasePage = args[0] 21 | try: 22 | logging.info("run " + func.__name__ + "\n args: \n" + repr(args[1:]) + "\n" + repr(kwargs)) 23 | element = func(*args, **kwargs) 24 | _error_num = 0 25 | # 隐式等待回复原来的等待, 26 | instance._driver.implicitly_wait(10) 27 | return element 28 | except Exception as e: 29 | instance.screenshot("tmp.png") 30 | with open("tmp.png", "rb") as f: 31 | content = f.read() 32 | allure.attach(content,attachment_type=allure.attachment_type.PNG) 33 | logging.error("element not found, handle black list") 34 | instance._driver.get_screenshot_as_png() 35 | instance._driver.implicitly_wait(1) 36 | # 判断异常处理次数 37 | if _error_num > _max_num: 38 | raise e 39 | _error_num += 1 40 | # 处理黑名单里面的弹框 41 | for ele in _black_list: 42 | elelist = instance.finds(*ele) 43 | if len(elelist) > 0: 44 | elelist[0].click() 45 | # 处理完弹框,再将去查找目标元素 46 | return wrapper(*args, **kwargs) 47 | raise e 48 | 49 | return wrapper 50 | -------------------------------------------------------------------------------- /appium_xueqiu/testcase/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceshiren/HogwartsSDET12/8a8a71a996344735cb9a891eb9df679a6c059eb2/appium_xueqiu/testcase/__init__.py -------------------------------------------------------------------------------- /appium_xueqiu/testcase/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shlex 3 | import signal 4 | import subprocess 5 | from typing import List 6 | 7 | import pytest 8 | 9 | 10 | # @pytest.fixture(scope="class", autouse=True) 11 | # def record(): 12 | # cmd = shlex.split("scrcpy --record tmp.mp4") 13 | # p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 14 | # yield 15 | # os.kill(p.pid, signal.CTRL_C_EVENT) 16 | 17 | 18 | def pytest_collection_modifyitems( 19 | session: "Session", config: "Config", items: List["Item"] 20 | ) -> None: 21 | # 倒序执行 items里面的测试用例 22 | for item in items: 23 | item.name = item.name.encode('utf-8').decode('unicode-escape') 24 | item._nodeid = item.nodeid.encode('utf-8').decode('unicode-escape') 25 | -------------------------------------------------------------------------------- /appium_xueqiu/testcase/test_search.py: -------------------------------------------------------------------------------- 1 | import os 2 | import signal 3 | import subprocess 4 | import time 5 | 6 | import allure 7 | import pytest 8 | import yaml 9 | 10 | from appium_xueqiu.page.app import App 11 | 12 | projectpath = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) + '/resource/' 13 | videpath = projectpath + 'videos/' 14 | imagepath = projectpath + 'images/' 15 | 16 | 17 | class TestSearch(): 18 | def setup_class(self): 19 | self.app = App() 20 | self.search = self.app.start().main().goto_market().goto_search() 21 | 22 | @pytest.mark.parametrize("name", yaml.safe_load(open("./test_search.yaml", encoding="utf-8"))) 23 | def test_search(self, name): 24 | videfile = videpath + 'video_' + str(time.time()) + '.mp4' 25 | imagefile = imagepath + "searchresult.png" 26 | p = subprocess.Popen(f"scrcpy -r {videfile}", shell=True) 27 | try: 28 | self.search.search(name) 29 | if self.search.is_choose(name): 30 | self.search.reset(name) 31 | self.search.add(name) 32 | assert self.search.is_choose(name) 33 | 34 | finally: 35 | self.search.get_screenshot(imagefile) 36 | os.kill(p.pid, signal.SIGTERM) 37 | time.sleep(2) 38 | allure.attach.file(imagefile, name='截图', attachment_type=allure.attachment_type.PNG) 39 | allure.attach.file(videfile, name='视频', attachment_type=allure.attachment_type.MP4) 40 | 41 | def teardown(self): 42 | self.app.back() 43 | 44 | def teardown_class(self): 45 | self.app.stop() 46 | -------------------------------------------------------------------------------- /appium_xueqiu/testcase/test_search.yaml: -------------------------------------------------------------------------------- 1 | - "阿里巴巴-SW" 2 | - "阿里巴巴" -------------------------------------------------------------------------------- /appium_xueqiu/testcase/test_stack.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | 3 | 4 | def a(): 5 | print(inspect.stack()[2].function) 6 | print("a") 7 | 8 | 9 | def b(): 10 | a() 11 | 12 | 13 | def test_stack(): 14 | b() 15 | -------------------------------------------------------------------------------- /appium_xueqiu/testcase/test_vedio.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shlex 3 | import signal 4 | import subprocess 5 | from time import sleep 6 | 7 | 8 | def test_vedio(): 9 | cmd=shlex.split("scrcpy --record tmp.mp4") 10 | p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 11 | print(p) 12 | sleep(10) 13 | os.kill(p.pid, signal.CTRL_C_EVENT) -------------------------------------------------------------------------------- /appium_xueqiu/testcase/test_wrapper.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | 4 | def f(a): 5 | def extend(fuc): 6 | @wraps(fuc) 7 | def hello(*args, **kwargs): 8 | print(a) 9 | print("hello") 10 | fuc(*args, **kwargs) 11 | print("good bye") 12 | return hello 13 | return extend 14 | 15 | 16 | @f("AAAAAAAAAAAAA") 17 | def tmp(): 18 | print("tmp") 19 | 20 | 21 | def tmp1(): 22 | print("tmp1") 23 | 24 | 25 | def test_wrapper(): 26 | print(tmp()) 27 | -------------------------------------------------------------------------------- /appium_xueqiu/testcase/test_yaml_load.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | 3 | 4 | def test_yaml_load(): 5 | with open("../page/main.yaml", encoding="utf-8") as f: 6 | steps = yaml.safe_load(f) 7 | for step in steps: 8 | if "by" in step.keys(): 9 | print("查找元素") 10 | if "action" in step.keys(): 11 | print("多个动作解析") 12 | action = step["action"] 13 | if "click" == action: 14 | print("click操作") 15 | if "send" == action: 16 | value = step["value"] 17 | print(f"send({value})") 18 | 19 | 20 | def test_replace(): 21 | _parame={"name":"12345"} 22 | str = "xxxxxxxxx ${name}lll${name}llll${name}lllllllllllllll" 23 | for key,value in _parame.items(): 24 | str = str.replace('${'+key+'}', value) 25 | print(str) -------------------------------------------------------------------------------- /appium_xueqiu/testcase/tmp.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceshiren/HogwartsSDET12/8a8a71a996344735cb9a891eb9df679a6c059eb2/appium_xueqiu/testcase/tmp.mp4 -------------------------------------------------------------------------------- /appium_xueqiu/testcase/tmp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceshiren/HogwartsSDET12/8a8a71a996344735cb9a891eb9df679a6c059eb2/appium_xueqiu/testcase/tmp.png -------------------------------------------------------------------------------- /mitmproxy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceshiren/HogwartsSDET12/8a8a71a996344735cb9a891eb9df679a6c059eb2/mitmproxy/__init__.py -------------------------------------------------------------------------------- /mitmproxy/maplocal.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | def response(flow): 5 | if "quote.json" in flow.request.pretty_url: 6 | data = json.loads(flow.response.content) 7 | data['data']['items'][0]['quote']['name'] = "hogwarts00001" 8 | data['data']['items'][1]['quote']['name'] = "hogwarts00002" 9 | data['data']['items'][1]['quote']['current'] = 123000 10 | flow.response.text = json.dumps(data) 11 | -------------------------------------------------------------------------------- /performance_xueqiu/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceshiren/HogwartsSDET12/8a8a71a996344735cb9a891eb9df679a6c059eb2/performance_xueqiu/__init__.py -------------------------------------------------------------------------------- /performance_xueqiu/test_xueqiu.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import subprocess 3 | from time import sleep 4 | 5 | from appium import webdriver 6 | from matplotlib import pyplot 7 | from selenium.webdriver.common.by import By 8 | 9 | 10 | def test_xueqiu(): 11 | caps = {} 12 | caps["platformName"] = "Android" 13 | caps["deviceName"] = "127.0.0.1:7555" 14 | caps["appPackage"] = "com.xueqiu.android" 15 | caps["appActivity"] = ".view.WelcomeActivityAlias" 16 | caps['noReset'] = "true" 17 | caps['chromedriverExecutable'] = "D:/develop/chromedriver/2.20.exe" 18 | driver = webdriver.Remote("http://localhost:4723/wd/hub", caps) 19 | driver.implicitly_wait(15) 20 | driver.find_element(By.XPATH, "//*[@text='交易']").click() 21 | webview = driver.contexts[-1] 22 | driver.switch_to.context(webview) 23 | performance = driver.execute_script("return window.performance.timing") 24 | print(performance) 25 | print(performance['responseEnd'] - performance['connectStart']) 26 | 27 | 28 | logging.basicConfig(level=logging.INFO) 29 | 30 | 31 | def test_vmstat(): 32 | cmd = "adb shell vmstat" 33 | res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 34 | logging.info(str(res.stdout.read(), encoding="utf-8").split("\r\n")[2].split()[3]) 35 | 36 | 37 | def test_navigation(): 38 | caps = {} 39 | caps["platformName"] = "Android" 40 | caps["deviceName"] = "127.0.0.1:7555" 41 | caps["appPackage"] = "com.xueqiu.android" 42 | caps["appActivity"] = ".view.WelcomeActivityAlias" 43 | caps['noReset'] = "true" 44 | caps['chromedriverExecutable'] = "D:/develop/chromedriver/2.20.exe" 45 | driver = webdriver.Remote("http://localhost:4723/wd/hub", caps) 46 | driver.implicitly_wait(15) 47 | driver.find_element(By.XPATH, "//*[@text='交易']").click() 48 | webview = driver.contexts[-1] 49 | driver.switch_to.context(webview) 50 | # 查看刚进入页面的操作码:0 51 | print(driver.execute_script("return window.location.href")) 52 | 53 | 54 | def test_draw(): 55 | cmd = "adb shell dumpsys gfxinfo com.xueqiu.android" 56 | res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 57 | lines = res.stdout.readlines() 58 | i = 1 59 | # 数据的提取 60 | for line in lines: 61 | i += 1 62 | if "com.xueqiu.android.common.MainActivity" in line.decode("utf-8"): 63 | break 64 | # 处理,删除 windows 特殊字符,并提取 120 帧 65 | lines = [x.decode("utf-8").replace("\r\n", "").replace("\t", " ").strip() for x in lines] 66 | lines = lines[i:i + 120] 67 | datas = [[] for row in range(4)] 68 | # 把四列数据分别存入二维 list 69 | for x in lines: 70 | datas[0].append(float(x.split()[0])) 71 | datas[1].append(float(x.split()[1])) 72 | datas[2].append(float(x.split()[2])) 73 | datas[3].append(float(x.split()[3])) 74 | # 生成画布 75 | fig = pyplot.figure() 76 | # 折线图,第一个图片 77 | ax1 = fig.add_subplot(2, 2, 1) 78 | ax1.plot(datas[0]) 79 | ax1.set_title("draw") 80 | # 直方图,第二个图片 81 | ax1 = fig.add_subplot(2, 2, 2) 82 | ax1.hist(datas[1], range(5)) 83 | ax1.set_title("prepare") 84 | # 散点图,第三个图片 85 | ax1 = fig.add_subplot(2, 2, 3) 86 | ax1.scatter(range(120), datas[2]) 87 | ax1.set_title("process") 88 | # 虚线图,第图个图片 89 | ax1 = fig.add_subplot(2, 2, 4) 90 | ax1.plot(range(120),datas[3], 'k--') 91 | ax1.set_title("execute") 92 | pyplot.show() 93 | 94 | -------------------------------------------------------------------------------- /requests_wework/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceshiren/HogwartsSDET12/8a8a71a996344735cb9a891eb9df679a6c059eb2/requests_wework/__init__.py -------------------------------------------------------------------------------- /requests_wework/action/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceshiren/HogwartsSDET12/8a8a71a996344735cb9a891eb9df679a6c059eb2/requests_wework/action/__init__.py -------------------------------------------------------------------------------- /requests_wework/action/api_action.py: -------------------------------------------------------------------------------- 1 | from requests_wework.core.base_action import BaseAction 2 | 3 | 4 | class api_action(BaseAction): 5 | pass 6 | -------------------------------------------------------------------------------- /requests_wework/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceshiren/HogwartsSDET12/8a8a71a996344735cb9a891eb9df679a6c059eb2/requests_wework/api/__init__.py -------------------------------------------------------------------------------- /requests_wework/api/address.py: -------------------------------------------------------------------------------- 1 | from requests_wework.api.base_api import BaseApi 2 | from requests_wework.api.wework import WeWork 3 | 4 | 5 | class Address(BaseApi): 6 | def __init__(self): 7 | secrete = "T72_Vgw9TaNS-FLDU2gJlw6AteerMXsuMval9kGNZbc" 8 | self.token = WeWork().get_token(secrete) 9 | 10 | def create(self, userid, name, mobile): 11 | data = { 12 | "method": "post", 13 | "url": "https://qyapi.weixin.qq.com/cgi-bin/user/create", 14 | "params": { 15 | "access_token": self.token 16 | }, 17 | "json": { 18 | "userid": userid, 19 | "name": name, 20 | "mobile": mobile, 21 | "department": [1] 22 | } 23 | } 24 | return self.send(data) 25 | 26 | def update(self, userid, name, mobile): 27 | data = { 28 | "method": "post", 29 | "url": "https://qyapi.weixin.qq.com/cgi-bin/user/update", 30 | "params": { 31 | "access_token": self.token 32 | }, 33 | "json": { 34 | "userid": userid, 35 | "name": name, 36 | "mobile": mobile, 37 | } 38 | } 39 | 40 | return self.send(data) 41 | 42 | def delete(self, userid): 43 | data = { 44 | "method": "get", 45 | "url": "https://qyapi.weixin.qq.com/cgi-bin/user/delete", 46 | "params": { 47 | "access_token": self.token, 48 | "userid": userid 49 | 50 | } 51 | } 52 | return self.send(data) 53 | -------------------------------------------------------------------------------- /requests_wework/api/base_api.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import allure 4 | import requests 5 | 6 | 7 | class BaseApi: 8 | 9 | def send(self, data): 10 | allure.attach(str(data), attachment_type=allure.attachment_type.TEXT) 11 | return requests.request(**data).json() 12 | -------------------------------------------------------------------------------- /requests_wework/api/update.yaml: -------------------------------------------------------------------------------- 1 | update: 2 | "method": "post", 3 | "url": "https://qyapi.weixin.qq.com/cgi-bin/user/update", 4 | "params": { 5 | "access_token": ${self.token} 6 | }, 7 | "json": { 8 | "userid": ${userid}, 9 | "name": ${name}, 10 | "mobile": ${mobile}, 11 | } 12 | -------------------------------------------------------------------------------- /requests_wework/api/wework.py: -------------------------------------------------------------------------------- 1 | from requests_wework.api.base_api import BaseApi 2 | 3 | 4 | class WeWork(BaseApi): 5 | def get_token(self, secrete): 6 | corpid = "wwe653983e4c732493" 7 | corpsecret = secrete 8 | data = { 9 | "method": "get", 10 | "url": "https://qyapi.weixin.qq.com/cgi-bin/gettoken", 11 | "params": { 12 | "corpid": corpid, 13 | "corpsecret": corpsecret 14 | } 15 | } 16 | 17 | return self.send(data)["access_token"] -------------------------------------------------------------------------------- /requests_wework/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceshiren/HogwartsSDET12/8a8a71a996344735cb9a891eb9df679a6c059eb2/requests_wework/core/__init__.py -------------------------------------------------------------------------------- /requests_wework/core/base_action.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import re 3 | import types 4 | 5 | import yaml 6 | 7 | from requests_wework.core.content import Content 8 | 9 | 10 | class BaseAction: 11 | depends = [] 12 | local_variable = {} 13 | 14 | def __init__(self, content: Content): 15 | self.data = content.get_data() 16 | # 扫描函数,把函数存入局部变量表 17 | self.parse_content() 18 | 19 | def parse_content(self): 20 | for command_name, command_content in self.data.items(): 21 | if "depend" == command_name: 22 | for depend in command_content: 23 | self.depends.append(importlib.import_module(depend)) 24 | elif command_name.startswith('def_'): 25 | fun_name = command_name[4:] 26 | 27 | def __init__(s, int_command_content, in_fun_name): 28 | s.fun_name = in_fun_name 29 | s.command_content = int_command_content 30 | 31 | def run(s): 32 | for in_command_name, in_command_content in s.command_content.items(): 33 | if in_command_name.startswith('run_'): 34 | in_fun_name = in_command_name[4:] 35 | try: 36 | return self.local_variable[in_fun_name].run() 37 | except KeyError: 38 | for import_mod in self.depends: 39 | if hasattr(import_mod, in_fun_name): 40 | content_format = self.parse_value(in_command_content) 41 | return getattr(import_mod, in_fun_name)\ 42 | (**content_format).json() 43 | 44 | cls_dict = { 45 | '__init__': __init__, 46 | 'run': run 47 | } 48 | tmp_class = types.new_class('tmp_class', (), {}, lambda ns: ns.update(cls_dict)) 49 | self.local_variable[fun_name] = tmp_class(command_content, fun_name) 50 | 51 | def parse_value(self, content): 52 | raw = yaml.dump(content) 53 | functions = re.findall(r"\$\((.*)\)", raw) 54 | for function in functions: 55 | parse_res = self.run_fun(function) 56 | if "access_token" in parse_res.keys(): 57 | raw = raw.replace(f"$({function})", repr(parse_res["access_token"])) 58 | else: 59 | raw = raw.replace(f"$({function})", repr(parse_res)) 60 | return yaml.load(raw) 61 | 62 | def run_fun(self, fun_name): 63 | return self.local_variable[fun_name].run() 64 | -------------------------------------------------------------------------------- /requests_wework/core/content.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import yaml 4 | 5 | from requests_wework.core.exception import FileNotFound 6 | 7 | 8 | class Content: 9 | def __init__(self, path): 10 | if not os.path.isfile(path): 11 | raise FileNotFound(f"{path} not file") 12 | file_suffix = os.path.splitext(path)[1].lower() 13 | if file_suffix in [".yaml", ".yml"]: 14 | self.path = path 15 | with open(path) as f: 16 | self._data = yaml.safe_load(f) 17 | self._filepath, self._tmpfilename = os.path.split(path) 18 | 19 | def get_data(self): 20 | return self._data 21 | -------------------------------------------------------------------------------- /requests_wework/core/exception.py: -------------------------------------------------------------------------------- 1 | class FileNotFound(FileNotFoundError, Exception): 2 | pass -------------------------------------------------------------------------------- /requests_wework/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | #addopts = -n auto 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /requests_wework/test_partment.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | def test_token(): 5 | res = None 6 | # 获取 token 7 | corpid = "wwe653983e4c732493" 8 | corpsecret = "T72_Vgw9TaNS-FLDU2gJlw6AteerMXsuMval9kGNZbc" 9 | res = requests.get(f"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={corpid}&" 10 | f"corpsecret={corpsecret}") 11 | return res.json()["access_token"] 12 | 13 | 14 | def test_create(): 15 | data = { 16 | "name": "广州研发中心", 17 | "parentid": 1 18 | } 19 | res = requests.post(f"https://qyapi.weixin.qq.com/cgi-bin/department/create?access_token={test_token()}", 20 | json=data) 21 | print(res.json()) 22 | 23 | 24 | def test_update(): 25 | data = { 26 | "id": 2, 27 | "name": "广州研发中心123456" 28 | } 29 | res = requests.post(f"https://qyapi.weixin.qq.com/cgi-bin/department/update?access_token={test_token()}", 30 | json=data) 31 | print(res.json()) 32 | 33 | 34 | def test_delete(): 35 | res = requests.get(f"https://qyapi.weixin.qq.com/cgi-bin/department/delete?access_token={test_token()}&id=2") 36 | print(res.json()) 37 | 38 | 39 | def test_get(): 40 | res = requests.get(f"https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token={test_token()}") 41 | print(res.json()) 42 | -------------------------------------------------------------------------------- /requests_wework/test_requests.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import pytest 4 | import requests 5 | from filelock import FileLock 6 | 7 | from requests_wework.api.wework import WeWork 8 | 9 | 10 | @pytest.fixture(scope="session") 11 | def test_token(): 12 | res = None 13 | # 获取 token 14 | while FileLock("session.lock"): 15 | corpid = "wwe653983e4c732493" 16 | corpsecret = "T72_Vgw9TaNS-FLDU2gJlw6AteerMXsuMval9kGNZbc" 17 | res = requests.get(f"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={corpid}&" 18 | f"corpsecret={corpsecret}") 19 | return res.json()["access_token"] 20 | 21 | 22 | def test_get(userid, test_token): 23 | # 根据 user-id查询成员 24 | res = requests.get(f"https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token={test_token}&userid={userid}") 25 | return res.json() 26 | 27 | 28 | def test_create(userid, name, mobile, test_token): 29 | # 添加成员 30 | data = { 31 | "userid": userid, 32 | "name": name, 33 | "mobile": mobile, 34 | "department": [1], 35 | } 36 | res = requests.post(f"https://qyapi.weixin.qq.com/cgi-bin/user/create?access_token={test_token}", 37 | json=data 38 | ) 39 | print(res.json()) 40 | return res.json() 41 | 42 | 43 | def test_update(userid, name, mobile, test_token): 44 | # 更新成员 45 | data = { 46 | "userid": userid, 47 | "name": name, 48 | "mobile": mobile, 49 | } 50 | res = requests.post(f"https://qyapi.weixin.qq.com/cgi-bin/user/update?access_token={test_token}", 51 | json=data) 52 | return res.json() 53 | 54 | 55 | def test_delete(userid, test_token): 56 | # 删除成员 57 | res = requests.get(f"https://qyapi.weixin.qq.com/cgi-bin/user/delete?access_token={test_token}&userid={userid}") 58 | return res.json() 59 | 60 | 61 | def test_create_data(): 62 | data = [("wu123fff" + str(x), "zhangsan", "138%08d" % x) for x in range(20)] 63 | return data 64 | 65 | 66 | @pytest.mark.parametrize("userid, name, mobile", test_create_data()) 67 | def test_all(userid, name, mobile, test_token): 68 | # 可能发生创建失败 69 | try: 70 | assert "created" == test_create(userid, name, mobile, test_token)["errmsg"] 71 | except AssertionError as e: 72 | if "mobile existed" in e.__str__(): 73 | # 如果手机号被使用了,找出使用手机号的 userid ,进行删除 74 | re_userid = re.findall(":(.*)", e.__str__())[0] 75 | if re_userid.endswith("'") or re_userid.endswith('"'): 76 | re_userid = re_userid[:-1] 77 | assert "deleted" == test_delete(re_userid, test_token)['errmsg'] 78 | assert 60111 == test_get(re_userid, test_token)['errcode'] 79 | assert "created" == test_create(userid, name, mobile, test_token)["errmsg"] 80 | # 可能发生userid不存在异常 81 | assert name == test_get(userid, test_token)['name'] 82 | assert "updated" == test_update(userid, "xxxxxxx", mobile, test_token)['errmsg'] 83 | assert "xxxxxxx" == test_get(userid, test_token)['name'] 84 | assert "deleted" == test_delete(userid, test_token)['errmsg'] 85 | assert 60111 == test_get(userid, test_token)['errcode'] 86 | 87 | 88 | def test_session(): 89 | s = requests.Session() 90 | s.params = { 91 | "access_token": WeWork().get_token("T72_Vgw9TaNS-FLDU2gJlw6AteerMXsuMval9kGNZbc") 92 | } 93 | 94 | res = s.get("https://qyapi.weixin.qq.com/cgi-bin/user/get?userid=abcdef") 95 | print(res.json()) 96 | -------------------------------------------------------------------------------- /requests_wework/testcase/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceshiren/HogwartsSDET12/8a8a71a996344735cb9a891eb9df679a6c059eb2/requests_wework/testcase/__init__.py -------------------------------------------------------------------------------- /requests_wework/testcase/test_a.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | 3 | 4 | def test_a(): 5 | yaml.load("avcde") -------------------------------------------------------------------------------- /requests_wework/testcase/test_address.py: -------------------------------------------------------------------------------- 1 | from requests_wework.api.address import Address 2 | 3 | 4 | class TestAddress: 5 | 6 | def setup(self): 7 | self.address = Address() 8 | 9 | def test_create(self): 10 | print(self.address.create("zhangsan2222", "wangwu", "13899999999")) 11 | 12 | def test_update(self): 13 | print(self.address.update("zhangsan2222", "wangwufffff", "13899999999")) 14 | 15 | def test_delete(self): 16 | print(self.address.delete("zhangsan2222")) 17 | -------------------------------------------------------------------------------- /requests_wework/testcase/test_send_api.py: -------------------------------------------------------------------------------- 1 | from requests_wework.action.api_action import api_action 2 | from requests_wework.core.content import Content 3 | 4 | 5 | def test_send_api(): 6 | content = Content("./work.yaml") 7 | expression = api_action(content) 8 | res = expression.run_fun('get') 9 | print(res) -------------------------------------------------------------------------------- /requests_wework/testcase/work.yaml: -------------------------------------------------------------------------------- 1 | depend: ['requests'] 2 | 3 | def_get_token: 4 | run_request: 5 | method: get 6 | url: https://qyapi.weixin.qq.com/cgi-bin/gettoken 7 | params: 8 | corpid: wwe653983e4c732493 9 | corpsecret: T72_Vgw9TaNS-FLDU2gJlw6AteerMXsuMval9kGNZbc 10 | 11 | def_get: 12 | run_request: 13 | method: get 14 | url: https://qyapi.weixin.qq.com/cgi-bin/user/get 15 | params: 16 | access_token: 17 | $(get_token) 18 | userid: 19 | YuRuoTong 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /selenium_cookies/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceshiren/HogwartsSDET12/8a8a71a996344735cb9a891eb9df679a6c059eb2/selenium_cookies/__init__.py -------------------------------------------------------------------------------- /selenium_cookies/test_demo.py: -------------------------------------------------------------------------------- 1 | # Generated by Selenium IDE 2 | import shelve 3 | from time import sleep 4 | 5 | from selenium import webdriver 6 | from selenium.webdriver.chrome.options import Options 7 | from selenium.webdriver.common.by import By 8 | 9 | 10 | class TestDemo(): 11 | def setup_method(self, method): 12 | options = Options() 13 | options.debugger_address = "127.0.0.1:9222" 14 | self.driver = webdriver.Chrome() 15 | self.vars = {} 16 | 17 | def teardown_method(self, method): 18 | self.driver.quit() 19 | 20 | def test_demo(self): 21 | #print(self.driver.get_cookies()) 22 | self.driver.get('https://work.weixin.qq.com/wework_admin/frame') 23 | db = shelve.open("cookies") 24 | #db['cookie'] = self.driver.get_cookies() 25 | cookies = db['cookie'] 26 | for cookie in cookies: 27 | if "expiry" in cookie.keys(): 28 | cookie.pop("expiry") 29 | self.driver.add_cookie(cookie) 30 | self.driver.get('https://work.weixin.qq.com/wework_admin/frame') 31 | sleep(3) 32 | db.close() 33 | -------------------------------------------------------------------------------- /selenium_wework_login/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceshiren/HogwartsSDET12/8a8a71a996344735cb9a891eb9df679a6c059eb2/selenium_wework_login/__init__.py -------------------------------------------------------------------------------- /selenium_wework_login/index.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.common.by import By 3 | 4 | from selenium_wework_login.login import Login 5 | from selenium_wework_login.register import Register 6 | 7 | 8 | class Index: 9 | def __init__(self): 10 | self._driver = webdriver.Chrome() 11 | self._driver.get('https://work.weixin.qq.com/') 12 | 13 | def goto_login(self): 14 | # click login 15 | self._driver.find_element(By.CSS_SELECTOR, '.index_top_operation_loginBtn').click() 16 | return Login(self._driver) 17 | 18 | def goto_register(self): 19 | # click register 20 | self._driver.find_element(By.CSS_SELECTOR, '.index_head_info_pCDownloadBtn').click() 21 | return Register(self._driver) 22 | -------------------------------------------------------------------------------- /selenium_wework_login/login.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.common.by import By 2 | from selenium.webdriver.remote.webdriver import WebDriver 3 | 4 | from selenium_wework_login.register import Register 5 | 6 | 7 | class Login: 8 | def __init__(self, driver: WebDriver): 9 | self._driver = driver 10 | 11 | def scanf(self): 12 | pass 13 | 14 | def goto_register(self): 15 | # click register 16 | self._driver.find_element(By.CSS_SELECTOR, '.login_registerBar_link').click() 17 | 18 | return Register(self._driver) 19 | -------------------------------------------------------------------------------- /selenium_wework_login/register.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | 3 | from selenium.webdriver.common.by import By 4 | from selenium.webdriver.remote.webdriver import WebDriver 5 | 6 | 7 | class Register: 8 | def __init__(self, driver: WebDriver): 9 | self._driver = driver 10 | 11 | def register_true(self): 12 | # send content 13 | # click element 14 | sleep(2) 15 | self._driver.find_element(By.ID, 'corp_name').send_keys("hello11111") 16 | self._driver.find_element(By.ID, 'manager_name').send_keys("hello11111") 17 | sleep(5) 18 | self._driver.quit() 19 | return True 20 | -------------------------------------------------------------------------------- /selenium_wework_login/test_register.py: -------------------------------------------------------------------------------- 1 | from selenium_wework_login.index import Index 2 | 3 | 4 | class TestRegister: 5 | def setup(self): 6 | self.index = Index() 7 | 8 | def test_register(self): 9 | #self.index.goto_login().goto_register().register() 10 | self.index.goto_register().register() 11 | -------------------------------------------------------------------------------- /selenium_wework_main/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceshiren/HogwartsSDET12/8a8a71a996344735cb9a891eb9df679a6c059eb2/selenium_wework_main/__init__.py -------------------------------------------------------------------------------- /selenium_wework_main/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceshiren/HogwartsSDET12/8a8a71a996344735cb9a891eb9df679a6c059eb2/selenium_wework_main/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /selenium_wework_main/page/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceshiren/HogwartsSDET12/8a8a71a996344735cb9a891eb9df679a6c059eb2/selenium_wework_main/page/__init__.py -------------------------------------------------------------------------------- /selenium_wework_main/page/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceshiren/HogwartsSDET12/8a8a71a996344735cb9a891eb9df679a6c059eb2/selenium_wework_main/page/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /selenium_wework_main/page/add_member.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | 3 | from selenium.webdriver.common.by import By 4 | 5 | from selenium_wework_main.page.base_page import BasePage 6 | 7 | 8 | class AddMember(BasePage): 9 | 10 | def add_member(self): 11 | # sendkeys 12 | self.find(By.ID, 'username').send_keys("abcdefffff") 13 | self.find(By.ID, 'memberAdd_acctid').send_keys("abcdefffff") 14 | self.find(By.ID, 'memberAdd_acctid').send_keys("abcdefffff") 15 | self.find(By.ID, 'memberAdd_phone').send_keys("11111111111") 16 | self.find(By.CSS_SELECTOR, '.js_btn_save').click() 17 | 18 | def update_page(self): 19 | content: str = self.find(By.CSS_SELECTOR, '.ww_pageNav_info_text').text 20 | # 对 1/10 进行切割 21 | return [int(x) for x in content.split('/', 1)] 22 | 23 | def get_member(self, value): 24 | self.wait_for_click((By.CSS_SELECTOR, ".ww_checkbox")) 25 | cur_page, total_page = self.update_page() 26 | while True: 27 | elements = self.finds(By.CSS_SELECTOR, '.member_colRight_memberTable_td:nth-child(2)') 28 | for element in elements: 29 | if value == element.get_attribute("title"): 30 | return True 31 | cur_page = self.update_page()[0] 32 | if cur_page == total_page: 33 | return False 34 | self.find(By.CSS_SELECTOR, '.js_next_page').click() 35 | -------------------------------------------------------------------------------- /selenium_wework_main/page/base_page.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.chrome.options import Options 3 | from selenium.webdriver.remote.webdriver import WebDriver 4 | from selenium.webdriver.support import expected_conditions 5 | from selenium.webdriver.support.wait import WebDriverWait 6 | 7 | 8 | class BasePage: 9 | _driver = None 10 | _base_url = "" 11 | 12 | def __init__(self, driver: WebDriver = None): 13 | if driver is None: 14 | options = Options() 15 | options.debugger_address = "127.0.0.1:9222" 16 | self._driver = webdriver.Chrome(options=options) 17 | self._driver.implicitly_wait(3) 18 | 19 | else: 20 | self._driver = driver 21 | 22 | if self._base_url != "": 23 | self._driver.get(self._base_url) 24 | 25 | def find(self, by, locator): 26 | return self._driver.find_element(by, locator) 27 | 28 | def finds(self, by, locator): 29 | return self._driver.find_elements(by, locator) 30 | 31 | def wait_for_click(self, locator, time=10): 32 | WebDriverWait(self._driver, time).until(expected_conditions.element_to_be_clickable(locator)) 33 | 34 | def wait_for_elem(self, conditions, time=10): 35 | WebDriverWait(self._driver, time).until(conditions) 36 | -------------------------------------------------------------------------------- /selenium_wework_main/page/main.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.common.by import By 2 | from selenium.webdriver.support import expected_conditions 3 | from selenium.webdriver.support.wait import WebDriverWait 4 | 5 | from selenium_wework_main.page.add_member import AddMember 6 | from selenium_wework_main.page.base_page import BasePage 7 | 8 | 9 | class Main(BasePage): 10 | _base_url = 'https://work.weixin.qq.com/wework_admin/frame' 11 | 12 | def goto_add_member(self): 13 | # click add member 14 | # self.find(By.CSS_SELECTOR, '.index_service_cnt_itemWrap:nth-child(1)').click() 15 | self.find(By.ID, 'menu_contacts').click() 16 | def wait_add_member(x): 17 | elements_len = len(self.finds(By.CSS_SELECTOR, '#username')) 18 | if elements_len <= 0: 19 | self.find(By.CSS_SELECTOR, '.js_has_member>div:nth-child(1)>a:nth-child(2)').click() 20 | return elements_len > 0 21 | #self.waif_for_click((By.CSS_SELECTOR, '.js_has_member>div:nth-child(1)>a:nth-child(2)')) 22 | self.wait_for_elem(wait_add_member) 23 | return AddMember(self._driver) 24 | -------------------------------------------------------------------------------- /selenium_wework_main/test_case/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceshiren/HogwartsSDET12/8a8a71a996344735cb9a891eb9df679a6c059eb2/selenium_wework_main/test_case/__init__.py -------------------------------------------------------------------------------- /selenium_wework_main/test_case/test_add_member.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | 3 | from selenium_wework_main.page.main import Main 4 | 5 | 6 | class TestAddMember: 7 | def setup(self): 8 | self.main = Main() 9 | 10 | def test_addmember(self): 11 | add_member = self.main.goto_add_member() 12 | add_member.add_member() 13 | assert add_member.get_member('abcdefffff') 14 | 15 | -------------------------------------------------------------------------------- /tmp.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceshiren/HogwartsSDET12/8a8a71a996344735cb9a891eb9df679a6c059eb2/tmp.xml -------------------------------------------------------------------------------- /venv/pyvenv.cfg: -------------------------------------------------------------------------------- 1 | home = D:\develop\python3.7.4 2 | include-system-site-packages = false 3 | version = 3.7.4 4 | --------------------------------------------------------------------------------