(旧文新发)Selenium Best Practices

15 篇文章 0 订阅
6 篇文章 0 订阅
本文分享了使用Selenium进行自动化测试的一些最佳实践,包括采用PageObjects模式保持测试代码和页面代码分离,使用Fluent API提高代码可读性,选择合适的元素定位策略以增强测试的健壮性和可移植性,避免使用Thread.sleep,利用相对URL,以及针对不同浏览器和用户语言的适配。此外,还提到了处理特定UI元素如文件上传、选择器和日期选择器的策略,并强调在必要时不要害怕对Selenium进行适当的调整。
摘要由CSDN通过智能技术生成

Selenium Best Practices

It’s a summary (and few extras) of  test_design_considerations
Use PageObjects pattern
Be fluent with
  - return this, varargs, generics,
  - reuse your model and jodatime
Be robust and portable
  - Prefered selector order : id > name > css > xpath 
    css is faster than xpath. but xpath could have lots of usage, such as finding parents, children, sibling, ancestor, etc. Please see  W3C Xpath教程

  - Avoid Thread.sleep prefer Wait or FluentWait
  - Use relative URLs
  - Don’t rely on specific Driver implementation
  - Create your dataset
Know your new tool
  - Keep up to date (versions and usage pattern)
  - Troubleshooting
      - jre 1.6
      - IE (zoom, Protected mode setting )
      - Firefox/firebug startpage
  - How to deal with UI components like... fileupload, datepicker, ajaxtables,...
  - Detect when selenium isn't the good tool for the job
  - Don't be afraid to hack around selenium

Use PageObjects pattern

Page Object is a Design Pattern which has become popular in test automation for enhancing test maintenance and reducing code duplication. A page object is an object-oriented class that serves as an interface to a page of your Application Under Test. The tests then use the methods of this page object class whenever they need to interact with that page of the UI. The benefit is that if the UI changes for the page, the tests themselves don’t need to change, only the code within the page object needs to change. Subsequently all changes to support that new UI are located in one place.
The Page Object Design Pattern provides the following advantages :
  • There is a clean separation between test code and page specific code such as locators (or their use if you’re using a UI map) and layout.
  • There is  a single repository for the services or operations offered by the page rather than having these services scattered through out the tests.
For example you have a LoginPage that you can reuse in all your integration test.
if the logon has now a new option or the layout changed a bit… adapt the LoginPage… that’s it !
More on page object :  page-objects-in-selenium-2- ,  PageObjectFactory ,  simple example  based on google search page

Be fluent !

With return this

