Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
test
autotest-playwright-web-spd
Commits
e1191306
Commit
e1191306
authored
Oct 24, 2026
by
liguangyu06
Browse files
新增 Jenkins 日常验证流水线 Jenkinsfile.verify
供 spd-ui-verify 手工跑 smoke/单模块,与夜间 full 分离;并补充任务创建文档。
parent
1a4c1b8b
Changes
4
Hide whitespace changes
Inline
Side-by-side
Jenkinsfile.verify
0 → 100644
View file @
e1191306
// 日常验证专用流水线:默认 smoke,超时 2 小时,不设定时;与夜间 SPD/full 任务分离。
// Jenkins 任务 Script Path 填 Jenkinsfile.verify
def splitEmailAddrs(String raw) {
return (raw ?: '')
.split(/[,;\s]+/)
.collect { it.trim() }
.findAll { it.contains('@') }
}
def loadDefaultReportRecipients() {
def path = 'config/report-email-recipients.txt'
if (!fileExists(path)) {
return []
}
return readFile(encoding: 'UTF-8', file: path)
.split('\n')
.collect { it.trim() }
.findAll { it && !it.startsWith('#') && it.contains('@') }
}
def mergeEmailAddrs(List parts) {
def seen = []
def all = []
parts.flatten().findAll { it }.each { addr ->
def key = addr.toLowerCase()
if (!seen.contains(key)) {
seen.add(key)
all.add(addr)
}
}
return all
}
def sendReportEmail() {
def extraTo = (params.REPORT_EMAIL_TO ?: '').trim()
if (!extraTo) {
extraTo = (env.REPORT_EMAIL_TO ?: '').trim()
}
def cc = (params.REPORT_EMAIL_CC ?: '').trim()
if (!cc) {
cc = (env.REPORT_EMAIL_CC ?: '').trim()
}
def recipients = mergeEmailAddrs([
loadDefaultReportRecipients(),
splitEmailAddrs(extraTo),
splitEmailAddrs(cc),
])
if (!recipients) {
echo '未配置收件人,跳过邮件(日常验证任务通常不必发信)。'
return
}
def to = recipients.join(',')
def subject = fileExists('reports/latest/email-subject.txt')
? readFile(encoding: 'UTF-8', file: 'reports/latest/email-subject.txt').trim()
: "【SPD UI自动化·验证】${params.SUITE} ${currentBuild.currentResult} #${env.BUILD_NUMBER}"
def body = fileExists('reports/latest/email-summary.html')
? readFile(encoding: 'UTF-8', file: 'reports/latest/email-summary.html')
: """<p>构建 ${currentBuild.currentResult},未生成详细摘要。</p>
<p>测试范围:${params.SUITE}</p>
<p>Playwright 报告:<a href='${env.BUILD_URL}Playwright_20Report/'>打开 Playwright 报告</a></p>"""
if (!fileExists('reports/latest/email-summary.html')) {
writeFile encoding: 'UTF-8', file: 'reports/latest/email-summary.html', text: body
}
if (!fileExists('reports/latest/email-subject.txt')) {
writeFile encoding: 'UTF-8', file: 'reports/latest/email-subject.txt', text: subject + '\n'
}
try {
def credId = env.SMTP_CREDENTIALS_ID ?: 'sinopharm-smtp'
withCredentials([
usernamePassword(credentialsId: credId, usernameVariable: 'SMTP_USER', passwordVariable: 'SMTP_PASS')
]) {
withEnv([
"EMAIL_TO=${to}",
'SMTP_HOST=mail.sinopharm.com',
'SMTP_PORT=465',
'SMTP_FROM=liguangyu@sinopharm.com'
]) {
if (isUnix()) {
sh 'python3 scripts/send-ci-email.py'
} else {
bat 'python scripts\\send-ci-email.py'
}
}
}
echo "已发送报告邮件至 ${to}"
} catch (err) {
echo "邮件发送失败:${err}"
}
}
def runCi() {
if (isUnix()) {
sh 'node scripts/run-ci.mjs --suite="$SUITE" --shards="$SHARDS"'
} else {
bat '''
set PATH=C:\\Program Files\\nodejs;%PATH%
node scripts\\run-ci-win.mjs --suite=%SUITE% --shards=%SHARDS%
'''
}
}
pipeline {
agent any
options {
timestamps()
disableConcurrentBuilds()
timeout(time: 2, unit: 'HOURS')
buildDiscarder(logRotator(numToKeepStr: '30', artifactNumToKeepStr: '5'))
}
parameters {
choice(
name: 'SUITE',
choices: ['smoke', 'login', 'vendor', 'admit', 'basic', 'surgery', 'warehouse', 'flow', 'center', 'dept', 'nightly', 'full', 'invoice', 'trace', 'consume', 'recon', 'cost', 'warn', 'pol', 'exc', 'ana', 'ops', 'reag', 'invoice-trace', 'recon-cost', 'extended', 'quarantine'],
description: '日常验证默认 smoke(@smoke);改脚本后可单模块如 vendor / admit'
)
string(name: 'WORKERS', defaultValue: '3', description: '并行文件数,验证环境建议 2~3')
string(name: 'SHARDS', defaultValue: '1', description: '分片数,日常验证保持 1')
string(name: 'RETRIES', defaultValue: '0', description: '用例级重试,保持 0')
booleanParam(name: 'RUN_FULL', defaultValue: false, description: '含 @full 全列排序(验证任务一般不要勾)')
string(name: 'GREP', defaultValue: '', description: '只跑标题匹配的用例,例如 STAT-IES-001 或 @smoke')
booleanParam(name: 'ALLURE', defaultValue: false, description: '额外生成 Allure')
string(name: 'KEEP_ARCHIVES', defaultValue: '0', description: '验证任务建议 0,少占磁盘')
string(name: 'REPORT_EMAIL_TO', defaultValue: '', description: '追加收件人,留空则不发邮件')
string(name: 'REPORT_EMAIL_CC', defaultValue: '', description: '抄送,可选')
}
environment {
CI = '1'
SUITE = "${params.SUITE}"
PW_WORKERS = "${params.WORKERS}"
SHARDS = "${params.SHARDS}"
PW_RETRIES = "${params.RETRIES}"
RUN_FULL = "${params.RUN_FULL ? '1' : '0'}"
PW_GREP = "${params.GREP ?: ''}"
PLAYWRIGHT_BROWSERS_PATH = "${WORKSPACE}/.playwright-browsers"
ALLURE = "${params.ALLURE ? '1' : '0'}"
PW_KEEP_ARCHIVES = "${params.KEEP_ARCHIVES ?: '0'}"
SPD_CREDENTIALS_ID = "${env.SPD_CREDENTIALS_ID ?: 'spd-ui-test-account'}"
JAVA_HOME = "${env.JAVA_HOME ?: 'E:/Program Files/Java/jdk-11'}"
}
stages {
stage('Prepare') {
steps {
script {
if (isUnix()) {
sh '''
set -e
node -v
npm -v
node -e "if (parseInt(process.versions.node.split('.')[0], 10) < 20) process.exit(1)"
'''
} else {
bat '''
node -v
npm -v
node -e "if (parseInt(process.versions.node.split('.')[0], 10) < 20) process.exit(1)"
'''
}
}
}
}
stage('Install') {
steps {
script {
if (isUnix()) {
sh '''
set -e
npm ci
npx playwright install-deps chromium || true
npm run playwright:install
'''
} else {
bat '''
call npm ci
if errorlevel 1 exit /b 1
call npm run playwright:install
if errorlevel 1 exit /b 1
'''
}
}
}
}
stage('Typecheck') {
steps {
script {
if (isUnix()) {
sh 'npm run typecheck'
} else {
bat 'call npm run typecheck'
}
}
}
}
stage('Test') {
steps {
script {
if (env.TEST_USER?.trim() || fileExists('.env')) {
runCi()
} else {
withCredentials([
usernamePassword(
credentialsId: env.SPD_CREDENTIALS_ID,
usernameVariable: 'TEST_USER',
passwordVariable: 'TEST_PASS'
)
]) {
runCi()
}
}
}
}
}
}
post {
always {
script {
try {
junit allowEmptyResults: true, testResults: 'reports/latest/junit.xml'
} catch (err) {
echo "跳过 JUnit:${err}"
}
try {
archiveArtifacts artifacts: 'reports/latest/**, reports/ci-*/summary.json, reports/ci-*/meta.txt', allowEmptyArchive: true, fingerprint: true
} catch (err) {
echo "跳过归档:${err}"
}
try {
if (fileExists('reports/latest/summary.json')) {
def descScript = 'node -e "var s=require(\'./reports/latest/summary.json\'); var c=s.counts||{}; console.log(s.suite+\' fail=\'+(c.failures||0)+\'/\'+(c.tests||0))"'
def desc = isUnix()
? sh(script: descScript, returnStdout: true).trim()
: bat(script: "@echo off\n${descScript}", returnStdout: true).trim()
currentBuild.description = desc
}
} catch (err) {
echo "跳过构建描述:${err}"
}
try {
publishHTML([
allowMissing: true,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'reports/latest/playwright-report',
reportFiles: 'index.html',
reportName: 'Playwright Report'
])
} catch (err) {
echo "跳过 HTML 报告:${err}"
}
try {
withEnv(["BUILD_RESULT=${currentBuild.currentResult}"]) {
if (isUnix()) {
sh 'node scripts/ci-email-summary.mjs || true'
} else {
bat returnStatus: true, script: 'node scripts\\ci-email-summary.mjs'
}
}
sendReportEmail()
} catch (err) {
echo "跳过邮件:${err}"
}
}
}
}
}
docs/Jenkins配置指南.md
View file @
e1191306
...
...
@@ -3,7 +3,7 @@
不要选「自由风格」里的
**Execute Windows batch command**
手写一长串命令。
选
**Pipeline**
,它会自动读仓库里的
`Jenkinsfile`
。流水线已改成
**Windows 的 cmd**
,不用 Git Bash。
首页原来的
`spd3.0`
、电解任务不要动。
只新
建
`spd-ui-smoke`
。
首页原来的
`spd3.0`
、电解任务不要动。
日常验证新建
**`spd-ui-verify`**
(见
[
jenkins-job-spd-ui-verify.md
](
./jenkins-job-spd-ui-verify.md
)
);定时冒烟可另
建
`spd-ui-smoke`
。
---
...
...
docs/jenkins-job-spd-ui-verify.md
0 → 100644
View file @
e1191306
# Jenkins 任务:spd-ui-verify(平时验证脚本)
用于
**手工触发**
的冒烟/单模块验证,与夜间
`SPD`
(full/nightly)分离。
**不要**
给本任务配 Build periodically(定时);需要时再点
**Build with Parameters**
。
## 1. 新建任务(约 3 分钟)
1.
Jenkins 首页 →
**新建 Item**
2.
名称:
`spd-ui-verify`
(勿与现有
`spd-ui-smoke`
、
`SPD`
重名)
3.
类型:
**Pipeline**
→
**OK**
4.
拉到最下方
**Pipeline**
:
| 项 | 值 |
|----|-----|
| Definition | Pipeline script from SCM |
| SCM | Git |
| Repository URL |
`http://10.17.65.20/gitlab/test/autotest-playwright-web-spd.git`
|
| Credentials |
`gitlab-spd-ui`
(与现有 SPD 任务相同) |
| Branch |
`*/main`
|
| Script Path |
**`Jenkinsfile.verify`**
(不是
`Jenkinsfile`
) |
5.
**不要**
勾选 Build periodically
6.
建议勾选
**This project is parameterized**
(首次构建后 Jenkins 会从流水线读参数,可不动)
7.
**Save**
## 2. 第一次运行
1.
点
**Build with Parameters**
2.
默认即可:
-
**SUITE**
=
`smoke`
(
`@smoke`
用例,约数十分钟级)
-
**WORKERS**
=
`3`
-
**SHARDS**
=
`1`
-
**RUN_FULL**
= 不勾
-
**KEEP_ARCHIVES**
=
`0`
(少占盘)
3.
构建完成后打开左侧
**Playwright Report**
,看通过/失败与 trace
## 3. 常见用法
| 场景 | 参数 |
|------|------|
| 改完框架/公共 PO 后快速回归 |
`SUITE=smoke`
|
| 只验证登录 |
`SUITE=login`
|
| 只验证厂商模块 |
`SUITE=vendor`
|
| 只跑某条用例标题 |
`GREP=STAT-IES-001`
或
`GREP=@smoke`
|
| 本地改 spec 已 push 到 main | 直接 Build,拉最新 main |
本地等价:
```
bash
npm run
test
:ci:smoke
# 或
SUITE
=
vendor
PW_WORKERS
=
2 npm run
test
:ci
```
## 4. 凭据与节点
与
[
Jenkins配置指南.md
](
./Jenkins配置指南.md
)
相同:
-
GitLab:
`gitlab-spd-ui`
-
SPD 账号:
`spd-ui-test-account`
(
`TEST_USER`
/
`TEST_PASS`
)
-
Node
**20+**
,Windows 节点用 cmd(流水线已适配)
## 5. 与 temp / main 的关系
-
Jenkins
**只拉 `main`**
(正式自动化仓库)
-
在
`temp`
开发时:先合 selective 到
`main`
,或临时把 Branch 改成
`*/temp`
做试验(勿长期这样)
## 6. 和 spd-ui-smoke 的区别
| 任务 | Script Path | 默认套件 | 定时 |
|------|-------------|----------|------|
|
`spd-ui-smoke`
|
`Jenkinsfile`
| 需在参数里选 smoke | 可配工作时段 cron |
|
**`spd-ui-verify`**
|
**`Jenkinsfile.verify`**
|
**默认 smoke**
|
**仅手工**
|
两者可并存:smoke 负责定时巡检,verify 负责你 push 后随手点一次。
docs/jenkins.md
View file @
e1191306
...
...
@@ -8,6 +8,7 @@
| 任务 | 套件 | 建议 cron | 说明 |
|------|------|-----------|------|
|
**`spd-ui-verify`**
|
**`smoke`(默认)**
|
**无(仅手工)**
|
**平时验证脚本**
:Script Path 用
`Jenkinsfile.verify`
,见
[
jenkins-job-spd-ui-verify.md
](
./jenkins-job-spd-ui-verify.md
)
|
|
`spd-ui-smoke`
|
`smoke`
|
`H 8-18 * * 1-5`
| 工作时段冒烟:报表与基础设置中已打
`@smoke`
的用例(含厂商、准入、基础管理、手术、库房、流程) |
|
`spd-ui-nightly`
/
`SPD`
|
`nightly`
/
`full`
|
`H 23 * * 1,3,5`
| 周一、三、五
**23 点**
回归(Jenkins 任务
`SPD`
当前按此时段跑 full) |
|
`spd-ui-full`
|
`full`
|
`H 3 * * 0`
| 周末含全列排序,最慢 |
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment