I am using Selenium Page factory and I want to make certain extensions to it, but can't as they are sealed, so I want to write my own custom logic.
Question :
How does PageFactory.InitElements work so that all the properties loads their values when they are getting used and not when this method is called..
So, To Explain it with an example
//// Button on page load
[FindsBy(How = How.CssSelector, Using = "#lst-ib")]
public IWebElement Btn;
//// Button redirecting to Page 2
[FindsBy(How = How.CssSelector, Using = "#lst-ib")]
public IWebElement LinkBtn;
////Button on second page
[FindsBy(How=How.CssSelector, Using = "#rso > div:nth-child(1) > div > div:nth-child(1) > div > div > h3 > a")]
public IWebElement NewBtn;
So, the beauty of their page factory is that all the elements are loaded but they convert to webelements when they are in use, beacause if all the properties are assigned values on initilisation, NewBtn Property would always fail, as it is on page 2 .
So, what concept they might be using of initializing properties so they are assigned on usage and not at the run time , any dummy code would be great and appreciated to understand
Thanks in Advance
I have got absolutely no experience in C# but I checked the source code of the C# selenium implementation and seems pretty much identical to the Java code.
PageFactory.cs - This class provides the overall framework of how the elements are initialized.
The initElements() gets all the fields in the pageobject. For each field gets the annotation on them. Then it creates a Proxy for each field.
Then it stores the actual locator to be used. The actual call to findElement or findElements is inside the Invoke method of the proxy.
This is all handled by two classes - DefaultPageObjectMemberDecorator.cs and DefaultElementLocator.cs. There are two proxies which handle invocations - WebElementProxy.cs and WebElementListProxy.cs
So the custom logic that you want to write could be done by creating new classes which implement the appropriate interfaces. Then pass these new classes into the appropriate initElement() method of PageFactory class.
For a detailed understanding look at the Decorate() method of DefaultPageObjectMemberDecorator.cs class.
When creating the page object, the "page object logic" (IMO, from my experience and what I have understood so far from c# and selenium) it expects all the elements mentioned in an element map to exist in the DOM.
If your NewBtn exists in the dome but is hidden (and becomes visible when navigating to second page, basically when the page does not get refreshed and there is some ajax to it), then it stands to reason that the element map won't have any problem handling the element. You just have to assure that the element is visible with an IF statement or add a wait (to see if you are indeed to the second page).
If the page gets refreshed when navigating to the second page, then simply re-initialize your page object with new MyPageObject() so that all elements get mapped again, to avoid StaleElement and/or ElementNotFound exceptions etc.
Related
In my effort of trying to find a neat way to validate which buttons are visible to the user based on his Role and Permissions i have encountered a problem. I am using the Page Object Model design pattern with selenium to test a website. A few pages have a different set of buttons which should either be visible or not to the user according to his role.
I keep a Dictionary<Permission,IWebElement> and initialize it in the constructor of the page (class representing a certain page in the site).
All web elements are defined as follows:
private IWebElement btn_openShop => driver.ById("open_shop");
(ById is equivalent to FindsElement(By.Id("open_shop"))
The problem is that if the button shouldn't exist an exception is thrown when adding it to the Dictionary.
Note: moving the initialization of the Dictionary wont help since i test both cases (one in which the user should see the button and one in which he shouldn't).
I changed the Dictionary to be of type <Permission,Lazy<IWebElement>>
and added items to it as follows:
dictionary.Add(somePermission,new Lazy<IWebElement>(()=>the button))
Edit: this technique works but it seems that when debugging in visual studio the code crashes.
Any thoughts? (no exceptions are thrown during test execution).
Here is a little background on the specifications of my project:
We use Specflow and Microsoft CodedUI Framework for UI Automation
I have built a PageFactory that combines three Abstract Base Classes : BasePage, BaseMap, and BaseValidator that all Maps, Pages, and Validators inherit
Our Application that we are automating has numerous workflows that make defined HTML Controls have different InnerText Values (HTMLComboBoxes for example)
Everything is and needs to be abstracted from the actual Specflow Test Code in the Page Object Pattern, no unique code can exist within a Specflow Step
In my Maps I have certain controls like a combobox that has an InnerText change if a certain workflow is selected. I need to build assertion and verification statements to make sure the InnerText is correct for the workflow that is selected. This is not a problem. However, I do not want to just define a new variable for every InnerText change(There are A LOT).
Is there any way I can account for the InnerText variations in the Page Object Pattern and not have to code a new variable for every single one?
Here is an example of a Map Entry:
public HtmlComboBox NextActionControlDropDownList()
{
var NextActionControlDropDownList = new PropertyExpressionCollection {
new PropertyExpression(HtmlComboBox.PropertyNames.Id, "MEDCHARTContent_EmmpsContent_nextActionControl_ActionDropDownList", PropertyExpressionOperator.EqualTo)
};
return Window.Find<HtmlComboBox>(NextActionControlDropDownList);
}
This is the Base Control definition. It can also be this:
public HtmlComboBox NextActionControlARFormalComplReview()
{
var NextActionControlARFormalComplReview = new PropertyExpressionCollection {
new PropertyExpression(HtmlComboBox.PropertyNames.Id, "MEDCHARTContent_EmmpsContent_nextActionControl_ActionDropDownList", PropertyExpressionOperator.EqualTo),
new PropertyExpression(HtmlComboBox.PropertyNames.InnerText, "--Select Action-- Return to USARC ", PropertyExpressionOperator.EqualTo)
};
return Window.Find<HtmlComboBox>(NextActionControlARFormalComplReview);
}
My thoughts so far were to maybe make another map and inherit it? But that wouldn't solve my initial problem of too many variables for a single control. I don't see how If statements would help either because it needs to be defined for the framework to find the control. Maybe I could store the differing values in a collection of sorts and have a parameter key value to access them... but that seems like I would run into a lot of issues.
If you try and see the methods under PropertyExpressionOperator you would see something called Contains.
new PropertyExpression(HtmlComboBox.PropertyNames.InnerText, "--Select Action--", PropertyExpressionOperator.Contains)
I'm trying to do something my teacher says can't be done; I would like to prove him wrong.
In the CreateChildControls method of my SharePoint 2010 webpart, I am referencing a User Control file called "ChartUserControl.ascx" in my project that contains the ASP.NET code for a WebChartControl object configured just the way I want it. WebChartControl has an ID of "OrderQtyChart".
What I want to do is take the code from that UserControl and use it create a new WebChartControl, called "chart", with matching configuration. I'm trying to do this because there are callbacks etc. that need to be performed on the chart after it's created to actually populate it with chart-stuff.
So, my code:
WebChartControl chart;
protected override void CreateChildControls()
{
ChartUserControl userControl = new ChartUserControl();
// referencing file ChartUserControl.ascx as an object
chart = userControl.FindControl("OrderQtyChart") as WebChartControl;
// or
chart = (WebChartControl)userControl.FindControl("OrderQtyChart");
// Trying to tell the code to create 'chart' using the code defined in object
"OrderQtyChart" located in ChartUserControl.ascx
}
Or something like that. In either instance above, 'chart' will return null.
I'm trying to use the front end code of OrderQtyChart as a template for 'chart'; they're both the same type of object and I don't get any errors until I try to create 'chart' on my page, at which point I'm told it's null.
Is there a way to do this? It would save me a ton of time not to have to configure 'chart' completely at creation time. Even if I have to reference my front-end code for OrderQtyChart a different way.
Thanks.
[Edited 7/9 for clarity]
What you are trying to do seems very well possible and I assume your teacher did not understand your question correctly. Here are a few tips on how this is done:
Object A could be one of these:
A visual control such as label or textbox. In this case your will have to traverse the visual controls from parent to child by doing direct parent.FindControl("ObjectA");
It is an instance of a class. This might be a MyClass or a new textbox that is created by code. In this case you'll have to create a public property that has a getter which returns ObjectA. although you can use FindControl in case ObjectA is a UI component that is created and added dynamically at run-time. Otherwise, you'll have to stick with property.
FindControl will not traverse the parent to child hierarchy, so you'll have to do a recursive method in order to successfully find the ObjectA or if you have access to its direct parent, call FindControl on that. More info here: http://geekswithblogs.net/QuandaryPhase/archive/2009/05/06/asp.net-recursive-findcontrol-amp-extension-methods.aspx
Page life cycle plays an important role here, so make sure that you keep it in mind or you'll end up with a null reference that is not really caused by FindControl
Gah, never mind. I realized I can just call the user control directly and I'm seriously overcomplicating this.
That's a whole new question, so I'll just start a different thread.
I need to hook into the Creating event in Orchard CMS and use the Title of the content that's being created (Along with some other properties) to create an item in a 3rd party system and return the ID of that to set in the content type in Orchard.
I have a custom content type, but when I try to hook into the events (as explained in the docs here and also by looking at code on the built in Orchard Core content parts) all properties are null.
Are they just lazy loaded? Is there a way to populate them? Overriding any of the shape methods (GetItemMetadata / BuildDisplayShape / BuildEditorShape / UpdateEditorShape) doesn't seem right as this should only fire when initially creating the content type.
My code is:
public MyContentPartHandler(IRepository<MyContentPartRecord> repository, IOrchardServices orchardServices, Lazy<IMyContentPartService> myContentPartService) {
_orchardServices = orchardServices;
_myContentPartService = myContentPartService;
Filters.Add(StorageFilter.For(repository));
OnCreating<MyContentPart>(CreateTPItemAndAssignIdentity);
}
protected void CreateTPItemAndAssignIdentity(CreateContentContext context, MyContentPart part)
{
//create item in 3rd party system
var item = _myContentPartService.Value.CreateNewItem(part.Title, part.Path);
part.ExternalIdentity = item.FriendlyId;
}
The CreateNewItem() method fails as the part.Title and part.Path are null. Do I need to try and get at the Record? (I thought not, as the Orchard CMS record has not been created at that point)
UPDATE - I have also tried to use the OnCreated event instead, but I run into the same problem with the properties not being populated. I place a breakpoint in the code, and noticed that when the OnCreated breakpoint was hit - the data did not actually exist in the database at that time.
OnCreating/OnCreated events are fired when an item object is constructed, not populated with data.
If you want to be sure that data is already there, use either OnPublished event (fired after item has been updated) or OnLoading/OnLoaded events if you want to run some code after an item has been fully loaded from database.
This description of certain events might also be helpful.
I am writing custom property for umbraco that counts hits on document.
Is there any way how can I hook up my logic to page when document is loading?
Example:
I have structure:
Home
About me
Both have my custom Property.
I would like to record when they have been loaded.
Note: I do not want to use any javascript, or actions on page. I would like to have it as one custom DLL.
Solution that I have chosen is:
use IHttpModule as umbraco does not have any event that can be used for this.
As to configuration:
I have decided dynamicaly hook up my module after application start.
Thnx all for considering my question.
One way to do it would to have a separate db table specifically for page hits, e.g. [PageHit] which had two columns [NodeId] and [HitCount].
A hit on a page either created a new record if no hits had previously been recorded, or incremented the count on an existing record.
You would need to ensure that the hit was recorded per page, so recording it in the your base Masterpage or in a base Controller would be my approach.
You could then have a property based upon a custom datatype that looked up the hit count for the specific node that the property existed upon. The custom datatype could be a usercontrol, and since in the Umbraco backoffice when you load a node in the content editor the node's ID is in the query of the URL, you can access this in the usercontrol to query the database.
Using a separate table allows you to separate things into a separate DLL too as you would not need to be reliant on the Umbraco API as you could use an ORM like PetaPoco or basic ADO.
I certainly wouldn't write directly back to the node itself as this would cause an unnecessary load on the database, causing latency on the site's page loads and generally slow things down.
If you want to keep this data inside the Umbraco backend, you'd need to create an usercontrol and embed that control in the masterpage (top masterpage);
In the usercontrol add these usings:
using umbraco.cms.businesslogic.web;
using umbraco.BusinessLogic;
using umbraco.presentation.nodeFactory;
And a method similar to this:
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack) // don't want to count postbacks
{
Document nodeCount = new Document(Node.GetCurrent().Id);
int Count = 0;
try
{
Count = Convert.ToInt32(nodeCount.getProperty("hitCount").Value);
}
catch
{
Count = 0; // value == null, not set yet
}
nodeCount.getProperty("hitCount").Value = Count + 1;
nodeCount.Save();
nodeCount.Publish(new User(0));
umbraco.library.UpdateDocumentCache(nodeCount.Id);
}
PS this is written without testing.. just of the top of my head