UI自动化测试指南(二):常用方法

那么上一篇,小编讲到一些自动化测试中,一些概念讲解

那么接下来,小编继续分享下基于selenium框架下,一些常用方法

构建依赖环境:

复制代码
java 复制代码
<!-- 请根据需要选择版本 -->
<!-- 测试框架依赖 -->
<dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-java</artifactId>
    <version>4.0.0</version>
</dependency>
<!-- 驱动管理依赖 -->
<dependency>
    <groupId>io.github.bonigarcia</groupId>
    <artifactId>webdrivermanager</artifactId>
    <version>5.8.0</version>
    <scope>test</scope>
</dependency>

构建第一个驱动程序:

复制代码
java 复制代码
public void test01(){
    //安装驱动管理
    WebDriverManager.chromedriver().setup();
    //设置谷歌浏览器选项
    ChromeOptions options=new ChromeOptions();
    options.addArguments("--remote-allow-origins=*");
    //获取浏览器驱动对象
    WebDriver driver=new ChromeDriver(options);

    //获取网页链接标题
    driver.get("https://www.baidu.com");
    String title=driver.getTitle();
    if(title.contains("百度")){
        System.out.println("测试通过!");
    }else {
        System.out.println("测试失败!");
    }

    //关闭驱动链接
    driver.quit();
}

运行结果:

解析代码:

WebDriverManager.chromedriver().setup();

这里以谷歌浏览器作为例子

这行代码作用是:

自动下载当前系统中chrome浏览器版本兼容的chromeDriver,并将其路径设置到系统属性中,以便selenium能正确启动Chrome浏览器

注意:

  • setUp()方法是不会启动浏览器的,它只是"准备驱动"

  • 首次运行会稍慢,因为要先下载驱动,后续运行后会从缓存加载,速度快

这里也有解决方案:

复制代码
java 复制代码
    //下载驱动
WebDriverManager.chromedriver()
        //固定版本测试(需要本地已有该驱动包,以及本地浏览器大版本对应)
        .driverVersion("141.0.7390.123")
        .avoidBrowserDetection()  //关闭自动检测
        .setup();
  • 如若是网络问题,那么可以配置镜像源

配置镜像源:

复制代码
java 复制代码
WebDriverManager.chromedriver()
    .driverRepositoryUrl(URI.create("https://npmmirror.com/mirrors/chromedriver"))
    .setup();

作用: 解决因CORS(跨域)安全策略导致的DevToolsActivePort或链接拒绝错误,尤其是在Selenium 4.8+ 与新版的Chrome(V111+)配合使用时

原因:

自Chrome 111版本开始,Google加强了DevTools协议的安全策略,默认禁止远程(remote)连接未授权的源(Origin)

而Selenium通过ChromeDirver 与 Chrome浏览器进行通信时,本质上是通过WebSocket 连接到DevTools的调试端口,这被Chrome视为"远程连接"

  • --remote-allow-origins 是 Chrome 的一个命令行启动参数;

  • * 表示允许所有来源(origins)进行远程连接;

  • 这样 Chrome 就会接受来自 ChromeDriver(localhost 或 127.0.0.1)的 DevTools 连接请求。

注意:* 是通配符,表示"全部允许"。在生产环境或高安全要求场景中,建议指定具体来源,例如:

复制代码

options.addArguments("--remote-allow-origins=http://localhost:12345");

WebDriver driver=new ChromeDriver(options);

核心作用:

创建一个 WebDriver 实例,启动一个由 ChromeDriver 控制的 Chrome 浏览器窗口,并应用你指定的启动选项(如无头模式、允许跨域等)

那么以上就是第一个驱动程序,代码简单讲解。

接下来,就是重头戏了,毕竟Web自动化核心就是找到页面对应的元素,然后对元素进行相应操作。

一、元素定位:

铺垫: findElement:表示查找元素,findElements:表示查找多个元素

该方法需要提供一个重要的参数By.xxx

By:

它是selenium的"定位器工厂",即是一个抽象基类,它本身不执行查找操作,而是封装了"如何定位元素"的策略

可以理解为"查找元素方式的描述符"

1.1 cssSelector:

其原本是CSS中用来选定HTML元素并应用样式的语法。

Selenium 借用了这一标准语法,将其作为定位页面元素的一种方式

优势:

  • 语法简洁、表达力强

  • 支持层级、属性、伪类等复杂匹配

  • 执行速度快(浏览器原生支持)

  • 比 XPath 更轻量、更推荐用于现代 Web 自动化

举例:

常见语法: 示例页面:

复制代码
html 复制代码
<input id="username" class="form-input" type="text" placeholder="请输入用户名">
<button class="btn primary" data-action="submit">登录</button>
<div id="container">
  <span class="error-message">用户名不能为空</span>
</div>

标签名选中:

复制代码

driver.findElement(By.cssSelector("input"));

此时是匹配第一个<input>元素

ID名选中

复制代码

driver.findElement(By.cssSelector("#username"));

#表示ID选择器

class选中

复制代码

driver.findElement(By.cssSelector(".form-input")); driver.findElement(By.cssSelector(".btn.primary")); // 同时包含 btn 和 primary 两个 class

. 表示 class 选择器;多个 class 连写(无空格)表示"同时拥有"

属性选中:

复制代码

driver.findElement(By.cssSelector("input[type='text']")); driver.findElement(By.cssSelector("button[data-action='submit']"));

[属性名='值'] 精确匹配属性

组合定位选中(层级关系)

复制代码

// 在 #container 内部查找 .error-message driver.findElement(By.cssSelector("#container .error-message")); // 直接子元素(> 表示父子关系) driver.findElement(By.cssSelector("#container > span"));

伪类选择器选中(常用于动态元素)

复制代码

// 第一个 input driver.findElement(By.cssSelector("input:first-child")); // 最后一个 button driver.findElement(By.cssSelector("button:last-of-type"));

1.2 Xpath

Xpath最初用于XML文档导航设计的,但由于HTML本质是一种"宽松"的XML,因此XPath也被广泛用于在网页中查找元素

Selenium支持通过By.xpath()使用Xpath表达式定位元素上的任意元素

核心能力:

  • 按元素层级路径定位

  • 按文本内容定位

  • 向上查找父元素(CSS Selector 无法做到!)

  • 使用逻辑运算符(and/or)、函数(contains, text(), starts-with 等)

Xpath常用语法: 获取到HTML页面所有的节点:

//*

获取HTML页面上指定的节点

//[指定节点]

//ul:获取HTML页面所有的ul节点

//input:获取HTML页面上所有的input节点

获取该节点下的某个子节点

/

//span/input

获取一个节点的父节点

..

//input/.. :获取input节点的父节点

实现节点属性的匹配: [@...]

//*[@id='kw'] 匹配HTML页面中id属性为KW的节点

使用指定索引的方式来获取对应的节点内容:

注意:xpath的索引从1开始

//*[@id="hotsearch-content-wrapper"]/li[3] :百度首页的热搜第三个

常用语法:

页面示例:

复制代码
html 复制代码
<div class="form-container">
  <label for="email">邮箱</label>
  <input id="email_123abc" type="email" placeholder="请输入邮箱">
  <button class="btn">提交</button>
  <div class="alert error">邮箱格式错误</div>
</div>

绝对路径:

复制代码

driver.findElement(By.xpath("/html/body/div[1]/input"))

脆弱:页面结构一变就失效

相对路径(推荐)

复制代码

driver.findElement(By.xpath("//input")); // 任意位置的第一个 input

// 开头,从任意位置开始匹配

按属性定位

复制代码

driver.findElement(By.xpath("//input[@id='email_123abc']")); driver.findElement(By.xpath("//input[@type='email']"));

按文本内容定位

复制代码

driver.findElement(By.xpath("//button[text()='提交']")); driver.findElement(By.xpath("//div[contains(text(), '邮箱格式错误')]"));

使用函数:contains(), starts-with()

复制代码

// ID 动态但包含 "email" driver.findElement(By.xpath("//input[contains(@id, 'email')]")); // placeholder 以 "请输入" 开头 driver.findElement(By.xpath("//input[starts-with(@placeholder, '请输入')]"));

轴(Axes):定位父级、兄弟等

复制代码

// 找到"邮箱"label 的下一个兄弟 input driver.findElement(By.xpath("//label[text()='邮箱']/following-sibling::input")); // 找到包含"错误"的 div 的父容器 driver.findElement(By.xpath("//div[contains(text(), '错误')]/.."));

逻辑组合(and / or)

复制代码

// 同时满足 type=email 且 placeholder 包含"邮箱" driver.findElement(By.xpath("//input[@type='email' and contains(@placeholder, '邮箱')]"));

Xpath和cssSelector区别:

