| // 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:async'; | |
| import 'dart:io'; | |
| import 'package:path/path.dart' as path; | |
| import 'package:flutter_devicelab/framework/browser.dart'; | |
| import 'package:meta/meta.dart'; | |
| import 'package:shelf/shelf.dart'; | |
| import 'package:shelf_static/shelf_static.dart'; | |
| import 'package:shelf/shelf_io.dart' as shelf_io; | |
| 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'); | |
| // Run a web service worker test. The expectations are currently stored here | |
| // instead of in the application. This is not run on CI due to the requirement | |
| // of having a headful chrome instance. | |
| Future<void> main() async { | |
| await _runWebServiceWorkerTest('lib/service_worker_test.dart'); | |
| } | |
| Future<void> _runWebServiceWorkerTest(String target, { | |
| List<String> additionalArguments = const<String>[], | |
| }) async { | |
| final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web'); | |
| final String appBuildDirectory = path.join(testAppDirectory, 'build', 'web'); | |
| // Build the app. | |
| await Process.run( | |
| flutter, | |
| <String>[ 'clean' ], | |
| workingDirectory: testAppDirectory, | |
| ); | |
| await Process.run( | |
| flutter, | |
| <String>[ | |
| 'build', | |
| 'web', | |
| '--release', | |
| ...additionalArguments, | |
| '-t', | |
| target, | |
| ], | |
| workingDirectory: testAppDirectory, | |
| environment: <String, String>{ | |
| 'FLUTTER_WEB': 'true', | |
| }, | |
| ); | |
| final List<Uri> requests = <Uri>[]; | |
| final List<Map<String, String>> headers = <Map<String, String>>[]; | |
| await runRecordingServer( | |
| appUrl: 'http://localhost:8080/', | |
| appDirectory: appBuildDirectory, | |
| requests: requests, | |
| headers: headers, | |
| browserDebugPort: null, | |
| ); | |
| final List<String> requestedPaths = requests.map((Uri uri) => uri.toString()).toList(); | |
| final List<String> expectedPaths = <String>[ | |
| // Initial page load | |
| '', | |
| 'main.dart.js', | |
| 'assets/FontManifest.json', | |
| 'flutter_service_worker.js', | |
| 'manifest.json', | |
| 'favicon.ico', | |
| // Service worker install. | |
| 'main.dart.js', | |
| 'index.html', | |
| 'assets/LICENSE', | |
| 'assets/AssetManifest.json', | |
| 'assets/FontManifest.json', | |
| '', | |
| // Second page load all cached. | |
| ]; | |
| print('requests: $requestedPaths'); | |
| // The exact order isn't important or deterministic. | |
| for (final String path in requestedPaths) { | |
| if (!expectedPaths.remove(path)) { | |
| print('unexpected service worker request: $path'); | |
| exit(1); | |
| } | |
| } | |
| if (expectedPaths.isNotEmpty) { | |
| print('Missing service worker requests from expected paths: $expectedPaths'); | |
| exit(1); | |
| } | |
| } | |
| /// This server runs a release web application and verifies that the service worker | |
| /// caches files correctly, by checking the request resources over HTTP. | |
| /// | |
| /// When it receives a request for `CLOSE` the server will be torn down. | |
| /// | |
| /// Expects a path to the `build/web` directory produced from `flutter build web`. | |
| Future<void> runRecordingServer({ | |
| @required String appUrl, | |
| @required String appDirectory, | |
| @required List<Uri> requests, | |
| @required List<Map<String, String>> headers, | |
| int serverPort = 8080, | |
| int browserDebugPort = 8081, | |
| }) async { | |
| Chrome chrome; | |
| HttpServer server; | |
| final Completer<void> completer = Completer<void>(); | |
| Directory userDataDirectory; | |
| try { | |
| server = await HttpServer.bind('localhost', serverPort); | |
| final Cascade cascade = Cascade() | |
| .add((Request request) async { | |
| if (request.url.toString().contains('CLOSE')) { | |
| completer.complete(); | |
| return Response.notFound(''); | |
| } | |
| requests.add(request.url); | |
| headers.add(request.headers); | |
| return Response.notFound(''); | |
| }) | |
| .add(createStaticHandler(appDirectory, defaultDocument: 'index.html')); | |
| shelf_io.serveRequests(server, cascade.handler); | |
| userDataDirectory = Directory.systemTemp.createTempSync('chrome_user_data_'); | |
| chrome = await Chrome.launch(ChromeOptions( | |
| headless: false, | |
| debugPort: browserDebugPort, | |
| url: appUrl, | |
| userDataDirectory: userDataDirectory.path, | |
| windowHeight: 500, | |
| windowWidth: 500, | |
| ), onError: completer.completeError); | |
| await completer.future; | |
| } finally { | |
| chrome?.stop(); | |
| await server?.close(); | |
| userDataDirectory.deleteSync(recursive: true); | |
| } | |
| } |