├── .gitignore ├── py └── selenium │ └── webdriver │ ├── android │ ├── __init__.py │ ├── webdriver.py │ └── service.py │ └── __init__.py ├── example └── example.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | .*.pyc 3 | -------------------------------------------------------------------------------- /py/selenium/webdriver/android/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2008-2009 WebDriver committers 2 | # Copyright 2008-2009 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | -------------------------------------------------------------------------------- /py/selenium/webdriver/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright 2008-2010 Webdriver_name committers 4 | # Copyright 2008-2010 Google Inc. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | # added Android - Sean Wang 19 | from android.webdriver import WebDriver as Android 20 | from firefox.webdriver import WebDriver as Firefox 21 | from firefox.firefox_profile import FirefoxProfile 22 | from chrome.webdriver import WebDriver as Chrome 23 | from ie.webdriver import WebDriver as Ie 24 | from remote.webdriver import WebDriver as Remote 25 | from common.desired_capabilities import DesiredCapabilities 26 | from common.action_chains import ActionChains 27 | 28 | __version__ = '2.8.0' 29 | -------------------------------------------------------------------------------- /example/example.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @author: Sean Wang: xiao.wang@symbio.com 4 | 5 | This demo is written according to the two demos in selenium AndroidDriver wiki: 6 | 7 | http://code.google.com/p/selenium/wiki/AndroidDriver#Run_the_Tests 8 | and 9 | http://code.google.com/p/selenium/wiki/AndroidDriver#Using_the_Android_Test_Framework 10 | 11 | to demostrate that why I implement Selenium AndroidDriver for Python Client: 12 | Python is so simple and elegant 13 | """ 14 | 15 | import unittest 16 | from selenium import webdriver 17 | 18 | class Test(unittest.TestCase): 19 | 20 | def setUp(self): 21 | self.driver=webdriver.Android() 22 | self.driver.implicitly_wait(30) 23 | self.driver.get("http://www.google.com.hk") 24 | 25 | def tearDown(self): 26 | self.driver.quit() 27 | 28 | def testDemo(self): 29 | searchBox=self.driver.find_element_by_name('q') 30 | searchBox.send_keys("Symbio") 31 | searchBox.submit() 32 | print "Page title is: %s"%self.driver.title 33 | #Ensure the title contains "Google" 34 | self.assertTrue("Google" in self.driver.title) 35 | #Ensure that there is at least one link with the keyword "Symbio" 36 | self.assertTrue(len(self.driver.find_elements_by_partial_link_text("Symbio"))>0) 37 | 38 | if __name__ == "__main__": 39 | unittest.main() 40 | -------------------------------------------------------------------------------- /py/selenium/webdriver/android/webdriver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright 2011 Webdriver_name committers 4 | # Copyright Sean Wang : xiao.wang@symbio.com 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver 19 | from selenium.webdriver.common.desired_capabilities import DesiredCapabilities 20 | from selenium.webdriver.remote.command import Command 21 | from service import Service 22 | 23 | class WebDriver(RemoteWebDriver): 24 | 25 | def __init__(self,deviceID=None,port=0): 26 | self.service = Service(deviceID,port=port) 27 | self.service.start() 28 | RemoteWebDriver.__init__(self, 29 | command_executor=self.service.service_url, 30 | desired_capabilities=DesiredCapabilities.ANDROID) 31 | 32 | def quit(self): 33 | """ Close AndroidDriver application on device""" 34 | try: 35 | RemoteWebDriver.quit(self) 36 | except httplib.BadStatusLine: 37 | pass 38 | finally: 39 | self.service.stop() 40 | if __name__ == '__main__': 41 | #driver= WebDriver('emulator-5554') 42 | driver= WebDriver() 43 | driver.get("http://www.symbio.com") 44 | driver.quit() 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | _Author: Sean Wang xiao.wang@symbio.com_ 4 | 5 | AndroidWebDriver4Python is an addon to [Selenium Python Client Driver](http://pypi.python.org/pypi/selenium) 6 | 7 | This addon supports multiple real devices simultaneously from release 1.1, but not supports emulators. I have not figured out yet. 8 | 9 | 10 | As I could not find Android WebDriver implementation in it,and I really like Python as opposed to Java. So I want to implement one. 11 | I know that I am a newbie, I do not expect to commit in the Selenium project. 12 | 13 | 14 | More infomation about *Selenium*, plz check http://code.google.com/p/selenium/ 15 | 16 | # Installing 17 | 18 | To install this AndroidDriver for Python, you need: 19 | 20 | 1. download AndroidWebDriver4Python using command: 21 | 22 | ``` 23 | $ git clone git://github.com/truebit/AndroidWebDriver4Python.git 24 | ``` 25 | 26 | 2. download and extract [Selenium Python client](http://pypi.python.org/pypi/selenium#downloads) 27 | 3. copy the entire `py` folder under AndroidWebDriver4Python to merge the 28 | same one in root directory of AndroidDriver for Python 29 | 4. back to the root directory of Selenium Python Client, to install this modified version using command: 30 | 31 | ``` 32 | $ python setup.py install 33 | ``` 34 | 35 | Here you have installed this AndroidWebDriver4Python add-on. 36 | There are some prerequisites to use AndroidWebDriver4Python. 37 | 38 | * [Install Android SDK](http://developer.android.com/sdk/installing.html) and set its `tools` and `platform-tools` in your PATH 39 | * Install Android server side application [android-server-2.x.x.apk](http://code.google.com/p/selenium/downloads/list) on your device 40 | * enable `USB Debugging` in your device and disable autolock, which normally could found from `Home>Settings>Applications>Development>USB debugging` 41 | 42 | * connect USB cable between device and PC, install adb drivers 43 | 44 | # Example 45 | ```python 46 | from selenium import webdriver 47 | 48 | # if only one device connected, you do not need to specifiy the serial id. 49 | # exmaple driver=webdriver.Android() 50 | driver1= webdriver.Android('HT1234567') 51 | driver2=webdriver.Android('091012345601E00D') 52 | driver1.get("http://www.symbio.com") 53 | driver2.get("http://www.google.com.hk") 54 | driver1.quit() 55 | driver2.quit() 56 | ``` 57 | 58 | If you want more detailed example, plz check [example.py](AndroidWebDriver4Python/blob/master/example/example.py) 59 | 60 | # Documentation 61 | 62 | The latest [Selenium Python Bindings documentation](http://readthedocs.org/docs/selenium-python/en/latest) 63 | 64 | # Use The Source Luke! 65 | 66 | As this Android Driver I implemented is herited from [webdriver.py](http://code.google.com/p/selenium/source/browse/trunk/py/selenium/webdriver/remote/webdriver.py), so plz see its source code to use Android driver just like other WebDriver. 67 | -------------------------------------------------------------------------------- /py/selenium/webdriver/android/service.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright 2011 Webdriver_name committers 4 | # Copyright Sean Wang : xiao.wang@symbio.com 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | import subprocess 18 | from subprocess import PIPE 19 | import time 20 | from selenium.common.exceptions import WebDriverException 21 | from selenium.webdriver.common import utils 22 | 23 | 24 | 25 | class Service(object): 26 | """ Object that manages the starting and stopping of the AndroidDriver """ 27 | CMD_NOT_IN_PATH = """Could not find adb command, AndroidDriver needs Android SDK. 28 | Please download from http://developer.android.com/sdk/index.html 29 | and add directories 'tools' and 'platform-tools' in PATH.""" 30 | 31 | ANDROID_DRIVER_CLIENT_APP_CMP= r'org.openqa.selenium.android.app/.MainActivity' 32 | 33 | def __init__(self,device=None,port=0): 34 | """ Creates a new instance of the Service 35 | Args: 36 | device: serial ID of the Android device. 37 | Could be seen by command 'android devices'. 38 | If only one device connected. you do not need to assign it 39 | """ 40 | self.device= Service.initDevice(device) 41 | self.adbCmd= r'adb -s %s '%self.device 42 | self.port = port 43 | if self.port == 0: 44 | self.port = utils.free_port() 45 | 46 | @staticmethod 47 | def initDevice(deviceID=None): 48 | deviceInfo = [] 49 | # as adb server launching process made the script stuck, 50 | # so I use hard-coded wait time to make it through 51 | # I do not know why subprocess could not get output in such situation 52 | cmd1= 'adb start-server' 53 | cmd2= 'adb devices' 54 | p=subprocess.Popen(cmd1,stdout=PIPE, stderr=PIPE,shell=True) 55 | count=0 56 | while count<30: 57 | time.sleep(1) 58 | if p.poll() is 0: 59 | break 60 | else: 61 | raise WebDriverException("adb could not get device info after 30 seconds.") 62 | output=subprocess.check_output(cmd2,shell=True).split()[4:] 63 | for i, v in enumerate(output): 64 | if i + 1 < len(output) and i % 2 == 0: 65 | deviceInfo.append((v, output[i + 1])) 66 | if deviceInfo: 67 | # check if all devices are online 68 | if 'device' not in [i[1] for i in deviceInfo]: 69 | raise WebDriverException( """No device is online. 70 | Reconnect devices and retry. 71 | Only a deviceID followed with 'device' would work.""") 72 | if deviceID: 73 | # check if device with given deviceID is connected 74 | if deviceID in [i[0] for i in deviceInfo]: 75 | #print "Connected to %s..." % deviceID 76 | return deviceID 77 | else: 78 | raise WebDriverException("""Could not find device with serial id %r. 79 | Plz make sure you got the right ID."""%deviceID) 80 | else: 81 | for i in deviceInfo: 82 | if i[1] =='device': 83 | #print "Connected to %s..." % i[0] 84 | return i[0] 85 | else: 86 | raise WebDriverException("""No devices found. 87 | plz make sure you have attached devices""") 88 | 89 | def start(self): 90 | """ Starts the AndroidDriver Service. 91 | @Exceptions 92 | WebDriverException : Raised either when it can't start the service 93 | or when it can't connect to the service""" 94 | 95 | #print 'start tcp port 8080 forwarding' 96 | subprocess.call(r'%s forward tcp:%d tcp:8080'%(self.adbCmd,self.port),shell=True) 97 | 98 | 99 | # this is not mandatory as we already killed adb server, but this could 100 | # decrease the webview created in andriod server application. maybe 101 | # it's a bug to create one webview per launch of app? 102 | #print 'stop existing android server by sending back key' 103 | for i in xrange(4): 104 | subprocess.call(r'%s shell input keyevent 4'%self.adbCmd,shell=True) 105 | 106 | #print 'start android server activity' 107 | output=subprocess.check_output(r'%s shell am start -n %s'%(self.adbCmd, 108 | Service.ANDROID_DRIVER_CLIENT_APP_CMP), 109 | stderr=subprocess.STDOUT,shell=True).split() 110 | if len(output)> 5: #if app not installed, there would be error messages 111 | raise WebDriverException("""AndroidDriver needs to be installed on device. 112 | Download android-server-2.x.apk from 113 | http://code.google.com/p/selenium/downloads/list""") 114 | # wait for WebDriver Client to be launched completely 115 | time.sleep(2) 116 | print "AndroidDriver started on device %s" % repr(self.device) 117 | 118 | @property 119 | def service_url(self): 120 | """ Gets the url of the AndroidDriver Service """ 121 | return "http://127.0.0.1:%d/wd/hub"%self.port 122 | 123 | def stop(self): 124 | """ Close AndroidDriver by sending BACK keyevent to device""" 125 | try: 126 | print 'stopping AndroidDriver' 127 | subprocess.Popen(r'%s shell input keyevent 4'%self.adbCmd, 128 | stdout=PIPE, stderr=PIPE,shell=True) 129 | except: 130 | print """AndroidDriver was not closed. Close by yourself by tapping 131 | back key to exit AndroidDriver on device.""" 132 | --------------------------------------------------------------------------------