有以下常用的模块可以套用WEB自动化和APP自动化
# 主要模块
-- 项目名称
-- page_object
-- data
-- locator
-- page
-- test(自己备用的,可以忽略)
-- test_case
-- utils
二、创建启动APP或WEB文件
1、启动app.py
from appium import webdriver
from Appium_20210407.pageObject.page.main_page import MainPage
from Appium_20210407.pageObject.utils.functions import Functions as Fun
class APP(object): appData = Fun().getYamlData('app') caps = appData['caps'] server = appData['server'] ip = server['ip'] port = server['port'] def startApp(self): """启动APP""" self.driver = webdriver.Remote(f"http://{self.ip}:{self.port}/wd/hub", self.caps) self.driver.implicitly_wait(15) return self def stopApp(self): """关闭APP""" self.driver.quit() def goto_main(self): """跳转主页""" return MainPage(self.driver)
①获取package和activity:
adb shell dumpsys activity | grep LAUNCHER
②app对应caps参数:
caps: platformName: "android" deviceName: "emulator-5554" appPackage: "com.tencent.wework" appActivity: ".launch.LaunchSplashActivity" noReset: True skipServerInstallation: True # 跳过UIautomator2 server安装 skipDeviceInitialization: True # 跳过设备的初始化 # waitForIdleTimeout: 1 # automationName: "UiAutomator2" # Toast内容 # dontStopAppOnReset: True # 测试之前不停止app运行 server: ip: 127.0.0.1 port: 4723
③对应的方法文件
import os
import yaml
class Functions: def pathUp(self): """获取上级路径""" path = os.path.dirname(os.path.dirname(__file__)) return path def getYaml(self,path): """获得yaml数据""" with open(path,encoding='utf-8') as file: data = yaml.load(file) return data def getCommonYaml(self): """获得公共yaml数据""" commonPath = self.pathUp()+ "/data/common.yaml" return self.getYaml(commonPath) def getYamlData(self,data): """获取当前的yaml数据""" yamlPath = self.pathUp() + self.getCommonYaml()[data] return self.getYaml(yamlPath)
2、启动web.py
from selenium import webdriver
from page_object.page.login_page import LoginPage
class Web: def startWeb(self): """开启WEB自动化""" self.driver = webdriver.Chrome("XXX/XXX/Administration_UI_Automate_Code/page_object/utils/chromedriver") self.driver.get("https://recycle_dev.XXXXXX.cn:9700/#/login") self.driver.implicitly_wait(10) return self def stopWeb(self): """关闭浏览器""" self.driver.quit() def goto_loginPage(self): """跳转登录页""" return LoginPage(self.driver)
①对应的方法文件:
import os
import time
import pyautogui
import pyperclip
import yaml
from pykeyboard import PyKeyboard
from pymouse import PyMouse
class Functions: def upPath(self): """获取上级路径""" path = os.path.dirname(os.path.dirname(__file__)) return path def getData(self, path): """获取yaml数据""" with open(path, 'r', encoding='utf-8') as file: data = yaml.load(file) return data def getYamlPath(self): """获取公共不同yaml路径""" path = self.upPath() + '/data/common.yaml' return self.getData(path) def getYamlData(self, yamlName): """获取当前yaml数据""" path = self.upPath() + self.getYamlPath()[yamlName] return self.getData(path) def getIndex(self,type,name): """获取区级对应角标index""" list = self.getYamlData('selectData')[type] for index,value in enumerate(list): if name in value: return index+1 def upload_file(self, path): """PyUserInput方法""" # 创建鼠标对象 k = PyKeyboard() # 创建键盘对象 m = PyMouse() filepath = "/" # 模拟快捷键Command+Shift+G k.press_keys(["Command", "Shift", "G"]) # 输入文件路径 x_dim, y_dim = m.screen_size() m.click(x_dim // 2, y_dim // 2, 1) # 复制文件路径开头的斜杠/ pyperclip.copy(filepath) # 粘贴斜杠/ k.press_keys(["Command", "V"]) time.sleep(2) # 输入文件全路径进去 k.type_string(path) fileName = '机构信息-批量导入模板 (9).xls' pyperclip.copy(fileName) k.press_keys(["Command", "V"]) time.sleep(2) k.press_key("Return") time.sleep(2) k.press_key("Return") time.sleep(2) def upload_file2(self,path): """pyautogui方法【不用】""" filepath = "/" pyautogui.press('shiftleft') pyautogui.hotkey("Command", "Shift", "G") pyautogui.typewrite(path, interval=0.25) pyautogui.press('return') time.sleep(2) pyautogui.press('return') time.sleep(2)
②对应的方法文件:
import os import yaml class Functions: def curPath(self): """获取本层目录""" basePath = os.path.dirname(os.path.abspath(__file__)) return basePath def upPath(self): """获取上级目录路径""" basePath = os.path.dirname(os.path.dirname(__file__)) return basePath def getYamlData(self,yamlName): """获取yaml数据""" yamlPath = self.upPath() + f'/data/{yamlName}Data.yaml' with open(yamlPath,encoding='utf-8') as file: data = yaml.load(file,Loader=yaml.FullLoader) return data[yamlName]
三、创建base_page文件
1、app自动化base
from appium.webdriver.webdriver import WebDriver
class Page: def __init__(self,driver:WebDriver): self.driver = driver def find_element(self,loc): """查找单元素""" return self.driver.find_element(*loc) def find_elements(self,loc): """查找多元素""" return self.driver.find_elements(*loc) def el_sendKeys(self,loc,text): """输入事件""" self.find_element(loc).send_keys(text) def el_click(self,loc): """点击事件""" self.find_element(loc).click() def swipeUp(self,loc): """上滑操作""" size = self.driver.get_window_size() width = size['width'] height = size['height'] while(True): try: # ele = self.driver.find_element(MobileBy.XPATH,loc) ele = self.find_element(loc) return ele except: print("继续上滑") self.driver.swipe(0.5 * width, 0.7 * height, 0.5 * width, 0.3 * height)
2、web自动化base
from selenium.webdriver.chrome.webdriver import WebDriver from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.action_chains import ActionChains as AC class Base: def __init__(self,driver: WebDriver): self.driver = driver def find_element(self,loc): """查找元素""" return self.driver.find_element(*loc) def find_elements(self,loc): """查找多组元素""" return self.driver.find_element(*loc) def el_click(self,loc): """点击事件""" self.webDriverWait(loc).click() def el_sendKeys(self,loc,text): """输入事件""" self.webDriverWait(loc).send_keys(text) def webDriverWait(self,loc): """显式等待,查找元素""" WebDriverWait(self.driver,10).until(EC.visibility_of_element_located(loc)) return self.find_element(loc) def handles(self): """获取所有页面handles""" return self.driver.window_handles def enter_window(self,index): """进入页面""" handles = self.handles() self.driver.switch_to.window(handles[index-1]) def get_text(self,loc): """获取文字内容""" return self.webDriverWait(loc).text def move_to_element(self,loc): """移动鼠标到元素上""" AC(self.driver).move_to_element(self.webDriverWait(loc)).perform() def el_clear(self,loc): """清空数据""" self.webDriverWait(loc).clear() def el_clear_sendKeys(self,loc,text): """清空数据并输入数据""" self.webDriverWait(loc).clear() self.el_sendKeys(loc,text)
四、关联每个页面跳转
1、举例web自动化
①跳转到登录页
class Web: def startWeb(self): """开启WEB自动化""" def stopWeb(self): """关闭浏览器""" def goto_loginPage(self): """跳转登录页""" return LoginPage(self.driver)
②登录页跳转到首页
from page_object.locator.loginPage_loc import LoginPageLoc as loc from page_object.page.base_page import Page from page_object.page.main_page import MainPage from page_object.utils.functions import Functions as Fun class LoginPage(Page): """登录页""" loginData = Fun().getYamlData("login") def login_action(self): """登录操作""" self.el_sendKeys(loc.telephone_loc, self.loginData['telephone']) self.el_sendKeys(loc.password_loc, self.loginData['password']) self.el_click(loc.loginButton_loc) def goto_mainPage(self): """跳转首页""" self.login_action() return MainPage(self.driver)
③首页跳转到信息列表页面
from page_object.page.base_page import Page from page_object.page.organList_page import OrganListPage from page_object.locator.mainPage_loc import MainMenuLoc as loc class MainPage(Page): """首页""" def goto_organListPage(self): """跳转信息列表页面""" self.el_click(loc.baseInfo_loc) self.el_click(loc.organInfo_loc) return OrganListPage(self.driver)
④列表页面跳转到新增页面
from page_object.locator.organListPage_loc import OrganListPageLoc as loc from page_object.page.base_page import Page from page_object.page.newOrgan_page import NewOrganPage from page_object.utils.functions import Functions as Fun class OrganListPage(Page): """XX信息列表页面""" organListData = Fun().getYamlData('organList') filePath = organListData['filePath'] def goto_newOrganPage(self): """跳转新增XX页面""" # 点击新增XX按钮 self.el_click(loc.addOrganButton_loc) return NewOrganPage(self.driver) def upload(self): """上传文件""" self.el_click(loc.batchImportButton_loc) self.el_click(loc.uploadOfCheckButton_loc) Fun().upload_file(self.filePath)
⑤新增页面
from page_object.page.base_page import Page from page_object.locator.newOrganPage_loc import NewOrganPageLoc as loc class NewOrganPage(Page): """新增机构页面""" def newOrgan(self): """新增机构""" # 输入XX名称 self.el_sendKeys(loc.organName_loc, loc.organName) # 输入XX编码 self.el_sendKeys(loc.organCode_loc, loc.organCode)# 输入详细地址 self.webDriverWait(loc.fullAddress_loc).send_keys(loc.fullAddressText) # 请输入联系人 self.webDriverWait(loc.contacts_loc).send_keys(loc.contactsText) # 请输入联系电话 self.webDriverWait(loc.telePhone_loc).send_keys(loc.telePhoneText) # 请输入说明 self.webDriverWait(loc.explain_loc).send_keys(loc.explainText) # 点击保存按钮 self.el_click(loc.saveButton_loc)
五、元素定位文件和yaml数据文件
1、创建定位文件
在locator目录下,,例如:newOrganPage_loc.py
from selenium.webdriver.common.by import By from page_object.utils.functions import Functions as Fun class NewOrganPageLoc: organData = Fun().getYamlData('organ') provinceLevelArea = organData['provinceLevelArea'] municipalLevelArea = organData['municipalLevelArea'] districtLevelArea = organData['districtLevelArea'] districtName = organData['districtName'] organType = organData['organType'] organTypeName = organData['organTypeName'] addressName = organData['addressName'] fullAddress = organData['fullAddress'] contacts = organData['contacts'] telePhone = organData['telePhone'] explain = organData['explain'] organName = organData['organName'] organCode = organData['organCode'] fullAddressText = organData['fullAddressText'] contactsText = organData['contactsText'] telePhoneText = organData['telePhoneText'] explainText = organData['explainText'] # XX名称 organName_loc = (By.CSS_SELECTOR, ".formtable > form > div:nth-child(1) >div>div>div>input") # XX编码 organCode_loc = (By.CSS_SELECTOR, ".formtable > form > div:nth-child(2) >div>div>div>input") # 单位级别 unitLevel_loc = (By.CSS_SELECTOR, ".formtable > form > div:nth-child(3) >div>div>div>div>input") # 选择XX级别:区级 selectDistrictLevel_loc = (By.XPATH, "//body/div[2]/div/div/ul/li[2]") # 所属行政区:省级地区 provinceLevelArea_loc = (By.XPATH, "//*[@placeholder='{}']".format(provinceLevelArea))# XX地址:区 districtLevel_loc = (By.XPATH, "//*[@placeholder='"+districtLevelArea[0]+"']") # XX地址区级:某某区 TJDistrict2_loc = (By.XPATH, "//body/div[9]/div/div/ul/li["+ str(Fun().getIndex(f'{districtLevelArea}',f'{addressName}')) +"]") # 详细地址 fullAddress_loc = (By.XPATH, f"//*[@placeholder='{fullAddress}']") # 联系人 contacts_loc = (By.XPATH, f"//*[@placeholder='{contacts}']") # 联系电话 telePhone_loc = (By.XPATH, f"//*[@placeholder='{telePhone}']") # 说明 explain_loc = (By.XPATH, f"//*[@placeholder='{explain}']") # XX保存按钮 saveButton_loc = (By.CSS_SELECTOR, ".el-button--primary")
2、相关yaml数据读取文件:
①这一种适用于下拉框的数据选择
区级地区: - 请选择 - 和平区 - 河东区 - 河西区 - 南开区 - 河北区 - 红桥区 - 东丽区 - 西青区 - 津南区 - 北辰区 - 武清区 - 宝坻区 - 滨海新区 - 宁河区 - 静海区 - 蓟州区 XX类型: - 卫生事业 - 国家机关 - 教育事业 - 文化事业 - 科技事业 - 体育事业 - 团体组织 - 其他 区域: - 省级地区 - 市级地区 - 区级地区
并结合下面的方法:
def getIndex(self,type,name): """获取区级对应角标index""" list = self.getYamlData('selectData')[type] for index,value in enumerate(list): if name in value: return index+1
②普通yaml格式
organName: XX51201 organCode: jg51201 provinceLevelArea: 省级地区 municipalLevelArea: 市级地区 districtLevelArea: 区级地区 districtName: 滨海新区 # 经常改的 organType: 请选择XX类型 organTypeName: 其他 # 经常改的 addressName: 蓟州区 # 经常改的 fullAddress: 请输入详细地址 fullAddressText: 蓟州区黄崖关 contacts: 请输入联系人 contactsText: zc联系人 telePhone: 请您输入手机号 telePhoneText: 136420xxxxx explain: 请输入说明 (100字以内) explainText: zcXX说明
六、创建测试用例
1、web自动化
①基础用例
from page_object.page.web import Web from page_object.utils.functions import Functions as Fun class BaseTestCase: loginData = Fun().getYamlData("login") def setup(self): self.web = Web().startWeb(self.loginData['url']) self.login = self.web.goto_loginPage() self.organListPage = self.login.goto_mainPage().goto_organListPage() def teardown(self): # Web().stopWeb() self.web.stopWeb()
②测试用例
from page_object.page.web import Web class TestOrgan: def setup(self): self.web = Web().startWeb() self.login = self.web.goto_loginPage() def teardown(self): self.web.stopWeb()
def test_addOrgan(self): """添加机构""" self.login.goto_mainPage()\ .goto_organListPage()\ .goto_newOrganPage()\ .newOrgan() def test_upload(self): """上传文件""" self.login.goto_mainPage()\ .goto_organListPage()\ .upload()
2、app自动化
from Appium_20210407.pageObject.page.app import APP class TestUser: def setup(self): self.app = APP().startApp() self.main = self.app.goto_main() def teardown(self): self.app.stopApp() def test_addUser(self): self.main.goto_addressList().goto_addUser().addUser_action()
七、接口自动化目录模板
参考项目地址:https://github.com/Owen-ET/2021_Python_HogwartsSDE17_Project_Practice/tree/master/API
# 主要模块
-- 项目名称
-- Server/API
-- data
-- feishu_work
--calendar_api
--base.py
--feishuWorkAddress.py
-- test_case
--calendar
--baseCalendarsTestData.py
--testCalendars.py
--base_testcase.py
-- utils
--functions.py
1、calendar_api接口
下面写base和增删改查等等接口
base.py
import requests from API.utils.functions import Functions class Base: def __init__(self): self.s = requests.Session() self.token = self.get_token() self.s.headers = { 'Authorization': f"Bearer {self.token}", 'Content-Type': "application/json; charset=utf-8" } self.baseUrl = self.base['calendarsUrl'] self.list = [] def get_token(self): '''获取token''' self.base = Functions().getYamlData('base') url = self.base['token']['url'] params = self.base['token']['params'] r = self.send('POST',url,json=params).json() return r['tenant_access_token'] def send(self,*args,**kwargs): return self.s.request(*args,**kwargs)
feishuWorkAddress.py
from API.feishu_work.calendar_api.base import Base class FeishuWorkCalendars(Base): def get_calendarsList_info(self,calendar_id): '''获取日历列表''' url = f'{self.baseUrl}{calendar_id}' # print(self.get_token()) # headers = { # 'Authorization': f"Bearer {self.get_token()}", # 'Content-Type': "application/json; charset=utf-8" # } # r = self.s.get(url).json() r = self.send('GET',url).json() return r def create_calendars(self,summary,description,permissions,color,summary_alias): '''创建日历''' url = f'{self.baseUrl}' # headers = { # 'Authorization': f"Bearer {self.get_token()}", # 'Content-Type': "application/json; charset=utf-8" # } data = { 'summary': summary, 'description': description, 'permissions': permissions, 'color': color, 'summary_alias': summary_alias } r = self.send('POST',url,json=data).json() return r def update_calendars(self,calendar_id,summary,description,permissions,color,summary_alias): '''修改日历''' url = f'{self.baseUrl}{calendar_id}' # headers = { # 'Authorization': f"Bearer {self.get_token()}", # 'Content-Type': "application/json; charset=utf-8" # } data = { 'summary': summary, 'description': description, 'permissions': permissions, 'color': color, 'summary_alias': summary_alias } r = self.send('PATCH',url, json=data).json() return r def delete_calendars(self,calendar_id): '''删除日历''' url = f'{self.baseUrl}{calendar_id}' # headers = { # 'Authorization': f"Bearer {self.get_token()}", # 'Content-Type': "application/json; charset=utf-8" # } r = self.send('DELETE',url).json() return r def delete_error_calendar_id(self,calendar_err,calendar_id_null): '''清除错误日历id''' # 查询全部日历 res_info = self.get_calendarsList_info(calendar_id_null) print(res_info) # 循环取出所有日历id到数组中 for i in range(2): result = res_info['data']['calendar_list'][i]['calendar_id'] self.list.append(result) # 清除错误日历id self.list.remove(calendar_err) return self.list[0]
2、test_case测试用例
分为三个部分:
①base用例:base_testcase.py
from API.feishu_work.calendar_api.feishuWorkAddress import FeishuWorkCalendars from API.test_case.calendar.baseCalendarsTestData import BaseCalendarsTestData class BaseTestCase: def setup_class(self): self.data = BaseCalendarsTestData() self.calendars = FeishuWorkCalendars() def setup(self): print("=======case_start=======") try: self.calendar_id = self.calendars.delete_error_calendar_id(self.data.calendar_err,self.data.calendar_id_null) except: pass def teardown(self): print("=======case_stop=======")
②用例数据:baseCalendarsTestData.py
from API.utils.functions import Functions class BaseCalendarsTestData(Functions): baseData = Functions().getYamlData('calendars') summary = baseData['summary'] newSummary = baseData['newSummary'] description = baseData['description'] permissions = baseData['permissions'] color = baseData['color'] summary_alias = baseData['summary_alias'] calendar_err = baseData['calendar_err'] calendar_id_null = baseData['calendar_id_null']
③测试用例:testCalendars.py
from API.test_case.base_testcase import BaseTestCase class TestCalendars(BaseTestCase): def test_create_calendars(self): res_create = self.calendars.create_calendars(self.data.summary, self.data.description, self.data.permissions, self.data.color, self.data.summary_alias) assert res_create['code'] == 0 result = self.calendars.get_calendarsList_info(self.calendars.delete_error_calendar_id(self.data.calendar_err,self.data.calendar_id_null)) assert result['code'] == 0 def test_get_calendars(self): res_get = self.calendars.get_calendarsList_info(self.data.calendar_id_null) print(res_get) assert res_get['code'] == 0 def test_update_calendars(self): res_update = self.calendars.update_calendars(self.calendar_id, self.data.newSummary, self.data.description, self.data.permissions, self.data.color, self.data.summary_alias) assert res_update['code'] == 0 res_get = self.calendars.get_calendarsList_info(self.data.calendar_id_null) print(res_get) assert res_get['code'] == 0 def test_delete_calendars(self): res_delete = self.calendars.delete_calendars(self.calendar_id) assert res_delete['code'] == 0 res_info = self.calendars.get_calendarsList_info(self.calendar_id) assert res_info['code'] == 0 print(res_info)
3、封装的公共方法utils
functions.py
import os import yaml class Functions: def upPath(self): '''获取上级目录路径''' basePath = os.path.dirname(os.path.dirname(__file__)) return basePath def getYamlData(self,yamlName='base'): '''获取yaml数据''' yamlPath = self.upPath() + f'/data/{yamlName}Data.yaml' with open(yamlPath,encoding='utf-8') as file: data = yaml.load(file) return data[yamlName]
4、yaml数据
baseData.yaml
base: calendarsUrl: "https://open.feishu.cn/open-apis/calendar/v4/calendars/" token: url: 'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/' params: app_id: 'cli_a18de0f937f9500d' app_secret: 'nUzgBNrv1sX20ARjI4dXUbVMYsDkUyPp'