TestStack / TestStack.Seleno

Seleno helps you write automated UI tests in the right way by implementing Page Objects and Page Components and by reading from and writing to web pages using strongly typed view models.
http://teststack.github.com/TestStack.Seleno/
MIT License
180 stars 60 forks source link

(Non Issue) Page With IFrames #154

Open 99sono opened 10 years ago

99sono commented 10 years ago

Hi, I have a doubt concerning the modeling of pages that are composed by SubPages which are included in the form of IFrames.

For example, Imagine that you have page that has in the main two relevant components.

(a) It has a typical left side menu, that you can click here and there to affect the page content.

(b) It has an iframe where the page content of the menu option that you choose is displayed.

What this means is, if we forget about the modeling of this design, is that if you open a debugger console on the page (e.g. chrome F12), when you want to interact with an element of the menu (e.g. to nagivate to another page / load new page content), you could do so by directly targetting the page element. E.g. jQuery("#idOfMenuItem").click(); This would work.

On the other hand, if you were trying to interact with the page content, that is in the iframe. Then, instead, you would have to come up with messier JQuery. Namely, you would have to: jQuery( jQuery("#myIframIdWithPageCotent")[0].contentWindow.document ).find("#elementWithinIFramTahtIWantToInteractWith")

So, if this how All the pages are organized (potentially there being more iframes within the page content ....

What would be a tidy way to organize this thing? And namely to encapsulate the call to the webDriver.switchTo("iframe").

I imagine that (A) we need an abstraction for the Overall template:


-- OVERALL TEMPLATE

MyGenericStructurePage : Page Where T : ContentPage {

// Every Page Has A Left Menu LeftMenu LeftMenu { get { return GetComponent(); } }

// Every Page has page content that is specific to itself // I am not yet familar with C# declarion of abstract methos ... but yout get the point abstract T getContent(); }


-- IMPLEMENTATION CLASS

Later, we might decide to code a page for managing users

class MangeUsersPage : MyGenericStructurePage {

ManageUsersContentPage getContent(){ return GetComponent(ManageUsersContentPage ); } }


And finally, there is the fact that each interaction with things outside the page content, such as within the left menu component is done in the main window. However, every action within the page content, e.g. would have to be switched to the IFrame. getContent().addUser(UserModel);

Is there an elegant way of doing this, or not really? Thank you for any suggestion.

MehdiK commented 10 years ago

For menus I personally design my pages like you mentioned: a base class for the page layout (or master page) which has components for each shared UI component, and then subclasses for each page object that inherits from that master/layout. Then you'd have access to menus from each page through inheritance as if the menus are defined on the page without having to duplicate the menu code.

With regards to your frame issue, to get Seleno/Selenium to work with frames you have to explicitly tell it to. In your case, in your base class you can create a SwitchTo method that gets called whenever there is need to switch to another frame (more on this shortly). To implement the SwitchTo method you can use the Browser protected property exposed from UiComponent and call it as Browser.SwitchTo().Frame(/*frame name or index*/).

You may want to keep track of the active frame yourself (using an ActiveFrame field/property on the base class). Each page would also have to know which frame it's on (perhaps through an abstract PageFrame property). Then whenever there is a call to a method/property on the page, you check if the active frame is the frame the page is on, and if not SwitchTo. You can do the "routing" on each property/method on each page, or much better you can deal with it as an aspect injected into all your methods and properties and let it take care of itself - that way your page objects look just like normal page objects as if there are no frames (and hopefully make your life easier when you get rid of your frames ;)).

Hope this helps.

99sono commented 10 years ago

Thank you for the feedback. Concerning the switchto, that is what i have been using, but indeed putting at the start of each method a switch to, and at the end a return to default() is a bit anoying. AOP would fit nicely to adress this, though the assumption that after switching to an Iframe one should revert to the default windows is not gonna work once more than one level of IFrames exist. Then it would be necessary to carry a stack of Previous active IFRame to know to which one we should revert at the end of the method.

Also, I have question that you may know how to answer:

The Selenium documentation wiki considers both the PageObject design pattern, and also a second design pattern called LoadableComponent. This second Pattern, however, it appears to me that it cannot really be consiliated with the "smart factory"/"builder"/"dependency injection" pattern. Am I wrong? Could one use both the loadable component design pattern along with the GetComponent()?

My thoughts: In this pattern each page offers a isLoaded() and Load() method. The load() method would be called whenever the page object would determine itself not to be currently loaded. However, and this is the deal breaker, the load() method of a given page object may depend, typically does, on a parent page. Where typically: (a) I am not loaded, then (b) I ask my parent to loadItself parent.get(); (c) once my parent is loaded I know how to load myself (navigate from parent to the desired page object).

But because of step (b), our target page cannot be constiructed by a builder engine, because the builder could never know which page object is the parent of our target page (at least for any page where there is the possibility to navigate form more than one parent).

Of course, if in every page we were able to say my Parent has this concrete type, than the builder could probably construct our target page because it would know how to fill in the parent attribute.

So, and truning back to the initial question, the loadableComponent design pattern is something that would typically force the developer out of using a builder correct? It is better to leave without loadbale components and have simple test cases that take us to the desired page, than have a page object that we can just ask get().

Thanks for your input.

MehdiK commented 10 years ago

I am honestly not quite sure how LoadableComponent could help you (on dealing with iframes).

99sono commented 10 years ago

With dealing with iframes it would not help me. This is more of a framework approach question when implementing UI Testing. In the Seleno framework, I believe that we always follow a from this page onto the next page approach. Where within each page object with which we deal, we shall have an API that will allow us to navigate onto the next page.

However, with the loadable component design, each PageObject encapsulates within itself the knowledge of how to get itself loaded onto the screen. And my question is simply weather or not this loadble component design pattern is compatible with a "Component Builder" framework? Such as the "autofact" one that seleno uses to build the UI components.

MehdiK commented 10 years ago

The Loadable Component pattern is definitely compatible with IoC containers (e.g. autofac) and somewhat with Seleno: you can add the methods to a base component class and implement them in the page object classes.

That all said I don't like the pattern for a few reasons:

When I load the login page
Then I load the homepage 

When I search for "something"
and load the shopping cart
and load the checkout page
Then I load the order page

Instead I have:

When I navigate to the login page
And submit my credentials 
Then I arrive at the homepage

When I search for "something"
And add it to my shopping cart
And check out
Then I get my order"

One might argue I don't have to word my acceptance criteria based on my implementation details; but that's what page objects are for me: they encapsulate the logic required for automating a page as stated in business terms. In fact IMO one should be able to reverse engineer the acceptance criteria by reading the UI test script using reflection!

This got a bit too philosophical but I hope it makes sense.

99sono commented 10 years ago

Once again thank you for your constructive reply. I completely understand and agree with your arguments. It makes more sense to navigate the story from the begining to the end, than to start at the end and load the beginning.

With that said, you say that the LooadableComponent pattern is completely compatible with the IoC approach. In that case, can you please clarify how build a new component can be constructed from an IoC container when the component to be built declares an injectable attribute called Parent, such that the parent Type is generic (e.g. Page).

How does the container decide which Page, out of the many possible page implementations, to inject into the component being built?

I think that it cannot. A factory can only inject components into injectable attributes if the container can also identify the correct instance to put in there. When you have multiple alternatives, you create an unsolvable problem for the builder.

When you are deploying an application such that you have multiple alternatives for an injectable attribute, you typically resolve the ambiguity by informing the container which implementation out of the many alternatives is the right one.

But again, I might be wrong, but If I am, I want to see what I am missing.

As always, I am very grateful for your support.

MehdiK commented 10 years ago

With that said, you say that the LooadableComponent pattern is completely compatible with the IoC approach.

The pattern is compatible but you may have some trouble trying to retrofit the existing LoadableComponent from Selenium into Seleno. What I meant is that you can implement it yourself in your page objects. After all it's just two simple methods: Load and IsLoaded.


In that case, can you please clarify how build a new component can be constructed from an IoC container when the component to be built declares an injectable attribute called Parent, such that the parent Type is generic (e.g. Page).

How does the container decide which Page, out of the many possible page implementations, to inject into the component being built?

I am not sure I understand your question properly. If you give me a code snippet, or better a repo, I may be able to help you better.