C# Selenium如何根据Appsettings选择执行对应step defs或类
C# Selenium 技术栈下跨平台UI自动化复用实现方案
核心思路是通过抽象页面对象接口隔离平台差异,结合依赖注入在运行时根据配置加载对应平台的页面对象实现,完全复用同一套feature文件和step定义,不需要为两个平台维护重复的测试逻辑。
第一步:定义页面对象抽象层
先把两个平台共有的页面业务行为抽成公共接口,只定义业务动作,不涉及任何具体平台的元素定位逻辑。以登录页为例:
// 接口完全和业务绑定,与具体平台实现无关 public interface ILoginPage { void InputUsername(string username); void InputPassword(string password); void ClickLoginButton(); bool CheckLoginSuccessVisible(); }
所有业务页面都按这个规则抽取对应接口,比如IHomePage、IOrderListPage,接口只放两个平台都支持的公共操作,平台独有的逻辑不要放到公共接口里,单独做扩展即可。
第二步:分别实现两个平台的页面对象类
为平台A、平台B分别编写独立的页面对象实现类,所有类继承对应页面的公共接口,各自维护自己的XPath、元素标识符、组件定位逻辑,方法名、入参出参严格和接口保持一致。
平台A的登录页实现示例:
// 建议按命名空间区分平台实现,比如YourTest.Pages.PA、YourTest.Pages.PB,避免类名冲突 public class PALoginPage : ILoginPage { private readonly IWebDriver _driver; // 平台A专属元素定位规则,和平台B完全隔离 private IWebElement UsernameInput => _driver.FindElement(By.Id("pa-user-input")); private IWebElement PasswordInput => _driver.FindElement(By.XPath("//div[@class='pa-pwd-wrap']/input")); private IWebElement LoginBtn => _driver.FindElement(By.CssSelector(".pa-login-submit")); private IWebElement SuccessTip => _driver.FindElement(By.ClassName("pa-welcome-banner")); public PALoginPage(IWebDriver driver) { _driver = driver; } public void InputUsername(string username) => UsernameInput.SendKeys(username); public void InputPassword(string password) => PasswordInput.SendKeys(password); public void ClickLoginButton() => LoginBtn.Click(); public bool CheckLoginSuccessVisible() => SuccessTip.Displayed; }
平台B的登录页实现逻辑完全对齐接口,仅替换为平台B的元素定位规则即可:
public class PBLoginPage : ILoginPage { private readonly IWebDriver _driver; // 平台B专属元素定位规则 private IWebElement UsernameInput => _driver.FindElement(By.Name("pb-account-field")); private IWebElement PasswordInput => _driver.FindElement(By.XPath("//section[@id='pb-login-module']//input[@type='password']")); private IWebElement LoginBtn => _driver.FindElement(By.Id("pb-login-submit")); private IWebElement SuccessTip => _driver.FindElement(By.CssSelector(".pb-user-avatar-wrap")); public PBLoginPage(IWebDriver driver) { _driver = driver; } public void InputUsername(string username) => UsernameInput.SendKeys(username); public void InputPassword(string password) => PasswordInput.SendKeys(password); public void ClickLoginButton() => LoginBtn.Click(); public bool CheckLoginSuccessVisible() => SuccessTip.Displayed; }
第三步:添加运行时平台配置
在appsettings.json中添加目标平台配置项,运行前修改配置即可切换测试平台:
{ "TestConfig": { "TargetPlatform": "PA", // 可取值为PA/PB "PlatformBaseUrl": "对应平台的访问地址" } }
编写简单的配置读取类加载配置:
public class TestConfig { public string TargetPlatform { get; set; } public string PlatformBaseUrl { get; set; } public static TestConfig Load() { var config = new ConfigurationBuilder() .AddJsonFile("appsettings.json", optional: false) .AddEnvironmentVariables() // 支持CI环境通过环境变量覆盖配置 .Build(); return config.GetSection("TestConfig").Get<TestConfig>(); } }
第四步:通过依赖注入动态绑定平台实现
如果使用SpecFlow编写feature文件,直接用框架自带的DI容器即可,不需要引入第三方依赖。添加全局测试钩子类,在场景执行前根据配置注册对应平台的页面对象实现:
[Binding] public class TestStartup { private readonly IObjectContainer _container; private readonly TestConfig _config = TestConfig.Load(); public TestStartup(IObjectContainer container) { _container = container; } [BeforeScenario] public void InitTestEnv() { // 初始化浏览器驱动,可根据平台配置加差异化启动参数 var driver = new ChromeDriver(); driver.Manage().Window.Maximize(); driver.Navigate().GoToUrl(_config.PlatformBaseUrl); _container.RegisterInstanceAs<IWebDriver>(driver); // 核心逻辑:根据配置注入对应平台的页面对象 switch (_config.TargetPlatform.ToUpper()) { case "PA": _container.RegisterTypeAs<PALoginPage, ILoginPage>(); // 其余页面按相同格式注册,比如 // _container.RegisterTypeAs<PAHomePage, IHomePage>(); // _container.RegisterTypeAs<PAOrderPage, IOrderPage>(); break; case "PB": _container.RegisterTypeAs<PBLoginPage, ILoginPage>(); // 对应注册平台B的所有页面实现 // _container.RegisterTypeAs<PBHomePage, IHomePage>(); // _container.RegisterTypeAs<PBOrderPage, IOrderPage>(); break; default: throw new ArgumentException($"配置的目标平台{_config.TargetPlatform}不支持"); } } [AfterScenario] public void CleanupTestEnv() { var driver = _container.Resolve<IWebDriver>(); driver.Quit(); driver.Dispose(); } }
第五步:编写公共Step定义,完全复用测试逻辑
Step定义层只依赖公共页面对象接口,完全不感知当前运行的是哪个平台,两个平台100%共用:
[Binding] public class LoginStepDefs { private readonly ILoginPage _loginPage; // 构造函数注入的ILoginPage会自动根据配置加载对应平台的实现 public LoginStepDefs(ILoginPage loginPage) { _loginPage = loginPage; } [When(@"用户输入用户名 (.*) 密码 (.*)")] public void InputAccountInfo(string username, string pwd) { _loginPage.InputUsername(username); _loginPage.InputPassword(pwd); } [When(@"点击登录按钮")] public void SubmitLogin() { _loginPage.ClickLoginButton(); } [Then(@"登录成功状态正常展示")] public void VerifyLoginResult() { Assert.IsTrue(_loginPage.CheckLoginSuccessVisible()); } }
对应的feature文件不需要做任何平台适配,两个平台完全共用:
Feature: 用户登录 作为平台用户 我可以通过账号密码登录系统 Scenario: 正常账号登录成功 When 用户输入用户名 test001 密码 Pass@123 And 点击登录按钮 Then 登录成功状态正常展示
落地实用提示
- 若两个平台存在极个别流程分支差异,直接在step中读取当前平台配置走对应分支即可,不需要拆分两套step定义
- 通用页面操作(元素显式等待、页面滚动、全局弹窗处理等)可以抽成页面对象基类,让PA、PB的页面实现都继承基类,减少重复代码
- 后续平台迭代只需要修改对应平台的页面对象类即可,公共的feature、step逻辑完全不需要改动,维护成本很低
内容的提问来源于stack exchange,提问作者Ben Greenhill
相关产品推荐
相关产品推荐

