mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-13 17:52:38 +02:00
521 lines
13 KiB
Markdown
521 lines
13 KiB
Markdown
# CI: CircleCI, Azure DevOps, and Jenkins
|
|
|
|
> **When to use**: Running Playwright tests in CI platforms other than GitHub Actions or GitLab.
|
|
|
|
## Table of Contents
|
|
|
|
1. [Common Commands](#common-commands)
|
|
2. [Jenkins](#jenkins)
|
|
3. [CircleCI](#circleci)
|
|
4. [Azure DevOps](#azure-devops)
|
|
5. [JUnit Reporter Config](#junit-reporter-config)
|
|
6. [Platform Comparison](#platform-comparison)
|
|
7. [Troubleshooting](#troubleshooting)
|
|
8. [Anti-Patterns](#anti-patterns)
|
|
|
|
---
|
|
|
|
## Common Commands
|
|
|
|
```bash
|
|
npx playwright install --with-deps # browsers + OS dependencies
|
|
npx playwright test --shard=1/4 # parallel sharding
|
|
npx playwright merge-reports ./blob-report # combine shard results
|
|
npx playwright test --reporter=dot,html # multiple reporters
|
|
```
|
|
|
|
## Jenkins
|
|
|
|
### Declarative Pipeline
|
|
|
|
```groovy
|
|
// Jenkinsfile
|
|
pipeline {
|
|
agent {
|
|
docker {
|
|
image 'mcr.microsoft.com/playwright:v1.48.0-noble'
|
|
args '-u root'
|
|
}
|
|
}
|
|
|
|
environment {
|
|
CI = 'true'
|
|
HOME = '/root'
|
|
npm_config_cache = "${WORKSPACE}/.npm"
|
|
}
|
|
|
|
options {
|
|
timeout(time: 30, unit: 'MINUTES')
|
|
disableConcurrentBuilds()
|
|
}
|
|
|
|
stages {
|
|
stage('Install') {
|
|
steps {
|
|
sh 'npm ci'
|
|
}
|
|
}
|
|
|
|
stage('Test') {
|
|
steps {
|
|
sh 'npx playwright test'
|
|
}
|
|
post {
|
|
always {
|
|
junit allowEmptyResults: true,
|
|
testResults: 'results/junit.xml'
|
|
archiveArtifacts artifacts: 'pw-report/**',
|
|
allowEmptyArchive: true
|
|
archiveArtifacts artifacts: 'results/**',
|
|
allowEmptyArchive: true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
post {
|
|
failure {
|
|
echo 'Tests failed!'
|
|
}
|
|
cleanup {
|
|
cleanWs()
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Parallel Shards
|
|
|
|
```groovy
|
|
// Jenkinsfile (sharded)
|
|
pipeline {
|
|
agent none
|
|
|
|
environment {
|
|
CI = 'true'
|
|
HOME = '/root'
|
|
}
|
|
|
|
options {
|
|
timeout(time: 30, unit: 'MINUTES')
|
|
}
|
|
|
|
stages {
|
|
stage('Test') {
|
|
parallel {
|
|
stage('Shard 1') {
|
|
agent {
|
|
docker {
|
|
image 'mcr.microsoft.com/playwright:v1.48.0-noble'
|
|
args '-u root'
|
|
}
|
|
}
|
|
steps {
|
|
sh 'npm ci'
|
|
sh 'npx playwright test --shard=1/4'
|
|
}
|
|
post {
|
|
always {
|
|
archiveArtifacts artifacts: 'blob-report/**',
|
|
allowEmptyArchive: true
|
|
}
|
|
}
|
|
}
|
|
stage('Shard 2') {
|
|
agent {
|
|
docker {
|
|
image 'mcr.microsoft.com/playwright:v1.48.0-noble'
|
|
args '-u root'
|
|
}
|
|
}
|
|
steps {
|
|
sh 'npm ci'
|
|
sh 'npx playwright test --shard=2/4'
|
|
}
|
|
post {
|
|
always {
|
|
archiveArtifacts artifacts: 'blob-report/**',
|
|
allowEmptyArchive: true
|
|
}
|
|
}
|
|
}
|
|
stage('Shard 3') {
|
|
agent {
|
|
docker {
|
|
image 'mcr.microsoft.com/playwright:v1.48.0-noble'
|
|
args '-u root'
|
|
}
|
|
}
|
|
steps {
|
|
sh 'npm ci'
|
|
sh 'npx playwright test --shard=3/4'
|
|
}
|
|
post {
|
|
always {
|
|
archiveArtifacts artifacts: 'blob-report/**',
|
|
allowEmptyArchive: true
|
|
}
|
|
}
|
|
}
|
|
stage('Shard 4') {
|
|
agent {
|
|
docker {
|
|
image 'mcr.microsoft.com/playwright:v1.48.0-noble'
|
|
args '-u root'
|
|
}
|
|
}
|
|
steps {
|
|
sh 'npm ci'
|
|
sh 'npx playwright test --shard=4/4'
|
|
}
|
|
post {
|
|
always {
|
|
archiveArtifacts artifacts: 'blob-report/**',
|
|
allowEmptyArchive: true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## CircleCI
|
|
|
|
### Basic Pipeline
|
|
|
|
```yaml
|
|
# .circleci/config.yml
|
|
version: 2.1
|
|
|
|
executors:
|
|
pw:
|
|
docker:
|
|
- image: mcr.microsoft.com/playwright:v1.48.0-noble
|
|
working_directory: ~/app
|
|
|
|
jobs:
|
|
install:
|
|
executor: pw
|
|
steps:
|
|
- checkout
|
|
- restore_cache:
|
|
keys:
|
|
- deps-{{ checksum "package-lock.json" }}
|
|
- run: npm ci
|
|
- save_cache:
|
|
key: deps-{{ checksum "package-lock.json" }}
|
|
paths:
|
|
- node_modules
|
|
- persist_to_workspace:
|
|
root: .
|
|
paths:
|
|
- node_modules
|
|
|
|
test:
|
|
executor: pw
|
|
parallelism: 4
|
|
steps:
|
|
- checkout
|
|
- attach_workspace:
|
|
at: .
|
|
- run:
|
|
name: Run tests
|
|
command: |
|
|
npx playwright test --shard=$((CIRCLE_NODE_INDEX + 1))/$CIRCLE_NODE_TOTAL
|
|
- store_artifacts:
|
|
path: pw-report
|
|
destination: pw-report
|
|
- store_artifacts:
|
|
path: results
|
|
destination: results
|
|
- store_test_results:
|
|
path: results/junit.xml
|
|
|
|
workflows:
|
|
test:
|
|
jobs:
|
|
- install
|
|
- test:
|
|
requires:
|
|
- install
|
|
```
|
|
|
|
### Using Orbs
|
|
|
|
```yaml
|
|
# .circleci/config.yml
|
|
version: 2.1
|
|
|
|
orbs:
|
|
node: circleci/node@latest
|
|
|
|
executors:
|
|
pw:
|
|
docker:
|
|
- image: mcr.microsoft.com/playwright:v1.48.0-noble
|
|
|
|
jobs:
|
|
e2e:
|
|
executor: pw
|
|
parallelism: 4
|
|
steps:
|
|
- checkout
|
|
- node/install-packages
|
|
- run:
|
|
name: Run tests
|
|
command: npx playwright test --shard=$((CIRCLE_NODE_INDEX + 1))/$CIRCLE_NODE_TOTAL
|
|
- store_artifacts:
|
|
path: pw-report
|
|
- store_test_results:
|
|
path: results/junit.xml
|
|
|
|
workflows:
|
|
main:
|
|
jobs:
|
|
- e2e
|
|
```
|
|
|
|
## Azure DevOps
|
|
|
|
### Basic Pipeline
|
|
|
|
```yaml
|
|
# azure-pipelines.yml
|
|
trigger:
|
|
branches:
|
|
include:
|
|
- main
|
|
|
|
pr:
|
|
branches:
|
|
include:
|
|
- main
|
|
|
|
pool:
|
|
vmImage: "ubuntu-latest"
|
|
|
|
variables:
|
|
CI: "true"
|
|
npm_config_cache: $(Pipeline.Workspace)/.npm
|
|
|
|
steps:
|
|
- task: NodeTool@0
|
|
inputs:
|
|
versionSpec: "20.x"
|
|
displayName: "Install Node.js"
|
|
|
|
- task: Cache@2
|
|
inputs:
|
|
key: 'npm | "$(Agent.OS)" | package-lock.json'
|
|
restoreKeys: |
|
|
npm | "$(Agent.OS)"
|
|
path: $(npm_config_cache)
|
|
displayName: "Cache npm"
|
|
|
|
- script: npm ci
|
|
displayName: "Install dependencies"
|
|
|
|
- script: npx playwright install --with-deps
|
|
displayName: "Install browsers"
|
|
|
|
- script: npx playwright test
|
|
displayName: "Run tests"
|
|
|
|
- task: PublishTestResults@2
|
|
condition: always()
|
|
inputs:
|
|
testResultsFormat: "JUnit"
|
|
testResultsFiles: "results/junit.xml"
|
|
mergeTestResults: true
|
|
testRunTitle: "E2E Tests"
|
|
displayName: "Publish results"
|
|
|
|
- task: PublishPipelineArtifact@1
|
|
condition: always()
|
|
inputs:
|
|
targetPath: pw-report
|
|
artifact: pw-report
|
|
publishLocation: "pipeline"
|
|
displayName: "Upload report"
|
|
```
|
|
|
|
### With Sharding
|
|
|
|
```yaml
|
|
# azure-pipelines.yml
|
|
trigger:
|
|
branches:
|
|
include:
|
|
- main
|
|
|
|
pr:
|
|
branches:
|
|
include:
|
|
- main
|
|
|
|
variables:
|
|
CI: "true"
|
|
|
|
stages:
|
|
- stage: Test
|
|
jobs:
|
|
- job: E2E
|
|
pool:
|
|
vmImage: "ubuntu-latest"
|
|
strategy:
|
|
matrix:
|
|
shard1:
|
|
SHARD: "1/4"
|
|
shard2:
|
|
SHARD: "2/4"
|
|
shard3:
|
|
SHARD: "3/4"
|
|
shard4:
|
|
SHARD: "4/4"
|
|
steps:
|
|
- task: NodeTool@0
|
|
inputs:
|
|
versionSpec: "20.x"
|
|
|
|
- script: npm ci
|
|
displayName: "Install dependencies"
|
|
|
|
- script: npx playwright install --with-deps
|
|
displayName: "Install browsers"
|
|
|
|
- script: npx playwright test --shard=$(SHARD)
|
|
displayName: "Run tests (shard $(SHARD))"
|
|
|
|
- task: PublishPipelineArtifact@1
|
|
condition: always()
|
|
inputs:
|
|
targetPath: blob-report
|
|
artifact: blob-report-$(System.JobPositionInPhase)
|
|
displayName: "Upload blob report"
|
|
|
|
- stage: Report
|
|
dependsOn: Test
|
|
condition: always()
|
|
jobs:
|
|
- job: MergeReports
|
|
pool:
|
|
vmImage: "ubuntu-latest"
|
|
steps:
|
|
- task: NodeTool@0
|
|
inputs:
|
|
versionSpec: "20.x"
|
|
|
|
- script: npm ci
|
|
displayName: "Install dependencies"
|
|
|
|
- task: DownloadPipelineArtifact@2
|
|
inputs:
|
|
patterns: "blob-report-*/**"
|
|
path: all-blob-reports
|
|
displayName: "Download blob reports"
|
|
|
|
- script: npx playwright merge-reports --reporter=html ./all-blob-reports
|
|
displayName: "Merge reports"
|
|
|
|
- task: PublishPipelineArtifact@1
|
|
inputs:
|
|
targetPath: pw-report
|
|
artifact: pw-report
|
|
displayName: "Upload merged report"
|
|
```
|
|
|
|
## JUnit Reporter Config
|
|
|
|
All platforms benefit from JUnit output for native test result display:
|
|
|
|
```typescript
|
|
// playwright.config.ts
|
|
import { defineConfig } from "@playwright/test";
|
|
|
|
export default defineConfig({
|
|
reporter: process.env.CI
|
|
? [
|
|
["dot"],
|
|
["html", { open: "never" }],
|
|
["junit", { outputFile: "results/junit.xml" }],
|
|
]
|
|
: [["html", { open: "on-failure" }]],
|
|
});
|
|
```
|
|
|
|
## Platform Comparison
|
|
|
|
| Feature | CircleCI | Azure DevOps | Jenkins |
|
|
| ----------------- | ----------------------------------------------- | -------------------------------- | ---------------------- |
|
|
| Docker support | `docker:` executor | `vmImage` or container jobs | Docker Pipeline plugin |
|
|
| Parallelism | `parallelism: N` + `CIRCLE_NODE_INDEX` | `strategy.matrix` | `parallel` stages |
|
|
| Artifact upload | `store_artifacts` | `PublishPipelineArtifact@1` | `archiveArtifacts` |
|
|
| JUnit integration | `store_test_results` | `PublishTestResults@2` | `junit` step |
|
|
| Shard variable | `$((CIRCLE_NODE_INDEX + 1))/$CIRCLE_NODE_TOTAL` | Define in matrix: `SHARD: '1/4'` | Hardcode per stage |
|
|
| Cache key | `checksum "package-lock.json"` | `Cache@2` with key template | `stash`/`unstash` |
|
|
| Secrets | Context + env variables | Variable groups | Credentials plugin |
|
|
|
|
## Troubleshooting
|
|
|
|
### Jenkins: "Browser closed unexpectedly"
|
|
|
|
Running as non-root in container causes sandbox issues.
|
|
|
|
```groovy
|
|
agent {
|
|
docker {
|
|
image 'mcr.microsoft.com/playwright:v1.48.0-noble'
|
|
args '-u root'
|
|
}
|
|
}
|
|
environment {
|
|
HOME = '/root'
|
|
}
|
|
```
|
|
|
|
### CircleCI: "Executable doesn't exist"
|
|
|
|
Image version mismatch with `@playwright/test` version. Use `latest` tag or match versions:
|
|
|
|
```yaml
|
|
docker:
|
|
- image: mcr.microsoft.com/playwright:v1.48.0-noble
|
|
```
|
|
|
|
### Azure DevOps: Test results not showing
|
|
|
|
Missing JUnit reporter or `PublishTestResults@2` task:
|
|
|
|
```typescript
|
|
reporter: [['junit', { outputFile: 'results/junit.xml' }]],
|
|
```
|
|
|
|
```yaml
|
|
- task: PublishTestResults@2
|
|
condition: always()
|
|
inputs:
|
|
testResultsFormat: "JUnit"
|
|
testResultsFiles: "results/junit.xml"
|
|
```
|
|
|
|
### Shard index off by one
|
|
|
|
CircleCI's `CIRCLE_NODE_INDEX` is 0-based, Playwright's `--shard` is 1-based:
|
|
|
|
```yaml
|
|
# CircleCI - add 1
|
|
command: npx playwright test --shard=$((CIRCLE_NODE_INDEX + 1))/$CIRCLE_NODE_TOTAL
|
|
```
|
|
|
|
## Anti-Patterns
|
|
|
|
| Anti-Pattern | Problem | Solution |
|
|
| ----------------------------------- | ----------------------------------------- | ---------------------------------------------------- |
|
|
| Missing `--with-deps` on bare metal | OS libs missing, browser launch fails | Use Playwright Docker image or `--with-deps` |
|
|
| No JUnit reporter | CI can't display test results | Add `['junit', { outputFile: 'results/junit.xml' }]` |
|
|
| No job timeout | Hung tests consume resources indefinitely | Set explicit timeout (20-30 min) |
|
|
| No artifact upload on success | Can't verify passing results | Always upload reports (`condition: always()`) |
|
|
| Non-root in container without setup | Permission errors on browser binaries | Run as root or configure permissions |
|
|
| Hardcoded shard count | Must update multiple places | Use CI-native variables |
|