Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
df6cd19
feat: Create WhatsApp automation application
google-labs-jules[bot] Dec 3, 2025
c93d831
build: Package WhatsApp automation application
google-labs-jules[bot] Dec 3, 2025
f816fcb
Merge pull request #1 from rohanrlobo/whatsapp-automation-feature
rohanrlobo Dec 3, 2025
d57a3b0
Merge pull request #2 from rohanrlobo/package-whatsapp-automation
rohanrlobo Dec 3, 2025
f90e2bb
Fix: Use send_keys for message sending
google-labs-jules[bot] Dec 4, 2025
c35eca5
Merge pull request #3 from rohanrlobo/fix/whatsapp-message-sending
rohanrlobo Dec 4, 2025
034dfee
Feat: Add requirements.txt file
google-labs-jules[bot] Dec 4, 2025
9067ecc
Merge pull request #4 from rohanrlobo/feat/add-requirements-file
rohanrlobo Dec 4, 2025
6d16b0c
Feat: Add detailed error logging
google-labs-jules[bot] Dec 4, 2025
8a0ed56
Merge branch 'main' into feat/detailed-error-logging
rohanrlobo Dec 4, 2025
cb6eb67
Merge pull request #5 from rohanrlobo/feat/detailed-error-logging
rohanrlobo Dec 4, 2025
9a3db43
Feat: Add detailed error logging and resolve conflicts
google-labs-jules[bot] Dec 4, 2025
da7dc92
Feat: Add detailed error logging and resolve conflicts
google-labs-jules[bot] Dec 4, 2025
b37e6d9
Merge branch 'main' into feat/detailed-error-logging-resolved
rohanrlobo Dec 4, 2025
e73bee4
Merge pull request #6 from rohanrlobo/feat/detailed-error-logging-res…
rohanrlobo Dec 4, 2025
8730a50
Feat: Add a comprehensive test suite
google-labs-jules[bot] Dec 4, 2025
0671ba5
Feat: Add comprehensive test suite and resolve conflicts
google-labs-jules[bot] Dec 4, 2025
13f22f7
Merge branch 'main' into feat/comprehensive-test-suite
rohanrlobo Dec 4, 2025
e7c91af
Merge pull request #7 from rohanrlobo/feat/comprehensive-test-suite
rohanrlobo Dec 4, 2025
0c1e9b4
Merge branch 'main' into feat/comprehensive-test-suite-resolved
rohanrlobo Dec 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added whatsapp_automation.tar.gz
Binary file not shown.
1 change: 1 addition & 0 deletions whatsapp_automation/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.venv/\n__pycache__/\n*.log
53 changes: 53 additions & 0 deletions whatsapp_automation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# WhatsApp Automation

This script automates sending personalized WhatsApp messages from an Excel file.

## Disclaimer

**Please be aware that automating WhatsApp messages may violate their Terms of Service and could result in your account being banned. Use this script at your own risk.**

## Setup

1. **Install Python:** If you don't have Python installed, download and install it from [python.org](https://python.org).

2. **Install Google Chrome:** This script uses the Chrome browser, so you'll need to have it installed.

3. **Install Dependencies:**
* Open a terminal or command prompt.
* Navigate to the project directory (`whatsapp_automation`).
* Run the following command to install the required Python libraries:
```bash
pip install -r requirements.txt
```
* The script will automatically download and manage the correct version of ChromeDriver for you.

## How to Run

1. **Prepare your Excel file:**
* Create an Excel file with the following columns:
* `First name`
* `Last name`
* `Telephone number` (including the country code, e.g., +1234567890)
* `Message` (you can use `{first_name}` and `{last_name}` as placeholders for personalization)

2. **Run the script:**
* Open a terminal or command prompt.
* Navigate to the project directory.
* Run the following command:
```bash
python main.py
```

3. **Log in to WhatsApp:**
* The script will open a new Chrome window with WhatsApp Web.
* Use your phone to scan the QR code and log in.

4. **Select your Excel file:**
* A file dialog will open. Select the Excel file you prepared.

5. **Let it run:**
* The script will start sending the messages one by one.
* Do not close the Chrome window until the script has finished.

