Skip to content

Commit

Permalink
JimEvans: Introduces the Updating the CustomFinderType property to th…
Browse files Browse the repository at this point in the history
…e .NET FindsByAttribute. This allows use of custom By subclasses in the PageFactory. The custom finder must be a subclass of By, and it must expose a public constructor that takes a string argument. To use a custom finder with the FindsByAttribute, use the following syntax:

[FindsBy(How = How.Custom, Using = "customCriteria", CustomFinderType = typeof(MyCustomBySubclass))]

Using How.Custom without specifying a CustomFinderType, or using a CustomFinderType that either does not descend from By or does not have a proper constructor will throw an exception.

Fixes issue SeleniumHQ#4878

r18291
  • Loading branch information
jimevans committed Dec 10, 2012
1 parent f5b59c6 commit c7fce19
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 4 deletions.
20 changes: 20 additions & 0 deletions dotnet/src/WebDriver.Support/PageObjects/ByFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

using System;
using System.Globalization;
using System.Reflection;

namespace OpenQA.Selenium.Support.PageObjects
{
Expand Down Expand Up @@ -53,6 +54,25 @@ public static By From(FindsByAttribute attribute)
return By.PartialLinkText(usingValue);
case How.XPath:
return By.XPath(usingValue);
case How.Custom:
if (attribute.CustomFinderType == null)
{
throw new ArgumentException("Cannot use How.Custom without supplying a custom finder type");
}

if (!attribute.CustomFinderType.IsSubclassOf(typeof(By)))
{
throw new ArgumentException("Custom finder type must be a descendent of the By class");
}

ConstructorInfo ctor = attribute.CustomFinderType.GetConstructor(new Type[] { typeof(string) });
if (ctor == null)
{
throw new ArgumentException("Custom finder type must expose a public constructor with a string argument");
}

By finder = ctor.Invoke(new object[] { usingValue }) as By;
return finder;
}

throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Did not know how to construct How from how {0}, using {1}", how, usingValue));
Expand Down
13 changes: 10 additions & 3 deletions dotnet/src/WebDriver.Support/PageObjects/FindsByAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ public sealed class FindsByAttribute : Attribute, IComparable
[DefaultValue(0)]
public int Priority { get; set; }

/// <summary>
/// Gets or sets a value indicating the <see cref="Type"/> of the custom finder. The custom finder must
/// descend from the <see cref="By"/> class, and expose a public constructor that takes a <see cref="String"/>
/// argument.
/// </summary>
public Type CustomFinderType { get; set; }

/// <summary>
/// Gets or sets an explicit <see cref="By"/> object to find by.
/// Setting this property takes precedence over setting the How or Using properties.
Expand Down Expand Up @@ -135,9 +142,9 @@ internal By Finder
/// <returns>A value that indicates the relative order of the objects being compared. The return value has these meanings:
/// <list type="table">
/// <listheader>Value</listheader><listheader>Meaning</listheader>
/// <item><description>Less than zero</description><description>This instance precedes obj in the sort order.</description></item>
/// <item><description>Zero</description><description>This instance occurs in the same position in the sort order as obj.</description></item>
/// <item><description>Greater than zero</description><description>This instance follows obj in the sort order. </description></item>
/// <item><description>Less than zero</description><description>This instance precedes <paramref name="obj"/> in the sort order.</description></item>
/// <item><description>Zero</description><description>This instance occurs in the same position in the sort order as <paramref name="obj"/>.</description></item>
/// <item><description>Greater than zero</description><description>This instance follows <paramref name="obj"/> in the sort order. </description></item>
/// </list>
/// </returns>
public int CompareTo(object obj)
Expand Down
7 changes: 6 additions & 1 deletion dotnet/src/WebDriver.Support/PageObjects/How.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ public enum How
/// <summary>
/// Finds by <see cref="OpenQA.Selenium.By.XPath" />
/// </summary>
XPath
XPath,

/// <summary>
/// Finds by a custom <see cref="By"/> implementation.
/// </summary>
Custom
}
}
117 changes: 117 additions & 0 deletions dotnet/test/WebDriver.Support.Tests/PageObjects/PageFactoryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,51 @@ public void CachesIfClassMarkedCachedElement()
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);
}

#region Test helper methods

private void ExpectOneLookup()
Expand Down Expand Up @@ -305,6 +350,78 @@ private class CachedClassPage
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<IWebElement>();
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<IWebElement>();
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;
}

#pragma warning restore 649
#endregion
}
Expand Down

0 comments on commit c7fce19

Please sign in to comment.