diff --git a/README.md b/README.md index bfd98c4..d116c72 100755 --- a/README.md +++ b/README.md @@ -13,111 +13,125 @@ Python [unittest](https://docs.python.org/2/library/unittest.html) test cases. $ pip install robotframework-pageobjects -## How it Works + +## Demo + +Check out and run the [demo](https://github.com/ncbi/robotframework-pageobjects/tree/master/demo). + +## How the demo works Here's a Robot test case using some page objects written using the `Page` base class. We need to import any page objects libraries we need in our test case. **Note**: The `Page` class inherits from Selenium2Library, so all methods (keywords) in Selenium2Library are available in your tests, and from `self` from within one of your page objects. -*test_google.robot*: +*test_pubmed.txt*: *** Settings *** - - Documentation Tests searching Google and ending up on Apple. + Documentation My first IFT tests ... - Library google.Page - Library google.ResultPage - + Library pubmed.PubmedHomePage + Library pubmed.PubmedDocsumPage + Library pubmed.PubmedArticlePage + *** Test Cases *** + When a user searches Pubmed for a term, the first result page's body should contain the search term + Open Pubmed + Search For cat + Click Result On Pubmed Docsum Page 1 + Pubmed Article Page Body Should Contain cat + [Teardown] Close Pubmed Article Page - Test Google To Apple - Open Google - Search Google For Apple Computers - On Google Result Page Click Result 1 - Title Should Be Apple - [Teardown] Close Google - -This shows you can write the same test, using the same page object libraries outside of Robot, using, for example, Python's unittest module: +This shows you can write the same test, using the same page object libraries outside of Robot, using, for example, Python's unittest module. *test_google.py*: + from pubmed import PubmedHomePage import unittest - import google - class TestGoogleSearch(unittest.TestCase): + class PubmedTestCase(unittest.TestCase): def setUp(self): - self.google_page = google.Page().open() + self.pubmed_homepage = PubmedHomePage() + self.pubmed_homepage.open() - def test_google_search_to_apple(self): - result_page = self.google_page.search("apple computers") - result_page.click_result(1) - result_page.title_should_be("Apple") + def test_first_result_page_body_should_contain_search_term(self): + pubmed_docsum_page = self.pubmed_homepage.search_for("cat") + self.article_page = pubmed_docsum_page.click_result(1) + self.article_page.body_should_contain("cat") def tearDown(self): - self.google_page.close() - - unittest.main() + self.article_page.close() + if __name__ == "__main__": + unittest.main() -Now we need an actual Google Robot library to make the test work: +Now we need an actual pubmed page objects to make the test work: -*google.py*: +*pubmed.py*: from robotpageobjects import Page, robot_alias + from robot.utils import asserts - class GooglePage(Page): - - """ - Base Google Page - - """ - uri = "/" + class PubmedHomePage(Page): + """ Models the Pubmed home page at: + HOST://ncbi.nlm.nih.gov/pubmed""" - # name attribute tells Robot Keywords what name to put - # after the defined method. So, def foo.. aliases to "Foo Google". - # If no name is defined, the name will be the name of the page object - # class, in this case `Page`. - name = "Google" + name = "Pubmed" + uri = "/pubmed" - # selectors dictionary is an inheritable dictionary - # mapping names to Selenium2Library locators. selectors = { - "search input": "xpath=//input[@name='q']", - "search button: "id=gbqfba", + "search input": "id=term", + "search button": "id=search", } - def search(self, term): - self.input_text("search input", term) - self.click_element("search button") - return ResultPage() -**Note**: You must return *something* from public (non-underscored) page object methods: either a value from a getter method or a page object instance from non-getter methods. Remember, when you navigate to a new page by clicking a link, submitting a form etc. you should return the appropriate page object. + @robot_alias("type_in__name__search_box") + def type_in_search_box(self, txt): + self.input_text("search input", txt) + return self -Now we want to code a Google search result page. Here's the Google Result page object: + @robot_alias("click__name__search_button") + def click_search_button(self): + self.click_button("search button") + return PubmedDocsumPage() -*google.py*: + @robot_alias("search__name__for") + def search_for(self, term): + self.type_in_search_box(term) + return self.click_search_button() - ... - class ResultPage(Page): - """ - A Google Result page. Inherits from Google Page. - """ - # Google uses ajax requests for their searches. - uri = "/#q=cat" + class PubmedDocsumPage(Page): + """Models a Pubmed search result page. For example: + http://www.ncbi.nlm.nih.gov/pubmed?term=cat """ + + uri = "/pubmed/?term={term}" - name = "Google Result Page" + selectors = { + "nth result link": "xpath=(//div[@class='rslt'])[{n}]/p/a", + } + @robot_alias("click_result_on__name__") def click_result(self, i): - # Calling resolve_selector fills in the "n" variable in - # the selector template at-run-time for the "nth selector link". - # We need to cast the 'i' method parameter to an 'int' because Robot passes - # in all keyword parameters as strings. locator = self.resolve_selector("nth result link", n=int(i)) - - # Now, we pass the resolved locator to the inherited click_link method. self.click_link(locator) - return Page() + return PubmedArticlePage() + + class PubmedArticlePage(Page): + + uri = "/pubmed/{article_id}" + + @robot_alias("__name__body_should_contain") + def body_should_contain(self, str, ignore_case=True): + ref_str = str.lower() if ignore_case else str + ref_str = ref_str.encode("utf-8") + body_txt = self.get_text("css=body").encode("utf-8").lower() + asserts.assert_true(ref_str in body_txt, "body text does not contain %s" %ref_str) + return self + + +**Note**: You must return *something* from public (non-underscored) page object methods: either a value from a getter method or a page object instance from non-getter methods. Remember, when you navigate to a new page by clicking a link, submitting a form etc. you should return the appropriate page object. + +The rest of this README explains many more details around writing page objects and putting them to work in tests. ## Setting Options diff --git a/demo/README.md b/demo/README.md index d012dc0..c6ae591 100755 --- a/demo/README.md +++ b/demo/README.md @@ -5,21 +5,16 @@ This directory contains a demo of the Robot Page Objects package. It demonstrate suprememly readable Robot test leveraging Selenium2Library to create page object libraries, which are usable in Robot and outside of Robot. -How to run the demos +How to run the demo -------------------- -1. Create a virtual environment -2. pip install . -3. activate the virtual environment -4. $ pybot -vbrowser:firefox -vbaseurl:http://www.google.com test_google.robot +1. Create a virtual environment, then +1. pip install robotframeworkpageobjects +2. $ pybot -vbrowser:firefox -vbaseurl:http://www.ncbi.nlm.nih.gov test_pubmed.txt To run the Python unittest example: -$ export PO_BASURL=http://www.google.com -$ python test_google.py +$ export PO_BASEURL=http://www.ncbi.nlm.nih.gov +$ export PO_BROWSER=firefox +$ python test_pubmed.py -To run the unittest example in Firefox, set the browser environment variable: -$ export PO_BASURL=http://www.google.com -$ export PO_BROWSERL=firefox -$ python test_google.py - -This demo no longer works in Phantomjs. We plan on fixing that in the future. +By default tests will run in PhantomJS unless you specify otherwise. diff --git a/demo/google.py b/demo/google.py deleted file mode 100755 index c8e0a89..0000000 --- a/demo/google.py +++ /dev/null @@ -1,63 +0,0 @@ -from robotpageobjects import Page, robot_alias -from robot.utils import asserts - - -class GoogleHomePage(Page): - """ Models the Google home page """ - - name = "Google" - uri = "/" - - selectors = { - "search input": "xpath=//input[@name='q']", - "search button": "xpath=//button[@name='btnG']|//input[@name='btnG']", - } - - - @robot_alias("type_in__name__search_box") - def type_in_search_box(self, txt): - self.input_text("search input", txt) - return self - - @robot_alias("click__name__search_button") - def click_search_button(self): - self.click_button("search button") - return GoogleSearchResultPage() - - @robot_alias("search__name__for") - def search_for(self, term): - self.type_in_search_box(term) - return self.click_search_button() - - -class GoogleSearchResultPage(Page): - """Models a Google search result page. For example: - http://www.google.com/#q=cat """ - - uri_template = "/#q={term}" - - selectors = { - "nth result link": "xpath=(//li[@class='g'])[{n}]//div//h3//a | (//li[@class='g'])[{n}]/h3/a", - } - - @robot_alias("click_result_on__name__") - def click_result(self, i): - locator = self.resolve_selector("nth result link", n=int(i)) - self.click_link(locator) - return DestinationPage() - -class DestinationPage(Page): - - uri_template = "/{path}" - - selectors = { - "title element": "xpath=//head//title", - } - - @robot_alias("__name__title_should_contain") - def title_should_contain(self, str, ignore_case=True): - ref_str = str.lower() if ignore_case else str - ref_str = ref_str.encode("utf-8") - title = self.get_title().encode("utf-8").lower() - asserts.assert_true(ref_str in title, "%s does not contain %s" %(title, ref_str)) - return self diff --git a/demo/pubmed.py b/demo/pubmed.py new file mode 100755 index 0000000..94ed9fb --- /dev/null +++ b/demo/pubmed.py @@ -0,0 +1,88 @@ +from robotpageobjects import Page, robot_alias +from robot.utils import asserts + + +class PubmedHomePage(Page): + """ Models the Pubmed home page at: + HOST://ncbi.nlm.nih.gov/pubmed""" + + + # Allows us to call this page + # something other than the default "Pubmed Home Page" + # at the end of keywords. + name = "Pubmed" + + # This page is found at baseurl + "/pubmed" + uri = "/pubmed" + + # inheritable dictionary mapping human-readable names + # to Selenium2Library locators. You can then pass in the + # keys to Selenium2Library actions instead of the locator + # strings. + selectors = { + "search input": "id=term", + "search button": "id=search", + } + + + # Use robot_alias and the "__name__" token to customize + # where to insert the optional page object name + # when calling this keyword. Without the "__name__" + # token this method would map to either "Type In Search Box", + # or "Type In Search Box Pubmed". Using "__name__" we can call + # "Type in Pubmed Search Box foo". + @robot_alias("type_in__name__search_box") + def type_in_search_box(self, txt): + self.input_text("search input", txt) + + # We always return something from a page object, + # even if it's the same page object instance we are + # currently on. + return self + + @robot_alias("click__name__search_button") + def click_search_button(self): + self.click_button("search button") + + # When navigating to another type of page, return + # the appropriate page object. + return PubmedDocsumPage() + + @robot_alias("search__name__for") + def search_for(self, term): + self.type_in_search_box(term) + return self.click_search_button() + + +class PubmedDocsumPage(Page): + """Models a Pubmed search result page. For example: + http://www.ncbi.nlm.nih.gov/pubmed?term=cat """ + + uri = "/pubmed/?term={term}" + + # This is a "selector template". We are parameterizing the + # nth result in this xpath. We call this from click_result, below. + selectors = { + "nth result link": "xpath=(//div[@class='rslt'])[{n}]/p/a", + } + + @robot_alias("click_result_on__name__") + def click_result(self, i): + + # For selector templates, we need to resolve the selector to the + # locator first, before finding or acting on the element. + locator = self.resolve_selector("nth result link", n=int(i)) + self.click_link(locator) + return PubmedArticlePage() + +class PubmedArticlePage(Page): + + uri = "/pubmed/{article_id}" + + @robot_alias("__name__body_should_contain") + def body_should_contain(self, str, ignore_case=True): + ref_str = str.lower() if ignore_case else str + ref_str = ref_str.encode("utf-8") + body_txt = self.get_text("css=body").encode("utf-8").lower() + asserts.assert_true(ref_str in body_txt, "body text does not contain %s" %ref_str) + return self diff --git a/demo/test_google.py b/demo/test_google.py deleted file mode 100755 index 1e39327..0000000 --- a/demo/test_google.py +++ /dev/null @@ -1,20 +0,0 @@ -from google import GoogleHomePage -import unittest - - -class GoogleTestCase(unittest.TestCase): - - def setUp(self): - self.google_homepage = GoogleHomePage() - self.google_homepage.open() - - def test_first_result_page_title_should_contain_search_term(self): - search_result_page = self.google_homepage.search_for("cat") - self.destination_page = search_result_page.click_result(1) - self.destination_page.title_should_contain("cat") - - def tearDown(self): - self.destination_page.close() - -if __name__ == "__main__": - unittest.main() diff --git a/demo/test_google.txt b/demo/test_google.txt deleted file mode 100755 index 188747f..0000000 --- a/demo/test_google.txt +++ /dev/null @@ -1,14 +0,0 @@ -*** Settings *** -Documentation My first IFT tests -... -Library google.GoogleHomePage -Library google.GoogleSearchResultPage -Library google.DestinationPage - -*** Test Cases *** -When a user searches Google for a term, the first result page's title should contain the search term - Open Google - Search For cat - Click Result On Google Search Result Page 1 - Destination Page Title Should Contain cat - [Teardown] Close Destination Page diff --git a/demo/test_pubmed.py b/demo/test_pubmed.py new file mode 100755 index 0000000..589fdee --- /dev/null +++ b/demo/test_pubmed.py @@ -0,0 +1,20 @@ +from pubmed import PubmedHomePage +import unittest + + +class PubmedTestCase(unittest.TestCase): + + def setUp(self): + self.pubmed_homepage = PubmedHomePage() + self.pubmed_homepage.open() + + def test_first_result_page_body_should_contain_search_term(self): + pubmed_docsum_page = self.pubmed_homepage.search_for("cat") + self.article_page = pubmed_docsum_page.click_result(1) + self.article_page.body_should_contain("cat") + + def tearDown(self): + self.article_page.close() + +if __name__ == "__main__": + unittest.main() diff --git a/demo/test_pubmed.txt b/demo/test_pubmed.txt new file mode 100755 index 0000000..0e41872 --- /dev/null +++ b/demo/test_pubmed.txt @@ -0,0 +1,14 @@ +*** Settings *** +Documentation My first IFT tests +... +Library pubmed.PubmedHomePage +Library pubmed.PubmedDocsumPage +Library pubmed.PubmedArticlePage + +*** Test Cases *** +When a user searches Pubmed for a term, the first result page's body should contain the search term + Open Pubmed + Search For cat + Click Result On Pubmed Docsum Page 1 + Pubmed Article Page Body Should Contain cat + [Teardown] Close Pubmed Article Page