使用Selenium自动化测试解决报告生成失败问题及Linux部署指南

简介: 这篇文章介绍了使用Selenium自动化测试解决报告生成失败问题的方法,包括Linux环境下的部署指南和代码实现。
介绍背景
情况是这样的,某段时间之前,开发就想找我用ui自动化帮他们实现一个功能:在系统某些时候生成报告的时候会fail,
但是又不再重新生成,需要人工edit再次submit才能生成,原因png是由当前html页面生成。但是作为测试的我有一个疑惑?
开始不是常用定时任务或是失败重试吗?怎么不这样做呢?或者有其他办法使之成功呢?然后开发自己优化了一下,就默默的成功了,
这事儿就算过去了,不曾想几天前又复活了,需要我来协助,然后问,这玩意儿不能使用接口去完成吗?开发解释:不能,why?
原因是html转png:前端拿到接口响应数据,动态绘制html,然后在生成png。问题来了:为什么会失败?什么情况下会失败呢?
开发与实现

作者的python开发环境那是有好几套,接口、ui自动化的环境那是现成的,拿来即用,这里用来演示步骤,就不截图了。

部署python本地开发环境
# 安装工具库
pip install selenium

再下载对应chromedriver浏览器驱动

开始码代码

又因为作者是有比较系统的ui自动化测试思想,首先是po模式,但是这个需求是一次性的,所以并不想把它复杂化<相对线性脚本>,本着开发效率出发<不曾想也花了一天时间>

#!/bin/python3
# -*-coding: utf-8 -*-

import json
import logging
from time import sleep
import time

import requests
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait


# 日志系统
log_format = '%(asctime)s - %(levelname)s - %(message)s'
logging.basicConfig(filename="scoreSys.log", filemode='a+', format=log_format, level=logging.DEBUG)

options = webdriver.ChromeOptions()
#options.add_argument('--disable-gpu')
#options.add_argument('--headless')
#options.add_argument('--no-sandbox')
#options.add_argument('--disable-dev-shm-usage')

# 初始化页面对象
driver = webdriver.Chrome(executable_path="../chromedriver.exe", options=options)


"""打开浏览器"""
# https://score.1111.com/
logging.info(">>>>>>>>>>>>>>>>>打开口语报告系统<<<<<<<<<<<<<<<<<<<<<<")
#     driver.get("https://xxx.xxx.com/home")
driver.get("https://uat.xxx.xxx.com/xxx/home")
driver.maximize_window()
driver.implicitly_wait(10)

# 登录页,页面元素
account_input = (By.XPATH, "//input[@placeholder='Email']")
passwd_input = (By.XPATH, "//input[@placeholder='Password']")
verify_code = (By.XPATH, "//input[@placeholder='Code']")
signIn_btn = (By.XPATH, "//span[text()=' Sign In ']")
# 登录的操作步骤
driver.find_element(*account_input).send_keys("xxx.x@hcp.tech")
driver.find_element(*passwd_input).send_keys("xxxxx")
driver.find_element(*verify_code).send_keys("1234")
driver.find_element(*signIn_btn).click()
logging.info(">>>>>>>>>>>>>>>>>用户登录口语报告系统<<<<<<<<<<<<<<<<<<<<<<")
sleep(1)


# ui登录后获取请求头中token属性
headers = {"content-type":"application/json"}
# 登录系统后用户名元素
account_text = (By.CSS_SELECTOR, ".user_name")
change_passwd = (By.CSS_SELECTOR, ".pwdBtn")

"""检查登录状态"""
account_info = driver.find_element(*account_text).text
logging.info(">>>>>>>>>>>>>>>>>检查用户是否成功登录系统<<<<<<<<<<<<<<<<<<<<<<")
try:
    assert account_info == "xxxx", "断言失败"
except:
    logging.info(">>>>>>>>>>>>>>>>>用户:{},登录失败!!!<<<<<<<<<<<<<<<<<<<<<<".format(account_info))