表格 还在加载中,请等待加载完成后再尝试复制

当然,除了这个,findElement还支持其他寻找方式:

复制代码

driver.findElement(By.id("kw"));

这个是代表寻找页面中,寻找id为kw的元素

复制代码

driver.findElement(By.name("wd"));

这个是代表寻找HTML标签定义的name属性值

二:操作测试对象

场景:

有以下页面:

当我们需要进行文本输入,按钮点击,文本清空再输入时,就可以使用以下方法

文本点击

复制代码

//举例,百度一下 WebElement element=driver.findElement(By.cssSelector("#chat-submit-button") element.click()

文本输入

复制代码

//举例,百度搜索框输入 WebElement element=driver.findElement(By.cssSelector("#chat-textarea") element.sendKeys("UI自动化测试")

文本清空

复制代码

//举例,百度搜索框输入 WebElement element=driver.findElement(By.cssSelector("#chat-textarea") element.sendKeys("UI自动化测试") element.clear()

除此之外,对于选中的元素还可以获取它的文本内容

获取文本内容:

复制代码

//举例,获取文本内容 WebElement element=driver.findElement(By.cssSelector("#hotsearch-content-wrapper > li:nth-child(2) > a > span.title-content-title") Strinig text=element.getText()

值得一提的是,driver也提供两个较为常用的方法

getTitle():获取当前页面标题信息

getCurrentUrl():获取当前页面的URL

三、窗口切换

场景:

从百度搜索页点击图片超链接进入图片页面

那么,可以观察到出现了两个窗口,那么也有对应的方法可操控

先值得一说的是,driver方法指的是当前页面的,当跳到第二个页面时,也不可操作第二个页面的元素

1.获取当前页面句柄方法

复制代码

driver.getWindowHandle()

2.获取所有页面的句柄

复制代码

driver.getWindowHandles()

以下方法是获取最新句柄,

复制代码
java 复制代码
String curWindow = driver.getWindowHandle();           // 获取当前窗口句柄
Set<String> allWindows = driver.getWindowHandles();    // 获取所有窗口句柄

for (String w : allWindows) {
    if (!w.equals(curWindow)) {                        // 注意:应使用 .equals() 而非 !=
        driver.switchTo().window(w);                   // 切换到新窗口
        break; //  建议加上 break!
    }
}

3.窗口大小设置

复制代码
java 复制代码
driver.manage().window().maximize();//窗口最大化

driver.manage().window().minimize();//窗口最小化

driver.manage().window().fullscreen();//全屏窗口

driver.manage().window().setSize(new Dimension(1024,768))//手动设置窗口

4.窗口关闭

复制代码

driver.close() //注意窗口关闭后,driver不再生效,需重新定义

5.屏幕截图

该方法需要引入依赖:

复制代码
java 复制代码
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

这里提供一个方法:

复制代码
java 复制代码
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class ScreenshotUtil {

    private static final Logger logger = LoggerFactory.getLogger(ScreenshotUtil.class);

    // 默认截图根目录(可根据项目结构调整)
    private static final String SCREENSHOT_BASE_DIR = "./screenshots";

    /**
     * 截图并保存到指定目录,文件名格式:{prefix}-{yyyyMMdd-HHmmss}.png
     *
     * @param driver WebDriver 实例
     * @param prefix 文件名前缀,如 "login_error", "goods_browser"
     * @return 截图文件的绝对路径(可用于测试报告嵌入),失败返回 null
     */
    public static String takeScreenshot(WebDriver driver, String prefix) {
        if (driver == null) {
            logger.warn("WebDriver 为 null,无法截图");
            return null;
        }

        try {
            // 1. 生成时间戳
            String datePart = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd", Locale.ENGLISH));
            String timePart = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HHmmss", Locale.ENGLISH));

            // 2. 构建目录路径(按日期分文件夹)
            String folderPath = SCREENSHOT_BASE_DIR + File.separator + "autotest-" + datePart;
            File folder = new File(folderPath);
            if (!folder.exists()) {
                boolean created = folder.mkdirs(); // 自动创建多级目录
                if (!created) {
                    logger.error("无法创建截图目录: {}", folderPath);
                    return null;
                }
            }

            // 3. 构建完整文件名
            String fileName = String.format("%s-%s.png", prefix, timePart);
            //使用 File.separator(Windows 用 \,macOS/Linux 用 /);
            String fullPath = folderPath + File.separator + fileName;

            // 4. 执行截图
            File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);

            // 5. 保存到指定路径
            FileUtils.copyFile(screenshot, new File(fullPath));

            logger.info("截图成功保存: {}", fullPath);
            return new File(fullPath).getAbsolutePath();

        } catch (IOException e) {
            logger.error("截图保存失败", e);
            return null;
        } catch (ClassCastException e) {
            logger.error("当前 WebDriver 不支持截图(需实现 TakesScreenshot 接口)", e);
            return null;
        }
    }
}

