Mastering the Moves: Advanced Interactions & Forms
You've mastered the fundamentals – locating elements, implementing waits, organizing code with Page Objects, and building a solid framework foundation. Your tests can navigate pages, fill forms, and click buttons with confidence. But the web is more sophisticated than simple click-and-type interactions.
Real applications demand complex user behaviors: hovering over menus to reveal hidden options, dragging items between containers, selecting from intricate dropdowns, uploading files, and executing precise keyboard combinations. These interactions separate basic automation scripts from professional-grade test suites that truly mirror human behavior.
This lesson transforms you from someone who can automate simple workflows into someone who can tackle any interaction challenge the modern web throws at you. We're going to master the Actions API, conquer complex form elements, and integrate these advanced techniques seamlessly into your existing framework. 🎯
Beyond Basic Clicks – The Actions API
Up until now, we've relied on the simple Click() and SendKeys() methods available directly on IWebElement. These methods work perfectly for straightforward interactions, but they represent only a fraction of what users actually do on web pages. Consider these common scenarios that basic methods simply cannot handle:
A navigation menu that only appears when you hover over a parent item. An image that needs to be dragged from one container to another. A form that requires holding Ctrl while clicking multiple checkboxes to select them all. A context menu that appears only on right-click, revealing additional options.
This is where the Actions API becomes essential. Think of it as upgrading from a basic remote control with just power and volume buttons to a sophisticated universal remote that can orchestrate complex sequences of commands across multiple devices.
Introducing the Actions Class
The Actions class in Selenium is a builder pattern implementation that allows you to chain together multiple low-level interactions before executing them as a single, coordinated sequence. Rather than immediately performing actions like Click() does, Actions builds up a sequence of commands and then executes them all at once when you call Perform().
using OpenQA.Selenium.Interactions;
// Create an Actions instance tied to your WebDriver
Actions actions = new Actions(driver);
// Build a sequence of actions (this doesn't execute yet)
actions.MoveToElement(menuItem)
.Click(subMenuItem)
.Perform(); // This executes the entire sequence
The beauty of this approach lies in its precision. When you call Perform(), Selenium sends the entire sequence to the browser as a coordinated set of instructions, ensuring that timing between actions is consistent and that the browser treats them as a unified interaction rather than separate, potentially interrupted commands.
Hover Interactions – Revealing Hidden Elements
Hover interactions are perhaps the most commonly needed advanced technique in web automation. Many modern interfaces use hover states to reveal additional navigation options, show tooltips, or display contextual information. The MoveToElement() method simulates moving the mouse cursor to hover over an element, triggering any associated CSS :hover pseudo-class styles and JavaScript event handlers.
// Example: Hovering over a menu to reveal a submenu
IWebElement mainMenu = driver.FindElement(By.Id("products-menu"));
IWebElement subMenu = driver.FindElement(By.LinkText("Laptops"));
Actions actions = new Actions(driver);
actions.MoveToElement(mainMenu)
.Pause(TimeSpan.FromMilliseconds(500)) // Allow hover effect to activate
.Click(subMenu)
.Perform();
Notice the Pause() method in this example. This is crucial because hover effects often involve CSS transitions or JavaScript delays. The pause ensures that the hover state has time to fully activate before attempting to interact with newly revealed elements.
Timing is Everything with Hover
Different websites implement hover effects with varying timing mechanisms. Some reveal menus instantly, while others use CSS transitions that take several hundred milliseconds. When your hover interactions seem inconsistent, experiment with different pause durations. A good starting point is 300-500 milliseconds, but complex animations might require up to a full second.
Right-Click and Context Menus
Right-clicking to access context menus is another interaction that requires the Actions API. The ContextClick() method simulates a right mouse button click, which typically opens a context menu with additional options not available through the standard interface.
// Example: Right-clicking on an image to access context options
IWebElement image = driver.FindElement(By.CssSelector("img.gallery-photo"));
IWebElement saveOption = driver.FindElement(By.LinkText("Save Image"));
Actions actions = new Actions(driver);
actions.ContextClick(image)
.Click(saveOption)
.Perform();
Context menus present a particular challenge because they often appear dynamically and may position themselves differently based on screen space and cursor location. Always ensure that you wait for the context menu to appear before attempting to interact with its options.
Orchestrating Complex Gestures
While hover and right-click interactions solve many common automation challenges, the true power of the Actions API emerges when you need to coordinate multiple actions in precise sequences. This includes drag-and-drop operations, keyboard combinations, and multi-step interactions that mirror sophisticated user workflows.
Drag and Drop Operations
Drag-and-drop functionality appears in countless modern web applications: moving files between folders, reordering items in lists, transferring data between containers, or arranging elements in visual designers. The Actions API provides two approaches for implementing drag-and-drop: DragAndDrop() for simple scenarios and DragAndDropToOffset() for precise positioning.
// Simple drag and drop between two elements
IWebElement sourceElement = driver.FindElement(By.Id("draggable-item"));
IWebElement targetElement = driver.FindElement(By.Id("drop-zone"));
Actions actions = new Actions(driver);
actions.DragAndDrop(sourceElement, targetElement)
.Perform();
// Drag and drop to a specific coordinate offset
Actions actionsWithOffset = new Actions(driver);
actionsWithOffset.DragAndDropToOffset(sourceElement, 200, 100)
.Perform();
The DragAndDrop() method handles the complete sequence internally: it clicks and holds the source element, moves to the target element, and releases the mouse button. However, some drag-and-drop implementations require more granular control, which is where the step-by-step approach becomes valuable.
// Granular drag and drop for complex scenarios
Actions detailedDragDrop = new Actions(driver);
detailedDragDrop.ClickAndHold(sourceElement)
.Pause(TimeSpan.FromMilliseconds(200))
.MoveToElement(targetElement)
.Pause(TimeSpan.FromMilliseconds(200))
.Release()
.Perform();
This granular approach is particularly useful when dealing with applications that have complex drop validation logic or visual feedback systems that need time to process each step of the drag operation.
Keyboard Combinations and Multi-Key Sequences
Modern web applications extensively use keyboard shortcuts for power user functionality. Think of common combinations like Ctrl+A to select all text, Ctrl+C to copy, or Shift+Click to select multiple items. The Actions API handles these combinations through key down and key up sequences that mirror exactly how users perform these interactions.
// Example: Select all text and copy it
IWebElement textArea = driver.FindElement(By.Id("document-editor"));
Actions actions = new Actions(driver);
actions.Click(textArea) // Focus the element first
.KeyDown(Keys.Control)
.SendKeys("a") // Ctrl+A to select all
.KeyUp(Keys.Control)
.KeyDown(Keys.Control)
.SendKeys("c") // Ctrl+C to copy
.KeyUp(Keys.Control)
.Perform();
The sequence of KeyDown(), SendKeys(), and KeyUp() precisely mimics how a user physically presses and holds the Control key, presses another key, and then releases the Control key. This level of accuracy is crucial because many applications distinguish between different types of key events.
Cross-Platform Keyboard Considerations
Be aware that keyboard shortcuts vary between operating systems. Ctrl+C on Windows becomes Cmd+C on macOS. If your tests need to run across multiple platforms, consider detecting the operating system and using the appropriate key combinations, or focus on testing applications that handle cross-platform compatibility internally.
Building and Debugging Complex Action Chains
As your action sequences become more sophisticated, debugging becomes increasingly important. The Actions API builds commands in memory before execution, which means that if something goes wrong, it can be challenging to identify exactly which step in the sequence failed.
// Example: Complex interaction with built-in debugging
Actions complexSequence = new Actions(driver);
try
{
// Step 1: Focus the container
complexSequence.Click(containerElement);
// Step 2: Multi-select items with Ctrl+Click
complexSequence.KeyDown(Keys.Control)
.Click(firstItem)
.Click(secondItem)
.Click(thirdItem)
.KeyUp(Keys.Control);
// Step 3: Open context menu on selection
complexSequence.ContextClick(firstItem);
// Execute the entire sequence
complexSequence.Perform();
// Verify the result
Assert.IsTrue(contextMenu.Displayed, "Context menu should be visible after complex sequence");
}
catch (Exception ex)
{
// Log the state for debugging
Console.WriteLine($"Complex action sequence failed: {ex.Message}");
Console.WriteLine($"Current URL: {driver.Url}");
Console.WriteLine($"Active element: {driver.SwitchTo().ActiveElement().TagName}");
throw;
}
This approach breaks down complex sequences into logical steps with clear comments, making it easier to identify where problems occur. The exception handling provides additional context that can be invaluable when diagnosing timing issues or unexpected page states.
Dropdown Mastery with SelectElement
Dropdown elements represent one of the most deceptive challenges in web automation. They appear simple on the surface – just click and choose an option – but the reality is far more complex. Modern web applications implement dropdowns using various approaches: traditional HTML <select> elements, custom JavaScript widgets, CSS-styled pseudo-dropdowns, and hybrid implementations that combine multiple techniques.
Understanding when and how to use the SelectElement class versus other interaction approaches is crucial for building reliable tests that work across different dropdown implementations.
When SelectElement is Your Best Friend
The SelectElement class is specifically designed for traditional HTML <select> elements. These elements have a standardized structure with <option> children and well-defined behavior across browsers. When you encounter a true <select> element, SelectElement provides robust, reliable methods that handle the complexity of option selection automatically.
using OpenQA.Selenium.Support.UI;
// Example: Working with a traditional select dropdown
IWebElement selectElement = driver.FindElement(By.Id("country-selector"));
SelectElement dropdown = new SelectElement(selectElement);
// Multiple ways to select options
dropdown.SelectByText("Ukraine"); // By visible text
dropdown.SelectByValue("UA"); // By value attribute
dropdown.SelectByIndex(1); // By position (0-based)
The SelectElement class handles browser differences automatically. For instance, some browsers require clicking the dropdown before selecting an option, while others allow direct option selection. SelectElement abstracts away these implementation details, providing consistent behavior regardless of the underlying browser engine.
Querying Dropdown State and Options
Beyond selection, SelectElement provides powerful methods for inspecting dropdown state and available options. This capability is essential for data-driven tests and dynamic validation scenarios where you need to verify that dropdowns contain expected options or current selections.
SelectElement countryDropdown = new SelectElement(driver.FindElement(By.Id("country")));
// Get information about current state
bool isMultiSelect = countryDropdown.IsMultiple;
IWebElement selectedOption = countryDropdown.SelectedOption;
string selectedText = selectedOption.Text;
// Get all available options
IList<IWebElement> allOptions = countryDropdown.Options;
Console.WriteLine($"Dropdown contains {allOptions.Count} options");
// Verify specific options exist
bool hasCanada = allOptions.Any(option => option.Text == "Canada");
Assert.IsTrue(hasCanada, "Canada should be available in country dropdown");
// Get all currently selected options (useful for multi-select)
IList<IWebElement> selectedOptions = countryDropdown.AllSelectedOptions;
These query methods enable sophisticated validation logic. You can verify that dropdowns are populated correctly, that dependent dropdowns update appropriately when parent selections change, and that multi-select dropdowns maintain proper state across interactions.
Handling Multi-Select Dropdowns
Multi-select dropdowns allow users to choose multiple options simultaneously, typically by holding Ctrl while clicking or by using checkboxes within the dropdown interface. SelectElement provides specialized methods for working with these more complex scenarios.
// Example: Working with a multi-select dropdown
SelectElement skillsDropdown = new SelectElement(driver.FindElement(By.Id("skills")));
// Verify it's actually a multi-select
if (skillsDropdown.IsMultiple)
{
// Select multiple options
skillsDropdown.SelectByText("C#");
skillsDropdown.SelectByText("JavaScript");
skillsDropdown.SelectByText("SQL");
// Verify selections
var selectedSkills = skillsDropdown.AllSelectedOptions;
Assert.AreEqual(3, selectedSkills.Count, "Should have 3 skills selected");
// Deselect specific options
skillsDropdown.DeselectByText("SQL");
// Clear all selections
skillsDropdown.DeselectAll();
}
Multi-select scenarios require careful validation because the selection state is more complex than single-select dropdowns. Always verify both positive selections (what should be selected) and negative selections (what should not be selected) to ensure complete test coverage.
When SelectElement Won't Work – Custom Dropdowns
Many modern web applications abandon traditional <select> elements in favor of custom implementations built with <div>, <ul>, and <li> elements styled with CSS and powered by JavaScript. These custom dropdowns provide more visual flexibility and interactive features but require different automation approaches.
// Example: Handling a custom dropdown (not a real <select>)
// This dropdown opens when you click a trigger element
IWebElement dropdownTrigger = driver.FindElement(By.CssSelector(".custom-dropdown-trigger"));
dropdownTrigger.Click();
// Wait for the dropdown options to appear
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
IWebElement optionsList = wait.Until(d => d.FindElement(By.CssSelector(".dropdown-options")));
// Click the desired option
IWebElement desiredOption = driver.FindElement(By.XPath("//li[contains(text(), 'Premium Plan')]"));
desiredOption.Click();
// Verify the selection was made (custom dropdowns often update a hidden field or display element)
IWebElement selectedDisplay = driver.FindElement(By.CssSelector(".selected-value"));
Assert.AreEqual("Premium Plan", selectedDisplay.Text);
Custom dropdowns require understanding the specific implementation patterns used by each application. Some close automatically after selection, others require explicit closing actions, and many update hidden form fields or display elements to reflect the current selection.
Identifying Dropdown Types
Before writing dropdown automation code, inspect the element in browser DevTools. Look for the <select> tag – if present, use SelectElement. If you see <div>, <ul>, or other non-select elements, you're dealing with a custom implementation that requires standard WebDriver interactions like Click() and possibly the Actions API.
File Upload Automation & Test Data Strategy
File upload functionality presents unique challenges in test automation. Unlike other form elements that accept text input, file uploads require providing actual file paths and often involve complex JavaScript widgets, progress indicators, and validation logic. Additionally, file uploads force us to confront an important testing principle: how to organize and manage test data effectively.
Understanding File Input Elements
At its core, every file upload mechanism relies on HTML <input type="file"> elements. However, modern web applications often hide these elements behind custom styling and JavaScript interactions, making them appear as buttons, drag-and-drop zones, or sophisticated upload widgets. The key insight is that regardless of the visual presentation, the underlying file input element is what actually receives the file path.
// Example: Basic file upload using SendKeys
IWebElement fileInput = driver.FindElement(By.CssSelector("input[type='file']"));
// Provide the absolute path to the file
string filePath = @"C:\TestData\sample-document.pdf";
fileInput.SendKeys(filePath);
// Many applications automatically trigger upload after file selection
// Others require clicking an upload button
IWebElement uploadButton = driver.FindElement(By.Id("upload-btn"));
uploadButton.Click();
The SendKeys() approach works directly with visible file input elements, but many modern interfaces hide these elements for aesthetic reasons. In such cases, you might need to use JavaScript execution to make the element visible or directly set its value.
// Example: Handling hidden file input elements
IWebElement hiddenFileInput = driver.FindElement(By.CssSelector("input[type='file'][style*='display: none']"));
// Make the hidden element visible temporarily
IJavaScriptExecutor js = (IJavaScriptExecutor)driver;
js.ExecuteScript("arguments[0].style.display = 'block';", hiddenFileInput);
// Now it's visible and can receive the file path
hiddenFileInput.SendKeys(filePath);
// Hide it again to maintain the original UI state
js.ExecuteScript("arguments[0].style.display = 'none';", hiddenFileInput);
Cross-Platform File Path Handling
Hard-coding file paths as strings creates immediate portability problems. A test that works on Windows will fail on macOS or Linux due to different path separators and drive letter conventions. The solution is to use Path.Combine() and relative paths based on your test project structure.
// Example: Portable file path construction
public class FileUploadTests : BaseTest
{
private string GetTestFilePath(string fileName)
{
// Get the directory where the test assembly is located
string testDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
// Navigate to the test data folder relative to the test assembly
string testDataPath = Path.Combine(testDirectory, "TestData", fileName);
// Verify the file exists before returning the path
if (!File.Exists(testDataPath))
{
throw new FileNotFoundException($"Test file not found: {testDataPath}");
}
return testDataPath;
}
[Test]
public void UploadDocument_ValidPDF_ShouldSucceed()
{
// Navigate to upload page
driver.Navigate().GoToUrl("https://example.com/upload");
// Get portable file path
string pdfPath = GetTestFilePath("sample-document.pdf");
// Perform upload
IWebElement fileInput = driver.FindElement(By.CssSelector("input[type='file']"));
fileInput.SendKeys(pdfPath);
// Verify upload success
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(30));
IWebElement successMessage = wait.Until(d => d.FindElement(By.CssSelector(".upload-success")));
Assert.IsTrue(successMessage.Displayed);
}
}
Test Data Organization Strategy
Effective file upload testing requires a thoughtful approach to test data organization. You need different file types, sizes, and characteristics to test various scenarios: valid uploads, invalid file types, oversized files, corrupted files, and edge cases like empty files or files with special characters in names.
// Example: Organized test data structure
/*
TestProject/
├── Tests/
│ └── FileUploadTests.cs
├── TestData/
│ ├── ValidFiles/
│ │ ├── small-document.pdf (Under 1MB)
│ │ ├── large-document.pdf (Over size limit)
│ │ ├── sample-image.jpg
│ │ └── spreadsheet.xlsx
│ ├── InvalidFiles/
│ │ ├── executable.exe (Blocked file type)
│ │ ├── corrupted.pdf (Damaged file)
│ │ └── empty-file.txt (Zero bytes)
│ └── EdgeCases/
│ ├── file-with-spaces.pdf
│ ├── file_with_underscores.pdf
│ └── файл-unicode.pdf (Unicode characters)
*/
public static class TestFiles
{
public static readonly string SmallPDF = GetTestFilePath("ValidFiles", "small-document.pdf");
public static readonly string LargePDF = GetTestFilePath("ValidFiles", "large-document.pdf");
public static readonly string InvalidExecutable = GetTestFilePath("InvalidFiles", "executable.exe");
public static readonly string CorruptedFile = GetTestFilePath("InvalidFiles", "corrupted.pdf");
private static string GetTestFilePath(string category, string fileName)
{
string testDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
return Path.Combine(testDirectory, "TestData", category, fileName);
}
}
Upload Progress and Validation
Modern file upload interfaces often include progress indicators, real-time validation, and dynamic feedback. Testing these features requires understanding the timing and sequence of events that occur during the upload process.
// Example: Testing upload progress and validation
[Test]
public void UploadLargeFile_ShouldShowProgressIndicator()
{
// Navigate and start upload
driver.Navigate().GoToUrl("https://example.com/upload");
IWebElement fileInput = driver.FindElement(By.CssSelector("input[type='file']"));
fileInput.SendKeys(TestFiles.LargePDF);
// Verify progress indicator appears
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));
IWebElement progressBar = wait.Until(d => d.FindElement(By.CssSelector(".upload-progress")));
Assert.IsTrue(progressBar.Displayed, "Progress indicator should appear for large file uploads");
// Wait for upload completion (longer timeout for large files)
WebDriverWait longWait = new WebDriverWait(driver, TimeSpan.FromSeconds(60));
IWebElement completionMessage = longWait.Until(d => d.FindElement(By.CssSelector(".upload-complete")));
// Verify progress indicator is hidden after completion
Assert.IsFalse(progressBar.Displayed, "Progress indicator should hide after completion");
}
[Test]
public void UploadInvalidFileType_ShouldShowErrorMessage()
{
driver.Navigate().GoToUrl("https://example.com/upload");
IWebElement fileInput = driver.FindElement(By.CssSelector("input[type='file']"));
fileInput.SendKeys(TestFiles.InvalidExecutable);
// Error should appear quickly for validation failures
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
IWebElement errorMessage = wait.Until(d => d.FindElement(By.CssSelector(".upload-error")));
StringAssert.Contains("file type not allowed", errorMessage.Text.ToLower());
}
File Upload Timing Considerations
File uploads often involve longer wait times than other web interactions. Large files, slow networks, and server processing can cause uploads to take several minutes. Always use generous timeouts for upload completion waits, and consider the actual file sizes in your test data when setting timeout values.
Integration with Framework Patterns
Advanced interactions become truly powerful when integrated seamlessly into your existing test framework. Rather than scattering Actions API calls and file upload logic throughout individual test methods, we can extend our BasePage class to provide reusable, robust methods that encapsulate these complex interactions while maintaining the wait strategies and error handling patterns we've established. You can find the updated code in the 07-advanced-interactions folder of the course repository.
Extending BasePage with Actions Support
The BasePage class we built in earlier lessons provides an ideal foundation for advanced interaction methods. By adding Actions-based methods to this base class, every page object automatically gains access to sophisticated interaction capabilities while maintaining consistent behavior and error handling.
public class BasePage
{
protected readonly IWebDriver driver;
private readonly int _defaultTimeoutSeconds;
private Actions _actions;
public BasePage(IWebDriver driver, int defaultTimeoutSeconds = 10)
{
this.driver = driver;
_defaultTimeoutSeconds = defaultTimeoutSeconds;
_actions = new Actions(driver); // Initialize Actions once
}
// Hover interaction with built-in wait strategy
protected void HoverOverElement(By locator, int timeoutSeconds = 0)
{
var element = WaitForElementToBeVisible(locator, timeoutSeconds);
_actions.MoveToElement(element).Perform();
// Brief pause to allow hover effects to activate
Thread.Sleep(300);
}
// Hover and click sequence for dropdown menus
protected void HoverAndClick(By hoverLocator, By clickLocator, int timeoutSeconds = 0)
{
var hoverElement = WaitForElementToBeVisible(hoverLocator, timeoutSeconds);
_actions.MoveToElement(hoverElement).Perform();
// Wait for the click target to become available after hover
var clickElement = WaitForElementToBeClickable(clickLocator, timeoutSeconds);
clickElement.Click();
}
// Right-click with context menu handling
protected void RightClickElement(By locator, int timeoutSeconds = 0)
{
var element = WaitForElementToBeClickable(locator, timeoutSeconds);
_actions.ContextClick(element).Perform();
}
// Robust drag and drop with retry logic
protected bool DragAndDrop(By sourceLocator, By targetLocator, int timeoutSeconds = 0)
{
try
{
var sourceElement = WaitForElementToBeVisible(sourceLocator, timeoutSeconds);
var targetElement = WaitForElementToBeVisible(targetLocator, timeoutSeconds);
_actions.DragAndDrop(sourceElement, targetElement).Perform();
// Verify the drag operation succeeded by checking element positions or states
return true;
}
catch (Exception ex)
{
Console.WriteLine($"Drag and drop operation failed: {ex.Message}");
return false;
}
}
// File upload with validation
protected void UploadFile(By fileInputLocator, string fileName, int timeoutSeconds = 0)
{
var fileInput = WaitForElementToBePresent(fileInputLocator, timeoutSeconds);
string filePath = GetTestFilePath(fileName);
// Handle hidden file inputs
if (!fileInput.Displayed)
{
var js = (IJavaScriptExecutor)driver;
js.ExecuteScript("arguments[0].style.display = 'block';", fileInput);
}
fileInput.SendKeys(filePath);
}
private string GetTestFilePath(string fileName)
{
string testDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string fullPath = Path.Combine(testDirectory, "TestData", fileName);
if (!File.Exists(fullPath))
{
throw new FileNotFoundException($"Test file not found: {fullPath}");
}
return fullPath;
}
}
This enhanced BasePage provides a consistent interface for advanced interactions while maintaining the robust error handling and wait strategies we've established. Notice how each method combines the Actions API with our existing wait patterns, ensuring that elements are ready for interaction before attempting complex gestures.
Page Object Integration Patterns
With our enhanced BasePage, individual page objects can now implement sophisticated user workflows using simple, readable method calls. This abstraction allows test writers to focus on business logic rather than low-level interaction details.
public class ProductCatalogPage : BasePage
{
private readonly By _categoriesMenu = By.Id("categories-menu");
private readonly By _electronicsSubmenu = By.LinkText("Electronics");
private readonly By _productGrid = By.CssSelector(".product-grid");
private readonly By _sortDropdown = By.Id("sort-options");
private readonly By _filterPanel = By.CssSelector(".filter-sidebar");
public ProductCatalogPage(IWebDriver driver) : base(driver) { }
public void NavigateToElectronics()
{
// Use the hover-and-click method from BasePage
HoverAndClick(_categoriesMenu, _electronicsSubmenu);
// Verify we're on the right page
WaitForElementToBeVisible(_productGrid);
}
public void SortProductsByPrice()
{
var sortElement = WaitForElementToBeClickable(_sortDropdown);
var dropdown = new SelectElement(sortElement);
dropdown.SelectByText("Price: Low to High");
// Wait for products to re-render after sorting
Thread.Sleep(1000); // In a real scenario, wait for loading indicator to disappear
}
public void DragProductToCompare(string productName, string compareAreaId)
{
var productLocator = By.XPath($"//div[@data-product-name='{productName}']");
var compareLocator = By.Id(compareAreaId);
bool success = DragAndDrop(productLocator, compareLocator);
if (!success)
{
throw new InvalidOperationException($"Failed to drag {productName} to comparison area");
}
}
}
Hands-On: The Interaction Gauntlet
It's time to integrate these advanced techniques into our framework. This exercise will challenge you to apply multiple skills from this lesson.
Your Task: Build a Comprehensive Test Suite
- Navigate to the new
07-advanced-interactionsfolder in the course repository. Your goal is to add new tests and Page Objects to this project. - Task 1 (Dropdowns): In your
ProductsTests.csfile, write a new test that:- Uses a helper method and the
SelectElementclass to change the product sort order on the `saucedemo.com` inventory page - Verifies the currently selected option matches what you selected
- Asserts that the products have been correctly re-sorted (e.g., check that prices are in descending order when "Price (high to low)" is selected)
- Uses a helper method and the
- Task 2 (Actions API): Using the-internet.herokuapp.com/hovers:
- Create a new Page Object class
HoversPage.cs - Implement a method that hovers over a specific user profile (by index)
- Write a test that hovers over each profile and verifies the "View Profile" link appears
- Add a test that hovers and then clicks the "View Profile" link
- Create a new Page Object class
- Task 3 (Drag and Drop): Using the-internet.herokuapp.com/drag_and_drop:
- Create a Page Object for the drag and drop page
- Implement both the simple
DragAndDropmethod and the manual approach - Write tests that swap the two boxes and verify their new positions
- Bonus Task (File Uploads): Using the-internet.herokuapp.com/upload:
- Implement the
GetTestFilePathhelper in yourBaseTest - Create a sample text file in your
TestFilesfolder - Create a Page Object with an
UploadFilemethod - Write a test that uploads the file and verifies the success message
- Implement the
Completing these tasks demonstrates a professional-level ability to analyze a UI challenge and apply the correct advanced interaction technique within a clean framework structure.
Key Takeaways
- Actions API enables sophisticated interactions: Use the
Actionsclass for hover, right-click, drag-and-drop, and keyboard combinations that basic WebDriver methods cannot handle. - SelectElement handles traditional dropdowns: For HTML
<select>elements,SelectElementprovides robust selection methods by text, value, or index, plus powerful querying capabilities. - Custom dropdowns require standard interactions: Many modern applications use custom dropdown implementations built with
<div>and<li>elements that need basicClick()interactions instead ofSelectElement. - File uploads need portable path handling: Use
Path.Combine()and relative paths to create cross-platform test data organization that works on Windows, macOS, and Linux. - Framework integration amplifies capabilities: Adding advanced interaction methods to your
BasePageclass makes sophisticated behaviors available across all page objects while maintaining consistent error handling.
Deepen Your Knowledge
- Selenium Docs: Actions API The official documentation for the Actions class, with examples of building complex interaction sequences in different languages.
- Selenium Docs: Select Lists The official guide to using the SelectElement helper class for dropdowns.
- Selenium Docs: Keyboard Actions Deep dive into keyboard interactions including key combinations and text manipulation.
- The Internet by Sauce Labs An excellent playground with dedicated pages for practicing almost every advanced interaction, including hovers, drag-and-drop, alerts, and more.