diff --git a/dotnet/src/support/PageObjects/DefaultElementLocator.cs b/dotnet/src/support/PageObjects/DefaultElementLocator.cs
new file mode 100644
index 0000000000000..6aba1e6a78d55
--- /dev/null
+++ b/dotnet/src/support/PageObjects/DefaultElementLocator.cs
@@ -0,0 +1,252 @@
+using OpenQA.Selenium.Internal;
+using OpenQA.Selenium.Support.PageObjects.Interfaces;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reflection;
+using OpenQA.Selenium.Support.UI;
+using System.Text;
+
+namespace OpenQA.Selenium.Support.PageObjects
+{
+ ///
+ /// The default element locator, which will lazily locate an element or an element list on a page. This class is
+ /// designed for use with the and uses
+ /// attributes and .
+ ///
+ public class DefaultElementLocator: IElementLocator, IWrapsDriver
+ {
+ protected readonly MemberInfo Member;
+ protected readonly ISearchContext SearchContext;
+ protected readonly SearchParameterContainer SearchParameters;
+ private readonly IAdjustableByTimeSpan TimeOutContainer;
+ private IWebElement CachedElement;
+ private ReadOnlyCollection CachedElementCollection;
+ private readonly bool ShouldCacheLookUp;
+
+
+ public DefaultElementLocator(MemberInfo member, ISearchContext searchContext, IAdjustableByTimeSpan timeOutContainer)
+ {
+ this.Member = member;
+ this.SearchContext = searchContext;
+ this.TimeOutContainer = timeOutContainer;
+ this.SearchParameters = new SearchParameterContainer();
+ this.SearchParameters.Context = this.SearchContext;
+ this.SearchParameters.TheGivenBys = CreateBys();
+ ShouldCacheLookUp = ShouldCacheLookup();
+ }
+
+ ///
+ /// An algorimthm which describes the waiting mechanism
+ ///
+ /// A read only collection of s which are found.
+ /// If there is no relevant elements when the result is null
+ private static Func> FindElements()
+ {
+ return (parameterContainer) => {
+ ISearchContext context = parameterContainer.Context;
+ ReadOnlyCollection bys = parameterContainer.TheGivenBys;
+ try
+ {
+ List collection = new List();
+ foreach (var by in bys)
+ {
+ ReadOnlyCollection list = context.FindElements(by);
+ collection.AddRange(list);
+ }
+ if (collection.Count > 0)
+ {
+ return collection.AsReadOnly();
+ }
+ return null;
+ }
+ catch (StaleElementReferenceException)
+ {
+ return null;
+ }
+
+ };
+ }
+
+ ///
+ ///
+ /// IWebdriver instatnce which is wrapped by the given search context. This method could be overriden
+ public IWebDriver WrappedDriver
+ {
+ get {
+ IWebDriver driver = SearchContext as IWebDriver;
+ if (driver != null)
+ {
+ return driver;
+ }
+ //RemoteWebElement implements IWrapsDriver and it is ISearchContext too
+ //So we can get IWebDriver from the element
+ //There can be something that wraps the original element
+ IWrapsElement wrapsElement = SearchContext as IWrapsElement;
+ IWebElement element = SearchContext as IWebElement;
+
+ while (wrapsElement != null)
+ {
+ element = ((IWrapsElement) element).WrappedElement;
+ wrapsElement = element as IWrapsElement;
+ }
+
+ //so we get to the original RemoteWebElement instance
+ return ((IWrapsDriver) element).WrappedDriver;
+ }
+ }
+
+ ///
+ /// This method creates strategies according to attributes
+ /// which mark the target field. If it is nesessary you can make your own
+ /// subclass of DefaultElementLocator and override this method
+ ///
+ ///
+ protected virtual ReadOnlyCollection CreateBys()
+ {
+ var useSequenceAttributes = Attribute.GetCustomAttributes(Member, typeof(FindsBySequenceAttribute), true);
+ bool useSequence = useSequenceAttributes.Length > 0;
+
+ List bys = new List();
+ var attributes = Attribute.GetCustomAttributes(Member, typeof(FindsByAttribute), true);
+ if (attributes.Length > 0)
+ {
+ Array.Sort(attributes);
+ foreach (var attribute in attributes)
+ {
+ var castedAttribute = (FindsByAttribute)attribute;
+ if (castedAttribute.Using == null)
+ {
+ castedAttribute.Using = Member.Name;
+ }
+
+ bys.Add(castedAttribute.Finder);
+ }
+
+ if (useSequence)
+ {
+ ByChained chained = new ByChained(bys.ToArray());
+ bys.Clear();
+ bys.Add(chained);
+ }
+ }
+
+ if (attributes.Length == 0)
+ {
+ bys.Add(new ByIdOrName(Member.Name));
+ }
+ return bys.AsReadOnly();
+ }
+
+ public IWebElement Element
+ {
+ get
+ {
+ if (CachedElement != null && ShouldCacheLookUp)
+ {
+ return CachedElement;
+ }
+ ReadOnlyCollection result = WaitFor();
+ if (result.Count == 0)
+ {
+ throw new NoSuchElementException("Cann't locate an element by this strategies: " + SearchParameters.TheGivenBys.ToString());
+ }
+
+ if (ShouldCacheLookUp)
+ {
+ CachedElement = result[0];
+ }
+ return result[0];
+ }
+ }
+
+ ///
+ /// This method checks the presence of
+ /// attribute. Field or class are checked.
+ ///
+ /// true if the given field or declaring class have attribute . False is returned otherwise.
+ private bool ShouldCacheLookup()
+ {
+ var cacheAttributeType = typeof(CacheLookupAttribute);
+ bool cache = Member.GetCustomAttributes(cacheAttributeType, true).Length != 0 ||
+ Member.DeclaringType.GetCustomAttributes(cacheAttributeType, true).Length != 0;
+ return cache;
+ }
+
+ private ReadOnlyCollection WaitFor()
+ {
+ IWebDriver driver = WrappedDriver;
+ ITimeouts timeOuts = driver.Manage().Timeouts();
+
+ DefaultWait wait = new DefaultWait(SearchParameters);
+ wait.Timeout = TimeOutContainer.WaitingTimeSpan;
+ wait.PollingInterval = TimeOutContainer.TimeForSleeping;
+
+ timeOuts.ImplicitlyWait(TimeSpan.MinValue);
+ ReadOnlyCollection result;
+ try
+ {
+ result = wait.Until(FindElements());
+ }
+ catch (WebDriverTimeoutException)
+ {
+ result = new ReadOnlyCollection(new List());
+ }
+ finally
+ {
+ timeOuts.ImplicitlyWait(TimeOutContainer.WaitingTimeSpan);
+ }
+ return result;
+ }
+
+ public ReadOnlyCollection Elements
+ {
+ get
+ {
+ if (CachedElementCollection != null && ShouldCacheLookUp)
+ {
+ return CachedElementCollection;
+ }
+
+ ReadOnlyCollection result = WaitFor();
+
+ if (ShouldCacheLookUp)
+ {
+ CachedElementCollection = result;
+ }
+ return result;
+ }
+
+ }
+
+ public class SearchParameterContainer
+ {
+ private ReadOnlyCollection Bys;
+ private ISearchContext SearchContext;
+
+ public ReadOnlyCollection TheGivenBys
+ {
+ set
+ {
+ Bys = value;
+ }
+ get
+ {
+ return Bys;
+ }
+ }
+ public ISearchContext Context
+ {
+ set
+ {
+ SearchContext = value;
+ }
+ get
+ {
+ return SearchContext;
+ }
+ }
+ }
+}
+}
diff --git a/dotnet/src/support/PageObjects/DefaultElementLocatorFactory.cs b/dotnet/src/support/PageObjects/DefaultElementLocatorFactory.cs
index 8c8770f9e8680..14c18e7817299 100644
--- a/dotnet/src/support/PageObjects/DefaultElementLocatorFactory.cs
+++ b/dotnet/src/support/PageObjects/DefaultElementLocatorFactory.cs
@@ -26,6 +26,7 @@ namespace OpenQA.Selenium.Support.PageObjects
/// A default locator for elements for use with the . This locator
/// implements no retry logic for elements not being found, nor for elements being stale.
///
+ [Obsolete("It is supposed to be removed. It is better to use OpenQA.Selenium.Support.PageObjects.DefaultLocatorFactory")]
public class DefaultElementLocatorFactory : IElementLocatorFactory
{
///
diff --git a/dotnet/src/support/PageObjects/DefaultFieldDecorator.cs b/dotnet/src/support/PageObjects/DefaultFieldDecorator.cs
new file mode 100644
index 0000000000000..0c8102bb9bd63
--- /dev/null
+++ b/dotnet/src/support/PageObjects/DefaultFieldDecorator.cs
@@ -0,0 +1,211 @@
+using OpenQA.Selenium.Internal;
+using OpenQA.Selenium.Remote;
+using OpenQA.Selenium.Support.PageObjects.Interfaces;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace OpenQA.Selenium.Support.PageObjects
+{
+ ///
+ /// Default decorator for use with . Will decorate 1) all of the
+ /// fields/properties and 2) IList of fields/properties that have
+ /// , ,
+ /// attributes with a proxy that locates the elements using the passed
+ /// in .
+ ///
+ /// Note!!! Your own attributes and are appreciated.
+ ///
+ public class DefaultFieldDecorator: IFieldDecorator, IAdjustableByTimeSpan
+ {
+
+ private readonly ILocatorFactory Factory;
+ private readonly List InterfacesThatCanBeProxiedAsWebElement = new List();
+
+ ///
+ /// This decorator uses ILocatorFactory instance in order to
+ /// populate fields / set properties
+ ///
+ ///
+ public DefaultFieldDecorator(ILocatorFactory factory)
+ {
+ if (factory == null)
+ {
+ throw new ArgumentNullException("The given Locator factory should not be NULL!");
+ }
+ this.Factory = factory;
+ InterfacesThatCanBeProxiedAsWebElement.AddRange(typeof(RemoteWebElement).GetInterfaces());
+ InterfacesThatCanBeProxiedAsWebElement.Add(typeof(IWrapsElement));
+ }
+
+ ///
+ /// This method can be used by external for Selenium projects where IWebElement implementors
+ /// implement more interfaces than it does RemoteWebElement
+ ///
+ /// Additional interfaces which have to be proxied. They should be related to
+ /// IWebElement implentors at external for Selenium projects
+ public void AddInterfacesToByProxied(List interfacesToByProxied)
+ {
+ foreach (var type in interfacesToByProxied)
+ {
+ if (type.IsInterface)
+ {
+ continue;
+ }
+ throw new ArgumentException("One of given types is not interface. It is " + type.Name, "interfacesToByProxied");
+ }
+ InterfacesThatCanBeProxiedAsWebElement.AddRange(interfacesToByProxied);
+ }
+
+ ///
+ /// This decorator returns values if :
+ /// - The declared type of field or property is IWebElement or any interface implemented by
+ /// - The declared type of field or property is IList. The declared
+ /// generic parameter type should be IWebElement or any interface implemented by .
+ ///
+ ///
+ ///
+ public object Decorate(MemberInfo member)
+ {
+ return CreateProxy(member);
+ }
+
+ ///
+ /// This method creates proxies in order to populate
+ /// fields/set properties of page object. It can be overridden when it is needed.
+ ///
+ ///
+ ///
+ protected virtual Object CreateProxy(MemberInfo member)
+ {
+ FieldInfo field = member as FieldInfo;
+ PropertyInfo property = member as PropertyInfo;
+
+ Type targetType = null;
+ if (field != null)
+ {
+ targetType = field.FieldType;
+ }
+
+ bool hasPropertySet = false;
+ if (property != null)
+ {
+ hasPropertySet = (property.CanWrite);
+ targetType = property.PropertyType;
+ }
+
+ if (field == null & (property == null | !hasPropertySet))
+ {
+ return null;
+ }
+
+ IElementLocator locator = Factory.CreateElementLocator(member);
+
+ if (InterfacesThatCanBeProxiedAsWebElement.Contains(targetType))
+ {
+ ElementProxy ep = new ElementProxy(CreateTypeForASingleElement(), locator);
+ return ep.GetTransparentProxy();
+ }
+
+ foreach (var type in InterfacesThatCanBeProxiedAsWebElement)
+ {
+ Type listType = typeof(IList<>).MakeGenericType(type);
+ if (listType.Equals(targetType))
+ {
+
+ ElementListProxy ep = new ElementListProxy(targetType, locator);
+ return ep.GetTransparentProxy();
+ }
+ }
+ return null;
+ }
+
+ ///
+ /// This property gets/sets waiting time at if the given
+ /// implements .
+ /// Otherwise it does nothing (set) and returns TimeSpan.MinVlue.
+ ///
+ public TimeSpan WaitingTimeSpan
+ {
+ get
+ {
+ IAdjustableByTimeSpan adjustableByTimeSpan = Factory as IAdjustableByTimeSpan;
+ if (adjustableByTimeSpan != null)
+ {
+ return adjustableByTimeSpan.WaitingTimeSpan;
+ }
+ return TimeSpan.MinValue;
+ }
+ set
+ {
+ IAdjustableByTimeSpan adjustableByTimeSpan = Factory as IAdjustableByTimeSpan;
+ if (adjustableByTimeSpan != null)
+ {
+ adjustableByTimeSpan.WaitingTimeSpan = value;
+ }
+ }
+ }
+
+ ///
+ /// This property gets/sets sleeping (polling) time at if the given
+ /// implements .
+ /// Otherwise it does nothing (set) and returns TimeSpan.MinVlue.
+ ///
+ public TimeSpan TimeForSleeping
+ {
+ get
+ {
+ IAdjustableByTimeSpan adjustableByTimeSpan = Factory as IAdjustableByTimeSpan;
+ if (adjustableByTimeSpan != null)
+ {
+ return adjustableByTimeSpan.TimeForSleeping;
+ }
+ return TimeSpan.MinValue;
+ }
+ set
+ {
+ IAdjustableByTimeSpan adjustableByTimeSpan = Factory as IAdjustableByTimeSpan;
+ if (adjustableByTimeSpan != null)
+ {
+ adjustableByTimeSpan.TimeForSleeping = value;
+ }
+ }
+ }
+
+ /// True if if the given
+ /// implements .
+ public bool IsAdjustableByTimeSpan()
+ {
+ return ((Factory as IAdjustableByTimeSpan) != null);
+ }
+
+ private Type CreateTypeForASingleElement()
+ {
+ var orginalAssemblyName = typeof(IWebElement).Assembly.GetName().Name;
+ ModuleBuilder moduleBuilder;
+
+ var tempAssemblyName = new AssemblyName(Guid.NewGuid().ToString());
+
+ var dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
+ tempAssemblyName,
+ System.Reflection.Emit.AssemblyBuilderAccess.RunAndCollect);
+
+ moduleBuilder = dynamicAssembly.DefineDynamicModule(
+ tempAssemblyName.Name,
+ tempAssemblyName + ".dll");
+
+ var typeBuilder = moduleBuilder.DefineType(
+ typeof(IWebElement).FullName,
+ TypeAttributes.Public | TypeAttributes.Interface | TypeAttributes.Abstract);
+
+ foreach (Type type in InterfacesThatCanBeProxiedAsWebElement)
+ {
+ typeBuilder.AddInterfaceImplementation(type);
+ }
+
+ return typeBuilder.CreateType();
+ }
+ }
+}
diff --git a/dotnet/src/support/PageObjects/DefaultLocatorFactory.cs b/dotnet/src/support/PageObjects/DefaultLocatorFactory.cs
new file mode 100644
index 0000000000000..dde667af19973
--- /dev/null
+++ b/dotnet/src/support/PageObjects/DefaultLocatorFactory.cs
@@ -0,0 +1,81 @@
+using OpenQA.Selenium.Support.PageObjects.Interfaces;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace OpenQA.Selenium.Support.PageObjects
+{
+ ///
+ /// The default ILocatorFactory implementor
+ ///
+ public class DefaultLocatorFactory: ILocatorFactory, IAdjustableByTimeSpan
+ {
+
+ private static readonly TimeSpan DefaultWaitingTime = TimeSpan.FromSeconds(5);
+ private static readonly TimeSpan DefaultSleepingTime = TimeSpan.FromMilliseconds(500);
+
+ protected TimeSpan WaitingTime;
+ protected TimeSpan SleepingTime;
+ protected readonly ISearchContext SearchContext;
+
+ ///
+ /// It uses the given ISearchContext (IWebdriver or IWebElement) instance and
+ /// the waiting/sleeping timeouts
+ ///
+ /// IWebdriver or IWebElement instance
+ /// The waiting timeout
+ /// The sleeping/polling timeout
+ public DefaultLocatorFactory(ISearchContext searchContext, TimeSpan waitingTime, TimeSpan sleepingTime)
+ {
+ this.SearchContext = searchContext;
+ this.WaitingTime = waitingTime;
+ this.SleepingTime = sleepingTime;
+ }
+
+ ///
+ /// It uses the given ISearchContext (IWebdriver or IWebElement) instance. The default waiting
+ /// timeout is 5 seconds. The default sleeping/polling timeout is 500 milliseconds.
+ ///
+ /// IWebdriver or IWebElement instance
+ public DefaultLocatorFactory(ISearchContext searchContext)
+ :this(searchContext, DefaultWaitingTime, DefaultSleepingTime)
+ {
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public IElementLocator CreateElementLocator(MemberInfo member)
+ {
+ return new DefaultElementLocator(member, SearchContext, this);
+ }
+
+ public TimeSpan WaitingTimeSpan
+ {
+ get
+ {
+ return WaitingTime;
+ }
+ set
+ {
+ WaitingTime = value;
+ }
+ }
+
+ public TimeSpan TimeForSleeping
+ {
+ get
+ {
+ return SleepingTime;
+ }
+ set
+ {
+ SleepingTime = value;
+ }
+ }
+ }
+}
diff --git a/dotnet/src/support/PageObjects/ElementListProxy.cs b/dotnet/src/support/PageObjects/ElementListProxy.cs
new file mode 100644
index 0000000000000..b566dc4c85e01
--- /dev/null
+++ b/dotnet/src/support/PageObjects/ElementListProxy.cs
@@ -0,0 +1,23 @@
+using OpenQA.Selenium.Support.PageObjects.Interfaces;
+using System;
+using System.Reflection;
+using System.Runtime.Remoting.Messaging;
+
+namespace OpenQA.Selenium.Support.PageObjects
+{
+ ///
+ /// Intercepts the request to a collection of 's
+ ///
+ sealed class ElementListProxy: Proxy
+ {
+ public ElementListProxy(Type typeToBeProxied, IElementLocator elementLocator)
+ :base(typeToBeProxied, elementLocator)
+ {}
+
+ public override IMessage Invoke(IMessage msg)
+ {
+ var elements = ElementLocator.Elements;
+ return base.Execute(msg as IMethodCallMessage, elements);
+ }
+ }
+}
diff --git a/dotnet/src/support/PageObjects/ElementProxy.cs b/dotnet/src/support/PageObjects/ElementProxy.cs
new file mode 100644
index 0000000000000..7c1d47816e8ef
--- /dev/null
+++ b/dotnet/src/support/PageObjects/ElementProxy.cs
@@ -0,0 +1,32 @@
+using OpenQA.Selenium.Support.PageObjects.Interfaces;
+using System;
+using System.Reflection;
+using OpenQA.Selenium.Internal;
+using System.Runtime.Remoting.Messaging;
+
+namespace OpenQA.Selenium.Support.PageObjects
+{
+ ///
+ /// Intercepts the request to a single
+ ///
+ sealed class ElementProxy : Proxy
+ {
+ public ElementProxy(Type typeToBeProxied, IElementLocator elementLocator)
+ : base(typeToBeProxied, elementLocator)
+ {}
+
+ public override IMessage Invoke(IMessage msg)
+ {
+ var element = ElementLocator.Element;
+ IMethodCallMessage call = msg as IMethodCallMessage;
+
+ if (typeof(IWrapsElement).IsAssignableFrom((call.MethodBase as MethodInfo).DeclaringType))
+ {
+ return new ReturnMessage(element, null, 0,
+ call.LogicalCallContext, call);
+ }
+ return base.Execute(call, element);
+
+ }
+ }
+}
diff --git a/dotnet/src/support/PageObjects/IElementLocatorFactory.cs b/dotnet/src/support/PageObjects/IElementLocatorFactory.cs
index 8f9a5ea5cc2a7..ef328b1f30380 100644
--- a/dotnet/src/support/PageObjects/IElementLocatorFactory.cs
+++ b/dotnet/src/support/PageObjects/IElementLocatorFactory.cs
@@ -25,6 +25,7 @@ namespace OpenQA.Selenium.Support.PageObjects
///
/// Interface describing how elements are to be located by a
///
+ [Obsolete("It is supposed to be removed", true)]
public interface IElementLocatorFactory
{
///
diff --git a/dotnet/src/support/PageObjects/Interfaces/IAdjustableByTimeSpan.cs b/dotnet/src/support/PageObjects/Interfaces/IAdjustableByTimeSpan.cs
new file mode 100644
index 0000000000000..96ad414b90c42
--- /dev/null
+++ b/dotnet/src/support/PageObjects/Interfaces/IAdjustableByTimeSpan.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace OpenQA.Selenium.Support.PageObjects.Interfaces
+{
+ ///
+ /// This interface is for entities which require
+ /// to change their behavior by the given value.
+ /// It is expected that they can wait for something.
+ ///
+ public interface IAdjustableByTimeSpan
+ {
+ ///
+ /// This property should get or set a timeout for the waiting.
+ ///
+ TimeSpan WaitingTimeSpan
+ {
+ set;
+ get;
+ }
+
+ ///
+ /// This property should get or set a timeout for the sleeping.
+ ///
+ TimeSpan TimeForSleeping
+ {
+ set;
+ get;
+ }
+ }
+}
diff --git a/dotnet/src/support/PageObjects/Interfaces/IElementLocator.cs b/dotnet/src/support/PageObjects/Interfaces/IElementLocator.cs
new file mode 100644
index 0000000000000..aa2d02499e59f
--- /dev/null
+++ b/dotnet/src/support/PageObjects/Interfaces/IElementLocator.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+
+namespace OpenQA.Selenium.Support.PageObjects.Interfaces
+{
+ ///
+ ///
+ ///
+ public interface IElementLocator
+ {
+ ///
+ /// This property should return a single IWebElement instance
+ ///
+ IWebElement Element { get; }
+ ///
+ /// This property should return a read-only collection of IWebElement instances
+ ///
+ ReadOnlyCollection Elements { get; }
+ }
+}
diff --git a/dotnet/src/support/PageObjects/Interfaces/IFieldDecorator.cs b/dotnet/src/support/PageObjects/Interfaces/IFieldDecorator.cs
new file mode 100644
index 0000000000000..8c018bfc628ba
--- /dev/null
+++ b/dotnet/src/support/PageObjects/Interfaces/IFieldDecorator.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace OpenQA.Selenium.Support.PageObjects.Interfaces
+{
+ ///
+ ///Allows the PageFactory to decorate fields.
+ ///
+ public interface IFieldDecorator
+ {
+ ///
+ /// This method is called by PageFactory on all fields to decide how to decorate the field.
+ ///
+ /// It is a a field OR property that is supposed to be decorated
+ /// An object that should be set up as member value
+ Object Decorate(MemberInfo member);
+ }
+}
diff --git a/dotnet/src/support/PageObjects/Interfaces/ILocatorFactory.cs b/dotnet/src/support/PageObjects/Interfaces/ILocatorFactory.cs
new file mode 100644
index 0000000000000..2278c37144992
--- /dev/null
+++ b/dotnet/src/support/PageObjects/Interfaces/ILocatorFactory.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace OpenQA.Selenium.Support.PageObjects.Interfaces
+{
+ ///
+ /// A factory for producing s. It is expected that a new instance will be
+ /// returned per call.
+ ///
+ public interface ILocatorFactory
+ {
+ ///
+ /// When a field/property of a class needs to be decorated with an this method will
+ /// be called.
+ ///
+ /// a field OR property that should be filled
+ /// An instance
+ IElementLocator CreateElementLocator(MemberInfo member);
+ }
+}
diff --git a/dotnet/src/support/PageObjects/PageFactory.cs b/dotnet/src/support/PageObjects/PageFactory.cs
index 0cd186da4c8da..e420c31fa83ba 100644
--- a/dotnet/src/support/PageObjects/PageFactory.cs
+++ b/dotnet/src/support/PageObjects/PageFactory.cs
@@ -23,6 +23,7 @@
using System.Reflection;
using OpenQA.Selenium.Interactions.Internal;
using OpenQA.Selenium.Internal;
+using OpenQA.Selenium.Support.PageObjects.Interfaces;
namespace OpenQA.Selenium.Support.PageObjects
{
@@ -52,13 +53,10 @@ private PageFactory()
///
///
/// thrown if no constructor to the class can be found with a single IWebDriver argument
- /// -or-
- /// if a field or property decorated with the is not of type
- /// or IList{IWebElement}.
///
public static T InitElements(IWebDriver driver)
{
- return InitElements(driver, new DefaultElementLocatorFactory());
+ return InitElements(driver, new DefaultLocatorFactory(driver));
}
///
@@ -66,7 +64,7 @@ public static T InitElements(IWebDriver driver)
///
/// The of the Page Object class.
/// The instance used to populate the page.
- /// The implementation that
+ /// The implementation that
/// determines how elements are located.
/// An instance of the Page Object class with the elements initialized.
///
@@ -77,11 +75,8 @@ public static T InitElements(IWebDriver driver)
///
///
/// thrown if no constructor to the class can be found with a single IWebDriver argument
- /// -or-
- /// if a field or property decorated with the is not of type
- /// or IList{IWebElement}.
///
- public static T InitElements(IWebDriver driver, IElementLocatorFactory locatorFactory)
+ public static T InitElements(IWebDriver driver, ILocatorFactory locatorFactory)
{
T page = default(T);
Type pageClassType = typeof(T);
@@ -92,24 +87,132 @@ public static T InitElements(IWebDriver driver, IElementLocatorFactory locato
}
page = (T)ctor.Invoke(new object[] { driver });
- InitElements(driver, page, locatorFactory);
+ InitElements(page, locatorFactory);
return page;
}
///
/// Initializes the elements in the Page Object.
///
- /// The driver used to find elements on the page.
+ /// The IWebDriver or IWebElement implementation used to find elements on the page.
+ /// The Page Object to be populated with elements.
+ public static void InitElements(ISearchContext searchContext, object page)
+ {
+ InitElements(page, new DefaultLocatorFactory(searchContext));
+ }
+
+ ///
+ /// Initializes the elements in the Page Object.
+ ///
+ /// The Page Object to be populated with elements.
+ /// The implementation that
+ /// determines how elements are located.
+ public static void InitElements(object page, ILocatorFactory locatorFactory)
+ {
+ if (page == null)
+ {
+ throw new ArgumentNullException("page", "page cannot be null");
+ }
+
+ if (locatorFactory == null)
+ {
+ throw new ArgumentNullException("locatorFactory", "locatorFactory cannot be null");
+ }
+
+ InitElements(new DefaultFieldDecorator(locatorFactory), page);
+ }
+
+ ///
+ /// Initializes the elements in the Page Object.
+ ///
/// The Page Object to be populated with elements.
+ /// The implementation that
+ /// decorates IWebElement or IList of IWebElement fields .
+ public static void InitElements(IFieldDecorator decorator, object page)
+ {
+ if (decorator == null)
+ {
+ throw new ArgumentNullException("decorator", "decorator cannot be null");
+ }
+
+ if (page == null)
+ {
+ throw new ArgumentNullException("page", "page cannot be null");
+ }
+
+ var members = new List();
+ var type = page.GetType();
+ const BindingFlags PublicBindingOptions = BindingFlags.Instance | BindingFlags.Public;
+ const BindingFlags NonPublicBindingOptions = BindingFlags.Instance | BindingFlags.NonPublic;
+
+ members.AddRange(type.GetFields(PublicBindingOptions));
+ members.AddRange(type.GetProperties(PublicBindingOptions));
+
+ while (type != null)
+ {
+ members.AddRange(type.GetFields(NonPublicBindingOptions));
+ members.AddRange(type.GetProperties(NonPublicBindingOptions));
+ type = type.BaseType;
+ }
+
+ foreach (var member in members){
+ var field = member as FieldInfo;
+ var property = member as PropertyInfo;
+
+ var value = decorator.Decorate(member);
+
+ if (value == null){
+ continue;
+ }
+
+ if (field != null){
+ field.SetValue(page, value);
+ }
+
+ if (property != null)
+ {
+ property.SetValue(page, value, null);
+ }
+ }
+ }
+
+ #region ObsolateAPI
+ ///
+ /// Initializes the elements in the Page Object with the given type.
+ ///
+ /// The of the Page Object class.
+ /// The instance used to populate the page.
+ /// The implementation that
+ /// determines how elements are located.
+ /// An instance of the Page Object class with the elements initialized.
+ ///
+ /// The class used in the argument must have a public constructor
+ /// that takes a single argument of type . This helps to enforce
+ /// best practices of the Page Object pattern, and encapsulates the driver into the Page
+ /// Object so that it can have no external WebDriver dependencies.
+ ///
///
- /// thrown if a field or property decorated with the is not of type
+ /// thrown if no constructor to the class can be found with a single IWebDriver argument
+ /// -or-
+ /// if a field or property decorated with the is not of type
/// or IList{IWebElement}.
///
- public static void InitElements(ISearchContext driver, object page)
+ [Obsolete("It is supposed to be removed. It is better to use InitElements(object page, ILocatorFactory locatorFactory)")]
+ public static T InitElements(IWebDriver driver, IElementLocatorFactory locatorFactory)
{
- InitElements(driver, page, new DefaultElementLocatorFactory());
+ T page = default(T);
+ Type pageClassType = typeof(T);
+ ConstructorInfo ctor = pageClassType.GetConstructor(new Type[] { typeof(IWebDriver) });
+ if (ctor == null)
+ {
+ throw new ArgumentException("No constructor for the specified class containing a single argument of type IWebDriver can be found");
+ }
+
+ page = (T)ctor.Invoke(new object[] { driver });
+ InitElements(driver, page, locatorFactory);
+ return page;
}
-
+
///
/// Initializes the elements in the Page Object.
///
@@ -121,6 +224,7 @@ public static void InitElements(ISearchContext driver, object page)
/// thrown if a field or property decorated with the is not of type
/// or IList{IWebElement}.
///
+ [Obsolete("It is supposed to be removed.")]
public static void InitElements(ISearchContext driver, object page, IElementLocatorFactory locatorFactory)
{
if (page == null)
@@ -156,7 +260,7 @@ public static void InitElements(ISearchContext driver, object page, IElementLoca
if (bys.Count > 0)
{
bool cache = ShouldCacheLookup(member);
-
+
object proxyObject = null;
var field = member as FieldInfo;
var property = member as PropertyInfo;
@@ -184,6 +288,7 @@ public static void InitElements(ISearchContext driver, object page, IElementLoca
}
}
+ [Obsolete("It is supposed to be removed.")]
private static List CreateLocatorList(MemberInfo member)
{
var useSequenceAttributes = Attribute.GetCustomAttributes(member, typeof(FindsBySequenceAttribute), true);
@@ -216,6 +321,7 @@ private static List CreateLocatorList(MemberInfo member)
return bys;
}
+ [Obsolete("It is supposed to be removed.")]
private static bool ShouldCacheLookup(MemberInfo member)
{
var cacheAttributeType = typeof(CacheLookupAttribute);
@@ -223,6 +329,7 @@ private static bool ShouldCacheLookup(MemberInfo member)
return cache;
}
+ [Obsolete("It is supposed to be removed.")]
private static object CreateProxyObject(Type memberType, ISearchContext driver, List bys, bool cache, IElementLocatorFactory locatorFactory)
{
object proxyObject = null;
@@ -237,5 +344,7 @@ private static object CreateProxyObject(Type memberType, ISearchContext driver,
return proxyObject;
}
+
+ #endregion ObsolateAPI
}
}
diff --git a/dotnet/src/support/PageObjects/Proxy.cs b/dotnet/src/support/PageObjects/Proxy.cs
new file mode 100644
index 0000000000000..0771b3d733bae
--- /dev/null
+++ b/dotnet/src/support/PageObjects/Proxy.cs
@@ -0,0 +1,34 @@
+using OpenQA.Selenium.Support.PageObjects.Interfaces;
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Runtime.Remoting.Messaging;
+using System.Runtime.Remoting.Proxies;
+
+namespace OpenQA.Selenium.Support.PageObjects
+{
+ abstract class Proxy: RealProxy
+ {
+ protected readonly IElementLocator ElementLocator;
+
+ public Proxy(Type typeToBeProxied, IElementLocator elementLocator)
+ :base(typeToBeProxied)
+ {
+ this.ElementLocator = elementLocator;
+ }
+
+ ///
+ /// This is the common behavior of proxy.
+ /// The method will be used or overridden by subclasses
+ ///
+ /// This is container of method invocation parameters
+ /// This the real proxied object
+ /// The ReturnMessage instance as a result of proxied method invocation.
+ protected ReturnMessage Execute(IMethodCallMessage msg, object proxied)
+ {
+ MethodInfo proxiedMethod = msg.MethodBase as MethodInfo;
+ return new ReturnMessage(proxiedMethod.Invoke(proxied, msg.Args), null, 0,
+ msg.LogicalCallContext, msg);
+ }
+ }
+}
diff --git a/dotnet/src/support/PageObjects/RetryingElementLocatorFactory.cs b/dotnet/src/support/PageObjects/RetryingElementLocatorFactory.cs
index b2597094c3741..8b9ad2869d052 100644
--- a/dotnet/src/support/PageObjects/RetryingElementLocatorFactory.cs
+++ b/dotnet/src/support/PageObjects/RetryingElementLocatorFactory.cs
@@ -27,6 +27,7 @@ namespace OpenQA.Selenium.Support.PageObjects
/// A locator for elements for use with the that retries locating
/// the element up to a timeout if the element is not found.
///
+ [Obsolete("It is supposed to be removed. It is better to use OpenQA.Selenium.Support.PageObjects.DefaultLocatorFactory")]
public class RetryingElementLocatorFactory : IElementLocatorFactory
{
private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(5);
@@ -140,7 +141,7 @@ public ReadOnlyCollection LocateElements(ISearchContext searchConte
collection.AddRange(list);
}
- timeoutReached = collection.Count == 0 && DateTime.Now > endTime;
+ timeoutReached = collection.Count != 0 || DateTime.Now > endTime;
if (!timeoutReached)
{
Thread.Sleep(this.pollingInterval);
diff --git a/dotnet/src/support/PageObjects/WebElementListProxy.cs b/dotnet/src/support/PageObjects/WebElementListProxy.cs
index 0a29f4f592fbe..f12210faf053f 100644
--- a/dotnet/src/support/PageObjects/WebElementListProxy.cs
+++ b/dotnet/src/support/PageObjects/WebElementListProxy.cs
@@ -27,6 +27,7 @@ namespace OpenQA.Selenium.Support.PageObjects
///
/// Represents a proxy class for a list of elements to be used with the PageFactory.
///
+ [Obsolete("It is supposed to be removed")]
internal class WebElementListProxy : IList
{
private readonly IElementLocatorFactory locatorFactory;
diff --git a/dotnet/src/support/PageObjects/WebElementProxy.cs b/dotnet/src/support/PageObjects/WebElementProxy.cs
index c0f9491a8158f..3e312f28a6998 100644
--- a/dotnet/src/support/PageObjects/WebElementProxy.cs
+++ b/dotnet/src/support/PageObjects/WebElementProxy.cs
@@ -24,11 +24,13 @@
using OpenQA.Selenium.Interactions.Internal;
using OpenQA.Selenium.Internal;
+
namespace OpenQA.Selenium.Support.PageObjects
{
///
/// Represents a proxy class for an element to be used with the PageFactory.
///
+ [Obsolete("It is supposed to be removed")]
internal class WebElementProxy : IWebElement, ILocatable, IWrapsElement
{
private readonly IElementLocatorFactory locatorFactory;
diff --git a/dotnet/src/support/WebDriver.Support.csproj b/dotnet/src/support/WebDriver.Support.csproj
index 98bdadb6ae277..9493a8c8b5acb 100644
--- a/dotnet/src/support/WebDriver.Support.csproj
+++ b/dotnet/src/support/WebDriver.Support.csproj
@@ -79,15 +79,25 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/test/support/PageObjects/DefaultLocatorTest.cs b/dotnet/test/support/PageObjects/DefaultLocatorTest.cs
new file mode 100644
index 0000000000000..2ee0c3964bea1
--- /dev/null
+++ b/dotnet/test/support/PageObjects/DefaultLocatorTest.cs
@@ -0,0 +1,95 @@
+using NMock2;
+using NUnit.Framework;
+using OpenQA.Selenium.Support.PageObjects.Interfaces;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace OpenQA.Selenium.Support.PageObjects
+{
+ [TestFixture]
+ public class DefaultLocatorTest
+ {
+ [FindsBy(How = How.Id, Using = "SomeId", Priority = 0)]
+ [FindsBy(How = How.ClassName, Using = "SomeClass", Priority = 1)]
+ [FindsBy(How = How.TagName, Using = "SomeTag", Priority = 2)]
+ public IWebElement F1;
+
+ [FindsBySequence]
+ [FindsBy(How = How.Id, Using = "SomeId", Priority = 0)]
+ [FindsBy(How = How.TagName, Using = "SomeTag", Priority = 2)]
+ [FindsBy(How = How.ClassName, Using = "SomeClass", Priority = 1)]
+ public IWebElement F2;
+
+ public IWebElement F3;
+
+ private static Mockery mocks = new Mockery();
+ private static ISearchContext mockDriver = mocks.NewMock();
+ private static TestLocatorFactory factory = new TestLocatorFactory(mockDriver);
+
+ [Test]
+ public void ShouldReturnCollectionOfBys()
+ {
+ FieldInfo f1 = this.GetType().GetField("F1");
+ TestLocator tl = (TestLocator)factory.CreateElementLocator(f1);
+ IList result = tl.getSearchParameters().TheGivenBys;
+ Assert.AreEqual(3, result.Count);
+
+ Assert.IsTrue(result[0].ToString().Contains("SomeId"));
+ Assert.IsTrue(result[1].ToString().Contains("SomeClass"));
+ Assert.IsTrue(result[2].ToString().Contains("SomeTag"));
+ }
+
+ [Test]
+ public void ShouldReturnByChained()
+ {
+ FieldInfo f2 = this.GetType().GetField("F2");
+ TestLocator t2 = (TestLocator)factory.CreateElementLocator(f2);
+ IList result = t2.getSearchParameters().TheGivenBys;
+ Assert.AreEqual(1, result.Count);
+ Assert.IsTrue(result[0] as ByChained != null);
+ }
+
+ [Test]
+ public void ShouldReturnByIdOrName()
+ {
+ FieldInfo f3 = this.GetType().GetField("F3");
+ TestLocator t3 = (TestLocator)factory.CreateElementLocator(f3);
+ IList result = t3.getSearchParameters().TheGivenBys;
+ Assert.AreEqual(1, result.Count);
+ Assert.IsTrue(result[0] as ByIdOrName != null);
+ Assert.IsTrue(result[0].ToString().Contains("F3"));
+ }
+ }
+
+ public class TestLocator : DefaultElementLocator
+ {
+
+ public TestLocator(MemberInfo member, ISearchContext searchContext, IAdjustableByTimeSpan timeOutContainer)
+ :base(member, searchContext, timeOutContainer)
+ {}
+
+ public SearchParameterContainer getSearchParameters()
+ {
+ return SearchParameters;
+ }
+ }
+
+ public class TestLocatorFactory : DefaultLocatorFactory
+ {
+ public TestLocatorFactory(ISearchContext searchContext)
+ :base(searchContext)
+ {
+ }
+
+ public IElementLocator CreateElementLocator(MemberInfo member)
+ {
+ return new TestLocator(member, SearchContext, this);
+ }
+ }
+
+
+}
diff --git a/dotnet/test/support/PageObjects/FieldPopulationByDefaultFieldDecoratorTest.cs b/dotnet/test/support/PageObjects/FieldPopulationByDefaultFieldDecoratorTest.cs
new file mode 100644
index 0000000000000..13cc19a4ec417
--- /dev/null
+++ b/dotnet/test/support/PageObjects/FieldPopulationByDefaultFieldDecoratorTest.cs
@@ -0,0 +1,125 @@
+using NMock2;
+using NUnit.Framework;
+using OpenQA.Selenium.Internal;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace OpenQA.Selenium.Support.PageObjects
+{
+ [TestFixture]
+ public class FieldPopulationByDefaultFieldDecoratorTest
+ {
+ private static Mockery mocks = new Mockery();
+ private ISearchContext mockDriver = mocks.NewMock();
+
+ [FindsBy(How = How.Id, Using = "SomeId")]
+ private IWebElement mockElement1;
+
+ [FindsBy(How = How.Id, Using = "SomeId")]
+ private IWebDriver mockElement2;
+
+ [FindsBy(How = How.Id, Using = "SomeId")]
+ private IFindsById mockElement3;
+
+ [FindsBy(How = How.Id, Using = "SomeId")]
+ private IList mockElements1;
+
+ [FindsBy(How = How.Id, Using = "SomeId")]
+ private IList mockElements2;
+
+ [FindsBy(How = How.Id, Using = "SomeId")]
+ private IList mockElements3;
+
+ private IWebDriver unmarkedField2;
+
+ private IWebElement unmarkedField1;
+
+ private IFindsById unmarkedField3;
+
+ private IList unmarkedField5;
+
+ private IList unmarkedField4;
+
+ private IList unmarkedField6;
+
+ [SetUp]
+ public void SetUp()
+ {
+ PageFactory.InitElements(mockDriver, this);
+ }
+
+ [Test]
+ public void IsPopulatedMarkedWebElementField()
+ {
+ Assert.NotNull(mockElement1);
+ }
+
+ [Test]
+ public void IsNotPopulatedMarkedNotWebElementField()
+ {
+ Assert.IsNull(mockElement2);
+ }
+
+ [Test]
+ public void IsPopulatedMarkedFieldWithInterfaceImplementedByRemoteWebElementent()
+ {
+ Assert.NotNull(mockElement3);
+ }
+
+ [Test]
+ public void IsPopulatedMarkedListOfWebElementField()
+ {
+ Assert.NotNull(mockElements1);
+ }
+
+ [Test]
+ public void IsNotPopulatedMarkedNotWebElementListField()
+ {
+ Assert.IsNull(mockElements2);
+ }
+
+ [Test]
+ public void IsPopulatedMarkedListFieldWithInterfaceImplementedByRemoteWebElementent()
+ {
+ Assert.NotNull(mockElements3);
+ }
+
+ [Test(Description = "At this case ByIdOrName locator strategy should be used")]
+ public void IsPopulatedWebElementFieldWithoutAttributes()
+ {
+ Assert.NotNull(unmarkedField1);
+ }
+
+ [Test]
+ public void IsNotPopulatedNotWebElementFieldWithoutAttributes()
+ {
+ Assert.IsNull(unmarkedField2);
+ }
+
+ [Test(Description = "At this case ByIdOrName locator strategy should be used")]
+ public void IsPopulatedFieldWithInterfaceImplementedByRemoteWebElemententWithoutAttributes()
+ {
+ Assert.NotNull(unmarkedField3);
+ }
+
+ [Test(Description = "At this case ByIdOrName locator strategy should be used")]
+ public void IsPopulatedListOfWebElementFieldWithoutAttributes()
+ {
+ Assert.NotNull(unmarkedField6);
+ }
+
+ [Test]
+ public void IsNotPopulatedNotWebElementListFieldWithoutAttributes()
+ {
+ Assert.IsNull(unmarkedField5);
+ }
+
+ [Test(Description = "At this case ByIdOrName locator strategy should be used")]
+ public void IsPopulatedListFieldWithInterfaceImplementedByRemoteWebElemententWithoutAttributes()
+ {
+ Assert.NotNull(unmarkedField4);
+ }
+ }
+}
diff --git a/dotnet/test/support/PageObjects/PageFactoryBrowserTest.cs b/dotnet/test/support/PageObjects/PageFactoryBrowserTest.cs
index 86d337fec6929..3480e25e21f9f 100644
--- a/dotnet/test/support/PageObjects/PageFactoryBrowserTest.cs
+++ b/dotnet/test/support/PageObjects/PageFactoryBrowserTest.cs
@@ -5,6 +5,7 @@
using OpenQA.Selenium.Support.UI;
using System.Collections.ObjectModel;
using System;
+using OpenQA.Selenium.Internal;
namespace OpenQA.Selenium.Support.PageObjects
{
@@ -35,33 +36,53 @@ public void LooksUpAgainAfterPageNavigation()
driver.Navigate().Refresh();
- Assert.True(page.formElement.Displayed);
+ Assert.True(page.formTestElement.Displayed);
+ }
+
+ [Test]
+ public void CheckThatListIsFoundByIdOrName()
+ {
+ driver.Url = xhtmlTestPage;
+ var page = new Page();
+
+ PageFactory.InitElements(driver, page);
+ Assert.GreaterOrEqual(1, page.someForm.Count);
+ }
+
+ [Test]
+ public void CheckThatElementIsFoundByIdOrName()
+ {
+ driver.Url = xhtmlTestPage;
+ var page = new Page();
+
+ PageFactory.InitElements(driver, page);
+ Assert.IsTrue(page.parent.Displayed);
}
[Test]
public void ElementEqualityWorks()
{
driver.Url = xhtmlTestPage;
- var page = new PageFactoryTest.Page();
+ var page = new Page();
PageFactory.InitElements(driver, page);
var expectedElement = driver.FindElement(By.Name("someForm"));
+ var result = ((IWrapsElement)page.formTestElement).WrappedElement;
- Assert.True(page.formElement.Equals(expectedElement));
- Assert.True(expectedElement.Equals(page.formElement));
- Assert.AreEqual(expectedElement.GetHashCode(), page.formElement.GetHashCode());
+ Assert.True(result.Equals(expectedElement));
+ Assert.AreEqual(expectedElement.GetHashCode(), result.GetHashCode());
}
[Test]
public void UsesElementAsScriptArgument()
{
driver.Url = xhtmlTestPage;
- var page = new PageFactoryTest.Page();
+ var page = new Page();
PageFactory.InitElements(driver, page);
- var tagName = (string)((IJavaScriptExecutor)driver).ExecuteScript("return arguments[0].tagName", page.formElement);
+ var tagName = (string)((IJavaScriptExecutor)driver).ExecuteScript("return arguments[0].tagName", page.formTestElement);
Assert.AreEqual("form", tagName.ToLower());
}
@@ -105,7 +126,10 @@ public void ShouldFindElementUsingSequence()
private class Page
{
[FindsBy(How = How.Name, Using = "someForm")]
- public IWebElement formElement;
+ public IWebElement formTestElement;
+
+ public IList someForm;
+ public IWebElement parent;
[FindsBySequence]
[FindsBy(How = How.Id, Using = "parent", Priority = 0)]
diff --git a/dotnet/test/support/PageObjects/PageFactoryTest.cs b/dotnet/test/support/PageObjects/PageFactoryTest.cs
deleted file mode 100644
index 3565855d7770f..0000000000000
--- a/dotnet/test/support/PageObjects/PageFactoryTest.cs
+++ /dev/null
@@ -1,493 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using NMock2;
-using NUnit.Framework;
-
-namespace OpenQA.Selenium.Support.PageObjects
-{
- [TestFixture]
- public class PageFactoryTest
- {
- private Mockery mocks;
- private ISearchContext mockDriver;
- private IWebElement mockElement;
-
- [SetUp]
- public void SetUp()
- {
- mocks = new Mockery();
- mockDriver = mocks.NewMock();
- mockElement = mocks.NewMock();
- }
-
- [TearDown]
- public void TearDown()
- {
- mocks.VerifyAllExpectationsHaveBeenMet();
- }
-
- [Test]
- public void ElementShouldBeNullUntilInitElementsCalled()
- {
- var page = new Page();
-
- Assert.Null(page.formElement);
-
- PageFactory.InitElements(mockDriver, page);
- Assert.NotNull(page.formElement);
- }
-
- [Test]
- public void ElementShouldBeAbleToUseGenericVersionOfInitElements()
- {
- IWebDriver driver = mocks.NewMock();
- var page = PageFactory.InitElements(driver);
- Assert.IsInstanceOf(page);
- Assert.NotNull(page.formElement);
- }
-
- [Test]
- public void FindsElement()
- {
- var page = new Page();
- AssertFindsElementByExactlyOneLookup(page, () => page.formElement);
- }
-
- [Test]
- public void FindsElementEachAccess()
- {
- var page = new Page();
-
- AssertFindsElementByExactlyOneLookup(page, () => page.formElement);
- mocks.VerifyAllExpectationsHaveBeenMet();
-
- ExpectOneLookup();
- AssertFoundElement(page.formElement);
- }
-
- [Test]
- public void FindsPrivateElement()
- {
- var page = new PrivatePage();
- AssertFindsElementByExactlyOneLookup(page, page.GetField);
- }
-
- [Test]
- public void FindsPropertyElement()
- {
- var page = new ElementAsPropertyPage();
- AssertFindsElementByExactlyOneLookup(page, () => page.FormElement);
- }
-
- [Test]
- public void FindsElementByNameIfUsingIsAbsent()
- {
- ExpectOneLookup();
-
- var page = new PageWithNameWithoutUsing();
- AssertFindsElement(page, () => page.someForm);
- }
-
- [Test]
- public void FindsElementByIdIfUsingIsAbsent()
- {
- Expect.Once.On(mockElement).GetProperty("TagName").Will(Return.Value("form"));
- Expect.Once.On(mockDriver).Method("FindElement").With(By.Id("someForm")).Will(Return.Value(mockElement));
-
- var page = new PageWithIdWithoutUsing();
- AssertFindsElement(page, () => page.someForm);
- }
-
- [Test]
- public void FindsParentAndChildElement()
- {
- ExpectOneLookup();
- var mockElement2 = mocks.NewMock();
- Expect.Once.On(mockDriver).Method("FindElement").With(By.TagName("body")).Will(Return.Value(mockElement2));
- Expect.Once.On(mockElement2).GetProperty("TagName").Will(Return.Value("body"));
-
- var page = new ChildPage();
-
- AssertFindsElement(page, () => page.formElement);
- AssertFoundElement(page.childElement, "body");
- }
-
- [Test]
- public void LooksUpPrivateFieldInSuperClass()
- {
- var page = new SubClassToPrivatePage();
- AssertFindsElementByExactlyOneLookup(page, page.GetField);
- }
-
- [Test]
- public void LooksUpOverridenVirtualParentClassElement()
- {
- ExpectOneLookup();
-
- var page = new AbstractChild();
- AssertFindsElement(page, () => page.element);
- }
-
- [Test]
- public void FallsBackOnOtherLocatorsOnFailure()
- {
- Expect.Once.On(mockDriver).Method("FindElement").With(By.Name("notthisname")).Will(Throw.Exception(new NoSuchElementException()));
- Expect.Once.On(mockDriver).Method("FindElement").With(By.TagName("form")).Will(Return.Value(mockElement));
- Expect.Never.On(mockDriver).Method("FindElement").With(By.Id("notthiseither")).Will(Return.Value(mockElement));
-
- Expect.Once.On(mockElement).GetProperty("TagName").Will(Return.Value("form"));
-
- var page = new FallsbackPage();
- PageFactory.InitElements(mockDriver, page);
-
- AssertFoundElement(page.formElement);
- }
-
- [Test]
- public void ThrowsIfAllLocatorsFail()
- {
- Expect.Once.On(mockDriver).Method("FindElement").With(By.Name("notthisname")).Will(Throw.Exception(new NoSuchElementException()));
- Expect.Once.On(mockDriver).Method("FindElement").With(By.TagName("notthiseither")).Will(Throw.Exception(new NoSuchElementException()));
- Expect.Once.On(mockDriver).Method("FindElement").With(By.Id("stillnotthis")).Will(Throw.Exception(new NoSuchElementException()));
-
- var page = new FailsToFallbackPage();
- PageFactory.InitElements(mockDriver, page);
-
- Assert.Throws(typeof(NoSuchElementException), page.formElement.Clear);
- }
-
- [Test]
- public void CachesElement()
- {
- Expect.Once.On(mockDriver).Method("FindElement").With(By.Name("someForm")).Will(Return.Value(mockElement));
- Expect.Exactly(2).On(mockElement).GetProperty("TagName").Will(Return.Value("form"));
-
- var page = new CachedPage();
-
- AssertFindsElement(page, () => page.formElement);
- AssertFoundElement(page.formElement);
- }
-
- [Test]
- public void CachesIfClassMarkedCachedElement()
- {
- Expect.Once.On(mockDriver).Method("FindElement").With(By.Name("someForm")).Will(Return.Value(mockElement));
- Expect.Exactly(2).On(mockElement).GetProperty("TagName").Will(Return.Value("form"));
-
- var page = new CachedClassPage();
-
- AssertFindsElement(page, () => page.formElement);
- AssertFoundElement(page.formElement);
- }
-
- [Test]
- public void UsingCustomBy()
- {
- Expect.Exactly(1).On(mockDriver).Method("FindElement").With(new CustomBy("customCriteria")).Will(Return.Value(mockElement));
- Expect.Exactly(1).On(mockElement).GetProperty("TagName").Will(Return.Value("form"));
-
- var page = new CustomByPage();
-
- AssertFindsElement(page, () => page.customFoundElement);
- }
-
- [Test]
- public void UsingCustomByNotFound()
- {
- Expect.Once.On(mockDriver).Method("FindElement").With(new CustomBy("customCriteriaNotFound")).Will(Throw.Exception(new NoSuchElementException()));
-
- var page = new CustomByNotFoundPage();
- PageFactory.InitElements(mockDriver, page);
- Assert.Throws(typeof(NoSuchElementException), page.customFoundElement.Clear);
- }
-
- [Test]
- [ExpectedException(typeof(ArgumentException), ExpectedMessage = "descendent of", MatchType = MessageMatch.Contains)]
- public void UsingCustomByWithInvalidSuperClass()
- {
- var page = new InvalidCustomFinderTypePage();
- PageFactory.InitElements(mockDriver, page);
- }
-
- [Test]
- [ExpectedException(typeof(ArgumentException), ExpectedMessage = "How.Custom", MatchType = MessageMatch.Contains)]
- public void UsingCustomByWithNoClass()
- {
- var page = new NoCustomFinderClassPage();
- PageFactory.InitElements(mockDriver, page);
- }
-
- [Test]
- [ExpectedException(typeof(ArgumentException), ExpectedMessage = "constructor", MatchType = MessageMatch.Contains)]
- public void UsingCustomByWithInvalidCtor()
- {
- var page = new InvalidCtorCustomByPage();
- PageFactory.InitElements(mockDriver, page);
- }
-
- [Test]
- [ExpectedException(typeof(ArgumentException), ExpectedMessage = "is not IWebElement or IList", MatchType = MessageMatch.Contains)]
- public void ThrowsIfElementTypeIsInvalid()
- {
- var page = new InvalidElementTypePage();
- PageFactory.InitElements(mockDriver, page);
- }
-
- [Test]
- [ExpectedException(typeof(ArgumentException), ExpectedMessage = "is not IWebElement or IList", MatchType = MessageMatch.Contains)]
- public void ThrowsIfElementCollectionTypeIsInvalid()
- {
- var page = new InvalidCollectionTypePage();
- PageFactory.InitElements(mockDriver, page);
- }
-
- [Test]
- [ExpectedException(typeof(ArgumentException), ExpectedMessage = "is not IWebElement or IList", MatchType = MessageMatch.Contains)]
- public void ThrowsIfConcreteCollectionTypeIsUsed()
- {
- var page = new ConcreteCollectionTypePage();
- PageFactory.InitElements(mockDriver, page);
- }
-
- #region Test helper methods
-
- private void ExpectOneLookup()
- {
- Expect.Exactly(1).On(mockDriver).Method("FindElement").With(By.Name("someForm")).Will(Return.Value(mockElement));
- Expect.Exactly(1).On(mockElement).GetProperty("TagName").Will(Return.Value("form"));
- }
-
- ///
- /// Asserts that getElement yields an element from page which can be interacted with, by exactly one element lookup
- ///
- private void AssertFindsElementByExactlyOneLookup(object page, Func getElement)
- {
- ExpectOneLookup();
- AssertFindsElement(page, getElement);
- }
-
- ///
- /// Asserts that getElement yields an element which can be interacted with, with no constraints on element lookups
- ///
- private void AssertFindsElement(object page, Func getElement)
- {
- PageFactory.InitElements(mockDriver, page);
-
- AssertFoundElement(getElement());
- }
-
- ///
- /// Asserts that the element has been found and can be interacted with
- ///
- private static void AssertFoundElement(IWebElement element)
- {
- AssertFoundElement(element, "form");
- }
-
- ///
- /// Asserts that the element has been found and has the tagName passed
- ///
- private static void AssertFoundElement(IWebElement element, string tagName)
- {
- Assert.AreEqual(tagName, element.TagName.ToLower());
- }
-
- #endregion
-
- #region Page classes for tests
- #pragma warning disable 649 //We set fields through reflection, so expect an always-null warning
-
- internal class Page
- {
- [FindsBy(How = How.Name, Using = "someForm")]
- public IWebElement formElement;
- }
-
- internal class PageWithNameWithoutUsing
- {
- [FindsBy(How = How.Name)]
- public IWebElement someForm;
- }
-
- internal class PageWithIdWithoutUsing
- {
- [FindsBy(How = How.Id)]
- public IWebElement someForm;
- }
-
- private class PrivatePage
- {
- [FindsBy(How = How.Name, Using = "someForm")]
- private IWebElement formElement;
-
- public IWebElement GetField()
- {
- return formElement;
- }
- }
-
- private class ChildPage : Page
- {
- [FindsBy(How = How.TagName, Using = "body")]
- public IWebElement childElement;
- }
-
- private class SubClassToPrivatePage : PrivatePage
- {
-
- }
-
- private class AbstractParent
- {
- [FindsBy(How = How.Name, Using = "someForm")]
- public virtual IWebElement element { get; set; }
- }
-
- private class AbstractChild : AbstractParent
- {
- public override IWebElement element { get; set; }
- }
-
- private class FallsbackPage
- {
- [FindsBy(How = How.Name, Using = "notthisname", Priority = 0)]
- [FindsBy(How = How.TagName, Using = "form", Priority = 1)]
- [FindsBy(How = How.Id, Using = "notthiseither", Priority = 2)]
- public IWebElement formElement;
- }
-
- private class FailsToFallbackPage
- {
- [FindsBy(How = How.Name, Using = "notthisname", Priority = 0)]
- [FindsBy(How = How.TagName, Using = "notthiseither", Priority = 1)]
- [FindsBy(How = How.Id, Using = "stillnotthis", Priority = 2)]
- public IWebElement formElement;
- }
-
- private class ElementAsPropertyPage
- {
- [FindsBy(How = How.Name, Using = "someForm")]
- public IWebElement FormElement { get; set; }
- }
-
- private class CachedPage
- {
- [FindsBy(How = How.Name, Using = "someForm")]
- [CacheLookup]
- public IWebElement formElement;
- }
-
- [CacheLookup]
- private class CachedClassPage
- {
- [FindsBy(How = How.Name, Using = "someForm")]
- public IWebElement formElement;
- }
-
- private class CustomBy : By
- {
- Mockery mocks = new Mockery();
- private string criteria;
-
- public CustomBy(string customByString)
- {
- criteria = customByString;
- this.FindElementMethod = (context) =>
- {
- if (this.criteria != "customCriteria")
- {
- throw new NoSuchElementException();
- }
-
- IWebElement mockElement = mocks.NewMock();
- return mockElement;
- };
- }
- }
-
- private class CustomByNoCtor : By
- {
- Mockery mocks = new Mockery();
- private string criteria;
-
- public CustomByNoCtor()
- {
- criteria = "customCriteria";
- this.FindElementMethod = (context) =>
- {
- if (this.criteria != "customCriteria")
- {
- throw new NoSuchElementException();
- }
-
- IWebElement mockElement = mocks.NewMock();
- return mockElement;
- };
- }
- }
-
- private class CustomByPage
- {
- [FindsBy(How = How.Custom, Using = "customCriteria", CustomFinderType = typeof(CustomBy))]
- public IWebElement customFoundElement;
- }
-
- private class CustomByNotFoundPage
- {
- [FindsBy(How = How.Custom, Using = "customCriteriaNotFound", CustomFinderType = typeof(CustomBy))]
- public IWebElement customFoundElement;
- }
-
- private class NoCustomFinderClassPage
- {
- [FindsBy(How = How.Custom, Using = "custom")]
- public IWebElement customFoundElement;
- }
-
- private class InvalidCustomFinderTypePage
- {
- [FindsBy(How = How.Custom, Using = "custom", CustomFinderType = typeof(string))]
- public IWebElement customFoundElement;
- }
-
- private class InvalidCtorCustomByPage
- {
- [FindsBy(How = How.Custom, Using = "custom", CustomFinderType = typeof(CustomByNoCtor))]
- public IWebElement customFoundElement;
- }
-
- private class GenericFactoryPage
- {
- private IWebDriver driver;
- public GenericFactoryPage(IWebDriver driver)
- {
- this.driver = driver;
- }
-
- [FindsBy(How = How.Name, Using = "someForm")]
- public IWebElement formElement;
- }
-
- private class InvalidElementTypePage
- {
- [FindsBy(How = How.Name, Using = "someForm")]
- public string myElement;
- }
-
- private class InvalidCollectionTypePage
- {
- [FindsBy(How = How.Name, Using = "someForm")]
- public List myElement;
- }
-
- private class ConcreteCollectionTypePage
- {
- [FindsBy(How = How.Name, Using = "someForm")]
- public ReadOnlyCollection myElement;
- }
-
- #pragma warning restore 649
- #endregion
- }
-}
\ No newline at end of file
diff --git a/dotnet/test/support/PageObjects/ThePropertySettingByDefaultFieldDecoratorTest.cs b/dotnet/test/support/PageObjects/ThePropertySettingByDefaultFieldDecoratorTest.cs
new file mode 100644
index 0000000000000..a105595d30a78
--- /dev/null
+++ b/dotnet/test/support/PageObjects/ThePropertySettingByDefaultFieldDecoratorTest.cs
@@ -0,0 +1,213 @@
+using NMock2;
+using NUnit.Framework;
+using OpenQA.Selenium.Internal;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace OpenQA.Selenium.Support.PageObjects
+{
+ [TestFixture]
+ public class ThePropertySettingByDefaultFieldDecoratorTest
+ {
+ private static Mockery mocks = new Mockery();
+ private ISearchContext mockDriver = mocks.NewMock();
+
+ private object mockElement1;
+ private object mockElement2;
+ private object mockElement3;
+
+ private object mockElements1;
+ private object mockElements2;
+ private object mockElements3;
+
+ private object unmarkedField2;
+ private object unmarkedField1;
+ private object unmarkedField3;
+
+ private object unmarkedField5;
+ private object unmarkedField4;
+ private object unmarkedField6;
+
+ [FindsBy(How = How.Id, Using = "SomeId")]
+ private IWebElement mockProperty1
+ {
+ set
+ {
+ mockElement1 = value;
+ }
+ }
+
+ [FindsBy(How = How.Id, Using = "SomeId")]
+ private IWebDriver mockProperty2
+ {
+ set
+ {
+ mockElement2 = value;
+ }
+ }
+
+ [FindsBy(How = How.Id, Using = "SomeId")]
+ private IFindsById mockProperty3
+ {
+ set
+ {
+ mockElement3 = value;
+ }
+ }
+
+ [FindsBy(How = How.Id, Using = "SomeId")]
+ private IList mockListProperty1
+ {
+ set
+ {
+ mockElements1 = value;
+ }
+ }
+
+ [FindsBy(How = How.Id, Using = "SomeId")]
+ private IList mockListProperty2
+ {
+ set
+ {
+ mockElements2 = value;
+ }
+ }
+
+ [FindsBy(How = How.Id, Using = "SomeId")]
+ private IList mockListProperty3
+ {
+ set
+ {
+ mockElements3 = value;
+ }
+ }
+
+ private IWebElement mockUnmarkedProperty1
+ {
+ set
+ {
+ unmarkedField1 = value;
+ }
+ }
+
+ private IWebDriver mockUnmarkedProperty2
+ {
+ set
+ {
+ unmarkedField2 = value;
+ }
+ }
+
+ private IFindsById mockUnmarkedProperty3
+ {
+ set
+ {
+ unmarkedField3 = value;
+ }
+ }
+
+ private IList mockUnmarkedListProperty1
+ {
+ set
+ {
+ unmarkedField6 = value;
+ }
+ }
+
+ private IList mockUnmarkedListProperty2
+ {
+ set
+ {
+ unmarkedField5 = value;
+ }
+ }
+
+ private IList mockUnmarkedListProperty3
+ {
+ set
+ {
+ unmarkedField4 = value;
+ }
+ }
+
+ [SetUp]
+ public void SetUp()
+ {
+ PageFactory.InitElements(mockDriver, this);
+ }
+
+ [Test]
+ public void IsPopulatedMarkedWebElementField()
+ {
+ Assert.NotNull(mockElement1);
+ }
+
+ [Test]
+ public void IsNotPopulatedMarkedNotWebElementField()
+ {
+ Assert.IsNull(mockElement2);
+ }
+
+ [Test]
+ public void IsPopulatedMarkedFieldWithInterfaceImplementedByRemoteWebElementent()
+ {
+ Assert.NotNull(mockElement3);
+ }
+
+ [Test]
+ public void IsPopulatedMarkedListOfWebElementField()
+ {
+ Assert.NotNull(mockElements1);
+ }
+
+ [Test]
+ public void IsNotPopulatedMarkedNotWebElementListField()
+ {
+ Assert.IsNull(mockElements2);
+ }
+
+ [Test]
+ public void IsPopulatedMarkedListFieldWithInterfaceImplementedByRemoteWebElementent()
+ {
+ Assert.NotNull(mockElements3);
+ }
+
+ [Test(Description = "At this case ByIdOrName locator strategy should be used")]
+ public void IsPopulatedWebElementFieldWithoutAttributes()
+ {
+ Assert.NotNull(unmarkedField1);
+ }
+
+ [Test]
+ public void IsNotPopulatedNotWebElementFieldWithoutAttributes()
+ {
+ Assert.IsNull(unmarkedField2);
+ }
+
+ [Test(Description = "At this case ByIdOrName locator strategy should be used")]
+ public void IsPopulatedFieldWithInterfaceImplementedByRemoteWebElemententWithoutAttributes()
+ {
+ Assert.NotNull(unmarkedField3);
+ }
+
+ [Test(Description = "At this case ByIdOrName locator strategy should be used")]
+ public void IsPopulatedListOfWebElementFieldWithoutAttributes()
+ {
+ Assert.NotNull(unmarkedField6);
+ }
+
+ [Test]
+ public void IsNotPopulatedNotWebElementListFieldWithoutAttributes()
+ {
+ Assert.IsNull(unmarkedField5);
+ }
+
+ [Test(Description = "At this case ByIdOrName locator strategy should be used")]
+ public void IsPopulatedListFieldWithInterfaceImplementedByRemoteWebElemententWithoutAttributes()
+ {
+ Assert.NotNull(unmarkedField4);
+ }
+ }
+}
diff --git a/dotnet/test/support/PageObjects/TimeOutOfTheWaitingChangeTest.cs b/dotnet/test/support/PageObjects/TimeOutOfTheWaitingChangeTest.cs
new file mode 100644
index 0000000000000..d49a8eafd07cf
--- /dev/null
+++ b/dotnet/test/support/PageObjects/TimeOutOfTheWaitingChangeTest.cs
@@ -0,0 +1,68 @@
+using NUnit.Framework;
+using OpenQA.Selenium.Environment;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace OpenQA.Selenium.Support.PageObjects
+{
+ [TestFixture]
+ public class TimeOutOfTheWaitingChangeTest : DriverTestFixture
+ {
+ [FindsBy(How = How.Id, Using = "FakeID", Priority = 1)]
+ private IList FakeElements;
+ private readonly double acceptableDeltaInMillis = 800;
+
+ [TestFixtureSetUp]
+ public void RunBeforeAnyTest()
+ {
+ EnvironmentManager.Instance.WebServer.Start();
+ }
+
+ [TestFixtureTearDown]
+ public void RunAfterAnyTests()
+ {
+ EnvironmentManager.Instance.CloseCurrentDriver();
+ EnvironmentManager.Instance.WebServer.Stop();
+ }
+
+ private double ConvertToMilliseconds(DateTime dateTime1, DateTime dateTime2)
+ {
+ return dateTime1.Subtract(
+ dateTime2
+ ).TotalMilliseconds;
+ }
+
+ [Test]
+ public void TestOfTheTimeOutChanging()
+ {
+ DefaultLocatorFactory factory = new DefaultLocatorFactory(driver, TimeSpan.FromSeconds(5), TimeSpan.FromMilliseconds(50));
+
+ PageFactory.InitElements(this, factory);
+ DateTime startTime = DateTime.Now;
+ int count = FakeElements.Count;
+ DateTime endTime = DateTime.Now;
+
+ Assert.IsTrue(Math.Abs((ConvertToMilliseconds(endTime, startTime)) - TimeSpan.FromSeconds(5).TotalMilliseconds)
+ <= acceptableDeltaInMillis);
+
+ factory.WaitingTimeSpan = TimeSpan.FromMilliseconds(1800);
+ startTime = DateTime.Now;
+ count = FakeElements.Count;
+ endTime = DateTime.Now;
+
+ Assert.IsTrue(Math.Abs((ConvertToMilliseconds(endTime, startTime)) - TimeSpan.FromMilliseconds(1800).TotalMilliseconds)
+ <= acceptableDeltaInMillis);
+
+ startTime = DateTime.Now;
+ driver.FindElements(By.Id("FakeID"));
+ endTime = DateTime.Now;
+
+ Assert.IsTrue(Math.Abs((ConvertToMilliseconds(endTime, startTime)) - TimeSpan.FromMilliseconds(1800).TotalMilliseconds)
+ <= acceptableDeltaInMillis);
+ }
+
+
+ }
+}
diff --git a/dotnet/test/support/WebDriver.Support.Tests.csproj b/dotnet/test/support/WebDriver.Support.Tests.csproj
index 1a52f7af7cb28..59989c2f3e50e 100644
--- a/dotnet/test/support/WebDriver.Support.Tests.csproj
+++ b/dotnet/test/support/WebDriver.Support.Tests.csproj
@@ -80,9 +80,12 @@
+
+
-
+
+