Issue while migrating from SpecFlow with hooks constructor injection
Reqnroll Version
2.1.0
Which test runner are you using?
NUnit
Test Runner Version Number
4.2.2
.NET Implementation
.NET 8.0
Test Execution Method
Visual Studio Test Explorer
Content of reqnroll.json configuration file
{ "$schema": "https://schemas.reqnroll.net/reqnroll-config-latest.json", "generator": { "addNonParallelizableMarkerForTags": [ "nonparallelizable" ] }, "runtime": { "missingOrPendingStepsOutcome": "Error" }, "trace": { "traceSuccessfulSteps": true, "traceTimings": false, "minTracedDuration": "0:0:0.0", "stepDefinitionSkeletonStyle": "RegexAttribute" } }
Issue Description
Note that I use my own implementations of Actions (I don't use Reqnroll/Specflow actions nuget packages)
I am unable to migrate AS IT my hooks class.
public Hooks(
IObjectContainer objectContainer,
IReqnrollOutputHelper reqnrollOutputHelper,
BrowserDriver browserDriver, // Injection doesn't work
IBrowserInteractions browserInteractions, // Injection doesn't work
ISeleniumConfiguration seleniumConfiguration, // Injection doesn't work
ITestThreadExecutionEventPublisher testThreadExecutionEventPublisher)
{
_objectContainer = objectContainer ?? throw new Exception($"{nameof(objectContainer)} is null");
_reqnrollOutputHelper = reqnrollOutputHelper ?? throw new Exception($"{nameof(reqnrollOutputHelper)} is null");
_browserInteractions = browserInteractions ?? throw new Exception($"{nameof(browserInteractions)} is null");
_browserDriver = browserDriver ?? throw new Exception($"{nameof(browserDriver)} is null");
_seleniumConfiguration = seleniumConfiguration ?? throw new Exception($"{nameof(seleniumConfiguration)} is null");
_testThreadExecutionEventPublisher = testThreadExecutionEventPublisher ?? throw new Exception($"{nameof(testThreadExecutionEventPublisher)} is null");
Constructor injection is working fine in SpecFlow for all constructor parameters but in Reqnroll it doesn't works for :
- BrowserDriver browserDriver
- IBrowserInteractions browserInteractions
- ISeleniumConfiguration seleniumConfiguration
For example, I get the following error when running a test in VS :
Reqnroll.BoDi.ObjectContainerException : Interface cannot be resolved: Reqnroll.Actions.Selenium.DriverInitialisers.IDriverInitialiser (resolution path: ALOP.Hooks-Reqnroll.Actions.Selenium.BrowserDriver)
Steps to Reproduce
Add parameter of the mentionned type in hooks contructor
Link to Repro Project
No response
I have also tried this :
_objectContainer = ((ObjectContainer)objectContainer).BaseContainer;
But with no luck
@gasparnagy : I can send you my VS solution if you need.
@DomZZ Could you please share the code where you register the implementations to the interfaces like IDriverInitialiser or IBrowserInteractions? The problem is most probably with that registrations not being executed (can you try with a break point)?
@DomZZ Do you have any feedback on this?
sorry for the late answer !
Basically, my local projects was forks of specflow actions projects. I've forked :
- SpecFlow.Actions.Configuration
- SpecFlow.Actions.Selenium
Injection are done in following classes :
- https://github.com/SpecFlowOSS/SpecFlow.Actions/blob/main/Plugins/SpecFlow.Actions.Configuration/SpecFlow.Actions.Configuration/ConfigurationRuntimePlugin.cs
- https://github.com/SpecFlowOSS/SpecFlow.Actions/blob/main/Plugins/SpecFlow.Actions.Selenium/SpecFlow.Actions.Selenium/SeleniumRuntimePlugin.cs
Then I renamed all "Specflow" strings with "Reqnroll" and update Specflow nuget package to Reqnroll
Note that the following file : https://github.com/SpecFlowOSS/SpecFlow.Actions/blob/main/Plugins/SpecFlow.Actions.Configuration/SpecFlow.Actions.Configuration/CurrentTargetIdentifier.cs
contains kind of a magic string that I renamed on my forked project also (__ReqnrollActionsConfigurationTarget) :
if (scenarioContext is not null && scenarioContext.ContainsKey("__SpecFlowActionsConfigurationTarget"))
{
return (string)scenarioContext["__SpecFlowActionsConfigurationTarget"];
}
Here is my SeleniumRuntimePlugin.cs (so equals to Specflow one with renaming) :
using Reqnroll.Actions.Selenium.Configuration;
using Reqnroll.Actions.Selenium.DriverInitialisers;
using Reqnroll.Actions.Selenium.Hoster;
using Reqnroll.BoDi;
using Reqnroll.Plugins;
using Reqnroll.UnitTestProvider;
using System;
[assembly: RuntimePlugin(typeof(SeleniumRuntimePlugin))]
namespace Reqnroll.Actions.Selenium
{
public class SeleniumRuntimePlugin : IRuntimePlugin
{
public void Initialize(RuntimePluginEvents runtimePluginEvents, RuntimePluginParameters runtimePluginParameters,
UnitTestProviderConfiguration unitTestProviderConfiguration)
{
runtimePluginEvents.CustomizeScenarioDependencies += RuntimePluginEvents_CustomizeScenarioDependencies;
}
private void RuntimePluginEvents_CustomizeScenarioDependencies(object sender, CustomizeScenarioDependenciesEventArgs e)
{
if (!e.ObjectContainer.IsRegistered<ISeleniumConfiguration>())
{
e.ObjectContainer.RegisterTypeAs<SeleniumConfiguration, ISeleniumConfiguration>();
}
if (!e.ObjectContainer.IsRegistered<IDriverInitialiser>())
{
RegisterInitialisers(e.ObjectContainer);
}
if (!e.ObjectContainer.IsRegistered<ICredentialProvider>())
{
e.ObjectContainer.RegisterTypeAs<NoCredentialsProvider, ICredentialProvider>();
}
e.ObjectContainer.RegisterTypeAs<BrowserInteractions, IBrowserInteractions>();
}
private void RegisterInitialisers(IObjectContainer objectContainer)
{
objectContainer.RegisterFactoryAs<IDriverInitialiser>(container =>
{
var config = container.Resolve<ISeleniumConfiguration>();
var credentialProvider = container.Resolve<ICredentialProvider>();
return config.Browser switch
{
Browser.Chrome => new ChromeDriverInitialiser(config, credentialProvider),
Browser.Firefox => new FirefoxDriverInitialiser(config, credentialProvider),
Browser.Edge => new EdgeDriverInitialiser(config, credentialProvider),
Browser.InternetExplorer => new InternetExplorerDriverInitialiser(config, credentialProvider),
Browser.Safari => new SafariDriverInitialiser(config, credentialProvider),
_ => throw new ArgumentOutOfRangeException($"Browser {config.Browser} not implemented")
};
});
}
}
}
When I debug I observe that :
- With Specflow nuget packages, SeleniumRuntimePlugin.Initialize method is called
- With Reqnroll nuget packages; SeleniumRuntimePlugin.Initialize method is NOT called
That explain why container injection is not done for IDriverInitialiser and IBrowserInteractions and for others also.
I saw that some plugins exists in Reqnroll source code (Autofac for example), I will check how you implemented IRuntimePlugin interface, I suppose it has changed ...
After checking, IRuntimePlugin Interface doesn't changed between SpecFlow and Reqnroll and code is pretty much the same ...
I am stupid, you can close it ! Assemblies were still using SpecflowRuntimePlugin in assembly name, then reqnroll didn't loads them