// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:core' hide print;
import 'dart:io' hide exit;

import 'package:path/path.dart' as path;
import 'package:shelf/shelf.dart';

import 'browser.dart';
import 'run_command.dart';
import 'test/common.dart';
import 'utils.dart';

final String _bat = Platform.isWindows ? '.bat' : '';
final String _flutterRoot = path.dirname(path.dirname(path.dirname(path.fromUri(Platform.script))));
final String _flutter = path.join(_flutterRoot, 'bin', 'flutter$_bat');
final String _testAppDirectory = path.join(_flutterRoot, 'dev', 'integration_tests', 'web');
final String _testAppWebDirectory = path.join(_testAppDirectory, 'web');
final String _appBuildDirectory = path.join(_testAppDirectory, 'build', 'web');
final String _target = path.join('lib', 'service_worker_test.dart');
final String _targetWithCachedResources = path.join('lib', 'service_worker_test_cached_resources.dart');
final String _targetWithBlockedServiceWorkers = path.join('lib', 'service_worker_test_blocked_service_workers.dart');
final String _targetPath = path.join(_testAppDirectory, _target);

enum ServiceWorkerTestType {
  // Mocks how FF disables service workers.
  blockedServiceWorkers,
  // Drops the main.dart.js directly on the page.
  withoutFlutterJs,
  // Uses the standard, promise-based, flutterJS initialization.
  withFlutterJs,
  // Uses the shorthand engineInitializer.autoStart();
  withFlutterJsShort,
  // Uses onEntrypointLoaded callback instead of returned promise.
  withFlutterJsEntrypointLoadedEvent,
  // Same as withFlutterJsEntrypointLoadedEvent, but with TrustedTypes enabled.
  withFlutterJsTrustedTypesOn,
  // Entrypoint generated by `flutter create`.
  generatedEntrypoint,
}

// Run a web service worker test as a standalone Dart program.
Future<void> main() async {
  // When updating this list, also update `dev/bots/test.dart`. This `main()`
  // function is only here for convenience. Adding tests here will not add them
  // to LUCI.
  await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withoutFlutterJs);
  await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJs);
  await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJsShort);
  await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent);
  await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn);
  await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withoutFlutterJs);
  await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJs);
  await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsShort);
  await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent);
  await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn);
  await runWebServiceWorkerTestWithGeneratedEntrypoint(headless: false);
  await runWebServiceWorkerTestWithBlockedServiceWorkers(headless: false);

  if (hasError) {
    print('One or more tests failed.');
    reportErrorsAndExit();
  }
}

// Regression test for https://github.com/flutter/flutter/issues/109093.
//
// Tests the entrypoint that's generated by `flutter create`.
Future<void> runWebServiceWorkerTestWithGeneratedEntrypoint({
  required bool headless,
}) async {
  await _generateEntrypoint();
  await runWebServiceWorkerTestWithCachingResources(headless: headless, testType: ServiceWorkerTestType.generatedEntrypoint);
}

Future<void> _generateEntrypoint() async {
  final Directory tempDirectory = Directory.systemTemp.createTempSync('flutter_web_generated_entrypoint.');
  await runCommand(
    _flutter,
    <String>[ 'create', 'generated_entrypoint_test' ],
    workingDirectory: tempDirectory.path,
  );
  final File generatedEntrypoint = File(path.join(tempDirectory.path, 'generated_entrypoint_test', 'web', 'index.html'));
  final String generatedEntrypointCode = generatedEntrypoint.readAsStringSync();
  final File testEntrypoint = File(path.join(
    _testAppWebDirectory,
    _testTypeToIndexFile(ServiceWorkerTestType.generatedEntrypoint),
  ));
  testEntrypoint.writeAsStringSync(generatedEntrypointCode);
  tempDirectory.deleteSync(recursive: true);
}

Future<void> _setAppVersion(int version) async {
  final File targetFile = File(_targetPath);
  await targetFile.writeAsString(
    (await targetFile.readAsString()).replaceFirst(
      RegExp(r'CLOSE\?version=\d+'),
      'CLOSE?version=$version',
    )
  );
}