else:
    logging.info(">>>>>>>>>>>>>>>>>用户:{},登录成功!!!<<<<<<<<<<<<<<<<<<<<<<".format(account_info))
    userInfo = json.loads(driver.execute_script('return localStorage.getItem("userInfo");'))
    v = json.loads(userInfo.get("v"))
    token = v.get("token")
    headers["token"] = token

# 搜索条件
select_box = (By.XPATH, "//input[@placeholder='Report status']")
select_input = (By.XPATH, "//span[text()='Failed']")
select_btn = (By.XPATH, "//span[text()='Search']")

"""输入fail点击查询"""
# 查询操作
driver.find_element(*select_box).click()
sleep(1)
driver.find_element(*select_input).click()
driver.find_element(*select_btn).click()


# 列表是否有失败状态的
fail_status = "//td//div[text()='Fail']"
# 找到失败的edit按钮
edit_btn = (By.XPATH, "{}/../parent::td[1]//following-sibling::td//span[text()='Edit']".format(fail_status))
submit_selector = (By.XPATH, "//div[contains(text(),'Submit')]")

"""提交报告"""
# 首先按fail条件查询
click_search()
# 找到更多需要edit的按钮
status_eles = driver.find_elements(*edit_btn)
ele_nums = len(status_eles) # 页面元素找到元素不唯一
# 接口获取fail总数及id列表
nums, fail_li = get_fail_nums()

count = 0    # 记录真实补偿次数
success_li = []    # 记录成功补偿报告id
while ele_nums > 0:
    status_eles = driver.find_elements(*edit_btn)
    try:
        for ele in status_eles:
            clicked = ele.is_displayed() # 因为找到元素不唯一,需要判断元素是否显示
            if clicked:
                ele.click()
                driver.find_element(*submit_selector).click()    # 提交
                success_li.append(driver.current_url.split("=")[-1])
                #等待提交之后跳转的页面元素是否出现
                WebDriverWait(driver, 50).until(EC.presence_of_element_located(change_passwd)) 
                count += 1
    except:
        ele_nums -= 1
    else:
        ele_nums -= 1
    finally:
        click_search() # 每次需重新查询

driver.quit()
思路分析
  1. 第一步,开发针对我的特殊帐号去除验证码登录,<毕竟实现验证码登录成本还是有的>
  2. 首页增加报告查询条件:success or failed;
  3. 需要发送钉钉通知<这是后面实现>
  4. 操作流程:登录-检查登录状态-查询fail条件的数据-进入编辑页面-点击submit-再重新查询fail条件-如有继续edit-submit,如初反复直到没有fail的数据为止-关闭浏览器
难点解析

在selenium做ui自动化的时候,最难的不是实现某个功能代码块,而是定位元素表达式,但是页面不全是id、name等唯一元素,更多是需要写css_selector\xpath

  • html的结果页是个table,那么在根据fail失败元素同级找到它的edit,这个如下所示;是不是比较懵圈
# 列表是否有失败状态的 
fail_status = "//td//div[text()='Fail']" 
# 找到失败的edit按钮 
edit_btn = (By.XPATH, "{}/../parent::td[1]//following-sibling::td//span[text()='Edit']".format(fail_status)) 
# 解释下上面的表达式:/.. 表示查找上级,parent::表示父级,following-sibling::平级中的下级
  • 其实实现之后并不需要如此,因为是先按fail条件查询,那么剩下就是fail,直接找edit即可。
  • 难点是ui+接口的结合;因为上述代码之初,在找到需要重新submit的报告,数目不正确,因为元素重复不唯一,

clicked = ele.is_displayed() # 因为找到元素不唯一,需要判断元素是否显示

  • 所以想通过接口来确定真正fail的条数,所以先ui登录,获取token传递给接口请求,

clipboard.png

# 这是个新技能点,每次尝试不同的需求,总会遇到不同的问题,然后找到解决方案 
userInfo = json.loads(driver.execute_script('return localStorage.getItem("userInfo");')) # 这个userInfo是key,而不是直接叫token
  • 从demo中可以看出作者的po思想,将定位元素的标识单独提取出来保存,没直接放在方法里
