Java使用Playwright自动化测试的超详细教程
作者:西凉的悲伤
前言
Playwright 是由微软(Microsoft)开发的开源自动化测试框架,它被设计用来克服 Selenium 这种“老牌工具”在现代 Web 应用(尤其是复杂的单页应用 React/Vue)中的种种局限性。
1. 什么是 Playwright?
Playwright 是一个能够跨浏览器(Chromium, Firefox, WebKit)进行网页自动化的库。它最显著的特点是快、稳、强。它不仅支持多种编程语言(Java, Python, JS, C#),还自带了强大的录制代码、调试控制台和网络拦截功能。
2. Playwright vs Selenium:核心差异
| 维度 | Selenium (传统派) | Playwright (现代派) |
|---|---|---|
| 等待机制 | 手动等待。通常需要编写复杂的 WebDriverWait 或显式等待,否则容易因元素未加载导致报错。 | 内置自动等待。在执行点击、输入等操作前,会自动检查元素是否可见、稳定、可操作。 |
| 浏览器驱动 | 依赖驱动程序。需要手动下载并配置与浏览器版本严格一致的 chromedriver 或 geckodriver。 | 内置浏览器内核。安装后直接使用 Chromium、Firefox 和 WebKit 内核,无需担心驱动版本冲突。 |
| 通信协议 | 基于 HTTP (JSON Wire)。每一个指令都要发送一次 HTTP 请求并等待响应,通信开销较大。 | 基于 WebSocket。与浏览器建立长连接,指令实时发送,执行速度显著提升。 |
| 多标签/窗口 | 切换复杂。需要通过 window_handles 手动管理和切换句柄。 | 原生支持上下文。通过 BrowserContext 轻松管理多个独立页面,捕获新窗口非常简单。 |
| 录屏与追踪 | 功能有限。通常需要集成第三方库来完成截图或视频录制。 | 全能追踪 器 (Trace Viewer)。原生支持录制视频、自动保存点击轨迹、网络请求以及控制台日志。 |
| 网络拦截 | 难以实现。很难直接在代码中修改或拦截网页发出的 API 请求。 | 原生支持。可以轻松拦截并篡改(Mock)网络请求和返回数据,方便进行前端边界测试。 |
| 并行执行 | 需要配合 Selenium Grid 才能实现高效并行。 | 单个浏览器实例即可开启多个独立上下文,原生支持极速并行运行。 |
3. 为什么现在大家都转向 Playwright?
- 自动等待(Auto-waiting)
Selenium 就像一个盲人,你得告诉他:“先等5秒,然后再摸按钮”。如果网络慢了,5秒不够,程序就崩了。 Playwright 就像一个智能助手,你告诉他“点击按钮”,他会盯着屏幕看,直到按钮出现并准备就绪才下手。 - 浏览器上下文(Context)
Playwright 引入了“上下文”的概念。你可以把它想象成浏览器的无痕模式。启动一个浏览器实例后,可以开启多个完全隔离的上下文,每个上下文都有自己的 Cookie 和缓存,互不干扰,且启动速度极快。 - 网络拦截
Playwright 可以拦截网页发送的 API 请求。比如你可以伪造后台返回的 JSON 数据,测试前端在接口报错时的反应,而 Selenium 很难直接做到这一点。
如果你在做新项目,追求测试速度、稳定性,或者需要处理复杂的现代网页,Playwright 是目前的最佳选择。
一、 maven
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.58.0</version>
<scope>compile</scope>
</dependency>二、第一个示例
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import java.nio.file.Paths;
public class MainServer {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.webkit().launch();
Page page = browser.newPage();
page.navigate("https://blog.csdn.net/qq_33697094");
//截图
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("example.png")));
}
}
}
运行上面的代码,会运行无头浏览器在后台打开 https://blog.csdn.net/qq_33697094 网页,然后截图为example.png 保存到当前项目目录下。
如果是第一次使用 Playwright ,运行代码时 Playwright 会将 Chromium、WebKit 和 Firefox 最新版本的浏览器和截图录频工具 ffmpeg 下载到操作系统特定的缓存文件夹中:
- Windows 系统:%USERPROFILE%\AppData\Local\ms-playwright
- macOS 系统:~/Library/Caches/ms-playwright
- Linux 系统:~/.cache/ms-playwright
三、有头浏览器和无头浏览器
有头浏览器会打开一个浏览器界面进行操作
try (Playwright playwright = Playwright.create()) {
// 使用有头浏览器,并且增加慢动作,方便看清操作
Browser browser = playwright.chromium().launch(
new BrowserType.LaunchOptions().setHeadless(false)
//如果在开发阶段你可以使用setSlowMo来放慢执行速度,方便在界面上观看
//.setSlowMo(100)
);
}
无头浏览器不会打开浏览器界面,只会在后台运行
try (Playwright playwright = Playwright.create()) {
// 使用无头浏览器
Browser browser = playwright.chromium().launch();
}
四、使用不同的浏览器
使用有头谷歌浏览器
try (Playwright playwright = Playwright.create()) {
// 使用有头谷歌浏览器
Browser browser = playwright.chromium().launch(
new BrowserType.LaunchOptions().setHeadless(false)
);
}
使用有头火狐浏览器
try (Playwright playwright = Playwright.create()) {
// 使用有头火狐浏览器
Browser browser = playwright.firefox().launch(
new BrowserType.LaunchOptions().setHeadless(false)
);
}
五、单页面与多页面
如果你的页面点击或者其他操作会打开新的 tab 页面,并且你需要在新页面进行操作,那么你需要使用创建上下文管理多页面
多页面:
// 创建上下文(Context),用于管理多个页面 BrowserContext context = browser.newContext(); Page page = context.newPage();
单页面:
Page page = browser.newPage();
六、定位 器
在 Playwright 中,通过标签、Class、ID 定位主要通过 page.locator() 方法实现。它支持标准的 CSS 选择器 语法。
1. 基础定位方式
以下是 Java 版本的语法对照表:
| 定位目标 | CSS 语法 | Playwright 代码示例 |
|---|---|---|
| ID | #id | page.locator("#mSearchInput") |
| Class | .class | page.locator(".m-search-btn") |
| 标签名 | tag | page.locator("input") |
| 属性匹配 | [attr=val] | page.locator("[type='submit']") |
| 包含匹配 | [attr*=val] | page.locator("[href*='csdn.net']") |
| 层级(后代) | parent child | page.locator(".container .title") |
| 层级(直接子级) | parent > child | page.locator("ul > li") |
2. 组合定位(更精准)
当单个属性不唯一时,你可以将它们组合起来:
- 标签 + ID:input#search-input(定位 ID 为 search-input 的 input 标签)
- 标签 + Class:div.content-box(定位含有 content-box 类的 div)
- 多类名组合:.btn.btn-primary.active(同时拥有这三个 class 的元素)
3. 层级定位(寻找子元素)
在实际业务中,由于 ID 可能会重复(虽然不合规范)或者 Class 太通用,我们常使用层级关系:
- 后代选择器 (空格):#container .title(寻找 ID 为 container 下的所有含 title 类的元素)
- 直接子元素 (>):ul > li(只寻找 ul 下的第一层 li 标签)
// 定位搜索列表下的第一个标题链接
page.locator(".search-list-con .title-box > a").first().click();
4. 属性选择器进阶
如果你不想写全称,或者某些属性值是动态生成的,可以使用“模糊匹配”:
- 包含某字符串 (=):[href=“csdn.net”](链接中包含 csdn.net)
- 以某字符串开头 (=):[id=“post-”](ID 以 post- 开头)
- 以某字符串结尾 ( = ) : [ s r c =):[src =):[src=“.png”](图片后缀是 .png)
5. Playwright 特有的增强定位
Playwright 对 CSS 选择器做了一些非常好用的扩展:
- 内部文本匹配 (:has-text):
// 找到一个 div,它里面必须包含 "登录" 这两个字
page.locator("div:has-text('登录')");
- 根据内部元素定位 (:has):
// 找到一个 div,它里面必须包含一个 <button> 标签
page.locator("div:has(button)");
6.实战建议:如何在控制台调试?
在浏览器 F12 的 Console 面板中,你可以直接测试你的 CSS 选择器是否正确:
使用 $$(“.your-class”):这会返回页面上所有匹配该 CSS 的元素。
使用 $(“.your-class”):这会返回匹配到的第一个元素。
七、打开指定链接
page.navigate("https://blog.csdn.net/qq_33697094?type=blog");
八、等待加载完成
等待页面加载完成
page.waitForLoadState();
等待指定元素标签加载完成
// 等待id是content_views的标签内容加载完成
rticlePage.locator("#content_views").waitFor();
等元素出现(最常用)
//等待指定元素标签加载完成
page.waitForSelector("#submit");
等元素可见
page.waitForSelector(
"#submit",
new Page.WaitForSelectorOptions().setState(WaitForSelectorState.VISIBLE)
);
等元素消失
page.waitForSelector(
".loading",
new Page.WaitForSelectorOptions().setState(WaitForSelectorState.DETACHED)
);
其他点击、文本填充操作时会自动等待
Playwright 内置自动等待 Playwright 在执行 click / fill / hover 等操作时,会自动进行: - 元素存在检查 - 元素可见性检查 - 元素是否被遮挡 - 元素是否处于稳定状态(动画结束) - 是否可接收事件 这些行为被称为 Actionability Checks。
九、获取所有的 p 标签内容
Page page = context.newPage();
//获取id是#content_views 的标签下的所有p标签
List<String> allParagraphs = page.locator("#content_views p").allInnerTexts();
十、获取某一指定文本的 p 标签内容
Page page = context.newPage();
//获取id是#content_views 的标签下的所有p标签,然后二次过滤搜索含有"手写大乐透"的p标签,获取标签中的内容
String specificText = articlePage.locator("#content_views p")
.filter(new Locator.FilterOptions().setHasText("手写大乐透"))
.innerText();
十一、获取某一指定文本的 a 标签然后点击
Page page = context.newPage();
//获取含有"Java实现彩票大乐透、双色球机选号"的a标签 (使用 getByRole 定位(语义化,推荐))
Locator articleLink = page.getByRole(AriaRole.LINK,
new Page.GetByRoleOptions().setName("Java实现彩票大乐透、双色球机选号"));
//点击
articleLink.click();
或者:
Page page = context.newPage();
//获取含有"Java实现彩票大乐透、双色球机选号"的a标签 (使用 CSS 选择器结合文本过滤(更灵活))
Locator articleLink = page.locator("a.block-title")
.filter(new Locator.FilterOptions().setHasText("Java实现彩票大乐透"));
articleLink.click();
十二、跳转到新页签后 Page 上下文的切换
// 创建上下文(Context),用于管理多个页面
BrowserContext context = browser.newContext();
Page page = context.newPage();
Locator searchInput = page.locator("#mSearchInput");
searchInput.fill("Java实现彩票大乐透、双色球机选号");
// 处理新标签页跳转,即点击后会打开新窗口,我们需要告诉 Playwright "等待新页面弹出"
Page newPage = context.waitForPage(() -> {
// 点击搜索按钮,这里执行触发跳转的动作,可以是点击按钮,也可以是回车
page.locator(".m-search-sure").click();
});
十三、如果按钮可见则点击按钮
Locator readMoreBtn = page.locator("#btn-readmore");
if (readMoreBtn.isVisible()) {
readMoreBtn.click();
}
十四、模拟按下键盘回车
page.keyboard().press("Enter");
十五、网络拦截与监听(接口过滤)。
如果我使用Playwright打开某一个链接,它会请求很多接口,你可以过滤搜索到指定的接口,然后获取接口里的响应。
你不需要为了数据去解析复杂的 HTML DOM,你可以直接“监听”后台接口返回的 JSON 数据,这通常比爬取页面元素更准确、更丰富。
在 Playwright (Java) 中,主要有两种方式来实现:
方案一:waitForResponse (最推荐,适合“触发-等待”场景)
这种方式的逻辑是:“我点击了一个按钮,我知道它会触发一个 API 请求,我让程序暂停,直到捕获到这个请求并拿回数据后再继续。”
这是处理搜索、翻页、加载更多等操作的最稳健方式。
代码示例:
假设你在 CSDN 点击搜索,想获取点击某个按钮触发的接口的返回 JSON 结果
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.Response;
import java.io.IOException;
import java.util.function.Predicate;
public class MainServer {
public static void main(String[] args) throws IOException {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch(
new BrowserType.LaunchOptions().setHeadless(false)
);
// 创建上下文(Context),用于管理多个页面
BrowserContext context = browser.newContext();
Page page = context.newPage();
// 1. 定义你想要捕获的接口特征 (Predicate)
// 比如:URL 包含 "community-search-pc" 且状态码是 200
Predicate<Response> searchResponsePredicate = response ->
response.url().contains("community-search-pc") && response.status() == 200;
page.navigate("https://blog.csdn.net/qq_33697094?type=blog");
// 2. 执行动作并等待响应
// waitForResponse 会等待内部的 callback (点击操作) 触发的那个请求
Response response = page.waitForResponse(searchResponsePredicate, () -> {
// 这里的页面点击会触发一个网络接口请求
page.locator(".search-btn").click();
});
// 3. 获取接口数据
System.out.println("接口 URL: " + response.url());
// 获取响应体 (通常是 JSON 字符串)
String jsonResponse = response.text();
System.out.println("接口返回数据: " + jsonResponse);
//关闭浏览器
browser.close();
}
}
}
方案二:onResponse (监听模式,适合“全局监控”)
这种方式的逻辑是:“不管我做什么操作,只要浏览器收到了符合条件的接口响应,就立刻记录下来。”
这适合用来被动收集数据,或者监听页面加载时自动发出的多个请求。
代码示例:
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import java.io.IOException;
public class MainServer {
public static void main(String[] args) throws IOException {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch(
new BrowserType.LaunchOptions().setHeadless(false)
);
// 创建上下文(Context),用于管理多个页面
BrowserContext context = browser.newContext();
Page page = context.newPage();
// 1. 在页面操作之前,先注册一个监听器
page.onResponse(response -> {
// 过滤条件:只关心包含 "get-business-list" 的接口
if (response.url().contains("get-business-list") && response.status() == 200) {
System.out.println("捕获到目标接口: " + response.url());
// 注意:response.text() 可能会抛出异常(如果响应还没完全接收),建议加 try-catch
try {
System.out.println("接口响应的数据内容: " + response.text());
} catch (Exception e) {
e.printStackTrace();
}
}
});
//打开链接,会自动监听所有请求,监听里然后会自动获取url含有 get-business-list 的接口,并打印它的响应
page.navigate("https://blog.csdn.net/qq_33697094?type=blog");
//关闭浏览器
browser.close();
}
}
}
十六、登录状态持久化
方案一:启动持久化上下文 (Persistent Context)
这种方式更接近于“指定特定账号 Profile”。它会像你平常用的 Chrome 浏览器一样,在磁盘上创建一个文件夹来存放所有的缓存、Cookie 和历史记录。
public static void main(String[] args) {
// 指定一个文件夹路径来存放用户数据
Path userDataDir = Paths.get("C:\\playwright_profiles\\my_user");
Playwright playwright = Playwright.create();
// 注意:这里用的是 launchPersistentContext,而不是 launch
BrowserContext context = playwright.chromium()
.launchPersistentContext(
userDataDir,
new BrowserType.LaunchPersistentContextOptions()
.setHeadless(false)
);
Page page = context.newPage();
page.navigate("https://csdn.net");
//你在弹出的浏览器里手动操作完成登录(验证码等)
page.pause();
// 完成登录后,点 Play ▶ 继续,或者关闭浏览器,所有登录状态都将保存
}
以后:直接用该账号打开网页
public static void main(String[] args) {
Path userDataDir = Paths.get("C:\\playwright_profiles\\my_user");
Playwright playwright = Playwright.create();
BrowserContext context = playwright.chromium()
.launchPersistentContext(
userDataDir,
new BrowserType.LaunchPersistentContextOptions()
.setHeadless(false)
);
Page page = context.newPage();
page.navigate("https://csdn.net");
// 已经是登录状态
//....
//关闭浏览器
Browser browser = context.browser();
browser.close();
}
方案二:使用 Storage State
public static void main(String[] args) {
Playwright playwright = Playwright.create();
Browser browser = playwright.chromium()
.launch(new BrowserType.LaunchOptions()
.setHeadless(false)); // 必须 false,方便你手动登录
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("https://csdn.net");
//程序在这里“冻结”,等你手动操作完成登录(验证码等)
page.pause();
// 完成登录后,点 Play ▶ 继续,手动触发保存
context.storageState(
new BrowserContext.StorageStateOptions()
.setPath(Paths.get("csdn-login.json"))
);
browser.close();
}
随后:直接复用登录状态
public static void main(String[] args) {
Playwright playwright = Playwright.create();
Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext(
new Browser.NewContextOptions()
.setStorageStatePath(Paths.get("csdn-login.json"))
);
Page page = context.newPage();
page.navigate("https://csdn.net");
// 已经是登录状态
}
两种方式的本质对比
| 维度 | Persistent Context | Storage State |
|---|---|---|
| 保存内容 | 整个浏览器环境 | cookies + localStorage |
| 是否像真实 Chrome | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| 是否包含插件/扩展 | ✅ | ❌ |
| 是否占用 Profile 锁 | 是 | 否 |
| 多账号并行 | 一般 | ⭐⭐⭐⭐⭐ |
| CI / 服务器 | 一般 | ⭐⭐⭐⭐⭐ |
| 稳定性 | 极高 | 高 |
| 推荐场景 | 长期账号、真人模拟 | 批量账号、自动化 |
十七、示例
示例一:
执行下面的操作,会打开我的博客主页,然后在输入框里输入 “Java实现彩票大乐透、双色球机选号”,然后点击搜索,在跳转后的搜索页面点击 “Java实现彩票大乐透、双色球机选号”
文章标题,跳转到新的博客文章页面,获取博客里的第一行文章内容。
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.options.AriaRole;
public class MainServer {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch(
new BrowserType.LaunchOptions().setHeadless(false)
);
// 创建上下文(Context),用于管理多个页面
BrowserContext context = browser.newContext();
Page page = context.newPage();
// 导航到博客主页
page.navigate("https://blog.csdn.net/qq_33697094?type=blog");
// 点击博客内的搜索图标(展开搜索框),click点击时内部会自动等待元素加载
page.locator(".m-search-btn").click();
// 输入搜索关键词
Locator searchInput = page.locator("#mSearchInput");
searchInput.fill("Java实现彩票大乐透、双色球机选号");
System.out.println("--------------------------------------------------------------------------");
// 处理点击跳转到新标签页跳转,CSDN 点击搜索后会打开新窗口,我们需要告诉 Playwright "等待新页面弹出"
Page searchPage = context.waitForPage(() -> {
// 点击搜索按钮
page.locator(".m-search-sure").click();
});
// 等待新页面加载完成
searchPage.waitForLoadState();
System.out.println("新页面标题: " + searchPage.title());
// 筛选并点击目标文章标题(使用 getByRole 定位(语义化,推荐))
Locator articleLink = searchPage.getByRole(AriaRole.LINK,
new Page.GetByRoleOptions().setName("Java实现彩票大乐透、双色球机选号"));
System.out.println("--------------------------------------------------------------------------");
// 处理点击跳转到新标签页博客文章
Page articlePage = context.waitForPage(() -> {
// 点击文章标题的动作
articleLink.click();
});
// 等待新页面加载完成
articlePage.waitForLoadState();
// 等待正文内容加载完成
articlePage.locator("#content_views").waitFor();
// 获取特定的某一段文字
String specificText = articlePage.locator("#content_views p")
.filter(new Locator.FilterOptions().setHasText("手写大乐透"))
.innerText();
System.out.println("找到的目标文字: " + specificText);
//关闭浏览器
browser.close();
}
}
}
示例二:
下面的代码会每隔5分钟运行一次,每次会打开 slack 聊天网站检查对方有没有给自己发送新的消息,如果有新消息则使用 ffemng 播放视频提醒
import com.hai.tang.util.FfmpegUtils;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.options.AriaRole;
import com.microsoft.playwright.options.WaitUntilState;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
public class MainServer {
public static void main(String[] args) throws IOException {
//程序运行时间,每隔5分钟运行一次
long intervalMinus= 5;
//对话里最后一条消息,用来和最新的消息比对,如果新消息里不包含该则文字,说明有新消息
String lastMessage = "Okay, this is noted. I will send an update if it's ready.";
long interval = intervalMinus * 60 * 1000;
System.out.println("=== 自动化监控脚本启动 ===");
while (true) {
try {
System.out.println("正在执行任务,当前时间: " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// 执行具体的业务逻辑
messageApiResponseGet_Kathryn(lastMessage);
System.out.println("本次任务结束,等待 20 分钟后继续...");
System.out.println("----------------------------------------");
// 休眠 2 分钟
Thread.sleep(interval);
} catch (InterruptedException e) {
e.printStackTrace();
break; // 如果被手动停止,跳出循环
} catch (Exception e) {
System.err.println("任务执行过程中发生异常: " + e.getMessage());
e.printStackTrace();
// 即使报错,通常也希望继续等待下一次运行,而不是直接崩溃退出
try {
Thread.sleep(interval);
} catch (InterruptedException ex) {
ex.printStackTrace();
break;
}
}
}
}
public static void messageApiResponseGet_Kathryn(String lastMessage) {
Path userDataDir = Paths.get("C:\\Users\\Public\\google_my_user");
Playwright playwright = Playwright.create();
// 注意:这里用的是 launchPersistentContext,而不是 launch
BrowserContext context = playwright.chromium()
.launchPersistentContext(
userDataDir,
new BrowserType.LaunchPersistentContextOptions()
.setHeadless(false)
);
Page page = context.newPage();
// 监听所有响应
page.onResponse(response -> {
String url = response.url();
// 过滤指定接口
if (url.contains("thethinklivebeteam.slack.com/api/conversations.view")) {
System.out.println("捕获到目标接口: " + url);
try {
String body = response.text(); // 获取响应体
System.out.println("接口响应数据:");
System.out.println(body);
//调用工具类解析响应,查找history下的messages
List<Map<String, Object>> jsonArrayAsListOfMap = LabelSearchUtil.getJsonArrayAsListOfMap(body, "history.messages");
if (null!=jsonArrayAsListOfMap){
Map<String, Object> stringObjectMap = jsonArrayAsListOfMap.get(0);
String text = String.valueOf(stringObjectMap.get("text"));
System.out.println(text);
if (!text.contains(lastMessage)){
System.out.println("you have new message from Kathryn!");
//调用工具类 使用ffemng 播放视频播放并指定循环次数
FfmpegUtils.playVideoAudio("C:\\Users\\LENOVO\\Downloads\\Videos2026-02-13_030052_345.mp4",10);
}else{
System.out.println("guan bi");
context.browser().close();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
page.navigate("https://thethinklivebeteam.slack.com/archives/D0A3V61JPQF",
new Page.NavigateOptions().setWaitUntil(WaitUntilState.DOMCONTENTLOADED));
System.out.println("111");
// 1. 定义定位 器:寻找文字包含 "open this link" 的链接
Locator targetLink = page.getByRole(AriaRole.LINK,
new Page.GetByRoleOptions().setName("open this link in your browser"));
System.out.println("222");
// 2. 判断逻辑:如果元素在当前页面可见,则点击
if (targetLink.isVisible()) {
System.out.println("检测到目标链接,正在点击...");
targetLink.click();
} else {
System.out.println("目标链接未出现,跳过。");
}
page.pause();
}
}上面每 5 分钟创建一次浏览器实例
,这会导致:内存泄漏、每次循环创建 browser Chrome 实例叠加 ,并且使用 page.onResponse 来异步监听,这并不可靠。
下面是优化版本:
import com.hai.tang.util.FfmpegUtils;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.Response;
import com.microsoft.playwright.TimeoutError;
import com.microsoft.playwright.options.AriaRole;
import com.microsoft.playwright.options.LoadState;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
public class MainServer {
public static void main(String[] args) {
long intervalMinus = 5;
String lastMessage = "Okay, this is noted. I will send an update if it's ready.";
long interval = intervalMinus * 60 * 1000;
System.out.println("=== 自动化监控脚本启动 ===");
Path userDataDir = Paths.get("C:\\Users\\Public\\google_my_user");
Playwright playwright = Playwright.create();
// 注意:这里用的是 launchPersistentContext,而不是 launch
BrowserContext context = playwright.chromium()
.launchPersistentContext(
userDataDir,
new BrowserType.LaunchPersistentContextOptions()
.setHeadless(false)
);
Page page = context.newPage();
page.navigate("https://thethinklivebeteam.slack.com/archives/D0A3V61JPQF");
while (true) {
try {
System.out.println("正在执行任务,当前时间: " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// 执行具体的业务逻辑
messageApiResponseGet_Kathryn(lastMessage, page);
System.out.println("本次任务结束,等待 20 分钟后继续...");
System.out.println("----------------------------------------");
// 休眠 2 分钟
Thread.sleep(interval);
} catch (InterruptedException e) {
e.printStackTrace();
break; // 如果被手动停止,跳出循环
} catch (TimeoutError e) {
System.out.println("未检测到接口响应,自动重试...");
page.reload();
}catch (Exception e) {
System.err.println("任务执行过程中发生异常: " + e.getMessage());
e.printStackTrace();
// 即使报错,通常也希望继续等待下一次运行,而不是直接崩溃退出
try {
Thread.sleep(interval);
} catch (InterruptedException ex) {
ex.printStackTrace();
break;
}
}
}
}
public static void messageApiResponseGet_Kathryn(String lastMessage, Page page) {
// 使用 waitForResponse 等待特定API响应
Response response = page.waitForResponse(
r -> r.url().contains("thethinklivebeteam.slack.com/api/conversations.view"),
new Page.WaitForResponseOptions().setTimeout(15000),
() -> {
page.reload();
page.waitForLoadState(LoadState.DOMCONTENTLOADED);
// 1. 定义定位 器:寻找文字包含 "open this link" 的链接
Locator targetLink = page.getByRole(
AriaRole.LINK,
new Page.GetByRoleOptions().setName("open this link in your browser")
);
// 2. 判断逻辑:如果元素在当前页面可见,则点击
if (targetLink.count() > 0 &&targetLink.isVisible()) {
System.out.println("检测到中间页,点击跳转...");
targetLink.click();
}
}
);
String body = response.text();
System.out.println("接口响应数据:");
System.out.println(body);
List<Map<String, Object>> jsonArrayAsListOfMap = LabelSearchUtil.getJsonArrayAsListOfMap(body, "history.messages");
if (null != jsonArrayAsListOfMap) {
Map<String, Object> stringObjectMap = jsonArrayAsListOfMap.get(0);
String text = String.valueOf(stringObjectMap.get("text"));
System.out.println(text);
if (!text.contains(lastMessage)) {
System.out.println("you have new message from Kathryn!");
//ffemng play music
//视频播放并指定循环次数
FfmpegUtils.playVideoAudio("C:\\Users\\LENOVO\\Downloads\\Videos2026-02-13_030052_345.mp4", 10);
}
}
}
}十八、使用CLI 工具运行自动代码生成器(Codegen)
不会写代码?直接录制!
Playwright 提供了极其强大的 codegen 工具。只需在终端输入一行命令,它就会打开浏览器并记录你的所有操作,自动生成对应的 Java 代码。
前提条件:
- 你必须已经安装了 Maven,并且 mvn 命令在你的环境变量中。
- 你的 maven项目的 pom.xml 必须已经引入了 Playwright 的依赖(因为 Maven 需要通过项目配置去下载并找到那个 CLI 类所在的 JAR 包)。
- 项目的 pom.xml 所在目录打开 cmd 命令行
命令行中执行命令:
mvn exec:java -e -Dexec.mainClass="com.microsoft.playwright.CLI" -Dexec.args="codegen https://blog.csdn.net"
这条命令的意思是通过 Maven 运行 Playwright 自带的 CLI 工具,启动 codegen(录制脚本)功能,并打开https://blog.csdn.net,如果你想操作其他网址,可替换命令行中的链接。
第一次执行命令可能会有点慢,它会下载必要的一些依赖,请耐心等待。
执行效果:
你会看到两个窗口同时弹出:
- 一个真实的浏览器窗口:你在这里面正常的点击、输入。
- 一个 Playwright Inspector 窗口:它会实时生成对应的 Java 代码。
最后你只需要直接把生成的代码贴进你的 main 方法里运行和微调即可。
下面的代码是生成的代码,和上面第十五的示例效果一样,但是更简洁
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.options.AriaRole;
public class MainServer {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch(new BrowserType.LaunchOptions()
.setHeadless(false));
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("https://blog.csdn.net/qq_33697094?type=blog");
page.locator(".m-search-btn").click();
page.getByRole(AriaRole.TEXTBOX, new Page.GetByRoleOptions().setName("搜TA的内容")).click();
page.getByRole(AriaRole.TEXTBOX, new Page.GetByRoleOptions().setName("搜TA的内容")).fill("Java实现彩票大乐透、双色球机选号");
Page page1 = page.waitForPopup(() -> {
page.locator("#navList-box").getByText("搜索").click();
});
Page page2 = page1.waitForPopup(() -> {
page1.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("Java实现彩票大乐透、双色球机选号")).click();
});
String specificText = page2.locator("#content_views p")
.filter(new Locator.FilterOptions().setHasText("手写大乐透"))
.innerText();
System.out.println("找到的目标文字: " + specificText);
//关闭浏览器
browser.close();
}
}
}
以上就是Java使用Playwright自动化测试的超详细教程的详细内容,更多关于Java Playwright自动化测试的资料请关注脚本之家其它相关文章!
