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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

探索 Java 死锁:常见原因与解决方案

什么是死锁&#xff1f; 死锁是一种特殊的情况&#xff0c;发生在两个或多个线程彼此等待对方持有的资源&#xff0c;从而陷入无限等待的状态。具体而言&#xff0c;死锁通常涉及以下四个必要条件&#xff1a; 互斥条件&#xff1a;至少有一个资源被一个线程独占。持有并等待…

解决Microsoft Edge浏览器无法使用英文翻译功能

一、问题描述 原来我们使用的Microsoft Edge浏览器是可以对英文界面选择翻译为中文的&#xff1b;但是最近该浏览器更新过后右上角的翻译图标找不到了&#xff0c;无法翻译英文界面内容。 二、解决方法 2.1、打开浏览器的设置界面 2.2、选择语言 2.3、将首选语言下除中文外的…

【2024德国工作】蓝卡攻略:人在中国,怎么去德国工作?

德国工作签证解析 外国人只要拥有符合德国劳动法的劳动合同&#xff0c;工资符合当地标准&#xff08;非紧缺专业&#xff0c;税前工资一般需达到49600欧元&#xff09;&#xff0c;并且具备一定的外语能力&#xff0c;就可以申请德国境内工作签证&#xff01;不申请者还需要有…

【electron 5】electron将获取的Uint8Array转为中文

使用protobufjs&#xff1a; 安装&#xff1a; yarn add global protobufjs yarn add global protobufjs-cli// 将项目中的*.proto文件编译成json文件 npx pbjs -t json electron/main/proto/*.proto > electron/main/proto/proto.json可以在src/proto目录下生成一个proto.…

国内Mac安装Homebrew方法

文章目录 前言步骤 参考&#xff1a;https://blog.csdn.net/itwangyang520/article/details/134125435 前言 今天尝试下载git&#xff0c;官方给的建议是使用Homebrew下载&#xff0c;但发现新电脑里没有&#xff0c;于是尝试Homebrew下载&#xff0c;但发现Homebrew不存在&am…

黑曼巴精神不死!Mamba 2 出世,性能狂飙8倍

年前&#xff0c;Mamba 被 ICLR 拒稿的消息曾引起轩然大波。然而&#xff0c;Mamba作者在6月初又发布了 Mamba 2 架构&#xff0c;这次&#xff0c;Mamba-2 顺利地拿下 ICML。就连 Nvidia 都被吸引&#xff0c;都用它重新训练了GPT3模型。 Transformer vs Mamba vs Mamba2 : 比…

一文读懂交换机MAC地址表:五大关键点,图解21步

HCIA 新班开课了华为HCIA课程介绍苏州面授班 | 全国直播班循环开班&#xff0c;免费重学前言 什么是MAC地址表?MAC地址表有什么作用&#xff1f;MAC地址表里面包含了哪些要素&#xff1f;今天带你好好唠唠。 我们以一个案例为例&#xff1a; 如上图&#xff1a;PC1和PC2通…

C#——堆栈和队列详情

堆栈和队列 堆栈 堆栈类表示一个后进先出的对象集合&#xff0c;当需要对项目进行后进先出的访问时&#xff0c;则可以使用堆栈。向堆栈中添加元素称为推入元素&#xff0c;从堆栈中移除元素称为弹出元素。 关键字: Stack stack常用属性 : 属性 描述Count获取堆栈中包含的…

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值&#xff1a;简单的数据即基础数据类型&#xff0c;按值访问。 引用值&#xff1a;由多个值构成的对象即复杂数据类型&#xff0c;按引用访问。 动态属性 对于引用值而言&#xff0c;可以随时添加、修改和删除其属性和方法。 let person new Object(); p…

昇思25天学习打卡营第1天|认识MindSpore

MindSpore 基本介绍 昇思MindSpore是一个全场景深度学习框架&#xff0c;旨在实现易开发、高效执行、全场景统一部署三大目标。 易开发&#xff1a;API友好、调试难度低高效执行&#xff1a;包括计算效率、数据预处理效率和分布式训练效率全场景&#xff1a;框架同时支持云、…

