ࡱ> 9;45678 rbjbjVV 4<<j     \jBa#a#a#a#<$<$<$jjjjjjjlpoj <$<$<$<$<$j  a#a#j$$$<$( a# a#j$<$j$$1NDOa#wa:d$(uO&{jj0jO&o$oLOOro 3jH<$<$$<$<$<$<$<$jj$<$<$<$j<$<$<$<$o<$<$<$<$<$<$<$<$<$ : Selenium Tutorial : Testing Strategies  9.Testing Strategies Selenium is able to test the webapp from a user perspective and so you can evaluate all of its aspects : navigation, controls, rendering, etc This broadness is a source of misbehaviors: you may be tempted to test all at once in the same tests. This is a bad idea youll end up with: A bloated and unmaintainable test infrastructure Test suites too slow to be exploitable Too much coverage is a source of brittleness 9.1.What Kinds Of Tests? 9.1.1.Acceptance tests Acceptance testing (also named Exploratory Testing) means that your application conforms to the required level of quality and functionality the client expects In practice, it means that the business processes are implemented and functional according to specifications: its functional testing This is the kind of tests you should do with Selenium If youre Agile, you can base your tests on your user stories (if you use that) This scenario integrates well with continuous integration frameworks and tools 9.1.2.Unit testing Dan Fabulich, who is one of the creators of Selenium RC proposed another way to use Selenium: UI unit-testing The idea is to unit test your controls at compile/deploy time (in an ant or rake task for instance) It increases test coverage compared to acceptance testing alone while limiting the size of the test infrastructure Each component (for instance a calendar control) is isolated in a dumb html file on disk for high-speed tests Ajax calls are redirected to a dummy backend injected in the page by overriding the window.XmlHTTPRequest object Figure8.Unit testing UI components with Selenium RC  INCLUDEPICTURE "testing_strategies_files/selenium-unit-test.png" \* MERGEFORMAT  Developer writes test-cases and targets html files embedding the control to unit-test Selenium RC interprets the test-cases and commands the browser Instead of targetting a webserver, the tests targets files on disk The pages contains a script overwriting the XmlHTTPRequest in order to redirect backend calls to a dummy backend object The dummy backend answers with whatever data is needed to test the control Selenium RC evaluates the control' state  INCLUDEPICTURE "testing_strategies_files/note.png" \* MERGEFORMAT NoteSome testing frameworks also provide server-side testing (eg: rspec view-testing in Rails)9.2.How Should You Test Your Apps 9.2.1.Testing For Missing Elements During your acceptance tests, you should test the presence of the elements before testing their functionality: describe "Google Search" do it "can find Selenium on Google" do page.open "http://www.google.com" page.title.should eql "Google" page.type "q", "Selenium seleniumhq" page.click "btnG" page.value("q").should eql("Selenium seleniumhq") page.text?("seleniumhq.org").should be_true page.title.should eql("Selenium seleniumhq - Google Search") page.element?("link=Cached").should be_true end end Could be improved in: describe "Google Search" do it "can find Selenium on Google" do page.open "http://www.google.com" page.title.should eql "Google" page.element?("q").should be_true # we test the "q" field is present page.type "q", "Selenium seleniumhq" page.element?("btnG").should be_true # same for btnG page.click "btnG" page.value("q").should eql("Selenium seleniumhq") page.text?("seleniumhq.org").should be_true page.title.should eql("Selenium seleniumhq - Google Search") page.element?("link=Cached").should be_true end end This can seems trivial on this example but when your tests fails, youll immediately know what went wrong instead of trying to figure out if the problem is related to the data used for the test (the search text here) or to the element itself. 9.2.2.Navigation A frequent yet trivial source of errors is navigation: Broken links: links that contains a typo Missing pages: pages not available for whatever reason You could leverage the get_all_links method of the API to to that (assuming your links have ids): it "has all links working" do ... links = page.get_all_links() links.each do | id | if not id.empty? linkText = page.js_eval("this.browserbot.findElement('" + id + "').innerHTML")  INCLUDEPICTURE "testing_strategies_files/1.png" \* MERGEFORMAT  page.click("id=#{id}") page.title.include?(linkText).should be_true  INCLUDEPICTURE "testing_strategies_files/2.png" \* MERGEFORMAT  page.go_back  INCLUDEPICTURE "testing_strategies_files/3.png" \* MERGEFORMAT  page.title.should eql "Index" end end end public void testLinks() { ... String[] links = selenium.getAllLinks(); foreach(String id : links) { if(id != null) { String linkText = selenium.getEval("this.browserbot.findElement('" + id + "').innerHTML;"); selenium.click(id); verifyTrue(selenium.getTitle().contains(linkText)); selenium.goBack(); verifyEqual(selenium.getTitle().contains("title of the first page"); } } } HYPERLINK "testing_strategies.html" \l "CO5-1" INCLUDEPICTURE "testing_strategies_files/1.png" \* MERGEFORMAT We get the link text HYPERLINK "testing_strategies.html" \l "CO5-2" INCLUDEPICTURE "testing_strategies_files/2.png" \* MERGEFORMAT We base our test on the assumption that the links text is contained into the title of the page theyre reffering to HYPERLINK "testing_strategies.html" \l "CO5-3" INCLUDEPICTURE "testing_strategies_files/3.png" \* MERGEFORMAT We go back to index.html  INCLUDEPICTURE "testing_strategies_files/note.png" \* MERGEFORMAT NoteThe example code is at code/ruby/working_links.rb9.2.3.Dynamic Elements Sometimes, you do not have control over your elements' ids (when using a web framework generating a list from a database for instance). In these cases: You can use xpath, dom or a structure-based locator (prefer CSS) Use the capabilities of the Selenium API with Selenium RC to handle loops, conditions etc Use the capability of Selenium to use JavaScript (see example above) 9.3.Best Practices 9.3.1.General rules Test-suites size As a general rule, you should try to keep your test-suites small; theyll execute faster and youll find bugs more easily: Test one application feature per suite Use one test-case per user-facing bug Testing time All your Selenium tests should not take more than 10 minutes to run for a given application: Parallelize your tests Test only what you need 9.3.2.Locators Avoid text-matching pattern Instead of testing the presence of a text pattern, look for the element (or its id) containing that text (and THEN eventually the value) With i18n, your text-based locators are useless! Changes in the text breaks your tests What if the same text appears twice in the same page? Globbing and Regexps are here for you! Helps with generated ids You have access to all the features of JavaScripts regexp implementation 9.3.3.Ajax Ajax calls dont trigger a page load event and currently selenium detects only these events You could use click followed by a pause(time) but: Its unreliable: you can only assume the call is complete by the time limit It doesnt tell you the call had the assumed effect What you shouldg use are waitFor...(timeout) statements Every accessor has a waitFor flavor as well as a complementary one: waitForElementPresent has waitForElementNotPresent For most complex logic, you can use waitForCondition(script,timeout) that takes a javascript and keeps executing it until it evaluates to true or timeout is reached In ruby, use the js_eval(script, timeout) binding Example of waitForCondition statement (Ruby). describe "Google Search" do it "can display suggestions on the search bar" do page.open "/" page.title.should eql "Google" page.element?("q").should be_true page.type_keys "q", "Selenium" # INCLUDEPICTURE "testing_strategies_files/1.png" \* MERGEFORMAT  script = "var result;" + "page = selenium.browserbot.getCurrentWindow().document;" + # INCLUDEPICTURE "testing_strategies_files/2.png" \* MERGEFORMAT  "var suggestionBox = page.getElementsByClassName('gac_od');" + # INCLUDEPICTURE "testing_strategies_files/3.png" \* MERGEFORMAT  " if (suggestionBox.visibility == 'hidden' && suggestionBox == null) {" + # INCLUDEPICTURE "testing_strategies_files/4.png" \* MERGEFORMAT  " result = false;" + "}" + "else {" + " result = true;" + "}" + "result;" page.js_eval(script, 30000).should be_true page.element?("class=gac_a").should be_true # INCLUDEPICTURE "testing_strategies_files/5.png" \* MERGEFORMAT  end end Example of waitForCondition statement (Java). package .... import .... public class TestGoogleSearch extends SeleneseTestCase { ... public void testGoogle() { selenium.open("/"); verifyEquals(selenium.getTitle(), "Google"); verifyTrue(selenium.isElementPresent("q")); selenium.typeKeys("q", "Selenium"); String script = "var result;" + "page = selenium.browserbot.getCurrentWindow().document;" + # INCLUDEPICTURE "testing_strategies_files/1.png" \* MERGEFORMAT  "var suggestionBox = page.getElementsByClassName('gac_od');" + # INCLUDEPICTURE "testing_strategies_files/2.png" \* MERGEFORMAT  " if (suggestionBox.visibility == 'hidden' && suggestionBox == null) {" + # INCLUDEPICTURE "testing_strategies_files/3.png" \* MERGEFORMAT  " result = false;" + "}" + "else {" + " result = true;" + "}" + "result;"; verifyEquals(selenium.getEval(script),"true"); verifyTrue(selenium.isElementPresent'class=gac_a")); # INCLUDEPICTURE "testing_strategies_files/4.png" \* MERGEFORMAT  } } HYPERLINK "testing_strategies.html" \l "CO6-1" INCLUDEPICTURE "testing_strategies_files/1.png" \* MERGEFORMAT We use type_keys instead of type to simulate keyboard typing instead of directly setting the value of the field HYPERLINK "testing_strategies.html" \l "CO6-2" INCLUDEPICTURE "testing_strategies_files/2.png" \* MERGEFORMAT HYPERLINK "testing_strategies.html" \l "CO6-6" INCLUDEPICTURE "testing_strategies_files/1.png" \* MERGEFORMAT The javascript executes in the context of Selenium Core so the JSs window object refers to the monitor window. This call allows us to get the actual application window HYPERLINK "testing_strategies.html" \l "CO6-3" INCLUDEPICTURE "testing_strategies_files/3.png" \* MERGEFORMAT HYPERLINK "testing_strategies.html" \l "CO6-7" INCLUDEPICTURE "testing_strategies_files/2.png" \* MERGEFORMAT We retrieve the suggestion box element by its class name HYPERLINK "testing_strategies.html" \l "CO6-4" INCLUDEPICTURE "testing_strategies_files/4.png" \* MERGEFORMAT HYPERLINK "testing_strategies.html" \l "CO6-8" INCLUDEPICTURE "testing_strategies_files/3.png" \* MERGEFORMAT If the