ado-sync 0.1.29 → 0.1.31
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 +469 -28
- package/dist/ai/summarizer.d.ts +73 -0
- package/dist/ai/summarizer.js +789 -0
- package/dist/ai/summarizer.js.map +1 -0
- package/dist/azure/test-cases.js +17 -2
- package/dist/azure/test-cases.js.map +1 -1
- package/dist/cli.js +29 -2
- package/dist/cli.js.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/parsers/csv.d.ts +13 -3
- package/dist/parsers/csv.js +87 -9
- package/dist/parsers/csv.js.map +1 -1
- package/dist/parsers/dart.d.ts +31 -0
- package/dist/parsers/dart.js +245 -0
- package/dist/parsers/dart.js.map +1 -0
- package/dist/parsers/excel.js +47 -8
- package/dist/parsers/excel.js.map +1 -1
- package/dist/parsers/javascript.d.ts +17 -7
- package/dist/parsers/javascript.js +20 -9
- package/dist/parsers/javascript.js.map +1 -1
- package/dist/parsers/swift.d.ts +34 -0
- package/dist/parsers/swift.js +250 -0
- package/dist/parsers/swift.js.map +1 -0
- package/dist/parsers/testcafe.d.ts +32 -0
- package/dist/parsers/testcafe.js +231 -0
- package/dist/parsers/testcafe.js.map +1 -0
- package/dist/sync/engine.d.ts +4 -1
- package/dist/sync/engine.js +48 -2
- package/dist/sync/engine.js.map +1 -1
- package/dist/sync/writeback.d.ts +6 -7
- package/dist/sync/writeback.js +22 -7
- package/dist/sync/writeback.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/docs/advanced.md +140 -0
- package/docs/configuration.md +1 -1
- package/docs/publish-test-results.md +48 -16
- package/docs/spec-formats.md +549 -0
- package/package.json +3 -2
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Swift / XCUITest parser for azure-test-sync.
|
|
4
|
+
*
|
|
5
|
+
* Handles iOS and macOS UI-automation tests written with Apple's XCTest framework.
|
|
6
|
+
* Tests are `func test*()` methods inside classes that extend `XCTestCase`.
|
|
7
|
+
*
|
|
8
|
+
* Detected test functions:
|
|
9
|
+
* func testUserCanLogin() { ... }
|
|
10
|
+
* func test_login_succeeds() { ... }
|
|
11
|
+
*
|
|
12
|
+
* Detected class blocks (used as the test group / describe equivalent):
|
|
13
|
+
* class LoginTests: XCTestCase { ... }
|
|
14
|
+
*
|
|
15
|
+
* Source mapping:
|
|
16
|
+
* /// Swift doc comment above func test* → TC Title (first non-numbered line)
|
|
17
|
+
* JSDoc /** ... * / doc comment above func test* → TC Title + Steps (same format)
|
|
18
|
+
* Numbered lines "N. text" → TC Steps (action)
|
|
19
|
+
* "N. Check: text" → TC Steps (expected result column)
|
|
20
|
+
* // @tags: smoke, regression → TC Tags (comma-separated list)
|
|
21
|
+
* // @smoke → TC Tag (single-word shorthand)
|
|
22
|
+
* // @{tagPrefix}:N comment above func → Azure TC ID (written back after push)
|
|
23
|
+
* {fileBasename} > {ClassName} > {method} → automatedTestName
|
|
24
|
+
*
|
|
25
|
+
* Method name → title fallback:
|
|
26
|
+
* "testUserCanLogin" → "User can login"
|
|
27
|
+
* "test_submit_form" → "Submit form"
|
|
28
|
+
*
|
|
29
|
+
* ID writeback:
|
|
30
|
+
* Inserts / updates // @tc:12345 immediately above the func test*() line.
|
|
31
|
+
*
|
|
32
|
+
* Path-based auto-tagging: directory segments starting with '@' become tags.
|
|
33
|
+
*/
|
|
34
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
35
|
+
if (k2 === undefined) k2 = k;
|
|
36
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
37
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
38
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
39
|
+
}
|
|
40
|
+
Object.defineProperty(o, k2, desc);
|
|
41
|
+
}) : (function(o, m, k, k2) {
|
|
42
|
+
if (k2 === undefined) k2 = k;
|
|
43
|
+
o[k2] = m[k];
|
|
44
|
+
}));
|
|
45
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
46
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
47
|
+
}) : function(o, v) {
|
|
48
|
+
o["default"] = v;
|
|
49
|
+
});
|
|
50
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
51
|
+
var ownKeys = function(o) {
|
|
52
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
53
|
+
var ar = [];
|
|
54
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
55
|
+
return ar;
|
|
56
|
+
};
|
|
57
|
+
return ownKeys(o);
|
|
58
|
+
};
|
|
59
|
+
return function (mod) {
|
|
60
|
+
if (mod && mod.__esModule) return mod;
|
|
61
|
+
var result = {};
|
|
62
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
63
|
+
__setModuleDefault(result, mod);
|
|
64
|
+
return result;
|
|
65
|
+
};
|
|
66
|
+
})();
|
|
67
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
68
|
+
exports.parseSwiftFile = parseSwiftFile;
|
|
69
|
+
const fs = __importStar(require("fs"));
|
|
70
|
+
const path = __importStar(require("path"));
|
|
71
|
+
const shared_1 = require("./shared");
|
|
72
|
+
// ─── Test method detection ────────────────────────────────────────────────────
|
|
73
|
+
const TEST_METHOD_RE = /^\s*func\s+(test\w+)\s*\(/;
|
|
74
|
+
const CLASS_RE = /^\s*(?:(?:open|final|public|internal|private)\s+)*class\s+(\w+)\s*(?::\s*[\w, ]+)?/;
|
|
75
|
+
// ─── Indentation helpers ──────────────────────────────────────────────────────
|
|
76
|
+
function getIndentLength(line) {
|
|
77
|
+
return (line.match(/^(\s*)/) ?? ['', ''])[1].length;
|
|
78
|
+
}
|
|
79
|
+
// ─── Enclosing class ──────────────────────────────────────────────────────────
|
|
80
|
+
function findEnclosingClass(lines, methodLineIdx) {
|
|
81
|
+
const methodIndent = getIndentLength(lines[methodLineIdx]);
|
|
82
|
+
for (let i = methodLineIdx - 1; i >= 0; i--) {
|
|
83
|
+
const lineIndent = getIndentLength(lines[i]);
|
|
84
|
+
if (lineIndent < methodIndent) {
|
|
85
|
+
const m = lines[i].trim().match(CLASS_RE);
|
|
86
|
+
if (m)
|
|
87
|
+
return m[1];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
// ─── Doc comment extraction ───────────────────────────────────────────────────
|
|
93
|
+
/**
|
|
94
|
+
* Extract Swift doc comments above func test*().
|
|
95
|
+
* Handles both triple-slash (///) single-line and block (/* * /) style.
|
|
96
|
+
* Skips blank lines and single-line // comments on the way up.
|
|
97
|
+
*/
|
|
98
|
+
function extractDocBefore(lines, methodLineIdx) {
|
|
99
|
+
let i = methodLineIdx - 1;
|
|
100
|
+
// Skip blank lines and non-doc single-line comments
|
|
101
|
+
while (i >= 0) {
|
|
102
|
+
const t = lines[i].trim();
|
|
103
|
+
if (t === '' || (t.startsWith('//') && !t.startsWith('///'))) {
|
|
104
|
+
i--;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
if (i < 0)
|
|
110
|
+
return [];
|
|
111
|
+
// /// triple-slash style (Swift idiomatic)
|
|
112
|
+
if (lines[i].trim().startsWith('///')) {
|
|
113
|
+
const docLines = [];
|
|
114
|
+
while (i >= 0 && lines[i].trim().startsWith('///')) {
|
|
115
|
+
docLines.unshift(lines[i].trim().replace(/^\/\/\/\s?/, ''));
|
|
116
|
+
i--;
|
|
117
|
+
}
|
|
118
|
+
return docLines.filter((l) => l !== '');
|
|
119
|
+
}
|
|
120
|
+
// /** ... */ block style
|
|
121
|
+
if (!lines[i].trim().endsWith('*/'))
|
|
122
|
+
return [];
|
|
123
|
+
const raw = [];
|
|
124
|
+
raw.unshift(lines[i]);
|
|
125
|
+
i--;
|
|
126
|
+
while (i >= 0) {
|
|
127
|
+
raw.unshift(lines[i]);
|
|
128
|
+
if (lines[i].trim().startsWith('/**') || lines[i].trim().startsWith('/*'))
|
|
129
|
+
break;
|
|
130
|
+
i--;
|
|
131
|
+
}
|
|
132
|
+
return raw
|
|
133
|
+
.map((l) => l
|
|
134
|
+
.replace(/^\s*\/\*\*?\s?/, '')
|
|
135
|
+
.replace(/\s*\*\/\s*$/, '')
|
|
136
|
+
.replace(/^\s*\*\s?/, '')
|
|
137
|
+
.trim())
|
|
138
|
+
.filter((l) => l !== '');
|
|
139
|
+
}
|
|
140
|
+
function extractCommentMetadataAbove(lines, methodLineIdx, tagPrefix) {
|
|
141
|
+
const tags = [];
|
|
142
|
+
let azureId;
|
|
143
|
+
const idRe = new RegExp(`//\\s*@${tagPrefix}:(\\d+)`);
|
|
144
|
+
const tagsListRe = /\/\/\s*@tags?\s*:\s*(.+)/i;
|
|
145
|
+
const singleTagRe = /\/\/\s*@(\w+)\s*$/;
|
|
146
|
+
for (let i = methodLineIdx - 1; i >= 0 && i >= methodLineIdx - 25; i--) {
|
|
147
|
+
const trimmed = lines[i].trim();
|
|
148
|
+
if (trimmed === '')
|
|
149
|
+
break;
|
|
150
|
+
if (!trimmed.startsWith('//') && !trimmed.startsWith('*') && !trimmed.startsWith('/*'))
|
|
151
|
+
break;
|
|
152
|
+
const idMatch = trimmed.match(idRe);
|
|
153
|
+
if (idMatch && azureId === undefined) {
|
|
154
|
+
azureId = parseInt(idMatch[1], 10);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const tagsMatch = trimmed.match(tagsListRe);
|
|
158
|
+
if (tagsMatch) {
|
|
159
|
+
tags.push(...tagsMatch[1].split(',').map((t) => t.trim()).filter(Boolean));
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const singleTag = trimmed.match(singleTagRe);
|
|
163
|
+
if (singleTag && singleTag[1] !== tagPrefix) {
|
|
164
|
+
tags.push(singleTag[1]);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { azureId, tags };
|
|
169
|
+
}
|
|
170
|
+
// ─── Method name → human-readable title ──────────────────────────────────────
|
|
171
|
+
/**
|
|
172
|
+
* Convert a Swift test method name to a sentence-style title.
|
|
173
|
+
* testUserCanLogin → "User can login"
|
|
174
|
+
* testLoginFails_badPassword → "Login fails bad password"
|
|
175
|
+
* test_submit_form → "Submit form"
|
|
176
|
+
*/
|
|
177
|
+
function methodNameToTitle(name) {
|
|
178
|
+
let s = name.replace(/^test_?/, '');
|
|
179
|
+
if (!s)
|
|
180
|
+
return name;
|
|
181
|
+
// snake_case → words
|
|
182
|
+
s = s.replace(/_/g, ' ');
|
|
183
|
+
// camelCase → words
|
|
184
|
+
s = s.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2');
|
|
185
|
+
s = s.toLowerCase().replace(/^./, (c) => c.toUpperCase());
|
|
186
|
+
return s;
|
|
187
|
+
}
|
|
188
|
+
// ─── Doc → title + steps ─────────────────────────────────────────────────────
|
|
189
|
+
const NUMBERED_STEP_RE = /^\d+\.\s+(.+)$/;
|
|
190
|
+
const CHECK_RE = /^[Cc]heck:\s+(.+)$/;
|
|
191
|
+
const META_RE = /^(?:test\s+case|user\s+story)[\s:]/i;
|
|
192
|
+
function parseSummary(docLines, fallbackTitle) {
|
|
193
|
+
let title = '';
|
|
194
|
+
const steps = [];
|
|
195
|
+
for (const line of docLines) {
|
|
196
|
+
if (!line || META_RE.test(line))
|
|
197
|
+
continue;
|
|
198
|
+
const numMatch = NUMBERED_STEP_RE.exec(line);
|
|
199
|
+
if (numMatch) {
|
|
200
|
+
const content = numMatch[1].trim();
|
|
201
|
+
const checkMatch = CHECK_RE.exec(content);
|
|
202
|
+
if (checkMatch) {
|
|
203
|
+
steps.push({ keyword: 'Then', text: checkMatch[1].trim() });
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
steps.push({ keyword: 'Step', text: content });
|
|
207
|
+
}
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (!title)
|
|
211
|
+
title = line;
|
|
212
|
+
}
|
|
213
|
+
return { title: title || fallbackTitle, steps };
|
|
214
|
+
}
|
|
215
|
+
// ─── Public parser ────────────────────────────────────────────────────────────
|
|
216
|
+
function parseSwiftFile(filePath, tagPrefix, linkConfigs) {
|
|
217
|
+
const source = fs.readFileSync(filePath, 'utf8');
|
|
218
|
+
const lines = source.split('\n');
|
|
219
|
+
const fileBaseName = path.basename(filePath).replace(/\.swift$/, '');
|
|
220
|
+
const pathTags = (0, shared_1.extractPathTags)(filePath);
|
|
221
|
+
const results = [];
|
|
222
|
+
for (let i = 0; i < lines.length; i++) {
|
|
223
|
+
const m = lines[i].match(TEST_METHOD_RE);
|
|
224
|
+
if (!m)
|
|
225
|
+
continue;
|
|
226
|
+
const methodName = m[1];
|
|
227
|
+
const methodLineIdx = i;
|
|
228
|
+
const docLines = extractDocBefore(lines, methodLineIdx);
|
|
229
|
+
const { azureId, tags: cTags } = extractCommentMetadataAbove(lines, methodLineIdx, tagPrefix);
|
|
230
|
+
const className = findEnclosingClass(lines, methodLineIdx);
|
|
231
|
+
const allTags = [...new Set([...pathTags, ...cTags])];
|
|
232
|
+
const fallbackTitle = methodNameToTitle(methodName);
|
|
233
|
+
const { title, steps } = parseSummary(docLines, fallbackTitle);
|
|
234
|
+
const automatedTestName = className
|
|
235
|
+
? [fileBaseName, className, methodName].join(' > ')
|
|
236
|
+
: [fileBaseName, methodName].join(' > ');
|
|
237
|
+
results.push({
|
|
238
|
+
filePath,
|
|
239
|
+
title,
|
|
240
|
+
steps,
|
|
241
|
+
tags: allTags,
|
|
242
|
+
azureId: azureId !== undefined && !isNaN(azureId) ? azureId : undefined,
|
|
243
|
+
line: methodLineIdx + 1,
|
|
244
|
+
linkRefs: (0, shared_1.extractLinkRefs)(allTags, linkConfigs),
|
|
245
|
+
automatedTestName,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return results;
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=swift.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swift.js","sourceRoot":"","sources":["../../src/parsers/swift.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TestCafe test parser for azure-test-sync.
|
|
3
|
+
*
|
|
4
|
+
* TestCafe uses a fixture / test API that is fundamentally different from
|
|
5
|
+
* describe() / it(), so it gets its own parser.
|
|
6
|
+
*
|
|
7
|
+
* Detected test functions:
|
|
8
|
+
* test('title', async t => { ... })
|
|
9
|
+
* test.skip('title', async t => { ... })
|
|
10
|
+
* test.only('title', async t => { ... })
|
|
11
|
+
*
|
|
12
|
+
* Detected fixture blocks (used as the test group / describe equivalent):
|
|
13
|
+
* fixture('Fixture title')
|
|
14
|
+
* fixture.skip('Fixture title')
|
|
15
|
+
* fixture.only('Fixture title')
|
|
16
|
+
*
|
|
17
|
+
* Source mapping:
|
|
18
|
+
* JSDoc /** ... * / first non-numbered line → TC Title
|
|
19
|
+
* Numbered lines "N. text" → TC Steps (action)
|
|
20
|
+
* "N. Check: text" → TC Steps (expected result column)
|
|
21
|
+
* // @tags: smoke, regression → TC Tags (comma-separated list)
|
|
22
|
+
* // @smoke → TC Tag (single-word shorthand)
|
|
23
|
+
* // @{tagPrefix}:N comment above test() → Azure TC ID (written back after push)
|
|
24
|
+
* {fileBasename} > {fixture} > {test title} → automatedTestName
|
|
25
|
+
*
|
|
26
|
+
* ID writeback:
|
|
27
|
+
* Inserts / updates // @tc:12345 immediately above the test() line.
|
|
28
|
+
*
|
|
29
|
+
* Path-based auto-tagging: directory segments starting with '@' become tags.
|
|
30
|
+
*/
|
|
31
|
+
import { LinkConfig, ParsedTest } from '../types';
|
|
32
|
+
export declare function parseTestCafeFile(filePath: string, tagPrefix: string, linkConfigs?: LinkConfig[]): ParsedTest[];
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* TestCafe test parser for azure-test-sync.
|
|
4
|
+
*
|
|
5
|
+
* TestCafe uses a fixture / test API that is fundamentally different from
|
|
6
|
+
* describe() / it(), so it gets its own parser.
|
|
7
|
+
*
|
|
8
|
+
* Detected test functions:
|
|
9
|
+
* test('title', async t => { ... })
|
|
10
|
+
* test.skip('title', async t => { ... })
|
|
11
|
+
* test.only('title', async t => { ... })
|
|
12
|
+
*
|
|
13
|
+
* Detected fixture blocks (used as the test group / describe equivalent):
|
|
14
|
+
* fixture('Fixture title')
|
|
15
|
+
* fixture.skip('Fixture title')
|
|
16
|
+
* fixture.only('Fixture title')
|
|
17
|
+
*
|
|
18
|
+
* Source mapping:
|
|
19
|
+
* JSDoc /** ... * / first non-numbered line → TC Title
|
|
20
|
+
* Numbered lines "N. text" → TC Steps (action)
|
|
21
|
+
* "N. Check: text" → TC Steps (expected result column)
|
|
22
|
+
* // @tags: smoke, regression → TC Tags (comma-separated list)
|
|
23
|
+
* // @smoke → TC Tag (single-word shorthand)
|
|
24
|
+
* // @{tagPrefix}:N comment above test() → Azure TC ID (written back after push)
|
|
25
|
+
* {fileBasename} > {fixture} > {test title} → automatedTestName
|
|
26
|
+
*
|
|
27
|
+
* ID writeback:
|
|
28
|
+
* Inserts / updates // @tc:12345 immediately above the test() line.
|
|
29
|
+
*
|
|
30
|
+
* Path-based auto-tagging: directory segments starting with '@' become tags.
|
|
31
|
+
*/
|
|
32
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
33
|
+
if (k2 === undefined) k2 = k;
|
|
34
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
35
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
36
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
37
|
+
}
|
|
38
|
+
Object.defineProperty(o, k2, desc);
|
|
39
|
+
}) : (function(o, m, k, k2) {
|
|
40
|
+
if (k2 === undefined) k2 = k;
|
|
41
|
+
o[k2] = m[k];
|
|
42
|
+
}));
|
|
43
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
44
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
45
|
+
}) : function(o, v) {
|
|
46
|
+
o["default"] = v;
|
|
47
|
+
});
|
|
48
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
49
|
+
var ownKeys = function(o) {
|
|
50
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
51
|
+
var ar = [];
|
|
52
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
53
|
+
return ar;
|
|
54
|
+
};
|
|
55
|
+
return ownKeys(o);
|
|
56
|
+
};
|
|
57
|
+
return function (mod) {
|
|
58
|
+
if (mod && mod.__esModule) return mod;
|
|
59
|
+
var result = {};
|
|
60
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
61
|
+
__setModuleDefault(result, mod);
|
|
62
|
+
return result;
|
|
63
|
+
};
|
|
64
|
+
})();
|
|
65
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
66
|
+
exports.parseTestCafeFile = parseTestCafeFile;
|
|
67
|
+
const fs = __importStar(require("fs"));
|
|
68
|
+
const path = __importStar(require("path"));
|
|
69
|
+
const shared_1 = require("./shared");
|
|
70
|
+
// ─── Test / fixture detection ─────────────────────────────────────────────────
|
|
71
|
+
const TEST_CALL_RE = /^(?:test|test\.skip|test\.only)\s*\(/;
|
|
72
|
+
const TEST_TITLE_RE = /^(?:test|test\.skip|test\.only)\s*\(\s*(['"`])((?:\\.|[^\\])*?)\1/;
|
|
73
|
+
const FIXTURE_TITLE_RE = /^(?:fixture|fixture\.skip|fixture\.only)\s*\(\s*(['"`])((?:\\.|[^\\])*?)\1/;
|
|
74
|
+
// ─── Indentation helpers ──────────────────────────────────────────────────────
|
|
75
|
+
function getIndentLength(line) {
|
|
76
|
+
return (line.match(/^(\s*)/) ?? ['', ''])[1].length;
|
|
77
|
+
}
|
|
78
|
+
// ─── Enclosing fixture ────────────────────────────────────────────────────────
|
|
79
|
+
/**
|
|
80
|
+
* Walk backward from the test() line to find the most recent fixture() title.
|
|
81
|
+
*/
|
|
82
|
+
function findEnclosingFixture(lines, testLineIdx) {
|
|
83
|
+
const testIndent = getIndentLength(lines[testLineIdx]);
|
|
84
|
+
for (let i = testLineIdx - 1; i >= 0; i--) {
|
|
85
|
+
const line = lines[i];
|
|
86
|
+
const trimmed = line.trim();
|
|
87
|
+
if (!trimmed)
|
|
88
|
+
continue;
|
|
89
|
+
const lineIndent = getIndentLength(line);
|
|
90
|
+
if (lineIndent <= testIndent) {
|
|
91
|
+
const m = trimmed.match(FIXTURE_TITLE_RE);
|
|
92
|
+
if (m) {
|
|
93
|
+
return m[2].replace(/\\'/g, "'").replace(/\\"/g, '"').replace(/\\`/g, '`');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
// ─── JSDoc extraction ─────────────────────────────────────────────────────────
|
|
100
|
+
function extractJsdocBefore(lines, testLineIdx) {
|
|
101
|
+
let i = testLineIdx - 1;
|
|
102
|
+
while (i >= 0) {
|
|
103
|
+
const t = lines[i].trim();
|
|
104
|
+
if (t === '' || t.startsWith('//')) {
|
|
105
|
+
i--;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
if (i < 0 || !lines[i].trim().endsWith('*/'))
|
|
111
|
+
return [];
|
|
112
|
+
const raw = [];
|
|
113
|
+
raw.unshift(lines[i]);
|
|
114
|
+
i--;
|
|
115
|
+
while (i >= 0) {
|
|
116
|
+
raw.unshift(lines[i]);
|
|
117
|
+
if (lines[i].trim().startsWith('/**'))
|
|
118
|
+
break;
|
|
119
|
+
i--;
|
|
120
|
+
}
|
|
121
|
+
return raw
|
|
122
|
+
.map((l) => l
|
|
123
|
+
.replace(/^\s*\/\*\*\s?/, '')
|
|
124
|
+
.replace(/\s*\*\/\s*$/, '')
|
|
125
|
+
.replace(/^\s*\*\s?/, '')
|
|
126
|
+
.trim())
|
|
127
|
+
.filter((l) => l !== '');
|
|
128
|
+
}
|
|
129
|
+
function extractCommentMetadataAbove(lines, testLineIdx, tagPrefix) {
|
|
130
|
+
const tags = [];
|
|
131
|
+
let azureId;
|
|
132
|
+
const idRe = new RegExp(`//\\s*@${tagPrefix}:(\\d+)`);
|
|
133
|
+
const tagsListRe = /\/\/\s*@tags?\s*:\s*(.+)/i;
|
|
134
|
+
const singleTagRe = /\/\/\s*@(\w+)\s*$/;
|
|
135
|
+
for (let i = testLineIdx - 1; i >= 0 && i >= testLineIdx - 25; i--) {
|
|
136
|
+
const trimmed = lines[i].trim();
|
|
137
|
+
if (trimmed === '')
|
|
138
|
+
break;
|
|
139
|
+
if (!trimmed.startsWith('//') && !trimmed.startsWith('*') && !trimmed.startsWith('/*'))
|
|
140
|
+
break;
|
|
141
|
+
const idMatch = trimmed.match(idRe);
|
|
142
|
+
if (idMatch && azureId === undefined) {
|
|
143
|
+
azureId = parseInt(idMatch[1], 10);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const tagsMatch = trimmed.match(tagsListRe);
|
|
147
|
+
if (tagsMatch) {
|
|
148
|
+
tags.push(...tagsMatch[1].split(',').map((t) => t.trim()).filter(Boolean));
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const singleTag = trimmed.match(singleTagRe);
|
|
152
|
+
if (singleTag && singleTag[1] !== tagPrefix) {
|
|
153
|
+
tags.push(singleTag[1]);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { azureId, tags };
|
|
158
|
+
}
|
|
159
|
+
// ─── JSDoc → title + steps ────────────────────────────────────────────────────
|
|
160
|
+
const NUMBERED_STEP_RE = /^\d+\.\s+(.+)$/;
|
|
161
|
+
const CHECK_RE = /^[Cc]heck:\s+(.+)$/;
|
|
162
|
+
const META_RE = /^(?:test\s+case|user\s+story)[\s:]/i;
|
|
163
|
+
function parseSummary(jsdocLines, fallbackTitle) {
|
|
164
|
+
let title = '';
|
|
165
|
+
const steps = [];
|
|
166
|
+
for (const line of jsdocLines) {
|
|
167
|
+
if (!line || META_RE.test(line))
|
|
168
|
+
continue;
|
|
169
|
+
const numMatch = NUMBERED_STEP_RE.exec(line);
|
|
170
|
+
if (numMatch) {
|
|
171
|
+
const content = numMatch[1].trim();
|
|
172
|
+
const checkMatch = CHECK_RE.exec(content);
|
|
173
|
+
if (checkMatch) {
|
|
174
|
+
steps.push({ keyword: 'Then', text: checkMatch[1].trim() });
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
steps.push({ keyword: 'Step', text: content });
|
|
178
|
+
}
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (!title)
|
|
182
|
+
title = line;
|
|
183
|
+
}
|
|
184
|
+
return { title: title || fallbackTitle, steps };
|
|
185
|
+
}
|
|
186
|
+
// ─── Public parser ────────────────────────────────────────────────────────────
|
|
187
|
+
function parseTestCafeFile(filePath, tagPrefix, linkConfigs) {
|
|
188
|
+
const source = fs.readFileSync(filePath, 'utf8');
|
|
189
|
+
const lines = source.split('\n');
|
|
190
|
+
const fileBaseName = path.basename(filePath)
|
|
191
|
+
.replace(/\.(spec|test)\.(js|ts|mjs|cjs)$/, '')
|
|
192
|
+
.replace(/\.(js|ts|mjs|cjs)$/, '');
|
|
193
|
+
const pathTags = (0, shared_1.extractPathTags)(filePath);
|
|
194
|
+
const results = [];
|
|
195
|
+
for (let i = 0; i < lines.length; i++) {
|
|
196
|
+
const trimmed = lines[i].trim();
|
|
197
|
+
if (!TEST_CALL_RE.test(trimmed))
|
|
198
|
+
continue;
|
|
199
|
+
const testLineIdx = i;
|
|
200
|
+
const m = trimmed.match(TEST_TITLE_RE);
|
|
201
|
+
if (!m)
|
|
202
|
+
continue;
|
|
203
|
+
const callTitle = m[2]
|
|
204
|
+
.replace(/\\'/g, "'")
|
|
205
|
+
.replace(/\\"/g, '"')
|
|
206
|
+
.replace(/\\`/g, '`')
|
|
207
|
+
.replace(/\\\\/g, '\\');
|
|
208
|
+
if (!callTitle)
|
|
209
|
+
continue;
|
|
210
|
+
const jsdocLines = extractJsdocBefore(lines, testLineIdx);
|
|
211
|
+
const { azureId, tags: cTags } = extractCommentMetadataAbove(lines, testLineIdx, tagPrefix);
|
|
212
|
+
const fixture = findEnclosingFixture(lines, testLineIdx);
|
|
213
|
+
const allTags = [...new Set([...pathTags, ...cTags])];
|
|
214
|
+
const { title, steps } = parseSummary(jsdocLines, callTitle);
|
|
215
|
+
const automatedTestName = fixture
|
|
216
|
+
? [fileBaseName, fixture, callTitle].join(' > ')
|
|
217
|
+
: [fileBaseName, callTitle].join(' > ');
|
|
218
|
+
results.push({
|
|
219
|
+
filePath,
|
|
220
|
+
title,
|
|
221
|
+
steps,
|
|
222
|
+
tags: allTags,
|
|
223
|
+
azureId: azureId !== undefined && !isNaN(azureId) ? azureId : undefined,
|
|
224
|
+
line: testLineIdx + 1,
|
|
225
|
+
linkRefs: (0, shared_1.extractLinkRefs)(allTags, linkConfigs),
|
|
226
|
+
automatedTestName,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
return results;
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=testcafe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testcafe.js","sourceRoot":"","sources":["../../src/parsers/testcafe.ts"],"names":[],"mappings":"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"}
|
package/dist/sync/engine.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sync engine — orchestrates push, pull, and status operations.
|
|
3
3
|
*/
|
|
4
|
+
import { AiSummaryOpts } from '../ai/summarizer';
|
|
4
5
|
import { SyncConfig, SyncResult } from '../types';
|
|
5
6
|
export interface SyncOpts {
|
|
6
7
|
dryRun?: boolean;
|
|
7
8
|
tags?: string;
|
|
8
9
|
/** Called after each test case is processed. Useful for rendering a live progress bar. */
|
|
9
10
|
onProgress?: (done: number, total: number, result: SyncResult) => void;
|
|
11
|
+
/** AI auto-summary options: generate title/steps for tests that have none. */
|
|
12
|
+
aiSummary?: AiSummaryOpts;
|
|
10
13
|
}
|
|
11
14
|
export declare function push(config: SyncConfig, configDir: string, opts?: SyncOpts): Promise<SyncResult[]>;
|
|
12
15
|
export declare function pull(config: SyncConfig, configDir: string, opts?: SyncOpts): Promise<SyncResult[]>;
|
|
13
|
-
export declare function status(config: SyncConfig, configDir: string, opts?: Pick<SyncOpts, 'tags' | 'onProgress'>): Promise<SyncResult[]>;
|
|
16
|
+
export declare function status(config: SyncConfig, configDir: string, opts?: Pick<SyncOpts, 'tags' | 'onProgress' | 'aiSummary'>): Promise<SyncResult[]>;
|
package/dist/sync/engine.js
CHANGED
|
@@ -46,16 +46,20 @@ const tag_expressions_1 = __importDefault(require("@cucumber/tag-expressions"));
|
|
|
46
46
|
const fs = __importStar(require("fs"));
|
|
47
47
|
const glob_1 = require("glob");
|
|
48
48
|
const path = __importStar(require("path"));
|
|
49
|
+
const summarizer_1 = require("../ai/summarizer");
|
|
49
50
|
const client_1 = require("../azure/client");
|
|
50
51
|
const test_cases_1 = require("../azure/test-cases");
|
|
51
52
|
const csharp_1 = require("../parsers/csharp");
|
|
52
53
|
const csv_1 = require("../parsers/csv");
|
|
54
|
+
const dart_1 = require("../parsers/dart");
|
|
53
55
|
const excel_1 = require("../parsers/excel");
|
|
54
56
|
const gherkin_1 = require("../parsers/gherkin");
|
|
55
57
|
const java_1 = require("../parsers/java");
|
|
56
58
|
const javascript_1 = require("../parsers/javascript");
|
|
57
59
|
const markdown_1 = require("../parsers/markdown");
|
|
58
60
|
const python_1 = require("../parsers/python");
|
|
61
|
+
const swift_1 = require("../parsers/swift");
|
|
62
|
+
const testcafe_1 = require("../parsers/testcafe");
|
|
59
63
|
const cache_1 = require("./cache");
|
|
60
64
|
const writeback_1 = require("./writeback");
|
|
61
65
|
// ─── Tag filtering ────────────────────────────────────────────────────────────
|
|
@@ -116,8 +120,24 @@ async function parseLocalFiles(filePaths, config, tagsFilter) {
|
|
|
116
120
|
tests = (0, python_1.parsePythonFile)(fp, tagPrefix, linkConfigs);
|
|
117
121
|
break;
|
|
118
122
|
case 'javascript':
|
|
123
|
+
case 'playwright':
|
|
124
|
+
case 'puppeteer':
|
|
125
|
+
case 'cypress':
|
|
126
|
+
case 'detox':
|
|
119
127
|
tests = (0, javascript_1.parseJavaScriptFile)(fp, tagPrefix, linkConfigs);
|
|
120
128
|
break;
|
|
129
|
+
case 'testcafe':
|
|
130
|
+
tests = (0, testcafe_1.parseTestCafeFile)(fp, tagPrefix, linkConfigs);
|
|
131
|
+
break;
|
|
132
|
+
case 'espresso':
|
|
133
|
+
tests = (0, java_1.parseJavaFile)(fp, tagPrefix, linkConfigs);
|
|
134
|
+
break;
|
|
135
|
+
case 'xcuitest':
|
|
136
|
+
tests = (0, swift_1.parseSwiftFile)(fp, tagPrefix, linkConfigs);
|
|
137
|
+
break;
|
|
138
|
+
case 'flutter':
|
|
139
|
+
tests = (0, dart_1.parseDartFile)(fp, tagPrefix, linkConfigs);
|
|
140
|
+
break;
|
|
121
141
|
default:
|
|
122
142
|
tests = (0, markdown_1.parseMarkdownFile)(fp, tagPrefix, linkConfigs, attachmentsConfig);
|
|
123
143
|
}
|
|
@@ -171,6 +191,28 @@ async function push(config, configDir, opts = {}) {
|
|
|
171
191
|
async function pushSingle(config, configDir, opts) {
|
|
172
192
|
const files = await discoverFiles(config.local.include, config.local.exclude, configDir);
|
|
173
193
|
const tests = await parseLocalFiles(files, config, opts.tags);
|
|
194
|
+
// AI auto-summary: for code-based local types, default to the local node-llama-cpp
|
|
195
|
+
// provider (with heuristic fallback) when no explicit aiSummary opts are provided.
|
|
196
|
+
// If no GGUF model path is set, the local provider transparently falls back to
|
|
197
|
+
// heuristic mode so the push always succeeds even without a model installed.
|
|
198
|
+
const CODE_TYPES = new Set(['javascript', 'playwright', 'puppeteer', 'cypress', 'testcafe', 'detox', 'espresso', 'xcuitest', 'flutter', 'java', 'csharp', 'python']);
|
|
199
|
+
const effectiveAiOpts = opts.aiSummary ?? (CODE_TYPES.has(config.local.type) ? { provider: 'local', heuristicFallback: true } : undefined);
|
|
200
|
+
if (effectiveAiOpts) {
|
|
201
|
+
for (const test of tests) {
|
|
202
|
+
const needsSteps = test.steps.length === 0;
|
|
203
|
+
const needsDescription = !test.description;
|
|
204
|
+
if (needsSteps || needsDescription) {
|
|
205
|
+
const result = await (0, summarizer_1.summarizeTest)(test, config.local.type, effectiveAiOpts);
|
|
206
|
+
if (needsSteps) {
|
|
207
|
+
test.title = result.title;
|
|
208
|
+
test.steps = result.steps;
|
|
209
|
+
}
|
|
210
|
+
if (needsDescription && result.description) {
|
|
211
|
+
test.description = result.description;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
174
216
|
const client = await client_1.AzureClient.create(config);
|
|
175
217
|
const tagPrefix = config.sync?.tagPrefix ?? 'tc';
|
|
176
218
|
const titleField = config.sync?.titleField ?? 'System.Title';
|
|
@@ -467,7 +509,7 @@ async function pullSingle(config, configDir, opts) {
|
|
|
467
509
|
}
|
|
468
510
|
// ─── Status ───────────────────────────────────────────────────────────────────
|
|
469
511
|
async function status(config, configDir, opts = {}) {
|
|
470
|
-
return push(config, configDir, { dryRun: true, tags: opts.tags, onProgress: opts.onProgress });
|
|
512
|
+
return push(config, configDir, { dryRun: true, tags: opts.tags, onProgress: opts.onProgress, aiSummary: opts.aiSummary });
|
|
471
513
|
}
|
|
472
514
|
// ─── Cache helpers ────────────────────────────────────────────────────────────
|
|
473
515
|
function updateCacheEntry(cache, test, remote) {
|
|
@@ -491,7 +533,11 @@ function applyRemoteToLocal(test, newTitle, newSteps, newDescription, localType,
|
|
|
491
533
|
else if (localType === 'markdown') {
|
|
492
534
|
applyRemoteToMarkdown(test, newTitle, newSteps, newDescription, tagPrefix);
|
|
493
535
|
}
|
|
494
|
-
|
|
536
|
+
else if (localType === 'csv') {
|
|
537
|
+
(0, csv_1.applyRemoteToCsv)(test.filePath, test.title, newTitle, newSteps);
|
|
538
|
+
}
|
|
539
|
+
// excel: pull not yet supported (in-place XML surgery for xlsx is complex)
|
|
540
|
+
// csharp / java / python / javascript: pull not supported (code files are managed locally)
|
|
495
541
|
}
|
|
496
542
|
function applyRemoteToGherkin(test, newTitle, newSteps) {
|
|
497
543
|
const raw = fs.readFileSync(test.filePath, 'utf8');
|