<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Kota Product Blog: Engineering]]></title><description><![CDATA[Some insights into some of the engineering problems we face at Kota, how we tackle them, some software topics we find interesting etc. ]]></description><link>https://kotaengineering.substack.com/s/engineering</link><image><url>https://substackcdn.com/image/fetch/$s_!b1uS!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78795fa4-2cc7-401b-8775-116a1b14e95b_600x600.png</url><title>Kota Product Blog: Engineering</title><link>https://kotaengineering.substack.com/s/engineering</link></image><generator>Substack</generator><lastBuildDate>Sat, 27 Jun 2026 16:42:08 GMT</lastBuildDate><atom:link href="https://kotaengineering.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Kota]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[kotaengineering@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[kotaengineering@substack.com]]></itunes:email><itunes:name><![CDATA[Ian Göbl]]></itunes:name></itunes:owner><itunes:author><![CDATA[Ian Göbl]]></itunes:author><googleplay:owner><![CDATA[kotaengineering@substack.com]]></googleplay:owner><googleplay:email><![CDATA[kotaengineering@substack.com]]></googleplay:email><googleplay:author><![CDATA[Ian Göbl]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Kota’s Method for Reliable Integration Tests]]></title><description><![CDATA[Fast, Predictable, and Repeatable: Why Our Integration Tests Are Now Faster to Write Than Testing Manually]]></description><link>https://kotaengineering.substack.com/p/kotas-method-for-reliable-integration</link><guid isPermaLink="false">https://kotaengineering.substack.com/p/kotas-method-for-reliable-integration</guid><dc:creator><![CDATA[Ian Göbl]]></dc:creator><pubDate>Mon, 08 Dec 2025 15:55:07 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Zm4y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1ed2d7e-b3b8-41f9-9adf-82d755fc1250_1024x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Zm4y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1ed2d7e-b3b8-41f9-9adf-82d755fc1250_1024x608.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Zm4y!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1ed2d7e-b3b8-41f9-9adf-82d755fc1250_1024x608.png 424w, https://substackcdn.com/image/fetch/$s_!Zm4y!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1ed2d7e-b3b8-41f9-9adf-82d755fc1250_1024x608.png 848w, https://substackcdn.com/image/fetch/$s_!Zm4y!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1ed2d7e-b3b8-41f9-9adf-82d755fc1250_1024x608.png 1272w, https://substackcdn.com/image/fetch/$s_!Zm4y!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1ed2d7e-b3b8-41f9-9adf-82d755fc1250_1024x608.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Zm4y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1ed2d7e-b3b8-41f9-9adf-82d755fc1250_1024x608.png" width="1024" height="608" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d1ed2d7e-b3b8-41f9-9adf-82d755fc1250_1024x608.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:608,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Zm4y!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1ed2d7e-b3b8-41f9-9adf-82d755fc1250_1024x608.png 424w, https://substackcdn.com/image/fetch/$s_!Zm4y!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1ed2d7e-b3b8-41f9-9adf-82d755fc1250_1024x608.png 848w, https://substackcdn.com/image/fetch/$s_!Zm4y!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1ed2d7e-b3b8-41f9-9adf-82d755fc1250_1024x608.png 1272w, https://substackcdn.com/image/fetch/$s_!Zm4y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1ed2d7e-b3b8-41f9-9adf-82d755fc1250_1024x608.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>At Kota, integration tests are a core part of how we ship reliable software. This post walks through the simple framework we use to catch issues early, move fast, and maintain confidence as our systems grow.</p><p>First things first, let&#8217;s clear up what <strong>we</strong> mean when we say &#8220;integration test.&#8221; For us, an integration test ensures that different components of our system interact with each other in the intended way. Some teams include UI in their integration tests (which of course can be valid), but we differentiate between tests that interact with the UI (covered in our previous blog post) and those that don&#8217;t. Here, we&#8217;re talking specifically about the ones that don&#8217;t touch the UI at all.</p><p>Our integration test suite uses real infrastructure (via Docker containers) to test the full application stack. This includes HTTP endpoints, database interactions, and authentication flows. Some might call this endpoint testing or API testing. Call it what you like; at least now we&#8217;re on the same page.</p><h1>Our Principles for Integration Tests &#128203;</h1><p>Let&#8217;s start by outlining some simple principles we follow for our integration tests. These aren&#8217;t strict &#8220;rules&#8221;, but rather practices we&#8217;ve arrived at after plenty of trial and error.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FRGH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8466a3bd-1736-4ed9-a8d3-b83fef967c7d_480x265.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FRGH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8466a3bd-1736-4ed9-a8d3-b83fef967c7d_480x265.gif 424w, https://substackcdn.com/image/fetch/$s_!FRGH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8466a3bd-1736-4ed9-a8d3-b83fef967c7d_480x265.gif 848w, https://substackcdn.com/image/fetch/$s_!FRGH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8466a3bd-1736-4ed9-a8d3-b83fef967c7d_480x265.gif 1272w, https://substackcdn.com/image/fetch/$s_!FRGH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8466a3bd-1736-4ed9-a8d3-b83fef967c7d_480x265.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FRGH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8466a3bd-1736-4ed9-a8d3-b83fef967c7d_480x265.gif" width="480" height="265" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8466a3bd-1736-4ed9-a8d3-b83fef967c7d_480x265.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:265,&quot;width&quot;:480,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1403536,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://kotaengineering.substack.com/i/179819381?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8466a3bd-1736-4ed9-a8d3-b83fef967c7d_480x265.gif&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!FRGH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8466a3bd-1736-4ed9-a8d3-b83fef967c7d_480x265.gif 424w, https://substackcdn.com/image/fetch/$s_!FRGH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8466a3bd-1736-4ed9-a8d3-b83fef967c7d_480x265.gif 848w, https://substackcdn.com/image/fetch/$s_!FRGH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8466a3bd-1736-4ed9-a8d3-b83fef967c7d_480x265.gif 1272w, https://substackcdn.com/image/fetch/$s_!FRGH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8466a3bd-1736-4ed9-a8d3-b83fef967c7d_480x265.gif 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3><strong>Test Isolation</strong></h3><p>Each test should be independent and able to run in parallel. This is important not only for keeping execution time low (you&#8217;re only as fast as your slowest test) but also because it better reflects how the application behaves in production. Requests can arrive at any time, in any order, and your system needs to be able to handle that.</p><h3><strong>Infrastructure Creation</strong></h3><p>Each test run should create the required infrastructure using Docker containers. The infrastructure should be created and destroyed for every run to avoid data corruption and guarantee a clean slate. This makes onboarding easier (new engineers can simply click &#8220;Run Tests&#8221;) and removes environment specific flakiness caused by inconsistent setups.</p><h3><strong>Direct Database Seeding</strong></h3><p>When a test depends on data existing in the database, it should insert that data directly rather than calling other endpoints to set it up. If we&#8217;re testing an endpoint such as <code>GET /pension/{pensionId}</code>, we don&#8217;t want the test to fail because another endpoint responsible for creating pensions unexpectedly broke or had delays. Seeding data directly isolates the behavior you want to test and significantly reduces flakiness.</p><h3><strong>No External Dependencies</strong></h3><p>External APIs should be mocked to ensure reliability. While there&#8217;s value in detecting breaking changes from third-party providers, we don&#8217;t want our pipelines blocked because of issues in someone else&#8217;s test infrastructure. Mocking keeps our tests consistent and under our control.</p><h3><strong>No Actual Messaging</strong></h3><p>If a test needs to assert behavior that normally results from a message consumer, we call that consumer directly rather than relying on a real message bus. While messages usually process quickly, they can occasionally be delayed. That delay can lead to slower, inconsistent test runs. In many cases, the consumer&#8217;s processing time isn&#8217;t relevant to what we&#8217;re actually testing. </p><p>For example, if an <code>EmployeeAddressUpdated</code> event is published, there might be many subscribers handling that event in separate processes. But for this test, we only care about one thing: <em>when we call the endpoint to update the address, is the address updated?</em></p><h1>So What Happens When a Test Runs? &#129335;&#8205;&#9794;&#65039;</h1><p>To follow the above rules, a few steps must occur before a test can actually run.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!V1CC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8916798a-fee3-41c4-9d4f-0e179b5c50bb_498x230.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!V1CC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8916798a-fee3-41c4-9d4f-0e179b5c50bb_498x230.gif 424w, https://substackcdn.com/image/fetch/$s_!V1CC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8916798a-fee3-41c4-9d4f-0e179b5c50bb_498x230.gif 848w, https://substackcdn.com/image/fetch/$s_!V1CC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8916798a-fee3-41c4-9d4f-0e179b5c50bb_498x230.gif 1272w, https://substackcdn.com/image/fetch/$s_!V1CC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8916798a-fee3-41c4-9d4f-0e179b5c50bb_498x230.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!V1CC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8916798a-fee3-41c4-9d4f-0e179b5c50bb_498x230.gif" width="680" height="314.0562248995984" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8916798a-fee3-41c4-9d4f-0e179b5c50bb_498x230.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:230,&quot;width&quot;:498,&quot;resizeWidth&quot;:680,&quot;bytes&quot;:4975365,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://kotaengineering.substack.com/i/179819381?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8916798a-fee3-41c4-9d4f-0e179b5c50bb_498x230.gif&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!V1CC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8916798a-fee3-41c4-9d4f-0e179b5c50bb_498x230.gif 424w, https://substackcdn.com/image/fetch/$s_!V1CC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8916798a-fee3-41c4-9d4f-0e179b5c50bb_498x230.gif 848w, https://substackcdn.com/image/fetch/$s_!V1CC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8916798a-fee3-41c4-9d4f-0e179b5c50bb_498x230.gif 1272w, https://substackcdn.com/image/fetch/$s_!V1CC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8916798a-fee3-41c4-9d4f-0e179b5c50bb_498x230.gif 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><ol><li><p> <strong>Docker Setup</strong><br>The integration tests download Docker images for the system&#8217;s dependencies (PostgreSQL, Redis) and start those containers using <strong><a href="https://testcontainers.com/">Testcontainers</a></strong>. The first time someone runs the integration tests this can take ~60 seconds, but with cached images subsequent runs take only 5&#8211;10 seconds.<br>This ensures we are always working with a fresh, empty database free from any pre-existing data or corruption.</p></li><li><p><strong>Application Factory</strong><br>We use <a href="https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-10.0&amp;pivots=nunit#customize-webapplicationfactory">WebApplicationFactory</a> to create a test server (i.e. to start the application we will be sending requests to). This lets us apply behaviors and configurations that are <em>only</em> active in tests, not in production.</p><pre><code><code>public class CustomAppFactory(Dependencies dependencies) : WebApplicationFactory&lt;Program&gt;
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        ConfigureSettings(builder);
        ConfigureTestAuthorization(builder);
    }

    private void ConfigureSettings(IWebHostBuilder builder)
    {
       builder.UseSetting(&#8221;IsIntegrationTests&#8221;, &#8220;true&#8221;);
 
       // set a fake connection string for service bus
       builder.UseSetting(&#8221;ConnectionStrings:ServiceBus&#8221;, &#8220;Endpoint=sb://fake-test-bus.servicebus.windows.net/&#8221;);
       
      // Set DB connection strings to our newly created image
        var postgres = dependencies.Postgres.GetConnectionString();
        builder.UseSetting(&#8221;ConnectionStrings:DbConnection&#8221;, postgres + &#8220;;Include Error Detail=true&#8221;);
    }
}</code></code></pre></li><li><p><strong>Override Settings</strong><br>By creating a <strong>CustomAppFactory</strong>, we can specify configurations that exist solely for our tests. In the example above, we override the connection strings for both the messaging service bus and the database. Our tests now run against the newly created database from step 1, and the messaging framework (MassTransit) no longer attempts to send messages to the real service bus, as it recognizes no such infrastructure exists.</p></li><li><p><strong>Override Authorization</strong><br>JWT validation is <strong>disabled</strong> in the test environment to simplify setup and avoid requiring our authentication service. This means:</p><ul><li><p>Tests create JWT tokens in-memory using helper methods.</p></li><li><p>We have helper methods for each type of user, since each requires different claims in the token.</p></li><li><p>No external identity provider is needed.</p></li><li><p>No token signing or validation occurs.</p></li><li><p>Authorization is still enforced at the application logic level. This is important, as it allows us to test that authorization rules are correctly applied. For example, we verify that only an admin token can access an admin-only endpoint.</p></li></ul></li></ol><pre><code><code>public static void ConfigureTestAuthorization(IWebHostBuilder builder)
{
    builder.ConfigureTestServices(services =&gt;
    {
       // Override the application AuthZ
        services.PostConfigure&lt;JwtBearerOptions&gt;(
            JwtBearerDefaults.AuthenticationScheme,
            options =&gt;
            {
              // Completely disable automatic configuration retrieval
              options.ConfigurationManager = null;
              options.Configuration = null;
              options.Authority = null;
              options.MetadataAddress = null;

              // Disable all validation            
              options.RequireHttpsMetadata = false;                    
              options.TokenValidationParameters.ValidateIssuer = false;
              // ........

              // signature validator accepts any JWT without validation
             options.TokenValidationParameters.SignatureValidator =          
              (token, parameters) =&gt;
              {
                var handler = new JwtSecurityTokenHandler();
                return handler.ReadJwtToken(token);
              };

             // Use events allow us to debug and authorization issues        
             // that may happen while running the tests                
             options.Events = new JwtBearerEvents                
             {                    

               OnAuthenticationFailed = context =&gt;                    
               {                        
                 var logger = 
                  context.HttpContext
                         .RequestServices
                         .GetRequiredService&lt;ILogger&lt;TestAuth&gt;&gt;();                        

                  logger.LogError(context.Exception, 
                   &#8220;== OnAuthenticationFailed ==&#8221;);                        

                  return Task.CompletedTask;                    
                 }                
               };            
             }        
            );    
     };
}</code>
</code></pre><ol start="5"><li><p><strong>Database Migrations</strong><br>Because these are fresh databases, we execute all database migrations on each schema to ensure the environment reflects the latest structure. This also required ensuring that all migrations work from a blank slate, which meant making a few retrospective edits to older migration scripts.</p><pre><code><code>private void ApplyMigrations(IServiceProvider services)
{
    using var scope = services.CreateScope();
    var logger = scope.ServiceProvider
                .GetRequiredService&lt;ILogger&lt;HarpAppFactory&gt;&gt;();

    var dbContextType = typeof(ApplicationDbContext);
    logger.LogInformation(
       &#8221;Running migrations for {DbContextType}...&#8221;,
        dbContextType.Name);
            
     using var dbContext = 
       (DbContext)scope.ServiceProvider
       .GetRequiredService(dbContextType);

     dbContext.Database.Migrate();

     logger.LogInformation(&#8221;Migrations applied successfully for 
     {DbContextType}&#8221;, dbContextType.Name);
}</code>
</code></pre></li><li><p><strong>Warmup</strong></p><p>At this point the hard part is done and the application is ready. We call a warmup endpoint to confirm that the application is running and ready to receive traffic.</p></li><li><p><strong>Tests run</strong><br>All tests are run in parallel. This keeps total runtime low and more closely mirrors real production load.</p></li><li><p><strong>Cleanup</strong> <br>After the test run completes, we dispose of all test infrastructure. The Docker containers are stopped and removed, and the environment is reset and ready for the next run where it all happens again.</p></li></ol><h1>Test Example &#128300;</h1><p>Below is an example of what an actual running test looks like in our system (yes, this is a real test).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PNiz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45fa8c79-091b-421f-84cf-dba78df23475_480x366.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PNiz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45fa8c79-091b-421f-84cf-dba78df23475_480x366.gif 424w, https://substackcdn.com/image/fetch/$s_!PNiz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45fa8c79-091b-421f-84cf-dba78df23475_480x366.gif 848w, https://substackcdn.com/image/fetch/$s_!PNiz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45fa8c79-091b-421f-84cf-dba78df23475_480x366.gif 1272w, https://substackcdn.com/image/fetch/$s_!PNiz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45fa8c79-091b-421f-84cf-dba78df23475_480x366.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PNiz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45fa8c79-091b-421f-84cf-dba78df23475_480x366.gif" width="480" height="366" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/45fa8c79-091b-421f-84cf-dba78df23475_480x366.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:366,&quot;width&quot;:480,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1244710,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://kotaengineering.substack.com/i/179819381?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45fa8c79-091b-421f-84cf-dba78df23475_480x366.gif&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!PNiz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45fa8c79-091b-421f-84cf-dba78df23475_480x366.gif 424w, https://substackcdn.com/image/fetch/$s_!PNiz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45fa8c79-091b-421f-84cf-dba78df23475_480x366.gif 848w, https://substackcdn.com/image/fetch/$s_!PNiz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45fa8c79-091b-421f-84cf-dba78df23475_480x366.gif 1272w, https://substackcdn.com/image/fetch/$s_!PNiz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45fa8c79-091b-421f-84cf-dba78df23475_480x366.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><pre><code>[Test]
[CancelAfter(2 * 60 * 1000)]
public async Task Pension_UpdatePension_Returns200(CancellationToken ct)
{
    <em>// Arrange
    // Create data in DB
    </em>await using var db = Bootstrapper.CreateDbContext();
    var org = DatabaseHelper.CreateOrg(db);
    var user = DatabaseHelper.CreateEmployee(db, org.id);
    var pension = DatabaseHelper.CreatePension(db, user.id);
    
    // Create http client + request
    using var client = SharedBootstrapper.CreateHttpClient();
    client.AddEmployeeAuthorization(user.Id);
    var patchRequest = 
      new UpdatePensionRequest { EmployeeContribution = 0.1m };
    
    // Act
    // Send Http Request
    var response = await client.SendAsync(
        client.CreateRequest(
            HttpMethod.Patch,
            Uris.<strong>PatchPensionContributionUri</strong>,
            patchRequest),
        ct);

    <em>// Assert
    </em>response.EnsureSuccessStatusCode();

    // Create fresh DB context, and assert that the Employee % is the   
       correct value based on our request
    await using var assertDbContext = Bootstrapper.CreateDbContext();
    var pension = 
        DatabaseHelper.GetPension(assertDbContext, pension.id);
    assertPension.EmployeePercentage.Should().Be(0.1m);
}</code></pre><p>We start by arranging any data that the test will rely on. In this case, we create an organization, an employee, and a pension in the database using helper methods. This demonstrates the direct database seeding approach mentioned earlier in our principles.</p><p>Next, we arrange the HTTP client that will send the request. This includes adding the appropriate authorization for the type of user (in this case, an <em>Employee</em>) that will be calling the endpoint. You&#8217;ll also notice that we reference the actual request type, <code>UpdatePensionRequest</code>, and allow the HTTP client to handle serialization.<br>This lets our tests easily construct requests and make assertions against the responses. In this particular example, there&#8217;s no response body, just an affirmative status code.</p><p>Finally, we assert that the response was successful and that the expected changes were applied to the database. And that&#8217;s it, we&#8217;ve written a complete integration test.</p><p>While there is some initial effort required to build helper methods and test utilities, once we established the foundation it fundamentally changes how we develop. It&#8217;s now faster and easier to write an integration test than to manually debug and test the endpoint. I firmly believe that people follow the path of least resistance, and by making integration tests simple to write, we&#8217;ve naturally encouraged a steadily growing suite of automated, repeatable tests.</p><h1>Wrapping up &#127873;</h1><p>We&#8217;ve built an integration testing setup that mirrors real infrastructure, isolates each test, eliminates external flakiness, all while staying fast enough to run on every machine, every time. We chose this approach because we wanted tests that developers actually want to write: predictable, debuggable, and reflective of real application behavior. Now that the foundation is in place we are implementing this across multiple teams and systems and <strong>it is paying off</strong>. Writing an integration test is often faster than manual debugging, and our growing suite of tests gives us confidence to move quickly. It&#8217;s become a core part of how we ship software at Kota, and it keeps getting better as we continue to invest in it.</p><p>Shoutout to the engineers here before my time for the great foundations they have laid within Kota in this area &#128079; If anybody reading would like more information feel free to reach out &#9996;&#65039;</p><p></p>]]></content:encoded></item><item><title><![CDATA[Faster, Safer Releases with Playwright]]></title><description><![CDATA[How we added end-to-end testing to our Embed product to cut down manual testing and bring speed and stability to our CI/CD pipeline]]></description><link>https://kotaengineering.substack.com/p/faster-safer-releases-with-playwright</link><guid isPermaLink="false">https://kotaengineering.substack.com/p/faster-safer-releases-with-playwright</guid><dc:creator><![CDATA[Aisling Gibbons]]></dc:creator><pubDate>Thu, 25 Sep 2025 13:35:38 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1498084393753-b411b2d26b34?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxmYXN0fGVufDB8fHx8MTc1ODgwNzgwNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1498084393753-b411b2d26b34?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxmYXN0fGVufDB8fHx8MTc1ODgwNzgwNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1498084393753-b411b2d26b34?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxmYXN0fGVufDB8fHx8MTc1ODgwNzgwNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1498084393753-b411b2d26b34?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxmYXN0fGVufDB8fHx8MTc1ODgwNzgwNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1498084393753-b411b2d26b34?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxmYXN0fGVufDB8fHx8MTc1ODgwNzgwNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1498084393753-b411b2d26b34?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxmYXN0fGVufDB8fHx8MTc1ODgwNzgwNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1498084393753-b411b2d26b34?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxmYXN0fGVufDB8fHx8MTc1ODgwNzgwNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="5955" height="3350" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1498084393753-b411b2d26b34?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxmYXN0fGVufDB8fHx8MTc1ODgwNzgwNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3350,&quot;width&quot;:5955,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;long exposure photography of road and cars&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="long exposure photography of road and cars" title="long exposure photography of road and cars" srcset="https://images.unsplash.com/photo-1498084393753-b411b2d26b34?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxmYXN0fGVufDB8fHx8MTc1ODgwNzgwNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1498084393753-b411b2d26b34?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxmYXN0fGVufDB8fHx8MTc1ODgwNzgwNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1498084393753-b411b2d26b34?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxmYXN0fGVufDB8fHx8MTc1ODgwNzgwNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1498084393753-b411b2d26b34?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxmYXN0fGVufDB8fHx8MTc1ODgwNzgwNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>At Kota, we&#8217;re always looking for ways to improve how we work - not just for our engineering team, but ultimately for our customers too. One of the biggest wins for us recently has been creating end-to-end (E2E) tests with Playwright, integrated directly into our GitHub Actions workflow. </p><p>Why? Because our embed product is complex, every provider has slightly different rules and flows and we spend lot&#8217;s of time manually checking them. Now we have built an automated safety net that catches issues early, ultimately saving hours of manual testing and makes everyone a little bit more confident when hitting &#8220;merge&#8221;.</p><h2>Why we went down the E2E Road..</h2><p>Let&#8217;s be honest: at this point, having automated tests in your CI isn&#8217;t groundbreaking - it&#8217;s become a fundamental part of engineering. But when you&#8217;re dealing with a product as complex as a multi-provider health insurance platform, &#8220;basic coverage&#8221; just wasn&#8217;t cutting it. <br><br>We needed something more comprehensive, something that gave us confidence in all of our user journey&#8217;s, from setting up an employer to onboarding employees, across multiple insurers. </p><p>That&#8217;s where our Playwright suite comes in. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XLEo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F259e1388-05bc-4f9e-a0b3-6964edd7f66c_1082x277.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XLEo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F259e1388-05bc-4f9e-a0b3-6964edd7f66c_1082x277.png 424w, https://substackcdn.com/image/fetch/$s_!XLEo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F259e1388-05bc-4f9e-a0b3-6964edd7f66c_1082x277.png 848w, https://substackcdn.com/image/fetch/$s_!XLEo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F259e1388-05bc-4f9e-a0b3-6964edd7f66c_1082x277.png 1272w, https://substackcdn.com/image/fetch/$s_!XLEo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F259e1388-05bc-4f9e-a0b3-6964edd7f66c_1082x277.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XLEo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F259e1388-05bc-4f9e-a0b3-6964edd7f66c_1082x277.png" width="1082" height="277" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/259e1388-05bc-4f9e-a0b3-6964edd7f66c_1082x277.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:277,&quot;width&quot;:1082,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:44881,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://aislinggibbons.substack.com/i/173756866?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42c1cd5f-6dc6-4db1-b729-c225c9372118_1082x277.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!XLEo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F259e1388-05bc-4f9e-a0b3-6964edd7f66c_1082x277.png 424w, https://substackcdn.com/image/fetch/$s_!XLEo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F259e1388-05bc-4f9e-a0b3-6964edd7f66c_1082x277.png 848w, https://substackcdn.com/image/fetch/$s_!XLEo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F259e1388-05bc-4f9e-a0b3-6964edd7f66c_1082x277.png 1272w, https://substackcdn.com/image/fetch/$s_!XLEo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F259e1388-05bc-4f9e-a0b3-6964edd7f66c_1082x277.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>What we built..</h2><p>We designed our tests around real-world flows, not just isolated cases. A few things we are happy about: </p><ul><li><p><strong>Provider-specific coverage:</strong> every insurer we support has dedicated tests</p></li><li><p><strong>Realistic test data:</strong> our scenarios look real</p></li><li><p><strong>Success + Failure checks:</strong> we don&#8217;t just test the happy path</p></li><li><p><strong>Reusable utilities:</strong> actions, selectors, navigation helpers and API utilities keep the suite clean and easy to extend. </p></li></ul><p>So basically we can spin up employers and employees on the fly, run them through real flows and get reliable results.</p><h2>How we integrated into our CI..</h2><p>Sure, running tests in CI is pretty standard, but we didn&#8217;t just want to tick the box. The challenge was making them <strong>fast and reliable</strong> so they actually help us, not slow everyone down.</p><h3>How we structured it</h3><ul><li><p><strong>Local backend runs</strong> &#8594; spin up the backend in Docker, so tests always hit the latest code</p></li><li><p><strong>Build first, then test</strong> &#8594; E2Es only run after a successful build, so we don&#8217;t waste time if things are already broken</p></li><li><p><strong>Parallel checkouts</strong> &#8594; frontend and backend clone side-by-side, no waiting around</p></li></ul><h3>How we made it faster</h3><ul><li><p><strong>Build cache hits</strong> save 5&#8211;8 minutes per PR</p></li><li><p><strong>Parallel jobs</strong> make validation about 3&#215; faster</p></li><li><p><strong>Browser caching</strong> shaves off another 2&#8211;3 minutes</p></li><li><p><strong>Smart cleanup</strong> keeps us under GitHub&#8217;s storage limits</p></li></ul><h3>What it adds up to</h3><ul><li><p><strong>Build</strong> &lt; 1 minute</p></li><li><p><strong>Validation</strong> (lint, unit tests, typecheck) ~2 minutes</p></li><li><p><strong>E2Es</strong> ~10 minutes (cloning backend, installing Playwright, running tests)</p></li></ul><p>Because validation and E2Es run in parallel, the <strong>total wall-clock time</strong> from PR to merge is ~ <strong>12 minutes</strong>. Engineers can open a PR, get the full suite run, and merge to <code>main</code> with confidence &#8212; all in under a quarter of an hour.</p><p>And for the smaller stuff, we&#8217;ve added a <strong>bypass label</strong>. If a PR is just a dependency bump or a trivial change, engineers can skip E2Es entirely, saving time without sacrificing safety where it really matters.</p><h2><br>Why does it matter?</h2><p>The payoff is simple: engineers can open a PR, get feedback fast, and merge with confidence. We don&#8217;t have to spend hours manually checking flows, and we catch issues earlier, before they sneak into production.</p><p>More importantly, it frees us up to focus on the fun stuff: building features that actually make health insurance easier for employers and employees.</p>]]></content:encoded></item></channel></rss>