Specflow 步骤定义 - 涉及页面对象时跨功能共享

Specflow Step Definition - sharing across features when page objects involved

我有许多功能文件需要在网页上断言预填充数据 - 每个功能文件都有自己的步骤定义文件

例如在客户功能步骤定义文件中:

[Then(@"I expect all fields on the screen populated with Customer Details from the Database")]
public void PopulatedWithCustomerDetailsFromTheDatabase ()
{
  foreach (var entry in dataDictionary)
  {
       Assert.That(pages.CustomerPage.GetText(entry.Key), Is.EqualTo(entry.Value));
  }
}

在公司步骤定义文件中:

[Then(@"I expect all fields on the screen populated with Company Details from the Database")]
public void PopulatedWithCompanyDetailsFromTheDatabase()
{
    foreach (var entry in dataDictionary)
    {
      Assert.That(pages.CompanyDetailsPage.GetText(entry.Key), Is.EqualTo(entry.Value));
    }
}

两者都使用相关的页面对象 class 从页面获取文本并断言其正确性。 建议我不要像这样共享步骤定义,因为它们使用我同意的不同页面对象。问题是我需要在另外 50 多个页面上执行此操作,CustomerPage、CompanyDetailsPage、StockPage、DeliveryPage 等...

关于我如何构造它以便我在步骤定义中有更多共性的任何建议? - 必须有更好的方法,我只是在复制代码,但努力从架构的角度可视化解决方案。

许多页面共有的动作应该封装成自己的页面模型,页面模型可以由多个页面模型组成——如何实现?

为了阐明每个页面都是不同的,所以除了上述步骤定义之外,对于地址页面功能,我还有一个步骤定义:

[Then(@"I expect all fields on the screen populated with AddressDetails from the Database")]
public void PopulatedWithAddressDetailsFromTheDatabase()
{
    foreach (var entry in dataDictionary)
    {
      Assert.That(pages.AddressDetailsPage.GetText(entry.Key), Is.EqualTo(entry.Value));
    }
}

其中包含地址行 1、地址行 2 等字段....

以及具有步骤定义的 Stock Page 功能:

[Then(@"I expect all fields on the screen populated with Stock from the Database")]
public void PopulatedWithStockFromTheDatabase()
{
    foreach (var entry in dataDictionary)
    {
      Assert.That(pages.StockPage.GetText(entry.Key), Is.EqualTo(entry.Value));
    }
}

其中有库存列表字段等...50 个不同的页面

您可以参数化页面名称并保留页面对象字典。

Then I should see the following details on the "Stock" page:
    | Field | Value |
    | ...   | ...   |

Then I should see the following details on the "Company Details" page:
    | Field | Value |
    | ...   | ...   |

您需要每个页面对象实现相同的接口,因此您可以将它们存储在字典中并在步骤定义中对它们调用 GetText 方法:

public interface IReadOnlyPageModel
{
    string GetText(string key);
}

然后在你的页面模型中实现这个接口:

public class StockDetailsPage : IReadOnlyPageModel
{
    // stuff specific to the stock details page

    public string GetText(string key)
    {
        // based on key, return text
    }
}

public class CompanyDetailsPage : IReadOnlyPageModel
{
    // stuff specific to the company details page

    public string GetText(string key)
    {
        // based on key, return text
    }
}

那么你的步骤定义中需要一个字典class,当然还有步骤定义:

[Binding]
public class StepDefinitions
{
    private readonly Dictionary<string, IReadOnlyPageModel> detailsPages;

    public StepDefinitions(IWebDriver driver)
    {
        // Or however you get access to the IWebDriver object
        detailsPages = = new Dictionary<string, IReadOnlyPageModel>()
        {
            { "Stock", new StockDetailsPage(driver) },
            { "Company", new CompanyDetailsPage(driver) }
        };
    }

    [Then(@"Then I should see the following details on the ""(.*)"" page:")]
    public void ThenIShouldSeeTheFollowingDetailsOnThePage(string pageName, Table table)
    {
        var page = detailsPages[pageName];

        foreach (var row in table.Rows)
        {
            Assert.That(page.GetText(row["Field"]), Is.EqualTo(row["Value"]));
        }
    }
}

如果您注意到您的元素定位器似乎都遵循某种模式,您可以创建一个通用页面模型:

public class GenericPageModel : IReadOnlyPageModel
{
    private readonly IWebDriver driver;

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

    public string GetText(string key)
    {
        return driver.FindElement(By.XPath($"//caption(contains(., 'Details')]/..//tr[contains(., '{key}')]/td[2]")).Text;
    }
}

如果在 detailsPages 字段中找不到条目,​​则修改步骤定义以使用通用页面模型。

var page = detailsPages.ContainsKey(pageName)
         ? detailsPages[pageName]
         : new GenericPageModel(driver); // Or however you get access to your IWebDriver object