一、前言

自动化测试的第一步往往是登录,然后进入列表页验证数据。这两个板块看似简单,但涉及元素定位、隐式等待与显式等待、浏览器弹窗(alert)、列表长度断言等常见场景。本文结合我的博客系统测试代码,按照“登录成功 → 登录失败 → 进入列表页 → 断言文章数量”的顺序,记录核心方法和踩坑经验。## 二、核心方法速查表

功能方法说明
定位单个元素driver.find_element(By.CSS_SELECTOR, "...")找不到抛异常
定位多个元素driver.find_elements(...)返回列表,可 len()
输入文本element.send_keys("文本")仅对 input/textarea 有效
点击element.click()需元素可交互
强制等待time.sleep(秒)简单但效率低
隐式等待driver.implicitly_wait(秒)全局查找元素超时,不影响 .text 或弹窗
显式等待WebDriverWait(driver, 秒).until(条件)灵活推荐,可等元素出现、弹窗等
弹窗切换driver.switch_to.alert需先等弹窗出现
获取当前标题driver.title常用于断言页面跳转

三、创建公共驱动

这里采用单例模式,提前创建好公共驱动,这样每次测试功能前就不用重复创建了。

class Driver:
    driver = None

    def __init__(self):
        if Driver.driver is None:
            options = Options()
            options.binary_location = r"D:\Program Files\Google\Chrome\Application\chrome.exe"
            Driver.driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
        self.driver = Driver.driver  # 让实例也有 driver 属性

    def getScreenShot(self):
        filename = "autotest-" + datetime.datetime.now().strftime('%Y-%m-%d-%H%M%S') + '.png'
        self.driver.save_screenshot('../test02/images/' + filename)


DriverTest = Driver()

四、板块1:登录测试(BlogLogin.py)

4.1 登录成功测试

import time
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from common.Utils import DriverTest


class BlogLogin:
    url = ""
    driver = ""

    def __init__(self):
        self.driver = DriverTest.driver
        print("login")
        print(self.driver)
        self.url = "url"
        self.driver.get(self.url)

    def backLogin(self):
        self.driver.back()

    def login(self):
        time.sleep(2)
        self.driver.find_element(By.CSS_SELECTOR, "#username").send_keys("zhangsan")
        self.driver.find_element(By.CSS_SELECTOR, "#password").send_keys("123456")
        self.driver.find_element(By.CSS_SELECTOR, "#submit").click()
        time.sleep(2)
        text = self.driver.find_element(By.CSS_SELECTOR, "body > div.container > div.left > div > h3").text
        print(text)
        assert text == "zhangsan"
        if text == "zhangsan":
            print("登录成功")
        DriverTest.getScreenShot()

    def clearInput(self):
        self.driver.find_element(By.CSS_SELECTOR, "#username").clear()
        self.driver.find_element(By.CSS_SELECTOR, "#password").clear()

    def loginFailTest(self):
        time.sleep(2)
        self.driver.find_element(By.CSS_SELECTOR, "#username").send_keys("zhangsan")
        self.driver.find_element(By.CSS_SELECTOR, "#password").send_keys("1234567")
        self.driver.find_element(By.CSS_SELECTOR, "#submit").click()
        print("弹窗测试开始")
        # 显式等待弹窗出现(最多等待 5 秒)
        WebDriverWait(self.driver, 5).until(EC.alert_is_present())
        alert = self.driver.switch_to.alert
        text = alert.text
        if text == "用户不存在":
            print("用户不存在,登录失败")
        if text == "密码错误":
            print("密码错误,登录失败")
        alert.accept()
        DriverTest.getScreenShot()
        self.driver.back()

4.2 登录测试运行代码

def run_tests():
    # 创建 BlogLogin 实例(此时会自动打开登录页面)
    blog_login = BlogLogin()

    # 1. 登录成功测试
    print("========== 登录成功测试 ==========")
    blog_login.login()
    blog_login.backLogin()
    blog_login.clearInput()
    # 注意:login() 方法最后执行了 self.driver.back(),返回了登录页
    # 但为了保险,可以加一点等待
    time.sleep(2)

    # 2. 登录失败测试(密码错误,预期弹窗“密码错误”)
    print("========== 登录失败测试(密码错误) ==========")
    blog_login.loginFailTest()

    # 所有测试完成后关闭浏览器
    DriverTest.driver.quit()

