问题
The Problem:
If I have more than one Steps file, when I execute tests it seems that the WebDriver creation for each is being happening regardless of which test(s) I run.
I was seeing a seemingly random Chrome Browser open up whenever I ran my tests. In an attempt to see if there was some sort of incompatibility between SpecFlow and ChromeDriver (a long shot, I know), I changed the WebDriver for my search tests to Firefox and left the WebDriver for my login tests as Chrome. No matter what test(s) I ran, I always saw 2 browsers open; one Chrome and one Firefox.
When I moved all of the steps from my SearchTestSteps.cs file into the LoginTestSteps.cs file, the problem disappeared.
So, yeah, this solves the immediate issue, but it is sub-optimal to have all of my steps in a single file. That can quickly become unwieldy.
Since each set of steps needs to have its own WebDriver, I'm at a loss.
Might this have something to do with folder structure and where things are stored? Here is what mine looks like.
Root
|-Page Object Files
|- Page Components
|- Pages
|- Test Tools
|- Step Definitions
|- <*Steps.cs>
|- TESTS
|- BDD Tests
|-<*.feature>
|- *standard selenium test files*
The Code:
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);
}
}
}
回答1:
I figured out how to fix this issue using Binding Scopes.
In each of the Steps files, I can do the following:
[BeforeFeature(), Scope(Feature = "SearchTests")]
public static void Startup()
{
_driver = new ChromeDriver();
}
[AfterFeature()]
public static void ShutDown()
{
_driver?.Close();
}
Doing this opens and closes the Driver for only the Test file that I want it to. I can also choose to scope to the tag before each test if I need to get more granular.
回答2:
You might have created driver instance in each of the .cs files. Eg: In the LoginSteps.cs you are creating chrome driver in the below loc.
private static readonly IWebDriver Driver = new ChromeDriver();
You should create the driver outside the xStep.cs files and just pass it to the class/method based on the framework.
回答3:
Ultimately this is being caused by creating the web drivers as static fields on your step definition classes. You need to centralize this logic.
You want to create your web driver before the feature, register it with the SpecFlow dependency injection container, and then pass that IWebDriver object around to your step definitions.
I found it a good idea to implement a "lazy" web driver, so the browser window only gets spawned when your C# code actually needs to interact with it. This LazyWebDriver
class implements the IWebDriver interface, and is a wrapper for a real web driver.
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(...)
}
Then using SpecFlow hooks (which is just fancy-talk for "events" in SpecFlow) you can create your real web driver and the lazy web driver and register it with the SpecFlow framework:
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();
}
}
Lastly, a few modifications to your LoginSteps.cs file:
[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);
}
}
Notice that the IWebDriver object is passed in as a constructor argument to LoginSteps. SpecFlow comes with a dependency injection framework, which is smart enough to pass the LazyWebDriver you registered in SeleniumHooks as the IWebDriver argument to the LoginSteps constructor.
Be sure to make the _loginPage
field an instance field rather than static.
来源:https://stackoverflow.com/questions/55071795/having-multiple-step-files-opens-multiple-browsers