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
f445d217
Commit
f445d217
authored
Sep 15, 2026
by
liguangyu06
Browse files
Make report emails lead with scope, counts, pass rate, and the Playwright link.
parent
4852f397
Changes
5
Hide whitespace changes
Inline
Side-by-side
Jenkinsfile
View file @
f445d217
...
...
@@ -16,7 +16,10 @@ def sendReportEmail() {
:
"【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},未生成摘要。请打开 <a href='${env.BUILD_URL}'>Jenkins</a></p>"
:
"""<p>构建 ${currentBuild.currentResult},未生成详细摘要。</p>
<p>测试范围:${params.SUITE}</p>
<p>Playwright 报告:<a href='${env.BUILD_URL}Playwright_20Report/'>${env.BUILD_URL}Playwright_20Report/</a></p>
<p>Jenkins 构建:<a href='${env.BUILD_URL}'>${env.BUILD_URL}</a></p>"""
if
(
cc
)
{
to
=
[
to
,
cc
].
findAll
{
it
}.
join
(
','
)
}
...
...
README.md
View file @
f445d217
...
...
@@ -34,7 +34,7 @@ Chromium 缓存在项目内 `.playwright-browsers/`(已 gitignore),与 `@p
并行默认按
**文件**
(
`PW_WORKERS`
)。不要开
`PW_FULLY_PARALLEL=1`
:本仓库大量同页 serial,拆开后同一账号会互踩。总浏览器约
`SHARDS × WORKERS`
,单机建议不超过 6。详见
[
docs/jenkins.md
](
docs/jenkins.md
)
。
产物在
`reports/latest/`
:Playwright HTML、
`junit.xml`
、Allure results、
`summary.json`
。定时跑完会发
**HTML 摘要邮件**
(
失败列表 + Jenkins
报告链接),不要把完整报告当附件。收件人配
`REPORT_EMAIL_TO`
,SMTP 与任务拆分见
[
docs/jenkins.md
](
docs/jenkins.md
)
。
产物在
`reports/latest/`
:Playwright HTML、
`junit.xml`
、Allure results、
`summary.json`
。定时跑完会发
**HTML 摘要邮件**
(
测试范围、执行数、成功/失败、通过率、
报告链接),不要把完整报告当附件。收件人配
`REPORT_EMAIL_TO`
,SMTP 与任务拆分见
[
docs/jenkins.md
](
docs/jenkins.md
)
。
Jenkins:仓库根目录
`Jenkinsfile`
。逐步配置见
[
docs/Jenkins配置指南.md
](
docs/Jenkins配置指南.md
)
,套件与并行约定见
[
docs/jenkins.md
](
docs/jenkins.md
)
。凭据 ID 默认
`spd-ui-test-account`
。
...
...
docs/Jenkins配置指南.md
View file @
f445d217
...
...
@@ -92,7 +92,7 @@ git --version
## 第五步:把测试报告用邮件发给同事
流水线跑完会自动发信,不用再加「构建后操作」。
邮件里是:
**
结果摘要 + 失败用例列表 + Jenkins/
Playwright 报告链接**
,并带上附件
`email-summary.html`
。
邮件里是:
**
测试范围、执行用例数、成功/失败、通过率、
Playwright 报告链接**
,
以及失败用例列表;
并带上附件
`email-summary.html`
。
完整截图/Trace 请同事点邮件里的链接打开(整包报告太大,邮箱会拒收,所以不当附件)。
### 5.1 确认插件
...
...
docs/jenkins.md
View file @
f445d217
...
...
@@ -98,7 +98,7 @@ npm run test:spd:shard
## 邮件发给领导 / 同事
邮件发的是
**一页摘要**
(
通过
/失败、
耗时
、失败用例列表)+ Jenkins 报告链接。
邮件发的是
**一页摘要**
(
测试范围、执行用例数、成功
/失败、
通过率
、失败用例列表)+
Playwright/
Jenkins 报告链接。
**不要**
把整个 Playwright 目录当附件:体积大,Trace 里还有页面数据。
### 1. Jenkins 插件与 SMTP
...
...
@@ -134,13 +134,14 @@ Default user e-mail suffix: @公司域名
### 3. 领导看到什么
主题示例:
`【SPD UI自动化】
nightly
失败 11
8
0/1
204 #88
`
主题示例:
`【SPD UI自动化】
smoke
失败 110/1
19(通过率 92.4%) #13
`
正文:
-
套件、通过/失败/跳过、耗时
-
测试范围(套件说明)
-
执行用例数、成功、失败、通过率
-
失败用例前 30 条
-
链接:
Jenkins 构建、
Playwright 报告、Allure(装了插件才有效)
-
链接:Playwright 报告、
Jenkins 构建、
Allure(装了插件才有效)
点 Playwright 报告即可看截图和 trace。若公司 Jenkins 需登录,请给领导开只读权限,或把 Jenkins 配在内网 SSO 后发信。
...
...
scripts/ci-email-summary.mjs
View file @
f445d217
...
...
@@ -9,6 +9,8 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import
path
from
'
node:path
'
;
import
{
fileURLToPath
}
from
'
node:url
'
;
import
{
getSuite
}
from
'
../config/suites.config.mjs
'
;
const
ROOT
=
path
.
resolve
(
path
.
dirname
(
fileURLToPath
(
import
.
meta
.
url
)),
'
..
'
);
const
LATEST
=
path
.
join
(
ROOT
,
'
reports
'
,
'
latest
'
);
...
...
@@ -66,6 +68,20 @@ function parseFailedTests(junitFile, limit = 30) {
return
cases
;
}
function
suiteScope
(
summary
)
{
if
(
summary
?.
description
)
return
summary
.
description
;
try
{
return
getSuite
(
summary
?.
suite
||
process
.
env
.
SUITE
).
description
;
}
catch
{
return
summary
?.
suite
||
process
.
env
.
SUITE
||
'
SPD UI 自动化
'
;
}
}
function
formatPassRate
(
passed
,
executed
)
{
if
(
!
executed
)
return
'
-
'
;
return
`
${((
passed
/
executed
)
*
100
).
toFixed
(
1
)}
%`
;
}
function
statusLabel
(
summary
,
jenkinsResult
)
{
if
(
jenkinsResult
&&
jenkinsResult
!==
'
SUCCESS
'
&&
jenkinsResult
!==
'
UNSTABLE
'
)
{
if
(
jenkinsResult
===
'
ABORTED
'
)
return
{
text
:
'
已中止
'
,
color
:
'
#6b7280
'
};
...
...
@@ -89,27 +105,40 @@ function main() {
const
failures
=
Number
(
counts
.
failures
||
0
);
const
errors
=
Number
(
counts
.
errors
||
0
);
const
skipped
=
Number
(
counts
.
skipped
||
0
);
const
passed
=
Math
.
max
(
0
,
tests
-
failures
-
errors
-
skipped
);
const
failTotal
=
failures
+
errors
;
const
passed
=
Math
.
max
(
0
,
tests
-
failures
-
errors
-
skipped
);
const
executed
=
passed
+
failTotal
;
const
passRate
=
formatPassRate
(
passed
,
executed
||
tests
);
const
jenkinsResult
=
process
.
env
.
BUILD_RESULT
||
process
.
env
.
JENKINS_BUILD_RESULT
||
''
;
const
label
=
statusLabel
(
summary
,
jenkinsResult
);
const
buildUrl
=
(
process
.
env
.
BUILD_URL
||
''
).
replace
(
/
\/?
$/
,
'
/
'
);
const
jobName
=
process
.
env
.
JOB_NAME
||
'
spd-ui-automation
'
;
const
jobName
=
process
.
env
.
JOB_NAME
||
'
SPD
'
;
const
buildNo
=
process
.
env
.
BUILD_NUMBER
||
summary
.
buildId
||
'
-
'
;
const
suite
=
summary
.
suite
||
process
.
env
.
SUITE
||
'
-
'
;
const
scope
=
suiteScope
(
summary
);
const
stamp
=
summary
.
stamp
?
new
Date
(
summary
.
stamp
).
toLocaleString
(
'
zh-CN
'
,
{
hour12
:
false
})
:
new
Date
().
toLocaleString
(
'
zh-CN
'
,
{
hour12
:
false
});
const
duration
=
formatDuration
(
summary
.
durationMs
);
const
failed
=
parseFailedTests
(
path
.
join
(
LATEST
,
'
junit.xml
'
));
const
moreFail
=
failTotal
>
failed
.
length
?
failTotal
-
failed
.
length
:
0
;
const
pwReportUrl
=
buildUrl
?
`
${
buildUrl
}
Playwright_20Report/`
:
''
;
const
jenkinsUrl
=
buildUrl
;
const
allureUrl
=
buildUrl
?
`
${
buildUrl
}
allure`
:
''
;
const
reportLinks
=
[];
if
(
buildUrl
)
{
reportLinks
.
push
(
`<a href="
${
escapeHtml
(
buildUrl
)}
">Jenkins 构建</a>`
);
reportLinks
.
push
(
`<a href="
${
escapeHtml
(
buildUrl
)}
Playwright_20Report/">Playwright 报告</a>`
);
reportLinks
.
push
(
`<a href="
${
escapeHtml
(
buildUrl
)}
allure/">Allure 报告</a>(若已装插件)`
);
}
else
{
reportLinks
.
push
(
'
本地运行:打开 reports/latest/playwright-report/index.html
'
);
}
const
metric
=
(
title
,
value
,
color
)
=>
`<td style="padding:10px 8px;text-align:center;width:25%;">
<div style="font-size:12px;color:#6b7280;">
${
title
}
</div>
<div style="font-size:26px;font-weight:700;color:
${
color
}
;margin-top:4px;line-height:1.2;">
${
value
}
</div>
</td>`
;
const
reportButtons
=
buildUrl
?
`<p style="margin:0 0 10px;">
<a href="
${
escapeHtml
(
pwReportUrl
)}
" style="display:inline-block;padding:10px 16px;background:#047857;color:#fff;text-decoration:none;border-radius:6px;font-weight:600;">打开 Playwright 测试报告</a>
</p>
<p style="margin:0;font-size:13px;line-height:1.8;">
Playwright 报告:<a href="
${
escapeHtml
(
pwReportUrl
)}
">
${
escapeHtml
(
pwReportUrl
)}
</a><br>
Jenkins 构建:<a href="
${
escapeHtml
(
jenkinsUrl
)}
">
${
escapeHtml
(
jenkinsUrl
)}
</a><br>
Allure 报告:<a href="
${
escapeHtml
(
allureUrl
)}
">
${
escapeHtml
(
allureUrl
)}
</a>(未装插件时可能打不开)
</p>`
:
`<p style="margin:0;font-size:13px;">本地报告:reports/latest/playwright-report/index.html</p>`
;
const
failRows
=
failed
.
length
?
failed
...
...
@@ -126,40 +155,54 @@ function main() {
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>SPD UI 自动化报告</title></head>
<body style="margin:0;padding:0;background:#f3f4f6;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;color:#111827;">
<div style="max-width:7
2
0px;margin:24px auto;background:#fff;border-radius:8px;overflow:hidden;border:1px solid #e5e7eb;">
<div style="max-width:7
6
0px;margin:24px auto;background:#fff;border-radius:8px;overflow:hidden;border:1px solid #e5e7eb;">
<div style="padding:16px 20px;background:
${
label
.
color
}
;color:#fff;">
<div style="font-size:13px;opacity:.9;">SPD 医用耗材 · UI 自动化</div>
<div style="font-size:22px;font-weight:700;margin-top:4px;">
${
escapeHtml
(
label
.
text
)}
·
${
escapeHtml
(
suite
)}
</div>
<div style="font-size:13px;opacity:.9;">SPD 医用耗材 · UI 自动化
测试报告
</div>
<div style="font-size:22px;font-weight:700;margin-top:4px;">
${
escapeHtml
(
label
.
text
)}
·
${
escapeHtml
(
suite
)}
· 通过率
${
escapeHtml
(
passRate
)}
</div>
</div>
<div style="padding:20px;">
<table style="border-collapse:collapse;width:100%;font-size:14px;">
<tr><td style="padding:6px 0;color:#6b7280;width:120px;">任务</td><td>
${
escapeHtml
(
jobName
)}
#
${
escapeHtml
(
buildNo
)}
</td></tr>
<tr><td style="padding:6px 0;color:#6b7280;">时间</td><td>
${
escapeHtml
(
stamp
)}
</td></tr>
<tr><td style="padding:6px 0;color:#6b7280;">耗时</td><td>
${
escapeHtml
(
duration
)}
</td></tr>
<tr><td style="padding:6px 0;color:#6b7280;">用例</td><td>共
${
tests
}
,通过
${
passed
}
,失败
${
failTotal
}
,跳过
${
skipped
}
</td></tr>
<tr><td style="padding:6px 0;color:#6b7280;">并行</td><td>workers=
${
escapeHtml
(
summary
.
workers
??
'
-
'
)}
shards=
${
escapeHtml
(
summary
.
shards
??
'
-
'
)}
</td></tr>
<tr><td style="padding:6px 0;color:#6b7280;">报告</td><td>
${
reportLinks
.
join
(
'
'
)}
</td></tr>
<table style="border-collapse:collapse;width:100%;font-size:14px;margin-bottom:16px;">
<tr><td style="padding:6px 0;color:#6b7280;width:96px;vertical-align:top;">测试范围</td><td>
${
escapeHtml
(
scope
)}
</td></tr>
<tr><td style="padding:6px 0;color:#6b7280;">任务</td><td>
${
escapeHtml
(
jobName
)}
#
${
escapeHtml
(
buildNo
)}
</td></tr>
<tr><td style="padding:6px 0;color:#6b7280;">执行时间</td><td>
${
escapeHtml
(
stamp
)}
(耗时
${
escapeHtml
(
duration
)}
)</td></tr>
</table>
<table style="border-collapse:collapse;width:100%;background:#f9fafb;border:1px solid #e5e7eb;border-radius:8px;">
<tr>
${
metric
(
'
执行用例数
'
,
executed
||
tests
,
'
#111827
'
)}
${
metric
(
'
成功
'
,
passed
,
'
#047857
'
)}
${
metric
(
'
失败
'
,
failTotal
,
failTotal
?
'
#b91c1c
'
:
'
#047857
'
)}
${
metric
(
'
通过率
'
,
passRate
,
failTotal
?
'
#b91c1c
'
:
'
#047857
'
)}
</tr>
</table>
<h3 style="margin:20px 0 8px;font-size:15px;">失败用例
${
moreFail
?
`(仅列出前
${
failed
.
length
}
条,另有
${
moreFail
}
条)`
:
''
}
</h3>
<p style="margin:8px 0 0;font-size:12px;color:#6b7280;">跳过
${
skipped
}
条未计入通过率;通过率 = 成功 / (成功 + 失败)。</p>
<h3 style="margin:22px 0 10px;font-size:15px;">测试报告</h3>
${
reportButtons
}
<h3 style="margin:22px 0 8px;font-size:15px;">失败用例
${
moreFail
?
`(仅列出前
${
failed
.
length
}
条,另有
${
moreFail
}
条)`
:
''
}
</h3>
<table style="border-collapse:collapse;width:100%;">
${
failRows
}
</table>
<p style="margin:20px 0 0;font-size:12px;color:#6b7280;">完整截图
/
Trace 请
打开 Jenkins 上
的 Playwright 报告
,邮件不附带大附件
。此邮件由定时任务自动发送。</p>
<p style="margin:20px 0 0;font-size:12px;color:#6b7280;">完整截图
与
Trace 请
点上面
的 Playwright 报告。此邮件由定时任务自动发送。</p>
</div>
</div>
</body>
</html>
`
;
const
subject
=
`【SPD UI自动化】
${
suite
}
${
label
.
text
}
${
passed
}
/
${
tests
||
'
-
'
}
#
${
buildNo
}
`
;
const
subject
=
`【SPD UI自动化】
${
suite
}
${
label
.
text
}
${
passed
}
/
${
executed
||
tests
||
'
-
'
}
(通过率
${
passRate
}
)
#
${
buildNo
}
`
;
writeFileSync
(
path
.
join
(
LATEST
,
'
email-summary.html
'
),
html
);
writeFileSync
(
path
.
join
(
LATEST
,
'
email-subject.txt
'
),
`
${
subject
}
\n`
);
writeFileSync
(
path
.
join
(
LATEST
,
'
email-summary.txt
'
),
[
subject
,
`测试范围:
${
scope
}
`
,
`任务:
${
jobName
}
#
${
buildNo
}
`
,
`结果:
${
label
.
text
}
通过
${
passed
}
/ 失败
${
failTotal
}
/ 跳过
${
skipped
}
/ 共
${
tests
}
`
,
`执行用例数:
${
executed
||
tests
}
`
,
`成功:
${
passed
}
`
,
`失败:
${
failTotal
}
`
,
`通过率:
${
passRate
}
`
,
skipped
?
`跳过:
${
skipped
}
`
:
''
,
`耗时:
${
duration
}
`
,
buildUrl
?
`Jenkins:
${
buildUrl
}
`
:
''
,
pwReportUrl
?
`Playwright 报告:
${
pwReportUrl
}
`
:
''
,
jenkinsUrl
?
`Jenkins 构建:
${
jenkinsUrl
}
`
:
''
,
''
,
]
.
filter
(
Boolean
)
...
...
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