String _testTypeToIndexFile(ServiceWorkerTestType type) {
  late String indexFile;
  switch (type) {
    case ServiceWorkerTestType.blockedServiceWorkers:
      indexFile = 'index_with_blocked_service_workers.html';
      break;
    case ServiceWorkerTestType.withFlutterJs:
      indexFile = 'index_with_flutterjs.html';
      break;
    case ServiceWorkerTestType.withoutFlutterJs:
      indexFile = 'index_without_flutterjs.html';
      break;
    case ServiceWorkerTestType.withFlutterJsShort:
      indexFile = 'index_with_flutterjs_short.html';
      break;
    case ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent:
      indexFile = 'index_with_flutterjs_entrypoint_loaded.html';
      break;
    case ServiceWorkerTestType.withFlutterJsTrustedTypesOn:
      indexFile = 'index_with_flutterjs_el_tt_on.html';
      break;
    case ServiceWorkerTestType.generatedEntrypoint:
      indexFile = 'generated_entrypoint.html';
      break;
  }
  return indexFile;
}

Future<void> _rebuildApp({ required int version, required ServiceWorkerTestType testType, required String target }) async {
  await _setAppVersion(version);
  await runCommand(
    _flutter,
    <String>[ 'clean' ],
    workingDirectory: _testAppDirectory,
  );
  await runCommand(
    'cp',
    <String>[
      _testTypeToIndexFile(testType),
      'index.html',
    ],
    workingDirectory: _testAppWebDirectory,
  );
  await runCommand(
    _flutter,
    <String>['build', 'web', '--profile', '-t', target],
    workingDirectory: _testAppDirectory,
    environment: <String, String>{
      'FLUTTER_WEB': 'true',
    },
  );
}

void _expectRequestCounts(
    Map<String, int> expectedCounts,
    Map<String, int> requestedPathCounts,
) {
  expect(requestedPathCounts, expectedCounts);
  requestedPathCounts.clear();
}

Future<void> _waitForAppToLoad(
    Map<String, int> waitForCounts,
    Map<String, int> requestedPathCounts,
    AppServer? server
) async {
  print('Waiting for app to load $waitForCounts');
  await Future.any(<Future<Object?>>[
    () async {
      while (!waitForCounts.entries.every((MapEntry<String, int> entry) => (requestedPathCounts[entry.key] ?? 0) >= entry.value)) {
        await Future<void>.delayed(const Duration(milliseconds: 100));
      }
    }(),
    server!.onChromeError.then((String error) {
      throw Exception('Chrome error: $error');
    }),
  ]);
}

/// A drop-in replacement for `package:test` expect that can run outside the
/// test zone.
void expect(Object? actual, Object? expected) {
  final Matcher matcher = wrapMatcher(expected);
  // matchState needs to be of type <Object?, Object?>, see https://github.com/flutter/flutter/issues/99522
  final Map<Object?, Object?> matchState = <Object?, Object?>{};
  if (matcher.matches(actual, matchState)) {
    return;
  }
  final StringDescription mismatchDescription = StringDescription();
  matcher.describeMismatch(actual, mismatchDescription, matchState, true);
  throw TestFailure(mismatchDescription.toString());
}

