Reqnroll icon indicating copy to clipboard operation
Reqnroll copied to clipboard

Issue while migrating from SpecFlow with hooks constructor injection

Open DomZZ opened this issue 1 year ago • 2 comments

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) image

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

DomZZ avatar Sep 19 '24 12:09 DomZZ

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 avatar Sep 19 '24 12:09 DomZZ

@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)?

gasparnagy avatar Oct 01 '24 10:10 gasparnagy

@DomZZ Do you have any feedback on this?

gasparnagy avatar Oct 14 '24 14:10 gasparnagy

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"];
}

DomZZ avatar Oct 17 '24 12:10 DomZZ

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")
                };
            });
        }
    }
}

DomZZ avatar Oct 17 '24 12:10 DomZZ

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 ...

DomZZ avatar Oct 18 '24 13:10 DomZZ

I am stupid, you can close it ! Assemblies were still using SpecflowRuntimePlugin in assembly name, then reqnroll didn't loads them

DomZZ avatar Oct 18 '24 13:10 DomZZ