ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

基于PageObject模式设计的web自动化测试示例

2021-10-17 12:29:59  阅读:134  来源: 互联网

标签:web log do 示例 loc self img PageObject login


PageObject模式

PageObject模式:顾名思义,就是页面对象。它的核心思想是分层设计, 强调测试、逻辑、数据和驱动相互分离。一般分层会分为:
1.对象库层
2.逻辑层
3.业务层
4.数据层
但是,具体分层,还是要根据系统去设计。

目录结构

下面,是基于PageObject模式,设计课堂派的登录测试。先说一下目录结构:
在这里插入图片描述

  • Common:存放公共封装类,公共配置文件。
  • Outputs:存放输出,日志、截图、测试报告等。
  • PageLocators:存放各个界面的定位。
  • PageObjects:存放各个界面的方法。
  • TestCases:存放测试用例。
  • TestDatas:存放测试数据。
  • main.py:用例执行入口文件。
    下面根据目录顺序,说明具体内容:

Common目录:

在这里插入图片描述

  • basepage.py,主要是公共方法的封装,比如元素等待、查找、点击等,并捕获异常,输出日志、截图信息等。具体代码如下:
import os
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from datetime import datetime
from Common.handle_logging import do_log

from Common.dir_config import screenshot_dir

"""目标:封装基本关键字,公共使用方法,对任何一个页面操作都可以实时捕捉异常,输出操作日志,失败截图"""


class BasePage:

    def __init__(self, driver: WebDriver):
        self.driver = driver

	# 等待元素可见方法封装
    def wait_element_visible(self, loc, img_doc, timeout=20, frequency=0.5):
        do_log.info("在{}等待元素{}可见".format(img_doc, loc))
        start_time = datetime.now()
        try:
            WebDriverWait(self.driver, timeout, frequency).until(EC.visibility_of_element_located(loc))
        except Exception as e:
            # 要异常截图 - 通过截图名称,知道是那个页面,那个模块失败的。
            self.save_screenshot(img_doc)
            # 异常日志捕获
            do_log.error("等待元素{}可见失败。".format(loc))
            # 抛出异常
            raise e
        else:
            do_log.info("等待元素{}可见成功。".format(loc))
            end_time = datetime.now()
            do_log.info("等待时长为:{}".format((end_time-start_time).seconds))
            
	# 等待元素存在方法封装
    def wait_page_contains_element(self, loc, img_doc, timeout=20, frequency=0.5):
        do_log.info("在{}等待元素{}存在。".format(img_doc, loc))
        start_time = datetime.now()
        try:
            WebDriverWait(self.driver, timeout, frequency).until(EC.presence_of_element_located(loc))
        except Exception as e:
            self.save_screenshot(img_doc)
            do_log.error("等待元素{}存在失败。".format(loc))
            raise e
        else:
            do_log.info("等待元素{}存在成功。".format(loc))
            end_time = datetime.now()
            do_log.info("等待的时长为:{}".format((end_time-start_time).seconds))

	# 获取元素方法封装
    def get_element(self, loc, img_doc):
        """
        查找元素。
        loc:元素定位
        img_doc: 图片描述
        """
        do_log.info("在{}查找元素{}".format(img_doc, loc))
        start_time = datetime.now()
        try:
            ele = self.driver.find_element(*loc)
        except Exception as e:
            self.driver.save_screenshot(img_doc)
            do_log.error("查找元素{}失败。".format(loc))
            raise e
        else:
            do_log.error("查找元素{}成功。".format(loc))
            end_time = datetime.now()
            do_log.info("查找元素的时长为:{}".format((end_time - start_time).seconds))
            return ele

	# 点击元素方法封装
    def click_element(self, loc, img_doc, timeout=20, frequency=0.5):
        """
        前提:元素可见,找到元素
        """
        self.wait_element_visible(loc, img_doc, timeout, frequency)
        ele = self.get_element(loc, img_doc)
        do_log.info("在{}点击元素{}".format(img_doc, loc))
        try:
            ele.click()
            do_log.info("元素{}点击成功。".format(loc))
        except Exception as e:
            self.save_screenshot(img_doc)
            do_log.error("元素{}点击失败。".format(loc))
            raise e

	# 输入内容方法封装
    def input_text(self, loc, img_doc, text, timeout=20, frequency=0.5):
        """
        前提:元素可见,找到元素
        """
        self.wait_element_visible(loc, img_doc, timeout, frequency)
        ele = self.get_element(loc, img_doc)
        do_log.info("在{}的输入框{},输入:{}".format(img_doc, loc, text))
        try:
            ele.send_keys(text)
            do_log.info("元素{}输入内容{}成功".format(loc, text))
        except Exception as e:
            self.save_screenshot(img_doc)
            do_log.error("元素{}输入文本失败。".format(loc))
            raise e

	# 获取元素的文本内容方法封装
    def get_element_text(self, loc, img_doc, timeout=20, frequency=0.5):
        """
        前提:元素存在,找到元素
        """
        self.wait_page_contains_element(loc, img_doc, timeout, frequency)
        ele = self.get_element(loc, img_doc)
        do_log.info("在{}的获取元素{}的文本值".format(img_doc, loc))
        try:
            text = ele.text
        except Exception as e:
            self.save_screenshot(img_doc)
            do_log.error("获取文本值失败。")
            raise e
        else:
            do_log.info("获取元素{}文本内容成功,获取到的文本元素内容为:{}".format(loc, text))
            return text

	# 获取元素属性的方法封装
    def get_element_attr(self, loc, attr_name, img_doc, timeout=20, frequency=0.5):
        """
        前提:元素存在,找到元素
        """
        self.wait_page_contains_element(loc, img_doc, timeout, frequency)
        ele = self.get_element(loc, img_doc)
        do_log.info("在{}获取元素{}的属性{}。".format(img_doc, loc, attr_name))
        try:
            value = ele.get_attribute(attr_name)
        except Exception as e:
            self.save_screenshot(img_doc)
            do_log.error("获取元素{}属性值失败。".format(loc))
            raise e
        else:
            do_log.info("获取到的元素{}属性值成功,属性值为:{}".format(loc, value))
            return value

	# 保存截图的方法
    def save_screenshot(self, img_doc):
        # 存储到指定目录下
        # filename = screenshot_dir + "{}_{}.png".format(datetime.strftime(datetime.now(), '%Y_%m_%d_%H_%M_%S'), img_doc)
        filename = os.path.join(screenshot_dir,
                                "{}_{}.png".format(datetime.strftime(datetime.now(), '%Y_%m_%d_%H_%M_%S'), img_doc))
        self.driver.save_screenshot(filename)
        do_log.info("页面截图文件,保存在{}".format(filename))

  • handle_logging.py,日志的封装,定义日志输出信息,主要代码如下:
