是否可以使用页面对象为多个类似屏幕创建可重复使用的通用Specflow步骤定义? [英] Is it possible to create reusable and generic Specflow step definitions for multiple similar screens using page objects?

查看:85
本文介绍了是否可以使用页面对象为多个类似屏幕创建可重复使用的通用Specflow步骤定义?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用具有许多相似的数据表/ CRUD屏幕的应用程序。我使用带有页面对象模式的Selenium在应用程序中导航,并使用对象母亲创建预定义的测试数据,尤其是对于具有许多输入的表单。

I am working on an app with many similar datatable / CRUD screens. I use Selenium with page objects pattern to navigate in the app, and object mothers to create predefined test data especially for forms with many inputs.

在编写功能文件时,我想到测试是如此相似,为了重用和简化,应该有可能归纳一些通用步骤。干燥。 DataTable页面对象很简单,因为所有页面的选择器都相同。因此,我创建了一个 DataTable 页面对象,该对象具有进行过滤,选择行等所需的操作。我通过Specflow的上下文注入机制使用了DI。

While writing feature files, it occurred to me that the tests are so similar to each other and that it should be possible to generalize some common steps for the sake of reuse and DRYness. The DataTable page object was easy, since selectors are the same for all the pages. So I created a DataTable page object that has the necessary operations for filtering, selecting the rows etc. I used DI via Specflow's Context Injection mechanism.

示例:

Scenario: List and search Users
    When I type 'test@test.test' in filter
    Then I should only see items with 'email' = 'test@test.test'


$的项目b $ b

和步骤:

And steps:

[Binding]
public class DataTableSteps
{
    private DataTable _page;
    public DataTableSteps(DataTable page) => _page = page;

    [When(@"I type '(.*)' in filter")]
    public void WhenITypeInFilter(string value) { 
        _page.FilterSearch (value);    
      }
    [Then(@"I should see items with '(.*)' = '(.*)'")]
    public void ThenIShouldSeeAListWhereContains(string column, string value)
    {
        _page.VisibleInTable(column, value).Should().BeTrue();
    }
}

问题是,我找不到一个好的设计,当我尝试制作一个通用步骤类 WebFormSteps 来处理表单数据,以便使用不同的 WebForm 页面填充各种输入时对象。我想执行以下操作:

The problem is, I can't find a good design when I try to make a common step class WebFormSteps that handles form data for filling in various inputs with different WebForm page objects. I want to do the following:

Scenario: Add User
    When I go to '/Users'
    When I create a 'validUser' Item  #'validUser' comes from the object mother
    Then 'validUser' should be added

并为产品使用相同的步骤和步骤定义,例如:

And use the same steps and step definitions for, product for instance:

Scenario: Add Product
    When I go to '/Products'
    When I create a 'validProduct' Item
    Then 'validProduct' should be added

我想到了使用接口或抽象类,并让 NewUserInputWebForm 页面对象和 UserMother 来实现这些,但是我找不到在运行时注入正确的具体类型的方法。 (我考虑将具体的对象母体放到相关的页面对象中,这意味着对该步骤的依赖性降低了,但是我没有理由将对象母体添加到这样一个不相关的类中,即页面对象)。

