Unverified Commit 31209d04 authored by Jackson Gardner's avatar Jackson Gardner Committed by GitHub

`flutter test --wasm` support (#145347)

* Adds support for `flutter test --wasm`.
  * The test compilation flow is a bit different now, so that it supports compilers other than DDC. Specifically, when we run a set of unit tests, we generate a "switchboard" main function that imports each unit test and runs the main function for a specific one based off of a value set by the JS bootstrapping code. This way, there is one compile step and the same compile output is invoked for each unit test file.
* Also, removes all references to `dart:html` from flutter/flutter.
* Adds CI steps for running the framework unit tests with dart2wasm+skwasm
  * These steps are marked as `bringup: true`, so we don't know what kind of failures they will result in. Any failures they have will not block the tree at all yet while we're still in `bringup: true`. Once this PR is merged, I plan on looking at any failures and either fixing them or disabling them so we can get these CI steps running on presubmit.

This fixes https://github.com/flutter/flutter/issues/126692
parent 98d10b62
...@@ -1725,6 +1725,190 @@ targets: ...@@ -1725,6 +1725,190 @@ targets:
- bin/** - bin/**
- .ci.yaml - .ci.yaml
- name: Linux web_skwasm_tests_0
bringup: true
recipe: flutter/flutter_drone
timeout: 60
properties:
dependencies: >-
[
{"dependency": "android_sdk", "version": "version:34v3"},
{"dependency": "chrome_and_driver", "version": "version:119.0.6045.9"},
{"dependency": "goldctl", "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd"}
]
shard: web_skwasm_tests
subshard: "0"
tags: >
["framework", "hostonly", "shard", "linux"]
# Retry for flakes caused by https://github.com/flutter/flutter/issues/132654
presubmit_max_attempts: "2"
runIf:
- dev/**
- packages/**
- bin/**
- .ci.yaml
- name: Linux web_skwasm_tests_1
bringup: true
recipe: flutter/flutter_drone
timeout: 60
properties:
dependencies: >-
[
{"dependency": "android_sdk", "version": "version:34v3"},
{"dependency": "chrome_and_driver", "version": "version:119.0.6045.9"},
{"dependency": "goldctl", "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd"}
]
shard: web_skwasm_tests
subshard: "1"
tags: >
["framework", "hostonly", "shard", "linux"]
# Retry for flakes caused by https://github.com/flutter/flutter/issues/132654
presubmit_max_attempts: "2"
runIf:
- dev/**
- packages/**
- bin/**
- .ci.yaml
- name: Linux web_skwasm_tests_2
bringup: true
recipe: flutter/flutter_drone
timeout: 60
properties:
dependencies: >-
[
{"dependency": "android_sdk", "version": "version:34v3"},
{"dependency": "chrome_and_driver", "version": "version:119.0.6045.9"},
{"dependency": "goldctl", "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd"}
]
shard: web_skwasm_tests
subshard: "2"
tags: >
["framework", "hostonly", "shard", "linux"]
# Retry for flakes caused by https://github.com/flutter/flutter/issues/132654
presubmit_max_attempts: "2"
runIf:
- dev/**
- packages/**
- bin/**
- .ci.yaml
- name: Linux web_skwasm_tests_3
bringup: true
recipe: flutter/flutter_drone
timeout: 60
properties:
dependencies: >-
[
{"dependency": "android_sdk", "version": "version:34v3"},
{"dependency": "chrome_and_driver", "version": "version:119.0.6045.9"},
{"dependency": "goldctl", "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd"}
]
shard: web_skwasm_tests
subshard: "3"
tags: >
["framework", "hostonly", "shard", "linux"]
# Retry for flakes caused by https://github.com/flutter/flutter/issues/132654
presubmit_max_attempts: "2"
runIf:
- dev/**
- packages/**
- bin/**
- .ci.yaml
- name: Linux web_skwasm_tests_4
bringup: true
recipe: flutter/flutter_drone
timeout: 60
properties:
dependencies: >-
[
{"dependency": "android_sdk", "version": "version:34v3"},
{"dependency": "chrome_and_driver", "version": "version:119.0.6045.9"},
{"dependency": "goldctl", "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd"}
]
shard: web_skwasm_tests
subshard: "4"
tags: >
["framework", "hostonly", "shard", "linux"]
# Retry for flakes caused by https://github.com/flutter/flutter/issues/132654
presubmit_max_attempts: "2"
runIf:
- dev/**
- packages/**
- bin/**
- .ci.yaml
- name: Linux web_skwasm_tests_5
bringup: true
recipe: flutter/flutter_drone
timeout: 60
properties:
dependencies: >-
[
{"dependency": "android_sdk", "version": "version:34v3"},
{"dependency": "chrome_and_driver", "version": "version:119.0.6045.9"},
{"dependency": "goldctl", "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd"}
]
shard: web_skwasm_tests
subshard: "5"
tags: >
["framework", "hostonly", "shard", "linux"]
# Retry for flakes caused by https://github.com/flutter/flutter/issues/132654
presubmit_max_attempts: "2"
runIf:
- dev/**
- packages/**
- bin/**
- .ci.yaml
- name: Linux web_skwasm_tests_6
bringup: true
recipe: flutter/flutter_drone
timeout: 60
properties:
dependencies: >-
[
{"dependency": "android_sdk", "version": "version:34v3"},
{"dependency": "chrome_and_driver", "version": "version:119.0.6045.9"},
{"dependency": "goldctl", "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd"}
]
shard: web_skwasm_tests
subshard: "6"
tags: >
["framework", "hostonly", "shard", "linux"]
# Retry for flakes caused by https://github.com/flutter/flutter/issues/132654
presubmit_max_attempts: "2"
runIf:
- dev/**
- packages/**
- bin/**
- .ci.yaml
- name: Linux web_skwasm_tests_7_last
bringup: true
recipe: flutter/flutter_drone
timeout: 60
properties:
dependencies: >-
[
{"dependency": "android_sdk", "version": "version:34v3"},
{"dependency": "chrome_and_driver", "version": "version:119.0.6045.9"},
{"dependency": "goldctl", "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd"}
]
shard: web_skwasm_tests
subshard: "7_last"
tags: >
["framework", "hostonly", "shard", "linux"]
# Retry for flakes caused by https://github.com/flutter/flutter/issues/132654
presubmit_max_attempts: "2"
runIf:
- dev/**
- packages/**
- bin/**
- .ci.yaml
- name: Linux web_tool_tests - name: Linux web_tool_tests
recipe: flutter/flutter_drone recipe: flutter/flutter_drone
timeout: 60 timeout: 60
......
...@@ -286,7 +286,7 @@ ...@@ -286,7 +286,7 @@
/dev/devicelab/bin/tasks/technical_debt__cost.dart @HansMuller @flutter/framework /dev/devicelab/bin/tasks/technical_debt__cost.dart @HansMuller @flutter/framework
/dev/devicelab/bin/tasks/web_benchmarks_canvaskit.dart @yjbanov @flutter/web /dev/devicelab/bin/tasks/web_benchmarks_canvaskit.dart @yjbanov @flutter/web
/dev/devicelab/bin/tasks/web_benchmarks_html.dart @yjbanov @flutter/web /dev/devicelab/bin/tasks/web_benchmarks_html.dart @yjbanov @flutter/web
/dev/devicelab/bin/tasks/web_benchmarks_skwasm.dart @jacksongardner @flutter/web /dev/devicelab/bin/tasks/web_benchmarks_skwasm.dart @eyebrowsoffire @flutter/web
/dev/devicelab/bin/tasks/windows_home_scroll_perf__timeline_summary.dart @jonahwilliams @flutter/engine /dev/devicelab/bin/tasks/windows_home_scroll_perf__timeline_summary.dart @jonahwilliams @flutter/engine
/dev/devicelab/bin/tasks/windows_startup_test.dart @loic-sharma @flutter/desktop /dev/devicelab/bin/tasks/windows_startup_test.dart @loic-sharma @flutter/desktop
...@@ -325,7 +325,7 @@ ...@@ -325,7 +325,7 @@
# flutter_plugins @stuartmorgan @flutter/plugin # flutter_plugins @stuartmorgan @flutter/plugin
# framework_tests @HansMuller @flutter/framework # framework_tests @HansMuller @flutter/framework
# fuchsia_precache @christopherfujino @flutter/tool # fuchsia_precache @christopherfujino @flutter/tool
# realm_checker @jacksongardner @flutter/tool # realm_checker @eyebrowsoffire @flutter/tool
# skp_generator @Hixie # skp_generator @Hixie
# test_ownership @keyonghan # test_ownership @keyonghan
# tool_host_cross_arch_tests @andrewkolos @flutter/tool # tool_host_cross_arch_tests @andrewkolos @flutter/tool
...@@ -336,4 +336,5 @@ ...@@ -336,4 +336,5 @@
# web_integration_tests @yjbanov @flutter/web # web_integration_tests @yjbanov @flutter/web
# web_long_running_tests @yjbanov @flutter/web # web_long_running_tests @yjbanov @flutter/web
# web_tests @yjbanov @flutter/web # web_tests @yjbanov @flutter/web
# web_skwasm_tests @eyebrowsoffire @flutter/web
# web_tool_tests @eliasyishak @flutter/tool # web_tool_tests @eliasyishak @flutter/tool
...@@ -242,6 +242,8 @@ Future<void> main(List<String> args) async { ...@@ -242,6 +242,8 @@ Future<void> main(List<String> args) async {
'web_tests': _runWebHtmlUnitTests, 'web_tests': _runWebHtmlUnitTests,
// All the unit/widget tests run using `flutter test --platform=chrome --web-renderer=canvaskit` // All the unit/widget tests run using `flutter test --platform=chrome --web-renderer=canvaskit`
'web_canvaskit_tests': _runWebCanvasKitUnitTests, 'web_canvaskit_tests': _runWebCanvasKitUnitTests,
// All the unit/widget tests run using `flutter test --platform=chrome --wasm --web-renderer=skwasm`
'web_skwasm_tests': _runWebSkwasmUnitTests,
// All web integration tests // All web integration tests
'web_long_running_tests': _runWebLongRunningTests, 'web_long_running_tests': _runWebLongRunningTests,
'flutter_plugins': _runFlutterPackagesTests, 'flutter_plugins': _runFlutterPackagesTests,
...@@ -1117,14 +1119,18 @@ Future<void> _runFrameworkCoverage() async { ...@@ -1117,14 +1119,18 @@ Future<void> _runFrameworkCoverage() async {
} }
Future<void> _runWebHtmlUnitTests() { Future<void> _runWebHtmlUnitTests() {
return _runWebUnitTests('html'); return _runWebUnitTests('html', false);
} }
Future<void> _runWebCanvasKitUnitTests() { Future<void> _runWebCanvasKitUnitTests() {
return _runWebUnitTests('canvaskit'); return _runWebUnitTests('canvaskit', false);
} }
Future<void> _runWebUnitTests(String webRenderer) async { Future<void> _runWebSkwasmUnitTests() {
return _runWebUnitTests('skwasm', true);
}
Future<void> _runWebUnitTests(String webRenderer, bool useWasm) async {
final Map<String, ShardRunner> subshards = <String, ShardRunner>{}; final Map<String, ShardRunner> subshards = <String, ShardRunner>{};
final Directory flutterPackageDirectory = Directory(path.join(flutterRoot, 'packages', 'flutter')); final Directory flutterPackageDirectory = Directory(path.join(flutterRoot, 'packages', 'flutter'));
...@@ -1160,6 +1166,7 @@ Future<void> _runWebUnitTests(String webRenderer) async { ...@@ -1160,6 +1166,7 @@ Future<void> _runWebUnitTests(String webRenderer) async {
index * testsPerShard, index * testsPerShard,
(index + 1) * testsPerShard, (index + 1) * testsPerShard,
), ),
useWasm,
); );
} }
...@@ -1175,16 +1182,19 @@ Future<void> _runWebUnitTests(String webRenderer) async { ...@@ -1175,16 +1182,19 @@ Future<void> _runWebUnitTests(String webRenderer) async {
(webShardCount - 1) * testsPerShard, (webShardCount - 1) * testsPerShard,
allTests.length, allTests.length,
), ),
useWasm,
); );
await _runFlutterWebTest( await _runFlutterWebTest(
webRenderer, webRenderer,
path.join(flutterRoot, 'packages', 'flutter_web_plugins'), path.join(flutterRoot, 'packages', 'flutter_web_plugins'),
<String>['test'], <String>['test'],
useWasm,
); );
await _runFlutterWebTest( await _runFlutterWebTest(
webRenderer, webRenderer,
path.join(flutterRoot, 'packages', 'flutter_driver'), path.join(flutterRoot, 'packages', 'flutter_driver'),
<String>[path.join('test', 'src', 'web_tests', 'web_extension_test.dart')], <String>[path.join('test', 'src', 'web_tests', 'web_extension_test.dart')],
useWasm,
); );
}; };
...@@ -1333,11 +1343,19 @@ Future<void> _runWebLongRunningTests() async { ...@@ -1333,11 +1343,19 @@ Future<void> _runWebLongRunningTests() async {
'html', 'html',
path.join(flutterRoot, 'packages', 'integration_test'), path.join(flutterRoot, 'packages', 'integration_test'),
<String>['test/web_extension_test.dart'], <String>['test/web_extension_test.dart'],
false,
), ),
() => _runFlutterWebTest( () => _runFlutterWebTest(
'canvaskit', 'canvaskit',
path.join(flutterRoot, 'packages', 'integration_test'), path.join(flutterRoot, 'packages', 'integration_test'),
<String>['test/web_extension_test.dart'], <String>['test/web_extension_test.dart'],
false,
),
() => _runFlutterWebTest(
'skwasm',
path.join(flutterRoot, 'packages', 'integration_test'),
<String>['test/web_extension_test.dart'],
true,
), ),
]; ];
...@@ -2302,13 +2320,19 @@ Future<void> _runWebDebugTest(String target, { ...@@ -2302,13 +2320,19 @@ Future<void> _runWebDebugTest(String target, {
} }
} }
Future<void> _runFlutterWebTest(String webRenderer, String workingDirectory, List<String> tests) async { Future<void> _runFlutterWebTest(
String webRenderer,
String workingDirectory,
List<String> tests,
bool useWasm,
) async {
await runCommand( await runCommand(
flutter, flutter,
<String>[ <String>[
'test', 'test',
'-v', '-v',
'--platform=chrome', '--platform=chrome',
if (useWasm) '--wasm',
'--web-renderer=$webRenderer', '--web-renderer=$webRenderer',
'--dart-define=DART_HHH_BOT=$_runningInDartHHHBot', '--dart-define=DART_HHH_BOT=$_runningInDartHHHBot',
...flutterTestArgs, ...flutterTestArgs,
......
...@@ -2,11 +2,12 @@ ...@@ -2,11 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:html' as html; import 'dart:js_interop';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:meta/dart2js.dart'; import 'package:meta/dart2js.dart';
import 'package:web/web.dart' as web;
// Tests that the framework prints stack traces in all build modes. // Tests that the framework prints stack traces in all build modes.
// //
...@@ -35,10 +36,12 @@ Future<void> main() async { ...@@ -35,10 +36,12 @@ Future<void> main() async {
} }
print(output); print(output);
html.HttpRequest.request( web.window.fetch(
'/test-result', '/test-result'.toJS,
method: 'POST', web.RequestInit(
sendData: '$output', method: 'POST',
body: '$output'.toJS,
)
); );
} }
......
...@@ -2,10 +2,13 @@ ...@@ -2,10 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:html' as html; import 'dart:js_interop';
import 'package:web/web.dart' as web;
Future<void> main() async { Future<void> main() async {
await html.window.navigator.serviceWorker?.ready; await web.window.navigator.serviceWorker.ready.toDart;
const String response = 'CLOSE?version=1'; final JSString response = 'CLOSE?version=1'.toJS;
await html.HttpRequest.getString(response); await web.window.fetch(response).toDart;
html.document.body?.appendHtml(response); web.document.body?.append(response);
} }
...@@ -2,9 +2,12 @@ ...@@ -2,9 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:html' as html; import 'dart:js_interop';
import 'package:web/web.dart' as web;
Future<void> main() async { Future<void> main() async {
const String response = 'CLOSE?version=1'; final JSString response = 'CLOSE?version=1'.toJS;
await html.HttpRequest.getString(response); await web.window.fetch(response).toDart;
html.document.body?.appendHtml(response); web.document.body?.append(response);
} }
...@@ -4,7 +4,9 @@ ...@@ -4,7 +4,9 @@
// @dart = 2.12 // @dart = 2.12
import 'dart:html' as html; import 'dart:js_interop';
import 'package:web/web.dart' as web;
// Verify that web applications can be run in sound mode. // Verify that web applications can be run in sound mode.
void main() { void main() {
...@@ -16,9 +18,11 @@ void main() { ...@@ -16,9 +18,11 @@ void main() {
output = '--- TEST SUCCEEDED ---'; output = '--- TEST SUCCEEDED ---';
} }
print(output); print(output);
html.HttpRequest.request( web.window.fetch(
'/test-result', '/test-result'.toJS,
method: 'POST', web.RequestInit(
sendData: output, method: 'POST',
body: output.toJS,
)
); );
} }
...@@ -2,11 +2,12 @@ ...@@ -2,11 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:html' as html; import 'dart:js_interop';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:meta/dart2js.dart'; import 'package:meta/dart2js.dart';
import 'package:web/web.dart' as web;
/// Expected sequence of method calls. /// Expected sequence of method calls.
const List<String> callChain = <String>['baz', 'bar', 'foo']; const List<String> callChain = <String>['baz', 'bar', 'foo'];
...@@ -32,7 +33,7 @@ const List<StackFrame> expectedDebugStackFrames = <StackFrame>[ ...@@ -32,7 +33,7 @@ const List<StackFrame> expectedDebugStackFrames = <StackFrame>[
packageScheme: 'package', packageScheme: 'package',
package: 'packages', package: 'packages',
packagePath: 'web_integration/stack_trace.dart', packagePath: 'web_integration/stack_trace.dart',
line: 119, line: 122,
column: 3, column: 3,
className: '<unknown>', className: '<unknown>',
method: 'baz', method: 'baz',
...@@ -43,7 +44,7 @@ const List<StackFrame> expectedDebugStackFrames = <StackFrame>[ ...@@ -43,7 +44,7 @@ const List<StackFrame> expectedDebugStackFrames = <StackFrame>[
packageScheme: 'package', packageScheme: 'package',
package: 'packages', package: 'packages',
packagePath: 'web_integration/stack_trace.dart', packagePath: 'web_integration/stack_trace.dart',
line: 114, line: 117,
column: 3, column: 3,
className: '<unknown>', className: '<unknown>',
method: 'bar', method: 'bar',
...@@ -54,7 +55,7 @@ const List<StackFrame> expectedDebugStackFrames = <StackFrame>[ ...@@ -54,7 +55,7 @@ const List<StackFrame> expectedDebugStackFrames = <StackFrame>[
packageScheme: 'package', packageScheme: 'package',
package: 'packages', package: 'packages',
packagePath: 'web_integration/stack_trace.dart', packagePath: 'web_integration/stack_trace.dart',
line: 109, line: 112,
column: 3, column: 3,
className: '<unknown>', className: '<unknown>',
method: 'foo', method: 'foo',
...@@ -97,10 +98,12 @@ void main() { ...@@ -97,10 +98,12 @@ void main() {
output.writeln('--- TEST FAILED ---'); output.writeln('--- TEST FAILED ---');
} }
print(output); print(output);
html.HttpRequest.request( web.window.fetch(
'/test-result', '/test-result'.toJS,
method: 'POST', web.RequestInit(
sendData: '$output', method: 'POST',
body: '$output'.toJS,
)
); );
} }
......
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:html' as html; import 'dart:js_interop';
import 'package:web/web.dart' as web;
Future<void> main() async { Future<void> main() async {
final StringBuffer output = StringBuffer(); final StringBuffer output = StringBuffer();
...@@ -16,9 +18,11 @@ Future<void> main() async { ...@@ -16,9 +18,11 @@ Future<void> main() async {
print('--- TEST FAILED ---'); print('--- TEST FAILED ---');
} }
html.HttpRequest.request( web.window.fetch(
'/test-result', '/test-result'.toJS,
method: 'POST', web.RequestInit(
sendData: '$output', method: 'POST',
body: '$output'.toJS,
)
); );
} }
...@@ -2,16 +2,20 @@ ...@@ -2,16 +2,20 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:html' as html; import 'dart:js_interop';
import 'package:web/web.dart' as web;
// Attempt to load a file that is hosted in the applications's `web/` directory. // Attempt to load a file that is hosted in the applications's `web/` directory.
Future<void> main() async { Future<void> main() async {
try { try {
final html.HttpRequest request = await html.HttpRequest.request( final web.Response response = await web.window.fetch(
'/example', '/example'.toJS,
method: 'GET', web.RequestInit(
); method: 'GET',
final String? body = request.responseText; ),
).toDart;
final String body = (await response.text().toDart).toDart;
if (body == 'This is an Example') { if (body == 'This is an Example') {
print('--- TEST SUCCEEDED ---'); print('--- TEST SUCCEEDED ---');
} else { } else {
......
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:html' as html; import 'dart:js_interop';
import 'package:web/web.dart' as web;
// Attempt to load CanvasKit resources hosted on gstatic. // Attempt to load CanvasKit resources hosted on gstatic.
Future<void> main() async { Future<void> main() async {
...@@ -12,12 +14,13 @@ Future<void> main() async { ...@@ -12,12 +14,13 @@ Future<void> main() async {
return; return;
} }
try { try {
final html.HttpRequest request = await html.HttpRequest.request( final web.Response response = await web.window.fetch(
'https://www.gstatic.com/flutter-canvaskit/$engineVersion/canvaskit.js', 'https://www.gstatic.com/flutter-canvaskit/$engineVersion/canvaskit.js'.toJS,
method: 'GET', web.RequestInit(
); method: 'GET',
final dynamic response = request.response; ),
if (response != null) { ).toDart;
if (response.ok) {
print('--- TEST SUCCEEDED ---'); print('--- TEST SUCCEEDED ---');
} else { } else {
print('--- TEST FAILED ---'); print('--- TEST FAILED ---');
...@@ -27,12 +30,13 @@ Future<void> main() async { ...@@ -27,12 +30,13 @@ Future<void> main() async {
print('--- TEST FAILED ---'); print('--- TEST FAILED ---');
} }
try { try {
final html.HttpRequest request = await html.HttpRequest.request( final web.Response response = await web.window.fetch(
'https://www.gstatic.com/flutter-canvaskit/$engineVersion/canvaskit.wasm', 'https://www.gstatic.com/flutter-canvaskit/$engineVersion/canvaskit.wasm'.toJS,
method: 'GET', web.RequestInit(
); method: 'GET',
final dynamic response = request.response; )
if (response != null) { ).toDart;
if (response.ok) {
print('--- TEST SUCCEEDED ---'); print('--- TEST SUCCEEDED ---');
} else { } else {
print('--- TEST FAILED ---'); print('--- TEST FAILED ---');
......
...@@ -14,10 +14,12 @@ dependencies: ...@@ -14,10 +14,12 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
web: 0.5.1
characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
collection: 1.18.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
material_color_utilities: 0.8.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.8.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
meta: 1.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
# PUBSPEC CHECKSUM: 6f95 # PUBSPEC CHECKSUM: 8d22
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
import 'package:web_integration/a.dart' import 'package:web_integration/a.dart'
if (dart.library.io) 'package:web_integration/b.dart' as message1; if (dart.library.io) 'package:web_integration/b.dart' as message1;
import 'package:web_integration/c.dart' import 'package:web_integration/c.dart'
if (dart.library.html) 'package:web_integration/d.dart' as message2; if (dart.library.js_interop) 'package:web_integration/d.dart' as message2;
void main() { void main() {
if (message1.message == 'a' && message2.message == 'd') { if (message1.message == 'a' && message2.message == 'd') {
......
...@@ -2,12 +2,11 @@ ...@@ -2,12 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:html';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:web/web.dart' as web;
/// Whether the current browser is Firefox. /// Whether the current browser is Firefox.
bool get isFirefox => window.navigator.userAgent.toLowerCase().contains('firefox'); bool get isFirefox => web.window.navigator.userAgent.toLowerCase().contains('firefox');
/// Finds elements in the DOM tree rendered by the Flutter Web engine. /// Finds elements in the DOM tree rendered by the Flutter Web engine.
/// ///
...@@ -15,8 +14,8 @@ bool get isFirefox => window.navigator.userAgent.toLowerCase().contains('firefox ...@@ -15,8 +14,8 @@ bool get isFirefox => window.navigator.userAgent.toLowerCase().contains('firefox
/// `<flt-glass-pane>` element. Otherwise, looks under `<flt-glass-pane>` /// `<flt-glass-pane>` element. Otherwise, looks under `<flt-glass-pane>`
/// without penetrating the shadow DOM. In the latter case, if the application /// without penetrating the shadow DOM. In the latter case, if the application
/// creates platform views, this will also find platform view elements. /// creates platform views, this will also find platform view elements.
List<Node> findElements(String selector) { web.NodeList findElements(String selector) {
final Element? flutterView = document.querySelector('flutter-view'); final web.Element? flutterView = web.document.querySelector('flutter-view');
if (flutterView == null) { if (flutterView == null) {
fail( fail(
......
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:html' as html;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:web/web.dart' as web;
void main() => runApp(const MyApp()); void main() => runApp(const MyApp());
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
...@@ -79,29 +79,25 @@ abstract class DeltaMode { ...@@ -79,29 +79,25 @@ abstract class DeltaMode {
void dispatchMouseWheelEvent(int mouseX, int mouseY, void dispatchMouseWheelEvent(int mouseX, int mouseY,
int deltaMode, double deltaX, double deltaY) { int deltaMode, double deltaX, double deltaY) {
final html.EventTarget target = html.document.elementFromPoint(mouseX, mouseY)!; final web.EventTarget target = web.document.elementFromPoint(mouseX, mouseY)!;
target.dispatchEvent(html.MouseEvent('mouseover', target.dispatchEvent(web.MouseEvent('mouseover', web.MouseEventInit(
screenX: mouseX, screenX: mouseX,
screenY: mouseY, screenY: mouseY,
clientX: mouseX, clientX: mouseX,
clientY: mouseY, clientY: mouseY,
)); )));
target.dispatchEvent(html.MouseEvent('mousemove', target.dispatchEvent(web.MouseEvent('mousemove', web.MouseEventInit(
screenX: mouseX, screenX: mouseX,
screenY: mouseY, screenY: mouseY,
clientX: mouseX, clientX: mouseX,
clientY: mouseY, clientY: mouseY,
)); )));
target.dispatchEvent(html.WheelEvent('wheel', target.dispatchEvent(web.WheelEvent('wheel', web.WheelEventInit(
screenX: mouseX,
screenY: mouseY,
clientX: mouseX,
clientY: mouseY,
deltaMode: deltaMode, deltaMode: deltaMode,
deltaX : deltaX, deltaX : deltaX,
deltaY : deltaY, deltaY : deltaY,
)); )));
} }
...@@ -21,6 +21,7 @@ dependencies: ...@@ -21,6 +21,7 @@ dependencies:
integration_test: integration_test:
sdk: flutter sdk: flutter
flutter_gallery_assets: 1.0.2 flutter_gallery_assets: 1.0.2
web: 0.5.1
async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
...@@ -82,7 +83,6 @@ dev_dependencies: ...@@ -82,7 +83,6 @@ dev_dependencies:
test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
web_socket_channel: 2.4.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
webkit_inspection_protocol: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
......
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:html' as html;
import 'dart:ui_web' as ui_web; import 'dart:ui_web' as ui_web;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'package:web/web.dart' as web;
import 'package:web_e2e_tests/platform_messages_main.dart' as app; import 'package:web_e2e_tests/platform_messages_main.dart' as app;
void main() { void main() {
...@@ -27,7 +27,7 @@ void main() { ...@@ -27,7 +27,7 @@ void main() {
await tester.tap(find.byKey(const Key('input'))); await tester.tap(find.byKey(const Key('input')));
// Focus in input, otherwise clipboard will fail with // Focus in input, otherwise clipboard will fail with
// 'document is not focused' platform exception. // 'document is not focused' platform exception.
html.document.querySelector('input')?.focus(); (web.document.querySelector('input') as web.HTMLElement?)?.focus();
await Clipboard.setData(const ClipboardData(text: 'sample text')); await Clipboard.setData(const ClipboardData(text: 'sample text'));
}, skip: true); // https://github.com/flutter/flutter/issues/54296 }, skip: true); // https://github.com/flutter/flutter/issues/54296
...@@ -38,7 +38,7 @@ void main() { ...@@ -38,7 +38,7 @@ void main() {
platformViewsRegistry.getNextPlatformViewId(); platformViewsRegistry.getNextPlatformViewId();
ui_web.platformViewRegistry.registerViewFactory('MyView', (int viewId) { ui_web.platformViewRegistry.registerViewFactory('MyView', (int viewId) {
viewInstanceCount += 1; viewInstanceCount += 1;
return html.DivElement(); return web.HTMLDivElement();
}); });
app.main(); app.main();
......
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:html';
import 'dart:js_util' as js_util; import 'dart:js_util' as js_util;
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'package:web/web.dart' as web;
import 'package:web_e2e_tests/common.dart'; import 'package:web_e2e_tests/common.dart';
import 'package:web_e2e_tests/text_editing_main.dart' as app; import 'package:web_e2e_tests/text_editing_main.dart' as app;
...@@ -26,9 +26,9 @@ void main() { ...@@ -26,9 +26,9 @@ void main() {
await tester.tap(find.byKey(const Key('input'))); await tester.tap(find.byKey(const Key('input')));
// A native input element will be appended to the DOM. // A native input element will be appended to the DOM.
final List<Node> nodeList = findElements('input'); final web.NodeList nodeList = findElements('input');
expect(nodeList.length, equals(1)); expect(nodeList.length, equals(1));
final InputElement input = nodeList[0] as InputElement; final web.HTMLInputElement input = nodeList.item(0)! as web.HTMLInputElement;
// The element's value will be the same as the textFormField's value. // The element's value will be the same as the textFormField's value.
expect(input.value, 'Text1'); expect(input.value, 'Text1');
...@@ -50,9 +50,9 @@ void main() { ...@@ -50,9 +50,9 @@ void main() {
await tester.tap(find.byKey(const Key('empty-input'))); await tester.tap(find.byKey(const Key('empty-input')));
// A native input element will be appended to the DOM. // A native input element will be appended to the DOM.
final List<Node> nodeList = findElements('input'); final web.NodeList nodeList = findElements('input');
expect(nodeList.length, equals(1)); expect(nodeList.length, equals(1));
final InputElement input = nodeList[0] as InputElement; final web.HTMLInputElement input = nodeList.item(0)! as web.HTMLInputElement;
// The element's value will be empty. // The element's value will be empty.
expect(input.value, ''); expect(input.value, '');
...@@ -81,7 +81,7 @@ void main() { ...@@ -81,7 +81,7 @@ void main() {
await tester.tap(find.byKey(const Key('input2'))); await tester.tap(find.byKey(const Key('input2')));
// Press Tab. This should trigger `onFieldSubmitted` of TextField. // Press Tab. This should trigger `onFieldSubmitted` of TextField.
final InputElement input = findElements('input')[0] as InputElement; final web.HTMLInputElement input = findElements('input').item(0)! as web.HTMLInputElement;
dispatchKeyboardEvent(input, 'keydown', <String, dynamic>{ dispatchKeyboardEvent(input, 'keydown', <String, dynamic>{
'keyCode': 13, // Enter. 'keyCode': 13, // Enter.
'cancelable': true, 'cancelable': true,
...@@ -111,9 +111,9 @@ void main() { ...@@ -111,9 +111,9 @@ void main() {
await tester.tap(find.byKey(const Key('input'))); await tester.tap(find.byKey(const Key('input')));
// A native input element will be appended to the DOM. // A native input element will be appended to the DOM.
final List<Node> nodeList = findElements('input'); final web.NodeList nodeList = findElements('input');
expect(nodeList.length, equals(1)); expect(nodeList.length, equals(1));
final InputElement input = nodeList[0] as InputElement; final web.HTMLInputElement input = nodeList.item(0)! as web.HTMLInputElement;
// Press Tab. The focus should move to the next TextFormField. // Press Tab. The focus should move to the next TextFormField.
dispatchKeyboardEvent(input, 'keydown', <String, dynamic>{ dispatchKeyboardEvent(input, 'keydown', <String, dynamic>{
...@@ -136,7 +136,7 @@ void main() { ...@@ -136,7 +136,7 @@ void main() {
// A native input element for the next TextField should be attached to the // A native input element for the next TextField should be attached to the
// DOM. // DOM.
final InputElement input2 = findElements('input')[0] as InputElement; final web.HTMLInputElement input2 = findElements('input').item(0)! as web.HTMLInputElement;
expect(input2.value, 'Text2'); expect(input2.value, 'Text2');
}, semanticsEnabled: false); }, semanticsEnabled: false);
...@@ -150,9 +150,9 @@ void main() { ...@@ -150,9 +150,9 @@ void main() {
await tester.tap(find.byKey(const Key('input'))); await tester.tap(find.byKey(const Key('input')));
// A native input element will be appended to the DOM. // A native input element will be appended to the DOM.
final List<Node> nodeList = findElements('input'); final web.NodeList nodeList = findElements('input');
expect(nodeList.length, equals(1)); expect(nodeList.length, equals(1));
final InputElement input = nodeList[0] as InputElement; final web.HTMLInputElement input = nodeList.item(0)! as web.HTMLInputElement;
// Press and release CapsLock. // Press and release CapsLock.
dispatchKeyboardEvent(input, 'keydown', <String, dynamic>{ dispatchKeyboardEvent(input, 'keydown', <String, dynamic>{
...@@ -191,7 +191,7 @@ void main() { ...@@ -191,7 +191,7 @@ void main() {
// A native input element for the next TextField should be attached to the // A native input element for the next TextField should be attached to the
// DOM. // DOM.
final InputElement input2 = findElements('input')[0] as InputElement; final web.HTMLInputElement input2 = findElements('input').item(0)! as web.HTMLInputElement;
expect(input2.value, 'Text2'); expect(input2.value, 'Text2');
}, semanticsEnabled: false); }, semanticsEnabled: false);
...@@ -215,16 +215,16 @@ void main() { ...@@ -215,16 +215,16 @@ void main() {
await gesture.up(); await gesture.up();
// A native input element will be appended to the DOM. // A native input element will be appended to the DOM.
final List<Node> nodeList = findElements('textarea'); final web.NodeList nodeList = findElements('textarea');
expect(nodeList.length, equals(1)); expect(nodeList.length, equals(1));
final TextAreaElement input = nodeList[0] as TextAreaElement; final web.HTMLTextAreaElement input = nodeList.item(0)! as web.HTMLTextAreaElement;
// The element's value should contain the selectable text. // The element's value should contain the selectable text.
expect(input.value, text); expect(input.value, text);
expect(input.hasAttribute('readonly'), isTrue); expect(input.hasAttribute('readonly'), isTrue);
// Make sure the entire text is selected. // Make sure the entire text is selected.
TextRange? range = TextRange? range =
TextRange(start: input.selectionStart!, end: input.selectionEnd!); TextRange(start: input.selectionStart, end: input.selectionEnd);
expect(range.textInside(text), text); expect(range.textInside(text), text);
// Double tap to select the first word. // Double tap to select the first word.
...@@ -236,7 +236,7 @@ void main() { ...@@ -236,7 +236,7 @@ void main() {
await gesture.up(); await gesture.up();
await gesture.down(firstWordOffset); await gesture.down(firstWordOffset);
await gesture.up(); await gesture.up();
range = TextRange(start: input.selectionStart!, end: input.selectionEnd!); range = TextRange(start: input.selectionStart, end: input.selectionEnd);
expect(range.textInside(text), 'Lorem'); expect(range.textInside(text), 'Lorem');
// Double tap to select the last word. // Double tap to select the last word.
...@@ -248,21 +248,21 @@ void main() { ...@@ -248,21 +248,21 @@ void main() {
await gesture.up(); await gesture.up();
await gesture.down(lastWordOffset); await gesture.down(lastWordOffset);
await gesture.up(); await gesture.up();
range = TextRange(start: input.selectionStart!, end: input.selectionEnd!); range = TextRange(start: input.selectionStart, end: input.selectionEnd);
expect(range.textInside(text), 'amet'); expect(range.textInside(text), 'amet');
}, semanticsEnabled: false); }, semanticsEnabled: false);
} }
KeyboardEvent dispatchKeyboardEvent( web.KeyboardEvent dispatchKeyboardEvent(
EventTarget target, String type, Map<String, dynamic> args) { web.EventTarget target, String type, Map<String, dynamic> args) {
final Object jsKeyboardEvent = js_util.getProperty(window, 'KeyboardEvent') as Object; final Object jsKeyboardEvent = js_util.getProperty(web.window, 'KeyboardEvent') as Object;
final List<dynamic> eventArgs = <dynamic>[ final List<dynamic> eventArgs = <dynamic>[
type, type,
args, args,
]; ];
final KeyboardEvent event = js_util.callConstructor( final web.KeyboardEvent event = js_util.callConstructor(
jsKeyboardEvent, js_util.jsify(eventArgs) as List<dynamic>) jsKeyboardEvent, js_util.jsify(eventArgs) as List<dynamic>)
as KeyboardEvent; as web.KeyboardEvent;
target.dispatchEvent(event); target.dispatchEvent(event);
return event; return event;
......
...@@ -3,13 +3,16 @@ ...@@ -3,13 +3,16 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:html' as html; import 'dart:js_interop';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'dart:ui_web';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'package:web/helpers.dart';
import 'package:web/web.dart' as web;
import 'package:web_e2e_tests/url_strategy_main.dart' as app; import 'package:web_e2e_tests/url_strategy_main.dart' as app;
void main() { void main() {
...@@ -56,7 +59,7 @@ class TestUrlStrategy extends UrlStrategy { ...@@ -56,7 +59,7 @@ class TestUrlStrategy extends UrlStrategy {
String getPath() => currentEntry.url; String getPath() => currentEntry.url;
@override @override
dynamic getState() => currentEntry.state; Object? getState() => currentEntry.state;
int _currentEntryIndex; int _currentEntryIndex;
final List<TestHistoryEntry> history; final List<TestHistoryEntry> history;
...@@ -111,10 +114,10 @@ class TestUrlStrategy extends UrlStrategy { ...@@ -111,10 +114,10 @@ class TestUrlStrategy extends UrlStrategy {
}); });
} }
final List<html.EventListener> listeners = <html.EventListener>[]; final List<PopStateListener> listeners = <PopStateListener>[];
@override @override
ui.VoidCallback addPopStateListener(html.EventListener fn) { ui.VoidCallback addPopStateListener(PopStateListener fn) {
listeners.add(fn); listeners.add(fn);
return () { return () {
// Schedule a micro task here to avoid removing the listener during // Schedule a micro task here to avoid removing the listener during
...@@ -134,9 +137,9 @@ class TestUrlStrategy extends UrlStrategy { ...@@ -134,9 +137,9 @@ class TestUrlStrategy extends UrlStrategy {
/// like a real browser. /// like a real browser.
void _firePopStateEvent() { void _firePopStateEvent() {
assert(withinAppHistory); assert(withinAppHistory);
final html.PopStateEvent event = html.PopStateEvent( final web.PopStateEvent event = web.PopStateEvent(
'popstate', 'popstate',
<String, dynamic>{'state': currentEntry.state}, PopStateEventInit(state: currentEntry.state?.toJSBox),
); );
for (int i = 0; i < listeners.length; i++) { for (int i = 0; i < listeners.length; i++) {
listeners[i](event); listeners[i](event);
...@@ -160,7 +163,7 @@ class TestUrlStrategy extends UrlStrategy { ...@@ -160,7 +163,7 @@ class TestUrlStrategy extends UrlStrategy {
class TestHistoryEntry { class TestHistoryEntry {
const TestHistoryEntry(this.state, this.title, this.url); const TestHistoryEntry(this.state, this.title, this.url);
final dynamic state; final Object? state;
final String? title; final String? title;
final String url; final String url;
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
import 'dart:async'; import 'dart:async';
import 'goldens_io.dart' if (dart.library.html) 'goldens_web.dart' as flutter_goldens; import 'goldens_io.dart' if (dart.library.js_interop) 'goldens_web.dart' as flutter_goldens;
Future<void> testExecutable(FutureOr<void> Function() testMain) { Future<void> testExecutable(FutureOr<void> Function() testMain) {
// Enable golden file testing using Skia Gold. // Enable golden file testing using Skia Gold.
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
import 'bitfield.dart' as bitfield; import 'bitfield.dart' as bitfield;
/// The dart:html implementation of [bitfield.kMaxUnsignedSMI]. /// The web implementation of [bitfield.kMaxUnsignedSMI].
/// ///
/// This value is used as an optimization to coerce some numbers to be within /// This value is used as an optimization to coerce some numbers to be within
/// the SMI range and avoid heap allocations. Because number encoding is /// the SMI range and avoid heap allocations. Because number encoding is
...@@ -13,14 +13,14 @@ import 'bitfield.dart' as bitfield; ...@@ -13,14 +13,14 @@ import 'bitfield.dart' as bitfield;
/// does not have to guarantee efficiency. /// does not have to guarantee efficiency.
const int kMaxUnsignedSMI = -1; const int kMaxUnsignedSMI = -1;
/// The dart:html implementation of [bitfield.Bitfield]. /// The web implementation of [bitfield.Bitfield].
class BitField<T extends dynamic> implements bitfield.BitField<T> { class BitField<T extends dynamic> implements bitfield.BitField<T> {
/// The dart:html implementation of [bitfield.Bitfield]. /// The web implementation of [bitfield.Bitfield].
// Can remove when we have metaclasses. // Can remove when we have metaclasses.
// ignore: avoid_unused_constructor_parameters // ignore: avoid_unused_constructor_parameters
BitField(int length); BitField(int length);
/// The dart:html implementation of [bitfield.Bitfield.filled]. /// The web implementation of [bitfield.Bitfield.filled].
// Can remove when we have metaclasses. // Can remove when we have metaclasses.
// ignore: avoid_unused_constructor_parameters // ignore: avoid_unused_constructor_parameters
BitField.filled(int length, bool value); BitField.filled(int length, bool value);
......
...@@ -6,7 +6,7 @@ import 'isolates.dart' as isolates; ...@@ -6,7 +6,7 @@ import 'isolates.dart' as isolates;
export 'isolates.dart' show ComputeCallback; export 'isolates.dart' show ComputeCallback;
/// The dart:html implementation of [isolate.compute]. /// The web implementation of [isolate.compute].
@pragma('dart2js:tryInline') @pragma('dart2js:tryInline')
Future<R> compute<M, R>(isolates.ComputeCallback<M, R> callback, M message, { String? debugLabel }) async { Future<R> compute<M, R>(isolates.ComputeCallback<M, R> callback, M message, { String? debugLabel }) async {
// To avoid blocking the UI immediately for an expensive function call, we // To avoid blocking the UI immediately for an expensive function call, we
......
...@@ -10,7 +10,7 @@ import 'package:flutter/rendering.dart'; ...@@ -10,7 +10,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
import '_goldens_io.dart' if (dart.library.html) '_goldens_web.dart' import '_goldens_io.dart' if (dart.library.js_interop) '_goldens_web.dart'
as flutter_goldens; as flutter_goldens;
/// If true, leak tracking is enabled for all `testWidgets`. /// If true, leak tracking is enabled for all `testWidgets`.
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
/// ///
/// See also: /// See also:
/// ///
/// * [_extension_web.dart], which has the dart:html implementation /// * [_extension_web.dart], which has the web implementation
void registerWebServiceExtension(Future<Map<String, dynamic>> Function(Map<String, String>) call) { void registerWebServiceExtension(Future<Map<String, dynamic>> Function(Map<String, String>) call) {
throw UnsupportedError('Use registerServiceExtension instead'); throw UnsupportedError('Use registerServiceExtension instead');
} }
...@@ -3,11 +3,13 @@ ...@@ -3,11 +3,13 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:convert'; import 'dart:convert';
import 'dart:html' as html; import 'dart:js_interop';
import 'dart:js'; import 'dart:js_interop_unsafe';
import 'dart:js_util' as js_util;
/// The dart:html implementation of [registerWebServiceExtension]. @JS('window')
external JSObject get _window;
/// The web implementation of [registerWebServiceExtension].
/// ///
/// Registers Web Service Extension for Flutter Web application. /// Registers Web Service Extension for Flutter Web application.
/// ///
...@@ -21,13 +23,13 @@ void registerWebServiceExtension(Future<Map<String, dynamic>> Function(Map<Strin ...@@ -21,13 +23,13 @@ void registerWebServiceExtension(Future<Map<String, dynamic>> Function(Map<Strin
// Define the result variable because packages/flutter_driver/lib/src/driver/web_driver.dart // Define the result variable because packages/flutter_driver/lib/src/driver/web_driver.dart
// checks for this value to become non-null when waiting for the result. If this value is // checks for this value to become non-null when waiting for the result. If this value is
// undefined at the time of the check, WebDriver throws an exception. // undefined at the time of the check, WebDriver throws an exception.
context[r'$flutterDriverResult'] = null; _window.setProperty(r'$flutterDriverResult'.toJS, null);
js_util.setProperty(html.window, r'$flutterDriver', allowInterop((dynamic message) async { _window.setProperty(r'$flutterDriver'.toJS, (JSAny message) {
final Map<String, String> params = Map<String, String>.from( final Map<String, String> params = Map<String, String>.from(
jsonDecode(message as String) as Map<String, dynamic>); jsonDecode((message as JSString).toDart) as Map<String, dynamic>);
final Map<String, dynamic> result = Map<String, dynamic>.from( call(params).then((Map<String, dynamic> result) {
await call(params)); _window.setProperty(r'$flutterDriverResult'.toJS, json.encode(result).toJS);
context[r'$flutterDriverResult'] = json.encode(result); });
})); }.toJS);
} }
...@@ -20,7 +20,7 @@ import '../common/error.dart'; ...@@ -20,7 +20,7 @@ import '../common/error.dart';
import '../common/find.dart'; import '../common/find.dart';
import '../common/handler_factory.dart'; import '../common/handler_factory.dart';
import '../common/message.dart'; import '../common/message.dart';
import '_extension_io.dart' if (dart.library.html) '_extension_web.dart'; import '_extension_io.dart' if (dart.library.js_interop) '_extension_web.dart';
const String _extensionMethodName = 'driver'; const String _extensionMethodName = 'driver';
......
...@@ -2,11 +2,15 @@ ...@@ -2,11 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:js' as js; import 'dart:js_interop';
import 'dart:js_interop_unsafe';
import 'package:flutter_driver/src/extension/_extension_web.dart'; import 'package:flutter_driver/src/extension/_extension_web.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
@JS('window')
external JSObject get _window;
void main() { void main() {
group('test web_extension', () { group('test web_extension', () {
late Future<Map<String, dynamic>> Function(Map<String, String>) call; late Future<Map<String, dynamic>> Function(Map<String, String>) call;
...@@ -21,11 +25,11 @@ void main() { ...@@ -21,11 +25,11 @@ void main() {
expect(() => registerWebServiceExtension(call), expect(() => registerWebServiceExtension(call),
returnsNormally); returnsNormally);
expect(js.context.hasProperty(r'$flutterDriver'), true); expect(_window.hasProperty(r'$flutterDriver'.toJS).toDart, true);
expect(js.context[r'$flutterDriver'], isNotNull); expect(_window.getProperty(r'$flutterDriver'.toJS), isNotNull);
expect(js.context.hasProperty(r'$flutterDriverResult'), true); expect(_window.hasProperty(r'$flutterDriverResult'.toJS).toDart, true);
expect(js.context[r'$flutterDriverResult'], isNull); expect(_window.getProperty(r'$flutterDriverResult'.toJS), isNull);
}); });
}); });
} }
...@@ -55,8 +55,9 @@ library flutter_test; ...@@ -55,8 +55,9 @@ library flutter_test;
export 'dart:async' show Future; export 'dart:async' show Future;
export 'src/_goldens_io.dart' if (dart.library.html) 'src/_goldens_web.dart'; export 'src/_goldens_io.dart' if (dart.library.js_interop) 'src/_goldens_web.dart';
export 'src/_matchers_io.dart' if (dart.library.html) 'src/_matchers_web.dart'; export 'src/_matchers_io.dart' if (dart.library.js_interop) 'src/_matchers_web.dart';
export 'src/_test_selector_io.dart' if (dart.library.js_interop) 'src/_test_selector_web.dart';
export 'src/accessibility.dart'; export 'src/accessibility.dart';
export 'src/animation_sheet.dart'; export 'src/animation_sheet.dart';
export 'src/binding.dart'; export 'src/binding.dart';
......
...@@ -292,6 +292,11 @@ ByteData _invert(ByteData imageBytes) { ...@@ -292,6 +292,11 @@ ByteData _invert(ByteData imageBytes) {
/// An unsupported [WebGoldenComparator] that exists for API compatibility. /// An unsupported [WebGoldenComparator] that exists for API compatibility.
class DefaultWebGoldenComparator extends WebGoldenComparator { class DefaultWebGoldenComparator extends WebGoldenComparator {
/// This is provided to prevent warnings from the analyzer.
DefaultWebGoldenComparator(Uri _) {
throw UnsupportedError('DefaultWebGoldenComparator is only supported on the web.');
}
@override @override
Future<bool> compare(double width, double height, Uri golden) { Future<bool> compare(double width, double height, Uri golden) {
throw UnsupportedError('DefaultWebGoldenComparator is only supported on the web.'); throw UnsupportedError('DefaultWebGoldenComparator is only supported on the web.');
......
...@@ -3,12 +3,13 @@ ...@@ -3,12 +3,13 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:convert'; import 'dart:convert';
import 'dart:html' as html; import 'dart:js_interop';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:matcher/expect.dart' show fail; import 'package:matcher/expect.dart' show fail;
import 'goldens.dart'; import 'goldens.dart';
import 'web.dart' as web;
/// An unsupported [GoldenFileComparator] that exists for API compatibility. /// An unsupported [GoldenFileComparator] that exists for API compatibility.
class LocalFileComparator extends GoldenFileComparator { class LocalFileComparator extends GoldenFileComparator {
...@@ -58,21 +59,23 @@ class DefaultWebGoldenComparator extends WebGoldenComparator { ...@@ -58,21 +59,23 @@ class DefaultWebGoldenComparator extends WebGoldenComparator {
@override @override
Future<bool> compare(double width, double height, Uri golden) async { Future<bool> compare(double width, double height, Uri golden) async {
final String key = golden.toString(); final String key = golden.toString();
final html.HttpRequest request = await html.HttpRequest.request( final web.Response response = await web.window.fetch(
'flutter_goldens', 'flutter_goldens'.toJS,
method: 'POST', web.RequestInit(
sendData: json.encode(<String, Object>{ method: 'POST',
'testUri': testUri.toString(), body: json.encode(<String, Object>{
'key': key, 'testUri': testUri.toString(),
'width': width.round(), 'key': key,
'height': height.round(), 'width': width.round(),
}), 'height': height.round(),
); }).toJS,
final String response = request.response as String; )
if (response == 'true') { ).toDart;
final String responseText = (await response.text().toDart).toDart;
if (responseText == 'true') {
return true; return true;
} }
fail(response); fail(responseText);
} }
@override @override
...@@ -85,20 +88,22 @@ class DefaultWebGoldenComparator extends WebGoldenComparator { ...@@ -85,20 +88,22 @@ class DefaultWebGoldenComparator extends WebGoldenComparator {
Future<bool> compareBytes(Uint8List bytes, Uri golden) async { Future<bool> compareBytes(Uint8List bytes, Uri golden) async {
final String key = golden.toString(); final String key = golden.toString();
final String bytesEncoded = base64.encode(bytes); final String bytesEncoded = base64.encode(bytes);
final html.HttpRequest request = await html.HttpRequest.request( final web.Response response = await web.window.fetch(
'flutter_goldens', 'flutter_goldens'.toJS,
method: 'POST', web.RequestInit(
sendData: json.encode(<String, Object>{ method: 'POST',
'testUri': testUri.toString(), body: json.encode(<String, Object>{
'key': key, 'testUri': testUri.toString(),
'bytes': bytesEncoded, 'key': key,
}), 'bytes': bytesEncoded,
); }).toJS,
final String response = request.response as String; )
if (response == 'true') { ).toDart;
final String responseText = (await response.text().toDart).toDart;
if (responseText == 'true') {
return true; return true;
} }
fail(response); fail(responseText);
} }
@override @override
......
// 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.
// This is an empty file that is imported instead of web_test_selector.dart on
// non-web platforms.
// 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:js_interop';
import 'dart:ui' as ui;
import 'dart:ui_web' as ui_web;
import 'package:stream_channel/stream_channel.dart';
import 'package:test_api/backend.dart';
import '_goldens_web.dart';
import 'goldens.dart';
import 'web.dart' as web;
// This file contains APIs that are used by the generated test harness for
// running flutter unit tests.
/// A `main` entry point for a test.
typedef EntryPoint = FutureOr<void> Function();
/// An entry point runner provided by a test config file
typedef EntryPointRunner = Future<void> Function(EntryPoint);
/// Metadata about a web test to run
typedef WebTest = ({
EntryPoint entryPoint,
EntryPointRunner? entryPointRunner,
Uri goldensUri,
});
/// Gets the test selector set by the test bootstrapping logic
String get testSelector {
final JSString? jsTestSelector = web.window.testSelector;
if (jsTestSelector == null) {
throw Exception('Test selector not set');
}
return jsTestSelector.toDart;
}
/// Runs a specific web test
Future<void> runWebTest(WebTest test) async {
ui_web.debugEmulateFlutterTesterEnvironment = true;
final Completer<void> completer = Completer<void>();
await ui_web.bootstrapEngine(runApp: () => completer.complete());
await completer.future;
webGoldenComparator = DefaultWebGoldenComparator(test.goldensUri);
/// This hard-codes the device pixel ratio to 3.0 and a 2400 x 1800 window
/// size for the purposes of testing.
ui_web.debugOverrideDevicePixelRatio(3.0);
ui.window.debugPhysicalSizeOverride = const ui.Size(2400, 1800);
final EntryPointRunner? entryPointRunner = test.entryPointRunner;
final EntryPoint entryPoint = test.entryPoint;
_internalBootstrapBrowserTest(() {
return entryPointRunner != null ? () => entryPointRunner(entryPoint) : entryPoint;
});
}
void _internalBootstrapBrowserTest(EntryPoint Function() getMain) {
final StreamChannel<Object?> channel = _serializeSuite(getMain, hidePrints: false);
_postMessageChannel().pipe(channel);
}
StreamChannel<Object?> _serializeSuite(EntryPoint Function() getMain, {bool hidePrints = true}) => RemoteListener.start(getMain, hidePrints: hidePrints);
StreamChannel<Object?> _postMessageChannel() {
final StreamChannelController<Object?> controller = StreamChannelController<Object?>(sync: true);
final web.MessageChannel channel = web.MessageChannel();
web.window.parent!.postMessage('port'.toJS, web.window.location.origin, <JSObject>[channel.port2].toJS);
final JSFunction eventCallback = (web.Event event) {
controller.local.sink.add(event.data.dartify());
}.toJS;
channel.port1.addEventListener('message'.toJS, eventCallback);
channel.port1.start();
controller.local.stream.listen(
(Object? message) => channel.port1.postMessage(message.jsify()),
onDone: () => channel.port1.removeEventListener('message'.toJS, eventCallback),
);
return controller.foreign;
}
...@@ -18,7 +18,7 @@ import 'package:stack_trace/stack_trace.dart' as stack_trace; ...@@ -18,7 +18,7 @@ import 'package:stack_trace/stack_trace.dart' as stack_trace;
import 'package:test_api/scaffolding.dart' as test_package show Timeout; import 'package:test_api/scaffolding.dart' as test_package show Timeout;
import 'package:vector_math/vector_math_64.dart'; import 'package:vector_math/vector_math_64.dart';
import '_binding_io.dart' if (dart.library.html) '_binding_web.dart' as binding; import '_binding_io.dart' if (dart.library.js_interop) '_binding_web.dart' as binding;
import 'goldens.dart'; import 'goldens.dart';
import 'platform.dart'; import 'platform.dart';
import 'restoration.dart'; import 'restoration.dart';
......
...@@ -8,7 +8,7 @@ import 'dart:ui'; ...@@ -8,7 +8,7 @@ import 'dart:ui';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import '_goldens_io.dart' if (dart.library.html) '_goldens_web.dart' as goldens; import '_goldens_io.dart' if (dart.library.js_interop) '_goldens_web.dart' as goldens;
/// Compares image pixels against a golden image file. /// Compares image pixels against a golden image file.
/// ///
......
...@@ -14,7 +14,7 @@ import 'package:matcher/expect.dart'; ...@@ -14,7 +14,7 @@ import 'package:matcher/expect.dart';
import 'package:matcher/src/expect/async_matcher.dart'; // ignore: implementation_imports import 'package:matcher/src/expect/async_matcher.dart'; // ignore: implementation_imports
import 'package:vector_math/vector_math_64.dart' show Matrix3; import 'package:vector_math/vector_math_64.dart' show Matrix3;
import '_matchers_io.dart' if (dart.library.html) '_matchers_web.dart' show MatchesGoldenFile, captureImage; import '_matchers_io.dart' if (dart.library.js_interop) '_matchers_web.dart' show MatchesGoldenFile, captureImage;
import 'accessibility.dart'; import 'accessibility.dart';
import 'binding.dart'; import 'binding.dart';
import 'controller.dart'; import 'controller.dart';
......
// 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.
// This code is copied from `package:web` which still needs its own
// documentation for public members. Since this is a shim that users should not
// use, we ignore this lint for this file.
// ignore_for_file: public_member_api_docs
/// A stripped down version of `package:web` to avoid pinning that repo in
/// Flutter as a dependency.
///
/// These are manually copied over from `package:web` as needed, and should stay
/// in sync with the latest package version as much as possible.
///
/// If missing members are needed, copy them over into the corresponding
/// extension or interface. If missing interfaces/types are needed, copy them
/// over while excluding unnecessary inheritance to make the copy minimal. These
/// types are erased at runtime, so excluding supertypes is safe. If a member is
/// needed that belongs to a supertype, then add the necessary `implements`
/// clause to the subtype when you add that supertype. Keep extensions next to
/// the interface they extend.
library;
import 'dart:js_interop';
@JS()
external Window get window;
extension type Window._(JSObject _) implements JSObject {
external JSPromise<Response> fetch(
JSAny input, [
RequestInit init,
]);
external Location get location;
external Window? get parent;
external void postMessage(JSAny message, JSString targetOrigin, JSArray<JSAny> transfers);
external JSString? get testSelector;
}
extension type Response._(JSObject _) implements JSObject {
external JSPromise<JSString> text();
}
extension type RequestInit._(JSObject _) implements JSObject {
external factory RequestInit({
String method,
JSAny? body,
});
}
extension type Location._(JSObject _) implements JSObject {
external JSString get origin;
}
extension type MessageChannel._(JSObject _) implements JSObject {
external factory MessageChannel();
external MessagePort port1;
external MessagePort port2;
}
extension type MessagePort._(JSObject _) implements JSObject {
external void addEventListener(JSString eventName, JSFunction callback);
external void removeEventListener(JSString eventName, JSFunction callback);
external void postMessage(JSAny? message);
external void start();
}
extension type Event._(JSObject _) implements JSObject {
external JSObject? get data;
}
name: flutter_test name: flutter_test
environment: environment:
sdk: '>=3.2.0-0 <4.0.0' sdk: '>=3.3.0-0 <4.0.0'
dependencies: dependencies:
# To update these, use "flutter update-packages --force-upgrade". # To update these, use "flutter update-packages --force-upgrade".
......
...@@ -12,6 +12,7 @@ import '../build_info.dart'; ...@@ -12,6 +12,7 @@ import '../build_info.dart';
import '../bundle_builder.dart'; import '../bundle_builder.dart';
import '../devfs.dart'; import '../devfs.dart';
import '../device.dart'; import '../device.dart';
import '../features.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../native_assets.dart'; import '../native_assets.dart';
import '../project.dart'; import '../project.dart';
...@@ -23,6 +24,7 @@ import '../test/test_time_recorder.dart'; ...@@ -23,6 +24,7 @@ import '../test/test_time_recorder.dart';
import '../test/test_wrapper.dart'; import '../test/test_wrapper.dart';
import '../test/watcher.dart'; import '../test/watcher.dart';
import '../web/compile.dart'; import '../web/compile.dart';
import '../web/web_constants.dart';
/// The name of the directory where Integration Tests are placed. /// The name of the directory where Integration Tests are placed.
/// ///
...@@ -233,7 +235,14 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { ...@@ -233,7 +235,14 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
'in seconds (e.g. "60s"), ' 'in seconds (e.g. "60s"), '
'as a multiplier of the default timeout (e.g. "2x"), ' 'as a multiplier of the default timeout (e.g. "2x"), '
'or as the string "none" to disable the timeout entirely.', 'or as the string "none" to disable the timeout entirely.',
)
..addFlag(
FlutterOptions.kWebWasmFlag,
help: 'Compile to WebAssembly rather than JavaScript.\n$kWasmMoreInfo',
negatable: false,
hide: !featureFlags.isFlutterWebWasmEnabled,
); );
addDdsOptions(verboseHelp: verboseHelp); addDdsOptions(verboseHelp: verboseHelp);
addServeObservatoryOptions(verboseHelp: verboseHelp); addServeObservatoryOptions(verboseHelp: verboseHelp);
usesFatalWarningsOption(verboseHelp: verboseHelp); usesFatalWarningsOption(verboseHelp: verboseHelp);
...@@ -256,6 +265,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { ...@@ -256,6 +265,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
final Set<Uri> _testFileUris = <Uri>{}; final Set<Uri> _testFileUris = <Uri>{};
bool get isWeb => stringArg('platform') == 'chrome'; bool get isWeb => stringArg('platform') == 'chrome';
bool get useWasm => boolArg(FlutterOptions.kWebWasmFlag);
@override @override
Future<Set<DevelopmentArtifact>> get requiredArtifacts async { Future<Set<DevelopmentArtifact>> get requiredArtifacts async {
...@@ -499,6 +509,10 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { ...@@ -499,6 +509,10 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
watcher = collector; watcher = collector;
} }
if (!isWeb && useWasm) {
throwToolExit('--wasm is only supported on the web platform');
}
Device? integrationTestDevice; Device? integrationTestDevice;
if (_isIntegrationTest) { if (_isIntegrationTest) {
integrationTestDevice = await findTargetDevice(); integrationTestDevice = await findTargetDevice();
...@@ -577,6 +591,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { ...@@ -577,6 +591,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
testAssetDirectory: testAssetDirectory, testAssetDirectory: testAssetDirectory,
flutterProject: flutterProject, flutterProject: flutterProject,
web: isWeb, web: isWeb,
useWasm: useWasm,
randomSeed: stringArg('test-randomize-ordering-seed'), randomSeed: stringArg('test-randomize-ordering-seed'),
reporter: stringArg('reporter'), reporter: stringArg('reporter'),
fileReporter: stringArg('file-reporter'), fileReporter: stringArg('file-reporter'),
......
...@@ -52,6 +52,7 @@ abstract class FlutterTestRunner { ...@@ -52,6 +52,7 @@ abstract class FlutterTestRunner {
String? icudtlPath, String? icudtlPath,
Directory? coverageDirectory, Directory? coverageDirectory,
bool web = false, bool web = false,
bool useWasm = false,
String? randomSeed, String? randomSeed,
String? reporter, String? reporter,
String? fileReporter, String? fileReporter,
...@@ -117,6 +118,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner { ...@@ -117,6 +118,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner {
String? icudtlPath, String? icudtlPath,
Directory? coverageDirectory, Directory? coverageDirectory,
bool web = false, bool web = false,
bool useWasm = false,
String? randomSeed, String? randomSeed,
String? reporter, String? reporter,
String? fileReporter, String? fileReporter,
...@@ -186,6 +188,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner { ...@@ -186,6 +188,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner {
testFiles: testFiles.map((Uri uri) => uri.toFilePath()).toList(), testFiles: testFiles.map((Uri uri) => uri.toFilePath()).toList(),
buildInfo: debuggingOptions.buildInfo, buildInfo: debuggingOptions.buildInfo,
webRenderer: debuggingOptions.webRenderer, webRenderer: debuggingOptions.webRenderer,
useWasm: useWasm,
); );
testArgs testArgs
..add('--platform=chrome') ..add('--platform=chrome')
...@@ -205,6 +208,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner { ...@@ -205,6 +208,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner {
webMemoryFS: result, webMemoryFS: result,
logger: globals.logger, logger: globals.logger,
fileSystem: globals.fs, fileSystem: globals.fs,
buildDirectory: globals.fs.directory(tempBuildDir),
artifacts: globals.artifacts, artifacts: globals.artifacts,
processManager: globals.processManager, processManager: globals.processManager,
chromiumLauncher: ChromiumLauncher( chromiumLauncher: ChromiumLauncher(
...@@ -217,6 +221,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner { ...@@ -217,6 +221,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner {
), ),
testTimeRecorder: testTimeRecorder, testTimeRecorder: testTimeRecorder,
webRenderer: debuggingOptions.webRenderer, webRenderer: debuggingOptions.webRenderer,
useWasm: useWasm,
); );
}, },
); );
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:package_config/package_config.dart'; import 'package:package_config/package_config.dart';
import 'package:package_config/package_config_types.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
import '../artifacts.dart'; import '../artifacts.dart';
...@@ -11,6 +12,7 @@ import '../base/config.dart'; ...@@ -11,6 +12,7 @@ import '../base/config.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/platform.dart'; import '../base/platform.dart';
import '../base/process.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../bundle.dart'; import '../bundle.dart';
import '../cache.dart'; import '../cache.dart';
...@@ -44,12 +46,74 @@ class WebTestCompiler { ...@@ -44,12 +46,74 @@ class WebTestCompiler {
final ProcessManager _processManager; final ProcessManager _processManager;
final Config _config; final Config _config;
Future<File> _generateTestEntrypoint({
required List<String> testFiles,
required Directory projectDirectory,
required Directory outputDirectory,
required LanguageVersion languageVersion,
}) async {
final List<WebTestInfo> testInfos = testFiles.map((String testFilePath) {
final List<String> relativeTestSegments = _fileSystem.path.split(
_fileSystem.path.relative(
testFilePath,
from: projectDirectory.childDirectory('test').path
)
);
final File? testConfigFile = findTestConfigFile(_fileSystem.file(testFilePath), _logger);
String? testConfigPath;
if (testConfigFile != null) {
testConfigPath = _fileSystem.path.split(
_fileSystem.path.relative(
testConfigFile.path,
from: projectDirectory.childDirectory('test').path
)
).join('/');
}
return (
entryPoint: relativeTestSegments.join('/'),
configFile: testConfigPath,
goldensUri: Uri.file(testFilePath),
);
}).toList();
return _fileSystem.file(_fileSystem.path.join(outputDirectory.path, 'main.dart'))
..createSync(recursive: true)
..writeAsStringSync(generateTestEntrypoint(
testInfos: testInfos,
languageVersion: languageVersion
)
);
}
Future<WebMemoryFS> initialize({ Future<WebMemoryFS> initialize({
required Directory projectDirectory, required Directory projectDirectory,
required String testOutputDir, required String testOutputDir,
required List<String> testFiles, required List<String> testFiles,
required BuildInfo buildInfo, required BuildInfo buildInfo,
required WebRendererMode webRenderer, required WebRendererMode webRenderer,
required bool useWasm,
}) async {
return useWasm ? _compileWasm(
projectDirectory: projectDirectory,
testOutputDir: testOutputDir,
testFiles: testFiles,
buildInfo: buildInfo,
webRenderer: webRenderer,
) : _compileJS(
projectDirectory: projectDirectory,
testOutputDir: testOutputDir,
testFiles: testFiles,
buildInfo: buildInfo,
webRenderer: webRenderer,
);
}
Future<WebMemoryFS> _compileJS({
required Directory projectDirectory,
required String testOutputDir,
required List<String> testFiles,
required BuildInfo buildInfo,
required WebRendererMode webRenderer,
}) async { }) async {
LanguageVersion languageVersion = LanguageVersion(2, 8); LanguageVersion languageVersion = LanguageVersion(2, 8);
late final String platformDillName; late final String platformDillName;
...@@ -78,32 +142,12 @@ class WebTestCompiler { ...@@ -78,32 +142,12 @@ class WebTestCompiler {
final Directory outputDirectory = _fileSystem.directory(testOutputDir) final Directory outputDirectory = _fileSystem.directory(testOutputDir)
..createSync(recursive: true); ..createSync(recursive: true);
final List<File> generatedFiles = <File>[]; final File testFile = await _generateTestEntrypoint(
for (final String testFilePath in testFiles) { testFiles: testFiles,
final List<String> relativeTestSegments = _fileSystem.path.split( projectDirectory: projectDirectory,
_fileSystem.path.relative(testFilePath, from: projectDirectory.childDirectory('test').path)); outputDirectory: outputDirectory,
final File generatedFile = _fileSystem.file( languageVersion: languageVersion
_fileSystem.path.join(outputDirectory.path, '${relativeTestSegments.join('_')}.test.dart')); );
generatedFile
..createSync(recursive: true)
..writeAsStringSync(generateTestEntrypoint(
relativeTestPath: relativeTestSegments.join('/'),
absolutePath: testFilePath,
testConfigPath: findTestConfigFile(_fileSystem.file(testFilePath), _logger)?.path,
languageVersion: languageVersion,
));
generatedFiles.add(generatedFile);
}
// Generate a fake main file that imports all tests to be executed. This will force
// each of them to be compiled.
final StringBuffer buffer = StringBuffer('// @dart=${languageVersion.major}.${languageVersion.minor}\n');
for (final File generatedFile in generatedFiles) {
buffer.writeln('import "${_fileSystem.path.basename(generatedFile.path)}";');
}
buffer.writeln('void main() {}');
_fileSystem.file(_fileSystem.path.join(outputDirectory.path, 'main.dart'))
..createSync()
..writeAsStringSync(buffer.toString());
final String cachedKernelPath = getDefaultCachedKernelPath( final String cachedKernelPath = getDefaultCachedKernelPath(
trackWidgetCreation: buildInfo.trackWidgetCreation, trackWidgetCreation: buildInfo.trackWidgetCreation,
...@@ -139,7 +183,7 @@ class WebTestCompiler { ...@@ -139,7 +183,7 @@ class WebTestCompiler {
); );
final CompilerOutput? output = await residentCompiler.recompile( final CompilerOutput? output = await residentCompiler.recompile(
Uri.parse('org-dartlang-app:///main.dart'), Uri.parse('org-dartlang-app:///${testFile.basename}'),
<Uri>[], <Uri>[],
outputPath: outputDirectory.childFile('out').path, outputPath: outputDirectory.childFile('out').path,
packageConfig: buildInfo.packageConfig, packageConfig: buildInfo.packageConfig,
...@@ -157,7 +201,67 @@ class WebTestCompiler { ...@@ -157,7 +201,67 @@ class WebTestCompiler {
final File manifestFile = outputDirectory.childFile('${output.outputFilename}.json'); final File manifestFile = outputDirectory.childFile('${output.outputFilename}.json');
final File sourcemapFile = outputDirectory.childFile('${output.outputFilename}.map'); final File sourcemapFile = outputDirectory.childFile('${output.outputFilename}.map');
final File metadataFile = outputDirectory.childFile('${output.outputFilename}.metadata'); final File metadataFile = outputDirectory.childFile('${output.outputFilename}.metadata');
return WebMemoryFS() return WebMemoryFS()
..write(codeFile, manifestFile, sourcemapFile, metadataFile); ..write(codeFile, manifestFile, sourcemapFile, metadataFile);
} }
Future<WebMemoryFS> _compileWasm({
required Directory projectDirectory,
required String testOutputDir,
required List<String> testFiles,
required BuildInfo buildInfo,
required WebRendererMode webRenderer,
}) async {
final Directory outputDirectory = _fileSystem.directory(testOutputDir)
..createSync(recursive: true);
final File testFile = await _generateTestEntrypoint(
testFiles: testFiles,
projectDirectory: projectDirectory,
outputDirectory: outputDirectory,
languageVersion: currentLanguageVersion(_fileSystem, Cache.flutterRoot!),
);
final String dartSdkPath = _artifacts.getArtifactPath(Artifact.engineDartSdkPath, platform: TargetPlatform.web_javascript);
final String platformBinariesPath = _artifacts.getHostArtifact(HostArtifact.webPlatformKernelFolder).path;
final String platformFilePath = _fileSystem.path.join(platformBinariesPath, 'dart2wasm_platform.dill');
final List<String> dartDefines = webRenderer.updateDartDefines(buildInfo.dartDefines);
final File outputWasmFile = outputDirectory.childFile('main.dart.wasm');
final List<String> compilationArgs = <String>[
_artifacts.getArtifactPath(Artifact.engineDartBinary, platform: TargetPlatform.web_javascript),
'compile',
'wasm',
'--packages=.dart_tool/package_config.json',
'--extra-compiler-option=--dart-sdk=$dartSdkPath',
'--extra-compiler-option=--platform=$platformFilePath',
'--extra-compiler-option=--multi-root-scheme=org-dartlang-app',
'--extra-compiler-option=--multi-root=${projectDirectory.childDirectory('test').path}',
'--extra-compiler-option=--multi-root=${outputDirectory.path}',
if (webRenderer == WebRendererMode.skwasm) ...<String>[
'--extra-compiler-option=--import-shared-memory',
'--extra-compiler-option=--shared-memory-max-pages=32768',
],
...buildInfo.extraFrontEndOptions,
for (final String dartDefine in dartDefines)
'-D$dartDefine',
'-O1',
'-o',
outputWasmFile.path,
testFile.path, // dartfile
];
final ProcessUtils processUtils = ProcessUtils(
logger: _logger,
processManager: _processManager,
);
await processUtils.run(
throwOnError: true,
compilationArgs,
);
return WebMemoryFS();
}
} }
...@@ -387,59 +387,70 @@ define("$bootstrapModule", ["$entrypoint", "dart_sdk"], function(app, dart_sdk) ...@@ -387,59 +387,70 @@ define("$bootstrapModule", ["$entrypoint", "dart_sdk"], function(app, dart_sdk)
'''; ''';
} }
/// Generates the bootstrap logic required for a flutter test running in a browser. typedef WebTestInfo = ({
String entryPoint,
Uri goldensUri,
String? configFile,
});
/// Generates the bootstrap logic required for running a group of unit test
/// files in the browser.
/// ///
/// This hard-codes the device pixel ratio to 3.0 and a 2400 x 1800 window size. /// This creates one "switchboard" main function that imports all the main
/// functions of the unit test files that need to be run. The javascript code
/// that starts the test sets a `window.testSelector` that specifies which main
/// function to invoke. This allows us to compile all the unit test files as a
/// single web application and invoke that with a different selector for each
/// test.
String generateTestEntrypoint({ String generateTestEntrypoint({
required String relativeTestPath, required List<WebTestInfo> testInfos,
required String absolutePath,
required String? testConfigPath,
required LanguageVersion languageVersion, required LanguageVersion languageVersion,
}) { }) {
return ''' final List<String> importMainStatements = <String>[];
// @dart = ${languageVersion.major}.${languageVersion.minor} final List<String> importTestConfigStatements = <String>[];
import 'org-dartlang-app:///$relativeTestPath' as test; final List<String> webTestPairs = <String>[];
import 'dart:ui' as ui;
import 'dart:ui_web' as ui_web; for (int index = 0; index < testInfos.length; index++) {
import 'dart:html'; final WebTestInfo testInfo = testInfos[index];
import 'dart:js'; final String entryPointPath = testInfo.entryPoint;
${testConfigPath != null ? "import '${Uri.file(testConfigPath)}' as test_config;" : ""} importMainStatements.add("import 'org-dartlang-app:///${Uri.file(entryPointPath)}' as test_$index show main;");
import 'package:stream_channel/stream_channel.dart';
import 'package:flutter_test/flutter_test.dart'; final String? testConfigPath = testInfo.configFile;
import 'package:test_api/backend.dart'; String? testConfigFunction = 'null';
if (testConfigPath != null) {
Future<void> main() async { importTestConfigStatements.add(
ui_web.debugEmulateFlutterTesterEnvironment = true; "import 'org-dartlang-app:///${Uri.file(testConfigPath)}' as test_config_$index show testExecutable;"
await ui_web.bootstrapEngine(); );
webGoldenComparator = DefaultWebGoldenComparator(Uri.parse('${Uri.file(absolutePath)}')); testConfigFunction = 'test_config_$index.testExecutable';
ui_web.debugOverrideDevicePixelRatio(3.0); }
ui.window.debugPhysicalSizeOverride = const ui.Size(2400, 1800); webTestPairs.add('''
'$entryPointPath': (
internalBootstrapBrowserTest(() { entryPoint: test_$index.main,
return ${testConfigPath != null ? "() => test_config.testExecutable(test.main)" : "test.main"}; entryPointRunner: $testConfigFunction,
}); goldensUri: Uri.parse('${testInfo.goldensUri}'),
),
''');
} }
return '''
// @dart = ${languageVersion.major}.${languageVersion.minor}
void internalBootstrapBrowserTest(Function getMain()) { ${importMainStatements.join('\n')}
var channel = serializeSuite(getMain, hidePrints: false);
postMessageChannel().pipe(channel);
}
StreamChannel serializeSuite(Function getMain(), {bool hidePrints = true}) => RemoteListener.start(getMain, hidePrints: hidePrints); ${importTestConfigStatements.join('\n')}
StreamChannel postMessageChannel() { import 'package:flutter_test/flutter_test.dart';
var controller = StreamChannelController<Object?>(sync: true);
var channel = MessageChannel();
window.parent!.postMessage('port', window.location.origin, [channel.port2]);
var portSubscription = channel.port1.onMessage.listen((message) { Map<String, WebTest> webTestMap = <String, WebTest>{
controller.local.sink.add(message.data); ${webTestPairs.join('\n')}
}); };
controller.local.stream
.listen(channel.port1.postMessage, onDone: portSubscription.cancel);
return controller.foreign; Future<void> main() {
final WebTest? webTest = webTestMap[testSelector];
if (webTest == null) {
throw Exception('Web test for \${testSelector} not found');
} }
return runWebTest(webTest);
}
'''; ''';
} }
......
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
// of your plugin as a separate package, instead of inlining it in the same // of your plugin as a separate package, instead of inlining it in the same
// package as the core of your plugin. // package as the core of your plugin.
// ignore: avoid_web_libraries_in_flutter // ignore: avoid_web_libraries_in_flutter
import 'dart:html' as html show window;
import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'package:web/web.dart' as web;
import '{{projectName}}_platform_interface.dart'; import '{{projectName}}_platform_interface.dart';
...@@ -20,7 +20,7 @@ class {{pluginDartClass}}Web extends {{pluginDartClass}}Platform { ...@@ -20,7 +20,7 @@ class {{pluginDartClass}}Web extends {{pluginDartClass}}Platform {
/// Returns a [String] containing the version of the platform. /// Returns a [String] containing the version of the platform.
@override @override
Future<String?> getPlatformVersion() async { Future<String?> getPlatformVersion() async {
final version = html.window.navigator.userAgent; final version = web.window.navigator.userAgent;
return version; return version;
} }
} }
...@@ -13,6 +13,7 @@ dependencies: ...@@ -13,6 +13,7 @@ dependencies:
{{#web}} {{#web}}
flutter_web_plugins: flutter_web_plugins:
sdk: flutter sdk: flutter
web: ^0.5.1
{{/web}} {{/web}}
plugin_platform_interface: ^2.0.2 plugin_platform_interface: ^2.0.2
......
...@@ -87,11 +87,13 @@ void main() { ...@@ -87,11 +87,13 @@ void main() {
buildInfo: const BuildInfo(BuildMode.debug, '', treeShakeIcons: false), buildInfo: const BuildInfo(BuildMode.debug, '', treeShakeIcons: false),
webMemoryFS: WebMemoryFS(), webMemoryFS: WebMemoryFS(),
fileSystem: fileSystem, fileSystem: fileSystem,
buildDirectory: fileSystem.directory('build'),
logger: logger, logger: logger,
chromiumLauncher: chromiumLauncher, chromiumLauncher: chromiumLauncher,
artifacts: artifacts, artifacts: artifacts,
processManager: processManager, processManager: processManager,
webRenderer: WebRendererMode.canvaskit, webRenderer: WebRendererMode.canvaskit,
useWasm: false,
serverFactory: () async => server, serverFactory: () async => server,
testPackageUri: Uri.parse('test'), testPackageUri: Uri.parse('test'),
); );
...@@ -134,11 +136,13 @@ void main() { ...@@ -134,11 +136,13 @@ void main() {
), ),
webMemoryFS: WebMemoryFS(), webMemoryFS: WebMemoryFS(),
fileSystem: fileSystem, fileSystem: fileSystem,
buildDirectory: fileSystem.directory('build'),
logger: logger, logger: logger,
chromiumLauncher: chromiumLauncher, chromiumLauncher: chromiumLauncher,
artifacts: artifacts, artifacts: artifacts,
processManager: processManager, processManager: processManager,
webRenderer: WebRendererMode.canvaskit, webRenderer: WebRendererMode.canvaskit,
useWasm: false,
serverFactory: () async => server, serverFactory: () async => server,
testPackageUri: Uri.parse('test'), testPackageUri: Uri.parse('test'),
); );
......
...@@ -1386,6 +1386,7 @@ class FakeFlutterTestRunner implements FlutterTestRunner { ...@@ -1386,6 +1386,7 @@ class FakeFlutterTestRunner implements FlutterTestRunner {
String? icudtlPath, String? icudtlPath,
Directory? coverageDirectory, Directory? coverageDirectory,
bool web = false, bool web = false,
bool useWasm = false,
String? randomSeed, String? randomSeed,
String? reporter, String? reporter,
String? fileReporter, String? fileReporter,
......
...@@ -91,6 +91,7 @@ void main() { ...@@ -91,6 +91,7 @@ void main() {
testFiles: <String>['project/test/fake_test.dart'], testFiles: <String>['project/test/fake_test.dart'],
buildInfo: buildInfo, buildInfo: buildInfo,
webRenderer: WebRendererMode.canvaskit, webRenderer: WebRendererMode.canvaskit,
useWasm: false,
); );
expect(processManager.hasRemainingExpectations, isFalse); expect(processManager.hasRemainingExpectations, isFalse);
......
...@@ -138,37 +138,18 @@ void main() { ...@@ -138,37 +138,18 @@ void main() {
expect(result, contains('el.setAttribute("data-main", \'foo.dart.js\');')); expect(result, contains('el.setAttribute("data-main", \'foo.dart.js\');'));
}); });
test('generateTestEntrypoint does not generate test config wrappers when testConfigPath is not passed', () { test('generateTestEntrypoint generates proper imports and mappings for tests', () {
final String result = generateTestEntrypoint( final String result = generateTestEntrypoint(
relativeTestPath: 'relative_path.dart', testInfos: <WebTestInfo>[
absolutePath: 'absolute_path.dart', (entryPoint: 'foo.dart', goldensUri: Uri.parse('foo.dart'), configFile: null),
testConfigPath: null, (entryPoint: 'bar.dart', goldensUri: Uri.parse('bar.dart'), configFile: 'bar_config.dart'),
],
languageVersion: LanguageVersion(2, 8), languageVersion: LanguageVersion(2, 8),
); );
expect(result, isNot(contains('test_config.testExecutable'))); expect(result, contains("import 'org-dartlang-app:///foo.dart'"));
}); expect(result, contains("import 'org-dartlang-app:///bar.dart'"));
expect(result, contains("import 'org-dartlang-app:///bar_config.dart'"));
test('generateTestEntrypoint generates test config wrappers when testConfigPath is passed', () {
final String result = generateTestEntrypoint(
relativeTestPath: 'relative_path.dart',
absolutePath: 'absolute_path.dart',
testConfigPath: 'test_config_path.dart',
languageVersion: LanguageVersion(2, 8),
);
expect(result, contains('test_config.testExecutable'));
});
test('generateTestEntrypoint embeds urls correctly', () {
final String result = generateTestEntrypoint(
relativeTestPath: 'relative_path.dart',
absolutePath: '/test/absolute_path.dart',
testConfigPath: null,
languageVersion: LanguageVersion(2, 8),
);
expect(result, contains("Uri.parse('file:///test/absolute_path.dart')"));
}); });
group('Using the DDC module system', () { group('Using the DDC module system', () {
...@@ -299,38 +280,5 @@ void main() { ...@@ -299,38 +280,5 @@ void main() {
expect(result, contains('el.setAttribute("data-main", \'foo.dart.js\');')); expect(result, contains('el.setAttribute("data-main", \'foo.dart.js\');'));
}); });
test('generateTestEntrypoint does not generate test config wrappers when testConfigPath is not passed', () {
final String result = generateTestEntrypoint(
relativeTestPath: 'relative_path.dart',
absolutePath: 'absolute_path.dart',
testConfigPath: null,
languageVersion: LanguageVersion(2, 8),
);
expect(result, isNot(contains('test_config.testExecutable')));
});
test('generateTestEntrypoint generates test config wrappers when testConfigPath is passed', () {
final String result = generateTestEntrypoint(
relativeTestPath: 'relative_path.dart',
absolutePath: 'absolute_path.dart',
testConfigPath: 'test_config_path.dart',
languageVersion: LanguageVersion(2, 8),
);
expect(result, contains('test_config.testExecutable'));
});
test('generateTestEntrypoint embeds urls correctly', () {
final String result = generateTestEntrypoint(
relativeTestPath: 'relative_path.dart',
absolutePath: '/test/absolute_path.dart',
testConfigPath: null,
languageVersion: LanguageVersion(2, 8),
);
expect(result, contains("Uri.parse('file:///test/absolute_path.dart')"));
});
}); });
} }
...@@ -9,12 +9,11 @@ ...@@ -9,12 +9,11 @@
// gestures. You can also use WidgetTester to find child widgets in the widget // gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct. // tree, read text, and verify that the values of widget properties are correct.
import 'dart:html' as html;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'package:integration_test_example/main.dart' as app; import 'package:integration_test_example/main.dart' as app;
import 'package:web/web.dart' as web;
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
...@@ -31,7 +30,7 @@ void main() { ...@@ -31,7 +30,7 @@ void main() {
(Widget widget) => (Widget widget) =>
widget is Text && widget is Text &&
widget.data! widget.data!
.startsWith('Platform: ${html.window.navigator.platform}\n'), .startsWith('Platform: ${web.window.navigator.platform}\n'),
), ),
findsOneWidget, findsOneWidget,
); );
......
...@@ -9,12 +9,12 @@ ...@@ -9,12 +9,12 @@
// gestures. You can also use WidgetTester to find child widgets in the widget // gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct. // tree, read text, and verify that the values of widget properties are correct.
import 'dart:html' as html;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'package:integration_test_example/main.dart' as app; import 'package:integration_test_example/main.dart' as app;
import 'package:web/web.dart' as web;
void main() { void main() {
final IntegrationTestWidgetsFlutterBinding binding = final IntegrationTestWidgetsFlutterBinding binding =
...@@ -47,7 +47,7 @@ void main() { ...@@ -47,7 +47,7 @@ void main() {
(Widget widget) => (Widget widget) =>
widget is Text && widget is Text &&
widget.data! widget.data!
.startsWith('Platform: ${html.window.navigator.platform}\n'), .startsWith('Platform: ${web.window.navigator.platform}\n'),
), ),
findsOneWidget, findsOneWidget,
); );
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import '_example_test_io.dart' if (dart.library.html) '_example_test_web.dart' as tests; import '_example_test_io.dart' if (dart.library.js_interop) '_example_test_web.dart' as tests;
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import '_extended_test_io.dart' if (dart.library.html) '_extended_test_web.dart' as tests; import '_extended_test_io.dart' if (dart.library.js_interop) '_extended_test_web.dart' as tests;
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
......
...@@ -2,6 +2,6 @@ ...@@ -2,6 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'my_app.dart' if (dart.library.html) 'my_web_app.dart'; import 'my_app.dart' if (dart.library.js_interop) 'my_web_app.dart';
void main() => startApp(); void main() => startApp();
...@@ -2,9 +2,10 @@ ...@@ -2,9 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:html' as html;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:web/web.dart' as web;
// ignore_for_file: public_member_api_docs // ignore_for_file: public_member_api_docs
void startApp() => runApp(const MyWebApp()); void startApp() => runApp(const MyWebApp());
...@@ -26,7 +27,7 @@ class _MyWebAppState extends State<MyWebApp> { ...@@ -26,7 +27,7 @@ class _MyWebAppState extends State<MyWebApp> {
), ),
body: Center( body: Center(
key: const Key('mainapp'), key: const Key('mainapp'),
child: Text('Platform: ${html.window.navigator.platform}\n'), child: Text('Platform: ${web.window.navigator.platform}\n'),
), ),
), ),
); );
......
...@@ -11,6 +11,7 @@ dependencies: ...@@ -11,6 +11,7 @@ dependencies:
sdk: flutter sdk: flutter
cupertino_icons: 1.0.6 cupertino_icons: 1.0.6
web: 0.5.1
characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
collection: 1.18.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
...@@ -78,7 +79,6 @@ dev_dependencies: ...@@ -78,7 +79,6 @@ dev_dependencies:
typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
web_socket_channel: 2.4.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
webdriver: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
webkit_inspection_protocol: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
......
...@@ -15,7 +15,7 @@ import 'channel.dart'; ...@@ -15,7 +15,7 @@ import 'channel.dart';
/// ///
/// See also: /// See also:
/// ///
/// * `_callback_web.dart`, which has the dart:html implementation /// * `_callback_web.dart`, which has the web implementation
CallbackManager get callbackManager => _singletonCallbackManager; CallbackManager get callbackManager => _singletonCallbackManager;
/// IOCallbackManager singleton. /// IOCallbackManager singleton.
......
...@@ -6,7 +6,7 @@ import 'dart:async'; ...@@ -6,7 +6,7 @@ import 'dart:async';
import '../common.dart'; import '../common.dart';
/// The dart:html implementation of [CallbackManager]. /// The web implementation of [CallbackManager].
/// ///
/// See also: /// See also:
/// ///
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
/// ///
/// See also: /// See also:
/// ///
/// * `_extension_web.dart`, which has the dart:html implementation /// * `_extension_web.dart`, which has the web implementation
void registerWebServiceExtension( void registerWebServiceExtension(
Future<Map<String, dynamic>> Function(Map<String, String>) call) { Future<Map<String, dynamic>> Function(Map<String, String>) call) {
throw UnsupportedError('Use registerServiceExtension instead'); throw UnsupportedError('Use registerServiceExtension instead');
......
...@@ -4,9 +4,11 @@ ...@@ -4,9 +4,11 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:html' as html; import 'dart:js_interop';
import 'dart:js'; import 'dart:js_interop_unsafe';
import 'dart:js_util' as js_util;
@JS('window')
external JSObject get _window;
/// The web implementation of [registerWebServiceExtension]. /// The web implementation of [registerWebServiceExtension].
/// ///
...@@ -20,21 +22,25 @@ void registerWebServiceExtension(Future<Map<String, dynamic>> Function(Map<Strin ...@@ -20,21 +22,25 @@ void registerWebServiceExtension(Future<Map<String, dynamic>> Function(Map<Strin
// Define the result variable because packages/flutter_driver/lib/src/driver/web_driver.dart // Define the result variable because packages/flutter_driver/lib/src/driver/web_driver.dart
// checks for this value to become non-null when waiting for the result. If this value is // checks for this value to become non-null when waiting for the result. If this value is
// undefined at the time of the check, WebDriver throws an exception. // undefined at the time of the check, WebDriver throws an exception.
context[r'$flutterDriverResult'] = null; _window.setProperty(r'$flutterDriverResult'.toJS, null);
js_util.setProperty(html.window, r'$flutterDriver', allowInterop((dynamic message) async { _window.setProperty(r'$flutterDriver'.toJS, (JSAny message) {
try { (() async {
final Map<String, dynamic> messageJson = jsonDecode(message as String) as Map<String, dynamic>; try {
final Map<String, String> params = messageJson.cast<String, String>(); final Map<String, dynamic> messageJson = jsonDecode((message as JSString).toDart) as Map<String, dynamic>;
final Map<String, dynamic> result = await callback(params); final Map<String, String> params = messageJson.cast<String, String>();
context[r'$flutterDriverResult'] = json.encode(result); final Map<String, dynamic> result = await callback(params);
} catch (error, stackTrace) { _window.setProperty(r'$flutterDriverResult'.toJS, json.encode(result).toJS);
// Encode the error in the same format the FlutterDriver extension uses. } catch (error, stackTrace) {
// See //packages/flutter_driver/lib/src/extension/extension.dart // Encode the error in the same format the FlutterDriver extension uses.
context[r'$flutterDriverResult'] = json.encode(<String, dynamic>{ // See //packages/flutter_driver/lib/src/extension/extension.dart
'isError': true, _window.setProperty(r'$flutterDriverResult'.toJS,
'response': '$error\n$stackTrace', json.encode(<String, dynamic>{
}); 'isError': true,
} 'response': '$error\n$stackTrace',
})); }).toJS
);
}
})();
}.toJS);
} }
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
export '_callback_io.dart' if (dart.library.html) '_callback_web.dart'; export '_callback_io.dart' if (dart.library.js_interop) '_callback_web.dart';
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
export '_extension_io.dart' if (dart.library.html) '_extension_web.dart'; export '_extension_io.dart' if (dart.library.js_interop) '_extension_web.dart';
...@@ -5,19 +5,23 @@ ...@@ -5,19 +5,23 @@
@Tags(<String>['web']) @Tags(<String>['web'])
library; library;
import 'dart:js' as js; import 'dart:js_interop';
import 'dart:js_interop_unsafe';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
@JS('window')
external JSObject get _window;
void main() { void main() {
IntegrationTestWidgetsFlutterBinding(); IntegrationTestWidgetsFlutterBinding();
test('IntegrationTestWidgetsFlutterBinding on the web should register certain global properties', () { test('IntegrationTestWidgetsFlutterBinding on the web should register certain global properties', () {
expect(js.context.hasProperty(r'$flutterDriver'), true); expect(_window.hasProperty(r'$flutterDriver'.toJS).toDart, true);
expect(js.context[r'$flutterDriver'], isNotNull); expect(_window.getProperty(r'$flutterDriver'.toJS), isNotNull);
expect(js.context.hasProperty(r'$flutterDriverResult'), true); expect(_window.hasProperty(r'$flutterDriverResult'.toJS).toDart, true);
expect(js.context[r'$flutterDriverResult'], isNull); expect(_window.getProperty(r'$flutterDriverResult'.toJS), isNull);
}); });
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment