My new open source project - PYTAF (Python Test Automation Framework) - is in the process of being announced on Freecode.com. The project can be accessed directly through GitHub. It's a distillation of the various test harnesses I've developed over the past several years, beginning in Java, polished in Ruby, until finally fully formed in Python. This version is actually in Python 3, which I'm not using yet in everyday work. It's not much different, but developing this project was partly a mechanism for learning about it.
I'm happy to put it out there in the world, as happy as putting out any of my ebooks or earlier open-source projects. If one person finds it useful, that will be 'abundance'.
announcement also here on opencode
Tuesday, April 10, 2012
Thursday, April 5, 2012
Porting PTest from Python 2.6 to Python 3.0
work-in-progress ... experiment porting my python automation test framework from Python 2.6 to Python 3.0 (on Windows 7, it should be noted)
python.org/ftp/python/3.2.2
---------------------
import changes
---------------------
import httplib becomes import http.client as httplib
import urllib2 becomes import urllib as urllib2
import urllib also becomes import urllib as urllib
import MySQLdb - no such module. download pymsql instead and, after installing, import pymysql
lxml
from http://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml, lxml-2.3.4.win-amd64-py3.2.exe
failed to install - couldn't find python3.2 in the freaking registry
so ... not using lxml (BeautifulSoup is way better anyway)
import thread becomes import _thread
BeautifulSoup 4 - https://groups.google.com/forum/#!msg/beautifulsoup/VpNNflJ1rPI/sum07jmEwvgJ
and after installing
from BeautifulSoup import BeautifulSoup becomes from bs4 import BeautifulSoup
import Queue becomes import queue
--------------------------
execution changes
--------------------------
#set the right TEST_HOME
set TEST_HOME=C:\Users\tlichtenberg\workspace\tlichtenberg_win\QA\ptest3
# use the right python
c:\python32\python ptest3.py -c api_config.json -t test_api
---------------------
code changes
---------------------
print statements are now functions
print "this is my statement"
becomes
print("this is my statement")
MySQLdb.Connection(... = pymysql.connect(...
thread.get_ident() becomes _thread.get_ident()
http response.read() returns a bytes object, not a string. so it needs to be
response.read().decode() to turn it into a string
for k,v in dict.iteritems():
print k,v
becomes
for (k,v) in dict.items():
print(k,v)
--------------
unicode
________
all text is unicode
u"\xe9" no longer supported
backslashes in strings are now just backslashes, not escapes
? not sure about this yet ...
instead of u'\x80abc'.encode('utf-8')
b'\x80abc'.decode("utf-8", "replace")
Friday, January 20, 2012
Basic Django on Apache2
My Django project is called 'automation' and lives in the directory /home/tlichtenberg/workspace/ptest/django/src/automation
It has CSS files in the regression/static/styles directory under the home project. I needed to add some stuff to Apache to allow it to find the files it needs to run the Django project
--------------------------------
My Apache2 httpd.conf:
WSGIScriptAlias / /home/tlichtenberg/workspace/ptest/django/src/automation/django.wsgi
AliasMatch ^/([^/]*\.css) /home/tlichtenberg/workspace/ptest/django/src/automation/regression/static/styles/$1
Alias /static/ /home/tlichtenberg/workspace/ptest/django/src/automation/regression/static/
<Directory /home/tlichtenberg/workspace/ptest/django/src/automation/regression/static>
Order deny,allow
Allow from all
</Directory>
<Directory /home/tlichtenberg/workspace/ptest/django/src/automation>
Order deny,allow
Allow from all
</Directory>
------------------------------
The django,wsgi file is critical for setting the path and connecting Django to Apache. It looks like this:
import os
import sys
path = '/home/tlichtenberg/workspace/ptest/django/src/automation/'
if path not in sys.path:
sys.path.append(path)
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
------------------------------------------
The 'settings' defined by django,wsgi points to the Django project's 'settings.py' file, which defines a lot of the paths that Django needs. Also important is the 'urls.py' file, like settings.py, it is a standard for all Django projects and defines all the URLs associated with the web applications being served by Django. For example:
settings.py:
STATIC_ROOT = '/home/tlichtenberg/workspace/ptest/django/src/automation/regression/static/'
STATIC_URL = '/static/'
ROOT_URLCONF = 'urls'
INSTALLED_APPS = (
'my_project',
'my_other_project',
)
urls.py
(r'^my_project/$', 'my_project.views.my_project_main'),
defines that any url beginning with 'my_project' (e.g. http://localhost/my_project) will direct the code to the my_project_main method in the project's views.py file.
---------------------------------------------------
It has CSS files in the regression/static/styles directory under the home project. I needed to add some stuff to Apache to allow it to find the files it needs to run the Django project
--------------------------------
My Apache2 httpd.conf:
WSGIScriptAlias / /home/tlichtenberg/workspace/ptest/django/src/automation/django.wsgi
AliasMatch ^/([^/]*\.css) /home/tlichtenberg/workspace/ptest/django/src/automation/regression/static/styles/$1
Alias /static/ /home/tlichtenberg/workspace/ptest/django/src/automation/regression/static/
<Directory /home/tlichtenberg/workspace/ptest/django/src/automation/regression/static>
Order deny,allow
Allow from all
</Directory>
<Directory /home/tlichtenberg/workspace/ptest/django/src/automation>
Order deny,allow
Allow from all
</Directory>
------------------------------
The django,wsgi file is critical for setting the path and connecting Django to Apache. It looks like this:
import os
import sys
path = '/home/tlichtenberg/workspace/ptest/django/src/automation/'
if path not in sys.path:
sys.path.append(path)
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
------------------------------------------
The 'settings' defined by django,wsgi points to the Django project's 'settings.py' file, which defines a lot of the paths that Django needs. Also important is the 'urls.py' file, like settings.py, it is a standard for all Django projects and defines all the URLs associated with the web applications being served by Django. For example:
settings.py:
STATIC_ROOT = '/home/tlichtenberg/workspace/ptest/django/src/automation/regression/static/'
STATIC_URL = '/static/'
ROOT_URLCONF = 'urls'
INSTALLED_APPS = (
'my_project',
'my_other_project',
)
urls.py
(r'^my_project/$', 'my_project.views.my_project_main'),
defines that any url beginning with 'my_project' (e.g. http://localhost/my_project) will direct the code to the my_project_main method in the project's views.py file.
---------------------------------------------------
Pyjamas on Django for Web Automation
I've been working on developing a web application for use in automating some product testing. The commands being implemented are communicated via HTTP, so there isn't strictly a need for a browser interface just to test the api's, but certain functionality requires doing stuff on the device, then going to a different website and verifying some side effects (e.g. purchases recorded, emails sent, etc ...) Using a web application for these cases lets me do some automation using Selenium.
When I first thought about the design, I assumed I would do a Java Servlet or Java Server Pages, but since I've been mostly working in Python recently, I thought I'd see if there was anything I could do that way. Sure enough, I came across a project called Pyjamas, a Python port of Google Web Toolkit. I was interested right away, because not only was it Python, but it could run in Django, and I was already running a Django server on Apache2 on Ubuntu. This web app would theoretically plug right in. And it did, after some minor bumps in the road.
Pyjamas is a sort of miracle wonder drug, where you create your UI in Python, and then "build" it into JavaScript/HTML/Ajax using its 'pyjsbuild' tool. So you can make real-world web apps without having to look under the hood, so to speak. It provides a simple GUI kit, similar to Java AWT 1.0 - primitive but useful. If I was a better UI designer, the above would look a lot nicer, but since I'm primarily a backend coder, this will just have to do :}
Codewise, you import a lot of Pyjamas modules:
from pyjamas.ui.TextBox import TextBox
and then go about building your UI, creating panels and sticking widgets into them. I won't go into great detail here. There are a lot of excellent examples in the Pyjamas download and elsewhere, but basically, something like this to get going ...
class RemoteController:
def onModuleLoad(self):
self.remote = DataService()
dockPanel = DockPanel()
...
# create all your widgets in here, add them to you main panel,
# and then add the main panel to the RootPanel object
self.myTextBox = HTML()
self. myTextBox.setID('my_text_box')
dockPanel.add( myTextBox )
RootPanel().add(dockPanel)
The DataService class is interesting - this is what lets you connect into functions running on Django, and mine looks like this:
from pyjamas.JSONService import JSONProxy
class DataService(JSONProxy):
def __init__(self):
JSONProxy.__init__(self, "/services/", ["method_one", "method_two"])
In my Django views.py, I just have to define those methods:
from django.pimentech.network import * # use the pimentech module described here to define the JSON service and decorator
service = JSONRPCService()
@jsonremote(service)
def method_one(request):
print >> sys.stderr, 'method_one called"
request.session['ip'] = text # set the ip as specific to this session
ecp.set_url(text)
return "put this into my text box, please"
now in your Django urls.py you define the /services/ path used by the DataService class above:
(r'^services/$', 'YOUR_PROJECT.views.service'),
when your method returns from Django, you handle it in your Pyjamas class like this:
def onRemoteResponse(self, response, request_info):
try:
if request_info.method == 'method_one':
self.myMyTextBox.setText(response)
Other GUI stuff should be familiar if you've done any similar programming - with clickListeners, mouseListeners, keyListeners and so on.
finally, have your main and have it call onModuleLoad:
if __name__ == "__main__":
app = RemoteController()
app.onModuleLoad()
I have myTextBox an id just like Ajax would. I did this for Selenium. My Selenium test could now find the widget By.Id or by xpath using the id:
text = selenium.get_text("//*[@id='my_text_box']")
For Selenium you usually want to be able to get text out of text fields but the PyjamasTextBox and TextArea widgets don't actually put the text into the HTML they render. Instead, I used an HTML() widget. It's like a Label and DOES embed the text in the web page so you can get to it from Selenium.
Some other stuff:
CSS - It's nice to set the background colors of your various widgets, and you can do that using inline CSS in the Python code like this:
from pyjamas import DOM
DOM.setStyleAttribute(myWidget.getElement(), "background-color", "#8fbc8f")
If you have multiple concurrent users, you want your web app to be session based. This is done, in Django, by adding a few things to your settings.py. I also had some issues with CSRF so I needed to disable that (you may not want to, depending on your security concerns. I had none in my test environment)
MIDDLEWARE_CLASSES = (
'regression.middleware.DisableCSRF',
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.middleware.csrf.CsrfResponseMiddleware',
'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
)
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': 'c:/django_cache',
}
}
CACHE_MIDDLEWARE_ALIAS = "default"
CACHE_MIDDLEWARE_SECONDS = 600
CACHE_MIDDLEWARE_KEY_PREFIX = ""
Wednesday, December 28, 2011
Selenium Grid 2 - Up and Running
WebDriver and Grid 2
WebDriver is the latest greatest API for testing browsers, and is incorporated into Selenium 2 in a variety of languages, including Python. I'm building it into my ptest framework as well, in the WebDriverLib class in the weblib.py module.
The plan is to move all the legacy Selenium 1 tests to Grid 2
Since all of my tests are in Selenium 1 and WebDriver is a completely different API (woo hoo) I have a translation layer to map the existing tests to the new API.
cd $TEST_HOME/src
python
then, in the python shell ...
from weblib import WebDriverLib settings = {"browser": "*iehta"}
lib = WebDriverLib(settings)
email = settings.get('email', 'auto_test@your.co.com')
password = settings.get('password', 'autotest1')
dev_url = settings.get("dev_url", "https://your.co.com")
lib.open(dev_url) # translation layer for 'get'
lib.wait_and_type("//input[@id='Email']", email) # translation layer for find element and send_keys
lib.wait_and_type("//input[@id='Password']", password)
lib.wait_and_click("//a[@class='ui-button submit']") # translation layer for find element and click it
lib.wait_for_page_to_load(30000)
Grid 2
BASICS:
to launch the hub locally:
java -jar selenium-server-standalone-2.15.0.jar -role hub
to launch a node (formerly 'remote control') locally:
java -jar selenium-server-standalone-2.15.0.jar -role node -port 5555 -hubHost localhost -hubPort 4444 -hub http://localhost:4444/grid/register
running on my qa-jenkins machine at port 4445:
hub:
nohup java -jar selenium-server-standalone-2.15.0.jar -role hub -port 4445 node: java -jar selenium-server-standalone-2.15.0.jar -role node -port 5555 -hubHost qa-jenkins -hubPort 4445 -hub http://qa-jenkins:4445/grid/register node
multiple firefox nodes on the remote control machine (note the different versions and executable paths specified)
java -jar selenium-server-standalone-2.15.0.jar -role node -port 2333 -hubHost qa-jenkins -hubPort 4445 -hub http:// qa-jenkins :4445/grid/register -browser browserName=firefox,version=3.6,firefox_binary=c:\progra~2\mozill~2\firefox.exe,maxInstances=1,platform=WINDOWS
and
java -jar selenium-server-standalone-2.15.0.jar -role node -port 2888 -hubHost qa-jenkins -hubPort 4445 -hub http:// qa-jenkins :4445/grid/register -browser browserName=firefox,version=8,firefox_binary=c:\progra~2\mozill~1\firefox.exe,maxInstances=1,platform=WINDOWS
for ptest config:
-b *firefox3 will invoke the 3.6 node, and *firefox8 will invoke the 8 node ptest
example:
python ptest.py -c webtest_config.json -t test_webdriver -w true -b *firefox8
Internet Explorer node:
java -jar selenium-server-standalone-2.15.0.jar -role node -hub http://qa-jenkins:4445/grid/register -browser browserName=iexplore,version=9,platform=WINDOWS -port 8000
Google Chrome node: (note the path is to the chromedriver executable, not to an installed Chrome browser. Also, the path to the chromedriver executable must be set in the PATH environment variable)
java -jar selenium-server-standalone-2.15.0.jar -role node -port 6000 -hubHost qa-jenkins -hubPort 4445 -hub http://qa-jenkins:4445/grid/register -browser browserName=chrome,chrome_binary=C:\chromedriver\chromedriver,maxInstances=1,platform=WINDOWS
see http://code.google.com/p/selenium/wiki/ChromeDriver for more information
For SSL Issues with Google Chrome, initialize the WebDriver client with a chrome.switches array as below (python bindings example) ->
from selenium import webdriver
dc = webdriver.DesiredCapabilities.CHROME
dc["chrome.switches"] = ["--ignore-certificate-errors"]
driver = webdriver.Remote(str(remote_url), self.dc)
The plan is to move all the legacy Selenium 1 tests to Grid 2
Since all of my tests are in Selenium 1 and WebDriver is a completely different API (woo hoo) I have a translation layer to map the existing tests to the new API.
cd $TEST_HOME/src
python
then, in the python shell ...
from weblib import WebDriverLib settings = {"browser": "*iehta"}
lib = WebDriverLib(settings)
email = settings.get('email', 'auto_test@your.co.com')
password = settings.get('password', 'autotest1')
dev_url = settings.get("dev_url", "https://your.co.com")
lib.open(dev_url) # translation layer for 'get'
lib.wait_and_type("//input[@id='Email']", email) # translation layer for find element and send_keys
lib.wait_and_type("//input[@id='Password']", password)
lib.wait_and_click("//a[@class='ui-button submit']") # translation layer for find element and click it
lib.wait_for_page_to_load(30000)
Grid 2
BASICS:
to launch the hub locally:
java -jar selenium-server-standalone-2.15.0.jar -role hub
to launch a node (formerly 'remote control') locally:
java -jar selenium-server-standalone-2.15.0.jar -role node -port 5555 -hubHost localhost -hubPort 4444 -hub http://localhost:4444/grid/register
running on my qa-jenkins machine at port 4445:
hub:
nohup java -jar selenium-server-standalone-2.15.0.jar -role hub -port 4445 node: java -jar selenium-server-standalone-2.15.0.jar -role node -port 5555 -hubHost qa-jenkins -hubPort 4445 -hub http://qa-jenkins:4445/grid/register node
multiple firefox nodes on the remote control machine (note the different versions and executable paths specified)
java -jar selenium-server-standalone-2.15.0.jar -role node -port 2333 -hubHost qa-jenkins -hubPort 4445 -hub http:// qa-jenkins :4445/grid/register -browser browserName=firefox,version=3.6,firefox_binary=c:\progra~2\mozill~2\firefox.exe,maxInstances=1,platform=WINDOWS
and
java -jar selenium-server-standalone-2.15.0.jar -role node -port 2888 -hubHost qa-jenkins -hubPort 4445 -hub http:// qa-jenkins :4445/grid/register -browser browserName=firefox,version=8,firefox_binary=c:\progra~2\mozill~1\firefox.exe,maxInstances=1,platform=WINDOWS
for ptest config:
-b *firefox3 will invoke the 3.6 node, and *firefox8 will invoke the 8 node ptest
example:
python ptest.py -c webtest_config.json -t test_webdriver -w true -b *firefox8
Internet Explorer node:
java -jar selenium-server-standalone-2.15.0.jar -role node -hub http://qa-jenkins:4445/grid/register -browser browserName=iexplore,version=9,platform=WINDOWS -port 8000
Google Chrome node: (note the path is to the chromedriver executable, not to an installed Chrome browser. Also, the path to the chromedriver executable must be set in the PATH environment variable)
java -jar selenium-server-standalone-2.15.0.jar -role node -port 6000 -hubHost qa-jenkins -hubPort 4445 -hub http://qa-jenkins:4445/grid/register -browser browserName=chrome,chrome_binary=C:\chromedriver\chromedriver,maxInstances=1,platform=WINDOWS
see http://code.google.com/p/selenium/wiki/ChromeDriver for more information
For SSL Issues with Google Chrome, initialize the WebDriver client with a chrome.switches array as below (python bindings example) ->
from selenium import webdriver
dc = webdriver.DesiredCapabilities.CHROME
dc["chrome.switches"] = ["--ignore-certificate-errors"]
driver = webdriver.Remote(str(remote_url), self.dc)
Thursday, December 8, 2011
Selenium and SSL
If you have to test https sites using Selenium on a variety of browsers, you are asking for a world of pain! Practically every version of every browser on every platform requires either a different jar file or different arguments for both the selenium server and the remote client.
First of all, the all-important version disclaimer!
selenium-server-standalone-2.15.0.jar
selenium-grid-1.0.8
Firefox 3.6 on Windows 7 and Ubuntu 11.04
Firefox 8 on Windows7
Chrome 15.0.874.121 on Windows 7
Internet Explorer 9 on Windows 7
For Local Selenium Server
-------------------------------------
java -jar selenium-server-standalone-2.15.0.jar
For Selenium RC Clients (your test app)
-----------------------------------------------------
call selenium.start with the commandLineFlag -disable-web-security
Python example:
from selenium import selenium
sel = selenium(test_host, int(test_port), self.browser, url)
sel.start('commandLineFlags=-disable-web-security')
For Selenium Servers and Selenium Grid Remote Clients
---------------------------------------------------------------------------
For Firefox, I needed to use a profile and pass it into the startup script.
1. start firefox from the command-line with "firefox -profileManager" and create a new profile
2. Manually go into the site using Firefox, accept the various certificate challenges, and then quit the browser
2. Start the selenium client using your new firefox profile. For example, as a Selenium Grid remote client:
ant -Dport=5777 -Denvironment="*chrome" -Dhost=MY_IP
-DhubURL=http://SELENIUM_GRID_SERVER_IP:4444 -DseleniumArgs="-firefoxProfileTemplate C:\Users\ME\FIREFOX_PROFILE_DIRECTORY_COPY\firefox_profile" launch-remote-control
you may need to add some lines to the prefs.js file in the firefox profile. for example, if you see 403's and/or 404's being returned because of the browser looking for /favico.ico, you should add these two lines
user_pref("browser.chrome.favicons", false);
user_pref("browser.chrome.site_icons", false);
NOTE that this USUALLY WORKS but, in my case, it simply STOPPED WORKING on Windows 7. The -firefoxProfileTemplate argument passed in to ant as seleniumArgs is not passed along by the remote control to the firefox startup command. Here the specified profile is simply ignored:
[java] 09:16:47.113 INFO - Preparing Firefox profile...
[java] 09:16:49.072 INFO - Launching Firefox...
[java] 09:16:49.074 DEBUG - Execute:Java13CommandLauncher: Executing 'C:\Program Files
(x86)\Mozilla Firefox\firefox.exe' with arguments:
[java] '-profile'
[java] 'C:\Users\ME\AppData\Local\Temp\customProfileDirc829f057ff9e497ea065add1ca892726'
The solution was to replace the selenium-server-standalone jar file in selenium-grid-XX/vendor with a newer one from Selenium org (2.15) - However, this broke IE which needed selenium server standaloine 2.12.0 !!!)
For Internet Explorer
Disable popup blockers - Select Tools/Popup Blocker/Turn off pop-up blocker
Disable IE protected mode - Untick Tools/Internet Options/Security/Enable protected mode - do this for all four zones
First of all, the all-important version disclaimer!
selenium-server-standalone-2.15.0.jar
selenium-grid-1.0.8
Firefox 3.6 on Windows 7 and Ubuntu 11.04
Firefox 8 on Windows7
Chrome 15.0.874.121 on Windows 7
Internet Explorer 9 on Windows 7
For Local Selenium Server
-------------------------------------
java -jar selenium-server-standalone-2.15.0.jar
For Selenium RC Clients (your test app)
-----------------------------------------------------
call selenium.start with the commandLineFlag -disable-web-security
Python example:
from selenium import selenium
sel = selenium(test_host, int(test_port), self.browser, url)
sel.start('commandLineFlags=-disable-web-security')
For Selenium Servers and Selenium Grid Remote Clients
---------------------------------------------------------------------------
For Firefox, I needed to use a profile and pass it into the startup script.
1. start firefox from the command-line with "firefox -profileManager" and create a new profile
2. Manually go into the site using Firefox, accept the various certificate challenges, and then quit the browser
2. Start the selenium client using your new firefox profile. For example, as a Selenium Grid remote client:
ant -Dport=5777 -Denvironment="*chrome" -Dhost=MY_IP
-DhubURL=http://SELENIUM_GRID_SERVER_IP:4444 -DseleniumArgs="-firefoxProfileTemplate C:\Users\ME\FIREFOX_PROFILE_DIRECTORY_COPY\firefox_profile" launch-remote-control
you may need to add some lines to the prefs.js file in the firefox profile. for example, if you see 403's and/or 404's being returned because of the browser looking for /favico.ico, you should add these two lines
user_pref("browser.chrome.favicons", false);
user_pref("browser.chrome.site_icons", false);
NOTE that this USUALLY WORKS but, in my case, it simply STOPPED WORKING on Windows 7. The -firefoxProfileTemplate argument passed in to ant as seleniumArgs is not passed along by the remote control to the firefox startup command. Here the specified profile is simply ignored:
[java] 09:16:47.113 INFO - Preparing Firefox profile...
[java] 09:16:49.072 INFO - Launching Firefox...
[java] 09:16:49.074 DEBUG - Execute:Java13CommandLauncher: Executing 'C:\Program Files
(x86)\Mozilla Firefox\firefox.exe' with arguments:
[java] '-profile'
[java] 'C:\Users\ME\AppData\Local\Temp\customProfileDirc829f057ff9e497ea065add1ca892726'
The solution was to replace the selenium-server-standalone jar file in selenium-grid-XX/vendor with a newer one from Selenium org (2.15) - However, this broke IE which needed selenium server standaloine 2.12.0 !!!)
For Internet Explorer
Disable popup blockers - Select Tools/Popup Blocker/Turn off pop-up blocker
Disable IE protected mode - Untick Tools/Internet Options/Security/Enable protected mode - do this for all four zones
For Google Chrome
Open Chrome Options
go to 'Under the Hood'
click on the 'Manage Certificates' button at HTTPS/SSL
IMPORT your https server's PFX certificate and save it under Trusted Root Certification Authorities (input the password when prompted)
Wednesday, November 30, 2011
Selenium xpath arrays
Accessing xpath array elements in Selenium can be tricky! Sometimes you may have a number of elements on a page that can only be referenced with the same locator (for example, you can't rely on unique id's, only on some field they all have in common)
example:
a website has 4 buttons with 'addToCart' in onClick being the only reliable bit to check:
("//button[contains(@onclick,'addToCart')])
you might think you could click on them by subscripting like this:
sel.click("//button[contains(@onclick,'addToCart')][1]")
sel.click("//button[contains(@onclick,'addToCart')][2]")
but no. the first one succeeds and fakes you into thinking you can access the array this way. but you can't. if you take out the [1] you get the same result.
So what you have to do is isolate the array first, using the 'xpath=' notation, and then subscript it:
sel.click("xpath=(//button[contains(@onclick,'addToCart')])[3]")
notice the use of 'xpath=' and the extra parens around the locator, followed by the subscript!
Subscribe to:
Posts (Atom)
