UI test automation solution for the Telerik Cart system, built with Selenium WebDriver and C#. This framework uses modern test automation practices, including the Page Object Model (POM) pattern, extensible reporting, and a modular test architecture.
The test suite structure:
- Page Objects: Encapsulate page-specific interactions and validations.
- Core Framework: Contains base classes, utilities, and test infrastructure.
- Test Cases: Organized by functional areas with categorization.
- Reporting: Using ExtentReports for detailed test execution documentation.
- .NET 8.0+
- Selenium WebDriver
- NUnit Test Framework
- ExtentReports
- WebDriverManager
- .NET 8.0 SDK or higher
- Chrome browser (latest version)
- IDE with .NET support (e.g., Visual Studio or JetBrains Rider)
git clone https://github.com/nikolarss0n/int-task-cart-ui-tests.git
cd int-task-cart-ui-tests
dotnet restore
Multiple options are available for executing different testing scenarios.
Execute specific test categories:
# Run all tests
dotnet test --logger "console;verbosity=detailed"
# Smoke tests (tests categorized as 'Smoke')
dotnet test --filter "TestCategory~Smoke" --logger "console;verbosity=detailed"
# Bundle Purchase tests
dotnet test --filter "TestCategory~'Bundle Purchase'" --logger "console;verbosity=detailed"
# Contact Info tests
dotnet test --filter "TestCategory~'Contact Info'" --logger "console;verbosity=detailed"
Note: Use TestCategory~
for partial matching of test categories, especially when tests have multiple categories.
Test behavior can be controlled via environment variables or command-line parameters:
# Run tests with the browser UI visible
TEST_HEADLESS=false dotnet test
Additional filtering mechanisms to block tracking and other unnecessary resources that could interfere with tests or impact performance. This is done through custom Chrome options settings in DriverFactory
.
- Disable tracking-related resources: The Chrome driver is configured to block or limit access to tracking scripts, ads, and other unnecessary content. This is achieved using
options.AddUserProfilePreference()
to manage settings like cookies, images, and other permissions:
options.AddUserProfilePreference("profile.default_content_settings.cookies", 2);
options.AddUserProfilePreference("profile.default_content_settings.images", 2);
options.AddUserProfilePreference("profile.default_content_settings.popups", 2);
options.AddUserProfilePreference("profile.default_content_settings.notifications", 2);
options.AddUserProfilePreference("profile.password_manager_enabled", false);
options.AddUserProfilePreference("credentials_enable_service", false);
These settings help ensure a cleaner, more predictable testing environment, reducing the risk of interference from third-party tracking components.
The framework generates detailed HTML reports using ExtentReports.
- Report Path: The reports are saved in the
TestResults/Report_[DATE_TIME]/
directory. - Accessing Reports: After test execution, you can find the report at the path specified by
ExtentService.ReportPath
. This path is also logged in the test execution summary.
Reports include:
- Execution summary
- Detailed test steps and logs
- Failure screenshots
- Execution metrics
The test framework includes a custom retry mechanism to handle transient failures and ensure test reliability.
The RetryUntilSuccess<T>
method is a generic utility that attempts to execute a specified action until it succeeds or a timeout is reached. It's particularly useful for handling flakiness due to asynchronous page updates or transient issues in UI tests.
public T RetryUntilSuccess<T>(
Func<T> action,
Func<T, bool> validateResult,
string operationName,
TimeSpan? timeout = null,
TimeSpan? interval = null)
action
: The operation to perform.validateResult
: A function to validate the result of the action.operationName
: A descriptive name of the operation for logging purposes.timeout
: The maximum duration to keep retrying.interval
: The wait time between retries.
public T RetryUntilSuccess<T>(
Func<T> action,
Func<T, bool> validateResult,
string operationName,
TimeSpan? timeout = null,
TimeSpan? interval = null)
{
timeout ??= TimeSpan.FromSeconds(10);
interval ??= TimeSpan.FromMilliseconds(500);
var stopwatch = Stopwatch.StartNew();
var attempts = 0;
Exception? lastException = null;
while (stopwatch.Elapsed < timeout)
{
attempts++;
try
{
Log($"Attempting {operationName}", $"Attempt {attempts}");
var result = action();
if (validateResult(result))
{
LogSuccess($"{operationName} succeeded", $"Attempt {attempts}, Duration: {stopwatch.ElapsedMilliseconds}ms");
return result;
}
Log($"Validation failed for {operationName}", $"Attempt {attempts}");
}
catch (Exception ex) when (ex is StaleElementReferenceException
|| ex is NoSuchElementException
|| ex is ElementNotInteractableException)
{
lastException = ex;
LogWarning($"{operationName} failed", $"Attempt {attempts}, Error: {ex.Message}");
}
if (stopwatch.Elapsed + interval.Value < timeout.Value)
{
Thread.Sleep(interval.Value);
}
}
var errorMessage = $"{operationName} failed after {attempts} attempts ({stopwatch.ElapsedMilliseconds}ms)";
LogError(errorMessage, lastException);
throw new WebDriverTimeoutException(errorMessage, lastException);
}
In the CartPage
class, the UpdateQuantity
method uses RetryUntilSuccess
to ensure the price stabilizes after updating the quantity.
public void UpdateQuantity(int quantity)
{
try
{
Log("Updating quantity", quantity.ToString());
// Use the common SelectKendoDropDownListOption method
SelectKendoDropDownListOption(
_updateLicenseQuantity,
quantity.ToString()
);
// Use the common retry mechanism for price stabilization
_commonComponents.RetryUntilSuccess(
CheckPriceStability,
isStable => isStable,
"Wait for price stabilization"
);
LogSuccess("Updated quantity", quantity.ToString());
}
catch (Exception ex)
{
LogError($"Failed to update quantity to {quantity}", ex);
TakeScreenshot("QuantityUpdateFailure");
throw;
}
}
├── Core/
│ ├── Base/ # Framework foundation classes
│ │ ├── BasePage.cs
│ │ ├── BaseTest.cs
│ │ ├── CommonComponents.cs
│ │ └── DriverFactory.cs
│ ├── Data/ # Test data models and generators
│ │ └── ContactFormData.cs
│ ├── Helpers/ # Utility functions and extensions
│ │ ├── PriceUtils.cs
│ │ └── RetryHelper.cs # Contains RetryUntilSuccess<T> method
│ └── Reporting/ # Reporting infrastructure
│ ├── Documentation/
│ │ ├── Extensions/
│ │ │ ├── DocumentationExtensions.cs
│ │ │ └── TechnicalLoggingExtension.cs
│ │ ├── Models/
│ │ │ ├── TestDoc.cs
│ │ │ └── TestResult.cs
│ │ ├── Templates/
│ │ │ ├── DocumentationTemplates.cs
│ │ │ └── ResultsTemplates.cs
│ │ └── TestDocumentation.cs
│ ├── ExtentService.cs
│ └── ExtentTestManager.cs
├── Pages/
│ ├── CartPage.cs
│ ├── ContactInfoPage.cs
│ ├── PurchasePage.cs
│ └── ReviewOrderPage.cs
├── Tests/
│ ├── BundleTests.cs
│ └── ContactInfoTests.cs
└── TestResults/
└── Report_[DATE_TIME]/ # Generated test reports
- Helpers/:
PriceUtils.cs
: Contains methods that helps with price conversion.
- Base/: Contains base classes for pages and tests (
BasePage.cs
,BaseTest.cs
, etc.). - Data/: Houses data models and test data management (
ContactFormData.cs
). - Reporting/: Implements ExtentReports configuration and custom reporters.
- Documentation/: Extensions and templates for test documentation.
Page Object Model (POM) classes for the application under test:
- CartPage.cs
- ContactInfoPage.cs
- PurchasePage.cs
- ReviewOrderPage.cs
Contains test classes organized by functionality:
- BundleTests.cs: Tests related to bundle purchases.
- ContactInfoTests.cs: Tests for the contact information flow.
The project uses modern C# features and nullable reference types:
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
When filtering tests by category, use the ~
operator for partial matching, especially if tests have multiple categories:
dotnet test --filter "TestCategory~Smoke"
To see which tests are executed during the test run, use the --logger
option with increased verbosity:
dotnet test --filter "TestCategory~Smoke" --logger "console;verbosity=detailed"
If the test report is not being generated:
- Ensure that
ExtentService.Flush()
is called after all tests have run. - Check that the report path is correctly configured in
ExtentService.cs
. - Verify that your tests are not exiting prematurely due to unhandled exceptions.
Parallel execution might interfere with report generation or test reliability.