栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

安卓APPUI自动化测试:python+uiautomator2+pytest+pytest-html

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

安卓APPUI自动化测试:python+uiautomator2+pytest+pytest-html

  • 自动化测试框架

自动化测试框架:python+uiautomator2+pytest+pytest-html

  • 自动化的设计模式

设计模式:POM(PageObject Model)

说明文件(readme.txt),具体如下:

 

  • 概述

该DEMO项目用于自动化测试APP导航零号的一框搜索功能,内容如下:

 

 

  • base目录

basepage.py

# 创建者: zhangxuexin

# 创建时间: :2022/5/10  15:25

import os

import time

from time import sleep

import uiautomator2 as u2

from PIL import Image

from config.config_read import config

from config.log_read import logger





class BasePage():

    '''该类用于封装uiautomator2的各种操作,以作为工具类来使用'''



    def __init__(self, d):

        self.d = d



    # 元素定位

    def loction(self, attribute, loc):

        '''

        :param attribute:元素属性名称相当于key

        :param loc: 元素属性值,相当于value

        :return: 定位到的元素

        '''

        try:

            if attribute == 'resourceId':

                return self.d(resourceId=loc)

            elif attribute == 'text':

                return self.d(text=loc)

            logger.error(f'获取元素信息为:{loc},元素属性为:{attribute},元素定位成功')

        except Exception as e:

            logger.error(f'获取元素信息为:{loc},元素属性为:{attribute},元素定位失败')



    # app启动

    def start_app(self, package_name):

        self.d.app_start(package_name)



    # app退出

    def stop_app(self, package_name):

        self.d.app_stop(package_name)



    # app全部退出

    def stop_allapp(self):

        self.d.app_stop_all()

        logger.info('退出设备所有程序')



    # 点击操作

    def click(self, attribute, loc):

        try:

            self.loction(attribute, loc).click()

            logger.info(f'点击信息为:{loc},元素属性为:{attribute},点击操作成功')

        except Exception as e:

            logger.error(f'点击信息为:{loc},元素属性为:{attribute},点击操作失败')



    # 输入字符

    def send_keys(self, attribute, loc, text):

        try:

            self.loction(attribute, loc).send_keys(text)

            logger.info(f'获取信息为:{loc},元素属性为:{attribute},输入:{text},输入操作成功')

        except Exception as e:

            logger.error(f'获取信息为:{loc},元素属性为:{attribute},输入:{text},输入操作失败')



    # 用于截图并保存至特定目录

    def image_save(self):

        img = self.d.screenshot()  # 进行截屏

        image_name = f'{time.strftime("%Y%m%d%H%M%S")}.png'  # 设置图片名称按照当前时间

        image_path = "F:\u2demo_test\report\image\" + image_name  # 设置图片存储路径

        img.save(image_path)



    # 用于捕获所定位元素是否存在的判断

    def assert_text(self, attribute, loc):

        '用于判断所要获取的元素是否存在'

        try:

            assert self.loction(attribute, loc).exists(timeout=5)  # 可能发生问题的代码

            logger.info(f'验证信息为:{loc},元素属性为:{attribute},验证成功')

        except Exception as e:

            logger.error(f'验证信息为:{loc},元素属性为:{attribute},验证失败')  # 如果出现异常后要执行的代码

            self.image_save()  # 如果验证失败,则进行当前页面的截图

            sleep(2)

  • config目录

Config_read.py

# 创建者: zhangxuexin

# 创建时间: :2022/5/14  15:01

import configparser as cp



# 配置文件路径

file_path = "..config.ini"





class Config_Read():

    rc = cp.ConfigParser()  # 创建ConfigParser文件配置对象

    rc.read(file_path, 'utf-8')  # 读取配置文件目录



    def read_config(self, section, value):

        '''该方法用于获取配置文件中某个section的值'''

        return self.rc.get(section, value)



    def read_package(self):

        '''该方法用于获取默认APP包名'''

        return self.rc.get('Default', 'package')



    def read_device_id(self):

        '''该方法用于获取默认设备序列号'''

        return self.rc.get('Default', 'device_id')



    def read_Overall_level(self):

        '''该方法用于获取默认总的日志等级'''

        return self.rc.get('Default', 'Overall_level')



    def read_data_address(self):

        '''该方法用于获取默认数据地址'''

        return self.rc.get('Default', 'data_address')





config = Config_Read()



if __name__ == '__main__':

    print(config.read_device_id())

log_read.py