6. **Check the log:**
* Once the script is done, a `message_log.csv` file will be created in the project directory, showing the status of each message.
136 changes: 136 additions & 0 deletions whatsapp_automation/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import pandas as pd
import tkinter as tk
from tkinter import filedialog
from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import time
import random
import urllib.parse

def get_excel_file():
"""Opens a file dialog to select an Excel file."""
root = tk.Tk()
root.withdraw() # Hide the main window
file_path = filedialog.askopenfilename(
title="Select the Excel file with contacts",
filetypes=[("Excel Files", "*.xlsx *.xls")]
)
return file_path

def read_contacts(file_path):
"""Reads contacts from the specified Excel file."""
if not file_path:
print("No file selected. Exiting.")
return None
try:
df = pd.read_excel(file_path)
print("Successfully loaded contacts:")
print(df)
return df
except FileNotFoundError:
print(f"Error: The file '{file_path}' was not found.")
return None
except Exception as e:
print(f"An error occurred while reading the Excel file: {e}")
return None

def initialize_driver():
"""Initializes the Chrome WebDriver using webdriver-manager."""
try:
print("Setting up ChromeDriver...")
service = ChromeService(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service)
driver.get("https://web.whatsapp.com")
print("Please scan the QR code to log in to WhatsApp Web.")
return driver
except Exception as e:
print(f"Error initializing WebDriver: {e}")
print("Please make sure you have Google Chrome installed.")
return None

def send_whatsapp_message(driver, phone_number, message):
"""Sends a WhatsApp message to a given phone number."""
try:
# Wait for the user to log in by looking for the search bar
WebDriverWait(driver, 60).until(
EC.presence_of_element_located((By.XPATH, '//div[@data-testid="chat-list-search"]')))

# Format the phone number
if not str(phone_number).startswith('+'):
phone_number = '+' + str(phone_number)

# Create the URL to open a chat
url = f"https://web.whatsapp.com/send?phone={phone_number}"
driver.get(url)

# Wait for the message box to be ready and type the message
message_box = WebDriverWait(driver, 30).until(
EC.presence_of_element_located((By.XPATH, '//div[@data-testid="conversation-compose-box"]//div[@role="textbox"]'))
)
message_box.send_keys(message)

# Click the send button
send_button = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.XPATH, '//button[@data-testid="send"]'))
)
send_button.click()

time.sleep(random.uniform(1, 3)) # Random delay
return True, "Message sent successfully."
except TimeoutException:
error_message = f"Timed out waiting for element for {phone_number}. The number might be invalid or not on WhatsApp."
print(error_message)
return False, error_message
except NoSuchElementException:
error_message = f"Could not find an element for {phone_number}. The WhatsApp Web interface might have changed."
print(error_message)
return False, error_message
except Exception as e:
error_message = f"An unexpected error occurred for {phone_number}: {e}"
print(error_message)
return False, error_message

def main():
"""Main function to run the application."""
excel_file = get_excel_file()
contacts_df = read_contacts(excel_file)
if contacts_df is not None:
driver = initialize_driver()
if driver:
log = []
for index, row in contacts_df.iterrows():
first_name = row.get('First name', '')
last_name = row.get('Last name', '')
phone_number = row.get('Telephone number')
message = row.get('Message', '')

if pd.isna(phone_number) or not message:
print(f"Skipping row {index + 2}: Missing phone number or message.")
log.append({'phone_number': phone_number, 'status': 'Skipped - Missing data'})
continue

# Personalize the message
personalized_message = message.replace('{first_name}', str(first_name)).replace('{last_name}', str(last_name))

print(f"Sending message to {first_name} {last_name} ({phone_number})...")
success, status_message = send_whatsapp_message(driver, phone_number, personalized_message)

if success:
print("Message sent successfully.")
log.append({'phone_number': phone_number, 'status': 'Success'})
else:
print("Failed to send message.")
log.append({'phone_number': phone_number, 'status': status_message})

driver.quit()
log_df = pd.DataFrame(log)
log_df.to_csv("message_log.csv", index=False)
print("\nFinished sending messages. A log file 'message_log.csv' has been created.")

