n8n-nodes-nvk-browser 1.0.3 → 1.0.4

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 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.',
26
26
  typeOptions: {
27
27
  rows: 4,
28
28
  },
@@ -146,28 +146,66 @@ 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));
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);
152
+ 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
+ }
160
+ }
161
+ else {
162
+ // Fallback: find any Locator.race block
163
+ const anyRaceBlockMatch = selector.match(/puppeteer\.Locator\.race\s*\(\s*\[([\s\S]*?)\]\s*\)/);
164
+ if (anyRaceBlockMatch && anyRaceBlockMatch[1]) {
165
+ raceBlockContent = anyRaceBlockMatch[1];
166
+ }
167
+ }
168
+ if (raceBlockContent) {
169
+ // Extract all locator calls from the race block
170
+ // 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
+ }
158
181
  }
159
- }
160
- });
182
+ });
183
+ }
161
184
  }
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})`));
169
- }
170
- });
185
+ if (locators.length === 0) {
186
+ // Fallback: Parse CSS selectors (lines with targetPage.locator('...') or page.locator('...'))
187
+ const cssMatches = selector.match(/(?:targetPage|page)\.locator\(['"]([^'"]+)['"]\)/g);
188
+ if (cssMatches) {
189
+ cssMatches.forEach(match => {
190
+ const cssSel = match.match(/['"]([^'"]+)['"]/)?.[1];
191
+ if (cssSel && !cssSel.startsWith('::-p-xpath')) {
192
+ // Use page.locator if available, otherwise fallback
193
+ if (typeof page.locator === 'function') {
194
+ locators.push(page.locator(cssSel));
195
+ }
196
+ }
197
+ });
198
+ }
199
+ // Parse XPath selectors (::-p-xpath(...))
200
+ const xpathMatches = selector.match(/::-p-xpath\(([^)]+)\)/g);
201
+ if (xpathMatches) {
202
+ xpathMatches.forEach(match => {
203
+ const xpath = match.match(/::-p-xpath\(([^)]+)\)/)?.[1];
204
+ if (xpath && typeof page.locator === 'function') {
205
+ locators.push(page.locator(`::-p-xpath(${xpath})`));
206
+ }
207
+ });
208
+ }
171
209
  }
172
210
  // Also check for >CSS> and >XPATH> prefixes
173
211
  if (selector.includes('>CSS>') || selector.includes('>XPATH>')) {
@@ -213,11 +251,24 @@ class MoveAndClick {
213
251
  if (waitForClick > 0) {
214
252
  await page.waitForTimeout(waitForClick);
215
253
  }
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
+ }
216
263
  // Click with options
217
264
  const clickOptions = {
218
265
  button: button,
219
266
  clickCount: clickCount,
220
267
  };
268
+ // Add offset if found in code
269
+ if (offset) {
270
+ clickOptions.offset = offset;
271
+ }
221
272
  if (typeof locator.click === 'function') {
222
273
  await locator.click(clickOptions);
223
274
  }
@@ -227,19 +278,13 @@ class MoveAndClick {
227
278
  }
228
279
  else {
229
280
  // Fallback to simple selector parsing
230
- throw new Error('Could not parse selector from code or Locator API not available');
281
+ 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
282
  }
232
283
  }
233
284
  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
- });
285
+ // If code parsing fails, provide helpful error message
286
+ const errorMessage = error instanceof Error ? error.message : String(error);
287
+ 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
288
  }
244
289
  }
245
290
  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.4",
4
4
  "description": "n8n nodes for managing Chrome browser profiles and page interactions",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",