Future<void> runWebServiceWorkerTest({
  required bool headless,
  required ServiceWorkerTestType testType,
}) async {
  final Map<String, int> requestedPathCounts = <String, int>{};
  void expectRequestCounts(Map<String, int> expectedCounts) =>
      _expectRequestCounts(expectedCounts, requestedPathCounts);

  AppServer? server;
  Future<void> waitForAppToLoad(Map<String, int> waitForCounts) async =>
      _waitForAppToLoad(waitForCounts, requestedPathCounts, server);

  String? reportedVersion;

  Future<void> startAppServer({
    required String cacheControl,
  }) async {
    final int serverPort = await findAvailablePortAndPossiblyCauseFlakyTests();
    final int browserDebugPort = await findAvailablePortAndPossiblyCauseFlakyTests();
    server = await AppServer.start(
      headless: headless,
      cacheControl: cacheControl,
      // TODO(yjbanov): use a better port disambiguation strategy than trying
      //                to guess what ports other tests use.
      appUrl: 'http://localhost:$serverPort/index.html',
      serverPort: serverPort,
      browserDebugPort: browserDebugPort,
      appDirectory: _appBuildDirectory,
      additionalRequestHandlers: <Handler>[
        (Request request) {
          final String requestedPath = request.url.path;
          requestedPathCounts.putIfAbsent(requestedPath, () => 0);
          requestedPathCounts[requestedPath] = requestedPathCounts[requestedPath]! + 1;
          if (requestedPath == 'CLOSE') {
            reportedVersion = request.url.queryParameters['version'];
            return Response.ok('OK');
          }
          return Response.notFound('');
        },
      ],
    );
  }

  // Preserve old index.html as index_og.html so we can restore it later for other tests
  await runCommand(
    'mv',
    <String>[
      'index.html',
      'index_og.html',
    ],
    workingDirectory: _testAppWebDirectory,
  );

  final bool shouldExpectFlutterJs = testType != ServiceWorkerTestType.withoutFlutterJs;

  print('BEGIN runWebServiceWorkerTest(headless: $headless, testType: $testType)');

  try {
    /////
    // Attempt to load a different version of the service worker!
    /////
    await _rebuildApp(version: 1, testType: testType, target: _target);

    print('Call update() on the current web worker');
    await startAppServer(cacheControl: 'max-age=0');
    await waitForAppToLoad(<String, int> {
      if (shouldExpectFlutterJs)
        'flutter.js': 1,
      'CLOSE': 1,
    });
    expect(reportedVersion, '1');
    reportedVersion = null;

    await server!.chrome.reloadPage(ignoreCache: true);
    await waitForAppToLoad(<String, int> {
      if (shouldExpectFlutterJs)
        'flutter.js': 2,
      'CLOSE': 2,
    });
    expect(reportedVersion, '1');
    reportedVersion = null;

    await _rebuildApp(version: 2, testType: testType, target: _target);

    await server!.chrome.reloadPage(ignoreCache: true);
    await waitForAppToLoad(<String, int>{
      if (shouldExpectFlutterJs)
        'flutter.js': 3,
      'CLOSE': 3,
    });
    expect(reportedVersion, '2');

    reportedVersion = null;
    requestedPathCounts.clear();
    await server!.stop();

    //////////////////////////////////////////////////////
    // Caching server
    //////////////////////////////////////////////////////
    await _rebuildApp(version: 1, testType: testType, target: _target);

    print('With cache: test first page load');
    await startAppServer(cacheControl: 'max-age=3600');
    await waitForAppToLoad(<String, int>{
      'CLOSE': 1,
      'flutter_service_worker.js': 1,
    });

    expectRequestCounts(<String, int>{
      // Even though the server is caching index.html is downloaded twice,
      // once by the initial page load, and once by the service worker.
      // Other resources are loaded once only by the service worker.
      'index.html': 2,
      if (shouldExpectFlutterJs)
        'flutter.js': 1,
      'main.dart.js': 1,
      'flutter_service_worker.js': 1,
      'assets/FontManifest.json': 1,
      'assets/AssetManifest.json': 1,
      'assets/fonts/MaterialIcons-Regular.otf': 1,
      'CLOSE': 1,
      // In headless mode Chrome does not load 'manifest.json' and 'favicon.ico'.
      if (!headless)
        ...<String, int>{
          'manifest.json': 1,
          'favicon.ico': 1,
        },
    });
    expect(reportedVersion, '1');
    reportedVersion = null;

    print('With cache: test page reload');
    await server!.chrome.reloadPage();
    await waitForAppToLoad(<String, int>{
      'CLOSE': 1,
      'flutter_service_worker.js': 1,
    });

    expectRequestCounts(<String, int>{
      'flutter_service_worker.js': 1,
      'CLOSE': 1,
    });
    expect(reportedVersion, '1');
    reportedVersion = null;

    print('With cache: test page reload after rebuild');
    await _rebuildApp(version: 2, testType: testType, target: _target);

    // Since we're caching, we need to ignore cache when reloading the page.
    await server!.chrome.reloadPage(ignoreCache: true);
    await waitForAppToLoad(<String, int>{
      'CLOSE': 1,
      'flutter_service_worker.js': 2,
    });
    expectRequestCounts(<String, int>{
      'index.html': 2,
      if (shouldExpectFlutterJs)
        'flutter.js': 1,
      'flutter_service_worker.js': 2,
      'main.dart.js': 1,
      'assets/AssetManifest.json': 1,
      'assets/FontManifest.json': 1,
      'CLOSE': 1,
      if (!headless)
        'favicon.ico': 1,
    });

    expect(reportedVersion, '2');
    reportedVersion = null;
    await server!.stop();


    //////////////////////////////////////////////////////
    // Non-caching server
    //////////////////////////////////////////////////////
    print('No cache: test first page load');
    await _rebuildApp(version: 3, testType: testType, target: _target);
    await startAppServer(cacheControl: 'max-age=0');
    await waitForAppToLoad(<String, int>{
      'CLOSE': 1,
      'flutter_service_worker.js': 1,
    });

    expectRequestCounts(<String, int>{
      'index.html': 2,
      if (shouldExpectFlutterJs)
        'flutter.js': 1,
      // We still download some resources multiple times if the server is non-caching.
      'main.dart.js': 2,
      'assets/FontManifest.json': 2,
      'flutter_service_worker.js': 1,
      'assets/AssetManifest.json': 1,
      'assets/fonts/MaterialIcons-Regular.otf': 1,
      'CLOSE': 1,
      // In headless mode Chrome does not load 'manifest.json' and 'favicon.ico'.
      if (!headless)
        ...<String, int>{
          'manifest.json': 1,
          'favicon.ico': 1,
        },
    });

    expect(reportedVersion, '3');
    reportedVersion = null;

    print('No cache: test page reload');
    await server!.chrome.reloadPage();
    await waitForAppToLoad(<String, int>{
      'CLOSE': 1,
      if (shouldExpectFlutterJs)
        'flutter.js': 1,
      'flutter_service_worker.js': 1,
    });

    expectRequestCounts(<String, int>{
      if (shouldExpectFlutterJs)
        'flutter.js': 1,
      'flutter_service_worker.js': 1,
      'assets/fonts/MaterialIcons-Regular.otf': 1,
      'CLOSE': 1,
      if (!headless)
        'manifest.json': 1,
    });
    expect(reportedVersion, '3');
    reportedVersion = null;

    print('No cache: test page reload after rebuild');
    await _rebuildApp(version: 4, testType: testType, target: _target);

    // TODO(yjbanov): when running Chrome with DevTools protocol, for some
    // reason a hard refresh is still required. This works without a hard
    // refresh when running Chrome manually as normal. At the time of writing
    // this test I wasn't able to figure out what's wrong with the way we run
    // Chrome from tests.
    await server!.chrome.reloadPage(ignoreCache: true);
    await waitForAppToLoad(<String, int>{
      'CLOSE': 1,
      'flutter_service_worker.js': 1,
    });
    expectRequestCounts(<String, int>{
      'index.html': 2,
      if (shouldExpectFlutterJs)
        'flutter.js': 1,
      'flutter_service_worker.js': 2,
      'main.dart.js': 2,
      'assets/AssetManifest.json': 1,
      'assets/FontManifest.json': 2,
      'assets/fonts/MaterialIcons-Regular.otf': 1,
      'CLOSE': 1,
      if (!headless)
        ...<String, int>{
          'manifest.json': 1,
          'favicon.ico': 1,
        },
    });

    expect(reportedVersion, '4');
    reportedVersion = null;
  } finally {
    await runCommand(
      'mv',
      <String>[
        'index_og.html',
        'index.html',
      ],
      workingDirectory: _testAppWebDirectory,
    );
    await _setAppVersion(1);
    await server?.stop();
  }

  print('END runWebServiceWorkerTest(headless: $headless, testType: $testType)');
}

