自动化测试入门:从零开始搭建你的第一个WebUI项目

为什么要做自动化测试?

在深入技术细节前,我们先了解自动化测试的价值。手动测试虽然直观,但存在明显局限性:重复劳动、容易出错、耗时耗力且无法快速反馈。而自动化测试恰能弥补这些不足:

  • 提升测试效率:自动化脚本可以24小时不间断执行
  • 提高测试准确性:避免人为疏忽导致的错误
  • 增强测试覆盖:可轻松执行数千个测试用例
  • 快速反馈:问题及早发现,降低修复成本
  • 回归测试利器:每次代码变更后快速验证现有功能

虽然自动化测试有诸多优势,但并非所有场景都适合自动化。UI频繁变更、一次性功能、主观体验测试等还是更适合手动测试。

自动化测试技术选型

WebUI自动化测试领域有多种工具可选,主流选择包括:

  • Selenium:最老牌、应用最广泛的Web自动化工具,支持多语言和多浏览器
  • Cypress:新兴的测试框架,以其强大的调试能力和执行速度著称
  • Puppeteer:由Google开发,主要用于Chrome浏览器自动化
  • Playwright:微软推出的测试工具,支持多浏览器且功能强大

对于初学者,Selenium是最佳起点,因为它生态成熟、资料丰富,且是许多公司实际使用的技术。

环境搭建与工具准备

下面我们开始搭建Selenium自动化测试环境。

1. 安装编程语言环境

Selenium支持多种编程语言,这里我们选择Python,因为它语法简洁、上手快速。

访问Python官网下载并安装最新版本。安装时记得勾选"Add Python to PATH"选项。

安装完成后,打开终端/命令提示符,验证安装是否成功:

css 复制代码
python --version
pip --version

2. 安装Selenium库

通过pip安装Selenium包:

复制代码
pip install selenium

3. 下载浏览器驱动

Selenium需要通过浏览器驱动来控制浏览器。这里以Chrome为例:

  • 查看Chrome浏览器版本:点击Chrome菜单 → 帮助 → 关于Google Chrome
  • 访问ChromeDriver下载页面
  • 下载与你的Chrome版本匹配的ChromeDriver
  • 将解压后的chromedriver.exe放在合适位置(建议放在项目目录或系统PATH包含的目录)

4. 选择开发工具

推荐使用以下任一IDE:

  • PyCharm:功能强大的Python专用IDE
  • VS Code:轻量级且扩展丰富
  • Jupyter Notebook:适合交互式学习和调试

第一个Selenium脚本

环境准备就绪后,让我们编写第一个自动化脚本。

基础脚本示例

python 复制代码
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
import time
# 设置ChromeDriver路径
driver_path = "./chromedriver"  # 根据实际情况调整路径
# 创建浏览器驱动实例
service = Service(executable_path=driver_path)
driver = webdriver.Chrome(service=service)
try:
    # 打开网页
    driver.get("https://www.baidu.com")

    # 打印页面标题
    print("页面标题:", driver.title)

    # 查找搜索框并输入关键词
    search_box = driver.find_element(By.ID, "kw")
    search_box.send_keys("自动化测试")

    # 查找搜索按钮并点击
    search_btn = driver.find_element(By.ID, "su")
    search_btn.click()

    # 等待结果加载
    time.sleep(3)

    # 打印新页面标题
    print("搜索后页面标题:", driver.title)

finally:
    # 关闭浏览器
    driver.quit()

脚本解析

  1. 导入模块:引入必要的Selenium组件
  2. 配置驱动:指定ChromeDriver路径
  3. 初始化驱动:创建浏览器实例
  4. 打开网页:使用get方法导航至目标URL
  5. 定位元素:通过ID定位搜索框和按钮
  6. 执行操作:在搜索框输入文本并点击按钮
  7. 清理资源:关闭浏览器释放资源

运行此脚本,你将看到自动打开Chrome浏览器、访问百度、执行搜索并关闭浏览器的完整过程。