if __name__ == "__main__":
main()
4 changes: 4 additions & 0 deletions whatsapp_automation/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pandas
openpyxl
selenium
webdriver-manager
133 changes: 133 additions & 0 deletions whatsapp_automation/test_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import unittest
from unittest.mock import MagicMock, patch, call
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
import pandas as pd
import io
from selenium.webdriver.common.by import By
import pandas as pd

# This is a bit of a hack to make the import work without changing the project structure
import sys
import os
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
import main

class TestSendMessage(unittest.TestCase):

@patch('main.WebDriverWait')
def test_tc01_send_whatsapp_message_sends_keys_and_clicks_send(self, mock_wait):
def test_send_whatsapp_message_sends_keys_and_clicks_send(self, mock_wait):
# Arrange
mock_driver = MagicMock()
mock_message_box = MagicMock()
mock_send_button = MagicMock()

# Configure the mock_wait to return the mock elements for each of the 3 waits.
# Configure the mock_wait to return the mock elements.
# We use side_effect to return different mock objects for different waits.
mock_wait.return_value.until.side_effect = [
MagicMock(), # For the initial chat-list-search wait
mock_message_box, # For the conversation-compose-box wait
mock_send_button # For the send button wait
]

phone_number = "1234567890"
message = "Hello, this is a test message."

# Act
success, status_message = main.send_whatsapp_message(mock_driver, phone_number, message)

# Assert
mock_message_box.send_keys.assert_called_once_with(message)
mock_send_button.click.assert_called_once()
self.assertTrue(success)
self.assertEqual(status_message, "Message sent successfully.")

@patch('main.WebDriverWait')
def test_tc02_send_whatsapp_message_handles_timeout(self, mock_wait):
# Arrange
mock_driver = MagicMock()

# Simulate a TimeoutException when waiting for an element
mock_wait.return_value.until.side_effect = [
MagicMock(), # For the initial chat-list-search wait
TimeoutException()
]

phone_number = "1234567890"
message = "This message will fail."

# Act
success, status_message = main.send_whatsapp_message(mock_driver, phone_number, message)

# Assert
self.assertFalse(success)
self.assertIn("Timed out", status_message)
self.assertIn(phone_number, status_message)

@patch('main.webdriver.Chrome')
@patch('main.WebDriverWait')
def test_tc03_phone_number_formatting(self, mock_wait, mock_driver):
# Arrange
phone_number = "919876543210"
expected_url = f"https://web.whatsapp.com/send?phone=+{phone_number}"

# Act
main.send_whatsapp_message(mock_driver, phone_number, "message")

# Assert
# Check that the driver was called with the correctly formatted number
self.assertIn(call.get(expected_url), mock_driver.method_calls)

class TestDataHandling(unittest.TestCase):

@patch('pandas.read_excel')
def test_tc04_valid_excel_file_reading(self, mock_read_excel):
# Arrange
# Create a dummy dataframe that looks like the real data
d = {'First name': ['test'], 'Last name': ['user'], 'Telephone number': [123], 'Message': ['hello']}
df = pd.DataFrame(data=d)
mock_read_excel.return_value = df

# Act
result = main.read_contacts("dummy_path.xlsx")

# Assert
self.assertIsNotNone(result)
pd.testing.assert_frame_equal(result, df)

@patch('pandas.read_excel', side_effect=FileNotFoundError)
def test_tc05_missing_excel_file(self, mock_read_excel):
# Act
result = main.read_contacts("non_existent_path.xlsx")

# Assert
self.assertIsNone(result)

success = main.send_whatsapp_message(mock_driver, phone_number, message)

# Assert
# 1. Check if the correct URL was opened
mock_driver.get.assert_called_once_with(f"https://web.whatsapp.com/send?phone=+{phone_number}")

# 2. Check the waits for elements
mock_driver_wait_calls = [
call(mock_driver, 60),
call(mock_driver, 30),
call(mock_driver, 10),
]
self.assertEqual(mock_wait.call_args_list, mock_driver_wait_calls)


# 3. Check if send_keys was called on the message box
mock_message_box.send_keys.assert_called_once_with(message)

# 4. Check if the send button was clicked
mock_send_button.click.assert_called_once()

# 5. Check the function's return value
self.assertTrue(success)

if __name__ == '__main__':
unittest.main()