4.等待

场景: 由于网络因素,或是代码层级因素,比如网络过慢,规定时间内无法加载元素,代码执行速度过快,无法有效定位元素,所以这些问题,可能会导致结果是误报的。

解决以上场景,可引入等待策略

4.1强制等待

使用Thread.sleep(毫秒) 让线程无条件暂停指定时间

复制代码

Thread.sleep(3000)

  • 特点: 简单粗暴:不管页面是否加载完成,都等满指定时间

  • 不智能:如若元素1秒出现,仍需等待数秒,浪费时间

  • 不稳妥:如若网络慢,3秒不够,依旧会失败

适用场景:

极少数的调试场景(如临时观察页面)

4.2隐式等待

设置一个全局的超时时间,WebDriver 在查找元素时,如若第一次没找到,则隔一段时间重试,直到重试

复制代码

driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));

工作原理:

  • 设置后,对所有 findElement/findElements 调用生效;

  • 如果元素立即存在 → 立即返回;

  • 如果元素不存在 → 每隔几百毫秒重试,最多等 10 秒;

  • 超时后抛出 NoSuchElementException

优点:

简单,一行代码覆盖全局

避免因网络延时导致的偶发失败

缺点:

  • 只对"查找元素"有效,对其他操作(如判断元素是否可点击,弹窗)无效;

  • 一旦设置,全程生效,无法针对特定场景调整;

  • 与显式等待混用可能产生叠加效应(总等待时间 = 隐式 + 显式),导致脚本变慢

4.3显示等待

针对特定条件进行等待,直到条件满足或超时,使用WebDriverWait + ExpectedConditions

复制代码

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); WebElement element = wait.until( ExpectedConditions.elementToBeClickable(By.id("submit-btn")) );

工作原理:

  • 每隔 500ms(默认)检查一次条件;

  • 条件满足 → 立即返回元素或 true;

  • 超时未满足 → 抛出 TimeoutException

常用等待条件:

表格 还在加载中,请等待加载完成后再尝试复制

优点:

  • 精准:只在需要的地方等待;

  • 灵活:可自定义任意等待条件(甚至结合 lambda 表达式);

  • 高效:条件一旦满足立即继续,不浪费时间;

  • 稳定:能处理动态内容、AJAX 加载、弹窗等复杂场景

5.浏览器导航

场景:

当执行页面跳转时,需要进行回退操作,此时需要用到浏览器导航

常见操作: 1.打开网站

复制代码

driver.navigate().to("xxxxxx") //更为简洁方法 driver.get("xxxxxx")

2.浏览器的前进、后退、刷新

复制代码

driver.navigate().back();//后退 driver.navigate().forword();//前进 driver.navigate().refresh();//刷新

6.弹窗

场景: 当对页面进行操作时,弹出弹窗,此时需要进行相关操作

6.1警告弹窗+确认弹窗

复制代码

Alert alert=driver.swithTo.alert(); //确认 alert.accpet() //取消 alert.dismiss()

6.2 提示弹窗

复制代码

Alert alert=driver.swithTo.alert(); //发送文本 alert.sendKeys("hello") //确认 alert.accpet() //取消 alert.dismiss()

7.文件上传

此时,代码操作如下:

复制代码

WebElement element = driver.findElement(By.cssSelector("body > div > div > input[type=file]")); element.sendKeys("xxxxxx")//通过发送关键词,效果类似上传文件

8.浏览器参数设置

为什么设置浏览器参数?

  • 默认启动的浏览器可能包含弹窗、扩展、缓存等干扰自动化;

  • 某些网站会检测自动化工具(如 navigator.webdriver);

  • 需要在 CI/CD 环境中无界面运行(Headless);

  • 模拟真实用户环境(如 User-Agent、地理位置、语言);

  • 提升执行速度或稳定性(如禁用 GPU、沙箱)。

核心类:ChromeOptions