元素定位策略

元素定位是Web自动化测试的核心。Selenium提供多种定位策略:

1. ID定位

最可靠且高效的定位方式,前提是元素有唯一ID。

ini 复制代码
element = driver.find_element(By.ID, "username")

2. Name定位

通过name属性定位

ini 复制代码
element = driver.find_element(By.NAME, "password")

3. XPath定位

功能强大的定位方式,可通过元素路径、属性等定位。

css 复制代码
# 绝对路径(脆弱,不推荐)
element = driver.find_element(By.XPATH, "/html/body/div[1]/form/input[1]")
# 相对路径(更健壮)
element = driver.find_element(By.XPATH, "//input[@placeholder='请输入用户名']")
# 包含文本
element = driver.find_element(By.XPATH, "//button[contains(text(),'登录')]")

4. CSS选择器定位

语法简洁,性能优于XPath。

ini 复制代码
# 通过class定位
element = driver.find_element(By.CSS_SELECTOR, ".submit-btn")
# 通过属性定位
element = driver.find_element(By.CSS_SELECTOR, "input[type='email']")
# 层级组合
element = driver.find_element(By.CSS_SELECTOR, "div.form-group > input")

5. 链接文本定位

专门用于定位超链接。

ini 复制代码
# 完整文本匹配
element = driver.find_element(By.LINK_TEXT, "忘记密码?")
# 部分文本匹配
element = driver.find_element(By.PARTIAL_LINK_TEXT, "忘记")

6. 类名定位

通过class属性定位。

ini 复制代码
element = driver.find_element(By.CLASS_NAME, "btn-primary")

7. 标签名定位

通过HTML标签名定位。

ini 复制代码
element = driver.find_element(By.TAG_NAME, "h1")

定位策略优先级建议:ID > Name > CSS选择器 > XPath > 其他。优先使用稳定且唯一的标识符。

常用浏览器操作

掌握元素定位后,我们来看常用的浏览器操作:

窗口管理

ini 复制代码
# 获取当前窗口句柄
current_window = driver.current_window_handle
# 获取所有窗口句柄
all_windows = driver.window_handles
# 切换窗口
driver.switch_to.window(all_windows[1])
# 最大化窗口
driver.maximize_window()
# 设置窗口尺寸
driver.set_window_size(1200, 800)
# 前进/后退
driver.forward()
driver.back()
# 刷新页面
driver.refresh()

表单操作

ini 复制代码
# 输入文本
search_input = driver.find_element(By.ID, "search")
search_input.send_keys("测试数据")
# 清空输入框
search_input.clear()
# 单选按钮/复选框
checkbox = driver.find_element(By.ID, "agree")
checkbox.click()  # 选择
checkbox.click()  # 取消选择
# 下拉选择框
from selenium.webdriver.support.ui import Select
dropdown = Select(driver.find_element(By.ID, "city"))
dropdown.select_by_visible_text("北京")  # 按文本选择
dropdown.select_by_value("beijing")     # 按值选择
dropdown.select_by_index(1)             # 按索引选择

鼠标和键盘操作

ini 复制代码
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
# 鼠标悬停
element = driver.find_element(By.ID, "menu")
action = ActionChains(driver)
action.move_to_element(element).perform()
# 双击
action.double_click(element).perform()
# 拖放
source = driver.find_element(By.ID, "source")
target = driver.find_element(By.ID, "target")
action.drag_and_drop(source, target).perform()
# 键盘操作
search_input = driver.find_element(By.ID, "search")
search_input.send_keys("自动化测试")
search_input.send_keys(Keys.ENTER)  # 按回车键

等待机制

动态加载内容是现代Web应用的常见特性,因此等待机制对自动化测试至关重要。

1. 强制等待(不推荐)

css 复制代码
import time
time.sleep(5)  # 无条件等待5秒

缺点:固定等待时间,无论页面是否加载完成。

2. 隐式等待

