@mediar-ai/terminator 0.23.0 → 0.23.2
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/package.json +6 -6
- package/tests/execute-browser-script-wrapper.test.js +75 -24
- package/tests/fixtures/script-with-env.js +16 -0
- package/wrapper.js +34 -8
- package/wrapper.ts +158 -103
package/package.json
CHANGED
|
@@ -40,11 +40,11 @@
|
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
"optionalDependencies": {
|
|
43
|
-
"@mediar-ai/terminator-darwin-arm64": "0.23.
|
|
44
|
-
"@mediar-ai/terminator-darwin-x64": "0.23.
|
|
45
|
-
"@mediar-ai/terminator-linux-x64-gnu": "0.23.
|
|
46
|
-
"@mediar-ai/terminator-win32-arm64-msvc": "0.23.
|
|
47
|
-
"@mediar-ai/terminator-win32-x64-msvc": "0.23.
|
|
43
|
+
"@mediar-ai/terminator-darwin-arm64": "0.23.2",
|
|
44
|
+
"@mediar-ai/terminator-darwin-x64": "0.23.2",
|
|
45
|
+
"@mediar-ai/terminator-linux-x64-gnu": "0.23.2",
|
|
46
|
+
"@mediar-ai/terminator-win32-arm64-msvc": "0.23.2",
|
|
47
|
+
"@mediar-ai/terminator-win32-x64-msvc": "0.23.2"
|
|
48
48
|
},
|
|
49
49
|
"repository": {
|
|
50
50
|
"type": "git",
|
|
@@ -62,5 +62,5 @@
|
|
|
62
62
|
"test-hook": "powershell.exe -ExecutionPolicy Bypass -File \"../../.git/hooks/pre-push.ps1\""
|
|
63
63
|
},
|
|
64
64
|
"types": "wrapper.d.ts",
|
|
65
|
-
"version": "0.23.
|
|
65
|
+
"version": "0.23.2"
|
|
66
66
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
const assert = require(
|
|
2
|
-
const path = require(
|
|
3
|
-
const fs = require(
|
|
1
|
+
const assert = require("assert");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const fs = require("fs");
|
|
4
4
|
|
|
5
|
-
const { Desktop } = require(
|
|
5
|
+
const { Desktop } = require("../wrapper.js");
|
|
6
6
|
|
|
7
7
|
async function testFunctionInput() {
|
|
8
8
|
let capturedScript = null;
|
|
9
|
-
const expected = { greeting:
|
|
9
|
+
const expected = { greeting: "hello", answer: 42 };
|
|
10
10
|
const fake = {
|
|
11
11
|
_originalExecuteBrowserScript: async (script) => {
|
|
12
12
|
capturedScript = script;
|
|
@@ -17,68 +17,119 @@ async function testFunctionInput() {
|
|
|
17
17
|
const result = await Desktop.prototype.executeBrowserScript.call(
|
|
18
18
|
fake,
|
|
19
19
|
({ greeting, answer }) => ({ greeting, answer }),
|
|
20
|
-
{ greeting:
|
|
20
|
+
{ greeting: "hello", answer: 42 },
|
|
21
21
|
);
|
|
22
22
|
|
|
23
23
|
assert.deepStrictEqual(result, expected);
|
|
24
24
|
assert.ok(
|
|
25
25
|
capturedScript.includes('"greeting":"hello"') &&
|
|
26
26
|
capturedScript.includes('"answer":42'),
|
|
27
|
-
|
|
27
|
+
"env payload should be embedded in generated script",
|
|
28
28
|
);
|
|
29
29
|
assert.ok(
|
|
30
|
-
capturedScript.trim().startsWith(
|
|
31
|
-
|
|
30
|
+
capturedScript.trim().startsWith("(async function()"),
|
|
31
|
+
"generated script should be wrapped in async IIFE",
|
|
32
32
|
);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
async function testStringInput() {
|
|
36
36
|
const fake = {
|
|
37
|
-
_originalExecuteBrowserScript: async () =>
|
|
37
|
+
_originalExecuteBrowserScript: async () => "raw-result",
|
|
38
38
|
};
|
|
39
39
|
const script = '(() => "ignored")()';
|
|
40
|
-
const result = await Desktop.prototype.executeBrowserScript.call(
|
|
41
|
-
|
|
40
|
+
const result = await Desktop.prototype.executeBrowserScript.call(
|
|
41
|
+
fake,
|
|
42
|
+
script,
|
|
43
|
+
);
|
|
44
|
+
assert.strictEqual(result, "raw-result");
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
async function testFileInput() {
|
|
45
|
-
const fixturePath = path.join(
|
|
46
|
-
|
|
48
|
+
const fixturePath = path.join(
|
|
49
|
+
__dirname,
|
|
50
|
+
"fixtures",
|
|
51
|
+
"sample-browser-script.js",
|
|
52
|
+
);
|
|
53
|
+
const expectedScript = fs.readFileSync(fixturePath, "utf8");
|
|
47
54
|
let receivedScript = null;
|
|
48
55
|
const fake = {
|
|
49
56
|
_originalExecuteBrowserScript: async (script) => {
|
|
50
57
|
receivedScript = script;
|
|
51
|
-
return
|
|
58
|
+
return "file-result";
|
|
52
59
|
},
|
|
53
60
|
};
|
|
54
61
|
|
|
62
|
+
// Test without env - should not inject
|
|
55
63
|
const result = await Desktop.prototype.executeBrowserScript.call(fake, {
|
|
56
64
|
file: fixturePath,
|
|
57
|
-
env: { unused: true },
|
|
58
65
|
});
|
|
59
66
|
|
|
60
|
-
assert.strictEqual(result,
|
|
67
|
+
assert.strictEqual(result, "file-result");
|
|
61
68
|
assert.strictEqual(receivedScript, expectedScript);
|
|
62
69
|
}
|
|
63
70
|
|
|
71
|
+
async function testFileInputWithEnvInjection() {
|
|
72
|
+
const fixturePath = path.join(__dirname, "fixtures", "script-with-env.js");
|
|
73
|
+
const columnPositions = [10, 20, 30];
|
|
74
|
+
let receivedScript = null;
|
|
75
|
+
const fake = {
|
|
76
|
+
_originalExecuteBrowserScript: async (script) => {
|
|
77
|
+
receivedScript = script;
|
|
78
|
+
// Simulate executing the script with injected env
|
|
79
|
+
const column_positions = columnPositions;
|
|
80
|
+
const result = eval(script);
|
|
81
|
+
return JSON.stringify(result);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const result = await Desktop.prototype.executeBrowserScript.call(fake, {
|
|
86
|
+
file: fixturePath,
|
|
87
|
+
env: { column_positions: columnPositions },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Result comes back as JSON string for file-based scripts
|
|
91
|
+
const parsed = JSON.parse(result);
|
|
92
|
+
assert.deepStrictEqual(parsed, {
|
|
93
|
+
positions: columnPositions,
|
|
94
|
+
count: 3,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Verify the env was injected into the script as an env object
|
|
98
|
+
assert.ok(
|
|
99
|
+
receivedScript.includes("const env = "),
|
|
100
|
+
"env object should be injected into script",
|
|
101
|
+
);
|
|
102
|
+
assert.ok(
|
|
103
|
+
receivedScript.includes('"column_positions"'),
|
|
104
|
+
"column_positions key should be in env object",
|
|
105
|
+
);
|
|
106
|
+
assert.ok(
|
|
107
|
+
receivedScript.includes(JSON.stringify(columnPositions)),
|
|
108
|
+
"column_positions value should be embedded",
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
64
112
|
async function run() {
|
|
65
113
|
try {
|
|
66
114
|
await testFunctionInput();
|
|
67
|
-
console.log(
|
|
115
|
+
console.log("✅ executeBrowserScript handles function input");
|
|
68
116
|
|
|
69
117
|
await testStringInput();
|
|
70
|
-
console.log(
|
|
118
|
+
console.log("✅ executeBrowserScript preserves string behavior");
|
|
71
119
|
|
|
72
120
|
await testFileInput();
|
|
73
|
-
console.log(
|
|
121
|
+
console.log("✅ executeBrowserScript loads scripts from files");
|
|
74
122
|
|
|
75
|
-
|
|
123
|
+
await testFileInputWithEnvInjection();
|
|
124
|
+
console.log(
|
|
125
|
+
"✅ executeBrowserScript injects env variables into file scripts",
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
console.log("🎉 All executeBrowserScript wrapper tests passed");
|
|
76
129
|
} catch (err) {
|
|
77
|
-
console.error(
|
|
130
|
+
console.error("❌ executeBrowserScript wrapper test failed:", err);
|
|
78
131
|
process.exitCode = 1;
|
|
79
132
|
}
|
|
80
133
|
}
|
|
81
134
|
|
|
82
135
|
run();
|
|
83
|
-
|
|
84
|
-
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Browser script that uses injected env object
|
|
2
|
+
(() => {
|
|
3
|
+
// env object should be auto-injected by wrapper
|
|
4
|
+
if (typeof env === "undefined") {
|
|
5
|
+
throw new Error("env not injected");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (!env.column_positions) {
|
|
9
|
+
throw new Error("column_positions not in env");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
positions: env.column_positions,
|
|
14
|
+
count: env.column_positions.length,
|
|
15
|
+
};
|
|
16
|
+
})();
|
package/wrapper.js
CHANGED
|
@@ -152,25 +152,35 @@ function mapNativeError(error) {
|
|
|
152
152
|
if (!error.message) return error;
|
|
153
153
|
const message = error.message;
|
|
154
154
|
if (message.startsWith("ELEMENT_NOT_FOUND:")) {
|
|
155
|
-
return new ElementNotFoundError(
|
|
155
|
+
return new ElementNotFoundError(
|
|
156
|
+
message.replace("ELEMENT_NOT_FOUND:", "").trim()
|
|
157
|
+
);
|
|
156
158
|
}
|
|
157
159
|
if (message.startsWith("OPERATION_TIMED_OUT:")) {
|
|
158
160
|
return new TimeoutError(message.replace("OPERATION_TIMED_OUT:", "").trim());
|
|
159
161
|
}
|
|
160
162
|
if (message.startsWith("PERMISSION_DENIED:")) {
|
|
161
|
-
return new PermissionDeniedError(
|
|
163
|
+
return new PermissionDeniedError(
|
|
164
|
+
message.replace("PERMISSION_DENIED:", "").trim()
|
|
165
|
+
);
|
|
162
166
|
}
|
|
163
167
|
if (message.startsWith("PLATFORM_ERROR:")) {
|
|
164
168
|
return new PlatformError(message.replace("PLATFORM_ERROR:", "").trim());
|
|
165
169
|
}
|
|
166
170
|
if (message.startsWith("UNSUPPORTED_OPERATION:")) {
|
|
167
|
-
return new UnsupportedOperationError(
|
|
171
|
+
return new UnsupportedOperationError(
|
|
172
|
+
message.replace("UNSUPPORTED_OPERATION:", "").trim()
|
|
173
|
+
);
|
|
168
174
|
}
|
|
169
175
|
if (message.startsWith("UNSUPPORTED_PLATFORM:")) {
|
|
170
|
-
return new UnsupportedPlatformError(
|
|
176
|
+
return new UnsupportedPlatformError(
|
|
177
|
+
message.replace("UNSUPPORTED_PLATFORM:", "").trim()
|
|
178
|
+
);
|
|
171
179
|
}
|
|
172
180
|
if (message.startsWith("INVALID_ARGUMENT:")) {
|
|
173
|
-
return new InvalidArgumentError(
|
|
181
|
+
return new InvalidArgumentError(
|
|
182
|
+
message.replace("INVALID_ARGUMENT:", "").trim()
|
|
183
|
+
);
|
|
174
184
|
}
|
|
175
185
|
if (message.startsWith("INTERNAL_ERROR:")) {
|
|
176
186
|
return new InternalError(message.replace("INTERNAL_ERROR:", "").trim());
|
|
@@ -180,6 +190,7 @@ function mapNativeError(error) {
|
|
|
180
190
|
async function enhancedExecuteBrowserScript(scriptOrFunction, envOrOptions) {
|
|
181
191
|
let script;
|
|
182
192
|
let env = {};
|
|
193
|
+
let shouldInjectEnv = false;
|
|
183
194
|
if (typeof scriptOrFunction === "string") {
|
|
184
195
|
if (scriptOrFunction.endsWith(".ts") || scriptOrFunction.endsWith(".js")) {
|
|
185
196
|
const filePath = path.resolve(scriptOrFunction);
|
|
@@ -197,11 +208,15 @@ async function enhancedExecuteBrowserScript(scriptOrFunction, envOrOptions) {
|
|
|
197
208
|
});
|
|
198
209
|
fileContent = result.code;
|
|
199
210
|
} catch (e) {
|
|
200
|
-
console.warn(
|
|
211
|
+
console.warn(
|
|
212
|
+
"esbuild not found - using TypeScript file as-is:",
|
|
213
|
+
e.message
|
|
214
|
+
);
|
|
201
215
|
}
|
|
202
216
|
}
|
|
203
217
|
script = fileContent;
|
|
204
218
|
env = envOrOptions || {};
|
|
219
|
+
shouldInjectEnv = true;
|
|
205
220
|
} else {
|
|
206
221
|
script = scriptOrFunction;
|
|
207
222
|
}
|
|
@@ -240,13 +255,24 @@ async function enhancedExecuteBrowserScript(scriptOrFunction, envOrOptions) {
|
|
|
240
255
|
});
|
|
241
256
|
fileContent = result.code;
|
|
242
257
|
} catch (e) {
|
|
243
|
-
console.warn(
|
|
258
|
+
console.warn(
|
|
259
|
+
"esbuild not found - using TypeScript file as-is:",
|
|
260
|
+
e.message
|
|
261
|
+
);
|
|
244
262
|
}
|
|
245
263
|
}
|
|
246
264
|
script = fileContent;
|
|
247
265
|
env = options.env || {};
|
|
266
|
+
shouldInjectEnv = true;
|
|
248
267
|
} else {
|
|
249
|
-
throw new Error(
|
|
268
|
+
throw new Error(
|
|
269
|
+
"Invalid argument to executeBrowserScript: expected string, function, or {file, env} object"
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
if (shouldInjectEnv && env && Object.keys(env).length > 0) {
|
|
273
|
+
const envObject = `const env = ${JSON.stringify(env)};`;
|
|
274
|
+
script = `${envObject}
|
|
275
|
+
${script}`;
|
|
250
276
|
}
|
|
251
277
|
const resultStr = await this._originalExecuteBrowserScript(script);
|
|
252
278
|
if (typeof scriptOrFunction === "function") {
|
package/wrapper.ts
CHANGED
|
@@ -1,27 +1,35 @@
|
|
|
1
|
-
import * as native from
|
|
2
|
-
import * as util from
|
|
3
|
-
import * as fs from
|
|
4
|
-
import * as path from
|
|
1
|
+
import * as native from "./index.js";
|
|
2
|
+
import * as util from "util";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import * as path from "path";
|
|
5
5
|
|
|
6
6
|
// Type definitions for native classes
|
|
7
|
-
type NativeClass =
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
type NativeClass =
|
|
8
|
+
| typeof native.Desktop
|
|
9
|
+
| typeof native.Element
|
|
10
|
+
| typeof native.Locator
|
|
11
|
+
| typeof native.Selector;
|
|
12
|
+
|
|
13
|
+
function patchInspector(
|
|
14
|
+
Klass: any,
|
|
15
|
+
methodName = "toString",
|
|
16
|
+
forcePlainObject = false,
|
|
17
|
+
): void {
|
|
18
|
+
if (!Klass || typeof Klass !== "function") {
|
|
19
|
+
console.log("inspect not a function");
|
|
12
20
|
return;
|
|
13
21
|
}
|
|
14
22
|
const proto = Klass.prototype;
|
|
15
23
|
const original = proto[util.inspect.custom];
|
|
16
|
-
proto[util.inspect.custom] = function(...args: any[]) {
|
|
17
|
-
if (typeof this[methodName] ===
|
|
24
|
+
proto[util.inspect.custom] = function (...args: any[]) {
|
|
25
|
+
if (typeof this[methodName] === "function") {
|
|
18
26
|
const result = this[methodName](...args);
|
|
19
|
-
if (forcePlainObject && result && typeof result ===
|
|
27
|
+
if (forcePlainObject && result && typeof result === "object") {
|
|
20
28
|
return { ...result };
|
|
21
29
|
}
|
|
22
30
|
return result;
|
|
23
31
|
}
|
|
24
|
-
if (typeof original ===
|
|
32
|
+
if (typeof original === "function") {
|
|
25
33
|
return original.apply(this, args);
|
|
26
34
|
}
|
|
27
35
|
return { ...this };
|
|
@@ -29,12 +37,12 @@ function patchInspector(Klass: any, methodName = 'toString', forcePlainObject =
|
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
function wrapNativeFunction<T extends Function>(fn: T): T {
|
|
32
|
-
if (typeof fn !==
|
|
33
|
-
return function(this: any, ...args: any[]) {
|
|
40
|
+
if (typeof fn !== "function") return fn;
|
|
41
|
+
return function (this: any, ...args: any[]) {
|
|
34
42
|
try {
|
|
35
43
|
const result = fn.apply(this, args);
|
|
36
44
|
if (result instanceof Promise) {
|
|
37
|
-
return result.catch(error => {
|
|
45
|
+
return result.catch((error) => {
|
|
38
46
|
throw mapNativeError(error);
|
|
39
47
|
});
|
|
40
48
|
}
|
|
@@ -48,15 +56,18 @@ function wrapNativeFunction<T extends Function>(fn: T): T {
|
|
|
48
56
|
function wrapClassMethods<T extends NativeClass>(Class: T): T {
|
|
49
57
|
const prototype = Class.prototype;
|
|
50
58
|
const methods = Object.getOwnPropertyNames(prototype);
|
|
51
|
-
methods.forEach(method => {
|
|
52
|
-
if (method !==
|
|
59
|
+
methods.forEach((method) => {
|
|
60
|
+
if (method !== "constructor" && typeof prototype[method] === "function") {
|
|
53
61
|
prototype[method] = wrapNativeFunction(prototype[method]);
|
|
54
62
|
}
|
|
55
63
|
});
|
|
56
64
|
return Class;
|
|
57
65
|
}
|
|
58
66
|
|
|
59
|
-
function wrapClass<T extends NativeClass>(
|
|
67
|
+
function wrapClass<T extends NativeClass>(
|
|
68
|
+
Class: T,
|
|
69
|
+
...inspectOptions: any[]
|
|
70
|
+
): T {
|
|
60
71
|
const Wrapped = wrapClassMethods(Class);
|
|
61
72
|
patchInspector(Wrapped, ...(inspectOptions || []));
|
|
62
73
|
return Wrapped;
|
|
@@ -64,91 +75,101 @@ function wrapClass<T extends NativeClass>(Class: T, ...inspectOptions: any[]): T
|
|
|
64
75
|
|
|
65
76
|
// Custom error classes
|
|
66
77
|
export class ElementNotFoundError extends Error {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
constructor(message: string) {
|
|
79
|
+
super(message);
|
|
80
|
+
this.name = "ElementNotFoundError";
|
|
81
|
+
}
|
|
71
82
|
}
|
|
72
83
|
|
|
73
84
|
export class TimeoutError extends Error {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
85
|
+
constructor(message: string) {
|
|
86
|
+
super(message);
|
|
87
|
+
this.name = "TimeoutError";
|
|
88
|
+
}
|
|
78
89
|
}
|
|
79
90
|
|
|
80
91
|
export class PermissionDeniedError extends Error {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
92
|
+
constructor(message: string) {
|
|
93
|
+
super(message);
|
|
94
|
+
this.name = "PermissionDeniedError";
|
|
95
|
+
}
|
|
85
96
|
}
|
|
86
97
|
|
|
87
98
|
export class PlatformError extends Error {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
99
|
+
constructor(message: string) {
|
|
100
|
+
super(message);
|
|
101
|
+
this.name = "PlatformError";
|
|
102
|
+
}
|
|
92
103
|
}
|
|
93
104
|
|
|
94
105
|
export class UnsupportedOperationError extends Error {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
106
|
+
constructor(message: string) {
|
|
107
|
+
super(message);
|
|
108
|
+
this.name = "UnsupportedOperationError";
|
|
109
|
+
}
|
|
99
110
|
}
|
|
100
111
|
|
|
101
112
|
export class UnsupportedPlatformError extends Error {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
113
|
+
constructor(message: string) {
|
|
114
|
+
super(message);
|
|
115
|
+
this.name = "UnsupportedPlatformError";
|
|
116
|
+
}
|
|
106
117
|
}
|
|
107
118
|
|
|
108
119
|
export class InvalidArgumentError extends Error {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
120
|
+
constructor(message: string) {
|
|
121
|
+
super(message);
|
|
122
|
+
this.name = "InvalidArgumentError";
|
|
123
|
+
}
|
|
113
124
|
}
|
|
114
125
|
|
|
115
126
|
export class InternalError extends Error {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
127
|
+
constructor(message: string) {
|
|
128
|
+
super(message);
|
|
129
|
+
this.name = "InternalError";
|
|
130
|
+
}
|
|
120
131
|
}
|
|
121
132
|
|
|
122
133
|
// Error mapping function
|
|
123
134
|
function mapNativeError(error: any): Error {
|
|
124
|
-
|
|
135
|
+
if (!error.message) return error;
|
|
125
136
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
137
|
+
const message = error.message;
|
|
138
|
+
if (message.startsWith("ELEMENT_NOT_FOUND:")) {
|
|
139
|
+
return new ElementNotFoundError(
|
|
140
|
+
message.replace("ELEMENT_NOT_FOUND:", "").trim(),
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
if (message.startsWith("OPERATION_TIMED_OUT:")) {
|
|
144
|
+
return new TimeoutError(message.replace("OPERATION_TIMED_OUT:", "").trim());
|
|
145
|
+
}
|
|
146
|
+
if (message.startsWith("PERMISSION_DENIED:")) {
|
|
147
|
+
return new PermissionDeniedError(
|
|
148
|
+
message.replace("PERMISSION_DENIED:", "").trim(),
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
if (message.startsWith("PLATFORM_ERROR:")) {
|
|
152
|
+
return new PlatformError(message.replace("PLATFORM_ERROR:", "").trim());
|
|
153
|
+
}
|
|
154
|
+
if (message.startsWith("UNSUPPORTED_OPERATION:")) {
|
|
155
|
+
return new UnsupportedOperationError(
|
|
156
|
+
message.replace("UNSUPPORTED_OPERATION:", "").trim(),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
if (message.startsWith("UNSUPPORTED_PLATFORM:")) {
|
|
160
|
+
return new UnsupportedPlatformError(
|
|
161
|
+
message.replace("UNSUPPORTED_PLATFORM:", "").trim(),
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
if (message.startsWith("INVALID_ARGUMENT:")) {
|
|
165
|
+
return new InvalidArgumentError(
|
|
166
|
+
message.replace("INVALID_ARGUMENT:", "").trim(),
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
if (message.startsWith("INTERNAL_ERROR:")) {
|
|
170
|
+
return new InternalError(message.replace("INTERNAL_ERROR:", "").trim());
|
|
171
|
+
}
|
|
172
|
+
return error;
|
|
152
173
|
}
|
|
153
174
|
|
|
154
175
|
// Types for executeBrowserScript arguments
|
|
@@ -164,46 +185,51 @@ type BrowserScriptInput = string | BrowserScriptFunction | BrowserScriptOptions;
|
|
|
164
185
|
async function enhancedExecuteBrowserScript(
|
|
165
186
|
this: any,
|
|
166
187
|
scriptOrFunction: BrowserScriptInput,
|
|
167
|
-
envOrOptions?: any
|
|
188
|
+
envOrOptions?: any,
|
|
168
189
|
): Promise<any> {
|
|
169
190
|
let script: string;
|
|
170
191
|
let env: any = {};
|
|
192
|
+
let shouldInjectEnv = false; // Only inject env for file-based and string scripts, not functions
|
|
171
193
|
|
|
172
194
|
// Handle different input types
|
|
173
|
-
if (typeof scriptOrFunction ===
|
|
195
|
+
if (typeof scriptOrFunction === "string") {
|
|
174
196
|
// Check if it's a file path
|
|
175
|
-
if (scriptOrFunction.endsWith(
|
|
197
|
+
if (scriptOrFunction.endsWith(".ts") || scriptOrFunction.endsWith(".js")) {
|
|
176
198
|
// File path - read and compile
|
|
177
199
|
const filePath = path.resolve(scriptOrFunction);
|
|
178
200
|
if (!fs.existsSync(filePath)) {
|
|
179
201
|
throw new Error(`Browser script file not found: ${filePath}`);
|
|
180
202
|
}
|
|
181
203
|
|
|
182
|
-
let fileContent = fs.readFileSync(filePath,
|
|
204
|
+
let fileContent = fs.readFileSync(filePath, "utf-8");
|
|
183
205
|
|
|
184
206
|
// If TypeScript, compile it
|
|
185
|
-
if (filePath.endsWith(
|
|
207
|
+
if (filePath.endsWith(".ts")) {
|
|
186
208
|
try {
|
|
187
|
-
const esbuild = require(
|
|
209
|
+
const esbuild = require("esbuild");
|
|
188
210
|
const result = await esbuild.transform(fileContent, {
|
|
189
|
-
loader:
|
|
190
|
-
target:
|
|
191
|
-
format:
|
|
211
|
+
loader: "ts",
|
|
212
|
+
target: "es2020",
|
|
213
|
+
format: "iife",
|
|
192
214
|
});
|
|
193
215
|
fileContent = result.code;
|
|
194
216
|
} catch (e: any) {
|
|
195
217
|
// If esbuild not available, try to use as-is (may work for simple TS)
|
|
196
|
-
console.warn(
|
|
218
|
+
console.warn(
|
|
219
|
+
"esbuild not found - using TypeScript file as-is:",
|
|
220
|
+
e.message,
|
|
221
|
+
);
|
|
197
222
|
}
|
|
198
223
|
}
|
|
199
224
|
|
|
200
225
|
script = fileContent;
|
|
201
226
|
env = envOrOptions || {};
|
|
227
|
+
shouldInjectEnv = true; // Inject env for file paths passed as strings
|
|
202
228
|
} else {
|
|
203
229
|
// Plain string script - use as-is (backward compatible)
|
|
204
230
|
script = scriptOrFunction;
|
|
205
231
|
}
|
|
206
|
-
} else if (typeof scriptOrFunction ===
|
|
232
|
+
} else if (typeof scriptOrFunction === "function") {
|
|
207
233
|
// Function - convert to IIFE with proper wrapping
|
|
208
234
|
const funcString = scriptOrFunction.toString();
|
|
209
235
|
env = envOrOptions || {};
|
|
@@ -224,7 +250,10 @@ async function enhancedExecuteBrowserScript(
|
|
|
224
250
|
return null;
|
|
225
251
|
})()
|
|
226
252
|
`;
|
|
227
|
-
} else if (
|
|
253
|
+
} else if (
|
|
254
|
+
typeof scriptOrFunction === "object" &&
|
|
255
|
+
(scriptOrFunction as BrowserScriptOptions).file
|
|
256
|
+
) {
|
|
228
257
|
// Object with file property
|
|
229
258
|
const options = scriptOrFunction as BrowserScriptOptions;
|
|
230
259
|
const filePath = path.resolve(options.file);
|
|
@@ -232,34 +261,47 @@ async function enhancedExecuteBrowserScript(
|
|
|
232
261
|
throw new Error(`Browser script file not found: ${filePath}`);
|
|
233
262
|
}
|
|
234
263
|
|
|
235
|
-
let fileContent = fs.readFileSync(filePath,
|
|
264
|
+
let fileContent = fs.readFileSync(filePath, "utf-8");
|
|
236
265
|
|
|
237
266
|
// If TypeScript, compile it
|
|
238
|
-
if (filePath.endsWith(
|
|
267
|
+
if (filePath.endsWith(".ts")) {
|
|
239
268
|
try {
|
|
240
|
-
const esbuild = require(
|
|
269
|
+
const esbuild = require("esbuild");
|
|
241
270
|
const result = await esbuild.transform(fileContent, {
|
|
242
|
-
loader:
|
|
243
|
-
target:
|
|
244
|
-
format:
|
|
271
|
+
loader: "ts",
|
|
272
|
+
target: "es2020",
|
|
273
|
+
format: "iife",
|
|
245
274
|
});
|
|
246
275
|
fileContent = result.code;
|
|
247
276
|
} catch (e: any) {
|
|
248
|
-
console.warn(
|
|
277
|
+
console.warn(
|
|
278
|
+
"esbuild not found - using TypeScript file as-is:",
|
|
279
|
+
e.message,
|
|
280
|
+
);
|
|
249
281
|
}
|
|
250
282
|
}
|
|
251
283
|
|
|
252
284
|
script = fileContent;
|
|
253
285
|
env = options.env || {};
|
|
286
|
+
shouldInjectEnv = true; // Inject env for file option objects
|
|
254
287
|
} else {
|
|
255
|
-
throw new Error(
|
|
288
|
+
throw new Error(
|
|
289
|
+
"Invalid argument to executeBrowserScript: expected string, function, or {file, env} object",
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// If env variables are provided and we should inject them (file-based scripts only)
|
|
294
|
+
if (shouldInjectEnv && env && Object.keys(env).length > 0) {
|
|
295
|
+
// Inject as an env object that scripts can access
|
|
296
|
+
const envObject = `const env = ${JSON.stringify(env)};`;
|
|
297
|
+
script = `${envObject}\n${script}`;
|
|
256
298
|
}
|
|
257
299
|
|
|
258
300
|
// Call the original native method
|
|
259
301
|
const resultStr = await this._originalExecuteBrowserScript(script);
|
|
260
302
|
|
|
261
303
|
// If function was passed, try to parse JSON result
|
|
262
|
-
if (typeof scriptOrFunction ===
|
|
304
|
+
if (typeof scriptOrFunction === "function") {
|
|
263
305
|
try {
|
|
264
306
|
return JSON.parse(resultStr);
|
|
265
307
|
} catch (e) {
|
|
@@ -280,15 +322,28 @@ export const Selector = wrapClass(native.Selector);
|
|
|
280
322
|
|
|
281
323
|
// Patch executeBrowserScript on Desktop and Element
|
|
282
324
|
if (Desktop.prototype.executeBrowserScript) {
|
|
283
|
-
(Desktop.prototype as any)._originalExecuteBrowserScript =
|
|
325
|
+
(Desktop.prototype as any)._originalExecuteBrowserScript =
|
|
326
|
+
Desktop.prototype.executeBrowserScript;
|
|
284
327
|
Desktop.prototype.executeBrowserScript = enhancedExecuteBrowserScript;
|
|
285
328
|
}
|
|
286
329
|
|
|
287
330
|
if (Element.prototype.executeBrowserScript) {
|
|
288
|
-
(Element.prototype as any)._originalExecuteBrowserScript =
|
|
331
|
+
(Element.prototype as any)._originalExecuteBrowserScript =
|
|
332
|
+
Element.prototype.executeBrowserScript;
|
|
289
333
|
Element.prototype.executeBrowserScript = enhancedExecuteBrowserScript;
|
|
290
334
|
}
|
|
291
335
|
|
|
292
336
|
// Re-export native types
|
|
293
|
-
export type {
|
|
294
|
-
|
|
337
|
+
export type {
|
|
338
|
+
ValidationResult,
|
|
339
|
+
Bounds,
|
|
340
|
+
Coordinates,
|
|
341
|
+
ClickResult,
|
|
342
|
+
CommandOutput,
|
|
343
|
+
Monitor,
|
|
344
|
+
MonitorScreenshotPair,
|
|
345
|
+
ScreenshotResult,
|
|
346
|
+
UIElementAttributes,
|
|
347
|
+
UINode,
|
|
348
|
+
} from "./index.js";
|
|
349
|
+
export { PropertyLoadingMode, TextPosition } from "./index.js";
|