n8n-nodes-nvk-browser 1.0.8 → 1.0.10
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.
|
@@ -148,8 +148,10 @@ class MoveAndClick {
|
|
|
148
148
|
try {
|
|
149
149
|
const actionBlocks = [];
|
|
150
150
|
// Find all click blocks
|
|
151
|
-
// Improved regex to handle
|
|
152
|
-
|
|
151
|
+
// Improved regex to handle .setTimeout() in between and closing braces
|
|
152
|
+
// Pattern: puppeteer.Locator.race([...]).setTimeout(...).click({...})
|
|
153
|
+
// Match with or without .setTimeout(), handle multiline, and optional semicolon
|
|
154
|
+
const clickBlockPattern = /puppeteer\.Locator\.race\s*\(\s*\[([\s\S]*?)\]\s*\)(?:\s*\.setTimeout\s*\([^)]*\))?[\s\S]*?\.click\s*\(\s*\{([\s\S]*?)\}\s*\)(?:\s*;)?/g;
|
|
153
155
|
let clickMatch;
|
|
154
156
|
let clickIndex = 0;
|
|
155
157
|
// Reset regex lastIndex to ensure we find all matches
|
|
@@ -186,8 +188,10 @@ class MoveAndClick {
|
|
|
186
188
|
}
|
|
187
189
|
}
|
|
188
190
|
// Find all fill blocks
|
|
189
|
-
// Improved regex to handle
|
|
190
|
-
|
|
191
|
+
// Improved regex to handle .setTimeout() in between and closing braces
|
|
192
|
+
// Pattern: puppeteer.Locator.race([...]).setTimeout(...).fill('...')
|
|
193
|
+
// Match with or without .setTimeout(), handle multiline, and optional semicolon
|
|
194
|
+
const fillBlockPattern = /puppeteer\.Locator\.race\s*\(\s*\[([\s\S]*?)\]\s*\)(?:\s*\.setTimeout\s*\([^)]*\))?[\s\S]*?\.fill\s*\(\s*['"]([^'"]*(?:\\.[^'"]*)*)['"]\s*\)(?:\s*;)?/g;
|
|
191
195
|
let fillMatch;
|
|
192
196
|
let fillIndex = 0;
|
|
193
197
|
// Reset regex lastIndex to ensure we find all matches
|
|
@@ -219,7 +223,53 @@ class MoveAndClick {
|
|
|
219
223
|
// Execute all blocks sequentially
|
|
220
224
|
executedActionsInfo = [];
|
|
221
225
|
if (actionBlocks.length === 0) {
|
|
222
|
-
|
|
226
|
+
// Fallback: Try to parse as a single block without full code structure
|
|
227
|
+
// This handles cases where user pastes just the Locator.race() block
|
|
228
|
+
const singleBlockMatch = selector.match(/puppeteer\.Locator\.race\s*\(\s*\[([\s\S]*?)\]\s*\)(?:\s*\.setTimeout\s*\([^)]*\))?[\s\S]*?(?:\.click\s*\(\s*\{([\s\S]*?)\}\s*\)|\.fill\s*\(\s*['"]([^'"]*(?:\\.[^'"]*)*)['"]\s*\))/);
|
|
229
|
+
if (singleBlockMatch) {
|
|
230
|
+
const raceBlockContent = singleBlockMatch[1];
|
|
231
|
+
const clickOptionsContent = singleBlockMatch[2] || '';
|
|
232
|
+
const fillTextValue = singleBlockMatch[3] ? singleBlockMatch[3].replace(/\\(.)/g, '$1') : null;
|
|
233
|
+
// Extract locators
|
|
234
|
+
const locators = [];
|
|
235
|
+
const locatorPattern = /(?:targetPage|page)\.locator\((['"])((?:(?!\1)[^\\]|\\.)*)\1\)/g;
|
|
236
|
+
let locatorMatch;
|
|
237
|
+
while ((locatorMatch = locatorPattern.exec(raceBlockContent)) !== null) {
|
|
238
|
+
const selectorValue = locatorMatch[2].replace(/\\(.)/g, '$1');
|
|
239
|
+
if (selectorValue && typeof page.locator === 'function') {
|
|
240
|
+
locators.push(page.locator(selectorValue));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (locators.length > 0) {
|
|
244
|
+
if (fillTextValue) {
|
|
245
|
+
actionBlocks.push({
|
|
246
|
+
type: 'fill',
|
|
247
|
+
locators,
|
|
248
|
+
fillText: fillTextValue,
|
|
249
|
+
index: 0,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
let offset;
|
|
254
|
+
const offsetMatch = clickOptionsContent.match(/offset:\s*\{[\s\S]*?x:\s*(\d+)[\s\S]*?y:\s*(\d+)[\s\S]*?\}/);
|
|
255
|
+
if (offsetMatch && offsetMatch[1] && offsetMatch[2]) {
|
|
256
|
+
offset = {
|
|
257
|
+
x: parseInt(offsetMatch[1], 10),
|
|
258
|
+
y: parseInt(offsetMatch[2], 10),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
actionBlocks.push({
|
|
262
|
+
type: 'click',
|
|
263
|
+
locators,
|
|
264
|
+
offset,
|
|
265
|
+
index: 0,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (actionBlocks.length === 0) {
|
|
271
|
+
throw new Error('No valid Locator.race() blocks found in code. Please ensure your code contains puppeteer.Locator.race([...]).click() or .fill() patterns.');
|
|
272
|
+
}
|
|
223
273
|
}
|
|
224
274
|
for (let blockIndex = 0; blockIndex < actionBlocks.length; blockIndex++) {
|
|
225
275
|
const block = actionBlocks[blockIndex];
|
|
@@ -51,7 +51,7 @@ exports.runJavaScriptFields = [
|
|
|
51
51
|
},
|
|
52
52
|
required: true,
|
|
53
53
|
default: '',
|
|
54
|
-
description: 'JavaScript code to execute',
|
|
54
|
+
description: 'JavaScript code to execute. If code uses Puppeteer API (page.goto, page.click, etc.), it will run in Node.js context with page object available. Otherwise, it runs in browser context.',
|
|
55
55
|
displayOptions: {
|
|
56
56
|
show: {
|
|
57
57
|
resource: ['page'],
|
|
@@ -105,8 +105,38 @@ class RunJavaScript {
|
|
|
105
105
|
if (!page) {
|
|
106
106
|
throw new Error(`Could not get page for profile ${profileId}`);
|
|
107
107
|
}
|
|
108
|
-
//
|
|
109
|
-
|
|
108
|
+
// Check if code uses Puppeteer API (page.goto, page.click, etc.)
|
|
109
|
+
// If it does, execute in Node.js context, otherwise execute in browser context
|
|
110
|
+
const usesPuppeteerAPI = /page\.(goto|click|fill|waitFor|evaluate|setViewport|keyboard|mouse|locator|waitForSelector|waitForTimeout)/.test(javascriptCode) ||
|
|
111
|
+
/await\s+page\./.test(javascriptCode) ||
|
|
112
|
+
/puppeteer\./.test(javascriptCode);
|
|
113
|
+
let result;
|
|
114
|
+
if (usesPuppeteerAPI) {
|
|
115
|
+
// Execute in Node.js context with page exposed
|
|
116
|
+
// Check if code is already wrapped in async IIFE or function
|
|
117
|
+
const isWrapped = /^\s*\(?\s*async\s*\([^)]*\)\s*=>/.test(javascriptCode.trim()) ||
|
|
118
|
+
/^\s*\(?\s*async\s*function/.test(javascriptCode.trim());
|
|
119
|
+
if (isWrapped) {
|
|
120
|
+
// Code is already wrapped, just execute it with page in scope
|
|
121
|
+
const asyncFunction = new Function('page', `
|
|
122
|
+
return ${javascriptCode};
|
|
123
|
+
`);
|
|
124
|
+
result = await asyncFunction(page);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// Wrap code in async function
|
|
128
|
+
const asyncFunction = new Function('page', `
|
|
129
|
+
return (async () => {
|
|
130
|
+
${javascriptCode}
|
|
131
|
+
})();
|
|
132
|
+
`);
|
|
133
|
+
result = await asyncFunction(page);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
// Execute in browser context (original behavior)
|
|
138
|
+
result = await page.evaluate(javascriptCode);
|
|
139
|
+
}
|
|
110
140
|
returnData.push({
|
|
111
141
|
json: {
|
|
112
142
|
success: true,
|