设置全局等待时间,在查找元素时如未立即找到会轮询等待。

bash 复制代码
driver.implicitly_wait(10)  # 设置隐式等待10秒

缺点:只能用于元素查找,不能处理其他条件。

3. 显式等待(推荐)

针对特定条件等待,更加灵活智能。

vbnet 复制代码
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 等待元素可点击
element = WebDriverWait(driver, 10).until(
    EC.element_to_be_clickable((By.ID, "submit-btn"))
)
# 等待元素可见
element = WebDriverWait(driver, 10).until(
    EC.visibility_of_element_located((By.ID, "result"))
)
# 等待元素存在(不一定可见)
element = WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "dynamic-content"))
)
# 等待文本出现在元素中
element = WebDriverWait(driver, 10).until(
    EC.text_to_be_present_in_element((By.ID, "status"), "成功")
)

常用预期条件:

  • element_to_be_clickable:元素可点击
  • visibility_of_element_located:元素可见
  • presence_of_element_located:元素存在于DOM
  • text_to_be_present_in_element:元素包含特定文本
  • title_contains:页面标题包含特定文本
  • alert_is_present:出现警告框

第一个完整的测试用例

现在我们综合运用以上知识,创建一个完整的测试用例:

python 复制代码
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.service import Service
import unittest
import time
class BaiduSearchTest(unittest.TestCase):

    def setUp(self):
        """测试前置条件"""
        self.driver_path = "./chromedriver"
        self.service = Service(executable_path=self.driver_path)
        self.driver = webdriver.Chrome(service=self.service)
        self.driver.maximize_window()
        self.wait = WebDriverWait(self.driver, 10)

    def tearDown(self):
        """测试清理"""
        if self.driver:
            self.driver.quit()

    def test_search_functionality(self):
        """测试百度搜索功能"""
        driver = self.driver
        wait = self.wait

        # 打开百度首页
        driver.get("https://www.baidu.com")

        # 验证页面标题
        self.assertIn("百度一下", driver.title)

        # 定位搜索框并输入关键词
        search_input = wait.until(
            EC.presence_of_element_located((By.ID, "kw"))
        )
        search_input.send_keys("Selenium自动化测试")

        # 点击搜索按钮
        search_btn = driver.find_element(By.ID, "su")
        search_btn.click()

        # 等待搜索结果加载
        results = wait.until(
            EC.presence_of_element_located((By.ID, "content_left"))
        )

        # 验证搜索结果页面标题
        self.assertIn("Selenium自动化测试", driver.title)

        # 验证搜索结果包含预期内容
        self.assertIn("selenium", driver.page_source.lower())
if __name__ == "__main__":
    unittest.main()

这个测试用例使用了Python的unittest框架,包含了标准的测试结构:

  • setUp:测试前的准备工作
  • test_*:具体的测试方法
  • tearDown:测试后的清理工作

测试框架搭建

单个测试脚本难以应对复杂项目,我们需要建立测试框架来更好地组织和管理测试。

项目目录结构

bash 复制代码
automation_framework/
├── config/
│   ├── __init__.py
│   └── config.py          # 配置文件
├── pages/                 # 页面对象模型
│   ├── __init__.py
│   ├── base_page.py      # 基础页面类
│   ├── home_page.py      # 首页
│   └── search_page.py    # 搜索结果页
├── tests/                # 测试用例
│   ├── __init__.py
│   ├── test_search.py
│   └── test_login.py
├── utils/               # 工具类
│   ├── __init__.py
│   └── helper.py
├── reports/             # 测试报告
├── logs/               # 日志文件
└── requirements.txt    # 依赖列表

页面对象模式(Page Object Model)

页面对象模式是自动化测试的最佳实践,它将页面封装成对象,提高代码复用性和可维护性。

base_page.py - 基础页面类:

