n8n-nodes-nvk-browser 1.0.7 → 1.0.9

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,9 +148,14 @@ class MoveAndClick {
148
148
  try {
149
149
  const actionBlocks = [];
150
150
  // Find all click blocks
151
- const clickBlockPattern = /puppeteer\.Locator\.race\s*\(\s*\[([\s\S]*?)\]\s*\)[\s\S]*?\.click\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;
152
155
  let clickMatch;
153
156
  let clickIndex = 0;
157
+ // Reset regex lastIndex to ensure we find all matches
158
+ clickBlockPattern.lastIndex = 0;
154
159
  while ((clickMatch = clickBlockPattern.exec(selector)) !== null) {
155
160
  const raceBlockContent = clickMatch[1];
156
161
  const clickOptionsContent = clickMatch[2] || '';
@@ -183,9 +188,14 @@ class MoveAndClick {
183
188
  }
184
189
  }
185
190
  // Find all fill blocks
186
- const fillBlockPattern = /puppeteer\.Locator\.race\s*\(\s*\[([\s\S]*?)\]\s*\)[\s\S]*?\.fill\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;
187
195
  let fillMatch;
188
196
  let fillIndex = 0;
197
+ // Reset regex lastIndex to ensure we find all matches
198
+ fillBlockPattern.lastIndex = 0;
189
199
  while ((fillMatch = fillBlockPattern.exec(selector)) !== null) {
190
200
  const raceBlockContent = fillMatch[1];
191
201
  const fillTextValue = fillMatch[2].replace(/\\(.)/g, '$1'); // Unescape
@@ -213,9 +223,56 @@ class MoveAndClick {
213
223
  // Execute all blocks sequentially
214
224
  executedActionsInfo = [];
215
225
  if (actionBlocks.length === 0) {
216
- 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
+ }
217
273
  }
218
- for (const block of actionBlocks) {
274
+ for (let blockIndex = 0; blockIndex < actionBlocks.length; blockIndex++) {
275
+ const block = actionBlocks[blockIndex];
219
276
  try {
220
277
  // Create locator from block's locators
221
278
  let locator;
@@ -240,10 +297,28 @@ class MoveAndClick {
240
297
  locator = locator.setTimeout(timeout);
241
298
  }
242
299
  }
300
+ // Wait for element to be visible/actionable before executing
301
+ // Try to wait for the locator to be ready
302
+ try {
303
+ if (typeof locator.wait === 'function') {
304
+ await locator.wait({ timeout: timeout });
305
+ }
306
+ else if (typeof page.waitForSelector === 'function') {
307
+ // Fallback: try to wait using first locator's selector if available
308
+ // This is a best-effort approach
309
+ }
310
+ }
311
+ catch (waitError) {
312
+ // Continue even if wait fails - element might already be ready
313
+ }
243
314
  // Wait before action if specified
244
315
  if (waitForClick > 0) {
245
316
  await page.waitForTimeout(waitForClick);
246
317
  }
318
+ // Add small delay between actions to ensure page state is stable
319
+ if (blockIndex > 0) {
320
+ await page.waitForTimeout(100); // Small delay between actions
321
+ }
247
322
  // Execute action
248
323
  if (block.type === 'fill' && block.fillText) {
249
324
  if (typeof locator.fill === 'function') {
@@ -269,11 +344,23 @@ class MoveAndClick {
269
344
  clickOptions.offset = block.offset;
270
345
  }
271
346
  if (typeof locator.click === 'function') {
347
+ // Ensure element is actionable before clicking
348
+ try {
349
+ // Try to scroll into view if needed
350
+ if (typeof locator.scrollIntoViewIfNeeded === 'function') {
351
+ await locator.scrollIntoViewIfNeeded();
352
+ }
353
+ }
354
+ catch (scrollError) {
355
+ // Continue even if scroll fails
356
+ }
272
357
  await locator.click(clickOptions);
358
+ // Wait a bit after click to ensure action is processed
359
+ await page.waitForTimeout(50);
273
360
  executedActionsInfo.push({
274
361
  type: 'click',
275
362
  success: true,
276
- message: 'Click performed successfully',
363
+ message: `Click performed successfully (block ${blockIndex + 1}/${actionBlocks.length})`,
277
364
  });
278
365
  actionType = 'click';
279
366
  }
@@ -283,10 +370,11 @@ class MoveAndClick {
283
370
  }
284
371
  }
285
372
  catch (blockError) {
373
+ const errorMessage = blockError instanceof Error ? blockError.message : String(blockError);
286
374
  executedActionsInfo.push({
287
375
  type: block.type,
288
376
  success: false,
289
- message: blockError instanceof Error ? blockError.message : String(blockError),
377
+ message: `Block ${blockIndex + 1}/${actionBlocks.length} failed: ${errorMessage}`,
290
378
  });
291
379
  // Continue with next block even if one fails
292
380
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-nvk-browser",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "n8n nodes for managing Chrome browser profiles and page interactions",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",