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是完全自己实现了浏览器整个生命周期管理,会更加复杂一些。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/740704.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Shopee API接口:轻松获取虾皮购物平台的商品数据信息

一、核心功能介绍——获取虾皮购物平台的商品数据信息 实时获取商品数据&#xff1a;Shopee接口提供实时更新的商品数据&#xff0c;包括商品名称、价格、库存、描述等详细信息&#xff0c;确保商家能够及时掌握商品最新状态。高效数据检索&#xff1a;商家可以根据不同的需求…

作为一名车载测试工程师,核心能力是什么?

最近经常有人会问我&#xff0c;说XX培训机构专门培训车载测试&#xff0c;我要去&#xff0c;而且薪资很高&#xff0c;现在是风口&#xff0c;你是否也听过这样的销售话语&#xff1f; 然后进去培训2-3个月&#xff0c;包括上车测试&#xff0c;后来进去后发现原来真实的场景…

为什么都放弃了LangChain?

或许从诞生那天起&#xff0c;LangChain 就注定是一个口碑两极分化的产品。 看好 LangChain 的人欣赏它丰富的工具和组建和易于集成等特点&#xff0c;不看好 LangChain 的人&#xff0c;认为它注定失败 —— 在这个技术变化如此之快的年代&#xff0c;用 LangChain 来构建一切…

活动预告|探索 LLM 大模型的小型化 —— 微软 Phi3在 NVIDIA Jetson 与 NIM 平台的最佳实践

在当前高速发展的人工智能领域&#xff0c;如何高效的部署和优化 SLM (小型的大模型) 成为关键。随着微软 Phi-3 系列模型的发布&#xff0c;让 SLM 在 NVIDIA Jetson 边缘计算平台上的部署成为可能。同时 Phi-3 系列模型已在 NVIDIA NIM 平台提供加速的推理服务。 NVIDIA NIM…

ONLYOFFICE 桌面编辑器8.1---一个高效且强大的办公软件

软件介绍 ONLYOFFICE 桌面编辑器经过不断的更新换代现在迎来了&#xff0c;功能更加强大的ONLYOFFICE 桌面编辑器8.1是一个功能强大的办公套件&#xff0c;专为多平台设计&#xff0c;包括Windows、Linux和macOS。它提供了一套全面的办公工具&#xff0c;包括文档处理、电子表…

Scope XY Project的使用

1.Scope XY Project的功能介绍与使用方法 添加监控变量 绘制成一个三角形 XY进行对调操作 修改XY轴的比例修改显示输出 2.Cursor的使用方法 游标线的添加测量 3.Reporting功能的使用方法 到处对应的报表数据 添加对应的报告数据

Pytorch之视频流猫狗识别

1. 导入资源包 // An highlighted block var foo bar;注&#xff1a; 1. import cv2: 导入OpenCV库&#xff0c;这是一个非常强大的计算机视觉库&#xff0c;用于处理图像和视频数据。 2. import tkinter as tk: 导入Tkinter库&#xff0c;这是Python的标准GUI库&#xff0c;…

Java包介绍

今天看jdk文档&#xff0c;顺便写一下java几个包的作用。 java.applet 主要用于创建java applet小应用程序&#xff0c;可以嵌入到网页中能够呈现出特殊的效果&#xff0c;现在基本已经被废弃&#xff0c;很少使用。 java.awt AWT 是Abstract Window ToolKit (抽象窗口工具包…

Python 类对象

Python 类对象 经典迭代器 可迭代对象的定义&#xff1a; 使用内置的iter可以获取迭代器的对象。如果对象实现了能返回迭代器的__iter__方法&#xff0c;那么对象就是可迭代的。序列都可以迭代。实现了__getitem__方法&#xff0c;而且接受从0开始的索引&#xff0c;这种对象也…

perfect-scrollbar缩小浏览器窗口滚动条无线滚动的bug

https://github.com/mdbootstrap/perfect-scrollbar/issues/153

微信小程序之横向列表展示

效果图 参考微信小程序可看 代码&#xff1a; <view class"lbtClass"><view class"swiper-container"><scroll-view class"swiper" scroll-x"true" :scroll-left"scrollLeft"><block v-for"(six…

支付互通新进展:微信收款码全场景接入银联网络

随着支付便利化工作的不断推进&#xff0c;条码支付互联互通已经成为行业发展的重要趋势。 近日&#xff0c;银联网络迎来微信支付收款码场景的全面接入。 这一举措推动条码支付互联互通取得新进展&#xff0c;为境内外广大消费者提供更多支付选择、更好支付体验。无论从行业…

【C++】二叉搜索树|Key模型|key_value模型|基本操作

目录 ​编辑 二叉搜索树的定义&#xff0c;创建&#xff08;Key模型&#xff09; 定义 创建 基本操作 插入 查找 删除 Key模型和Key_Value模型 二叉搜索树&#xff08;Key_Value模型&#xff09; 定义 创建 基本操作 插入 应用 二叉搜索树的定义&#xff0c;创建&…

数据模型(models)

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 &#xff08;1&#xff09;在App中添加数据模型 在app1的models.py中添加如下代码&#xff1a; from django.db import models # 引入django.…

【面试题】马上金九银十了,简历该准备起来了,面试题你准备好了吗 ?浅谈 JS 浅拷贝和深拷贝

代码展示 let obj_old {name: Tom,age: 15,favorite: {food: bread,drink: milk} } let obj_new {...obj_old} console.log(obj_old obj_new) // false console.log(obj_old.name obj_new.name) // true console.log(obj_old.favorite obj_new.favorite) // true3. Ar…

编程精粹—— Microsoft 编写优质无错 C 程序秘诀 08:剩下的就是态度问题

这是一本老书&#xff0c;作者 Steve Maguire 在微软工作期间写了这本书&#xff0c;英文版于 1993 年发布。2013 年推出了 20 周年纪念第二版。我们看到的标题是中译版名字&#xff0c;英文版的名字是《Writing Clean Code ─── Microsoft’s Techniques for Developing》&a…

vue配置中的process.env

项目中的.env开头的文件是否知道是干什么的呢 主要是为了区分测试环境还是生产环境env.development为测试环境 # 测试环境 NODE_ENV development VUE_APP_BASE_API http://xxxxxxxxx // 命名一定要以 VUE_APP_ 开头&#xff0c;要不然根本取不到 .env.production为生产环境…

企业文件传输系统只能传输?分享功能同样重要!(上)

在当今的商业世界里&#xff0c;企业间的协作和信息交流已经变得极其重要&#xff0c;特别是在处理那些庞大的文件时。想象一下&#xff0c;设计图、高清视频、大数据分析&#xff0c;还有软件开发包&#xff0c;这些文件的大小往往达到GB甚至TB级别&#xff0c;它们是企业日常…

猫头虎 分享已解决Error || **Data Leakage**: `Unexpectedly high validation performance`

猫头虎 分享已解决Error || Data Leakage: Unexpectedly high validation performance &#x1f42f; 摘要 &#x1f4c4; 大家好&#xff0c;我是猫头虎&#xff0c;一名专注于人工智能领域的博主。在AI开发中&#xff0c;我们经常会遇到各种各样的错误&#xff0c;其中Data…

LONGHEADS:无需训练的多头注意力长文本处理框架

大模型&#xff08;LLMs&#xff09;在处理海量文本数据时展现出了前所未有的能力。然而这些模型在面对超出其训练时所见序列长度的长文本时存在两个主要问题&#xff1a;一是模型对于超出预训练长度的文本难以有效泛化&#xff0c;二是注意力机制的二次方时间复杂度导致计算成…