@@ -20,6 +20,7 @@ final String _testAppDirectory = path.join(_flutterRoot, 'dev', 'integration_tes
2020final  String  _testAppWebDirectory =  path.join (_testAppDirectory, 'web' );
2121final  String  _appBuildDirectory =  path.join (_testAppDirectory, 'build' , 'web' );
2222final  String  _target =  path.join ('lib' , 'service_worker_test.dart' );
23+ final  String  _targetWithCachedResources =  path.join ('lib' , 'service_worker_test_cached_resources.dart' );
2324final  String  _targetPath =  path.join (_testAppDirectory, _target);
2425
2526enum  ServiceWorkerTestType  {
@@ -30,9 +31,12 @@ enum ServiceWorkerTestType {
3031
3132// Run a web service worker test as a standalone Dart program. 
3233Future <void > main () async  {
33-   await  runWebServiceWorkerTest (headless:  false , testType:  ServiceWorkerTestType .withFlutterJs);
3434  await  runWebServiceWorkerTest (headless:  false , testType:  ServiceWorkerTestType .withoutFlutterJs);
35+   await  runWebServiceWorkerTest (headless:  false , testType:  ServiceWorkerTestType .withFlutterJs);
3536  await  runWebServiceWorkerTest (headless:  false , testType:  ServiceWorkerTestType .withFlutterJsShort);
37+   await  runWebServiceWorkerTestWithCachingResources (headless:  false , testType:  ServiceWorkerTestType .withoutFlutterJs);
38+   await  runWebServiceWorkerTestWithCachingResources (headless:  false , testType:  ServiceWorkerTestType .withFlutterJs);
39+   await  runWebServiceWorkerTestWithCachingResources (headless:  false , testType:  ServiceWorkerTestType .withFlutterJsShort);
3640}
3741
3842Future <void > _setAppVersion (int  version) async  {
@@ -61,7 +65,7 @@ String _testTypeToIndexFile(ServiceWorkerTestType type) {
6165  return  indexFile;
6266}
6367
64- Future <void > _rebuildApp ({ required  int  version, required  ServiceWorkerTestType  testType }) async  {
68+ Future <void > _rebuildApp ({ required  int  version, required  ServiceWorkerTestType  testType,  required   String  target  }) async  {
6569  await  _setAppVersion (version);
6670  await  runCommand (
6771    _flutter,
@@ -78,14 +82,40 @@ Future<void> _rebuildApp({ required int version, required ServiceWorkerTestType
7882  );
7983  await  runCommand (
8084    _flutter,
81-     < String > ['build' , 'web' , '--profile' , '-t' , _target ],
85+     < String > ['build' , 'web' , '--profile' , '-t' , target ],
8286    workingDirectory:  _testAppDirectory,
8387    environment:  < String , String > {
8488      'FLUTTER_WEB' :  'true' ,
8589    },
8690  );
8791}
8892
93+ void  _expectRequestCounts (
94+     Map <String , int > expectedCounts,
95+     Map <String , int > requestedPathCounts,
96+ ) {
97+   expect (requestedPathCounts, expectedCounts);
98+   requestedPathCounts.clear ();
99+ }
100+ 
101+ Future <void > _waitForAppToLoad (
102+     Map <String , int > waitForCounts,
103+     Map <String , int > requestedPathCounts,
104+     AppServer ?  server
105+ ) async  {
106+   print ('Waiting for app to load $waitForCounts ' );
107+   await  Future .any (< Future <Object ?>> [
108+     () async  {
109+       while  (! waitForCounts.entries.every ((MapEntry <String , int > entry) =>  (requestedPathCounts[entry.key] ??  0 ) >=  entry.value)) {
110+         await  Future <void >.delayed (const  Duration (milliseconds:  100 ));
111+       }
112+     }(),
113+     server! .onChromeError.then ((String  error) {
114+       throw  Exception ('Chrome error: $error ' );
115+     }),
116+   ]);
117+ }
118+ 
89119/// A drop-in replacement for `package:test`  expect that can run outside the 
90120/// test zone. 
91121void  expect (Object ?  actual, Object ?  expected) {
@@ -105,25 +135,12 @@ Future<void> runWebServiceWorkerTest({
105135  required  ServiceWorkerTestType  testType,
106136}) async  {
107137  final  Map <String , int > requestedPathCounts =  < String , int > {};
108-   void  expectRequestCounts (Map <String , int > expectedCounts) {
109-     expect (requestedPathCounts, expectedCounts);
110-     requestedPathCounts.clear ();
111-   }
138+   void  expectRequestCounts (Map <String , int > expectedCounts) => 
139+       _expectRequestCounts (expectedCounts, requestedPathCounts);
112140
113141  AppServer ?  server;
114-   Future <void > waitForAppToLoad (Map <String , int > waitForCounts) async  {
115-     print ('Waiting for app to load $waitForCounts ' );
116-     await  Future .any (< Future <Object ?>> [
117-       () async  {
118-         while  (! waitForCounts.entries.every ((MapEntry <String , int > entry) =>  (requestedPathCounts[entry.key] ??  0 ) >=  entry.value)) {
119-           await  Future <void >.delayed (const  Duration (milliseconds:  100 ));
120-         }
121-       }(),
122-       server! .onChromeError.then ((String  error) {
123-         throw  Exception ('Chrome error: $error ' );
124-       }),
125-     ]);
126-   }
142+   Future <void > waitForAppToLoad (Map <String , int > waitForCounts) async  => 
143+       _waitForAppToLoad (waitForCounts, requestedPathCounts, server);
127144
128145  String ?  reportedVersion;
129146
@@ -174,7 +191,7 @@ Future<void> runWebServiceWorkerTest({
174191    ///// 
175192     // Attempt to load a different version of the service worker! 
176193    ///// 
177-      await  _rebuildApp (version:  1 , testType:  testType);
194+      await  _rebuildApp (version:  1 , testType:  testType, target :  _target );
178195
179196    print ('Call update() on the current web worker' );
180197    await  startAppServer (cacheControl:  'max-age=0' );
@@ -195,7 +212,7 @@ Future<void> runWebServiceWorkerTest({
195212    expect (reportedVersion, '1' );
196213    reportedVersion =  null ;
197214
198-     await  _rebuildApp (version:  2 , testType:  testType);
215+     await  _rebuildApp (version:  2 , testType:  testType, target :  _target );
199216
200217    await  server! .chrome.reloadPage (ignoreCache:  true );
201218    await  waitForAppToLoad (< String , int > {
@@ -212,7 +229,7 @@ Future<void> runWebServiceWorkerTest({
212229    ////////////////////////////////////////////////////// 
213230     // Caching server 
214231    ////////////////////////////////////////////////////// 
215-      await  _rebuildApp (version:  1 , testType:  testType);
232+      await  _rebuildApp (version:  1 , testType:  testType, target :  _target );
216233
217234    print ('With cache: test first page load' );
218235    await  startAppServer (cacheControl:  'max-age=3600' );
@@ -232,6 +249,7 @@ Future<void> runWebServiceWorkerTest({
232249      'flutter_service_worker.js' :  1 ,
233250      'assets/FontManifest.json' :  1 ,
234251      'assets/AssetManifest.json' :  1 ,
252+       'assets/fonts/MaterialIcons-Regular.otf' :  1 ,
235253      'CLOSE' :  1 ,
236254      // In headless mode Chrome does not load 'manifest.json' and 'favicon.ico'. 
237255      if  (! headless)
@@ -258,7 +276,7 @@ Future<void> runWebServiceWorkerTest({
258276    reportedVersion =  null ;
259277
260278    print ('With cache: test page reload after rebuild' );
261-     await  _rebuildApp (version:  2 , testType:  testType);
279+     await  _rebuildApp (version:  2 , testType:  testType, target :  _target );
262280
263281    // Since we're caching, we need to ignore cache when reloading the page. 
264282    await  server! .chrome.reloadPage (ignoreCache:  true );
@@ -288,7 +306,7 @@ Future<void> runWebServiceWorkerTest({
288306     // Non-caching server 
289307    ////////////////////////////////////////////////////// 
290308     print ('No cache: test first page load' );
291-     await  _rebuildApp (version:  3 , testType:  testType);
309+     await  _rebuildApp (version:  3 , testType:  testType, target :  _target );
292310    await  startAppServer (cacheControl:  'max-age=0' );
293311    await  waitForAppToLoad (< String , int > {
294312      'CLOSE' :  1 ,
@@ -304,6 +322,7 @@ Future<void> runWebServiceWorkerTest({
304322      'assets/FontManifest.json' :  2 ,
305323      'flutter_service_worker.js' :  1 ,
306324      'assets/AssetManifest.json' :  1 ,
325+       'assets/fonts/MaterialIcons-Regular.otf' :  1 ,
307326      'CLOSE' :  1 ,
308327      // In headless mode Chrome does not load 'manifest.json' and 'favicon.ico'. 
309328      if  (! headless)
@@ -329,6 +348,7 @@ Future<void> runWebServiceWorkerTest({
329348      if  (shouldExpectFlutterJs)
330349        'flutter.js' :  1 ,
331350      'flutter_service_worker.js' :  1 ,
351+       'assets/fonts/MaterialIcons-Regular.otf' :  1 ,
332352      'CLOSE' :  1 ,
333353      if  (! headless)
334354        'manifest.json' :  1 ,
@@ -337,7 +357,7 @@ Future<void> runWebServiceWorkerTest({
337357    reportedVersion =  null ;
338358
339359    print ('No cache: test page reload after rebuild' );
340-     await  _rebuildApp (version:  4 , testType:  testType);
360+     await  _rebuildApp (version:  4 , testType:  testType, target :  _target );
341361
342362    // TODO(yjbanov): when running Chrome with DevTools protocol, for some 
343363    // reason a hard refresh is still required. This works without a hard 
@@ -357,6 +377,7 @@ Future<void> runWebServiceWorkerTest({
357377      'main.dart.js' :  2 ,
358378      'assets/AssetManifest.json' :  1 ,
359379      'assets/FontManifest.json' :  2 ,
380+       'assets/fonts/MaterialIcons-Regular.otf' :  1 ,
360381      'CLOSE' :  1 ,
361382      if  (! headless)
362383        ...< String , int > {
@@ -382,3 +403,162 @@ Future<void> runWebServiceWorkerTest({
382403
383404  print ('END runWebServiceWorkerTest(headless: $headless , testType: $testType )\n ' );
384405}
406+ 
407+ Future <void > runWebServiceWorkerTestWithCachingResources ({
408+   required  bool  headless,
409+   required  ServiceWorkerTestType  testType
410+ }) async  {
411+   final  Map <String , int > requestedPathCounts =  < String , int > {};
412+   void  expectRequestCounts (Map <String , int > expectedCounts) => 
413+       _expectRequestCounts (expectedCounts, requestedPathCounts);
414+ 
415+   AppServer ?  server;
416+   Future <void > waitForAppToLoad (Map <String , int > waitForCounts) async  => 
417+       _waitForAppToLoad (waitForCounts, requestedPathCounts, server);
418+ 
419+   Future <void > startAppServer ({
420+     required  String  cacheControl,
421+   }) async  {
422+     final  int  serverPort =  await  findAvailablePort ();
423+     final  int  browserDebugPort =  await  findAvailablePort ();
424+     server =  await  AppServer .start (
425+       headless:  headless,
426+       cacheControl:  cacheControl,
427+       // TODO(yjbanov): use a better port disambiguation strategy than trying 
428+       //                to guess what ports other tests use. 
429+       appUrl:  'http://localhost:$serverPort /index.html' ,
430+       serverPort:  serverPort,
431+       browserDebugPort:  browserDebugPort,
432+       appDirectory:  _appBuildDirectory,
433+       additionalRequestHandlers:  < Handler > [
434+             (Request  request) {
435+           final  String  requestedPath =  request.url.path;
436+           requestedPathCounts.putIfAbsent (requestedPath, () =>  0 );
437+           requestedPathCounts[requestedPath] =  requestedPathCounts[requestedPath]!  +  1 ;
438+           if  (requestedPath ==  'assets/fonts/MaterialIcons-Regular.otf' ) {
439+             return  Response .internalServerError ();
440+           }
441+           return  Response .notFound ('' );
442+         },
443+       ],
444+     );
445+   }
446+ 
447+   // Preserve old index.html as index_og.html so we can restore it later for other tests 
448+   await  runCommand (
449+     'mv' ,
450+     < String > [
451+       'index.html' ,
452+       'index_og.html' ,
453+     ],
454+     workingDirectory:  _testAppWebDirectory,
455+   );
456+ 
457+   final  bool  shouldExpectFlutterJs =  testType !=  ServiceWorkerTestType .withoutFlutterJs;
458+ 
459+   print ('BEGIN runWebServiceWorkerTestWithCachingResources(headless: $headless , testType: $testType )\n ' );
460+ 
461+   try  {
462+     ////////////////////////////////////////////////////// 
463+      // Caching server 
464+     ////////////////////////////////////////////////////// 
465+      await  _rebuildApp (version:  1 , testType:  testType, target:  _targetWithCachedResources);
466+ 
467+     print ('With cache: test first page load' );
468+     await  startAppServer (cacheControl:  'max-age=3600' );
469+     await  waitForAppToLoad (< String , int > {
470+       'assets/fonts/MaterialIcons-Regular.otf' :  1 ,
471+       'flutter_service_worker.js' :  1 ,
472+     });
473+ 
474+     expectRequestCounts (< String , int > {
475+       // Even though the server is caching index.html is downloaded twice, 
476+       // once by the initial page load, and once by the service worker. 
477+       // Other resources are loaded once only by the service worker. 
478+       'index.html' :  2 ,
479+       if  (shouldExpectFlutterJs)
480+         'flutter.js' :  1 ,
481+       'main.dart.js' :  1 ,
482+       'flutter_service_worker.js' :  1 ,
483+       'assets/FontManifest.json' :  1 ,
484+       'assets/AssetManifest.json' :  1 ,
485+       'assets/fonts/MaterialIcons-Regular.otf' :  1 ,
486+       // In headless mode Chrome does not load 'manifest.json' and 'favicon.ico'. 
487+       if  (! headless)
488+         ...< String , int > {
489+           'manifest.json' :  1 ,
490+           'favicon.ico' :  1 ,
491+         },
492+     });
493+ 
494+     print ('With cache: test first page reload' );
495+     await  server! .chrome.reloadPage ();
496+     await  waitForAppToLoad (< String , int > {
497+       'assets/fonts/MaterialIcons-Regular.otf' :  1 ,
498+       'flutter_service_worker.js' :  1 ,
499+     });
500+     expectRequestCounts (< String , int > {
501+       'assets/fonts/MaterialIcons-Regular.otf' :  1 ,
502+       'flutter_service_worker.js' :  1 ,
503+     });
504+ 
505+     print ('With cache: test second page reload' );
506+     await  server! .chrome.reloadPage ();
507+     await  waitForAppToLoad (< String , int > {
508+       'assets/fonts/MaterialIcons-Regular.otf' :  1 ,
509+       'flutter_service_worker.js' :  1 ,
510+     });
511+     expectRequestCounts (< String , int > {
512+       'assets/fonts/MaterialIcons-Regular.otf' :  1 ,
513+       'flutter_service_worker.js' :  1 ,
514+     });
515+ 
516+     print ('With cache: test third page reload' );
517+     await  server! .chrome.reloadPage ();
518+     await  waitForAppToLoad (< String , int > {
519+       'assets/fonts/MaterialIcons-Regular.otf' :  1 ,
520+       'flutter_service_worker.js' :  1 ,
521+     });
522+     expectRequestCounts (< String , int > {
523+       'assets/fonts/MaterialIcons-Regular.otf' :  1 ,
524+       'flutter_service_worker.js' :  1 ,
525+     });
526+ 
527+     print ('With cache: test page reload after rebuild' );
528+     await  _rebuildApp (version:  1 , testType:  testType, target:  _targetWithCachedResources);
529+ 
530+     // Since we're caching, we need to ignore cache when reloading the page. 
531+     await  server! .chrome.reloadPage (ignoreCache:  true );
532+     await  waitForAppToLoad (< String , int > {
533+       'assets/fonts/MaterialIcons-Regular.otf' :  1 ,
534+       'flutter_service_worker.js' :  1 ,
535+     });
536+     expectRequestCounts (< String , int > {
537+       'index.html' :  2 ,
538+       if  (shouldExpectFlutterJs)
539+         'flutter.js' :  1 ,
540+       'main.dart.js' :  1 ,
541+       'flutter_service_worker.js' :  2 ,
542+       'assets/FontManifest.json' :  1 ,
543+       'assets/AssetManifest.json' :  1 ,
544+       'assets/fonts/MaterialIcons-Regular.otf' :  1 ,
545+       // In headless mode Chrome does not load 'manifest.json' and 'favicon.ico'. 
546+       if  (! headless)
547+         ...< String , int > {
548+           'favicon.ico' :  1 ,
549+         },
550+     });
551+   } finally  {
552+     await  runCommand (
553+       'mv' ,
554+       < String > [
555+         'index_og.html' ,
556+         'index.html' ,
557+       ],
558+       workingDirectory:  _testAppWebDirectory,
559+     );
560+     await  server? .stop ();
561+   }
562+ 
563+   print ('END runWebServiceWorkerTestWithCachingResources(headless: $headless , testType: $testType )\n ' );
564+ }
0 commit comments