1
votes

I have a number of feature files which need to assert the pre-populated data on the webpage - each feature file has its own step definition file

e.g in customer feature step def file:

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

in the company step definition file:

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

Both use the relevant page object class to get the text from the page and assert its correct. Its been recommended that i dont share step definitions like this as they are using different page objects which i agree with. The problem is i will need to do this on another 50+ pages, CustomerPage, CompanyDetailsPage, StockPage, DeliveryPage etc...

Any advice on how i could structure this so i have more commonality in the step defs? - there must be a better way, i'm just duplicating code but struggling to visualise a solution from an architectural perspective.

Actions common to many pages should be encapsulated as the own page models and Page models can be composed of multiple page models - how can this be achieved?

To clarify each page is different so in addition to above step defs, for the Address page feature i have a step def:

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

which has fields like address line1, address line 2 etc....

and the Stock Page feature which has step def:

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

which has stock list fields etc...for 50 different pages

1
Do you have 50 different company details pages, are you checking the company details page for 50 different companies, or do the company details appear on 50 separate pages? This difference in perspective is important. - Greg Burghardt
You could put a method in each page object that scrapes the page and returns the company details in a List<string>. Then inside your test, call a helper method that pulls the data from the database in a List<string>. At that point, you can use a CollectionAssert to compare the two Lists inside the test. - JeffC
Its 50 different pages i will be checking the data on e. g. CustomerPage, CompanyDetailsPage, StockPage, DeliveryPage. All are separate entities in their own right with own page object classs and each page has its own set of tests in a feature file. I will need to validate the data on each page in most cases (like the step defs above) thanks - m_finn
Is the HTML structure for the company details information the same on each of the pages? - Greg Burghardt
Yes it will be in at least 90%+ of the time - company details page html will be formatted like customer page structure, stock page etc. Very consistent - m_finn

1 Answers

2
votes

You can parameterize the name of the page and keep a dictionary of page objects.

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

You will need each of your page objects to implement the same interface, so you can store them in a dictionary and call the GetText method on them in the step definition:

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

Then implement this interface in your page models:

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

Then you will need a dictionary in your step definition class, and of course the step definition:

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

If you notice that your element locators all seem to follow a pattern, you could create a generic page model:

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

And modify the step definition to use the generic page model if an entry is not found in the detailsPages field.

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