@stubber/virtual-worker 1.6.0 → 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 CHANGED
@@ -1,4 +1,4 @@
1
- FILESERVER_URL=https://uploads.secure-link.services.dev.stubber.com/api/v1
1
+ FILESERVER_URL=https://uploads.secure-link.services.staging.stubber.com/api/v1
2
2
  API_KEY=123-456-789
3
3
  WORKER_NAME=worker-01
4
4
  VNC_ORIGIN=ws://localhost:3000
@@ -4,8 +4,8 @@ import { create_error_technical } from "#app/functions/create_error_technical.js
4
4
  import { create_success } from "#app/functions/create_success.js";
5
5
  import { get_chromium_page } from "../../helpers/get_chromium_page.js";
6
6
  import * as playwright from "playwright";
7
+ import { upload_files } from "../file-server/upload_files.js";
7
8
 
8
- const DEFAULT_TIMEOUT_MS = 30000;
9
9
  const MAX_SERIALIZATION_DEPTH = 6;
10
10
  const MAX_SERIALIZATION_KEYS = 100;
11
11
  const async_function_constructor = async function () {}.constructor;
@@ -29,7 +29,6 @@ const async_function_constructor = async function () {}.constructor;
29
29
  * @param {Object} params
30
30
  * @param {string} params.code - The Playwright code to execute
31
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
32
  *
34
33
  *
35
34
  */
@@ -46,17 +45,12 @@ export const browser_execute = async (params, stubber_context) => {
46
45
  const page = page_result.payload;
47
46
  const context = page.context();
48
47
  const browser = context.browser();
49
- const timeout_ms = normalize_timeout(params.timeout_ms);
50
48
 
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
- }
49
+ const logs = [];
50
+ const attachments = [];
57
51
 
58
52
  try {
59
- const logs = [];
53
+ const uploadFile = create_upload_file(stubber_context, attachments);
60
54
  const execution_console = create_execution_console(logs);
61
55
  const execution = run_user_code(params.code, {
62
56
  page,
@@ -66,6 +60,7 @@ export const browser_execute = async (params, stubber_context) => {
66
60
  params,
67
61
  args: params.args || {},
68
62
  console: execution_console,
63
+ uploadFile,
69
64
  setTimeout,
70
65
  clearTimeout,
71
66
  setInterval,
@@ -78,7 +73,7 @@ export const browser_execute = async (params, stubber_context) => {
78
73
  AbortSignal,
79
74
  });
80
75
 
81
- const raw_result = await with_timeout(Promise.resolve(execution), timeout_ms);
76
+ const raw_result = await Promise.resolve(execution);
82
77
  const result = serialize_for_payload(raw_result);
83
78
 
84
79
  return create_success({
@@ -86,13 +81,44 @@ export const browser_execute = async (params, stubber_context) => {
86
81
  payload: {
87
82
  result,
88
83
  logs,
84
+ attachments,
89
85
  },
90
86
  });
91
87
  } catch (error) {
92
- return create_error_technical(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;
93
92
  }
94
93
  };
95
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
+
96
122
  /**
97
123
  * @param {string} code
98
124
  * @param {object} execution_scope
@@ -105,48 +131,6 @@ const run_user_code = (code, execution_scope) => {
105
131
  return execute(...parameter_values);
106
132
  };
107
133
 
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
134
  /**
151
135
  * @param {Array<{ level: string, args: unknown[] }>} logs
152
136
  */
@@ -168,9 +152,6 @@ const create_execution_console = (logs) => {
168
152
  const log_execution = (level, args, logs) => {
169
153
  const serialized_args = args.map((arg) => serialize_for_payload(arg));
170
154
  logs.push({ level, args: serialized_args });
171
-
172
- const logger = console[level] || console.log;
173
- logger("[browser_execute]", ...serialized_args);
174
155
  };
175
156
 
176
157
  /**
@@ -72,11 +72,14 @@ export const upload_files = async (params, _stubber) => {
72
72
  });
73
73
 
74
74
  if (!response.ok) {
75
- let error_payload = null;
75
+ let error_payload;
76
+
77
+ const raw = await response.text();
78
+
76
79
  try {
77
- error_payload = await response.json();
78
- } catch (_) {
79
- error_payload = { raw_error: await response.text() };
80
+ error_payload = JSON.parse(raw);
81
+ } catch {
82
+ error_payload = { raw_error: raw };
80
83
  }
81
84
 
82
85
  return create_error_conceptual({
@@ -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.");
@@ -25,7 +25,7 @@ stubber-virtual-worker-apikey: {{apiKey}}
25
25
  "args": {
26
26
  "selector": "h1"
27
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() };"
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
29
  }
30
30
  }
31
31
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stubber/virtual-worker",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "Template to easily create a node app and keep development standards",
5
5
  "main": "app.js",
6
6
  "directories": {