【发布时间】:2021-06-08 15:59:56
【问题描述】:
我有一个像这样的selenium-webdriver-di.cs 文件:
using TechTalk.SpecFlow;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium;
using BoDi;
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
public class WebDriverHooks
{
private readonly IObjectContainer container;
private static Dictionary<string, ChromeDriver> drivers = new Dictionary<string, ChromeDriver>();
private ScenarioContext _scenarioContext;
private FeatureContext _featureContext;
public WebDriverHooks(IObjectContainer container, ScenarioContext scenarioContext, FeatureContext featureContext)
{
this.container = container;
_scenarioContext = scenarioContext;
_featureContext = featureContext;
}
[BeforeFeature]
public static void CreateWebDriver(FeatureContext featureContext)
{
Console.WriteLine("BeforeFeature");
var chromeOptions = new ChromeOptions();
chromeOptions.AddArguments("--window-size=1920,1080");
drivers[featureContext.FeatureInfo.Title] = new ChromeDriver(chromeOptions);
}
[BeforeScenario]
public void InjectWebDriver(FeatureContext featureContext)
{
if (!featureContext.ContainsKey("driver"))
{
featureContext.Add("driver", drivers[featureContext.FeatureInfo.Title]);
}
}
[AfterFeature]
public static void DeleteWebDriver(FeatureContext featureContext)
{
((IWebDriver)featureContext["driver"]).Close();
((IWebDriver)featureContext["driver"]).Quit();
}
然后在每个包含步骤定义的 .cs 文件中,我都有这样的构造函数:
using System;
using TechTalk.SpecFlow;
using NUnit.Framework;
using OpenQA.Selenium;
using System.Collections.Generic;
using PfizerWorld2019.CommunityCreationTestAutomation.SeleniumUtils;
using System.Threading;
using System.IO;
namespace PfizerWorld2019
{
[Binding]
public class SharePointListAssets
{
private readonly IWebDriver driver;
public SharePointListAssets(FeatureContext featureContext)
{
this.driver = (IWebDriver)featureContext["driver"];
}
}
}
然后我在类的所有函数中使用driver 变量。最后,我有一个名为 Assembly.cs 的文件,用于 NUnit 夹具级并行化:
using NUnit.Framework;
[assembly: Parallelizable(ParallelScope.Fixtures)]
这在 SpecFlow 的术语中意味着功能级别的并行化(1 .feature 文件 = 1 Nunit 测试 = 1 Nunit 夹具)
如果我连续运行测试,它们可以正常工作。
但是如果我并行运行 2 个测试,任何两个测试,总是会发生一些有趣的事情。例如:第一个 Chromedriver 窗口尝试点击一个元素,当且仅当第二个 Chromedriver 窗口(运行不同的测试)呈现网站的完全相同的部分时,它才会点击它。但它会将点击发送到正确的窗口(第一个)。
我试过了:
- 使用
IObjectContainer接口,然后在InjectWebDriver函数中做containers[featureContext.FeatureInfo.Title].RegisterInstanceAs<IWebDriver>(drivers[featureContext.FeatureInfo.Title]) - 使用
Thread.CurrentThread.ToString()代替featureContext.FeatureInfo.Title进行索引 - 在
CreateWebDriver函数中执行featureContext.Add(featureContext.FeatureInfo.Title + "driver", new ChromeDriver(chromeOptions)而不是drivers[featureContext.FeatureInfo.Title] = new ChromeDriver(chromeOptions);。
我只是不明白是什么允许这种“共享”。既然FeatureContext 用于所有与驱动程序生成和销毁相关的事情,那么两个 chromedrivers 如何相互通信?
更新:我尝试了驱动程序初始化和这样的共享:
[BeforeFeature]
public static void CreateWebDriver(FeatureContext featureContext)
{
var chromeOptions = new ChromeOptions();
chromeOptions.AddArguments("--window-size=1920,1080");
chromeOptions.AddArguments("--user-data-dir=C:/ChromeProfiles/Profile" + uniqueIndex);
WebdriverSafeSharing.setWebDriver(TestContext.CurrentContext.WorkerId, new ChromeDriver(chromeOptions));
}
我制作了一个 webdriver-safe-sharing.cs 文件,如下所示:
class WebdriverSafeSharing
{
private static Dictionary<string, IWebDriver> webdrivers = new Dictionary<string, IWebDriver>();
public static void setWebDriver(string driver_identification, IWebDriver driver)
{
webdrivers[driver_identification] = driver;
}
public static IWebDriver getWebDriver(string driver_identification)
{
return webdrivers[driver_identification];
}
}
然后在每个步骤定义函数中,我只是调用WebdriverSafeSharing.getWebDriver(TestContext.CurrentContext.WorkerId) 而不涉及FeatureContext。我仍然遇到同样的问题。请注意我是如何处理chromeOptions.AddArguments("--user-data-dir=C:/ChromeProfiles/Profile" + uniqueIndex); 的,因为我也开始不相信 chromedriver 本身是线程安全的。但即便如此也无济于事。
更新 2:它尝试了一个更加偏执的 webdriver-safe-sharing.cs 类:
class WebdriverSafeSharing
{
private static readonly Dictionary<string, ThreadLocal<IWebDriver>> webdrivers = new Dictionary<string, ThreadLocal<IWebDriver>>();
private static int port = 7000;
public static void setWebDriver(string driver_identification)
{
lock (webdrivers)
{
ChromeDriverService service = ChromeDriverService.CreateDefaultService();
service.Port = port;
var chromeOptions = new ChromeOptions();
chromeOptions.AddArguments("--window-size=1920,1080");
ThreadLocal<IWebDriver> driver =
new ThreadLocal<IWebDriver>(() =>
{
return new ChromeDriver(service, chromeOptions);
});
webdrivers[driver_identification] = driver;
port += 1;
Thread.Sleep(1000);
}
}
public static IWebDriver getWebDriver(string driver_identification)
{
return webdrivers[driver_identification].Value;
}
它有一个锁、一个 Threadlocal 和唯一的端口。它仍然不起作用。完全相同的问题。
更新 3:如果我运行两个单独的 Visual Studio 实例并为每个实例运行 1 个测试,它就可以工作。或者让两个相同的项目并排运行
然后选择并行运行测试:
【问题讨论】:
-
有点困惑:“1 .feature 文件 = 1 Nunit 测试 = 1 Nunit 夹具。” NUnit 测试与 NUnit 夹具不同。有一个文档参考来描述 SpecFlow 如何将功能和场景映射到 NUnit 实体会有所帮助。
-
@charlie 我在 specflow 文档中找不到任何此类文档。您对 1 Nunit 夹具!= 1 Nunit 测试的评论让我感到困扰。如果 1 个 Nunit 测试包含超过 1 个固定装置,那对我来说不是问题吗?但是,Specflow 自动生成的代码为每个特征文件创建 1 个夹具,然后 Nunit 将每个特征文件视为测试,这就是我制作这个 3ple 方程的原因
-
NUnit 中的测试分类...
-
NUnit 使用术语测试来表示一种测试,简单的或复合的。简单(单个)测试是测试用例,由方法和可选参数表示。有各种各样的套件,其中之一是 TestFixture,表示为一个类的单个实例,可以使用或不使用构造函数参数创建。您的示例 TestFixture 包含两个 TestCases。我猜您对“测试”一词的使用在这种情况下只是意味着其他东西,所以我们可能都是“正确的”,但是在处理 NUnit 时,了解 NUnit 术语是件好事。跨度>
-
归根结底是你最终通过 NUnit 框架运行 NUnit 测试,它完全不了解特性、场景、特性上下文等。可惜没有公共文档SpecFlow 如何将其自己的概念转换为 NUnit,以便更广泛的人(比如我!)实际上可以帮助解决此类问题。
标签: c# selenium parallel-processing nunit specflow