博客 / 詳情

返回

FE.CLI-使用playwright對pywebview應用做自動化測試

背景

在 基於pywebview搭建企業級桌面端 這篇中記錄了多種webview桌面端的框架,並記錄了pywebview的使用和相關的坑。本篇記錄如何在pywebview中實現自動化測試。

由於pywebview沒有提供測試套件,只有幾個基礎的操作接口,所以需要結合一些測試框架來實現。

如果pywebview中不涉及bridge通信,那可以當做普通瀏覽器來測試,以上框架都支持。但pywebview中涉及bridge通信,那有特定接口無法模擬,比如自實現的系統文件操作或者窗體調整等超出了瀏覽器的功能,只能考慮變通的方式,比如CDP(chrome devtools protocol) 或者 BiDi (browser-to-browser protocol) 或者圖形化識別。

CDP和BiDi

兩者都是用過websocket通信,差異體現在:

  • 設計目標:BiDi 協議旨在成為一種通用的、標準化的瀏覽器自動化和調試協議,注重跨瀏覽器的兼容性和簡潔性。CDP 最初是為 Chrome 瀏覽器的開發者工具調試功能設計的,更側重於滿足 Chrome 瀏覽器的特定調試需求,功能豐富且細緻,但相對複雜,並且主要適用於基於 Chromium 內核的瀏覽器。
  • 功能範圍:BiDi 協議提供了一些高級的、通用的功能,如頁面導航、元素交互、腳本執行、監聽 DOM 事件、捕獲 JavaScript 錯誤和控制枱消息、記錄網絡流量等。CDP 除了包含上述功能外,還提供了對瀏覽器渲染引擎、緩存策略、Cookie 管理等更底層的調試功能。

在常見基礎操作上命名稍微有點區別:

功能 BiDi CDP
導航 browsingContext.navigate Page.navigate
執行js script.evaluate Runtime.evaluate

具體文檔可以參考cdp 和 BiDi

讓pywebview支持CDP或BiDi

首先官網web engine 列舉了pywebview在不同平台下的web engine

Platform Code Renderer Provider Browser compatibility
Android WebKit Ever - green Chromium
GTK gtk WebKit WebKit2 (minimum version >2.2)
macOS WebKit WebKit.WKWebView (bundled with OS)
QT qt WebKit QtWebEngine / QtWebKit
Windows edgechromium Chromium > .NET Framework 4.6.2 and Edge Runtime installed Ever - green Chromium
Windows cef CEF CEF Python Chrome 66
Windows mshtml MSHTML DEPRECATED Internet Explorer MSHTML IE11 (Windows 10/8/7)

查看changelog 發現 pywebview 5.4增加特性:

  • windows下支持 EdgeChromium通過添加遠程調試支持設置webview.settings['REMOTE_DEBUGGING_PORT'] 來打開cdp.源碼位置

    #https://github.com/r0x0r/pywebview/blob/master/webview/platforms/edgechromium.py
    if webview_settings['REMOTE_DEBUGGING_PORT'] is not None:
      props.AdditionalBrowserArguments += f' --remote-debugging-port={webview_settings["REMOTE_DEBUGGING_PORT"]}'

但是實測發現即使設置了也沒有效果,端口並不通。

隨後查找其他方式,避免直接修改庫源碼。發現支持通過環境變量來設置,回退到pywebivew 4.3.3並啓動前執行

os.environ["WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS"] = "--remote-debugging-port=9222"

至此selenium,playwright,pyppeteer都可以使用cdp操作了。

代碼示例

各大框架比對見附錄。考慮到playwright支持瀏覽器廣,上層sdk支持多種語言,且測試報告,調試,錄製,vscode插件等都支持,這裏以playwright為例。

線性執行

