bk-press 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +244 -0
- package/lib/command/index.js +19 -0
- package/lib/command/init.js +177 -0
- package/lib/command/login.js +109 -0
- package/lib/command/run.js +28 -0
- package/lib/config.js +76 -0
- package/lib/index.js +15 -0
- package/lib/util/file.js +151 -0
- package/lib/util/index.js +17 -0
- package/lib/util/pkg.js +28 -0
- package/package.json +41 -0
- package/src/command/index.ts +3 -0
- package/src/command/init.ts +202 -0
- package/src/command/login.ts +133 -0
- package/src/command/run.ts +27 -0
- package/src/config.ts +87 -0
- package/src/index.ts +19 -0
- package/src/util/file.ts +152 -0
- package/src/util/index.ts +1 -0
- package/tsconfig.json +15 -0
package/lib/util/file.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.readFile = readFile;
|
|
7
|
+
exports.deleteFile = deleteFile;
|
|
8
|
+
exports.writeFile = writeFile;
|
|
9
|
+
exports.writeEditableFile = writeEditableFile;
|
|
10
|
+
exports.createFolder = createFolder;
|
|
11
|
+
exports.deleteFolder = deleteFolder;
|
|
12
|
+
exports.isFileOrFolderExist = isFileOrFolderExist;
|
|
13
|
+
exports.isFolder = isFolder;
|
|
14
|
+
exports.isFile = isFile;
|
|
15
|
+
const fs_1 = __importDefault(require("fs"));
|
|
16
|
+
function readFile(path, isJSONFile = true) {
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
// 异步地读取JSON文件
|
|
19
|
+
fs_1.default.readFile(path, "utf8", (err, data) => {
|
|
20
|
+
if (err) {
|
|
21
|
+
console.error("An error occurred while reading the JSON file:", err);
|
|
22
|
+
resolve("");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (isJSONFile) {
|
|
26
|
+
try {
|
|
27
|
+
// 解析JSON数据到对象
|
|
28
|
+
const jsonObj = JSON.parse(data);
|
|
29
|
+
// 现在您可以使用这个对象了
|
|
30
|
+
resolve(jsonObj);
|
|
31
|
+
}
|
|
32
|
+
catch (parseError) {
|
|
33
|
+
console.error("Error parsing JSON:", parseError);
|
|
34
|
+
resolve("");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
resolve(data);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function deleteFile(path) {
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
fs_1.default.unlink(path, (err) => {
|
|
46
|
+
if (err) {
|
|
47
|
+
console.error("Failed to delete file: ", err);
|
|
48
|
+
resolve(false);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
resolve(true);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function writeFile(path, cotent) {
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
fs_1.default.writeFile(path, cotent, "utf8", (fileErr) => {
|
|
58
|
+
if (fileErr) {
|
|
59
|
+
console.error("Failed to write file: ", fileErr);
|
|
60
|
+
resolve(false);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
resolve(true);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function writeEditableFile(path, cotent) {
|
|
68
|
+
return new Promise((resolve) => {
|
|
69
|
+
fs_1.default.writeFile(path, cotent, { mode: 0o666 }, (fileErr) => {
|
|
70
|
+
if (fileErr) {
|
|
71
|
+
console.error("Failed to write file: ", fileErr);
|
|
72
|
+
resolve(false);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
resolve(true);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function createFolder(path) {
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
fs_1.default.mkdir(path, { recursive: true }, (folderErr) => {
|
|
82
|
+
if (folderErr) {
|
|
83
|
+
// vscode.window.showErrorMessage('Failed to create folder');
|
|
84
|
+
console.error("Failed to create folder: ", folderErr);
|
|
85
|
+
resolve(false);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
resolve(true);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function deleteFolder(path) {
|
|
93
|
+
return new Promise((resolve) => {
|
|
94
|
+
fs_1.default.rm(path, { recursive: true, force: true }, (err) => {
|
|
95
|
+
if (err) {
|
|
96
|
+
console.error(`Failed to delete folder ${path}: `, err);
|
|
97
|
+
resolve(false);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
resolve(true);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function isFileOrFolderExist(path) {
|
|
105
|
+
return new Promise((resolve) => {
|
|
106
|
+
fs_1.default.access(path, fs_1.default.constants.F_OK, (err) => {
|
|
107
|
+
if (err) {
|
|
108
|
+
console.error("file or folder not exist: ", err);
|
|
109
|
+
// 处理文件或文件夹不存在的逻辑
|
|
110
|
+
resolve(false);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
console.log(`${path} exists`);
|
|
114
|
+
// 处理文件或文件夹存在的逻辑
|
|
115
|
+
resolve(true);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function isFolder(path) {
|
|
121
|
+
return new Promise((resolve) => {
|
|
122
|
+
fs_1.default.stat(path, (err, stats) => {
|
|
123
|
+
if (err) {
|
|
124
|
+
console.error("isFolder err: ", err);
|
|
125
|
+
resolve(false);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (stats.isDirectory()) {
|
|
129
|
+
resolve(true);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
resolve(false);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function isFile(path) {
|
|
137
|
+
return new Promise((resolve) => {
|
|
138
|
+
fs_1.default.stat(path, (err, stats) => {
|
|
139
|
+
if (err) {
|
|
140
|
+
console.error("isFile err: ", err);
|
|
141
|
+
resolve(false);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (stats.isFile()) {
|
|
145
|
+
resolve(true);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
resolve(false);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./file"), exports);
|
package/lib/util/pkg.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.installDependencies = installDependencies;
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const which_pm_runs_1 = __importDefault(require("which-pm-runs"));
|
|
9
|
+
function installDependencies() {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
console.log("开始安装自动化测试相关依赖...");
|
|
12
|
+
// 确定使用 npm 还是 yarn(可根据项目检测)
|
|
13
|
+
const pkgTool = (0, which_pm_runs_1.default)()?.name || "npm";
|
|
14
|
+
const installPrefix = pkgTool === "npm" ? `${pkgTool} install -D` : `${pkgTool} add -D`;
|
|
15
|
+
(0, child_process_1.exec)(`${installPrefix} cypress-parallel cypress-multi-reporters start-server-and-test`, {
|
|
16
|
+
cwd: process.cwd(), // 关键:指定工作目录为项目路径
|
|
17
|
+
}, (error, stdout, stderr) => {
|
|
18
|
+
if (error) {
|
|
19
|
+
console.error("依赖安装失败:", error);
|
|
20
|
+
reject(error);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
console.log("依赖安装成功!");
|
|
24
|
+
console.log(stdout);
|
|
25
|
+
resolve(stdout);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bk-press",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A CLI for cypress automation testing",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"bkpress": "./lib/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "ts-node --transpile-only src/index.ts",
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"start": "node lib/index.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"bkpress",
|
|
16
|
+
"bk-press",
|
|
17
|
+
"自动化测试",
|
|
18
|
+
"端到端测试",
|
|
19
|
+
"组件测试",
|
|
20
|
+
"Cypress"
|
|
21
|
+
],
|
|
22
|
+
"author": "austinqli",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/fs-extra": "^11.0.4",
|
|
26
|
+
"@types/inquirer": "^9.0.9",
|
|
27
|
+
"@types/node": "^24.10.0",
|
|
28
|
+
"@types/which-pm-runs": "^1.0.2",
|
|
29
|
+
"ts-node": "^10.9.2",
|
|
30
|
+
"typescript": "^5.9.3"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"commander": "^14.0.2",
|
|
34
|
+
"cypress-multi-reporters": "^2.0.5",
|
|
35
|
+
"cypress-parallel": "^0.15.0",
|
|
36
|
+
"fs-extra": "^11.3.2",
|
|
37
|
+
"inquirer": "^12.11.0",
|
|
38
|
+
"puppeteer": "^24.29.1",
|
|
39
|
+
"which-pm-runs": "^1.1.0"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { exec } from "child_process";
|
|
2
|
+
import whichPMRuns from "which-pm-runs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs-extra";
|
|
5
|
+
import {
|
|
6
|
+
readFile,
|
|
7
|
+
writeFile,
|
|
8
|
+
createFolder,
|
|
9
|
+
deleteFolder,
|
|
10
|
+
isFileOrFolderExist,
|
|
11
|
+
} from "../util";
|
|
12
|
+
|
|
13
|
+
export async function init() {
|
|
14
|
+
console.log("开始初始化Cypress自动化测试...");
|
|
15
|
+
console.log("安装相关依赖...");
|
|
16
|
+
await installDependencies();
|
|
17
|
+
console.log("添加类型到 tsconfig.json 文件...");
|
|
18
|
+
await addCypressTypesToTsconfigJsonFile();
|
|
19
|
+
console.log("添加命令到 package.json...");
|
|
20
|
+
await addScriptsToPackageJson();
|
|
21
|
+
console.log("创建 cypress 文件夹并拉取模板用例文件...");
|
|
22
|
+
await addTemplateFiles();
|
|
23
|
+
console.log("创建 cypress.config.ts 配置文件...");
|
|
24
|
+
await addCypressConfigFile();
|
|
25
|
+
console.log("创建 cypress.env.json 配置文件...");
|
|
26
|
+
await addCypressEnvJsonFile();
|
|
27
|
+
console.log("添加忽略文件到 .gitignore 文件...");
|
|
28
|
+
await addIgnoreToGitignoreFile();
|
|
29
|
+
console.log("Cypress自动化测试初始化完成!");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 安装自动化测试相关依赖
|
|
33
|
+
function installDependencies() {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const pkgTool = whichPMRuns()?.name || "npm";
|
|
36
|
+
|
|
37
|
+
const installPrefix =
|
|
38
|
+
pkgTool === "npm" ? `${pkgTool} install -D` : `${pkgTool} add -D`;
|
|
39
|
+
|
|
40
|
+
exec(
|
|
41
|
+
`${installPrefix} cypress cypress-parallel cypress-multi-reporters start-server-and-test`,
|
|
42
|
+
{
|
|
43
|
+
cwd: process.cwd(),
|
|
44
|
+
},
|
|
45
|
+
(error, stdout, stderr) => {
|
|
46
|
+
if (error) {
|
|
47
|
+
console.error("依赖安装失败:", error);
|
|
48
|
+
reject(error);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
console.log("依赖安装成功!");
|
|
52
|
+
resolve(stdout);
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 添加脚本到 package.json
|
|
59
|
+
async function addScriptsToPackageJson() {
|
|
60
|
+
const packageJsonFile = path.join(process.cwd(), "package.json");
|
|
61
|
+
const packageJson = (await readFile(packageJsonFile)) as Record<string, any>;
|
|
62
|
+
packageJson.scripts["e2e-dev"] = "npx cypress open";
|
|
63
|
+
packageJson.scripts["e2e"] = "npx cypress run --browser chrome --headless";
|
|
64
|
+
packageJson.scripts["component-test"] =
|
|
65
|
+
"npx cypress run --component --browser chrome";
|
|
66
|
+
await writeFile(packageJsonFile, JSON.stringify(packageJson, null, 2));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 创建 cypress 文件夹并拉取模板文件
|
|
70
|
+
async function addTemplateFiles() {
|
|
71
|
+
const isTsProject = await checkIsTsProject();
|
|
72
|
+
console.log("当前项目是否是 TypeScript 项目: ", isTsProject);
|
|
73
|
+
await createCypressFolder();
|
|
74
|
+
await cloneTemplateFiles(isTsProject);
|
|
75
|
+
await copyTemplateFiles();
|
|
76
|
+
await deleteGitFolder();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 创建 cypress 文件夹
|
|
80
|
+
async function createCypressFolder() {
|
|
81
|
+
const cypressFolder = path.join(process.cwd(), "cypress");
|
|
82
|
+
await createFolder(cypressFolder);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function checkIsTsProject() {
|
|
86
|
+
const tsconfigJsonFile = path.join(process.cwd(), "tsconfig.json");
|
|
87
|
+
const isTsProject = await isFileOrFolderExist(tsconfigJsonFile);
|
|
88
|
+
return isTsProject;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 克隆模板文件
|
|
92
|
+
function cloneTemplateFiles(isTsProject = true) {
|
|
93
|
+
const targetDir = path.join(process.cwd(), "cypress");
|
|
94
|
+
const repoListMap = {
|
|
95
|
+
ts: "git@github.com:jinquantianxia/cypress-template-ts.git",
|
|
96
|
+
js: "git@github.com:jinquantianxia/cypress-template-js.git",
|
|
97
|
+
};
|
|
98
|
+
const repoUrl = repoListMap[isTsProject ? "ts" : "js"];
|
|
99
|
+
return new Promise((resolve, reject) => {
|
|
100
|
+
exec(
|
|
101
|
+
`git clone ${repoUrl}`,
|
|
102
|
+
{
|
|
103
|
+
cwd: targetDir,
|
|
104
|
+
},
|
|
105
|
+
(error, stdout, stderr) => {
|
|
106
|
+
if (error) {
|
|
107
|
+
console.error("克隆模板文件失败:", error);
|
|
108
|
+
reject(stderr);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
resolve(stdout);
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 删除 cypress 文件夹中的 .git 文件夹
|
|
118
|
+
async function deleteGitFolder() {
|
|
119
|
+
const targetDir = path.join(process.cwd(), "cypress", ".git");
|
|
120
|
+
await deleteFolder(targetDir);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 复制模板文件到 cypress 文件夹
|
|
124
|
+
async function copyTemplateFiles() {
|
|
125
|
+
const sourceDir = path.join(process.cwd(), "cypress", "cypress-template-ts");
|
|
126
|
+
const targetDir = path.join(process.cwd(), "cypress");
|
|
127
|
+
// 确保目标目录存在
|
|
128
|
+
await fs.ensureDir(targetDir);
|
|
129
|
+
// 复制源目录下所有内容到目标目录
|
|
130
|
+
await fs.copy(sourceDir, targetDir);
|
|
131
|
+
await deleteFolder(sourceDir);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 添加 cypress.config.ts 文件
|
|
135
|
+
async function addCypressConfigFile() {
|
|
136
|
+
const cypressConfigFile = path.join(process.cwd(), "cypress.config.ts");
|
|
137
|
+
const fileContent = `import { defineConfig } from 'cypress';
|
|
138
|
+
|
|
139
|
+
export default defineConfig({
|
|
140
|
+
e2e: {}
|
|
141
|
+
});`;
|
|
142
|
+
await writeFile(cypressConfigFile, fileContent);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 添加 cypress.env.json 文件
|
|
146
|
+
async function addCypressEnvJsonFile() {
|
|
147
|
+
const cypressEnvFile = path.join(process.cwd(), "cypress.env.json");
|
|
148
|
+
const isExist = await isFileOrFolderExist(cypressEnvFile);
|
|
149
|
+
if (isExist) {
|
|
150
|
+
console.log("cypress.env.json 文件已存在,跳过创建");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const fileContent = `{
|
|
154
|
+
"DEV_URL": "",
|
|
155
|
+
"LOCAL_URL": "",
|
|
156
|
+
"COOKIE_DOMAIN": ""
|
|
157
|
+
}
|
|
158
|
+
`;
|
|
159
|
+
await writeFile(cypressEnvFile, fileContent);
|
|
160
|
+
console.log("cypress.env.json 文件创建成功");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 添加忽略文件到 .gitignore 文件
|
|
164
|
+
async function addIgnoreToGitignoreFile() {
|
|
165
|
+
const gitignoreFile = path.join(process.cwd(), ".gitignore");
|
|
166
|
+
const oldFileContent = await readFile(gitignoreFile, false);
|
|
167
|
+
const fileContent = `${oldFileContent}\n
|
|
168
|
+
cypress/parallel-weights.json
|
|
169
|
+
cypress/screenshots
|
|
170
|
+
runner-results/
|
|
171
|
+
cypress.env.json
|
|
172
|
+
multi-reporter-config.json`;
|
|
173
|
+
await writeFile(gitignoreFile, fileContent);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 添加 cypress 类型到 tsconfig.json 文件
|
|
177
|
+
async function addCypressTypesToTsconfigJsonFile() {
|
|
178
|
+
const tsconfigJsonFile = path.join(process.cwd(), "tsconfig.json");
|
|
179
|
+
const tsconfigJson = (await readFile(tsconfigJsonFile)) as Record<
|
|
180
|
+
string,
|
|
181
|
+
any
|
|
182
|
+
>;
|
|
183
|
+
if (tsconfigJson.include) {
|
|
184
|
+
tsconfigJson.include.push("cypress/**/*");
|
|
185
|
+
} else {
|
|
186
|
+
tsconfigJson.include = ["cypress/**/*"];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (tsconfigJson.compilerOptions) {
|
|
190
|
+
if (tsconfigJson.compilerOptions.types) {
|
|
191
|
+
tsconfigJson.compilerOptions.types.push("cypress");
|
|
192
|
+
} else {
|
|
193
|
+
tsconfigJson.compilerOptions.types = ["cypress"];
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
tsconfigJson.compilerOptions = {
|
|
197
|
+
types: ["cypress"],
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const fileContent = JSON.stringify(tsconfigJson, null, 2);
|
|
201
|
+
await writeFile(tsconfigJsonFile, fileContent);
|
|
202
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import puppeteer, { type Cookie } from "puppeteer";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { readFile, writeFile, isFileOrFolderExist } from "../util";
|
|
4
|
+
|
|
5
|
+
export async function login() {
|
|
6
|
+
const config = await readCypressEnvConfig();
|
|
7
|
+
if (!config.devUrl) {
|
|
8
|
+
throw new Error("cypress.env.json 文件中 DEV_URL 配置不存在");
|
|
9
|
+
}
|
|
10
|
+
const browser = await puppeteer.launch({
|
|
11
|
+
headless: false,
|
|
12
|
+
args: ["--ignore-certificate-errors"],
|
|
13
|
+
});
|
|
14
|
+
console.log("浏览器启动成功!");
|
|
15
|
+
const page = await browser.newPage();
|
|
16
|
+
console.log(`${config.devUrl}开发环境页面打开成功!`);
|
|
17
|
+
await page.goto(config.devUrl);
|
|
18
|
+
console.log("登录后不要手动关闭浏览器,完成后回到终端按回车键继续...");
|
|
19
|
+
await waitForEnter(); // 等待用户在终端输入回车
|
|
20
|
+
|
|
21
|
+
// 4. 登录成功后,获取当前页面的所有 Cookie
|
|
22
|
+
const cookies = await page.cookies();
|
|
23
|
+
await writeCookiesToCypressEnvFile(cookies, config.domainMap);
|
|
24
|
+
await writeCookiesToCypressCommandFiles(cookies, config.domainMap);
|
|
25
|
+
|
|
26
|
+
// 5. 将 Cookie 写入本地文件(例如 cookies.json)
|
|
27
|
+
// fs.writeFileSync("cookies.json", JSON.stringify(cookies, null, 2));
|
|
28
|
+
console.log("登录成功,Cookie 已保存至 cypress 命令文件");
|
|
29
|
+
|
|
30
|
+
await browser.close(); // 关闭浏览器
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function readCypressEnvConfig() {
|
|
34
|
+
const cypressEnvFile = path.join(process.cwd(), "cypress.env.json");
|
|
35
|
+
const isExist = await isFileOrFolderExist(cypressEnvFile);
|
|
36
|
+
if (!isExist) {
|
|
37
|
+
throw new Error("cypress.env.json 文件不存在");
|
|
38
|
+
}
|
|
39
|
+
const envJson = (await readFile(cypressEnvFile)) as Record<string, string>;
|
|
40
|
+
const domainMap: Record<string, string> = {};
|
|
41
|
+
Object.entries(envJson).forEach(([key, value]) => {
|
|
42
|
+
if (value.includes(".com")) {
|
|
43
|
+
domainMap[value] = key;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
devUrl: envJson.DEV_URL || "",
|
|
48
|
+
localUrl: envJson.LOCAL_URL || "",
|
|
49
|
+
domainMap,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function writeCookiesToCypressEnvFile(
|
|
54
|
+
cookies: Cookie[],
|
|
55
|
+
domainMap: Record<string, string>
|
|
56
|
+
) {
|
|
57
|
+
const cypressEnvFile = path.join(process.cwd(), "cypress.env.json");
|
|
58
|
+
const isExist = await isFileOrFolderExist(cypressEnvFile);
|
|
59
|
+
if (!isExist) {
|
|
60
|
+
throw new Error("cypress.env.json 文件不存在");
|
|
61
|
+
}
|
|
62
|
+
const envJson = (await readFile(cypressEnvFile)) as Record<string, string>;
|
|
63
|
+
cookies.forEach((cookie) => {
|
|
64
|
+
envJson[cookie.name] = cookie.value;
|
|
65
|
+
});
|
|
66
|
+
Object.entries(domainMap).forEach(([key, value]) => {
|
|
67
|
+
envJson[value] = key;
|
|
68
|
+
});
|
|
69
|
+
await writeFile(cypressEnvFile, JSON.stringify(envJson, null, 2));
|
|
70
|
+
console.log("cypress.env.json 文件更新成功");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function writeCookiesToCypressCommandFiles(
|
|
74
|
+
cookies: Cookie[],
|
|
75
|
+
domainMap: Record<string, string>
|
|
76
|
+
) {
|
|
77
|
+
const cypressCommandFile = path.join(
|
|
78
|
+
process.cwd(),
|
|
79
|
+
"cypress",
|
|
80
|
+
"support",
|
|
81
|
+
"login.ts"
|
|
82
|
+
);
|
|
83
|
+
const isLoginFileExist = await isFileOrFolderExist(cypressCommandFile);
|
|
84
|
+
if (!isLoginFileExist) {
|
|
85
|
+
const fileContent = `
|
|
86
|
+
Cypress.Commands.add('login' as any, () => {
|
|
87
|
+
// 缓存登录会话
|
|
88
|
+
cy.session('login', () => {
|
|
89
|
+
${cookies
|
|
90
|
+
.map(
|
|
91
|
+
(cookie) =>
|
|
92
|
+
`cy.setCookie('${cookie.name}', Cypress.env('${
|
|
93
|
+
cookie.name
|
|
94
|
+
}'), { path: '${cookie.path}', domain: Cypress.env('${
|
|
95
|
+
domainMap[cookie.domain]
|
|
96
|
+
}') });`
|
|
97
|
+
)
|
|
98
|
+
.join("\n ")}
|
|
99
|
+
});
|
|
100
|
+
});`;
|
|
101
|
+
await writeFile(cypressCommandFile, fileContent);
|
|
102
|
+
console.log("cypress/support/login.ts 文件创建成功");
|
|
103
|
+
}
|
|
104
|
+
console.log("cypress/support/login.ts 文件已存在,跳过创建");
|
|
105
|
+
const cypressE2eFile = path.join(
|
|
106
|
+
process.cwd(),
|
|
107
|
+
"cypress",
|
|
108
|
+
"support",
|
|
109
|
+
"e2e.ts"
|
|
110
|
+
);
|
|
111
|
+
const e2eFileContent = (await readFile(cypressE2eFile, false)) as string;
|
|
112
|
+
if (e2eFileContent.includes("./login")) {
|
|
113
|
+
console.log("cypress/support/e2e.ts 文件已存在 ./login 导入,跳过更新");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const newE2eFileContent = e2eFileContent + "\nimport './login';";
|
|
117
|
+
await writeFile(cypressE2eFile, newE2eFileContent);
|
|
118
|
+
console.log("cypress/support/e2e.ts 文件导入 login.ts 更新成功");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 一个简单的等待用户输入回车键的函数
|
|
122
|
+
function waitForEnter() {
|
|
123
|
+
return new Promise((resolve) => {
|
|
124
|
+
const stdin = process.stdin;
|
|
125
|
+
stdin.setRawMode(true);
|
|
126
|
+
stdin.resume();
|
|
127
|
+
stdin.once("data", () => {
|
|
128
|
+
stdin.setRawMode(false);
|
|
129
|
+
stdin.pause();
|
|
130
|
+
resolve(true);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { exec } from "child_process";
|
|
2
|
+
import { readCypressEnvConfig } from "./login";
|
|
3
|
+
import os from "os";
|
|
4
|
+
|
|
5
|
+
export async function run() {
|
|
6
|
+
console.log("开始运行自动化测试...");
|
|
7
|
+
const config = await readCypressEnvConfig();
|
|
8
|
+
const cpuCount = os.cpus().length;
|
|
9
|
+
console.log("当前CPU核心数: ", cpuCount, ", 设置测试并发数: ", cpuCount);
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
exec(
|
|
12
|
+
`start-server-and-test dev ${config.localUrl} 'cypress-parallel -s e2e -t ${cpuCount} -d cypress/e2e'`,
|
|
13
|
+
{
|
|
14
|
+
cwd: process.cwd(),
|
|
15
|
+
},
|
|
16
|
+
(error, stdout, stderr) => {
|
|
17
|
+
if (error) {
|
|
18
|
+
console.error("运行自动化测试失败:", error);
|
|
19
|
+
reject(stderr);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
console.log("自动化测试运行成功!");
|
|
23
|
+
resolve(stdout);
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
import { readFileSync, existsSync } from "fs";
|
|
3
|
+
import { join, resolve } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
|
|
6
|
+
// 定义配置对象的类型接口,确保类型安全
|
|
7
|
+
export interface CliConfig {
|
|
8
|
+
project_url?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// 配置读取器函数
|
|
12
|
+
export function loadConfig(): CliConfig | null {
|
|
13
|
+
// 定义可能的配置文件名
|
|
14
|
+
const fileName = "bkpress.json";
|
|
15
|
+
|
|
16
|
+
let configPath: string | null = null;
|
|
17
|
+
let configContent: string | null = null;
|
|
18
|
+
|
|
19
|
+
// 1. 在项目目录中查找配置文件
|
|
20
|
+
const potentialPath = join(process.cwd(), fileName);
|
|
21
|
+
if (existsSync(potentialPath)) {
|
|
22
|
+
configPath = potentialPath;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 也支持检查用户主目录下的全局配置(可选)
|
|
26
|
+
// const globalConfigPath = join(homedir(), '.my-cli.config.json');
|
|
27
|
+
// if (!configPath && existsSync(globalConfigPath)) {
|
|
28
|
+
// configPath = globalConfigPath;
|
|
29
|
+
// }
|
|
30
|
+
|
|
31
|
+
if (!configPath) {
|
|
32
|
+
console.log("未找到脚手架配置文件,将使用默认配置或命令行参数。");
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
// 2. 读取文件内容
|
|
38
|
+
configContent = readFileSync(configPath, "utf-8");
|
|
39
|
+
|
|
40
|
+
// 3. 根据文件扩展名解析内容
|
|
41
|
+
if (configPath.endsWith(".json") || configPath.endsWith(".my-clirc")) {
|
|
42
|
+
// 解析 JSON,可去除注释(需自行处理或使用支持注释的JSON解析器)
|
|
43
|
+
return JSON.parse(configContent) as CliConfig;
|
|
44
|
+
} else if (configPath.endsWith(".ts")) {
|
|
45
|
+
// 对于 TS 文件,可能需要动态导入(需额外处理,复杂度较高,通常更推荐 JSON 或静态配置)
|
|
46
|
+
console.warn(
|
|
47
|
+
"TypeScript 配置文件支持可能需要额外编译步骤,建议使用 JSON 格式。"
|
|
48
|
+
);
|
|
49
|
+
// 示例性实现,实际中需谨慎处理
|
|
50
|
+
// 可能需要先使用 ts-node 或将其编译成 JS 再 require
|
|
51
|
+
// const tsConfig = require(configPath);
|
|
52
|
+
// return tsConfig;
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error(`读取或解析配置文件失败 (${configPath}):`, error);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 获取默认配置
|
|
62
|
+
export function getDefaultConfig() {
|
|
63
|
+
return {
|
|
64
|
+
local_url: "http://localhost:8080",
|
|
65
|
+
api_base: "/api",
|
|
66
|
+
build_output: "dist",
|
|
67
|
+
features: {
|
|
68
|
+
typescript: false,
|
|
69
|
+
lint: false,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 合并配置(用户配置覆盖默认配置)
|
|
75
|
+
export function mergeConfigs(defaultCfg: CliConfig, userCfg: CliConfig | null) {
|
|
76
|
+
if (!userCfg) return defaultCfg;
|
|
77
|
+
|
|
78
|
+
// 简单的深度合并,对于复杂结构可使用 lodash.merge 等库
|
|
79
|
+
return {
|
|
80
|
+
...defaultCfg,
|
|
81
|
+
...userCfg,
|
|
82
|
+
features: {
|
|
83
|
+
// ...defaultCfg.features,
|
|
84
|
+
// ...userCfg.features,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|