Repository: dotnet/aspnetcore
File: src/Components/test/E2ETest/Tests/BootResourceCachingTest.cs (C#)

Code before:
        Assert.DoesNotContain(cacheEntryForComponentsDll, cacheEntryUrls2);
        Assert.DoesNotContain(cacheEntryForDotNetWasm, cacheEntryUrls2);
        Assert.Contains(cacheEntryForDotNetWasmWithChangedHash, cacheEntryUrls2);

        // On the next load, we'll fetch only the items we need (not things already cached)
        GetAndClearRequestedPaths();
        Navigate("about:blank");
        Browser.Equal(string.Empty, () => Browser.Title);
        Navigate("/");
        WaitUntilLoaded();
        var subsequentResourcesRequested = GetAndClearRequestedPaths();
        Assert.Collection(subsequentResourcesRequested.Where(url => url.Contains(".wasm")),
            requestedDll => Assert.Contains("/Microsoft.AspNetCore.Components.wasm", requestedDll),
            requestedDll => Assert.Contains("/dotnet.native.wasm", requestedDll));

        var cacheEntryUrls3=GetCacheEntryUrls();
        // wait until the cache was cleaned, max 500ms
        for(int i=0; i<5; i++)
        {
            if(!cacheEntryUrls3.Contains(cacheEntryForDotNetWasmWithChangedHash))
            {
                break;
            }
            await Task.Delay(100); // wait for cache purge
            cacheEntryUrls3 = GetCacheEntryUrls();
        }
        Assert.Contains(cacheEntryForComponentsDll, cacheEntryUrls3);
        Assert.Contains(cacheEntryForDotNetWasm, cacheEntryUrls3);
        Assert.DoesNotContain(cacheEntryForDotNetWasmWithChangedHash, cacheEntryUrls3);
    }

    private IReadOnlyCollection<string> GetCacheEntryUrls()
    {
        var js = @"
                (async function(cacheName, completedCallback) {
                    const cache = await caches.open(cacheName);
                    const keys = await cache.keys();
                    const urls = keys.map(r => r.url);
                    completedCallback(urls);
                }).apply(null, arguments)";
        var jsExecutor = (IJavaScriptExecutor)Browser;
        var result = (IEnumerable<object>)jsExecutor.ExecuteAsyncScript(js, CacheName);
        return result.Cast<string>().ToList();
    }

    private void RemoveCacheEntry(string url)
    {
        var js = @"
                (async function(cacheName, urlToRemove, completedCallback) {
                    const cache = await caches.open(cacheName);
                    await cache.delete(urlToRemove);

Review comment (performance):
Nit: formatting
```suggestion
        var cacheEntryUrls3 = GetCacheEntryUrls();
        // wait until the cache was cleaned, max 500ms
        for (var i = 0; i < 5; i++)
        {
            if (!cacheEntryUrls3.Contains(cacheEntryForDotNetWasmWithChangedHash))
            {
                break;
            }
            await Task.Delay(100); // wait for cache purge
            cacheEntryUrls3 = GetCacheEntryUrls();
        }
```

Code after:
        Assert.DoesNotContain(cacheEntryForComponentsDll, cacheEntryUrls2);
        Assert.DoesNotContain(cacheEntryForDotNetWasm, cacheEntryUrls2);
        Assert.Contains(cacheEntryForDotNetWasmWithChangedHash, cacheEntryUrls2);

        // On the next load, we'll fetch only the items we need (not things already cached)
        GetAndClearRequestedPaths();
        Navigate("about:blank");
        Browser.Equal(string.Empty, () => Browser.Title);
        Navigate("/");
        WaitUntilLoaded();
        var subsequentResourcesRequested = GetAndClearRequestedPaths();
        Assert.Collection(subsequentResourcesRequested.Where(url => url.Contains(".wasm")),
            requestedDll => Assert.Contains("/Microsoft.AspNetCore.Components.wasm", requestedDll),
            requestedDll => Assert.Contains("/dotnet.native.wasm", requestedDll));

        var cacheEntryUrls3 = GetCacheEntryUrls();
        // wait until the cache was cleaned, max 500ms
        for (var i = 0; i < 5; i++)
        {
            if (!cacheEntryUrls3.Contains(cacheEntryForDotNetWasmWithChangedHash))
            {
                break;
            }
            await Task.Delay(100); // wait for cache purge
            cacheEntryUrls3 = GetCacheEntryUrls();
        }
        Assert.Contains(cacheEntryForComponentsDll, cacheEntryUrls3);
        Assert.Contains(cacheEntryForDotNetWasm, cacheEntryUrls3);
        Assert.DoesNotContain(cacheEntryForDotNetWasmWithChangedHash, cacheEntryUrls3);
    }

    private IReadOnlyCollection<string> GetCacheEntryUrls()
    {
        var js = @"
                (async function(cacheName, completedCallback) {
                    const cache = await caches.open(cacheName);
                    const keys = await cache.keys();
                    const urls = keys.map(r => r.url);
                    completedCallback(urls);
                }).apply(null, arguments)";
        var jsExecutor = (IJavaScriptExecutor)Browser;
        var result = (IEnumerable<object>)jsExecutor.ExecuteAsyncScript(js, CacheName);
        return result.Cast<string>().ToList();
    }

    private void RemoveCacheEntry(string url)
    {
        var js = @"
                (async function(cacheName, urlToRemove, completedCallback) {
                    const cache = await caches.open(cacheName);
                    await cache.delete(urlToRemove);