python 复制代码
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class BasePage:
    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)

    def find_element(self, by, value):
        return self.wait.until(EC.presence_of_element_located((by, value)))

    def find_clickable_element(self, by, value):
        return self.wait.until(EC.element_to_be_clickable((by, value)))

    def click(self, by, value):
        element = self.find_clickable_element(by, value)
        element.click()

    def input_text(self, by, value, text):
        element = self.find_element(by, value)
        element.clear()
        element.send_keys(text)

home_page.py - 首页封装:

python 复制代码
from selenium.webdriver.common.by import By
from .base_page import BasePage
class HomePage(BasePage):
    # 元素定位器
    SEARCH_INPUT = (By.ID, "kw")
    SEARCH_BUTTON = (By.ID, "su")

    def __init__(self, driver):
        super().__init__(driver)
        self.url = "https://www.baidu.com"

    def open(self):
        self.driver.get(self.url)
        return self

    def search(self, keyword):
        self.input_text(*self.SEARCH_INPUT, keyword)
        self.click(*self.SEARCH_BUTTON)
        return SearchPage(self.driver)

search_page.py - 搜索结果页封装:

python 复制代码
from selenium.webdriver.common.by import By
from .base_page import BasePage
class SearchPage(BasePage):
    RESULTS = (By.ID, "content_left")

    def get_results_count(self):
        results = self.find_element(*self.RESULTS)
        return len(results.find_elements(By.TAG_NAME, "div"))

    def is_results_displayed(self):
        try:
            return self.find_element(*self.RESULTS).is_displayed()
        except:
            return False

配置文件

config.py - 统一配置管理:

ini 复制代码
class Config:
    # 浏览器配置
    BROWSER = "chrome"
    CHROME_DRIVER_PATH = "./chromedriver"
    IMPLICIT_WAIT = 10
    EXPLICIT_WAIT = 10

    # 测试环境
    BASE_URL = "https://www.baidu.com"

    # 测试数据
    TEST_SEARCH_KEYWORD = "自动化测试"

改进的测试用例

使用页面对象模式重构测试用例:

python 复制代码
import unittest
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from config.config import Config
from pages.home_page import HomePage
class TestBaiduSearch(unittest.TestCase):

    def setUp(self):
        config = Config()
        service = Service(executable_path=config.CHROME_DRIVER_PATH)
        self.driver = webdriver.Chrome(service=service)
        self.driver.implicitly_wait(config.IMPLICIT_WAIT)
        self.driver.maximize_window()

    def tearDown(self):
        if self.driver:
            self.driver.quit()

    def test_search_functionality(self):
        """使用页面对象模式测试搜索功能"""
        home_page = HomePage(self.driver)
        search_page = home_page.open().search("自动化测试")

        # 验证搜索结果
        self.assertTrue(search_page.is_results_displayed())
        self.assertGreater(search_page.get_results_count(), 0)
if __name__ == "__main__":
    unittest.main()

高级技巧与最佳实践

1. 数据驱动测试

使用参数化减少代码重复:

kotlin 复制代码
import unittest
from ddt import ddt, data, unpack

@ddt
class DataDrivenTest(unittest.TestCase):

    @data(("Python", 10), ("Java", 8), ("自动化测试", 5))
    @unpack
    def test_search_different_keywords(self, keyword, min_results):
        # 测试逻辑
        home_page = HomePage(self.driver)
        search_page = home_page.open().search(keyword)

        results_count = search_page.get_results_count()
        self.assertGreaterEqual(results_count, min_results)

2. 失败截图

测试失败时自动截图:

python 复制代码
def tearDown(self):
    if hasattr(self, '_outcome') and self._outcome.errors:
        # 测试失败
        screenshot_path = f"screenshots/failure_{self.id()}.png"
        self.driver.save_screenshot(screenshot_path)
        print(f"测试失败,截图已保存至: {screenshot_path}")

    if self.driver:
        self.driver.quit()

3. 日志记录

添加详细日志帮助调试:

ini 复制代码
import logging

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler("logs/automation.log"),
        logging.StreamHandler()
    ]
)

