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 closing braces - semicolon is optional (for last block)
152
- const clickBlockPattern = /puppeteer\.Locator\.race\s*\(\s*\[([\s\S]*?)\]\s*\)[\s\S]*?\.click\s*\(([\s\S]*?)\)(?:\s*;|\s*$)/g;
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 closing braces - semicolon is optional (for last block)
190
- const fillBlockPattern = /puppeteer\.Locator\.race\s*\(\s*\[([\s\S]*?)\]\s*\)[\s\S]*?\.fill\s*\(\s*['"]([^'"]*(?:\\.[^'"]*)*)['"]\s*\)(?:\s*;|\s*$)/g;
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
- throw new Error('No valid Locator.race() blocks found in code. Please ensure your code contains puppeteer.Locator.race([...]).click() or .fill() patterns.');
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
- // Execute JavaScript
109
- const result = await page.evaluate(javascriptCode);
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-nvk-browser",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "n8n nodes for managing Chrome browser profiles and page interactions",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",