Selenium - Best practices
来源:互联网 发布:买断软件源码 编辑:程序博客网 时间:2024/05/17 20:21
Functional testing is difficult to get right for many reasons. As if application state, complexity, and dependencies don't make testing difficult enough, dealing with browsers – and especially cross-browser incompatibilities – makes writing good tests a challenge.
Selenium provides tools to make functional user interaction easier, but doesn't help you write well-architected test suites. In this chapter we offer advice, or best practices if you will, on how to approach functional web page automation.
This chapter records software design patterns popular amongst many of the users of Selenium that have proven successful over the years.
PAGE OBJECT MODELS
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 AUT. 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 advantage: There is clean separation between test code and page specific code such as locators (or their use if you’re using a UI map) and layout.
Page object methods should return a value
- If you submit a page and are redirected, it should return the new page object
- If you click submit on login and you want to check to see if a user is logged in it should return True or False in a method
DOMAIN SPECIFIC LANGUAGE
A domain specific language (DSL) is a system which provides the user an expressive means of solving a problem. It allows a user to interact with the system on their terms – not just programmer-speak.
Your users, in general, don't care how your site looks. They don't care about the decoration or the animations or the graphics. They want to use your system to push their new employees through the process with minimal difficulty. They want to book travel to Alaska. They want to configure and buy unicorns at a discount. Your job as the tester is to come as close as you can to “capturing” this mind-set. With that in mind, we set about “modeling” the application you're working on, such that the test scripts (the user's only pre-release proxy) “speak” for and represent the user.
With Selenium, DSL is usually represented by methods, written to make the API simple and readable – they enable a repport between the developers and the stakeholders (users, product owners, business intelligence specialists, etc.).
Benefits
- Readable: Business stake holders can understand it.
- Writable: Easy to write, avoids unnecessary duplication.
- Extensible: Functionality can (reasonably) be added without breaking contracts and existing functionality.
- Maintainable: By leaving the implementation details out of test cases, you are well-insulated against changes to the AUT.
Java
Here is an example of a reasonable DSL method in Java. For brevity's sake, it assumes the `driver` object is pre-defined and available to the method.
/** * Takes a username and password, fills out the fields, and clicks "login" * @returns An instance of the AccountPage */public AccountPage loginAsUser(String username, String password) { driver.findElement(By.id("loginField")).clear(); driver.findElement(By.id("loginField")).sendKeys(testData); //Fill out the password field. The locator we're using is "By.id", and we should have it // defined elsewhere in the class driver.findElement(By.id("password")).clear(); driver.findElement(By.id("password")).sendKeys(); //Click the login button, which happens to have the id "submit" driver.findElement(By.id("submit")).click(); //Create and return a new instance of the AccountPage (via the built-in Selenium PageFactory) return PageFactory.newInstance(AccountPage.class);}
This method completely abstracts the concepts of input fields, buttons, clicking, and even pages from your test code. Using this approach, all your tester has to do is call this method. This gives you a maintenance advantage: if the login fields ever changed, you would only ever have to change this method--not your tests.
public void loginTest() { loginAsUser("cbrown", "cl0wn3"); //now that we're logged in, do some other stuff--since we used a DSL to support our testers, it's // as easy as choosing from available methods do.something(); do.somethingElse(); Assert.assertTrue("Something should have been done!", something.wasDone(); //Note that we still haven't referred to a button or web control anywhere in this script...}
It bears repeating: One of your primary goals should be writing an API that allows your tests to address the problem at hand, and NOT the problem of the UI. The UI is a secondary concern for your users – they don't care about the UI, they just want to get their job done. Your test scripts should read like a laundry list of things the user wants to DO, and the things they want to KNOW. The tests should not concern themselves with HOW the UI requires you to go about it.
GENERATING APPLICATION STATE
Selenium should not be used to prepare a test case. All repetitive actions, and prepration for a test case should be done through other methods. An example, most Web UIs have authentication (e.g., a login form). Eliminating logging in via web browser before every test will improve both the speed and stability of the test. A method should be created to gain access to the AUT (e.g. using an API to login and set cookie inbrowser object). Also, creating methods to pre-load data for testing should not be done using Selenium. As mentioned previously, existing APIs should be leveraged to create data for the AUT.
MOCK EXTERNAL SERVICES
Eliminating the dependencies on external services will greatly improve the speed and stability of your tests.
IMPROVED REPORTING
Selenium is not designed to report on the status of test cases run. Taking advantage of the built-in reporting capabilities of unit test frameworks is a good start. Most unit test frameworks have reports that can generate xUnit or HTML formatted reports. xUnit reports are popular for importing results to a Continuous Integration (CI) server like Jenkins, Travis, Bamboo, etc. Here are some links for more information regarding report outputs for several languages.
AVOID SHARING STATE
TEST INDEPENDENCY
Write each test as its own unit. Write the tests in a way that won't be reliant on other tests to complete.
CONSIDER USING A FLUENT API
Selenium already implements something like this in their *FluentWait* class which is meant as an alternative to the standard *Wait* class. You could enable the Fluent API design pattern in your page object and then query the Google search page with a code snippet like this one:
driver.get( "http://www.google.com/webhp?hl=en&tab=ww" );GoogleSearchPage gsp = new GoogleSearchPage();gsp.withFluent().setSearchString().clickSearchButton();The Google page object class with this fluent behavior might look like this:
public class GoogleSearchPage extends LoadableComponent<GoogleSearchPage> { private GSPFluentInterface gspfi; public class GSPFluentInterface { private GoogleSearchPage gsp; public GSPFluentInterface(GoogleSearchPage googleSearchPage) { gsp = googleSearchPage; } public GSPFluentInterface clickSearchButton() { gsp.searchButton.click(); return this; } public GSPFluentInterface setSearchString( String sstr ) { clearAndType( gsp.searchField, sstr ); return this; } } @FindBy(id = "gbqfq") private WebElement searchField; @FindBy(id = "gbqfb") private WebElement searchButton; public GoogleSearchPage() { gspfi = new GSPFluentInterface( this ); this.get(); // if load() fails, calls isLoaded() until page is finished loading PageFactory.initElements(driver, this); // initialize WebElements on page } public GSPFluentInterface withFluent() { return gspfi; } public void clickSearchButton() { searchButton.click(); } public void setSearchString( String sstr ) { clearAndType( searchField, sstr ); } @Override protected void isLoaded() throws Error { Assert.assertTrue("Google search page is not yet loaded.", isSearchFieldVisible() ); } @Override protected void load() { if ( isSFieldPresent ) { Wait<WebDriver> wait = new WebDriverWait( driver, 3 ); wait.until( visibilityOfElementLocated( By.id("gbqfq") ) ).click(); } }}
FRESH BROWSER PER TEST
Start each test from a clean known state. Ideally spin up a new virtual machine for each test. If spinning up a new virtual machine is not practical, at least start a new WebDriver for each test. For Firefox, start a WebDriver with your known profile.
FirefoxProfile profile = new FirefoxProfile(new File("pathToFirefoxProfile"));WebDriver driver = new FirefoxDriver(profile);
- Selenium - Selenium best practices
- Selenium - Best practices
- Best Practices -
- Web Services Best Practices
- JUnit best practices
- Javascript Best Practices
- CAB Best Practices
- 最佳实践(Best Practices)
- Java Database Best Practices
- 一些C# Best Practices
- LIVE Networking: Best Practices
- Scalability Best Practices
- Best Practices for WOW64
- Siebel Scripting Best Practices
- Javascript Best Practices
- Log4j Best Practices
- Best practices when developing
- Java Best Practices
- eclipse的web project 的src目录问题
- 配图快速入门及地图性能优化(1)
- pyinstaller打包exe后报fatal error return -1
- 冲突解决策略是定义一个序列F(i)=ri,其中r0=0且r1,r2……rN是前N个整数的随机排列(每个整数恰好出现一次)
- windows下消息机制
- Selenium - Best practices
- andriod studio 快捷键
- android studio单元测试 无法进入网络请求的回调
- c++ 赋值操作符
- 通俗易懂之epoll--转自”知乎“
- Unity3D 游戏引擎之C#使用Socket与HTTP连接服务器传输数据包
- iOS开发工具Xcode史上最全快捷键
- 初步了解java中的注解
- ubuntu输入正确用户密码重新跳到无法登录