reqnroll / Reqnroll

Open-source Cucumber-style BDD test automation framework for .NET.
https://reqnroll.net
BSD 3-Clause "New" or "Revised" License
310 stars 32 forks source link

`BoDi.ObjectContainerException`: Interface cannot be resolved: `Microsoft.Playwright.IBrowser` #58

Closed konarx closed 4 months ago

konarx commented 4 months ago

Reqnroll Version

1.0.1

Which test runner are you using?

NUnit

Test Runner Version Number

4.5.0

.NET Implementation

.NET 6.0

Test Execution Method

ReSharper Test Runner

Content of reqnroll.json configuration file

{
  "$schema": "https://schemas.reqnroll.net/reqnroll-config-latest.json",
  "bindingAssemblies": [
    {
      "assembly": "My.External.NuGet"
    }
  ]
}

Issue Description

I am using Rider with the latest build of the Rider.Reqnroll plugin. I have two .cs files for Hooks, TestRunLevelHooks:

[Binding]
public sealed class TestRunLevelHooks
{
    private static IPlaywright _playwright;
    private static IBrowser _browser;
    private static IObjectContainer _objectContainer;

    [BeforeTestRun]
    public static async Task BeforeTestRun(IObjectContainer objectContainer)
    {
        _objectContainer = objectContainer;

        var env = Environment.GetEnvironmentVariable("ENV")?.ToLower() ?? "";

        var testConfig = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("test_settings.json", false, true)
            .AddJsonFile($"test_settings.{env}.json", true, true)
            .AddEnvironmentVariables()
            .Build();

        TestConfiguration.BrowserOptions = testConfig.GetSection("BrowserOptions")
            .Get<BrowserTypeLaunchOptions>();

        _playwright = await Playwright.CreateAsync();

        _browser = await _playwright.Chromium.LaunchAsync(TestConfiguration.BrowserOptions);

        _objectContainer.RegisterInstanceAs(_playwright);
        _objectContainer.RegisterInstanceAs(_browser);
    }

    [AfterTestRun]
    public static async Task AfterTestRun()
    {
        await _browser.CloseAsync();
        _playwright.Dispose();
    }
}

and ScenarioLevelHooks:

[Binding]
public sealed class ScenarioLevelHooks
{
    private readonly IBrowser _browser;
    private readonly IObjectContainer _objectContainer;
    private readonly ScenarioContext _scenarioContext;
    private IBrowserContext _browserContext;
    private IPage _page;

    public ScenarioLevelHooks(IObjectContainer objectContainer, IBrowser browser, ScenarioContext scenarioContext)
    {
        _browser = browser;
        _scenarioContext = scenarioContext;
        _objectContainer = objectContainer;
    }

    [BeforeScenario(Order = 0)]
    public async Task BeforeScenario()
    {
        _browserContext =
            await _browser.NewContextAsync(TestConfiguration.BrowserContextOptions);

        _page = await _browserContext.NewPageAsync();

        _page.SetDefaultTimeout(30000);
        _page.SetDefaultNavigationTimeout(30000);

        _objectContainer.RegisterInstanceAs(_browserContext);
        _objectContainer.RegisterInstanceAs(_page);
    }

    [BeforeScenario(Order = 1)]
    public async Task StartTracing()
    {
        await _browserContext.Tracing.StartAsync(new TracingStartOptions
        {
            Screenshots = true,
            Snapshots = true,
            Sources = true
        });
    }

    [AfterScenario]
    public async Task AfterScenario()
    {
        await _page.CloseAsync();
        await _browserContext.CloseAsync();
    }
}

I execute a test using Test Explorer in Rider and getting the following error:

BoDi.ObjectContainerException : Interface cannot be resolved: Microsoft.Playwright.IBrowser (resolution path: MyProject.UITests.Hooks.ScenarioLevelHooks) TearDown : BoDi.ObjectContainerException : Interface cannot be resolved: Microsoft.Playwright.IBrowser (resolution path: MyProject.UITests.Hooks.ScenarioLevelHooks) at BoDi.ObjectContainer.TypeRegistration.<>cDisplayClass3_0.b1() at BoDi.ObjectContainer.RegistrationWithStrategy.ExecuteWithLock(Object lockObject, Func1 getter, Func1 factory, ResolutionList resolutionPath) at BoDi.ObjectContainer.TypeRegistration.ResolvePerContext(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath) at BoDi.ObjectContainer.RegistrationWithStrategy.Resolve(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath) at BoDi.ObjectContainer.ResolveObject(RegistrationKey keyToResolve, ResolutionList resolutionPath) at BoDi.ObjectContainer.Resolve(Type typeToResolve, ResolutionList resolutionPath, String name) at BoDi.ObjectContainer.<>cDisplayClass71_0.b0(ParameterInfo p) at System.Linq.Enumerable.SelectArrayIterator2.ToArray() at BoDi.ObjectContainer.ResolveArguments(IEnumerable1 parameters, RegistrationKey keyToResolve, ResolutionList resolutionPath) at BoDi.ObjectContainer.CreateObject(Type type, ResolutionList resolutionPath, RegistrationKey keyToResolve) at BoDi.ObjectContainer.TypeRegistration.<>c__DisplayClass3_0.b__1() at BoDi.ObjectContainer.RegistrationWithStrategy.ExecuteWithLock(Object lockObject, Func1 getter, Func1 factory, ResolutionList resolutionPath) at BoDi.ObjectContainer.TypeRegistration.ResolvePerContext(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath) at BoDi.ObjectContainer.RegistrationWithStrategy.Resolve(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath) at BoDi.ObjectContainer.ResolveObject(RegistrationKey keyToResolve, ResolutionList resolutionPath) at BoDi.ObjectContainer.Resolve(Type typeToResolve, ResolutionList resolutionPath, String name) at BoDi.ObjectContainer.Resolve(Type typeToResolve, String name) at Reqnroll.Infrastructure.TestObjectResolver.ResolveBindingInstance(Type bindingType, IObjectContainer container) at lambda_method25(Closure , IContextManager )

The issue started after migrate to Reqnroll using the namespace changes method. It was not reproduced when using SpecFlow and I have kept the same dependency injection methods.

Here is the .csproj (if it matters):

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <LangVersion>10</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <Using Include="Reqnroll"/>
    <Using Include="Microsoft.Playwright"/>
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="My.External.NuGet" Version="1.1.0-rc1"/>
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0"/>
    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1"/>
    <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0"/>
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0"/>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0"/>
    <PackageReference Include="Microsoft.Playwright" Version="1.41.2"/>
    <PackageReference Include="ReportPortal.Extensions.SourceBack" Version="2.1.0"/>
    <PackageReference Include="ReportPortal.Reqnroll" Version="1.0.0"/>
    <PackageReference Include="Reqnroll.NUnit" Version="1.0.1"/>
    <PackageReference Include="RestSharp" Version="110.2.0"/>
    <PackageReference Include="nunit" Version="4.1.0"/>
    <PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
    <PackageReference Include="FluentAssertions" Version="6.12.0"/>
  </ItemGroup>

  <ItemGroup>
    <None Update="test_settings*.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="ReportPortal.config.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>

  <ItemGroup>
    <Content Include="Features\*.feature"/>
  </ItemGroup>

</Project>

Steps to Reproduce

When debugging, I managed to see that the [BeforeTestRun] executed successfully, registering all objects to the _objectContainer. The error occurs in the ScenarioLevelHooks.

Link to Repro Project

No response

gasparnagy commented 4 months ago

@konarx Which version of SpecFlow did you use before migrating?

gasparnagy commented 4 months ago

I was able to reproduce the issue. I think this was an issue already in SpecFlow v4 beta, but I am waiting for your confirmation on that.

The problem is that the IObjectContainer you get in the [BeforeTestRun] hook should be the "global container", but for some reason the "test thread container" is injected instead. For some other reason (I'm not sure if this is OK), the scenario and the related [BeforeScenario] hook is running in another test thread so it does not "see" the registrations you made on the container.

This has to be fixed.

Workaround: The workaround is to get the "base container" of the "test thread container" in the [BeforeTestRun] hook. So you need to change the line in the BeforeTestRun method from:

_objectContainer = objectContainer;

to

_objectContainer = ((ObjectContainer)objectContainer).BaseContainer;

(this workaround needs to be reverted once the issue is fixed)

konarx commented 4 months ago

Thank you @gasparnagy, the suggested workaround does the trick indeed. I can proceed with the migration now.

gasparnagy commented 4 months ago

It is fixed now by #59 and will be included in the next release. In the meantime please use the workaround.