Future<void> runWebServiceWorkerTestWithCachingResources({
  required bool headless,
  required ServiceWorkerTestType testType
}) async {
  final Map<String, int> requestedPathCounts = <String, int>{};
  void expectRequestCounts(Map<String, int> expectedCounts) =>
      _expectRequestCounts(expectedCounts, requestedPathCounts);

  AppServer? server;
  Future<void> waitForAppToLoad(Map<String, int> waitForCounts) async =>
      _waitForAppToLoad(waitForCounts, requestedPathCounts, server);

  Future<void> startAppServer({
    required String cacheControl,
  }) async {
    final int serverPort = await findAvailablePortAndPossiblyCauseFlakyTests();
    final int browserDebugPort = await findAvailablePortAndPossiblyCauseFlakyTests();
    server = await AppServer.start(
      headless: headless,
      cacheControl: cacheControl,
      // TODO(yjbanov): use a better port disambiguation strategy than trying
      //                to guess what ports other tests use.
      appUrl: 'http://localhost:$serverPort/index.html',
      serverPort: serverPort,
      browserDebugPort: browserDebugPort,
      appDirectory: _appBuildDirectory,
      additionalRequestHandlers: <Handler>[
            (Request request) {
          final String requestedPath = request.url.path;
          requestedPathCounts.putIfAbsent(requestedPath, () => 0);
          requestedPathCounts[requestedPath] = requestedPathCounts[requestedPath]! + 1;
          if (requestedPath == 'assets/fonts/MaterialIcons-Regular.otf') {
            return Response.internalServerError();
          }
          return Response.notFound('');
        },
      ],
    );
  }

  // Preserve old index.html as index_og.html so we can restore it later for other tests
  await runCommand(
    'mv',
    <String>[
      'index.html',
      'index_og.html',
    ],
    workingDirectory: _testAppWebDirectory,
  );

  final bool shouldExpectFlutterJs = testType != ServiceWorkerTestType.withoutFlutterJs;

  print('BEGIN runWebServiceWorkerTestWithCachingResources(headless: $headless, testType: $testType)');

  try {
    //////////////////////////////////////////////////////
    // Caching server
    //////////////////////////////////////////////////////
    await _rebuildApp(version: 1, testType: testType, target: _targetWithCachedResources);

    print('With cache: test first page load');
    await startAppServer(cacheControl: 'max-age=3600');
    await waitForAppToLoad(<String, int>{
      'assets/fonts/MaterialIcons-Regular.otf': 1,
      'flutter_service_worker.js': 1,
    });

    expectRequestCounts(<String, int>{
      // Even though the server is caching index.html is downloaded twice,
      // once by the initial page load, and once by the service worker.
      // Other resources are loaded once only by the service worker.
      'index.html': 2,
      if (shouldExpectFlutterJs)
        'flutter.js': 1,
      'main.dart.js': 1,
      'flutter_service_worker.js': 1,
      'assets/FontManifest.json': 1,
      'assets/AssetManifest.json': 1,
      'assets/fonts/MaterialIcons-Regular.otf': 1,
      // In headless mode Chrome does not load 'manifest.json' and 'favicon.ico'.
      if (!headless)
        ...<String, int>{
          'manifest.json': 1,
          'favicon.ico': 1,
        },
    });

    print('With cache: test first page reload');
    await server!.chrome.reloadPage();
    await waitForAppToLoad(<String, int>{
      'assets/fonts/MaterialIcons-Regular.otf': 1,
      'flutter_service_worker.js': 1,
    });
    expectRequestCounts(<String, int>{
      'assets/fonts/MaterialIcons-Regular.otf': 1,
      'flutter_service_worker.js': 1,
    });

    print('With cache: test second page reload');
    await server!.chrome.reloadPage();
    await waitForAppToLoad(<String, int>{
      'assets/fonts/MaterialIcons-Regular.otf': 1,
      'flutter_service_worker.js': 1,
    });
    expectRequestCounts(<String, int>{
      'assets/fonts/MaterialIcons-Regular.otf': 1,
      'flutter_service_worker.js': 1,
    });

    print('With cache: test third page reload');
    await server!.chrome.reloadPage();
    await waitForAppToLoad(<String, int>{
      'assets/fonts/MaterialIcons-Regular.otf': 1,
      'flutter_service_worker.js': 1,
    });
    expectRequestCounts(<String, int>{
      'assets/fonts/MaterialIcons-Regular.otf': 1,
      'flutter_service_worker.js': 1,
    });

    print('With cache: test page reload after rebuild');
    await _rebuildApp(version: 1, testType: testType, target: _targetWithCachedResources);

    // Since we're caching, we need to ignore cache when reloading the page.
    await server!.chrome.reloadPage(ignoreCache: true);
    await waitForAppToLoad(<String, int>{
      'assets/fonts/MaterialIcons-Regular.otf': 1,
      'flutter_service_worker.js': 1,
    });
    expectRequestCounts(<String, int>{
      'index.html': 2,
      if (shouldExpectFlutterJs)
        'flutter.js': 1,
      'main.dart.js': 1,
      'flutter_service_worker.js': 2,
      'assets/FontManifest.json': 1,
      'assets/AssetManifest.json': 1,
      'assets/fonts/MaterialIcons-Regular.otf': 1,
      // In headless mode Chrome does not load 'manifest.json' and 'favicon.ico'.
      if (!headless)
        ...<String, int>{
          'favicon.ico': 1,
        },
    });
  } finally {
    await runCommand(
      'mv',
      <String>[
        'index_og.html',
        'index.html',
      ],
      workingDirectory: _testAppWebDirectory,
    );
    await server?.stop();
  }

  print('END runWebServiceWorkerTestWithCachingResources(headless: $headless, testType: $testType)');
}