playwright默認是併發執行的,假設應用場景是這個桌面應用不支持多開,或者測試場景是線性執行,比如登錄,操作,退出。需要做如下配置:

  • playwright.config.js配置workers: 1使得單個線程執行
  • 多個test用serial 標記,使得test之間串行執行

    test.describe.serial('測試集合',()=>{
        test.describe('測試A',()=>{})
        test.describe('測試B',()=>{})
    })
  • 用一個page實例,避免每次重連

    let page;
    
    test.beforeAll(async () => {
        const browser = await playwright.chromium.connectOverCDP('http://127.0.0.1:9222');
        const defaultContext = browser.contexts()[0];
        page = defaultContext.pages()[0];
    });

    文件

  • 獲取本地路徑

    const directory = path.join(__dirname, `../fixtures/test.json`);

    模擬pywebview.api

  • 執行腳本覆蓋pywebview的bridge通信

    注意變量只能通過函數入參的形式傳入,因為可執行的腳本作用域在瀏覽器而不是測試腳本,兩者是隔離的

    await page.evaluate((directory) => {
        // @ts-ignore
        window.pywebview = {
            api: {
                choosePath: () => Promise.resolve(JSON.stringify({ status: 'ok', directory })),
                getFileSize: () => Promise.resolve(1024)
            }
        };
    }, directory);

    操作行為

  • 常見操作

    await page.goto('http://localhost:5000/');
    await page.getByText('選擇文件').click()
    await page.waitForTimeout(4000);
    await page.getByRole('radio', { name: config.radioValue, exact: true }).check();
    await page.locator('select').selectOption('value')
    await page.locator('.col:last-child').textContent();//querySelector
    await page.locator('//div[@class=".btn" and text()="確定"]').click();//xpath
  • 可選的操作

    try {
        // 等待 1 秒,如果元素出現則點擊,超時則跳過
        await page.waitForSelector(':text("×")', { timeout: 1000 });
        await page.getByText('×').click();
    } catch (error) {
    }

斷言

@playwright/test 是 Playwright 官方提供的測試框架,它自帶的 expect 斷言風格簡潔,與 Playwright 的 API 配合良好。

const { test, expect } = require('@playwright/test');

test('測試頁面標題', async ({ page }) => {
    // 導航到頁面
    await page.goto('https://www.example.com');
    // 斷言頁面標題是否包含指定字符串
    await expect(page).toHaveTitle(/Example/);

    // 獲取頁面上某個元素(假設存在一個id為myElement的元素)
    const element = page.locator('#myElement');
    // 斷言元素是否可見
    await expect(element).toBeVisible();

    // 斷言元素的文本內容
    const text = await element.textContent();
    await expect(text).toContain('Some text');
});

測試報告

playwright提供了豐富的報告選項,包括 HTML、JSON等格式,可以自定義報告的樣式和內容。

// playwright.config.js
reporter:  [
    ['json', { outputFile: 'test-results.json' }],
    ['html',{open: 'never'}],
  ]

上述配置會生成一個包含測試結果的 HTML 報告,以及一個 JSON 文件,其中包含測試結果的詳細信息。
如果接入CI,可以通過json獲取關鍵信息,便於判斷計數。這邊給出關鍵部分代碼

// 初始化計數器
let allCount = 0;
let passedCount = 0;
let failedCount = 0;
let flakyCount = 0;
let skippedCount = 0;

// 遞歸函數,用於遍歷所有測試用例
function traverseSuites(suites) {
  suites.forEach((suite) => {
    if (suite.specs) {
      suite.specs.forEach((spec) => {
        spec.tests.forEach((test) => {
          allCount++;
          const result = test.results[0];
          if (result.status === "passed") {
            passedCount++;
          } else if (
            result.status === "timedOut" ||
            result.status === "failed"
          ) {
            failedCount++;
          } else if (result.status === "flaky") {
            flakyCount++;
          } else if (result.status === "skipped") {
            skippedCount++;
          }
        });
      });
    }
    if (suite.suites) {
      traverseSuites(suite.suites);
    }
  });
}

錄製腳本

官方提供的錄製是彈出瀏覽器,不能直接在cdp的場景下錄製。所以不適用。但能通過 --ui的方式審查元素,比純手動簡單一些。

關於圖像識別的自動化測試

此外還調研了sikuli 基於圖像識別的自動化測試框架,直接執行sikuli的jar包很方便,但基於圖像識別不同操作系統會有差異,識別率要微調,不能做到一端開發,多端執行。但好處是跨平台,可以操作任何桌面應用。

附錄

框架對比

