【问题标题】:Common Step Definitions in Specflow with Page Object Model带有页面对象模型的 Specflow 中的常见步骤定义
【发布时间】:2020-02-08 21:02:50
【问题描述】:

我的登录页面绑定中有一个步骤定义

[When(@"I click the '(.*)' button")]
public void IClickTheButton(string buttonName)
{
    LoginPage loginPage = new LoginPage();
    loginPage.ClickTheButton(buttonName);
}

我的页面对象是使用 BasePage 中的 ClickTheButton 方法设置的:

public class LoginPage : BasePage
{
    public LoginPage(IWebDriver _driver)
    {
        driver = _driver;
    }
    // some methods

}
public class HomePage : BasePage
{
    public HomePage(IWebDriver _driver)
    {
        driver = _driver;
    }
    // some methods
}
public class BasePage
{
    //no constructor atm
    public void ClickTheButton(string buttonName)
    {
        driver.GetFirstButtonWithTextContaining(buttonName).Click();
    }
    // more methods
}      

应用程序中的所有按钮都采用相同的格式,因此 GetFirstButtonWithTextContaining 方法将使用

全部单击它们
driver.FindElements(By.TagName("button")).Where(x => x.Text == buttonName).First();

问题是我将在我的所有功能文件(如主页功能)中使用“我单击'(。*)'按钮”,因此使用使用实例的登录页面步骤定义似乎不正确其他页面上所有按钮的登录页面类。

我正在考虑为这些类型的方法创建一个通用步骤 defs 文件,但是当我将构造函数添加到 BasePage(与其他页面对象类相同)并在通用步骤 defs 绑定中执行以下操作时:

BasePage basePage = new BasePage();
basePage.ClickTheButton(buttonName);

有更好的实现吗?...使用 BasePage 类似乎是错误的,但我看不到如何在使用页面对象时跨多个功能共享步骤定义。我正在尝试创建尽可能多的通用步骤,以便在所有功能之间共享。

【问题讨论】:

    标签: c# selenium-webdriver specflow gherkin pageobjects


    【解决方案1】:

    您的页面模型并不是真正的页面模型。它们只是使用 Selenium 进行操作的便捷包装器,并没有提供良好的抽象层。

    在您的步骤定义中直接使用扩展方法:

    [When(@"I click the '(.*)' button")]
    public void IClickTheButton(string buttonName)
    {
        driver.GetFirstButtonWithTextContaining(buttonName).Click();
    }
    

    或者重写你的页面模型来封装页面上的用户操作:

    public class LoginPage
    {
        private readonly IWebDriver driver;
        private IWebElement Password => driver.FindElement(By.Id("Password"));
        private IWebElement Username => driver.FindElement(By.Id("Username"));
        private IWebElement LoginButton => driver.FindElement(By.XPath("//button[contains(., 'Log In')]"));
    
        public LoginPage(IWebDriver driver)
        {
            this.driver = driver;
        }
    
        public HomePage Login(string username, string password = "test")
        {
            Username.SendKeys(username);
            Password.SendKeys(password);
            LoginButton.Click();
    
            var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(30));
    
            wait.Until(ExpectedConditions.StalenessOf(LoginButton));
    
            return new HomePage(driver);
        }
    }
    

    这意味着将您的步骤从程序样式(我点击这个;我这样做)更改为一种更受行为驱动的样式。例如,通常使用程序步骤“登录”:

    Given I am on the login page
    When I enter "foo" for the "Username"
    And I enter "bar" for the "Password"
    And I click the "Log In" button
    

    相反,行为驱动步骤将是一种快速的单行方式,将其大部分行为委托给页面模型:

    Given I am logged in as "foo"
    
    [Given(@"I am logged in as ""(.+)""")]
    public GivenIAmLoggedInAs(string username)
    {
        var loginPage = new LoginPage(driver);
    
        loginPage.LogIn(username);
    }
    

    您的黄瓜步骤应该是一层薄薄的胶合板,将您的功能文件中的应用程序行为描述粘合到在页面中封装该行为的页面模型。

    【讨论】:

    • 感谢 Greg - 非常有用。它确认我过去一直在正确使用页面对象。但是这个新应用程序非常通用,它让我看到了像这样的常见步骤 ``` 我完成了以下文本字段: |姓名 |价值 | |地址行 1 |史密斯街 13 号 | |姓氏|史密斯 | ``` 我可以通过查找 标签的标签来使用此步骤来完成任何页面上的任何文本字段。如果我根本需要页面对象,这让我感到困惑。
    • 您没有纯文本中的“操作”,这些操作在所有页面中都通用并且可以由所有功能/页面共享?
    • 许多页面共有的动作应该被封装为自己的页面模型。优先组合而不是继承。页面模型可以由多个页面模型组成。
    • 我曾经创建通过标签文本查找字段的通用步骤。这听起来是个好主意,但是对标签文本的任何更改都会破坏所有测试。人们开始相信这些测试是不稳定的。而且字段并不总是有<label>,这意味着您需要非常灵活的xpath 表达式。这会导致您的测试运行速度变慢。
    • 许多页面共有的操作应封装为自己的页面模型。优先组合而不是继承。 页面模型可以由多个页面模型组成我认为这是我想要实现的,但错误地使用了 BasePage。我很难想象这一点,你能提供一个简短的例子吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-16
    • 2013-06-21
    • 2014-09-26
    • 2011-07-10
    相关资源
    最近更新 更多