skip to Main Content

Does anyone know why I sometimes get exception when I use Selenium together with Testcontainers. See below:

Exception has occurred: CLR/OpenQA.Selenium.WebDriverException
An exception of type ‘OpenQA.Selenium.WebDriverException’ occurred in WebDriver.dll but was not handled in user code: ‘An unknown exception was encountered sending an HTTP request to the remote WebDriver server for URL http://localhost:4444/session. The exception message was: An error occurred while sending the request.’
Inner exceptions found, see $exception in variables window for more details.
Innermost exception System.IO.IOException : The response ended prematurely.
at System.Net.Http.HttpConnection.d__61.MoveNext()

This happens half the time when i run the following test constructor (C# / xUnit.net):

public DockerShould()
{
    var gridNetwork = new NetworkBuilder()
        .WithName("gridNetwork")
        .Build();

    const int SessionPort = 4444;
    var containerHub = new ContainerBuilder()
        .WithImage("selenium/hub:4.8")
        .WithName("selenium-hub")
        .WithPortBinding(4442, 4442)
        .WithPortBinding(4443, 4443)
        .WithPortBinding(SessionPort, SessionPort)
        .WithNetwork(gridNetwork)
        .Build();

    var firefoxEnvironment = new Dictionary<string, string>()
    {
        {"SE_EVENT_BUS_HOST", "selenium-hub"},
        {"SE_EVENT_BUS_PUBLISH_PORT", "4442"},
        {"SE_EVENT_BUS_SUBSCRIBE_PORT", "4443"}
    };

    var containerFirefox = new ContainerBuilder()
        .WithImage("selenium/node-firefox:4.8")
        .WithEnvironment(firefoxEnvironment)
        .WithNetwork(gridNetwork)
        .Build();

    var firefoxVideoEnvironment = new Dictionary<string, string>()
    {
        {"DISPLAY_CONTAINER_NAME", "firefox"},
        {"FILE_NAME", "firefox.mp4"}
    };      
    
    var containerFirefoxVideo = new ContainerBuilder()
        .WithImage("selenium/video:ffmpeg-4.3.1-20230210")
        .WithNetwork(gridNetwork)            
        .WithEnvironment(firefoxVideoEnvironment)
        // .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(SessionPort))
        .Build();        
    
    gridNetwork.CreateAsync().Wait();
    containerHub.StartAsync().Wait();
    containerFirefox.StartAsync().Wait();
    containerFirefoxVideo.StartAsync().Wait();

    Thread.Sleep(5000);
    _remoteWebDriver = new RemoteWebDriver(new Uri("http://localhost:4444"), new FirefoxOptions());
}

The exception occurs when creating the new RemoteWebDriver. I’ve added a thread.sleep to give a bit of a delay before the variable is created. I’m not sure it’s really helping much. Is there a more elegant way to ensure all containers are started up before attempting to create the web driver (which i’m assuming is the problem)?

2

Answers


  1. Your configuration has a few shortcomings. I am uncertain as to which one is ultimately causing the issue, but I have provided a working example below. The crucial parts have been commented. Please note that the example does not incorporate a wait strategy to determine the readiness of the container or the service inside it. That is an aspect that you will still need to address. But first lets take a look at some basics.

    1. Please consider reading the article Consuming the Task-based Asynchronous Pattern. Testcontainers for .NET utilizes the Task-based Asynchronous Pattern (TAP). I noticed that you tend to block the asynchronous context frequently.
    2. You do not need to bind ports for container-to-container communication.
    3. Avoid using fixed port bindings such as WithPortBinding(4444, 4444). To prevent port conflicts, assign a random host port by using WithPortBinding(4444, true) and retrieve it from the container instance using GetMappedPublicPort(4444).
    4. Do not use fixed container names for the same reason mentioned in 3. Use WithNetworkAliases(string) instead.
    5. Do not use localhost to access services running inside containers. The endpoint varies according to the Docker environment. Use the Hostname property instead.
    public sealed class StackOverflow : IAsyncLifetime
    {
        private const ushort WebDriverPort = 4444;
    
        private readonly INetwork _network;
    
        private readonly IContainer _selenium;
    
        private readonly IContainer _firefox;
    
        private readonly IContainer _ffmpg;
    
        public StackOverflow()
        {
            _network = new NetworkBuilder()
                .Build();
    
            _selenium = new ContainerBuilder()
                .WithImage("selenium/hub:4.8")
    
                // Use random assigned host ports to access the service running inside the containers.
                .WithPortBinding(WebDriverPort, true)
                .WithNetwork(_network)
    
                // Use a network-alias to communication between containers.
                .WithNetworkAliases(nameof(_selenium))
                .Build();
    
            _firefox = new ContainerBuilder()
                .WithImage("selenium/node-firefox:4.8")
                .WithEnvironment("SE_EVENT_BUS_HOST", nameof(_selenium))
                .WithEnvironment("SE_EVENT_BUS_PUBLISH_PORT", "4442")
                .WithEnvironment("SE_EVENT_BUS_SUBSCRIBE_PORT", "4443")
                .WithNetwork(_network)
                .WithNetworkAliases(nameof(_firefox))
                .Build();
    
            _ffmpg = new ContainerBuilder()
                .WithImage("selenium/video:ffmpeg-4.3.1-20230210")
                .WithEnvironment("DISPLAY_CONTAINER_NAME", nameof(_firefox))
                .WithEnvironment("FILE_NAME", nameof(_firefox) + ".mp4")
                .WithNetwork(_network)
                .WithNetworkAliases(nameof(_ffmpg))
                .Build();
        }
    
        public async Task InitializeAsync()
        {
            await _network.CreateAsync()
                .ConfigureAwait(false);
    
            // You can await Task.WhenAll(params Task[]) too.
            await _selenium.StartAsync()
                .ConfigureAwait(false);
    
            await _firefox.StartAsync()
                .ConfigureAwait(false);
    
            await _ffmpg.StartAsync()
                .ConfigureAwait(false);
        }
    
        public async Task DisposeAsync()
        {
            await _selenium.DisposeAsync()
                .ConfigureAwait(false);
    
            await _firefox.DisposeAsync()
                .ConfigureAwait(false);
    
            await _ffmpg.DisposeAsync()
                .ConfigureAwait(false);
    
            await _network.DeleteAsync()
                .ConfigureAwait(false);
        }
    
        [Fact]
        public async Task Question()
        {
            // TODO: The container configurations mentioned above lack a wait strategy. It is crucial that you add a wait strategy to each of them to determine readiness (afterwards remove this line).
            await Task.Delay(TimeSpan.FromSeconds(15))
                .ConfigureAwait(false);
    
            // Use the Hostname property instead of localhost. Get the random assigned host port with GetMappedPublicPort(ushort).
            var webDriver = new RemoteWebDriver(new UriBuilder(Uri.UriSchemeHttp, _selenium.Hostname, _selenium.GetMappedPublicPort(WebDriverPort)).Uri, new FirefoxOptions());
            Assert.NotNull(webDriver.SessionId);
        }
    }
    
    Login or Signup to reply.
  2. I will like to ask you please: What do you use hub and node selenium mode?
    I recommended using in this case standalone mode – and why?
    Because the webDriver testcoaniner in my opinion works like a dynamic grid in s: docker-selenium github
    I am also asking because I just working on that: Feature- WebDriver container
    So I would like your opinion and how I can map between the testcotanienr and the RemoteWebDriver capabilities

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search