pi-harness-runtime 1.1.180 → 1.1.182
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 +84 -0
- 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,142 @@
|
|
|
1
|
+
# E2E Tester - Agent-Friendly Testing
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
Simple E2E testing that agents can reliably use without confusion.
|
|
5
|
+
|
|
6
|
+
## Key Rules for Agents
|
|
7
|
+
|
|
8
|
+
### ALWAYS RUN LOCALLY
|
|
9
|
+
```
|
|
10
|
+
⚠️ IMPORTANT: E2E tests MUST run on LOCAL machine (your laptop/desktop), NOT on remote servers.
|
|
11
|
+
- Remote servers typically have no browser installed
|
|
12
|
+
- Use ctx_execute to run tests locally
|
|
13
|
+
- Never try to run Playwright on SSH to a server
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### Test Location
|
|
17
|
+
```
|
|
18
|
+
Local machine: /home/frappe/amos-saas/ (or wherever the app is)
|
|
19
|
+
Remote server: SSH frappe@217.216.37.251 (NO BROWSER - DO NOT RUN TESTS HERE)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { E2ETester } from "@pi-harness/e2e-tester";
|
|
26
|
+
|
|
27
|
+
const tester = new E2ETester({
|
|
28
|
+
browser: "chromium", // chromium, firefox, webkit
|
|
29
|
+
headless: true, // true for CI, false for debugging
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
await tester.start();
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
// Login first
|
|
36
|
+
await tester.login("https://your-app.com/login", {
|
|
37
|
+
email: "test@example.com",
|
|
38
|
+
password: "TestPass123!",
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Navigate and test
|
|
42
|
+
await tester.goto("https://your-app.com/dashboard");
|
|
43
|
+
await tester.click("Submit Button");
|
|
44
|
+
await tester.fill("Email", "user@example.com");
|
|
45
|
+
await tester.assertVisible("Welcome");
|
|
46
|
+
|
|
47
|
+
} finally {
|
|
48
|
+
await tester.stop();
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Natural Language Test Format
|
|
53
|
+
|
|
54
|
+
Create test files like `test-register.md`:
|
|
55
|
+
|
|
56
|
+
```markdown
|
|
57
|
+
# Registration Flow Test
|
|
58
|
+
|
|
59
|
+
URL: https://your-app.com/register
|
|
60
|
+
|
|
61
|
+
Auth:
|
|
62
|
+
email: test@example.com
|
|
63
|
+
password: TestPass123!
|
|
64
|
+
|
|
65
|
+
Steps:
|
|
66
|
+
1. Fill email with "test@example.com"
|
|
67
|
+
2. Fill password with "TestPass123!"
|
|
68
|
+
3. Click Submit button
|
|
69
|
+
4. Wait 2 seconds
|
|
70
|
+
5. Assert "Welcome" message is visible
|
|
71
|
+
6. Assert URL contains "/dashboard"
|
|
72
|
+
|
|
73
|
+
Expected:
|
|
74
|
+
- Success message appears
|
|
75
|
+
- Redirects to dashboard
|
|
76
|
+
- No console errors
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Running Tests
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Run single test
|
|
83
|
+
bun scripts/run-e2e-test.ts --scenario test-register.md
|
|
84
|
+
|
|
85
|
+
# Run all tests
|
|
86
|
+
bun scripts/run-e2e-test.ts --scenario ./tests/
|
|
87
|
+
|
|
88
|
+
# With screenshots
|
|
89
|
+
bun scripts/run-e2e-test.ts --scenario test.md --screenshots --output ./test-output
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Troubleshooting
|
|
93
|
+
|
|
94
|
+
### "Cannot find browser"
|
|
95
|
+
```bash
|
|
96
|
+
# Install browsers
|
|
97
|
+
npx playwright install chromium
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### "Login failed"
|
|
101
|
+
```typescript
|
|
102
|
+
// Check if login form selectors are correct
|
|
103
|
+
await tester.debug(); // Opens browser in non-headless mode
|
|
104
|
+
|
|
105
|
+
// Or provide custom selectors
|
|
106
|
+
await tester.login(url, {
|
|
107
|
+
selectors: {
|
|
108
|
+
email: "#email-input",
|
|
109
|
+
password: "[name='password']",
|
|
110
|
+
submit: "button[type='submit']",
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### "Test running on server"
|
|
116
|
+
```
|
|
117
|
+
STOP! You are trying to run E2E tests on a remote server.
|
|
118
|
+
E2E tests MUST run locally where a browser is available.
|
|
119
|
+
|
|
120
|
+
Use ctx_execute to run tests on your LOCAL machine.
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Architecture
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
┌─────────────────┐
|
|
127
|
+
│ Test Scenario │ ← Markdown or TypeScript
|
|
128
|
+
│ (Natural) │
|
|
129
|
+
└────────┬────────┘
|
|
130
|
+
│
|
|
131
|
+
▼
|
|
132
|
+
┌─────────────────┐
|
|
133
|
+
│ E2E Tester │ ← Parses and executes
|
|
134
|
+
│ (Playwright) │
|
|
135
|
+
└────────┬────────┘
|
|
136
|
+
│
|
|
137
|
+
▼
|
|
138
|
+
┌─────────────────┐
|
|
139
|
+
│ Local Browser │ ← Chromium/Firefox/WebKit
|
|
140
|
+
│ (Your Machine) │
|
|
141
|
+
└─────────────────┘
|
|
142
|
+
```
|
|
@@ -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
|
+
}
|