C# 如何为使用OWIN、信号器和Selenium/量角器的web应用程序执行E2E测试?我只能通过第一个测试

C# 如何为使用OWIN、信号器和Selenium/量角器的web应用程序执行E2E测试?我只能通过第一个测试,c#,selenium,signalr,owin,xunit.net,C#,Selenium,Signalr,Owin,Xunit.net,我正在尝试执行E2E测试,在每个测试中,我创建一个新网站并回滚一个数据库。我正在使用信号器将数据推送到客户端。我的问题是,当我一次运行所有测试时,我的第一个测试才通过,即使每个测试都单独通过我如何连续运行一系列测试,在每次运行之间清理SignalR/Owin?我找不到任何SignalR+E2E测试的示例,尽管官方文档确实显示了单元测试 虽然进行了一些推断和记录,但我认为是信号器未正确处理和/或初始化。以下是我所做的,以确认这是问题所在: 于2013年开放Visual Studio 创建了一个使用

我正在尝试执行E2E测试,在每个测试中,我创建一个新网站并回滚一个数据库。我正在使用信号器将数据推送到客户端。我的问题是,当我一次运行所有测试时,我的第一个测试才通过,即使每个测试都单独通过我如何连续运行一系列测试,在每次运行之间清理SignalR/Owin?我找不到任何SignalR+E2E测试的示例,尽管官方文档确实显示了单元测试

