// 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, // Same as withFlutterJsEntrypointLoadedEvent, but with nonce required. withFlutterJsNonceOn, // Uses custom serviceWorkerVersion. withFlutterJsCustomServiceWorkerVersion, // Entrypoint generated by `flutter create`. generatedEntrypoint, } // Run a web service worker test as a standalone Dart program. Future 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 runWebServiceWorkerTest( headless: false, testType: ServiceWorkerTestType.withFlutterJsNonceOn, ); 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); await runWebServiceWorkerTestWithCustomServiceWorkerVersion(headless: false); if (hasError) { reportErrorsAndExit('${bold}One or more tests failed.$reset'); } reportSuccessAndExit('${bold}Tests successful.$reset'); } // Regression test for https://github.com/flutter/flutter/issues/109093. // // Tests the entrypoint that's generated by `flutter create`. Future runWebServiceWorkerTestWithGeneratedEntrypoint({required bool headless}) async { await _generateEntrypoint(); await runWebServiceWorkerTestWithCachingResources( headless: headless, testType: ServiceWorkerTestType.generatedEntrypoint, ); } Future _generateEntrypoint() async { final Directory tempDirectory = Directory.systemTemp.createTempSync( 'flutter_web_generated_entrypoint.', ); await runCommand(_flutter, [ '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 _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) { return switch (type) { ServiceWorkerTestType.blockedServiceWorkers => 'index_with_blocked_service_workers.html', ServiceWorkerTestType.withFlutterJs => 'index_with_flutterjs.html', ServiceWorkerTestType.withoutFlutterJs => 'index_without_flutterjs.html', ServiceWorkerTestType.withFlutterJsShort => 'index_with_flutterjs_short.html', ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent => 'index_with_flutterjs_entrypoint_loaded.html', ServiceWorkerTestType.withFlutterJsTrustedTypesOn => 'index_with_flutterjs_el_tt_on.html', ServiceWorkerTestType.withFlutterJsNonceOn => 'index_with_flutterjs_el_nonce.html', ServiceWorkerTestType.withFlutterJsCustomServiceWorkerVersion => 'index_with_flutterjs_custom_sw_version.html', ServiceWorkerTestType.generatedEntrypoint => 'generated_entrypoint.html', }; } Future _rebuildApp({ required int version, required ServiceWorkerTestType testType, required String target, }) async { await _setAppVersion(version); await runCommand(_flutter, ['clean'], workingDirectory: _testAppDirectory); await runCommand('cp', [ _testTypeToIndexFile(testType), 'index.html', ], workingDirectory: _testAppWebDirectory); await runCommand( _flutter, ['build', 'web', '--web-resources-cdn', '--profile', '-t', target], workingDirectory: _testAppDirectory, environment: {'FLUTTER_WEB': 'true'}, ); } void _expectRequestCounts(Map expectedCounts, Map requestedPathCounts) { expect(requestedPathCounts, expectedCounts); requestedPathCounts.clear(); } Future _waitForAppToLoad( Map waitForCounts, Map requestedPathCounts, AppServer? server, ) async { print('Waiting for app to load $waitForCounts'); await Future.any(>[ () async { int tries = 1; while (!waitForCounts.entries.every( (MapEntry entry) => (requestedPathCounts[entry.key] ?? 0) >= entry.value, )) { if (tries++ % 20 == 0) { print('Still waiting. Requested so far: $requestedPathCounts'); } await Future.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 , see https://github.com/flutter/flutter/issues/99522 final Map matchState = {}; if (matcher.matches(actual, matchState)) { return; } final StringDescription mismatchDescription = StringDescription(); matcher.describeMismatch(actual, mismatchDescription, matchState, true); throw TestFailure(mismatchDescription.toString()); } Future runWebServiceWorkerTest({ required bool headless, required ServiceWorkerTestType testType, }) async { final Map requestedPathCounts = {}; void expectRequestCounts(Map expectedCounts) => _expectRequestCounts(expectedCounts, requestedPathCounts); AppServer? server; Future waitForAppToLoad(Map waitForCounts) async => _waitForAppToLoad(waitForCounts, requestedPathCounts, server); String? reportedVersion; Future 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: [ (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', [ '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({if (shouldExpectFlutterJs) 'flutter.js': 1, 'CLOSE': 1}); expect(reportedVersion, '1'); reportedVersion = null; await server!.chrome.reloadPage(ignoreCache: true); await waitForAppToLoad({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({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({'CLOSE': 1, 'flutter_service_worker.js': 1}); expectRequestCounts({ // 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, 'flutter_bootstrap.js': 1, 'assets/FontManifest.json': 1, 'assets/AssetManifest.bin.json': 1, 'assets/fonts/MaterialIcons-Regular.otf': 1, 'CLOSE': 1, // In headless mode Chrome does not load 'manifest.json' and 'favicon.png'. if (!headless) ...{'manifest.json': 1, 'favicon.png': 1}, }); expect(reportedVersion, '1'); reportedVersion = null; print('With cache: test page reload'); await server!.chrome.reloadPage(); await waitForAppToLoad({'CLOSE': 1, 'flutter_service_worker.js': 1}); expectRequestCounts({'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({'CLOSE': 1, 'flutter_service_worker.js': 2}); expectRequestCounts({ 'index.html': 2, if (shouldExpectFlutterJs) 'flutter.js': 1, 'flutter_service_worker.js': 2, 'flutter_bootstrap.js': 1, 'main.dart.js': 1, 'assets/AssetManifest.bin.json': 1, 'assets/FontManifest.json': 1, 'CLOSE': 1, if (!headless) 'favicon.png': 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({'CLOSE': 1, 'flutter_service_worker.js': 1}); expectRequestCounts({ 'index.html': 2, if (shouldExpectFlutterJs) 'flutter.js': 1, 'main.dart.js': 1, 'assets/FontManifest.json': 1, 'flutter_service_worker.js': 1, 'flutter_bootstrap.js': 1, 'assets/AssetManifest.bin.json': 1, 'assets/fonts/MaterialIcons-Regular.otf': 1, 'CLOSE': 1, // In headless mode Chrome does not load 'manifest.json' and 'favicon.png'. if (!headless) ...{'manifest.json': 1, 'favicon.png': 1}, }); expect(reportedVersion, '3'); reportedVersion = null; print('No cache: test page reload'); await server!.chrome.reloadPage(); await waitForAppToLoad({ 'CLOSE': 1, if (shouldExpectFlutterJs) 'flutter.js': 1, 'flutter_service_worker.js': 1, }); expectRequestCounts({ if (shouldExpectFlutterJs) 'flutter.js': 1, 'flutter_service_worker.js': 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({'CLOSE': 1, 'flutter_service_worker.js': 1}); expectRequestCounts({ 'index.html': 2, if (shouldExpectFlutterJs) 'flutter.js': 1, 'flutter_service_worker.js': 2, 'flutter_bootstrap.js': 1, 'main.dart.js': 1, 'assets/AssetManifest.bin.json': 1, 'assets/FontManifest.json': 1, 'CLOSE': 1, if (!headless) ...{'manifest.json': 1, 'favicon.png': 1}, }); expect(reportedVersion, '4'); reportedVersion = null; } finally { await runCommand('mv', [ 'index_og.html', 'index.html', ], workingDirectory: _testAppWebDirectory); await _setAppVersion(1); await server?.stop(); } print('END runWebServiceWorkerTest(headless: $headless, testType: $testType)'); } Future runWebServiceWorkerTestWithCachingResources({ required bool headless, required ServiceWorkerTestType testType, }) async { final Map requestedPathCounts = {}; void expectRequestCounts(Map expectedCounts) => _expectRequestCounts(expectedCounts, requestedPathCounts); AppServer? server; Future waitForAppToLoad(Map waitForCounts) async => _waitForAppToLoad(waitForCounts, requestedPathCounts, server); Future 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: [ (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', [ 'index.html', 'index_og.html', ], workingDirectory: _testAppWebDirectory); final bool usesFlutterBootstrapJs = testType == ServiceWorkerTestType.generatedEntrypoint; final bool shouldExpectFlutterJs = !usesFlutterBootstrapJs && 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({ 'assets/fonts/MaterialIcons-Regular.otf': 1, 'flutter_service_worker.js': 1, }); expectRequestCounts({ // 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, 'flutter_bootstrap.js': usesFlutterBootstrapJs ? 2 : 1, 'assets/FontManifest.json': 1, 'assets/AssetManifest.bin.json': 1, 'assets/fonts/MaterialIcons-Regular.otf': 1, // In headless mode Chrome does not load 'manifest.json' and 'favicon.png'. if (!headless) ...{'manifest.json': 1, 'favicon.png': 1}, }); print('With cache: test first page reload'); await server!.chrome.reloadPage(); await waitForAppToLoad({ 'assets/fonts/MaterialIcons-Regular.otf': 1, 'flutter_service_worker.js': 1, }); expectRequestCounts({ 'assets/fonts/MaterialIcons-Regular.otf': 1, 'flutter_service_worker.js': 1, }); print('With cache: test second page reload'); await server!.chrome.reloadPage(); await waitForAppToLoad({ 'assets/fonts/MaterialIcons-Regular.otf': 1, 'flutter_service_worker.js': 1, }); expectRequestCounts({ 'assets/fonts/MaterialIcons-Regular.otf': 1, 'flutter_service_worker.js': 1, }); print('With cache: test third page reload'); await server!.chrome.reloadPage(); await waitForAppToLoad({ 'assets/fonts/MaterialIcons-Regular.otf': 1, 'flutter_service_worker.js': 1, }); expectRequestCounts({ '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({ 'assets/fonts/MaterialIcons-Regular.otf': 1, 'flutter_service_worker.js': 1, }); expectRequestCounts({ 'index.html': 2, if (shouldExpectFlutterJs) 'flutter.js': 1, 'main.dart.js': 1, 'flutter_service_worker.js': 2, 'flutter_bootstrap.js': usesFlutterBootstrapJs ? 2 : 1, 'assets/FontManifest.json': 1, 'assets/AssetManifest.bin.json': 1, 'assets/fonts/MaterialIcons-Regular.otf': 1, // In headless mode Chrome does not load 'manifest.json' and 'favicon.png'. if (!headless) ...{'favicon.png': 1}, }); } finally { await runCommand('mv', [ 'index_og.html', 'index.html', ], workingDirectory: _testAppWebDirectory); await server?.stop(); } print( 'END runWebServiceWorkerTestWithCachingResources(headless: $headless, testType: $testType)', ); } Future runWebServiceWorkerTestWithBlockedServiceWorkers({required bool headless}) async { final Map requestedPathCounts = {}; void expectRequestCounts(Map expectedCounts) => _expectRequestCounts(expectedCounts, requestedPathCounts); AppServer? server; Future waitForAppToLoad(Map waitForCounts) async => _waitForAppToLoad(waitForCounts, requestedPathCounts, server); Future 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: [ (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', [ '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({'CLOSE': 1}); expectRequestCounts({ '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.png'. if (!headless) ...{'manifest.json': 1, 'favicon.png': 1}, }); } finally { await runCommand('mv', [ 'index_og.html', 'index.html', ], workingDirectory: _testAppWebDirectory); await server?.stop(); } print('END runWebServiceWorkerTestWithBlockedServiceWorkers(headless: $headless)'); } /// Regression test for https://github.com/flutter/flutter/issues/130212. Future runWebServiceWorkerTestWithCustomServiceWorkerVersion({required bool headless}) async { final Map requestedPathCounts = {}; void expectRequestCounts(Map expectedCounts) => _expectRequestCounts(expectedCounts, requestedPathCounts); AppServer? server; Future waitForAppToLoad(Map waitForCounts) async => _waitForAppToLoad(waitForCounts, requestedPathCounts, server); Future 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: [ (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', [ 'index.html', 'index_og.html', ], workingDirectory: _testAppWebDirectory); print('BEGIN runWebServiceWorkerTestWithCustomServiceWorkerVersion(headless: $headless)'); try { await _rebuildApp( version: 1, testType: ServiceWorkerTestType.withFlutterJsCustomServiceWorkerVersion, target: _target, ); print('Test page load'); await startAppServer(cacheControl: 'max-age=0'); await waitForAppToLoad({ 'CLOSE': 1, 'flutter_service_worker.js': 1, 'assets/fonts/MaterialIcons-Regular.otf': 1, }); expectRequestCounts({ 'index.html': 2, 'flutter.js': 1, 'main.dart.js': 1, 'CLOSE': 1, 'flutter_service_worker.js': 1, 'flutter_bootstrap.js': 1, 'assets/FontManifest.json': 1, 'assets/AssetManifest.bin.json': 1, 'assets/fonts/MaterialIcons-Regular.otf': 1, // In headless mode Chrome does not load 'manifest.json' and 'favicon.png'. if (!headless) ...{'manifest.json': 1, 'favicon.png': 1}, }); print('Test page reload, ensure service worker is not reloaded'); await server!.chrome.reloadPage(ignoreCache: true); await waitForAppToLoad({'CLOSE': 1, 'flutter.js': 1}); expectRequestCounts({ '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.png'. if (!headless) ...{'manifest.json': 1, 'favicon.png': 1}, }); print('Test page reload after rebuild, ensure service worker is not reloaded'); await _rebuildApp( version: 1, testType: ServiceWorkerTestType.withFlutterJsCustomServiceWorkerVersion, target: _target, ); await server!.chrome.reloadPage(ignoreCache: true); await waitForAppToLoad({'CLOSE': 1, 'flutter.js': 1}); expectRequestCounts({ '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.png'. if (!headless) ...{'manifest.json': 1, 'favicon.png': 1}, }); } finally { await runCommand('mv', [ 'index_og.html', 'index.html', ], workingDirectory: _testAppWebDirectory); await server?.stop(); } print('END runWebServiceWorkerTestWithCustomServiceWorkerVersion(headless: $headless)'); }