@epoint-testtech/ep-stage-skill 0.0.3-alpha.1 → 0.0.4-alpha.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/SKILL.md +2 -2
- package/codex-skill/ep-stage/glue-create-project/SKILL.md +186 -0
- package/codex-skill/ep-stage/glue-generate-testcase/SKILL.md +199 -0
- package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-schema.md +112 -0
- package/codex-skill/ep-stage/glue-run-test/SKILL.md +249 -0
- package/codex-skill/ep-stage/glue-run-test/references/crud-pipeline.md +145 -0
- package/codex-skill/ep-stage/{glue-test → glue-run-test}/scripts/generate-crud-spec.mjs +3 -3
- package/codex-skill/ep-stage/recording-to-glue/SKILL.md +1 -0
- package/codex-skill/ep-stage/scripts/validate-skill.mjs +29 -7
- package/dist/src/cli/dev/extract-contract.d.ts +14 -0
- package/dist/src/cli/dev/extract-contract.d.ts.map +1 -0
- package/dist/src/cli/dev/extract-contract.js +114 -0
- package/dist/src/cli/generate-crud-contract.js +7 -77
- package/dist/src/cli/generate-playwright-tests.d.ts +0 -28
- package/dist/src/cli/generate-playwright-tests.d.ts.map +1 -1
- package/dist/src/cli/generate-playwright-tests.js +4 -81
- package/dist/src/cli/generate-testcase.d.ts +83 -0
- package/dist/src/cli/generate-testcase.d.ts.map +1 -0
- package/dist/src/cli/generate-testcase.js +197 -0
- package/dist/src/cli/index.d.ts +18 -0
- package/dist/src/cli/index.d.ts.map +1 -0
- package/dist/src/cli/index.js +55 -0
- package/dist/src/cli/probe.d.ts +44 -0
- package/dist/src/cli/probe.d.ts.map +1 -0
- package/dist/src/cli/probe.js +221 -0
- package/dist/src/cli/run-gap-pipeline.js +4 -0
- package/dist/src/cli/run.d.ts +63 -0
- package/dist/src/cli/run.d.ts.map +1 -0
- package/dist/src/cli/run.js +116 -0
- package/dist/src/cli/spec.d.ts +45 -0
- package/dist/src/cli/spec.d.ts.map +1 -0
- package/dist/src/cli/spec.js +74 -0
- package/dist/src/context/stage-context.d.ts +72 -8
- package/dist/src/context/stage-context.d.ts.map +1 -1
- package/dist/src/context/stage-context.js +61 -15
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/testcase/testcase-generator.d.ts.map +1 -1
- package/dist/src/testcase/testcase-generator.js +4 -0
- package/dist/src/testcase/testcase-v2.d.ts +50 -0
- package/dist/src/testcase/testcase-v2.d.ts.map +1 -0
- package/dist/src/testcase/testcase-v2.js +1 -0
- package/dist/src/util/credentials.d.ts +12 -0
- package/dist/src/util/credentials.d.ts.map +1 -0
- package/dist/src/util/credentials.js +19 -0
- package/dist/src/util/i18n-testcase.d.ts +8 -0
- package/dist/src/util/i18n-testcase.d.ts.map +1 -0
- package/dist/src/util/i18n-testcase.js +55 -0
- package/dist/src/util/softlink.d.ts +33 -0
- package/dist/src/util/softlink.d.ts.map +1 -0
- package/dist/src/util/softlink.js +43 -0
- package/dist/src/validation/credentials.d.ts +19 -0
- package/dist/src/validation/credentials.d.ts.map +1 -0
- package/dist/src/validation/credentials.js +38 -0
- package/dist/src/validation/index.d.ts +5 -0
- package/dist/src/validation/index.d.ts.map +1 -0
- package/dist/src/validation/index.js +3 -0
- package/dist/src/validation/projects-index.d.ts +13 -0
- package/dist/src/validation/projects-index.d.ts.map +1 -0
- package/dist/src/validation/projects-index.js +37 -0
- package/dist/src/validation/testcase.d.ts +13 -0
- package/dist/src/validation/testcase.d.ts.map +1 -0
- package/dist/src/validation/testcase.js +53 -0
- package/dist/test/cli/extract-contract.test.d.ts +2 -0
- package/dist/test/cli/extract-contract.test.d.ts.map +1 -0
- package/dist/test/cli/extract-contract.test.js +32 -0
- package/dist/test/cli/generate-testcase.test.d.ts +2 -0
- package/dist/test/cli/generate-testcase.test.d.ts.map +1 -0
- package/dist/test/cli/generate-testcase.test.js +130 -0
- package/dist/test/cli/index.test.d.ts +2 -0
- package/dist/test/cli/index.test.d.ts.map +1 -0
- package/dist/test/cli/index.test.js +93 -0
- package/dist/test/cli/run.test.d.ts +2 -0
- package/dist/test/cli/run.test.d.ts.map +1 -0
- package/dist/test/cli/run.test.js +149 -0
- package/dist/test/cli/spec.test.d.ts +2 -0
- package/dist/test/cli/spec.test.d.ts.map +1 -0
- package/dist/test/cli/spec.test.js +196 -0
- package/dist/test/stage-context.test.js +145 -13
- package/dist/test/util/credentials.test.d.ts +2 -0
- package/dist/test/util/credentials.test.d.ts.map +1 -0
- package/dist/test/util/credentials.test.js +64 -0
- package/dist/test/util/i18n-testcase.test.d.ts +2 -0
- package/dist/test/util/i18n-testcase.test.d.ts.map +1 -0
- package/dist/test/util/i18n-testcase.test.js +119 -0
- package/dist/test/util/softlink.test.d.ts +2 -0
- package/dist/test/util/softlink.test.d.ts.map +1 -0
- package/dist/test/util/softlink.test.js +82 -0
- package/dist/test/validation/credentials.test.d.ts +2 -0
- package/dist/test/validation/credentials.test.d.ts.map +1 -0
- package/dist/test/validation/credentials.test.js +72 -0
- package/dist/test/validation/projects-index.test.d.ts +2 -0
- package/dist/test/validation/projects-index.test.d.ts.map +1 -0
- package/dist/test/validation/projects-index.test.js +48 -0
- package/dist/test/validation/testcase.test.d.ts +2 -0
- package/dist/test/validation/testcase.test.d.ts.map +1 -0
- package/dist/test/validation/testcase.test.js +129 -0
- package/docs/README.md +6 -6
- package/docs/mvp-usage-guide.md +3 -3
- package/package.json +9 -4
- package/codex-skill/ep-stage/create-project/SKILL.md +0 -59
- package/codex-skill/ep-stage/glue-test/SKILL.md +0 -258
- package/codex-skill/ep-stage/glue-test/references/crud-pipeline.md +0 -139
- package/codex-skill/ep-stage/glue-testcase/SKILL.md +0 -31
- package/codex-skill/ep-stage/glue-testcase/references/testcase-schema.md +0 -67
- /package/codex-skill/ep-stage/{glue-testcase → glue-generate-testcase}/examples/observable-testcase.json +0 -0
- /package/codex-skill/ep-stage/{glue-test → glue-run-test}/references/gap-review-protocol.md +0 -0
- /package/codex-skill/ep-stage/{glue-test → glue-run-test}/references/harness-principles.md +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync, existsSync, readFileSync, lstatSync, mkdirSync, symlinkSync } from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { createSoftlinkWithFallback, softlinkFs } from '../../src/util/softlink.js';
|
|
6
|
+
/**
|
|
7
|
+
* REQ-FILE-02:跨平台软链 fallback util
|
|
8
|
+
* 三档 fallback:symlink(macOS/Linux)→ hardlink → copy + 时间戳警示(Windows 无管理员权限)。
|
|
9
|
+
*
|
|
10
|
+
* 注:fallback 路径通过 softlinkFs.* spy 模拟失败;ESM namespace 不可写,
|
|
11
|
+
* 因此 softlink.ts 显式暴露 softlinkFs 对象供测试替换。
|
|
12
|
+
*/
|
|
13
|
+
describe('createSoftlinkWithFallback', () => {
|
|
14
|
+
let tmpDir;
|
|
15
|
+
let target;
|
|
16
|
+
let linkPath;
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
tmpDir = mkdtempSync(path.join(os.tmpdir(), 'softlink-test-'));
|
|
19
|
+
target = path.join(tmpDir, 'code_list.md');
|
|
20
|
+
linkPath = path.join(tmpDir, 'requirement', 'code_list.md');
|
|
21
|
+
writeFileSync(target, '# upstream code list\n');
|
|
22
|
+
mkdirSync(path.dirname(linkPath), { recursive: true });
|
|
23
|
+
});
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
vi.restoreAllMocks();
|
|
26
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
27
|
+
});
|
|
28
|
+
it('symlink 成功时返回 strategy=symlink,且链接指向 target', () => {
|
|
29
|
+
const result = createSoftlinkWithFallback(target, linkPath);
|
|
30
|
+
expect(result.strategy).toBe('symlink');
|
|
31
|
+
expect(result.warned).toBeUndefined();
|
|
32
|
+
expect(existsSync(linkPath)).toBe(true);
|
|
33
|
+
expect(lstatSync(linkPath).isSymbolicLink()).toBe(true);
|
|
34
|
+
// 通过链接读到的内容应该和源文件一致
|
|
35
|
+
expect(readFileSync(linkPath, 'utf8')).toBe('# upstream code list\n');
|
|
36
|
+
});
|
|
37
|
+
it('目标已存在时先 unlink 再创建(覆盖语义)', () => {
|
|
38
|
+
// 先放一个老文件占位
|
|
39
|
+
writeFileSync(linkPath, '# stale content\n');
|
|
40
|
+
const result = createSoftlinkWithFallback(target, linkPath);
|
|
41
|
+
expect(result.strategy).toBe('symlink');
|
|
42
|
+
expect(lstatSync(linkPath).isSymbolicLink()).toBe(true);
|
|
43
|
+
expect(readFileSync(linkPath, 'utf8')).toBe('# upstream code list\n');
|
|
44
|
+
});
|
|
45
|
+
it('目标已存在且为旧 symlink 时也能覆盖', () => {
|
|
46
|
+
// 先建一个指向别处的 symlink
|
|
47
|
+
const altTarget = path.join(tmpDir, 'alt.md');
|
|
48
|
+
writeFileSync(altTarget, '# alt\n');
|
|
49
|
+
symlinkSync(altTarget, linkPath, 'file');
|
|
50
|
+
expect(lstatSync(linkPath).isSymbolicLink()).toBe(true);
|
|
51
|
+
const result = createSoftlinkWithFallback(target, linkPath);
|
|
52
|
+
expect(result.strategy).toBe('symlink');
|
|
53
|
+
expect(readFileSync(linkPath, 'utf8')).toBe('# upstream code list\n');
|
|
54
|
+
});
|
|
55
|
+
it('symlink 失败时回退到 hardlink', () => {
|
|
56
|
+
// mock softlinkFs.symlinkSync 抛错,模拟 Windows 无权限
|
|
57
|
+
vi.spyOn(softlinkFs, 'symlinkSync').mockImplementation(() => {
|
|
58
|
+
throw new Error('EPERM: operation not permitted');
|
|
59
|
+
});
|
|
60
|
+
const result = createSoftlinkWithFallback(target, linkPath);
|
|
61
|
+
expect(result.strategy).toBe('hardlink');
|
|
62
|
+
expect(result.warned).toBeUndefined();
|
|
63
|
+
expect(existsSync(linkPath)).toBe(true);
|
|
64
|
+
expect(lstatSync(linkPath).isSymbolicLink()).toBe(false);
|
|
65
|
+
expect(readFileSync(linkPath, 'utf8')).toBe('# upstream code list\n');
|
|
66
|
+
});
|
|
67
|
+
it('symlink 与 hardlink 均失败时回退到 copy,并返回 warned=true', () => {
|
|
68
|
+
vi.spyOn(softlinkFs, 'symlinkSync').mockImplementation(() => {
|
|
69
|
+
throw new Error('EPERM: symlink not permitted');
|
|
70
|
+
});
|
|
71
|
+
vi.spyOn(softlinkFs, 'linkSync').mockImplementation(() => {
|
|
72
|
+
throw new Error('EXDEV: cross-device link not permitted');
|
|
73
|
+
});
|
|
74
|
+
const result = createSoftlinkWithFallback(target, linkPath);
|
|
75
|
+
expect(result.strategy).toBe('copy');
|
|
76
|
+
expect(result.warned).toBe(true);
|
|
77
|
+
expect(existsSync(linkPath)).toBe(true);
|
|
78
|
+
expect(lstatSync(linkPath).isSymbolicLink()).toBe(false);
|
|
79
|
+
// copy 的文件内容等于源文件
|
|
80
|
+
expect(readFileSync(linkPath, 'utf8')).toBe('# upstream code list\n');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.test.d.ts","sourceRoot":"","sources":["../../../test/validation/credentials.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { validateCredentials } from '../../src/validation/credentials.js';
|
|
3
|
+
describe('validateCredentials', () => {
|
|
4
|
+
it('合法数组(≥1 条)通过', () => {
|
|
5
|
+
const result = validateCredentials([
|
|
6
|
+
{
|
|
7
|
+
url: 'http://example.test',
|
|
8
|
+
username: 'admin',
|
|
9
|
+
password: 'pwd',
|
|
10
|
+
role: '系统管理员',
|
|
11
|
+
systemName: '基础平台',
|
|
12
|
+
},
|
|
13
|
+
]);
|
|
14
|
+
expect(result).toHaveLength(1);
|
|
15
|
+
expect(result[0]).toMatchObject({
|
|
16
|
+
url: 'http://example.test',
|
|
17
|
+
username: 'admin',
|
|
18
|
+
password: 'pwd',
|
|
19
|
+
role: '系统管理员',
|
|
20
|
+
systemName: '基础平台',
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
it('允许无 role / systemName 的条目', () => {
|
|
24
|
+
const result = validateCredentials([
|
|
25
|
+
{ url: 'http://example.test', username: 'admin', password: 'pwd' },
|
|
26
|
+
]);
|
|
27
|
+
expect(result[0]).toEqual({
|
|
28
|
+
url: 'http://example.test',
|
|
29
|
+
username: 'admin',
|
|
30
|
+
password: 'pwd',
|
|
31
|
+
});
|
|
32
|
+
expect(result[0]).not.toHaveProperty('role');
|
|
33
|
+
expect(result[0]).not.toHaveProperty('systemName');
|
|
34
|
+
});
|
|
35
|
+
it('非数组报错', () => {
|
|
36
|
+
expect(() => validateCredentials({ url: 'x' })).toThrow(/数组/);
|
|
37
|
+
});
|
|
38
|
+
it('空数组报错', () => {
|
|
39
|
+
expect(() => validateCredentials([])).toThrow(/至少|1 条/);
|
|
40
|
+
});
|
|
41
|
+
it('条目缺 url 报错', () => {
|
|
42
|
+
expect(() => validateCredentials([{ username: 'admin', password: 'pwd' }])).toThrow(/url/);
|
|
43
|
+
});
|
|
44
|
+
it('条目缺 username 报错', () => {
|
|
45
|
+
expect(() => validateCredentials([{ url: 'http://example.test', password: 'pwd' }])).toThrow(/username/);
|
|
46
|
+
});
|
|
47
|
+
it('条目缺 password 报错', () => {
|
|
48
|
+
expect(() => validateCredentials([{ url: 'http://example.test', username: 'admin' }])).toThrow(/password/);
|
|
49
|
+
});
|
|
50
|
+
it('条目空字符串字段(空白)报错', () => {
|
|
51
|
+
expect(() => validateCredentials([{ url: ' ', username: 'admin', password: 'pwd' }])).toThrow(/url/);
|
|
52
|
+
});
|
|
53
|
+
it('同 (url, role) 重复报错', () => {
|
|
54
|
+
expect(() => validateCredentials([
|
|
55
|
+
{ url: 'http://example.test', username: 'a', password: 'p1', role: '管理员' },
|
|
56
|
+
{ url: 'http://example.test', username: 'b', password: 'p2', role: '管理员' },
|
|
57
|
+
])).toThrow(/重复/);
|
|
58
|
+
});
|
|
59
|
+
it('同 url 但 role 不同允许', () => {
|
|
60
|
+
const result = validateCredentials([
|
|
61
|
+
{ url: 'http://example.test', username: 'a', password: 'p1', role: '管理员' },
|
|
62
|
+
{ url: 'http://example.test', username: 'b', password: 'p2', role: '业务员' },
|
|
63
|
+
]);
|
|
64
|
+
expect(result).toHaveLength(2);
|
|
65
|
+
});
|
|
66
|
+
it('同 url 都没 role 视作 (url, "") 也会触发重复', () => {
|
|
67
|
+
expect(() => validateCredentials([
|
|
68
|
+
{ url: 'http://example.test', username: 'a', password: 'p1' },
|
|
69
|
+
{ url: 'http://example.test', username: 'b', password: 'p2' },
|
|
70
|
+
])).toThrow(/重复/);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects-index.test.d.ts","sourceRoot":"","sources":["../../../test/validation/projects-index.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { validateProjectsIndex } from '../../src/validation/projects-index.js';
|
|
3
|
+
describe('validateProjectsIndex', () => {
|
|
4
|
+
it('通过合法结构(含 systems[] 与 stageCreateVersion)', () => {
|
|
5
|
+
const obj = {
|
|
6
|
+
projects: [
|
|
7
|
+
{
|
|
8
|
+
projectName: 'demo',
|
|
9
|
+
projectDir: '/abs/demo',
|
|
10
|
+
mode: 'glue',
|
|
11
|
+
createdAt: '2026-06-25T00:00:00Z',
|
|
12
|
+
stageCreateVersion: '0.0.4-alpha.0',
|
|
13
|
+
requirements: [],
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
systems: [],
|
|
17
|
+
};
|
|
18
|
+
const result = validateProjectsIndex(obj);
|
|
19
|
+
expect(result.projects).toHaveLength(1);
|
|
20
|
+
expect(result.projects[0].stageCreateVersion).toBe('0.0.4-alpha.0');
|
|
21
|
+
expect(result.systems).toEqual([]);
|
|
22
|
+
});
|
|
23
|
+
it('缺 systems 字段时报结构化错误', () => {
|
|
24
|
+
expect(() => validateProjectsIndex({ projects: [] })).toThrow(/systems/);
|
|
25
|
+
});
|
|
26
|
+
it('项目条目缺 stageCreateVersion 报错(stage-context.md 砍掉后的元信息承接)', () => {
|
|
27
|
+
expect(() => validateProjectsIndex({
|
|
28
|
+
projects: [{ projectName: 'x', projectDir: '/x', mode: 'glue', requirements: [] }],
|
|
29
|
+
systems: [],
|
|
30
|
+
})).toThrow(/stageCreateVersion/);
|
|
31
|
+
});
|
|
32
|
+
it('不允许残留 stageContextPath / envPath / knowledgeRoot 字段', () => {
|
|
33
|
+
expect(() => validateProjectsIndex({
|
|
34
|
+
projects: [
|
|
35
|
+
{
|
|
36
|
+
projectName: 'x',
|
|
37
|
+
projectDir: '/x',
|
|
38
|
+
mode: 'glue',
|
|
39
|
+
createdAt: '2026-06-25T00:00:00Z',
|
|
40
|
+
stageCreateVersion: '0.0.4-alpha.0',
|
|
41
|
+
requirements: [],
|
|
42
|
+
stageContextPath: '/x/stage-context.md', // 应被拒绝
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
systems: [],
|
|
46
|
+
})).toThrow(/stageContextPath|removed/);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testcase.test.d.ts","sourceRoot":"","sources":["../../../test/validation/testcase.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { validateTestcase } from '../../src/validation/testcase.js';
|
|
3
|
+
/**
|
|
4
|
+
* 构造一份满足 v2 schema 的最小合法 testcase(REQ-DATA-02)。
|
|
5
|
+
*
|
|
6
|
+
* @returns 合法 testcase v2 对象。
|
|
7
|
+
*/
|
|
8
|
+
function makeValidTestcase() {
|
|
9
|
+
return {
|
|
10
|
+
schemaVersion: 'v2',
|
|
11
|
+
scenario: '组织管理 CRUD 主流程',
|
|
12
|
+
moduleId: 'org-management',
|
|
13
|
+
moduleName: '组织管理',
|
|
14
|
+
menuPath: '系统管理 > 组织管理',
|
|
15
|
+
requiredSystems: [{ url: 'http://example.test', systemName: '基础平台' }],
|
|
16
|
+
requiredRoles: [{ role: '系统管理员', hint: '可访问组织管理菜单' }],
|
|
17
|
+
coveredActions: [
|
|
18
|
+
{ actionId: 'create', kind: 'create', evidence: ['html=org-list.html#btn-add'] },
|
|
19
|
+
],
|
|
20
|
+
uncoveredCandidates: [
|
|
21
|
+
{
|
|
22
|
+
actionId: 'export',
|
|
23
|
+
label: '导出',
|
|
24
|
+
status: 'planned',
|
|
25
|
+
requiredRole: '系统管理员',
|
|
26
|
+
businessIntent: '导出当前查询结果',
|
|
27
|
+
assertionExpectation: '断言下载文件存在',
|
|
28
|
+
evidence: ['hint=ModuleHints.export'],
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
cases: [
|
|
32
|
+
{
|
|
33
|
+
caseId: 'CASE-001',
|
|
34
|
+
title: '管理员新增组织',
|
|
35
|
+
scenario: '组织管理 CRUD 主流程',
|
|
36
|
+
reviewStatus: 'confirmed',
|
|
37
|
+
requiredRole: '系统管理员',
|
|
38
|
+
evidence: ['spec=org-management.spec.yaml#create'],
|
|
39
|
+
steps: ['打开菜单', '点击新增'],
|
|
40
|
+
assertions: ['列表出现新组织'],
|
|
41
|
+
unresolved: [],
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
reasoningSummary: {
|
|
45
|
+
conclusion: '主流程覆盖增删改查',
|
|
46
|
+
evidenceChain: ['html=org-list.html'],
|
|
47
|
+
alternatives: [],
|
|
48
|
+
confidence: 'high',
|
|
49
|
+
risks: [],
|
|
50
|
+
needsHumanReview: false,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
describe('validateTestcase', () => {
|
|
55
|
+
it('合法 v2 testcase 通过', () => {
|
|
56
|
+
const result = validateTestcase(makeValidTestcase());
|
|
57
|
+
expect(result.schemaVersion).toBe('v2');
|
|
58
|
+
expect(result.cases).toHaveLength(1);
|
|
59
|
+
});
|
|
60
|
+
it('缺 schemaVersion 报版本错误引导重跑(v1 升级路径)', () => {
|
|
61
|
+
const tc = makeValidTestcase();
|
|
62
|
+
delete tc.schemaVersion;
|
|
63
|
+
expect(() => validateTestcase(tc)).toThrow(/schemaVersion|v2|重跑/);
|
|
64
|
+
});
|
|
65
|
+
it('schemaVersion 非 v2(视作 v1)也走版本错误引导重跑', () => {
|
|
66
|
+
const tc = { ...makeValidTestcase(), schemaVersion: 'v1' };
|
|
67
|
+
expect(() => validateTestcase(tc)).toThrow(/schemaVersion|v2|重跑/);
|
|
68
|
+
});
|
|
69
|
+
it('缺 scenario 报错', () => {
|
|
70
|
+
const tc = makeValidTestcase();
|
|
71
|
+
delete tc.scenario;
|
|
72
|
+
expect(() => validateTestcase(tc)).toThrow(/scenario/);
|
|
73
|
+
});
|
|
74
|
+
it('缺 menuPath 报错', () => {
|
|
75
|
+
const tc = makeValidTestcase();
|
|
76
|
+
delete tc.menuPath;
|
|
77
|
+
expect(() => validateTestcase(tc)).toThrow(/menuPath/);
|
|
78
|
+
});
|
|
79
|
+
it('缺 requiredSystems[] 报错', () => {
|
|
80
|
+
const tc = makeValidTestcase();
|
|
81
|
+
delete tc.requiredSystems;
|
|
82
|
+
expect(() => validateTestcase(tc)).toThrow(/requiredSystems/);
|
|
83
|
+
});
|
|
84
|
+
it('缺 requiredRoles[] 报错', () => {
|
|
85
|
+
const tc = makeValidTestcase();
|
|
86
|
+
delete tc.requiredRoles;
|
|
87
|
+
expect(() => validateTestcase(tc)).toThrow(/requiredRoles/);
|
|
88
|
+
});
|
|
89
|
+
it('缺 coveredActions[] 报错', () => {
|
|
90
|
+
const tc = makeValidTestcase();
|
|
91
|
+
delete tc.coveredActions;
|
|
92
|
+
expect(() => validateTestcase(tc)).toThrow(/coveredActions/);
|
|
93
|
+
});
|
|
94
|
+
it('缺 uncoveredCandidates[] 报错', () => {
|
|
95
|
+
const tc = makeValidTestcase();
|
|
96
|
+
delete tc.uncoveredCandidates;
|
|
97
|
+
expect(() => validateTestcase(tc)).toThrow(/uncoveredCandidates/);
|
|
98
|
+
});
|
|
99
|
+
it('uncoveredCandidates[].requiredRole 缺失报错', () => {
|
|
100
|
+
const tc = makeValidTestcase();
|
|
101
|
+
const uncovered = tc.uncoveredCandidates;
|
|
102
|
+
delete uncovered[0].requiredRole;
|
|
103
|
+
expect(() => validateTestcase(tc)).toThrow(/uncoveredCandidates.*requiredRole/);
|
|
104
|
+
});
|
|
105
|
+
it('coveredActions[].evidence 非 <kind>=<ref> 报错', () => {
|
|
106
|
+
const tc = makeValidTestcase();
|
|
107
|
+
const covered = tc.coveredActions;
|
|
108
|
+
covered[0].evidence = ['某条非法 evidence 字符串'];
|
|
109
|
+
expect(() => validateTestcase(tc)).toThrow(/evidence/);
|
|
110
|
+
});
|
|
111
|
+
it('uncoveredCandidates[].evidence 非 <kind>=<ref> 报错', () => {
|
|
112
|
+
const tc = makeValidTestcase();
|
|
113
|
+
const uncovered = tc.uncoveredCandidates;
|
|
114
|
+
uncovered[0].evidence = ['plainstring'];
|
|
115
|
+
expect(() => validateTestcase(tc)).toThrow(/evidence/);
|
|
116
|
+
});
|
|
117
|
+
it('cases[].evidence 非 <kind>=<ref> 报错', () => {
|
|
118
|
+
const tc = makeValidTestcase();
|
|
119
|
+
const cases = tc.cases;
|
|
120
|
+
cases[0].evidence = ['noequalsign'];
|
|
121
|
+
expect(() => validateTestcase(tc)).toThrow(/evidence/);
|
|
122
|
+
});
|
|
123
|
+
it('evidence kind 必须在 html/java/spec/contract/skeleton/hint 范围内', () => {
|
|
124
|
+
const tc = makeValidTestcase();
|
|
125
|
+
const covered = tc.coveredActions;
|
|
126
|
+
covered[0].evidence = ['unknownkind=foo.html'];
|
|
127
|
+
expect(() => validateTestcase(tc)).toThrow(/evidence/);
|
|
128
|
+
});
|
|
129
|
+
});
|
package/docs/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @epoint/ep-stage-skill
|
|
1
|
+
# @epoint-testtech/ep-stage-skill
|
|
2
2
|
|
|
3
3
|
> 胶水编码链路 Agent Skill — 解析上游物料,按契约生成可执行测试脚本
|
|
4
4
|
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
AI Agent 可发现的 Skill 入口(Claude Code 扫 `.claude/skills/`、Codex 扫 `.agents/skills/`,均 symlink 到包内源):
|
|
12
12
|
|
|
13
13
|
```text
|
|
14
|
-
.claude/skills/{glue-
|
|
15
|
-
.agents/skills/{glue-
|
|
14
|
+
.claude/skills/{glue-create-project, glue-generate-testcase, glue-run-test, recording-to-glue}
|
|
15
|
+
.agents/skills/{glue-create-project, glue-generate-testcase, glue-run-test, recording-to-glue}
|
|
16
16
|
→ packages/ep-stage-skill/codex-skill/ep-stage/<skill>/
|
|
17
17
|
```
|
|
18
18
|
|
|
@@ -31,11 +31,11 @@ AI Agent 可发现的 Skill 入口(Claude Code 扫 `.claude/skills/`、Codex
|
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
33
|
# 可观测默认入口(推荐)
|
|
34
|
-
pnpm --filter @epoint/ep-stage-skill run:gap-pipeline -- \
|
|
34
|
+
pnpm --filter @epoint-testtech/ep-stage-skill run:gap-pipeline -- \
|
|
35
35
|
--code-list <code_list.md>
|
|
36
36
|
|
|
37
37
|
# 生成契约
|
|
38
|
-
pnpm --filter @epoint/ep-stage-skill generate:crud-contract -- \
|
|
38
|
+
pnpm --filter @epoint-testtech/ep-stage-skill generate:crud-contract -- \
|
|
39
39
|
--module-id <模块ID> \
|
|
40
40
|
--docs <上游文档目录> \
|
|
41
41
|
--code-list <code_list.md> \
|
|
@@ -45,7 +45,7 @@ pnpm --filter @epoint/ep-stage-skill generate:crud-contract -- \
|
|
|
45
45
|
--out output/<module>.crud.contract.json
|
|
46
46
|
|
|
47
47
|
# 生成测试脚本
|
|
48
|
-
pnpm --filter @epoint/ep-stage-skill generate:playwright-tests -- \
|
|
48
|
+
pnpm --filter @epoint-testtech/ep-stage-skill generate:playwright-tests -- \
|
|
49
49
|
--contract output/<module>.crud.contract.json \
|
|
50
50
|
--out <glue项目>/src/tests/<name>.spec.ts \
|
|
51
51
|
--menu "一级菜单>二级菜单" \
|
package/docs/mvp-usage-guide.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# glue-test CRUD 契约生成器 — MVP 使用手册
|
|
1
|
+
# glue-run-test CRUD 契约生成器 — MVP 使用手册
|
|
2
2
|
|
|
3
3
|
## 概述
|
|
4
4
|
|
|
5
|
-
`glue-test` 是一个 TypeScript agent skill,用于从上游业务模块物料中提取结构化信息,归一化为 `crud-business-module/v1` 契约 JSON。该契约作为 AI Agent 生成 Playwright 测试脚本的地基,确保 Agent 不需要自己编造 selector、按钮文本或业务预期。
|
|
5
|
+
`glue-run-test` 是一个 TypeScript agent skill,用于从上游业务模块物料中提取结构化信息,归一化为 `crud-business-module/v1` 契约 JSON。该契约作为 AI Agent 生成 Playwright 测试脚本的地基,确保 Agent 不需要自己编造 selector、按钮文本或业务预期。
|
|
6
6
|
|
|
7
7
|
当前 MVP 只面向公司自研 `zwplace` CRUD 场景,负责:
|
|
8
8
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
## 数据流
|
|
15
15
|
|
|
16
16
|
```
|
|
17
|
-
上游物料 glue-test 输出
|
|
17
|
+
上游物料 glue-run-test 输出
|
|
18
18
|
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
19
19
|
│ HTML 页面 │──extractor──→ │ │ │ │
|
|
20
20
|
│ spec.yaml │──extractor──→ │ normalizer │──CLI 输出──→ │ 契约 JSON │
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@epoint-testtech/ep-stage-skill",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4-alpha.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ep-stage-skill": "./dist/src/cli/index.js"
|
|
9
|
+
},
|
|
7
10
|
"files": [
|
|
8
11
|
"dist",
|
|
9
12
|
"codex-skill",
|
|
@@ -31,8 +34,10 @@
|
|
|
31
34
|
"typecheck": "tsc --noEmit",
|
|
32
35
|
"test": "vitest run",
|
|
33
36
|
"build": "tsc",
|
|
34
|
-
"generate:crud-contract": "node --import tsx src/cli/
|
|
35
|
-
"generate:playwright-tests": "node --import tsx src/cli/
|
|
36
|
-
"run:gap-pipeline": "node --import tsx src/cli/run-gap-pipeline.ts"
|
|
37
|
+
"generate:crud-contract": "node --import tsx src/cli/dev/extract-contract.ts",
|
|
38
|
+
"generate:playwright-tests": "node --import tsx src/cli/index.ts spec",
|
|
39
|
+
"run:gap-pipeline": "node --import tsx src/cli/run-gap-pipeline.ts",
|
|
40
|
+
"cli": "node --import tsx src/cli/index.ts",
|
|
41
|
+
"generate:testcase": "node --import tsx src/cli/index.ts testcase"
|
|
37
42
|
}
|
|
38
43
|
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: create-project
|
|
3
|
-
description: 使用时机:当用户需要通过现有 stage-create CLI 创建 ep-stage Playwright 项目(尤其是 default 或 glue 模式项目)时使用。
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# ep-stage: create-project
|
|
7
|
-
|
|
8
|
-
当用户需要创建 ep-stage 项目,或要求生成 glue 模式(glue-mode)Playwright 脚手架时,使用本 skill。
|
|
9
|
-
|
|
10
|
-
## 行为契约(Behavior Contract)
|
|
11
|
-
|
|
12
|
-
本 skill 引导 Agent 调用现有 `@epoint/stage-create` CLI,不重新实现脚手架逻辑。
|
|
13
|
-
|
|
14
|
-
当前命令示例:
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
stage-create my-glue-project --mode glue
|
|
18
|
-
stage-create my-default-project --mode default
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## 必填输入
|
|
22
|
-
|
|
23
|
-
- 项目名称(project name):必填,缺失时必须向用户询问。
|
|
24
|
-
- 模式(mode):`glue` 或 `default`,缺失时必须向用户询问。
|
|
25
|
-
|
|
26
|
-
不要自行猜测项目名称。不要发明名为 `ep-stage: create-project` 的命令——那是 skill 名称,不是 shell 命令。
|
|
27
|
-
|
|
28
|
-
## 模式选择指引
|
|
29
|
-
|
|
30
|
-
- 当项目需要包含胶水骨架(glue skeletons)、页面组件(page components)、报告辅助(report helpers)以及物料驱动的测试生成时,使用 `glue` 模式。
|
|
31
|
-
- 当项目只需要标准 Playwright 基线时,使用 `default` 模式。
|
|
32
|
-
|
|
33
|
-
## 验证
|
|
34
|
-
|
|
35
|
-
创建项目后,验证输出目录存在且包含:
|
|
36
|
-
|
|
37
|
-
```text
|
|
38
|
-
package.json
|
|
39
|
-
playwright.config.ts
|
|
40
|
-
tsconfig.json
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
对于 `glue` 模式,还需验证:
|
|
44
|
-
|
|
45
|
-
```text
|
|
46
|
-
src/skeletons/
|
|
47
|
-
src/web/component/
|
|
48
|
-
src/report/
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## glue 项目上下文
|
|
52
|
-
|
|
53
|
-
创建 glue 项目后必须生成(由 stage-create CLI 交互写入):
|
|
54
|
-
|
|
55
|
-
- `<project>/stage-context.md`
|
|
56
|
-
- `<project>/.env`
|
|
57
|
-
- `~/.ep-stage/projects.index.json5`
|
|
58
|
-
|
|
59
|
-
这些文件供后续 `glue-testcase` 和 `glue-test` 内置读取,不依赖用户项目存在 `AGENTS.md`。
|