java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java Playwright自动化测试

Java使用Playwright自动化测试的超详细教程

作者:西凉的悲伤

Playwright 是由微软开发的开源自动化测试框架,它被设计用来克服 Selenium 这种老牌工具在现代 Web 应用(尤其是复杂的单页应用 React/Vue)中的种种局限性,本文给大家介绍了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 或显式等待,否则容易因元素未加载导致报错。内置自动等待。在执行点击、输入等操作前,会自动检查元素是否可见、稳定、可操作。
浏览器驱动依赖驱动程序。需要手动下载并配置与浏览器版本严格一致的 chromedrivergeckodriver内置浏览器内核。安装后直接使用 Chromium、Firefox 和 WebKit 内核,无需担心驱动版本冲突。
通信协议基于 HTTP (JSON Wire)。每一个指令都要发送一次 HTTP 请求并等待响应,通信开销较大。基于 WebSocket。与浏览器建立长连接,指令实时发送,执行速度显著提升。
多标签/窗口切换复杂。需要通过 window_handles 手动管理和切换句柄。原生支持上下文。通过 BrowserContext 轻松管理多个独立页面,捕获新窗口非常简单。
录屏与追踪功能有限。通常需要集成第三方库来完成截图或视频录制。全能追踪 器 (Trace Viewer)。原生支持录制视频、自动保存点击轨迹、网络请求以及控制台日志。
网络拦截难以实现。很难直接在代码中修改或拦截网页发出的 API 请求。原生支持。可以轻松拦截并篡改(Mock)网络请求和返回数据,方便进行前端边界测试。
并行执行需要配合 Selenium Grid 才能实现高效并行。单个浏览器实例即可开启多个独立上下文,原生支持极速并行运行。

3. 为什么现在大家都转向 Playwright?

如果你在做新项目,追求测试速度、稳定性,或者需要处理复杂的现代网页,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 下载到操作系统特定的缓存文件夹中:

三、有头浏览器和无头浏览器

有头浏览器会打开一个浏览器界面进行操作

        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#idpage.locator("#mSearchInput")
Class.classpage.locator(".m-search-btn")
标签名tagpage.locator("input")
属性匹配[attr=val]page.locator("[type='submit']")
包含匹配[attr*=val]page.locator("[href*='csdn.net']")
层级(后代)parent childpage.locator(".container .title")
层级(直接子级)parent > childpage.locator("ul > li")

2. 组合定位(更精准)

当单个属性不唯一时,你可以将它们组合起来:

3. 层级定位(寻找子元素)

在实际业务中,由于 ID 可能会重复(虽然不合规范)或者 Class 太通用,我们常使用层级关系:

// 定位搜索列表下的第一个标题链接
page.locator(".search-list-con .title-box > a").first().click();

4. 属性选择器进阶

如果你不想写全称,或者某些属性值是动态生成的,可以使用“模糊匹配”:

5. Playwright 特有的增强定位

Playwright 对 CSS 选择器做了一些非常好用的扩展:

// 找到一个 div,它里面必须包含 "登录" 这两个字
page.locator("div:has-text('登录')");
// 找到一个 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 ContextStorage 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 代码。

前提条件:

命令行中执行命令:

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,如果你想操作其他网址,可替换命令行中的链接。

第一次执行命令可能会有点慢,它会下载必要的一些依赖,请耐心等待。

执行效果:

你会看到两个窗口同时弹出:

最后你只需要直接把生成的代码贴进你的 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自动化测试的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文