n8n-nodes-nvk-browser 1.0.4 → 1.0.6

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.
@@ -22,7 +22,7 @@ exports.moveAndClickFields = [
22
22
  type: 'string',
23
23
  required: true,
24
24
  default: '',
25
- description: 'For Puppeteer: CSS selector, XPath (with >XPATH> prefix), or Locator.race() code block. Examples: "textarea", ">XPATH>/html/body/div", or paste the Locator.race() block (e.g., "puppeteer.Locator.race([targetPage.locator(\'selector\')]).click()"). The node will automatically extract locators from the first .click() block. For Javascript: CSS selector only.',
25
+ description: 'For Puppeteer: CSS selector, XPath (with >XPATH> prefix), or paste the full Puppeteer code from Chrome Recorder (export as "Puppeteer"). The node will automatically detect and execute .click() or .fill() actions from Locator.race() blocks. Examples: "textarea", ">XPATH>/html/body/div", or full code with puppeteer.Locator.race([...]).click()/.fill(). For Javascript: CSS selector only. Note: The node only executes click/fill actions, not goto() or other navigation actions.',
26
26
  typeOptions: {
27
27
  rows: 4,
28
28
  },
@@ -99,6 +99,8 @@ class MoveAndClick {
99
99
  const timeout = this.getNodeParameter('timeout', i) || 30000;
100
100
  const tabIndex = this.getNodeParameter('tabIndex', i) || 0;
101
101
  const autoStart = this.getNodeParameter('autoStart', i) || false;
102
+ // Track action type for return value (default to click)
103
+ let actionType = 'click';
102
104
  let instance = browserManager.getInstance(profileId);
103
105
  // Auto start profile nếu chưa chạy và option được bật
104
106
  if (!instance && autoStart) {
@@ -146,40 +148,48 @@ class MoveAndClick {
146
148
  try {
147
149
  // Parse selectors from the code pattern
148
150
  const locators = [];
149
- // Find Locator.race blocks - prioritize blocks that end with .click()
150
- // First, try to find blocks with .click()
151
- const clickBlocks = selector.match(/puppeteer\.Locator\.race\s*\(\s*\[([\s\S]*?)\]\s*\)[\s\S]*?\.click\s*\(/g);
151
+ // Find Locator.race blocks - check for both .click() and .fill() actions
152
+ // Improved regex to handle multiline code from Chrome Recorder
153
+ // Match pattern: puppeteer.Locator.race([...]).setTimeout(...).click(...) or .fill(...)
154
+ const clickBlockPattern = /puppeteer\.Locator\.race\s*\(\s*\[([\s\S]*?)\]\s*\)[\s\S]*?\.click\s*\(/;
155
+ const fillBlockPattern = /puppeteer\.Locator\.race\s*\(\s*\[([\s\S]*?)\]\s*\)[\s\S]*?\.fill\s*\(/;
156
+ const clickBlockMatch = selector.match(clickBlockPattern);
157
+ const fillBlockMatch = selector.match(fillBlockPattern);
152
158
  let raceBlockContent = null;
153
- if (clickBlocks && clickBlocks.length > 0) {
154
- // Use the first click block
155
- const firstClickBlock = clickBlocks[0];
156
- const match = firstClickBlock.match(/puppeteer\.Locator\.race\s*\(\s*\[([\s\S]*?)\]\s*\)/);
157
- if (match && match[1]) {
158
- raceBlockContent = match[1];
159
+ let fillText = null;
160
+ // Prioritize fill if both exist (fill usually comes after click)
161
+ if (fillBlockMatch && fillBlockMatch[1]) {
162
+ raceBlockContent = fillBlockMatch[1];
163
+ actionType = 'fill';
164
+ // Extract fill text value
165
+ const fillTextMatch = selector.match(/\.fill\s*\(\s*['"]([^'"]*(?:\\.[^'"]*)*)['"]\s*\)/);
166
+ if (fillTextMatch && fillTextMatch[1]) {
167
+ fillText = fillTextMatch[1].replace(/\\(.)/g, '$1'); // Unescape
159
168
  }
160
169
  }
170
+ else if (clickBlockMatch && clickBlockMatch[1]) {
171
+ raceBlockContent = clickBlockMatch[1];
172
+ actionType = 'click';
173
+ }
161
174
  else {
162
- // Fallback: find any Locator.race block
175
+ // Fallback: find any Locator.race block (even without .click() or .fill())
163
176
  const anyRaceBlockMatch = selector.match(/puppeteer\.Locator\.race\s*\(\s*\[([\s\S]*?)\]\s*\)/);
164
177
  if (anyRaceBlockMatch && anyRaceBlockMatch[1]) {
165
178
  raceBlockContent = anyRaceBlockMatch[1];
179
+ actionType = 'click'; // Default to click
166
180
  }
167
181
  }
168
182
  if (raceBlockContent) {
169
183
  // Extract all locator calls from the race block
170
184
  // Handle both single and double quotes, and handle escaped quotes
171
- const locatorMatches = raceBlockContent.match(/(?:targetPage|page)\.locator\(['"]([^'"]*(?:\\.[^'"]*)*)['"]\)/g);
172
- if (locatorMatches) {
173
- locatorMatches.forEach(match => {
174
- // Extract selector value, handling escaped quotes
175
- const selectorMatch = match.match(/['"]([^'"]*(?:\\.[^'"]*)*)['"]/);
176
- if (selectorMatch && selectorMatch[1]) {
177
- const selectorValue = selectorMatch[1].replace(/\\(.)/g, '$1'); // Unescape
178
- if (selectorValue && typeof page.locator === 'function') {
179
- locators.push(page.locator(selectorValue));
180
- }
181
- }
182
- });
185
+ // Improved regex to handle complex selectors with parentheses and special characters
186
+ const locatorPattern = /(?:targetPage|page)\.locator\((['"])((?:(?!\1)[^\\]|\\.)*)\1\)/g;
187
+ let locatorMatch;
188
+ while ((locatorMatch = locatorPattern.exec(raceBlockContent)) !== null) {
189
+ const selectorValue = locatorMatch[2].replace(/\\(.)/g, '$1'); // Unescape
190
+ if (selectorValue && typeof page.locator === 'function') {
191
+ locators.push(page.locator(selectorValue));
192
+ }
183
193
  }
184
194
  }
185
195
  if (locators.length === 0) {
@@ -251,29 +261,43 @@ class MoveAndClick {
251
261
  if (waitForClick > 0) {
252
262
  await page.waitForTimeout(waitForClick);
253
263
  }
254
- // Parse offset from code if present
255
- let offset;
256
- const offsetMatch = selector.match(/offset:\s*\{\s*x:\s*(\d+),\s*y:\s*(\d+)\s*\}/);
257
- if (offsetMatch) {
258
- offset = {
259
- x: parseInt(offsetMatch[1], 10),
260
- y: parseInt(offsetMatch[2], 10),
261
- };
262
- }
263
- // Click with options
264
- const clickOptions = {
265
- button: button,
266
- clickCount: clickCount,
267
- };
268
- // Add offset if found in code
269
- if (offset) {
270
- clickOptions.offset = offset;
271
- }
272
- if (typeof locator.click === 'function') {
273
- await locator.click(clickOptions);
264
+ // Execute action based on detected type
265
+ if (actionType === 'fill' && fillText) {
266
+ // Fill action
267
+ if (typeof locator.fill === 'function') {
268
+ await locator.fill(fillText);
269
+ }
270
+ else {
271
+ throw new Error('Locator.fill is not available');
272
+ }
274
273
  }
275
274
  else {
276
- throw new Error('Locator.click is not available');
275
+ // Click action (default)
276
+ // Parse offset from code if present (handle multiline)
277
+ let offset;
278
+ // Match offset in click options, handling multiline and whitespace
279
+ const offsetMatch = selector.match(/\.click\s*\(\s*\{[\s\S]*?offset:\s*\{[\s\S]*?x:\s*(\d+)[\s\S]*?y:\s*(\d+)[\s\S]*?\}[\s\S]*?\}/);
280
+ if (offsetMatch && offsetMatch[1] && offsetMatch[2]) {
281
+ offset = {
282
+ x: parseInt(offsetMatch[1], 10),
283
+ y: parseInt(offsetMatch[2], 10),
284
+ };
285
+ }
286
+ // Click with options
287
+ const clickOptions = {
288
+ button: button,
289
+ clickCount: clickCount,
290
+ };
291
+ // Add offset if found in code
292
+ if (offset) {
293
+ clickOptions.offset = offset;
294
+ }
295
+ if (typeof locator.click === 'function') {
296
+ await locator.click(clickOptions);
297
+ }
298
+ else {
299
+ throw new Error('Locator.click is not available');
300
+ }
277
301
  }
278
302
  }
279
303
  else {
@@ -351,7 +375,8 @@ class MoveAndClick {
351
375
  success: true,
352
376
  selector,
353
377
  method: clickMethod,
354
- message: 'Click performed successfully',
378
+ action: actionType || 'click',
379
+ message: actionType === 'fill' ? 'Fill performed successfully' : 'Click performed successfully',
355
380
  },
356
381
  });
357
382
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-nvk-browser",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "n8n nodes for managing Chrome browser profiles and page interactions",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",