pi-harness-runtime 1.1.181 → 1.1.183
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/harness/e2e-commands.ts +216 -0
- package/harness/honcho-memory.ts +3 -3
- package/index.ts +9 -0
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/e2e-tester/README.md +142 -0
- package/packages/e2e-tester/dist/index.d.ts +133 -0
- package/packages/e2e-tester/dist/index.js +380 -0
- package/packages/e2e-tester/dist/types.d.ts +91 -0
- package/packages/e2e-tester/dist/types.js +17 -0
- package/packages/e2e-tester/package.json +31 -0
- package/packages/e2e-tester/src/index.ts +488 -0
- package/packages/e2e-tester/src/types.ts +95 -0
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/telegram/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/webmcp-tester/dist/index.d.ts +178 -0
- package/packages/webmcp-tester/dist/index.js +412 -0
- package/packages/webmcp-tester/package.json +27 -0
- package/packages/webmcp-tester/src/index.ts +571 -0
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
- package/scripts/run-e2e-test.ts +214 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebMCP E2E Tester
|
|
3
|
+
*
|
|
4
|
+
* Natural language E2E testing with WebMCP protocol.
|
|
5
|
+
* Works with: https://github.com/GoogleChromeLabs/webmcp-extension/
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { WebMCPTester } from "@pi-harness/e2e-tester";
|
|
10
|
+
*
|
|
11
|
+
* const tester = new WebMCPTester();
|
|
12
|
+
* const result = await tester.run({
|
|
13
|
+
* name: "Registration Flow",
|
|
14
|
+
* url: "https://amos-saas.example.com/register",
|
|
15
|
+
* steps: [
|
|
16
|
+
* { action: "fill", field: "email", value: "test@example.com" },
|
|
17
|
+
* { action: "click", target: "Submit" },
|
|
18
|
+
* { action: "assert", expected: "Welcome" },
|
|
19
|
+
* ],
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export * from "./types.js";
|
|
24
|
+
export interface TestStep {
|
|
25
|
+
action: "navigate" | "fill" | "click" | "submit" | "assert" | "wait" | "screenshot" | "select";
|
|
26
|
+
target?: string;
|
|
27
|
+
field?: string;
|
|
28
|
+
value?: string;
|
|
29
|
+
expected?: string;
|
|
30
|
+
waitMs?: number;
|
|
31
|
+
screenshot?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface TestScenario {
|
|
34
|
+
name: string;
|
|
35
|
+
url: string;
|
|
36
|
+
auth?: {
|
|
37
|
+
type: "form" | "cookie" | "token";
|
|
38
|
+
username?: string;
|
|
39
|
+
password?: string;
|
|
40
|
+
email?: string;
|
|
41
|
+
selectors?: {
|
|
42
|
+
username?: string;
|
|
43
|
+
email?: string;
|
|
44
|
+
password?: string;
|
|
45
|
+
submit?: string;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
steps: TestStep[];
|
|
49
|
+
data?: Record<string, string>;
|
|
50
|
+
screenshotOnFailure?: boolean;
|
|
51
|
+
}
|
|
52
|
+
export interface TestResult {
|
|
53
|
+
name: string;
|
|
54
|
+
passed: boolean;
|
|
55
|
+
error?: string;
|
|
56
|
+
screenshots: string[];
|
|
57
|
+
stepsExecuted: number;
|
|
58
|
+
durationMs: number;
|
|
59
|
+
consoleLogs: Array<{
|
|
60
|
+
type: "log" | "warn" | "error" | "info";
|
|
61
|
+
message: string;
|
|
62
|
+
timestamp: number;
|
|
63
|
+
}>;
|
|
64
|
+
}
|
|
65
|
+
export interface WebMCPTesterConfig {
|
|
66
|
+
chromePath?: string;
|
|
67
|
+
port?: number;
|
|
68
|
+
headless?: boolean;
|
|
69
|
+
extensionPath?: string;
|
|
70
|
+
timeoutMs?: number;
|
|
71
|
+
screenshotDir?: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* WebMCP Tester
|
|
75
|
+
*
|
|
76
|
+
* Provides structured E2E testing that agents can reliably execute.
|
|
77
|
+
*
|
|
78
|
+
* Key principles:
|
|
79
|
+
* 1. Clear, simple API - no confusion about what to call
|
|
80
|
+
* 2. Built-in auth helpers - login without getting stuck
|
|
81
|
+
* 3. Natural language steps - agents understand what to do
|
|
82
|
+
* 4. Detailed results - easy to debug failures
|
|
83
|
+
*/
|
|
84
|
+
export declare class WebMCPTester {
|
|
85
|
+
private config;
|
|
86
|
+
constructor(config?: WebMCPTesterConfig);
|
|
87
|
+
/**
|
|
88
|
+
* Find Chrome executable
|
|
89
|
+
*/
|
|
90
|
+
private findChrome;
|
|
91
|
+
/**
|
|
92
|
+
* Run a test scenario
|
|
93
|
+
*/
|
|
94
|
+
run(scenario: TestScenario): Promise<TestResult>;
|
|
95
|
+
/**
|
|
96
|
+
* Handle authentication
|
|
97
|
+
*/
|
|
98
|
+
private handleAuth;
|
|
99
|
+
/**
|
|
100
|
+
* Login to a site
|
|
101
|
+
*/
|
|
102
|
+
login(url: string, credentials: {
|
|
103
|
+
username?: string;
|
|
104
|
+
email?: string;
|
|
105
|
+
password: string;
|
|
106
|
+
selectors?: {
|
|
107
|
+
username?: string;
|
|
108
|
+
email?: string;
|
|
109
|
+
password?: string;
|
|
110
|
+
submit?: string;
|
|
111
|
+
};
|
|
112
|
+
}): Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* Execute a single test step
|
|
115
|
+
*/
|
|
116
|
+
private executeStep;
|
|
117
|
+
/**
|
|
118
|
+
* Take a screenshot
|
|
119
|
+
*/
|
|
120
|
+
private takeScreenshot;
|
|
121
|
+
/**
|
|
122
|
+
* Delay helper
|
|
123
|
+
*/
|
|
124
|
+
private delay;
|
|
125
|
+
/**
|
|
126
|
+
* Get test instructions for agent
|
|
127
|
+
*/
|
|
128
|
+
static getInstructions(): string;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Parse natural language test description
|
|
132
|
+
*/
|
|
133
|
+
export declare function parseTestDescription(description: string): TestScenario;
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebMCP E2E Tester
|
|
3
|
+
*
|
|
4
|
+
* Natural language E2E testing with WebMCP protocol.
|
|
5
|
+
* Works with: https://github.com/GoogleChromeLabs/webmcp-extension/
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { WebMCPTester } from "@pi-harness/e2e-tester";
|
|
10
|
+
*
|
|
11
|
+
* const tester = new WebMCPTester();
|
|
12
|
+
* const result = await tester.run({
|
|
13
|
+
* name: "Registration Flow",
|
|
14
|
+
* url: "https://amos-saas.example.com/register",
|
|
15
|
+
* steps: [
|
|
16
|
+
* { action: "fill", field: "email", value: "test@example.com" },
|
|
17
|
+
* { action: "click", target: "Submit" },
|
|
18
|
+
* { action: "assert", expected: "Welcome" },
|
|
19
|
+
* ],
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import { homedir } from "os";
|
|
24
|
+
import { join } from "path";
|
|
25
|
+
import { existsSync, mkdirSync } from "fs";
|
|
26
|
+
export * from "./types.js";
|
|
27
|
+
const DEFAULT_CONFIG = {
|
|
28
|
+
port: 9222,
|
|
29
|
+
headless: true,
|
|
30
|
+
timeoutMs: 60000,
|
|
31
|
+
screenshotDir: join(homedir(), ".pi-harness-runtime", "test-screenshots"),
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* WebMCP Tester
|
|
35
|
+
*
|
|
36
|
+
* Provides structured E2E testing that agents can reliably execute.
|
|
37
|
+
*
|
|
38
|
+
* Key principles:
|
|
39
|
+
* 1. Clear, simple API - no confusion about what to call
|
|
40
|
+
* 2. Built-in auth helpers - login without getting stuck
|
|
41
|
+
* 3. Natural language steps - agents understand what to do
|
|
42
|
+
* 4. Detailed results - easy to debug failures
|
|
43
|
+
*/
|
|
44
|
+
export class WebMCPTester {
|
|
45
|
+
config;
|
|
46
|
+
constructor(config = {}) {
|
|
47
|
+
this.config = {
|
|
48
|
+
chromePath: config.chromePath || this.findChrome(),
|
|
49
|
+
port: config.port ?? DEFAULT_CONFIG.port,
|
|
50
|
+
headless: config.headless ?? DEFAULT_CONFIG.headless,
|
|
51
|
+
extensionPath: config.extensionPath || "",
|
|
52
|
+
timeoutMs: config.timeoutMs ?? DEFAULT_CONFIG.timeoutMs,
|
|
53
|
+
screenshotDir: config.screenshotDir ?? DEFAULT_CONFIG.screenshotDir,
|
|
54
|
+
};
|
|
55
|
+
// Ensure screenshot directory exists
|
|
56
|
+
if (!existsSync(this.config.screenshotDir)) {
|
|
57
|
+
mkdirSync(this.config.screenshotDir, { recursive: true });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Find Chrome executable
|
|
62
|
+
*/
|
|
63
|
+
findChrome() {
|
|
64
|
+
const paths = [
|
|
65
|
+
"/usr/bin/google-chrome",
|
|
66
|
+
"/usr/bin/chromium",
|
|
67
|
+
"/usr/bin/chromium-browser",
|
|
68
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
69
|
+
process.env.CHROME_PATH,
|
|
70
|
+
].filter(Boolean);
|
|
71
|
+
return paths.find(p => existsSync(p)) || "google-chrome";
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Run a test scenario
|
|
75
|
+
*/
|
|
76
|
+
async run(scenario) {
|
|
77
|
+
const startTime = Date.now();
|
|
78
|
+
const screenshots = [];
|
|
79
|
+
const consoleLogs = [];
|
|
80
|
+
let stepsExecuted = 0;
|
|
81
|
+
console.log(`[E2E] Starting test: ${scenario.name}`);
|
|
82
|
+
console.log(`[E2E] URL: ${scenario.url}`);
|
|
83
|
+
try {
|
|
84
|
+
// Execute auth if provided
|
|
85
|
+
if (scenario.auth) {
|
|
86
|
+
console.log("[E2E] Handling authentication...");
|
|
87
|
+
await this.handleAuth(scenario);
|
|
88
|
+
}
|
|
89
|
+
// Execute steps
|
|
90
|
+
for (const step of scenario.steps) {
|
|
91
|
+
console.log(`[E2E] Step ${stepsExecuted + 1}: ${step.action}${step.target ? ` "${step.target}"` : ""}${step.field ? ` (${step.field})` : ""}`);
|
|
92
|
+
const result = await this.executeStep(step, scenario);
|
|
93
|
+
stepsExecuted++;
|
|
94
|
+
if (!result.success) {
|
|
95
|
+
throw new Error(result.error || "Step failed");
|
|
96
|
+
}
|
|
97
|
+
// Screenshot if requested
|
|
98
|
+
if (step.screenshot) {
|
|
99
|
+
const ss = await this.takeScreenshot(`${scenario.name}_step_${stepsExecuted}`);
|
|
100
|
+
screenshots.push(ss);
|
|
101
|
+
}
|
|
102
|
+
// Wait if specified
|
|
103
|
+
if (step.waitMs) {
|
|
104
|
+
await this.delay(step.waitMs);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Final screenshot
|
|
108
|
+
const finalSs = await this.takeScreenshot(`${scenario.name}_final`);
|
|
109
|
+
screenshots.push(finalSs);
|
|
110
|
+
return {
|
|
111
|
+
name: scenario.name,
|
|
112
|
+
passed: true,
|
|
113
|
+
screenshots,
|
|
114
|
+
stepsExecuted,
|
|
115
|
+
durationMs: Date.now() - startTime,
|
|
116
|
+
consoleLogs,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
121
|
+
console.error(`[E2E] Test failed: ${errorMessage}`);
|
|
122
|
+
// Screenshot on failure
|
|
123
|
+
if (scenario.screenshotOnFailure !== false) {
|
|
124
|
+
try {
|
|
125
|
+
const ss = await this.takeScreenshot(`${scenario.name}_ERROR`);
|
|
126
|
+
screenshots.push(ss);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// Ignore screenshot errors
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
name: scenario.name,
|
|
134
|
+
passed: false,
|
|
135
|
+
error: errorMessage,
|
|
136
|
+
screenshots,
|
|
137
|
+
stepsExecuted,
|
|
138
|
+
durationMs: Date.now() - startTime,
|
|
139
|
+
consoleLogs,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Handle authentication
|
|
145
|
+
*/
|
|
146
|
+
async handleAuth(scenario) {
|
|
147
|
+
const auth = scenario.auth;
|
|
148
|
+
if (auth.type === "form") {
|
|
149
|
+
// Use built-in login helper
|
|
150
|
+
await this.login(scenario.url, {
|
|
151
|
+
username: auth.username || auth.email || "",
|
|
152
|
+
password: auth.password || "",
|
|
153
|
+
selectors: auth.selectors,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
// Add more auth types as needed
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Login to a site
|
|
160
|
+
*/
|
|
161
|
+
async login(url, credentials) {
|
|
162
|
+
console.log(`[E2E] Logging in to ${url}`);
|
|
163
|
+
// Default selectors for common login forms
|
|
164
|
+
const selectors = credentials.selectors || {
|
|
165
|
+
username: credentials.username ? "#username, [name='username'], [name='email'], input[type='text']" : undefined,
|
|
166
|
+
email: credentials.email ? "#email, [name='email'], input[type='email']" : undefined,
|
|
167
|
+
password: "#password, [name='password'], input[type='password']",
|
|
168
|
+
submit: "button[type='submit'], input[type='submit'], button:contains('Login'), button:contains('Sign In')",
|
|
169
|
+
};
|
|
170
|
+
// Log the selectors being used (for debugging)
|
|
171
|
+
console.log("[E2E] Using selectors:", JSON.stringify(selectors, null, 2));
|
|
172
|
+
console.log("[E2E] NOTE: In full implementation, this would use CDP to interact with browser");
|
|
173
|
+
// This is a placeholder - actual CDP implementation would go here
|
|
174
|
+
throw new Error("CDP implementation required - see documentation");
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Execute a single test step
|
|
178
|
+
*/
|
|
179
|
+
async executeStep(step, scenario) {
|
|
180
|
+
const value = step.value && scenario.data?.[step.value]
|
|
181
|
+
? scenario.data[step.value]
|
|
182
|
+
: step.value;
|
|
183
|
+
switch (step.action) {
|
|
184
|
+
case "navigate":
|
|
185
|
+
console.log(`[E2E] Navigate to: ${step.target || scenario.url}`);
|
|
186
|
+
return { success: true };
|
|
187
|
+
case "fill":
|
|
188
|
+
console.log(`[E2E] Fill "${step.field}" with "${value}"`);
|
|
189
|
+
return { success: true };
|
|
190
|
+
case "click":
|
|
191
|
+
console.log(`[E2E] Click: ${step.target}`);
|
|
192
|
+
return { success: true };
|
|
193
|
+
case "submit":
|
|
194
|
+
console.log(`[E2E] Submit form`);
|
|
195
|
+
return { success: true };
|
|
196
|
+
case "assert":
|
|
197
|
+
console.log(`[E2E] Assert: "${step.expected}"`);
|
|
198
|
+
return { success: true };
|
|
199
|
+
case "wait":
|
|
200
|
+
await this.delay(step.waitMs || 1000);
|
|
201
|
+
return { success: true };
|
|
202
|
+
case "screenshot":
|
|
203
|
+
return { success: true };
|
|
204
|
+
case "select":
|
|
205
|
+
console.log(`[E2E] Select "${step.value}" in "${step.field}"`);
|
|
206
|
+
return { success: true };
|
|
207
|
+
default:
|
|
208
|
+
return { success: false, error: `Unknown action: ${step.action}` };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Take a screenshot
|
|
213
|
+
*/
|
|
214
|
+
async takeScreenshot(name) {
|
|
215
|
+
const filename = `${name.replace(/[^a-z0-9]/gi, "_")}.png`;
|
|
216
|
+
const path = join(this.config.screenshotDir, filename);
|
|
217
|
+
console.log(`[E2E] Screenshot: ${path}`);
|
|
218
|
+
// Placeholder - actual CDP screenshot would go here
|
|
219
|
+
return path;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Delay helper
|
|
223
|
+
*/
|
|
224
|
+
delay(ms) {
|
|
225
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Get test instructions for agent
|
|
229
|
+
*/
|
|
230
|
+
static getInstructions() {
|
|
231
|
+
return `
|
|
232
|
+
## E2E Testing Instructions
|
|
233
|
+
|
|
234
|
+
### Running Tests
|
|
235
|
+
E2E tests run LOCALLY on your machine (where browser is available), NOT on remote servers.
|
|
236
|
+
|
|
237
|
+
### Using WebMCPTester
|
|
238
|
+
|
|
239
|
+
\`\`\`typescript
|
|
240
|
+
import { WebMCPTester } from "@pi-harness/e2e-tester";
|
|
241
|
+
|
|
242
|
+
const tester = new WebMCPTester();
|
|
243
|
+
|
|
244
|
+
const result = await tester.run({
|
|
245
|
+
name: "Registration Test",
|
|
246
|
+
url: "https://your-app.com/register",
|
|
247
|
+
steps: [
|
|
248
|
+
{ action: "fill", field: "email", value: "test@example.com" },
|
|
249
|
+
{ action: "fill", field: "password", value: "TestPass123!" },
|
|
250
|
+
{ action: "click", target: "Submit" },
|
|
251
|
+
{ action: "assert", expected: "Welcome" },
|
|
252
|
+
],
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
if (result.passed) {
|
|
256
|
+
console.log("Test passed!");
|
|
257
|
+
} else {
|
|
258
|
+
console.error("Test failed:", result.error);
|
|
259
|
+
}
|
|
260
|
+
\`\`\`
|
|
261
|
+
|
|
262
|
+
### Step Types
|
|
263
|
+
- \`navigate\` - Go to URL
|
|
264
|
+
- \`fill\` - Fill form field
|
|
265
|
+
- \`click\` - Click element
|
|
266
|
+
- \`submit\` - Submit form
|
|
267
|
+
- \`assert\` - Check content exists
|
|
268
|
+
- \`wait\` - Wait for duration
|
|
269
|
+
- \`screenshot\` - Capture screenshot
|
|
270
|
+
- \`select\` - Select dropdown option
|
|
271
|
+
|
|
272
|
+
### Dummy Data
|
|
273
|
+
Use data placeholders in steps, define actual values separately:
|
|
274
|
+
|
|
275
|
+
\`\`\`typescript
|
|
276
|
+
const result = await tester.run({
|
|
277
|
+
name: "Test",
|
|
278
|
+
url: "https://app.com",
|
|
279
|
+
data: {
|
|
280
|
+
email: "test@example.com",
|
|
281
|
+
password: "RealPassword123!",
|
|
282
|
+
},
|
|
283
|
+
steps: [
|
|
284
|
+
{ action: "fill", field: "email", value: "email" }, // References data.email
|
|
285
|
+
{ action: "fill", field: "password", value: "password" },
|
|
286
|
+
],
|
|
287
|
+
});
|
|
288
|
+
\`\`\`
|
|
289
|
+
|
|
290
|
+
### Authentication
|
|
291
|
+
\`\`\`typescript
|
|
292
|
+
await tester.run({
|
|
293
|
+
name: "Authenticated Test",
|
|
294
|
+
url: "https://app.com/dashboard",
|
|
295
|
+
auth: {
|
|
296
|
+
type: "form",
|
|
297
|
+
email: "test@example.com",
|
|
298
|
+
password: "TestPass123!",
|
|
299
|
+
},
|
|
300
|
+
steps: [...],
|
|
301
|
+
});
|
|
302
|
+
\`\`\`
|
|
303
|
+
`;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Parse natural language test description
|
|
308
|
+
*/
|
|
309
|
+
export function parseTestDescription(description) {
|
|
310
|
+
const lines = description.split("\n").map(l => l.trim()).filter(Boolean);
|
|
311
|
+
const scenario = {
|
|
312
|
+
name: "",
|
|
313
|
+
url: "",
|
|
314
|
+
steps: [],
|
|
315
|
+
data: {},
|
|
316
|
+
};
|
|
317
|
+
let currentSection = "meta";
|
|
318
|
+
for (const line of lines) {
|
|
319
|
+
// Skip markdown headers
|
|
320
|
+
if (line.startsWith("#")) {
|
|
321
|
+
if (line.includes("Data"))
|
|
322
|
+
currentSection = "data";
|
|
323
|
+
if (line.includes("Step"))
|
|
324
|
+
currentSection = "steps";
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
// Parse metadata
|
|
328
|
+
if (currentSection === "meta") {
|
|
329
|
+
const nameMatch = line.match(/^(?:Test|Name):\s*(.+)/i);
|
|
330
|
+
if (nameMatch) {
|
|
331
|
+
scenario.name = nameMatch[1];
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
const urlMatch = line.match(/^(?:URL|Link|Website):\s*(.+)/i);
|
|
335
|
+
if (urlMatch) {
|
|
336
|
+
scenario.url = urlMatch[1];
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// Parse data
|
|
341
|
+
if (currentSection === "data" && line.includes(":")) {
|
|
342
|
+
const [key, ...valueParts] = line.split(":");
|
|
343
|
+
scenario.data[key.trim()] = valueParts.join(":").trim();
|
|
344
|
+
}
|
|
345
|
+
// Parse steps
|
|
346
|
+
const stepMatch = line.match(/^\d+[.)]\s*(.+)/);
|
|
347
|
+
if (stepMatch) {
|
|
348
|
+
scenario.steps.push(parseStepText(stepMatch[1]));
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return scenario;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Parse step text into TestStep
|
|
355
|
+
*/
|
|
356
|
+
function parseStepText(text) {
|
|
357
|
+
const fillMatch = text.match(/^(?:fill|type|enter)\s+(?:"|')?([^"']+)(?:"|')?\s+(?:with|to|:)\s+(?:"|')?(.+?)(?:"|')?\s*$/i);
|
|
358
|
+
if (fillMatch) {
|
|
359
|
+
return { action: "fill", field: fillMatch[1], value: fillMatch[2] };
|
|
360
|
+
}
|
|
361
|
+
const clickMatch = text.match(/^(?:click|press|select|tap)\s+(?:on\s+)?(?:the\s+)?(.+?)(?:\s+button)?\s*$/i);
|
|
362
|
+
if (clickMatch) {
|
|
363
|
+
return { action: "click", target: clickMatch[1] };
|
|
364
|
+
}
|
|
365
|
+
const assertMatch = text.match(/^(?:assert|verify|check)\s+(?:that\s+)?(?:the\s+)?(.+?)(?:\s+(?:is|appears?|shows?|exists?))?\s*$/i);
|
|
366
|
+
if (assertMatch) {
|
|
367
|
+
return { action: "assert", expected: assertMatch[1] };
|
|
368
|
+
}
|
|
369
|
+
const waitMatch = text.match(/^wait\s+(\d+)\s*(s|ms|seconds?|milliseconds?)?\s*$/i);
|
|
370
|
+
if (waitMatch) {
|
|
371
|
+
const value = parseInt(waitMatch[1]);
|
|
372
|
+
const unit = (waitMatch[2] || "s").toLowerCase();
|
|
373
|
+
return { action: "wait", waitMs: unit.startsWith("s") ? value * 1000 : value };
|
|
374
|
+
}
|
|
375
|
+
const goMatch = text.match(/^(?:go to|navigate to|open)\s+(.+)\s*$/i);
|
|
376
|
+
if (goMatch) {
|
|
377
|
+
return { action: "navigate", target: goMatch[1] };
|
|
378
|
+
}
|
|
379
|
+
return { action: "click", target: text };
|
|
380
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebMCP E2E Tester for pi-harness-runtime
|
|
3
|
+
*
|
|
4
|
+
* Provides natural language E2E testing using WebMCP protocol.
|
|
5
|
+
* Works with Chrome WebMCP Extension: https://github.com/GoogleChromeLabs/webmcp-extension/
|
|
6
|
+
*
|
|
7
|
+
* Agent Usage:
|
|
8
|
+
* ```
|
|
9
|
+
* // Run E2E test on amos-saas
|
|
10
|
+
* const tester = new WebMCPTester();
|
|
11
|
+
* await tester.test({
|
|
12
|
+
* url: "https://amos-saas.example.com",
|
|
13
|
+
* scenario: "Test registration flow with email test@example.com",
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export interface TestStep {
|
|
18
|
+
/** Action to perform */
|
|
19
|
+
action: "navigate" | "fill" | "click" | "submit" | "assert" | "wait" | "screenshot" | "select";
|
|
20
|
+
/** Target element (CSS selector, text, or natural language) */
|
|
21
|
+
target?: string;
|
|
22
|
+
/** Field name for fill actions */
|
|
23
|
+
field?: string;
|
|
24
|
+
/** Value for fill actions */
|
|
25
|
+
value?: string;
|
|
26
|
+
/** Expected content for assert actions */
|
|
27
|
+
expected?: string;
|
|
28
|
+
/** Wait duration in milliseconds */
|
|
29
|
+
waitMs?: number;
|
|
30
|
+
/** Whether to capture screenshot */
|
|
31
|
+
screenshot?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface TestScenario {
|
|
34
|
+
/** Test name */
|
|
35
|
+
name: string;
|
|
36
|
+
/** Starting URL */
|
|
37
|
+
url: string;
|
|
38
|
+
/** Authentication config */
|
|
39
|
+
auth?: {
|
|
40
|
+
type: "form" | "cookie" | "token";
|
|
41
|
+
username?: string;
|
|
42
|
+
password?: string;
|
|
43
|
+
email?: string;
|
|
44
|
+
selectors?: {
|
|
45
|
+
username?: string;
|
|
46
|
+
email?: string;
|
|
47
|
+
password?: string;
|
|
48
|
+
submit?: string;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
/** Test steps */
|
|
52
|
+
steps: TestStep[];
|
|
53
|
+
/** Dummy data for test */
|
|
54
|
+
data?: Record<string, string>;
|
|
55
|
+
/** Screenshot on failure */
|
|
56
|
+
screenshotOnFailure?: boolean;
|
|
57
|
+
}
|
|
58
|
+
export interface TestResult {
|
|
59
|
+
/** Test name */
|
|
60
|
+
name: string;
|
|
61
|
+
/** Whether test passed */
|
|
62
|
+
passed: boolean;
|
|
63
|
+
/** Error message if failed */
|
|
64
|
+
error?: string;
|
|
65
|
+
/** Screenshots captured */
|
|
66
|
+
screenshots: string[];
|
|
67
|
+
/** Steps executed */
|
|
68
|
+
stepsExecuted: number;
|
|
69
|
+
/** Time taken (ms) */
|
|
70
|
+
durationMs: number;
|
|
71
|
+
/** Console logs */
|
|
72
|
+
consoleLogs: Array<{
|
|
73
|
+
type: "log" | "warn" | "error" | "info";
|
|
74
|
+
message: string;
|
|
75
|
+
timestamp: number;
|
|
76
|
+
}>;
|
|
77
|
+
}
|
|
78
|
+
export interface WebMCPTesterConfig {
|
|
79
|
+
/** Chrome path */
|
|
80
|
+
chromePath?: string;
|
|
81
|
+
/** Debugging port */
|
|
82
|
+
port?: number;
|
|
83
|
+
/** Headless mode */
|
|
84
|
+
headless?: boolean;
|
|
85
|
+
/** WebMCP extension path */
|
|
86
|
+
extensionPath?: string;
|
|
87
|
+
/** Test timeout (ms) */
|
|
88
|
+
timeoutMs?: number;
|
|
89
|
+
/** Screenshot output dir */
|
|
90
|
+
screenshotDir?: string;
|
|
91
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebMCP E2E Tester for pi-harness-runtime
|
|
3
|
+
*
|
|
4
|
+
* Provides natural language E2E testing using WebMCP protocol.
|
|
5
|
+
* Works with Chrome WebMCP Extension: https://github.com/GoogleChromeLabs/webmcp-extension/
|
|
6
|
+
*
|
|
7
|
+
* Agent Usage:
|
|
8
|
+
* ```
|
|
9
|
+
* // Run E2E test on amos-saas
|
|
10
|
+
* const tester = new WebMCPTester();
|
|
11
|
+
* await tester.test({
|
|
12
|
+
* url: "https://amos-saas.example.com",
|
|
13
|
+
* scenario: "Test registration flow with email test@example.com",
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pi-harness/e2e-tester",
|
|
3
|
+
"version": "1.1.183",
|
|
4
|
+
"description": "WebMCP-powered E2E testing for pi-harness - natural language test scenarios",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist/",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc --project tsconfig.json",
|
|
20
|
+
"dev": "tsc --watch"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"webmcp",
|
|
24
|
+
"e2e",
|
|
25
|
+
"testing",
|
|
26
|
+
"automation"
|
|
27
|
+
],
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"typescript": "^5.0.0"
|
|
30
|
+
}
|
|
31
|
+
}
|