代码升级

在测试领域中,分层测试并不是说ui不支持接口自动化,意思是某些场景需要与之结合。

加入接口请求

发送钉钉请求是接口 登录系统是接口 查询还是接口

#!/bin/python3
# -*-coding: utf-8 -*-

import json
import logging
from time import sleep
import time

import requests
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait


# 日志系统
log_format = '%(asctime)s - %(levelname)s - %(message)s'
logging.basicConfig(filename="scoreSys.log", filemode='a+', format=log_format, level=logging.DEBUG)

options = webdriver.ChromeOptions()
options.add_argument('--disable-gpu')
options.add_argument('--headless')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')

# 初始化页面对象
driver = webdriver.Chrome(executable_path="../chromedriver.exe", options=options)

def send_dingding(before_of_after, nums, fail_li, time):
    """处理数据前后发送钉钉业务通知"""
    dingDing_robot = "https://oapi.xxxx.com/robot/send?access_token=xxxx"
#     dingDing_robot = "https://oapi.xxxx.com/robot/send?access_token=xxxx"
    title = "【Teacher Work Platform \n 业务预警提示:{}】".format(time)
    if before_of_after == "before":
        warning_msg = {"msgtype": "text", "text":{"content":"{}: \n你有{}条口语外教报告id:{}待补偿!!!".format(title, nums, fail_li)}}
        requests.post(dingDing_robot, json=warning_msg, headers={"content-type":"application/json"})
    elif before_of_after == "after":
        warning_msg = {"msgtype": "text", "text":{"content":"{}: \n已成功补偿id:{},{}条口语外教报告!!!".format(title,fail_li, nums)}}
        requests.post(dingDing_robot, json=warning_msg, headers={"content-type":"application/json"})


def open_browser():
    """打开浏览器"""
    # https://score.1111.com/
    logging.info(">>>>>>>>>>>>>>>>>打开口语报告系统<<<<<<<<<<<<<<<<<<<<<<")
#     driver.get("https://xxx.xxx.com/home")
    driver.get("https://uat.xxx.xxx.com/xxx/home")
    driver.maximize_window()
    driver.implicitly_wait(10)

# 登录页,页面元素
account_input = (By.XPATH, "//input[@placeholder='Email']")
passwd_input = (By.XPATH, "//input[@placeholder='Password']")
verify_code = (By.XPATH, "//input[@placeholder='Code']")
signIn_btn = (By.XPATH, "//span[text()=' Sign In ']")
def login_system():
    """登录报告系统"""
    # 用户登录信息
    driver.find_element(*account_input).send_keys("xxx.x@xxx.tech")
    driver.find_element(*passwd_input).send_keys("xxxx")
    driver.find_element(*verify_code).send_keys("1234")
    driver.find_element(*signIn_btn).click()
    logging.info(">>>>>>>>>>>>>>>>>用户登录口语报告系统<<<<<<<<<<<<<<<<<<<<<<")
    sleep(1)


# ui登录后获取请求头中token属性
headers = {"content-type":"application/json"}
# 登录系统后用户名元素
account_text = (By.CSS_SELECTOR, ".user_name")
change_passwd = (By.CSS_SELECTOR, ".pwdBtn")
def check_status():
    """检查登录状态"""
    account_info = driver.find_element(*account_text).text
    logging.info(">>>>>>>>>>>>>>>>>检查用户是否成功登录系统<<<<<<<<<<<<<<<<<<<<<<")
    try:
        assert account_info == "xxxx", "断言失败"
    except:
        logging.info(">>>>>>>>>>>>>>>>>用户:{},登录失败!!!<<<<<<<<<<<<<<<<<<<<<<".format(account_info))
    else:
        logging.info(">>>>>>>>>>>>>>>>>用户:{},登录成功!!!<<<<<<<<<<<<<<<<<<<<<<".format(account_info))
        userInfo = json.loads(driver.execute_script('return localStorage.getItem("userInfo");'))
        v = json.loads(userInfo.get("v"))
        token = v.get("token")
        headers["token"] = token

