| // 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. | |
| // When run locally this test may require a manifest key. This makes it easy to | |
| // just skip it. | |
| @Tags(['extension']) | |
| @Timeout(Duration(minutes: 2)) | |
| @OnPlatform({ | |
| 'windows': Skip('https://github.com/dart-lang/webdev/issues/711'), | |
| 'linux': Skip('https://github.com/dart-lang/webdev/issues/2114'), | |
| }) | |
| library; | |
| import 'package:dwds/src/connections/debug_connection.dart'; | |
| import 'package:dwds/src/handlers/injector.dart'; | |
| import 'package:http/http.dart' as http; | |
| import 'package:test/test.dart'; | |
| import 'package:dwds_test_common/test_sdk_configuration.dart'; | |
| // ignore: deprecated_member_use | |
| import 'package:webdriver/io.dart'; | |
| import 'fixtures/context.dart'; | |
| import 'fixtures/project.dart'; | |
| import 'fixtures/utilities.dart'; | |
| // Instructions for running: | |
| // * From the /dwds, run: dart test test/debug_extension_test.dart | |
| // * See note for Googlers below as well | |
| // [For Googlers] | |
| // A whitelisted developer key is needed to run these tests locally. | |
| // Add a developer key to dwds/debug_extension/build/web_prod/manifest.json. | |
| // Otherwise, you will get 'Error Loading Extension' alert. | |
| // Remove the key before pushing code to GitHub. | |
| // See go/extension-identification. | |
| void main() async { | |
| final provider = TestSdkConfigurationProvider(); | |
| tearDownAll(provider.dispose); | |
| final context = TestContext(TestProject.test, provider); | |
| Future<void> waitForDartDevToolsWithRetry({ | |
| int retryCount = 6, | |
| Duration retryWait = const Duration(seconds: 1), | |
| }) async { | |
| if (retryCount == 0) return; | |
| final windows = await context.webDriver.windows.toList(); | |
| await context.webDriver.driver.switchTo.window(windows.last); | |
| final title = await context.webDriver.title; | |
| if (title == 'Dart DevTools') return; | |
| await Future.delayed(retryWait); | |
| return waitForDartDevToolsWithRetry( | |
| retryCount: retryCount--, | |
| retryWait: retryWait, | |
| ); | |
| } | |
| for (final useSse in [true, false]) { | |
| group(useSse ? 'SSE' : 'WebSockets', () { | |
| group('Without encoding', () { | |
| setUp(() async { | |
| await context.setUp( | |
| debugSettings: TestDebugSettings.withDevToolsLaunch( | |
| context, | |
| ).copyWith(enableDebugExtension: true, useSse: useSse), | |
| ); | |
| await context.extensionConnection.sendCommand('Runtime.evaluate', { | |
| 'expression': 'fakeClick()', | |
| }); | |
| // Wait for DevTools to actually open. | |
| await waitForDartDevToolsWithRetry(); | |
| }); | |
| tearDown(() async { | |
| await context.tearDown(); | |
| }); | |
| test('can launch DevTools', () async { | |
| final windows = await context.webDriver.windows.toList(); | |
| await context.webDriver.driver.switchTo.window(windows.last); | |
| expect(await context.webDriver.title, contains('Dart DevTools')); | |
| expect( | |
| await context.webDriver.currentUrl, | |
| contains('ide=DebugExtension'), | |
| ); | |
| }); | |
| test('can close DevTools and relaunch', () async { | |
| for (final window in await context.webDriver.windows.toList()) { | |
| await context.webDriver.driver.switchTo.window(window); | |
| if (await context.webDriver.title == 'Dart DevTools') { | |
| await window.close(); | |
| break; | |
| } | |
| } | |
| // Relaunch DevTools by (fake) clicking the extension. | |
| await context.extensionConnection.sendCommand('Runtime.evaluate', { | |
| 'expression': 'fakeClick()', | |
| }); | |
| await waitForDartDevToolsWithRetry(); | |
| expect(await context.webDriver.title, 'Dart DevTools'); | |
| }); | |
| test('sends script parsed events', () async { | |
| // Check if the extension debugger receives Debugger.ScriptParsed | |
| // events for some important scripts. | |
| final service = fetchChromeProxyService(context.debugConnection); | |
| final scripts = service.remoteDebugger.scripts; | |
| expect( | |
| scripts.values.map((s) => s.url), | |
| containsAllInOrder([ | |
| contains('stack_trace_mapper.dart.js'), | |
| contains('hello_world/main.ddc.js'), | |
| contains('packages/path/path.ddc.js'), | |
| contains('dev_compiler/dart_sdk.js'), | |
| contains('dwds/src/injected/client.js'), | |
| ]), | |
| ); | |
| }); | |
| }); | |
| group('With a sharded Dart app', () { | |
| setUp(() async { | |
| await context.setUp( | |
| debugSettings: TestDebugSettings.withDevToolsLaunch( | |
| context, | |
| ).copyWith(enableDebugExtension: true, useSse: useSse), | |
| ); | |
| final htmlTag = await context.webDriver.findElement( | |
| const By.tagName('html'), | |
| ); | |
| await context.webDriver.execute( | |
| "arguments[0].setAttribute('data-multiple-dart-apps', 'true');", | |
| [htmlTag], | |
| ); | |
| }); | |
| tearDown(() async { | |
| await context.tearDown(); | |
| }); | |
| test('opens an alert', () async { | |
| await context.extensionConnection.sendCommand('Runtime.evaluate', { | |
| 'expression': 'fakeClick()', | |
| }); | |
| // Wait for the alert to open. | |
| final alert = await retryFn<Alert>( | |
| () => context.webDriver.switchTo.alert, | |
| ); | |
| expect(alert, isNotNull); | |
| }); | |
| }); | |
| // TODO(elliette): Figure out a way to verify that the Dart panel is added | |
| // to Chrome DevTools. This might not be possible to test with WebDriver, | |
| // because WebDriver doesn't allow you to interact with Chrome DevTools. | |
| group('With an internal Dart app', () { | |
| setUp(() async { | |
| await context.setUp( | |
| debugSettings: TestDebugSettings.withDevToolsLaunch( | |
| context, | |
| ).copyWith(enableDebugExtension: true, useSse: false), | |
| ); | |
| final htmlTag = await context.webDriver.findElement( | |
| const By.tagName('html'), | |
| ); | |
| await context.webDriver.execute( | |
| "arguments[0].setAttribute('data-ddr-dart-app', 'true');", | |
| [htmlTag], | |
| ); | |
| await context.extensionConnection.sendCommand('Runtime.evaluate', { | |
| 'expression': 'fakeClick()', | |
| }); | |
| // Wait for DevTools to actually open. | |
| await waitForDartDevToolsWithRetry(); | |
| }); | |
| tearDown(() async { | |
| await context.tearDown(); | |
| }); | |
| test('can launch DevTools', () async { | |
| final windows = await context.webDriver.windows.toList(); | |
| await context.webDriver.driver.switchTo.window(windows.last); | |
| expect(await context.webDriver.title, 'Dart DevTools'); | |
| }); | |
| test('can close DevTools and relaunch', () async { | |
| for (final window in await context.webDriver.windows.toList()) { | |
| await context.webDriver.driver.switchTo.window(window); | |
| if (await context.webDriver.title == 'Dart DevTools') { | |
| await window.close(); | |
| break; | |
| } | |
| } | |
| // Relaunch DevTools by (fake) clicking the extension. | |
| await context.extensionConnection.sendCommand('Runtime.evaluate', { | |
| 'expression': 'fakeClick()', | |
| }); | |
| await waitForDartDevToolsWithRetry(); | |
| expect(await context.webDriver.title, 'Dart DevTools'); | |
| }); | |
| test('sends script parsed events', () async { | |
| // Check if the extension debugger receives Debugger.ScriptParsed | |
| // events for some important scripts. | |
| final service = fetchChromeProxyService(context.debugConnection); | |
| final scripts = service.remoteDebugger.scripts; | |
| expect( | |
| scripts.values.map((s) => s.url), | |
| containsAllInOrder([ | |
| contains('stack_trace_mapper.dart.js'), | |
| contains('hello_world/main.ddc.js'), | |
| contains('packages/path/path.ddc.js'), | |
| contains('dev_compiler/dart_sdk.js'), | |
| contains('dwds/src/injected/client.js'), | |
| ]), | |
| ); | |
| }); | |
| }); | |
| }); | |
| } | |
| group('With encoding', () { | |
| setUp(() async { | |
| await context.setUp( | |
| debugSettings: TestDebugSettings.noDevToolsLaunch().copyWith( | |
| enableDebugExtension: true, | |
| urlEncoder: (url) async => | |
| url.endsWith(r'/$debug') ? 'http://some-encoded-url:8081/' : url, | |
| ), | |
| ); | |
| }); | |
| tearDown(() async { | |
| await context.tearDown(); | |
| }); | |
| test('uses the encoded URI', () async { | |
| final result = await http.get( | |
| Uri.parse( | |
| 'http://localhost:${context.port}/hello_world/main.dart$bootstrapJsExtension', | |
| ), | |
| ); | |
| expect(result.body.contains('dartExtensionUri'), isTrue); | |
| expect(result.body.contains('http://some-encoded-url:8081/'), isTrue); | |
| }); | |
| }); | |
| group('With "any" hostname', () { | |
| final uriPattern = RegExp(r'dartExtensionUri = "([^"]+)";'); | |
| setUp(() async { | |
| await context.setUp( | |
| appMetadata: TestAppMetadata.externalApp().copyWith(hostname: 'any'), | |
| debugSettings: TestDebugSettings.noDevToolsLaunch().copyWith( | |
| enableDebugExtension: true, | |
| ), | |
| ); | |
| }); | |
| tearDown(() async { | |
| await context.tearDown(); | |
| }); | |
| test('generates an extensionUri with a valid valid hostname', () async { | |
| final result = await http.get( | |
| Uri.parse( | |
| 'http://localhost:${context.port}/hello_world/main.dart$bootstrapJsExtension', | |
| ), | |
| ); | |
| expect(result.body.contains('dartExtensionUri'), isTrue); | |
| final extensionUri = Uri.parse( | |
| uriPattern.firstMatch(result.body)!.group(1)!, | |
| ); | |
| expect( | |
| extensionUri.host, | |
| anyOf( | |
| // The hostname should've been mapped from "any" to one of the local | |
| // loopback addresses/IPs. | |
| equals('localhost'), | |
| equals('127.0.0.1'), | |
| equals('::'), | |
| equals('::1'), | |
| ), | |
| ); | |
| }); | |
| }); | |
| } |