selenium框架相关的问题与总结

霍格沃兹总结心得
https://ceshiren.com/t/topic/20967


来源


1.selenium options类

  • 是一个配置浏览器启动的选项类,用于自定义和配置Driver会话。
  • 在创建 WebDriver实例时,可以配置它的启动参数以进行一些初始设置,这些设置将会在 WebDriver 的整个生命周期内生效对于不同类型的浏览器,WebDriver 传入的参数并不相同
  • 常见使用场景:
    • 设置无头模式:不会显示调用浏览器,避免人为干扰的问题。
      • options.add_argument('--headless')
    • 设置调试模式:调试自动化测试代码(浏览器复用)。
      • option.debugger_address = "localhost:9222"
  • 特别注意: 当使用了options选项之后,在创建driver的时候需要传入参数options=
from selenium import webdriver
from selenium.webdriver.common.by import By
# 注意:导包的时候不要导错,chrome.options和edge.options要分清楚
from selenium.webdriver.chrome.options import Options

# Edge浏览器选项
# from selenium.webdriver.edge.options import Options
# 火狐浏览器选项
# from selenium.webdriver.firefox.options import Options

# 获取浏览器配置的实例对象
option = Options()
# 设置窗口最大化
# option.add_argument("start-maximized")
# 指定浏览器分辨率
option.add_argument('window-size=777×777')
# 无头模式
# option.add_argument('--headless')
driver = webdriver.Chrome(options=option)
# 打开测试人页面
driver.get("https://ceshiren.com/")

2.capablility

  • Capabilities是WebDriver支持的标准命令之外的扩展命令(配置信息)
  • 配置web驱动的属性,如浏览器名称、浏览器平台等。
  • 结合Selenium Grid完成分布式、兼容性等测试

一、特殊场景

1.浏览器复用

1.1应用场景:

  • 存在人为介入场景:需要扫描登录
  • 提高调试UI自动化脚本效率

1.2复用步骤:
以goole为例子

  • 1、配置环境变量,保证命令行输入chrome命令可以正常启动浏览器;
    • 将Chrome浏览器的安装目录添加到环境变量;
  • 2、 关闭所有的chrome浏览器窗口及进程;
    • windows:查看任务管理器里的浏览器进程;
    • linux/mac:使⽤ps aux|grep chrome|grep -v 'grep’查看是否有chrome进程存在。确保没有chrome进程被启动过。
  • 3、重启命令行窗口,在命令行输入命令启动浏览器:
    • windows:chrome --remote-debugging-port=9222
    • mac:Google\ Chrome --remote-debugging-port=9222
  • 4、验证浏览器是否启动成功
    • 在打开的浏览器中输入localhost:9222只要不显示无法访问此网站就表示配置成功
  • 5、 代码中增加debug参数;
from selenium import webdriver
# 注意:导包的时候不要导错,chrome.options和edge.options要分清楚
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By

# Edge浏览器选项
# from selenium.webdriver.edge.options import Options
# 火狐浏览器选项
# from selenium.webdriver.firefox.options import Options

# 获取浏览器配置的实例对象
option = Options()
# 设置浏览器的debug模式和地址属性---值就是通过命令行启动浏览器时的localhost:端口
option.debugger_address = "localhost:9222"
# 创建浏览器驱动的时候需要把option对象传入
driver = webdriver.Chrome(options=option)
driver.maximize_window()

# 访问企业微信
driver.get("https://work.weixin.qq.com/wework_admin/frame")

2.Cookie复用

2.1.为什么要使用Cookie自动化登录

  • 复用浏览器仍然在每次用例开始都需要人为介入启动浏览器;
  • 若用例需要经常执行,复用浏览器则不是一个好的选择;
  • 大部分cookie的时效性都很长,扫一次登录获取cookie后可以使用多次;

2.2cookie复用流程

  • 1、编写脚本访问登录页面,设置强制等待时间,这个时间用于我们手动去登录;
  • 2、打开浏览器,扫码登录;
  • 3、确保登录之后,通过Python代码获取cookies,并确认是否获取成功;
    • driver.get_cookies()
  • 4、将获取到的cookie信息做持久化存储;
  • 5、Python代码中设置cookies,之后访问页面,后续的调试或者脚本运行就可以携带上这个cookie就行操作了;
    • driver.add_cookie(cookie)
  • 常见问题:
      1. 企业微信cookie有互踢机制。在获取cookie成功之后。不要再进行扫码操作!!!!
      1. 获取cookie的时候,即执行代码获取cookie时,一定要确保已经登录。
      1. 植入cookie的时候是给当前driver打开的地址设置cookie,所以在植入cookie之前需要先访问登录登录页面/刷新浏览器;

