工具 – Playwright
前言
一年多前,我写了一篇 e2e 测试工具 Cypress。
当时它的 npm 下载量是 e2e 测试工具里排最高的。
没曾想,短短一年间,它就被 Playwright 完全赶超了😱
Playwright 是谁?怎么这么厉害?
Playwright 大有来头,它是 Microsoft 开源的 e2e 测试工具。
团队是从 Google Puppeteer 过来的大佬。
因此它不仅仅是 e2e 测试工具,也可以向 Puppeteer 那样作为 headless browser 使用。
除此之外,它还支持很多语言,不仅有 JavaScript, TypeScript,后端语言 C#,Java,Python 它也都支持。
不得不说,这么强大,要不火都难啊。
本篇我会延用上一篇 Cypress 的例子,重新用 Playwright 实现一遍,体验一下 Playwright 的强大。
Playwright 环境
单元测试,我们通常会把测试代码和被测试代码放到一块,under 同一个项目。
而 e2e 测试则不一定,有些人 prefer 分开两个项目管理,有些人则会放到一块。
Playwright 俩都支持,本篇会采用放到一块的方式。
此外,Playwright 支持多平台,想使用 JS / TS 的话,就搭配 Node.js,想使用 C# 就搭配 .NET。
这意味着,我们甚至可以用 C# 写测试代码,然后去测试一个 Angular (非 C#) 的项目。
本篇会采用 JS / TS + Node.js 的方式。
Get Started
create project
创建一个项目,这里我用 Vite TypeScript,你也可以用其它的。
写一个简单的页面
.html

<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Vite + TS</title> </head> <body> <form autocomplete="off"> <input placeholder="First Name"> <input placeholder="Last Name"> <button>Submit</button> <p class="thank-you">Thank you! We have received your enquiry.</p> </form> <script type="module" src="/src/main.ts"></script> </body> </html>
.scss

* { padding: 0; margin: 0; box-sizing: border-box; &:focus { outline: none; } button { border-width: 0; } } body { height: 100vh; display: flex; justify-content: center; align-items: center; form { min-width: 320px; display: flex; flex-direction: column; gap: 16px; input { border-radius: 4px; border: 1px solid black; padding: 16px; } button { border-radius: 4px; background-color: pink; color: red; padding: 20px; font-size: 20px; text-transform: uppercase; letter-spacing: 1px; } .thank-you { line-height: 1.5; &:not(.shown) { display: none; } } } }
.ts

