Python+selenium+unittest框架实现网易邮箱的自动登录

概要

  本实例只针对简单的账密登录场景做处理,涉及登录方式切换、人机检测部分未作处理,后续会跟进处理。

整体架构流程

  • 依赖环境

        Python3.7

        selenium 3.141.0

        ddt 1.6.0

        

  • 目录结构

    

  目录说明

1. base: 公共基础方法,定位元素
2. business: 业务层,处理具体一件事情,如登录
3. case: 用例
4. config: 全局配置文件,主要配置项目 url 及页面元素定位信息
5. handle: 处理具体一件事情的某个步骤,比如输入用户名,输入验证码等
6. log: 日志文件夹
7. page: 定位具体页面的元素
8. report: 报告路径
9. screenCapture: 屏幕截图
10. util: 工具类方法,比如读取配置,获取验证码等
  • 调用关系
case->business->handle->page
  • 代码构建

1、创建projectConfig.ini项目配置文件

test/config/projectConfig.ini

[Project]
loginUrl = https://mail.163.com
timeout=5
username = username
password = password

2、创建globalElConfig.ini全局元素配置文件

test/config/globalElConfig.ini

[Login]
Tips=xpath:/html/body/header/div[1]/ul[1]/li[1]/div/span[1]
search_iframe=xpath://iframe[starts-with(@id,"x-URS")]
search_iframe2=xpath://iframe[starts-with(@id,"getMarkedContacts")]
option=xpath:lbApp
username=xpath://input[@name="email"]
password=xpath://input[@name="password"]
login_btn=xpath://*[@id="dologin"]

3、创建配置读取公共类

test/config/read_ini.py

# coding=utf-8

import configparser



class ReadIni(object):

    def __init__(self, node, file_name, encoding='utf-8'):
        self.node = node
        self.encoding = encoding
        self.cf = self.load_ini(file_name)

    def load_ini(self, file_name):
        cf = configparser.ConfigParser()
        cf.read(file_name, encoding=self.encoding)
        return cf

    def get_value(self, key):
        return self.cf.get(self.node, key)



4、创建Log日志类,记录执行日志、错误日志

test/log/user_log.py

import logging
import os
import datetime


class UserLog():
    def __init__(self):
        self.logger = logging.getLogger()
        self.logger.setLevel(logging.INFO)
        self.fileStream = logging.FileHandler(self.__get_log_name(),encoding='utf-8')
        self.__init_logger_handle()

    def __get_log_name(self):
        log_path = os.path.join(os.path.dirname(
            os.path.abspath(__file__)), "logs")
        log_file = datetime.datetime.now().strftime("%Y-%m-%d")+".log"
        return log_path+"/"+log_file

    def __init_logger_handle(self):
        formatter = logging.Formatter(
            '%(asctime)s %(filename)s %(funcName)s %(lineno)s %(levelname)s --->%(message)s')
        self.fileStream.setFormatter(formatter)
        self.logger.addHandler(self.fileStream)

    def close(self):
        self.fileStream.close()
        self.logger.removeHandler(self.fileStream)

    def get_logger(self):
        return self.logger


if __name__ == "__main__":
    log = UserLog()
    logger = log.get_logger()
    logger.info("打一年工搬一年砖")
    log.close()

5、创建元素查找公共类

test/base/find_element.py

# encoding=utf-8
from config.read_ini import ReadIni
import os
from selenium.webdriver.common.by import By
import selenium.webdriver.support.expected_conditions as EC
import selenium.webdriver.support.ui as ui


