Playwright在Azure Pipeline配置
2024年10月2日,颱風天雖然無風無雨,但還是放了颱風假,只好待在家裡研究最近上保哥的 Playwright 課程後的一些心得和小成果。
從在本地環境使用 Node.js 和 TypeScript 建立測試案例到執行,已經有很多文件和參考資料,所以這部分就先略過。
不過對於自動化框架已經有這麼多套了後,為什麼目前選擇playwright呢?上完課初步瞭解後,我自己整理有以下幾點
- playwright簡化了selector,重新設計了定位器,符合無障礙網頁設計規範,與autowait,並可以透過 playwright生態與Api支援下 ,測試可讀性也大幅增加,配合強大工具進行錄製,大幅降低了e2e測試案例寫code的困難度,也降低了維護上的成本。
- 強大的Mocking的機制,還有諸多黑科技機制 ,不旦可以mocking api , browser 的storage, 有機會達成更多深入的驗證情境(例如pass 人類、簡訊等雙因子驗證)
- 支持所有顯示主流語言,包含java , c# , nodejsd的type script,且可以透過 框架一鍵切換,文件也很完整。
- 微軟爸爸金主萬歲
其實,E2E的測試,還有一個就是文化面的觀點,就是所有的測試都必須跟RD雙向奔赴,一面測試能夠做到反饋,產品主動優化,這樣正向循環才有助於產品品質的發展。
作為一個自動化測試框架,通常後續整合到專案或產品上有兩個實作方向:
1. 作為前端專案的介面整合測試
在這種情況下,測試案例的版控權限需要開放到前端專案中。因此,如果開發人員以外的人(如 QA、PM)要協助編修測試案例,可能會頻繁變動專案。而且可能還需要在建置過程中處理 localhost 的啟動及套件測試驗證。這樣的測試其實有點像界面的單元測試,只是更依賴於前端專案,並且還要考慮相關的服務或其他環境的設定。
當然這種方式的好處在於,測試流程可以隨著介面的變更一起調整,與前端工程師的工作息息相關。因此,測試案例的防守範圍包含了 RD,可以隨著功能特性(feature)的調整,達到與版本部署的同步。
但維護成本也相對高(跟專案綁在一起的關係)
2. 作為產品介面的回歸測試
這種方式著重於將較為穩定且不常變動的介面優先建置測試案例。目前,我們專案正朝著這樣的 E2E 測試流程發展,希望能針對不同環境(如 tst、uat、preprod、prod)進行測試。藉由Azure Pileine配置獨立的 E2E 測試專案及執行指令,可以靈活地測試不同環境的標的。
獨立了 test 專案後,測試team(當然也可以RD、PM都一起來寫XD)可以依據不同環境規劃測試案例的目錄。透過 npx playwright test 指令,可以方便地測試指定的案例目錄及不同瀏覽器參數。這種方式相當便捷(先前也玩過 Cypress),並且獨立版控的測試專案也帶來了一些好處,例如可以在 CI 流程中靈活分配,無論是每次上版到不同環境時進行或是定期測試、在指定條件下執行測試,都能自由安排。
這次我希望達到的是上述第二種切入點的做法,因為我們的 E2E 測試希望從使用者操作的角度進行,以 “指定環境” 的 Web 站台為測試對象。
因此我們將BASE_URL(測試站台的進入點)作為 參數由外部傳入
另外,因為考慮了playwright的覆用性,因此也把playwright的大部分Task包成另一個 Azure Template
這樣一來,不同環境就可以建置不同的Pipeline去定義 參數後再傳入PipelineTemplate執行標準的測試 Task
Main Pipeline
(以下範例移除了公司的標記,可以依有抽出參數的部分自由參考運用)
pool:
vmImage: ubuntu-latest
trigger:
branches:
include:
- 'main'
resources:
repositories:
- repository: PipelineTemplates
type: git
name: {版控}/Pipeline-Templates
- repository: E2ETesting
type: git
name: {專案}-End2EndTesting
name: $(Build.BuildId)
stages:
- stage: qa
displayName: 'Run Automation Test - QA'
dependsOn: []
jobs:
- template: template/playwright-template.yml@PipelineTemplates
parameters:
BASE_URL: 'https://tst-{站台}/zh-TW/home' #測試站點
End2EndProjectKey: E2ETesting #測試專案版控Key
End2EndProjectName: {專案}-End2EndTesting #測試專案目錄名稱(用於組合路徑用)
PipelineTemplates
(作為Function可以被不同的pipeline參考執行,注意,這個是 playwright-template.yml的內容,另外一個檔案)
parameters:
- name: BASE_URL
type: string
- name: End2EndProjectKey
type: string
- name: End2EndProjectName
type: string
jobs:
- job: test
displayName: Run Playwright Tests
steps:
- download: none
- checkout: self
persistCredentials: true
- checkout: ${{ parameters.End2EndProjectKey }}
persistCredentials: true
- script: |
ls $(System.DefaultWorkingDirectory)
displayName: "Print Working Dir"
- task: NodeTool@0
displayName: 'Use Node version 18'
inputs:
versionSpec: 18.x
- script: |
cd ${{ parameters.End2EndProjectName }}
ls .
displayName: "Cd E2E Folder and ls folder "
- script: |
cd ${{ parameters.End2EndProjectName }}
ls .
npm ci
displayName: "NPM Install"
- script: |
cd ${{ parameters.End2EndProjectName }}
ls .
pwd
echo $(Pipeline.Workspace)/s/${{ parameters.End2EndProjectName }}/browsers
cd /home/vsts/
PLAYWRIGHT_BROWSERS_PATH=/home/vsts/
npx playwright install --with-deps
#note 不要用sudo安裝,會裝到/root去專案跑的時候會找不到browser driver
displayName: "Playwright Install"
- script: |
echo "BASE_URL= ${{ parameters.BASE_URL }}"
displayName: "Print Variables"
- script: |
cd ${{ parameters.End2EndProjectName }}
BASE_URL=${{ parameters.BASE_URL }} CI=true PLAYWRIGHT_JUNIT_OUTPUT_NAME=test-results/results.xml npx playwright test
displayName: "Run Playwright Tests"
continueOnError: true
- task: ArchiveFiles@2
displayName: 'Add playwright-report to Archive'
inputs:
rootFolderOrFile: '$(Pipeline.Workspace)/s/${{ parameters.End2EndProjectName }}/playwright-report/'
archiveFile: '$(Agent.TempDirectory)/$(Build.BuildId)_$(System.JobAttempt)$(System.StageAttempt).zip'
- task: ArchiveFiles@2
displayName: 'Add test-results to Archive'
inputs:
rootFolderOrFile: '$(Pipeline.Workspace)/s/${{ parameters.End2EndProjectName }}/test-results/'
archiveFile: '$(Agent.TempDirectory)/$(Build.BuildId)_$(System.JobAttempt)$(System.StageAttempt).zip'
replaceExistingArchive: false
- task: PublishPipelineArtifact@1
displayName: 'Publish Pipeline Artifacts'
inputs:
targetPath: '$(Agent.TempDirectory)/$(Build.BuildId)_$(System.JobAttempt)$(System.StageAttempt).zip'
artifact: pipeline-artifacts
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '*.xml'
searchFolder: '$(Pipeline.Workspace)/s/${{ parameters.End2EndProjectName }}/test-results'
testRunTitle: 'Playwright ADO Demo - $(System.StageName)'
displayName: 'Publish Test Results'