[料理佳餚] 用 Playwright 來為我們的網頁做自動化的端到端測試

Playwright 是由微軟開源專案開發維護類似像 SeleniumPuppeteerCypress 的 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 使用經驗,或是不一樣的網頁端到端測試經驗,也請不吝留言跟我們分享。

相關資源

C# 指南
ASP.NET 教學
ASP.NET MVC 指引
Azure SQL Database 教學
SQL Server 教學
Xamarin.Forms 教學