Commit a238d36b authored by liguangyu06's avatar liguangyu06
Browse files

补充 spd-ui-verify 文档与 Jenkins 建任务脚本,明确不发邮件

parent a9af67a1
......@@ -3,7 +3,7 @@
不要选「自由风格」里的 **Execute Windows batch command** 手写一长串命令。
选 **Pipeline**,它会自动读仓库里的 `Jenkinsfile`。流水线已改成 **Windows 的 cmd**,不用 Git Bash。
首页原来的 `spd3.0`、电解任务不要动。日常验证新建 **`spd-ui-verify`**(见 [jenkins-job-spd-ui-verify.md](./jenkins-job-spd-ui-verify.md));定时冒烟可另建 `spd-ui-smoke`。
首页原来的 `spd3.0`、电解任务不要动。与现有 **`SPD`** 同一 Jenkins:日常验证复制 `SPD` 建 **`spd-ui-verify`**,Script Path 改为 `Jenkinsfile.verify`(见 [jenkins-job-spd-ui-verify.md](./jenkins-job-spd-ui-verify.md))。
---
......
......@@ -4,7 +4,7 @@
用于**手工触发**的冒烟/单模块验证;夜间 **`SPD`** 仍用 `Jenkinsfile` 跑 full/nightly 并**照常发邮件**。
**不要**给 `spd-ui-verify` 配 Build periodically;需要时点 **Build with Parameters**。
**不发邮件**(验证流水线已去掉邮件步骤;报告只看本任务 **Playwright Report**)。
**不发邮件**:`Jenkinsfile.verify` 无 SMTP/摘要邮件步骤;任务里勿保留 `REPORT_EMAIL_*` 参数或「构建后发邮件」。报告只看 **Playwright Report**。
## 1. 新建任务(推荐:复制 SPD)
......
......@@ -28,6 +28,7 @@
"quarantine:sync": "node scripts/sync-quarantine.mjs",
"quarantine:apply": "node scripts/sync-quarantine.mjs --write",
"email:summary": "node scripts/ci-email-summary.mjs",
"jenkins:create-verify": "node scripts/jenkins/create-spd-ui-verify-job.mjs",
"test:quarantine": "node scripts/run-ci.mjs --suite=quarantine",
"test:smoke": "playwright test tests/spd/login.spec.ts tests/spd/home.spec.ts --grep @smoke",
"test:smoke:allure": "ALLURE=1 npm run test:smoke && npm run allure:generate",
......
#!/usr/bin/env node
/**
* 在 Jenkins 上复制 SPD 任务为 spd-ui-verify,并改 Script Path、去掉定时与构建后邮件。
*
* 用法(凭据勿写入仓库,用环境变量):
* JENKINS_URL=http://10.17.65.19:8080 \
* JENKINS_USER=你的账号 \
* JENKINS_TOKEN=API_Token \
* node scripts/jenkins/create-spd-ui-verify-job.mjs
*
* 需要具备「创建任务 / 配置任务」权限(只读报告账号通常不够)。
*/
import process from 'node:process';
const JENKINS_URL = (process.env.JENKINS_URL || 'http://10.17.65.19:8080').replace(/\/$/, '');
const USER = process.env.JENKINS_USER || process.env.JENKINS_USERNAME || '';
const TOKEN = process.env.JENKINS_TOKEN || process.env.JENKINS_API_TOKEN || process.env.JENKINS_PASSWORD || '';
const SOURCE = process.env.JENKINS_COPY_FROM || 'SPD';
const TARGET = process.env.JENKINS_VERIFY_JOB || 'spd-ui-verify';
const SCRIPT_PATH = process.env.JENKINS_VERIFY_SCRIPT || 'Jenkinsfile.verify';
function authHeader() {
if (!USER || !TOKEN) {
console.error('[jenkins] 请设置 JENKINS_USER 与 JENKINS_TOKEN(或 JENKINS_API_TOKEN)');
process.exit(2);
}
const basic = Buffer.from(`${USER}:${TOKEN}`, 'utf8').toString('base64');
return `Basic ${basic}`;
}
async function jenkinsFetch(path, { method = 'GET', headers = {}, body } = {}) {
const url = `${JENKINS_URL}${path.startsWith('/') ? path : `/${path}`}`;
const res = await fetch(url, {
method,
headers: {
Authorization: authHeader(),
...headers,
},
body,
});
const text = await res.text();
return { res, text };
}
async function getCrumb() {
const { res, text } = await jenkinsFetch('/crumbIssuer/api/json');
if (!res.ok) {
return { field: null, value: null };
}
try {
const j = JSON.parse(text);
return { field: j.crumbRequestField, value: j.crumb };
} catch {
return { field: null, value: null };
}
}
async function jobExists(name) {
const { res } = await jenkinsFetch(`/job/${encodeURIComponent(name)}/api/json`);
return res.status === 200;
}
function patchConfigXml(xml) {
let out = xml;
out = out.replace(/<scriptPath>Jenkinsfile<\/scriptPath>/, `<scriptPath>${SCRIPT_PATH}</scriptPath>`);
out = out.replace(/<scriptPath>.*?<\/scriptPath>/, `<scriptPath>${SCRIPT_PATH}</scriptPath>`);
out = out.replace(/<triggers>[\s\S]*?<\/triggers>\s*/g, '');
out = out.replace(/<hudson\.tasks\.Mailer[\s\S]*?<\/hudson\.tasks\.Mailer>/g, '');
out = out.replace(
/<hudson\.plugins\.emailext[\s\S]*?<\/hudson\.plugins\.emailext>/g,
'',
);
if (!out.includes('<description>')) {
out = out.replace(
/<description\/>/,
'<description>日常验证(smoke/单模块),手工触发,不发邮件。Script: Jenkinsfile.verify</description>',
);
}
return out;
}
async function createFromCopy(crumb) {
const qs = new URLSearchParams({ name: TARGET, mode: 'copy', from: SOURCE });
const headers = { 'Content-Type': 'application/xml; charset=UTF-8' };
if (crumb.field && crumb.value) {
headers[crumb.field] = crumb.value;
}
const { res, text } = await jenkinsFetch(`/createItem?${qs}`, {
method: 'POST',
headers,
body: '',
});
if (res.status === 200 || res.status === 302) {
return true;
}
console.error(`[jenkins] 复制任务失败 HTTP ${res.status}: ${text.slice(0, 500)}`);
return false;
}
async function updateConfig(crumb) {
const { res, text } = await jenkinsFetch(`/job/${encodeURIComponent(TARGET)}/config.xml`);
if (!res.ok) {
console.error(`[jenkins] 读取 config.xml 失败 HTTP ${res.status}`);
process.exit(1);
}
const patched = patchConfigXml(text);
const headers = {
'Content-Type': 'text/xml; charset=UTF-8',
};
if (crumb.field && crumb.value) {
headers[crumb.field] = crumb.value;
}
const post = await jenkinsFetch(`/job/${encodeURIComponent(TARGET)}/config.xml`, {
method: 'POST',
headers,
body: patched,
});
if (!post.res.ok) {
console.error(`[jenkins] 更新 config.xml 失败 HTTP ${post.res.status}: ${post.text.slice(0, 500)}`);
process.exit(1);
}
}
async function main() {
console.log(`[jenkins] ${JENKINS_URL} source=${SOURCE} target=${TARGET} script=${SCRIPT_PATH}`);
const me = await jenkinsFetch('/me/api/json');
if (!me.res.ok) {
console.error(`[jenkins] 登录失败 HTTP ${me.res.status}(检查账号、API Token、权限)`);
process.exit(1);
}
const crumb = await getCrumb();
if (await jobExists(TARGET)) {
console.log(`[jenkins] 任务 ${TARGET} 已存在,仅更新 config.xml`);
} else {
const ok = await createFromCopy(crumb);
if (!ok) {
process.exit(1);
}
console.log(`[jenkins] 已从 ${SOURCE} 复制为 ${TARGET}`);
}
await updateConfig(crumb);
console.log(`[jenkins] 完成:${JENKINS_URL}/job/${TARGET}/`);
console.log('[jenkins] 请在 Jenkins 点 Build with Parameters(默认 SUITE=smoke)试跑。');
}
main().catch((err) => {
console.error('[jenkins]', err);
process.exit(1);
});
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment