C#自动化测试Selenium脚本从入门到精通:解决浏览器兼容性与元素定位难题的实战指南
引言:自动化测试的重要性与挑战
在现代软件开发中,自动化测试已经成为保证软件质量的关键环节。Selenium作为业界最流行的Web自动化测试框架,配合C#这门强大的编程语言,为测试工程师提供了无与伦比的测试能力。然而,许多初学者在实际应用中常常面临两个核心难题:浏览器兼容性问题和元素定位失败。
本文将从零开始,系统地讲解如何使用C#和Selenium构建稳定、可靠的自动化测试脚本,并深入剖析解决浏览器兼容性和元素定位难题的实战技巧。
第一部分:环境搭建与基础入门
1.1 开发环境准备
在开始编写Selenium测试脚本之前,我们需要准备以下开发环境:
- Visual Studio 2019⁄2022:推荐使用Community版本(免费)
- .NET Framework 4.7.2 或 .NET 6.0+
- Selenium WebDriver NuGet包
- 浏览器驱动:ChromeDriver、GeckoDriver(Firefox)、EdgeDriver
安装步骤:
- 创建新的C#控制台项目
- 通过NuGet包管理器安装以下包:
<!-- 在.csproj文件中添加或通过NuGet管理器安装 --> <PackageReference Include="Selenium.WebDriver" Version="4.15.0" /> <PackageReference Include="Selenium.Support" Version="4.15.0" /> <PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="119.0.6045.10500" /> <PackageReference Include="Selenium.WebDriver.GeckoDriver" Version="0.33.0" /> <PackageReference Include="Selenium.WebDriver.EdgeDriver" Version="119.0.2151.4" /> 1.2 第一个Selenium测试脚本
让我们从一个简单的例子开始,打开百度首页并搜索”C#自动化测试”:
using OpenQA.Selenium; using OpenQA.Selenium.Chrome; using System; using System.Threading; namespace SeleniumDemo { class Program { static void Main(string[] args) |// 1. 创建Chrome浏览器实例 using (IWebDriver driver = new ChromeDriver()) { // 2. 设置隐式等待时间(单位:秒) driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10); // 3. 打开百度首页 driver.Navigate().GoToUrl("https://www.baidu.com"); // 4. 定位搜索输入框并输入内容 IWebElement searchBox = driver.FindElement(By.Id("kw")); searchBox.Clear(); searchBox.SendKeys("C#自动化测试"); // 5. 定位搜索按钮并点击 IWebElement searchButton = driver.FindElement(By.Id("su")); searchButton.Click(); // 6. 等待搜索结果加载 Thread.Sleep(3000); // 7. 获取页面标题并输出 Console.WriteLine($"页面标题: {driver.Title}"); // 8. 关闭浏览器 driver.Quit(); } } } 1.3 代码详解与最佳实践
关键点说明:
- using语句:确保浏览器实例正确释放,避免资源泄漏
- ImplicitWait:设置隐式等待,避免硬编码的Sleep
- FindElement:定位元素的核心方法
- Quit() vs Close():Quit关闭所有窗口,Close关闭当前窗口
第二部分:元素定位策略详解
元素定位是Selenium自动化测试中最基础也最重要的环节。定位失败是导致脚本不稳定的主要原因。
2.1 八种定位方式详解
Selenium提供了8种元素定位方式,在C#中对应如下:
using OpenQA.Selenium; using System.Collections.Generic; public class ElementLocationDemo { public void DemonstrateAllLocators(IWebDriver driver) { // 1. By.Id - 最推荐,唯一且稳定 IWebElement elementById = driver.FindElement(By.Id("username")); // 2. By.Name - 常用于表单元素 IWebElement elementByName = driver.FindElement(By.Name("password")); // 3. By.ClassName - 注意:类名可能包含多个,用空格分隔 IWebElement elementByClassName = driver.FindElement(By.ClassName("login-button")); // 4. By.TagName - 用于定位特定标签 IWebElement elementByTagName = driver.FindElement(By.TagName("input")); // 5. By.LinkText - 精确匹配链接文本 IWebElement elementByLinkText = driver.FindElement(By.LinkText("登录")); // 6. By.PartialLinkText - 部分匹配链接文本 IWebElement elementByPartialLinkText = driver.FindElement(By.PartialLinkText("登录")); // 7. By.XPath - 最强大但性能较差 IWebElement elementByXPath = driver.FindElement(By.XPath("//input[@id='username']")); // 8. By.CssSelector - 推荐,性能好且灵活 IWebElement elementByCssSelector = driver.FindElement(By.CssSelector("input#username")); } } 2.2 高级定位技巧:相对定位
Selenium 4引入了相对定位(Relative Locators),这是解决复杂定位问题的利器:
using OpenQA.Selenium; using OpenQA.Selenium.Support.UI; using System; public class RelativeLocatorsDemo { public void RelativeLocationExample(IWebDriver driver) { // 先定位一个已知元素 IWebElement knownElement = driver.FindElement(By.Id("submit-button")); // 1. 定位已知元素上方的元素 IWebElement elementAbove = driver.FindElement( RelativeLocators.Above(knownElement)); // 2. 定位已知元素下方的元素 IWebElement elementBelow = driver.FindElement( RelativeLocators.Below(knownElement)); // 3. 定位已知元素左侧的元素 IWebElement elementLeft = driver.FindElement( RelativeLocators.ToLeftOf(knownElement)); // 4. 定位已知元素右侧的元素 IWebElement elementRight = driver.FindElement( RelativeLocators.ToRightOf(「knownElement)); // 5. 定位已知元素附近的元素(附近区域) IWebElement elementNear = driver.FindElement( RelativeLocators.Near(knownElement, 100)); // 100像素范围内 } } 2.3 动态元素定位实战
动态元素的ID或Class经常变化,我们需要更稳定的定位策略:
public class DynamicElementLocator { // 场景1:ID前缀固定,后缀动态变化 public IWebElement LocateDynamicElementByIdPrefix(IWebDriver driver) { // 使用XPath starts-with函数 return driver.FindElement(By.XPath("//div[starts-with(@id, 'user-profile-')]")); } // 场景2:元素包含特定文本 public IWebElement LocateElementByText(IWebDriver driver) { // 精确文本匹配 return driver.FindElement(By.XPath("//button[text()='提交']")); // 包含文本匹配 return driver.FindElement(By.XPath("//button[contains(text(), '提交')]")); } // 场景3:多个相似元素,通过父元素区分 public IWebElement LocateElementByParent(IWebDriver driver) { // 定位父容器下的特定子元素 return driver.FindElement(By.XPath("//div[@class='form-container']//input[@name='email']")); } // 场景4:使用CSS选择器高级语法 public IWebElement LocateElementByCssSelector(IWebDriver driver) { // 属性选择器 return driver.FindElement(By.CssSelector("input[type='email']")); // 子元素选择器 return driver.FindElement(By.CssSelector("div.form-group > input")); // 伪类选择器 return driver.FindElement(By.CssSelector("input:first-of-type")); } } 2.4 元素定位失败处理与重试机制
using System; using System.Threading; public class RetryLocator { /// <summary> /// 带重试机制的元素定位方法 </summary> public IWebElement FindElementWithRetry(IWebDriver driver, By by, int maxRetries = 3, int retryIntervalMs = 500) { for (int i = 0; i < maxRetries; i++) { try { return driver.FindElement(by); } catch (NoSuchElementException) { if (i == maxRetries - 1) throw; // 最后一次尝试失败,抛出异常 Thread.Sleep(retryIntervalMs); } } return null; } /// <summary> /// 等待元素出现并定位 </summary> public IWebElement WaitForElement(IWebDriver driver, By by, int timeoutSeconds = 10) { WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutSeconds)); return wait.Until(drv => { try { var element = drv.FindElement(by); return element.Displayed ? element : null; } catch (NoSuchElementException) { return null; } }); } } 第三部分:浏览器兼容性解决方案
浏览器兼容性是自动化测试中最具挑战性的问题之一。不同浏览器(Chrome、Firefox、Edge)以及同一浏览器的不同版本都可能导致脚本行为不一致。
3.1 多浏览器测试框架搭建
构建一个支持多浏览器的测试框架是解决兼容性问题的基础:
using OpenQA.Selenium; using OpenQA.Selenium.Chrome; using OpenQA.Selenium.Firefox; using OpenQA.Selenium.Edge; using System; using System.Collections.Generic; public enum BrowserType { Chrome, Firefox, Edge } public class BrowserFactory { /// <summary> /// 创建浏览器驱动实例 </summary> public static IWebDriver CreateDriver(BrowserType browserType, bool headless = false) { IWebDriver driver = null; switch (browserType) { case BrowserType.Chrome: var chromeOptions = new ChromeOptions(); if (headless) { chromeOptions.AddArgument("--headless"); } // 解决中文乱码问题 chromeOptions.AddArgument("--lang=zh-CN"); // 禁用图片加载以提升速度 chromeOptions.AddExcludedArgument("enable-automation"); chromeOptions.AddAdditionalOption("useAutomationExtension", false); driver = new ChromeDriver(chromeOptions); break; case BrowserType.Firefox: var firefoxOptions = new FirefoxOptions(); if (headless) { firefoxOptions.AddArgument("--headless"); } driver = new FirefoxDriver(firefoxOptions); break; case BrowserType.Edge: var edgeOptions = new EdgeOptions(); if (headless) { edgeOptions.AddArgument("--headless"); } driver = new EdgeDriver(edgeOptions); break; } // 统一设置 driver.Manage().Window.Maximize(); driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10); return driver; } /// <summary> /// 批量创建多个浏览器实例 </summary> public static List<IWebDriver> CreateMultipleDrivers(List<BrowserType> browserTypes) { var drivers = new List<IWebDriver>(); foreach (var browserType in browserTypes) { drivers.Add(CreateDriver(browserType)); } return drivers; } } 3.2 浏览器特定配置与优化
Chrome浏览器优化配置:
public class ChromeConfiguration { public static ChromeOptions GetOptimizedChromeOptions() { var options = new ChromeOptions(); // 基础配置 options.AddArgument("--start-maximized"); options.AddArgument("--disable-blink-features=AutomationControlled"); options.AddExcludedArgument("enable-automation"); options.AddAdditionalOption("useAutomationExtension", false); // 性能优化 options.AddArgument("--disable-gpu"); options.AddArgument("--no-sandbox"); options.AddArgument("--disable-dev-shm-usage"); // 解决常见问题 options.AddArgument("--lang=zh-CN"); options.AddArgument("--disable-web-security"); options.AddArgument("--allow-running-insecure-content"); // 隐藏自动化提示 options.AddExcludedArgument("enable-automation"); options.AddAdditionalOption("useAutomationExtension", false); // 设置用户代理(解决某些网站检测) options.AddArgument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"); return options; } } Firefox浏览器特殊配置:
public class FirefoxConfiguration { public static FirefoxOptions GetOptimizedFirefoxOptions() { var options = new FirefoxOptions(); // Firefox特定配置 options.SetPreference("dom.webdriver.enabled", false); options.SetPreference("useAutomationExtension", false); options.SetPreference("browser.download.folderList", 2); options.SetPreference("browser.download.manager.showWhenStarting", false); // 解决中文路径问题 options.SetPreference("intl.accept_languages", "zh-CN"); // 禁用图片加载(可选) options.SetPreference("permissions.default.image", 2); return options; } } 3.3 跨浏览器测试执行器
using System; using System.Collections.Generic; using System.Threading.Tasks; public class CrossBrowserTestExecutor { private readonly List<BrowserType> _browsersToTest; public CrossBrowserTestExecutor(List<BrowserType> browsers) { _browsersToTest = browsers; } /// <summary> /// 在所有浏览器上执行相同的测试逻辑 </summary> public async Task ExecuteCrossBrowserTest(Action<IWebDriver> testAction) { var tasks = new List<Task>(); foreach (var browserType in _browsersToTest) { tasks.Add(Task.Run(async () => { IWebDriver driver = null; try { driver = BrowserFactory.CreateDriver(browserType); Console.WriteLine($"开始在 {browserType} 上执行测试..."); // 执行测试逻辑 testAction(driver); Console.WriteLine($"在 {browserType} 上测试完成!"); } catch (Exception ex) { Console.WriteLine($"在 {browserType} 上测试失败: {ex.Message}"); // 截图保存失败信息 TakeScreenshot(driver, browserType.ToString()); } finally { driver?.Quit(); } })); } await Task.WhenAll(tasks); } private void TakeScreenshot(IWebDriver driver, string browserName) { if (driver == null) return; try { Screenshot screenshot = ((ITakesScreenshot)driver).GetScreenshot(); string fileName = $"error_{browserName}_{DateTime.Now:yyyyMMdd_HHmmss}.png"; screenshot.SaveAsFile(fileName); Console.WriteLine($"截图已保存: {fileName}"); } catch (Exception ex) { Console.WriteLine($"截图失败: {ex.Message}"); } } } 3.4 浏览器版本兼容性处理
public class BrowserVersionHandler { /// <summary> /// 检测浏览器版本并应用兼容性配置 </summary> public void ApplyCompatibilitySettings(IWebDriver driver) { IJavaScriptExecutor js = (IJavaScriptExecutor)driver; // 检测浏览器类型和版本 string browserName = driver.ExecuteScript("return navigator.appName").ToString(); string userAgent = driver.ExecuteScript("return navigator.userAgent").ToString(); Console.WriteLine($"浏览器信息: {userAgent}"); // 针对不同浏览器版本的特殊处理 if (userAgent.Contains("Chrome")) { // Chrome特定兼容性设置 ApplyChromeCompatibility(js); } else if (userAgent.Contains("Firefox")) { // Firefox特定兼容性设置 ApplyFirefoxCompatibility(js); } } private void ApplyChromeCompatibility(IJavaScriptExecutor js) { // 解决Chrome 115+版本某些元素点击问题 js.ExecuteScript(@" // 禁止元素被其他元素覆盖的检测 Element.prototype._originalClick = Element.prototype.click; Element.prototype.click = function() { try { this._originalClick(); } catch(e) { this.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window })); } }; "); } private void ApplyFirefoxCompatibility(IJavaScriptExecutor js) { // Firefox特定的DOM操作兼容性 js.ExecuteScript(@" // 确保focus事件正常工作 if (!Element.prototype._originalFocus) { Element.prototype._originalFocus = Element.prototype.focus; Element.prototype.focus = function() { this._originalFocus(); this.dispatchEvent(new Event('focus', { bubbles: true })); }; } "); } } 第四部分:高级定位技巧与实战案例
4.1 处理动态ID和随机属性
public class DynamicElementHandler { /// <summary> /// 处理ID随机生成的元素 </summary> public IWebElement LocateDynamicIdElement(IWebDriver driver, string idPrefix) { // 方法1: 使用XPath starts-with var xpath = $"//div[starts-with(@id, '{idPrefix}')]"; return driver.FindElement(By.XPath(xpath)); } /// <summary> /// 处理class随机生成的元素 </summary> public IWebElement LocateDynamicClassElement(IWebDriver driver, string className) { // 方法2: 使用CSS contains var css = $"[class*='{className}']"; return driver.FindElement(By.CssSelector(css)); } /// <summary> /// 处理多个相似元素,通过文本和位置组合定位 </summary> public IWebElement LocateElementByMultipleCriteria(IWebDriver driver, string containerClass, string buttonText) { // 组合定位:容器 + 按钮文本 var xpath = $"//div[@class='{containerClass}']//button[contains(text(), '{buttonText}')]"; return driver.FindElement(By.XPath(xpath)); } } 4.2 处理iframe和多窗口
public class FrameAndWindowHandler { /// <summary> /// 切换到iframe并操作元素 </summary> public void OperateInFrame(IWebDriver driver, string frameId) { // 等待iframe加载 WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10)); wait.Until(drv => drv.FindElement(By.Id(frameId))); // 切换到iframe driver.SwitchTo().Frame(frameId); try { // 在iframe内操作元素 IWebElement element = driver.FindElement(By.Id("inner-element")); element.Click(); } finally |// 切换回主文档 driver.SwitchTo().DefaultContent(); } /// <summary> /// 处理多窗口切换 </summary> public void SwitchToNewWindow(IWebDriver driver) { string originalWindow = driver.CurrentWindowHandle; var allWindows = driver.WindowHandles; // 等待新窗口出现 WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10)); wait.Until(drv => drv.WindowHandles.Count > 1); // 切换到新窗口 foreach (string window in driver.WindowHandles) { if (window != originalWindow) { driver.SwitchTo().Window(window); break; } } // 操作新窗口内容 Console.WriteLine($"新窗口标题: {driver.Title}"); // 关闭新窗口并切换回原窗口 driver.Close(); driver.SwitchTo().Window(originalWindow); } } 4.3 处理AJAX动态加载内容
public class AjaxContentHandler { /// <summary> /// 等待AJAX内容加载完成 </summary> public void WaitForAjax(IWebDriver driver, int timeoutSeconds = 10) { WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutSeconds)); wait.Until(drv => { IJavaScriptExecutor js = (IJavaScriptExecutor)drv; return (bool)js.ExecuteScript("return jQuery.active == 0"); }); } /// < /// 等待Angular应用加载完成 </summary> public void WaitForAngular(IWebDriver driver) { IJavaScriptExecutor js = (IJavaScriptExecutor)driver; js.ExecuteScript(@" var callback = arguments[arguments.length - 1]; if (window.angular) { angular.element(document).injector().get('$browser').notifyWhenNoOutstandingRequests(callback); } else { callback(); } "); } /// <summary> /// 等待React应用加载完成 </summary> public void WaitForReact(IWebDriver driver, int timeoutSeconds = 10) { WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutSeconds)); wait.Until(drv => { IJavaScriptExecutor js = (IJavaScriptExecutor)drv; return (bool)js.ExecuteScript(@" return window.__REACT_DEVTOOLS_GLOBAL_HOOK__ && window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.size > 0; "); }); } } 4.4 实战案例:电商网站登录与购物车操作
using System; using OpenQA.Selenium; using OpenQA.Selenium.Support.UI; public class EcommerceTestScenario { public void ExecuteEcommerceTest(IWebDriver driver) { try |// 步骤1: 登录 Login(driver, "testuser@example.com", "password123"); // 步骤2: 搜索商品 SearchProduct(driver, "笔记本电脑"); // 步骤3: 添加商品到购物车 AddToCart(driver); // 步骤4: 验证购物车 VerifyCart(driver); Console.WriteLine("电商测试场景执行成功!"); } catch (Exception ex) { Console.WriteLine($"测试失败: {ex.Message}"); TakeScreenshot(driver, "ecommerce_failure"); throw; } } private void Login(IWebDriver driver, string username, string password) { // 等待页面加载 WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10)); wait.Until(drv => drv.FindElement(By.Id("login-button"))); // 输入用户名 IWebElement usernameField = driver.FindElement(By.Id("username")); usernameField.Clear(); usernameField.SendKeys(username); // 输入密码 IWebElement passwordField = driver.FindElement(By.Id("password")); passwordField.Clear(); passwordField.SendKeys(password); // 点击登录 driver.FindElement(By.Id("login-button")).Click(); // 验证登录成功 wait.Until(drv => drv.FindElement(By.ClassName("user-menu"))); } private void SearchProduct(IWebDriver driver, string keyword) { IWebElement searchBox = driver.FindElement(By.Id("search-input")); searchBox.Clear(); searchBox.SendKeys(keyword); searchBox.SendKeys(Keys.Enter); // 等待搜索结果 WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10)); wait.Until(drv => drv.FindElements(By.ClassName("product-item")).Count > 0); } private void AddToCart(IWebDriver driver) { // 点击第一个商品 IWebElement firstProduct = driver.FindElement(By.CssSelector(".product-item:first-child")); firstProduct.Click(); // 等待商品详情页加载 WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10)); wait.Until(drv => drv.FindElement(By.Id("add-to-cart"))); // 点击加入购物车 driver.FindElement(By.Id("add-to-cart")).Click(); // 等待购物车更新提示 wait.Until(drv => drv.FindElement(By.ClassName("cart-count"))); } private void VerifyCart(IWebDriver driver) { // 进入购物车页面 driver.Navigate().GoToUrl("https://example.com/cart"); // 验证商品存在 WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10)); wait.Until(drv => drv.FindElement(By.ClassName("cart-item"))); // 验证商品数量 IWebElement quantity = driver.FindElement(By.ClassName("quantity")); Console.WriteLine($"购物车商品数量: {quantity.Text}"); } private void TakeScreenshot(IWebDriver driver, string name) { try { Screenshot screenshot = ((ITakesScreenshot)driver).GetScreenshot(); screenshot.SaveAsFile($"{name}_{DateTime.Now:yyyyMMdd_HHmmss}.png"); } catch { /* 忽略截图失败 */ } } } 第五部分:异常处理与日志记录
5.1 完善的异常处理机制
using System; using System.IO; using NLog; // 需要安装NLog NuGet包 public class SeleniumExceptionHandler { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); /// <summary> /// 带有详细异常信息的元素定位 </summary> public IWebElement SafeFindElement(IWebDriver driver, By by, int timeoutSeconds = 10) { try { WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutSeconds)); return wait.Until(drv => { try { var element = drv.FindElement(by); return element.Displayed ? element : null; } catch (NoSuchElementException) { Logger.Warn($"元素未找到: {by}"); return null; } }); } catch (WebDriverTimeoutException) { Logger.Error($"元素定位超时: {by}"); throw new NoSuchElementException($"无法在{timeoutSeconds}秒内找到元素: {by}"); } } /// <summary> /// 智能点击元素(处理各种点击异常) </summary> public void SafeClick(IWebDriver driver, IWebElement element) { try { // 方法1: 普通点击 element.Click(); } catch (ElementClickInterceptedException) { Logger.Warn("元素被遮挡,尝试使用JavaScript点击"); // 方法2: JavaScript点击 IJavaScriptExecutor js = (IJavaScriptExecutor)driver; js.ExecuteScript("arguments[0].click();", element); } catch (StaleElementReferenceException) { Logger.Warn("元素引用失效,重新定位后点击"); // 方法3: 重新定位后点击 By originalLocator = GetElementLocator(element); if (originalLocator != null) { IWebElement newElement = SafeFindElement(driver, originalLocator); newElement.Click(); } } } private By GetElementLocator(IWebElement element) { // 这里简化处理,实际项目中需要维护元素定位器映射 return null; } } 5.2 日志记录与调试
using System; using System.Diagnostics; using NLog; public class SeleniumLogger { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); /// <summary> |// 记录测试步骤 </summary> public void LogTestStep(string stepDescription) { Logger.Info($"【测试步骤】{stepDescription}"); Trace.WriteLine($"[STEP] {DateTime.Now:HH:mm:ss} - {stepDescription}"); } /// <summary> |// 记录页面信息 </summary> public void LogPageInfo(IWebDriver driver) { Logger.Info($"当前URL: {driver.Url}"); Logger.Info($"页面标题: {driver.Title}"); Logger.Info($"窗口句柄: {driver.CurrentWindowHandle}"); } /// <summary> |// 记录元素信息 </summary> public void LogElementInfo(IWebElement element) { Logger.Info($"元素文本: {element.Text}"); Logger.Info($"元素是否显示: {element.Displayed}"); Logger.Info($"元素是否可用: {element.Enabled}"); Logger.Info($"元素位置: ({element.Location.X}, {element.Location.Y})"); Logger.Info($"元素尺寸: {element.Size.Width}x{element.Size.Height}"); } /// <summary> |// 记录性能信息 </summary> public void LogPerformanceInfo(IWebDriver driver, string operationName, Stopwatch stopwatch) { stopwatch.Stop(); Logger.Info($"操作 '{operationName}' 耗时: {stopwatch.ElapsedMilliseconds}ms"); // 记录浏览器性能 IJavaScriptExecutor js = (IJavaScriptExecutor)driver; var timing = js.ExecuteScript(@" var performance = window.performance || {}; var timing = performance.timing; return { loadEventEnd: timing.loadEventEnd, domComplete: timing.domComplete, navigationStart: timing.navigationStart }; "); if (timing is System.Collections.Generic.Dictionary<string, object> timingDict) { long loadTime = (long)timingDict["loadEventEnd"] - (long)timingDict["navigationStart"]; Logger.Info($"页面加载时间: {loadTime}ms"); } } } 5.3 截图与错误报告
public class ErrorReporter { /// <summary> |// 生成详细的错误报告 </summary> public void GenerateErrorReport(IWebDriver driver, Exception ex, string testName) { string reportDir = $"ErrorReports/{DateTime.Now:yyyyMMdd}"; Directory.CreateDirectory(reportDir); string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); string reportPath = Path.Combine(reportDir, $"{testName}_{timestamp}.txt"); using (StreamWriter writer = new StreamWriter(reportPath)) { writer.WriteLine("=== Selenium Test Error Report ==="); writer.WriteLine($"时间: {DateTime.Now}"); writer.WriteLine($"测试名称: {testName}"); writer.WriteLine($"异常类型: {ex.GetType().Name}"); writer.WriteLine($"异常消息: {ex.Message}"); writer.WriteLine($"堆栈跟踪:n{ex.StackTrace}"); writer.WriteLine("n=== 页面信息 ==="); writer.WriteLine($"URL: {driver?.Url}"); writer.WriteLine($"标题: {driver?.Title}"); writer.WriteLine($"窗口句柄: {driver?.CurrentWindowHandle}"); writer.WriteLine($"窗口数量: {driver?.WindowHandles.Count}"); // 截图 if (driver != null) { try { Screenshot screenshot = ((ITakesScreenshot)driver).GetScreenshot(); string screenshotPath = Path.Combine(reportDir, $"{testName}_{timestamp}.png"); screenshot.SaveAsFile(screenshotPath); writer.WriteLine($"n截图已保存: {screenshotPath}"); } catch (Exception screenshotEx) { writer.WriteLine($"n截图失败: {screenshotEx.Message}"); } } } Console.WriteLine($"错误报告已生成: {reportPath}"); } } 第六部分:测试框架设计与最佳实践
6.1 Page Object Model (POM) 模式
POM是Selenium测试的最佳实践,将页面元素和操作封装成类:
// BasePage.cs - 所有页面的基类 public abstract class BasePage { protected IWebDriver Driver; protected WebDriverWait Wait; protected BasePage(IWebDriver driver) |// 验证页面是否正确加载 public abstract bool IsPageLoaded(); // 等待页面加载完成 public void WaitForPageLoad() { Wait.Until(drv => ((IJavaScriptExecutor)drv).ExecuteScript("return document.readyState").Equals("complete")); } } // LoginPage.cs - 登录页面 public class LoginPage : BasePage { // 页面元素定义 private By UsernameField = By.Id("username"); private By PasswordField = By.Id("password"); private By LoginButton = By.Id("login-button"); private By ErrorMessage = By.ClassName("error-message"); public LoginPage(IWebDriver driver) : base(driver) { } public override bool IsPageLoaded() { try { return Wait.Until(drv => drv.FindElement(LoginButton)).Displayed; } catch (WebDriverTimeoutException) { return false; } } // 页面操作方法 public void EnterUsername(string username) { IWebElement element = Wait.Until(drv => drv.FindElement(UsernameField)); element.Clear(); element.SendKeys(username); } public void EnterPassword(string password) { IWebElement element = Wait.Until(drv => drv.FindElement(PasswordField)); element.Clear(); element.SendKeys(password); } public void ClickLoginButton() { IWebElement button = Wait.Until(drv => drv.FindElement(LoginButton)); button.Click(); } public void Login(string username, string password) { EnterUsername(username); EnterPassword(password); ClickLoginButton(); } public string GetErrorMessage() { try { return Wait.Until(drv => drv.FindElement(ErrorMessage)).Text; } catch (WebDriverTimeoutException) { return null; } } } // HomePage.cs - 首页 public class HomePage : BasePage { private By UserMenu = By.ClassName("user-menu"); private By SearchBox = By.Id("search-input"); private By LogoutButton = By.LinkText("退出"); public HomePage(IWebDriver driver) : base(driver) { } public override bool IsPageLoaded() { try { return Wait.Until(drv => drv.FindElement(UserMenu)).Displayed; } catch (WebDriverTimeoutException) { return false; } } public bool IsUserLoggedIn() { try { return Driver.FindElement(UserMenu).Displayed; } catch (NoSuchElementException) { return false; } } public void Search(string keyword) { IWebElement searchBox = Wait.Until(drv => drv.FindElement(SearchBox)); searchBox.Clear(); searchBox.SendKeys(keyword); searchBox.SendKeys(Keys.Enter); } public void Logout() { Driver.FindElement(UserMenu).Click(); Wait.Until(drv => drv.FindElement(LogoutButton)).Click(); } } 6.2 测试基类与数据驱动测试
using NUnit.Framework; // 使用NUnit测试框架 using System.Collections.Generic; [TestFixture] public abstract class TestBase { protected IWebDriver Driver; protected SeleniumLogger Logger; protected ErrorReporter ErrorReporter; [SetUp] public void Setup() { Logger = new SeleniumLogger(); ErrorReporter = new ErrorReporter(); // 创建浏览器实例 Driver = BrowserFactory.CreateDriver(BrowserType.Chrome); Logger.LogTestStep("浏览器启动完成"); } [TearDown] public void Teardown() { if (TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Failed) { // 测试失败时生成报告 ErrorReporter.GenerateErrorReport( Driver, TestContext.CurrentContext.Result.Message, TestContext.CurrentContext.Test.Name); } if (Driver != null) { Driver.Quit(); Logger.LogTestStep("浏览器已关闭"); } } /// <summary> |// 数据驱动测试示例 </summary> public static IEnumerable<TestCaseData> LoginTestData { get { yield return new TestCaseData("validuser@example.com", "password123", true); yield return new TestCaseData("invaliduser@example.com", "wrongpass", false); yield return new TestCaseData("", "password123", false); yield return new TestCaseData("validuser@example.com", "", false); } } } // 具体的测试类 [TestFixture] public class LoginTests : TestBase { [Test] [TestCaseSource(nameof(LoginTestData))] public void TestLogin(string username, string password, bool shouldSucceed) { var loginPage = new LoginPage(Driver); Driver.Navigate().GoToUrl("https://example.com/login"); loginPage.Login(username, password); if (shouldSucceed) { var homePage = new HomePage(Driver); Assert.IsTrue(homePage.IsUserLoggedIn(), "用户应该登录成功"); } else { Assert.IsFalse(new HomePage(Driver).IsUserLoggedIn(), "用户不应该登录成功"); Assert.IsNotNull(loginPage.GetErrorMessage(), "应该显示错误消息"); } } } 6.3 并行测试执行
using System; using System.Threading.Tasks; using System.Collections.Concurrent; public class ParallelTestRunner { private readonly ConcurrentBag<TestResult> _results = new ConcurrentBag<TestResult>(); /// <summary> |// 并行执行多个测试 </summary> public async Task<ConcurrentBag<TestResult>> RunTestsInParallel( List<Func<Task>> testMethods, int maxDegreeOfParallelism = 3) { var options = new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }; await Parallel.ForEachAsync(testMethods, options, async (testMethod, cancellationToken) => { var driver = BrowserFactory.CreateDriver(BrowserType.Chrome); var result = new TestResult { TestName = testMethod.Method.Name }; try { var stopwatch = System.Diagnostics.Stopwatch.StartNew(); await testMethod(); stopwatch.Stop(); result.IsSuccess = true; result.Duration = stopwatch.ElapsedMilliseconds; result.Message = "测试通过"; } catch (Exception ex) { result.IsSuccess = false; result.Duration = 0; result.Message = ex.Message; } finally { driver?.Quit(); _results.Add(result); } }); return _results; } } public class TestResult { public string TestName { get; set; } public bool IsSuccess { get; set; } public long Duration { get; set; } public string Message { get; set; } } 第七部分:持续集成与自动化执行
7.1 与Jenkins集成
// Jenkinsfile 示例(Groovy) /* pipeline { agent any environment { NUGET_PATH = 'C:\tools\nuget.exe' TEST_RESULT_DIR = 'TestResults' } stages { stage('Checkout') { steps { checkout scm } } stage('Restore Packages') { steps { bat '"${NUGET_PATH}" restore' } } stage('Build') { steps { bat 'dotnet build --configuration Release' } } stage('Run Tests') { steps { bat 'dotnet test --logger "trx;LogFileName=test_results.trx" --results-directory "${TEST_RESULT_DIR}"' } } stage('Publish Results') { steps { publishHTML([ allowMissing: false, alwaysLinkToLastBuild: true, keepAll: true, reportDir: "${TEST_RESULT_DIR}", reportFiles: '*.html', reportName: 'Test Results' ]) } } } post { always { archiveArtifacts artifacts: '**/*.png', allowEmptyArchive: true junit '**/test_results.trx' } failure { emailext ( subject: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'", body: '''<p>测试失败,请查看详细日志。</p>''', to: "qa-team@example.com" ) } } } */ 7.2 Docker容器化部署
# Dockerfile FROM mcr.microsoft.com/dotnet/sdk:6.0 # 安装Chrome浏览器 RUN apt-get update && apt-get install -y wget gnupg && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list && apt-get update && apt-get install -y google-chrome-stable && rm -rf /var/lib/apt/lists/* # 安装ChromeDriver RUN wget -O /tmp/chromedriver.zip https://chromedriver.storage.googleapis.com/119.0.6045.105/chromedriver_linux64.zip && unzip /tmp/chromedriver.zip -d /usr/local/bin/ && rm /tmp/chromedriver.zip && chmod +x /usr/local/bin/chromedriver WORKDIR /app COPY . . # 恢复NuGet包 RUN dotnet restore # 构建项目 RUN dotnet build --configuration Release # 运行测试 CMD ["dotnet", "test", "--logger", "trx"] 7.3 测试报告生成
using System; using System.IO; using System.Xml.Linq; public class TestReportGenerator { /// <summary> |// 生成HTML测试报告 </summary> public void GenerateHtmlReport(List<TestResult> results, string outputPath) { var html = $@" <!DOCTYPE html> <html> <head> <title>测试报告</title> <style> body {{ font-family: Arial; margin: 20px; }} .header {{ background: #f0f0f0; padding: 15px; }} .result {{ padding: 10px; margin: 5px 0; border-left: 4px solid; }} .success {{ border-color: green; background: #e8f5e9; }} .failed {{ border-color: red; background: #ffebee; }} .summary {{ font-size: 18px; margin: 20px 0; }} </style> </head> <body> <div class='header'> <h1>自动化测试报告</h1> <p>生成时间: {DateTime.Now}</p> </div> <div class='summary'> <p>总测试数: {results.Count}</p> <p>通过: {results.Count(r => r.IsSuccess)}</p> <p>失败: {results.Count(r => !r.IsSuccess)}</p> <p>通过率: {(results.Count(r => r.IsSuccess) * 100.0 / results.Count):F2}%</p> </div> <div class='results'>"; foreach (var result in results) { string cssClass = result.IsSuccess ? "success" : "failed"; html += $@" <div class='result {cssClass}'> <strong>{result.TestName}</strong><br/> 状态: {(result.IsSuccess ? "通过" : "失败")}<br/> 耗时: {result.Duration}ms<br/> 消息: {result.Message} </div>"; } html += "</div></body></html>"; File.WriteAllText(outputPath, html); Console.WriteLine($"报告已生成: {outputPath}"); } } 第八部分:性能优化与高级技巧
8.1 显式等待与自定义等待条件
public class AdvancedWaitHandler { /// <summary> |// 等待元素可点击(考虑遮挡和动画) </summary> public IWebElement WaitForClickable(IWebDriver driver, By by, int timeoutSeconds = 10) { WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutSeconds)); return wait.Until(drv => { try { var element = drv.FindElement(by); return element.Displayed && element.Enabled ? element : null; } catch (StaleElementReferenceException) { return null; } }); } /// <summary> |// 等待元素文本变化 </summary> public void WaitForTextChange(IWebDriver driver, IWebElement element, string originalText, int timeoutSeconds = 10) { WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutSeconds)); wait.Until(drv => !element.Text.Equals(originalText)); } /// <summary> |// 等待元素数量变化 </summary> public void WaitForElementCount(IWebDriver driver, By by, int expectedCount, int timeoutSeconds = 10) { WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutSeconds)); wait.Until(drv => drv.FindElements(by).Count == expectedCount); } /// <summary> |// 自定义等待条件:等待元素包含特定属性值 </summary> public IWebElement WaitForAttributeContains(IWebDriver driver, By by, string attributeName, string expectedValue, int timeoutSeconds = 10) { WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutSeconds)); return wait.Until(drv => { try { var element = drv.FindElement(by); string actualValue = element.GetAttribute(attributeName); return actualValue != null && actualValue.Contains(expectedValue) ? element : null; } catch (NoSuchElementException) { return null; } }); } } 8.2 JavaScript执行与浏览器交互
public class JavaScriptExecutorHelper { private readonly IWebDriver _driver; private readonly IJavaScriptExecutor _js; public JavaScriptExecutorHelper(IWebDriver driver) { _driver = driver; _js = (IJavaScriptExecutor)driver; } /// <summary> |// 滚动到元素可见区域 </summary> public void ScrollToElement(IWebElement element) { _js.ExecuteScript(@" arguments[0].scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' }); ", element); } /// <summary> |// 滚动到页面底部 </summary> public void ScrollToBottom() { _js.ExecuteScript("window.scrollTo(0, document.body.scrollHeight);"); } /// <summary> |// 滚动到页面顶部 </summary> public void ScrollToTop() { _js.ExecuteScript("window.scrollTo(0, 0);"); } /// <summary> |// 检查元素是否在视口中 </summary> public bool IsElementInViewport(IWebElement element) { return (bool)_js.ExecuteScript(@" var rect = arguments[0].getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); ", element); } /// <summary> |// 获取页面性能指标 </summary> public Dictionary<string, long> GetPerformanceMetrics() { var result = _js.ExecuteScript(@" var performance = window.performance || {}; var timing = performance.timing; return { navigationStart: timing.navigationStart, unloadEventStart: timing.unloadEventStart, unloadEventEnd: timing.unloadEventEnd, redirectStart: timing.redirectStart, redirectEnd: timing.redirectEnd, fetchStart: timing.fetchStart, domainLookupStart: timing.domainLookupStart, domainLookupEnd: timing.domainLookupEnd, connectStart: timing.connectStart, connectEnd: timing.connectEnd, secureConnectionStart: timing.secureConnectionStart, requestStart: timing.requestStart, responseStart: timing.responseStart, responseEnd: timing.responseEnd, domLoading: timing.domLoading, domInteractive: timing.domInteractive, domContentLoadedEventStart: timing.domContentLoadedEventStart, domContentLoadedEventEnd: timing.domContentLoadedEventEnd, domComplete: timing.domComplete, loadEventStart: timing.loadEventStart, loadEventEnd: timing.loadEventEnd }; "); return result as Dictionary<string, long>; } /// <summary> |// 模拟网络延迟(用于测试) </summary> public void SimulateNetworkDelay(int delayMs) { _js.ExecuteScript($@" var originalFetch = window.fetch; window.fetch = function() {{ return new Promise((resolve) => {{ setTimeout(() => {{ originalFetch.apply(this, arguments).then(resolve); }}, {delayMs}); }}); }}; "); } } 8.3 性能监控与优化
public class PerformanceMonitor { private readonly List<PerformanceRecord> _records = new List<PerformanceRecord>(); /// <summary> |// 记录操作性能 </summary> public void RecordOperation(string operationName, Action action) { var stopwatch = System.Diagnostics.Stopwatch.StartNew(); try { action(); } finally { stopwatch.Stop(); _records.Add(new PerformanceRecord { OperationName = operationName, Duration = stopwatch.ElapsedMilliseconds, Timestamp = DateTime.Now }); } } /// <summary> |// 生成性能报告 </summary> public void GeneratePerformanceReport() { Console.WriteLine("n=== 性能报告 ==="); foreach (var record in _records.OrderBy(r => r.Timestamp)) { Console.WriteLine($"{record.Timestamp:HH:mm:ss} - {record.OperationName}: {record.Duration}ms"); } var total = _records.Sum(r => r.Duration); var avg = _records.Average(r => r.Duration); var max = _records.Max(r => r.Duration); Console.WriteLine($"n总耗时: {total}ms"); Console.WriteLine($"平均耗时: {avg:F2}ms"); Console.WriteLine($"最大耗时: {max}ms"); } private class PerformanceRecord { public string OperationName { get; set; } public long Duration { get; set; } public DateTime Timestamp { get; set; } } } 第九部分:常见问题解决方案
9.1 元素被遮挡无法点击
public class ElementClickHandler { /// <summary> |// 多种点击方式组合 </summary> public void ClickElement(IWebDriver driver, IWebElement element) { try { // 方法1: 普通点击 element.Click(); } catch (ElementClickInterceptedException) { try { // 方法2: JavaScript点击 IJavaScriptExecutor js = (IJavaScriptExecutor)driver; js.ExecuteScript("arguments[0].click();", element); } catch { // 方法3: 滚动到元素并点击 ScrollToElement(driver, element); System.Threading.Thread.Sleep(500); // 短暂等待动画完成 element.Click(); } } } private void ScrollToElement(IWebDriver driver, IWebElement element) { IJavaScriptExecutor js = (IJavaScriptExecutor)driver; js.ExecuteScript(@" arguments[0].scrollIntoView({ behavior: 'smooth', block: 'center' }); ", element); } } 9.2 处理文件下载
public class FileDownloadHandler { /// <summary> |// 配置Chrome下载选项 </summary> public ChromeOptions GetDownloadOptions(string downloadDirectory) { var options = new ChromeOptions(); // 设置下载路径 var prefs = new Dictionary<string, object> { { "download.default_directory", downloadDirectory }, { "download.prompt_for_download", false }, { "download.directory_upgrade", true }, { "safebrowsing.enabled", true } }; options.AddUserProfilePreference("prefs", prefs); return options; } /// <summary> |// 等待文件下载完成 </summary> public void WaitForDownload(string downloadDirectory, string fileName, int timeoutSeconds = 30) { var filePath = Path.Combine(downloadDirectory, fileName); var startTime = DateTime.Now; while (DateTime.Now - startTime < TimeSpan.FromSeconds(timeoutSeconds)) { if (File.Exists(filePath)) { // 等待文件完全写入(检查文件大小是否稳定) long size1 = new FileInfo(filePath).Length; System.Threading.Thread.Sleep(1000); long size2 = new FileInfo(filePath).Length; if (size1 == size2 && size1 > 0) { Console.WriteLine($"文件下载完成: {filePath}"); return; } } System.Threading.Thread.Sleep(500); } throw new TimeoutException($"文件 {fileName} 未在 {timeoutSeconds} 秒内下载完成"); } } 9.3 处理文件上传
public class FileUploadHandler { /// <summary> |// 使用AutoIt处理文件上传(适用于Windows) </summary> public void UploadFileWithAutoIt(string filePath) { // 需要安装AutoItX NuGet包 var autoIt = new AutoItX3(); // 等待上传窗口出现 autoIt.WinWait("[CLASS:#32770]", "", 10); // 设置文件路径 autoIt.ControlSetText("[CLASS:#32770]", "", "Edit1", filePath); // 点击打开按钮 autoIt.ControlClick("[CLASS:#32770]", "", "Button1"); } /// <summary> |// 使用SendKeys处理文件上传(简单场景) </summary> public void UploadFileWithSendKeys(IWebDriver driver, IWebElement fileInput, string filePath) { // 确保文件路径是绝对路径 string absolutePath = Path.GetFullPath(filePath); // 点击文件输入框 fileInput.Click(); // 使用SendKeys输入文件路径(需要窗口焦点) System.Windows.Forms.SendKeys.SendWait(absolutePath); System.Windows.Forms.SendKeys.SendWait("{ENTER}"); } /// <summary> |// 直接使用SendKeys到input元素(推荐) </summary> public void UploadFileDirectly(IWebDriver driver, By fileInputLocator, string filePath) { IWebElement fileInput = driver.FindElement(fileInputLocator); fileInput.SendKeys(Path.GetFullPath(filePath)); } } 9.4 处理验证码
public class CaptchaHandler { /// <summary> |// 注入验证码(测试环境) </summary> public void BypassCaptchaInTestEnvironment(IWebDriver driver, string captchaCode) { // 方法1: 如果验证码是input,直接填入 try { IWebElement captchaInput = driver.FindElement(By.Id("captcha-input")); captchaInput.SendKeys(captchaCode); return; } catch (NoSuchElementException) { } // 方法2: 通过JavaScript注入验证码值 IJavaScriptExecutor js = (IJavaScriptExecutor)driver; js.ExecuteScript(@" // 尝试多种可能的验证码字段 var captchaFields = ['captcha', 'verificationCode', 'checkcode', 'validateCode']; for (var i = 0; i < captchaFields.length; i++) { var field = document.querySelector(`input[name='${captchaFields[i]}']`); if (field) { field.value = arguments[0]; field.dispatchEvent(new Event('input', { bubbles: true })); return true; } } return false; ", captchaCode); } /// <summary> |// 使用第三方验证码识别服务(生产环境) </summary> public async Task<string> SolveCaptchaWithService(IWebDriver driver, string apiKey) { // 截图验证码区域 IWebElement captchaImage = driver.FindElement(By.Id("captcha-image")); Screenshot screenshot = ((ITakesScreenshot)driver).GetScreenshot(); // 保存验证码图片 string captchaPath = $"captcha_{DateTime.Now:yyyyMMdd_HHmmss}.png"; screenshot.SaveAsFile(captchaPath); // 调用验证码识别API(示例) // var result = await CaptchaService.Recognize(captchaPath, apiKey); // return result.Code; return "1234"; // 示例返回 } } 第十部分:总结与进阶学习
10.1 核心要点回顾
- 元素定位:优先使用ID和CSS选择器,XPath作为备选
- 等待机制:优先使用显式等待,避免硬编码Sleep
- 浏览器兼容性:使用BrowserFactory统一管理浏览器配置
- 异常处理:完善的异常处理和日志记录是脚本稳定性的关键
- POM模式:将页面元素和操作封装,提高代码可维护性
10.2 性能优化清单
- [ ] 使用隐式等待而非Sleep
- [ ] 优先使用CSS选择器而非XPath
- [ ] 减少不必要的页面刷新
- [ ] 使用无头模式进行批量测试
- [ ] 实现并行测试执行
- [ ] 缓存常用元素定位器
- [ ] 优化浏览器启动参数
10.3 进阶学习方向
- Selenium Grid:分布式测试执行
- Appium:移动端自动化测试
- Playwright:微软新一代自动化框架
- API测试集成:结合Postman或RestSharp
- AI辅助测试:使用机器学习优化元素定位
10.4 推荐工具与资源
- 测试框架:NUnit、xUnit、MSTest
- 日志框架:NLog、Serilog、log4net
- 报告框架:Allure、ExtentReports
- 持续集成:Jenkins、Azure DevOps、GitHub Actions
- 容器化:Docker、Kubernetes
10.5 完整项目结构示例
SeleniumAutomation/ ├── Pages/ # 页面对象 │ ├── BasePage.cs │ ├── LoginPage.cs │ ├── HomePage.cs │ └── ProductPage.cs ├── Utilities/ # 工具类 │ ├── BrowserFactory.cs │ ├── ElementLocator.cs │ ├── Logger.cs │ └── ReportGenerator.cs ├── Tests/ # 测试用例 │ ├── LoginTests.cs │ ├── ProductTests.cs │ └── CartTests.cs ├── TestData/ # 测试数据 │ ├── LoginData.json │ └── ProductData.json ├── Reports/ # 测试报告 ├── Drivers/ # 浏览器驱动 ├── app.config # 配置文件 └── SeleniumAutomation.csproj 结语
C#与Selenium的结合为Web自动化测试提供了强大的解决方案。通过本文的系统学习,您应该已经掌握了从基础入门到精通的完整知识体系。记住,优秀的自动化测试脚本不仅要能运行,更要稳定、可维护、可扩展。
在实际项目中,建议从简单的测试场景开始,逐步积累经验,不断完善测试框架。遇到问题时,善用日志、截图和调试工具,这些都是解决问题的利器。
最后,自动化测试是一个持续学习的过程,保持对新技术的关注,不断优化测试策略,您将成为一名出色的自动化测试工程师!
附录:常用代码片段速查表
// 等待元素可见 Wait.Until(drv => drv.FindElement(By.Id("element")).Displayed); // 等待元素可点击 Wait.Until(ExpectedConditions.ElementToBeClickable(By.Id("button"))); // 等待元素消失 Wait.Until(ExpectedConditions.InvisibilityOfElementLocated(By.Id("loading"))); // 等待元素文本变化 Wait.Until(drv => drv.FindElement(By.Id("status")).Text != "加载中..."); // 执行JavaScript IJavaScriptExecutor js = (IJavaScriptExecutor)driver; js.ExecuteScript("return document.readyState"); // 截图 Screenshot screenshot = ((ITakesScreenshot)driver).GetScreenshot(); screenshot.SaveAsFile("screenshot.png"); // 处理弹窗 IAlert alert = driver.SwitchTo().Alert(); alert.Accept(); // 或 alert.Dismiss(); // 切换frame driver.SwitchTo().Frame("frameId"); // 切换回主文档 driver.SwitchTo().DefaultContent(); // 多窗口切换 string mainWindow = driver.CurrentWindowHandle; foreach (string window in driver.WindowHandles) { if (window != mainWindow) { driver.SwitchTo().Window(window); break; } }
支付宝扫一扫
微信扫一扫