首页 > 解决方案 > Specflow 步骤定义 - 涉及页面对象时跨功能共享

问题描述

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

例如在客户特征步骤 def 文件中:

[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));
    }
}

两者都使用相关的页面对象类从页面中获取文本并断言其正确。建议我不要共享这样的步骤定义,因为它们使用我同意的不同页面对象。问题是我需要在另外 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等字段......

以及具有步骤 def 的 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 个不同的页面

标签: c#selenium-webdriverspecflowgherkinpageobjects

解决方案


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

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
    }
}

然后,您将需要在步骤定义类中使用字典,当然还有步骤定义:

[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

推荐阅读