n8n-nodes-nvk-browser 1.0.7 → 1.0.9
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.
|
@@ -148,9 +148,14 @@ class MoveAndClick {
|
|
|
148
148
|
try {
|
|
149
149
|
const actionBlocks = [];
|
|
150
150
|
// Find all click blocks
|
|
151
|
-
|
|
151
|
+
// Improved regex to handle .setTimeout() in between and closing braces
|
|
152
|
+
// Pattern: puppeteer.Locator.race([...]).setTimeout(...).click({...})
|
|
153
|
+
// Match with or without .setTimeout(), handle multiline, and optional semicolon
|
|
154
|
+
const clickBlockPattern = /puppeteer\.Locator\.race\s*\(\s*\[([\s\S]*?)\]\s*\)(?:\s*\.setTimeout\s*\([^)]*\))?[\s\S]*?\.click\s*\(\s*\{([\s\S]*?)\}\s*\)(?:\s*;)?/g;
|
|
152
155
|
let clickMatch;
|
|
153
156
|
let clickIndex = 0;
|
|
157
|
+
// Reset regex lastIndex to ensure we find all matches
|
|
158
|
+
clickBlockPattern.lastIndex = 0;
|
|
154
159
|
while ((clickMatch = clickBlockPattern.exec(selector)) !== null) {
|
|
155
160
|
const raceBlockContent = clickMatch[1];
|
|
156
161
|
const clickOptionsContent = clickMatch[2] || '';
|
|
@@ -183,9 +188,14 @@ class MoveAndClick {
|
|
|
183
188
|
}
|
|
184
189
|
}
|
|
185
190
|
// Find all fill blocks
|
|
186
|
-
|
|
191
|
+
// Improved regex to handle .setTimeout() in between and closing braces
|
|
192
|
+
// Pattern: puppeteer.Locator.race([...]).setTimeout(...).fill('...')
|
|
193
|
+
// Match with or without .setTimeout(), handle multiline, and optional semicolon
|
|
194
|
+
const fillBlockPattern = /puppeteer\.Locator\.race\s*\(\s*\[([\s\S]*?)\]\s*\)(?:\s*\.setTimeout\s*\([^)]*\))?[\s\S]*?\.fill\s*\(\s*['"]([^'"]*(?:\\.[^'"]*)*)['"]\s*\)(?:\s*;)?/g;
|
|
187
195
|
let fillMatch;
|
|
188
196
|
let fillIndex = 0;
|
|
197
|
+
// Reset regex lastIndex to ensure we find all matches
|
|
198
|
+
fillBlockPattern.lastIndex = 0;
|
|
189
199
|
while ((fillMatch = fillBlockPattern.exec(selector)) !== null) {
|
|
190
200
|
const raceBlockContent = fillMatch[1];
|
|
191
201
|
const fillTextValue = fillMatch[2].replace(/\\(.)/g, '$1'); // Unescape
|
|
@@ -213,9 +223,56 @@ class MoveAndClick {
|
|
|
213
223
|
// Execute all blocks sequentially
|
|
214
224
|
executedActionsInfo = [];
|
|
215
225
|
if (actionBlocks.length === 0) {
|
|
216
|
-
|
|
226
|
+
// Fallback: Try to parse as a single block without full code structure
|
|
227
|
+
// This handles cases where user pastes just the Locator.race() block
|
|
228
|
+
const singleBlockMatch = selector.match(/puppeteer\.Locator\.race\s*\(\s*\[([\s\S]*?)\]\s*\)(?:\s*\.setTimeout\s*\([^)]*\))?[\s\S]*?(?:\.click\s*\(\s*\{([\s\S]*?)\}\s*\)|\.fill\s*\(\s*['"]([^'"]*(?:\\.[^'"]*)*)['"]\s*\))/);
|
|
229
|
+
if (singleBlockMatch) {
|
|
230
|
+
const raceBlockContent = singleBlockMatch[1];
|
|
231
|
+
const clickOptionsContent = singleBlockMatch[2] || '';
|
|
232
|
+
const fillTextValue = singleBlockMatch[3] ? singleBlockMatch[3].replace(/\\(.)/g, '$1') : null;
|
|
233
|
+
// Extract locators
|
|
234
|
+
const locators = [];
|
|
235
|
+
const locatorPattern = /(?:targetPage|page)\.locator\((['"])((?:(?!\1)[^\\]|\\.)*)\1\)/g;
|
|
236
|
+
let locatorMatch;
|
|
237
|
+
while ((locatorMatch = locatorPattern.exec(raceBlockContent)) !== null) {
|
|
238
|
+
const selectorValue = locatorMatch[2].replace(/\\(.)/g, '$1');
|
|
239
|
+
if (selectorValue && typeof page.locator === 'function') {
|
|
240
|
+
locators.push(page.locator(selectorValue));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (locators.length > 0) {
|
|
244
|
+
if (fillTextValue) {
|
|
245
|
+
actionBlocks.push({
|
|
246
|
+
type: 'fill',
|
|
247
|
+
locators,
|
|
248
|
+
fillText: fillTextValue,
|
|
249
|
+
index: 0,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
let offset;
|
|
254
|
+
const offsetMatch = clickOptionsContent.match(/offset:\s*\{[\s\S]*?x:\s*(\d+)[\s\S]*?y:\s*(\d+)[\s\S]*?\}/);
|
|
255
|
+
if (offsetMatch && offsetMatch[1] && offsetMatch[2]) {
|
|
256
|
+
offset = {
|
|
257
|
+
x: parseInt(offsetMatch[1], 10),
|
|
258
|
+
y: parseInt(offsetMatch[2], 10),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
actionBlocks.push({
|
|
262
|
+
type: 'click',
|
|
263
|
+
locators,
|
|
264
|
+
offset,
|
|
265
|
+
index: 0,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (actionBlocks.length === 0) {
|
|
271
|
+
throw new Error('No valid Locator.race() blocks found in code. Please ensure your code contains puppeteer.Locator.race([...]).click() or .fill() patterns.');
|
|
272
|
+
}
|
|
217
273
|
}
|
|
218
|
-
for (
|
|
274
|
+
for (let blockIndex = 0; blockIndex < actionBlocks.length; blockIndex++) {
|
|
275
|
+
const block = actionBlocks[blockIndex];
|
|
219
276
|
try {
|
|
220
277
|
// Create locator from block's locators
|
|
221
278
|
let locator;
|
|
@@ -240,10 +297,28 @@ class MoveAndClick {
|
|
|
240
297
|
locator = locator.setTimeout(timeout);
|
|
241
298
|
}
|
|
242
299
|
}
|
|
300
|
+
// Wait for element to be visible/actionable before executing
|
|
301
|
+
// Try to wait for the locator to be ready
|
|
302
|
+
try {
|
|
303
|
+
if (typeof locator.wait === 'function') {
|
|
304
|
+
await locator.wait({ timeout: timeout });
|
|
305
|
+
}
|
|
306
|
+
else if (typeof page.waitForSelector === 'function') {
|
|
307
|
+
// Fallback: try to wait using first locator's selector if available
|
|
308
|
+
// This is a best-effort approach
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
catch (waitError) {
|
|
312
|
+
// Continue even if wait fails - element might already be ready
|
|
313
|
+
}
|
|
243
314
|
// Wait before action if specified
|
|
244
315
|
if (waitForClick > 0) {
|
|
245
316
|
await page.waitForTimeout(waitForClick);
|
|
246
317
|
}
|
|
318
|
+
// Add small delay between actions to ensure page state is stable
|
|
319
|
+
if (blockIndex > 0) {
|
|
320
|
+
await page.waitForTimeout(100); // Small delay between actions
|
|
321
|
+
}
|
|
247
322
|
// Execute action
|
|
248
323
|
if (block.type === 'fill' && block.fillText) {
|
|
249
324
|
if (typeof locator.fill === 'function') {
|
|
@@ -269,11 +344,23 @@ class MoveAndClick {
|
|
|
269
344
|
clickOptions.offset = block.offset;
|
|
270
345
|
}
|
|
271
346
|
if (typeof locator.click === 'function') {
|
|
347
|
+
// Ensure element is actionable before clicking
|
|
348
|
+
try {
|
|
349
|
+
// Try to scroll into view if needed
|
|
350
|
+
if (typeof locator.scrollIntoViewIfNeeded === 'function') {
|
|
351
|
+
await locator.scrollIntoViewIfNeeded();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
catch (scrollError) {
|
|
355
|
+
// Continue even if scroll fails
|
|
356
|
+
}
|
|
272
357
|
await locator.click(clickOptions);
|
|
358
|
+
// Wait a bit after click to ensure action is processed
|
|
359
|
+
await page.waitForTimeout(50);
|
|
273
360
|
executedActionsInfo.push({
|
|
274
361
|
type: 'click',
|
|
275
362
|
success: true,
|
|
276
|
-
message:
|
|
363
|
+
message: `Click performed successfully (block ${blockIndex + 1}/${actionBlocks.length})`,
|
|
277
364
|
});
|
|
278
365
|
actionType = 'click';
|
|
279
366
|
}
|
|
@@ -283,10 +370,11 @@ class MoveAndClick {
|
|
|
283
370
|
}
|
|
284
371
|
}
|
|
285
372
|
catch (blockError) {
|
|
373
|
+
const errorMessage = blockError instanceof Error ? blockError.message : String(blockError);
|
|
286
374
|
executedActionsInfo.push({
|
|
287
375
|
type: block.type,
|
|
288
376
|
success: false,
|
|
289
|
-
message:
|
|
377
|
+
message: `Block ${blockIndex + 1}/${actionBlocks.length} failed: ${errorMessage}`,
|
|
290
378
|
});
|
|
291
379
|
// Continue with next block even if one fails
|
|
292
380
|
}
|