{"id":2919,"date":"2015-06-14T12:11:56","date_gmt":"2015-06-14T16:11:56","guid":{"rendered":"http:\/\/qxf2.com\/blog\/?p=2919"},"modified":"2018-04-03T10:45:45","modified_gmt":"2018-04-03T14:45:45","slug":"page-object-model-selenium-python","status":"publish","type":"post","link":"https:\/\/qxf2.com\/blog\/page-object-model-selenium-python\/","title":{"rendered":"Page Object Model (Selenium, Python)"},"content":{"rendered":"<p>We have come a long way since our post on implementing the Page Object Model<br \/>\n&#8211;<a href=\"https:\/\/qxf2.com\/blog\/page-object-model-selenium\/\">Implementing the Page Object Model (Selenium + Python)<\/a><br \/>\nWhile the above post is useful and we rank high on Google, we routinely hear two criticisms of it:<br \/>\na) the post is too high level<br \/>\nb) the application being tested is not very repeatable<\/p>\n<p>So we thought we would rework the above piece to address the drawbacks. In this post we shall give you a more detailed architecture, provide many more code snippets and write an automated test for Gmail.<\/p>\n<hr>\n<h3>Overview of Page Object Model<\/h3>\n<p>A page object represents an area in the web application user interface that your test is interacting with. Page objects reduces the amount of duplicated code and if the user interface changes, the fix needs changes in one place only.[<a href=\"http:\/\/selenium-python.readthedocs.org\/en\/latest\/page-objects.html\">1<\/a>]<\/p>\n<p><strong>WHAT vs HOW<\/strong><br \/>\nUsually the testers write the test cases describing <strong>&#8216;what&#8217;<\/strong> is to be tested, this depends on the product functionality. But the implementation of this functionality by the developers keeps changing till the final code freeze is done, hence testers should know <strong>&#8216;how&#8217;<\/strong> to implement the test cases so that the changes to the test scripts are minimal in case of code changes by the the developers. Page Objects encapsulates the finer details(locators and methods) of the pages from the test script and make the test script more readable and robust.<\/p>\n<hr>\n<h3>Sample Test Case &#8211; (WHAT)<\/h3>\n<p>We are going to explain about page objects with a very simple test case for Gmail.<br \/>\n-Goto http:\/\/gmail.com<br \/>\n-Enter the username, click Next<br \/>\n-Enter the password, click Sign in<br \/>\n-Perform search on the inbox &#8216;subject:POM&#8217;<br \/>\n-Click on the search result<br \/>\n-Click on inbox<\/p>\n<p>A simple approach would be to write a test script with all the xpaths and the methods required for the execution of the above listed steps in one single file. The test would run fine and achieve the purpose but one major drawback is the test script is brittle. For any minor UI change on any page, the test script would have to be updated.To overcome this problem we use the page object pattern. As its name suggests,each page of the application to be tested is treated like an object which has the variables (xpaths) and methods (actions that can be performed on that particular page). This in turn makes the test script much cleaner. <\/p>\n<hr>\n<h3>Implementing the test case using POM templates (HOW)<\/h3>\n<p>Given below is the pictorial description of the various page objects used for the implementation of the test case.<\/p>\n<p><a href=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/06\/Gmail_POM-1.jpg\" data-rel=\"lightbox-image-0\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/06\/Gmail_POM-1.jpg\" alt=\"page_object_model_classes\" width=\"960\" height=\"540\" class=\"alignnone size-full wp-image-2958\" srcset=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/06\/Gmail_POM-1.jpg 960w, https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/06\/Gmail_POM-1-300x169.jpg 300w\" sizes=\"auto, (max-width: 960px) 100vw, 960px\" \/><\/a><\/p>\n<hr>\n<p>Lets start with the main hero &#8211; <strong>Page.py<\/strong><br \/>\nAll page models can inherit from the Page class. This has useful wrappers for common Selenium operations<\/p>\n<pre lang=\"python\">\r\nclass Page(unittest.TestCase):\r\n    \"Page class that all page models can inherit from\"\r\n\r\n    def __init__(self,selenium_driver,base_url='https:\/\/mail.google.com\/'):\r\n        \"Constructor\"\r\n        #We assume relative URLs start without a \/ in the beginning\r\n        if base_url[-1] != '\/': \r\n            base_url += '\/' \r\n        self.base_url = base_url\r\n        self.driver = selenium_driver\r\n        #Visit and initialize xpaths for the appropriate page\r\n        self.start() \r\n        #Initialize the logger object\r\n        self.log_obj = Base_Logging(level=logging.DEBUG)\r\n     \r\n\r\n    def open(self,url):\r\n        \"Visit the page base_url + url\"\r\n        url = self.base_url + url\r\n        self.driver.get(url)\r\n\r\n    def get_xpath(self,xpath):\r\n        \"Return the DOM element of the xpath OR the 'None' object if the element is not found\"\r\n\r\n    def click_element(self,xpath):\r\n        \"Click the button supplied\"\r\n    .\r\n    .\r\n    def write(self,msg,level='info'):\r\n        self.log_obj.write(msg,level)\r\n        \r\n\r\n    def wait(self,wait_seconds=5):\r\n        \" Performs wait for time provided\"\r\n        time.sleep(wait_seconds)\r\n<\/pre>\n<p>Next is the <strong>Login_Page.py<\/strong> which handles the common functionality of user login. This will be the most re-used class.<\/p>\n<pre lang=\"python\">\r\nfrom Page import Page\r\nclass Login_Page(Page):\r\n    \"Page object for the Login page\"\r\n\r\n    def start(self):\r\n        self.url = \"\"\r\n        self.open(self.url) \r\n        # Assert Title of the Login Page and Login\r\n        self.assertIn(\"Gmail\", self.driver.title)      \r\n\r\n        \"Xpath of all the field\"\r\n        #Login \r\n        self.login_email = \"\/\/input[@name='Email']\"\r\n        self.login_next_button = \"\/\/input[@id='next']\"\r\n        self.login_password = \"\/\/input[@placeholder='Password']\"\r\n        self.login_signin_button = \"\/\/input[@id='signIn']\"\r\n    \r\n\r\n    def login(self,username,password):\r\n        \"Login using credentials provided\" \r\n        self.set_login_email(username)\r\n        self.submit_next()\r\n        self.set_login_password(password)\r\n        self.submit_login()\r\n        if 'Qxf2 Mail' in self.driver.title :\r\n            self.write(\"Login Success\")\r\n            return True\r\n        else:\r\n            self.write(\"FAIL: Login error\")\r\n            return False\r\n\r\n    def set_login_email(self,username):\r\n        \"Set the username on the login screen\"\r\n       \r\n    def submit_next(self):\r\n        self.click_element(self.login_next_button)\r\n        self.wait(3)\r\n  \r\n    def set_login_password(self,password):\r\n        \"Set the password on the login screen\"\r\n\r\n    def submit_login(self):\r\n        \"Submit the login form\"\r\n<\/pre>\n<p>Once we login, the main page is displayed which consists of the header (which contains the search box, user profile options),the navigation menu on the left side of the page and the main body. As the header and the navigation menu are common to all pages we created page objects for each of them. Here is a snippet of each of the classes.<\/p>\n<p><strong>Nav_Menu.py<\/strong><\/p>\n<pre lang=\"python\">\r\nfrom Page import Page\r\nclass Nav_Menu(Page):\r\n    \"Page object for the side menu\"\r\n\r\n    def start(self):      \r\n        \"Xpath of all the field\"\r\n        #Navigation Menu\r\n        self.inbox = \"\/\/a[contains(@href, '#inbox')]\"\r\n        self.sent_mail = \"\/\/a[contains(@href, '#sent')]\"\r\n        self.drafts= \"\/\/a[contains(@href, '#drafts')]\"\r\n\t\t\r\n\t\r\n    def select_menu_item(self,menu_item):\r\n\t\"select menu item\"\r\n<\/pre>\n<p><strong>Header_Section.py<\/strong><\/p>\n<pre lang=\"python\">\r\nfrom Page import Page\r\nclass Header_Section(Page):\r\n    \"Page object for the page header\"\r\n\r\n    def start(self):\r\n        \"Xpath of all the fields\"\r\n        #Search and profile\r\n        self.search_textbox = \"\/\/input[@id='gbqfq']\"\r\n        self.search_button = \"\/\/button[@id='gbqfb']\"\r\n        self.signout_button = \"\/\/a[text()='Sign out']\"\r\n        self.search_result = \"\/\/span[contains(text(),'%s')]\"\r\n\r\n    def search_by_subject(self,searchtext):\r\n        self.set_text(self.search_textbox,'subject:'+searchtext)\r\n      .\r\n      .\r\n<\/pre>\n<p>Now, the <strong>Main_Page.py<\/strong> will contain the objects of the above two classes.<\/p>\n<pre lang=\"python\">\r\nclass Main_Page(Page):\r\n    \"Page object for the Main page\"\r\n\r\n    def start(self):\r\n        self.url = \"\"\r\n        self.open(self.url)\r\n        #Create a Header Section object\r\n        self.header_obj = Header_Section(self.driver)\r\n        #Create a Menu object\r\n        self.menu_obj = Nav_Menu(self.driver)\r\n<\/pre>\n<p>This completes the page objects needed for this particular test case.<br \/>\n**Please note &#8211; as an alternate way, we can also have a &#8216;Template_Page'(which inherits from the Page class and has the common objects) and have all pages(except Login page) derive from it. <\/p>\n<p>In addition to these we have the following files<br \/>\n<strong>PageFactory.py<\/strong><br \/>\nPageFactory uses the factory design pattern. get_page_object() returns the appropriate page object.<\/p>\n<pre lang=\"python\">\r\ndef get_page_object(page_name,driver,base_url='https:\/\/gmail.com\/'):\r\n    \"Return the appropriate page object based on page_name\"\r\n    test_obj = None\r\n    page_name = page_name.lower()\r\n    if page_name == \"login\":\r\n        test_obj = Login_Page(driver,base_url=base_url)\r\n    elif page_name == \"main\":\r\n        test_obj = Main_Page(driver,base_url=base_url)  \r\n    \r\n    return test_obj\r\n<\/pre>\n<p><strong>DriverFactory.py<\/strong> which returns the appropriate driver for firefox or chrome or IE browser.<br \/>\n<strong>login.credentials<\/strong> file contains the username , password required for authentication.<\/p>\n<p>Finally , we have our test script which puts it all together and executes the test case.<br \/>\n<strong>Search_Inbox_Test.py<\/strong><\/p>\n<pre lang=\"python\">\r\ndef run_search_inbox_test(browser,conf,base_url,sauce_flag,browser_version,platform,testrail_run_id):\r\n    \"Login to Gmail using the page object model\"\r\n    # get the test account credentials from the .credentials file\r\n    credentials_file = os.path.join(os.path.dirname(__file__),'login.credentials')\r\n    username = Conf_Reader.get_value(credentials_file,'LOGIN_USER')\r\n    password = Conf_Reader.get_value(credentials_file,'LOGIN_PASSWORD')\r\n\r\n    #Result flag used by TestRail\r\n    result_flag = False\r\n    \r\n    #Setup a driver\r\n    #create object of driver factory\r\n    driver_obj = DriverFactory()\r\n    driver = driver_obj.get_web_driver(browser,sauce_flag,browser_version,platform)\r\n    driver.maximize_window()\r\n    \r\n\r\n    #Create a login page object\r\n    login_obj = PageFactory.get_page_object(\"login\",driver)\r\n    if (login_obj.login(username,password)):\r\n        msg = \"Login was successful\"\r\n        result_flag = True\r\n        login_obj.write(msg)\r\n    else:\r\n        msg = \"Login failed\"\r\n        login_obj.write(msg)\r\n        \r\n    #Create an object for main page with header and menu\r\n    main_obj = PageFactory.get_page_object(\"main\",driver)\r\n    main_obj.wait(3)\r\n    \r\n    #Search the inbox for message by subject 'POM' and open the message\r\n    if main_obj.header_obj.search_by_subject('POM'):\r\n        main_obj.write(\"Search successful\")\r\n        result_flag = True\r\n    else:\r\n        main_obj.write(\"Search text was not found\")\r\n        result_flag = False\r\n        \r\n    #Go to inbox\r\n    main_obj.menu_obj.select_menu_item('inbox')\r\n<\/pre>\n<p>As you must have noticed the final test is very easy to read and need not be modified in case of any underlying changes to individual pages.<\/p>\n<h3>Running the test <\/h3>\n<p>Let us execute the test,<br \/>\n<a href=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/06\/run_test.gif\" data-rel=\"lightbox-image-1\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/06\/run_test-300x75.gif\" alt=\"run_test_page_object_model\" width=\"300\" height=\"75\" class=\"alignnone size-medium wp-image-2956\" \/><\/a><\/p>\n<p>and here is the log file for the test run.<br \/>\n<a href=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/06\/log_file.gif\" data-rel=\"lightbox-image-2\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/06\/log_file-300x63.gif\" alt=\"log_file_page_object_model\" width=\"300\" height=\"63\" class=\"alignnone size-medium wp-image-2957\" \/><\/a><\/p>\n<hr>\n<p>So now, we all agree that the page objects make it really easy for the tester to convert the documented test case to an automated test case. Not only that, maintaining these tests is also very easy.<\/p>\n<p>Don&#8217;t want to implement a Pythonic test automation framework from scratch? You should try our open-sourced <a href=\"https:\/\/github.com\/qxf2\/qxf2-page-object-model\">Web automation framework based on the page object model<\/a>.<\/p>\n<p><strong>If you are a startup finding it hard to hire technical QA engineers, learn more <a href=\"https:\/\/qxf2.com\/blog\/about-qxf2\/\">about Qxf2 Services<\/a>.<\/strong><\/p>\n<hr>\n<h3>References:<\/h3>\n<p>1. <a href=\"http:\/\/selenium-python.readthedocs.org\/en\/latest\/page-objects.html\">http:\/\/selenium-python.readthedocs.org\/en\/latest\/page-objects.html<\/a><br \/>\n2. <a href=\"https:\/\/code.google.com\/p\/selenium\/wiki\/PageObjects\">https:\/\/code.google.com\/p\/selenium\/wiki\/PageObjects<\/a><br \/>\n3. <a href=\"https:\/\/qxf2.com\/blog\/page-object-tutorial\">Batman and Page Objects<\/a> <\/p>\n<hr>\n<script>(function() {\n\twindow.mc4wp = window.mc4wp || {\n\t\tlisteners: [],\n\t\tforms: {\n\t\t\ton: function(evt, cb) {\n\t\t\t\twindow.mc4wp.listeners.push(\n\t\t\t\t\t{\n\t\t\t\t\t\tevent   : evt,\n\t\t\t\t\t\tcallback: cb\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n})();\n<\/script><!-- Mailchimp for WordPress v4.10.1 - https:\/\/wordpress.org\/plugins\/mailchimp-for-wp\/ --><form id=\"mc4wp-form-1\" class=\"mc4wp-form mc4wp-form-6165 mc4wp-form-theme mc4wp-form-theme-blue\" method=\"post\" data-id=\"6165\" data-name=\"Newsletter\" ><div class=\"mc4wp-form-fields\"><div style=\"border:3px; border-style:dashed;border-color:#56d1e1;padding:1.2em;\">\r\n  <h1 style=\"text-align: center; padding-top: 20px; padding-bottom: 20px; color: #592b1b;\">Subscribe to our weekly Newsletter<\/h1>\r\n  <input style=\"margin: auto;\" type=\"email\" name=\"EMAIL\" placeholder=\"Your email address\" required \/>\r\n  <br>\r\n  <p style=\"text-align: center;\">\r\n    <input style=\"background-color: #890c06 !important; border-color: #890c06;\" type=\"submit\" value=\"Sign up\" \/>\r\n    \r\n  <\/p>\r\n  <p style=\"text-align: center;\">\r\n    <a href=\"http:\/\/mailchi.mp\/c9c7b81ddf13\/the-informed-testers-newsletter-20-oct-2017\"><small>View a sample<\/small><\/a>\r\n  <\/p>\r\n  <br>\r\n<\/div><\/div><label style=\"display: none !important;\">Leave this field empty if you're human: <input type=\"text\" name=\"_mc4wp_honeypot\" value=\"\" tabindex=\"-1\" autocomplete=\"off\" \/><\/label><input type=\"hidden\" name=\"_mc4wp_timestamp\" value=\"1777399247\" \/><input type=\"hidden\" name=\"_mc4wp_form_id\" value=\"6165\" \/><input type=\"hidden\" name=\"_mc4wp_form_element_id\" value=\"mc4wp-form-1\" \/><div class=\"mc4wp-response\"><\/div><\/form><!-- \/ Mailchimp for WordPress Plugin -->\n<hr>\n","protected":false},"excerpt":{"rendered":"<p>We have come a long way since our post on implementing the Page Object Model &#8211;Implementing the Page Object Model (Selenium + Python) While the above post is useful and we rank high on Google, we routinely hear two criticisms of it: a) the post is too high level b) the application being tested is not very repeatable So we [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[38,63,30],"tags":[],"class_list":["post-2919","post","type-post","status-publish","format-standard","hentry","category-automation","category-page-object-model","category-selenium"],"_links":{"self":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts\/2919","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/comments?post=2919"}],"version-history":[{"count":30,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts\/2919\/revisions"}],"predecessor-version":[{"id":8682,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts\/2919\/revisions\/8682"}],"wp:attachment":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/media?parent=2919"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/categories?post=2919"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/tags?post=2919"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}