@stubber/virtual-worker 1.5.5 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/* eslint-disable id-match */
|
|
2
|
+
import { create_error_conceptual } from "#app/functions/create_error_conceptual.js";
|
|
3
|
+
import { create_error_technical } from "#app/functions/create_error_technical.js";
|
|
4
|
+
import { create_success } from "#app/functions/create_success.js";
|
|
5
|
+
import { get_chromium_page } from "../../helpers/get_chromium_page.js";
|
|
6
|
+
import * as playwright from "playwright";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_TIMEOUT_MS = 30000;
|
|
9
|
+
const MAX_SERIALIZATION_DEPTH = 6;
|
|
10
|
+
const MAX_SERIALIZATION_KEYS = 100;
|
|
11
|
+
const async_function_constructor = async function () {}.constructor;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Runs user-provided Playwright code against the current Chromium page.
|
|
15
|
+
* The code is executed as the body of an async function, so `await` and `return`
|
|
16
|
+
* can be used directly.
|
|
17
|
+
*
|
|
18
|
+
* Exposed variables inside the execution context:
|
|
19
|
+
* - `page`: current Playwright page
|
|
20
|
+
* - `context`: the page's BrowserContext
|
|
21
|
+
* - `browser`: the underlying browser instance, if available
|
|
22
|
+
* - `playwright`: Playwright module namespace
|
|
23
|
+
* - `args`: arbitrary user-provided arguments
|
|
24
|
+
* - `params`: original params object
|
|
25
|
+
* - `stubber_context`: task stubber context
|
|
26
|
+
*
|
|
27
|
+
* This runs in-process by design. Isolation is expected to come from the
|
|
28
|
+
* worker container, not from a JavaScript sandbox.
|
|
29
|
+
* @param {Object} params
|
|
30
|
+
* @param {string} params.code - The Playwright code to execute
|
|
31
|
+
* @param {Object} [params.args] - Optional arguments to pass to the code
|
|
32
|
+
* @param {number} [params.timeout_ms] - Optional timeout for code execution in milliseconds (default: 30000)
|
|
33
|
+
*
|
|
34
|
+
*
|
|
35
|
+
*/
|
|
36
|
+
export const browser_execute = async (params, stubber_context) => {
|
|
37
|
+
if (!params || typeof params.code !== "string" || !params.code.trim()) {
|
|
38
|
+
return create_error_conceptual({ message: "Missing required parameter: code" });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const page_result = await get_chromium_page(params, stubber_context);
|
|
42
|
+
if (!page_result.success) {
|
|
43
|
+
return page_result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const page = page_result.payload;
|
|
47
|
+
const context = page.context();
|
|
48
|
+
const browser = context.browser();
|
|
49
|
+
const timeout_ms = normalize_timeout(params.timeout_ms);
|
|
50
|
+
|
|
51
|
+
if (timeout_ms === null) {
|
|
52
|
+
return create_error_conceptual({
|
|
53
|
+
message: "timeout_ms must be a non-negative number",
|
|
54
|
+
details: { timeout_ms: params.timeout_ms },
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const logs = [];
|
|
60
|
+
const execution_console = create_execution_console(logs);
|
|
61
|
+
const execution = run_user_code(params.code, {
|
|
62
|
+
page,
|
|
63
|
+
context,
|
|
64
|
+
browser,
|
|
65
|
+
playwright,
|
|
66
|
+
params,
|
|
67
|
+
args: params.args || {},
|
|
68
|
+
console: execution_console,
|
|
69
|
+
setTimeout,
|
|
70
|
+
clearTimeout,
|
|
71
|
+
setInterval,
|
|
72
|
+
clearInterval,
|
|
73
|
+
URL,
|
|
74
|
+
URLSearchParams,
|
|
75
|
+
TextEncoder,
|
|
76
|
+
TextDecoder,
|
|
77
|
+
AbortController,
|
|
78
|
+
AbortSignal,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const raw_result = await with_timeout(Promise.resolve(execution), timeout_ms);
|
|
82
|
+
const result = serialize_for_payload(raw_result);
|
|
83
|
+
|
|
84
|
+
return create_success({
|
|
85
|
+
message: "Playwright code executed successfully",
|
|
86
|
+
payload: {
|
|
87
|
+
result,
|
|
88
|
+
logs,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return create_error_technical(error);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {string} code
|
|
98
|
+
* @param {object} execution_scope
|
|
99
|
+
*/
|
|
100
|
+
const run_user_code = (code, execution_scope) => {
|
|
101
|
+
const parameter_names = Object.keys(execution_scope);
|
|
102
|
+
const parameter_values = Object.values(execution_scope);
|
|
103
|
+
const execute = new async_function_constructor(...parameter_names, code);
|
|
104
|
+
|
|
105
|
+
return execute(...parameter_values);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param {unknown} timeout_ms
|
|
110
|
+
* @returns {number|null}
|
|
111
|
+
*/
|
|
112
|
+
const normalize_timeout = (timeout_ms) => {
|
|
113
|
+
if (!timeout_ms) {
|
|
114
|
+
return DEFAULT_TIMEOUT_MS;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const normalized = Number(timeout_ms);
|
|
118
|
+
|
|
119
|
+
if (Number.isNaN(normalized) || normalized < 0) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return normalized;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {Promise<unknown>} promise
|
|
128
|
+
* @param {number} timeout_ms
|
|
129
|
+
*/
|
|
130
|
+
const with_timeout = async (promise, timeout_ms) => {
|
|
131
|
+
if (timeout_ms === 0) {
|
|
132
|
+
return await promise;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let timeout_id;
|
|
136
|
+
|
|
137
|
+
const timeout_promise = new Promise((_, reject) => {
|
|
138
|
+
timeout_id = setTimeout(() => {
|
|
139
|
+
reject(new Error(`Playwright code execution timed out after ${timeout_ms}ms`));
|
|
140
|
+
}, timeout_ms);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
return await Promise.race([promise, timeout_promise]);
|
|
145
|
+
} finally {
|
|
146
|
+
clearTimeout(timeout_id);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @param {Array<{ level: string, args: unknown[] }>} logs
|
|
152
|
+
*/
|
|
153
|
+
const create_execution_console = (logs) => {
|
|
154
|
+
return {
|
|
155
|
+
debug: (...args) => log_execution("debug", args, logs),
|
|
156
|
+
info: (...args) => log_execution("info", args, logs),
|
|
157
|
+
log: (...args) => log_execution("log", args, logs),
|
|
158
|
+
warn: (...args) => log_execution("warn", args, logs),
|
|
159
|
+
error: (...args) => log_execution("error", args, logs),
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @param {string} level
|
|
165
|
+
* @param {unknown[]} args
|
|
166
|
+
* @param {Array<{ level: string, args: unknown[] }>} logs
|
|
167
|
+
*/
|
|
168
|
+
const log_execution = (level, args, logs) => {
|
|
169
|
+
const serialized_args = args.map((arg) => serialize_for_payload(arg));
|
|
170
|
+
logs.push({ level, args: serialized_args });
|
|
171
|
+
|
|
172
|
+
const logger = console[level] || console.log;
|
|
173
|
+
logger("[browser_execute]", ...serialized_args);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @param {unknown} value
|
|
178
|
+
* @param {WeakSet<object>} [seen]
|
|
179
|
+
* @param {number} [depth]
|
|
180
|
+
* @returns {unknown}
|
|
181
|
+
*/
|
|
182
|
+
const serialize_for_payload = (value, seen = new WeakSet(), depth = 0) => {
|
|
183
|
+
if (value === null || value === undefined) {
|
|
184
|
+
return value ?? null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (depth >= MAX_SERIALIZATION_DEPTH) {
|
|
188
|
+
return `[MaxDepth:${get_constructor_name(value)}]`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const value_type = typeof value;
|
|
192
|
+
|
|
193
|
+
if (value_type === "string" || value_type === "number" || value_type === "boolean") {
|
|
194
|
+
return value;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (value_type === "bigint") {
|
|
198
|
+
return value.toString();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (value_type === "function") {
|
|
202
|
+
return `[Function:${value.name || "anonymous"}]`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (Array.isArray(value)) {
|
|
206
|
+
return value.map((item) => serialize_for_payload(item, seen, depth + 1));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (value instanceof Error) {
|
|
210
|
+
return {
|
|
211
|
+
name: value.name,
|
|
212
|
+
message: value.message,
|
|
213
|
+
stack: value.stack?.split("\n").slice(0, 2).join("\n"),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (value instanceof Date) {
|
|
218
|
+
return value.toISOString();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (value_type === "object") {
|
|
222
|
+
if (seen.has(value)) {
|
|
223
|
+
return `[Circular:${get_constructor_name(value)}]`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
seen.add(value);
|
|
227
|
+
|
|
228
|
+
const playwright_summary = summarize_playwright_object(value);
|
|
229
|
+
if (playwright_summary) {
|
|
230
|
+
return playwright_summary;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const result = {};
|
|
234
|
+
const entries = Object.entries(value).slice(0, MAX_SERIALIZATION_KEYS);
|
|
235
|
+
for (const [key, entry_value] of entries) {
|
|
236
|
+
result[key] = serialize_for_payload(entry_value, seen, depth + 1);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return String(value);
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* @param {unknown} value
|
|
247
|
+
*/
|
|
248
|
+
const get_constructor_name = (value) => {
|
|
249
|
+
return value && typeof value === "object" && value.constructor?.name ? value.constructor.name : typeof value;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* @param {unknown} value
|
|
254
|
+
* @returns {object|null}
|
|
255
|
+
*/
|
|
256
|
+
const summarize_playwright_object = (value) => {
|
|
257
|
+
const constructor_name = get_constructor_name(value);
|
|
258
|
+
|
|
259
|
+
if (constructor_name === "Page") {
|
|
260
|
+
return {
|
|
261
|
+
type: "Page",
|
|
262
|
+
url: safely_call(value, "url"),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (constructor_name === "BrowserContext") {
|
|
267
|
+
const pages = safely_call(value, "pages");
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
type: "BrowserContext",
|
|
271
|
+
page_count: Array.isArray(pages) ? pages.length : null,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (constructor_name === "Browser") {
|
|
276
|
+
return {
|
|
277
|
+
type: "Browser",
|
|
278
|
+
is_connected: safely_call(value, "isConnected"),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (constructor_name === "Locator") {
|
|
283
|
+
return {
|
|
284
|
+
type: "Locator",
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (constructor_name === "ElementHandle") {
|
|
289
|
+
return {
|
|
290
|
+
type: "ElementHandle",
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return null;
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* @param {unknown} value
|
|
299
|
+
* @param {string} method_name
|
|
300
|
+
*/
|
|
301
|
+
const safely_call = (value, method_name) => {
|
|
302
|
+
try {
|
|
303
|
+
if (value && typeof value[method_name] === "function") {
|
|
304
|
+
return value[method_name]();
|
|
305
|
+
}
|
|
306
|
+
} catch {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return null;
|
|
311
|
+
};
|
package/app/commands/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import { api_proxy } from "./api_proxy/api_proxy.js";
|
|
|
17
17
|
import { browser_extract_markdown } from "./browser/browser_extract_markdown.js";
|
|
18
18
|
import { browser_wait } from "./browser/browser_wait.js";
|
|
19
19
|
import { browser_find } from "./browser/browser_find.js";
|
|
20
|
+
import { browser_execute } from "./browser/browser_execute.js";
|
|
20
21
|
|
|
21
22
|
const all_commands = {
|
|
22
23
|
browser_click,
|
|
@@ -32,6 +33,7 @@ const all_commands = {
|
|
|
32
33
|
browser_manage_sessions,
|
|
33
34
|
browser_wait,
|
|
34
35
|
browser_find,
|
|
36
|
+
browser_execute,
|
|
35
37
|
|
|
36
38
|
cli_run,
|
|
37
39
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
@baseUrl = http://localhost:3000
|
|
2
|
+
@apiKey = 123-456-789
|
|
3
|
+
@orguuid = c8cfd7f1-8015-43ff-8878-d22c136a2325
|
|
4
|
+
@stubref = my-stub-ref
|
|
5
|
+
|
|
6
|
+
POST {{baseUrl}}/api/v1/task-gateway/virtual_worker_send_commands
|
|
7
|
+
Content-Type: application/json
|
|
8
|
+
stubber-virtual-worker-apikey: {{apiKey}}
|
|
9
|
+
|
|
10
|
+
{
|
|
11
|
+
"task": {
|
|
12
|
+
"tasktype": "virtual_worker_send_commands",
|
|
13
|
+
"task_name": "virtual_worker_send_commands",
|
|
14
|
+
"params": {
|
|
15
|
+
"commands": {
|
|
16
|
+
"command_1": {
|
|
17
|
+
"commandtype": "browser_navigate",
|
|
18
|
+
"params": {
|
|
19
|
+
"url": "https://en.wikipedia.org/wiki/Main_Page"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"command_2": {
|
|
23
|
+
"commandtype": "browser_execute",
|
|
24
|
+
"params": {
|
|
25
|
+
"args": {
|
|
26
|
+
"selector": "h1"
|
|
27
|
+
},
|
|
28
|
+
"code": "const title = await page.title();\nconst headings = await page.locator(args.selector).allInnerTexts();\nconsole.log({ title, headings });\nreturn { title, headings, url: page.url() };"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"_stubber": {
|
|
34
|
+
"orguuid": "{{orguuid}}",
|
|
35
|
+
"stubref": "{{stubref}}"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
###
|
|
41
|
+
|
|
42
|
+
# Run eval on the current page context.
|
|
43
|
+
# Assumes the virtual worker already has an active browser page for this stub.
|
|
44
|
+
POST {{baseUrl}}/api/v1/task-gateway/virtual_worker_send_commands
|
|
45
|
+
Content-Type: application/json
|
|
46
|
+
stubber-virtual-worker-apikey: {{apiKey}}
|
|
47
|
+
|
|
48
|
+
{
|
|
49
|
+
"task": {
|
|
50
|
+
"tasktype": "virtual_worker_send_commands",
|
|
51
|
+
"task_name": "virtual_worker_send_commands",
|
|
52
|
+
"params": {
|
|
53
|
+
"commands": {
|
|
54
|
+
"command_1": {
|
|
55
|
+
"commandtype": "browser_execute",
|
|
56
|
+
"params": {
|
|
57
|
+
"args": {
|
|
58
|
+
"expression": "document.title"
|
|
59
|
+
},
|
|
60
|
+
"code": "const result = await page.evaluate((expression) => eval(expression), args.expression);\nreturn { expression: args.expression, result, url: page.url() };"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"_stubber": {
|
|
66
|
+
"orguuid": "{{orguuid}}",
|
|
67
|
+
"stubref": "{{stubref}}"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
@baseUrl = http://localhost:3000
|
|
2
|
+
@apiKey = 123-456-789
|
|
3
|
+
@orguuid = c8cfd7f1-8015-43ff-8878-d22c136a2325
|
|
4
|
+
@stubref = my-stub-ref
|
|
5
|
+
|
|
6
|
+
POST {{baseUrl}}/api/v1/task-gateway/virtual_worker_send_commands
|
|
7
|
+
Content-Type: application/json
|
|
8
|
+
stubber-virtual-worker-apikey: {{apiKey}}
|
|
9
|
+
|
|
10
|
+
{
|
|
11
|
+
"task": {
|
|
12
|
+
"tasktype": "virtual_worker_send_commands",
|
|
13
|
+
"task_name": "virtual_worker_send_commands",
|
|
14
|
+
"params": {
|
|
15
|
+
"commands": {
|
|
16
|
+
"command_1": {
|
|
17
|
+
"commandtype": "browser_navigate",
|
|
18
|
+
"params": {
|
|
19
|
+
"url": "https://fast.com"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"command_2": {
|
|
23
|
+
"commandtype": "browser_wait",
|
|
24
|
+
"params": {
|
|
25
|
+
"ms": 2000
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"command_3": {
|
|
29
|
+
"commandtype": "browser_extract_markdown",
|
|
30
|
+
"params": {}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"_stubber": {
|
|
35
|
+
"orguuid": "{{orguuid}}",
|
|
36
|
+
"stubref": "{{stubref}}"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
@baseUrl = http://localhost:3000
|
|
2
|
+
@apiKey = 123-456-789
|
|
3
|
+
@orguuid = c8cfd7f1-8015-43ff-8878-d22c136a2325
|
|
4
|
+
@stubref = my-stub-ref
|
|
5
|
+
|
|
6
|
+
POST {{baseUrl}}/api/v1/task-gateway/virtual_worker_send_commands
|
|
7
|
+
Content-Type: application/json
|
|
8
|
+
stubber-virtual-worker-apikey: {{apiKey}}
|
|
9
|
+
|
|
10
|
+
{
|
|
11
|
+
"task": {
|
|
12
|
+
"tasktype": "virtual_worker_send_commands",
|
|
13
|
+
"task_name": "virtual_worker_send_commands",
|
|
14
|
+
"params": {
|
|
15
|
+
"commands": {
|
|
16
|
+
"command_1": {
|
|
17
|
+
"commandtype": "browser_navigate",
|
|
18
|
+
"params": {
|
|
19
|
+
"url": "https://berkshirehathaway.com"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"command_2": {
|
|
23
|
+
"commandtype": "browser_find",
|
|
24
|
+
"params": {
|
|
25
|
+
"text": {
|
|
26
|
+
"pattern": "Warren.*Buffett",
|
|
27
|
+
"flags": "i"
|
|
28
|
+
},
|
|
29
|
+
"parent_levels": 1
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"_stubber": {
|
|
35
|
+
"orguuid": "{{orguuid}}",
|
|
36
|
+
"stubref": "{{stubref}}"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
package/package.json
CHANGED
package/devel/tests.sh
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
curl -X POST "http://localhost:3000/api/v1/task-gateway/virtual_worker_send_commands" \
|
|
2
|
-
-H "Content-Type: application/json" \
|
|
3
|
-
-H "stubber-virtual-worker-apikey: 123-456-789" \
|
|
4
|
-
-d '{
|
|
5
|
-
"task": {
|
|
6
|
-
"tasktype": "virtual_worker_send_commands",
|
|
7
|
-
"task_name": "virtual_worker_send_commands",
|
|
8
|
-
"params": {
|
|
9
|
-
"commands": {
|
|
10
|
-
"command_1": {
|
|
11
|
-
"commandtype": "browser_navigate",
|
|
12
|
-
"params": {
|
|
13
|
-
"url": "https://fast.com"
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"command_2": {
|
|
17
|
-
"commandtype": "browser_wait",
|
|
18
|
-
"params": {
|
|
19
|
-
"ms": 2000
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
"command_3": {
|
|
23
|
-
"commandtype": "browser_extract_markdown",
|
|
24
|
-
"params": {
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
"_stubber": {
|
|
30
|
-
"orguuid": "c8cfd7f1-8015-43ff-8878-d22c136a2325",
|
|
31
|
-
"stubref": "my-stub-ref"
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}'
|
|
35
|
-
|
|
36
|
-
curl -X POST "http://localhost:3000/api/v1/task-gateway/virtual_worker_send_commands" \
|
|
37
|
-
-H "Content-Type: application/json" \
|
|
38
|
-
-H "stubber-virtual-worker-apikey: 123-456-789" \
|
|
39
|
-
-d '{
|
|
40
|
-
"task": {
|
|
41
|
-
"tasktype": "virtual_worker_send_commands",
|
|
42
|
-
"task_name": "virtual_worker_send_commands",
|
|
43
|
-
"params": {
|
|
44
|
-
"commands": {
|
|
45
|
-
"command_1": {
|
|
46
|
-
"commandtype": "browser_navigate",
|
|
47
|
-
"params": {
|
|
48
|
-
"url": "https://berkshirehathaway.com"
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
"command_2": {
|
|
52
|
-
"commandtype": "browser_find",
|
|
53
|
-
"params": {
|
|
54
|
-
"text": {
|
|
55
|
-
"pattern": "Warren.*Buffett",
|
|
56
|
-
"flags": "i"
|
|
57
|
-
},
|
|
58
|
-
"parent_levels": 1
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
"_stubber": {
|
|
64
|
-
"orguuid": "c8cfd7f1-8015-43ff-8878-d22c136a2325",
|
|
65
|
-
"stubref": "my-stub-ref"
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}'
|