@elizaos/app-core 2.0.0-alpha.21 → 2.0.0-alpha.22
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/.turbo/turbo-lint$colon$check.log +2 -0
- package/.turbo/turbo-typecheck.log +1 -0
- package/dist/actions/onboarding.d.ts +1 -1
- package/dist/actions/onboarding.d.ts.map +1 -1
- package/dist/actions/onboarding.js +0 -2
- package/dist/components/OnboardingWizard.d.ts.map +1 -1
- package/dist/components/OnboardingWizard.js +14 -4
- package/dist/components/onboarding/IdentityStep.d.ts.map +1 -1
- package/dist/components/onboarding/IdentityStep.js +161 -42
- package/dist/package.json +3 -3
- package/dist/state/AppContext.d.ts.map +1 -1
- package/dist/state/AppContext.js +2 -10
- package/dist/state/onboarding-resume.js +1 -1
- package/dist/state/persistence.d.ts.map +1 -1
- package/dist/state/persistence.js +0 -1
- package/dist/state/types.d.ts +1 -1
- package/dist/state/types.d.ts.map +1 -1
- package/dist/state/types.js +0 -5
- package/package.json +4 -4
- package/src/actions/onboarding.ts +0 -4
- package/src/components/OnboardingWizard.test.tsx +4 -8
- package/src/components/OnboardingWizard.tsx +26 -9
- package/src/components/onboarding/IdentityStep.tsx +224 -87
- package/src/state/AppContext.tsx +2 -13
- package/src/state/onboarding-resume.test.ts +2 -2
- package/src/state/onboarding-resume.ts +1 -1
- package/src/state/persistence.ts +0 -1
- package/src/state/types.ts +0 -6
- package/test/app/advanced-trajectory-fine-tuning.e2e.test.ts +15 -11
- package/test/browser-extension/README.md +138 -0
- package/test/browser-extension/test-harness.ts +499 -0
- package/test/capacitor-plugins.e2e.test.ts +153 -0
- package/test/test-types.ts +5 -0
- package/tsconfig.typecheck.json +12 -0
- package/dist/components/onboarding/WakeUpStep.d.ts +0 -2
- package/dist/components/onboarding/WakeUpStep.d.ts.map +0 -1
- package/dist/components/onboarding/WakeUpStep.js +0 -82
- package/src/components/onboarding/WakeUpStep.tsx +0 -184
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Browser Extension Test Harness
|
|
2
|
+
|
|
3
|
+
This directory contains tools for testing the ComputerUse Bridge Extension.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The ComputerUse Bridge Extension is a Chrome MV3 extension that enables JavaScript
|
|
8
|
+
evaluation in browser tabs via a WebSocket connection. This test harness validates
|
|
9
|
+
that the extension correctly:
|
|
10
|
+
|
|
11
|
+
1. Connects to the WebSocket server at `ws://127.0.0.1:17373`
|
|
12
|
+
2. Receives eval requests with the correct protocol
|
|
13
|
+
3. Evaluates JavaScript in the active tab
|
|
14
|
+
4. Returns results (or errors) with the correct format
|
|
15
|
+
|
|
16
|
+
## Prerequisites
|
|
17
|
+
|
|
18
|
+
1. **Chrome/Chromium browser** with Developer Mode enabled
|
|
19
|
+
2. **The extension loaded** from `eliza/packages/computeruse/crates/computeruse/browser-extension/`
|
|
20
|
+
3. **At least one browser tab** open (extension needs a tab to evaluate code in)
|
|
21
|
+
4. **Node.js 22+** with tsx installed
|
|
22
|
+
|
|
23
|
+
## Loading the Extension
|
|
24
|
+
|
|
25
|
+
1. Open Chrome and navigate to `chrome://extensions`
|
|
26
|
+
2. Enable "Developer mode" (toggle in top right)
|
|
27
|
+
3. Click "Load unpacked"
|
|
28
|
+
4. Select the `browser-extension/` directory
|
|
29
|
+
5. Verify the extension appears and is enabled
|
|
30
|
+
|
|
31
|
+
## Running the Test Harness
|
|
32
|
+
|
|
33
|
+
### Basic Test Run
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# From milady root
|
|
37
|
+
npx tsx test/browser-extension/test-harness.ts
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Options
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Verbose output (show all messages)
|
|
44
|
+
npx tsx test/browser-extension/test-harness.ts --verbose
|
|
45
|
+
|
|
46
|
+
# Interactive mode (keep server running for manual testing)
|
|
47
|
+
npx tsx test/browser-extension/test-harness.ts --interactive
|
|
48
|
+
|
|
49
|
+
# Custom port (default: 17373)
|
|
50
|
+
npx tsx test/browser-extension/test-harness.ts --port 17374
|
|
51
|
+
|
|
52
|
+
# Custom timeout (default: 30000ms)
|
|
53
|
+
npx tsx test/browser-extension/test-harness.ts --timeout 60000
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Protocol Specification
|
|
57
|
+
|
|
58
|
+
### Request Format
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"id": "unique-request-id",
|
|
63
|
+
"action": "eval",
|
|
64
|
+
"code": "document.title",
|
|
65
|
+
"awaitPromise": false
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Success Response
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"id": "unique-request-id",
|
|
74
|
+
"ok": true,
|
|
75
|
+
"result": "Page Title"
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Error Response
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"id": "unique-request-id",
|
|
84
|
+
"ok": false,
|
|
85
|
+
"error": "ReferenceError: foo is not defined"
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Test Categories
|
|
90
|
+
|
|
91
|
+
The harness runs tests in these categories:
|
|
92
|
+
|
|
93
|
+
1. **Basic JavaScript Evaluation** - Simple expressions, arrays, objects
|
|
94
|
+
2. **DOM Access** - document.title, location.href, querySelectorAll
|
|
95
|
+
3. **Promise Handling** - awaitPromise flag, async operations
|
|
96
|
+
4. **Error Handling** - Syntax errors, runtime errors, rejected promises
|
|
97
|
+
5. **Complex Operations** - Page metadata extraction, finding elements
|
|
98
|
+
|
|
99
|
+
## Troubleshooting
|
|
100
|
+
|
|
101
|
+
### Extension doesn't connect
|
|
102
|
+
|
|
103
|
+
1. Check that the extension is loaded and enabled in `chrome://extensions`
|
|
104
|
+
2. Check that Developer mode is enabled
|
|
105
|
+
3. Reload the extension by clicking the refresh button
|
|
106
|
+
4. Make sure at least one tab is open (not just chrome:// pages)
|
|
107
|
+
|
|
108
|
+
### Tests timeout
|
|
109
|
+
|
|
110
|
+
1. The extension may need a moment to connect after loading
|
|
111
|
+
2. Try increasing the timeout: `--timeout 60000`
|
|
112
|
+
3. Check Chrome DevTools for extension errors (go to `chrome://extensions`, click "Inspect views" on the extension)
|
|
113
|
+
|
|
114
|
+
### "Cannot evaluate in this tab"
|
|
115
|
+
|
|
116
|
+
Some pages block extension access:
|
|
117
|
+
- `chrome://` pages
|
|
118
|
+
- `edge://` pages
|
|
119
|
+
- Extension pages
|
|
120
|
+
- Some protected sites
|
|
121
|
+
|
|
122
|
+
Navigate to a regular webpage (like https://example.com) before running tests.
|
|
123
|
+
|
|
124
|
+
## Manual Testing
|
|
125
|
+
|
|
126
|
+
For manual testing, start the harness in interactive mode:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
npx tsx test/browser-extension/test-harness.ts --interactive --verbose
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Then send requests manually using a tool like `wscat`:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
wscat -c ws://127.0.0.1:17373
|
|
136
|
+
> {"id":"1","action":"eval","code":"document.title"}
|
|
137
|
+
< {"id":"1","ok":true,"result":"Page Title"}
|
|
138
|
+
```
|
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Extension Test Harness
|
|
3
|
+
*
|
|
4
|
+
* This test harness validates the ComputerUse Bridge Extension by:
|
|
5
|
+
* 1. Starting a WebSocket server on port 17373 (extension's expected port)
|
|
6
|
+
* 2. Accepting connections from the extension
|
|
7
|
+
* 3. Sending eval requests and validating responses
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* npx tsx test/browser-extension/test-harness.ts [options]
|
|
11
|
+
*
|
|
12
|
+
* Options:
|
|
13
|
+
* --port <number> Port to listen on (default: 17373)
|
|
14
|
+
* --timeout <ms> Test timeout (default: 30000)
|
|
15
|
+
* --interactive Keep server running for manual testing
|
|
16
|
+
* --verbose Show all messages
|
|
17
|
+
*
|
|
18
|
+
* The extension must be loaded in Chrome and the browser must have
|
|
19
|
+
* at least one tab open for tests to work.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { createServer, type Server } from "node:http";
|
|
23
|
+
import { type WebSocket, WebSocketServer } from "ws";
|
|
24
|
+
|
|
25
|
+
// Configuration
|
|
26
|
+
const DEFAULT_PORT = 17373;
|
|
27
|
+
const DEFAULT_TIMEOUT = 30000;
|
|
28
|
+
|
|
29
|
+
interface EvalRequest {
|
|
30
|
+
id: string;
|
|
31
|
+
action: "eval";
|
|
32
|
+
code: string;
|
|
33
|
+
awaitPromise?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface EvalResponse {
|
|
37
|
+
id: string;
|
|
38
|
+
ok: boolean;
|
|
39
|
+
result?: unknown;
|
|
40
|
+
error?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface TestResult {
|
|
44
|
+
name: string;
|
|
45
|
+
passed: boolean;
|
|
46
|
+
duration: number;
|
|
47
|
+
error?: string;
|
|
48
|
+
details?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Parse command line args
|
|
52
|
+
const args = process.argv.slice(2);
|
|
53
|
+
const PORT = parseInt(
|
|
54
|
+
args.find((_a, i) => args[i - 1] === "--port") || String(DEFAULT_PORT),
|
|
55
|
+
10,
|
|
56
|
+
);
|
|
57
|
+
const TIMEOUT = parseInt(
|
|
58
|
+
args.find((_a, i) => args[i - 1] === "--timeout") || String(DEFAULT_TIMEOUT),
|
|
59
|
+
10,
|
|
60
|
+
);
|
|
61
|
+
const INTERACTIVE = args.includes("--interactive");
|
|
62
|
+
const VERBOSE = args.includes("--verbose");
|
|
63
|
+
|
|
64
|
+
// Test harness class
|
|
65
|
+
class ExtensionTestHarness {
|
|
66
|
+
private server: Server;
|
|
67
|
+
private wss: WebSocketServer;
|
|
68
|
+
private connection: WebSocket | null = null;
|
|
69
|
+
private pendingRequests: Map<
|
|
70
|
+
string,
|
|
71
|
+
{
|
|
72
|
+
resolve: (value: EvalResponse) => void;
|
|
73
|
+
reject: (error: Error) => void;
|
|
74
|
+
timeout: NodeJS.Timeout;
|
|
75
|
+
}
|
|
76
|
+
> = new Map();
|
|
77
|
+
private requestId = 0;
|
|
78
|
+
private results: TestResult[] = [];
|
|
79
|
+
|
|
80
|
+
constructor(private port: number) {
|
|
81
|
+
this.server = createServer();
|
|
82
|
+
this.wss = new WebSocketServer({ server: this.server });
|
|
83
|
+
|
|
84
|
+
this.wss.on("connection", (ws) => {
|
|
85
|
+
this.log("Extension connected");
|
|
86
|
+
this.connection = ws;
|
|
87
|
+
|
|
88
|
+
ws.on("message", (data) => {
|
|
89
|
+
try {
|
|
90
|
+
const message = JSON.parse(data.toString()) as EvalResponse;
|
|
91
|
+
this.handleResponse(message);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
this.log(`Failed to parse message: ${err}`);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
ws.on("close", () => {
|
|
98
|
+
this.log("Extension disconnected");
|
|
99
|
+
this.connection = null;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
ws.on("error", (err) => {
|
|
103
|
+
this.log(`WebSocket error: ${err.message}`);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private log(message: string) {
|
|
109
|
+
if (VERBOSE) {
|
|
110
|
+
console.log(`[harness] ${message}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private handleResponse(response: EvalResponse) {
|
|
115
|
+
this.log(`Received response: ${JSON.stringify(response).slice(0, 200)}...`);
|
|
116
|
+
|
|
117
|
+
const pending = this.pendingRequests.get(response.id);
|
|
118
|
+
if (pending) {
|
|
119
|
+
clearTimeout(pending.timeout);
|
|
120
|
+
this.pendingRequests.delete(response.id);
|
|
121
|
+
pending.resolve(response);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async start(): Promise<void> {
|
|
126
|
+
return new Promise((resolve) => {
|
|
127
|
+
this.server.listen(this.port, () => {
|
|
128
|
+
console.log(`Test harness listening on ws://127.0.0.1:${this.port}`);
|
|
129
|
+
resolve();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async stop(): Promise<void> {
|
|
135
|
+
return new Promise((resolve) => {
|
|
136
|
+
this.wss.close(() => {
|
|
137
|
+
this.server.close(() => {
|
|
138
|
+
resolve();
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async waitForConnection(timeoutMs: number = 30000): Promise<void> {
|
|
145
|
+
const start = Date.now();
|
|
146
|
+
while (!this.connection && Date.now() - start < timeoutMs) {
|
|
147
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
148
|
+
}
|
|
149
|
+
if (!this.connection) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
"Extension did not connect within timeout. Make sure:\n" +
|
|
152
|
+
"1. The extension is loaded in Chrome (chrome://extensions)\n" +
|
|
153
|
+
"2. Developer mode is enabled\n" +
|
|
154
|
+
"3. At least one browser tab is open",
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async eval(code: string, awaitPromise = false): Promise<EvalResponse> {
|
|
160
|
+
if (!this.connection) {
|
|
161
|
+
throw new Error("No extension connected");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const id = `test-${++this.requestId}`;
|
|
165
|
+
const request: EvalRequest = {
|
|
166
|
+
id,
|
|
167
|
+
action: "eval",
|
|
168
|
+
code,
|
|
169
|
+
awaitPromise,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
return new Promise((resolve, reject) => {
|
|
173
|
+
const timeout = setTimeout(() => {
|
|
174
|
+
this.pendingRequests.delete(id);
|
|
175
|
+
reject(new Error(`Eval request timed out: ${code.slice(0, 50)}...`));
|
|
176
|
+
}, TIMEOUT);
|
|
177
|
+
|
|
178
|
+
this.pendingRequests.set(id, { resolve, reject, timeout });
|
|
179
|
+
|
|
180
|
+
this.log(`Sending eval: ${JSON.stringify(request)}`);
|
|
181
|
+
this.connection?.send(JSON.stringify(request));
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// =========================================================================
|
|
186
|
+
// TEST CASES
|
|
187
|
+
// =========================================================================
|
|
188
|
+
|
|
189
|
+
async runTest(
|
|
190
|
+
name: string,
|
|
191
|
+
testFn: () => Promise<void>,
|
|
192
|
+
): Promise<TestResult> {
|
|
193
|
+
const start = Date.now();
|
|
194
|
+
try {
|
|
195
|
+
await testFn();
|
|
196
|
+
const result: TestResult = {
|
|
197
|
+
name,
|
|
198
|
+
passed: true,
|
|
199
|
+
duration: Date.now() - start,
|
|
200
|
+
};
|
|
201
|
+
this.results.push(result);
|
|
202
|
+
console.log(` ✓ ${name} (${result.duration}ms)`);
|
|
203
|
+
return result;
|
|
204
|
+
} catch (err) {
|
|
205
|
+
const result: TestResult = {
|
|
206
|
+
name,
|
|
207
|
+
passed: false,
|
|
208
|
+
duration: Date.now() - start,
|
|
209
|
+
error: err instanceof Error ? err.message : String(err),
|
|
210
|
+
};
|
|
211
|
+
this.results.push(result);
|
|
212
|
+
console.log(` ✗ ${name} (${result.duration}ms)`);
|
|
213
|
+
console.log(` Error: ${result.error}`);
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async runAllTests(): Promise<void> {
|
|
219
|
+
console.log(
|
|
220
|
+
"\n═══════════════════════════════════════════════════════════",
|
|
221
|
+
);
|
|
222
|
+
console.log(" Browser Extension Test Suite");
|
|
223
|
+
console.log(
|
|
224
|
+
"═══════════════════════════════════════════════════════════\n",
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
// Basic eval tests
|
|
228
|
+
console.log("Basic JavaScript Evaluation:");
|
|
229
|
+
|
|
230
|
+
await this.runTest("Simple expression evaluation", async () => {
|
|
231
|
+
const response = await this.eval("1 + 1");
|
|
232
|
+
if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
|
|
233
|
+
if (response.result !== 2) {
|
|
234
|
+
throw new Error(`Expected 2, got ${response.result}`);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
await this.runTest("String expression", async () => {
|
|
239
|
+
const response = await this.eval('"hello" + " " + "world"');
|
|
240
|
+
if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
|
|
241
|
+
if (response.result !== "hello world") {
|
|
242
|
+
throw new Error(`Expected 'hello world', got ${response.result}`);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
await this.runTest("Array operations", async () => {
|
|
247
|
+
const response = await this.eval("[1, 2, 3].map(x => x * 2)");
|
|
248
|
+
if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
|
|
249
|
+
const result = response.result as number[];
|
|
250
|
+
if (!Array.isArray(result) || result.join(",") !== "2,4,6") {
|
|
251
|
+
throw new Error(`Expected [2,4,6], got ${JSON.stringify(result)}`);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
await this.runTest("Object creation", async () => {
|
|
256
|
+
const response = await this.eval('({ name: "test", value: 42 })');
|
|
257
|
+
if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
|
|
258
|
+
const result = response.result as { name: string; value: number };
|
|
259
|
+
if (result.name !== "test" || result.value !== 42) {
|
|
260
|
+
throw new Error(`Unexpected object: ${JSON.stringify(result)}`);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// DOM access tests
|
|
265
|
+
console.log("\nDOM Access:");
|
|
266
|
+
|
|
267
|
+
await this.runTest("Get document title", async () => {
|
|
268
|
+
const response = await this.eval("document.title");
|
|
269
|
+
if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
|
|
270
|
+
if (typeof response.result !== "string") {
|
|
271
|
+
throw new Error(`Expected string, got ${typeof response.result}`);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
await this.runTest("Get current URL", async () => {
|
|
276
|
+
const response = await this.eval("window.location.href");
|
|
277
|
+
if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
|
|
278
|
+
if (
|
|
279
|
+
typeof response.result !== "string" ||
|
|
280
|
+
!response.result.startsWith("http")
|
|
281
|
+
) {
|
|
282
|
+
throw new Error(`Expected URL, got ${response.result}`);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
await this.runTest("Query DOM elements", async () => {
|
|
287
|
+
const response = await this.eval(
|
|
288
|
+
"document.querySelectorAll('*').length > 0",
|
|
289
|
+
);
|
|
290
|
+
if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
|
|
291
|
+
if (response.result !== true) {
|
|
292
|
+
throw new Error("Expected page to have DOM elements");
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
await this.runTest("Get body text content", async () => {
|
|
297
|
+
const response = await this.eval(
|
|
298
|
+
"document.body ? document.body.innerText.slice(0, 100) : 'no body'",
|
|
299
|
+
);
|
|
300
|
+
if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
|
|
301
|
+
if (typeof response.result !== "string") {
|
|
302
|
+
throw new Error(`Expected string, got ${typeof response.result}`);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// Promise handling tests
|
|
307
|
+
console.log("\nPromise Handling:");
|
|
308
|
+
|
|
309
|
+
await this.runTest("Await simple promise", async () => {
|
|
310
|
+
const response = await this.eval(
|
|
311
|
+
"Promise.resolve(42)",
|
|
312
|
+
true, // awaitPromise
|
|
313
|
+
);
|
|
314
|
+
if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
|
|
315
|
+
if (response.result !== 42) {
|
|
316
|
+
throw new Error(`Expected 42, got ${response.result}`);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
await this.runTest("Await delayed promise", async () => {
|
|
321
|
+
const response = await this.eval(
|
|
322
|
+
"new Promise(r => setTimeout(() => r('delayed'), 100))",
|
|
323
|
+
true,
|
|
324
|
+
);
|
|
325
|
+
if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
|
|
326
|
+
if (response.result !== "delayed") {
|
|
327
|
+
throw new Error(`Expected 'delayed', got ${response.result}`);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
await this.runTest("Await fetch (if available)", async () => {
|
|
332
|
+
const response = await this.eval(
|
|
333
|
+
`typeof fetch === 'function' ?
|
|
334
|
+
fetch('https://httpbin.org/get').then(r => r.ok) :
|
|
335
|
+
true`,
|
|
336
|
+
true,
|
|
337
|
+
);
|
|
338
|
+
if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
|
|
339
|
+
if (response.result !== true) {
|
|
340
|
+
throw new Error(`Expected true, got ${response.result}`);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Error handling tests
|
|
345
|
+
console.log("\nError Handling:");
|
|
346
|
+
|
|
347
|
+
await this.runTest("Handle syntax error", async () => {
|
|
348
|
+
const response = await this.eval("function { invalid");
|
|
349
|
+
if (response.ok) {
|
|
350
|
+
throw new Error("Expected syntax error to fail");
|
|
351
|
+
}
|
|
352
|
+
if (!response.error) {
|
|
353
|
+
throw new Error("Expected error message");
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
await this.runTest("Handle runtime error", async () => {
|
|
358
|
+
const response = await this.eval("nonExistentVariable.property");
|
|
359
|
+
if (response.ok) {
|
|
360
|
+
throw new Error("Expected reference error to fail");
|
|
361
|
+
}
|
|
362
|
+
if (!response.error) {
|
|
363
|
+
throw new Error("Expected error message");
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
await this.runTest("Handle rejected promise", async () => {
|
|
368
|
+
const response = await this.eval(
|
|
369
|
+
"Promise.reject(new Error('test rejection'))",
|
|
370
|
+
true,
|
|
371
|
+
);
|
|
372
|
+
if (response.ok) {
|
|
373
|
+
throw new Error("Expected promise rejection to fail");
|
|
374
|
+
}
|
|
375
|
+
if (!response.error || !response.error.includes("test rejection")) {
|
|
376
|
+
throw new Error(`Expected rejection error, got: ${response.error}`);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Complex operations
|
|
381
|
+
console.log("\nComplex Operations:");
|
|
382
|
+
|
|
383
|
+
await this.runTest("Get page metadata", async () => {
|
|
384
|
+
const response = await this.eval(`({
|
|
385
|
+
title: document.title,
|
|
386
|
+
url: window.location.href,
|
|
387
|
+
elementCount: document.querySelectorAll('*').length,
|
|
388
|
+
hasBody: !!document.body
|
|
389
|
+
})`);
|
|
390
|
+
if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
|
|
391
|
+
const result = response.result as Record<string, unknown>;
|
|
392
|
+
if (!result.title || !result.url || !result.elementCount) {
|
|
393
|
+
throw new Error(`Incomplete metadata: ${JSON.stringify(result)}`);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
await this.runTest("Find all links", async () => {
|
|
398
|
+
const response = await this.eval(
|
|
399
|
+
"Array.from(document.querySelectorAll('a')).slice(0, 10).map(a => ({href: a.href, text: a.textContent?.slice(0, 50)}))",
|
|
400
|
+
);
|
|
401
|
+
if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
|
|
402
|
+
if (!Array.isArray(response.result)) {
|
|
403
|
+
throw new Error(`Expected array, got ${typeof response.result}`);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
await this.runTest("Find interactive elements", async () => {
|
|
408
|
+
const response = await this.eval(`
|
|
409
|
+
Array.from(document.querySelectorAll('button, input, select, textarea, [role="button"]'))
|
|
410
|
+
.slice(0, 10)
|
|
411
|
+
.map(el => ({
|
|
412
|
+
tag: el.tagName,
|
|
413
|
+
type: el.getAttribute('type'),
|
|
414
|
+
name: el.getAttribute('name'),
|
|
415
|
+
id: el.id || null
|
|
416
|
+
}))
|
|
417
|
+
`);
|
|
418
|
+
if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
|
|
419
|
+
if (!Array.isArray(response.result)) {
|
|
420
|
+
throw new Error(`Expected array, got ${typeof response.result}`);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Print summary
|
|
425
|
+
this.printSummary();
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
printSummary(): void {
|
|
429
|
+
const passed = this.results.filter((r) => r.passed).length;
|
|
430
|
+
const failed = this.results.filter((r) => !r.passed).length;
|
|
431
|
+
const totalDuration = this.results.reduce((sum, r) => sum + r.duration, 0);
|
|
432
|
+
|
|
433
|
+
console.log(
|
|
434
|
+
"\n═══════════════════════════════════════════════════════════",
|
|
435
|
+
);
|
|
436
|
+
console.log(" Summary");
|
|
437
|
+
console.log("═══════════════════════════════════════════════════════════");
|
|
438
|
+
console.log(` Passed: ${passed}`);
|
|
439
|
+
console.log(` Failed: ${failed}`);
|
|
440
|
+
console.log(` Total: ${this.results.length}`);
|
|
441
|
+
console.log(` Duration: ${totalDuration}ms`);
|
|
442
|
+
console.log(
|
|
443
|
+
"═══════════════════════════════════════════════════════════\n",
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
if (failed > 0) {
|
|
447
|
+
console.log("Failed tests:");
|
|
448
|
+
for (const result of this.results.filter((r) => !r.passed)) {
|
|
449
|
+
console.log(` - ${result.name}: ${result.error}`);
|
|
450
|
+
}
|
|
451
|
+
console.log("");
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
getResults(): TestResult[] {
|
|
456
|
+
return this.results;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// ============================================================================
|
|
461
|
+
// MAIN
|
|
462
|
+
// ============================================================================
|
|
463
|
+
|
|
464
|
+
async function main() {
|
|
465
|
+
console.log("Browser Extension Test Harness");
|
|
466
|
+
console.log("==============================\n");
|
|
467
|
+
|
|
468
|
+
const harness = new ExtensionTestHarness(PORT);
|
|
469
|
+
|
|
470
|
+
try {
|
|
471
|
+
await harness.start();
|
|
472
|
+
|
|
473
|
+
console.log("Waiting for extension to connect...");
|
|
474
|
+
console.log("Make sure the extension is loaded in Chrome and");
|
|
475
|
+
console.log("at least one tab is open.\n");
|
|
476
|
+
|
|
477
|
+
if (INTERACTIVE) {
|
|
478
|
+
console.log("Interactive mode - server will stay running.");
|
|
479
|
+
console.log("Press Ctrl+C to stop.\n");
|
|
480
|
+
// Keep running
|
|
481
|
+
await new Promise(() => {});
|
|
482
|
+
} else {
|
|
483
|
+
await harness.waitForConnection(TIMEOUT);
|
|
484
|
+
await harness.runAllTests();
|
|
485
|
+
|
|
486
|
+
const results = harness.getResults();
|
|
487
|
+
const failed = results.filter((r) => !r.passed).length;
|
|
488
|
+
|
|
489
|
+
await harness.stop();
|
|
490
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
491
|
+
}
|
|
492
|
+
} catch (err) {
|
|
493
|
+
console.error("\nError:", err instanceof Error ? err.message : String(err));
|
|
494
|
+
await harness.stop();
|
|
495
|
+
process.exit(1);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
main();
|