It makes often your tests more readable if your “void” methods return “this”.
1
2
3
4
5
6
publicclassCampaignCreatePageextendsAbstractPage {
    ...
 publicCampaignCreatePage withCampaignName(String campaignName) {
     name.sendKeys(campaignName);
     returnthis;
 }
 
So you can chain the calls like :
1
2
3
4
5
6
7
8
9
10
11
@Test
publicvoidshouldntSaveWithoutAnyErrors()throwsException {
    CampaignCreatePage createPage = openCampaignCreatePage();
    createPage.hasNumberOfErrors(4);
    createPage.withCampaignName("SELENIUM").save();
    createPage.hasNumberOfErrors(3);
    createPage.withAgent("firefox").save();
    createPage.hasNumberOfErrors(2);
    createPage.withAssignee("tom").save();
    createPage.hasNumberOfErrors(1);
}
 

With varargs

You can also use varargs to make the api cleaner
1
2
3
4
publicCampaignCreatePage withCampaignTargetsIds(long... targets) {
    targetsIds.sendKeys(StringUtils.join(target,","));
    returnthis;
    }
 
In the test this give us something like
1
2
3
4
5
6
@Test
publicvoidshouldntSaveTargetUnknownIds()throwsException {
    CampaignCreatePage createPage = openCampaignCreatePage();
    createPage.withCampaignName("SELENIUM").withAgent("firefox").withAssignee("tom").withProduct(0).withCampaignTargetsIds(1,2,3,4);
    createPage.save().hasNumberOfErrors(1);
}
 

With generics

If you have a component that don’t have access to your pageObject. For example, a generic menu where plugins can contribute links to their own pages.
you can use this trick :
1
2
3
4
public  T clickOnMenu(String path, Class pageClassToProxy) {
    clickOnMenu(path);
    returnPageFactory.initElements(getDriver(), pageClassToProxy);
}
 
 
1
2
3
4
5
6
7
8
   @Test
   publicvoidtestBrodocWithoutMemberOrProspectIdentified() {
       processLogon();
       MemberIdentificationPage page = openMemberIdentificationPage();
       page = page.getMenuElement().clickOnMenu(LuceneSearchPage.URL, LuceneSearchPage.class);
       page.hasInfoMessage();
       page.withNameFilter("mestachs").search().getResults().hasResults()
}
 

By reusing your model and joda

For example the CampaignSearchPage can reuse the SearchCampaignParameter
1
2
3
4
5
6
7
8
9
10
11
12
...
     publicCampaignSearchPage searchFor(SearchCampaignParameter parameter) {
        clearForm();
        selectRadio("campaignTargetType", parameter.getPersonType());
        sendString(campaignName, parameter.getCampaignName());
        sendDate(creationDateFrom, parameter.getCreationDateFrom());
        sendDate(creationDateTo, parameter.getCreationDateTo());
        sendDate(campaignDateFrom, parameter.getCampaignDateFrom());
        sendDate(campaignDateTo, parameter.getCampaignDateTo());
        findCampaigns.click();
    }
...
 

Be robust and portable

Preferred selector order : id > name > css > xpath

To locate an element we can use
  • the element’s ID
  • the element’s name attribute
  • an XPath statement
  • by a links text
  • document object model (DOM)
In practice, you will discover
  • id and name are often the easiest and sure way.
  • xpath are often brittle. for example you have 3 tables displayed but sometimes there are no data and the table isn’t rendered, your xpath locating the second table will not always return the intented one.
  • css are the way to go in conjunctionof id and name !
  • locating links in an internationalizedapplication is sometimes hard… try to use partial href.

Avoid Thread.sleep prefer Wait or FluentWait

Instead of sleep
1
2
3
4
publicvoidclickOnContactType() {
    getDriver().findElement(By.id("contacttypelink")).click();
    sleep(500);
}
 
Prefer this fluentWait
1
2
3
4
publicvoidclickOnContactType() {
    getDriver().findElement(By.id("contacttypelink")).click();
    SeleniumUtil.fluentWait(By.name("handle"), getDriver());
}
 
It’s more robust, deterministic, in case of element not found… the exception will be clearer.
Another alternative is
1
2
3
4
(newWebDriverWait(driver,30)).until(newExpectedCondition() {
                publicBoolean apply(WebDriver d) {
                    returnd.getTitle().toLowerCase().startsWith("java developer");
                }
 

Use relative URLs

Avoid hardcoding hosts
1
2
3
getDriver().get(" http://nl.wikipedia.org/wiki/Wiki ");
WikiMainPage page = PageFactory.initElements(getDriver(), WikiMainPage.class);
page.search("sdfsdf");
 
Prefer configuration and pass relative urls like
1
openPartialUrl("/");
 
That use an abstract method
1
2
3
4
5
6
7
8
9
10
11
12
13
protectedvoidopenPartialUrl(String partialurl) {
    getDriver().get(getUrlPrefix() + partialurl +"?siteLanguage="+this.settings.getLanguage());
}
privatestaticString getPrefix() {
    returnStringUtils.replace(System.getProperty("selenium.website.url", HTTP_PREFIX),"@localhost@", getCanonicalHostName());
}
privatestaticString getCanonicalHostName() {
    try{
        returnjava.net.InetAddress.getLocalHost().getCanonicalHostName();
    }catch(Exception e) {
        return"127.0.0.1";
    }
}
 
It is easy then to launch these tests against another server (integration,acceptance,… or jetty in integration tests)

Don’t rely on specific Driver implementation

Don’t assume that driver will be an instance of FireFoxDriver or InternetExplorerDriver. For example when running the integration tests on your continuous build (linux) you will receive a RemoteDriver.
It’s quite easy to create a small framework around selenium using :
  • LabelledParameterized : to be able to run with different browsers IE,FF,… or with different user language fr/nl/en
  • ScreenShotWatchMan : that takes screenshot on exception and logs the html sources. (see ./target/screenshot/*.png)

Create your dataset

Avoid something like assuming that the data is always there.
1
2
3
4
CampaignConsultPage consult = openCampaignConsultPage(2132103215L);
CampaignEditPage edit = consult.goToEditPage();
consult = edit.save();
consult.hasInfoMessage();
 
Create a new campaign and check consult
1
2
3
4
5
6
7
8
9
CampaignCreatePage createPage = openCampaignCreatePage();
createPage.withCampaignName("SELENIUM"+newDateTime()).withAgent("tom").withAssignee("tom").withProduct(0);
createPage.save().hasNumberOfErrors(0);
createPage.hasInfoMessage();
Long campaignId = createPage.getCampaignId();
CampaignConsultPage consult = openCampaignConsultPage(campaignId);
CampaignEditPage edit = consult.goToEditPage();
consult = edit.save();
consult.hasInfoMessage();
 

Know your new tool

Keep up to date

Keep your binaries and knowledgeup to date !
This official
  blog  contains announcements and also some a lot of links to possible application of selenium
StackOverflow  forums are really good to learn from other mistakes. The official Sauce Labs  Blog  is also really interesting for their real life experiences.

Use jre 1.6

If while starting your integration tests you encounter :
java.lang.NoSuchFieldError: HOURS
at org.openqa.selenium.remote.internal.HttpClientFactory.(HttpClientFactory.java:44)
or
java.lang.NoSuchFieldError: java/util/concurrent/TimeUnit.HOURS
Run your test with a jre 1.6

How to close Firebug startpage

FirefoxProfile profile = new FirefoxProfile();
profile.setPreference("extensions.firebug.currentVersion", "1.8.2");
driver = new FirefoxDriver(profile);
note that you may need to adapt the version.

IE not working

check
  • The browser zoom level must be set to 100% so that the native mouse events can be set to the correct coordinates.
  • On IE 7 or higher on Windows Vista or Windows 7, you must set the Protected Mode settings for each zone to be the same value. The value can be on or off, as long as it is the same for every zone. To set the Protected Mode settings, choose “Internet Options…” from the Tools menu, and click on the Security tab. For each zone, there will be a check box at the bottom of the tab labeled “Enable Protected Mode”.
Possible workaround for Protected Mode
1
2
3
4
DesiredCapabilities ieCapabilities = DesiredCapabilities.internetExplorer();
ieCapabilities.setCapability(InternetExplorerDriver.INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS,true);
ieCapabilities.setCapability("ensureCleanSession",true);
driver =newInternetExplorerDriver(ieCapabilities);</pre>
 

How to deal with ui elements like

File upload

Single and multi select

One option
1
2
3
4
5
6
7
publicclassAbstractPage {
...
   protectedvoidselectRadio(String name, String code) {
    WebElement select =this.webDriver.findElement(By.xpath("//input[@value='"+ code+"'and @name='"+ name +"' ]"));
    select.click();
   }
...
 
Maybe a better option is using org.openqa.selenium.support.ui.Select
1
2
3
4
5
6
7
8
Select singleSelection =newSelect(getDriver().findElement(By.id("single-selection")));
singleSelection.selectByVisibleText("July");
Select mulitpleSelection =newSelect(getDriver().findElement(By.xpath("//select[@multiple='multiple' and @size=12]")));
mulitpleSelection.deselectAll();
// Select February, August and November using different functions.
mulitpleSelection.selectByIndex(1);// February
mulitpleSelection.selectByValue("Aug");// Select ...
mulitpleSelection.selectByVisibleText("November");// Select November
 

DatePicker

1
2
3
4
publicclassCampaignSearchPageextendsAbstractPage {
  ...
    sendDate(creationDateFrom, parameter.getCreationDateFrom());
  ...</pre>
 
sendDate is defined in AbstractPage
1
2
3
4
5
protectedvoidsendDate(WebElement e, DateMidnight dm) {
 if(dm !=null) {
     sendString(e, dm.toString("dd/MM/YYYY"));
 }
}
 

AjaxTableElement

If you use table elemenst with paging, sorting,… you can perhaps create a component AjaxTableElement
add in pageObject :
1
2
3
4
5
6
publicclassCampaignSearchPageextendsAbstractPage {
          ....
         publicAjaxTableElement getTableResult() {
        returnnewAjaxTableElement(getDriver(),"results");
    }
}
 
Then you can use the AjaxTableElement
1
2
3
page.searchFor(param);
page.getTableResult().hasResult();
page.getTableResult().clickOnResult(1);
 

Detect when selenium isn’t the good tool for the job

Selenium can’t deal with OS level dialogs so first avoid them as much as possible…
For eg don’t try to download a pdf through
  selenium  and open acrobat reader.
You may be tempted to use tool like :
  autoit  or  sikuli  ( his java api ) but you will loose portability and may be they will only offer you a false sense of quality.

Don’t be afraid to hack around selenium

A lot of people are using it but also trying new stuff like :
- collecting performance
  statistics .
- creating a full acceptance
  framework
- implementing a new  webdriver
 


Written on Apr 27 2013

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值