当前位置: 首页 > news >正文

playwright录制脚本原理

 Paywright录制工具UI

  在上一篇博客中介绍了如何从0构建一款具备录制UI测试的小工具。此篇博客将从源码层面上梳理playwright录制原理。当打开playwright vscode插件时,点击录制按钮,会开启一个新浏览器,如下图所示,在新开浏览器页面上,有录制,查看等按钮。

  查看vscode的源码,会看到有个recorder的folder,该folder下由react构建了一个应用的UI,执行npm run dev,在5173端口启动这样一个web应用,web应用的UI如下图所示,可以看到里面的录制按钮等和上图vscode插件大家的相同。从这里可以推断,recorder里面用react构建的componen被嵌入到了开启的浏览器中。

 通过上一篇博客的介绍,我们知道,实现录制功能的核心原理是是在浏览器中注入了脚本,通过监听用户行为,并将用户行为转换为playwright的语法,从而实现录制脚本的能力。

Playwright的脚本注入

  查看playwright得source code,在playwright-core/src/server/injected目录下就是注入脚本相关的内容。查看injected/recorder/recorder.ts脚本,在该脚本中在interface RecordTool中定义了大量操作页面元素的方法,例如onClick,onInput等。

  在recorder.ts中,在document对象上添加了很多listener,如下图所示:

  具体每个listener完成了哪些逻辑呢?以onInput为例子,下面的onInput方法的部分代码,可以看到,首先是获取Input的目标对象target,再依次判断Input的具体属性,例如时textarea,或者select,或者checkbox等。根据判断的结果返回不同的内容。

 生成locator

 下面是playwright中生产locator的一个function,可以看到通过注入脚本injectedScript._evaluator.begin()开始,这段代码的主要作用是为指定的HTML元素生成一个或多个唯一的CSS选择器,并返回相关的选择器和匹配的元素列表,以便用于自动化测试或其他需要唯一定位元素的场景。首先是初始化,开始评估选择器生成过程,启用ARIA缓存。如果选项中包含 forTextExpect,则会尝试为目标元素生成一个带有文本的选择器。否则,首先尝试在目标元素的父元素或影子宿主中找到符合特定角色(如按钮、链接等)的元素。然后根据是否允许多个选择器,生成一个或多个选择器,可能包含或不包含文本和CSS ID。生成locator结束后,使用Set去重生成的选择器列表,确保唯一性。最后返回结果。可以看到,为了生成合理的locator,playwright进行很多逻辑处理来保证生成locator的唯一性和合理性。

