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.rhkb.cn/news/357414.html

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

相关文章

机器学习课程复习——奇异值分解

1. 三种奇异值分解 奇异值分解&#xff08;Singular Value Decomposition, SVD&#xff09;包含了&#xff1a; 完全奇异值分解&#xff08;Complete Singular Value Decomposition, CSVD&#xff09;紧奇异值分解&#xff08;Tight Singular Value Decomposition, TSVD&…

快速UDP网络连接之QUIC协议介绍

文章目录 一、QUIC协议历史1.1 问题&#xff1a;QUIC为什么在应用层实现1.2 QUIC协议相关术语1.3 QUIC和TCP对比1.4 QUIC报文格式1.4.1 QUIC报文格式-Stream帧11.4.2 QUIC报文格式-Stream帧2 二、QUIC的特点2.1 连接建立低时延&#xff0c;2.2 多路复用流复用-HTTP1.1流复用-HT…

ubuntu22.04笔记: 更换为阿里源

没有按照LTS 版本 会遇到下面问题&#xff1a; 参考&#xff1a;https://zhuanlan.zhihu.com/p/691625646 Ubuntu 22.04代号为&#xff1a;jammy Ubuntu 20.04代号为&#xff1a;focal Ubuntu 19.04代号为&#xff1a;disco Ubuntu 18.04代号为&#xff1a;bionic Ubuntu …

ReactNative和Android通信

初始化一个RN项目以后&#xff0c;接下来想要让Android与React Native通信 写一个继承自ReactContextBaseJavaModule类的子类&#xff0c;重写getName方法 package com.awesomeprojectimport android.util.Log import android.widget.Toast import com.facebook.react.bridge.…

Apache Doris 之 Docker 部署篇

前言 在现代数据驱动的商业环境中&#xff0c;实时数据分析和高并发查询能力是企业成功的关键因素之一。传统的数据仓库和分析工具在面对大规模数据处理和实时分析需求时&#xff0c;往往力不从心。Apache Doris 作为一个现代的 MPP 数据库管理系统&#xff0c;凭借其强大的查…

最新Sublime Text软件安装包分享(汉化版本)

Sublime Text 是一款广受欢迎的跨平台文本编辑器&#xff0c;专为代码、标记和散文编辑而设计。它以其简洁的用户界面、强大的功能和高性能而著称&#xff0c;深受开发者和写作者的喜爱。 一、下载地址 链接&#xff1a;https://pan.baidu.com/s/1kErSkvc7WnML7fljQZlcOg?pwdk…

监控 Prometheus源码安装实战和动态更新 Centos7

安装go环境 下载go安装包 #创建文件夹 mkdir /usr/local/software #进入文件夹 cd /usr/local/software #下载安装包 wget https://dl.google.com/go/go1.17.6.linux-amd64.tar.gz配置go环境变量 #解压 tar -zxvf go1.17.6.linux-amd64.tar.gz#配置环境变量 echo "exp…

【GD32】从零开始学兆易创新32位微处理器——RTC实时时钟+日历例程

1 简介 RTC实时时钟顾名思义作用和墙上挂的时钟差不多&#xff0c;都是用于记录时间和日历&#xff0c;同时也有闹钟的功能。从硬件实现上来说&#xff0c;其实它就是一个特殊的计时器&#xff0c;它内部有一个32位的寄存器用于计时。RTC在低功耗应用中可以说相当重要&#xf…

【调试笔记-20240619-Windows-Typescripts中类型不匹配的解决方法】

调试笔记-系列文章目录 调试笔记-20240619-Windows-Typescripts中类型不匹配的解决方法 文章目录 调试笔记-系列文章目录调试笔记-20240619-Windows-Typescripts中类型不匹配的解决方法 前言一、调试环境操作系统&#xff1a;Windows 10 专业版调试环境调试目标 二、调试步骤搜…

Linux 五种IO模型

注&#xff1a;还有一种信号驱动IO&#xff0c;使用较少暂不讨论&#xff1b; 一&#xff0c;区分阻塞、非阻塞和同步、异步 看了很多文章对这两组概念解释和对比&#xff0c;说的太复杂了&#xff0c;其实没必要&#xff0c;两句话就能说清楚。 首先&#xff0c;对于读数据rec…