# 创建者: zhangxuexin

# 创建时间: :2022/5/11  21:52

import logging

import time





class My_Log():

    '''该类用于进行日志的收集'''



    def __init__(self, logfile_path=f"..//report/log"

                                    f"/log[{time.strftime('%Y-%m-%d')}].txt"):  # 按照日期来生成一个log文件

        '''

        :param logfile_path: 定义log文件输出方式的文件路径

        :param log_level: 定义log日志级别信息

        :param handles_level: 定义输出渠道级别信息

        '''

        # 创建日志收集器

        self.logger = logging.getLogger(__name__)  # 定义一个日志收集器

        self.logger.setLevel('DEBUG')  # 定义日志收集器的日志等级

        # info =  # 定义日志输出格式

        fmt = logging.Formatter(

            "[%(asctime)s]:[%(filename)s[%(lineno)d]][%(levelname)s]:%(message)s")  # 定义好的日志输入格式(info)放入日志收集器



        # 创建日志通过控制台输出

        ch = logging.StreamHandler()  # 定义控制台输出方式处理器

        ch.setLevel('DEBUG')  # 定义控制台输出方式日志等级

        ch.setFormatter(fmt)  # 将日志收集器中的日志输出格式传给控制台输出方式处理器

        self.logger.addHandler(ch)  # 日志收集器添加控制台输出方式处理器



        # 创建日志通过文件方式输出

        fh = logging.FileHandler(logfile_path,

                                 mode='a', encoding='utf-8')  # 定义文件输出方式处理器

        fh.setLevel('DEBUG')  # 定义文件输出方式日志等级

        fh.setFormatter(fmt)  # 将日志收集器中的日志输出格式传给文本输出方式处理器

        self.logger.addHandler(fh)  # 日志收集器添加文本输出方式处理器



    def getlog(self):

        return self.logger





logger = My_Log().getlog()



if __name__ == "__main__":

    logger.debug('123')

    logger.info('123')

    logger.warning('123')

    logger.error('123')

    logger.critical('123')

    logger.info(f"{time.strftime('%Y-%m-%d')}")

Data_read.py

# 创建者: zhangxuexin

# 创建时间: :2022/5/10  20:42

import openpyxl as op





class Data_Read():

    '''此类用于读取数据文件内容'''



    # 这个方法是该类的专属方法,所以使用静态方法

    @staticmethod

    def excle_read(address, sheetname):

        wb = op.load_workbook(address)

        ws = wb[sheetname]

        data_list = []

        for row in ws['A']:

            # print(row.value)

            data_list.append(row.value)

        # print(data_list)

        return data_list





if __name__ == '__main__':

    dr = Data_Read()

    dr.excle_read("..//data/address_data.xlsx", 'Sheet1')

  • page_object目录

mapmain_page.py

# 创建者: zhangxuexin

# 创建时间: :2022/5/10  20:11

from base.basepage import BasePage

from config.config_read import config

from config.log_read import logger

import uiautomator2 as u2





# d = u2.connect()

# logger.info('设备连接成功')

# d.screen_on()

# logger.info('屏幕亮起')

# try:

#     d.app_start(config.read_package())

#     logger.info(f'成功启动:{config.read_config("Default", "package_name")}')

#     d.implicitly_wait(10)

# except Exception as e:

#     logger.critical(f'启动失败:{config.read_config("Default", "package_name")}')

#     exit()





class Map_Page(BasePage):

    '该类用于封装主地图页面的页面元素和主要业务'



    # 页面元素

    search_lable = "com.mapbar.navigation.zero:id/rl_topSearchBarContainer"  # 一框搜索栏

    Routeplan_button = "com.mapbar.navigation.zero:id/ll_enterRoutePlanView"  # 路径规划按键

    search_lable_text = '在此输入搜索词'  # 搜索栏文本



    def search(self):

        '''跳转到一框搜索页面操作'''

        logger.info(f'点击一框搜索栏(search_lable),元素属性为:resourceId')

        self.click('resourceId', self.search_lable)

        logger.info('验证信息为:在此输入搜索词,元素属性为:text')

        self.assert_text('text', self.search_lable_text)



    def Route_plan(self):

        '''跳转到路线规划页面操作'''

        self.click('resourceId', self.Routeplan_button)





if __name__ == '__main__':

    mp = Map_Page(d)

    mp.search()

search_page.py

# 创建者: zhangxuexin

# 创建时间: :2022/5/10  16:22

from time import sleep

