@vscjava/vscode-autotest 0.1.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/README.md +276 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.js +69 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/drivers/vscodeDriver.d.ts +179 -0
- package/dist/drivers/vscodeDriver.js +886 -0
- package/dist/drivers/vscodeDriver.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/operators/actionResolver.d.ts +23 -0
- package/dist/operators/actionResolver.js +186 -0
- package/dist/operators/actionResolver.js.map +1 -0
- package/dist/operators/llmClient.d.ts +33 -0
- package/dist/operators/llmClient.js +117 -0
- package/dist/operators/llmClient.js.map +1 -0
- package/dist/operators/planParser.d.ts +10 -0
- package/dist/operators/planParser.js +92 -0
- package/dist/operators/planParser.js.map +1 -0
- package/dist/operators/stepVerifier.d.ts +34 -0
- package/dist/operators/stepVerifier.js +182 -0
- package/dist/operators/stepVerifier.js.map +1 -0
- package/dist/operators/testRunner.d.ts +29 -0
- package/dist/operators/testRunner.js +181 -0
- package/dist/operators/testRunner.js.map +1 -0
- package/dist/types.d.ts +137 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StepVerifier — runs verification checks for a test step.
|
|
3
|
+
*
|
|
4
|
+
* Supports both deterministic checks (verifyFile, verifyEditor, verifyProblems, etc.)
|
|
5
|
+
* and LLM-powered natural language verification (verify field + screenshot).
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from "node:fs";
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
export class StepVerifier {
|
|
10
|
+
driver;
|
|
11
|
+
llm;
|
|
12
|
+
constructor(driver, options = {}) {
|
|
13
|
+
this.driver = driver;
|
|
14
|
+
this.llm = options.llmClient ?? null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Verify a step against all its verification criteria.
|
|
18
|
+
* Runs deterministic checks first, then LLM verification if needed.
|
|
19
|
+
*
|
|
20
|
+
* @param screenshotPath — path to the after-action screenshot (for LLM verify)
|
|
21
|
+
*/
|
|
22
|
+
async verify(step, screenshotPath) {
|
|
23
|
+
// If no verification defined, auto-pass
|
|
24
|
+
if (!step.verify && !step.verifyFile && !step.verifyNotification
|
|
25
|
+
&& !step.verifyEditor && !step.verifyProblems && !step.verifyCompletion) {
|
|
26
|
+
return { passed: true };
|
|
27
|
+
}
|
|
28
|
+
// ── Deterministic verifications (run all, fail fast) ──
|
|
29
|
+
const fileResult = await this.verifyFile(step);
|
|
30
|
+
if (fileResult && !fileResult.passed)
|
|
31
|
+
return fileResult;
|
|
32
|
+
const notifResult = await this.verifyNotification(step);
|
|
33
|
+
if (notifResult && !notifResult.passed)
|
|
34
|
+
return notifResult;
|
|
35
|
+
const editorResult = await this.verifyEditor(step);
|
|
36
|
+
if (editorResult && !editorResult.passed)
|
|
37
|
+
return editorResult;
|
|
38
|
+
const problemsResult = await this.verifyProblems(step);
|
|
39
|
+
if (problemsResult && !problemsResult.passed)
|
|
40
|
+
return problemsResult;
|
|
41
|
+
const completionResult = await this.verifyCompletion(step);
|
|
42
|
+
if (completionResult && !completionResult.passed)
|
|
43
|
+
return completionResult;
|
|
44
|
+
// ── LLM verification for natural language `verify` field ──
|
|
45
|
+
if (step.verify) {
|
|
46
|
+
const llmResult = await this.verifyWithLLM(step.verify, screenshotPath);
|
|
47
|
+
return llmResult;
|
|
48
|
+
}
|
|
49
|
+
return { passed: true };
|
|
50
|
+
}
|
|
51
|
+
// ─── Deterministic Verifiers ─────────────────────────────
|
|
52
|
+
async verifyFile(step) {
|
|
53
|
+
if (!step.verifyFile)
|
|
54
|
+
return null;
|
|
55
|
+
const filePath = path.resolve(step.verifyFile.path);
|
|
56
|
+
if (step.verifyFile.exists === false) {
|
|
57
|
+
const exists = await this.driver.fileExists(filePath);
|
|
58
|
+
if (exists)
|
|
59
|
+
return { passed: false, reason: `File should not exist: ${filePath}` };
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
if (!await this.driver.fileExists(filePath)) {
|
|
63
|
+
return { passed: false, reason: `File not found: ${filePath}` };
|
|
64
|
+
}
|
|
65
|
+
if (step.verifyFile.contains) {
|
|
66
|
+
const contains = await this.driver.fileContains(filePath, step.verifyFile.contains);
|
|
67
|
+
if (!contains) {
|
|
68
|
+
return { passed: false, reason: `File does not contain: "${step.verifyFile.contains}"` };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { passed: true };
|
|
73
|
+
}
|
|
74
|
+
async verifyNotification(step) {
|
|
75
|
+
if (!step.verifyNotification)
|
|
76
|
+
return null;
|
|
77
|
+
const notifications = await this.driver.getNotifications();
|
|
78
|
+
const found = notifications.some((n) => n.includes(step.verifyNotification));
|
|
79
|
+
if (!found) {
|
|
80
|
+
return {
|
|
81
|
+
passed: false,
|
|
82
|
+
reason: `Notification not found: "${step.verifyNotification}". Got: [${notifications.join(", ")}]`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return { passed: true };
|
|
86
|
+
}
|
|
87
|
+
async verifyEditor(step) {
|
|
88
|
+
if (!step.verifyEditor?.contains)
|
|
89
|
+
return null;
|
|
90
|
+
const found = await this.driver.editorContains(step.verifyEditor.contains);
|
|
91
|
+
if (!found) {
|
|
92
|
+
return {
|
|
93
|
+
passed: false,
|
|
94
|
+
reason: `Editor does not contain: "${step.verifyEditor.contains}"`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return { passed: true };
|
|
98
|
+
}
|
|
99
|
+
async verifyProblems(step) {
|
|
100
|
+
if (!step.verifyProblems)
|
|
101
|
+
return null;
|
|
102
|
+
const maxWait = (step.timeout ?? 30) * 1000;
|
|
103
|
+
const pollInterval = 3000;
|
|
104
|
+
const deadline = Date.now() + maxWait;
|
|
105
|
+
let lastCounts = { errors: 0, warnings: 0 };
|
|
106
|
+
while (Date.now() < deadline) {
|
|
107
|
+
lastCounts = await this.driver.getProblemsCount();
|
|
108
|
+
const errorsOk = step.verifyProblems.errors === undefined || (step.verifyProblems.atLeast
|
|
109
|
+
? lastCounts.errors >= step.verifyProblems.errors
|
|
110
|
+
: lastCounts.errors === step.verifyProblems.errors);
|
|
111
|
+
const warningsOk = step.verifyProblems.warnings === undefined || (step.verifyProblems.atLeast
|
|
112
|
+
? lastCounts.warnings >= step.verifyProblems.warnings
|
|
113
|
+
: lastCounts.warnings === step.verifyProblems.warnings);
|
|
114
|
+
if (errorsOk && warningsOk)
|
|
115
|
+
return { passed: true };
|
|
116
|
+
await this.driver.wait(pollInterval / 1000);
|
|
117
|
+
}
|
|
118
|
+
const parts = [];
|
|
119
|
+
if (step.verifyProblems.errors !== undefined) {
|
|
120
|
+
const cmp = step.verifyProblems.atLeast ? "at least " : "";
|
|
121
|
+
parts.push(`Expected ${cmp}${step.verifyProblems.errors} errors, got ${lastCounts.errors}`);
|
|
122
|
+
}
|
|
123
|
+
if (step.verifyProblems.warnings !== undefined) {
|
|
124
|
+
const cmp = step.verifyProblems.atLeast ? "at least " : "";
|
|
125
|
+
parts.push(`Expected ${cmp}${step.verifyProblems.warnings} warnings, got ${lastCounts.warnings}`);
|
|
126
|
+
}
|
|
127
|
+
return { passed: false, reason: parts.join("; ") };
|
|
128
|
+
}
|
|
129
|
+
async verifyCompletion(step) {
|
|
130
|
+
if (!step.verifyCompletion)
|
|
131
|
+
return null;
|
|
132
|
+
const items = await this.driver.triggerCompletion();
|
|
133
|
+
await this.driver.dismissCompletion();
|
|
134
|
+
if (step.verifyCompletion.notEmpty && items.length === 0) {
|
|
135
|
+
return { passed: false, reason: "Expected non-empty completion list, got empty" };
|
|
136
|
+
}
|
|
137
|
+
if (step.verifyCompletion.contains) {
|
|
138
|
+
for (const expected of step.verifyCompletion.contains) {
|
|
139
|
+
const found = items.some((item) => item.toLowerCase().includes(expected.toLowerCase()));
|
|
140
|
+
if (!found) {
|
|
141
|
+
return {
|
|
142
|
+
passed: false,
|
|
143
|
+
reason: `Completion list missing "${expected}". Got: [${items.slice(0, 10).join(", ")}${items.length > 10 ? "..." : ""}]`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return { passed: true };
|
|
149
|
+
}
|
|
150
|
+
// ─── LLM Verification ───────────────────────────────────
|
|
151
|
+
async verifyWithLLM(description, screenshotPath) {
|
|
152
|
+
// If no LLM client or not configured, auto-pass with warning
|
|
153
|
+
if (!this.llm || !this.llm.isConfigured()) {
|
|
154
|
+
console.log(` 🤖 AI verify skipped (not configured): "${description}"`);
|
|
155
|
+
return { passed: true, reason: `LLM not configured — auto-pass: ${description}` };
|
|
156
|
+
}
|
|
157
|
+
// Read screenshot as base64
|
|
158
|
+
if (!screenshotPath || !fs.existsSync(screenshotPath)) {
|
|
159
|
+
console.log(` 🤖 AI verify skipped (no screenshot): "${description}"`);
|
|
160
|
+
return { passed: true, reason: `No screenshot for LLM verify: ${description}` };
|
|
161
|
+
}
|
|
162
|
+
console.log(` 🤖 AI verifying: "${description}"`);
|
|
163
|
+
const screenshotBase64 = fs.readFileSync(screenshotPath).toString("base64");
|
|
164
|
+
try {
|
|
165
|
+
const result = await this.llm.verifyScreenshot(screenshotBase64, description);
|
|
166
|
+
const icon = result.passed ? "✅" : "❌";
|
|
167
|
+
console.log(` 🤖 ${icon} confidence=${result.confidence.toFixed(2)}: ${result.reasoning}`);
|
|
168
|
+
return {
|
|
169
|
+
passed: result.passed,
|
|
170
|
+
reason: `[LLM ${result.confidence.toFixed(2)}] ${result.reasoning}`,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
console.log(` 🤖 ⚠️ LLM error: ${e.message}`);
|
|
175
|
+
return {
|
|
176
|
+
passed: true,
|
|
177
|
+
reason: `LLM error (auto-pass): ${e.message}`,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=stepVerifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stepVerifier.js","sourceRoot":"","sources":["../../src/operators/stepVerifier.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Runner — orchestrates test plan execution.
|
|
3
|
+
*
|
|
4
|
+
* Delegates action resolution to ActionResolver and verification to StepVerifier.
|
|
5
|
+
* Handles lifecycle (launch/close), screenshots, and reporting.
|
|
6
|
+
*/
|
|
7
|
+
import type { TestPlan, TestReport } from "../types.js";
|
|
8
|
+
export interface TestRunnerOptions {
|
|
9
|
+
/** Output directory for this test run. Contains screenshots/ and results.json. */
|
|
10
|
+
outputDir?: string;
|
|
11
|
+
/** Disable LLM verification (auto-pass all `verify` fields). */
|
|
12
|
+
noLLM?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare class TestRunner {
|
|
15
|
+
private driver;
|
|
16
|
+
private plan;
|
|
17
|
+
private actionResolver;
|
|
18
|
+
private verifier;
|
|
19
|
+
private outputDir;
|
|
20
|
+
private screenshotDir;
|
|
21
|
+
private screenshotCounter;
|
|
22
|
+
constructor(plan: TestPlan, options?: TestRunnerOptions);
|
|
23
|
+
/** Force-close the VSCode instance (for signal handlers) */
|
|
24
|
+
cleanup(): Promise<void>;
|
|
25
|
+
run(): Promise<TestReport>;
|
|
26
|
+
private executeStep;
|
|
27
|
+
private takeScreenshot;
|
|
28
|
+
private cloneRepos;
|
|
29
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Runner — orchestrates test plan execution.
|
|
3
|
+
*
|
|
4
|
+
* Delegates action resolution to ActionResolver and verification to StepVerifier.
|
|
5
|
+
* Handles lifecycle (launch/close), screenshots, and reporting.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from "node:fs";
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
import { execSync } from "node:child_process";
|
|
10
|
+
import { VscodeDriver } from "../drivers/vscodeDriver.js";
|
|
11
|
+
import { ActionResolver } from "./actionResolver.js";
|
|
12
|
+
import { LLMClient } from "./llmClient.js";
|
|
13
|
+
import { StepVerifier } from "./stepVerifier.js";
|
|
14
|
+
export class TestRunner {
|
|
15
|
+
driver;
|
|
16
|
+
plan;
|
|
17
|
+
actionResolver;
|
|
18
|
+
verifier;
|
|
19
|
+
outputDir;
|
|
20
|
+
screenshotDir;
|
|
21
|
+
screenshotCounter = 0;
|
|
22
|
+
constructor(plan, options = {}) {
|
|
23
|
+
this.plan = plan;
|
|
24
|
+
this.outputDir = options.outputDir ?? null;
|
|
25
|
+
this.screenshotDir = this.outputDir ? path.join(this.outputDir, "screenshots") : null;
|
|
26
|
+
this.driver = new VscodeDriver({
|
|
27
|
+
vscodeVersion: plan.setup.vscodeVersion,
|
|
28
|
+
extensionPath: plan.setup.extensionPath,
|
|
29
|
+
extensions: plan.setup.extensions,
|
|
30
|
+
workspacePath: plan.setup.workspace,
|
|
31
|
+
filePath: plan.setup.file,
|
|
32
|
+
settings: plan.setup.settings,
|
|
33
|
+
});
|
|
34
|
+
this.actionResolver = new ActionResolver(this.driver, {
|
|
35
|
+
lsTimeout: (plan.setup.timeout ?? 120) * 1000,
|
|
36
|
+
});
|
|
37
|
+
const llm = options.noLLM ? null : new LLMClient();
|
|
38
|
+
this.verifier = new StepVerifier(this.driver, { llmClient: llm });
|
|
39
|
+
}
|
|
40
|
+
/** Force-close the VSCode instance (for signal handlers) */
|
|
41
|
+
async cleanup() {
|
|
42
|
+
await this.driver.close();
|
|
43
|
+
}
|
|
44
|
+
async run() {
|
|
45
|
+
const startTime = new Date();
|
|
46
|
+
const results = [];
|
|
47
|
+
// Prepare output directory — clean stale data from previous runs
|
|
48
|
+
if (this.outputDir) {
|
|
49
|
+
if (fs.existsSync(this.outputDir)) {
|
|
50
|
+
fs.rmSync(this.outputDir, { recursive: true, force: true });
|
|
51
|
+
}
|
|
52
|
+
fs.mkdirSync(this.outputDir, { recursive: true });
|
|
53
|
+
fs.mkdirSync(this.screenshotDir, { recursive: true });
|
|
54
|
+
console.log(`📂 Output → ${this.outputDir}`);
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
// Clone required repos if specified
|
|
58
|
+
if (this.plan.setup.repos?.length) {
|
|
59
|
+
await this.cloneRepos(this.plan.setup.repos);
|
|
60
|
+
}
|
|
61
|
+
console.log(`\n🚀 Launching VSCode for: ${this.plan.name}`);
|
|
62
|
+
await this.driver.launch();
|
|
63
|
+
console.log(`✅ VSCode ready\n`);
|
|
64
|
+
// Brief wait for UI to settle (not the full setup.timeout — that's for LS steps)
|
|
65
|
+
await this.driver.wait(3);
|
|
66
|
+
for (const step of this.plan.steps) {
|
|
67
|
+
const result = await this.executeStep(step);
|
|
68
|
+
results.push(result);
|
|
69
|
+
const icon = result.status === "pass" ? "✅" : result.status === "fail" ? "❌" : "⏭️";
|
|
70
|
+
console.log(`${icon} [${result.stepId}] ${result.action} (${result.duration}ms)`);
|
|
71
|
+
if (result.reason) {
|
|
72
|
+
console.log(` → ${result.reason}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
console.error(`\n💥 Fatal error: ${e.message}`);
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
await this.driver.close();
|
|
81
|
+
}
|
|
82
|
+
const endTime = new Date();
|
|
83
|
+
const summary = {
|
|
84
|
+
total: results.length,
|
|
85
|
+
passed: results.filter((r) => r.status === "pass").length,
|
|
86
|
+
failed: results.filter((r) => r.status === "fail").length,
|
|
87
|
+
skipped: results.filter((r) => r.status === "skip").length,
|
|
88
|
+
errors: results.filter((r) => r.status === "error").length,
|
|
89
|
+
};
|
|
90
|
+
console.log(`\n📊 Results: ${summary.passed}/${summary.total} passed`);
|
|
91
|
+
const report = {
|
|
92
|
+
planName: this.plan.name,
|
|
93
|
+
startTime: startTime.toISOString(),
|
|
94
|
+
endTime: endTime.toISOString(),
|
|
95
|
+
duration: endTime.getTime() - startTime.getTime(),
|
|
96
|
+
results,
|
|
97
|
+
summary,
|
|
98
|
+
};
|
|
99
|
+
// Save results.json into output directory
|
|
100
|
+
if (this.outputDir) {
|
|
101
|
+
const reportPath = path.join(this.outputDir, "results.json");
|
|
102
|
+
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
|
103
|
+
console.log(`📄 Report → ${reportPath}`);
|
|
104
|
+
}
|
|
105
|
+
return report;
|
|
106
|
+
}
|
|
107
|
+
async executeStep(step) {
|
|
108
|
+
const start = Date.now();
|
|
109
|
+
let beforePath;
|
|
110
|
+
try {
|
|
111
|
+
if (step.waitBefore) {
|
|
112
|
+
await this.driver.wait(step.waitBefore);
|
|
113
|
+
}
|
|
114
|
+
beforePath = await this.takeScreenshot(step.id, "before");
|
|
115
|
+
// Delegate action execution to ActionResolver
|
|
116
|
+
await this.actionResolver.resolve(step.action);
|
|
117
|
+
const afterPath = await this.takeScreenshot(step.id, "after");
|
|
118
|
+
// Delegate verification to StepVerifier (pass screenshot for LLM)
|
|
119
|
+
const verifyResult = await this.verifier.verify(step, afterPath);
|
|
120
|
+
return {
|
|
121
|
+
stepId: step.id,
|
|
122
|
+
action: step.action,
|
|
123
|
+
status: verifyResult.passed ? "pass" : "fail",
|
|
124
|
+
reason: verifyResult.reason,
|
|
125
|
+
duration: Date.now() - start,
|
|
126
|
+
screenshot: afterPath,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
const errorPath = await this.takeScreenshot(step.id, "error");
|
|
131
|
+
return {
|
|
132
|
+
stepId: step.id,
|
|
133
|
+
action: step.action,
|
|
134
|
+
status: "error",
|
|
135
|
+
reason: e.message,
|
|
136
|
+
duration: Date.now() - start,
|
|
137
|
+
screenshot: errorPath,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async takeScreenshot(stepId, phase) {
|
|
142
|
+
if (!this.screenshotDir)
|
|
143
|
+
return undefined;
|
|
144
|
+
try {
|
|
145
|
+
// Dismiss notifications before every screenshot for clean captures
|
|
146
|
+
await this.driver.dismissAllNotifications();
|
|
147
|
+
const seq = String(++this.screenshotCounter).padStart(2, "0");
|
|
148
|
+
const fileName = `${seq}_${stepId}_${phase}.png`;
|
|
149
|
+
const filePath = path.join(this.screenshotDir, fileName);
|
|
150
|
+
await this.driver.screenshot(filePath);
|
|
151
|
+
return filePath;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async cloneRepos(repos) {
|
|
158
|
+
for (const repo of repos) {
|
|
159
|
+
// Derive local path from URL if not specified
|
|
160
|
+
const repoName = repo.url.replace(/\.git$/, "").split("/").pop() ?? "repo";
|
|
161
|
+
const targetPath = repo.path ?? path.resolve(repoName);
|
|
162
|
+
if (fs.existsSync(targetPath)) {
|
|
163
|
+
console.log(`📦 Repo already exists: ${targetPath}`);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
console.log(`📦 Cloning ${repo.url} → ${targetPath}`);
|
|
167
|
+
const branchArg = repo.branch ? `--branch ${repo.branch}` : "";
|
|
168
|
+
try {
|
|
169
|
+
execSync(`git clone --depth 1 ${branchArg} ${repo.url} "${targetPath}"`, {
|
|
170
|
+
stdio: "pipe",
|
|
171
|
+
timeout: 120_000,
|
|
172
|
+
});
|
|
173
|
+
console.log(`📦 Clone complete`);
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
throw new Error(`Failed to clone ${repo.url}: ${e.message.slice(0, 200)}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=testRunner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testRunner.js","sourceRoot":"","sources":["../../src/operators/testRunner.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for VSCode AutoTest framework.
|
|
3
|
+
*/
|
|
4
|
+
export interface TestPlan {
|
|
5
|
+
name: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
setup: TestSetup;
|
|
8
|
+
steps: TestStep[];
|
|
9
|
+
}
|
|
10
|
+
export interface TestSetup {
|
|
11
|
+
extension: string;
|
|
12
|
+
extensionPath?: string;
|
|
13
|
+
/** Marketplace extensions to install before launch (e.g. ["vscjava.vscode-java-pack"]) */
|
|
14
|
+
extensions?: string[];
|
|
15
|
+
vscodeVersion?: "stable" | "insiders";
|
|
16
|
+
/** Workspace folder to open. Mutually exclusive with `file`. */
|
|
17
|
+
workspace?: string;
|
|
18
|
+
/** Single file to open (no workspace). For testing LS in no-workspace mode. */
|
|
19
|
+
file?: string;
|
|
20
|
+
/** Git repos to clone before running. Each entry: { url, path?, branch? } */
|
|
21
|
+
repos?: RepoClone[];
|
|
22
|
+
settings?: Record<string, unknown>;
|
|
23
|
+
timeout?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface RepoClone {
|
|
26
|
+
/** Git clone URL */
|
|
27
|
+
url: string;
|
|
28
|
+
/** Local path to clone into (relative to plan file). If omitted, derived from repo name. */
|
|
29
|
+
path?: string;
|
|
30
|
+
/** Branch or tag to checkout */
|
|
31
|
+
branch?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface TestStep {
|
|
34
|
+
id: string;
|
|
35
|
+
action: string;
|
|
36
|
+
verify?: string;
|
|
37
|
+
verifyFile?: FileVerification;
|
|
38
|
+
verifyNotification?: string;
|
|
39
|
+
verifyEditor?: EditorVerification;
|
|
40
|
+
verifyProblems?: ProblemsVerification;
|
|
41
|
+
verifyCompletion?: CompletionVerification;
|
|
42
|
+
timeout?: number;
|
|
43
|
+
waitBefore?: number;
|
|
44
|
+
}
|
|
45
|
+
export interface FileVerification {
|
|
46
|
+
path: string;
|
|
47
|
+
exists?: boolean;
|
|
48
|
+
contains?: string;
|
|
49
|
+
matches?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface EditorVerification {
|
|
52
|
+
fileName?: string;
|
|
53
|
+
contains?: string;
|
|
54
|
+
language?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface ProblemsVerification {
|
|
57
|
+
errors?: number;
|
|
58
|
+
warnings?: number;
|
|
59
|
+
/** If true, check errors >= value instead of exact match */
|
|
60
|
+
atLeast?: boolean;
|
|
61
|
+
}
|
|
62
|
+
export interface CompletionVerification {
|
|
63
|
+
/** Completion list must not be empty */
|
|
64
|
+
notEmpty?: boolean;
|
|
65
|
+
/** Completion list must include these items (partial match on label) */
|
|
66
|
+
contains?: string[];
|
|
67
|
+
}
|
|
68
|
+
export interface A11yNode {
|
|
69
|
+
role: string;
|
|
70
|
+
name?: string;
|
|
71
|
+
value?: string;
|
|
72
|
+
description?: string;
|
|
73
|
+
focused?: boolean;
|
|
74
|
+
checked?: boolean;
|
|
75
|
+
disabled?: boolean;
|
|
76
|
+
expanded?: boolean;
|
|
77
|
+
children?: A11yNode[];
|
|
78
|
+
}
|
|
79
|
+
export interface Diagnostic {
|
|
80
|
+
severity: "error" | "warning" | "info" | "hint";
|
|
81
|
+
message: string;
|
|
82
|
+
source?: string;
|
|
83
|
+
file?: string;
|
|
84
|
+
line?: number;
|
|
85
|
+
}
|
|
86
|
+
export interface VscodeDriverOptions {
|
|
87
|
+
vscodeVersion?: "stable" | "insiders";
|
|
88
|
+
extensionPath?: string;
|
|
89
|
+
/** Marketplace extension IDs to install before launch */
|
|
90
|
+
extensions?: string[];
|
|
91
|
+
workspacePath?: string;
|
|
92
|
+
/** Single file to open (no workspace) */
|
|
93
|
+
filePath?: string;
|
|
94
|
+
userDataDir?: string;
|
|
95
|
+
settings?: Record<string, unknown>;
|
|
96
|
+
launchArgs?: string[];
|
|
97
|
+
/** Connect to an existing VSCode instance via CDP port instead of launching */
|
|
98
|
+
attachPort?: number;
|
|
99
|
+
}
|
|
100
|
+
export interface StepResult {
|
|
101
|
+
stepId: string;
|
|
102
|
+
action: string;
|
|
103
|
+
status: "pass" | "fail" | "skip" | "error";
|
|
104
|
+
reason?: string;
|
|
105
|
+
duration: number;
|
|
106
|
+
snapshot?: A11yNode;
|
|
107
|
+
screenshot?: string;
|
|
108
|
+
}
|
|
109
|
+
export interface TestReport {
|
|
110
|
+
planName: string;
|
|
111
|
+
startTime: string;
|
|
112
|
+
endTime: string;
|
|
113
|
+
duration: number;
|
|
114
|
+
results: StepResult[];
|
|
115
|
+
summary: {
|
|
116
|
+
total: number;
|
|
117
|
+
passed: number;
|
|
118
|
+
failed: number;
|
|
119
|
+
skipped: number;
|
|
120
|
+
errors: number;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
export interface ActionMapping {
|
|
124
|
+
/** Original natural language action */
|
|
125
|
+
action: string;
|
|
126
|
+
/** Mapped driver method calls */
|
|
127
|
+
calls: DriverCall[];
|
|
128
|
+
}
|
|
129
|
+
export interface DriverCall {
|
|
130
|
+
method: string;
|
|
131
|
+
args: unknown[];
|
|
132
|
+
}
|
|
133
|
+
export interface VerificationResult {
|
|
134
|
+
passed: boolean;
|
|
135
|
+
reasoning: string;
|
|
136
|
+
confidence: number;
|
|
137
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vscjava/vscode-autotest",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI-driven VSCode extension E2E testing framework — YAML test plans + Playwright + LLM verification",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"autotest": "dist/cli/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"vscode",
|
|
17
|
+
"testing",
|
|
18
|
+
"e2e",
|
|
19
|
+
"playwright",
|
|
20
|
+
"java",
|
|
21
|
+
"language-server",
|
|
22
|
+
"automation"
|
|
23
|
+
],
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/wenytang-ms/javaext-autotest.git"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/wenytang-ms/javaext-autotest#readme",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/wenytang-ms/javaext-autotest/issues"
|
|
31
|
+
},
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"author": "vscjava",
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc",
|
|
39
|
+
"dev": "tsc --watch",
|
|
40
|
+
"test": "vitest",
|
|
41
|
+
"lint": "eslint src/",
|
|
42
|
+
"prepublishOnly": "npm run build",
|
|
43
|
+
"run:test-plan": "tsx src/cli/index.ts run",
|
|
44
|
+
"run:all": "npm run build && node -e \"const{readdirSync}=require('fs'),{execSync}=require('child_process');readdirSync('test-plans').filter(f=>f.endsWith('.yaml')).forEach(f=>{console.log('\\n=== '+f+' ===');try{execSync('npx autotest run test-plans/'+f,{stdio:'inherit'})}catch{}})\""
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@azure/openai": "^2.0.0",
|
|
48
|
+
"@playwright/test": "^1.50.0",
|
|
49
|
+
"@vscode/test-electron": "^2.4.0",
|
|
50
|
+
"commander": "^12.0.0",
|
|
51
|
+
"js-yaml": "^4.1.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/js-yaml": "^4.0.9",
|
|
55
|
+
"@types/node": "^22.0.0",
|
|
56
|
+
"tsx": "^4.19.0",
|
|
57
|
+
"typescript": "^5.6.0",
|
|
58
|
+
"vitest": "^2.0.0"
|
|
59
|
+
}
|
|
60
|
+
}
|