import logging
import os
from datetime import datetime
from Common.common_conf import logger_name, log_filename, logger_level, console_level, simple_formatter, file_level, verbose_formatter
from Common.dir_config import logs_dir


class HandleLog:
    """
    封装日志处理的类
    """
    def __init__(self):

        # 定义日志收起器, 创建logger对象
        self.case_logger = logging.getLogger(logger_name)

        # 日志等级 NOTSET(0), DEBUG(10), INFO(20), WARNING(30), ERROR(40), CRITICAL(50)
        # 设置之后,只能收集当前等级及以上的日志信息。如设置为warning级别,只能手机warning、error、critical等级的
        # case_logger.setLevel(logging.DEBUG)
        self.case_logger.setLevel(logger_level)

        # 定义日志输出渠道
        # 输出到控制台
        console_handler = logging.StreamHandler(console_level)
        # 输出到文件
        print("log输出路径:{}".format(logs_dir))
        log_name = os.path.join(logs_dir, datetime.strftime(datetime.now(), '%Y_%m_%d_%H_%M_%S') + log_filename)

        file_handler = logging.FileHandler(log_name,
                                           encoding='utf-8')

        # 日志输出渠道的等级, 日志输出的等级,不能高于收集器的等级
        # console_handler.setLevel(logging.ERROR) 与 console_handler.setLevel("DEBUG")等价
        console_handler.setLevel(console_level)
        file_handler.setLevel(file_level)

        # 定义日志显示的格式
        # 简单格式
        simple = logging.Formatter(simple_formatter)
        # 稍微详细的格式
        verbose = logging.Formatter(verbose_formatter)

        # 控制台显示简单日志
        console_handler.setFormatter(simple)
        # 文件显示稍微详细的日志
        file_handler.setFormatter(verbose)

        # 将日志收集器与输出渠道对接
        self.case_logger.addHandler(console_handler)
        self.case_logger.addHandler(file_handler)

    def get_logger(self):
        return self.case_logger

