所以我一直在研究 Selenium 的 POM(页面对象模型),特别是 FindsBy 注释。
[FindsBy(How = How.XPath, Using = "//a[@attribute='some_value']")]
public IWebElement ElementDescription;
但是我遇到了麻烦,需要等待对象并且必须使用 FindsBy 注释中使用的相同定位器调用 WebDriverWait 函数:
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(30));
wait.Until(SeleniumExtras.WaitHelpers.ExpectedConditions.ElementToBeClickable(By.XPath("//a[@attribute='some_value']")));
这不太容易维护,因为当对象的定位器发生变化时,您必须更新多个定位器。
我的问题是,如果方法本身在使用对象之前等待对象,那么每个元素的方法会工作得更好吗?
public IWebElement ElementDescription(int seconds = 30)
{
const string locator = "//a[@attribute='some_value']";
// Wait for the element to be visible before returning it
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(seconds));
IWebElement element = wait.Until(SeleniumExtras.WaitHelpers.ExpectedConditions.ElementToBeClickable(By.XPath(locator)));
// Return the element
return element;
}
我使用的许多元素都是根据您在应用程序中所做的不同选择动态生成的,因此我必须等待数据加载或元素可见。
您将 PageFactory 与页面对象模型 (POM) 混淆了。
FindsBy
等都是PageFactory的一部分。 POM 不是 Selenium 的一部分,它是一种组织和构造表示网站页面(或部分页面)的类的方法。
Selenium 领导和开发人员建议您不要使用 PageFactory,请参阅 此视频。另一方面,强烈建议使用POM。
您提出的有关 PageFactory 的问题是我几年前停止使用它的主要原因之一,直到我得知 Selenium 团队不鼓励使用它。
我解决这个问题的方法是创建一个 BasePage 类,该类包含一堆常见 Selenium 方法的便捷包装器,如
.FindElement()
、.Click()
、.SendKeys()
等。
这是我的 BasePage,其中包含一个示例方法,
.Click()
。您可以看到它需要一个定位器和超时(如果需要)。它处理等待可点击等。
BasePage.cs
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using ExpectedConditions = SeleniumExtras.WaitHelpers.ExpectedConditions;
namespace SeleniumFramework.PageObjects
{
public class BasePage
{
protected IWebDriver Driver;
public BasePage(IWebDriver driver)
{
Driver = driver;
}
/// <summary>
/// Clicks the element.
/// </summary>
/// <param name="locator">The By locator for the desired element.</param>
/// <param name="timeOutSeconds">[Optional] How long to wait for the element. The default is 10.</param>
public void Click(By locator, int timeOutSeconds = 10)
{
DateTime expire = DateTime.Now.AddSeconds(timeOutSeconds);
while (DateTime.Now < expire)
{
try
{
new WebDriverWait(Driver, TimeSpan.FromSeconds(timeOutSeconds)).Until(ExpectedConditions.ElementToBeClickable(locator)).Click();
return;
}
catch (Exception e) when (e is ElementClickInterceptedException || e is StaleElementReferenceException)
{
// do nothing, loop again
}
}
throw new Exception($"Not able to click element <{locator}> within {timeOutSeconds}s.");
}
}
}
这是一个示例 LoginPage 页面对象,显示了 BasePage 方法的使用。
登录页面.cs
using OpenQA.Selenium;
namespace SeleniumFramework.PageObjects.TheInternet
{
class LoginPage : BasePage
{
private readonly By _loginButtonLocator = By.CssSelector("button");
private readonly By _passwordLocator = By.Id("password");
private readonly By _usernameLocator = By.Id("username");
public LoginPage(IWebDriver driver) : base(driver)
{
}
/// <summary>
/// Logs in with the provided username and password.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="password">The password.</param>
public void Login(string username, string password)
{
SendKeys(_usernameLocator, username);
SendKeys(_passwordLocator, password);
Click(_loginButtonLocator);
}
}
}
最后,这是一个使用 LoginPage 的示例测试。
using SeleniumFramework.PageObjects.TheInternet;
namespace SeleniumFramework.Tests.TheInternet
{
public class LoginTest : BaseTest
{
[Test]
[Category("Login")]
public void Login()
{
string username = TestData["username"];
string password = TestData["password"];
new LoginPage(Driver.Value!).Login(username, password);
new SecurePage(Driver.Value!).Logout();
Assert.That(Driver.Value!.Url, Is.EqualTo(Url), "Verify login URL");
}
}
}