Build a Python RPA Bot

Automate employee data entry from Excel into a web form using Python.

Introduction

30 Second Summary

Every company has repetitive data entry tasks that eat up hours of someone's day. Copying rows from a spreadsheet into a web form, clicking submit, and doing it again for the next record. Robotic Process Automation (RPA) bots handle this work automatically, and companies pay serious money for people who can build them.

In this project, you will build a Python RPA bot that reads employee data from an Excel spreadsheet and automatically fills out a web registration form for each record. Your bot will handle bad data gracefully, retry when things go wrong, and produce a report of everything it did.

What You'll Build

When you run your finished bot, you will watch a browser open on its own, navigate to a form, and fill in employee details row by row from your spreadsheet. No mouse clicks, no typing. The bot processes 8 records, skips the ones with bad data, retries any system glitches, and prints a summary of what happened.

By the end of this project, you'll have:

  • A Python RPA bot that opens a browser, fills form fields, and clicks submit for each employee record with zero human input.
  • An Excel-driven work queue where each row is an independent transaction the bot processes on its own, so one failure never takes down the whole run.
  • Enterprise-grade error handling that distinguishes bad data from system glitches and retries transient failures automatically.
  • A formatted execution report (terminal and Excel) showing pass/fail/retry counts for every record processed.
  • Secret Mission: Build a second bot from scratch that automates file renaming to prove you can apply the patterns independently.

Are there any prerequisites?

You should be familiar with basic Python concepts like functions, loops, and imports. No prior RPA or web automation experience is needed.

Before We Start

Before We Start...

Before we dive in, let's lock in what you're about to build and why it matters.

Taking a moment to articulate the goal helps you stay oriented as you wire up each piece of the bot.

Set Up Your RPA Environment

An RPA bot depends on specialized libraries to control browsers and read spreadsheets. Without confirming these tools work upfront, you risk debugging installation issues in the middle of building your automation.

In this step, you'll create your project folder, set up an isolated Python environment, and install the two core libraries your bot needs: rpaframework for browser automation and openpyxl for reading Excel files.

In this step, get ready to:
  • Create a project folder on your Desktop and open it in VS Code.
  • Install rpaframework and openpyxl in a virtual environment.
  • Create the starter files your bot will use.
Create your project folder and virtual environment

Your bot needs its own folder and an isolated Python environment so its libraries don't conflict with anything else on your system.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter to open it.
  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop.
  • Click New Folder and name it rpa-bot.
  • Select the rpa-bot folder and click Open.

