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()
|
|
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 -
|
|
150
|
-
//
|
|
151
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
//
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
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
|
-
|
|
378
|
+
action: actionType || 'click',
|
|
379
|
+
message: actionType === 'fill' ? 'Fill performed successfully' : 'Click performed successfully',
|
|
355
380
|
},
|
|
356
381
|
});
|
|
357
382
|
}
|