Black Hole Proxy Pattern for Reducing Test Instability

Black Hole Proxy Pattern for Reducing Test Instability

In this article, we will review the Black Hole Proxy Pattern. It tries to reduce test instability by getting rid of as many third-party uncertainties as possible. We will look at two solutions, one using a custom HTTP proxy library called Titanium.Web.Proxy and a second using the new Selenium 4.0 Chrome Dev Tools API.

For more detailed overview and usage of many more design patterns and best practices in automated testing, check my book “Design Patterns for High-Quality Automated Tests, C# Edition, High-Quality Tests Attributes, and Best Practices”.  The first part of the article is part of Chapter 3 of the book. You can read part of three of the other chapters:

Defining High-Quality Test Attributes for Automated Tests

Benchmarking for Assessing Automated Test Components Performance

Generic Repository Design Pattern- Test Data Preparation

Black Hole Proxy Pattern Definition

Definition

The Black Hole Proxy Pattern tries to reduce test instability by getting rid of as many third-party uncertainties as possible. Modern websites have a lot of third-party content loaded on every page.

 There are social networking buttons, images coming from CDNs, tracking pixels, analytics, and much more. All these items can destabilize our tests at any point. Black Hole Proxy takes all HTTP requests going to third-party websites and blocks them, as if the request was sucked into a black hole.

Titanium.Web.Proxy Black Hole Proxy Implementation

Our website integrates with a couple of third-party user tracking services that collect analytics data and use others to download the fonts. We may not need to test these services in all our tests, so we can use the Black Hole Proxy approach to block them.

We will be taking advantage of the HTTP proxy settings that all browsers use. Our tests will send all the HTTP traffic to a fake proxy that will swallow all the requests and will send only the relevant ones.

Note

For .NET we will use a lightweight HTTP(S) proxy server written in C# called Titanium-Web-Proxy. You just need to install the Titanium-WebProxy NuGet package to your test project.


public class CaptureHttpTrafficTests
{
    private static IWebDriver _driver;
    private static ProxyServer _proxyServer;
    private static IDictionary<int, Proxy.Request> _requestsHistory;
    private static IDictionary<int, Proxy.Response> _responsesHistory;
    private static ConcurrentBag<string> _blockUrls;

    
    public static void OnClassInitialize(TestContext context)
    {
        _proxyServer = new ProxyServer();
        _blockUrls = new ConcurrentBag<string>();
        _responsesHistory = new ConcurrentDictionary<int, Proxy.Response>();
        _requestsHistory = new ConcurrentDictionary<int, Proxy.Request>();
        var explicitEndPoint = new ExplicitProxyEndPoint(System.Net.IPAddress.Any, 18882, true);
        _proxyServer.AddEndPoint(explicitEndPoint);
        _proxyServer.Start();
        _proxyServer.SetAsSystemHttpProxy(explicitEndPoint);
        _proxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
        _proxyServer.BeforeRequest += OnRequestBlockResourceEventHandler;
        _proxyServer.BeforeRequest += OnRequestCaptureTrafficEventHandler;
        _proxyServer.BeforeResponse += OnResponseCaptureTrafficEventHandler;
    }

    
    public static void ClassCleanup()
    {
        _proxyServer.Stop();
    }

    
    public void TestInitialize()
    {
        var proxy = new OpenQA.Selenium.Proxy
        {
            HttpProxy = "http://localhost:18882",
            SslProxy = "http://localhost:18882",
            FtpProxy = "http://localhost:18882"
        };
        var options = new ChromeOptions { Proxy = proxy };
        _driver = new ChromeDriver(Environment.CurrentDirectory, options);
    }

    
    public void TestCleanup()
    {
        _driver.Dispose();
        _requestsHistory.Clear();
        _responsesHistory.Clear();
    }

    
    public void FontRequestsNotMade_When_FontRequestSetToBeBlocked()
    {
        _blockUrls.Add("fontawesome-webfont.woff");
        _driver.Navigate().GoToUrl("https://automatetheplanet.com/");
    }

    private static async Task OnRequestBlockResourceEventHandler(
        object sender,
        SessionEventArgs e
    ) =>
        await Task.Run(() =>
        {
            if (_blockUrls.Count > 0)
            {
                foreach (var urlToBeBlocked in _blockUrls)
                {
                    if (e.HttpClient.Request.RequestUri.ToString().Contains(urlToBeBlocked))
                    {
                        string customBody = string.Empty;
                        e.Ok(Encoding.UTF8.GetBytes(customBody));
                    }
                }
            }
        });

    private static async Task OnRequestCaptureTrafficEventHandler(
        object sender,
        SessionEventArgs e
    ) =>
        await Task.Run(() =>
        {
            if (
                !_requestsHistory.ContainsKey(e.HttpClient.Request.GetHashCode())
                && e.HttpClient.Request != null
            )
            {
                _requestsHistory.Add(e.HttpClient.Request.GetHashCode(), e.HttpClient.Request);
            }
        });