import './style.scss' document.querySelector('form')!.addEventListener('submit', e => { e.preventDefault(); document.querySelector('.thank-you')!.classList.add('shown'); });
执行 command
yarn run vite
效果
install Playwright
yarn create playwright
安装过程中,它会问 3 个问题
第一个是,e2e 测试代码文件要放在哪个 folders,默认是 /tests。
第二个是,要不要搭配 Github,每当 checkin 的时候自动做测试,我没有这个习惯,所以选了 No。
第三个是,要不要安装游览器,我已经安装过了,所以选 No,第一次安装的可以选 Yes,或者之后单独安装也可以
Playwright 主要使用的游览器是 Chromium (Chrome / Edge 底层游览器)、Firefox、WebKit (它不是 Safari 游览器,而是一个搭载 WebKit 渲染引擎和 JavaScriptCore JS 引擎的 Safari 环境模拟器)
通常做 e2e 测试,这三个主流游览器都会一起测试。
安装好后会多出 3 个 files
往后的测试文件通通放入 tests folder 里。
tests-example 只是一个参考例子,可以删掉。
playwright.config.ts 是配置 playwright 的文件。
write test and view result
把我们的测试代码写进 example.spec.ts 里
import { test, expect } from '@playwright/test'; test('test form', async ({ page }) => { await page.goto('http://localhost:5173'); // 访问我们的程序页面 await expect(page.locator('.thank-you')).toBeHidden(); // 检查 thank you message 必须是 hidden await page.getByPlaceholder('First Name').fill('Derrick'); // 查找 input 'First Name' 输入 'Derrick' await page.getByPlaceholder('Last Name').fill('Yam'); // 查找 input 'Last Name' 输入 'Yam' await page.locator('button').click(); // 点击 submit await expect(page.locator('.thank-you')).toBeVisible(); // 检查 thank you message 必须是 shown });
Playwright 的测试代码大致上就是 UI 操作,然后检查 UI 的变化。
当然它也可以发 request 检查资料是否输入到 database 等等。
反正人怎么 test 它就怎么 test 就对了。
执行 command
yarn dev
yarn playwright test
效果
之所以是 3 passed,3 workers 是因为默认启动了 3 个游览器测试。(主:如果觉得 Firefox 没有必要的话,可以去 playwright.config.ts 把它移除)
如果觉得 command 的 result 太单调,可以接着执行这个 command
yarn playwright show-report
它会以页面的方式呈现之前的 result。(注:如果测试 failed,它会自动显示这个 report,如果成功默认不会额外显示这个 report)
另外呢,它还有一个更细节操控的模式,执行 command
yarn playwright test --ui
在执行 test command 时加上一个 --ui
它会打开一个 dialog,我们可以选定要执行的 test,可以看到每一行 expect 的执行过程,非常细,细到可以 debug 了。
Codegen の recording a test
不知道大家用过 Excel macro 没有,我依稀记得在还没有学习编程以前,我只会在 Excel 上写点 formula,用 macro 录制生成一些 Visual Basic 代码。
那时厂里有个资深工人会写 Visual Basic,我就觉得他好厉害啊。
好,回忆结束,回来。
Playwright 也有类似 marco 的功能,我们可以录制生成测试代码。
执行 command
yarn playwright codegen http://localhost:5173
它会 popup 2 个窗口
左边是我们的录制程序,右边是自动生成出来的测试代码。
用法很简单,当我们 hover 到 'First Name' input 时,它会显示相关的测试代码
点击后会自动生成一行测试代码
我们继续填写资料到 input 上
它会持续添加测试代码
最后
它会生成出
用 recording 的方式 generate 测试代码,虽然可能不太精准,但体验还是挺好的,推荐大家去玩一玩。
常用招数
base URL
去 playwright.config.ts 加上 baseUrl
export default defineConfig({ use: { baseURL: 'http://localhost:5173', }, });
效果
await page.goto('http://localhost:5173'); // before await page.goto('/'); // after
testMatch
Playwright 默认测试文件的路径是 tests/**/*.spec.ts
有时候我们更希望把 .spec.ts 和测试页面放到一块
可以这样配置
export default defineConfig({ // testDir: './tests', testMatch: ['/src/**/*.spec.ts', '/tests/**/*.spec.ts'], testIgnore: [], });
把 testDir 改成 testMatch,然后写 Glob Patterns。
要 skip 掉某些 file 或 folder,可以使用 testIgnore。
query element and doing something / check with element
输入字到 input
await page.locator('input').fill('Derrick');
check 是否可见
await expect(page.locator('.thank-you')).toBeHidden();
TODO
等我以后用到了才回来补。
HTML to PDF in .NET by Playwright
很多年前我写过一篇 Html to PDF。
当时的做法是在 .NET 利用 Javascript.NodeJS 调用 Puppeteer 来 convert HTML to PDF。
非常麻烦的做法。后来 .NET 有了 port to Puppeteer 的 library -- Puppeteer Sharp,至少是少了 Node.js 这个环节。
如今有了 Playwright 连 Puppeteer 都不需要了。
Playwright 支持 .NET,同时它具备 headless browser 的特性 (类似 puppeteer),所以它可以直接 convert HTML to PDF。
invoice.html

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Invoice</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f4f4f4; } .invoice { max-width: 800px; margin: 20px auto; padding: 20px; background-color: #fff; border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); } .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .header h1 { margin: 0; } .company-info { text-align: left; } .invoice-info { text-align: right; } .client-info { margin: 20px 0; } .items-table { width: 100%; border-collapse: collapse; margin-bottom: 20px; } .items-table thead { background-color: #f4f4f4; } .items-table th, .items-table td { padding: 10px; border: 1px solid #ddd; text-align: left; } .items-table th { background-color: #e0e0e0; } .text-right { text-align: right; } tfoot td { font-weight: bold; } .footer { text-align: center; margin-top: 20px; font-size: 0.9em; color: #666; } </style> </head> <body> <div class="invoice"> <!-- Header Section --> <div class="header"> <h1>Invoice</h1> <div class="company-info"> <h2>Electronic Instruments Co., Ltd.</h2> <p>123 Tech Avenue, Silicon Valley, CA 94043</p> <p>Email: contact@eico.com | Phone: (123) 456-7890</p> </div> <div class="invoice-info"> <p>Invoice #: 001234</p> <p>Date: 2025-01-14</p> <p>Due Date: 2025-01-31</p> </div> </div> <!-- Client Information --> <div class="client-info"> <h3>Billed To:</h3> <p>Company Name: ABC Tech Solutions</p> <p>Address: 456 Business Road, Tech City, TX 75001</p> <p>Email: billing@abctech.com</p> </div> <!-- Product Items Section --> <table class="items-table"> <thead> <tr> <th>Item</th> <th>Description</th> <th>Quantity</th> <th>Unit Price</th> <th>Total</th> </tr> </thead> <tbody> <tr> <td>001</td> <td>Precision Oscilloscope</td> <td>2</td> <td>$1,200.00</td> <td>$2,400.00</td> </tr> <tr> <td>002</td> <td>Digital Multimeter</td> <td>5</td> <td>$300.00</td> <td>$1,500.00</td> </tr> <tr> <td>003</td> <td>Signal Generator</td> <td>1</td> <td>$1,000.00</td> <td>$1,000.00</td> </tr> </tbody> <tfoot> <tr> <td colspan="4" class="text-right">Subtotal:</td> <td>$4,900.00</td> </tr> <tr> <td colspan="4" class="text-right">Tax (10%):</td> <td>$490.00</td> </tr> <tr> <td colspan="4" class="text-right"><strong>Total:</strong></td> <td><strong>$5,390.00</strong></td> </tr> </tfoot> </table> <!-- Footer Section --> <div class="footer"> <p>Thank you for your business!</p> <p>Please make payment to the above address by the due date.</p> </div> </div> </body> </html>
这是要 convert to PDF 的 HTML。
安装 Playwright
dotnet add package Microsoft.Playwright
program.cs
using Microsoft.Playwright; var rootPath = Path.Combine(AppContext.BaseDirectory, @"..\..\..\"); string htmlFilePath = Path.Combine(rootPath, "invoice.html"); string pdfFilePath = Path.Combine(rootPath, "invoice.pdf"); string rawHtml = File.ReadAllText(htmlFilePath); using var playwright = await Playwright.CreateAsync(); await using var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions { Headless = true }); var page = await browser.NewPageAsync(); await page.SetContentAsync(rawHtml); await page.PdfAsync(new PagePdfOptions { Path = pdfFilePath, // Output PDF file path Format = "A4", // PDF format, e.g., A4, Letter PrintBackground = true // To include background colors/images in the PDF });
代码很简单,我就不解释了。
总结
Playwright 真的很强大,不愧是 Microsoft 出品,有那个水准,希望微软未来能多开源这类的项目。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· 【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体
2022-01-13 Azure – Azure Active Directory