【问题标题】:Having Multiple Step Files Opens Multiple Browsers拥有多个步骤文件会打开多个浏览器
【发布时间】:2019-07-30 23:49:22
【问题描述】:

问题:

如果我有多个 Steps 文件,当我执行测试时,无论我运行哪个测试,似乎都在为每个 Steps 创建 WebDriver。

每当我运行测试时,我都会看到一个看似随机的 Chrome 浏览器打开。为了查看 SpecFlow 和 ChromeDriver 之间是否存在某种不兼容(我知道,这是一个很长的问题),我将用于搜索测试的 WebDriver 更改为 Firefox,并将用于登录测试的 WebDriver 保留为 Chrome。无论我运行什么测试,我总是看到 2 个浏览器打开;一台 Chrome 和一台 Firefox。

当我将 SearchTestSteps.cs 文件中的所有步骤移到 LoginTestSteps.cs 文件中时,问题就消失了。

所以,是的,这解决了眼前的问题,但将我的所有步骤放在一个文件中并不是最佳选择。这很快就会变得笨拙。

由于每组步骤都需要有自己的WebDriver,我很茫然。

这可能与文件夹结构和存储内容有关吗?这是我的样子。

Root
 |-Page Object Files
      |- Page Components
      |- Pages
      |- Test Tools  
 |- Step Definitions
      |- <*Steps.cs>  
 |- TESTS
      |- BDD Tests
          |-<*.feature>
      |- *standard selenium test files*

代码:

Login.feature
Feature: Login
    In order to be able to use Laserfiche
    As a legitimate user
    I want to be able to log into the repository

@SmokeTest
Scenario: Login with correct credentials
    Given I am on the Login page 
    And I have a good username/password combination
    And I select a repository
    When I fill out the form and submit
    Then I am taken to the repo page

---------------
LoginSteps.cs (I also have a SearchTestSteps.cs that looks very similar)
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using Selenium_C_Sharp_POC.Page_Object_Files.Pages;
using Selenium_C_Sharp_POC.Page_Object_Files.Test_Tools;
using TechTalk.SpecFlow;

namespace Selenium_C_Sharp_POC.StepDefinitions
{
    [Binding]
    public class LoginSteps
    {
        private static readonly IWebDriver Driver = new ChromeDriver();

        private static LoginPage _loginPage;
        private static string _username;
        private static string _password;
        private static string _repo;

        [AfterTestRun]
        public static void ShutDown()
        {
            Driver?.Close();
        }

        [Given(@"I am on the Login page")]
        public void GivenIAmOnTheLoginPage()
        {
            _loginPage = new LoginPage(Driver);
        }

        [Given(@"I have a good username/password combination")]
        public void GivenIHaveAGoodUsernamePasswordCombination()
        {
            _username = Nomenclature.WebClientPersonalUsername;
            _password = Nomenclature.WebClientPersonalPassword;
        }
        [Given(@"I select a repository")]
        public void GivenISelectARepository()
        {
            _repo = Nomenclature.RepoUnderTest;
        }

        [When(@"I fill out the form and submit")]
        public void WhenIFillOutTheFormAndSubmit()
        {
            _loginPage.Login(
                username: _username, 
                password: _password, 
                repo: _repo);
        }

        [Then(@"I am taken to the repo page")]
        public void ThenIAmTakenToTheRepoPage()
        {
            Assert.AreEqual(
                expected: _repo,
                actual: Driver.Title);

            HelperMethods.Logout(Driver);
        }
    }
}