export function generateSelector(injectedScript: InjectedScript, targetElement: Element, options: GenerateSelectorOptions): { selector: string, selectors: string[], elements: Element[] } {injectedScript._evaluator.begin();beginAriaCaches();try {let selectors: string[] = [];if (options.forTextExpect) {let targetTokens = cssFallback(injectedScript, targetElement.ownerDocument.documentElement, options);for (let element: Element | undefined = targetElement; element; element = parentElementOrShadowHost(element)) {const tokens = generateSelectorFor(injectedScript, element, { ...options, noText: true });if (!tokens)continue;const score = combineScores(tokens);if (score <= kScoreThresholdForTextExpect) {targetTokens = tokens;break;}}selectors = [joinTokens(targetTokens)];} else {targetElement = closestCrossShadow(targetElement, 'button,select,input,[role=button],[role=checkbox],[role=radio],a,[role=link]', options.root) || targetElement;if (options.multiple) {const withText = generateSelectorFor(injectedScript, targetElement, options);const withoutText = generateSelectorFor(injectedScript, targetElement, { ...options, noText: true });let tokens = [withText, withoutText];// Clear cache to re-generate without css id.cacheAllowText.clear();cacheDisallowText.clear();if (withText && hasCSSIdToken(withText))tokens.push(generateSelectorFor(injectedScript, targetElement, { ...options, noCSSId: true }));if (withoutText && hasCSSIdToken(withoutText))tokens.push(generateSelectorFor(injectedScript, targetElement, { ...options, noText: true, noCSSId: true }));tokens = tokens.filter(Boolean);if (!tokens.length) {const css = cssFallback(injectedScript, targetElement, options);tokens.push(css);if (hasCSSIdToken(css))tokens.push(cssFallback(injectedScript, targetElement, { ...options, noCSSId: true }));}selectors = [...new Set(tokens.map(t => joinTokens(t!)))];} else {const targetTokens = generateSelectorFor(injectedScript, targetElement, options) || cssFallback(injectedScript, targetElement, options);selectors = [joinTokens(targetTokens)];}}const selector = selectors[0];const parsedSelector = injectedScript.parseSelector(selector);return {selector,selectors,elements: injectedScript.querySelectorAll(parsedSelector, options.root ?? targetElement.ownerDocument)};} finally {cacheAllowText.clear();cacheDisallowText.clear();endAriaCaches();injectedScript._evaluator.end();}
}

   定义在injectedScript.js中的generateSelector方法,实际在recorder.ts中被调用。下图是recorder.ts中onMouseMove方法的完整代码。可以看到,通过注入脚本,playwright获取到了目标对象event,将event作为参数传入方法中,在生成selector部分就是通过injectedScript.generateSelector生成的。

onMouseMove(event: MouseEvent) {consumeEvent(event);let target: HTMLElement | null = this._recorder.deepEventTarget(event);if (!target.isConnected)target = null;if (this._hoveredElement === target)return;this._hoveredElement = target;let model: HighlightModel | null = null;let selectors: string[] = [];if (this._hoveredElement) {const generated = this._recorder.injectedScript.generateSelector(this._hoveredElement, { testIdAttributeName: this._recorder.state.testIdAttributeName, multiple: false });selectors = generated.selectors;model = {selector: generated.selector,elements: generated.elements,tooltipText: this._recorder.injectedScript.utils.asLocator(this._recorder.state.language, generated.selector),tooltipFooter: selectors.length > 1 ? `Click to select, right-click for more options` : undefined,color: this._assertVisibility ? '#8acae480' : undefined,};}if (this._hoveredModel?.selector === model?.selector)return;this._hoveredModel = model;this._hoveredSelectors = selectors;this._recorder.updateHighlight(model, true);}

    上面大致解释了playwright如何通过注入脚本的方式来录制脚本,接下来看看playwright是如何启动浏览器的。

启动浏览器

   在util/protocol-types-generator目录下的index.js文件中,可以看到调用了playwright.launch()方法可以加载启动不同的浏览器,例如chromium,firefox,webkit等。这里调用的还是playwright-core中的chromium包来launch浏览器的。

  继续查看源代码,可以看到,在playwright-core/server/chromium目录下,有很多代码,这里就是playwright自己实现的管理整个浏览器生命周期的代码。

  以上就是playwright实现录制的大致过程。当调用playwright的功能录制到页面内容后,再调用vscode插件的textedit对象,将生成的内容写入当前打开的测试文件中即可。playwright是一个非常强大的工具,源代码相对比较复杂,如果要快速理解如何通过注入脚本实现录制功能,可以参考上一篇博客,他们在实现思路上是一致的。上一遍博客直接调用puppeteer来启动浏览器,playwright是完全自己实现了浏览器整个生命周期管理,会更加复杂一些。

相关文章:

playwright录制脚本原理

Paywright录制工具UI 在上一篇博客中介绍了如何从0构建一款具备录制UI测试的小工具。此篇博客将从源码层面上梳理playwright录制原理。当打开playwright vscode插件时&#xff0c;点击录制按钮&#xff0c;会开启一个新浏览器&#xff0c;如下图所示&#xff0c;在新开浏览器页…...

awk脚本监控

awk脚本监控 使用脚本监控内存&#xff0c;cpu和硬盘的根目录&#xff0c;超过80%提示用户&#xff0c;写成函数库的行&#xff0c;每天早上 的8.50分&#xff0c;执行一次脚本 现在脚本中写需要的内容 cpuu () {aa$(top -b -n 1 |awk NR3 {printf "%.F",$2$4})if …...

Python高压电容导电体和水文椭圆微分

&#x1f3af;要点 &#x1f3af;二维热传导二阶偏微分方程 | &#x1f3af;调和函数和几何图曲率 | &#x1f3af;解潮汐波动方程 | &#x1f3af;解静止基态旋转球体流体运动函数 | &#x1f3af;水文空间插值 | &#x1f3af;流体流动模拟求解器 | &#x1f3af;随机算法解…...

微信小程序 引入MiniProgram Design失败

这tm MiniProgramDesign 是我用过最垃圾的框架没有之一 我按照官网的指示安装居然能安装不成功,牛! 这里说明我是用js开发的 到以上步骤没有报错什么都没有,然后在引入组件的时候报错 Component is not found in path “./miniprogram _npm/vant/weapp/button/index” (using…...

Java 8 Date and Time API

Java 8引入了新的日期和时间API&#xff0c;位于java.time包下&#xff0c;旨在替代旧的java.util.Date和java.util.Calendar类。新API更为简洁&#xff0c;易于使用&#xff0c;并且与Joda-Time库的一些理念相吻合。以下是Java 8 Date and Time API中几个核心类的简要概述&…...

pyppeteer模块经常使用的功能,相关操作案例

官方仓库地址&#xff1a;https://github.com/miyakogi/pyppeteer 官方文档地址&#xff1a;API Reference — Pyppeteer 0.0.25 documentation Selenium环境的相关配置比较繁琐&#xff0c;此外&#xff0c;有的网站会对selenium和webdriver进行识别和反爬&#xff0c;因此在…...

nginx+keepalived+tomcat集群实验

如遇星河 | nginx+keepalived高可用集群实验 木子87 | Keepalived+Nginx+Tomcat 实现高可用Web集群 环境 192.168.40.204 tomcat-1 192.168.40.138 tomcat-2 安装tomcat [root@bogon local]# vim /etc/profile 添加环境变量 JAVA_HOME=/usr/local/java PATH=$J…...

vue脚手架 axios的二次封装

目录 01 路由懒加载(重要) 02 axios在脚手架中的使用 03.axios的二次封装 04 组件缓存 01 路由懒加载(重要) 一次性导入会出现严重的问题 : 首屏卡顿 因为main.js中引入了router/index.js router/index.js又使用了import语句 静态的引入了每一个组件 导致了首屏卡顿 所以我…...

人机恋爱新趋势:与AI男友谈恋爱的甜蜜与挑战

"我曾经把ChatGPT当成工具&#xff0c;从未追过星&#xff0c;也没有嗑过CP。没想到&#xff0c;到了36岁&#xff0c;我竟然嗑上了AI男友。Open AI&#xff0c;你赢了。你不仅是最好的AI公司&#xff0c;还是乙女游戏公司。" 转行大龄互联网人&#xff0c;走遍20国…...

文生视频开源产品的一些调研(一)

笔者尝试AI视频生成的几个特点&#xff1a; 玄学prompt&#xff0c;每个视频的prompt可能也需要微调很多次&#xff0c;需要找到使用模型的最佳prompt词组合&#xff0c;不恰当的比喻&#xff0c;骑自行车&#xff0c;座位高度等都是人与车彼此熟悉玄学生成&#xff0c;因为需…...

一切前端概念,都是纸老虎

4、listener可以通过 store.getState() 得到当前状态。如果使用的是 React&#xff0c;这时可以触发重新渲染 View。 function listerner() { let newState store.getState(); component.setState(newState); } 对比 Flux 和 Flux 比较一下&#xff1a;Flux 中 Store 是…...

使用自签名 TLS 将 Dremio 连接到 MinIO

Dremio 是一个开源的分布式分析引擎&#xff0c;为数据探索、转换和协作提供简单的自助服务界面。Dremio 的架构建立在 Apache Arrow&#xff08;一种高性能列式内存格式&#xff09;之上&#xff0c;并利用 Parquet 文件格式实现高效存储。有关 Dremio 的更多信息&#xff0c;…...

嵌入式系统软件开发环境_2.一般架构

1.Eclipse框架 嵌入式系统软件开发环境是可帮助用户开发嵌入式软件的一组工具的集合&#xff0c;其架构的主要特征离不开“集成”问题&#xff0c;采用什么样的架构框架是决定开发环境优劣主要因素。Eclipse框架是当前嵌入式系统软件开发环境被普遍公认的一种基础环境框架。目…...

单门户上集成多种数据库查询入口

&#xff08;作者&#xff1a;陈玓玏&#xff09; 开源项目&#xff0c;欢迎star哦&#xff0c;https://github.com/tencentmusic/cube-studio 在一家公司&#xff0c;我们通常会有多种数据库&#xff0c;每种数据库因为其特性承担不同的角色&#xff0c;比如mysql这种轻量…...

华芯微特SWM34-使用定时器捕获快速解码EV1527编码

在无线应用领域&#xff0c;很多433Mhz和315Mhz的遥控器&#xff0c;红外探测器&#xff0c;门磁报警器&#xff0c;无线门铃等都使用EV1527编码格式来发射数据。发射和接收均有对应的RF芯片完成&#xff0c;而且成本极低&#xff08;目前市场价3毛钱不到&#xff09;。接收芯片…...

小程序安卓手机点击uni-data-select 下拉框选择器会出现蓝色阴影

解决方法&#xff1a;在导入的包中找到uni-data-select.vue&#xff0c;接着找到.uni-stat__select样式&#xff0c;把cursor: pointer去掉。 如果出现穿透问题&#xff0c;uni-select__selector的z-index加高&#xff0c;默认是2。...

playwright vscode 插件源码解析

Playwright vscode插件主要功能 Playwright是微软开发的一款主要用于UI自动化测试的工具&#xff0c;在vscode中上安装playwright vscode插件&#xff0c;可以运行&#xff0c;录制UI自动化测试。 playwright vscode插件主要包括两块功能&#xff0c;功能一是在Test Explorer中…...

Mysql: SQL-DDL

一.SQL通用语法 1.SQL可以单行或者多行书写,以分号结尾。 2.SQL语句可以使用空格/缩进来增强语句的可读性。 3.MySQL数据库的SQL语句不区分大小写,关键字建议用大写。 4.注释: 单行注释:注释内容或#注释内容(Mysql特有) 多行注释&#xff1a;/*注释内容*/ 二.SQL分类 1.D…...

Java中的加密与解密:实现安全的数据传输

Java中的加密与解密&#xff1a;实现安全的数据传输 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;在当今信息安全至关重要的时代&#xff0c;保护数据的安全性…...

基于SSM的美食推荐系统

文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于SSM的美食推荐系统,java项目。 ecli…...

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…...

业务系统对接大模型的基础方案:架构设计与关键步骤

业务系统对接大模型&#xff1a;架构设计与关键步骤 在当今数字化转型的浪潮中&#xff0c;大语言模型&#xff08;LLM&#xff09;已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中&#xff0c;不仅可以优化用户体验&#xff0c;还能为业务决策提供…...

springboot 百货中心供应链管理系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;百货中心供应链管理系统被用户普遍使用&#xff0c;为方…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销&#xff0c;平衡网络负载&#xff0c;延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库&#xff0c;获取股票数据&#xff0c;并生成TabPFN这个模型 可以识别、处理的格式&#xff0c;写一个完整的预处理示例&#xff0c;并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务&#xff0c;进行预测并输…...

C++中string流知识详解和示例

一、概览与类体系 C 提供三种基于内存字符串的流&#xff0c;定义在 <sstream> 中&#xff1a; std::istringstream&#xff1a;输入流&#xff0c;从已有字符串中读取并解析。std::ostringstream&#xff1a;输出流&#xff0c;向内部缓冲区写入内容&#xff0c;最终取…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存

文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

Angular微前端架构:Module Federation + ngx-build-plus (Webpack)

以下是一个完整的 Angular 微前端示例&#xff0c;其中使用的是 Module Federation 和 npx-build-plus 实现了主应用&#xff08;Shell&#xff09;与子应用&#xff08;Remote&#xff09;的集成。 &#x1f6e0;️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...

【Go语言基础【13】】函数、闭包、方法

文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数&#xff08;函数作为参数、返回值&#xff09; 三、匿名函数与闭包1. 匿名函数&#xff08;Lambda函…...