2.3案例

  • 需求:对企业微信通讯录页面进行UI自动化测试,使用cookie处理登录;
    • 地址:https://work.weixin.qq.com/wework_admin/frame#contacts
  • 特别说明:植入cookie的时候是给当前driver打开的地址设置cookie,所以在植入cookie之前需要先访问登录登录页面
import time
import pytest
import yaml
from selenium import webdriver

class TestWeWork:

    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()

    def teardown_class(self):
        self.driver.quit()

    # 给这条用例做个标记,这是专门用于获取cookies的用例,当cookies没有过期的时候就复用之前获取的cookie,那就通过标记跳过这条用例
    @pytest.mark.cookie
    def test_get_cookies(self):
        # 1、访问企业微信首页
        self.driver.get("https://work.weixin.qq.com/wework_admin/frame")
        # 2、设置等待时间,这个时间用于去页面上手动扫描成功登录
        time.sleep(20)
        # 3、在页面上确认成功登录之后,在这里获取登录后的cookie信息
        cookies = self.driver.get_cookies()
        # 4、打印cookies信息确保成功获取到cookies
        print(cookies)
        # 5、将获取的cookie做持久化存储
        with open("wework_cookies.yaml","w") as f:
            yaml.safe_dump(cookies,f)

    def test_add_cookie(self):
        # 6、植入cookie的时候是给当前driver打开的地址设置cookie,所以在植入cookie之前需要先访问登录登录页面--或者刷新浏览器
        # self.driver.refresh()
        self.driver.get("https://work.weixin.qq.com/wework_admin/frame")
        # 7、从文件中获取保持的cookies信息
        with open("wework_cookies.yaml",'r') as f:
            cookies = yaml.safe_load(f)
            print(cookies)
        # 8、设置cookie信息---从源码中发现add_cookie()就收的是字典,而cookies中是字典列表,所以需要遍历cookies将每个字典都添加进去
        for cookie in cookies:
            self.driver.add_cookie(cookie)
        # 9、访问目标页面,因为此时已经设置了cookie,已经不再需要扫描登录
        self.driver.get("https://work.weixin.qq.com/wework_admin/frame#contacts")
        time.sleep(5)

3.自动化关键数据记录

内容 作用
日志 1. 记录代码的执行记录,方便复现场景;2. 可以作为bug依据
截图 1. 断言失败或成功截图;2.异常截图达到丰富报告的作用;3. 可以作为bug依据
page source 1. 协助排查报错时元素当时是否存在页面上

3.1行为日志
脚本日志级别:

  • debug记录步骤信息
  • info记录关键信息,比如断言等

3.2异常截图

  • driver.save_screenshot(截图路径+名称)
    • 这个方法底层调用了get_screenshot_as_file(filename)方法;
  • 记录关键页面
    • 断言页面
    • 重要的业务场景页面
    • 容易出错的页面

部分元素截图
方式1:
优化方案:定义一个装饰器实现异常处理,在basepage的封装的查询UI元素的方法上使用该装饰器(也可以装饰测试用例)

import time
import allure
from selenium import webdriver
from selenium.webdriver.common.by import By

def ui_exception_recode(func):
    # 通用装饰器形式(*args,**kwargs),任何方法都可以被装饰
    def inner(*args,**kwargs):
        # 执行被装饰函数,如果函数有异常则捕获
        try:
            # 因为被装饰函数需要拿到元素之后进行操作,所以这里需要有返回值
            return func(*args,**kwargs)
        except Exception:
            # 因为在异常处理过程中需要用到driver,所以从传过来的参数中获取driver
            driver = args[0].driver

            # 捕获异常之后保留截图和页面源码
            # print("异常了。。。")
            # 拼接文件路径
            temp_time = time.strftime('%Y-%m-%d-%H-%M-%S', time.localtime())
            shotpath = "screenshot/search/" + temp_time + "百度首页截图.png"
            # 保存截图
            driver.save_screenshot(shotpath)
            # 将截图文件上传到allure报告
            allure.attach.file(
                # 文件路径
                source=shotpath,
                # allure上文件名称
                name="页面截图",
                # 文件类型
                attachment_type=allure.attachment_type.PNG,
                # 文件扩展名
                extension="png"
            )

            # 拼接文件路径
            sourcepath = "pagesource/search/" + temp_time + "百度首页源码.html"
            # 保存页面源码
            with open(sourcepath, 'w', encoding='utf-8') as f:
                f.write(driver.page_source)
            # 将源码文件上传到allure报告
            allure.attach.file(
                source=sourcepath,
                name="页面源码",
                attachment_type=allure.attachment_type.TEXT,
                extension="html"
            )

            # 为了不然异常处理影响用例的真实运行结果,需要再次把异常跑出去
            raise Exception
    return inner

