Selenium+pytest自动化框架搭建练习

说明

1.项目采用PO设计思想,一个页面一个Page.py,并在其中定义元素和操作方法;在TestCase中直接调用页面中封装好的操作方法操作页面。
2.使用python+selenium+pytest+allure进行编写
3.目录结构:
在这里插入图片描述
common: 存放通用方法文件夹

  • base.py 页面操作基础类封装
  • driver.py 浏览器驱动类封装
  • get_config.py 读取ini配置文件类封装
  • get_yaml.py 读取yaml数据类封装
  • Log.py log模块封装
  • send_email.py 发送邮件模块封装

config 存放配置文件夹

  • config.ini 存放全局配置数据
  • setting_path.py 存放文件路径

local_data 存放定位元素数据文件夹
Log 存放日志文件
page_obj 存放页面操作类

  • login_obj.py 封装登录界面的页面操作类

report 存放测试报告
screen_shop 存放截图
test_case 存放测试用例文件
test_data 存放测试用例数据文件

代码实现:

base负责封装页面的基础操作类,这里只填入了我当前页面的部分webdriver基础操作类,可以根据需要再进行添加;
base.py

from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from common.Log import Get_log
from config.setting_path import screen_path
from selenium.common.exceptions import TimeoutException
import time


# 公用的基础操作类
class BasePage(object):
    # 构造函数里面的参数就是类的所有参数
    def __init__(self,driver,url):
        self.driver = driver
        self.url = url
        self.log = Get_log()

    # 打开对应的网页
    def open_browser(self):
        self.driver.get(self.url)

    #查找元素,传入方法和元素两个参数
    def wait_element(self,ele):
        try:
            WebDriverWait(self.driver,5,0.5).until(EC.visibility_of_element_located(ele))
            return True
        except TimeoutException:
            self.log.log_error("没有定位到元素{0}".format(ele))
            return False
        except Exception as e:
            raise e

    def find_ele(self, ele):
        if self.wait_element(ele):
            try:
                return self.driver.find_element(*ele)
            except Exception as e:
                raise e
        else:
            self.log.log_error("没有定位到元素")
            return False
    # 点击定位的元素
    def click(self,div_click):
        self.find_ele(div_click).click()

    # 输入内容
    def send_key(self,div,data):
        self.find_ele(div).send_keys(data)

    # 获取定位元素文本内容
    def get_text(self,ele):
        return self.find_ele(ele).text

    # 获取当前网页url地址
    def get_curr_url(self):
        url = self.driver.current_url
        return url

    # 截图
    def get_screen_shot(self):
        current_time = time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime(time.time()))
        file_name = screen_path + '\\' + current_time + '.png'
        self.driver.get_screenshot_as_file(file_name)
        self.driver.save_screenshot(file_name)

driver.py

from selenium import webdriver

def driver():
    driver = webdriver.Chrome()
    driver.implicitly_wait(3)
    driver.maximize_window()
    return driver

get_config.py

import configparser
from config import setting_path

class Get_config():
    def __init__(self):
        self.path = setting_path.conf_path
        self.conf = configparser.ConfigParser()
        self.conf.read(self.path)

    #读取配置文件
    def read_config(self,option,section):
        data = self.conf.get(option,section)
        return data

    # 写入配置
    def write_config(self,section,option,value):
        self.conf.add_section(section)
        self.conf.set(section,option,value)
        # 修改文件后需要写入保存
        self.conf.write(open(self.path,"w"))

    def remove_config(self,section):
        self.conf.remove_section(section)
        # 修改文件后需要写入保存
        self.conf.write(open(self.path, "w"))

get_yaml.py

import yaml
from config.setting_path import case_data

class Get_yaml_data():
    def __init__(self,file_path):
        self.path = file_path

    # 读取yaml中所有数据,返回字典格式
    def get_all_data(self):
        with open(self.path,encoding="utf-8") as f:
            data = yaml.load(f,Loader=yaml.FullLoader)
            return data

    # 获取元素
    def get_element(self,i):
        data = self.get_all_data()
        return data["testcase"][i]["element_info"]

    # 定位元素的方法
    def find_type(self,i):
        return self.get_all_data()["testcase"][i]["find_type"]

    # 执行操作
    def operate_type(self,i):
        return self.get_all_data()["testcase"][i]["operate_type"]