class FindElement(object):

    def __init__(self, driver, node,
                 config=os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'config',
                                     'globalElConfig.ini')):
        self.driver = driver
        self.read_ini = ReadIni(node, config)
        self.read_project = ReadIni('Project',
                                    os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'config',
                                                 'projectConfig.ini'))
        self.config = config

    def get_element(self, key):
        timeout = self.read_project.get_value('timeout')
        webDriverWait = ui.WebDriverWait(self.driver, int(timeout))
        try:
            by, value = self.get_locator(key)
            if not by or not value:
                print(f"Failed to get locator for key {key}")
                return None
            print(f"Finding element by {by} with value {value}")
            if by == 'id':
                return webDriverWait.until(EC.presence_of_element_located((By.ID, value)))
            elif by == 'name':
                return webDriverWait.until(EC.presence_of_element_located((By.NAME, value)))
            elif by == 'classname':
                return webDriverWait.until(EC.presence_of_element_located((By.CLASS_NAME, value)))
            elif by == 'cssSelector':
                return webDriverWait.until(EC.presence_of_element_located((By.CSS_SELECTOR, value)))
            else:
                return webDriverWait.until(EC.presence_of_element_located((By.XPATH, value)))
        except Exception as e:
            print(f"Exception occurred while finding element: {e}")
            return None

    def get_elements(self, key):
        timeout = self.read_project.get_value('timeout')
        webDriverWait = ui.WebDriverWait(self.driver, int(timeout))
        try:
            by, value = self.get_locator(key)
            if not by or not value:
                print(f"Failed to get locator for key {key}")
                return None
            print(f"Finding elements by {by} with value {value}")
            if by == 'id':
                return webDriverWait.until(EC.visibility_of_all_elements_located((By.ID, value)))
            elif by == 'name':
                return webDriverWait.until(EC.visibility_of_all_elements_located((By.NAME, value)))
            elif by == 'classname':
                return webDriverWait.until(EC.visibility_of_all_elements_located((By.CLASS_NAME, value)))
            elif by == 'cssSelector':
                return webDriverWait.until(EC.visibility_of_all_elements_located((By.CSS_SELECTOR, value)))
            else:
                return webDriverWait.until(EC.visibility_of_all_elements_located((By.XPATH, value)))
        except Exception as e:
            print(f"Exception occurred while finding elements: {e}")
            return None

6、创建webdriver工具类

test/util/webdriver.py

import json
import sys

from selenium import webdriver
from selenium.webdriver import DesiredCapabilities


class myWebdriver(object):
    @staticmethod
    def get_driver():
        chrome_options = webdriver.ChromeOptions()  # 创建 Chrome 浏览器选项
        chrome_options.add_experimental_option('w3c', False)  # 设置 w3c 实验性选项为 False,确保浏览器兼容
        caps = DesiredCapabilities.CHROME  # 设置浏览器能力 (caps):
        caps['loggingPrefs'] = {'performance': 'ALL'}  # 设置了 loggingPrefs,以便收集所有性能相关的日志
        chrome_options.add_argument('--disable-gpu')  # 添加禁用 GPU 的选项
        if 'linux' in sys.platform:  # 根据操作系统设置无头模式
            chrome_options.add_argument('--headless')
        driver = webdriver.Chrome(desired_capabilities=caps, options=chrome_options)  # 创建 Chrome 浏览器驱动
        driver.implicitly_wait(10)  # 设置隐式等待时间:10s
        driver.set_window_size(1936,
                               1056) if 'linux' in sys.platform else driver.maximize_window()  # 最大化窗口在--headless模式下无效,导致linux截图太小,所以手动设置
        return driver

    @staticmethod
    def save_performance_log(driver, filename):
        logs = [json.loads(log['message'])['message'] for log in driver.get_log("performance")]
        with open(filename, 'a') as f:
            json.dump(logs, f, indent=4, ensure_ascii=False)

7、创建页面page元素获取类,处理具体一件事情的某个步骤

test/page/login_page.py

# coding=utf-8
from base.find_element import FindElement
import os


class LoginPage(object):
    #  获取globalElConfig.ini配置Login节点下配置
    def __init__(self, driver):
        self.fd = FindElement(driver, 'Login')

    # 初始化iframe框架,默认嵌套一层
    def get_iframe_element(self):
        return self.fd.get_element("search_iframe")

    #  多个iframe框架切换
    def get_iframe2_element(self):
        return self.fd.get_element("search_iframe2")

    #  获取断言结果元素
    def get_err_tips_elements(self):
        return self.fd.get_elements("errTips")

    #  获取账密登录按钮
    def get_option_element(self):
        return self.fd.get_element("option")

    # 获取用户名元素
    def get_username_element(self):
        return self.fd.get_element("username")

    # 获取密码元素
    def get_password_element(self):
        return self.fd.get_element("password")

    # 获取登录按钮元素
    def get_login_btn_element(self):
        return self.fd.get_element("login_btn")