def login_by_accout():
    """帐号登录系统"""
    url="https://uat.teacher.1111.com/hcp/1111/oralUsers/login"
    # url="https://score.1111.com/hcp/1111/oralUsers/login"
    data={"password": "xxx", "email": "xxx.x@xxx.tech","captcha":2953,"uuid":"12344"}
    res=requests.post(url,json=data,headers={"content-type":"application/json"}).json()
    token=res.get("content").get("oralUserToken")
    headers["token"]=token


def get_fail_nums():
    """通过接口获取失败记录条数"""
    url = "https://uat.xxx.xxx.com/xxx/xxx/xxx/page"
    # url = "https://xxx.xxxx.com/xxx/xxx/xxx/page"
    data = {"reportStatus":0, "curPage":1, "limit":8}
    res = requests.get(url, params=data, headers=headers).json()
    nums = res.get("content").get("total")
    fails = res.get("content").get("list")
    fail_li=[] # 记录fail报告id
    for fail in fails:
        fail_li.append(fail.get("id"))
    return nums, fail_li

# 搜索条件
select_box = (By.XPATH, "//input[@placeholder='Report status']")
select_input = (By.XPATH, "//span[text()='Failed']")
select_btn = (By.XPATH, "//span[text()='Search']")
def click_search():
    """输入fail点击查询"""
    # 查询操作
    driver.find_element(*select_box).click()
    sleep(1)
    driver.find_element(*select_input).click()
    driver.find_element(*select_btn).click()


# 列表是否有失败状态的
fail_status = "//td//div[text()='Fail']"
# 找到失败的edit按钮
edit_btn = (By.XPATH, "{}/../parent::td[1]//following-sibling::td//span[text()='Edit']".format(fail_status))
submit_selector = (By.XPATH, "//div[contains(text(),'Submit')]")
def submit_report():
    """提交报告"""
    # 首先按fail条件查询
    click_search()
    # 找到更多需要edit的按钮
    status_eles = driver.find_elements(*edit_btn)
    ele_nums = len(status_eles) # 页面元素找到元素不唯一
    # 接口获取fail总数及id列表
    nums, fail_li = get_fail_nums()
    # 补偿当前时间
    now_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 
    if nums >= 1:    # 有数据才发送钉钉
        send_dingding("before", nums, fail_li, now_time)
    else:
        logging.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>没有fail状态的数据需要处理<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
        return None

    count = 0    # 记录真实补偿次数
    success_li = []    # 记录成功补偿报告id
    while ele_nums > 0:
        status_eles = driver.find_elements(*edit_btn)
        try:
            for ele in status_eles:
                clicked = ele.is_displayed() # 因为找到元素不唯一,需要判断元素是否显示
                if clicked:
                    ele.click()
                    driver.find_element(*submit_selector).click()    # 提交
                    success_li.append(driver.current_url.split("=")[-1])
                    #等待提交之后跳转的页面元素是否出现
                    WebDriverWait(driver, 50).until(EC.presence_of_element_located(change_passwd)) 

                    count += 1
        except:
            ele_nums -= 1
        else:
            ele_nums -= 1
        finally:
            click_search() # 每次需重新查询

    duration_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 
    send_dingding("after", count, success_li, duration_time)

def close_browser():
    """退出浏览器"""
    driver.quit()


if __name__ == '__main__':
    while True:
        # 一直循环去查fail数据,然后进行补偿
        login_by_accout()
        nums,fail_li=get_fail_nums()
        if nums:
            print("有fail需补偿数据")
            open_browser()
            login_system()
            check_status()
            submit_report()
            close_browser()
            print("补偿完了")
        else:
            print("没有补偿数据")
        sleep(60)
        if driver:
            driver.quit()