log.py

import logging,os,time
from config.setting_path import log_path


class Get_log():
    def __init__(self):
        # 创建一个记录器
        self.log = logging.getLogger()
        # 设置记录器记录的BUG等级
        self.log.setLevel(level=logging.INFO)
        # 定义一个格式化输出的格式
        self.formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
        # log保存的路径及格式,每运行一次就根据时间创建一个log文件,会产生较多log文件
        # self.log_path = os.path.join(log_path,'%s.log'%time.strftime('%Y-%m-%d %H_%M_%S'))
        # 写死log文件名,直接在原本的基础上添加新的log数据
        self.log_path = os.path.join(log_path,"log_result.log")

    # 私有化,外部无法调用
    def __get_log(self,level,message):
        #创建一个handler,处理器,有FileHandler和StremaHandler,分别输出log到文件或者控制台
        fh = logging.FileHandler(self.log_path,encoding="utf-8")
        #设置handler的输出等级
        fh.setLevel(logging.INFO)
        #设置handler的输出格式
        fh.setFormatter(self.formatter)
        #把handler添加到记录器
        self.log.addHandler(fh)

        #创建一个输出log信息到控制台的StreamHandler
        sh = logging.StreamHandler()
        sh.setLevel(logging.INFO)
        sh.setFormatter(self.formatter)
        # 把这个处理器也添加到记录器中,一个记录器可以存在多个处理器,这样log文件记录的同时屏幕也会输出log信息
        self.log.addHandler(sh)

        #判断并输出对应的log信息
        if level == "debug":
            self.log.debug(message)
        elif level == "info":
            self.log.info(message)
        elif level == "warning":
            self.log.warning(message)
        elif level == "error":
            self.log.error(message)
        elif level == "critical":
            self.log.critical(message)

        # 调用后需要移除处理器,否则会导致执行多个用例后输出重复的问题
        self.log.removeHandler(sh)
        self.log.removeHandler(fh)
        #关闭log文件
        fh.close()

    # 创建对应的log输出函数
    def log_debug(self,message):
        self.__get_log("debug",message)

    def log_info(self,message):
        self.__get_log("info",message)

    def log_warning(self,message):
        self.__get_log("warning",message)

    def log_error(self,message):
        self.__get_log("error",message)

    def log_critical(self,message):
        self.__get_log("critical",message)

email模块我实际上没有去使用这里就不放了;
下面是元素定位文件的数据格式
Login_local_data.yaml

testinfo:
      - id: test_login001
        title: 小米帐号 - 登录
        info: 打开抽屉首页
testcase:
      - element_info: username
        find_type: ID
        operate_type: send_keys
        info: 账号输入框
      - element_info: pwd
        find_type: ID
        operate_type: send_keys
        info: 密码输入框
      - element_info: login-button
        find_type: ID
        operate_type: click
        info: 登录按钮
      - element_info: error-con
        find_type: class
        operate_type: text
        info: 密码错误文本内容
      - element_info: na-num
        find_type: class
        operate_type: text
        info: 登录成功后个人账户信息
      - element_info: logoutLink
        find_type: ID
        operate_type: click
        info: 退出登录按钮

登录页面操作类的封装,每一个页面要封装一个页面操作类,此处是登录页面所以封装一个登录页面的操作
login_obj.py

from selenium.webdriver.common.by import By
from common.base import BasePage
from selenium import webdriver
from config.setting_path import yaml_local_path
from common.get_yaml import Get_yaml_data

file_path = yaml_local_path + "/" + "Login_local_data.yaml"
# 定位元素方法库
Testdata = Get_yaml_data(file_path)

