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
|
|
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:
|
|
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: ['
|
|
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: ['
|
|
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: ['
|
|
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
|
|
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
|
-
//
|
|
113
|
-
|
|
114
|
+
// Set default timeout
|
|
115
|
+
page.setDefaultTimeout(timeout);
|
|
114
116
|
if (clickMethod === 'javascript') {
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
await page.waitForTimeout(waitForClick);
|
|
118
|
-
}
|
|
117
|
+
// Use Javascript Click - simple implementation
|
|
118
|
+
await page.waitForSelector(selector, { timeout });
|
|
119
119
|
// JavaScript click
|
|
120
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
|
133
|
+
}, selector);
|
|
141
134
|
}
|
|
142
135
|
else if (clickMethod === 'puppeteer') {
|
|
143
|
-
// Puppeteer
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
//
|
|
148
|
-
const
|
|
149
|
-
if
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
}
|