I thought of using interfaces or abstract classes and let NewUserInputWebForm page object and UserMother to implement these, however I could not find a way to inject the right concrete type at run time. (I considered to put the concrete object mothers to the related page objects, that would mean one less dependency to the step, but I couldn't justify to add an object mother to such an unrelated class, i.e. page object).

[Binding]
class WebFormSteps
{
    IWebFormObject formObject;
    IObjectMother objectMother;

    public WebFormSteps(IWebFormObject formObject, IObjectMother objectMother)
    {
        this.formObject = formObject;
        this.objectMother = objectMother;
    }

    [When(@"I create a '([^']*)' item")]
    public void WhenICreate(string exampleName)
    {
        form = objectMother.Create(exampleName);
        formObject.FillForm(form);
        formObject.Submit();
    }
}

我想到的一件事是使用作用域的钩子和寄存器接口与正确的对象,但是在这种情况下,将有大量的标记和钩子方法。同样,我可以在其他步骤中注册类型,例如当我转到 / Users 步骤时,但这会使事情变得更加复杂。可以使用反射或在步骤定义中指定类型名称:当我创建一个名为 validUser的用户 时,但这会使脆弱的测试变得均匀更脆。此外,测试将变得过于技术性,而不是黄瓜。

One thing that came to my mind is to use a scoped hook and register interface with the right objects, however there will be an abundance of tags and hook methods in that case. Similarly, I can register the type at some other step, for instance When I go to '/Users' step, but it would complicate things more. Reflection may be used or type name can be specified in the step definition: When I create a 'User' named 'validUser', but that would make the brittle tests to be even more brittle. Furthermore, tests would become too technical, not cucumber at all.

是否有实现此目标的好方法?我什至不确定这种方法是否是一种好的做法(包括数据表),因为我找不到比这种用法更重要的例子了。我应该遵循以下专门步骤吗?

Is there a good way to achieve this? I am not even sure if such approach is good practice (including data table) because I was not able to find more than trivial examples on such usage. Should I stick to specialized steps like:

Scenario: Add User
    When I go to '/Users'
    When I create a user named 'validUser'
    Then 'validUser' should be added to users


推荐答案

您可以使用表格和代表该表格中值的数据传输对象简化所有操作:

You can simplify all of this using a table and a data transfer object representing values from that table:

When I create the following user:
    | Field      | Value |
    | Username   | test  |
    | First name | John  |
    | Last name  | Doe   |

步骤定义将获得 Table 对象。您可以在 TechTalk.SpecFlow.Assist 命名空间中使用 CreateInstance< T>()扩展方法来映射表到其中字段列与类的属性名称匹配的对象:

The step definition will get a Table object. You can use the CreateInstance<T>() extension method in the TechTalk.SpecFlow.Assist namespace to map the table to an object where the "Field" column matches property names on the class:

[When(@"I create the following user:")]
public void WhenICreateTheFollowingUser(Table table)
{
    var user = table.CreateInstance<UserRow>();
    var userForm = new AddEditUserPageObject(driver);

    // Send user object to Selenium page object in order to enter
    // data into form fields
    userForm.FillForm(user);
}

从表中映射的类为:

public class UserRow
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Username { get; set; }
}

还有硒的示例页面对象:

And a sample page object for Selenium:

public class AddEditUserPageObject
{
    private readonly IWebDriver driver;

    public AddEditUserPageObject(IWebDriver driver)
    {
        this.driver = driver;
    }

    public void FillForm(UserRow data)
    {
        Username.SendKeys(data.Username);
        FirstName.SendKeys(data.FirstName);
        LastName.SendKeys(data.LastName);
    }
}

如果您想利用对象母亲来设置一些默认参数,可以使用 table.CreateInstance< T>()方法的不同重载来执行此操作,该方法允许您指定用于创建新Lambda表达式的方法。 UserRow的实例,然后可以使用对象Mother:

If you want to utilize an object mother in order to set some default parameters, you can do this using a different overload of the table.CreateInstance<T>() method that allows you to specify a lambda expression used to create a new instance of UserRow, which could then use the object mother:

[When(@"I create the following user:")]
public void WhenICreateTheFollowingUser(Table table)
{
    var user = table.CreateInstance<UserRow>(() => objectMother.CreateUser("someExample"));
    var userForm = new AddEditUserPageObject(driver);

    // Send user object to Selenium page object in order to enter
    // data into form fields
    userForm.FillForm(user);
}

如果要参数化传递给对象mother的值,您可以随时添加新步骤:

And if you wanted to parameterize the value passed to the object mother you can always add a new step:

[When(@"I create a user named ""(.*)"":")]
public void WhenICreateTheFollowingUser(string username, Table table)
{
    var user = table.CreateInstance<UserRow>(() => objectMother.CreateUser(username));
    var userForm = new AddEditUserPageObject(driver);

    // Send user object to Selenium page object in order to enter
    // data into form fields
    userForm.FillForm(user);
}

功能文件中的步骤如下:

And the step in your feature file would look like:

When I create a user named "test"

这篇关于是否可以使用页面对象为多个类似屏幕创建可重复使用的通用Specflow步骤定义?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