logger = logging.getLogger(__name__)

# 在测试中使用日志
logger.info("开始执行测试: %s", self.id())
logger.debug("搜索关键词: %s", keyword)
logger.error("测试失败: %s", str(e))

4. 测试报告

使用HTMLTestRunner生成美观的测试报告:

ini 复制代码
import HTMLTestRunner
import unittest
import os

# 发现测试用例
test_dir = "./tests"
discover = unittest.defaultTestLoader.discover(test_dir, pattern="test_*.py")

# 生成报告
report_path = "./reports/test_report.html"
with open(report_path, "wb") as f:
    runner = HTMLTestRunner.HTMLTestRunner(
        stream=f,
        title="自动化测试报告",
        description="测试结果详情"
    )
    runner.run(discover)

常见问题与解决方案

1. 元素定位失败

  • 原因:元素未加载、iframe嵌套、动态ID等
  • 解决方案
    • 增加等待时间
    • 使用更稳定的定位策略
    • 处理iframe:driver.switch_to.frame("frame_name")

2. 浏览器兼容性问题

  • 解决方案
    • 使用WebDriver管理器自动下载匹配的驱动
    • 跨浏览器测试:分别配置不同浏览器的驱动

3. 执行速度慢

  • 优化方法
    • 减少不必要的等待
    • 使用headless模式(无界面)
    • 并行执行测试用例

4. 测试稳定性差

  • 改进措施
    • 增加重试机制
    • 使用更可靠的定位方式
    • 定期维护测试用例

持续学习路径

掌握了基础WebUI自动化测试后,你可以继续深入学习:

  1. 移动端自动化:Appium框架
  2. API测试:Requests + Pytest
  3. 性能测试:JMeter、LoadRunner
  4. 测试框架进阶:Pytest、Robot Framework
  5. 持续集成:Jenkins、GitLab CI集成自动化测试
  6. 容器化:Docker运行测试环境

结语

自动化测试是一门既需要技术能力,又需要测试思维的综合技能。通过本文,你已经了解了从环境搭建到框架设计的完整流程。记住,实践是最好的老师------从简单的测试用例开始,逐步构建复杂的测试场景,不断优化和改进你的测试框架。

自动化测试不是一蹴而就的,需要持续的学习和实践。希望这篇文章能为你的自动化测试之旅奠定坚实基础,助你在质量保障的道路上越走越远!

本文原创于【程序员二黑】公众号,转载请注明出处!

欢迎大家关注笔者的公众号:程序员二黑,专注于软件测试干活分享,全套测试资源可免费分享!

最后如果你想学习软件测试,欢迎加入笔者的交流群:785128166,里面会有很多资源和大佬答疑解惑,我们一起交流一起学习!

相关推荐
zhonghaoxincekj11 小时前
晶体管的定义,晶体管测量参数和参数测量仪器
功能测试·单片机·学习·测试工具·单元测试·制造
从前慢,现在也慢11 小时前
(3)Bug篇
学习·bug·测试
川石课堂软件测试1 天前
自动化测试之 Cucumber 工具
数据库·功能测试·网络协议·测试工具·mysql·单元测试·prometheus
执剑、天涯1 天前
通过一个typescript的小游戏,使用单元测试实战(二)
javascript·typescript·单元测试
啊森要自信1 天前
【GUI自动化测试】Python 自动化测试框架 pytest 全面指南:基础语法、核心特性(参数化 / Fixture)及项目实操
开发语言·python·ui·单元测试·pytest
川石课堂软件测试2 天前
MySQL数据库之DBA命令
数据库·网络协议·mysql·http·单元测试·prometheus·dba
lifewange2 天前
幂等机制
功能测试·单元测试
=>>漫反射=>>3 天前
单元测试 vs Main方法调试:何时使用哪种方式?
java·spring boot·单元测试
霍格沃兹_测试3 天前
软件测试 | 测试开发 | 一文带你了解K8S容器编排(上)
测试