Of course! Here is a comprehensive guide on how to work with PDF files using Appium and Python. This is a common requirement for mobile automation, especially for applications that generate or display invoices, reports, or contracts.

The core challenge is that Appium does not have built-in, direct methods to read or manipulate PDF files. Appium's primary job is to interact with mobile application UI elements. Therefore, we need a multi-step approach that combines Appium for UI interaction and standard Python libraries for PDF processing.
The Core Strategy
The general workflow for most PDF-related tasks is:
- Locate the PDF: Use Appium to navigate your mobile app and find the PDF file. This could be a link to download, a button to open a document, or a file already saved in the app's sandbox.
- Get the PDF File: Extract the PDF file from the mobile device to your local machine. This is the most critical step.
- If it's a download, you can configure Appium to download it directly to your host machine.
- If it's already on the device, you'll need to pull it from the device's file system.
- Process the PDF with Python: Once the PDF is on your local machine, use a powerful Python library like
PyPDF2orpdfplumberto read its content, extract text, or perform other validations.
Method 1: Handling Downloadable PDFs (The Easiest Case)
This is the most straightforward scenario. Your app has a "Download PDF" button, and you want to automate the download and then verify its content.
Step 1: Set Up Your Environment
First, install the necessary Python libraries.

pip install Appium-Python-Client
For PDF processing, we'll use PyPDF2 for basic text extraction and pdfplumber for more advanced text layout analysis.
pip install PyPDF2 pip install pdfplumber
Step 2: The Python Automation Script
The key here is to tell Appium where to save the downloaded file using the desiredCapabilities dictionary. We'll use the autoDownload capability for Android.
import os
import time
from appium import webdriver
from appium.options.android import UiAutomator2Options
import PyPDF2
import pdfplumber
# --- 1. Configure Appium Capabilities ---
# IMPORTANT: Update these values for your specific setup
appium_server_url = 'http://localhost:4723/wd/hub'
app_package = 'com.example.myapp' # Replace with your app's package
app_activity = 'com.example.myapp.MainActivity' # Replace with your app's activity
# Define the path where you want to save downloaded files
# Make sure this directory exists
download_dir = os.path.abspath('downloads')
if not os.path.exists(download_dir):
os.makedirs(download_dir)
capabilities = {
'platformName': 'Android',
'deviceName': 'Pixel_API_33', # Or your device name
'appPackage': app_package,
'appActivity': app_activity,
'automationName': 'UiAutomator2',
'autoDownload': True,
'downloadDir': download_dir, # This is the magic line!
'noReset': False,
'fullReset': False
}
# --- 2. Initialize the Appium Driver ---
driver = webdriver.Remote(appium_server_url, options=UiAutomator2Options().load_capabilities(capabilities))
# --- 3. Automate the Download Process ---
try:
# Navigate to the screen with the download button (example)
# driver.find_element('id', 'navigation_button').click()
# time.sleep(2)
# Find and click the "Download PDF" button
# This is a hypothetical locator. Replace with your actual locator.
download_button = driver.find_element('xpath', '//android.widget.Button[@text="Download Report"]')
print("Found download button. Clicking it...")
download_button.click()
# Wait for the download to complete
# This is a simple wait. A more robust solution would check for the file's existence.
print("Waiting for download to complete...")
time.sleep(10) # Adjust this time as needed
# --- 4. Find the Downloaded File ---
# List files in the download directory
downloaded_files = os.listdir(download_dir)
pdf_files = [f for f in downloaded_files if f.endswith('.pdf')]
if not pdf_files:
raise FileNotFoundError("No PDF file found in the download directory.")
latest_pdf_file = os.path.join(download_dir, pdf_files[0])
print(f"Successfully downloaded PDF: {latest_pdf_file}")
# --- 5. Process the PDF with Python ---
print("\n--- Processing PDF with PyPDF2 ---")
with open(latest_pdf_file, 'rb') as file:
reader = PyPDF2.PdfReader(file)
num_pages = len(reader.pages)
print(f"PDF has {num_pages} pages.")
# Extract text from the first page
page = reader.pages[0]
text = page.extract_text()
print("Text from first page (PyPDF2):")
print(text)
# Example: Check if a specific string is in the PDF
if "Invoice Total" in text:
print("SUCCESS: Found 'Invoice Total' in the PDF.")
else:
print("FAILURE: 'Invoice Total' not found in the PDF.")
print("\n--- Processing PDF with pdfplumber (better for layout) ---")
with pdfplumber.open(latest_pdf_file) as pdf:
first_page = pdf.pages[0]
text_with_layout = first_page.extract_text()
print("Text from first page (pdfplumber):")
print(text_with_layout)
# Example: Extract all table-like data
tables = first_page.extract_tables()
if tables:
print("\nTables found on the first page:")
for i, table in enumerate(tables):
print(f"Table {i+1}:")
for row in table:
print(row)
finally:
# --- 6. Clean Up ---
print("\nQuitting the driver.")
driver.quit()
Method 2: Accessing an Existing PDF on the Device (More Advanced)
Sometimes the PDF is already on the device (e.g., in the app's internal storage). You need to pull it to your local machine before processing.
Prerequisites
- Android: You need the
adb(Android Debug Bridge) tool installed and in your system's PATH. - iOS: The process is more complex and involves using
ideviceinstallerandlibimobiledevicetools, which are outside the scope of a simple Appium guide. We'll focus on Android here.
The Python Script
The script will first use Appium to find the file path on the device and then use a subprocess call to adb pull the file.

import os
import time
import subprocess
from appium import webdriver
from appium.options.android import UiAutomator2Options
import pdfplumber
# --- 1. Configure Appium Capabilities ---
appium_server_url = 'http://localhost:4723/wd/hub'
app_package = 'com.example.myapp'
app_activity = 'com.example.myapp.MainActivity'
# Local directory to save the pulled PDF
local_dir = os.path.abspath('pulled_pdfs')
if not os.path.exists(local_dir):
os.makedirs(local_dir)
capabilities = {
'platformName': 'Android',
'deviceName': 'Pixel_API_33',
'appPackage': app_package,
'appActivity': app_activity,
'automationName': 'UiAutomator2',
'noReset': True
}
# --- 2. Initialize the Appium Driver ---
driver = webdriver.Remote(appium_server_url, options=UiAutomator2Options().load_capabilities(capabilities))
# --- 3. Find the PDF on the Device ---
try:
# Navigate to the screen where the PDF is located
# driver.find_element('id', 'open_docs_button').click()
# time.sleep(2)
# Find the file name (e.g., from a list view)
# This is a hypothetical locator.
file_element = driver.find_element('xpath', '//android.widget.TextView[@text="my_report.pdf"]')
file_name = file_element.text
print(f"Found file: {file_name}")
# Get the full path of the file.
# This requires knowing the app's data directory structure.
# A common location is /data/data/<app_package>/files/
# We can use the 'shell' command via Appium to find the absolute path.
# This command lists files and finds our PDF, then gets its full path.
# The exact command depends on your device's shell and file system.
# A more robust way is to get the content-desc or other attributes if available.
# For this example, let's assume we know the path structure.
# A more dynamic approach:
# file_path = driver.execute_script('mobile: shell', {'command': f'find /data/data/{app_package} -name "{file_name}"'})
# This is complex and device-dependent. A simpler path is often used in examples.
# Let's assume a simpler, known path for demonstration.
# In a real scenario, you'd need to figure this out.
remote_file_path = f'/data/data/{app_package}/files/{file_name}'
print(f"Attempting to pull file from: {remote_file_path}")
# --- 4. Pull the File from Device to Host ---
local_file_path = os.path.join(local_dir, file_name)
# Use subprocess to run the adb pull command
print("Executing 'adb pull' command...")
pull_command = f'adb pull "{remote_file_path}" "{local_file_path}"'
print(f"Command: {pull_command}")
process = subprocess.Popen(pull_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
if process.returncode == 0:
print(f"SUCCESS: File pulled to {local_file_path}")
else:
print(f"FAILURE: Could not pull file. Error: {stderr.decode('utf-8')}")
raise Exception("File pull failed.")
# --- 5. Process the Pulled PDF ---
if os.path.exists(local_file_path):
print("\n--- Processing Pulled PDF ---")
with pdfplumber.open(local_file_path) as pdf:
first_page_text = pdf.pages[0].extract_text()
print("Content of the first page:")
print(first_page_text)
# Add your validation logic here
else:
print("ERROR: Pulled file does not exist at the expected path.")
finally:
# --- 6. Clean Up ---
print("\nQuitting the driver.")
driver.quit()
Key Challenges and Best Practices
- File Paths on Device: The hardest part of Method 2 is figuring out the exact path to the file on the Android device. It's often buried deep in
/data/data/<your_package>/. You may need to useadb shellcommands manually to explore the file system first. - Permissions: Your app and the test environment might need special permissions to access certain parts of the file system.
adb pullfrom app-private directories usually works without extra permissions from the app itself. - Handling Dynamic File Names: If the PDF name is dynamic (e.g.,
report_2025-10-27.pdf), you'll need to use Appium to get the text of the element and parse the filename from it, as shown in the script. - Timing: Always add appropriate waits (
time.sleepor explicit waits) after actions that trigger file operations (clicking download, pulling file) to ensure the process completes before your script tries to access the file. - iOS Consideration: As mentioned, iOS is sandboxed. Pulling files from an iOS app's container is not as direct as with Android. You would typically need to use tools like
ideviceinstallerto list apps andlibimobiledeviceto pull files from the app's Documents directory, which is much more complex than the Androidadb pullmethod. For iOS, the "download" method is often more practical.
