| // 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'), |
| }) |
| |
| import 'package:dwds/config.dart'; |
| 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: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.testWithSoundNullSafety, 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 (var useSse in [true, false]) { |
| group(useSse ? 'SSE' : 'WebSockets', () { |
| group('Without encoding', () { |
| setUp(() async { |
| await context.setUp( |
| debugSettings: TestDebugSettings.withDevTools(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 (var 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.withDevTools(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.withDevTools(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 (var 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.noDevTools().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( |
| debugSettings: |
| TestDebugSettings.noDevTools().copyWith(enableDebugExtension: true), |
| appMetadata: AppMetadata(hostname: 'any'), |
| ); |
| }); |
| |
| 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'), |
| ), |
| ); |
| }); |
| }); |
| } |