Future<void> runWebServiceWorkerTestWithBlockedServiceWorkers({
  required bool headless
}) async {
  final Map<String, int> requestedPathCounts = <String, int>{};
  void expectRequestCounts(Map<String, int> expectedCounts) =>
      _expectRequestCounts(expectedCounts, requestedPathCounts);

  AppServer? server;
  Future<void> waitForAppToLoad(Map<String, int> waitForCounts) async =>
      _waitForAppToLoad(waitForCounts, requestedPathCounts, server);

  Future<void> startAppServer({
    required String cacheControl,
  }) async {
    final int serverPort = await findAvailablePortAndPossiblyCauseFlakyTests();
    final int browserDebugPort = await findAvailablePortAndPossiblyCauseFlakyTests();
    server = await AppServer.start(
      headless: headless,
      cacheControl: cacheControl,
      // TODO(yjbanov): use a better port disambiguation strategy than trying
      //                to guess what ports other tests use.
      appUrl: 'http://localhost:$serverPort/index.html',
      serverPort: serverPort,
      browserDebugPort: browserDebugPort,
      appDirectory: _appBuildDirectory,
      additionalRequestHandlers: <Handler>[
        (Request request) {
          final String requestedPath = request.url.path;
          requestedPathCounts.putIfAbsent(requestedPath, () => 0);
          requestedPathCounts[requestedPath] = requestedPathCounts[requestedPath]! + 1;
          if (requestedPath == 'CLOSE') {
            return Response.ok('OK');
          }
          return Response.notFound('');
        },
      ],
    );
  }

  // Preserve old index.html as index_og.html so we can restore it later for other tests
  await runCommand(
    'mv',
    <String>[
      'index.html',
      'index_og.html',
    ],
    workingDirectory: _testAppWebDirectory,
  );

  print('BEGIN runWebServiceWorkerTestWithBlockedServiceWorkers(headless: $headless)');
  try {
    await _rebuildApp(version: 1, testType: ServiceWorkerTestType.blockedServiceWorkers, target: _targetWithBlockedServiceWorkers);

    print('Ensure app starts (when service workers are blocked)');
    await startAppServer(cacheControl: 'max-age=3600');
    await waitForAppToLoad(<String, int>{
      'CLOSE': 1,
    });
    expectRequestCounts(<String, int>{
      'index.html': 1,
      'flutter.js': 1,
      'main.dart.js': 1,
      'assets/FontManifest.json': 1,
      'assets/fonts/MaterialIcons-Regular.otf': 1,
      'CLOSE': 1,
      // In headless mode Chrome does not load 'manifest.json' and 'favicon.ico'.
      if (!headless)
        ...<String, int>{
          'manifest.json': 1,
          'favicon.ico': 1,
        },
    });
  } finally {
    await runCommand(
      'mv',
      <String>[
        'index_og.html',
        'index.html',
      ],
      workingDirectory: _testAppWebDirectory,
    );
    await server?.stop();
  }
  print('END runWebServiceWorkerTestWithBlockedServiceWorkers(headless: $headless)');
}
