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

playwright vscode 插件源码解析

Playwright vscode插件主要功能

  Playwright是微软开发的一款主要用于UI自动化测试的工具,在vscode中上安装playwright vscode插件,可以运行,录制UI自动化测试。

  playwright vscode插件主要包括两块功能,功能一是在Test Explorer中显示项目中所有的测试文件,选择某个测试文件,可以执行这些由playwright编写的测试。功能二是Playwright webview视图,里面可以选择projects,即选择执行测试的浏览器,settings设置,选择显示浏览器,还是trace viewer,Tools目录下有Pick locator,Record new,Record at cursor菜单,点击Pick locator或者录制菜单,会启动一个浏览器,在浏览器中输入被测web应用的url,就可以开始录制了,录制的代码会写到vscode管理下的测试文件中。

Test Explorer实现原理

  那么,如何实现在Test Explorer中显示测试文件并执行的呢?查看playwright vscode插件源码,testTree and testModel文件主要负责以treeview的方式显示测试文件,testModel里面封装了运行或者调试测试的代码。settingView里面是一个webview,palaywright下面的projects,settings,tools等UI的显示,都是由settingView里面编写。playwrightTestServer.ts 和playwrightTestCLI.ts主要是实现运行,录制测试的具体逻辑。backend.ts里面是启动一个websocket服务,通过发送和监听消息来执行测试。

  关于如何通过vscode提供的treeview和testcontroller来实现Test explorer下显示测试文件的部分,可以先阅读这两篇博客,treeview使用,testcontroller使用,这两篇博客中给出了构建treeview和testcontroller的简单易懂的例子。Test explorer中显示的测试文件实现思路和上面例子大致相同,解析source code中测试文件信息,并以treeview的方式显示出来。当选择某个文件执行后,会在测试文件名称下面显示测试执行时间,测试名称等。在测试执行是,获取测试时间,执行状态等信息,组装成testItem,再添加到testcontroller中,通过testcontroller来管理整个测试的生命周期。

  测试执行的底层逻辑是什么

  上面主要介绍的playwright vscode插件的UI部分,那么底层是如何实现测试执行的呢?下面是playwrightTestCLI.ts文件中runTests方法的代码。下面code中,首先设置了一些测试执行参数,例如--headed,--workers,--trace等,然后通过__innerSpawn方法执行测试,另外还创建ReportServer,监听测试执行结果信息。__innerSpawn通过child_process.spawn 方法启动一个新的 Node.js 进程来运行 Playwright 测试。具体步骤和效果如下:

启动一个 Node.js 进程:使用 node 可执行文件和 Playwright CLI 来运行测试。
传递命令行参数
this._model.config.cli: Playwright CLI 路径。
'test': 运行测试的命令。
'-c', configFile: 指定配置文件。
...extraArgs: 额外的命令行参数。
...escapedLocations: 要运行的测试文件或目录。
设置工作目录:使用 configFolder 作为当前工作目录。
设置标准输入输出:重定向子进程的所有标准输入输出流(包括标准输入、标准输出、标准错误输出和额外的自定义流)。
配置环境变量:通过扩展当前进程的环境变量,并添加或覆盖一些特定的环境变量,例如 CI, NODE_OPTIONS, PW_TEST_REUSE_CONTEXT, PW_TEST_CONNECT_WS_ENDPOINT 等。

 async runTests(items: vscodeTypes.TestItem[], options: PlaywrightTestRunOptions, reporter: reporterTypes.ReporterV2, token: vscodeTypes.CancellationToken): Promise<void> {const { locations, parametrizedTestTitle } = this._narrowDownLocations(items);if (!locations)return;const args = [];this._model.enabledProjectsFilter().forEach(p => args.push(`--project=${p}`));if (parametrizedTestTitle)args.push(`--grep=${escapeRegex(parametrizedTestTitle)}`);args.push('--repeat-each=1');args.push('--retries=0');if (options.headed)args.push('--headed');if (options.workers)args.push(`--workers=${options.workers}`);if (options.trace)args.push(`--trace=${options.trace}`);await this._innerSpawn(locations, args, options, reporter, token);}async _innerSpawn(locations: string[], extraArgs: string[], options: PlaywrightTestRunOptions, reporter: reporterTypes.ReporterV2, token: vscodeTypes.CancellationToken) {if (token?.isCancellationRequested)return;// Playwright will restart itself as child process in the ESM mode and won't inherit the 3/4 pipes.// Always use ws transport to mitigate it.const reporterServer = new ReporterServer(this._vscode);const node = await findNode(this._vscode, this._model.config.workspaceFolder);const configFolder = path.dirname(this._model.config.configFile);const configFile = path.basename(this._model.config.configFile);const escapedLocations = locations.map(escapeRegex).sort();{// For tests.const relativeLocations = locations.map(f => path.relative(configFolder, f)).map(escapeRegex).sort();const printArgs = extraArgs.filter(a => !a.includes('--repeat-each') && !a.includes('--retries') && !a.includes('--workers') && !a.includes('--trace'));this._log(`${escapeRegex(path.relative(this._model.config.workspaceFolder, configFolder))}> playwright test -c ${configFile}${printArgs.length ? ' ' + printArgs.join(' ') : ''}${relativeLocations.length ? ' ' + relativeLocations.join(' ') : ''}`);}const childProcess = spawn(node, [this._model.config.cli,'test','-c', configFile,...extraArgs,...escapedLocations,], {cwd: configFolder,stdio: ['pipe', 'pipe', 'pipe', 'pipe', 'pipe'],env: {...process.env,CI: this._options.isUnderTest ? undefined : process.env.CI,// Don't debug tests when running them.NODE_OPTIONS: undefined,...this._options.envProvider(),PW_TEST_REUSE_CONTEXT: options.reuseContext ? '1' : undefined,PW_TEST_CONNECT_WS_ENDPOINT: options.connectWsEndpoint,...(await reporterServer.env()),// Reset VSCode's options that affect nested Electron.ELECTRON_RUN_AS_NODE: undefined,FORCE_COLOR: '1',PW_TEST_HTML_REPORT_OPEN: 'never',PW_TEST_NO_REMOVE_OUTPUT_DIRS: '1',}});const stdio = childProcess.stdio;stdio[1].on('data', data => reporter.onStdOut?.(data));stdio[2].on('data', data => reporter.onStdErr?.(data));await reporterServer.wireTestListener(reporter, token);}

  在test-explorer中选择某个文件,选择dubug test,会在debug output窗口中显示如下信息,这段信息和上面的代码是完全匹配的,从这里可以看到当在vscode extension窗口中选择某个测试文件,点击执行按钮时,实际背后是通过node执行playwright的cli命令完成执行的。

  有了CLI为什么还要启动Backend呢

  查看testModel.ts文件中的code,会有这段代码,通过这行代码可以知道PlaywrightTestCLI方式是遗留的老方式,新方式是调用PlaywrightTestServer来执行测试。而PlaywrightTestServer.ts文件里面执行测试的时候需要启动Backend server。说明CLI方式是早期的方式,现在又构建了新的方式,即启动backend server的方式来执行测试。

    this._playwrightTest =  this._useLegacyCLIDriver ? new PlaywrightTestCLI(vscode, this, options) : new PlaywrightTestServer(vscode, this, options);

   BackendServer的实现逻辑是什么?

  查看PlaywrightTestServer.ts中runTest方法,实际调用的是testServerConnection.ts中的runTest方法,代码细节如下所示,这表明,启动backend server后,通过发送和监听消息的方式来执行或者录制测试。

  async runTests(params: Parameters<TestServerInterface['runTests']>[0]): ReturnType<TestServerInterface['runTests']> {return await this._sendMessage('runTests', params);}

  查看backend.ts的代码,如下所示,这里是启动backend server的方法,可以看到启动backend server本质上也是使用spawn调用node进程而已。

export async function startBackend(vscode: vscodeTypes.VSCode, options: BackendServerOptions & { onError: (error: Error) => void, onClose: () => void }): Promise<string | null> {const node = await findNode(vscode, options.cwd);const serverProcess = spawn(node, options.args, {cwd: options.cwd,stdio: 'pipe',env: {...process.env,...options.envProvider(),},});serverProcess.stderr?.on('data', data => {if (options.dumpIO)console.log('[server err]', data.toString());});serverProcess.on('error', options.onError);serverProcess.on('close', options.onClose);return new Promise(fulfill => {serverProcess.stdout?.on('data', async data => {if (options.dumpIO)console.log('[server out]', data.toString());const match = data.toString().match(/Listening on (.*)/);if (!match)return;const wse = match[1];fulfill(wse);});serverProcess.on('exit', () => fulfill(null));});
}

  查看testServerConnection的构造方法,可以看到,这里通过new WebSocket启动了一个websocket服务,且这个服务队message进行监听处理。具体代码如下所示:

constructor(wsURL: string) {this.onClose = this._onCloseEmitter.event;this.onReport = this._onReportEmitter.event;this.onStdio = this._onStdioEmitter.event;this.onListChanged = this._onListChangedEmitter.event;this.onTestFilesChanged = this._onTestFilesChangedEmitter.event;this.onLoadTraceRequested = this._onLoadTraceRequestedEmitter.event;this._ws = new WebSocket(wsURL);this._ws.addEventListener('message', event => {const message = JSON.parse(String(event.data));const { id, result, error, method, params } = message;if (id) {const callback = this._callbacks.get(id);if (!callback)return;this._callbacks.delete(id);if (error)callback.reject(new Error(error));elsecallback.resolve(result);} else {this._dispatchEvent(method, params);}});const pingInterval = setInterval(() => this._sendMessage('ping').catch(() => {}), 30000);this._connectedPromise = new Promise<void>((f, r) => {this._ws.addEventListener('open', () => f());this._ws.addEventListener('error', r);});this._ws.addEventListener('close', () => {this._isClosed = true;this._onCloseEmitter.fire();clearInterval(pingInterval);});}

  在playwrightTestServer.ts文件中有一个createTestServer的私有方法,该方法返回的是TestServerConnection对象,在这个方法中,首先调用startBackend 得到wsEnpoint,即websocket服务的endpoint信息,在将wsEnpoint传入TestServerConnection这个class中。最终实现启动一个websocket,通过向websocket服务发送消息的方式来执行、停止测试等操作。

 private async _createTestServer(): Promise<TestServerConnection | null> {const args = [this._model.config.cli, 'test-server', '-c', this._model.config.configFile];const wsEndpoint = await startBackend(this._vscode, {args,cwd: this._model.config.workspaceFolder,envProvider: () => {return {...this._options.envProvider(),FORCE_COLOR: '1',};},dumpIO: false,onClose: () => {this._testServerPromise = undefined;},onError: error => {this._testServerPromise = undefined;},});if (!wsEndpoint)return null;const testServer = new TestServerConnection(wsEndpoint);testServer.onTestFilesChanged(params => this._testFilesChanged(params.testFiles));await testServer.initialize({serializer: require.resolve('./oopReporter'),interceptStdio: true,closeOnDisconnect: true,});return testServer;}

    查看testserverConnection,可以看到定义了很多message,除了运行和停止测试外,还包括listFile,listtest等。

  总结而言,不管是直接调用playwrighttestCLI文件里面的方法执行测试,还是启动backend的websocket服务,本质上都是启动node,调用playwright.cli.js文件,传入测试文件名称等参数来实现测试执行的。那么如何类似下面的node命令封装成websocket服务来执行测试呢?下一篇博客将介绍这个点的详细内容。

/opt/homebrew/bin/node ./node_modules/@playwright/test/cli.js test -c playwright.config.js --headed --project=chromium --repeat-each=1 --retries=0 --timeout=0 --workers=1 /Users/taoli/study/playwrightDemo/tests/test-1.spec.ts:3

相关文章:

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…...

Pycharm利用Anaconda环境

创建环境 conda create --name d2l python3.11 -y 激活环境 conda activate d2l 配置环境 以torch环境为例 我们可以按如下⽅式安装PyTorch的CPU或GPU版本&#xff1a; pip install torch pip install torchvision 我们的下⼀步是安装d2l包&#xff0c;以⽅便调取本书中经…...

Python函数魔术:深入理解18个高级函数特性

今天&#xff0c;我们将一起探索那些让代码瞬间变得优雅而强大的高级函数。准备好&#xff0c;让我们一起揭开它们的神秘面纱吧&#xff01; 1. map()&#xff1a;一招制胜&#xff0c;批量操作 想象一下&#xff0c;你需要给一个数字列表的每个元素加上5。普通的循环是不是让…...

在大型单页应用(SPA)中,如何处理状态管理的

在大型单页应用&#xff08;SPA&#xff09;中&#xff0c;如何处理状态管理的 在大型单页应用&#xff08;SPA&#xff09;中&#xff0c;状态管理是一个关键环节&#xff0c;确保数据的一致性和可预测的变更。以下是一些常见的状态管理方法&#xff1a; Redux&#xff1a; …...

力扣78 子集

给你一个整数数组 nums &#xff0c;数组中的元素 互不相同 。返回该数组所有可能的 子集&#xff08;幂集&#xff09;。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[],[1],[2],[1,2],[3],…...

C语言从头学24——函数原型

前面学习函数时已经知道必须先存在一个函数然后才能使用&#xff0c;且这个函数的位置还要在 main()函数之前&#xff0c;否则的话编译时会报错。但我们写程序时&#xff0c;main() 函数是程序入口&#xff0c;程序的主要部分都在 main() 函数中&#xff0c;且其它函数也都要通…...

Vue中使用ElementUI组件Form组件的校验validate

先准备一些el-form元素 这里面el-form中:model(v-bind:model)是单项绑定的&#xff0c;如果你写成了v-model""可能会出现校验没有效果的情况。 这是校验过后的结果了 现在开始使用下吧&#xff01; 1.在el-form中绑定一个ref&#xff0c;名字自拟,后续触发检验结果…...

PostgreSQL性能优化之分区表 #PG培训

在处理大规模数据时&#xff0c;PostgreSQL的性能优化是一个非常重要的话题&#xff0c;其中分区表&#xff08;Partitioned Tables&#xff09;是提高查询和数据管理效率的重要手段。本文将详细介绍PostgreSQL分区表的概念、优势、创建与管理方法以及一些常见的优化策略。 #P…...

SAPUI5基础知识9 - JSON Module与数据绑定

1. 背景 在前面的博客中&#xff0c;我们已经学习了SAPUI5中视图和控制器的使用&#xff0c;在本篇博客中&#xff0c;让我们学习下MVC架构中的M-模型了。 SAPUI5中的JSON Model是一个客户端模型&#xff0c;可以用于在SAPUI5应用程序中处理和操作JSON数据。SAPUI5提供了绑定…...

解决vue3使用ref 获取不到子组件属性问题

需求&#xff1a; 父子组件使用<script setup>语法糖&#xff0c;父组件通过给子组件定义ref访问子组件内部属性或事件。 关键点&#xff1a; 子组件中&#xff0c;setup语法糖需要用defineExpose把要读取的属性和方法单独暴露出去&#xff0c;否则会访问失败&#xf…...

使用STL容器还是Qt容器?

在C编程中&#xff0c;选择合适的容器库对于编写高效、可维护的代码至关重要。两大主流选择是STL容器&#xff08;如std::map&#xff0c;std::vector等&#xff09;和Qt容器&#xff08;如QMap&#xff0c;QVector等&#xff09;。本文将探讨两者的优缺点&#xff0c;以帮助开…...

Android 2ndBLE的实现

没有需求创造需求 有没有想过一件事情&#xff0c;假如你的手机关机了&#xff0c;而且在家里怎么都找不到的情况&#xff1f; 那么通过另外一种手机进行查找是不是可以&#xff1f;听上去有点搞笑&#xff0c;但实际上确实有这样的需求存在。实现方案是用超低功耗蓝牙&#…...

常见硬件工程师面试题(二)

大家好&#xff0c;我是山羊君Goat。 对于硬件工程师&#xff0c;学习的东西主要和电路硬件相关&#xff0c;所以在硬件工程师的面试中&#xff0c;对于经验是十分看重的&#xff0c;像PCB设计&#xff0c;电路设计原理&#xff0c;模拟电路&#xff0c;数字电路等等相关的知识…...

java构造方法的重载

在java中&#xff0c;与普通方法一样&#xff0c;构造方法也可以重载&#xff0c;在一个类中可以定义多个构造方法&#xff0c;但是要求每个构造方法的参数类型或参数不同。在创建对象时&#xff0c;可以通过调用不同的构造方法为不同属性赋值。 示例代码如下 class Student5…...

webpack 压缩图片

压缩前&#xff1a; 压缩后&#xff1a; 压缩后基本上是压缩了70-80%左右 1.依赖版本及配置 "imagemin-webpack-plugin": "^2.4.2", "imagemin-mozjpeg": "^7.0.0", "imagemin-pngquant": "^5.0.1", "webpa…...

JAVA每日作业day6.24

ok了家人们今天学习了一些关键字&#xff0c;用法和注意事项&#xff0c;静态代码块这些知识&#xff0c;闲话少叙我们一起看看吧。 一&#xff0c;final关键字 1.1 final关键字的概述 final&#xff1a; 不可改变。可以用于修饰类、方法和变量。 类&#xff1a;被修饰的类&a…...

鸿蒙开发系统基础能力:【@ohos.hiTraceChain (分布式跟踪)】

分布式跟踪 本模块提供了端侧业务流程调用链跟踪的打点能力&#xff0c;包括业务流程跟踪的启动、结束、信息埋点等能力。 说明&#xff1a; 本模块首批接口从API version 8开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import hi…...

.git目录解读

在执行 git init 命令时&#xff0c;Git 会在当前目录中创建一个名为 .git 的目录。该目录包含 Git 所需的所有元数据和对象&#xff0c;用于版本控制。以下是 .git 目录结构的示意图&#xff0c;并附有每个目录和文件的作用说明&#xff1a; .git #…...

如何在Java中处理InterruptedException异常?

如何在Java中处理InterruptedException异常&#xff1f; 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 在Java编程中&#xff0c;多线程是一个常见的应用场景…...

深入解读Netty中的NIO:原理、架构与实现详解

深入解读Netty中的NIO&#xff1a;原理、架构与实现详解 Netty是一个基于Java的异步事件驱动网络应用框架&#xff0c;广泛用于构建高性能、高可扩展性的网络服务器和客户端&#xff08;学习netty请参考&#xff1a;深入浅出Netty&#xff1a;高性能网络应用框架的原理与实践&…...

Vim和Nano简介

**Vim**&#xff1a; - Vim 是一个文本编辑器&#xff0c;它是 Vi 编辑器的一个改进版本&#xff0c;Vi 编辑器最初由 Bill Joy 在1976年为 BSD Unix 开发。 - Vim 由 Bram Moolenaar 开发&#xff0c;其第一个版本在1991年发布。Vim 的设计理念是“持继改进”&#xff0c;它的…...

mysql的information_schema浅析

information_schema 是 MySQL 中的一个虚拟数据库&#xff0c;它包含了关于 MySQL 服务器的所有元数据。 information_schema 作用 元数据管理&#xff1a;提供关于数据库、表、列、索引、权限等的信息。 性能优化&#xff1a;帮助了解数据库结构和索引使用情况&#xff0c;便…...

力扣爆刷第153天之TOP100五连刷26-30(接雨水、环形链表、最长上升子序列)

力扣爆刷第153天之TOP100五连刷26-30&#xff08;接雨水、环形链表、最长上升子序列&#xff09; 文章目录 力扣爆刷第153天之TOP100五连刷26-30&#xff08;接雨水、环形链表、最长上升子序列&#xff09;一、300. 最长递增子序列二、415. 字符串相加三、143. 重排链表四、42.…...

【Linux】—Apache Hive 安装部署

文章目录 前言认识Metadata认识Metastoremetastore三种配置方式 一、安装前准备二、下载hive-3.1.2安装包三、下载完成后&#xff0c;通过xftp6上传到Linux服务器上四、解压Hive安装包五、配置Hive六、内嵌模型安装—Hive元数据配置到Derby七、本地模式安装—Hive元数据配置到M…...

组装盒示范程序

代码; #include <gtk-2.0/gtk/gtk.h> #include <glib-2.0/glib.h> #include <stdio.h>int main(int argc, char *argv[]) {gtk_init(&argc, &argv);GtkWidget *window;window gtk_window_new(GTK_WINDOW_TOPLEVEL);gtk_window_set_title(GTK_WINDO…...

推荐一款AI修图工具,支持AI去水印,AI重绘,AI抠图...

不知道大家有没有这样的一个痛点&#xff0c;发现了一张不错的“素材”&#xff0c; 但是有水印&#xff0c;因此不能采用&#xff0c;但找来找去&#xff0c;还是觉得初见的那个素材不错&#xff0c;怎么办&#xff1f; 自己先办法呗。 二师兄发现了一款功能强大的AI修图工具…...

2024广东省职业技能大赛云计算赛项实战——容器化部署Nginx

容器化部署Nginx 前言 编写Dockerfile文件构建nginx镜像&#xff0c;要求基于centos完成Nginx服务的安装和配置&#xff0c;并设置服务开机自启。 编写Dockerfile构建镜像erp-nginx:v1.0&#xff0c;要求使用centos7.9.2009镜像作为基础镜像&#xff0c;完成Nginx服务的安装&…...