coffeeinabit 0.0.20 → 0.0.22

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.
@@ -51,6 +51,31 @@ export class LinkedInAutomation {
51
51
  return delay;
52
52
  }
53
53
 
54
+ hasActiveBrowser() {
55
+ const contextOpen = this.context && typeof this.context.isClosed === 'function' ? !this.context.isClosed() : Boolean(this.context);
56
+ const pageOpen = this.page && typeof this.page.isClosed === 'function' ? !this.page.isClosed() : Boolean(this.page);
57
+ return Boolean(this.browser && contextOpen && pageOpen);
58
+ }
59
+
60
+ ensureBrowserAvailable(context) {
61
+ if (this.hasActiveBrowser()) {
62
+ return true;
63
+ }
64
+ this.handleBrowserUnavailable(context);
65
+ return false;
66
+ }
67
+
68
+ handleBrowserUnavailable(context) {
69
+ console.warn(`[LinkedInAutomation] Browser unavailable during ${context}. Stopping background activities...`);
70
+ this.stopActionPolling();
71
+ this.stopScreenshotCapture();
72
+ this.stopAmbientMouseMovements();
73
+ if (this.status !== 'browser_unavailable') {
74
+ this.status = 'browser_unavailable';
75
+ this.emitStatus();
76
+ }
77
+ }
78
+
54
79
  getLinkedInSessionPath() {
55
80
  const sanitizedEmail = this.userEmail.replace(/[^a-zA-Z0-9]/g, '_');
56
81
  return path.join(this.linkedInSessionDir, `linkedin_auth_${sanitizedEmail}.json`);
@@ -183,7 +208,7 @@ export class LinkedInAutomation {
183
208
  }
184
209
  }
185
210
 
