// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. @OnPlatform({'windows': Skip('appveyor is not setup to install Chrome')}) library; import 'dart:async'; import 'dart:io'; import 'package:browser_launcher/src/chrome.dart'; import 'package:logging/logging.dart'; import 'package:test/test.dart'; import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; const _headlessOnlyEnvironment = 'HEADLESS_ONLY'; bool get headlessOnlyEnvironment => Platform.environment[_headlessOnlyEnvironment] == 'true'; void _configureLogging(bool verbose) { Logger.root.level = verbose ? Level.ALL : Level.INFO; Logger.root.onRecord.listen((record) { print('${record.level.name}: ${record.time}: ${record.message}'); }); } void main() { Chrome? chrome; // Pass 'true' for debugging. _configureLogging(false); Future getTab(String url) => chrome!.chromeConnection.getTab( (t) => t.url.contains(url), retryFor: const Duration(seconds: 5), ); Future?> getTabs() => chrome!.chromeConnection.getTabs( retryFor: const Duration(seconds: 5), ); Future connectToTab(String url) async { final tab = await getTab(url); expect(tab, isNotNull); return tab!.connect(); } Future openTab(String url) => chrome!.chromeConnection.getUrl(_openTabUrl(url)); Future launchChromeWithDebugPort({ int port = 0, String? userDataDir, bool signIn = false, bool headless = false, }) async { chrome = await Chrome.startWithDebugPort( [_googleUrl], debugPort: port, userDataDir: userDataDir, signIn: signIn, headless: headless, ); } Future launchChrome({bool headless = false}) async { await Chrome.start([_googleUrl], args: [if (headless) '--headless']); } final headlessModes = [ true, if (!headlessOnlyEnvironment) false, ]; for (var headless in headlessModes) { group('(headless: $headless)', () { group('chrome with temp data dir', () { tearDown(() async { await chrome?.close(); chrome = null; }); test('can launch chrome', () async { await launchChrome(headless: headless); expect(chrome, isNull); }); test('can launch chrome with debug port', () async { await launchChromeWithDebugPort(headless: headless); expect(chrome, isNotNull); }); test('has a working debugger', () async { await launchChromeWithDebugPort(headless: headless); final tabs = await getTabs(); expect( tabs, contains( const TypeMatcher() .having((t) => t.url, 'url', _googleUrl), ), ); }); test('uses open debug port if provided port is 0', () async { await launchChromeWithDebugPort(headless: headless); expect(chrome!.debugPort, isNot(equals(0))); }); test('can provide a specific debug port', () async { final port = await findUnusedPort(); await launchChromeWithDebugPort(port: port, headless: headless); expect(chrome!.debugPort, port); }); }); group('chrome with user data dir', () { late Directory dataDir; const waitMilliseconds = Duration(milliseconds: 100); for (var signIn in [false, true]) { group('and signIn = $signIn', () { setUp(() { dataDir = Directory.systemTemp.createTempSync(_userDataDirName); }); tearDown(() async { await chrome?.close(); chrome = null; var attempts = 0; while (true) { try { attempts++; await Future.delayed(waitMilliseconds); dataDir.deleteSync(recursive: true); break; } catch (_) { if (attempts > 3) rethrow; } } }); test('can launch with debug port', () async { await launchChromeWithDebugPort( userDataDir: dataDir.path, signIn: signIn, headless: headless, ); expect(chrome, isNotNull); }); test('has a working debugger', () async { await launchChromeWithDebugPort( userDataDir: dataDir.path, signIn: signIn, headless: headless, ); final tabs = await getTabs(); expect( tabs, contains( const TypeMatcher() .having((t) => t.url, 'url', _googleUrl), ), ); }); test( 'has correct profile path', () async { await launchChromeWithDebugPort( userDataDir: dataDir.path, signIn: signIn, headless: headless, ); await openTab(_chromeVersionUrl); final wipConnection = await connectToTab(_chromeVersionUrl); await wipConnection.debugger.enable(); await wipConnection.runtime.enable(); final result = await _evaluate( wipConnection.page, "document.getElementById('profile_path').textContent", ); expect(result, contains(_userDataDirName)); }, // Note: When re-enabling, skip for headless mode because headless // mode does not allow chrome: urls. skip: 'https://github.com/dart-lang/sdk/issues/52357', ); }); } }); }); } } String _openTabUrl(String url) => '/json/new?$url'; Future _evaluate(WipPage page, String expression) async { String? result; const stopInSeconds = Duration(seconds: 5); const waitMilliseconds = Duration(milliseconds: 100); final stopTime = DateTime.now().add(stopInSeconds); while (result == null && DateTime.now().isBefore(stopTime)) { await Future.delayed(waitMilliseconds); try { final wipResponse = await page.sendCommand( 'Runtime.evaluate', params: {'expression': expression}, ); final response = wipResponse.json['result'] as Map; final value = (response['result'] as Map)['value']; result = value?.toString(); } catch (_) { return null; } } return result; } const _googleUrl = 'https://www.google.com/'; const _chromeVersionUrl = 'chrome://version/'; const _userDataDirName = 'data dir';