    private static async Task OnResponseCaptureTrafficEventHandler(
        object sender,
        SessionEventArgs e
    ) =>
        await Task.Run(() =>
        {
            if (
                !_responsesHistory.ContainsKey(e.HttpClient.Response.GetHashCode())
                && e.HttpClient.Response != null
            )
            {
                _responsesHistory.Add(e.HttpClient.Response.GetHashCode(), e.HttpClient.Response);
            }
        });
}

The preceding code performs the following actions:

  • It creates an instance of the ChromeOptions class

  • It configures the HTTP proxy to point to a non-existing proxy on 127.0.0.1 with port of 18889

Before all the tests in the class, we start the proxy server. Also, we use special .NET collections that can handle a parallel code since most of the web requests are happening in parallel. If we use regular collections deadlocks may occur.

Note

A deadlock occurs when the waiting process is still holding on to another resource that the first needs before it can finish We subscribe to the BeforeRequest event, which is raised before each request in the handler method. If the URL of the request was set to be blocked, we would prevent it by returning status code 200 with no content instead of making the actual request.

Chrome DevTools Black Hole Proxy Implementation

We can use the new Selenium WebDriver 4.0 Chrome DevTools API support to implement the pattern. The code is much simpler.

DevTools API offers great capabilities for controlling the Browser and the web traffic. The complete API can be found here: https://chromedevtools.github.io/devtools-protocol/

Added Chrome DevTools Protocol (CDP) support to .NET bindings. By casting a driver instance to IDevTools, users can now create sessions to use CDP calls for Chromium-based browsers. The DevTools API is implemented using .NET classes, and can send commands and listen to events raised by the browser’s DevTools implementation. Please note that CDP domains listed as “experimental” in the protocol definition are not implemented at present. Additionally, the current API is to be considered highly experimental, and subject to change between releases until the alpha/beta period is over. Feedback is requested.


public class CaptureHttpTrafficDevToolsTests
{
    private ChromeDriver _driver;

    
    public void TestInitialize()
    {
        _driver = new ChromeDriver();
    }

    
    public void TestCleanup()
    {
        _driver.Quit();
    }

    
    public void FontRequestsNotMade_When_FontRequestSetToBeBlocked_DevTools()
    {
        var devToolssession = _driver.CreateDevToolsSession();
        var blockedUrlSettings = new SetBlockedURLsCommandSettings();
        blockedUrlSettings.Urls = new string[]
        {
            "http://demos.bellatrix.solutions/wp-content/themes/storefront/assets/fonts/fontawesome-webfont.woff2?v=4.7.0"
        };

        devToolssession.Network.SetBlockedURLs(blockedUrlSettings);
        _driver.Navigate().GoToUrl("http://demos.bellatrix.solutions/");
        IWebElement imageTitle = _driver.FindElement(By.XPath("//h2[text()='Falcon 9']"));

        IWebElement falconSalesButton = _driver.FindElement(
            RelativeBy.WithTagName("span").Below(imageTitle)
        );

        falconSalesButton.Click();
    }
}

From what I tried, the only limitation is that you need to provide the full URL.

Summary

Through the Black Hole Proxy Pattern, our tests can speed up significantly by not waiting for 3rd-party services loading. Furthermore, the tests will be more hermetically sealed by blocking the third-party requests, reducing the external dependencies that often cause test failures.

Related Articles

Design Patterns

Improved Facade Design Pattern in Automated Testing v.2.0

The today's article is dedicated once again to the Facade Design Pattern. In my previous article on the matter, I showed you how you can utilize the pattern to

Improved Facade Design Pattern in Automated Testing v.2.0

Design Patterns

Rules Design Pattern in Automated Testing

Separate the logic of each individual rule and its effects into its own class. Separate the selection and processing of rules into a separate Evaluator class.

Rules Design Pattern in Automated Testing

Design Patterns

Strategy Design Pattern in Automated Testing

In my previous articles from the series "Design Patterns in Automated Testing", I explained in details how to make your test automation framework better through

Strategy Design Pattern in Automated Testing

Design Architecture, Design Patterns

Failed Tests Аnalysis- Ambient Context Design Pattern

Here I will present to you the second version of the Failed Tests Analysis engine part of the Design Patterns in Automated Testing Series. The first version of

Failed Tests Аnalysis- Ambient Context Design Pattern

Design Patterns

Advanced Observer Design Pattern via Events and Delegates in Automated Testing

In my articles from the series "Design Patterns in Automated Testing", I am sharing with you ideas how to integrate the most useful code design patterns in the

Advanced Observer Design Pattern via Events and Delegates in Automated Testing

Design Patterns

Page Objects- Partial Classes Fluent API- WebDriver C#

Editorial Note: I originally wrote this post for the Test Huddle Blog. You can check out the original here, at their site.

Page Objects- Partial Classes Fluent API- WebDriver C#
Anton Angelov

About the author

Anton Angelov is Managing Director, Co-Founder, and Chief Test Automation Architect at Automate The Planet — a boutique consulting firm specializing in AI-augmented test automation strategy, implementation, and enablement. He is the creator of BELLATRIX, a cross-platform framework for web, mobile, desktop, and API testing, and the author of 8 bestselling books on test automation. A speaker at 60+ international conferences and researcher in AI-driven testing and LLM-based automation, he has been recognized as QA of the Decade and Webit Changemaker 2025.