具有多个步骤文件会打开多个浏览器

时间:2019-03-08 22:20:34

标签: c# selenium selenium-webdriver bdd specflow

问题:

如果我有多个“步骤”文件,则在执行测试时,无论我运行哪个测试,似乎每个WebDriver的创建过程都在进行。

每次运行测试时,我都会看到一个看似随机的Chrome浏览器打开。为了查看SpecFlow和ChromeDriver之间是否存在某种不兼容(我知道这是一个很长的眼光),我将用于搜索测试的WebDriver更改为Firefox,并保留了用于Chrome的登录测试的WebDriver。不管我运行了什么测试,我总是看到两个浏览器都打开了。一台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);
        }
    }
}

3 个答案:

答案 0 :(得分:1)

我弄清楚了如何使用“绑定范围”解决此问题。

在每个“步骤”文件中,我可以执行以下操作:

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

  [AfterFeature()]
  public static void ShutDown()
  {
      _driver?.Close();
  }

执行此操作将仅为我想要的测试文件打开和关闭驱动程序。 如果需要更详细的说明,我还可以选择在每次测试之前将范围限定在标签上。

答案 1 :(得分:0)

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

private static readonly IWebDriver Driver = new ChromeDriver();

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

答案 2 :(得分:0)

最终,这是由于在步骤定义类上将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字段设为实例字段而不是静态字段。