@stubber/virtual-worker 1.5.5 → 1.6.1
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/.env_dev +1 -1
- package/app/commands/browser/browser_execute.js +292 -0
- package/app/commands/file-server/upload_files.js +7 -4
- package/app/commands/index.js +2 -0
- package/app/helpers/get_chromium_page.js +1 -0
- package/devel/browser_execute.http +70 -0
- package/devel/browser_extract_markdown.http +39 -0
- package/devel/browser_find.http +39 -0
- package/package.json +1 -1
- package/devel/tests.sh +0 -68
package/.env_dev
CHANGED
|
@@ -0,0 +1,292 @@
|
|
|
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
|
+
import { upload_files } from "../file-server/upload_files.js";
|
|
8
|
+
|
|
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
|
+
*
|
|
33
|
+
*
|
|
34
|
+
*/
|
|
35
|
+
export const browser_execute = async (params, stubber_context) => {
|
|
36
|
+
if (!params || typeof params.code !== "string" || !params.code.trim()) {
|
|
37
|
+
return create_error_conceptual({ message: "Missing required parameter: code" });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const page_result = await get_chromium_page(params, stubber_context);
|
|
41
|
+
if (!page_result.success) {
|
|
42
|
+
return page_result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const page = page_result.payload;
|
|
46
|
+
const context = page.context();
|
|
47
|
+
const browser = context.browser();
|
|
48
|
+
|
|
49
|
+
const logs = [];
|
|
50
|
+
const attachments = [];
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const uploadFile = create_upload_file(stubber_context, attachments);
|
|
54
|
+
const execution_console = create_execution_console(logs);
|
|
55
|
+
const execution = run_user_code(params.code, {
|
|
56
|
+
page,
|
|
57
|
+
context,
|
|
58
|
+
browser,
|
|
59
|
+
playwright,
|
|
60
|
+
params,
|
|
61
|
+
args: params.args || {},
|
|
62
|
+
console: execution_console,
|
|
63
|
+
uploadFile,
|
|
64
|
+
setTimeout,
|
|
65
|
+
clearTimeout,
|
|
66
|
+
setInterval,
|
|
67
|
+
clearInterval,
|
|
68
|
+
URL,
|
|
69
|
+
URLSearchParams,
|
|
70
|
+
TextEncoder,
|
|
71
|
+
TextDecoder,
|
|
72
|
+
AbortController,
|
|
73
|
+
AbortSignal,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const raw_result = await Promise.resolve(execution);
|
|
77
|
+
const result = serialize_for_payload(raw_result);
|
|
78
|
+
|
|
79
|
+
return create_success({
|
|
80
|
+
message: "Playwright code executed successfully",
|
|
81
|
+
payload: {
|
|
82
|
+
result,
|
|
83
|
+
logs,
|
|
84
|
+
attachments,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
} catch (error) {
|
|
88
|
+
const technical_error = create_error_technical(error);
|
|
89
|
+
technical_error.error.details.logs = logs;
|
|
90
|
+
technical_error.error.details.attachments = attachments;
|
|
91
|
+
return technical_error;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const create_upload_file = (stubber_context, attachments) => {
|
|
96
|
+
return async (screenshot_path) => {
|
|
97
|
+
if (!screenshot_path) {
|
|
98
|
+
return create_error_conceptual({ message: "No screenshot path provided for upload" });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const upload_result = await upload_files({ files: [screenshot_path] }, stubber_context);
|
|
102
|
+
if (!upload_result.success) {
|
|
103
|
+
return upload_result;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const uploaded_files = upload_result.payload.uploaded_files;
|
|
107
|
+
const file_info = uploaded_files.length > 0 ? uploaded_files[0] : null;
|
|
108
|
+
|
|
109
|
+
if (file_info && attachments) {
|
|
110
|
+
attachments.push(file_info);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return create_success({
|
|
114
|
+
message: "Screenshot uploaded successfully",
|
|
115
|
+
payload: {
|
|
116
|
+
file_info,
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @param {string} code
|
|
124
|
+
* @param {object} execution_scope
|
|
125
|
+
*/
|
|
126
|
+
const run_user_code = (code, execution_scope) => {
|
|
127
|
+
const parameter_names = Object.keys(execution_scope);
|
|
128
|
+
const parameter_values = Object.values(execution_scope);
|
|
129
|
+
const execute = new async_function_constructor(...parameter_names, code);
|
|
130
|
+
|
|
131
|
+
return execute(...parameter_values);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @param {Array<{ level: string, args: unknown[] }>} logs
|
|
136
|
+
*/
|
|
137
|
+
const create_execution_console = (logs) => {
|
|
138
|
+
return {
|
|
139
|
+
debug: (...args) => log_execution("debug", args, logs),
|
|
140
|
+
info: (...args) => log_execution("info", args, logs),
|
|
141
|
+
log: (...args) => log_execution("log", args, logs),
|
|
142
|
+
warn: (...args) => log_execution("warn", args, logs),
|
|
143
|
+
error: (...args) => log_execution("error", args, logs),
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @param {string} level
|
|
149
|
+
* @param {unknown[]} args
|
|
150
|
+
* @param {Array<{ level: string, args: unknown[] }>} logs
|
|
151
|
+
*/
|
|
152
|
+
const log_execution = (level, args, logs) => {
|
|
153
|
+
const serialized_args = args.map((arg) => serialize_for_payload(arg));
|
|
154
|
+
logs.push({ level, args: serialized_args });
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @param {unknown} value
|
|
159
|
+
* @param {WeakSet<object>} [seen]
|
|
160
|
+
* @param {number} [depth]
|
|
161
|
+
* @returns {unknown}
|
|
162
|
+
*/
|
|
163
|
+
const serialize_for_payload = (value, seen = new WeakSet(), depth = 0) => {
|
|
164
|
+
if (value === null || value === undefined) {
|
|
165
|
+
return value ?? null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (depth >= MAX_SERIALIZATION_DEPTH) {
|
|
169
|
+
return `[MaxDepth:${get_constructor_name(value)}]`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const value_type = typeof value;
|
|
173
|
+
|
|
174
|
+
if (value_type === "string" || value_type === "number" || value_type === "boolean") {
|
|
175
|
+
return value;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (value_type === "bigint") {
|
|
179
|
+
return value.toString();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (value_type === "function") {
|
|
183
|
+
return `[Function:${value.name || "anonymous"}]`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (Array.isArray(value)) {
|
|
187
|
+
return value.map((item) => serialize_for_payload(item, seen, depth + 1));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (value instanceof Error) {
|
|
191
|
+
return {
|
|
192
|
+
name: value.name,
|
|
193
|
+
message: value.message,
|
|
194
|
+
stack: value.stack?.split("\n").slice(0, 2).join("\n"),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (value instanceof Date) {
|
|
199
|
+
return value.toISOString();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (value_type === "object") {
|
|
203
|
+
if (seen.has(value)) {
|
|
204
|
+
return `[Circular:${get_constructor_name(value)}]`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
seen.add(value);
|
|
208
|
+
|
|
209
|
+
const playwright_summary = summarize_playwright_object(value);
|
|
210
|
+
if (playwright_summary) {
|
|
211
|
+
return playwright_summary;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const result = {};
|
|
215
|
+
const entries = Object.entries(value).slice(0, MAX_SERIALIZATION_KEYS);
|
|
216
|
+
for (const [key, entry_value] of entries) {
|
|
217
|
+
result[key] = serialize_for_payload(entry_value, seen, depth + 1);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return String(value);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* @param {unknown} value
|
|
228
|
+
*/
|
|
229
|
+
const get_constructor_name = (value) => {
|
|
230
|
+
return value && typeof value === "object" && value.constructor?.name ? value.constructor.name : typeof value;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* @param {unknown} value
|
|
235
|
+
* @returns {object|null}
|
|
236
|
+
*/
|
|
237
|
+
const summarize_playwright_object = (value) => {
|
|
238
|
+
const constructor_name = get_constructor_name(value);
|
|
239
|
+
|
|
240
|
+
if (constructor_name === "Page") {
|
|
241
|
+
return {
|
|
242
|
+
type: "Page",
|
|
243
|
+
url: safely_call(value, "url"),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (constructor_name === "BrowserContext") {
|
|
248
|
+
const pages = safely_call(value, "pages");
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
type: "BrowserContext",
|
|
252
|
+
page_count: Array.isArray(pages) ? pages.length : null,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (constructor_name === "Browser") {
|
|
257
|
+
return {
|
|
258
|
+
type: "Browser",
|
|
259
|
+
is_connected: safely_call(value, "isConnected"),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (constructor_name === "Locator") {
|
|
264
|
+
return {
|
|
265
|
+
type: "Locator",
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (constructor_name === "ElementHandle") {
|
|
270
|
+
return {
|
|
271
|
+
type: "ElementHandle",
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return null;
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* @param {unknown} value
|
|
280
|
+
* @param {string} method_name
|
|
281
|
+
*/
|
|
282
|
+
const safely_call = (value, method_name) => {
|
|
283
|
+
try {
|
|
284
|
+
if (value && typeof value[method_name] === "function") {
|
|
285
|
+
return value[method_name]();
|
|
286
|
+
}
|
|
287
|
+
} catch {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return null;
|
|
292
|
+
};
|
|
@@ -72,11 +72,14 @@ export const upload_files = async (params, _stubber) => {
|
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
if (!response.ok) {
|
|
75
|
-
let error_payload
|
|
75
|
+
let error_payload;
|
|
76
|
+
|
|
77
|
+
const raw = await response.text();
|
|
78
|
+
|
|
76
79
|
try {
|
|
77
|
-
error_payload =
|
|
78
|
-
} catch
|
|
79
|
-
error_payload = { raw_error:
|
|
80
|
+
error_payload = JSON.parse(raw);
|
|
81
|
+
} catch {
|
|
82
|
+
error_payload = { raw_error: raw };
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
return create_error_conceptual({
|
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
|
|
|
@@ -41,6 +41,7 @@ export const get_chromium_page = async (params, stubber_context) => {
|
|
|
41
41
|
headless: headless === true || headless === "true",
|
|
42
42
|
// eslint-disable-next-line id-match
|
|
43
43
|
slowMo: slow_mo,
|
|
44
|
+
// executablePath: "/usr/bin/google-chrome",
|
|
44
45
|
});
|
|
45
46
|
browser.on("disconnected", () => {
|
|
46
47
|
console.log("Chromium browser disconnected. Cleaning up browser instance.");
|
|
@@ -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();const headings = await page.locator(args.selector).allInnerTexts();const screenshotPath = `./screenshot-${Date.now()}.png`;await page.screenshot({ path: screenshotPath, fullPage: true });const upload = await uploadScreenshot(screenshotPath);console.log({ title, headings, upload });return {title,headings,url: page.url(),screenshotPath};"
|
|
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
|
-
}'
|