This error means your driver “options” object is None; pass a real options instance or load capabilities correctly to start the session.
Seeing this stack trace in Python can stall any mobile or browser test run. The root is simple: Selenium’s remote session builder expects an options object that can produce W3C capabilities. When it receives None, the call to to_capabilities() fails and you get the message above. The good news: a few clean adjustments fix it for Appium (Android/iOS), local Selenium, and grid/cloud runs.
Why You See AttributeError: ‘NoneType’ Object Has No Attribute ‘To_Capabilities’
Quick context: In Selenium 4, sessions are created from browser or driver options. Internally, Selenium calls options.to_capabilities() to produce the payload. If your code passes options=None or a wrong type, None has no such method, so the call crashes. Appium’s Python client integrates with Selenium’s flow, so the same rule applies across Android, iOS, and mobile web.
What “None” usually means: a variable named options never got assigned, a helper returned None, or you used a legacy desired_capabilities=... path that no longer wires options in Selenium 4.
Quick Checks Before You Change Code
- Print The Variable — Add
print(type(options))right beforewebdriver.Remote(...). If you see<class 'NoneType'>, you found the cause. - Pick The Right Options Class — Use the exact class that matches your target:
- Android (Appium):
from appium.options.android import UiAutomator2Options - iOS (Appium):
from appium.options.ios import XCUITestOptions - Desktop Chrome:
from selenium.webdriver.chrome.options import Options - Edge/Firefox/Safari: their matching
Optionsclasses
- Android (Appium):
- Avoid Mixed Paths — Don’t pass both
desired_capabilities=...andoptions=.... Stick tooptionsonly. - Verify Package Versions — Keep Selenium/Appium client versions that work together. Old code with new clients is a common mismatch.
Fix Attributeerror ‘Nonetype’ Object Has No Attribute ‘To_Capabilities’ — By Setup
Android With Appium (UiAutomator2)
Use UiAutomator2Options, then .load_capabilities() if you want to keep a dict, or set fields directly on the options object.
from appium import webdriver
from appium.options.android import UiAutomator2Options
caps = {
"platformName": "Android",
"automationName": "UiAutomator2",
"deviceName": "Android Emulator",
"appPackage": "com.android.settings",
"appActivity": ".Settings",
"language": "en",
"locale": "US"
}
options = UiAutomator2Options().load_capabilities(caps)
driver = webdriver.Remote("http://127.0.0.1:4723", options=options)
iOS With Appium (XCUITest)
from appium import webdriver
from appium.options.ios import XCUITestOptions
options = XCUITestOptions()
options.platformVersion = "17.0"
options.deviceName = "iPhone 15"
options.bundleId = "com.example.app"
driver = webdriver.Remote("http://127.0.0.1:4723", options=options)
Desktop Chrome (Local)
Pass a real Chrome Options object.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
chrome_options = Options()
chrome_options.add_argument("--start-maximized")
driver = webdriver.Chrome(options=chrome_options)
Remote Grid Or Cloud (Desktop Browser)
When pointing to a Selenium Grid or a cloud hub, you still pass a concrete options object. The hub URL changes; the pattern does not.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
chrome_options = Options()
chrome_options.set_capability("browserVersion", "latest")
chrome_options.set_capability("platformName", "Windows")
driver = webdriver.Remote("https://your.grid.or.cloud/wd/hub", options=chrome_options)
Mobile Web On Android (Chrome Under Appium)
Use Android options and add browserName.
from appium import webdriver
from appium.options.android import UiAutomator2Options
caps = {
"platformName": "Android",
"automationName": "UiAutomator2",
"deviceName": "Pixel_7_Pro_API_34",
"browserName": "Chrome"
}
options = UiAutomator2Options().load_capabilities(caps)
driver = webdriver.Remote("http://127.0.0.1:4723", options=options)
Working Examples You Can Copy
Keep Your Old Dict? Load It Into Options
# You can keep your old capability dicts.
caps = {"platformName": "Android", "automationName": "UiAutomator2", "deviceName": "Android"}
from appium.options.android import UiAutomator2Options
driver = webdriver.Remote("http://localhost:4723", options=UiAutomator2Options().load_capabilities(caps))
Set On The Options Object Directly
from appium.options.ios import XCUITestOptions
opts = XCUITestOptions()
opts.deviceName = "iPhone 14"
opts.platformVersion = "16.4"
opts.bundleId = "com.example.app"
driver = webdriver.Remote("http://localhost:4723", options=opts)
Selenium Wire, Proxies, Or Wrappers
Some wrappers change how you pass options. If a wrapper expects its own “options” type or returns None, route the underlying Selenium options explicitly. As a safe baseline, build a standard Options object first and pass it through the wrapper in the way the wrapper’s docs show.
Common Pitfalls And How To Avoid Them
- Overwriting The Variable — Calling a method that returns
Noneand reassigning it tooptionswipes the object. Keep mutations and assignment separate. - Using Desired Capabilities Alone — In Selenium 4,
desired_capabilitiesis deprecated and removed in many layers. Build fromOptionsand passoptions=.... - Mismatched Packages — New Appium client with very old Selenium, or the reverse, can break the handoff. Pin known-good pairs in
requirements.txt. - Wrong Options Class —
UiAutomator2Optionsfor Android;XCUITestOptionsfor iOS; the desktop browser’s ownOptionsfor web. - Passing Both Options And Caps — When you pass a dict via
desired_capabilitiesand also passoptions, only one wins. Stick to the options path shown above. - Typos In Keys —
browserNamevsbrowsernameor a misspelled automation driver can lead to silent failures that bubble up as the same crash.
Troubleshooting Table
| Symptom | Likely Cause | Fix |
|---|---|---|
AttributeError: 'NoneType' object has no attribute 'to_capabilities' on webdriver.Remote |
options is None |
Build the correct options class and pass it via options=... |
| Same error after updating Appium client | Switched from desired caps to options; old code path | Use UiAutomator2Options()/XCUITestOptions() and .load_capabilities() |
| Remote/cloud run fails before session | Wrong options class or missing browser fields | Use browser’s Options and set browserVersion/platformName |
| Works locally, fails on grid | Wrapper/proxy stripped your options | Create native Selenium Options, then pass through wrapper as documented |
| Appium Android won’t start | Used Selenium’s Chrome Options instead of UiAutomator2Options |
Switch to UiAutomator2Options or load your dict into it |
Version Notes, Clean Installs, And Safe Upgrades
Selenium 4 standardized on options classes across languages. That’s why the attributeerror: ‘nonetype’ object has no attribute ‘to_capabilities’ often appears right after an upgrade or when you copy an older snippet. The cure is to migrate everything to the options pattern. The Selenium docs show options per browser, and Appium’s quickstarts show the platform-specific options classes for Android and iOS.
Fresh Setup Steps
- Create A New Virtual Environment — Keep test tools isolated.
- Install Matching Clients —
pip install selenium appium-python-client. - Pin Versions — Add an entry like
selenium==4.14.*and theappium-python-clientversion you validated. - Switch To Options Only — Replace any
desired_capabilities=...arguments with a concrete options object. - Smoke-Test A Minimal Script — One page open or one activity launch proves the stack before big suites run.
Reference Patterns You Can Trust
- Appium Android — Create
UiAutomator2Options, thenwebdriver.Remote(server, options=opts). - Appium iOS — Create
XCUITestOptions, thenwebdriver.Remote(server, options=opts). - Desktop Browsers — Use each browser’s
Optionsand passoptions=...locally or to a hub.
Wrap-Up: Confirm The Fix
Run your minimal script. If it launches, commit the options pattern across your suite. Keep one or two tiny samples in your repo as guardrails. That way, the next time code paths change, you won’t trip the same attributeerror: ‘nonetype’ object has no attribute ‘to_capabilities’.