Qt制作程序启动界面类QSplashScreen实例测试详解

目录 一、QSplashScreen的概述 二、QSplashScreen静态图片加载 1、主程序实现 2、mainwindow.h实现 3、mainwindows.cpp实现 三、QSplashScreen动态图片加载 1、主程序实现 2、mainwindow.h实现 3、mainwindows.cpp实现 一、QSplashScreen的概述 QSplashScreen&#x…

C++并发之协程实例(三)(co_await)

目录 1 协程2 实例3 运行 1 协程 协程(Coroutines)是一个可以挂起执行以便稍后恢复的函数。协程是无堆栈的&#xff1a;它们通过返回到调用方来暂停执行&#xff0c;并且恢复执行所需的数据与堆栈分开存储。这允许异步执行的顺序代码&#xff08;例如&#xff0c;在没有显式回调…

指纹浏览器与虚拟机的区别及在跨境电商中的应用

在如今数字化世界中&#xff0c;隐私和安全变得愈发重要。许多人在网络上进行敏感操作&#xff0c;如网上购物、在线银行、社交媒体管理等。为了保护自己的隐私&#xff0c;人们常常会寻求一些额外的工具&#xff0c;比如指纹浏览器和虚拟机。这两种工具在保护个人隐私方面都有…

sqoop的安装配置

1. 上传并解压安装包 tar -zxvf sqoop-1.4.7.bin__hadoop-2.6.0.tar.gz -C ../server/ 重命名&#xff1a;mv sqoop-1.4.7.bin__hadoop-2.6.0 sqoop 2. 配置环境变量 sudo vim /etc/profile # 配置sqoop的环境变量 export SQOOP_HOME/export/server/sqoop export PATH$PATH…

Linux htop命令使用

文章目录 简介界面介绍第一行第二行第三行第四行 如何使用 简介 htop 是一个类似于 top 的命令&#xff0c;但具有更丰富的功能和更友好的界面。它可以实时显示系统中各个进程的资源占用情况&#xff0c;如 CPU 使用率、内存使用率等。以下是对 htop 命令的完全解析&#xff1…

嵌入式实验---实验五 串口数据接收实验

一、实验目的 1、掌握STM32F103串口数据接收程序设计流程&#xff1b; 2、熟悉STM32固件库的基本使用。 二、实验原理 1、STM32F103R6能通过查询中断方式接收数据&#xff0c;每接收到一个字节&#xff0c;立即向对方发送一个相同内容的字节&#xff0c;并把该字节的十六进…

Python: HexBinDecOct

因为&#xff1a; f0b1001110# 十进制 int()a0*2**01*2**11*2**21*2**30*2**40*2**51*2**6print(a)# 八进制 oct()print(78/8,78%8)# 110 001 001 8 116print(1*2**00*2**10*2**2,1*2**00*2**10*2**2,0*2**01*2**11*2**2)#十六进制 hex()#0 100 1110 16 4Eprint(sixteenFoo(0*…

如何在Qt Designer中管理QSplitter

问题描述 当按下按钮时&#xff0c;我希望弹出一个对话框&#xff0c;用户可以在其中选择内容并最终按下 ‘Ok’ 按钮。我想在这个对话框中放置一个 QSplitter&#xff0c;左侧面板将显示树状结构&#xff0c;右侧将显示其他内容。如何正确实现这一点&#xff1f; 从 Qt 的示…

oracle 主从库中,从库APPLIED为YES ,但是主库任然为NO

主库 从库 从库已经APPLIED但是主库为APPLIED&#xff0c; 主数据库和备用数据库之间的ARCH-RFS心跳Ping负责更新主数据库上v$archived_log的APPLICED列。 在主数据库上有一个指定的心跳ARCn进程来执行此Ping。如果此进程开始挂起&#xff0c;它将不再与远程RFS进程通信&#…

git拉取gitee项目到本地

git安装等不做赘述。 根据需要选择不同操作 1.只是单纯拉取个项目&#xff0c;没有后续的追踪等操作 不需要使用git init初始化本地文件夹 新建一个文件夹用于存储项目&#xff0c;右键选择 git bash here 会出现命令行窗口 如果像我一样&#xff0c;只是拉取个项目作业&…