1.1 什么是 Playwright Playwright 是Microsoft(微软)推出的一款现代化的自动化测试框架,支持 Chromium、Firefox 和 WebKit 三大浏览器引擎,并提供 TypeScript/JavaScript、Python、Java、.NET快速的端到端测试能力,适用于 Web 应用测试、爬虫开发和自动化任务。
1.2 核心优势
多浏览器支持 :一套代码在 Chromium、Firefox、WebKit 上运行
多语言支持 :支持 TypeScript、JavaScript、Python、.NET、Java
自动等待机制 :智能等待元素可操作,无需手动添加 sleep
强大的选择器 :支持文本、CSS、XPath、自定义选择器
网络拦截 :可以模拟、修改、阻止网络请求
并行执行 :支持测试并行运行,提高效率
丰富的调试工具 :Trace Viewer、Codegen、Inspector
移动端模拟 :支持移动设备视口和触摸事件模拟
1.3 Playwright vs Selenium vs Cypress 对比
特性
Playwright
Selenium
Cypress
跨浏览器
Chromium, Firefox, WebKit
所有主流浏览器
Chromium, Firefox (实验性)
语言支持
JS/TS, Python, Java, .NET
Java, Python, JS, C#, Ruby
JS/TS
自动等待
✅ 内置
❌ 需手动配置
✅ 内置
执行速度
快
中等
快
网络拦截
✅ 强大
⚠️ 有限
✅ 强大
多标签页
✅ 支持
✅ 支持
❌ 不支持
学习曲线
中等
陡峭
平缓
二、 环境搭建与安装 2.1 系统要求
2.2 Maven 依赖配置 在 pom.xml 中添加 Playwright 依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > com.example</groupId > <artifactId > playwright-demo</artifactId > <version > 1.0-SNAPSHOT</version > <properties > <maven.compiler.source > 11</maven.compiler.source > <maven.compiler.target > 11</maven.compiler.target > <playwright.version > 1.59.0</playwright.version > <junit.version > 5.10.0</junit.version > </properties > <dependencies > <dependency > <groupId > com.microsoft.playwright</groupId > <artifactId > playwright</artifactId > <version > ${playwright.version}</version > </dependency > <dependency > <groupId > org.junit.jupiter</groupId > <artifactId > junit-jupiter-api</artifactId > <version > ${junit.version}</version > <scope > test</scope > </dependency > <dependency > <groupId > org.junit.jupiter</groupId > <artifactId > junit-jupiter-engine</artifactId > <version > ${junit.version}</version > <scope > test</scope > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-surefire-plugin</artifactId > <version > 3.2.2</version > </plugin > </plugins > </build > </project >
2.3 Gradle 依赖配置 在 build.gradle 中配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 plugins { id 'java' } group = 'com.example' version = '1.0-SNAPSHOT' repositories { mavenCentral() } dependencies { implementation 'com.microsoft.playwright:playwright:1.59.0' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.0' } test { useJUnitPlatform() }
2.4 浏览器安装 首次运行时,Playwright 会自动下载浏览器二进制文件 ,无需手动安装。也可以手动执行安装命令:
1 2 3 4 5 mvn exec :java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install" ./gradlew run --args="install"
安装指定浏览器:
1 2 3 4 5 mvn exec :java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install chromium" mvn exec :java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install-deps"
2.5 第一个 Playwright 程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import com.microsoft.playwright.*;public class FirstPlaywright { public static void main (String[] args) { try (Playwright playwright = Playwright.create()) { Browser browser = playwright.chromium().launch( new BrowserType .LaunchOptions() .setHeadless(false ) .setSlowMo(100 ) ); Page page = browser.newPage(); page.navigate("https://www.example.com" ); System.out.println("页面标题: " + page.title()); System.out.println("当前URL: " + page.url()); page.screenshot(new Page .ScreenshotOptions() .setPath(java.nio.file.Paths.get("example.png" )) .setFullPage(true )); browser.close(); System.out.println("执行完成!" ); } } }
三、基础入门 3.1 核心对象模型 Playwright Java 的核心对象层级:
Text 1 2 3 4 5 Playwright └── Browser (浏览器实例) └── BrowserContext (浏览器上下文,隔离环境) └── Page (页面/标签页) └── Locator (元素定位器)
3.2 浏览器启动配置 3.2.1 启动不同浏览器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import com.microsoft.playwright.*;public class BrowserLaunchExample { public static void main (String[] args) { try (Playwright playwright = Playwright.create()) { Browser chromium = playwright.chromium().launch( new BrowserType .LaunchOptions().setHeadless(false ) ); Browser firefox = playwright.firefox().launch( new BrowserType .LaunchOptions().setHeadless(false ) ); Browser webkit = playwright.webkit().launch( new BrowserType .LaunchOptions().setHeadless(false ) ); System.out.println("Chromium 版本: " + chromium.version()); System.out.println("Firefox 版本: " + firefox.version()); System.out.println("WebKit 版本: " + webkit.version()); chromium.close(); firefox.close(); webkit.close(); } } }
3.2.2 高级启动选项 1 2 3 4 5 6 7 8 9 10 11 12 13 Browser browser = playwright.chromium().launch( new BrowserType .LaunchOptions() .setHeadless(false ) .setSlowMo(50 ) .setTimeout(60000 ) .setArgs(java.util.Arrays.asList( "--start-maximized" , "--disable-blink-features=AutomationControlled" )) .setChannel("chrome" ) .setDevtools(true ) );
3.3 BrowserContext 浏览器上下文 BrowserContext 提供了独立的浏览器会话环境 ,每个上下文有独立的 Cookie、LocalStorage、缓存。
1 2 3 4 5 6 7 8 9 10 11 BrowserContext context1 = browser.newContext();BrowserContext context2 = browser.newContext();Page page1 = context1.newPage();Page page2 = context2.newPage();page1.navigate("https://example.com" ); page2.navigate("https://example.com" );
上下文配置选项 1 2 3 4 5 6 7 8 9 10 11 12 13 BrowserContext context = browser.newContext( new Browser .NewContextOptions() .setViewportSize(1920 , 1080 ) .setUserAgent("Custom User Agent" ) .setLocale("zh-CN" ) .setTimezoneId("Asia/Shanghai" ) .setPermissions(java.util.Arrays.asList( "geolocation" , "notifications" )) .setGeolocation(31.2304 , 121.4737 ) .setAcceptDownloads(true ) .setIgnoreHTTPSErrors(true ) );
3.4 页面导航 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import com.microsoft.playwright.*;public class NavigationExample { public static void main (String[] args) { try (Playwright playwright = Playwright.create()) { Browser browser = playwright.chromium().launch( new BrowserType .LaunchOptions().setHeadless(false ) ); Page page = browser.newPage(); page.navigate("https://www.example.com" ); page.navigate("https://www.example.com" , new Page .NavigateOptions() .setTimeout(30000 ) .setWaitUntil(WaitUntilState.NETWORKIDLE) ); page.reload(); page.goBack(); page.goForward(); page.waitForLoadState(LoadState.DOMCONTENTLOADED); page.waitForLoadState(LoadState.LOAD); page.waitForLoadState(LoadState.NETWORKIDLE); browser.close(); } } }
3.5 元素定位(Locator) Locator 是 Playwright 的核心概念 ,代表随时可以重新查找元素的定位器,支持自动等待。
3.5.1 推荐定位方式(按优先级) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 page.getByRole(AriaRole.BUTTON, new Page .GetByRoleOptions().setName("提交" )); page.getByText("登录" ); page.getByText("欢迎" , new Page .GetByTextOptions().setExact(true )); page.getByLabel("用户名" ); page.getByLabel("密码" ); page.getByPlaceholder("请输入邮箱" ); page.getByAltText("产品图片" ); page.getByTitle("点击查看详情" ); page.getByTestId("submit-button" );
3.5.2 CSS 和 XPath 定位 1 2 3 4 5 6 7 8 9 10 11 12 13 14 page.locator("#username" ); page.locator(".btn-primary" ); page.locator("button[type='submit']" ); page.locator("div > span" ); page.locator("//button[text()='提交']" ); page.locator("//input[@name='username']" ); page.locator(".form-container" ) .locator("input" ) .first();
3.5.3 定位器过滤 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 page.locator("li" ) .filter(new Locator .FilterOptions().setHasText("苹果" )) .click(); page.locator("div.product-card" ) .filter(new Locator .FilterOptions() .setHas(page.locator(".out-of-stock" ))) .click(); page.locator("tr" ) .filter(new Locator .FilterOptions().setHasText("John" )) .filter(new Locator .FilterOptions() .setHas(page.locator(".status-active" )));
3.6 元素交互操作 3.6.1 基础交互 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 Locator button = page.getByRole(AriaRole.BUTTON, new Page .GetByRoleOptions().setName("提交" )); Locator input = page.getByLabel("用户名" );button.click(); button.click(new Locator .ClickOptions() .setTimeout(5000 ) .setForce(true ) .setClickCount(2 ) .setButton(MouseButton.RIGHT) ); button.dblclick(); button.click(new Locator .ClickOptions() .setButton(MouseButton.RIGHT)); input.fill("hello@example.com" ); input.clear(); input.press("Enter" ); input.press("Control+A" ); input.press("Backspace" );
3.6.2 复选框和单选框 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Locator checkbox = page.getByLabel("我同意服务条款" );Locator radio = page.getByLabel("男" );checkbox.check(); checkbox.uncheck(); radio.check(); System.out.println("是否选中: " + checkbox.isChecked());
3.6.3 下拉选择框 1 2 3 4 5 6 7 8 9 10 11 12 13 Locator select = page.locator("select#city" );select.selectOption("shanghai" ); select.selectOption(new SelectOption ().setLabel("上海" )); select.selectOption(new SelectOption ().setIndex(2 )); select.selectOption(new String []{"beijing" , "shanghai" , "guangzhou" });
3.6.4 悬停和拖拽 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 page.locator(".menu-item" ).hover(); Locator source = page.locator("#item-to-drag" );Locator target = page.locator("#drop-zone" );source.dragTo(target); source.hover(); page.mouse().down(); target.hover(); page.mouse().up(); page.mouse().move(100 , 100 ); page.mouse().down(); page.mouse().move(300 , 300 ); page.mouse().up();
四、核心功能 4.1 等待机制 4.1.1 自动等待(推荐) Playwright 在执行操作前会自动进行可操作性检查 :
元素是否可见
元素是否启用
元素是否在视口内
元素是否未被遮挡
元素是否可接收事件
1 2 3 4 5 6 7 page.getByRole(AriaRole.BUTTON).click(); page.getByLabel("用户名" ).fill("test" );
4.1.2 显式等待 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Locator locator = page.locator(".loading" );locator.waitFor(); locator.waitFor(new Locator .WaitForOptions() .setState(WaitForSelectorState.HIDDEN)); locator.waitFor(new Locator .WaitForOptions() .setState(WaitForSelectorState.DETACHED)); locator.waitFor(new Locator .WaitForOptions() .setTimeout(10000 ));
4.1.3 条件等待 1 2 3 4 5 6 7 8 9 10 11 12 13 page.waitForURL("**/dashboard" ); page.waitForURL(url -> url.contains("success" )); page.waitForTitle("登录成功" ); page.waitForTitle(title -> title.startsWith("欢迎" )); page.waitForFunction("() => document.readyState === 'complete'" ); page.waitForResponse("**/api/user" );
4.2 断言(Assertions) Playwright 提供了专门的断言 API,支持自动等待。
4.2.1 基础断言 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;assertThat(page.locator(".success-message" )).isVisible(); assertThat(page.locator(".error" )).isHidden(); assertThat(page.locator("h1" )).hasText("欢迎使用" ); assertThat(page.locator(".items" )).containsText("商品A" ); assertThat(page.locator("input" )).hasValue("test@example.com" ); assertThat(page.locator("button" )).hasAttribute("disabled" , "true" ); assertThat(page.locator(".active" )).hasClass("btn btn-primary active" ); assertThat(page.locator("input" )).isEnabled(); assertThat(page.locator(".checkbox" )).isChecked(); assertThat(page.locator("button" )).isDisabled(); assertThat(page.locator(".list-item" )).hasCount(5 );
4.2.2 页面断言 1 2 3 4 5 6 7 8 9 assertThat(page).hasURL("https://example.com/dashboard" ); assertThat(page).hasURL(url -> url.contains("login" )); assertThat(page).hasTitle("首页" ); assertThat(page).hasScreenshot("homepage.png" );
4.3 截图功能 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 page.screenshot(new Page .ScreenshotOptions() .setPath(Paths.get("screenshot.png" ))); page.screenshot(new Page .ScreenshotOptions() .setPath(Paths.get("fullpage.png" )) .setFullPage(true )); page.locator(".header" ).screenshot( new Locator .ScreenshotOptions() .setPath(Paths.get("header.png" ))); byte [] bytes = page.screenshot();page.screenshot(new Page .ScreenshotOptions() .setPath(Paths.get("custom.png" )) .setType(ScreenshotType.PNG) .setQuality(90 ) .setOmitBackground(true ) .setScale(ScreenshotScale.DEVICE) .setMask(java.util.Arrays.asList( page.locator(".user-avatar" ), page.locator(".email" ) )) );
4.4 录屏功能 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 BrowserContext context = browser.newContext( new Browser .NewContextOptions() .setRecordVideoDir(Paths.get("videos/" )) .setRecordVideoSize(1280 , 720 ) ); Page page = context.newPage();page.navigate("https://example.com" ); context.close(); Video video = page.video();if (video != null ) { Path videoPath = video.path(); System.out.println("视频已保存到: " + videoPath); video.saveAs(Paths.get("test-video.webm" )); }
4.5 网络拦截 4.5.1 监听网络请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 page.onRequest(request -> { System.out.println("请求: " + request.method() + " " + request.url()); }); page.onResponse(response -> { System.out.println("响应: " + response.status() + " " + response.url()); }); page.onRequestFailed(request -> { System.out.println("请求失败: " + request.url()); });
4.5.2 拦截并修改请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 page.route("**/api/user" , route -> { route.resume(new Route .ResumeOptions() .setHeaders(java.util.Map.of( "Authorization" , "Bearer custom-token" , "X-Custom-Header" , "value" )) ); }); page.route("**/api/products" , route -> { route.fulfill(new Route .FulfillOptions() .setStatus(200 ) .setContentType("application/json" ) .setBody("{\"products\": [{\"id\": 1, \"name\": \"Mock Product\"}]}" ) ); }); page.route("**/*.png" , route -> route.abort()); page.route("**/ads/**" , route -> route.abort());
4.5.3 获取响应数据 1 2 3 4 5 6 7 8 9 10 Response response = page.waitForResponse("**/api/user" , () -> { page.navigate("https://example.com/profile" ); }); System.out.println("状态码: " + response.status()); System.out.println("响应体: " + response.text()); String jsonBody = response.text();
4.6 登录状态复用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 page.navigate("/login" ); page.getByLabel("用户名" ).fill("user" ); page.getByLabel("密码" ).fill("pass" ); page.getByText("登录" ).click(); context.storageState( new BrowserContext .StorageStateOptions() .setPath(Paths.get("state.json" )) ); BrowserContext context = browser.newContext( new Browser .NewContextOptions() .setStorageStatePath(Paths.get("state.json" )) ); Page page = context.newPage();page.navigate("/dashboard" );
4.7 键盘操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 page.keyboard().press("Enter" ); page.keyboard().press("Tab" ); page.keyboard().press("Escape" ); page.keyboard().press("Control+A" ); page.keyboard().press("Control+C" ); page.keyboard().press("Control+V" ); page.keyboard().press("Meta+A" ); page.keyboard().type("Hello World" ); page.keyboard().type("Hello" , new Keyboard .TypeOptions().setDelay(100 )); page.keyboard().down("Shift" ); page.keyboard().press("A" ); page.keyboard().up("Shift" );
文档:https://playwright.dev/java/docs/input#keys-and-shortcuts
4.8 对话框处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 page.onDialog(dialog -> { System.out.println(dialog.message()); dialog.accept(); }); page.onDialog(dialog -> { dialog.dismiss(); }); page.onDialog(dialog -> { System.out.println(dialog.defaultValue()); dialog.accept("输入的文本" ); }); page.click("#show-alert" ); page.click("#show-confirm" ); page.click("#show-prompt" );
五、高级用法 5.1 多页面与多标签页 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 BrowserContext context = browser.newContext();Page page1 = context.newPage();Page page2 = context.newPage();page1.navigate("https://example.com" ); page2.navigate("https://google.com" ); List<Page> pages = context.pages(); System.out.println("打开的页面数: " + pages.size()); Page targetPage = pages.stream() .filter(p -> p.url().contains("google" )) .findFirst() .orElse(null ); Page newPage = context.waitForPage(() -> { page1.getByText("打开新窗口" ).click(); }); newPage.waitForLoadState(); System.out.println("新页面URL: " + newPage.url()); page2.close();
5.2 iframe 处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 FrameLocator frameLocator = page.frameLocator("#my-iframe" );frameLocator.getByLabel("用户名" ).fill("test" ); frameLocator.getByRole(AriaRole.BUTTON).click(); FrameLocator outerFrame = page.frameLocator(".outer-frame" );FrameLocator innerFrame = outerFrame.frameLocator(".inner-frame" );innerFrame.getByText("提交" ).click(); Frame frame = page.frame("frame-name" );if (frame != null ) { frame.locator("button" ).click(); } List<Frame> frames = page.frames(); for (Frame f : frames) { System.out.println("Frame URL: " + f.url()); }
5.3 文件上传 5.3.1 基础文件上传 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Locator fileInput = page.locator("input[type=file]" );fileInput.setInputFiles(Paths.get("document.pdf" )); fileInput.setInputFiles(new Path []{ Paths.get("file1.txt" ), Paths.get("file2.jpg" ) }); fileInput.setInputFiles(new Path [0 ]);
5.3.2 点击触发的文件选择 1 2 3 4 5 FileChooser fileChooser = page.waitForFileChooser(() -> { page.getByText("上传" ).click(); }); fileChooser.setFiles(Paths.get("data.csv" ));
5.3.3 内存文件上传 1 2 3 4 5 6 7 8 fileInput.setInputFiles(new FilePayload []{ new FilePayload ( "test.txt" , "text/plain" , "Hello World!" .getBytes() ) });
5.4 文件下载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 BrowserContext context = browser.newContext( new Browser .NewContextOptions() .setAcceptDownloads(true ) ); Page page = context.newPage();Download download = page.waitForDownload(() -> { page.getByText("下载文件" ).click(); }); System.out.println("文件名: " + download.filename()); System.out.println("下载URL: " + download.url()); download.saveAs(Paths.get("downloaded_file.pdf" )); if (download.failure() != null ) { System.out.println("下载失败: " + download.failure()); }
5.5 自定义移动端设备参数 1 2 3 4 5 6 7 8 BrowserContext customMobile = browser.newContext( new Browser .NewContextOptions() .setViewportSize(390 , 844 ) .setDeviceScaleFactor(3 ) .setIsMobile(true ) .setHasTouch(true ) .setUserAgent("Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X)" ) );
5.6 JavaScript 执行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 String title = page.evaluate("document.title" ).toString();System.out.println("标题: " + title); int sum = (int ) page.evaluate("([a, b]) => a + b" , new Object []{5 , 3 }); Locator element = page.locator("button" );String text = page.evaluate("el => el.textContent" , element);String data = page.evaluate("async () => {\n" + " const response = await fetch('/api/data');\n" + " return response.json();\n" + "}" ).toString(); ElementHandle handle = page.evaluateHandle("document.body" );
5.7 JUnit 5 集成 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 package com.example.test;import com.microsoft.playwright.*;import org.junit.jupiter.api.*;import static org.junit.jupiter.api.Assertions.*;@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class PlaywrightJUnitTest { private Playwright playwright; private Browser browser; private BrowserContext context; protected Page page; @BeforeAll void launchBrowser () { playwright = Playwright.create(); browser = playwright.chromium().launch( new BrowserType .LaunchOptions() .setHeadless(true ) ); } @BeforeEach void createContextAndPage () { context = browser.newContext(); page = context.newPage(); } @AfterEach void closeContext () { context.close(); } @AfterAll void closeBrowser () { browser.close(); playwright.close(); } @Test void testPageTitle () { page.navigate("https://example.com" ); assertEquals("Example Domain" , page.title()); } @Test void testNavigation () { page.navigate("https://example.com" ); page.getByText("More information..." ).click(); assertTrue(page.url().contains("iana.org" )); } }
六、常见问题与解决方案 6.1 环境相关问题 Q1: 首次运行下载浏览器很慢? 解决方案:
1 2 3 4 5 export PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright/mvn test -Dplaywright.download.host=https://npmmirror.com/mirrors/playwright/
Q2: Linux 环境缺少依赖? 解决方案:
1 2 mvn exec :java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install-deps"
6.2 元素定位问题 Q1: 元素找不到,报 TimeoutError? 排查步骤:
检查选择器是否正确
检查元素是否在 iframe 内
检查元素是否在 Shadow DOM 内
增加超时时间
截图确认页面状态
1 2 3 4 5 6 locator.click(new Locator .ClickOptions().setTimeout(30000 )); page.screenshot(new Page .ScreenshotOptions() .setPath(Paths.get("debug.png" )));
Q2: 元素被遮挡,点击失败? 解决方案:
1 2 3 4 5 6 7 8 9 locator.click(new Locator .ClickOptions().setForce(true )); locator.evaluate("el => el.click()" ); locator.scrollIntoViewIfNeeded(); locator.click();
Q3: 动态 ID 如何处理? 解决方案:
1 2 3 4 5 6 7 8 page.locator("[id^='prefix-']" ); page.locator("[id$='-suffix']" ); page.locator("[id*='contains']" ); page.getByRole(AriaRole.BUTTON); page.getByText("提交" );
6.3 并发与稳定性问题 Q1: 多线程环境报错? 重要:Playwright 对象不是线程安全的!
解决方案:
1 2 3 4 5 6 ThreadLocal<Playwright> playwrightThreadLocal = ThreadLocal.withInitial(() -> { return Playwright.create(); });
Q2: CI/CD 环境运行失败? 解决方案:
1 2 3 4 5 6 7 8 9 Browser browser = playwright.chromium().launch( new BrowserType .LaunchOptions() .setHeadless(true ) .setArgs(java.util.Arrays.asList( "--no-sandbox" , "--disable-dev-shm-usage" )) );
6.4 功能使用问题 Q1: 文件上传失败? 解决方案:
1 2 3 4 5 6 7 8 page.locator("input[type=file]" ).setInputFiles(path); page.locator("input[type=file]" ).setInputFiles( path, new Locator .SetInputFilesOptions().setNoWaitAfter(true ) );
Q2: 下载没有触发? 解决方案:
1 2 3 4 5 BrowserContext context = browser.newContext( new Browser .NewContextOptions() .setAcceptDownloads(true ) );
Q3: iframe 内元素找不到? 解决方案:
1 2 3 4 5 FrameLocator frame = page.frameLocator("#iframe-id" );frame.getByText("按钮" ).click();
6.5 性能问题 Q1: 测试执行太慢? 优化建议:
启用并行执行 - 最有效
复用浏览器实例 - 不要每个测试都启动浏览器
复用登录状态 - 使用 storageState
阻止不必要资源 - 图片、广告、分析脚本
使用 API 准备测试数据 - 不要通过 UI 创建数据
减少不必要的截图 / 录屏
Q2: 内存占用过高? 解决方案:
1 2 3 4 5 6 7 8 9 10 11 context.close(); if (testCount > 100 ) { browser.close(); browser = playwright.chromium().launch(); }
七、总结 Playwright Java SDK 提供了强大而现代的浏览器自动化能力,通过此文章你已经掌握了:
环境搭建 - Maven/Gradle 配置,浏览器管理
基础操作 - 浏览器启动、导航、元素定位、交互
核心功能 - 等待机制、断言、截图、录屏、网络拦截
高级用法 - 多页面、iframe、文件处理
问题排查 - 常见问题的解决方案
下一步建议:
阅读 官方文档 获取最新 API
在实际项目中练习使用
关注 Playwright 版本更新