VS Code reloads with rpa-bot as your workspace. You should see the folder name in the Explorer sidebar on the left.

  • Open the integrated terminal by pressing Ctrl+` (both macOS and Windows).

What is a virtual environment?

A virtual environment is an isolated copy of Python for one project. Libraries you install inside it stay separate from the rest of your system, so different projects can use different library versions without conflicts.

  • Create and activate your virtual environment by following the instructions for your operating system:

macOS

  • Create the virtual environment by running:
python3 -m venv .venv
  • Activate it by running:
source .venv/bin/activate

Windows

  • Create the virtual environment by running:
python -m venv .venv
  • Activate it by running:
.venv\Scripts\activate

Your terminal prompt now starts with (.venv). This confirms the virtual environment is active.

Don't see (.venv) in your prompt?

Make sure you ran the activation command (not just the creation command). On Windows, if you see an execution policy error, run Set-ExecutionPolicy -Scope CurrentUser RemoteSigned first, then try the activation command again.

Help me fix my virtual environment activation

Install the RPA libraries

With your virtual environment active, any packages you install stay isolated inside this project.

  • Install both libraries by running:
pip install rpaframework==32.0.1 openpyxl==3.1.5

What are these libraries?

rpaframework is an open-source RPA library that wraps Selenium for browser control and bundles tools for Excel, email, and PDF automation. It uses the same vocabulary as enterprise tools like UiPath.

openpyxl reads and writes Excel .xlsx files directly from Python. You'll use it to build the work queue your bot reads from and to generate execution reports.

The install may take a minute or two since rpaframework has many dependencies. Wait for the terminal to return to your prompt.

Before you run the next command, what do you think will happen if the install succeeded?

  • Verify the installation by running:
python -c "from RPA.Browser.Selenium import Selenium; print('RPA Framework ready')"

You should see RPA Framework ready printed with no errors.

Seeing a ModuleNotFoundError?

Make sure your prompt still shows (.venv). If it disappeared, re-run the activation command from the previous substep. If you see a permissions error during install, confirm you're using the terminal inside VS Code (not a system terminal outside the project folder).

Help me troubleshoot my RPA import error

Create the project file structure

Your bot needs two files: the Python script that runs the automation, and an Excel spreadsheet that holds the employee data. You'll populate them in the next steps.

  • In VS Code's Explorer sidebar, hover over the RPA-BOT heading and click the New File icon (a page with a plus sign).
  • Name the file bot.py and press Enter.
  • Click the New File icon again and create a file named employees.xlsx.
  • Confirm both files exist by running this in your terminal:
ls

You should see bot.py and employees.xlsx listed alongside the .venv folder.

Files not showing up?

Check that you created the files at the top level of rpa-bot, not inside the .venv folder. If you accidentally created them in the wrong place, drag them to the root of the Explorer sidebar in VS Code.

Help me fix my project file structure

Your environment is ready and verified. Next up, you'll create the Excel work queue and the target web form that your bot will automate against.

Create the Work Queue and Target Form

Your RPA environment is ready. Now you need the two things every RPA bot depends on: input data to process, and a target system to automate against.

In this step, you will create both. First, an Excel spreadsheet that acts as your work queue with employee records. Second, a local HTML form that simulates a company's internal registration system.

In this step, get ready to:
  • Create an Excel work queue with 8 employee records (including intentional bad data).
  • Build a local web form that simulates an employee registration system.
  • Test the form manually to confirm it works before automating it.
Create the Excel work queue

In enterprise RPA, a work queue is the list of items a bot processes one by one. Each item is called a transaction. Your work queue is an Excel file where each row represents one employee to register.

  • In VS Code, create a new file called create_data.py in your rpa-bot folder.
  • Paste the following code into create_data.py to generate your work queue:
from openpyxl import Workbook

wb = Workbook()
ws = wb.active
ws.title = "Employees"

headers = ["Name", "Email", "Department", "Start Date"]
ws.append(headers)

employees = [
    ["Alice Johnson", "alice.johnson@company.com", "Engineering", "2026-09-01"],
    ["Bob Smith", "bob.smith@company.com", "Marketing", "2026-09-01"],
    ["Carol Williams", "carol.williams@company.com", "Engineering", "2026-09-15"],
    ["David Brown", "david.brown@company.com", "Sales", "2026-10-01"],
    ["Eve Davis", "", "Marketing", "2026-09-01"],
    ["Frank Miller", "frank.miller@company.com", "InvalidDept", "2026-09-15"],
    ["Grace Wilson", "grace.wilson@company.com", "Sales", "2026-10-01"],
    ["Henry Taylor", "henry.taylor@company.com", "Engineering", "2026-10-15"],
]

for emp in employees:
    ws.append(emp)

wb.save("employees.xlsx")
print("Created employees.xlsx with 8 records")

What does this code do?

This script uses openpyxl to create a real Excel workbook with 8 employee records.

  • 6 records are valid and will register successfully.
  • Eve Davis (row 5) has a blank email field. This will trigger a business exception later because the data itself is bad.
  • Frank Miller (row 6) has InvalidDept as his department. This will trigger another business exception because the value does not match any valid option.
  • Save create_data.py.
  • Run the script to generate the spreadsheet by running this command in your terminal:
python create_data.py

You should see Created employees.xlsx with 8 records printed in the terminal.

  • Confirm the file was generated by running:
ls

You should see employees.xlsx in the output alongside bot.py and create_data.py.

Seeing a ModuleNotFoundError?

If you see ModuleNotFoundError: No module named 'openpyxl', your virtual environment may not be activated. Check that your terminal prompt shows (.venv) at the start.

Activate it again with .venv\Scripts\activate (Windows) or source .venv/bin/activate (macOS/Linux), then re-run the script.

Help me fix this ModuleNotFoundError for openpyxl.

✔️ Awesome, I've got everything!

Great. Double check you have saved create_data.py and that the employees.xlsx file now exists in your project folder.

ⓧ I'd like to double check the full code

from openpyxl import Workbook

wb = Workbook()
ws = wb.active
ws.title = "Employees"

headers = ["Name", "Email", "Department", "Start Date"]
ws.append(headers)

employees = [
    ["Alice Johnson", "alice.johnson@company.com", "Engineering", "2026-09-01"],
    ["Bob Smith", "bob.smith@company.com", "Marketing", "2026-09-01"],
    ["Carol Williams", "carol.williams@company.com", "Engineering", "2026-09-15"],
    ["David Brown", "david.brown@company.com", "Sales", "2026-10-01"],
    ["Eve Davis", "", "Marketing", "2026-09-01"],
    ["Frank Miller", "frank.miller@company.com", "InvalidDept", "2026-09-15"],
    ["Grace Wilson", "grace.wilson@company.com", "Sales", "2026-10-01"],
    ["Henry Taylor", "henry.taylor@company.com", "Engineering", "2026-10-15"],
]

for emp in employees:
    ws.append(emp)

wb.save("employees.xlsx")
print("Created employees.xlsx with 8 records")
Create the target web form

Your bot needs something to automate against. In the real world, this would be a company's internal HR system. You will build a local HTML form that simulates one, complete with a random failure mechanism so you can practice handling errors.

  • Create a new file called form.html in your rpa-bot folder.
  • Start by pasting the page head and styling into form.html:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Employee Registration</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 500px; margin: 50px auto; padding: 20px; }
        label { display: block; margin-top: 15px; font-weight: bold; }
        input, select { width: 100%; padding: 8px; margin-top: 5px; box-sizing: border-box; }
        button { margin-top: 20px; padding: 10px 20px; background: #007bff; color: white; border: none; cursor: pointer; }
        #result { margin-top: 20px; padding: 15px; border-radius: 5px; display: none; }
        .success { background: #d4edda; color: #155724; }
        .error { background: #f8d7da; color: #721c24; }
    </style>
</head>

What does this section do?

This defines the page structure and CSS styling. The .success and .error classes create green and red result banners that your bot will read to determine whether a submission succeeded.

  • Directly below the </head> tag, paste the form body with all the input fields:
<body>
    <h1>Employee Registration</h1>
    <form id="regForm">
        <label for="fullname">Full Name</label>
        <input type="text" id="fullname" name="fullname" required>

        <label for="email">Email</label>
        <input type="email" id="email" name="email" required>

        <label for="department">Department</label>
        <select id="department" name="department" required>
            <option value="">-- Select --</option>
            <option value="Engineering">Engineering</option>
            <option value="Marketing">Marketing</option>
            <option value="Sales">Sales</option>
            <option value="HR">HR</option>
        </select>

        <label for="startdate">Start Date</label>
        <input type="date" id="startdate" name="startdate" required>

        <button type="submit" id="submitBtn">Register Employee</button>
    </form>
    <div id="result"></div>

What does this form contain?

  • A text input for Full Name (id:fullname).
  • An email input (id:email).
  • A dropdown for Department with four valid options: Engineering, Marketing, Sales, and HR.
  • A date picker for Start Date (id:startdate).
  • A submit button (id:submitBtn) and an empty result div where success or error messages appear.
  • Finally, paste the JavaScript that handles form submission, directly below the <div id="result"></div> line:
    <script>
        document.getElementById('regForm').addEventListener('submit', function(e) {
            e.preventDefault();
            var result = document.getElementById('result');
            // 10% chance of system error to simulate transient failures
            if (Math.random() < 0.1) {
                result.className = 'error';
                result.textContent = 'System Error: Please try again later.';
            } else {
                result.className = 'success';
                result.textContent = 'Success: Employee registered successfully!';
            }
            result.style.display = 'block';
        });
    </script>
</body>
</html>

Why the random failure?

The Math.random() < 0.1 check gives a 10% chance that any submission returns a system error instead of success. This simulates the real-world transient failures that enterprise bots encounter (network timeouts, server hiccups).

Your bot will need retry logic to handle these gracefully. That comes in a later step.

  • Save form.html.

✔️ Awesome, I've got everything!

Great. Double check you have saved form.html and that it contains all three sections: the head with styles, the form body with fields, and the script with the random failure logic.

ⓧ I'd like to double check the full code

Your form.html should contain these three sections in order. Scroll up and confirm each one is present:

  • The <head> section with the <style> block containing .success and .error classes.
  • The <body> with the <form id="regForm"> containing all four input fields and the submit button.
  • The <script> block with the Math.random() < 0.1 check, followed by </body> and </html>.
Test the form manually

Before you automate the form, you need to confirm it works by submitting a record yourself. Python has a built-in HTTP server that can serve your HTML file locally.

  • Start the local web server by running this command:
python -m http.server 8000

What does this command do?

Python's built-in http.server module serves all files in the current directory over HTTP on port 8000. This turns your project folder into a tiny local web server. The terminal will stay occupied while the server runs.

  • Open your browser and go to http://localhost:8000/form.html.

Before you submit anything, what do you expect to see on the page?

You should see an employee registration form with fields for Full Name, Email, Department (a dropdown), Start Date, and a blue Register Employee button.

  • Type Test User in the Full Name field.
  • Type test@example.com in the Email field.
  • Select Engineering from the Department dropdown.
  • Pick any date for Start Date.
  • Click Register Employee.

You should see a green banner that says Success: Employee registered successfully! appear below the button. There is a 10% chance you see a red error banner instead. Either result means the form is working correctly.

Seeing a directory listing instead of the form?

If your browser shows a list of files instead of the form, make sure you included /form.html at the end of the URL. The server shows a directory listing by default when you visit the base URL.

If you see a blank page, open your browser's developer tools (F12) and check the Console tab for JavaScript errors. Make sure your form.html has the closing </script>, </body>, and </html> tags.

Help me troubleshoot my form.html not loading in the browser.

What is a work queue?

In enterprise RPA tools like UiPath, a work queue (also called a queue or transaction queue) holds items that a bot processes one at a time. Each item is independent. If one fails, the bot moves on to the next.

Your Excel file is a simple version of this pattern. Each row is one transaction. The bot will read them one by one and process each independently.

Leave the server running in this terminal. You will need it for the next step when your bot automates the form. To stop it later, press Ctrl+C in that terminal.

You now have your work queue and target system ready. Next up, you will build a bot that opens a browser, reads from the spreadsheet, and fills this form automatically.

Build the Basic Bot

Your work queue and target form are ready. Now it is time to build the bot itself.

Before adding error handling or retry logic, you need a bot that can do the simplest version of the job: open a browser, navigate to the form, fill in fields, and click submit. This is the core RPA loop that every enterprise bot is built on.

In this step, get ready to:
  • Write the bot skeleton that loads employee records from Excel into a list of dictionaries.
  • Implement the form-filling function that populates every field and clicks submit.
  • Run the bot against the first 3 records to confirm it works end-to-end.
Write the bot skeleton

The bot needs two capabilities before it can fill any forms: reading the Excel work queue and controlling a browser. You will build both in a single file.

  • Open bot.py in VS Code and add the imports, form URL constant, and the load_work_queue function by pasting this code:
import time
from openpyxl import load_workbook
from RPA.Browser.Selenium import Selenium

# Target form URL (local server)
FORM_URL = "http://localhost:8000/form.html"


def load_work_queue(filepath):
    # Open the Excel workbook
    wb = load_workbook(filepath)
    ws = wb.active
    # Read column headers from first row
    headers = [cell.value for cell in ws[1]]
    records = []
    # Convert each row into a dictionary using headers as keys
    for row in ws.iter_rows(min_row=2, values_only=True):
        record = dict(zip(headers, row))
        records.append(record)
    return records

What does this code do?

The imports bring in three tools: time for short pauses between actions, load_workbook from openpyxl to read Excel files, and Selenium from rpaframework to control the browser.

The load_work_queue function reads the first row as column headers, then builds a list of dictionaries where each dictionary represents one employee record. This is your work queue.

  • Save bot.py.
  • Verify the loading works by running this quick test in your terminal:
python -c "from bot import load_work_queue; records = load_work_queue('employees.xlsx'); print(f'{len(records)} records loaded'); print(records[0])"

You should see 8 records loaded followed by a dictionary for Alice Johnson with her name, email, department, and start date.

Seeing a ModuleNotFoundError?

  • Make sure your virtual environment is activated. You should see (.venv) at the start of your terminal prompt.
  • If you see an error about RPA, confirm you installed rpaframework in Step 1 by running pip list | grep rpaframework.
  • If employees.xlsx is not found, make sure you are running the command from inside the rpa-bot folder.

Still stuck? Why can't Python find my modules when I run bot.py?

Implement the form-filling function

Now the bot can read data. It needs a function that takes one employee record and fills the HTML form fields with it.

  • In bot.py, add the fill_form function below the load_work_queue function:
def fill_form(browser, record):
    # Fill each form field using the record's values
    browser.input_text("id:fullname", str(record["Name"]))
    browser.input_text("id:email", str(record["Email"]))
    browser.select_from_list_by_value("id:department", str(record["Department"]))
    # Extract just the date part (remove any time component)
    start_date = str(record["Start Date"]).split(" ")[0]
    browser.input_text("id:startdate", start_date)
    # Submit the form and wait for result
    browser.click_button("id:submitBtn")
    time.sleep(1)

What does this code do?

Each input_text call targets a form field by its HTML id attribute (e.g. id:fullname matches <input id="fullname"> in your form.html).

The select_from_list_by_value method picks the matching <option> from the department dropdown. The .split(" ")[0] on the date strips any time component that openpyxl might add when reading a date cell.

  • Now add the main function that ties everything together. Paste this below fill_form:
def main():
    records = load_work_queue("employees.xlsx")
    print(f"Loaded {len(records)} records from work queue")

    # Open a browser and navigate to the form
    browser = Selenium()
    browser.open_available_browser(FORM_URL, headless=False)
    time.sleep(1)

    # Process only the first 3 records for now
    for i, record in enumerate(records[:3], 1):
        print(f"\nProcessing record {i}: {record.get('Name', 'Unknown')}")
        browser.go_to(FORM_URL)
        time.sleep(0.5)
        fill_form(browser, record)
        print(f"  Submitted record for {record['Name']}")

    browser.close_all_browsers()


if __name__ == "__main__":
    main()

What does this code do?

The main function orchestrates the full run. It loads the work queue, opens a visible browser window, then loops through only the first 3 records (records[:3]). You are limiting to 3 so you can confirm the core loop works before processing all 8.

The open_available_browser method auto-detects which browser is installed (Chrome, Firefox, or Edge) and downloads the matching driver automatically. Setting headless=False means you can watch the bot fill in each form in real time.

  • Save bot.py.

✔️ Awesome, I've got everything!

Great. Double check you have saved bot.py and your file has three functions: load_work_queue, fill_form, and main.

ⓧ I'd like to double check the full code

Scroll up and confirm your bot.py contains these sections in order:

  • Imports (time, load_workbook, Selenium) and the FORM_URL constant.
  • The load_work_queue function that reads Excel rows into a list of dictionaries.
  • The fill_form function that populates the form fields and clicks submit.
  • The main function that loads records, opens a browser, loops through records[:3], and closes the browser.
  • The if __name__ == "__main__" guard at the bottom.
Run the bot against the first 3 records

The bot needs the local web server running so it has a form to fill. You will start the server in one terminal and run the bot in another.

  • Make sure the local server is running. If it is not still running from Step 2, open a second terminal in VS Code (click the + icon in the terminal panel) and start it by running:
python -m http.server 8000

Why a second terminal?

The server occupies the terminal while it runs. You need a separate terminal to run bot.py. Keep the server terminal open in the background.

  • Switch back to your original terminal (the one with the (.venv) prompt).

Before you run the bot, think about what you expect to happen. Will it open a browser? Will you see the form fields fill in on their own?

  • Run the bot:
python bot.py

A browser window opens automatically. You will see the bot navigate to the form, type in Alice Johnson's details, select her department, fill the date, and click Register Employee. The green success message appears. Then it repeats for Bob Smith and Carol Williams.

In your terminal, you will see output like this:

Loaded 8 records from work queue

Processing record 1: Alice Johnson
  Submitted record for Alice Johnson

Processing record 2: Bob Smith
  Submitted record for Bob Smith

Processing record 3: Carol Williams
  Submitted record for Carol Williams

What just happened?

Your bot just automated three form submissions with zero human input. It read the data from Excel, opened a browser, filled four fields per record, and clicked submit. This is the same core loop that enterprise RPA bots use to process invoices, onboard employees, and move data between systems.

Browser did not open?

  • Make sure you have Chrome, Firefox, or Edge installed on your machine. The open_available_browser method auto-detects whichever browser is available.
  • If you see a WebDriver error, try running the command again. Selenium Manager downloads the correct driver automatically on first run, which can time out on slow connections.
  • Confirm the local server is running in your other terminal. If port 8000 is not serving the form, the bot has nothing to navigate to.

Still stuck? My RPA bot won't open a browser or navigate to the form.

Notice the bot only processed 3 of 8 records

You hardcoded records[:3] to limit the run. The remaining records include one with a missing email and one with an invalid department. If you removed the slice right now, the bot would crash on those bad records. The next step adds error handling so the bot can survive bad data gracefully.

Your bot can fill forms on autopilot. Next up, you will remove the 3-record limit and add transaction processing so the bot handles bad data and system errors without crashing.

Add Transaction Processing and Error Handling

Your basic bot successfully fills the form for clean records. But your work queue has 8 records, and not all of them are clean.

In a real enterprise, bots process thousands of records per day. If a single bad record crashes the entire run, the remaining records never get processed. Enterprise RPA bots solve this by treating each record as an independent transaction. If one fails, the bot logs the failure and moves on to the next.

In this step, get ready to:
  • See the bot crash on invalid data, revealing why transaction isolation matters.
  • Add custom exception classes and input validation to catch bad data before it hits the form.
  • Implement retry logic for transient system errors and build a results tracker.
Run the bot against all 8 records

Right now your bot only processes the first 3 records because of the [:3] slice in the loop. Your work queue has 8 records, and two of them have intentionally bad data.

  • In bot.py, find this line inside the main() function:
    for i, record in enumerate(records[:3], 1):
  • Remove the [:3] slice and update the print statement so the line now looks like this:
    for i, record in enumerate(records, 1):
        print(f"\nProcessing record {i}/{len(records)}: {record.get('Name', 'Unknown')}")

Before you run this, what do you think will happen when the bot hits Eve Davis (who has no email) or Frank Miller (whose department is not in the dropdown)?

  • Make sure your local server is still running in a separate terminal. If not, start it by running:
python -m http.server 8000
  • In your other terminal (with the virtual environment active), run the bot:
python bot.py

The bot crashes with an error when it tries to select "InvalidDept" from the department dropdown. That value does not exist in the dropdown options, so rpaframework raises an exception and the entire script stops. Every record after Frank Miller never gets processed.

Why did it crash?

The select_from_list_by_value method fails when the requested value is not present in the dropdown. Without error handling, this unhandled exception kills the entire Python process. Records 7 and 8 are perfectly valid, but they never get a chance to run.

This is exactly the gap that transaction processing solves. Each record should be isolated so that one failure does not cascade to the rest.

Add custom exception classes and input validation

Enterprise RPA bots distinguish between two kinds of failures. A business exception means the input data is bad (missing fields, invalid values). A system exception means something went wrong with the target system (network timeout, server error). Each type gets different handling.

  • In bot.py, find this line at the top:
from openpyxl import load_workbook
  • Replace it with this updated import that also brings in Workbook (you will need it in the next step for the Excel report):
from openpyxl import Workbook, load_workbook
  • Directly below the updated import line and above FORM_URL, add the valid departments list, retry constant, and exception classes:
VALID_DEPARTMENTS = ["Engineering", "Marketing", "Sales", "HR"]
MAX_RETRIES = 2
FORM_URL = "http://localhost:8000/form.html"


class BusinessException(Exception):
    pass


class SystemException(Exception):
    pass

What do these do?

VALID_DEPARTMENTS lists every department the form accepts. Any record with a department not in this list is bad data.

MAX_RETRIES controls how many times the bot retries a record that hits a system error (like the form's random 10% failure rate).

BusinessException and SystemException are custom exception classes. Using distinct types lets your code catch each kind separately and respond appropriately.

  • Below the load_work_queue function, add the validate_record function that checks each record before the bot touches the browser:
def validate_record(record):
    if not record.get("Name") or not str(record["Name"]).strip():
        raise BusinessException("Missing employee name")
    if not record.get("Email") or not str(record["Email"]).strip():
        raise BusinessException(f"Missing email for {record['Name']}")
    if record.get("Department") not in VALID_DEPARTMENTS:
        raise BusinessException(
            f"Invalid department '{record.get('Department')}' for {record['Name']}"
        )
    if not record.get("Start Date"):
        raise BusinessException(f"Missing start date for {record['Name']}")

What does this function do?

validate_record checks four things: name exists, email exists, department is in the allowed list, and start date is present. If any check fails, it raises a BusinessException with a descriptive message. This catches bad data before the bot wastes time opening the form.

  • Save bot.py and test the validation by running this in your terminal:
python -c "from bot import validate_record, BusinessException; validate_record({'Name': 'Eve Davis', 'Email': '', 'Department': 'Marketing', 'Start Date': '2026-09-01'})"

You should see a traceback ending with BusinessException: Missing email for Eve Davis. That confirms your validation catches the bad record.

Seeing a different error?

If you see ImportError, make sure you saved bot.py after adding both the exception classes and the validate_record function. The import needs both to exist in the file.

help me debug my validate_record import error

Add helper functions for form checking and retry support

Your bot needs two more capabilities: detecting whether the form submission succeeded or hit a system error, and resetting the form between attempts.

  • In bot.py, add these three functions below fill_form:
def check_result(browser):
    # Read the result message the form displays after submit
    result_element = browser.get_text("id:result")
    if "System Error" in result_element:
        raise SystemException("System Error on form submission")
    if "Success" not in result_element:
        raise SystemException(f"Unexpected result: {result_element}")


def clear_form(browser):
    # Navigate back to a fresh form for the next attempt
    browser.go_to(FORM_URL)
    time.sleep(0.5)


def process_transaction(browser, record):
    # Full transaction: validate, fill, check
    validate_record(record)
    fill_form(browser, record)
    check_result(browser)

What do these functions do?

check_result reads the result message from the form after submission. If it contains "System Error", the bot knows the form's server had a transient glitch (your form has a 10% random failure rate). If it does not contain "Success", something unexpected happened.

clear_form reloads the form page so all fields are empty for the next attempt.

process_transaction combines all three steps (validate, fill, check) into one callable unit. This is the function that represents processing a single queue item.

  • Below process_transaction, add the generate_report function that prints a formatted summary of all results:
def generate_report(results):
    print("\n" + "=" * 70)
    print(f"{'Record':<20} {'Status':<22} {'Attempts':<10} {'Details'}")
    print("-" * 70)
    success_count = 0
    business_count = 0
    system_count = 0
    for r in results:
        print(f"{r['record']:<20} {r['status']:<22} {r['attempts']:<10} {r['message']}")
        if r["status"] == "Success":
            success_count += 1
        elif r["status"] == "Business Exception":
            business_count += 1
        else:
            system_count += 1
    print("-" * 70)
    print(
        f"Processed {len(results)} records: "
        f"{success_count} Success, "
        f"{business_count} Business Exception, "
        f"{system_count} System Exception"
    )
    print("=" * 70)

What does generate_report do?

This function takes the list of result dictionaries and prints a formatted table to the terminal. It counts successes, business exceptions, and system exceptions, then prints a summary line at the bottom. This is the audit trail that every enterprise bot needs.

  • Save bot.py and test generate_report with dummy data by running:
python -c "from bot import generate_report; generate_report([{'record': 'Test User', 'status': 'Success', 'attempts': 1, 'message': 'OK'}])"

You should see a formatted table with one row showing "Test User" as a success.

Seeing a syntax error?

Check that your f-strings use matching quote types. The format strings inside generate_report use single quotes inside the f-string braces and double quotes wrapping the string.

help me fix my generate_report syntax error

Rewrite main with transaction processing and retry logic

Now you have all the building blocks: validation, result checking, form clearing, and reporting. The final piece is rewriting main() to orchestrate everything with transaction isolation and retry logic.

  • In bot.py, delete your entire main() function (everything from def main(): down to the end of that function, but keep if __name__ == "__main__":).
  • Replace it with this first section that sets up the loop and catches business exceptions immediately:
def main():
    records = load_work_queue("employees.xlsx")
    print(f"Loaded {len(records)} records from work queue")

    browser = Selenium()
    browser.open_available_browser(FORM_URL, headless=False)
    time.sleep(1)

    results = []

    for i, record in enumerate(records, 1):
        print(f"\nProcessing record {i}/{len(records)}: {record.get('Name', 'Unknown')}")
        attempts = 0
        status = None
        message = ""

        try:
            validate_record(record)
        except BusinessException as e:
            status = "Business Exception"
            message = str(e)
            print(f"  BUSINESS EXCEPTION: {message}")
            results.append({
                "record": record.get("Name", "Unknown"),
                "status": status,
                "attempts": 0,
                "message": message,
            })
            continue

What does this section do?

The loop now processes ALL records (no [:3] slice). For each record, it first tries validate_record. If validation fails with a BusinessException, the record is logged as a business exception and the loop uses continue to skip to the next record. The bot never opens the form for bad data.

  • Directly below the continue statement (still inside the for loop), add the retry logic that handles system exceptions:
        for attempt in range(1, MAX_RETRIES + 2):
            attempts = attempt
            try:
                clear_form(browser)
                fill_form(browser, record)
                check_result(browser)
                status = "Success"
                message = "Registered successfully"
                print(f"  SUCCESS (attempt {attempt})")
                break
            except SystemException as e:
                message = str(e)
                print(f"  SYSTEM EXCEPTION (attempt {attempt}): {message}")
                if attempt <= MAX_RETRIES:
                    print(f"  Retrying in 2 seconds...")
                    time.sleep(2)
                else:
                    status = "System Exception"
                    print(f"  FAILED after {MAX_RETRIES + 1} attempts")

        if status is None:
            status = "System Exception"

        results.append({
            "record": record.get("Name", "Unknown"),
            "status": status,
            "attempts": attempts,
            "message": message,
        })

    browser.close_all_browsers()
    generate_report(results)

How does the retry logic work?

range(1, MAX_RETRIES + 2) gives attempts 1, 2, and 3 (the initial try plus 2 retries). If check_result detects a system error, the bot waits 2 seconds and tries again. If all 3 attempts fail, the record is logged as a system exception and the bot moves on.

After the retry loop finishes, the result is appended to the results list. Once every record has been processed, the browser closes and generate_report prints the summary table.

  • Save bot.py.

Before you run this, what do you expect to see for Eve Davis and Frank Miller now that validation catches them before the form is touched?

  • Run the bot:
python bot.py

The bot processes all 8 records. You should see it skip Eve Davis and Frank Miller with BUSINESS EXCEPTION messages, successfully register the 6 valid employees (with possible retries on system errors), and print a summary table at the end.

What should I see?

The terminal output shows each record being processed. Eve Davis prints "BUSINESS EXCEPTION: Missing email for Eve Davis". Frank Miller prints "BUSINESS EXCEPTION: Invalid department 'InvalidDept' for Frank Miller". The remaining 6 records show SUCCESS (possibly with retry attempts due to the 10% random failure). The summary table at the bottom shows the final counts.

Bot still crashing instead of handling errors?

Make sure the try: validate_record(record) block and the for attempt retry loop are both inside the outer for i, record loop. Indentation must be exactly 8 spaces (two levels) for the retry code.

If you see NameError: name 'BusinessException' is not defined, check that your exception classes are defined above main() in the file.

help me debug my bot's transaction processing

✔️ Awesome, I've got everything!

Great. Double check you have saved bot.py and that your bot successfully processes all 8 records with proper error handling.

ⓧ I'd like to double check the full code

Here is the complete bot.py at this point. Your file has several sections. Scroll through and confirm each one is present in order:

  • Imports (time, Workbook, load_workbook, Selenium).
  • Constants (VALID_DEPARTMENTS, MAX_RETRIES, FORM_URL).
  • Exception classes (BusinessException, SystemException).
  • Functions: load_work_queue, validate_record, fill_form, check_result, clear_form, process_transaction, generate_report, main.
  • The if __name__ == "__main__": guard at the bottom.

Your bot now handles bad data gracefully and retries transient failures. Next up, you will turn those terminal results into a proper Excel execution report that you could hand to a stakeholder.

Generate the Execution Report

Your bot now processes all 8 records, handles exceptions gracefully, and prints a summary to the terminal. That terminal output disappears the moment you close the window.

RPA Solutions Architects require every bot to produce a permanent audit trail. In this step, you will transform your results tracking into a proper Excel report that anyone on the team can open, filter, and archive.

In this step, get ready to:
  • Confirm the terminal report format is clean and readable.
  • Write an Excel report function that persists results to a file.
  • Run the complete bot and verify both the terminal table and the Excel output.
Confirm the terminal report format

Your generate_report() function from the previous step already prints a formatted table to the terminal. This is one half of your audit trail.

  • In bot.py, scroll to the generate_report() function and confirm it contains aligned columns for Record, Status, Attempts, and Details.

What does this terminal report show?

The terminal table gives you immediate feedback when the bot finishes. It prints each record with its status, how many attempts it took, and any error details. The summary line at the bottom counts successes, business exceptions, and system exceptions.

The problem is this output vanishes when you close the terminal. The next function creates a permanent, shareable file.

Write the Excel report function

Enterprise bots produce files that can be emailed, stored in SharePoint, or fed into dashboards. You will write a function that creates an Excel workbook mirroring the terminal table, complete with bold headers and a totals summary row.

  • In bot.py, find the end of your generate_report() function. Add the following new function directly below it:
def save_report_to_excel(results):
    wb = Workbook()
    ws = wb.active
    ws.title = "Execution Report"
    headers = ["Record", "Status", "Attempts", "Details"]
    ws.append(headers)
    for cell in ws[1]:
        cell.font = cell.font.copy(bold=True)
    for r in results:
        ws.append([r["record"], r["status"], r["attempts"], r["message"]])
    success_count = sum(1 for r in results if r["status"] == "Success")
    business_count = sum(1 for r in results if r["status"] == "Business Exception")
    system_count = sum(1 for r in results if r["status"] == "System Exception")
    ws.append([])
    ws.append(["TOTAL", f"{success_count}S / {business_count}B / {system_count}Sys", len(results), ""])
    wb.save("execution_report.xlsx")
    print("\nReport saved to execution_report.xlsx")

What does this code do?

  • Workbook() creates a fresh Excel file in memory. The active sheet gets renamed to "Execution Report".
  • cell.font.copy(bold=True) makes the header row bold so the columns stand out when someone opens the file.
  • The loop writes one row per employee using the same result dictionaries the terminal report uses.
  • The final ws.append([]) adds a blank separator row, then a TOTAL row summarises counts across all statuses.
Call the report function from main

The function exists but nothing calls it yet. You need to wire it into main() so it runs after the terminal report prints.

  • In bot.py, scroll to the bottom of your main() function.
  • Find this line:
    generate_report(results)
  • Add this line directly below it:
    save_report_to_excel(results)
  • Save bot.py.

Why two separate report functions?

The terminal report gives the developer instant feedback during a run. The Excel report gives managers and auditors a permanent artifact they can open without touching a terminal. Enterprise bots typically produce both.

Run the complete bot and verify both outputs
  • Make sure your local server is still running in a separate terminal. If you stopped it, start it again by running:
python -m http.server 8000

Before you run the bot, predict: what two things should appear when it finishes? Think about what outputs you just wired up.

  • In your other terminal (with the virtual environment active), run the bot:
python bot.py

The bot opens a browser, processes all 8 employee records, then prints the formatted table to your terminal. At the very end you should see the line "Report saved to execution_report.xlsx".

  • Confirm the Excel file was created by running:
ls execution_report.xlsx

You should see execution_report.xlsx listed in your project folder.

  • Open execution_report.xlsx in Excel (or any spreadsheet app). Confirm it has bold headers, one row per employee with their processing status, and a TOTAL summary row at the bottom.

Don't see the Excel file?

  • Check that you added save_report_to_excel(results) in main() after generate_report(results). The function must be called to produce the file.
  • Make sure the indentation of save_report_to_excel(results) is at the same level as generate_report(results) (inside main(), not outside it).
  • If you see an error about Workbook not being defined, confirm your import at the top of the file reads from openpyxl import Workbook, load_workbook.

Why isn't my execution_report.xlsx being generated?

✔️ Awesome, I've got everything!

Great. Double check you have saved bot.py and that execution_report.xlsx is in your project folder.

ⓧ I'd like to double check the full code

Your bot.py is now over 30 lines, so confirm these sections are present in order:

  • Imports: time, Workbook, load_workbook, Selenium.
  • Constants: VALID_DEPARTMENTS, MAX_RETRIES, FORM_URL.
  • Exception classes: BusinessException and SystemException.
  • Functions: load_work_queue(), validate_record(), fill_form(), check_result(), clear_form(), process_transaction().
  • generate_report() prints the terminal table.
  • save_report_to_excel() creates the Excel workbook with bold headers, data rows, and a TOTAL summary row.
  • main() ends with both generate_report(results) and save_report_to_excel(results) called after the browser closes.

Your RPA bot is now fully complete. It reads from a work queue, automates web form entry, handles both business and system exceptions, retries transient failures, and produces a permanent audit trail. Next up, you will put your skills to the test with a completely different automation challenge.

Secret mission

Build a File Renaming Bot from Scratch

You have built an RPA bot that automates web form data entry. Now prove you can apply the same patterns to a completely different domain. Build a file renaming bot from scratch that scans a folder, renames messy files to a consistent pattern, and produces an execution report.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally, so there are no ongoing costs.

Resources you used:

  • The rpa-bot project folder on your Desktop (contains all project files).
  • Virtual environment .venv with installed packages (rpaframework, openpyxl).
  • Generated data files: employees.xlsx and execution_report.xlsx.
  • The messy_files folder with renamed files (Secret Mission).
  • Local HTTP server process (if still running in a terminal).

Keep everything running

No action needed. Choose this if you are still actively building or want to experiment with the bot further.

  • Your rpa-bot folder stays on your Desktop with all files intact.
  • The virtual environment stays ready. Reactivate it anytime with .venv\Scripts\activate (Windows) or source .venv/bin/activate (macOS/Linux).
  • If the local HTTP server is still running, you can leave it or stop it with Ctrl+C in that terminal.

Pause - I'll come back to this later

Shut down running processes to free up memory. Your files stay on disk for when you return.

  • Stop the local HTTP server by pressing Ctrl+C in the terminal where it is running.
  • Deactivate the virtual environment by running:
deactivate
  • Close VS Code. When you come back, reopen the rpa-bot folder and reactivate the virtual environment.

Delete - I don't want to use this again

Remove all project resources and start fresh. This deletes everything you built in this project.

  • Stop the local HTTP server by pressing Ctrl+C in the terminal where it is running (if still active).
  • Deactivate the virtual environment by running:
deactivate
  • Navigate to your Desktop and delete the entire project folder. On macOS/Linux, run:
cd ~/Desktop
rm -rf rpa-bot
  • On Windows, run:
cd ~\Desktop
rmdir /s /q rpa-bot
  • This removes all project files, the virtual environment, generated Excel reports, and the messy_files folder.

Nice Work!

Nice Work!

You just built a complete Python RPA bot that automates web form data entry from an Excel work queue. You watched it open a browser, fill in fields, click submit, handle errors, and produce an audit trail. That is real-world automation.

You've learned how to:

  • Build an RPA bot that reads an Excel work queue and automates web form submissions with zero human input.
  • Implement transaction-based processing that isolates each record so one failure does not crash the entire run, with retry logic for transient system errors.
  • Distinguish between business exceptions (bad data you skip) and system exceptions (transient failures you retry), mirroring how UiPath REFramework handles errors in enterprise bots.
  • Secret Mission: Built a second automation from scratch (a file renaming bot), proving you can apply the transaction/exception/report pattern to any domain.

Ready to quiz yourself?