selenium + java:如何下载 PDF 并以不同的文件名保存?
可以复用当前 Selenium 登录会话。点击“generate document”,等待文档生成后,用 Java 的 HTTP 客户端请求包含 UUID 的 PDF 地址,将响应内容保存到指定路径。保存时直接指定所需文件名,无须先下载再重命名,也无须新建 Firefox 实例。
前提是该地址能通过 HTTP 请求返回 PDF,且下载请求携带了服务器要求的认证信息。
为什么不需要点击 PDF 下载按钮
浏览器直接显示 PDF 时,会使用内置查看器处理响应。取得实际的 PDF 地址后,就可以直接请求文件内容,无须操作查看器里的下载按钮。
Selenium 官方也建议通过 Selenium 获取下载链接和所需 Cookie,再用 HTTP 请求库下载。常规 WebDriver API 不提供浏览器下载进度,因此测试时较难判断文件何时下载完成。Selenium 文件下载说明
下载请求需要单独处理认证。新建的浏览器实例通常不会自动继承原实例的登录状态,单独创建的 Java HTTP 客户端也不会自动使用浏览器 Cookie。题目中的 agent config session error,仅凭错误名称无法确定具体原因。
处理步骤
- 使用现有、已登录的
WebDriver点击生成按钮。 - 确认文档已经生成,取得包含 UUID 的实际下载 URL。
- 从当前浏览器会话提取下载所需的 Cookie。
- 通过 HTTP GET 请求下载,检查状态码和响应内容。
- 将 PDF 写入指定目录,使用自定义文件名。
如果服务器异步生成文档,拿到 UUID 可能只表示任务已创建。应等页面显示完成状态,或通过业务提供的任务状态接口确认文档就绪,再开始下载。
Java 代码示例
示例使用 Java 11 及以上版本自带的 java.net.http.HttpClient,项目仍需保留现有 Selenium 依赖。BodyHandlers.ofByteArray() 将响应体读入字节数组,适合大小可控的测试文档。Java BodyHandlers 文档
使用此示例需满足以下条件:
- 当前浏览器页面与 PDF 地址同源,即协议、主机和有效端口一致。
- 下载使用 Cookie 认证,且提取出的 Cookie 均适用于下载路径。
- URL 直接返回 PDF,不需要跳转到其他地址。
import org.openqa.selenium.WebDriver;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.Duration;
import java.util.stream.Collectors;
public final class PdfDownloader {
private PdfDownloader() {
}
public static Path download(
WebDriver driver, URI pdfUri, Path target)
throws IOException, InterruptedException {
URI pageUri = URI.create(driver.getCurrentUrl());
if (!"https".equalsIgnoreCase(pdfUri.getScheme())
&& !"http".equalsIgnoreCase(pdfUri.getScheme())) {
throw new IllegalArgumentException(
"PDF 地址必须是 HTTP 或 HTTPS URL");
}
// 此示例只向当前页面的同源地址发送 Cookie。
if (pdfUri.getHost() == null
|| !pdfUri.getScheme()
.equalsIgnoreCase(pageUri.getScheme())
|| !pdfUri.getHost()
.equalsIgnoreCase(pageUri.getHost())
|| effectivePort(pdfUri) != effectivePort(pageUri)) {
throw new IllegalArgumentException(
"当前页面与 PDF 地址不同源,请单独处理下载认证");
}
// 前提:这些 Cookie 均适用于 PDF 下载路径。
String cookieHeader = driver.manage().getCookies().stream()
.map(cookie -> cookie.getName() + "=" + cookie.getValue())
.collect(Collectors.joining("; "));
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(15))
.followRedirects(HttpClient.Redirect.NEVER)
.build();
HttpRequest.Builder request = HttpRequest.newBuilder(pdfUri)
.timeout(Duration.ofSeconds(60))
.header("Accept", "application/pdf")
.GET();
if (!cookieHeader.isEmpty()) {
request.header("Cookie", cookieHeader);
}
HttpResponse<byte[]> response = client.send(
request.build(),
HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() != 200) {
throw new IOException(
"PDF 下载失败,HTTP 状态码:"
+ response.statusCode());
}
byte[] data = response.body();
// 基础检查:避免把登录页或错误页保存为 .pdf。
if (data.length < 5
|| !"%PDF-".equals(new String(
data, 0, 5, StandardCharsets.US_ASCII))) {
throw new IOException(
"响应内容不是预期的 PDF,请检查认证和下载地址");
}
Path destination = target.toAbsolutePath().normalize();
Files.createDirectories(destination.getParent());
// 写入完成后再移到目标路径。
Path temporary = Files.createTempFile(
destination.getParent(), "pdf-", ".part");
try {
Files.write(temporary, data);
Files.move(
temporary,
destination,
StandardCopyOption.REPLACE_EXISTING);
return destination;
} finally {
Files.deleteIfExists(temporary);
}
}
private static int effectivePort(URI uri) {
if (uri.getPort() != -1) {
return uri.getPort();
}
return "https".equalsIgnoreCase(uri.getScheme()) ? 443 : 80;
}
}
在现有测试流程中调用:
// driver 是现有、已登录的 WebDriver。
// 此前已点击生成按钮,并确认文档生成完成。
// serviceUrl 和 uuid 使用业务流程实际返回的值。
URI pdfUri = URI.create(serviceUrl + "/" + uuid);
Path savedFile = PdfDownloader.download(
driver,
pdfUri,
Path.of("test-output", "pdf", "验收记录-" + uuid + ".pdf"));
System.out.println("PDF 已保存到:" + savedFile);
URL 的拼接方式需按实际接口调整。如果业务已返回完整下载地址,直接使用即可。目标路径的最后一段就是本地文件名,服务器返回的 Content-Disposition 文件名不影响这里的保存名称。
认证失败时检查什么
driver.manage().getCookies() 读取当前域的 Cookie,因此调用下载方法时,应让 driver 停留在已登录的业务页面上。WebDriver Cookie API
如果下载请求失败,可对照浏览器中实际成功的 PDF 请求逐项检查:
- 返回
401或403:确认认证 Cookie 是否齐全、是否过期,以及接口是否还要求Authorization等请求头。 - 返回
302等重定向状态:检查Location。它可能指向登录页,也可能指向真正的文件地址。 - 返回
200,但内容是 HTML:服务器可能返回了登录页或错误页,不能仅凭状态码认定下载成功。 - URL 中带签名参数:完整保留这些参数,并确认链接尚未过期。
示例关闭了自动重定向,便于发现认证跳转。如果业务需要重定向,应先核实目标地址,再决定可以向该地址发送哪些凭据。
如果 PDF 位于另一个域,或 Cookie 有不同的 Path、Secure 限制,需要按下载地址筛选 Cookie,或通过独立的认证流程取得凭据。使用 Bearer Token、客户端证书或其他认证机制时,仅复制 Cookie 并不足够。
保存与测试时的注意事项
HTTP 下载由 Java 进程执行,因此 Chrome 是否运行在 headless mode 不影响这段代码,也无须配置浏览器下载目录。
目标目录位于运行 Java 测试进程的机器上。使用 Selenium Grid 时,通常是测试执行器所在机器,而不是远端浏览器节点。Java 测试进程还必须能够访问 PDF 服务。
示例使用 REPLACE_EXISTING,会覆盖同名文件。并行测试应在文件名中加入测试运行标识,避免互相覆盖,也避免将旧文件误判为本次下载结果。
%PDF- 检查只能排除明显的错误响应,不能证明 PDF 完整或内容正确。如果文件要作为流程完成的证据,还应按验收要求检查文档能否解析,以及其中的订单号、日期等业务字段。较大的 PDF 应改用流式下载,并为读取过程设置相应的超时和失败清理机制。
备注:内容仅供参考。