登录测试
登录测试

4.3 我遇到的错误

错误1:NoAlertPresentException – 弹窗未出现

  • 现象:点击登录按钮后,执行 switch_to.alert 立刻报错,提示没有弹窗。
  • 原因:后端验证需要时间,弹窗还没弹出,代码就已经尝试切换。
  • 解决方案:使用显式等待 WebDriverWait(driver, 5).until(EC.alert_is_present()),等弹窗出现后再切换。

错误2:UnexpectedAlertPresentException – 意外的弹窗仍存在

  • 现象:已经在代码中调用了 alert.accept(),但后面执行 find_element 时,仍然报错说有未处理的弹窗。
  • 原因:这是一个典型的时序失效问题。accept() 执行后,弹窗可能因为页面动画、后续 JavaScript 脚本而尚未完全关闭;或者页面刷新后再次弹出了新的确认框。代码执行速度太快,在弹窗真正消失前就去操作 DOM 元素,导致 Selenium 检测到弹窗还在。
  • 解决方案:在 accept() 之后,添加等待弹窗消失的逻辑。例如等待一个列表页的特征元素出现:
alert.accept()
WebDriverWait(driver, 5).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, "div.right"))
)

这样既等待了弹窗关闭,也等待了页面跳转完成。

五、板块2:博客列表页测试(BlogList.py)

5.1 进入列表页并断言文章数量

import time
from selenium.webdriver.common.by import By
from common.Utils import DriverTest


class BlogList:
    driver = ""

    def __init__(self):
        self.driver = DriverTest.driver
        print("List")
        print(self.driver)

    def ListTest(self):
        print("select body")
        time.sleep(2)
        # html = self.driver.page_source
        # print(html)
        # print(self.driver.current_url)
        articles = self.driver.find_elements(By.CSS_SELECTOR, "body > div.container > div.right > div")
        print(articles)
        assert len(articles) > 10
        self.driver.find_element(By.CSS_SELECTOR, "body > div.container > div.right > div:nth-child(121)").click()
        DriverTest.getScreenShot()
        title = self.driver.title
        assert title == "博客列表页"
        DriverTest.getScreenShot()

5.2 我遇到的错误

错误1:TypeError: object of type ‘WebElement’ has no len()

  • 现象:代码 articles = driver.find_element(...); assert len(articles) > 10 报错,提示 WebElement 没有 len()。
  • 原因:使用了 find_element(单数),返回的是单个元素对象,而不是列表。
  • 解决方案:改用 find_elements(复数)获取元素列表,再取长度。

错误2:NoSuchElementException – 点击第 121 篇文章时找不到元素

  • 现象:driver.find_element(By.CSS_SELECTOR, "...:nth-child(121)").click() 抛出元素不存在异常。
  • 原因:列表页实际文章数量不足 121 篇,或者页面结构发生变化。
  • 解决方案:先获取文章总数,动态决定点击哪一篇:
articles = driver.find_elements(By.CSS_SELECTOR, "div.right > div")
assert len(articles) > 10
if len(articles) >= 121:
    articles[120].click()   # 索引从 0 开始
else:
    articles[-1].click()    # 点击最后一篇

错误3:断言 title == "博客列表页" 失败

  • 现象:点击文章后,断言标题为“博客列表页”,但实际标题是“博客详情页”或其他。
  • 原因:点击文章链接后页面跳转到了详情页,而不是停留在列表页。测试预期写错了。
  • 解决方案:根据实际业务修改断言,例如 assert "详情" in driver.title 或直接删除该断言。

错误4:页面跳转后元素找不到(NoSuchElementException)

  • 现象:点击文章进入详情页后,后续代码仍用列表页的选择器找元素,报错找不到。
  • 原因:没有等待新页面加载,也没有切换窗口句柄(如果新窗口打开)。
  • 解决方案:点击后使用 WebDriverWait 等待新页面的特征元素出现;如果新窗口打开,还需用 driver.window_handles 切换。

六、总结(第一篇)

  • 登录模块核心:等待弹窗必须用显式等待。
  • 列表页模块核心:用 find_elements 获取列表长度,不要硬编码索引。
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