复制代码
java 复制代码
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless=new");          // 无头模式
options.addArguments("--disable-gpu");           // 禁用 GPU 加速
options.addArguments("--no-sandbox");            // 禁用沙箱(Linux/CI 必加)
options.addArguments("--remote-allow-origins=*"); // 允许远程连接(Chrome 111+ 必加)

WebDriver driver = new ChromeDriver(options);

注意:从 Chrome 109+ 开始,推荐使用 --headless=new 替代旧的 --headless,以获得完整 DevTools 支持。

常用参数说明: 基础参数

表格 还在加载中,请等待加载完成后再尝试复制

性能与稳定参数:

表格 还在加载中,请等待加载完成后再尝试复制

绕过安全参数:

表格 还在加载中,请等待加载完成后再尝试复制

模拟真实用户参数

表格 还在加载中,请等待加载完成后再尝试复制

这里有值得一说的是:同源策略

一、什么是同源策略?

同源策略(Same-Origin Policy)是浏览器实施的一种安全机制,用于限制一个源(origin)的文档或脚本如何与另一个源的资源进行交互。

它的核心目的是:防止恶意网站窃取用户数据或操作其他网站的内容。


二、"源"(Origin)由什么决定?

一个 URL 的"源"由以下三部分共同定义:

表格 还在加载中,请等待加载完成后再尝试复制

只有当两个 URL 的协议、主机名、端口完全相同时,才被认为是"同源"。

同源示例:

  • https://www.example.com/page1

  • https://www.example.com/page2?query=123 → 同源(路径和查询参数不影响)

非同源示例:

表格 还在加载中,请等待加载完成后再尝试复制

注意:localhost127.0.0.1 被视为不同源!


三、同源策略限制哪些行为?

浏览器主要限制以下跨源操作:

表格 还在加载中,请等待加载完成后再尝试复制

重点:跨域请求其实会发送到服务器,只是浏览器阻止你读取响应!


四、为什么需要同源策略?------安全场景举例

假设没有同源策略:

  1. 用户登录了银行网站 https:``//bank.com,Cookie 中保存了身份凭证;

  2. 用户又打开了恶意网站 https://evil.com

  3. evil.com 的 JS 发起请求:fetch('https://bank.com/transfer?to=hacker&amount=1000')

  4. 浏览器自动带上 bank.com 的 Cookie → 转账成功!

同源策略阻止了第 4 步中 JS 读取响应或发起敏感操作,从而保护用户。


五、如何合法地"跨域"?------CORS

为支持正当的跨域需求(如前后端分离、CDN),W3C 引入了 CORS(Cross-Origin Resource Sharing,跨域资源共享)。

工作原理:

  1. 浏览器检测到 AJAX 请求跨域;

  2. 自动在请求头中添加 Origin: https://your-site.com

  3. 服务器检查该 Origin 是否被允许;

  4. 若允许,返回响应头:

  5. Http

  6. 编辑

复制代码

1Access-Control-Allow-Origin: https://your-site.com

  1. 浏览器收到后,才将响应内容交给 JS。

关键点:CORS 是服务器控制的,前端无法绕过!

六、常见误区澄清

|------------------|-------------------------------------------|
| 误区 | 正确理解 |
| "跨域是服务器阻止的" | 实际是浏览器阻止,服务器可能根本不知道 |
| "JSONP 是绕过 CORS" | JSONP 利用了 <script> 标签不受同源限制的特性,但只支持 GET |
| "CORS = 允许所有跨域" | CORS 是精细化授权,可控制方法、头、凭证等 |

相关推荐
末央&34 分钟前
【天机论坛】项目环境搭建和数据库设计
java·数据库
枫叶落雨2221 小时前
ShardingSphere 介绍
java
花花鱼1 小时前
Spring Security 与 Spring MVC
java·spring·mvc
言慢行善2 小时前
sqlserver模糊查询问题
java·数据库·sqlserver
专吃海绵宝宝菠萝屋的派大星2 小时前
使用Dify对接自己开发的mcp
java·服务器·前端
大数据新鸟2 小时前
操作系统之虚拟内存
java·服务器·网络
Tong Z2 小时前
常见的限流算法和实现原理
java·开发语言
凭君语未可2 小时前
Java 中的实现类是什么
java·开发语言
He少年2 小时前
【基础知识、Skill、Rules和MCP案例介绍】
java·前端·python
克里斯蒂亚诺更新2 小时前
myeclipse的pojie
java·ide·myeclipse