20. 上传文件、下载文件
目录
文件上传概述
在Web自动化测试中,文件上传是一个常见的功能测试场景。Selenium提供了多种方式来处理文件上传操作,主要包括:
上传文件的常见场景
- 用户头像上传
- 文档附件上传
- 图片上传
- 批量文件上传
- 拖拽上传
上传文件的HTML元素类型
- input[type=”file”] – 最常见的文件上传元素
- 拖拽上传区域 – 需要模拟拖拽操作
- 第三方上传组件 – 如富文本编辑器中的上传功能
文件上传的实现方法
方法一:直接使用sendKeys()方法(推荐)
这是最简单、最可靠的文件上传方法,适用于标准的<input type="file">元素。
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import java.io.File;
public class FileUploadExample {
public static void main(String[] args) {
WebDriver driver = new ChromeDriver();
try {
// 访问包含文件上传功能的页面
driver.get("https://example.com/upload");
// 定位文件上传元素
WebElement fileInput = driver.findElement(By.xpath("//input[@type='file']"));
// 准备要上传的文件路径(绝对路径)
String filePath = System.getProperty("user.dir") + File.separator + "test-files" + File.separator + "sample.txt";
// 直接发送文件路径到input元素
fileInput.sendKeys(filePath);
// 点击上传按钮(如果需要)
WebElement uploadButton = driver.findElement(By.id("upload-btn"));
uploadButton.click();
System.out.println("文件上传成功!");
} catch (Exception e) {
e.printStackTrace();
} finally {
driver.quit();
}
}
}
方法二:处理隐藏的文件上传元素
有些网站的文件上传元素可能被CSS隐藏,需要先让其可见:
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
public class HiddenFileUploadExample {
public static void main(String[] args) {
WebDriver driver = new ChromeDriver();
JavascriptExecutor js = (JavascriptExecutor) driver;
try {
driver.get("https://example.com/upload");
// 定位隐藏的文件上传元素
WebElement hiddenFileInput = driver.findElement(By.xpath("//input[@type='file']"));
// 使用JavaScript使隐藏元素可见
js.executeScript("arguments[0].style.display = 'block';", hiddenFileInput);
js.executeScript("arguments[0].style.visibility = 'visible';", hiddenFileInput);
js.executeScript("arguments[0].style.opacity = '1';", hiddenFileInput);
// 上传文件
String filePath = "C:\Users\Administrator\Desktop\test.jpg";
hiddenFileInput.sendKeys(filePath);
// 触发change事件(某些情况下需要)
js.executeScript("arguments[0].dispatchEvent(new Event('change', {bubbles: true}));", hiddenFileInput);
} catch (Exception e) {
e.printStackTrace();
} finally {
driver.quit();
}
}
}
方法三:多文件上传
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
public class MultipleFileUploadExample {
public static void main(String[] args) {
WebDriver driver = new ChromeDriver();
try {
driver.get("https://example.com/multiple-upload");
// 定位支持多文件上传的元素
WebElement multiFileInput = driver.findElement(By.xpath("//input[@type='file' and @multiple]"));
// 准备多个文件路径,用换行符分隔
String file1 = "C:\Users\Administrator\Desktop\file1.txt";
String file2 = "C:\Users\Administrator\Desktop\file2.jpg";
String file3 = "C:\Users\Administrator\Desktop\file3.pdf";
String multipleFiles = file1 + "n" + file2 + "n" + file3;
// 一次性上传多个文件
multiFileInput.sendKeys(multipleFiles);
// 点击上传按钮
driver.findElement(By.id("upload-multiple-btn")).click();
} catch (Exception e) {
e.printStackTrace();
} finally {
driver.quit();
}
}
}
方法四:使用Robot类处理系统对话框(不推荐)
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.awt.event.KeyEvent;
public class RobotFileUploadExample {
public static void main(String[] args) {
WebDriver driver = new ChromeDriver();
try {
driver.get("https://example.com/upload");
// 点击文件选择按钮,会弹出系统文件选择对话框
WebElement fileButton = driver.findElement(By.id("file-select-btn"));
fileButton.click();
// 等待对话框出现
Thread.sleep(2000);
// 使用Robot类处理系统对话框
Robot robot = new Robot();
// 将文件路径复制到剪贴板
String filePath = "C:\Users\Administrator\Desktop\test.txt";
StringSelection stringSelection = new StringSelection(filePath);
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null);
// 粘贴文件路径
robot.keyPress(KeyEvent.VK_CONTROL);
robot.keyPress(KeyEvent.VK_V);
robot.keyRelease(KeyEvent.VK_V);
robot.keyRelease(KeyEvent.VK_CONTROL);
Thread.sleep(1000);
// 按回车确认
robot.keyPress(KeyEvent.VK_ENTER);
robot.keyRelease(KeyEvent.VK_ENTER);
} catch (Exception e) {
e.printStackTrace();
} finally {
driver.quit();
}
}
}
方法五:拖拽上传
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.JavascriptExecutor;
public class DragDropUploadExample {
public static void main(String[] args) {
WebDriver driver = new ChromeDriver();
JavascriptExecutor js = (JavascriptExecutor) driver;
try {
driver.get("https://example.com/drag-upload");
// 定位拖拽上传区域
WebElement dropZone = driver.findElement(By.id("drop-zone"));
// 使用JavaScript模拟文件拖拽
String filePath = "C:\Users\Administrator\Desktop\test.jpg";
String jsScript =
"var target = arguments[0];" +
"var file = new File(['file content'], '" + filePath + "', {type: 'image/jpeg'});" +
"var dataTransfer = new DataTransfer();" +
"dataTransfer.items.add(file);" +
"var event = new DragEvent('drop', {dataTransfer: dataTransfer});" +
"target.dispatchEvent(event);";
js.executeScript(jsScript, dropZone);
} catch (Exception e) {
e.printStackTrace();
} finally {
driver.quit();
}
}
}
文件下载概述
文件下载在自动化测试中主要涉及:
- 验证下载功能是否正常
- 检查下载的文件内容
- 处理不同类型的下载方式
下载文件的常见场景
- 报表导出(Excel、PDF等)
- 文档下载
- 图片下载
- 软件安装包下载
文件下载的实现方法
方法一:配置浏览器自动下载设置
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
public class FileDownloadExample {
public static void main(String[] args) {
// 设置下载目录
String downloadPath = System.getProperty("user.dir") + File.separator + "downloads";
// 创建下载目录
File downloadDir = new File(downloadPath);
if (!downloadDir.exists()) {
downloadDir.mkdirs();
}
// 配置Chrome选项
ChromeOptions options = new ChromeOptions();
// 设置下载相关参数
Map<String, Object> prefs = new HashMap<>();
prefs.put("profile.default_content_settings.popups", 0);
prefs.put("download.default_directory", downloadPath);
prefs.put("download.prompt_for_download", false);
prefs.put("download.directory_upgrade", true);
prefs.put("safebrowsing.enabled", true);
options.setExperimentalOption("prefs", prefs);
WebDriver driver = new ChromeDriver(options);
try {
driver.get("https://example.com/download-page");
// 点击下载链接
driver.findElement(By.linkText("下载文件")).click();
// 等待下载完成
Thread.sleep(5000);
// 验证文件是否下载成功
File downloadedFile = new File(downloadPath + File.separator + "expected-filename.pdf");
if (downloadedFile.exists()) {
System.out.println("文件下载成功:" + downloadedFile.getAbsolutePath());
System.out.println("文件大小:" + downloadedFile.length() + " bytes");
} else {
System.out.println("文件下载失败");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
driver.quit();
}
}
}
方法二:等待下载完成的工具方法
import java.io.File;
import java.time.Duration;
import java.time.Instant;
public class DownloadUtils {
/**
* 等待文件下载完成
* @param downloadPath 下载目录路径
* @param fileName 期望的文件名
* @param timeoutSeconds 超时时间(秒)
* @return 下载是否成功
*/
public static boolean waitForDownload(String downloadPath, String fileName, int timeoutSeconds) {
File downloadDir = new File(downloadPath);
File expectedFile = new File(downloadPath + File.separator + fileName);
Instant startTime = Instant.now();
while (Duration.between(startTime, Instant.now()).getSeconds() < timeoutSeconds) {
// 检查目标文件是否存在
if (expectedFile.exists() && expectedFile.length() > 0) {
// 检查是否还有临时下载文件(.crdownload, .tmp等)
boolean hasTemporaryFiles = false;
File[] files = downloadDir.listFiles();
if (files != null) {
for (File file : files) {
String name = file.getName().toLowerCase();
if (name.endsWith(".crdownload") || name.endsWith(".tmp") || name.endsWith(".part")) {
hasTemporaryFiles = true;
break;
}
}
}
if (!hasTemporaryFiles) {
return true;
}
}
try {
Thread.sleep(1000); // 等待1秒后再检查
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
return false;
}
/**
* 获取下载目录中最新的文件
* @param downloadPath 下载目录路径
* @return 最新下载的文件
*/
public static File getLatestDownloadedFile(String downloadPath) {
File downloadDir = new File(downloadPath);
File[] files = downloadDir.listFiles();
if (files == null || files.length == 0) {
return null;
}
File latestFile = files[0];
for (File file : files) {
if (file.lastModified() > latestFile.lastModified()) {
latestFile = file;
}
}
return latestFile;
}
/**
* 清空下载目录
* @param downloadPath 下载目录路径
*/
public static void clearDownloadDirectory(String downloadPath) {
File downloadDir = new File(downloadPath);
File[] files = downloadDir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isFile()) {
file.delete();
}
}
}
}
}
方法三:处理不同浏览器的下载
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.firefox.FirefoxProfile;
import java.util.HashMap;
import java.util.Map;
public class BrowserDownloadConfig {
public static WebDriver createChromeDriverWithDownload(String downloadPath) {
ChromeOptions options = new ChromeOptions();
Map<String, Object> prefs = new HashMap<>();
prefs.put("profile.default_content_settings.popups", 0);
prefs.put("download.default_directory", downloadPath);
prefs.put("download.prompt_for_download", false);
prefs.put("download.directory_upgrade", true);
prefs.put("safebrowsing.enabled", true);
prefs.put("plugins.always_open_pdf_externally", true); // PDF文件直接下载而不是在浏览器中打开
options.setExperimentalOption("prefs", prefs);
return new ChromeDriver(options);
}
public static WebDriver createFirefoxDriverWithDownload(String downloadPath) {
FirefoxProfile profile = new FirefoxProfile();
// 设置下载目录
profile.setPreference("browser.download.dir", downloadPath);
profile.setPreference("browser.download.folderList", 2);
// 禁用下载对话框
profile.setPreference("browser.download.useDownloadDir", true);
profile.setPreference("browser.helperApps.neverAsk.saveToDisk",
"application/pdf,application/octet-stream,application/x-winzip,application/x-pdf,application/x-gzip");
// 禁用PDF查看器
profile.setPreference("pdfjs.disabled", true);
FirefoxOptions options = new FirefoxOptions();
options.setProfile(profile);
return new FirefoxDriver(options);
}
}
方法四:验证下载文件内容
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class FileVerificationUtils {
/**
* 验证文件是否下载完整(通过文件大小)
* @param filePath 文件路径
* @param expectedSize 期望的文件大小
* @return 验证结果
*/
public static boolean verifyFileSize(String filePath, long expectedSize) {
File file = new File(filePath);
return file.exists() && file.length() == expectedSize;
}
/**
* 计算文件的MD5哈希值
* @param filePath 文件路径
* @return MD5哈希值
*/
public static String calculateMD5(String filePath) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
FileInputStream fis = new FileInputStream(filePath);
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
md.update(buffer, 0, bytesRead);
}
fis.close();
byte[] hashBytes = md.digest();
StringBuilder sb = new StringBuilder();
for (byte b : hashBytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
} catch (NoSuchAlgorithmException | IOException e) {
e.printStackTrace();
return null;
}
}
/**
* 验证文件完整性
* @param filePath 文件路径
* @param expectedMD5 期望的MD5值
* @return 验证结果
*/
public static boolean verifyFileIntegrity(String filePath, String expectedMD5) {
String actualMD5 = calculateMD5(filePath);
return expectedMD5.equalsIgnoreCase(actualMD5);
}
}
常见问题和解决方案
1. 文件上传问题
问题:找不到文件上传元素
// 解决方案:使用多种定位策略
WebElement fileInput = null;
// 尝试不同的定位方式
try {
fileInput = driver.findElement(By.xpath("//input[@type='file']"));
} catch (Exception e1) {
try {
fileInput = driver.findElement(By.cssSelector("input[type='file']"));
} catch (Exception e2) {
try {
fileInput = driver.findElement(By.name("file"));
} catch (Exception e3) {
System.out.println("无法找到文件上传元素");
}
}
}
问题:文件路径包含中文或特殊字符
// 解决方案:使用正确的编码处理
import java.nio.file.Paths;
public class FilePathUtils {
public static String getCorrectFilePath(String fileName) {
// 获取当前工作目录
String currentDir = System.getProperty("user.dir");
// 使用Paths.get()处理路径
String filePath = Paths.get(currentDir, "test-files", fileName).toString();
return filePath;
}
}
// 使用示例
String filePath = FilePathUtils.getCorrectFilePath("测试文件.txt");
fileInput.sendKeys(filePath);
问题:上传大文件超时
// 解决方案:增加超时时间和等待
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;
import java.time.Duration;
public void uploadLargeFile(WebDriver driver, String filePath) {
WebElement fileInput = driver.findElement(By.xpath("//input[@type='file']"));
fileInput.sendKeys(filePath);
// 点击上传按钮
WebElement uploadBtn = driver.findElement(By.id("upload-btn"));
uploadBtn.click();
// 等待上传完成的提示信息
WebDriverWait wait = new WebDriverWait(driver, Duration.ofMinutes(5));
wait.until(ExpectedConditions.presenceOfElementLocated(By.id("upload-success")));
}
2. 文件下载问题
问题:下载的文件名不确定
// 解决方案:获取最新下载的文件
public File getLatestDownloadedFile(String downloadPath, String fileExtension) {
File downloadDir = new File(downloadPath);
File[] files = downloadDir.listFiles((dir, name) -> name.toLowerCase().endsWith(fileExtension));
if (files == null || files.length == 0) {
return null;
}
// 按修改时间排序,获取最新的文件
Arrays.sort(files, (f1, f2) -> Long.compare(f2.lastModified(), f1.lastModified()));
return files[0];
}
问题:下载被浏览器阻止
// 解决方案:配置浏览器允许下载
ChromeOptions options = new ChromeOptions();
options.addArguments("--disable-web-security");
options.addArguments("--allow-running-insecure-content");
options.addArguments("--disable-extensions");
Map<String, Object> prefs = new HashMap<>();
prefs.put("profile.default_content_setting_values.automatic_downloads", 1);
options.setExperimentalOption("prefs", prefs);
3. 权限和安全问题
问题:文件路径权限不足
// 解决方案:检查和创建目录权限
public static boolean ensureDirectoryExists(String dirPath) {
File directory = new File(dirPath);
if (!directory.exists()) {
boolean created = directory.mkdirs();
if (!created) {
System.out.println("无法创建目录: " + dirPath);
return false;
}
}
// 检查读写权限
if (!directory.canRead() || !directory.canWrite()) {
System.out.println("目录权限不足: " + dirPath);
return false;
}
return true;
}
最佳实践
1. 文件上传最佳实践
public class FileUploadBestPractices {
/**
* 安全的文件上传方法
*/
public static boolean uploadFile(WebDriver driver, String fileInputLocator, String filePath) {
try {
// 1. 验证文件是否存在
File file = new File(filePath);
if (!file.exists()) {
System.out.println("文件不存在: " + filePath);
return false;
}
// 2. 验证文件大小(避免上传过大文件)
long maxSize = 10 * 1024 * 1024; // 10MB
if (file.length() > maxSize) {
System.out.println("文件过大: " + file.length() + " bytes");
return false;
}
// 3. 等待页面加载完成
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement fileInput = wait.until(ExpectedConditions.presenceOfElementLocated(By.xpath(fileInputLocator)));
// 4. 确保元素可交互
wait.until(ExpectedConditions.elementToBeClickable(fileInput));
// 5. 上传文件
fileInput.sendKeys(file.getAbsolutePath());
// 6. 验证文件是否被选中
String fileName = file.getName();
boolean fileSelected = fileInput.getAttribute("value").contains(fileName);
if (fileSelected) {
System.out.println("文件上传成功: " + fileName);
return true;
} else {
System.out.println("文件上传失败");
return false;
}
} catch (Exception e) {
System.out.println("文件上传异常: " + e.getMessage());
return false;
}
}
}
2. 文件下载最佳实践
public class FileDownloadBestPractices {
/**
* 完整的文件下载和验证流程
*/
public static boolean downloadAndVerifyFile(WebDriver driver, String downloadLinkLocator,
String expectedFileName, String downloadPath) {
try {
// 1. 清空下载目录
DownloadUtils.clearDownloadDirectory(downloadPath);
// 2. 记录下载前的文件列表
File downloadDir = new File(downloadPath);
int fileCountBefore = downloadDir.listFiles() != null ? downloadDir.listFiles().length : 0;
// 3. 点击下载链接
WebElement downloadLink = driver.findElement(By.xpath(downloadLinkLocator));
downloadLink.click();
// 4. 等待下载完成
boolean downloadSuccess = DownloadUtils.waitForDownload(downloadPath, expectedFileName, 30);
if (!downloadSuccess) {
System.out.println("下载超时或失败");
return false;
}
// 5. 验证文件
File downloadedFile = new File(downloadPath + File.separator + expectedFileName);
if (!downloadedFile.exists()) {
System.out.println("下载的文件不存在");
return false;
}
if (downloadedFile.length() == 0) {
System.out.println("下载的文件为空");
return false;
}
// 6. 验证文件数量增加
int fileCountAfter = downloadDir.listFiles().length;
if (fileCountAfter <= fileCountBefore) {
System.out.println("文件数量没有增加");
return false;
}
System.out.println("文件下载并验证成功: " + downloadedFile.getAbsolutePath());
System.out.println("文件大小: " + downloadedFile.length() + " bytes");
return true;
} catch (Exception e) {
System.out.println("文件下载异常: " + e.getMessage());
return false;
}
}
}
3. 完整的测试示例
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.AfterEach;
import static org.junit.jupiter.api.Assertions.*;
public class FileOperationTest {
private WebDriver driver;
private String downloadPath;
@BeforeEach
public void setUp() {
downloadPath = System.getProperty("user.dir") + File.separator + "test-downloads";
driver = BrowserDownloadConfig.createChromeDriverWithDownload(downloadPath);
}
@Test
public void testFileUploadAndDownload() {
try {
// 测试文件上传
driver.get("https://example.com/file-operations");
String testFilePath = createTestFile();
boolean uploadSuccess = FileUploadBestPractices.uploadFile(
driver, "//input[@type='file']", testFilePath);
assertTrue(uploadSuccess, "文件上传应该成功");
// 提交上传
driver.findElement(By.id("upload-submit")).click();
// 等待处理完成
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.presenceOfElementLocated(By.id("upload-success")));
// 测试文件下载
boolean downloadSuccess = FileDownloadBestPractices.downloadAndVerifyFile(
driver, "//a[@id='download-link']", "processed-file.txt", downloadPath);
assertTrue(downloadSuccess, "文件下载应该成功");
} catch (Exception e) {
fail("测试执行失败: " + e.getMessage());
}
}
private String createTestFile() throws IOException {
String testContent = "这是一个测试文件n用于自动化测试n" + System.currentTimeMillis();
String testFilePath = System.getProperty("user.dir") + File.separator + "test-file.txt";
try (FileWriter writer = new FileWriter(testFilePath)) {
writer.write(testContent);
}
return testFilePath;
}
@AfterEach
public void tearDown() {
if (driver != null) {
driver.quit();
}
// 清理测试文件
DownloadUtils.clearDownloadDirectory(downloadPath);
}
}
4. 配置文件管理
// config.properties
download.path=C:/selenium-downloads
upload.max.size=10485760
download.timeout=30
browser.type=chrome
// ConfigManager.java
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class ConfigManager {
private static Properties properties = new Properties();
static {
try {
properties.load(new FileInputStream("config.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}
public static String getDownloadPath() {
return properties.getProperty("download.path", System.getProperty("user.dir") + "/downloads");
}
public static long getMaxUploadSize() {
return Long.parseLong(properties.getProperty("upload.max.size", "10485760"));
}
public static int getDownloadTimeout() {
return Integer.parseInt(properties.getProperty("download.timeout", "30"));
}
}
总结
文件上传和下载是Web自动化测试中的重要功能,需要注意以下几点:
文件上传要点:
- 优先使用sendKeys()方法 – 最简单可靠
- 处理隐藏元素 – 使用JavaScript使元素可见
- 验证文件路径 – 确保文件存在且路径正确
- 处理多文件上传 – 使用换行符分隔多个文件路径
- 避免使用Robot类 – 不稳定且依赖系统环境
文件下载要点:
- 正确配置浏览器 – 设置下载路径和禁用提示
- 等待下载完成 – 检查临时文件和文件大小
- 验证下载结果 – 检查文件存在性和完整性
- 处理不同浏览器 – 针对不同浏览器使用相应配置
- 清理测试文件 – 测试后清理下载的文件
最佳实践:
- 使用工具类封装常用操作
- 添加适当的等待和超时处理
- 进行充分的异常处理和验证
- 保持测试环境的清洁
- 使用配置文件管理参数
通过遵循这些最佳实践,可以编写出稳定、可靠的文件操作自动化测试代码。