Playwright 是由微軟開源專案開發維護類似像 Selenium、Puppeteer、Cypress 的 WebDriver,它主打的是測試
,目前支援三大瀏覽器家族 Google Chrome / Microsoft Edge(with Chromium)、Apple Safari(with WebKit)、Mozilla Firefox,而且提供對測試友善的 API,讓測試人員在撰寫測試腳本的時候,可以更關注在 UI 的操作流程上,有需要對網頁進行自動化端到端測試的朋友,真的要試一下。
Playwright 目前支援的語言框架有 .NET、Node.js、Python、Java,我最熟悉的是 C# 自然是選擇 Playwright for .NET 來用。
有江湖傳言開發 Puppeteer 的同一群人現在在微軟上班,而 Playwright 也是由這群人開發的,不知道真的假的?
安裝 Microsoft.Playwright.CLI
首先第一件事情,強烈建議各位朋友一定要安裝 Playwright 的 CLI 工具,它可以讓我們節省很多時間,執行下面這一行指令,就可以將 Playwright CLI 工具裝起來。
dotnet tool install --global Microsoft.Playwright.CLI
建立專案
接著我們利用 dotnet CLI 工具建立一個 Console 專案,並且將 Microsoft.Playwright
套件加進來。
dotnet new console -n PlaywrightLab
cd .\PlaywrightLab\
dotnet add package Microsoft.Playwright
dotnet build
當然建立專案這件事情,我們也可以透過 Visual Studio 來做,這個部分我就不多做說明了。
安裝瀏覽器
Playwright 只是一個 WebDriver 的角色,本身並不包含瀏覽器,我們還需要安裝瀏覽器,透過 Playwright CLI 工具就可以將所有支援的瀏覽器安裝起來,指令如下:
playwright install
如果是 Windows 系統,安裝好的瀏覽器會放在 %USERPROFILE%\AppData\Local\ms-playwright
路徑底下。
那我們能不能使用已經安裝好的瀏覽器? 答案是可以的,這個要在我們撰寫程式時候去指定瀏覽器的執行檔路徑,下面的範例我就用我本機電腦已經安裝好的 Google Chrome 來執行測試腳本。
測通官網的範例程式碼
在 Playwright 官網上已經有一段範例程式碼了,我們從這段程式碼切入先測通整個 Playwright 的執行環境,但是這段程式碼要小改一下,改用我們已經安裝好的 Google Chrome 來執行,在 playwright.Chromium.LaunchAsync()
方法加入 BrowserTypeLaunchOptions
參數,並且指定 ExecutablePath
,為了可以看得到瀏覽器執行的狀況,我們順手將 Headless
關閉。
接著我們可以執行看看了,正常的話應該可以看到 Google Chrome 跑起來,而且是用無痕模式去瀏覽 https://playwright.dev/dotnet 網址。
Auto Waiting
Playwright 身為一個 WebDriver,提供的功能跟其他的 WebDriver 差不多,其中有一個我覺得值得一提的是它借鏡了 Cypress 的 Auto Waiting 功能,要知道 Cypress 並非是完全免費的,超過某個使用額度是要付費的,如果我現在有大量自動化網頁端到端測試的需求,在選擇解決方案的時候,Playwright 就有很大的機會雀屏中選。
那 Auto Waiting 是什麼? 我下面做個實驗給大家展示一下,我用 ASP.NET Core MVC 的範本網站當作我的測試目標,我在首頁埋了一段程式碼,有一個按鈕 MyButton
會在三秒內出現,按下去會改變 h1
的內容。
<script>
setTimeout(function () {
window.mybuttonclick = function () {
$("h1").text("My Button Clicked!");
}
$(".text-center").append('<button id="mybutton" onclick="window.mybuttonclick.call(this)">MyButton</button>');
}, Math.floor(Math.random() * 3000));
</script>
我要測試的情境是 MyButton 按鈕按下去之後,預期 h1 要出現「My Button Clicked!」文字,在古早的時候,由於我們不知道 MyButton 按鈕什麼時候會出現,所以執行按下按鈕這個動作之前,會需要加一段等待按鈕出現的程式碼,而 Auto Waiting 的機制則是由 Playwright 幫我們等待,我們再也不用去思考按鈕出現的時機。
以我要測試的情境為例,直接呼叫 await page.ClickAsync("#mybutton");
就好了,Playwright 會去幫我們處理等待按鈕出現的問題。
用 codegen 錄製操作腳本
Playwright CLI 工具提供了一個很方便的指令 codegen
,它可以幫我們把操作網頁的過程用指定的程式語言錄製下來,方便我們之後加入斷言(Assertion)製作成測試腳本,我們只要將想要測試的網址接在 codegen 後面當參數,就會啟動一個瀏覽器跟一個錄製視窗,我們就可以開始錄製了。
playwright codegen http://localhost:5000
與 NUit 整合
Playwright 並沒有一定要使用哪一套測試框架,官網提供的說明文件是用 NUnit,我們也用 NUnit 來跑測試腳本,官網還建議每一個測試案例跑在一個新的 BrowserContext
,這樣瀏覽器的狀態在測試案例之間可以有比較好的隔離。
所以我們就建立一個 NUnit 專案,加入 Microsoft.Playwright 套件,然後把官網的建議跟剛剛我們錄製的腳本,製作成一個測試案例,程式碼如下:
public class MyButtonTest
{
private IBrowserContext context;
[SetUp]
public async Task ContextSetUp()
{
this.context = await BrowserSetUp.Browser.NewContextAsync();
}
[Test]
public async Task H1TextShouldBeChanged()
{
var page = await this.context.NewPageAsync();
await page.GotoAsync("http://localhost:5000/");
await page.ClickAsync("text=MyButton");
var h1Text = await page.InnerTextAsync("h1");
Assert.AreEqual("My Button Clicked!", h1Text);
}
[TearDown]
public async Task ContextTearDown()
{
await this.context.CloseAsync();
}
}
[SetUpFixture]
public class BrowserSetUp
{
private IPlaywright playwright;
public static IBrowser Browser { get; private set; }
[OneTimeSetUp]
public async Task SetUp()
{
this.playwright = await Playwright.CreateAsync();
Browser = await this.playwright.Chromium.LaunchAsync(
new BrowserTypeLaunchOptions
{
ExecutablePath = @"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe", Headless = false
});
}
[OneTimeTearDown]
public async Task TearDown()
{
await Browser.CloseAsync();
}
}
官網的說明裡有提到要引用 Microsoft.Playwright.NUnit
套件,它方便的地方在於它提供了一些基礎類別,這些基礎類別幫我們管理 Page、BrowserContext、Browser、Playwright 的生命周期,但是它有一些設定目前是不可調的,像我要去改 Chrome 的執行檔路徑,它就沒有方法可以覆寫,所以我這邊就沒有引用這個套件。
EvaluateAsync() 及 EvaluateAsync<T>() 方法
最後要特別介紹一下 EvaluateAsync 方法,類似的 API 在其他 WebDriver 也有,主要的功能就是安插一段 JavaScript 程式碼給瀏覽器去執行,然後將執行的結果回傳回來,舉例來說,我想檢查瀏覽器 UserAgent 的值是否有包含 Chrome/95
?我們就用 EvaluateAsync<T>() 方法執行一段 JavaScript 取得 window.navigator.userAgent
的值回來檢查,而且它還會自動等待非同步方法回傳結果。
[Test]
public async Task UserAgentShouldContainsChrome95()
{
var page = await this.context.NewPageAsync();
await page.GotoAsync("http://localhost:5000/");
string userAgent;
userAgent = await page.EvaluateAsync<string>("() => window.navigator.userAgent");
Assert.IsTrue(userAgent.Contains("Chrome/95"));
userAgent = await page.EvaluateAsync<string>("() => Promise.resolve(window.navigator.userAgent)");
Assert.IsTrue(userAgent.Contains("Chrome/95"));
userAgent = await page.EvaluateAsync<string>("async () => await Promise.resolve(window.navigator.userAgent)");
Assert.IsTrue(userAgent.Contains("Chrome/95"));
}
如果我們回傳的結果是 JSON Object,我們可以改使用 EvaluateAsync() 方法。
[Test]
public async Task UserAgentShouldContainsChrome95()
{
var page = await this.context.NewPageAsync();
await page.GotoAsync("http://localhost:5000/");
JsonElement? jsonObj;
jsonObj = await page.EvaluateAsync("() => ({ userAgent: window.navigator.userAgent })");
Assert.IsTrue(jsonObj.Value.GetProperty("userAgent").GetString().Contains("Chrome/95"));
jsonObj = await page.EvaluateAsync("() => Promise.resolve({ userAgent: window.navigator.userAgent })");
Assert.IsTrue(jsonObj.Value.GetProperty("userAgent").GetString().Contains("Chrome/95"));
jsonObj = await page.EvaluateAsync("async () => await Promise.resolve({ userAgent: window.navigator.userAgent })");
Assert.IsTrue(jsonObj.Value.GetProperty("userAgent").GetString().Contains("Chrome/95"));
}
EvaluateAsync 方法提高了我們撰寫測試程式碼的彈性,甚至我們可以考慮把測試程式碼用 JavaScript 寫成一個個文字檔,然後載入丟給 EvaluateAsync 方法去執行,照樣跑測試。
以上,就是 Playwright 的基本使用方法分享給大家,如果大家有不同的 Playwright 使用經驗,或是不一樣的網頁端到端測試經驗,也請不吝留言跟我們分享。