csspilot 0.3.1 → 0.4.0
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/LICENSE +21 -0
- package/dist/bin.js +9933 -9749
- package/dist/index.js +332 -149
- package/dist/skills/testpilot/SKILL.md +10 -7
- package/dist/skills/testpilot/manifest.yaml +3 -2
- package/dist/skills/testpilot/workflows/create-case.md +10 -7
- package/dist/skills/testpilot/workflows/run-case.md +1 -0
- package/dist/skills/testpilot/workflows/test-requirements-review.md +61 -0
- package/package.json +11 -11
package/dist/index.js
CHANGED
|
@@ -10449,8 +10449,8 @@ function makeCiCommand() {
|
|
|
10449
10449
|
}
|
|
10450
10450
|
|
|
10451
10451
|
// src/commands/init/doctor.ts
|
|
10452
|
-
import { existsSync as
|
|
10453
|
-
import { join as
|
|
10452
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
10453
|
+
import { join as join22 } from "node:path";
|
|
10454
10454
|
|
|
10455
10455
|
// ../../packages/core/src/errors/index.ts
|
|
10456
10456
|
var TestPilotError = class extends Error {
|
|
@@ -30183,7 +30183,17 @@ var configSchema = external_exports.strictObject({
|
|
|
30183
30183
|
cliPath: external_exports.string().min(1).optional()
|
|
30184
30184
|
}).optional()
|
|
30185
30185
|
});
|
|
30186
|
+
function interpolateEnvRefs(value) {
|
|
30187
|
+
return value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (match, name) => process.env[name] ?? match);
|
|
30188
|
+
}
|
|
30189
|
+
function loadProjectEnvFile(root = process.cwd()) {
|
|
30190
|
+
try {
|
|
30191
|
+
process.loadEnvFile(join3(root, ".env"));
|
|
30192
|
+
} catch {
|
|
30193
|
+
}
|
|
30194
|
+
}
|
|
30186
30195
|
async function loadTestpilotConfig(cwd = process.cwd()) {
|
|
30196
|
+
loadProjectEnvFile(cwd);
|
|
30187
30197
|
const file2 = join3(cwd, "testpilot.yaml");
|
|
30188
30198
|
let raw;
|
|
30189
30199
|
try {
|
|
@@ -30199,19 +30209,44 @@ async function loadTestpilotConfig(cwd = process.cwd()) {
|
|
|
30199
30209
|
const detail = result.error.issues.map((issue2) => `${issue2.path.map(String).join(".")}: ${issue2.message}`).join("; ");
|
|
30200
30210
|
throw new Error(`testpilot.yaml \u914D\u7F6E\u4E0D\u5408\u6CD5:${detail}`);
|
|
30201
30211
|
}
|
|
30212
|
+
const parsed = result.data;
|
|
30213
|
+
const environment = parsed.environment ? Object.fromEntries(
|
|
30214
|
+
Object.entries(parsed.environment).map(([name, entry]) => {
|
|
30215
|
+
if (name === "default" || typeof entry === "string") {
|
|
30216
|
+
return [name, entry];
|
|
30217
|
+
}
|
|
30218
|
+
if (entry === void 0) {
|
|
30219
|
+
return [name, void 0];
|
|
30220
|
+
}
|
|
30221
|
+
return [
|
|
30222
|
+
name,
|
|
30223
|
+
{ baseUrl: entry.baseUrl === void 0 ? void 0 : interpolateEnvRefs(entry.baseUrl) }
|
|
30224
|
+
];
|
|
30225
|
+
})
|
|
30226
|
+
) : void 0;
|
|
30202
30227
|
return {
|
|
30203
|
-
version:
|
|
30228
|
+
version: parsed.version,
|
|
30204
30229
|
// 用例目录:旧字段 casesDir 优先,其次 test.caseDirectory,最后默认值
|
|
30205
|
-
casesDir:
|
|
30206
|
-
testDirectory:
|
|
30207
|
-
projectName:
|
|
30208
|
-
environment
|
|
30209
|
-
adapters:
|
|
30210
|
-
workspace:
|
|
30211
|
-
server:
|
|
30212
|
-
|
|
30213
|
-
|
|
30214
|
-
|
|
30230
|
+
casesDir: parsed.casesDir ?? parsed.test?.caseDirectory ?? DEFAULT_CASES_DIR,
|
|
30231
|
+
testDirectory: parsed.test?.directory,
|
|
30232
|
+
projectName: parsed.project?.name,
|
|
30233
|
+
environment,
|
|
30234
|
+
adapters: parsed.adapters,
|
|
30235
|
+
workspace: parsed.workspace,
|
|
30236
|
+
server: parsed.server ? {
|
|
30237
|
+
baseUrl: parsed.server.baseUrl === void 0 ? void 0 : interpolateEnvRefs(parsed.server.baseUrl)
|
|
30238
|
+
} : void 0,
|
|
30239
|
+
web: parsed.web ? {
|
|
30240
|
+
baseUrl: parsed.web.baseUrl === void 0 ? void 0 : interpolateEnvRefs(parsed.web.baseUrl)
|
|
30241
|
+
} : void 0,
|
|
30242
|
+
api: parsed.api ? {
|
|
30243
|
+
baseUrl: parsed.api.baseUrl === void 0 ? void 0 : interpolateEnvRefs(parsed.api.baseUrl),
|
|
30244
|
+
timeoutMs: parsed.api.timeoutMs
|
|
30245
|
+
} : void 0,
|
|
30246
|
+
miniapp: parsed.miniapp ? {
|
|
30247
|
+
projectPath: parsed.miniapp.projectPath === void 0 ? void 0 : interpolateEnvRefs(parsed.miniapp.projectPath),
|
|
30248
|
+
cliPath: parsed.miniapp.cliPath === void 0 ? void 0 : interpolateEnvRefs(parsed.miniapp.cliPath)
|
|
30249
|
+
} : void 0
|
|
30215
30250
|
};
|
|
30216
30251
|
}
|
|
30217
30252
|
|
|
@@ -32134,8 +32169,12 @@ async function collectEnvVarRefs(root) {
|
|
|
32134
32169
|
return [];
|
|
32135
32170
|
}
|
|
32136
32171
|
const names = /* @__PURE__ */ new Set();
|
|
32137
|
-
for (const
|
|
32138
|
-
|
|
32172
|
+
for (const line of text.split(/\r?\n/)) {
|
|
32173
|
+
const trimmed = line.trim();
|
|
32174
|
+
if (trimmed.startsWith("#")) continue;
|
|
32175
|
+
for (const match of trimmed.matchAll(ENV_REF_PATTERN)) {
|
|
32176
|
+
names.add(match[1]);
|
|
32177
|
+
}
|
|
32139
32178
|
}
|
|
32140
32179
|
return [...names].sort();
|
|
32141
32180
|
}
|
|
@@ -32227,6 +32266,144 @@ function adaptersFor(type) {
|
|
|
32227
32266
|
return ["playwright"];
|
|
32228
32267
|
}
|
|
32229
32268
|
|
|
32269
|
+
// src/commands/init/skill-installer.ts
|
|
32270
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "node:fs";
|
|
32271
|
+
import { access as access2, cp, mkdir as mkdir11, writeFile as writeFile9 } from "node:fs/promises";
|
|
32272
|
+
import { dirname as dirname7, join as join21 } from "node:path";
|
|
32273
|
+
import { fileURLToPath } from "node:url";
|
|
32274
|
+
var CORE_FILES = ["SKILL.md", "manifest.yaml"];
|
|
32275
|
+
var CORE_DIRS = ["rules", "workflows"];
|
|
32276
|
+
async function exists3(path) {
|
|
32277
|
+
try {
|
|
32278
|
+
await access2(path);
|
|
32279
|
+
return true;
|
|
32280
|
+
} catch {
|
|
32281
|
+
return false;
|
|
32282
|
+
}
|
|
32283
|
+
}
|
|
32284
|
+
function readManifestVersion(dir) {
|
|
32285
|
+
const manifestPath = join21(dir, "manifest.yaml");
|
|
32286
|
+
if (!existsSync6(manifestPath)) return void 0;
|
|
32287
|
+
return /^version:\s*([^\s#]+)/m.exec(readFileSync3(manifestPath, "utf8"))?.[1];
|
|
32288
|
+
}
|
|
32289
|
+
function resolveSkillSourceDir() {
|
|
32290
|
+
const fromEnv = process.env.TESTPILOT_SKILL_DIR;
|
|
32291
|
+
if (fromEnv) return fromEnv;
|
|
32292
|
+
const bundled = fileURLToPath(new URL("./skills/testpilot", import.meta.url));
|
|
32293
|
+
if (existsSync6(bundled)) return bundled;
|
|
32294
|
+
const inRepo = fileURLToPath(new URL("../../../../../skills/testpilot", import.meta.url));
|
|
32295
|
+
return existsSync6(inRepo) ? inRepo : void 0;
|
|
32296
|
+
}
|
|
32297
|
+
async function copyCoreFiles(src, dest) {
|
|
32298
|
+
for (const name of CORE_FILES) {
|
|
32299
|
+
await cp(join21(src, name), join21(dest, name), { recursive: true, force: true });
|
|
32300
|
+
}
|
|
32301
|
+
for (const dir of CORE_DIRS) {
|
|
32302
|
+
if (existsSync6(join21(src, dir))) {
|
|
32303
|
+
await cp(join21(src, dir), join21(dest, dir), { recursive: true, force: true });
|
|
32304
|
+
}
|
|
32305
|
+
}
|
|
32306
|
+
}
|
|
32307
|
+
async function syncSkillFiles(root, options = {}) {
|
|
32308
|
+
const dest = join21(root, ".agents", "skills", "testpilot");
|
|
32309
|
+
const bundledVersion = readManifestVersion(resolveSkillSourceDir() ?? "");
|
|
32310
|
+
if (!await exists3(join21(dest, "manifest.yaml"))) {
|
|
32311
|
+
const src = resolveSkillSourceDir();
|
|
32312
|
+
if (!src) {
|
|
32313
|
+
throw new Error(
|
|
32314
|
+
"\u65E0\u6CD5\u5B9A\u4F4D TestPilot Skill \u6E90\u76EE\u5F55;\u53EF\u901A\u8FC7\u73AF\u5883\u53D8\u91CF TESTPILOT_SKILL_DIR \u6307\u5B9A skills/testpilot \u8DEF\u5F84"
|
|
32315
|
+
);
|
|
32316
|
+
}
|
|
32317
|
+
await mkdir11(dirname7(dest), { recursive: true });
|
|
32318
|
+
await cp(src, dest, { recursive: true });
|
|
32319
|
+
return { core: "created", bundledVersion, dest };
|
|
32320
|
+
}
|
|
32321
|
+
const installedVersion = readManifestVersion(dest);
|
|
32322
|
+
const bundled = resolveSkillSourceDir();
|
|
32323
|
+
if (!bundled) {
|
|
32324
|
+
return { core: "current", installedVersion, dest };
|
|
32325
|
+
}
|
|
32326
|
+
if (!options.force && installedVersion === bundledVersion) {
|
|
32327
|
+
return { core: "current", installedVersion, bundledVersion, dest };
|
|
32328
|
+
}
|
|
32329
|
+
await copyCoreFiles(bundled, dest);
|
|
32330
|
+
return {
|
|
32331
|
+
core: "updated",
|
|
32332
|
+
installedVersion,
|
|
32333
|
+
bundledVersion,
|
|
32334
|
+
dest
|
|
32335
|
+
};
|
|
32336
|
+
}
|
|
32337
|
+
async function installSkill(root, info, adapters) {
|
|
32338
|
+
const dest = join21(root, ".agents", "skills", "testpilot");
|
|
32339
|
+
const sync = await syncSkillFiles(root);
|
|
32340
|
+
const core = sync.core === "created" ? "created" : sync.core === "updated" ? "updated" : "reused";
|
|
32341
|
+
const coreVersion = sync.core === "updated" || sync.core === "created" ? { from: sync.installedVersion, to: sync.bundledVersion } : void 0;
|
|
32342
|
+
const config2 = await loadTestpilotConfig(root).catch(() => void 0);
|
|
32343
|
+
const defaultEnvironment = typeof config2?.environment?.default === "string" ? config2.environment.default : "\u672A\u8BBE\u7F6E";
|
|
32344
|
+
const defaultWorkspace = config2?.workspace?.default ?? "\u672A\u8BBE\u7F6E(\u53EF\u5728 yaml workspace.default \u914D\u7F6E)";
|
|
32345
|
+
const refsDir = join21(dest, "references");
|
|
32346
|
+
const files = [
|
|
32347
|
+
["project.md", () => renderProjectMd(info, adapters, defaultEnvironment, defaultWorkspace)],
|
|
32348
|
+
["test-conventions.md", renderTestConventionsMd],
|
|
32349
|
+
["adapters.md", () => renderAdaptersMd(adapters)]
|
|
32350
|
+
];
|
|
32351
|
+
let references = "reused";
|
|
32352
|
+
for (const [name, render] of files) {
|
|
32353
|
+
const file2 = join21(refsDir, name);
|
|
32354
|
+
if (await exists3(file2)) continue;
|
|
32355
|
+
await mkdir11(refsDir, { recursive: true });
|
|
32356
|
+
await writeFile9(file2, render(), "utf8");
|
|
32357
|
+
references = "created";
|
|
32358
|
+
}
|
|
32359
|
+
return coreVersion ? { core, coreVersion, references, dest } : { core, references, dest };
|
|
32360
|
+
}
|
|
32361
|
+
function renderProjectMd(info, adapters, defaultEnvironment, defaultWorkspace) {
|
|
32362
|
+
const available = adapters.filter((item) => item.available).map((item) => item.id);
|
|
32363
|
+
return `# Project
|
|
32364
|
+
|
|
32365
|
+
\u81EA\u52A8\u751F\u6210\u4E8E \`csspilot init\`,\u63CF\u8FF0\u672C\u9879\u76EE\u7684 TestPilot \u63A5\u5165\u4FE1\u606F;Agent \u4F18\u5148\u4ECE\u8FD9\u91CC\u4E86\u89E3\u9879\u76EE\u3002
|
|
32366
|
+
|
|
32367
|
+
- Project name: ${info.name}
|
|
32368
|
+
- Project type: ${info.type}
|
|
32369
|
+
- Language: ${info.language}
|
|
32370
|
+
- Package manager: ${info.packageManager}
|
|
32371
|
+
- Test directory: tests/e2e
|
|
32372
|
+
- Case directory: tests/e2e/cases
|
|
32373
|
+
- Available adapters: ${available.length > 0 ? available.join(", ") : "(none detected)"}
|
|
32374
|
+
- Default environment: ${defaultEnvironment}
|
|
32375
|
+
- Default workspace: ${defaultWorkspace}
|
|
32376
|
+
`;
|
|
32377
|
+
}
|
|
32378
|
+
function renderTestConventionsMd() {
|
|
32379
|
+
return `# Test Conventions
|
|
32380
|
+
|
|
32381
|
+
\u672C\u9879\u76EE E2E \u6D4B\u8BD5\u7EA6\u5B9A,Agent \u751F\u6210/\u4FEE\u6539 Case \u65F6\u5FC5\u987B\u9075\u5B88:
|
|
32382
|
+
|
|
32383
|
+
1. E2E Case \u4F4D\u4E8E tests/e2e/cases
|
|
32384
|
+
2. Case \u4F7F\u7528 TestPilot DSL
|
|
32385
|
+
3. \u4E0D\u5141\u8BB8\u76F4\u63A5\u8C03\u7528 Playwright
|
|
32386
|
+
4. \u4E0D\u5141\u8BB8\u76F4\u63A5\u8C03\u7528 WeChatIDE
|
|
32387
|
+
5. \u4E0D\u5141\u8BB8\u5728 Case \u4E2D\u5199\u660E\u6587\u5BC6\u7801(\u7528 accountRef \u5F15\u7528)
|
|
32388
|
+
6. \u4F18\u5148\u590D\u7528\u5DF2\u6709 Case
|
|
32389
|
+
7. \u65B0\u589E Case \u5FC5\u987B\u901A\u8FC7 csspilot validate
|
|
32390
|
+
8. Case ID \u5FC5\u987B\u552F\u4E00
|
|
32391
|
+
`;
|
|
32392
|
+
}
|
|
32393
|
+
function renderAdaptersMd(adapters) {
|
|
32394
|
+
const lines = adapters.map(
|
|
32395
|
+
(item) => `- ${item.id}: ${item.available ? "\u53EF\u7528" : "\u4E0D\u53EF\u7528"}${item.required ? "(\u9879\u76EE\u9700\u8981)" : "(\u53EF\u9009)"} \u2014 ${item.detail}`
|
|
32396
|
+
);
|
|
32397
|
+
return `# Adapters
|
|
32398
|
+
|
|
32399
|
+
\u6267\u884C\u7AEF\u68C0\u6D4B\u7ED3\u679C(\u4EE5 \`csspilot doctor\` \u6700\u65B0\u8F93\u51FA\u4E3A\u51C6):
|
|
32400
|
+
|
|
32401
|
+
${lines.join("\n")}
|
|
32402
|
+
|
|
32403
|
+
Agent \u6C38\u8FDC\u4E0D\u76F4\u63A5\u64CD\u4F5C Adapter;Case \u7ECF Execution Engine \u8C03\u5EA6\u5230\u5BF9\u5E94 Adapter \u6267\u884C\u3002
|
|
32404
|
+
`;
|
|
32405
|
+
}
|
|
32406
|
+
|
|
32230
32407
|
// src/commands/init/doctor.ts
|
|
32231
32408
|
async function runDoctor(root) {
|
|
32232
32409
|
const sections = [];
|
|
@@ -32251,25 +32428,37 @@ async function runDoctor(root) {
|
|
|
32251
32428
|
const projectItems = [
|
|
32252
32429
|
ok(`Project ${info.name}(${info.type})`),
|
|
32253
32430
|
info.git.commit ? ok(`Git repository(${info.git.branch ?? "detached"} \xB7 ${info.git.commit.slice(0, 7)})`) : warn("Git repository \u672A\u68C0\u6D4B\u5230", "Case Metadata \u65E0\u6CD5\u540C\u6B65\u5230 Server"),
|
|
32254
|
-
|
|
32255
|
-
|
|
32431
|
+
existsSync7(join22(root, ".testpilot", "project.json")) ? ok(".testpilot/project.json") : error62(".testpilot/project.json", "\u8FD0\u884C npx csspilot init \u751F\u6210"),
|
|
32432
|
+
existsSync7(join22(root, "testpilot.yaml")) ? ok("testpilot.yaml") : error62("testpilot.yaml", "\u8FD0\u884C npx csspilot init \u751F\u6210")
|
|
32256
32433
|
];
|
|
32257
32434
|
push("Project", projectItems);
|
|
32258
32435
|
const config2 = await loadTestpilotConfig(root).catch(() => void 0);
|
|
32259
32436
|
const casesDir = config2?.casesDir ?? "tests/e2e/cases";
|
|
32260
|
-
const cases = await collectCases([
|
|
32437
|
+
const cases = await collectCases([join22(root, casesDir)], { root }).catch(() => []);
|
|
32261
32438
|
const invalidCount = cases.filter((item) => !item.valid).length;
|
|
32262
|
-
const testDir =
|
|
32439
|
+
const testDir = join22(root, "tests", "e2e");
|
|
32263
32440
|
push("Test", [
|
|
32264
|
-
|
|
32265
|
-
|
|
32441
|
+
existsSync7(testDir) ? ok("tests/e2e") : error62("tests/e2e", "\u8FD0\u884C npx csspilot init \u521B\u5EFA"),
|
|
32442
|
+
existsSync7(join22(root, casesDir)) ? ok(`cases directory(${casesDir})`) : error62(`cases directory(${casesDir})`, "\u8FD0\u884C npx csspilot init \u521B\u5EFA"),
|
|
32266
32443
|
cases.length === 0 ? warn("Case validation \u8DF3\u8FC7(\u672A\u627E\u5230\u7528\u4F8B)", "\u5728 tests/e2e/cases \u521B\u5EFA Case") : invalidCount === 0 ? ok(`Case validation(${cases.length} \u4E2A\u7528\u4F8B\u5168\u90E8\u6709\u6548)`) : error62(`Case validation(${invalidCount}/${cases.length} \u4E2A\u7528\u4F8B\u6821\u9A8C\u5931\u8D25)`, "\u8FD0\u884C npx csspilot validate \u67E5\u770B\u8BE6\u60C5")
|
|
32267
32444
|
]);
|
|
32268
|
-
const skillDir =
|
|
32269
|
-
|
|
32270
|
-
|
|
32271
|
-
|
|
32272
|
-
|
|
32445
|
+
const skillDir = join22(root, ".agents", "skills", "testpilot");
|
|
32446
|
+
const installedSkillVersion = readManifestVersion(skillDir);
|
|
32447
|
+
const skillSource = resolveSkillSourceDir();
|
|
32448
|
+
const bundledSkillVersion = skillSource ? readManifestVersion(skillSource) : void 0;
|
|
32449
|
+
const skillItems = [
|
|
32450
|
+
installedSkillVersion ? ok(`TestPilot Skill(.agents/skills/testpilot,v${installedSkillVersion})`) : error62("TestPilot Skill", "\u8FD0\u884C npx csspilot init \u5B89\u88C5"),
|
|
32451
|
+
existsSync7(join22(skillDir, "references", "project.md")) ? ok("Project testing guide(references/)") : warn("Project testing guide \u7F3A\u5931", "references/ \u7531 init \u751F\u6210;\u53EF\u91CD\u65B0\u8FD0\u884C npx csspilot init")
|
|
32452
|
+
];
|
|
32453
|
+
if (installedSkillVersion && bundledSkillVersion && installedSkillVersion !== bundledSkillVersion) {
|
|
32454
|
+
skillItems.push(
|
|
32455
|
+
warn(
|
|
32456
|
+
`Skill \u53EF\u66F4\u65B0(v${installedSkillVersion} -> v${bundledSkillVersion})`,
|
|
32457
|
+
"\u8FD0\u884C npx csspilot update \u5E76\u63D0\u4EA4 .agents/skills/ \u53D8\u66F4"
|
|
32458
|
+
)
|
|
32459
|
+
);
|
|
32460
|
+
}
|
|
32461
|
+
push("Agent", skillItems);
|
|
32273
32462
|
const adapters = await detectAdapters(root, info.type);
|
|
32274
32463
|
push(
|
|
32275
32464
|
"Adapters",
|
|
@@ -32354,11 +32543,11 @@ function makeDoctorCommand() {
|
|
|
32354
32543
|
import { join as join25 } from "node:path";
|
|
32355
32544
|
|
|
32356
32545
|
// src/commands/init/config-initializer.ts
|
|
32357
|
-
import { access as
|
|
32358
|
-
import { basename as basename2, join as
|
|
32359
|
-
async function
|
|
32546
|
+
import { access as access3, mkdir as mkdir12, readFile as readFile8, writeFile as writeFile10 } from "node:fs/promises";
|
|
32547
|
+
import { basename as basename2, join as join23 } from "node:path";
|
|
32548
|
+
async function exists4(path) {
|
|
32360
32549
|
try {
|
|
32361
|
-
await
|
|
32550
|
+
await access3(path);
|
|
32362
32551
|
return true;
|
|
32363
32552
|
} catch {
|
|
32364
32553
|
return false;
|
|
@@ -32399,28 +32588,72 @@ ${adapters}
|
|
|
32399
32588
|
# server:
|
|
32400
32589
|
# baseUrl: http://127.0.0.1:3000
|
|
32401
32590
|
|
|
32402
|
-
# Web \u7AEF:navigate \u76F8\u5BF9 url \u7684\u57FA\u51C6\u5730\u5740
|
|
32591
|
+
# Web \u7AEF:navigate \u76F8\u5BF9 url \u7684\u57FA\u51C6\u5730\u5740(\u6BCF\u4EBA\u7684\u5730\u5740\u4E0D\u540C,\u503C\u653E .env)
|
|
32403
32592
|
# web:
|
|
32404
|
-
# baseUrl:
|
|
32593
|
+
# baseUrl: \${WEB_BASE_URL}
|
|
32405
32594
|
|
|
32406
|
-
# \u5C0F\u7A0B\u5E8F\u7AEF:\u586B\u5165\u5C0F\u7A0B\u5E8F\u9879\u76EE\u76EE\u5F55\u540E\u5373\u53EF\u6267\u884C target: miniapp \u7684\u6B65\u9AA4
|
|
32595
|
+
# \u5C0F\u7A0B\u5E8F\u7AEF:\u586B\u5165\u5C0F\u7A0B\u5E8F\u9879\u76EE\u76EE\u5F55\u540E\u5373\u53EF\u6267\u884C target: miniapp \u7684\u6B65\u9AA4(\u8DEF\u5F84\u6BCF\u4EBA\u4E0D\u540C,\u8D70 .env)
|
|
32407
32596
|
# miniapp:
|
|
32408
|
-
# projectPath:
|
|
32409
|
-
# cliPath:
|
|
32597
|
+
# projectPath: \${MINIAPP_PROJECT_PATH}
|
|
32598
|
+
# cliPath: \${MINIAPP_CLI_PATH}
|
|
32410
32599
|
`;
|
|
32411
32600
|
}
|
|
32601
|
+
function renderDotEnvExample() {
|
|
32602
|
+
return `# TestPilot \u73AF\u5883\u53D8\u91CF\u793A\u4F8B:\u590D\u5236\u4E3A .env \u5E76\u586B\u5165\u771F\u5B9E\u503C(.env \u542B\u654F\u611F\u4FE1\u606F,\u4E0D\u8981\u63D0\u4EA4\u5230 Git)
|
|
32603
|
+
# \u4F18\u5148\u7EA7:\u771F\u5B9E\u73AF\u5883\u53D8\u91CF > .env \u6587\u4EF6;testpilot.yaml \u4E2D\u7684 \${VAR} \u5F15\u7528\u8FD0\u884C\u65F6\u81EA\u52A8\u6CE8\u5165
|
|
32604
|
+
|
|
32605
|
+
# \u73AF\u5883\u914D\u7F6E(environment \u6BB5)
|
|
32606
|
+
TEST_BASE_URL=http://127.0.0.1:8080
|
|
32607
|
+
STAGING_BASE_URL=https://staging.example.com
|
|
32608
|
+
|
|
32609
|
+
# Web \u7AEF\u76F8\u5BF9 url \u7684\u57FA\u51C6(\u53D6\u6D88 testpilot.yaml \u4E2D web \u6BB5\u6CE8\u91CA\u540E\u751F\u6548)
|
|
32610
|
+
# WEB_BASE_URL=http://127.0.0.1:8080
|
|
32611
|
+
|
|
32612
|
+
# \u5C0F\u7A0B\u5E8F\u7AEF(\u53D6\u6D88 testpilot.yaml \u4E2D miniapp \u6BB5\u6CE8\u91CA\u540E\u751F\u6548)
|
|
32613
|
+
# MINIAPP_PROJECT_PATH=path/to/miniprogram
|
|
32614
|
+
# MINIAPP_CLI_PATH=C:/Program Files (x86)/Tencent/\u5FAE\u4FE1web\u5F00\u53D1\u8005\u5DE5\u5177/cli.bat
|
|
32615
|
+
|
|
32616
|
+
# TestPilot Server(Control Plane,\u53EF\u9009)
|
|
32617
|
+
# TESTPILOT_SERVER_URL=http://127.0.0.1:3000
|
|
32618
|
+
`;
|
|
32619
|
+
}
|
|
32620
|
+
async function ensureGitignoreEntry(root, entry, comment = "TestPilot \u672C\u5730\u73AF\u5883\u53D8\u91CF") {
|
|
32621
|
+
const gitignorePath = join23(root, ".gitignore");
|
|
32622
|
+
if (!await exists4(gitignorePath)) {
|
|
32623
|
+
await writeFile10(gitignorePath, `# ${comment}
|
|
32624
|
+
${entry}
|
|
32625
|
+
`, "utf8");
|
|
32626
|
+
return "created";
|
|
32627
|
+
}
|
|
32628
|
+
const content = await readFile8(gitignorePath, "utf8");
|
|
32629
|
+
if (content.split(/\r?\n/).some((item) => item.trim() === entry)) {
|
|
32630
|
+
return "exists";
|
|
32631
|
+
}
|
|
32632
|
+
const separator = content.endsWith("\n") || content === "" ? "" : "\n";
|
|
32633
|
+
await writeFile10(gitignorePath, `${content}${separator}# ${comment}
|
|
32634
|
+
${entry}
|
|
32635
|
+
`, "utf8");
|
|
32636
|
+
return "updated";
|
|
32637
|
+
}
|
|
32412
32638
|
async function initTestPilotConfig(root, info, adapters) {
|
|
32413
|
-
const yamlPath =
|
|
32639
|
+
const yamlPath = join23(root, "testpilot.yaml");
|
|
32414
32640
|
let yaml = "reused";
|
|
32415
|
-
if (!await
|
|
32416
|
-
await
|
|
32641
|
+
if (!await exists4(yamlPath)) {
|
|
32642
|
+
await writeFile10(yamlPath, renderTestpilotYaml(info), "utf8");
|
|
32417
32643
|
yaml = "created";
|
|
32418
32644
|
}
|
|
32419
|
-
const
|
|
32420
|
-
|
|
32421
|
-
|
|
32422
|
-
|
|
32423
|
-
|
|
32645
|
+
const envExamplePath = join23(root, ".env.example");
|
|
32646
|
+
let envExample = "reused";
|
|
32647
|
+
if (!await exists4(envExamplePath)) {
|
|
32648
|
+
await writeFile10(envExamplePath, renderDotEnvExample(), "utf8");
|
|
32649
|
+
envExample = "created";
|
|
32650
|
+
}
|
|
32651
|
+
const gitignore = await ensureGitignoreEntry(root, ".env");
|
|
32652
|
+
const runtimeDir = join23(root, ".testpilot", "artifacts");
|
|
32653
|
+
await mkdir12(runtimeDir, { recursive: true });
|
|
32654
|
+
const keep = join23(runtimeDir, ".gitkeep");
|
|
32655
|
+
if (!await exists4(keep)) {
|
|
32656
|
+
await writeFile10(keep, "", "utf8");
|
|
32424
32657
|
}
|
|
32425
32658
|
const existing = await readProjectLink(root);
|
|
32426
32659
|
const defaultAdapter = defaultAdapterFor(info.type);
|
|
@@ -32439,7 +32672,14 @@ async function initTestPilotConfig(root, info, adapters) {
|
|
|
32439
32672
|
}
|
|
32440
32673
|
if (link.serverUrl === void 0 && serverUrl) link.serverUrl = serverUrl;
|
|
32441
32674
|
const projectLinkFile = await writeProjectLink(link, root);
|
|
32442
|
-
return {
|
|
32675
|
+
return {
|
|
32676
|
+
yaml,
|
|
32677
|
+
runtimeDir: join23(root, ".testpilot"),
|
|
32678
|
+
projectLink: link,
|
|
32679
|
+
projectLinkFile,
|
|
32680
|
+
envExample,
|
|
32681
|
+
gitignore
|
|
32682
|
+
};
|
|
32443
32683
|
}
|
|
32444
32684
|
async function createProjectOnServer(root, info, _adapters, serverUrl) {
|
|
32445
32685
|
const git = info.git;
|
|
@@ -32468,106 +32708,6 @@ function randomId() {
|
|
|
32468
32708
|
return Math.random().toString(16).slice(2, 10);
|
|
32469
32709
|
}
|
|
32470
32710
|
|
|
32471
|
-
// src/commands/init/skill-installer.ts
|
|
32472
|
-
import { existsSync as existsSync7 } from "node:fs";
|
|
32473
|
-
import { access as access3, cp, mkdir as mkdir12, writeFile as writeFile10 } from "node:fs/promises";
|
|
32474
|
-
import { dirname as dirname7, join as join23 } from "node:path";
|
|
32475
|
-
import { fileURLToPath } from "node:url";
|
|
32476
|
-
async function exists4(path) {
|
|
32477
|
-
try {
|
|
32478
|
-
await access3(path);
|
|
32479
|
-
return true;
|
|
32480
|
-
} catch {
|
|
32481
|
-
return false;
|
|
32482
|
-
}
|
|
32483
|
-
}
|
|
32484
|
-
function resolveSkillSourceDir() {
|
|
32485
|
-
const fromEnv = process.env.TESTPILOT_SKILL_DIR;
|
|
32486
|
-
if (fromEnv) return fromEnv;
|
|
32487
|
-
const bundled = fileURLToPath(new URL("./skills/testpilot", import.meta.url));
|
|
32488
|
-
if (existsSync7(bundled)) return bundled;
|
|
32489
|
-
const inRepo = fileURLToPath(new URL("../../../../../skills/testpilot", import.meta.url));
|
|
32490
|
-
return existsSync7(inRepo) ? inRepo : void 0;
|
|
32491
|
-
}
|
|
32492
|
-
async function installSkill(root, info, adapters) {
|
|
32493
|
-
const dest = join23(root, ".agents", "skills", "testpilot");
|
|
32494
|
-
let core = "reused";
|
|
32495
|
-
if (!await exists4(join23(dest, "manifest.yaml"))) {
|
|
32496
|
-
const src = resolveSkillSourceDir();
|
|
32497
|
-
if (!src) {
|
|
32498
|
-
throw new Error(
|
|
32499
|
-
"\u65E0\u6CD5\u5B9A\u4F4D TestPilot Skill \u6E90\u76EE\u5F55;\u53EF\u901A\u8FC7\u73AF\u5883\u53D8\u91CF TESTPILOT_SKILL_DIR \u6307\u5B9A skills/testpilot \u8DEF\u5F84"
|
|
32500
|
-
);
|
|
32501
|
-
}
|
|
32502
|
-
await mkdir12(dirname7(dest), { recursive: true });
|
|
32503
|
-
await cp(src, dest, { recursive: true });
|
|
32504
|
-
core = "created";
|
|
32505
|
-
}
|
|
32506
|
-
const config2 = await loadTestpilotConfig(root).catch(() => void 0);
|
|
32507
|
-
const defaultEnvironment = typeof config2?.environment?.default === "string" ? config2.environment.default : "\u672A\u8BBE\u7F6E";
|
|
32508
|
-
const defaultWorkspace = config2?.workspace?.default ?? "\u672A\u8BBE\u7F6E(\u53EF\u5728 yaml workspace.default \u914D\u7F6E)";
|
|
32509
|
-
const refsDir = join23(dest, "references");
|
|
32510
|
-
const files = [
|
|
32511
|
-
["project.md", () => renderProjectMd(info, adapters, defaultEnvironment, defaultWorkspace)],
|
|
32512
|
-
["test-conventions.md", renderTestConventionsMd],
|
|
32513
|
-
["adapters.md", () => renderAdaptersMd(adapters)]
|
|
32514
|
-
];
|
|
32515
|
-
let references = "reused";
|
|
32516
|
-
for (const [name, render] of files) {
|
|
32517
|
-
const file2 = join23(refsDir, name);
|
|
32518
|
-
if (await exists4(file2)) continue;
|
|
32519
|
-
await mkdir12(refsDir, { recursive: true });
|
|
32520
|
-
await writeFile10(file2, render(), "utf8");
|
|
32521
|
-
references = "created";
|
|
32522
|
-
}
|
|
32523
|
-
return { core, references, dest };
|
|
32524
|
-
}
|
|
32525
|
-
function renderProjectMd(info, adapters, defaultEnvironment, defaultWorkspace) {
|
|
32526
|
-
const available = adapters.filter((item) => item.available).map((item) => item.id);
|
|
32527
|
-
return `# Project
|
|
32528
|
-
|
|
32529
|
-
\u81EA\u52A8\u751F\u6210\u4E8E \`csspilot init\`,\u63CF\u8FF0\u672C\u9879\u76EE\u7684 TestPilot \u63A5\u5165\u4FE1\u606F;Agent \u4F18\u5148\u4ECE\u8FD9\u91CC\u4E86\u89E3\u9879\u76EE\u3002
|
|
32530
|
-
|
|
32531
|
-
- Project name: ${info.name}
|
|
32532
|
-
- Project type: ${info.type}
|
|
32533
|
-
- Language: ${info.language}
|
|
32534
|
-
- Package manager: ${info.packageManager}
|
|
32535
|
-
- Test directory: tests/e2e
|
|
32536
|
-
- Case directory: tests/e2e/cases
|
|
32537
|
-
- Available adapters: ${available.length > 0 ? available.join(", ") : "(none detected)"}
|
|
32538
|
-
- Default environment: ${defaultEnvironment}
|
|
32539
|
-
- Default workspace: ${defaultWorkspace}
|
|
32540
|
-
`;
|
|
32541
|
-
}
|
|
32542
|
-
function renderTestConventionsMd() {
|
|
32543
|
-
return `# Test Conventions
|
|
32544
|
-
|
|
32545
|
-
\u672C\u9879\u76EE E2E \u6D4B\u8BD5\u7EA6\u5B9A,Agent \u751F\u6210/\u4FEE\u6539 Case \u65F6\u5FC5\u987B\u9075\u5B88:
|
|
32546
|
-
|
|
32547
|
-
1. E2E Case \u4F4D\u4E8E tests/e2e/cases
|
|
32548
|
-
2. Case \u4F7F\u7528 TestPilot DSL
|
|
32549
|
-
3. \u4E0D\u5141\u8BB8\u76F4\u63A5\u8C03\u7528 Playwright
|
|
32550
|
-
4. \u4E0D\u5141\u8BB8\u76F4\u63A5\u8C03\u7528 WeChatIDE
|
|
32551
|
-
5. \u4E0D\u5141\u8BB8\u5728 Case \u4E2D\u5199\u660E\u6587\u5BC6\u7801(\u7528 accountRef \u5F15\u7528)
|
|
32552
|
-
6. \u4F18\u5148\u590D\u7528\u5DF2\u6709 Case
|
|
32553
|
-
7. \u65B0\u589E Case \u5FC5\u987B\u901A\u8FC7 csspilot validate
|
|
32554
|
-
8. Case ID \u5FC5\u987B\u552F\u4E00
|
|
32555
|
-
`;
|
|
32556
|
-
}
|
|
32557
|
-
function renderAdaptersMd(adapters) {
|
|
32558
|
-
const lines = adapters.map(
|
|
32559
|
-
(item) => `- ${item.id}: ${item.available ? "\u53EF\u7528" : "\u4E0D\u53EF\u7528"}${item.required ? "(\u9879\u76EE\u9700\u8981)" : "(\u53EF\u9009)"} \u2014 ${item.detail}`
|
|
32560
|
-
);
|
|
32561
|
-
return `# Adapters
|
|
32562
|
-
|
|
32563
|
-
\u6267\u884C\u7AEF\u68C0\u6D4B\u7ED3\u679C(\u4EE5 \`csspilot doctor\` \u6700\u65B0\u8F93\u51FA\u4E3A\u51C6):
|
|
32564
|
-
|
|
32565
|
-
${lines.join("\n")}
|
|
32566
|
-
|
|
32567
|
-
Agent \u6C38\u8FDC\u4E0D\u76F4\u63A5\u64CD\u4F5C Adapter;Case \u7ECF Execution Engine \u8C03\u5EA6\u5230\u5BF9\u5E94 Adapter \u6267\u884C\u3002
|
|
32568
|
-
`;
|
|
32569
|
-
}
|
|
32570
|
-
|
|
32571
32711
|
// src/commands/init/test-structure.ts
|
|
32572
32712
|
import { existsSync as existsSync8, readdirSync as readdirSync2 } from "node:fs";
|
|
32573
32713
|
import { mkdir as mkdir13, writeFile as writeFile11 } from "node:fs/promises";
|
|
@@ -32625,6 +32765,12 @@ async function runInit(root, options = {}) {
|
|
|
32625
32765
|
console.log(`\u2713 Runtime \u76EE\u5F55 ${join25(".testpilot", "artifacts")}`);
|
|
32626
32766
|
const serverMode = config2.projectLink.serverUrl ? `Server:${config2.projectLink.serverUrl}` : "\u672C\u5730\u6A21\u5F0F";
|
|
32627
32767
|
console.log(`\u2713 \u9879\u76EE\u5173\u8054 ${config2.projectLink.projectId}(${serverMode})`);
|
|
32768
|
+
console.log(
|
|
32769
|
+
config2.envExample === "created" ? "\u2713 \u73AF\u5883\u53D8\u91CF\u793A\u4F8B .env.example(\u590D\u5236\u4E3A .env \u586B\u5165\u771F\u5B9E\u503C)" : "\u2713 \u73AF\u5883\u53D8\u91CF\u793A\u4F8B .env.example(\u5DF2\u5B58\u5728,\u4FDD\u7559)"
|
|
32770
|
+
);
|
|
32771
|
+
console.log(
|
|
32772
|
+
config2.gitignore === "exists" ? "\u2713 Git \u5FFD\u7565 .env(\u5DF2\u5728 .gitignore)" : `\u2713 Git \u5FFD\u7565 .env(\u5DF2\u5199\u5165 .gitignore)`
|
|
32773
|
+
);
|
|
32628
32774
|
const testStructure = await initTestStructure(root, project);
|
|
32629
32775
|
const reuseNote = project.existingE2eDirs.filter((dir) => dir !== "tests/e2e");
|
|
32630
32776
|
console.log(
|
|
@@ -32634,9 +32780,15 @@ async function runInit(root, options = {}) {
|
|
|
32634
32780
|
console.log(` \u26A0 \u68C0\u6D4B\u5230\u5176\u4ED6 E2E \u76EE\u5F55:${reuseNote.join(", ")}(\u4FDD\u7559\u4E0D\u52A8,\u672A\u5F3A\u5236\u8FC1\u79FB)`);
|
|
32635
32781
|
}
|
|
32636
32782
|
const skill = await installSkill(root, project, adapters);
|
|
32637
|
-
|
|
32638
|
-
|
|
32639
|
-
|
|
32783
|
+
if (skill.core === "updated") {
|
|
32784
|
+
console.log(
|
|
32785
|
+
`\u2713 Skill \u66F4\u65B0 .agents/skills/testpilot/(v${skill.coreVersion?.from ?? "?"} -> v${skill.coreVersion?.to ?? "?"})`
|
|
32786
|
+
);
|
|
32787
|
+
} else {
|
|
32788
|
+
console.log(
|
|
32789
|
+
skill.core === "created" ? `\u2713 Skill \u5B89\u88C5 ${join25(".agents", "skills", "testpilot")}` : "\u2713 Skill \u5B89\u88C5 .agents/skills/testpilot/(\u5DF2\u5B58\u5728,\u8DF3\u8FC7)"
|
|
32790
|
+
);
|
|
32791
|
+
}
|
|
32640
32792
|
console.log(
|
|
32641
32793
|
skill.references === "created" ? "\u2713 Agent \u4E0A\u4E0B\u6587 references/{project,test-conventions,adapters}.md" : "\u2713 Agent \u4E0A\u4E0B\u6587 references/(\u5DF2\u5B58\u5728,\u4FDD\u7559)"
|
|
32642
32794
|
);
|
|
@@ -32866,6 +33018,37 @@ function makeSyncMetadataCommand() {
|
|
|
32866
33018
|
});
|
|
32867
33019
|
}
|
|
32868
33020
|
|
|
33021
|
+
// src/commands/update.ts
|
|
33022
|
+
import { writeFile as writeFile12 } from "node:fs/promises";
|
|
33023
|
+
import { join as join27 } from "node:path";
|
|
33024
|
+
function makeUpdateCommand() {
|
|
33025
|
+
const update = new Command("update").description("\u540C\u6B65 TestPilot \u63A5\u5165\u7269\u5230\u5F53\u524D CLI \u7248\u672C(Skill / .env.example;\u4E0D\u89E6\u78B0 .env \u4E0E\u7528\u4F8B)").option("--force", "Skill \u7248\u672C\u4E00\u81F4\u65F6\u4E5F\u5F3A\u5236\u8986\u76D6\u672C\u5730\u4FEE\u6539", false).action(async (options) => {
|
|
33026
|
+
const root = process.cwd();
|
|
33027
|
+
const dest = join27(".agents", "skills", "testpilot");
|
|
33028
|
+
const skill = await syncSkillFiles(root, { force: options.force });
|
|
33029
|
+
switch (skill.core) {
|
|
33030
|
+
case "created":
|
|
33031
|
+
console.log(`\u2713 Skill \u5B89\u88C5 ${dest}(v${skill.bundledVersion ?? "?"})`);
|
|
33032
|
+
break;
|
|
33033
|
+
case "updated":
|
|
33034
|
+
console.log(
|
|
33035
|
+
`\u2713 Skill \u66F4\u65B0 ${dest} v${skill.installedVersion ?? "?"} -> v${skill.bundledVersion ?? "?"}`
|
|
33036
|
+
);
|
|
33037
|
+
break;
|
|
33038
|
+
default:
|
|
33039
|
+
console.log(`\u2713 Skill \u5DF2\u662F\u6700\u65B0 v${skill.bundledVersion ?? "?"}`);
|
|
33040
|
+
}
|
|
33041
|
+
await writeFile12(join27(root, ".env.example"), renderDotEnvExample(), "utf8");
|
|
33042
|
+
console.log("\u2713 .env.example \u5DF2\u540C\u6B65\u4E3A\u5F53\u524D\u7248\u672C\u7684\u793A\u4F8B(.env \u4E0D\u53D7\u5F71\u54CD)");
|
|
33043
|
+
const gitignore = await ensureGitignoreEntry(root, ".env");
|
|
33044
|
+
console.log(
|
|
33045
|
+
gitignore === "exists" ? "\u2713 .gitignore \u5DF2\u5FFD\u7565 .env" : "\u2713 .gitignore \u5DF2\u8FFD\u52A0\u5FFD\u7565 .env"
|
|
33046
|
+
);
|
|
33047
|
+
console.log("\u63D0\u793A:\u63D0\u4EA4 .agents/skills/ \u4E0E .env.example \u7684\u53D8\u66F4,\u56E2\u961F\u4E0E AI \u5373\u53EF\u4F7F\u7528\u6700\u65B0\u80FD\u529B");
|
|
33048
|
+
});
|
|
33049
|
+
return update;
|
|
33050
|
+
}
|
|
33051
|
+
|
|
32869
33052
|
// src/commands/validate.ts
|
|
32870
33053
|
function makeValidateCommand() {
|
|
32871
33054
|
return new Command("validate").description("\u6821\u9A8C\u7528\u4F8B DSL").argument("[paths...]", "\u7528\u4F8B\u6587\u4EF6\u6216\u76EE\u5F55,\u9ED8\u8BA4\u6309\u9879\u76EE\u914D\u7F6E").action(async (paths) => {
|
|
@@ -32902,10 +33085,10 @@ function makeValidateCommand() {
|
|
|
32902
33085
|
}
|
|
32903
33086
|
|
|
32904
33087
|
// src/cli.ts
|
|
32905
|
-
var CLI_VERSION = "0.
|
|
33088
|
+
var CLI_VERSION = "0.4.0";
|
|
32906
33089
|
async function createCli() {
|
|
32907
33090
|
const program2 = new Command();
|
|
32908
|
-
program2.name("csspilot").description("TestPilot \u2014 AI Native \u8DE8\u7AEF\u4E1A\u52A1\u6D4B\u8BD5\u57FA\u7840\u8BBE\u65BD CLI").version(CLI_VERSION).addCommand(makeInitCommand()).addCommand(makeValidateCommand()).addCommand(makeListCommand()).addCommand(makeRunCommand()).addCommand(makeReportCommand()).addCommand(makeSyncMetadataCommand()).addCommand(makeDoctorCommand()).addCommand(makeCiCommand());
|
|
33091
|
+
program2.name("csspilot").description("TestPilot \u2014 AI Native \u8DE8\u7AEF\u4E1A\u52A1\u6D4B\u8BD5\u57FA\u7840\u8BBE\u65BD CLI").version(CLI_VERSION).addCommand(makeInitCommand()).addCommand(makeValidateCommand()).addCommand(makeListCommand()).addCommand(makeRunCommand()).addCommand(makeReportCommand()).addCommand(makeSyncMetadataCommand()).addCommand(makeDoctorCommand()).addCommand(makeUpdateCommand()).addCommand(makeCiCommand());
|
|
32909
33092
|
return program2;
|
|
32910
33093
|
}
|
|
32911
33094
|
export {
|
|
@@ -12,6 +12,7 @@ TestPilot is a **tool, not a business test project**. Business cases always belo
|
|
|
12
12
|
|
|
13
13
|
Use TestPilot when the user wants to:
|
|
14
14
|
|
|
15
|
+
- review test requirements for testability
|
|
15
16
|
- create business test cases
|
|
16
17
|
- validate test cases
|
|
17
18
|
- run tests
|
|
@@ -20,12 +21,13 @@ Use TestPilot when the user wants to:
|
|
|
20
21
|
## Core workflow
|
|
21
22
|
|
|
22
23
|
1. Understand user requirement
|
|
23
|
-
2.
|
|
24
|
-
3.
|
|
25
|
-
4.
|
|
26
|
-
5.
|
|
27
|
-
6.
|
|
28
|
-
7.
|
|
24
|
+
2. Review requirements & produce test points (`workflows/test-requirements-review.md`)
|
|
25
|
+
3. Inspect project
|
|
26
|
+
4. Identify test target
|
|
27
|
+
5. Create TestPilot Case (`tests/e2e/cases/*.yaml`, linked to test point IDs)
|
|
28
|
+
6. Validate Case (`npx csspilot validate`)
|
|
29
|
+
7. Run Case (`npx csspilot run`)
|
|
30
|
+
8. Analyze result (`npx csspilot report`)
|
|
29
31
|
|
|
30
32
|
## Important rules
|
|
31
33
|
|
|
@@ -72,4 +74,5 @@ Read `references/` first (generated by `csspilot init` for this project):
|
|
|
72
74
|
- `rules/dsl.md` — case structure and the V0.1 action set
|
|
73
75
|
- `rules/locator.md` — locator forms and the no-invention rule
|
|
74
76
|
- `rules/assertion.md` — assertions and variable references
|
|
75
|
-
- `workflows
|
|
77
|
+
- `workflows/test-requirements-review.md` — requirements review, test point checklist
|
|
78
|
+
- `workflows/` — step-by-step workflows (review / create / validate / run / analyze)
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
# `npx csspilot init` 会把整个 skills/testpilot/ 安装到业务项目 .agents/skills/testpilot/
|
|
3
3
|
id: testpilot
|
|
4
4
|
name: TestPilot
|
|
5
|
-
version: 0.
|
|
5
|
+
version: 0.4.0
|
|
6
6
|
|
|
7
7
|
description: >
|
|
8
8
|
AI Native cross-platform testing skill.
|
|
9
9
|
|
|
10
10
|
capabilities:
|
|
11
|
+
- test-requirements-review
|
|
11
12
|
- create-case
|
|
12
13
|
- validate
|
|
13
14
|
- run
|
|
@@ -37,4 +38,4 @@ permissions:
|
|
|
37
38
|
- npx
|
|
38
39
|
- pnpm
|
|
39
40
|
- npm
|
|
40
|
-
-
|
|
41
|
+
- csspilot
|