9、创建handle应用类,处理

test/handle/login_handle.py

from handle.handle import Handle
from page.login_page import LoginPage


class LoginHandle(Handle):
    def __init__(self, driver):
        super().__init__(driver)
        self.login_p = LoginPage(driver)

    # iframe框架调用
    def into_iframe_work(self):
        frame = self.login_p.get_iframe_element()
        self.driver.switch_to.frame(frame)

    def quit_iframe_work(self):
        self.driver.switch_to.default_content()

    # 输入用户名
    def send_user_name(self, username):
        username_element = self.login_p.get_username_element()
        username_element.send_keys(username)

    # 输入密码
    def send_user_password(self, password):
        password_element = self.login_p.get_password_element()
        password_element.send_keys(password)

    # 点击登录按钮
    def click_login_btn(self):
        self.login_p.get_login_btn_element().click()

    #  截取断言文本
    def get_tips(self):
        tips = self.login_p.get_err_tips_elements()
        if tips is None:
            print('元素未找到!')
            return None
        if len(tips) > 1:
            print('出现多条提示!')
        tip = tips[-1].text
        print(tip)
        return tip

10、创建business业务类,处理具体一件事情

test/business/login_business.py

# coding=utf-8
from handle.login_handle import LoginHandle


class LoginBusiness(object):
    def __init__(self, driver):
        self.login_h = LoginHandle(driver)

    def click_option_success(self, username, password):
        self.login_h.into_iframe_work()  # 进入iframe框架
        self.login_h.send_user_name(username)  # 输入用户名
        self.login_h.send_user_password(password)  # 输入密码
        self.login_h.click_login_btn()  # 点击登录按钮
        self.login_h.quit_iframe_work()  # 退出iframe框架,因为断言元素不在框架内
        return self.login_h.get_tips()  # 获取断言结果返回

11、创建TestCase类,初始化浏览器,利用ddt构建测试数据集,Unittest框架执行单元测试用例

test/case/login_case.py

# coding=utf-8
import os
import unittest
import datetime
import ddt
from log.user_log import UserLog
from business.login_business import LoginBusiness
from config.read_ini import ReadIni
from util.webdriver import myWebdriver


@ddt.ddt
class LoginCass(unittest.TestCase):
    @classmethod
    def setUpClass(cls):  # 读取配置节点
        cls.driver = myWebdriver.get_driver()
        cls.log = UserLog()
        cls.logger = cls.log.get_logger()
        cls.readIni = ReadIni('Project',
                              os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'config',
                                           'projectConfig.ini'))
        cls.loginUrl = cls.readIni.get_value('loginUrl')

    def setUp(self):
        self.driver.get(self.loginUrl)
        self.login_b = LoginBusiness(self.driver)

    def tearDown(self):
        for method_name, error in self._outcome.errors:
            if error:
                # case_name=self._testMethodName
                self.logger.info(error)
                case_name = str(method_name)[:str(method_name).find("(")]
                file_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'screenCapture',
                                         case_name + '.png')
                self.driver.save_screenshot(file_path)
        log_file = 'login_case_' + datetime.datetime.now().strftime("%Y-%m-%d") + ".log"
        myWebdriver.save_performance_log(self.driver,
                                         os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
                                                      'log', 'performancelog', log_file))

    @classmethod
    def tearDownClass(cls):
        cls.driver.close()
        cls.log.close()
 #  通过usernmae@163.com用户名断言判断是否登录成功
    @ddt.data(['username', 'password', 'username@163.com'])
    @ddt.unpack
    def test_1_login_case(self, username, password, tips):
        actual_tips = self.login_b.click_option_success(username, password)
        self.assertEqual(actual_tips, tips)

12、创建HTMLTestRunner测试报告类,记录测试过程,保存测试结果test/util/HTMLTestRunner.py

HTMLTestRunner最新内容见:

http://tungwaiyip.info/software/HTMLTestRunner.html