代码实现思路
  1. 注意看main中的代码块:先通过接口去登录、查询fail条件的数据,有则通知执行ui测试代码
  2. 再说接口:通过获取fail的总数及id,将数据通过钉钉发送到群里通知,最后count统计补偿的数据及id
  3. 这份升级代码是从一步步真是环境验证改进而来,一开始在uat环境是ok的,但在生产总会有点问题:原因是作者将元素个数和edit次数混用了,应该区分计数器控制循环
linux部署selenium运行环境
安装chrome浏览器

yum install dl.google.com/linux/direc…

注意在安装过程中google-chrome浏览器驱动的版本,执行:google-chrome

# 安装chrome是的版本: 
Package: google-chrome-stable-92.0.4515-107.x86_64 (google-chrome) 
# 以下信息,告诉咱们启动脚本时,需要带上--no-sandbox参数 
[6367:6367:0817/170516.458662:ERROR:zygote_host_impl_linux.cc(90)] Running as root without --no-sandbox is not supported. See https://crbug.com/638180.

在启动py脚本执行时报错:ChromeDriver is assuming that Chrome has crashed

clipboard.png 实际情况,linux系统下无头启动浏览器设置如下

chrome_options = webdriver.ChromeOptions() 
chrome_options.add_argument('--headless') # 浏览器不提供可视化页面. linux下如果系统不支持可视化不加这条会启动失败 
chrome_options.add_argument('--no-sandbox')# 解决DevToolsActivePort文件不存在的报错 
# 在windows下只需以上两项即可 
chrome_options.add_argument('--disable-gpu') # 谷歌文档提到需要加上这个属性来规避bug 
chrome_options.add_argument('--disable-dev-shm-usage')
下载chromedriver

chromedriver.storage.googleapis.com/index.html # 是选择对应浏览器的linux版本哦

解压得到chromedriver,执行输出如下信息,即正确:

Starting ChromeDriver 92.0.4515.107 (87a818b10553a07434ea9e2b6dccf3cbe7895134-refs/branch-heads/4515@{#1634}) on port 9515 
Only local connections are allowed. 
Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe. 
[1629191031.401][SEVERE]: bind() failed: Cannot assign requested address (99) 
ChromeDriver was started successfully.

重申:注意Chrome版本和ChromeDriver的版本要对应好,否则实际运行时会报错。当然一般chrome可以安装最新版本了,所以只要选择chromedriver时选择支持最新版本的即可

关于linux环境测试selenium环境代码如下:

#!/bin/python3 
# -*-coding: utf-8 -*- 
from selenium import webdriver 
opt=webdriver.ChromeOptions() 
opt.set_headless() # 自动适配对应参数 
opt.add_argument('--no-sandbox') # 解决DevToolsActivePort文件不存在的报错,去掉这行会提示错误:unknown error: DevToolsActivePort file doesn't exist 
driver=webdriver.Chrome(executable_path="./chromedriver",options=opt) 
driver.get("https://www.baidu.com") 
print(driver.title) 
driver.quit()

问题:UnicodeEncodeError: ‘ascii’ codec can’t encode characters; 在linux系统中,如果py脚本中有中文注释,执行的时候会包字符解码错误

解决办法:在文件头部添加注释==>>> #-*-coding: utf-8 -*-

总结

以上为本篇的全过程,如有不对或更好的方法,欢迎拍砖!!!

相关文章
|
5天前
|
数据采集 Web App开发 测试技术
使用Selenium与WebDriver实现跨浏览器自动化数据抓取
在网络爬虫领域,Selenium与WebDriver是实现跨浏览器自动化数据抓取的利器。本文详细介绍了如何利用Selenium和WebDriver结合代理IP技术提升数据抓取的稳定性和效率。通过设置user-agent和cookie来模拟真实用户行为,避免被网站检测和阻止。文章提供了具体的代码示例,展示了如何配置代理IP、设置user-agent和cookie,并实现了跨浏览器的数据抓取。合理的参数配置能有效减少爬虫被封禁的风险,提高数据抓取效率。
使用Selenium与WebDriver实现跨浏览器自动化数据抓取
|
11天前
|
NoSQL 关系型数据库 Redis
mall在linux环境下的部署(基于Docker容器),Docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongo
mall在linux环境下的部署(基于Docker容器),docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongodb、minio详细教程,拉取镜像、运行容器
mall在linux环境下的部署(基于Docker容器),Docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongo
|
5天前
|
Web App开发 Java 测试技术
自动化测试的利器:Selenium WebDriver入门与实践
【9月更文挑战第8天】在软件开发的海洋中,测试是确保我们不会溺水的那根救生索。Selenium WebDriver,作为自动化测试的明星工具,让这根救生索更加结实可靠。本文将带你快速上手Selenium WebDriver,从基础设置到实际操作,再到实战演练,让你的开发之旅更加平稳顺畅。
|
7天前
|
缓存 数据可视化 jenkins
推荐2款实用的持续集成与部署(CI&CD)自动化工具
推荐2款实用的持续集成与部署(CI&CD)自动化工具
|
11天前
|
Kubernetes Linux API
CentOS 7.6使用kubeadm部署k8s 1.17.2测试集群实战篇
该博客文章详细介绍了在CentOS 7.6操作系统上使用kubeadm工具部署kubernetes 1.17.2版本的测试集群的过程,包括主机环境准备、安装Docker、配置kubelet、初始化集群、添加节点、部署网络插件以及配置k8s node节点管理api server服务器。
45 0
CentOS 7.6使用kubeadm部署k8s 1.17.2测试集群实战篇
|
1天前
|
JavaScript 前端开发 测试技术
Selenium2Library实现基于GUI的测试
Selenium2Library实现基于GUI的测试
4 0
|
14天前
|
Web App开发 Java 测试技术
自动化测试的新篇章:使用Selenium WebDriver进行高效测试
【8月更文挑战第31天】 在软件开发的海洋中,自动化测试犹如一艘航船,带领着质量保证团队驶向效率与精准的彼岸。本文将揭开Selenium WebDriver的神秘面纱,通过实际案例引导您掌握这一强大的自动化测试工具。我们将从Selenium WebDriver的基础概念出发,逐步深入到代码示例,最后探讨其在现实项目中的应用场景和优势,旨在为您的软件测试之旅提供清晰的指南。
|
14天前
|
Web App开发 测试技术 持续交付
探索自动化测试:以Selenium和Python为例
【8月更文挑战第31天】自动化测试在现代软件开发中扮演着不可或缺的角色。本文将通过一个简化的示例,展示如何使用Selenium和Python进行Web应用的自动化测试。我们将从安装必要的工具开始,逐步构建一个简单的测试脚本,并执行它来验证其功能。通过这个过程,我们旨在揭示自动化测试的价值,并激励读者深入探索这一领域。
|
14天前
|
Web App开发 IDE 测试技术
自动化测试的利器:Selenium 框架深度解析
【8月更文挑战第31天】在软件开发的世界中,自动化测试是提高产品质量和开发效率不可或缺的一环。本文将深入探讨Selenium这一强大的自动化测试工具,从其架构、优势到实战应用,一步步揭示如何利用Selenium框架提升软件测试的效率和准确性。通过具体的代码示例,我们将展示Selenium如何简化测试流程,帮助开发者快速定位问题,确保软件的稳定性和可靠性。无论你是测试新手还是资深开发者,这篇文章都将为你打开一扇通往高效自动化测试的大门。
|
14天前
|
Java 测试技术 API
探索自动化测试的奥秘:从Selenium到Appium
【8月更文挑战第31天】本文旨在引导读者理解自动化测试的重要性,并逐步深入介绍如何利用Selenium和Appium这两个强大的工具来提升测试效率和质量。文章不仅分享理论知识,还通过具体代码示例,展示如何在Web应用和移动应用测试中实施自动化策略。

热门文章

最新文章