class BasePage:
      # 定位单个元素
    @expected_ui_error
    def do_find(self,by,locator=None):
        if locator:
            return self.driver.find_element(by,locator)
        else: # 如果传的是元组
            return self.driver.find_element(*by)

方式2:
参考链接youyou 13537355.html
说明:pytest 有个很好的钩子函数 pytest_runtest_makereport 可以获取到用例执行的结果,所以我们在这个钩子函数里面判断用例失败后截图就可以了。

import pytest
from selenium import webdriver
import os
import allure

_driver = None

@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    '''
    获取每个用例状态的钩子函数
    :param item:
    :param call:
    :return:
    '''
    # 获取钩子方法的调用结果
    outcome = yield
    rep = outcome.get_result()
    # 仅仅获取用例call 执行结果是失败的情况, 不包含 setup/teardown
    if rep.when == "call" and rep.failed:
        mode = "a" if os.path.exists("failures") else "w"
        with open("failures", mode) as f:
            # let's also access a fixture for the fun of it
            if "tmpdir" in item.fixturenames:
                extra = " (%s)" % item.funcargs["tmpdir"]
            else:
                extra = ""
            f.write(rep.nodeid + extra + "\n")
        # 添加allure报告截图
        if hasattr(_driver, "get_screenshot_as_png"):
            with allure.step('添加失败截图...'):
                allure.attach(_driver.get_screenshot_as_png(), "失败截图", allure.attachment_type.PNG)


@pytest.fixture(scope='session')
def browser():
    global _driver
    if _driver is None:
        _driver =webdriver.Chrome()
    yield _driver
    print("1111111111")
    _driver.quit()

用例方法中参数写入browser引用

import pytest
from selenium import webdriver
import allure

def test_login(browser):
    with allure.step("step1:打开登录首页"):
        browser.get("http://ip:6009/admin/login/?next=/admin/")
    with allure.step("step2:输入账号:admin"):
        browser.find_element_by_name("username").send_keys("admin")
    with allure.step("step2:输入密码:123456"):
        browser.find_element_by_name("password").send_keys("123456")
    # 故意断言失败,看是否会截图
    assert browser.title == "悠悠"

3.3page_source页面源码

  • 使用driver.page_source属性获取页面源码,返回的是字符串;
  • 在调试过程中,如果有找不到元素的错误时可以保存当时页面的page_source调试代码;
# 获取页面源码
source = driver.page_source
print(f"页面源码:{source}")
# 把源码写入html文件
with open("pagesource/double.html","w",encoding="utf-8") as f:
    f.write(source)

4.多浏览器处理

5. 验证码问题

  • 1、自动化时关闭验证码
  • 2、测试时使用万能验证码
  • 3、使用成功登录后的cookies绕过登录
  • 4、使用图像识别技术处理简单图片验证码-- ddddocr模块
  • 5、使用selenium模拟鼠标事件处理拖拽等验证码

二、UI自动化框架总结小技巧

1.搜索功能步骤写法
可以把所有搜索条件都列入一个方法中,然后判断参数是否正常传递再决定是否去输入字符到该搜索框
例子:
页面实现方法

