)
九、等待策略脚本执行速度通常比页面渲染快为了减少“页面还没出来就报找不到元素”的误报Selenium 提供了三种等待方式。9.1 强制等待time.sleep()用法简单调试阶段比较有效代价是影响运行效率白白消耗时间。9.2 隐式等待implicitly_wait()参数单位是秒。它是一种智能等待查找元素时在指定时间内不断重试找到就继续执行超时仍未找到才报错。driver.implicitly_wait(5)隐式等待作用于整个脚本只要 driver 对象未被释放quit设置就一直生效。优点是智能、作用于全局。9.3 显示等待显示等待同样是智能等待在指定超时时间内条件一旦满足就继续执行。需要配合 WebDriverWait 与 ExpectedConditions 使用。from selenium.webdriver.support import expected_conditions as EC wait WebDriverWait(driver, 2) wait.until(EC.invisibility_of_element((By.XPATH, //*[id2]/div/div/div[3]/div[1]/div[1]/div)))ExpectedConditions 提供了一批预定义方法常用几个整理如下方法说明title_is(title)检查页面标题是否等于期望值title_contains(title)检查标题是否包含指定子串区分大小写visibility_of_element_located(locator)检查元素存在于 DOM 且可见presence_of_element_located(locator)检查元素是否存在于 DOMvisibility_of(element)检查已知存在于 DOM 的元素是否可见alert_is_present()检查是否出现弹窗显示等待支持自定义条件操作更灵活不足之处是写法相对复杂。9.4 等待混用9.4 隐式等待与显示等待一起用效果如何有人做过一次测试隐式等待设 10 秒、显示等待设 15 秒结果并非简单的 1015多次重试都在约 20 秒后超时。隐式等待与显示等待混用的实测结果结论不要混用隐式等待与显示等待混用可能带来不可预测的等待时间。十、浏览器导航导航相关的操作集中在三个方法上后退、前进、刷新。driver.back() # 后退 driver.forward() # 前进 driver.refresh() # 刷新合 driver.get(在线工具 - 你的工具箱) 打开网站可以完成前进、后退、刷新一类的页面流转验证。十一、文件上传点击文件上传控件时系统会弹出文件选择窗口。Selenium 识别不了非 Web 控件上传窗口属于系统自带无法直接定位其中的元素。改用 send_keys 传入文件路径可以达到同样的上传效果ele driver.find_element(By.CSS_SELECTOR, body div div input[typefile]) ele.send_keys(D:\\file\\test.txt)把文件绝对路径直接喂给文件输入框即可。十二、浏览器参数某些场景下需要调整浏览器启动参数这里看两个常用设置。无头模式不显示浏览器窗口适合部署在服务器上运行options webdriver.ChromeOptions() options.add_argument(-headless) driver webdriver.Chrome(serviceChromeService(ChromeDriverManager().install()), optionsoptions)页面加载策略通过 page_load_strategy 指定常见三种策略说明normal默认值等待页面资源下载完成eagerDOM 就绪即可图片等资源可能仍在加载none不阻塞 WebDriver 的执行options webdriver.ChromeOptions() options.page_load_strategy eager driver webdriver.Chrome(serviceChromeService(ChromeDriverManager().install()), optionsoptions)十三、实战博客系统自动化前面介绍的方法散落在各个知识点里最后用一个完整的例子把它们串起来。这里以博客系统为例对 Web 项目做一次完整的自动化实施。13.1 实施步骤整个实施可以拆成两步一是编写 Web 测试用例明确被测功能与预期结果二是开发自动化测试脚本把用例翻译成可执行的脚本代码。13.2 测试用例设计用例设计阶段常用思维导图梳理测试点。下图为博客系统的 UI 自动化测试用例覆盖登录页面、博客列表页、编辑博客页、博客详情页四个模块每个模块再按登录 / 未登录状态拆出正常流程与异常边界。博客系统 UI 自动化测试用例13.3 脚本开发脚本按职责拆成几个文件common/Utils.py 负责创建驱动与截图cases 目录放各页面的用例RunCases.py 负责串联执行。import datetime import os.path import sys from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager class Driver: driver def __init__(self): options webdriver.ChromeOptions() self.driver webdriver.Chrome(serviceService(ChromeDriverManager().install()), optionsoptions) def getScreenShot(self): dirname datetime.datetime.now().strftime(%Y-%m-%d) if not os.path.exists(../images/ dirname): os.mkdir(../images/ dirname) filename sys._getframe().f_back.f_code.co_name - datetime.datetime.now().strftime( %Y-%m-%d-%H%M%S) .png self.driver.save_screenshot(f../images/{dirname}/ filename) BlogDriver Driver()import time from selenium.webdriver.common.by import By from common.Utils import BlogDriver class BlogLogin: url driver def __init__(self): self.url http://192.168.47.135:8653/blog_system/blog_login.html self.driver BlogDriver.driver self.driver.get(self.url) def loginSucTest(self): time.sleep(2) self.driver.find_element(By.CSS_SELECTOR, #username).clear() self.driver.find_element(By.CSS_SELECTOR, #password).clear() self.driver.find_element(By.CSS_SELECTOR, #username).send_keys(admin) self.driver.find_element(By.CSS_SELECTOR, #password).send_keys(123) self.driver.find_element(By.CSS_SELECTOR, #submit).click() # 登录成功的判定跳转到了博客列表页 self.driver.find_element(By.CSS_SELECTOR, body div.container div.left div img) BlogDriver.getScreenShot() self.driver.back() def loginFailTest(self): self.driver.find_element(By.CSS_SELECTOR, #username).clear() self.driver.find_element(By.CSS_SELECTOR, #password).clear() self.driver.find_element(By.CSS_SELECTOR, #username).send_keys(admin) self.driver.find_element(By.CSS_SELECTOR, #password).send_keys(111) self.driver.find_element(By.CSS_SELECTOR, #submit).click() # 登录失败的判定不能只看 body因为成功页也有 body 元素 expect 用户名或密码错误! actual self.driver.find_element(By.CSS_SELECTOR, body).text print(actual) BlogDriver.getScreenShot() assert expect actual self.driver.back()from selenium.webdriver.common.by import By from common.Utils import BlogDriver class BlogList: url driver def __init__(self): self.driver BlogDriver.driver self.url http://192.168.47.135:8653/blog_system/blog_list.html self.driver.get(self.url) # 前置条件登录状态下才能进入列表页 def ListTest(self): self.driver.find_element(By.CSS_SELECTOR, body div.container div.left div img) self.driver.find_element(By.CSS_SELECTOR, body div.container div.right div:nth-child(1) a) articles self.driver.find_elements(By.CSS_SELECTOR, body div.container div.right div) assert len(articles) 10 # 点击文章进入详情页 self.driver.find_element(By.CSS_SELECTOR, body div.container div.right div:nth-child(1) a).click() title self.driver.title assert title 博客详情页 BlogDriver.getScreenShot()from selenium.webdriver.common.by import By from common.Utils import BlogDriver class BlogDetail: url driver def __init__(self): self.url http://192.168.47.135:8653/blog_system/blog_detail.html?blogId15 self.driver BlogDriver.driver title self.driver.title # 从列表页已跳转过来无需再指定 url if not title 博客列表页: self.driver.get(self.url) def DetailCheck(self): BlogDriver.getScreenShot() self.driver.find_element(By.CSS_SELECTOR, body div.container div.right div h3) self.driver.find_element(By.CSS_SELECTOR, body div.container div.right div div.date) self.driver.find_element(By.CSS_SELECTOR, #content)from common.Utils import BlogDriver from tests import BlogLogin from tests import BlogList from tests import BlogDetail if __name__ __main__: BlogLogin.BlogLogin().loginSucTest() BlogList.BlogList().ListTest() BlogDetail.BlogDetail().DetailCheck() BlogDriver.driver.quit()几个要点值得留意登录成功与否不能只看页面是否打开成功页与失败页都可能有 body 元素判定要落在具体元素或文案上用例之间通过共享的 BlogDriver 保持同一个驱动会话最后统一 quit 释放资源。13.4 测试报告用例跑完之后把结果汇总到测试报告里。下图是一份博客系统的测试报告模板包含项目信息、测试范围、测试用例、测试分类、遗留风险与结果评估等部分测试安排按模块记录开发、测试、工时与进度测试分类区分功能测试与自动化测试自动化部分标注覆盖模块、用例数量与通过情况遗留风险与结果评估给出上线判断与后续建议。自动化不是跑完就结束把过程与结果沉淀成报告团队才能更好地评估质量与风险。十四、小结至此从自动化概念、Selenium 常用函数到博客系统实战一条完整的路径已经走了一遍。回到开头的问题自动化能替我们做很多重复的事但它需要持续维护也有适用边界。真正关键的是在合适的环节选择合适的手段让自动化服务于质量而不是为了自动化而自动化。