n8n-nodes-nvk-browser 1.0.1 → 1.0.3

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,23 @@ exports.moveAndClickFields = [
22
22
  type: 'string',
23
23
  required: true,
24
24
  default: '',
25
- description: 'CSS selector of the element to click',
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.',
26
+ typeOptions: {
27
+ rows: 4,
28
+ },
29
+ displayOptions: {
30
+ show: {
31
+ resource: ['page'],
32
+ operation: ['moveAndClick'],
33
+ },
34
+ },
35
+ },
36
+ {
37
+ displayName: 'Timeout (Milliseconds)',
38
+ name: 'timeout',
39
+ type: 'number',
40
+ default: 30000,
41
+ description: 'Timeout in milliseconds for waiting for the element',
26
42
  displayOptions: {
27
43
  show: {
28
44
  resource: ['page'],
@@ -35,10 +51,6 @@ exports.moveAndClickFields = [
35
51
  name: 'clickMethod',
36
52
  type: 'options',
37
53
  options: [
38
- {
39
- name: 'Use GhostCursor',
40
- value: 'ghostcursor',
41
- },
42
54
  {
43
55
  name: 'Use Puppeteer',
44
56
  value: 'puppeteer',
@@ -61,13 +73,13 @@ exports.moveAndClickFields = [
61
73
  displayName: 'Wait For Click (Milliseconds)',
62
74
  name: 'waitForClick',
63
75
  type: 'number',
64
- default: 0,
76
+ default: 500,
65
77
  description: 'Wait time before clicking in milliseconds',
66
78
  displayOptions: {
67
79
  show: {
68
80
  resource: ['page'],
69
81
  operation: ['moveAndClick'],
70
- clickMethod: ['javascript'],
82
+ clickMethod: ['puppeteer'],
71
83
  },
72
84
  },
73
85
  },
@@ -95,7 +107,7 @@ exports.moveAndClickFields = [
95
107
  show: {
96
108
  resource: ['page'],
97
109
  operation: ['moveAndClick'],
98
- clickMethod: ['javascript'],
110
+ clickMethod: ['puppeteer'],
99
111
  },
100
112
  },
101
113
  },
@@ -112,7 +124,7 @@ exports.moveAndClickFields = [
112
124
  show: {
113
125
  resource: ['page'],
114
126
  operation: ['moveAndClick'],
115
- clickMethod: ['javascript'],
127
+ clickMethod: ['puppeteer'],
116
128
  },
117
129
  },
118
130
  },
@@ -22,11 +22,15 @@ var __importStar = (this && this.__importStar) || function (mod) {
22
22
  __setModuleDefault(result, mod);
23
23
  return result;
24
24
  };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
25
28
  Object.defineProperty(exports, "__esModule", { value: true });
26
29
  exports.MoveAndClick = void 0;
27
30
  const BrowserManager_1 = require("../../../utils/BrowserManager");
28
31
  const MoveAndClick_description_1 = require("./MoveAndClick.description");
29
32
  const path = __importStar(require("path"));
33
+ const puppeteer_core_1 = __importDefault(require("puppeteer-core"));
30
34
  class MoveAndClick {
31
35
  constructor() {
32
36
  this.description = {
@@ -92,9 +96,7 @@ class MoveAndClick {
92
96
  const profileId = this.getNodeParameter('profileId', i);
93
97
  const selector = this.getNodeParameter('selector', i);
94
98
  const clickMethod = this.getNodeParameter('clickMethod', i) || 'puppeteer';
95
- const waitForClick = this.getNodeParameter('waitForClick', i) || 0;
96
- const button = this.getNodeParameter('button', i) || 'left';
97
- const clickCount = this.getNodeParameter('clickCount', i) || 1;
99
+ const timeout = this.getNodeParameter('timeout', i) || 30000;
98
100
  const tabIndex = this.getNodeParameter('tabIndex', i) || 0;
99
101
  const autoStart = this.getNodeParameter('autoStart', i) || false;
100
102
  let instance = browserManager.getInstance(profileId);
@@ -109,50 +111,193 @@ class MoveAndClick {
109
111
  if (!page) {
110
112
  throw new Error(`Could not get page for profile ${profileId}`);
111
113
  }
112
- // Wait for element if needed
113
- await page.waitForSelector(selector, { timeout: 30000 });
114
+ // Set default timeout
115
+ page.setDefaultTimeout(timeout);
114
116
  if (clickMethod === 'javascript') {
115
- // Wait before clicking
116
- if (waitForClick > 0) {
117
- await page.waitForTimeout(waitForClick);
118
- }
117
+ // Use Javascript Click - simple implementation
118
+ await page.waitForSelector(selector, { timeout });
119
119
  // JavaScript click
120
- const buttonMap = {
121
- left: 0,
122
- middle: 1,
123
- right: 2,
124
- };
125
- await page.evaluate((sel, btn, count) => {
120
+ await page.evaluate((sel) => {
126
121
  // This code runs in browser context, so DOM APIs are available
127
122
  // eslint-disable-next-line @typescript-eslint/no-implied-eval
128
123
  const element = document.querySelector(sel);
129
124
  if (element) {
130
- for (let i = 0; i < count; i++) {
131
- const event = new MouseEvent('click', {
132
- view: window,
133
- bubbles: true,
134
- cancelable: true,
135
- button: btn,
136
- });
137
- element.dispatchEvent(event);
138
- }
125
+ const event = new MouseEvent('click', {
126
+ view: window,
127
+ bubbles: true,
128
+ cancelable: true,
129
+ button: 0,
130
+ });
131
+ element.dispatchEvent(event);
139
132
  }
140
- }, selector, buttonMap[button] || 0, clickCount);
133
+ }, selector);
141
134
  }
142
135
  else if (clickMethod === 'puppeteer') {
143
- // Puppeteer native click
144
- await page.click(selector);
145
- }
146
- else if (clickMethod === 'ghostcursor') {
147
- // GhostCursor simulation (simplified - would need ghost-cursor package)
148
- const element = await page.$(selector);
149
- if (element) {
150
- const box = await element.boundingBox();
151
- if (box) {
152
- // Move mouse to center of element
153
- await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
154
- await page.waitForTimeout(100);
155
- await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
136
+ // Use Puppeteer - support Locator.race() pattern
137
+ const waitForClick = this.getNodeParameter('waitForClick', i) || 500;
138
+ const button = this.getNodeParameter('button', i) || 'left';
139
+ const clickCount = this.getNodeParameter('clickCount', i) || 1;
140
+ // Parse selector - check if it's code with Locator.race() or simple selector
141
+ const selectorTrimmed = selector.trim();
142
+ // Check if selector contains Locator.race pattern or is a code block
143
+ if (selectorTrimmed.includes('Locator.race') || selectorTrimmed.includes('puppeteer.Locator')) {
144
+ // Execute as code - user provided full code
145
+ // Extract the selector part from the code or execute the code directly
146
+ try {
147
+ // Parse selectors from the code pattern
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
+ });
161
+ }
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
+ });
171
+ }
172
+ // Also check for >CSS> and >XPATH> prefixes
173
+ if (selector.includes('>CSS>') || selector.includes('>XPATH>')) {
174
+ const parts = selector.split(/\s+/);
175
+ parts.forEach(part => {
176
+ if (part.startsWith('>CSS>') && typeof page.locator === 'function') {
177
+ const cssSel = part.replace('>CSS>', '');
178
+ locators.push(page.locator(cssSel));
179
+ }
180
+ else if (part.startsWith('>XPATH>') && typeof page.locator === 'function') {
181
+ const xpath = part.replace('>XPATH>', '');
182
+ locators.push(page.locator(`::-p-xpath(${xpath})`));
183
+ }
184
+ });
185
+ }
186
+ if (locators.length > 0 && typeof page.locator === 'function') {
187
+ // Use Locator.race() if multiple locators and API is available
188
+ let locator;
189
+ if (locators.length > 1) {
190
+ // Check if Locator.race is available
191
+ const LocatorClass = puppeteer_core_1.default.Locator;
192
+ if (LocatorClass && typeof LocatorClass.race === 'function') {
193
+ locator = LocatorClass.race(locators);
194
+ if (typeof locator.setTimeout === 'function') {
195
+ locator = locator.setTimeout(timeout);
196
+ }
197
+ }
198
+ else {
199
+ // Fallback: try first locator
200
+ locator = locators[0];
201
+ if (typeof locator.setTimeout === 'function') {
202
+ locator = locator.setTimeout(timeout);
203
+ }
204
+ }
205
+ }
206
+ else {
207
+ locator = locators[0];
208
+ if (typeof locator.setTimeout === 'function') {
209
+ locator = locator.setTimeout(timeout);
210
+ }
211
+ }
212
+ // Wait before clicking if specified
213
+ if (waitForClick > 0) {
214
+ await page.waitForTimeout(waitForClick);
215
+ }
216
+ // Click with options
217
+ const clickOptions = {
218
+ button: button,
219
+ clickCount: clickCount,
220
+ };
221
+ if (typeof locator.click === 'function') {
222
+ await locator.click(clickOptions);
223
+ }
224
+ else {
225
+ throw new Error('Locator.click is not available');
226
+ }
227
+ }
228
+ else {
229
+ // Fallback to simple selector parsing
230
+ throw new Error('Could not parse selector from code or Locator API not available');
231
+ }
232
+ }
233
+ 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
+ });
243
+ }
244
+ }
245
+ else {
246
+ // Simple selector - parse CSS or XPath prefixes
247
+ // Try to use Locator API if available, otherwise fallback to traditional API
248
+ if (typeof page.locator === 'function') {
249
+ let locator;
250
+ if (selectorTrimmed.startsWith('>CSS>')) {
251
+ const cssSel = selectorTrimmed.replace('>CSS>', '').trim();
252
+ locator = page.locator(cssSel);
253
+ }
254
+ else if (selectorTrimmed.startsWith('>XPATH>')) {
255
+ const xpath = selectorTrimmed.replace('>XPATH>', '').trim();
256
+ locator = page.locator(`::-p-xpath(${xpath})`);
257
+ }
258
+ else if (selectorTrimmed.startsWith('::-p-xpath')) {
259
+ // Already in XPath format
260
+ locator = page.locator(selectorTrimmed);
261
+ }
262
+ else {
263
+ // Default to CSS selector
264
+ locator = page.locator(selectorTrimmed);
265
+ }
266
+ // Set timeout if method exists
267
+ if (typeof locator.setTimeout === 'function') {
268
+ locator = locator.setTimeout(timeout);
269
+ }
270
+ // Wait before clicking if specified
271
+ if (waitForClick > 0) {
272
+ await page.waitForTimeout(waitForClick);
273
+ }
274
+ // Click with options
275
+ const clickOptions = {
276
+ button: button,
277
+ clickCount: clickCount,
278
+ };
279
+ if (typeof locator.click === 'function') {
280
+ await locator.click(clickOptions);
281
+ }
282
+ else {
283
+ // Fallback to traditional API
284
+ await page.waitForSelector(selectorTrimmed, { timeout });
285
+ if (waitForClick > 0) {
286
+ await page.waitForTimeout(waitForClick);
287
+ }
288
+ await page.click(selectorTrimmed, clickOptions);
289
+ }
290
+ }
291
+ else {
292
+ // Fallback to traditional API
293
+ await page.waitForSelector(selectorTrimmed, { timeout });
294
+ if (waitForClick > 0) {
295
+ await page.waitForTimeout(waitForClick);
296
+ }
297
+ await page.click(selectorTrimmed, {
298
+ button: button,
299
+ clickCount: clickCount,
300
+ });
156
301
  }
157
302
  }
158
303
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-nvk-browser",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "n8n nodes for managing Chrome browser profiles and page interactions",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",