虽然进行了一些推断和记录,但我认为是信号器未正确处理和/或初始化。以下是我所做的,以确认这是问题所在:

  • 于2013年开放Visual Studio
  • 创建了一个使用Wewb API的Web应用程序,名为“SignalsAndBox”
  • 托管NuGet软件包->已安装
    Microsoft.AspNet.signal
  • Managed NuGet packages->Installed
    angularjs
    (我的网站使用angular;为了尽可能接近我的真实环境,我使用的是量角器,而不是Selenium)
  • 添加了一个名为
    StartUp
    的类:
  • 添加了一个名为
    ChatHub
    的类:
  • 添加了一个名为
    索引
    的html文件:
  • 创建了一个名为“signalsandbox.Tests”的新类库项目
  • 添加了对网站项目的引用
  • 托管NuGet软件包->已安装
    xunit
  • 托管NuGet软件包->已安装
    量角器
  • 托管NuGet软件包->已安装
    Selenium.WebDriver.ChromeDriver
  • 托管NuGet软件包->已安装
    Microsoft.Owin.Hosting
  • 托管NuGet软件包->已安装
    Microsoft.Owin.Host.HttpListener
  • 托管NuGet软件包->已安装
    Microsoft.Owin.StaticFiles
  • 添加了一个名为
    MyTest
    的类:
  • 果不其然,只有第一次测试通过:

    StartUp.cs

    using Microsoft.Owin;
    using Owin;
    
    namespace SignalRSandbox
    {
        public class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                app.MapSignalR();
            }
        }
    } 
    
    using System;
    using System.Web;
    using Microsoft.AspNet.SignalR;
    
    namespace SignalRSandbox
    {
        public class ChatHub : Hub
        {
            public void Send(string name, string message)
            {
                Clients.All.broadcastMessage(name, message);
            }
        }
    }
    
    using Microsoft.Owin.Hosting;
    using OpenQA.Selenium.Chrome;
    using Protractor;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using Xunit;
    using Owin;
    using Microsoft.Owin.StaticFiles;
    using Microsoft.Owin.FileSystems;
    using OpenQA.Selenium;
    
    namespace SignalRSandbox.Tests
    {
        public class MyTest : IDisposable
        {
            private NgWebDriver _driver;
            private IDisposable _site;
            private string _url;
    
            public MyTest()
            {
                this._url = "http://localhost:8765/index.html";
                this._site = WebApp.Start(this._url, appBuilder =>
                {
                    // SignalR
                    new Startup().Configuration(appBuilder);
    
                    // Allow static content through
                    var options = new FileServerOptions();
                    options.FileSystem = new PhysicalFileSystem("../../../SignalRSandbox");
                    appBuilder.UseFileServer(options);
                });
    
                this._driver = new NgWebDriver(new ChromeDriver());
    
                this._driver.Manage().Timeouts().SetPageLoadTimeout(TimeSpan.FromMinutes(1));
                this._driver.Manage().Timeouts().SetScriptTimeout(TimeSpan.FromMinutes(1));
                this._driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(2));
    
                // We have to use the WrappedDriver otherwise the alert will cause an error.
                this._driver.WrappedDriver.Navigate().GoToUrl(this._url);
    
                // Enter the name
                var alert = this._driver.WrappedDriver.SwitchTo().Alert();
                alert.SendKeys("j");
                alert.Accept();
    
                // Enter a message
                this._driver.WrappedDriver.FindElement(By.Id("message")).SendKeys("test");
                this._driver.WrappedDriver.FindElement(By.Id("send")).Click();
                // Enter another message
                this._driver.WrappedDriver.FindElement(By.Id("message")).SendKeys("test1");
                this._driver.WrappedDriver.FindElement(By.Id("send")).Click();
    
                Assert.Equal("j:  test", this._driver.WrappedDriver.FindElement(By.Id("discussion0")).Text);
                Assert.Equal("j:  test1", this._driver.WrappedDriver.FindElement(By.Id("discussion1")).Text);
            }
    
            [Fact]
            public void Test()
            {
            }
    
            // All subsequent tests fail
            [Fact]
            public void Test1()
            {
            }
    
            [Fact]
            public void Test2()
            {
            }
    
            [Fact]
            public void Test3()
            {
            }
    
            public void Dispose()
            {
                if (this._driver != null)
                {
                    this._driver.Dispose();
                }
                if (this._site != null)
                {
                    this._site.Dispose();
                }
            }
        }
    }
    
    ChatHub.cs

    using Microsoft.Owin;
    using Owin;
    
    namespace SignalRSandbox
    {
        public class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                app.MapSignalR();
            }
        }
    } 
    
    using System;
    using System.Web;
    using Microsoft.AspNet.SignalR;
    
    namespace SignalRSandbox
    {
        public class ChatHub : Hub
        {
            public void Send(string name, string message)
            {
                Clients.All.broadcastMessage(name, message);
            }
        }
    }
    
    using Microsoft.Owin.Hosting;
    using OpenQA.Selenium.Chrome;
    using Protractor;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using Xunit;
    using Owin;
    using Microsoft.Owin.StaticFiles;
    using Microsoft.Owin.FileSystems;
    using OpenQA.Selenium;
    
    namespace SignalRSandbox.Tests
    {
        public class MyTest : IDisposable
        {
            private NgWebDriver _driver;
            private IDisposable _site;
            private string _url;
    
            public MyTest()
            {
                this._url = "http://localhost:8765/index.html";
                this._site = WebApp.Start(this._url, appBuilder =>
                {
                    // SignalR
                    new Startup().Configuration(appBuilder);
    
                    // Allow static content through
                    var options = new FileServerOptions();
                    options.FileSystem = new PhysicalFileSystem("../../../SignalRSandbox");
                    appBuilder.UseFileServer(options);
                });
    
                this._driver = new NgWebDriver(new ChromeDriver());
    
                this._driver.Manage().Timeouts().SetPageLoadTimeout(TimeSpan.FromMinutes(1));
                this._driver.Manage().Timeouts().SetScriptTimeout(TimeSpan.FromMinutes(1));
                this._driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(2));
    
                // We have to use the WrappedDriver otherwise the alert will cause an error.
                this._driver.WrappedDriver.Navigate().GoToUrl(this._url);
    
                // Enter the name
                var alert = this._driver.WrappedDriver.SwitchTo().Alert();
                alert.SendKeys("j");
                alert.Accept();
    
                // Enter a message
                this._driver.WrappedDriver.FindElement(By.Id("message")).SendKeys("test");
                this._driver.WrappedDriver.FindElement(By.Id("send")).Click();
                // Enter another message
                this._driver.WrappedDriver.FindElement(By.Id("message")).SendKeys("test1");
                this._driver.WrappedDriver.FindElement(By.Id("send")).Click();
    
                Assert.Equal("j:  test", this._driver.WrappedDriver.FindElement(By.Id("discussion0")).Text);
                Assert.Equal("j:  test1", this._driver.WrappedDriver.FindElement(By.Id("discussion1")).Text);
            }
    
            [Fact]
            public void Test()
            {
            }
    
            // All subsequent tests fail
            [Fact]
            public void Test1()
            {
            }
    
            [Fact]
            public void Test2()
            {
            }
    
            [Fact]
            public void Test3()
            {
            }
    
            public void Dispose()
            {
                if (this._driver != null)
                {
                    this._driver.Dispose();
                }
                if (this._site != null)
                {
                    this._site.Dispose();
                }
            }
        }
    }
    
    Index.html(有些代码可能看起来有点古怪-我只是想让Gradurator/Selenium正常等待)

    测试是使用测试资源管理器运行的(我想我也安装了一个XUnit.netvisualstudio插件)


    更新:通过切换到NUnit而不是xUnit并使用扩展名,我可以执行以下操作,但在这样做的过程中,我最终在测试方法中调用了Initialize和dispose,我不想重复,但它确实完成了任务

    如果有人不使用单独的应用程序域就可以工作,我会接受

    以下是更新后的MyTest.cs第2版:

    using Microsoft.Owin.Hosting;
    using OpenQA.Selenium.Chrome;
    using Protractor;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using Owin;
    using Microsoft.Owin.StaticFiles;
    using Microsoft.Owin.FileSystems;
    using OpenQA.Selenium;
    using NUnit.Framework;
    
    namespace SignalRSandbox.Tests
    {
        public class MyTest
        {
            private NgWebDriver _driver;
            private IDisposable _site;
            private string _url;
    
            public void Intialize()
            {
                this._url = "http://localhost:8765";
                this._site = WebApp.Start(this._url, appBuilder =>
                {
                    // SignalR
                    new Startup().Configuration(appBuilder);
    
                    // Allow static content through
                    var options = new FileServerOptions();
                    options.FileSystem = new PhysicalFileSystem("../../../SignalRSandbox");
                    appBuilder.UseFileServer(options);
                });
    
                this._driver = new NgWebDriver(new ChromeDriver());
    
                this._driver.Manage().Timeouts().SetPageLoadTimeout(TimeSpan.FromMinutes(1));
                this._driver.Manage().Timeouts().SetScriptTimeout(TimeSpan.FromMinutes(1));
                this._driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(2));
    
                // We have to use the WrappedDriver otherwise the alert will cause an error.
                this._driver.WrappedDriver.Navigate().GoToUrl(this._url);
    
                // Enter the name
                var alert = this._driver.WrappedDriver.SwitchTo().Alert();
                alert.SendKeys("j");
                alert.Accept();
    
                // Enter a message
                this._driver.WrappedDriver.FindElement(By.Id("message")).SendKeys("test");
                this._driver.WrappedDriver.FindElement(By.Id("send")).Click();
                // Enter another message
                this._driver.WrappedDriver.FindElement(By.Id("message")).SendKeys("test1");
                this._driver.WrappedDriver.FindElement(By.Id("send")).Click();
    
                Assert.AreEqual("j:  test", this._driver.WrappedDriver.FindElement(By.Id("discussion0")).Text);
                Assert.AreEqual("j:  test1", this._driver.WrappedDriver.FindElement(By.Id("discussion1")).Text);
            }
    
            [Test, RunInApplicationDomain]
            public void Test()
            {
                this.Intialize();
                this.Dispose();
            }
    
            // All subsequent tests fail
            [Test, RunInApplicationDomain]
            public void Test1()
            {
                this.Intialize();
                this.Dispose();
            }
    
            [Test, RunInApplicationDomain]
            public void Test2()
            {
                this.Intialize();
                this.Dispose();
            }
    
            [Test, RunInApplicationDomain]
            public void Test3()
            {
                this.Intialize();
                this.Dispose();
            }
    
            public void Dispose()
            {
                if (this._driver != null)
                {
                    this._driver.Dispose();
                }
                if (this._site != null)
                {
                    this._site.Dispose();
                }
            }
        }
    }
    

    如果要在同一AppDomain中多次调用
    MapSignalR
    ,每次都应传入一个新的
    DefaultDependencyResolver
    。如果您没有将自己的
    idependencysolver
    传递到
    mapsigner
    ,它将使用由
    GlobalHost.dependencysolver
    引用的

    您看到的测试失败可能是由于您在处理MyTest时处理了信号器依赖项而导致的

    您可以为每次调用
    Configure
    指定自己的
    idependencysolver
    ,如下所示:

    using Microsoft.AspNet.SignalR;
    using Microsoft.Owin;
    using Owin;
    
    namespace SignalRSandbox
    {
        public class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                app.MapSignalR(new HubConfiguration
                {
                    Resolver = new DefaultDependencyResolver()
                });
            }
        }
    }
    
    EDIT:上述解决方案将使
    GlobalHost
    不可用,因为每个
    GlobalHost
    属性都引用了
    GlobalHost.DependencyResolver
    ,信号器不再使用该属性

    您可以通过解析实际使用的
    idependencysolver
    信号器中的适当类型来解决此问题。例如,您可以执行以下操作,而不是使用
    GlobalHost.ConnectionManager

    public void Configuration(IAppBuilder app)
    {
        var resolver = new DefaultDependencyResolver();
        var connectionManager = resolver.Resolve<IConnectionManager>();
        var myHubContext = connectionManager.GetHubContext<MyHub>();
    
        app.MapSignalR(new HubConfiguration
        {
            Resolver = resolver
        });
    }
    

    谢谢回复!添加:
    config.Resolver=new DefaultDependencyResolver()
    会使信号器看起来似乎会导致我的两个集线器之一开始工作。如果我像这样设置依赖项解析器,我可以这样做:“GlobalHost.ConnectionManager.GetHubContext().Clients.All.myMethod()”?这是唯一奇怪的地方。我编辑了我的答案,解释了为什么如果您提供自定义
    IDependencyResolver
    ,GlobalHost会停止工作。谢谢!“或者,由于您没有同时运行多个SignalR端点…”这是否意味着这只适用于该集线器上的单个集线器/单个端点(我有一个客户端方法和一个服务器方法,实际上每个都在不同的集线器上)?所有集线器共享一个SignalR端点(默认路径为“/SignalR”)。PersistentConnections总是有自己的端点,但您似乎没有使用它们。我想说的主要一点是,如果您在配置方法中多次调用
    MapSignalR
    (如果您使用重载来调用
    MapSignalR
    ,则可以这样做),并且您不希望两个端点相互干扰,您不应该使用
    GlobalHost
    ,因为这会导致端点共享状态。明白了!所以我在
    WebApi中进行信号器配置。在WebApi之后启动
    。事实证明,Web API配置实例化了依赖于signer
    IHubContext
    的内容。点是它的工作后,我扭转了两个,所以信号器是配置第一。我已经在我的网站上尝试了2-3天的E2E测试(不同的问题)。一分钟前,这三个系统第一次按顺序运行。非常感谢:)