13、创建Test_main测试套件,用于批量执行测试用例,生成测试报告

test/case/test_main.py

import os
import unittest
from util.HTMLTestRunner import HTMLTestRunner

if __name__ == "__main__":
    # unittest.main()
    report_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'report', 'result.html')
    with open(report_path, 'wb') as f:
        discover = unittest.defaultTestLoader.discover('./', pattern="*_case.py")  # 批量执行case用例
        #suite = unittest.TestLoader().loadTestsFromTestCase(LoginCass)
        runner = HTMLTestRunner(
            stream=f, title="web自动化测试报告", description=u"网易邮箱", verbosity=2)
        runner.run(discover)

执行Test_main查看Result结果:

技术细节

  • driver.switch_to.frame()  #iframe框架切换

 driver.switch_to.frame() 方法是 Selenium 中用于切换到特定 iframe 或 frame 的方法。在网页自动化测试中,很多网页可能包含 iframe(内嵌框架),这些框架是嵌套在主页面中的独立 HTML 文档。要在这些 iframe 中进行操作,需要先切换到对应的 iframe。

语法:

//进入iframe框架     driver.switch_to.frame(reference)

//退出iframe框架     driver.switch_to.default_content()

参数:

reference 可以是以下三种之一:

  1. 索引(index):从 0 开始的整数,表示要切换到的 iframe 在页面上的顺序。
  2. 名字或 ID(name or id):iframe 的 nameid 属性的值。
  3. WebElement 对象:已经找到的 iframe 元素对象。

  • 分割定位器字符串,并提取定位器类型和值
# 分割定位器字符串
arr = value.split(':', 1)  # 只分割一次
if len(arr) != 2:
    print(f"Invalid locator format for key {key}: {value}")
    return None, None

  • 将读取到的值按照冒号 : 分割成两个部分,split(':', 1) 表示只分割一次。
  • 如果分割后的结果不是两个部分,则说明格式不正确,打印提示信息并返回 None, None
# 提取定位器类型和值
by, locator_value = arr[0], arr[1]
return by, locator_value
  • 将分割后的第一个部分赋值给 by,表示定位器类型(如 id, name, xpath 等)。
  • 将分割后的第二个部分赋值给 locator_value,表示定位器值。
  • 返回定位器类型和定位器值。

小结

       

UnitTest 是 Python 中的一个单元测试框架,用于编写和运行测试用例。它提供了一个结构化的方法来组织测试代码,并能够自动化执行测试并生成结果报告。

主要特点和用法:

  1. 测试用例(Test Case)

    • 测试用例是 unittest 的核心概念,通常继承自 unittest.TestCase 类。
    • 每个测试用例是一个独立的测试单元,用于验证被测试代码的某个方面或功能是否正确。
  2. 断言方法(Assertions)

    • unittest 提供了多种断言方法,用于检查预期结果与实际结果是否一致,例如 assertEqual(), assertTrue(), assertIn() 等。
    • 当断言失败时,会抛出 AssertionError 异常,标记测试失败。
  3. 测试装置(Test Fixture)

    • unittest 支持设置测试装置,包括 setUp()tearDown() 方法。
    • setUp() 方法在每个测试方法运行前执行,用于准备测试环境。
    • tearDown() 方法在每个测试方法运行后执行,用于清理测试环境。
  4. 测试套件(Test Suite)

    • 测试套件是一组相关的测试用例的集合,可以用来组织和运行多个测试。
    • 可以使用 unittest.TestSuite() 创建测试套件,并将测试用例添加到套件中。
  5. 运行器(Test Runner)

    • unittest 提供了命令行接口和 GUI 工具来运行测试用例。
    • 常用的运行器包括 unittest.TextTestRunner()(文本输出)、unittest.TextTestResult(详细文本输出)、unittest.XMLTestRunner(XML 格式输出)等。
  6. 跳过测试和预期异常

    • 可以使用装饰器 @unittest.skip(reason) 跳过某个测试用例。
    • 使用 @unittest.expectedFailure 装饰器标记预期会失败的测试用例。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

打一年工搬一年砖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值