【问题讨论】:

    标签: c# selenium selenium-webdriver bdd specflow


    【解决方案1】:

    我想出了如何使用绑定作用域来解决这个问题。

    在每个 Steps 文件中,我可以执行以下操作:

      [BeforeFeature(), Scope(Feature = "SearchTests")]
      public static void Startup()
      {
          _driver = new ChromeDriver();
      }
    
      [AfterFeature()]
      public static void ShutDown()
      {
          _driver?.Close();
      }
    

    这样做只会打开和关闭我想要的测试文件的驱动程序。 如果我需要更细化,我还可以选择在每次测试之前将范围限定为标签。

    【讨论】:

    • 酷。但我建议使用“open”方法创建一个 browser_steps.cs。在方法中检查驱动程序是否为零,然后根据您传递给方法的浏览器或在全局变量中创建驱动程序实例。通过这种方式,您不必在每个 step.cs 文件中处理驱动程序启动和拆卸。
    • 我也不愿意这样做。我创建了一个 baseSteps 类,该类具有使用前后功能方法创建的驱动程序...到目前为止,一切正常。
    • 酷。万事如意!
    【解决方案2】:

    您可能已经在每个 .cs 文件中创建了驱动程序实例。 例如:在 LoginSteps.cs 中,您正在以下位置创建 chrome 驱动程序。

    private static readonly IWebDriver Driver = new ChromeDriver();
    

    您应该在 xStep.cs 文件之外创建驱动程序,并将其传递给基于框架的类/方法。

    【讨论】:

    • 我想这就是我对 SpecFlow 的新奇之处。如何将驱动程序从 Gherkin 传递到步骤文件。似乎没有办法做到这一点。
    • 坦率地说,我对 SpecFlow 的操作并不多,但您必须使用“场景前”或“挂钩”。抱歉,我无法提供更多见解。检查此链接SpecFlow Documentation
    【解决方案3】:

    最终,这是由于在步骤定义类上将 Web 驱动程序创建为静态字段造成的。你需要集中这个逻辑。

    您想在功能之前创建 Web 驱动程序,将其注册到 SpecFlow 依赖注入容器,然后将该 IWebDriver 对象传递给您的步骤定义。

    我发现实现一个“惰性”Web 驱动程序是个好主意,这样浏览器窗口只会在您的 C# 代码实际需要与之交互时才会生成。这个LazyWebDriver 类实现了IWebDriver 接口,是一个真正的Web 驱动程序的包装器。

    LazyWebDriver.cs

    public sealed class LazyWebDriver : IWebDriver
    {
        private readonly Lazy<IWebDriver> driver;
    
        public string Title => driver.Value.Title;
    
        // Other properties defined in IWebDriver just pass through to driver.Value.Property
    
        public LazyWebDriver(Func<IWebDriver> driverFactory)
        {
            driver = new Lazy<IWebDriver>(driverFactory);
        }
    
        public IWebElement FindElement(By by)
        {
            return driver.Value.FindElement(by);
        }
    
        public void Close()
        {
            driver.Value.Close();
        }
    
        // other methods defined in IWebDriver just pass through to driver.Value.Method(...)
    }
    

    然后使用 SpecFlow 钩子(这只是 SpecFlow 中“事件”的花言巧语),您可以创建真正的 Web 驱动程序和惰性 Web 驱动程序,并将其注册到 SpecFlow 框架:

    SeleniumHooks.cs

    [Binding]
    public sealed class SeleniumHooks
    {
        private readonly IObjectContainer objectContainer;
    
        public SeleniumHooks(IObjectContainer objectContainer)
        {
            this.objectContainer = objectContainer;
        }
    
        [BeforeFeature]
        public void RegisterWebDriver()
        {
            objectContainer.RegisterInstanceAs<IWebDriver>(new LazyWebDriver(CreateWebDriver));
        }
    
        private IWebDriver CreateWebDriver()
        {
            return new ChromeDriver();
        }
    
        [AfterFeature]
        public void DestroyWebDriver()
        {
            objectContainer.Resolve<IWebDriver>()?.Close();
        }
    }
    

    最后,对 LoginSteps.cs 文件进行一些修改:

    [Binding]
    public class LoginSteps
    {
        private readonly IWebDriver Driver;
        private LoginPage _loginPage;
    
        private static string _username;
        private static string _password;
        private static string _repo;
    
        public LoginSteps(IWebDriver driver)
        {
            Driver = driver;
        }
    
        [Given(@"I am on the Login page")]
        public void GivenIAmOnTheLoginPage()
        {
            _loginPage = new LoginPage(Driver);
        }
    
        [Given(@"I have a good username/password combination")]
        public void GivenIHaveAGoodUsernamePasswordCombination()
        {
            _username = Nomenclature.WebClientPersonalUsername;
            _password = Nomenclature.WebClientPersonalPassword;
        }
        [Given(@"I select a repository")]
        public void GivenISelectARepository()
        {
            _repo = Nomenclature.RepoUnderTest;
        }
    
        [When(@"I fill out the form and submit")]
        public void WhenIFillOutTheFormAndSubmit()
        {
            _loginPage.Login(
                username: _username, 
                password: _password, 
                repo: _repo);
        }
    
        [Then(@"I am taken to the repo page")]
        public void ThenIAmTakenToTheRepoPage()
        {
            Assert.AreEqual(
                expected: _repo,
                actual: Driver.Title);
    
            HelperMethods.Logout(Driver);
        }
    }
    

    请注意,IWebDriver 对象作为构造函数参数传递给 LoginSteps。 SpecFlow 带有一个依赖注入框架,它足够智能,可以将您在 SeleniumHooks 中注册的 LazyWebDriver 作为 IWebDriver 参数传递给 LoginSteps 构造函数。

    确保将_loginPage 字段设为实例字段而不是静态字段。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-12
      • 1970-01-01
      相关资源
      最近更新 更多