A.Singh
A.Singh

Reputation: 35

How do I iterate through a table to enter data

I have a test framework which uses Specflow, Selenium and C#.

The Specflow test step looks like the below (I'm using datatables): And I create the below Loan Purposes in the Loan Purposes section

    |loan purposes     |moreinfo |active|
    |Debt Consolidation|True     |True  |
    |Home Improvements |True     |True  |

So obviously there are 2 rows in this dataTable with the top row just the header.

In the step definition for the above step, I first identify the count in the table and store that as a variable (which correctly gives me a count of 2 in the above instance). I then iterate over the number of rows in the table and click the ‘Add button’ on the UI to add as many rows as I need as per the test step – so in this instance, it correctly clicks the Add button twice which adds 2 blank rows.

    var data = dataTable.CreateSet<ProductLoanPurposeData>();

    var count = data.Count();
    for (int i = 0; i < count; i++)
    {
    ClickLoanPurposeAddButton();
    }

Here's where I’m getting stuck. So each row added on the UI has its own class – I’ve labelled this element in my PageClass ‘LoanPurposeRow’. What I want to do is take the data from the first row in the dataTable and input that on the first ‘LoanPurposeRow’ on the UI. If there are more rows in my dataTable, I then want to move onto the second ‘LoanPurposeRow’ and take the data from the 2nd row in the dataTable and so on.

I've been searching online and checked out various answers but not finding what I need in order to carry out the above. Thanks in advance

Upvotes: 2

Views: 2499

Answers (2)

Greg Burghardt
Greg Burghardt

Reputation: 18928

AutomatedOrder points you in the right direction. You need to take the data and call the Selenium API to input the data into the web page. I would like to take his answer one step further by putting on my "Architect" hat for a moment to give you a better idea how SpecFlow steps can be used along side the Page Object Model. This enforces Separation of Concerns between SpecFlow and Selenium.

  1. Initialize the Selenium web driver and register it with the SpecFlow dependency injection framework.

  2. Create a Page Object class that represents that part of your web page:

    public class LoanPurposesPageObject
    {
        private readonly IWebDriver driver;
    
        // TODO: Change element locators to match your HTML structure
        private IWebElement AddLoanPurposeButton => driver.FindElement(By.XPath("//button[contains(., 'Add Loan Purpose')]"));
        private IWebElement LoanPurposesTextBox => driver.FindElement(By.Id("LoanPurposes"));
        private IWebElement MoreInfoTextBox => driver.FindElement(By.Id("MoreInfo"));
    
        public LoanPurposesPageObject(IWebDriver driver)
        {
            this.driver = driver;
        }
    
        public void Add(string loanPurposes, string moreInfo, bool isActive)
        {
            LoanPurposesTextBox.SendKeys(loanPurposes);
            MoreInfoTextBox.SendKeys(moreInfo);
    
            if (isActive)
            {
                // TODO: Check radio button or select option in dropdown?
            }
            else
            {
                // TODO: Check radio button or select option in dropdown?
            }
    
            AddLoanPurposeButton.Click();
        }
    }
    
  3. Modify your step definition class to accept an IWebDriver object as a constructor argument and initialize the page object.

    [Binding]
    public class YourStepDefinitions
    {
        private readonly LoanPurposesPageObject loanPurposes;
        private readonly IWebDriver driver;
    
        public YourStepDefinitions(IWebDriver driver)
        {
            this.driver = driver;
            loanPurposes = new LoanPurposesPageObject(driver);
        }
    
  4. Modify the step definition method to pass data from your ProductLoanPurposeData object to the page object:

    [Binding]
    public class YourStepDefinitions
    {
        // ...
    
        [When(@"...")]
        public void YourStepThatUsesTheDataTable(Table table)
        {
            var data = table.CreateSet<ProductLoanPurposeData>();
    
            foreach (var row in data)
            {
                loanPurposes.Add(row.LoanPurposes, row.MoreInfo, row.Active);
            }
        }
    }
    

This gives you a clean separation between SpecFlow stuff and Selenium stuff and promotes a high amount of code reuse while maintaining proper decoupling.

Upvotes: 1

AutomatedOrder
AutomatedOrder

Reputation: 500

Your create set is converting your table to an object, so you would reference the object accordingly. You need to iterate though your data object, like so. Note that I removed the spacing in your table header

foreach(var item in data)
{
    driver.FindElement(By.Id("LoanPurposeRow")).SendKeys(item.loanpurposes);
ClickLoanPurposeAddButton();
}

It will grab the text for whatever "loanpurposes" is and input it into your element. You will need to adjust your By selector based on whatever row you're on. You could initialize a counter.

Admittedly I don't use CreateSet in my selenium code, I just iterate though the tables like below, and it also allows you to keep the spacing.

foreach(var item in data.Rows)
{
    driver.FindElement(By.Id("LoanPurposeRow")).SendKeys(item["loan purposes"]);
ClickLoanPurposeAddButton();
}

Either way you go, the first iteration will grab all the data from the first row, the second iteration from the second row, and so forth.

This link has a little more information on CreateSet

Upvotes: 1

Related Questions