n8n-nodes-nvk-browser 1.0.3 → 1.0.5

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 code pattern with targetPage.locator()/page.locator() and puppeteer.Locator.race(). Examples: "textarea", ">XPATH>/html/body/div", or code with Locator.race(). 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 extract the first Locator.race().click() block. Examples: "textarea", ">XPATH>/html/body/div", or full code with puppeteer.Locator.race([...]).click(). For Javascript: CSS selector only.',
26
26
  typeOptions: {
27
27
  rows: 4,
28
28
  },
@@ -146,28 +146,59 @@ class MoveAndClick {
146
146
  try {
147
147
  // Parse selectors from the code pattern
148
148
  const locators = [];
149
- // Parse CSS selectors (lines with targetPage.locator('...') or page.locator('...'))
150
- const cssMatches = selector.match(/(?:targetPage|page)\.locator\(['"]([^'"]+)['"]\)/g);
151
- if (cssMatches) {
152
- cssMatches.forEach(match => {
153
- const cssSel = match.match(/['"]([^'"]+)['"]/)?.[1];
154
- if (cssSel && !cssSel.startsWith('::-p-xpath')) {
155
- // Use page.locator if available, otherwise fallback
156
- if (typeof page.locator === 'function') {
157
- locators.push(page.locator(cssSel));
158
- }
159
- }
160
- });
149
+ // Find Locator.race blocks - prioritize blocks that end with .click()
150
+ // Improved regex to handle multiline code from Chrome Recorder
151
+ // Match pattern: puppeteer.Locator.race([...]).setTimeout(...).click(...)
152
+ const clickBlockPattern = /puppeteer\.Locator\.race\s*\(\s*\[([\s\S]*?)\]\s*\)[\s\S]*?\.click\s*\(/;
153
+ const clickBlockMatch = selector.match(clickBlockPattern);
154
+ let raceBlockContent = null;
155
+ if (clickBlockMatch && clickBlockMatch[1]) {
156
+ raceBlockContent = clickBlockMatch[1];
161
157
  }
162
- // Parse XPath selectors (::-p-xpath(...))
163
- const xpathMatches = selector.match(/::-p-xpath\(([^)]+)\)/g);
164
- if (xpathMatches) {
165
- xpathMatches.forEach(match => {
166
- const xpath = match.match(/::-p-xpath\(([^)]+)\)/)?.[1];
167
- if (xpath && typeof page.locator === 'function') {
168
- locators.push(page.locator(`::-p-xpath(${xpath})`));
158
+ else {
159
+ // Fallback: find any Locator.race block (even without .click())
160
+ const anyRaceBlockMatch = selector.match(/puppeteer\.Locator\.race\s*\(\s*\[([\s\S]*?)\]\s*\)/);
161
+ if (anyRaceBlockMatch && anyRaceBlockMatch[1]) {
162
+ raceBlockContent = anyRaceBlockMatch[1];
163
+ }
164
+ }
165
+ if (raceBlockContent) {
166
+ // Extract all locator calls from the race block
167
+ // Handle both single and double quotes, and handle escaped quotes
168
+ // Improved regex to handle complex selectors with parentheses and special characters
169
+ const locatorPattern = /(?:targetPage|page)\.locator\((['"])((?:(?!\1)[^\\]|\\.)*)\1\)/g;
170
+ let locatorMatch;
171
+ while ((locatorMatch = locatorPattern.exec(raceBlockContent)) !== null) {
172
+ const selectorValue = locatorMatch[2].replace(/\\(.)/g, '$1'); // Unescape
173
+ if (selectorValue && typeof page.locator === 'function') {
174
+ locators.push(page.locator(selectorValue));
169
175
  }
170
- });
176
+ }
177
+ }
178
+ if (locators.length === 0) {
179
+ // Fallback: Parse CSS selectors (lines with targetPage.locator('...') or page.locator('...'))
180
+ const cssMatches = selector.match(/(?:targetPage|page)\.locator\(['"]([^'"]+)['"]\)/g);
181
+ if (cssMatches) {
182
+ cssMatches.forEach(match => {
183
+ const cssSel = match.match(/['"]([^'"]+)['"]/)?.[1];
184
+ if (cssSel && !cssSel.startsWith('::-p-xpath')) {
185
+ // Use page.locator if available, otherwise fallback
186
+ if (typeof page.locator === 'function') {
187
+ locators.push(page.locator(cssSel));
188
+ }
189
+ }
190
+ });
191
+ }
192
+ // Parse XPath selectors (::-p-xpath(...))
193
+ const xpathMatches = selector.match(/::-p-xpath\(([^)]+)\)/g);
194
+ if (xpathMatches) {
195
+ xpathMatches.forEach(match => {
196
+ const xpath = match.match(/::-p-xpath\(([^)]+)\)/)?.[1];
197
+ if (xpath && typeof page.locator === 'function') {
198
+ locators.push(page.locator(`::-p-xpath(${xpath})`));
199
+ }
200
+ });
201
+ }
171
202
  }
172
203
  // Also check for >CSS> and >XPATH> prefixes
173
204
  if (selector.includes('>CSS>') || selector.includes('>XPATH>')) {
@@ -213,11 +244,25 @@ class MoveAndClick {
213
244
  if (waitForClick > 0) {
214
245
  await page.waitForTimeout(waitForClick);
215
246
  }
247
+ // Parse offset from code if present (handle multiline)
248
+ let offset;
249
+ // Match offset in click options, handling multiline and whitespace
250
+ 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]*?\}/);
251
+ if (offsetMatch && offsetMatch[1] && offsetMatch[2]) {
252
+ offset = {
253
+ x: parseInt(offsetMatch[1], 10),
254
+ y: parseInt(offsetMatch[2], 10),
255
+ };
256
+ }
216
257
  // Click with options
217
258
  const clickOptions = {
218
259
  button: button,
219
260
  clickCount: clickCount,
220
261
  };
262
+ // Add offset if found in code
263
+ if (offset) {
264
+ clickOptions.offset = offset;
265
+ }
221
266
  if (typeof locator.click === 'function') {
222
267
  await locator.click(clickOptions);
223
268
  }
@@ -227,19 +272,13 @@ class MoveAndClick {
227
272
  }
228
273
  else {
229
274
  // Fallback to simple selector parsing
230
- throw new Error('Could not parse selector from code or Locator API not available');
275
+ throw new Error('Could not parse Locator.race() from code. Please ensure your code contains a valid puppeteer.Locator.race([...]) pattern with targetPage.locator() or page.locator() calls.');
231
276
  }
232
277
  }
233
278
  catch (error) {
234
- // If code parsing fails, try to use as simple selector
235
- await page.waitForSelector(selector, { timeout });
236
- if (waitForClick > 0) {
237
- await page.waitForTimeout(waitForClick);
238
- }
239
- await page.click(selector, {
240
- button: button,
241
- clickCount: clickCount,
242
- });
279
+ // If code parsing fails, provide helpful error message
280
+ const errorMessage = error instanceof Error ? error.message : String(error);
281
+ throw new Error(`Failed to parse Puppeteer Locator code: ${errorMessage}. Please check that your selector field contains a valid Locator.race() pattern or a simple CSS/XPath selector.`);
243
282
  }
244
283
  }
245
284
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-nvk-browser",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "n8n nodes for managing Chrome browser profiles and page interactions",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",