// 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); } }