186
- async startAutomation(headless = false) {
211
+ async startAutomation(headless = true) {
187
212
  try {
188
213
  console.log('[LinkedInAutomation] Starting automation...');
189
214
  this.headless = headless;
@@ -461,6 +486,9 @@ export class LinkedInAutomation {
461
486
  }
462
487
 
463
488
  startScreenshotCapture() {
489
+ if (!this.ensureBrowserAvailable('startScreenshotCapture')) {
490
+ return;
491
+ }
464
492
  console.log('[LinkedInAutomation] Starting screenshot capture...');
465
493
 
466
494
  this.screenshotInterval = setInterval(async () => {
@@ -477,7 +505,9 @@ export class LinkedInAutomation {
477
505
  }
478
506
 
479
507
  async captureScreenshot() {
480
- if (!this.page) return;
508
+ if (!this.ensureBrowserAvailable('captureScreenshot')) {
509
+ return;
510
+ }
481
511
 
482
512
  try {
483
513
  const screenshot = await this.page.screenshot({
@@ -498,6 +528,9 @@ export class LinkedInAutomation {
498
528
 
499
529
  } catch (error) {
500
530
  console.error('[LinkedInAutomation] Failed to capture screenshot:', error.message);
531
+ if (typeof error.message === 'string' && error.message.includes('Target page, context or browser has been closed')) {
532
+ this.handleBrowserUnavailable('captureScreenshot_error');
533
+ }
501
534
  }
502
535
  }
503
536
 
@@ -506,6 +539,9 @@ export class LinkedInAutomation {
506
539
  console.log('[LinkedInAutomation] Action polling is already active, skipping...');
507
540
  return;
508
541
  }
542
+ if (!this.ensureBrowserAvailable('startActionPolling')) {
543
+ return;
544
+ }
509
545
 
510
546
  console.log('[LinkedInAutomation] Starting action polling with random intervals (20-40 seconds)...');
511
547
  this.isActionPollingActive = true;
@@ -540,6 +576,9 @@ export class LinkedInAutomation {
540
576
  }
541
577
 
542
578
  async pollForActions() {
579
+ if (!this.ensureBrowserAvailable('pollForActions')) {
580
+ return;
581
+ }
543
582
  try {
544
583
  console.log('[LinkedInAutomation] Polling for actions from backend...');
545
584
 
@@ -587,14 +626,48 @@ export class LinkedInAutomation {
587
626
  }
588
627
 
589
628
  async executeAction(action) {
590
- console.log('[LinkedInAutomation] Starting execution of action:', action.action, 'ID:', action.action_id);
591
- console.log('[LinkedInAutomation] Action parameters:', JSON.stringify(action.parameters, null, 2));
592
- this.currentAction = action;
593
- this.status = 'executing_action';
594
- this.emitActionUpdate();
595
- this.emitStatus();
629
+ const actionLogs = [];
630
+ const originalConsoleLog = console.log;
631
+ const originalConsoleError = console.error;
632
+ const originalConsoleWarn = console.warn;
633
+
634
+ const captureLog = (level, args) => {
635
+ const timestamp = new Date().toISOString();
636
+ const message = args.map(arg => {
637
+ if (typeof arg === 'string') return arg;
638
+ if (arg instanceof Error) return `${arg.message}\n${arg.stack}`;
639
+ try {
640
+ return JSON.stringify(arg);
641
+ } catch {
642
+ return String(arg);
643
+ }
644
+ }).join(' ');
645
+ actionLogs.push({ timestamp, level, message });
646
+ };
647
+
648
+ console.log = (...args) => {
649
+ captureLog('log', args);
650
+ originalConsoleLog.apply(console, args);
651
+ };
652
+
653
+ console.error = (...args) => {
654
+ captureLog('error', args);
655
+ originalConsoleError.apply(console, args);
656
+ };
657
+
658
+ console.warn = (...args) => {
659
+ captureLog('warn', args);
660
+ originalConsoleWarn.apply(console, args);
661
+ };
596
662
 
597
663
  try {
664
+ console.log('[LinkedInAutomation] Starting execution of action:', action.action, 'ID:', action.action_id);
665
+ console.log('[LinkedInAutomation] Action parameters:', JSON.stringify(action.parameters, null, 2));
666
+ this.currentAction = action;
667
+ this.status = 'executing_action';
668
+ this.emitActionUpdate();
669
+ this.emitStatus();
670
+
598
671
  let result = null;
599
672
 
600
673
  switch (action.action) {
@@ -654,16 +727,34 @@ export class LinkedInAutomation {
654
727
  }
655
728
 
656
729
  console.log('[LinkedInAutomation] Reporting action result for ID:', action.action_id);
730
+
731
+ if (result && typeof result === 'object') {
732
+ result.action_logs = actionLogs;
733
+ } else {
734
+ result = { ...result, action_logs: actionLogs };
735
+ }
736
+
657
737
  await this.reportActionResult(action.action_id, result, action);
658
738
  console.log('[LinkedInAutomation] Action completed successfully:', action.action);
659
739
 
660
740
  } catch (error) {
661
741
  console.error('[LinkedInAutomation] Error executing action:', action.action, 'Error:', error.message);
662
- await this.reportActionResult(action.action_id, { error: error.message }, action);
742
+ console.error('[LinkedInAutomation] Error stack:', error.stack);
743
+
744
+ const errorResult = {
745
+ error: error.message,
746
+ error_stack: error.stack,
747
+ action_logs: actionLogs
748
+ };
749
+ await this.reportActionResult(action.action_id, errorResult, action);
750
+ } finally {
751
+ console.log = originalConsoleLog;
752
+ console.error = originalConsoleError;
753
+ console.warn = originalConsoleWarn;
754
+
755
+ this.currentAction = null;
756
+ this.emitActionUpdate();
663
757
  }
664
-
665
- this.currentAction = null;
666
- this.emitActionUpdate();
667
758
  }
668
759
 
669
760
 
@@ -999,12 +1090,15 @@ export class LinkedInAutomation {
999
1090
 
1000
1091
 
1001
1092
  startAmbientMouseMovements() {
1002
- if (this.ambientMouseTimeout || !this.page) {
1093
+ if (this.ambientMouseTimeout) {
1094
+ return;
1095
+ }
1096
+ if (!this.ensureBrowserAvailable('startAmbientMouseMovements')) {
1003
1097
  return;
1004
1098
  }
1005
1099
 
1006
1100
  const scheduleMove = async () => {
1007
- if (!this.page || !this.isRunning) {
1101
+ if (!this.ensureBrowserAvailable('ambientMouseMovement') || !this.isRunning) {
1008
1102
  this.ambientMouseTimeout = null;
1009
1103
  return;
1010
1104
  }
@@ -1013,6 +1107,9 @@ export class LinkedInAutomation {
1013
1107
  await humanLikeAmbientMove(this.page);
1014
1108
  } catch (error) {
1015
1109
  console.warn('[LinkedInAutomation] Ambient mouse move failed:', error.message);
1110
+ if (typeof error.message === 'string' && error.message.includes('Target page, context or browser has been closed')) {
1111
+ this.handleBrowserUnavailable('ambientMouseMovement_error');
1112
+ }
1016
1113
  }
1017
1114
 
1018
1115
  const nextDelay = Math.floor(Math.random() * 7000) + 6000;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coffeeinabit",
3
- "version": "0.0.20",
3
+ "version": "0.0.22",
4
4
  "description": "CoffeeInABit App",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -168,14 +168,12 @@
168
168
  const keepBrowserToggle = document.getElementById('keepBrowserToggle');
169
169
 
170
170
  if (keepBrowserEnabled === null) {
171
- localStorage.setItem('keepBrowser', 'true');
171
+ localStorage.setItem('keepBrowser', 'false');
172
172
  if (keepBrowserToggle) {
173
- keepBrowserToggle.checked = true;
174
- }
175
- } else if (keepBrowserEnabled === 'true') {
176
- if (keepBrowserToggle) {
177
- keepBrowserToggle.checked = true;
173
+ keepBrowserToggle.checked = false;
178
174
  }
175
+ } else if (keepBrowserToggle) {
176
+ keepBrowserToggle.checked = keepBrowserEnabled === 'true';
179
177
  }
180
178
  }
181
179
 
@@ -334,7 +332,7 @@
334
332
  startAutomationBtn.disabled = true;
335
333
  startAutomationBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Starting...';
336
334
 
337
- const keepBrowser = localStorage.getItem('keepBrowser') !== 'false';
335
+ const keepBrowser = localStorage.getItem('keepBrowser') === 'true';
338
336
 
339
337
  const response = await fetch('/api/automation/start', {
340
338
  method: 'POST',
@@ -6,84 +6,147 @@ export async function executeCommentPost(page, action) {
6
6
  const postUrl = action.parameters?.url || action.url;
7
7
  const commentText = action.parameters?.comment_text || action.comment_text;
8
8
 
9
+ console.log('[CommentPost] Starting comment post action');
10
+ console.log('[CommentPost] Post URL:', postUrl);
11
+ console.log('[CommentPost] Comment text length:', commentText?.length || 0);
12
+
9
13
  if (!postUrl || !commentText) {
14
+ console.error('[CommentPost] Missing required parameters');
10
15
  throw new Error('Missing url or comment_text for comment_post');
11
16
  }
12
17
 
13
- await safeGoto(page, postUrl, {
14
- waitUntil: 'domcontentloaded',
15
- timeout: Math.floor(randomBetween(40000, 60000))
16
- });
18
+ console.log('[CommentPost] Navigating to post URL');
19
+ console.log('[CommentPost] Will wait up to 60 seconds for page load');
20
+ try {
21
+ await safeGoto(page, postUrl, {
22
+ waitUntil: 'domcontentloaded',
23
+ timeout: Math.floor(randomBetween(40000, 60000))
24
+ });
25
+ console.log('[CommentPost] Post page loaded successfully');
26
+ } catch (error) {
27
+ console.error('[CommentPost] Navigation error:', error.message);
28
+ if (error.message.includes('timeout') || error.message.includes('Timeout')) {
29
+ console.error('[CommentPost] Network timeout during post page navigation');
30
+ throw new Error(`Network timeout while navigating to post: ${error.message}`);
31
+ }
32
+ throw error;
33
+ }
17
34
 
18
- await page.waitForSelector('main', { timeout: 10000 });
35
+ console.log('[CommentPost] Waiting for main content to load');
36
+ try {
37
+ await page.waitForSelector('main', { timeout: 10000 });
38
+ console.log('[CommentPost] Main content loaded');
39
+ } catch (error) {
40
+ console.error('[CommentPost] Main content not found:', error.message);
41
+ if (error.message.includes('timeout')) {
42
+ console.error('[CommentPost] Timeout waiting for main content');
43
+ }
44
+ }
19
45
  await waitRandom(1200, 3200, page);
46
+ console.log('[CommentPost] Scrolling to middle of page');
20
47
  await page.evaluate(() => { window.scrollTo(0, document.body.scrollHeight / 2); });
21
48
  await waitRandom(900, 2100, page);
22
49
 
50
+ console.log('[CommentPost] Searching for comment editor');
23
51
  let commentEditor = await page.locator('.comments-comment-box-comment__text-editor .ql-editor[contenteditable="true"]').first();
24
52
 
25
53
  if (await commentEditor.count() === 0) {
54
+ console.log('[CommentPost] Primary editor not found, trying alternative selector');
26
55
  const altEditor = await page.locator('[data-test-ql-editor-contenteditable="true"]').first();
27
56
 
28
57
  if (await altEditor.count() === 0) {
58
+ console.error('[CommentPost] Comment editor not found with any selector');
29
59
  return {
30
60
  action: 'comment_post',
31
- result: { status: 'not_found', message: `Could not find comment editor for post: ${postUrl}` },
61
+ result: {
62
+ status: 'not_found',
63
+ message: `Could not find comment editor for post: ${postUrl}`,
64
+ details: 'Comment editor element not found, page may not have loaded or UI changed'
65
+ },
32
66
  status: 'failed'
33
67
  };
34
68
  }
35
69
 
70
+ console.log('[CommentPost] Alternative editor found, clicking and typing comment');
36
71
  await humanLikeClick(page, altEditor, { timeout: 5000 });
37
72
  await waitRandom(600, 900, page);
73
+ console.log('[CommentPost] Typing comment text');
38
74
  await humanLikeType(altEditor, commentText);
39
75
  await waitRandom(600, 900, page);
76
+ console.log('[CommentPost] Comment typed successfully, searching for submit button');
40
77
 
41
78
  let submitButton = await page.locator('.comments-comment-box__submit-button--cr').first();
42
79
 
43
80
  if (await submitButton.count() > 0) {
81
+ console.log('[CommentPost] Submit button found, clicking');
44
82
  await humanLikeClick(page, submitButton, { timeout: 5000 });
45
83
  await waitRandom(900, 1600, page);
84
+ console.log('[CommentPost] Submit button clicked, comment should be posted');
46
85
  } else {
86
+ console.log('[CommentPost] Primary submit button not found, trying alternative');
47
87
  const altSubmitButton = await page.locator('button:has-text("Comment")').first();
48
88
 
49
89
  if (await altSubmitButton.count() > 0) {
90
+ console.log('[CommentPost] Alternative submit button found, clicking');
50
91
  await humanLikeClick(page, altSubmitButton, { timeout: 5000 });
51
92
  await waitRandom(900, 1600, page);
93
+ console.log('[CommentPost] Comment submit button clicked');
52
94
  } else {
95
+ console.error('[CommentPost] Submit button not found after typing comment');
96
+ console.error('[CommentPost] Comment may have been typed but not submitted');
53
97
  return {
54
98
  action: 'comment_post',
55
- result: { status: 'submit_not_found', message: `Could not find Comment submit button for post: ${postUrl}` },
99
+ result: {
100
+ status: 'submit_not_found',
101
+ message: `Could not find Comment submit button for post: ${postUrl}`,
102
+ details: 'Comment was typed but submit button not found. Comment may not have been posted.'
103
+ },
56
104
  status: 'failed'
57
105
  };
58
106
  }
59
107
  }
60
108
  } else {
109
+ console.log('[CommentPost] Primary editor found, clicking and typing comment');
61
110
  await humanLikeClick(page, commentEditor, { timeout: 5000 });
62
111
  await waitRandom(600, 900, page);
112
+ console.log('[CommentPost] Typing comment text');
63
113
  await humanLikeType(commentEditor, commentText);
64
114
  await waitRandom(600, 900, page);
115
+ console.log('[CommentPost] Comment typed successfully, searching for submit button');
65
116
 
66
117
  let submitButton = await page.locator('.comments-comment-box__submit-button--cr').first();
67
118
 
68
119
  if (await submitButton.count() > 0) {
120
+ console.log('[CommentPost] Submit button found, clicking');
69
121
  await humanLikeClick(page, submitButton, { timeout: 5000 });
70
122
  await waitRandom(900, 1600, page);
123
+ console.log('[CommentPost] Submit button clicked, comment should be posted');
71
124
  } else {
125
+ console.log('[CommentPost] Primary submit button not found, trying alternative');
72
126
  const altSubmitButton = await page.locator('button:has-text("Comment")').first();
73
127
 
74
128
  if (await altSubmitButton.count() > 0) {
129
+ console.log('[CommentPost] Alternative submit button found, clicking');
75
130
  await humanLikeClick(page, altSubmitButton, { timeout: 5000 });
76
131
  await waitRandom(900, 1600, page);
132
+ console.log('[CommentPost] Comment submit button clicked');
77
133
  } else {
134
+ console.error('[CommentPost] Submit button not found after typing comment');
135
+ console.error('[CommentPost] Comment may have been typed but not submitted');
78
136
  return {
79
137
  action: 'comment_post',
80
- result: { status: 'submit_not_found', message: `Could not find Comment submit button for post: ${postUrl}` },
138
+ result: {
139
+ status: 'submit_not_found',
140
+ message: `Could not find Comment submit button for post: ${postUrl}`,
141
+ details: 'Comment was typed but submit button not found. Comment may not have been posted.'
142
+ },
81
143
  status: 'failed'
82
144
  };
83
145
  }
84
146
  }
85
147
  }
86
148
 
149
+ console.log('[CommentPost] Comment post action completed successfully');
87
150
  return {
88
151
  action: 'comment_post',
89
152
  result: { status: 'success', message: `Comment successfully posted on post: ${postUrl}` },
@@ -14,11 +14,21 @@ export async function executeGetMessages(page, action) {
14
14
 
15
15
  const profileUrl = `https://www.linkedin.com/in/${username}`;
16
16
  console.log('[get_messages] Navigating to profile:', profileUrl);
17
- await safeGoto(page, profileUrl, {
18
- waitUntil: 'load',
19
- timeout: Math.floor(randomBetween(40000, 60000))
20
- });
21
- console.log('[get_messages] Page loaded, URL:', page.url());
17
+ console.log('[get_messages] Will wait up to 60 seconds for page load');
18
+ try {
19
+ await safeGoto(page, profileUrl, {
20
+ waitUntil: 'load',
21
+ timeout: Math.floor(randomBetween(40000, 60000))
22
+ });
23
+ console.log('[get_messages] Page loaded successfully, URL:', page.url());
24
+ } catch (error) {
25
+ console.error('[get_messages] Navigation error:', error.message);
26
+ if (error.message.includes('timeout') || error.message.includes('Timeout')) {
27
+ console.error('[get_messages] Network timeout during profile navigation');
28
+ throw new Error(`Network timeout while navigating to profile: ${error.message}`);
29
+ }
30
+ throw error;
31
+ }
22
32
 
23
33
  await page.waitForLoadState('domcontentloaded');
24
34
  await waitRandom(900, 1400, page);
@@ -32,12 +42,18 @@ export async function executeGetMessages(page, action) {
32
42
  console.log('[get_messages] Closing all conversation bubbles');
33
43
  await closeAllConversationBubbles(page);
34
44
 
45
+ console.log('[get_messages] Waiting for message button to appear');
35
46
  try {
36
47
  await page.waitForSelector('button[aria-label^="Message"], main button.artdeco-button--primary', {
37
48
  timeout: 5000,
38
49
  state: 'attached'
39
50
  });
51
+ console.log('[get_messages] Message button selector found');
40
52
  } catch (error) {
53
+ console.warn('[get_messages] Message button selector not found immediately, will continue searching');
54
+ if (error.message.includes('timeout')) {
55
+ console.warn('[get_messages] Timeout waiting for message button selector');
56
+ }
41
57
  }
42
58
 
43
59
  await waitRandom(400, 650, page);
@@ -14,49 +14,85 @@ export async function executeGetProfile(page, action) {
14
14
 
15
15
  try{
16
16
  if (username === 'self') {
17
- await safeGoto(page, 'https://www.linkedin.com/feed/', {
18
- waitUntil: 'domcontentloaded',
19
- timeout: 60000
20
- });
17
+ console.log('[GetProfile] Fetching self profile, navigating to feed page');
18
+ try {
19
+ await safeGoto(page, 'https://www.linkedin.com/feed/', {
20
+ waitUntil: 'domcontentloaded',
21
+ timeout: 60000
22
+ });
23
+ console.log('[GetProfile] Feed page loaded successfully');
24
+ } catch (error) {
25
+ console.error('[GetProfile] Navigation error to feed page:', error.message);
26
+ if (error.message.includes('timeout') || error.message.includes('Timeout')) {
27
+ console.error('[GetProfile] Network timeout during feed page navigation');
28
+ throw new Error(`Network timeout while navigating to feed: ${error.message}`);
29
+ }
30
+ throw error;
31
+ }
21
32
 
22
33
  await page.waitForLoadState('domcontentloaded');
23
34
  await new Promise(resolve => setTimeout(resolve, 2000));
24
35
 
36
+ console.log('[GetProfile] Searching for profile card on feed page');
25
37
  const profileCard = await page.locator('.profile-card-member-details').first();
26
38
  if (await profileCard.count() === 0) {
39
+ console.error('[GetProfile] Profile card not found on feed page');
27
40
  throw new Error('Could not find profile card on /feed');
28
41
  }
42
+ console.log('[GetProfile] Profile card found');
29
43
 
44
+ console.log('[GetProfile] Searching for profile name link');
30
45
  const profileNameLink = await profileCard.locator('.profile-card-name').first();
31
46
  if (await profileNameLink.count() === 0) {
47
+ console.error('[GetProfile] Profile name link not found');
32
48
  throw new Error('Could not find profile name link on /feed');
33
49
  }
50
+ console.log('[GetProfile] Profile name link found, clicking to navigate to profile');
34
51
 
35
52
  await profileNameLink.click();
36
53
  await page.waitForLoadState('domcontentloaded');
37
54
  await new Promise(resolve => setTimeout(resolve, 2000));
38
55
 
39
56
  const currentUrl = page.url();
57
+ console.log('[GetProfile] Extracted profile URL:', currentUrl);
40
58
  const match = currentUrl.match(/\/in\/([^/]+)/);
41
59
  if (!match) {
60
+ console.error('[GetProfile] Could not extract username from URL:', currentUrl);
42
61
  throw new Error('Could not extract username from profile URL');
43
62
  }
44
63
  username = match[1];
64
+ console.log('[GetProfile] Extracted username:', username);
45
65
  }
46
66
 
47
67
  const profileUrl = `https://www.linkedin.com/in/${username}`;
48
- await safeGoto(page, profileUrl, {
49
- waitUntil: 'domcontentloaded',
50
- timeout: 60000
51
- });
68
+ console.log('[GetProfile] Navigating to profile URL:', profileUrl);
69
+ console.log('[GetProfile] Will wait up to 60 seconds for page load');
70
+ try {
71
+ await safeGoto(page, profileUrl, {
72
+ waitUntil: 'domcontentloaded',
73
+ timeout: 60000
74
+ });
75
+ console.log('[GetProfile] Profile page loaded successfully');
76
+ } catch (error) {
77
+ console.error('[GetProfile] Navigation error to profile:', error.message);
78
+ if (error.message.includes('timeout') || error.message.includes('Timeout')) {
79
+ console.error('[GetProfile] Network timeout during profile page navigation');
80
+ throw new Error(`Network timeout while navigating to profile: ${error.message}`);
81
+ }
82
+ throw error;
83
+ }
52
84
 
53
85
  await page.waitForLoadState('domcontentloaded');
54
86
  await new Promise(resolve => setTimeout(resolve, 2000));
87
+ console.log('[GetProfile] Page fully loaded, starting to scroll and extract content');
55
88
 
89
+ console.log('[GetProfile] Scrolling down to load profile content');
56
90
  await gentleScroll(page, 'down');
57
91
  await waitRandom(260, 480, page);
92
+ console.log('[GetProfile] Scrolling up');
58
93
  await gentleScroll(page, 'up');
59
94
  await waitRandom(240, 420, page);
95
+ console.log('[GetProfile] Extracting profile text content');
60
96
 
61
97
  const profileText = await page.evaluate(() => {
62
98
  const mainContent = document.querySelector('main');
@@ -75,21 +111,35 @@ export async function executeGetProfile(page, action) {
75
111
  return getAllText(mainContent);
76
112
  });
77
113
 
114
+ console.log('[GetProfile] Getting dropdown options and connection status');
78
115
  const { moreButtonOptions, isFirstDegreeConnection } = await getDropdownOptions(page);
116
+ console.log('[GetProfile] Dropdown options retrieved, isFirstDegreeConnection:', isFirstDegreeConnection);
79
117
 
80
118
  const recentActivityUrl = `https://www.linkedin.com/in/${username}/recent-activity/all/`;
119
+ console.log('[GetProfile] Navigating to recent activity page:', recentActivityUrl);
81
120
  let recentPosts = {};
82
121
 
83
122
  try {
84
- await safeGoto(page, recentActivityUrl, {
85
- waitUntil: 'domcontentloaded',
86
- timeout: 60000
87
- });
123
+ try {
124
+ await safeGoto(page, recentActivityUrl, {
125
+ waitUntil: 'domcontentloaded',
126
+ timeout: 60000
127
+ });
128
+ console.log('[GetProfile] Recent activity page loaded successfully');
129
+ } catch (error) {
130
+ console.error('[GetProfile] Navigation error to recent activity:', error.message);
131
+ if (error.message.includes('timeout') || error.message.includes('Timeout')) {
132
+ console.error('[GetProfile] Network timeout during recent activity page navigation');
133
+ }
134
+ throw error;
135
+ }
88
136
 
89
137
  await page.waitForLoadState('domcontentloaded');
90
138
  await new Promise(resolve => setTimeout(resolve, 2000));
91
139
 
140
+ console.log('[GetProfile] Scrolling recent activity page to load posts');
92
141
  await gentleScroll(page, 'down', { stepRange: [140, 260], pauseRange: [80, 160], maxIterations: 40 });
142
+ console.log('[GetProfile] Extracting recent posts');
93
143
 
94
144
  recentPosts = await page.evaluate(() => {
95
145
  const mainContent = document.querySelector('main');
@@ -149,8 +199,13 @@ export async function executeGetProfile(page, action) {
149
199
  });
150
200
 
151
201
  } catch (error) {
202
+ console.warn('[GetProfile] Error fetching recent activity:', error.message);
203
+ if (error.message.includes('timeout') || error.message.includes('Timeout')) {
204
+ console.warn('[GetProfile] Network timeout while fetching recent activity, continuing with profile data only');
205
+ }
152
206
  }
153
207
 
208
+ console.log('[GetProfile] Profile data extraction completed successfully');
154
209
  return {
155
210
  profileText,
156
211
  recentPosts,
@@ -159,7 +214,11 @@ export async function executeGetProfile(page, action) {
159
214
  };
160
215
 
161
216
  } catch (error) {
162
- console.error('[GetProfile] Error:', error.message);
217
+ console.error('[GetProfile] Fatal error during profile retrieval:', error.message);
218
+ console.error('[GetProfile] Error stack:', error.stack);
219
+ if (error.message.includes('timeout') || error.message.includes('Timeout')) {
220
+ console.error('[GetProfile] Network timeout detected - profile page may not have loaded completely');
221
+ }
163
222
  throw error;
164
223
  }
165
224
  }
@@ -4,38 +4,71 @@ import { humanLikeClick, waitRandom, randomBetween } from './human_mouse.js';
4
4
  export async function executeLikePost(page, action) {
5
5
  const postUrl = action.parameters?.url || action.url;
6
6
 
7
+ console.log('[LikePost] Starting like post action');
8
+ console.log('[LikePost] Post URL:', postUrl);
9
+
7
10
  if (!postUrl) {
11
+ console.error('[LikePost] Missing required parameter: url');
8
12
  throw new Error('Missing url for like_post');
9
13
  }
10
14
 
11
- await safeGoto(page, postUrl, {
12
- waitUntil: 'domcontentloaded',
13
- timeout: 60000
14
- });
15
+ console.log('[LikePost] Navigating to post URL');
16
+ console.log('[LikePost] Will wait up to 60 seconds for page load');
17
+ try {
18
+ await safeGoto(page, postUrl, {
19
+ waitUntil: 'domcontentloaded',
20
+ timeout: 60000
21
+ });
22
+ console.log('[LikePost] Post page loaded successfully');
23
+ } catch (error) {
24
+ console.error('[LikePost] Navigation error:', error.message);
25
+ if (error.message.includes('timeout') || error.message.includes('Timeout')) {
26
+ console.error('[LikePost] Network timeout during post page navigation');
27
+ throw new Error(`Network timeout while navigating to post: ${error.message}`);
28
+ }
29
+ throw error;
30
+ }
15
31
 
32
+ console.log('[LikePost] Waiting for main content to load');
16
33
  const mainTimeout = Math.floor(randomBetween(5000, 9000));
17
- await page.waitForSelector('main', { timeout: mainTimeout });
34
+ try {
35
+ await page.waitForSelector('main', { timeout: mainTimeout });
36
+ console.log('[LikePost] Main content loaded');
37
+ } catch (error) {
38
+ console.warn('[LikePost] Main content not found within timeout:', error.message);
39
+ if (error.message.includes('timeout')) {
40
+ console.warn('[LikePost] Timeout waiting for main content');
41
+ }
42
+ }
18
43
  await waitRandom(600, 3200, page);
19
44
 
45
+ console.log('[LikePost] Searching for Like button');
20
46
  let likeButton = await page.locator('main button[aria-label="React Like"], main button[aria-label="Unreact Like"]').first();
21
47
 
22
48
  if (await likeButton.count() === 0) {
49
+ console.log('[LikePost] Primary Like button not found, trying alternative selector');
23
50
  likeButton = await page.locator('main button:has-text("Like"), main button:has-text("Unlike")').first();
24
51
  }
25
52
 
26
53
  if (await likeButton.count() > 0) {
54
+ console.log('[LikePost] Like button found, checking current state');
27
55
  const ariaLabel = await likeButton.getAttribute('aria-label');
56
+ console.log('[LikePost] Like button aria-label:', ariaLabel);
28
57
 
29
58
  if (ariaLabel === 'Unreact Like') {
59
+ console.log('[LikePost] Post is already liked');
30
60
  return {
31
61
  action: 'like_post',
32
62
  result: { status: 'already_liked', message: `Post is already liked: ${postUrl}` },
33
63
  status: 'success'
34
64
  };
35
65
  } else if (ariaLabel === 'React Like') {
66
+ console.log('[LikePost] Post not liked yet, clicking Like button');
36
67
  await humanLikeClick(page, likeButton);
68
+ console.log('[LikePost] Like button clicked successfully');
37
69
 
38
70
  await waitRandom(700, 2400, page);
71
+ console.log('[LikePost] Like action completed');
39
72
 
40
73
  return {
41
74
  action: 'like_post',
@@ -43,6 +76,7 @@ export async function executeLikePost(page, action) {
43
76
  status: 'success'
44
77
  };
45
78
  } else {
79
+ console.warn('[LikePost] Could not determine Like button state, aria-label:', ariaLabel);
46
80
  return {
47
81
  action: 'like_post',
48
82
  result: { status: 'unknown', message: `Could not determine Like button state for post: ${postUrl}` },
@@ -50,9 +84,15 @@ export async function executeLikePost(page, action) {
50
84
  };
51
85
  }
52
86
  } else {
87
+ console.error('[LikePost] Like button not found with any selector');
88
+ console.error('[LikePost] Post may not have loaded or UI has changed');
53
89
  return {
54
90
  action: 'like_post',
55
- result: { status: 'not_found', message: `Could not find Like button for post: ${postUrl}` },
91
+ result: {
92
+ status: 'not_found',
93
+ message: `Could not find Like button for post: ${postUrl}`,
94
+ details: 'Like button element not found, page may not have loaded or UI changed'
95
+ },
56
96
  status: 'failed'
57
97
  };
58
98
  }
@@ -9,41 +9,70 @@ export async function executeSendConnectionRequest(page, action) {
9
9
 
10
10
  try {
11
11
  const profileUrl = `https://www.linkedin.com/in/${username}`;
12
- await safeGoto(page, profileUrl, {
13
- waitUntil: 'load',
14
- timeout: 60000
15
- });
12
+ console.log('[SendConnectionRequest] Starting connection request for:', username);
13
+ console.log('[SendConnectionRequest] Navigating to profile:', profileUrl);
14
+ console.log('[SendConnectionRequest] Will wait up to 60 seconds for page load');
15
+ try {
16
+ await safeGoto(page, profileUrl, {
17
+ waitUntil: 'load',
18
+ timeout: 60000
19
+ });
20
+ console.log('[SendConnectionRequest] Profile page loaded successfully');
21
+ } catch (error) {
22
+ console.error('[SendConnectionRequest] Navigation error:', error.message);
23
+ if (error.message.includes('timeout') || error.message.includes('Timeout')) {
24
+ console.error('[SendConnectionRequest] Network timeout during profile navigation');
25
+ throw new Error(`Network timeout while navigating to profile: ${error.message}`);
26
+ }
27
+ throw error;
28
+ }
16
29
 
17
30
  await page.waitForLoadState('load');
18
31
  await waitRandom(1700, 2600, page);
19
32
 
33
+ console.log('[SendConnectionRequest] Scrolling page and detecting connection status');
20
34
  await page.evaluate(() => { window.scrollTo(0, 300); });
21
35
  await waitRandom(600, 2500, page);
22
36
 
23
- return await detectConnectionStatus(page, username);
37
+ const result = await detectConnectionStatus(page, username);
38
+ console.log('[SendConnectionRequest] Connection status detected:', result.status);
39
+ return result;
24
40
 
25
41
  } catch (error) {
26
- console.error('[SendConnectionRequest] Error:', error.message);
42
+ console.error('[SendConnectionRequest] Fatal error:', error.message);
43
+ console.error('[SendConnectionRequest] Error stack:', error.stack);
44
+ if (error.message.includes('timeout') || error.message.includes('Timeout')) {
45
+ console.error('[SendConnectionRequest] Network timeout detected');
46
+ }
27
47
  throw error;
28
48
  }
29
49
  }
30
50
 
31
51
  async function detectConnectionStatus(page, username) {
52
+ console.log('[SendConnectionRequest] Starting connection status detection');
32
53
  let inMain = true;
33
54
 
55
+ console.log('[SendConnectionRequest] Attempting to find and click Connect button');
34
56
  const connectResult = await handleConnectButtonClick(page, username, inMain, true);
35
57
  if (connectResult.status === 'success') {
58
+ console.log('[SendConnectionRequest] Connection request sent successfully');
36
59
  return connectResult;
37
60
  }
61
+ console.log('[SendConnectionRequest] Connect button not found or click failed, checking connection status');
38
62
 
63
+ console.log('[SendConnectionRequest] Checking for "More" button to access connection options');
39
64
  let moreActionsSelectors = await getButtonByText(page, 'More', inMain);
40
65
  if (!moreActionsSelectors || !(await moreActionsSelectors.isVisible())) {
66
+ console.log('[SendConnectionRequest] "More" button not found, trying "More actions"');
41
67
  moreActionsSelectors = await getButtonByText(page, 'More actions', inMain);
42
68
  }
43
69
  if (moreActionsSelectors && (await moreActionsSelectors.isVisible())) {
70
+ console.log('[SendConnectionRequest] Found More button, clicking to access dropdown');
44
71
  await clickWithRetries(page, moreActionsSelectors);
45
72
  await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 1000) + 500));
46
73
  }
74
+
75
+ console.log('[SendConnectionRequest] Checking if already connected');
47
76
  let removeConnectionButton = await getButtonByText(page, 'Remove Connection', inMain);
48
77
  if (!removeConnectionButton) {
49
78
  removeConnectionButton = await getButtonByText(page, 'Remove', inMain);
@@ -52,13 +81,18 @@ async function detectConnectionStatus(page, username) {
52
81
  removeConnectionButton = await getButtonByText(page, 'Connected', inMain);
53
82
  }
54
83
  if (removeConnectionButton) {
84
+ console.log('[SendConnectionRequest] Already connected with', username);
55
85
  return { status: 'already_connected', message: `Already connected with ${username}.` };
56
86
  }
57
87
 
88
+ console.log('[SendConnectionRequest] Not connected, retrying Connect button click');
58
89
  const connectResultWithNote = await handleConnectButtonClick(page, username, inMain, true);
59
90
  if (connectResultWithNote.status === 'success') {
91
+ console.log('[SendConnectionRequest] Connection request sent successfully on retry');
60
92
  return connectResultWithNote;
61
93
  }
94
+
95
+ console.log('[SendConnectionRequest] Checking if connection request is pending');
62
96
  let sentConnectionButton = await getButtonByText(page, 'Sent connection', inMain);
63
97
  if (!sentConnectionButton) {
64
98
  sentConnectionButton = await getButtonByText(page, 'Pending', inMain);
@@ -67,9 +101,11 @@ async function detectConnectionStatus(page, username) {
67
101
  sentConnectionButton = await getButtonByText(page, 'Invitation sent', inMain);
68
102
  }
69
103
  if (sentConnectionButton) {
104
+ console.log('[SendConnectionRequest] Connection request already pending for', username);
70
105
  return { status: 'pending', message: `Connection request already pending for ${username}.` };
71
106
  }
72
107
 
108
+ console.error('[SendConnectionRequest] Could not find Connect button or determine connection status');
73
109
  return {
74
110
  status: 'not_found',
75
111
  message: `Could not find Connect button for ${username}. Please analyze dom and decide what to do next.`
@@ -77,51 +113,73 @@ async function detectConnectionStatus(page, username) {
77
113
  }
78
114
 
79
115
  async function handleConnectButtonClick(page, username, inMain = true, handleSendWithoutNote = true) {
116
+ console.log('[SendConnectionRequest] Searching for Connect button');
80
117
  let connectButton = await getButtonByText(page, 'Connect', inMain);
81
118
  if (!connectButton || !(await connectButton.isVisible())) {
119
+ console.log('[SendConnectionRequest] Connect button not found on first attempt, retrying');
82
120
  connectButton = await getButtonByText(page, 'Connect', inMain);
83
121
  }
84
122
 
85
123
  if (connectButton && (await connectButton.isVisible())) {
124
+ console.log('[SendConnectionRequest] Connect button found and visible, clicking');
86
125
  try {
87
126
  await clickWithRetries(page, connectButton);
127
+ console.log('[SendConnectionRequest] Connect button clicked successfully');
88
128
 
89
129
  if (handleSendWithoutNote) {
90
130
  await waitRandom(1700, 2600, page);
131
+ console.log('[SendConnectionRequest] Searching for "Send without a note" button');
91
132
 
92
133
  let sendWithoutNoteButton = await getButtonByText(page, 'Send without a note', false);
93
134
  if (!sendWithoutNoteButton) {
135
+ console.log('[SendConnectionRequest] Trying alternative text "without a note"');
94
136
  sendWithoutNoteButton = await getButtonByText(page, 'without a note', false);
95
137
  }
96
138
  if (!sendWithoutNoteButton) {
139
+ console.log('[SendConnectionRequest] Trying generic "Send" button');
97
140
  sendWithoutNoteButton = await getButtonByText(page, 'Send', false);
98
141
  }
99
142
 
100
143
  if (sendWithoutNoteButton && (await sendWithoutNoteButton.isVisible())) {
144
+ console.log('[SendConnectionRequest] Found send button, clicking');
101
145
  try {
102
146
  await clickWithRetries(page, sendWithoutNoteButton);
103
147
  await waitRandom(1700, 2600, page);
148
+ console.log('[SendConnectionRequest] Send button clicked, connection request should be sent');
104
149
  return { status: 'success', message: `Connection request sent successfully to ${username}` };
105
150
  } catch (error) {
106
- return { status: 'unclear', message: `Error clicking Send without a note button for ${username}` };
151
+ console.error('[SendConnectionRequest] Error clicking Send button:', error.message);
152
+ if (error.message.includes('timeout') || error.message.includes('Timeout')) {
153
+ console.error('[SendConnectionRequest] Network timeout while clicking Send button');
154
+ }
155
+ return { status: 'unclear', message: `Error clicking Send without a note button for ${username}: ${error.message}` };
107
156
  }
108
157
  } else {
158
+ console.log('[SendConnectionRequest] Send button not found, checking if modal is open');
109
159
  const existingModal = await page.locator('[data-test-modal-id="send-invite-modal"]').isVisible();
110
160
  if (existingModal) {
161
+ console.log('[SendConnectionRequest] Modal is open, assuming success');
111
162
  return { status: 'success', message: `Modal already open for ${username}` };
112
163
  } else {
164
+ console.warn('[SendConnectionRequest] No send button found and modal not open');
113
165
  return { status: 'unclear', message: `No send without a note button found for ${username}` };
114
166
  }
115
167
  }
116
168
  } else {
169
+ console.log('[SendConnectionRequest] Not handling send without note, waiting for auto-send');
117
170
  await waitRandom(4200, 6200, page);
118
171
  return { status: 'success', message: `Connection request sent successfully to ${username}` };
119
172
  }
120
173
  } catch (error) {
174
+ console.error('[SendConnectionRequest] Error clicking Connect button:', error.message);
175
+ if (error.message.includes('timeout') || error.message.includes('Timeout')) {
176
+ console.error('[SendConnectionRequest] Network timeout while clicking Connect button');
177
+ }
121
178
  return { status: 'error', message: `Error clicking Connect button for ${username}: ${error.message}` };
122
179
  }
123
180
  }
124
181
 
182
+ console.warn('[SendConnectionRequest] Connect button not found or not visible');
125
183
  return { status: 'not_found', message: `Connect button not found for ${username}` };
126
184
  }
127
185
 
@@ -19,12 +19,22 @@ export async function executeSendMessages(page, action) {
19
19
  }
20
20
 
21
21
  const profileUrl = `https://www.linkedin.com/in/${username}`;
22
- console.log('[send_messages] Navigating to profile:', profileUrl);
23
- await safeGoto(page, profileUrl, {
24
- waitUntil: 'load',
25
- timeout: Math.floor(randomBetween(40000, 60000))
26
- });
27
- console.log('[send_messages] Page loaded, URL:', page.url());
22
+ console.log('[send_messages] Starting navigation to profile:', profileUrl);
23
+ console.log('[send_messages] Will wait up to 60 seconds for page load');
24
+ try {
25
+ await safeGoto(page, profileUrl, {
26
+ waitUntil: 'load',
27
+ timeout: Math.floor(randomBetween(40000, 60000))
28
+ });
29
+ console.log('[send_messages] Page loaded successfully, current URL:', page.url());
30
+ } catch (error) {
31
+ console.error('[send_messages] Navigation error:', error.message);
32
+ if (error.message.includes('timeout') || error.message.includes('Timeout')) {
33
+ console.error('[send_messages] Network timeout during page navigation');
34
+ throw new Error(`Network timeout while navigating to profile: ${error.message}`);
35
+ }
36
+ throw error;
37
+ }
28
38
 
29
39
  await page.waitForLoadState('domcontentloaded');
30
40
  await waitRandom(800, 1400, page);
@@ -178,12 +188,12 @@ export async function executeSendMessages(page, action) {
178
188
 
179
189
  let editorReady = false;
180
190
  for (let attempt = 0; attempt < 5; attempt++) {
181
- console.log('[send_messages] Editor preparation attempt', attempt + 1, 'of 5');
191
+ console.log('[send_messages] Editor search attempt', attempt + 1, 'of 5');
182
192
  try {
183
193
  const isVisible = await editor.isVisible({ timeout: 2000 }).catch(() => false);
184
194
  console.log('[send_messages] Editor visible:', isVisible);
185
195
  if (isVisible) {
186
- console.log('[send_messages] Clicking editor');
196
+ console.log('[send_messages] Editor found, clicking to focus');
187
197
  await humanLikeClick(page, editor, { timeout: 3000, fallback: { force: true, timeout: 3000 } });
188
198
  await waitRandom(180, 260, page);
189
199
 
@@ -203,23 +213,34 @@ export async function executeSendMessages(page, action) {
203
213
 
204
214
  if (isFocused) {
205
215
  editorReady = true;
206
- console.log('[send_messages] Editor ready!');
216
+ console.log('[send_messages] Editor ready and focused!');
207
217
  break;
218
+ } else {
219
+ console.warn('[send_messages] Editor not focused after click, will retry');
208
220
  }
221
+ } else {
222
+ console.log('[send_messages] Editor not visible, will retry');
209
223
  }
210
224
  } catch (e) {
211
- console.log('[send_messages] Error preparing editor:', e.message);
225
+ console.warn('[send_messages] Error preparing editor on attempt', attempt + 1, '-', e.message);
226
+ if (e.message.includes('timeout') || e.message.includes('Timeout')) {
227
+ console.warn('[send_messages] Network timeout while searching for editor');
228
+ }
212
229
  }
213
230
 
214
231
  await waitRandom(260, 420, page);
215
- console.log('[send_messages] Retrying to find message input');
216
- await findAndClickMessageInput(page);
232
+ if (!editorReady) {
233
+ console.log('[send_messages] Retrying to find message input');
234
+ await findAndClickMessageInput(page);
235
+ }
217
236
  }
218
237
 
219
238
  if (!editorReady) {
220
- console.warn('[send_messages] Editor not ready after 5 attempts, trying one more time');
239
+ console.warn('[send_messages] Editor not ready after 5 attempts, trying alternative method');
240
+ console.warn('[send_messages] Attempting to find and click message input using alternative selector');
221
241
  await findAndClickMessageInput(page);
222
242
  await waitRandom(420, 620, page);
243
+ console.log('[send_messages] Alternative method completed, checking if editor is now available');
223
244
  }
224
245
 
225
246
  console.log('[send_messages] Typing message:', message);
@@ -257,22 +278,30 @@ export async function executeSendMessages(page, action) {
257
278
  console.log('[send_messages] Clicking send button');
258
279
  const clickedSend = await clickSendButton(page);
259
280
  if (!clickedSend) {
260
- console.error('[send_messages] Failed to click send button');
281
+ console.error('[send_messages] Failed to click send button after all attempts');
282
+ console.error('[send_messages] Message was typed but send button could not be found or clicked');
283
+ console.error('[send_messages] This may indicate a network timeout, page load issue, or UI change');
261
284
  return {
262
285
  action: 'send_messages',
263
- result: { status: 'send_button_not_found', message: 'Could not find send button.' },
286
+ result: {
287
+ status: 'send_button_not_found',
288
+ message: 'Could not find send button. Message may have been typed but not sent.',
289
+ details: 'Button search timed out or button not found after message was typed'
290
+ },
264
291
  status: 'failed'
265
292
  };
266
293
  }
267
294
  console.log('[send_messages] Send button clicked successfully');
295
+ console.log('[send_messages] Waiting for message to be sent...');
268
296
 
269
297
  await waitRandom(520, 1500, page);
298
+ console.log('[send_messages] Wait completed after send button click');
270
299
  }
271
300
 
272
- console.log('[send_messages] Closing all conversation bubbles');
301
+ console.log('[send_messages] All messages processed, closing conversation bubbles');
273
302
  await closeAllConversationBubbles(page);
274
303
 
275
- console.log('[send_messages] Successfully completed!');
304
+ console.log('[send_messages] Successfully completed sending all messages to', username);
276
305
  return {
277
306
  action: 'send_messages',
278
307
  result: { status: 'success', message: `Message successfully sent to ${username}` },
@@ -331,23 +360,34 @@ async function closeAllConversationBubbles(page) {
331
360
  }
332
361
 
333
362
  async function clickSendButton(page) {
334
- console.log('[send_messages] clickSendButton: Waiting 500ms');
363
+ console.log('[send_messages] clickSendButton: Starting button search, waiting 500ms');
335
364
  await waitRandom(420, 620, page);
336
365
 
337
366
  for (let attempt = 0; attempt < 10; attempt++) {
338
- console.log('[send_messages] clickSendButton: Attempt', attempt + 1, 'of 10');
339
- const button = page.locator('.msg-form__send-button:not([disabled])').first();
340
- const isVisible = await button.isVisible().catch(() => false);
341
- if (isVisible) {
342
- await humanLikeClick(page, button, { timeout: 3000 });
343
- console.log('[send_messages] Send button clicked successfully');
344
- return true;
367
+ console.log('[send_messages] clickSendButton: Attempt', attempt + 1, 'of 10 - searching for send button');
368
+ try {
369
+ const button = page.locator('.msg-form__send-button:not([disabled])').first();
370
+ const isVisible = await button.isVisible({ timeout: 3000 }).catch(() => false);
371
+ if (isVisible) {
372
+ console.log('[send_messages] clickSendButton: Send button found and visible, attempting click');
373
+ await humanLikeClick(page, button, { timeout: 3000 });
374
+ console.log('[send_messages] clickSendButton: Send button clicked successfully');
375
+ return true;
376
+ } else {
377
+ console.log('[send_messages] clickSendButton: Send button not visible, waiting before retry');
378
+ }
379
+ } catch (error) {
380
+ console.warn('[send_messages] clickSendButton: Error during attempt', attempt + 1, '-', error.message);
381
+ if (error.message.includes('timeout') || error.message.includes('Timeout')) {
382
+ console.warn('[send_messages] clickSendButton: Network timeout detected during button search');
383
+ }
345
384
  }
346
385
 
347
386
  await waitRandom(260, 420, page);
348
387
  }
349
388
 
350
- console.error('[send_messages] Failed to click send button after 10 attempts');
389
+ console.error('[send_messages] clickSendButton: Failed to click send button after 10 attempts');
390
+ console.error('[send_messages] clickSendButton: Possible causes: network timeout, button not found, page not loaded, or UI changed');
351
391
  return false;
352
392
  }
353
393