拥有多个步骤文件会打开多个浏览器
Having Multiple Step Files Opens Multiple Browsers
问题:
如果我有多个 Steps 文件,当我执行测试时,无论我执行哪个测试,似乎都会为每个文件创建 WebDriver 运行。
每当我 运行 我的测试时,我都会看到一个看似 运行dom 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);
}
}
}
您可能已经在每个 .cs 文件中创建了驱动程序实例。
例如:在 LoginSteps.cs 中,您正在以下位置创建 chrome 驱动程序。
private static readonly IWebDriver Driver = new ChromeDriver();
您应该在 xStep.cs 文件之外创建驱动程序,然后将其传递给基于框架的 class/method。
我想出了如何使用绑定范围解决这个问题。
在每个步骤文件中,我可以执行以下操作:
[BeforeFeature(), Scope(Feature = "SearchTests")]
public static void Startup()
{
_driver = new ChromeDriver();
}
[AfterFeature()]
public static void ShutDown()
{
_driver?.Close();
}
这样做只会打开和关闭我想要的测试文件的驱动程序。
如果我需要更细化,我也可以选择在每次测试之前将范围限定到标签。
最终这是由于在您的步骤定义 classes 中将 Web 驱动程序创建为静态字段引起的。你需要集中这个逻辑。
您想在该功能之前创建您的 Web 驱动程序,将其注册到 SpecFlow 依赖项注入容器,然后将该 IWebDriver 对象传递给您的步骤定义。
我发现实施 "lazy" 网络驱动程序是个好主意,因此浏览器 window 仅在您的 C# 代码实际需要与其交互时才会生成。这个 LazyWebDriver
class 实现了 IWebDriver 接口,是一个真正的网络驱动程序的包装器。
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 中 "events" 的花言巧语)您可以创建真正的 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
字段成为实例字段而不是静态字段。
问题:
如果我有多个 Steps 文件,当我执行测试时,无论我执行哪个测试,似乎都会为每个文件创建 WebDriver 运行。
每当我 运行 我的测试时,我都会看到一个看似 运行dom 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);
}
}
}
您可能已经在每个 .cs 文件中创建了驱动程序实例。 例如:在 LoginSteps.cs 中,您正在以下位置创建 chrome 驱动程序。
private static readonly IWebDriver Driver = new ChromeDriver();
您应该在 xStep.cs 文件之外创建驱动程序,然后将其传递给基于框架的 class/method。
我想出了如何使用绑定范围解决这个问题。
在每个步骤文件中,我可以执行以下操作:
[BeforeFeature(), Scope(Feature = "SearchTests")]
public static void Startup()
{
_driver = new ChromeDriver();
}
[AfterFeature()]
public static void ShutDown()
{
_driver?.Close();
}
这样做只会打开和关闭我想要的测试文件的驱动程序。 如果我需要更细化,我也可以选择在每次测试之前将范围限定到标签。
最终这是由于在您的步骤定义 classes 中将 Web 驱动程序创建为静态字段引起的。你需要集中这个逻辑。
您想在该功能之前创建您的 Web 驱动程序,将其注册到 SpecFlow 依赖项注入容器,然后将该 IWebDriver 对象传递给您的步骤定义。
我发现实施 "lazy" 网络驱动程序是个好主意,因此浏览器 window 仅在您的 C# 代码实际需要与其交互时才会生成。这个 LazyWebDriver
class 实现了 IWebDriver 接口,是一个真正的网络驱动程序的包装器。
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 中 "events" 的花言巧语)您可以创建真正的 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
字段成为实例字段而不是静态字段。