A .NET 8.0 console application that takes a URL as input and automatically generates a basic C# Page Object class suitable for use in Selenium-based test automation projects, particularly those using frameworks like Reqnroll (SpecFlow).
The generated class includes:
- CSS or XPath locators for common interactive HTML elements (inputs, buttons, links, selects, textareas).
- Basic interaction methods (Click, EnterText, GetText, Select) for these elements.
- Integration of
WebDriverWaitandExpectedConditionsto handle dynamic waits before interactions.
Goal: To accelerate the creation of Page Objects by automating the initial boilerplate code and locator identification.
- Fetches HTML content from a given URL.
- Parses the HTML using HtmlAgilityPack.
- Identifies potentially relevant interactive elements (
<input>,<button>,<a>,<select>,<textarea>). - Generates locators using a priority system:
Id(if unique and not likely dynamic)Namedata-testidattribute (as CSS Selector)- CSS Selector (based on tag, type, class, other attributes)
- XPath (as a fallback, typically based on attributes or text)
- Sanitizes element information to create valid C# PascalCase names for methods and fields, handling duplicates and keywords.
- Generates a C# class file (
.cs) containing:usingstatements for Selenium and common types.- A constructor accepting
IWebDriver. - Initialization of
WebDriverWait. private static readonly Byfields for each identified element locator.- Public methods for interacting with elements (e.g.,
ClickLoginButton(),EnterUsernameText(string username)). - Built-in explicit waits (
_wait.Until(...)) before most actions (e.g.,ElementToBeClickable,ElementIsVisible). - Basic error handling (
try-catch) within interaction methods.
- .NET 8.0 SDK or later installed.
-
Clone or Download: Get the source code for this project.
git clone <repository-url> cd PageObjectGenerator
(Replace
<repository-url>with the actual URL of your Git repository) -
Build the Project:
dotnet build
This will restore NuGet packages (like HtmlAgilityPack) and compile the application.
Run the generator from your terminal within the project directory (PageObjectGenerator).
Syntax:
dotnet run -- <URL> <OutputFilePath> [ClassName]Arguments:
<URL>: (Required) The full URL of the web page you want to generate a Page Object for (e.g.,https://github.com/login). Must start withhttp://orhttps://.<OutputFilePath>: (Required) The full or relative path where the generated C# file (.cs) should be saved (e.g.,./output/LoginPage.csor../MyReqnrollProject/PageObjects/LoginPage.cs).- If the directory doesn't exist, it will be created.
- If the path doesn't end with
.cs, the extension will be appended automatically.
[ClassName]: (Optional) The desired name for the generated C# class.- If omitted, the class name will be derived from the
<OutputFilePath>(e.g.,LoginPage.csresults in classLoginPage). - The name will be sanitized to be a valid C# identifier.
- If omitted, the class name will be derived from the
Important: Note the -- separator between dotnet run and the application arguments. This is required to pass arguments correctly to the application itself.
Examples:
# Generate a page object for Google search, save it locally
dotnet run -- "https://www.google.com" "./GoogleSearchPage.cs" GoogleSearchPage
# Generate a page object for GitHub login, save it to a specific project structure
# (Assumes the test project is in a sibling directory)
dotnet run -- "https://github.com/login" "../MyTests/PageObjects/GitHubLoginPage.cs"
# Generate a page object, letting the class name be derived from the file name
dotnet run -- "https://example.com" "./ExampleComPage.cs"The output .cs file will contain a class structure like this:
// Generated by PageObjectGenerator
// Timestamp: ...
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using SeleniumExtras.WaitHelpers;
// ... other usings
namespace YourProject.PageObjects // <-- CHANGE THIS NAMESPACE!
{
public class GeneratedClassName // e.g., LoginPage
{
private readonly IWebDriver _driver;
private readonly WebDriverWait _wait;
private readonly TimeSpan _defaultWaitTimeout = TimeSpan.FromSeconds(10);
public GeneratedClassName(IWebDriver driver)
{
_driver = driver ?? throw new ArgumentNullException(nameof(driver));
_wait = new WebDriverWait(_driver, _defaultWaitTimeout);
// Optional: Add wait for page load indicator here
}
// --- Locators ---
private static readonly By _elementNameLocator = By.Id("someId");
private static readonly By _anotherElementLocator = By.CssSelector("input[type='submit']");
// ... more locators
// --- Interaction Methods ---
public void ClickElementName()
{
// ... wait and click logic ...
}
public void EnterAnotherElementText(string text)
{
// ... wait, clear, and sendkeys logic ...
}
public string GetSomeElementText()
{
// ... wait and get text/value logic ...
}
public bool IsElementDisplayed()
{
// ... wait and check displayed logic ...
}
// ... more methods (Select for dropdowns, etc.)
// --- Helper Methods --- (Placeholder for custom waits)
}
}Key points about the generated code:
- Namespace: Defaults to
YourProject.PageObjects. You MUST change this to match the namespace structure of your target Reqnroll/Selenium project. - Dependencies: The class assumes your test project has references to
Selenium.WebDriver,Selenium.Support, andDotNetSeleniumExtras.WaitHelpersNuGet packages. - Constructor: Requires an
IWebDriverinstance. - Waits: Uses
WebDriverWaitandExpectedConditionsbefore actions.
- LOCATOR REVIEW IS ESSENTIAL: Auto-generated locators are a starting point. They might be brittle, non-unique, or based on attributes that change. Always review and refine the generated locators. Prefer stable attributes like
id,name, or customdata-testidattributes whenever possible. - Dynamic Content: The generator fetches the initial HTML source. It does not execute JavaScript. Elements loaded dynamically after the initial page load (via AJAX, etc.) might be missed or have inaccurate locators generated based on their initial state (if any).
- Frames/Iframes: The current version does not explicitly handle elements within
<iframe>or<frame>tags. You will need to manually add logic to switch frames (driver.SwitchTo().Frame(...)) in your test code or enhance the generator. - Shadow DOM: Elements within a Shadow DOM cannot be located using standard Selenium locators. This tool does not generate code to handle Shadow DOM interactions (which typically require
ExecuteScript). - Complex Interactions: Only basic interactions (click, type, select, get text) are generated. Actions like drag-and-drop, hover menus, file uploads, handling alerts, etc., need to be added manually.
- Wait Strategy: While explicit waits are included, the default timeout or conditions might need adjustment based on your specific application's performance and behaviour. Consider adding more sophisticated custom wait methods (e.g.,
WaitForAjaxToComplete,WaitForSpinnerToDisappear).
-
Place the File: Move the generated
.csfile into the appropriate Page Objects directory within your test project. -
Adjust Namespace: Open the file and change the
namespace YourProject.PageObjectsline to match your project's structure. -
Install Dependencies: Ensure your test project has the necessary NuGet packages:
Selenium.WebDriverSelenium.SupportDotNetSeleniumExtras.WaitHelpers
-
Instantiate in Steps: In your Reqnroll step definition files, you can instantiate the generated page object, passing the
IWebDriverinstance (which is typically managed by your test setup, possibly viaScenarioContextor a Dependency Injection container).// Example in a Reqnroll Step Definition class using YourProject.PageObjects; // Use the correct namespace // ... other usings [Binding] public class LoginSteps { private readonly IWebDriver _driver; private readonly LoginPage _loginPage; // Assuming generated class is LoginPage // Constructor injection is common public LoginSteps(IWebDriver driver) { _driver = driver; _loginPage = new LoginPage(_driver); } [When(@"I enter username ""(.*)""")] public void WhenIEnterUsername(string username) { _loginPage.EnterUsernameText(username); // Use generated method } [When(@"I click the login button")] public void WhenIClickTheLoginButton() { _loginPage.ClickLoginButton(); // Use generated method } // ... other steps }
- HtmlAgilityPack: Used for parsing the HTML content.