from base.basepage import BasePage

from config.config_read import config

from config.log_read import logger

import uiautomator2 as u2





# d = u2.connect()

# logger.info('设备连接成功')

# d.screen_on()

# logger.info('屏幕亮起')

# try:

#     d.app_start(config.read_package())

#     logger.info(f'成功启动:{config.read_config("Default", "package_name")}')

#     d.implicitly_wait(10)

# except Exception as e:

#     logger.critical(f'启动失败:{config.read_config("Default", "package_name")}')

#     exit()





class Search_Page(BasePage):

    '用于封装搜索页面的页面元素和主要业务'



    # 页面元素

    searchframe_lable = '在此输入搜索词'  # 一框搜索栏

    # searchframe_lable = "com.mapbar.navigation.zero:id/et_keyWord"   # 一框搜索栏

    search_button = "com.mapbar.navigation.zero:id/rl_showSearchView"  # 搜索按键

    back_button = "com.mapbar.navigation.zero:id/iv_searchViewBack"  # 返回按键

    list_result = 'com.mapbar.navigation.zero:id/ll_info'  # 搜索结果列表信息



    def search_frame(self, text):

        '''一框搜索页面操作'''



        logger.info(f'在一框搜索栏(searchframe_lable),元素属性为:text,输入:{text}')

        self.send_keys('text', self.searchframe_lable, text)

        sleep(2)

        # logger.info(f"等待了2秒")

        logger.info(f'点击搜索按键(search_button),元素属性为:resourceId')

        self.click('resourceId', self.search_button)

        logger.info(f'验证信息为:{self.list_result},元素属性为:resourceId')

        self.assert_text('resourceId', self.list_result)

        logger.info(f'点击返回按键(back_button),元素属性为:resourceId')

        self.click('resourceId', self.back_button)





if __name__ == '__main__':

    sp = Search_Page(d)

    sp.search_frame('美食')

  • testcase目录

tescase.py

# 创建者: zhangxuexin

# 创建时间: :2022/5/10  17:31

import pytest

import uiautomator2 as u2



from config.config_read import config

from config.log_read import logger

from page_object.mapmain_page import Map_Page

from page_object.search_page import Search_Page

from config.data_read import Data_Read



d = u2.connect()

logger.info('设备连接成功')

d.screen_on()

logger.info('屏幕亮起')

try:

    d.app_start(config.read_package())

    logger.info(f'成功启动:{config.read_config("Default", "package_name")}')

    d.implicitly_wait(10)

except Exception as e:

    logger.critical(f'启动失败:{config.read_config("Default", "package_name")}')

    exit()





class TestCase():

    " 该类用于设计测试用例 "



    # def setup_class(self):

    #     '''在所有的用例执行前执行一次'''



    def teardown_class(self):

        '''在所有的用例执行完成之后执行一次'''

        try:

            d.app_stop(config.read_package())

            logger.info(f'成功关闭:{config.read_config("Default", "package_name")}')

        except Exception as e:

            logger.error(f'关闭失败:{config.read_config("Default", "package_name")}')



    # 通过参数化测试搜索操作

    def test_01(self):

        '''跳转到一框搜画面用例'''

        mp = Map_Page(d)

        logger.info("用例开始:跳转到一框搜索画面")

        mp.search()

        logger.info("用例结束:跳转到一框搜索画面")



    # 使用Excle文件读取数据的方式进行参数化

    @pytest.mark.parametrize('data', Data_Read.excle_read(config.read_data_address(), 'Sheet1'))

    def test_02(self, data):

        '''进行一筐搜索'''

        SP = Search_Page(d)

        logger.info("用例开始:一框搜索操作")

        SP.search_frame(data)

        logger.info("用例结束:一框搜索操作")





if __name__ == '__main__':

    pytest.main(['testcase.py'])

  • config.ini

配置文件

#默认配置信息

[Default]

package = com.mapbar.navigation.zero

package_name = 导航零号

device_id = none

Overall_level = DEBUG

data_address = ..//data/address_data.xlsx



#打印log的配置信息,log的值有:'DEBUG','INFO','WARNING','ERROR','CRITICAL'

[loglevel]

consol_level = DEBUG

file_level = ERROR

  • pytest.ini

pytest运行参数设置文件

[pytest]

addopts = -s -v --html=..//report/report.html --capture=sys --reruns 0

testpaths = ./testcase

python_files = testcase*.py

python_classes = TestCase

python_functions = test_*

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/886464.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号