18 January 2026:


This forum is now archived and is in read-only mode. Please continue discussions on our improved new Sahi Pro Community forum.



Sahi Pro is an enterprise grade test automation platform which can automate web, mobile, API, windows and java based applications and SAP.

Anyone using page objects in Sahi

dasspunkdasspunk Members
edited November -1 in Sahi - Open Source
I'm interested in introducing a page object pattern to my sahi scripts and wanted to see if anyone here in the forum is doing this too... The pages themselves seem pretty straight forward but I'm interested in HOW you incorporate them into your tests. How you call them from your scripts and how you manage them in general. Any examples you could give would also be great.

Thanks a bunch,
Brian

Comments

  • dasspunkdasspunk Members
    So this is kinda where my head is going with page objects... but I'd like to get some feedback from folks actually using them in Sahi (if there are any folks doing so).

    In addition, I'm making some assumptions in OO javascript and I'd like to know if I'm using them correctly. For example, verifyPage is (in my mind) a private function and thus, I don't use "this" so when declaring it... is that correct use for such a thing?

    Thanks a bunch!
    Brian
    // my google search javascript class...
    function GoogleHomePage() {
    
        this.visit = function() {
            _navigateTo("http://google.com");
            verifyPage();
        };
    
        var verifyPage = function() {
            _assertEqual(document.title, "Google");
        };
    
        this.searchFor = function($searchText) {
            _setValue(_textbox("q"), $searchText);
            _click(_submit("Google Search"));
            return new GoogleResultsPage;
        }; 
    };
    
    function GoogleResultsPage() {
    
        this.getPageTitle = function() {
            _alert(document.title);
        };
    };
    
    // test it... 
    var googleHomePage = new GoogleHomePage;
    googleHomePage.visit();
    
    // get results... 
    var googleResultsPage = googleHomePage.searchFor("javascript page object pattern");
    googleResultsPage.getPageTitle();
    
  • dasspunkdasspunk Members
    Perhaps a better take on this... ?
    // page object... 
    var GoogleHome = {
        $url : "[url]http://google.com[/url]",
        $textbox : "q",
        $submitButton : "Google Search",
    
        visit : function() {
            $that = this;
            _navigateTo($that.$url);
        },
        searchFor : function($searchText) {
            $that = this;
            _setValue(_textbox($that.$textbox), $searchText);
            _click(_submit($that.$submitButton));
        }
    };
    
    // results page object... 
    var GoogleResults = {
    
        getFirstResult : function() {
            _alert(_getText(_link(0, _in(_div("ires")))));
        }
    };
    
    // test...
    GoogleHome.visit();
    GoogleHome.searchFor("javascript page object pattern");
    
    GoogleResults.getFirstResult();
    
  • globalwormingglobalworming Moderators
    Well..

    this looks quite good, but I don't think it does scale well. I am currently automating a ticket shop where you can choose from thousands of events. To test specific page details, I may have to write an own page object for each event. Well.. as I think about it.. it may be no great difference. I have no idea, I might just try it to get an impression. An events page object won't let me test details.. I might have to write some general event page object and other, more specific objects for that kind of page.

    One question arises: What do you do with reoccurring objects? Like having an _submit("next") on many pages? Do you refactor this occurrences into some object? Do you stores them as global variables?

    Your second code example is more beautiful, I just like that pattern more.

    Regards
    Wormi
  • dasspunkdasspunk Members
    Hi Wormi,

    Thanks for the reply. I have the same questions you have and am hoping to start a discussion to learn the answers myself. My second example was a refractor of my first, using an object literal... I too think it flows much better and doesn't require instantiating with "New". It did require a that=this workaround for scope... hope i'm not breaking any rules with it :)

    I started playing with page objects because I have loose code in my script (for navigation and such) that could cause my scripts to break shout they change... plus, I like the readability that I could get by using these objects.

    As for reocurring objects (_submit("next") or even navigation), I'd think you'd make that their own objects and include them... I'll have to play with it too.

    I've read a lot about page objects online but haven't really had the breakthrough moment where I have my head fully around it. I was hoping to find others that were using them push me in the right direction (or tell me I'm crazy).

    Brian
  • globalwormingglobalworming Moderators
    I don't think it's necessary to use $that=this, did you really have problems just using this? Might be a Sahi problem as it needs to have variables with the $ sign.

    I remember, I once tried something similar, not really object per page, more like object per context... but I didn't explored it further because I couldn't find an IDE which aided me with content assist on the different return types.. may the code explain this better:
    // I pretty much follow your pattern but will add a return statement everywhere
    
    var $GoogleHome = {
        $url : "http://google.com]",
        $textbox : "q",
        $submitButton : "Google Search",
    
        visit : function() {
            $that = this;
            _navigateTo($that.$url);
            return $that;
        },
        searchFor : function($searchText) {
            $that = this;
            _setValue(_textbox($that.$textbox), $searchText);
            _click(_submit($that.$submitButton));
            return $GoogleResults;
        }
    };
    
    var $GoogleResults = {
    
        getResult : function($i) {
            _alert(_getText(_link($i, _in(_div("ires")))));
            return $GoogleResults; 
        }
    };
    
    //this shoud change the code to this:
    GoogleHome.visit().searchFor("javascript page object pattern").getResult(4);
    

    With a good IDE (I recently discovered webstorm... quite neat) and some documentation I could easily write tests as oneliners. If the documentation on your functions is really good, you can have everyone writing test, you just need to write
    $Goo -> Content assist
    "Use this for the Google Homepage"
    $GoogleHome. -> Content assist
    "Use this to navigate to... Use this to search after, @param text, @return $GoogleResults"
    GoogleHome.visit().searchFor("javascript page object pattern"). -> Content assist on the $GoogleResults functions

    This seems pretty nice and readable (my opinion, correct me if you think different), but I just save some linebreaks and some variables.. worth it? I don't know.
    Storing elements/constants in objects is ok, but I can't figure out the sorting.. context:
    var $UsefulObjects={
      homePage:{
        send: _submit("send")
      },
      resultPage={
        send: this.homepage.send
      }
    } 
    
    or type?
    var $UsefulInput={
      search: _textbox("q"),
      username: _textbox("username")
    }
    

    When you translated you sites logic and flow into page objects, it might be pretty fast to write tests and easy to refactor, but I just can't imagine me writing >1000 page objects. Maybe, as I said earlier, we could use prototypes. Let a specific page inherit the navigation, logos etc....
  • dasspunkdasspunk Members
    As for $this=that, I contacted support about this to see if it's an issue with Sahi, my code or just a fact of scope. Regardless, this was the only way I could get it to run in Sahi.

    The point of page objects, in my mind, is to encapsulate the page services--so changes only need to occur in one place (instead of hundreds) and perhaps more importantly, code readability. For instance, I could had the following line to ANYbody and they'd know what it was doing:

    GoogleHomePage.searchFor("I like eggs");

    This seems to be the promise of page objects. These offerings would also lead me to think separating the elements/actions to go against the entire idea. I'd like a page to offer the "services" of a textbox, link, button, etc.... And although it would take some upfront time, I think it would likely pay off. Towards this goal also, I would add elements to a page object, only as needed... in that way, the upfront costs would be mitigated.

    As for common objects like nav, I would add these to their own object and/or incorporate them into a basePage class that could be inherited into other page objects... I've not tested it yet but that seems logical...

    Thoughts? Maybe it's only Wormy and I thinking about this?

    Brian
  • globalwormingglobalworming Moderators
    How did you like the method chaining with changing return types? Is it readable? Does it save code?
    GoogleHome.visit().searchFor("javascript page object pattern").getResult(4);

    I think you are right, upon finishing the page objects, there is no great difference to what I use right now. But I'd like to hear some other opinions too.
  • narayannarayan Administrators
    Hi guys,

    this.$x is parsed correctly in Sahi Pro. So this is valid code:
    // page object...
    var GoogleHome = {
        $url : "http://google.com",
        $textbox : "q",
        $submitButton : "Google Search",
     
        visit : function() {
            //$that = this;
            _navigateTo(this.$url);
        },
        searchFor : function($searchText) {
            _setValue(_textbox(this.$textbox), $searchText);
            _click(_submit(this.$submitButton));
        }
    };
    
    GoogleHome.visit();
    GoogleHome.searchFor("I love eggs");
    

    Regards,
    Narayan
  • dasspunkdasspunk Members
    Narayan,

    I just tried (again) using this.$url in the code and it fails with:
    ReferenceError: "$url" is not defined.
    

    This is why I did the $that=this workaround. I've only tried this on my Mac however... perhaps it's a bug on Mac? I'll try it on my PC when I get to work.

    Wormy, I dig the chaining... and that's the idea.

    Thanks,
    Brian
  • dasspunkdasspunk Members
    Great! this.$x does indeed work on my PC/Sahi Pro. Perhaps there's a bug in the Mac/Sahi OS version that fails as I stated above...

    Brian
  • globalwormingglobalworming Moderators
    We are currently adding a new feature on our site. For testing this, I tried that page object pattern again and its amazing.

    The bad thing is, I have to use that $this=this or it won't work.. maybe because of Sahi OS...
    and method chaining over various objects isn't working too (it used to work a few month ago), but this is no bad thing. I would have made my tests a one liner, now its pretty obvious when a page changes. My setup:

    Basic Page
    // template page 
    $Page = {
        $uniquePageElement: _heading("page"),
        isPage:function () {
            var $this=this;
            return _condition(_exists($this.$uniquePageElement));
        },
        onPage:function ($action) {
            var $this = this;
            if ( $this.isPage() ) {
                $action();
            } else {
                // todo throw exeption, error handling like logging URL, taking screenshots etc and
               // call the testcases tearDown() function
            }
        },
            // do something on the page
        doSomethingHere:function () {
            var $this = this;
            $this.onPage(function () {
                            // statements
            });
                    // to use method chaining
                    return $this;
        },
            // navigate somewhere else
        moveOn:function () {
            var $this = this;
            $this.wennAufSeite(function () {
                           // statements
            });
                    // if you can use it, add return $page2 and chain methods endless
        }
    }
    

    Abstract User and Navigation Object like:
    $navigation = {
        toPage4: function(){
                $Page1.login().moveOn();
                    $Page2.moveOn();
                    // ....
        }
    }
    

    Constant Element File
    /*  Submit */
    $SUBMIT=_submit("submit")
    $LOGOUT=_submit("/log*out/i")
    

    The benefit:

    I tests are easy to read and extremly fast to write (took 3 hours to write and refactor the objects, now I write tests within 20 min). Nearly instant refactor as you put every pages features into seperate functions. Higher Objects: shorten the tests, easy to read $navigation.goThere().doThat().goSomewhereCompletlyElse().
    onPage function: yeah.. execution takes longer, but I can make sure that the page I need is present and can have some standard error handling/logging when something isn't working as expected.

    Well.. thats pretty it. When the scripts fail somehow, other than "onPage" functions will get a similar error handling.
    Next Step: prototyping the page object..

    Does this make sense?
  • lonely_girl01lonely_girl01 Members
    edited May 2012
    Hi dasspunk,

    Seems you have used OO pretty well.. :)
    I tried it too but, got a problem on 'this' object so in my implementation, i only just use object variables.

    Right now, i have a function for verifying a page and for buttons that i can click within pages.
    $url_pages = {
          home: "/",
          login: "/login.html"
    };
    
    function verifyPage($page) {
         //loop in $url_pages
         for(var $key in $url_pages) {
              if($key == $page) {
                 _log("At "+$page+" page"); //I did this because i also created a record log
                 break;
              }
         }
    }
    
    $button = {
         login: _button("login"),
         home: _link("home")
    };
    
    function click($page) {
         for(var $key in $button) {
              if($key == $page) {
                 if(_isVisible($button[$key])) {
                   _click($button[$key]);
                   break;
                 }
              }
         }
    }
    
    //test
    verifyPage("login");
    click("home");
    


    Keep posting your updates. ;)
  • globalwormingglobalworming Moderators
    edited May 2012
    Hey lonely_girl01

    try to use var $this=this, works for me. What you did there looks quite promising, wrap an object around and you are done with the page object :)

    btw, this is no real object orientation so far. Yes, we use objects, but there is no scope whatsoever, no inheritance, polymorphism etc.

    But we could add that easily. I talked about page navigation and company logos which are present on all pages. We could easily define a navigation prototype which pages can inherit... here is an example which totally could be more abstracted, I think:
    //our abstract navigation element
    function NavigationElements($rootElement, $type){
        this.rootElement=$rootELement;
        this.elementType=$type;
        this.checkNavigation = function($navigationArray){
            switch (this.elementType){
                case "DIV":
                    // for-in loop over $navigationArray
                    // assertExists(_div($navigationArray[$i], _in(rootElement)
                    break;
                case "A":;
                    // for-in loop over $navigationArray
                    // assertExists(_link($navigationArray[$i], _in(rootElement))
                    break;
            }
        }
        // add something like this.elements:  _collect(_div(_in(rootElement)))
    }
    
    // we have a list of _divs to navigate to subsites
    $HomePage = {
        subsites:["About Us", "Downlaod"],
        navigation: new NavigationElements(_div("homepage navigation"), "DIV"),
        checkNavigation: this.navigation.checkNavigation(subsites)
    }
    
    // another site may have a lot of links to different sides
    $AboutUs = {
        coworkers:["Douglas Adams", "Mark Zuckerberg"],
        profileLinks: new NavigationElements(_div("profiles"), "A"),
        checkNavigation: this.navigation.checkNavigation(this.coworkers)
    }
    
    

    my next step would be to write a $Page prototype
  • dasspunkdasspunk Members
    Hmmmm...

    I dig the elegance of the object literal solution to this but it's lack of inheritance is problematic. I can't seem to find a way to inherit a common object, such as a header or navbar. Obviously, you could just make a navbar it's own object literal and go with it... but it'd be nice to be able to encapsulate it into the relevant page objects.

    I've tried a bunch of different things to work around this, including making the common objects constructors... but I can't seem to get my head around it.

    Anyone else have a solution?

    Eg. given the following code...
    // page object... 
    var GoogleHome = {
        $url : "http://google.com",
        $textbox : "q",
        $submitButton : "Google Search",
        $title : "Google",
    
        visit : function() {
            $this = this;
            _navigateTo($this.$url);
            $this.isPage(); // verify we're on the right page...
        },
        isPage : function() {
            _assertEqual($this.$title, _title());
        },
        searchFor : function($searchText) {
            $this = this;
            _setValue(_textbox($this.$textbox), $searchText);
            _click(_submit($this.$submitButton));
        }
    };
    
    // results page object... 
    var GoogleResults = {
        $title : new RegExp(".*Google"),
    
        isPage : function() {
            $this = this;
            _assertEqual($this.$title, _title());
        },
        getFirstResult : function() {
            _alert(_getText(_link(0, _in(_div("ires")))));
        }
    };
    
    var filterBy = {
        everything : function() {
            _click(_link("Everything", _in(_div("leftnav"))));
        },
        images : function() {
            _click(_link("Images", _in(_div("leftnav"))));
        },
        maps : function() {
            _click(_link("Maps", _in(_div("leftnav"))));
        }
    };
    
    // test...
    GoogleHome.visit();
    GoogleHome.searchFor("javascript page object pattern");
    
    GoogleResults.getFirstResult();
    filterBy.images(); // this works and filters results by image 
    
    

    Of course filterBy.images() does work, but I want to find a way to inherit filterBy into GoogleResults (and other pages that include this common object). I tried making it a constructor, returning the functions, etc....

    Anyone have a clever solution?

    Thanks,
    Brian
  • globalwormingglobalworming Moderators
    edited May 2012
    Hi again

    something like this... hope you can improve it
    function FilterableList ($filterElement, $resultElement, $resultPattern) {
        resultPattern=$resultPattern;
        resultElement=$resultElement;
        filterElement=$filterElement;
        results=[];
    }
    FilterableList.prototype.filterBy= function ($filter){
            _click(_div($filter , _in(this.filterElement)));
    };
    FilterableList.prototype.getResults= function () {
            this.results = _collect(this.resultPattern, _in(this.resultElement));
    };
    
    
    $LinkListPage= {
        linkList: new FilterableList(_div("sort"), _div("results"), [_link, "/.*/"])
    }
    $LinkListPage.linkList.filterBy("Image");
    

    I hope you get my idea :)
  • globalwormingglobalworming Moderators
    Another disadvantage: these Objects are hard to debug. When I forget a 'this' the error just says $variable is not defined, I really don't know where this $variable is. Additionally, when there is a typo in some function call, the error will just say "Object [object] has no function...".
    At least for the second handicap there is a solution. Taken from http://blog.anselmbradford.com/2009/04/05/object-oriented-javascript-tip-overriding-tostring-for-readable-object-imprints/ I can create a page prototype:
    function Page($name){
        this.name=$name;
    }
    Page.prototype.toString = function(){
        return "Page:  "+ this.name;
    }
    

    For specific pages:
    $HomePage =new Page("Home Page");
    $HomePage.welcomeHeading=_heading("welcome");
    $HomePage.checkHeading=_assertContainsText("Hello World", this.welcomeHeading);
    

    HomePage.checkheading() will now fail with "Page: Home Page has no function checkheading" istead of "Object [object] has no ...". This is pretty handy when you have many pages and some generic function names.
  • dasspunkdasspunk Members
    Okay, I was getting burned by scope and Sahi's need for the "$". Long story slightly longer, common page elements can be included in object literals via constructors. I had tried this a number of different ways but kept missing one of the required elements. Yeesh.

    Anyway, here's my working example. I left the $this=this (I preferred Wormy's way of doing this to my own (thanks Wormy!)) so this should also work on the current OS version.
    // page object... 
    var GoogleHome = {
        $url : "http://google.com",
        $textbox : "q",
        $submitButton : "Google Search",
        $title : "Google",
    
        visit : function() {
            $this = this;
            _navigateTo($this.$url);
            $this.isPage(); // verify we're on the right page...
        },
        isPage : function() {
            _assertEqual($this.$title, _title());
        },
        searchFor : function($searchText) {
            $this = this;
            _setValue(_textbox($this.$textbox), $searchText);
            _click(_submit($this.$submitButton));
        }
    };
    
    // results page object... 
    var GoogleResults = {
        $title : new RegExp(".*Google"),
        $filterBy : new SearchFilter, // include common page element...
    
        isPage : function() {
            $this = this;
            _assertEqual($this.$title, _title());
        },
        getFirstResult : function() {
            _alert(_getText(_link(0, _in(_div("ires")))));
        }
    };
    
    // share a common page element between page objects... 
    function SearchFilter() {
        this.everything = function() {
            _click(_link("Everything", _in(_div("leftnav"))));
        }
        this.images = function() {
            _click(_link("Images", _in(_div("leftnav"))));
        }
    };
    
    // test...
    GoogleHome.visit();
    GoogleHome.searchFor("javascript page object pattern");
    
    GoogleResults.getFirstResult();
    GoogleResults.$filterBy.images();
    
    
  • dasspunkdasspunk Members
    edited May 2012
    Okay, here's my final (I think) example for this... it was a great learning experience and as a proof of concept, it worked pretty well.

    I cleaned some things up and removed unnecessary"$" and $this=this statements, so this code will currently only run on Sahi Pro. I added comments to try and explain things a bit and I also updated how I'm calling the common SearchFilter to be a bit more elegant.

    Hope this helps!

    // page object via object literal... 
    var GoogleHome = {
        // you need the $ if passing to Sahi methods...
        $url : "http://google.com",
        $textbox : "q",
        $submitButton : "Google Search",
        $title : "Google",
        
        isPage : function() {
            _assertEqual(this.$title, _title());
        },
        visit : function() {
            _navigateTo(this.$url);
            this.isPage(); // verify we're on the right page... 
        },
        searchFor : function($searchText) {
            _setValue(_textbox(this.$textbox), $searchText);
            _click(_submit(this.$submitButton));
        }
    };
    
    // results page object... 
    var GoogleResults = {
        $title : new RegExp(".*Google"),
        SearchFilter : new SearchFilter, // include common page element...
    
        getFirstResult : function() {
            _alert(_getText(_link(0, _in(_div("ires")))));
        }
    };
    
    // share a common page element between page objects via constructor... 
    function SearchFilter() {
        // this. makes the method public... 
        this.filterBy = function($filterName) {
            _click(_link($filterName, _in(_div("leftnav"))));
        }
    };
    
    
    // test...
    GoogleHome.visit();
    GoogleHome.searchFor("javascript page object pattern");
    
    GoogleResults.getFirstResult();
    GoogleResults.SearchFilter.filterBy("Images");
    
  • globalwormingglobalworming Moderators
    Thanks for sharing dasspunk. I finally got my prototypes to work. I tried some Object.create() stuff, but Sahi doesn't support that.. I may try wirh Sahi Pro when I got time. I will post my page prototype on monday and an extended explaination in my blog.
  • dasspunkdasspunk Members
    Thanks a bunch everyone for their help and examples. I've posted my example and a bit of extra explanation on my blog: QualityShepherd.com

    GlobalWorming, I'm looking forward to see how you'll use inheritance in your example...

    Thanks again.
  • Hi GlobalWorming,

    I am trying the same thing as you guys discussed above, am also stuck in creating the page prototype. Am getting the page name as a string and want to have a function to dynamically include the page library and return the reference to the page objects and functions. Kindly let me know if you are able to create a prototype for a page. ? and creat the page references dynamically ?

    //trying to do this.
    var $home = new Page("Home")
    $home.navigate();
    $home.checkPage();

    //Page function
    function Page($p_sPage){
    _dynamicInclude("../Pages/"+$p_sPage);
    var $global = this;
    return new $global[$p_sPage]; // trying to convert string into an object, tried different ways including object.create() no luck :(
    }

    Any help on this will be appreciated.

    thanks
  • balajicbalajic Members
    edited May 2013
    it worked. the page prototype working model will be as mentioned below

    var $global = this; // this line of code should be in global name space

    //Home.js - create a library with respect to the page
    function Home(){
    //objects and methods w.r.t Home Page
    }

    //Page function
    function Page($Page){
    _dynamicInclude("../"+$Page+".js");
    var $a_oPage = $global[$Page];
    return $a_oPage;
    }

    //Script
    var $home = new Page("Home"); //create a reference to the Home object
    $home .checkPage(); // access the methods
    $home .Navigate();

    Thanks,
    Balaji
  • gaveyomgaveyom Members
    Hi All,

    can i know please, what is the advantage of using "page objects" coding technique instead of existing sahi script technique
Sign In or Register to comment.