# 创建对象,可以直接调用
do_log = HandleLog().get_logger()


if __name__ == "__main__":
    do_log.debug("debug")
  • common_conf.py,存放公共的配置信息,比如,我这里只配置了log的设置:
# 日志收集器名称
logger_name = "case_log"
# 日志收集器的级别
logger_level = "DEBUG"

# 输出的日志名称
log_filename = "cases.log"
# 输出到控制台的日志级别
console_level = "ERROR"
# 输出到日志文 件中的级别
file_level = "DEBUG"
# 日志输出内容
simple_formatter = "%(asctime)s - [%(levelname)s] - [msg]: %(message)s"
verbose_formatter = "%(asctime)s - [%(levelname)s] - %(lineno)d - %(name)s - [msg]: %(message)s"
  • dir_config.py,项目路径的配置文件:
import os


# 项目根目录
base_dir = os.path.split(os.path.split(os.path.abspath(__file__))[0])[0]
print(base_dir)
# 截图存放地址
screenshot_dir = os.path.join(base_dir, "Outputs\\screenshots")
# 日志存放地址
logs_dir = os.path.join(base_dir, "Outputs\\logs")
# 报告存放地址
reports_dir = os.path.join(base_dir, "Outputs\\reports")
# 测试用例存放地址
test_cases_dir = os.path.join(base_dir, "TestCases")

Outputs目录

在这里插入图片描述

  • logs存放日志文件
  • reports存放测试报告
  • screenshots存放错误截图

PageLocators

在这里插入图片描述

  • login_page_locators.py,登录界面的元素定位:
from selenium.webdriver.common.by import By


class LoginPageLocators:
    """
    账号登录的定位
    """
    # 账户输入框
    user_input_box = (By.XPATH, '//input[@class="el-input__inner" and @placeholder="请输入邮箱/手机号/账号"]')
    # 密码输入框
    password_input_box = (By.XPATH, '//input[@class="el-input__inner" and @placeholder="请输入密码"]')
    # 登录按钮
    login_button = (By.XPATH, '//div[@class="login-tab"]/div/div/button')

    # 不输入密码时的提示信息: 请输入密码 的定位
    not_input_password_err_msg = (By.XPATH, '//*[@class="el-form-item__error"]')

    # 不输入账户时弹出的提示信息:用户名不能为空 的定位
    user_is_null_msg = (By.XPATH, '//*[text()="用户名不能为空"]')

    # 输入不存在的用户时,弹出的提示信息:用户不存在 的定位
    user_not_exist_msg = (By.XPATH, '//*[text()="用户不存在"]')
  • main_page_locators.py, 主页元素定位:
from selenium.webdriver.common.by import By


class MainPageLocator:

    # 用户头像的元素定位
    user_logo = (By.XPATH, '//img[@class="avatar"]')

PageObjects

在这里插入图片描述

  • login_page.py,登录界面的操作方法:
from PageLocators.login_page_locators import LoginPageLocators
from Common.basepage import BasePage
from selenium.webdriver.common.keys import Keys


class LoginPage(BasePage):

    def account_login(self, username, password):
    	# 账户登录,登录方法
        # 输入用户名
        self.input_text(LoginPageLocators.user_input_box, "登录界面_账户登录_账户输入框", username)
        # 输入密码
        self.input_text(LoginPageLocators.password_input_box, "登录界面_账户登录_密码输入框", password)
        # 点击 登录按钮
        # self.click_element(LoginPageLocators.login_button, "登录界面_账户登录_登录按钮")
        # 点击enter键
        self.get_element(LoginPageLocators.login_button, "登录界面_账户登录_点击enter").send_keys(Keys.ENTER)

    def get_no_password_err_msg(self):
        """
        不输入密码时,会出现文本提示信息:请输入密码
        """
        text = self.get_element_text(LoginPageLocators.not_input_password_err_msg, "登录页_不输入密码_提示信息")
        # 返回错误提示信息
        return text

    def get_alert_err_msg(self, locator):
        """
        不输入用户名、密码错误、账户不存在时,点击登录,会弹出提示框提示,获取提示框中的错误信息
        """
        text = self.get_element_text(locator, "登录页_不输入用户名、密码错误、账户不存在_弹出错误信息")
        # 返回错误提示信息
        return text
  • main.py,主页中的操作方法:
from Common.basepage import BasePage
from PageLocators.main_page_locator import MainPageLocator


class MainPage(BasePage):

    def if_user_login_is_exist(self):
        """
        判断元素是否存在,存在返回True, 不存在返回False
        :return boolean
        """
        try:
            self.wait_element_visible(MainPageLocator.user_logo, "主页_用户头像元素")
        except TimeoutError:
            return False
        else:
            return True

TestCases

在这里插入图片描述

  • test_login.py,存放测试登录的用例(设计了4条用例):

import unittest
from selenium import webdriver
from PageLocators.login_page_locators import LoginPageLocators
from PageObjects.login_page import LoginPage
from PageObjects.main_page import MainPage
from TestDatas import common_datas as cd
from TestDatas import login_datas as ld
from Common.handle_logging import do_log


class TestLogin(unittest.TestCase):

    def setUp(self) -> None:
        do_log.info("打开浏览器")
        # 打开谷歌浏览器,访问课堂派
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
        self.driver.get(cd.login_url)
        self.lp = LoginPage(self.driver)

    def tearDown(self) -> None:
        do_log.info("退出浏览器")
        # 退出浏览器会话
        self.driver.quit()

    def test_login_success(self):
        """
        测试场景:登录成功
        """
        do_log.info("执行用例:test_login_success")
        self.lp.account_login(ld.normal_datas['user'], ld.normal_datas['passwd'])
        self.assertTrue(MainPage(self.driver).if_user_login_is_exist())

    def test_login_no_password(self):
        """
        测试场景:不输入密码
        """
        do_log.info("执行用例:test_login_no_password")
        self.lp.account_login(ld.login_no_password_datas['user'], ld.login_no_password_datas['passwd'])
        self.assertEqual(ld.login_no_password_datas['check'], self.lp.get_no_password_err_msg())

    def test_login_no_user(self):
        """
        测试场景:不输入账户
        """
        do_log.info("执行用例:test_login_no_user")
        self.lp.account_login(ld.login_no_user_datas['user'], ld.login_no_user_datas['passwd'])
        self.assertTrue(ld.login_no_user_datas['check'], self.lp.get_alert_err_msg(LoginPageLocators.user_is_null_msg))

    def test_login_user_not_exist(self):
        """
        测试场景:输入的账户不存在
        """
        do_log.info("执行用例:test_login_user_not_exist")
        self.lp.account_login(ld.login_user_not_exist['user'], ld.login_user_not_exist['passwd'])
        self.assertTrue(ld.login_user_not_exist['check'], self.lp.get_alert_err_msg(LoginPageLocators.user_not_exist_msg))

TestDatas

在这里插入图片描述

  • common_datas.py, 公共数据:
# 登录地址
login_url = '登录地址'

# 登录信息
test_datas = {"user": "账户信息", "passwd": "密码"}

# 正常场景的数据
normal_datas = {
    "user": "账户", "passwd": "密码"
}

# 异常场景的数据
login_no_password_datas = {
    "user": "账户", "passwd": "", "check": "请输入密码"
}

login_no_user_datas = {
    "user": "", "passwd": "密码", "check": "用户名不能为空"
}

login_user_not_exist = {
    "user": "不存在的账户", "passwd": "密码", "check": "用户不存在"
}

main.py

入口文件:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @Time : 2021/10/16 22:03
# @Author : admin
# @File : main.py
# @Software: PyCharm


import unittest
import os
from datetime import datetime
from HTMLTestRunner import HTMLTestRunner
from Common.dir_config import reports_dir, test_cases_dir


# 指定路径下查找用例, 可以输入. 代表当前目录
suit = unittest.defaultTestLoader.discover(test_cases_dir)

"""
以日期命名报告,避免覆盖
"""
str_now = datetime.strftime(datetime.now(), '%Y_%m_%d_%H_%M_%S')
print("report的输出路径:{}".format(reports_dir))
name = os.path.join(reports_dir, str_now +"_report.html")

report = open(name, mode='wb')
"""
verbosity 代表报告的详细程度, 0 report 2 , 2是最详细的
"""
runner = HTMLTestRunner(stream=report, title='课堂派的web的报告', verbosity=2, description='测试课堂派的web报告')
runner.run(suit)
report.close()

标签:web,log,do,示例,loc,self,img,PageObject,login
来源: https://blog.csdn.net/c_xiazai12345/article/details/120809109

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有