特性/方面 Playwright Puppeteer Selenium Cypress Sikuli
開發者 微軟 谷歌 最初是內部測試工具,後發展為廣泛使用項目 Cypress.io團隊 Ramanathan Guha和Tobias Grosser
發佈時間 2020年1月 2017年 (旨在彌補 Selenium 的不足) 2004年 2017年 2009年
流行度 近期發展勢頭強勁,但之前不如 Puppeteer 過去一年更受歡迎,但 Playwright 正在趕超 擁有龐大且活躍的社區 在前端開發者中較受歡迎 相對小眾,主要用於特定場景
核心功能 自動化網頁交互,提取目標數據 自動化網頁交互,提取目標數據 跨瀏覽器測試、自動化、網頁抓取 端到端測試、集成測試等 基於圖像識別的自動化操作
自動等待功能 具備,模仿人類用户行為,避免被檢測為機器人 缺乏便利性,需要手動設置計時器 (例如 Page.waitForSelector()) - 自動等待元素可交互,無需手動設置等待時間 基於圖像識別,等待目標圖像出現
反爬機制繞過 需要與第三方服務集成 (例如代理、AI解決方案) 需要與第三方服務集成 (例如代理、AI解決方案) - - 可繞過部分基於元素定位的反爬機制
客户端支持 異步和同步客户端 僅異步客户端 - 僅支持JavaScript編寫測試用例 支持Java、Python等
跨瀏覽器支持 強大,支持 Chrome/Chromium, WebKit (Safari), Firefox 主要支持 Chrome/Chromium,對 Firefox/Edge 的支持處於實驗階段 多種瀏覽器選項,但需要為每個瀏覽器安裝特定的 WebDriver (Selenium Manager 嘗試解決,仍在 Beta 階段) 主要支持Chrome、Firefox等主流瀏覽器 跨平台,可在Windows、Mac、Linux上運行
語言支持 Python, Java, JavaScript, TypeScript, .NET JavaScript (有非官方的 Python 端口 Pyppeteer) Java, Python, Ruby, C#, JavaScript,以及通過客户端語言綁定支持 Go, Haskell, PHP, Perl, R, Dart 等 JavaScript Java、Python等
瀏覽器功能 使用修補的瀏覽器版本,支持多版本瀏覽器測試,多上下文瀏覽,支持瀏覽器擴展 瀏覽器功能相對落後 - 支持調試工具,可實時查看測試步驟 不依賴瀏覽器,可操作任何桌面應用
社區支持 相對較新,不如 Puppeteer 廣泛 成熟的社區,出色的文檔,更成熟的生態系統 擁有龐大且活躍的社區,以及大量深入的文檔 有活躍的社區和豐富的插件 社區相對較小
網絡抓取支持 功能上略有優勢,補充功能多 官方支持,有網絡抓取教程 - 不專注於網絡抓取 -
設備模擬 開箱即用的設備模擬跨瀏覽器測試 未明確提及,可能需要額外配置 - 支持設備模擬 可模擬鼠標鍵盤操作任何設備上的應用
維護 多版本支持可能增加維護難度,未來可能出現重大變化 相對穩定 - 易於維護,測試用例編寫簡潔 圖像識別可能因界面變化需要更新圖像模板
速度 通常比 Selenium 快 - 相對較慢,更適合小型到中型的抓取項目 測試執行速度快 圖像識別有一定性能開銷
架構 基於響應事件的解耦系統 (事件驅動架構),異步通信 - 通過交換 JSON 有效負載使用 HTTP 與 Web 驅動程序交互 (JSON Wire 協議),可能產生延遲 基於Node.js,與瀏覽器直接通信 基於圖像識別算法
WebDriver 管理 內置驅動程序,實現更簡單 - 需要為每個瀏覽器安裝特定的 WebDriver (Selenium Manager 正在嘗試解決這個問題) 自動管理瀏覽器驅動 不涉及WebDriver
無頭瀏覽器能力 支持無頭瀏覽,可以模擬用户操作,解決動態頁面抓取問題 - 支持無頭瀏覽,可以模擬用户操作,解決動態頁面抓取問題 支持無頭模式運行測試 可操作無頭應用(如命令行工具)
易用性 更容易實現 - 相對複雜,需要配置 WebDriver 測試用例編寫簡單,易於上手 圖像識別配置相對複雜
更新程度 較新 - 較老 更新頻繁,功能不斷完善 更新相對較慢
適用場景 需要跨瀏覽器支持,需要多種語言支持,需要更強大的通用網絡自動化解決方案;項目需求可通過其支持的語言和瀏覽器滿足時,可獲得快速、高效且易於實現的無頭瀏覽器 需要大量同行指導,只使用 Chrome,時間有限,需要成熟的社區和文檔 需要靈活性,希望採用非常具體的瀏覽器和編程語言組合;考慮到在線可訪問的資源範圍,可能是學習使用無頭瀏覽器進行網頁抓取的有用工具 前端項目的快速測試,注重開發調試體驗 需要對桌面應用進行自動化操作,尤其是界面元素難以用代碼定位的情況
決策因素 先前的熟悉程度在決策中起着重要作用 先前的熟悉程度在決策中起着重要作用 - 項目前端技術棧和對測試速度的要求 應用類型和自動化操作的定位方式
定義 跨瀏覽器的 Web 自動化庫,旨在簡化端到端測試 - 專用於跨瀏覽器測試和自動化的開源框架 用於現代Web應用的快速、簡單、可靠的測試框架 基於圖像識別的自動化測試和操作工具
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.