LabVIEW操作系列

目的&#xff1a;初学LabVIEW&#xff0c;做记录 文章目录 前言一、LabVIEW操作界面1.1 新建vi1.2 控件1.3 加法案例 二、编程特点2.1 特点2.2 实现按顺序执行 三、程序结构3.1 平铺式顺序结构3.2 循环结构3.2.1 For循环3.2.2 While循环3.2.3 中途停止For循环3.2.4 中途停止Whi…

区块链实验室(37) - 交叉编译百度xuperchain for arm64

纠结了很久&#xff0c;终于成功编译xuperchain for arm64。踩到1个坑&#xff0c;说明如下。 1、官方文档是这么说的&#xff1a;go语言版本推荐1.5-1.8 2、但是同一个页面&#xff0c;又是这么说的&#xff1a;不推荐使用1.11之前的版本。 3、问题来了&#xff1a;用什么版本…

数据库拆分

1.1 数据库拆分的背景和意义 在互联网和大数据时代&#xff0c;企业和应用程序需要处理海量数据并提供高并发访问能力。传统的单一数据库架构在面对大规模数据和高并发请求时&#xff0c;逐渐暴露出性能瓶颈、扩展性不足、可用性差和维护成本高等问题。为了解决这些问题&#…

用于世界上最先进的医疗应用的精密电阻器

EAK的高性能电阻器使医疗产品设计人员能够继续改善全球患者的生活质量。我们的电阻器专为用于医疗诊断、治疗和预防的各种产品而设计。从小型植入式和非侵入性设备到大型诊断成像设备&#xff0c;医疗制造商之所以选择EAK 电阻器&#xff0c;是因为操作环境是高电压和磁场&…

ES内存溢出报错问题解决方案

博主有话说&#xff1a;该博文根据实际案例编写&#xff0c;在编写过程中将敏感信息进行替换&#xff0c;可能存在矛盾的地方&#xff0c;望见谅 1 错误详情 [typecircuit_breaking_exception, reason[parent] Data too large, data for [<http_request>] would be [1256…

Java--Data类

1.Data类 java.util.Date.表示指定的时间信息&#xff0c;不支持国际化 构造方法 new Date()&#xff1a;当前系统日期和时间 new Date(long)&#xff1a;给定日期和时间 主要方法&#xff1a; after(Date):判断当前日期对象是否在给定日期对象之后 before(Date):判断当前日期…

(上位机APP开发)调用华为云属性修改API接口修改设备属性

一、功能说明 通过调用华为云IOT提供的属性修改API接口,给设备下发属性修改消息。 API接口地址:https://support.huaweicloud.com/api-iothub/iot_06_v5_0034.html 此接口支持在线调试:https://console.huaweicloud.com/apiexplorer/#/openapi/IoTDA/doc?api=UpdatePrope…

(一)、配置服务器的多个网卡路由,访问多个不同网络段

一、现场网络关系说明 有这么一个需要&#xff0c;服务器有三个网口&#xff0c;网口一需要访问外网&#xff0c;网口二需要访问内网1&#xff0c;网口2需要访问内网2。需要配置路由来满足该网络访问需要。 图1 现场网络关系 二、配置教程 步骤1&#xff1a; a、命令行输入…

文本批量高效编辑神器:空格秒变分隔符,提升工作效率

在信息爆炸的时代&#xff0c;文本处理已成为我们日常工作中不可或缺的一部分。然而&#xff0c;面对海量的文本数据&#xff0c;如何高效、准确地进行编辑和整理&#xff0c;成为了我们面临的难题。今天&#xff0c;我要向大家介绍一款文本批量高效编辑神器——首助编辑高手&a…

Springboot整合Mongodb(含使用案例)

基础语法 插入 插入单条 // 插入一条数据到 "Books" 集合 db.Books.insertOne({title: "如何使用MongoDB",author: "IT小辉同学",year: 2023 })插入多条数据 // 插入十条数据到 "Books" 集合 db.Books.insertMany([{ title: "…