class SendOrderSearchPage(BasePage):
    __APPMAINFRAME = (By.ID,'appMainFrame')
    __SEARCHFCSENDPOLICY = (By.ID,'id_netstmrpacpadsearchFCSendPolicy')
    __DELIVERYID = (By.CSS_SELECTOR, '#deliveryId')  # 送单编号
    __CUSTNO = (By.CSS_SELECTOR, 'input#netsCustId.input_default')  # 客户编号
    __APPLICANTPHONENO = (By.CSS_SELECTOR, 'input#phoneNo.input_default')  # 投保人手机号
    __SEARCHBUTTON = (By.CSS_SELECTOR,"input[value='查 询']")  # 查询按钮

    def searchByApplicantPhoneNo(self,deliveryId=None,custNo=None,phoneNo=None):
        self.jump_out_of_all_frames()
        self.switch_new_frame(self.__APPMAINFRAME)
        self.switch_new_frame(self.__SEARCHFCSENDPOLICY)
        # 三个查询条件
        if deliveryId:  # 先判断是否正常传递了这个参数再去觉得是否输入这个搜索条件
            self.do_send_key(deliveryId,self.__DELIVERYID)
        if custNo:
            self.do_send_key(custNo,self.__CUSTNO)
        if phoneNo:
            self.do_send_key(phoneNo, self.__APPLICANTPHONENO)
        # 点击查询按钮
        self.do_find(self.__SEARCHBUTTON).click()
        logging.info('查询执行完毕')

用例

class TestSendOrder:
    @pytest.mark.parametrize('deliveryId,custNo,phoneNo',
                             [(None,None,'xx'),
                              ('100002',None,None)])
    def test_searchOrder(self,deliveryId,custNo,phoneNo,login_tmr):
        login_tmr.quickly_search('10112035349145')\
            .entryCustinfo_byID()\
            .intoServiceResultPage()\
            .dealServiceRessult()
        login_tmr.fc_orderSearch().searchByApplicantPhoneNo(deliveryId,custNo,phoneNo)

2.window切回原始窗口
在窗口1里跳转出来一个新窗口2,此时要在窗口2操作需要切换到窗口2才能操作。
当窗口2关闭,再需要操作窗口1,那么需要切换回去。(如果不切换找不到元素定位)

# 自己封装的,但是这个方法切不回原来的?
 def switch_window(self):
        current_window =self.driver.current_window_handle
        print(current_window)
        all_windows = self.driver.window_handles
        print(all_windows[-1])
        self.driver.switch_to.window(all_windows[-1])
 def dealServiceRessult(self):
        # 切换窗口,自己封装的
        self.switch_window()  
        self.jump_out_of_all_frames()
        # 点击咨询险种,等待1秒是为了等待子险种加载出来
        self.do_find(self.__FAMILYWEALTHINSURANCE).click()
        time.sleep(1)
        # 保存按钮
        self.do_find(self.__SAVEBUTTON).click()
        # 切换回原来的窗口
        windws_list = self.driver.window_handles
        self.driver.switch_to.window(windws_list[0])   # 不能用

3.conftest.py的login_tmr在用例中的调用

# conftest.py
test_user_data = [{'user': 'xx', 'pwd': 'xx'}]

@pytest.fixture(autouse=True,scope='session',params=test_user_data)
def login_tmr(request):
    user = request.param['user']
    pwd = request.param['pwd']
    yield  LoginPage().login(user, pwd)

用例中调用

class TestFcKeepFresh:

    def test_productKeepFresh(self,login_tmr ):
        # 1.首先是进入客户信息页面
        cust_page = login_tmr.quickly_search(cust_id='1000452573063').entryCustinfo_byID()
        # 2.客户信息页面找到保鲜按钮,对客户电话进行保鲜
        tel = cust_page.switch_to_jfKeepFresh().productlistCustPhone_update()
        logging.info(f'更新的电话是{tel}')
        # 3.最后返回到客户信息汇总页面查看客户电话是否被成功更新
        landPhone = login_tmr.quickly_search(cust_id='1000452573063').entryCustinfo_byID().get_custInfo()['landPhone']
        logging.info(f'固定电话是{landPhone}')

说明:
以上代码中第3步之前我只是保鲜操作后回到原信息汇总页面去查看客户电话,但是此时页面没有刷新,不会获取到更新后的电话。
所以解决办法是-》此时我再次使用fixture函数login_tmr去进行查询客户并重新进入到客户信息页。注意我在一个用例中使用login_tmr了两次,但是并没登录两次
原因:

  • 因为login_tmr是session级别
  • 且属于setup方法,是在用例之前运行的
  • 另外login_tmr在用例中就是相当于一个类的实例,不会因为多使用几次就重新进行初始化操作