# 登录操作的类
class LoginPage(BasePage):
    # 账号
    account_box = (By.ID, Testdata.get_element(0))
    # 密码
    password_box = (By.ID, Testdata.get_element(1))
    # 登录按钮
    click_btn = (By.ID, Testdata.get_element(2))
    # 密码错误文本内容
    error_pwd = (By.CLASS_NAME,Testdata.get_element(3))
    # 登录成功后个人账户
    account_num = (By.CLASS_NAME,Testdata.get_element(4))
    # 退出登录按钮
    logout_btn = (By.ID,Testdata.get_element(5))

    # 打开浏览器
    def login_index(self):
        self.open_browser()
    # 输入账号
    def input_account(self,account):
        self.send_key(self.account_box,account)
    # 输入密码
    def input_pwd(self,password):
        self.send_key(self.password_box,password)
    # 点击登录按钮
    def click_login_btn(self):
        self.click(self.click_btn)
    #检查提示信息
    def check_error_text(self):
        return self.get_text(self.error_pwd)
    # 获取账号信息
    def get_account_num(self):
        return self.get_text(self.account_num)
    # 退出登录
    def click_logout_btn(self):
        self.click(self.logout_btn)

用例数据,使用数据驱动的方式去执行用例;
Login_data.yaml

-
  id: login_01
  detail : 正确登录
  status : login_success
  data:
    account: "username"
    pwd: "password"
  check :
     - "user_number"
-
  id: login_02
  detail : 输入错误的账号
  status : login_fail
  data:
    account: "123456"
    pwd: "xxxx"
  check :
     - 用户名或密码不正确
-
  id: login_03
  detail : 账号输入为空
  status : login_fail
  data:
    account: ""
    pwd: "123456"
  check :
     - 请输入帐号
-
  id: login_04
  detail : 密码输入为空
  status : login_fail
  data:
    account: "123456"
    pwd: ""
  check :
     - 请输入密码

登录测试用例
test_login.py

import pytest,sys,allure
# 把项目运行目录添加到path中,不加的话在terminal上运行会报错
sys.path.append('../')
from common.get_yaml import Get_yaml_data
from config.setting_path import case_data
from common.Log import Get_log

login_case_path = case_data + "/" + "Login_data.yaml"
login_case = Get_yaml_data(login_case_path).get_all_data()

# 登录用例的封装
@allure.feature("测试用例运行")
class Test_CaseRun():
    # 测试步骤
    @allure.story("测试登录")
    @allure.title("登录用例")
    @pytest.mark.parametrize("case_data",login_case)
    def test_search(self,case_data,open_baidu):
        log = Get_log()
        # 打开登录页面
        open_baidu.login_index()
        log.log_info("执行用例:{0}".format(case_data["id"]))
        if case_data["status"] == "login_success":
            open_baidu.input_account(case_data["data"]["account"])
            open_baidu.input_pwd(case_data["data"]["pwd"])
            open_baidu.click_login_btn()
            account_num = open_baidu.get_account_num()
            try:
                assert account_num == case_data["check"][0]
                log.log_info("登录成功")
                log.log_info("用例: {0}成功运行".format(case_data["id"]))
                # 退出登录状态
                open_baidu.click_logout_btn()
            except Exception as e:
                log.log_error("登录失败,原因{0}".format(e))
                log.log_error("用例: {0}运行失败".format(case_data["id"]))
                open_baidu.get_screen_shot()
                raise e
        else:
            open_baidu.input_account(case_data["data"]["account"])
            open_baidu.input_pwd(case_data["data"]["pwd"])
            open_baidu.click_login_btn()
            # 获取登录失败时的提示文本信息
            error_text = open_baidu.check_error_text()
            try:
                assert error_text == case_data["check"][0]
                log.log_info("断言成功")
                log.log_info("用例: {0}成功运行".format(case_data["id"]))
            except Exception as e:
                log.log_error("{0}与{1}不一致".format(error_text,case_data["check"][0]))
                log.log_error("用例: {0}运行失败".format(case_data["id"]))
                # 断言失败时进行截图操作
                open_baidu.get_screen_shot()
                raise e

使用pytest去运行用例并生成allure报告

pytest -s --alluredir ../report/tmp/              #生成allure  json文件
allure generate ../report/tmp -o ../report/allure_result --clean      #生成html报告文件

执行用例完成并生成测试报告完成后可以打开html文件查看结果
在这里插入图片描述
在这里插入图片描述

此项目代码仅供学习参考,整体项目框架完善度不高,仅供个人学习记录使用;

项目源码地址: https://gitee.com/HZongJie/Auto_UI.git

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值