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
This commit is contained in:
parent
98d10b6211
commit
31209d04ff
184
.ci.yaml
184
.ci.yaml
@ -1725,6 +1725,190 @@ targets:
|
||||
- bin/**
|
||||
- .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
|
||||
recipe: flutter/flutter_drone
|
||||
timeout: 60
|
||||
|
@ -286,7 +286,7 @@
|
||||
/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_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_startup_test.dart @loic-sharma @flutter/desktop
|
||||
|
||||
@ -325,7 +325,7 @@
|
||||
# flutter_plugins @stuartmorgan @flutter/plugin
|
||||
# framework_tests @HansMuller @flutter/framework
|
||||
# fuchsia_precache @christopherfujino @flutter/tool
|
||||
# realm_checker @jacksongardner @flutter/tool
|
||||
# realm_checker @eyebrowsoffire @flutter/tool
|
||||
# skp_generator @Hixie
|
||||
# test_ownership @keyonghan
|
||||
# tool_host_cross_arch_tests @andrewkolos @flutter/tool
|
||||
@ -336,4 +336,5 @@
|
||||
# web_integration_tests @yjbanov @flutter/web
|
||||
# web_long_running_tests @yjbanov @flutter/web
|
||||
# web_tests @yjbanov @flutter/web
|
||||
# web_skwasm_tests @eyebrowsoffire @flutter/web
|
||||
# web_tool_tests @eliasyishak @flutter/tool
|
||||
|
@ -242,6 +242,8 @@ Future<void> main(List<String> args) async {
|
||||
'web_tests': _runWebHtmlUnitTests,
|
||||
// All the unit/widget tests run using `flutter test --platform=chrome --web-renderer=canvaskit`
|
||||
'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
|
||||
'web_long_running_tests': _runWebLongRunningTests,
|
||||
'flutter_plugins': _runFlutterPackagesTests,
|
||||
@ -1117,14 +1119,18 @@ Future<void> _runFrameworkCoverage() async {
|
||||
}
|
||||
|
||||
Future<void> _runWebHtmlUnitTests() {
|
||||
return _runWebUnitTests('html');
|
||||
return _runWebUnitTests('html', false);
|
||||
}
|
||||
|
||||
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 Directory flutterPackageDirectory = Directory(path.join(flutterRoot, 'packages', 'flutter'));
|
||||
@ -1160,6 +1166,7 @@ Future<void> _runWebUnitTests(String webRenderer) async {
|
||||
index * testsPerShard,
|
||||
(index + 1) * testsPerShard,
|
||||
),
|
||||
useWasm,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1175,16 +1182,19 @@ Future<void> _runWebUnitTests(String webRenderer) async {
|
||||
(webShardCount - 1) * testsPerShard,
|
||||
allTests.length,
|
||||
),
|
||||
useWasm,
|
||||
);
|
||||
await _runFlutterWebTest(
|
||||
webRenderer,
|
||||
path.join(flutterRoot, 'packages', 'flutter_web_plugins'),
|
||||
<String>['test'],
|
||||
useWasm,
|
||||
);
|
||||
await _runFlutterWebTest(
|
||||
webRenderer,
|
||||
path.join(flutterRoot, 'packages', 'flutter_driver'),
|
||||
<String>[path.join('test', 'src', 'web_tests', 'web_extension_test.dart')],
|
||||
useWasm,
|
||||
);
|
||||
};
|
||||
|
||||
@ -1333,11 +1343,19 @@ Future<void> _runWebLongRunningTests() async {
|
||||
'html',
|
||||
path.join(flutterRoot, 'packages', 'integration_test'),
|
||||
<String>['test/web_extension_test.dart'],
|
||||
false,
|
||||
),
|
||||
() => _runFlutterWebTest(
|
||||
'canvaskit',
|
||||
path.join(flutterRoot, 'packages', 'integration_test'),
|
||||
<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, {
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
flutter,
|
||||
<String>[
|
||||
'test',
|
||||
'-v',
|
||||
'--platform=chrome',
|
||||
if (useWasm) '--wasm',
|
||||
'--web-renderer=$webRenderer',
|
||||
'--dart-define=DART_HHH_BOT=$_runningInDartHHHBot',
|
||||
...flutterTestArgs,
|
||||
|
@ -2,11 +2,12 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:html' as html;
|
||||
import 'dart:js_interop';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:meta/dart2js.dart';
|
||||
import 'package:web/web.dart' as web;
|
||||
|
||||
// Tests that the framework prints stack traces in all build modes.
|
||||
//
|
||||
@ -35,10 +36,12 @@ Future<void> main() async {
|
||||
}
|
||||
|
||||
print(output);
|
||||
html.HttpRequest.request(
|
||||
'/test-result',
|
||||
method: 'POST',
|
||||
sendData: '$output',
|
||||
web.window.fetch(
|
||||
'/test-result'.toJS,
|
||||
web.RequestInit(
|
||||
method: 'POST',
|
||||
body: '$output'.toJS,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -2,10 +2,13 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// 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 {
|
||||
await html.window.navigator.serviceWorker?.ready;
|
||||
const String response = 'CLOSE?version=1';
|
||||
await html.HttpRequest.getString(response);
|
||||
html.document.body?.appendHtml(response);
|
||||
await web.window.navigator.serviceWorker.ready.toDart;
|
||||
final JSString response = 'CLOSE?version=1'.toJS;
|
||||
await web.window.fetch(response).toDart;
|
||||
web.document.body?.append(response);
|
||||
}
|
||||
|
@ -2,9 +2,12 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// 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 {
|
||||
const String response = 'CLOSE?version=1';
|
||||
await html.HttpRequest.getString(response);
|
||||
html.document.body?.appendHtml(response);
|
||||
final JSString response = 'CLOSE?version=1'.toJS;
|
||||
await web.window.fetch(response).toDart;
|
||||
web.document.body?.append(response);
|
||||
}
|
||||
|
@ -4,7 +4,9 @@
|
||||
|
||||
// @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.
|
||||
void main() {
|
||||
@ -16,9 +18,11 @@ void main() {
|
||||
output = '--- TEST SUCCEEDED ---';
|
||||
}
|
||||
print(output);
|
||||
html.HttpRequest.request(
|
||||
'/test-result',
|
||||
method: 'POST',
|
||||
sendData: output,
|
||||
web.window.fetch(
|
||||
'/test-result'.toJS,
|
||||
web.RequestInit(
|
||||
method: 'POST',
|
||||
body: output.toJS,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -2,11 +2,12 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:html' as html;
|
||||
import 'dart:js_interop';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:meta/dart2js.dart';
|
||||
import 'package:web/web.dart' as web;
|
||||
|
||||
/// Expected sequence of method calls.
|
||||
const List<String> callChain = <String>['baz', 'bar', 'foo'];
|
||||
@ -32,7 +33,7 @@ const List<StackFrame> expectedDebugStackFrames = <StackFrame>[
|
||||
packageScheme: 'package',
|
||||
package: 'packages',
|
||||
packagePath: 'web_integration/stack_trace.dart',
|
||||
line: 119,
|
||||
line: 122,
|
||||
column: 3,
|
||||
className: '<unknown>',
|
||||
method: 'baz',
|
||||
@ -43,7 +44,7 @@ const List<StackFrame> expectedDebugStackFrames = <StackFrame>[
|
||||
packageScheme: 'package',
|
||||
package: 'packages',
|
||||
packagePath: 'web_integration/stack_trace.dart',
|
||||
line: 114,
|
||||
line: 117,
|
||||
column: 3,
|
||||
className: '<unknown>',
|
||||
method: 'bar',
|
||||
@ -54,7 +55,7 @@ const List<StackFrame> expectedDebugStackFrames = <StackFrame>[
|
||||
packageScheme: 'package',
|
||||
package: 'packages',
|
||||
packagePath: 'web_integration/stack_trace.dart',
|
||||
line: 109,
|
||||
line: 112,
|
||||
column: 3,
|
||||
className: '<unknown>',
|
||||
method: 'foo',
|
||||
@ -97,10 +98,12 @@ void main() {
|
||||
output.writeln('--- TEST FAILED ---');
|
||||
}
|
||||
print(output);
|
||||
html.HttpRequest.request(
|
||||
'/test-result',
|
||||
method: 'POST',
|
||||
sendData: '$output',
|
||||
web.window.fetch(
|
||||
'/test-result'.toJS,
|
||||
web.RequestInit(
|
||||
method: 'POST',
|
||||
body: '$output'.toJS,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,9 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// 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 {
|
||||
final StringBuffer output = StringBuffer();
|
||||
@ -16,9 +18,11 @@ Future<void> main() async {
|
||||
print('--- TEST FAILED ---');
|
||||
}
|
||||
|
||||
html.HttpRequest.request(
|
||||
'/test-result',
|
||||
method: 'POST',
|
||||
sendData: '$output',
|
||||
web.window.fetch(
|
||||
'/test-result'.toJS,
|
||||
web.RequestInit(
|
||||
method: 'POST',
|
||||
body: '$output'.toJS,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -2,16 +2,20 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// 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.
|
||||
Future<void> main() async {
|
||||
try {
|
||||
final html.HttpRequest request = await html.HttpRequest.request(
|
||||
'/example',
|
||||
method: 'GET',
|
||||
);
|
||||
final String? body = request.responseText;
|
||||
final web.Response response = await web.window.fetch(
|
||||
'/example'.toJS,
|
||||
web.RequestInit(
|
||||
method: 'GET',
|
||||
),
|
||||
).toDart;
|
||||
final String body = (await response.text().toDart).toDart;
|
||||
if (body == 'This is an Example') {
|
||||
print('--- TEST SUCCEEDED ---');
|
||||
} else {
|
||||
|
@ -2,7 +2,9 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// 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.
|
||||
Future<void> main() async {
|
||||
@ -12,12 +14,13 @@ Future<void> main() async {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final html.HttpRequest request = await html.HttpRequest.request(
|
||||
'https://www.gstatic.com/flutter-canvaskit/$engineVersion/canvaskit.js',
|
||||
method: 'GET',
|
||||
);
|
||||
final dynamic response = request.response;
|
||||
if (response != null) {
|
||||
final web.Response response = await web.window.fetch(
|
||||
'https://www.gstatic.com/flutter-canvaskit/$engineVersion/canvaskit.js'.toJS,
|
||||
web.RequestInit(
|
||||
method: 'GET',
|
||||
),
|
||||
).toDart;
|
||||
if (response.ok) {
|
||||
print('--- TEST SUCCEEDED ---');
|
||||
} else {
|
||||
print('--- TEST FAILED ---');
|
||||
@ -27,12 +30,13 @@ Future<void> main() async {
|
||||
print('--- TEST FAILED ---');
|
||||
}
|
||||
try {
|
||||
final html.HttpRequest request = await html.HttpRequest.request(
|
||||
'https://www.gstatic.com/flutter-canvaskit/$engineVersion/canvaskit.wasm',
|
||||
method: 'GET',
|
||||
);
|
||||
final dynamic response = request.response;
|
||||
if (response != null) {
|
||||
final web.Response response = await web.window.fetch(
|
||||
'https://www.gstatic.com/flutter-canvaskit/$engineVersion/canvaskit.wasm'.toJS,
|
||||
web.RequestInit(
|
||||
method: 'GET',
|
||||
)
|
||||
).toDart;
|
||||
if (response.ok) {
|
||||
print('--- TEST SUCCEEDED ---');
|
||||
} else {
|
||||
print('--- TEST FAILED ---');
|
||||
|
@ -14,10 +14,12 @@ dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
web: 0.5.1
|
||||
|
||||
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"
|
||||
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"
|
||||
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 @@
|
||||
import 'package:web_integration/a.dart'
|
||||
if (dart.library.io) 'package:web_integration/b.dart' as message1;
|
||||
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() {
|
||||
if (message1.message == 'a' && message2.message == 'd') {
|
||||
|
@ -2,12 +2,11 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:html';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:web/web.dart' as web;
|
||||
|
||||
/// 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.
|
||||
///
|
||||
@ -15,8 +14,8 @@ bool get isFirefox => window.navigator.userAgent.toLowerCase().contains('firefox
|
||||
/// `<flt-glass-pane>` element. Otherwise, looks under `<flt-glass-pane>`
|
||||
/// without penetrating the shadow DOM. In the latter case, if the application
|
||||
/// creates platform views, this will also find platform view elements.
|
||||
List<Node> findElements(String selector) {
|
||||
final Element? flutterView = document.querySelector('flutter-view');
|
||||
web.NodeList findElements(String selector) {
|
||||
final web.Element? flutterView = web.document.querySelector('flutter-view');
|
||||
|
||||
if (flutterView == null) {
|
||||
fail(
|
||||
|
@ -2,10 +2,10 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:html' as html;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:web/web.dart' as web;
|
||||
|
||||
void main() => runApp(const MyApp());
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
@ -79,29 +79,25 @@ abstract class DeltaMode {
|
||||
|
||||
void dispatchMouseWheelEvent(int mouseX, int mouseY,
|
||||
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,
|
||||
screenY: mouseY,
|
||||
clientX: mouseX,
|
||||
clientY: mouseY,
|
||||
));
|
||||
)));
|
||||
|
||||
target.dispatchEvent(html.MouseEvent('mousemove',
|
||||
target.dispatchEvent(web.MouseEvent('mousemove', web.MouseEventInit(
|
||||
screenX: mouseX,
|
||||
screenY: mouseY,
|
||||
clientX: mouseX,
|
||||
clientY: mouseY,
|
||||
));
|
||||
)));
|
||||
|
||||
target.dispatchEvent(html.WheelEvent('wheel',
|
||||
screenX: mouseX,
|
||||
screenY: mouseY,
|
||||
clientX: mouseX,
|
||||
clientY: mouseY,
|
||||
target.dispatchEvent(web.WheelEvent('wheel', web.WheelEventInit(
|
||||
deltaMode: deltaMode,
|
||||
deltaX : deltaX,
|
||||
deltaY : deltaY,
|
||||
));
|
||||
)));
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ dependencies:
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
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"
|
||||
boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
@ -82,7 +83,6 @@ dev_dependencies:
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
|
@ -2,13 +2,13 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:html' as html;
|
||||
import 'dart:ui_web' as ui_web;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_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;
|
||||
|
||||
void main() {
|
||||
@ -27,7 +27,7 @@ void main() {
|
||||
await tester.tap(find.byKey(const Key('input')));
|
||||
// Focus in input, otherwise clipboard will fail with
|
||||
// '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'));
|
||||
}, skip: true); // https://github.com/flutter/flutter/issues/54296
|
||||
|
||||
@ -38,7 +38,7 @@ void main() {
|
||||
platformViewsRegistry.getNextPlatformViewId();
|
||||
ui_web.platformViewRegistry.registerViewFactory('MyView', (int viewId) {
|
||||
viewInstanceCount += 1;
|
||||
return html.DivElement();
|
||||
return web.HTMLDivElement();
|
||||
});
|
||||
|
||||
app.main();
|
||||
|
@ -2,13 +2,13 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:html';
|
||||
import 'dart:js_util' as js_util;
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_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/text_editing_main.dart' as app;
|
||||
|
||||
@ -26,9 +26,9 @@ void main() {
|
||||
await tester.tap(find.byKey(const Key('input')));
|
||||
|
||||
// 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));
|
||||
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.
|
||||
expect(input.value, 'Text1');
|
||||
|
||||
@ -50,9 +50,9 @@ void main() {
|
||||
await tester.tap(find.byKey(const Key('empty-input')));
|
||||
|
||||
// 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));
|
||||
final InputElement input = nodeList[0] as InputElement;
|
||||
final web.HTMLInputElement input = nodeList.item(0)! as web.HTMLInputElement;
|
||||
// The element's value will be empty.
|
||||
expect(input.value, '');
|
||||
|
||||
@ -81,7 +81,7 @@ void main() {
|
||||
await tester.tap(find.byKey(const Key('input2')));
|
||||
|
||||
// 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>{
|
||||
'keyCode': 13, // Enter.
|
||||
'cancelable': true,
|
||||
@ -111,9 +111,9 @@ void main() {
|
||||
await tester.tap(find.byKey(const Key('input')));
|
||||
|
||||
// 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));
|
||||
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.
|
||||
dispatchKeyboardEvent(input, 'keydown', <String, dynamic>{
|
||||
@ -136,7 +136,7 @@ void main() {
|
||||
|
||||
// A native input element for the next TextField should be attached to the
|
||||
// DOM.
|
||||
final InputElement input2 = findElements('input')[0] as InputElement;
|
||||
final web.HTMLInputElement input2 = findElements('input').item(0)! as web.HTMLInputElement;
|
||||
expect(input2.value, 'Text2');
|
||||
}, semanticsEnabled: false);
|
||||
|
||||
@ -150,9 +150,9 @@ void main() {
|
||||
await tester.tap(find.byKey(const Key('input')));
|
||||
|
||||
// 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));
|
||||
final InputElement input = nodeList[0] as InputElement;
|
||||
final web.HTMLInputElement input = nodeList.item(0)! as web.HTMLInputElement;
|
||||
|
||||
// Press and release CapsLock.
|
||||
dispatchKeyboardEvent(input, 'keydown', <String, dynamic>{
|
||||
@ -191,7 +191,7 @@ void main() {
|
||||
|
||||
// A native input element for the next TextField should be attached to the
|
||||
// DOM.
|
||||
final InputElement input2 = findElements('input')[0] as InputElement;
|
||||
final web.HTMLInputElement input2 = findElements('input').item(0)! as web.HTMLInputElement;
|
||||
expect(input2.value, 'Text2');
|
||||
}, semanticsEnabled: false);
|
||||
|
||||
@ -215,16 +215,16 @@ void main() {
|
||||
await gesture.up();
|
||||
|
||||
// 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));
|
||||
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.
|
||||
expect(input.value, text);
|
||||
expect(input.hasAttribute('readonly'), isTrue);
|
||||
|
||||
// Make sure the entire text is selected.
|
||||
TextRange? range =
|
||||
TextRange(start: input.selectionStart!, end: input.selectionEnd!);
|
||||
TextRange(start: input.selectionStart, end: input.selectionEnd);
|
||||
expect(range.textInside(text), text);
|
||||
|
||||
// Double tap to select the first word.
|
||||
@ -236,7 +236,7 @@ void main() {
|
||||
await gesture.up();
|
||||
await gesture.down(firstWordOffset);
|
||||
await gesture.up();
|
||||
range = TextRange(start: input.selectionStart!, end: input.selectionEnd!);
|
||||
range = TextRange(start: input.selectionStart, end: input.selectionEnd);
|
||||
expect(range.textInside(text), 'Lorem');
|
||||
|
||||
// Double tap to select the last word.
|
||||
@ -248,21 +248,21 @@ void main() {
|
||||
await gesture.up();
|
||||
await gesture.down(lastWordOffset);
|
||||
await gesture.up();
|
||||
range = TextRange(start: input.selectionStart!, end: input.selectionEnd!);
|
||||
range = TextRange(start: input.selectionStart, end: input.selectionEnd);
|
||||
expect(range.textInside(text), 'amet');
|
||||
}, semanticsEnabled: false);
|
||||
}
|
||||
|
||||
KeyboardEvent dispatchKeyboardEvent(
|
||||
EventTarget target, String type, Map<String, dynamic> args) {
|
||||
final Object jsKeyboardEvent = js_util.getProperty(window, 'KeyboardEvent') as Object;
|
||||
web.KeyboardEvent dispatchKeyboardEvent(
|
||||
web.EventTarget target, String type, Map<String, dynamic> args) {
|
||||
final Object jsKeyboardEvent = js_util.getProperty(web.window, 'KeyboardEvent') as Object;
|
||||
final List<dynamic> eventArgs = <dynamic>[
|
||||
type,
|
||||
args,
|
||||
];
|
||||
final KeyboardEvent event = js_util.callConstructor(
|
||||
final web.KeyboardEvent event = js_util.callConstructor(
|
||||
jsKeyboardEvent, js_util.jsify(eventArgs) as List<dynamic>)
|
||||
as KeyboardEvent;
|
||||
as web.KeyboardEvent;
|
||||
target.dispatchEvent(event);
|
||||
|
||||
return event;
|
||||
|
@ -3,13 +3,16 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:html' as html;
|
||||
import 'dart:js_interop';
|
||||
import 'dart:ui' as ui;
|
||||
import 'dart:ui_web';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_web_plugins/flutter_web_plugins.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;
|
||||
|
||||
void main() {
|
||||
@ -56,7 +59,7 @@ class TestUrlStrategy extends UrlStrategy {
|
||||
String getPath() => currentEntry.url;
|
||||
|
||||
@override
|
||||
dynamic getState() => currentEntry.state;
|
||||
Object? getState() => currentEntry.state;
|
||||
|
||||
int _currentEntryIndex;
|
||||
final List<TestHistoryEntry> history;
|
||||
@ -111,10 +114,10 @@ class TestUrlStrategy extends UrlStrategy {
|
||||
});
|
||||
}
|
||||
|
||||
final List<html.EventListener> listeners = <html.EventListener>[];
|
||||
final List<PopStateListener> listeners = <PopStateListener>[];
|
||||
|
||||
@override
|
||||
ui.VoidCallback addPopStateListener(html.EventListener fn) {
|
||||
ui.VoidCallback addPopStateListener(PopStateListener fn) {
|
||||
listeners.add(fn);
|
||||
return () {
|
||||
// Schedule a micro task here to avoid removing the listener during
|
||||
@ -134,9 +137,9 @@ class TestUrlStrategy extends UrlStrategy {
|
||||
/// like a real browser.
|
||||
void _firePopStateEvent() {
|
||||
assert(withinAppHistory);
|
||||
final html.PopStateEvent event = html.PopStateEvent(
|
||||
final web.PopStateEvent event = web.PopStateEvent(
|
||||
'popstate',
|
||||
<String, dynamic>{'state': currentEntry.state},
|
||||
PopStateEventInit(state: currentEntry.state?.toJSBox),
|
||||
);
|
||||
for (int i = 0; i < listeners.length; i++) {
|
||||
listeners[i](event);
|
||||
@ -160,7 +163,7 @@ class TestUrlStrategy extends UrlStrategy {
|
||||
class TestHistoryEntry {
|
||||
const TestHistoryEntry(this.state, this.title, this.url);
|
||||
|
||||
final dynamic state;
|
||||
final Object? state;
|
||||
final String? title;
|
||||
final String url;
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
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) {
|
||||
// Enable golden file testing using Skia Gold.
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
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
|
||||
/// the SMI range and avoid heap allocations. Because number encoding is
|
||||
@ -13,14 +13,14 @@ import 'bitfield.dart' as bitfield;
|
||||
/// does not have to guarantee efficiency.
|
||||
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> {
|
||||
/// The dart:html implementation of [bitfield.Bitfield].
|
||||
/// The web implementation of [bitfield.Bitfield].
|
||||
// Can remove when we have metaclasses.
|
||||
// ignore: avoid_unused_constructor_parameters
|
||||
BitField(int length);
|
||||
|
||||
/// The dart:html implementation of [bitfield.Bitfield.filled].
|
||||
/// The web implementation of [bitfield.Bitfield.filled].
|
||||
// Can remove when we have metaclasses.
|
||||
// ignore: avoid_unused_constructor_parameters
|
||||
BitField.filled(int length, bool value);
|
||||
|
@ -6,7 +6,7 @@ import 'isolates.dart' as isolates;
|
||||
|
||||
export 'isolates.dart' show ComputeCallback;
|
||||
|
||||
/// The dart:html implementation of [isolate.compute].
|
||||
/// The web implementation of [isolate.compute].
|
||||
@pragma('dart2js:tryInline')
|
||||
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
|
||||
|
@ -10,7 +10,7 @@ import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.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;
|
||||
|
||||
/// If true, leak tracking is enabled for all `testWidgets`.
|
||||
|
@ -6,7 +6,7 @@
|
||||
///
|
||||
/// 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) {
|
||||
throw UnsupportedError('Use registerServiceExtension instead');
|
||||
}
|
||||
|
@ -3,11 +3,13 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:html' as html;
|
||||
import 'dart:js';
|
||||
import 'dart:js_util' as js_util;
|
||||
import 'dart:js_interop';
|
||||
import 'dart:js_interop_unsafe';
|
||||
|
||||
/// 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.
|
||||
///
|
||||
@ -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
|
||||
// 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.
|
||||
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(
|
||||
jsonDecode(message as String) as Map<String, dynamic>);
|
||||
final Map<String, dynamic> result = Map<String, dynamic>.from(
|
||||
await call(params));
|
||||
context[r'$flutterDriverResult'] = json.encode(result);
|
||||
}));
|
||||
jsonDecode((message as JSString).toDart) as Map<String, dynamic>);
|
||||
call(params).then((Map<String, dynamic> result) {
|
||||
_window.setProperty(r'$flutterDriverResult'.toJS, json.encode(result).toJS);
|
||||
});
|
||||
}.toJS);
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ import '../common/error.dart';
|
||||
import '../common/find.dart';
|
||||
import '../common/handler_factory.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';
|
||||
|
||||
|
@ -2,11 +2,15 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// 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_test/flutter_test.dart';
|
||||
|
||||
@JS('window')
|
||||
external JSObject get _window;
|
||||
|
||||
void main() {
|
||||
group('test web_extension', () {
|
||||
late Future<Map<String, dynamic>> Function(Map<String, String>) call;
|
||||
@ -21,11 +25,11 @@ void main() {
|
||||
expect(() => registerWebServiceExtension(call),
|
||||
returnsNormally);
|
||||
|
||||
expect(js.context.hasProperty(r'$flutterDriver'), true);
|
||||
expect(js.context[r'$flutterDriver'], isNotNull);
|
||||
expect(_window.hasProperty(r'$flutterDriver'.toJS).toDart, true);
|
||||
expect(_window.getProperty(r'$flutterDriver'.toJS), isNotNull);
|
||||
|
||||
expect(js.context.hasProperty(r'$flutterDriverResult'), true);
|
||||
expect(js.context[r'$flutterDriverResult'], isNull);
|
||||
expect(_window.hasProperty(r'$flutterDriverResult'.toJS).toDart, true);
|
||||
expect(_window.getProperty(r'$flutterDriverResult'.toJS), isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -55,8 +55,9 @@ library flutter_test;
|
||||
|
||||
export 'dart:async' show Future;
|
||||
|
||||
export 'src/_goldens_io.dart' if (dart.library.html) 'src/_goldens_web.dart';
|
||||
export 'src/_matchers_io.dart' if (dart.library.html) 'src/_matchers_web.dart';
|
||||
export 'src/_goldens_io.dart' if (dart.library.js_interop) 'src/_goldens_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/animation_sheet.dart';
|
||||
export 'src/binding.dart';
|
||||
|
@ -292,6 +292,11 @@ ByteData _invert(ByteData imageBytes) {
|
||||
|
||||
/// An unsupported [WebGoldenComparator] that exists for API compatibility.
|
||||
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
|
||||
Future<bool> compare(double width, double height, Uri golden) {
|
||||
throw UnsupportedError('DefaultWebGoldenComparator is only supported on the web.');
|
||||
|
@ -3,12 +3,13 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:html' as html;
|
||||
import 'dart:js_interop';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:matcher/expect.dart' show fail;
|
||||
|
||||
import 'goldens.dart';
|
||||
import 'web.dart' as web;
|
||||
|
||||
/// An unsupported [GoldenFileComparator] that exists for API compatibility.
|
||||
class LocalFileComparator extends GoldenFileComparator {
|
||||
@ -58,21 +59,23 @@ class DefaultWebGoldenComparator extends WebGoldenComparator {
|
||||
@override
|
||||
Future<bool> compare(double width, double height, Uri golden) async {
|
||||
final String key = golden.toString();
|
||||
final html.HttpRequest request = await html.HttpRequest.request(
|
||||
'flutter_goldens',
|
||||
method: 'POST',
|
||||
sendData: json.encode(<String, Object>{
|
||||
'testUri': testUri.toString(),
|
||||
'key': key,
|
||||
'width': width.round(),
|
||||
'height': height.round(),
|
||||
}),
|
||||
);
|
||||
final String response = request.response as String;
|
||||
if (response == 'true') {
|
||||
final web.Response response = await web.window.fetch(
|
||||
'flutter_goldens'.toJS,
|
||||
web.RequestInit(
|
||||
method: 'POST',
|
||||
body: json.encode(<String, Object>{
|
||||
'testUri': testUri.toString(),
|
||||
'key': key,
|
||||
'width': width.round(),
|
||||
'height': height.round(),
|
||||
}).toJS,
|
||||
)
|
||||
).toDart;
|
||||
final String responseText = (await response.text().toDart).toDart;
|
||||
if (responseText == 'true') {
|
||||
return true;
|
||||
}
|
||||
fail(response);
|
||||
fail(responseText);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -85,20 +88,22 @@ class DefaultWebGoldenComparator extends WebGoldenComparator {
|
||||
Future<bool> compareBytes(Uint8List bytes, Uri golden) async {
|
||||
final String key = golden.toString();
|
||||
final String bytesEncoded = base64.encode(bytes);
|
||||
final html.HttpRequest request = await html.HttpRequest.request(
|
||||
'flutter_goldens',
|
||||
method: 'POST',
|
||||
sendData: json.encode(<String, Object>{
|
||||
'testUri': testUri.toString(),
|
||||
'key': key,
|
||||
'bytes': bytesEncoded,
|
||||
}),
|
||||
);
|
||||
final String response = request.response as String;
|
||||
if (response == 'true') {
|
||||
final web.Response response = await web.window.fetch(
|
||||
'flutter_goldens'.toJS,
|
||||
web.RequestInit(
|
||||
method: 'POST',
|
||||
body: json.encode(<String, Object>{
|
||||
'testUri': testUri.toString(),
|
||||
'key': key,
|
||||
'bytes': bytesEncoded,
|
||||
}).toJS,
|
||||
)
|
||||
).toDart;
|
||||
final String responseText = (await response.text().toDart).toDart;
|
||||
if (responseText == 'true') {
|
||||
return true;
|
||||
}
|
||||
fail(response);
|
||||
fail(responseText);
|
||||
}
|
||||
|
||||
@override
|
||||
|
6
packages/flutter_test/lib/src/_test_selector_io.dart
Normal file
6
packages/flutter_test/lib/src/_test_selector_io.dart
Normal file
@ -0,0 +1,6 @@
|
||||
// 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.
|
85
packages/flutter_test/lib/src/_test_selector_web.dart
Normal file
85
packages/flutter_test/lib/src/_test_selector_web.dart
Normal file
@ -0,0 +1,85 @@
|
||||
// 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;
|
||||
import 'package:test_api/scaffolding.dart' as test_package show Timeout;
|
||||
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 'platform.dart';
|
||||
import 'restoration.dart';
|
||||
|
@ -8,7 +8,7 @@ import 'dart:ui';
|
||||
import 'package:meta/meta.dart';
|
||||
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.
|
||||
///
|
||||
|
@ -14,7 +14,7 @@ import 'package:matcher/expect.dart';
|
||||
import 'package:matcher/src/expect/async_matcher.dart'; // ignore: implementation_imports
|
||||
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 'binding.dart';
|
||||
import 'controller.dart';
|
||||
|
74
packages/flutter_test/lib/src/web.dart
Normal file
74
packages/flutter_test/lib/src/web.dart
Normal file
@ -0,0 +1,74 @@
|
||||
// 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;
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
name: flutter_test
|
||||
|
||||
environment:
|
||||
sdk: '>=3.2.0-0 <4.0.0'
|
||||
sdk: '>=3.3.0-0 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
# To update these, use "flutter update-packages --force-upgrade".
|
||||
|
@ -12,6 +12,7 @@ import '../build_info.dart';
|
||||
import '../bundle_builder.dart';
|
||||
import '../devfs.dart';
|
||||
import '../device.dart';
|
||||
import '../features.dart';
|
||||
import '../globals.dart' as globals;
|
||||
import '../native_assets.dart';
|
||||
import '../project.dart';
|
||||
@ -23,6 +24,7 @@ import '../test/test_time_recorder.dart';
|
||||
import '../test/test_wrapper.dart';
|
||||
import '../test/watcher.dart';
|
||||
import '../web/compile.dart';
|
||||
import '../web/web_constants.dart';
|
||||
|
||||
/// The name of the directory where Integration Tests are placed.
|
||||
///
|
||||
@ -233,7 +235,14 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
|
||||
'in seconds (e.g. "60s"), '
|
||||
'as a multiplier of the default timeout (e.g. "2x"), '
|
||||
'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);
|
||||
addServeObservatoryOptions(verboseHelp: verboseHelp);
|
||||
usesFatalWarningsOption(verboseHelp: verboseHelp);
|
||||
@ -256,6 +265,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
|
||||
final Set<Uri> _testFileUris = <Uri>{};
|
||||
|
||||
bool get isWeb => stringArg('platform') == 'chrome';
|
||||
bool get useWasm => boolArg(FlutterOptions.kWebWasmFlag);
|
||||
|
||||
@override
|
||||
Future<Set<DevelopmentArtifact>> get requiredArtifacts async {
|
||||
@ -499,6 +509,10 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
|
||||
watcher = collector;
|
||||
}
|
||||
|
||||
if (!isWeb && useWasm) {
|
||||
throwToolExit('--wasm is only supported on the web platform');
|
||||
}
|
||||
|
||||
Device? integrationTestDevice;
|
||||
if (_isIntegrationTest) {
|
||||
integrationTestDevice = await findTargetDevice();
|
||||
@ -577,6 +591,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
|
||||
testAssetDirectory: testAssetDirectory,
|
||||
flutterProject: flutterProject,
|
||||
web: isWeb,
|
||||
useWasm: useWasm,
|
||||
randomSeed: stringArg('test-randomize-ordering-seed'),
|
||||
reporter: stringArg('reporter'),
|
||||
fileReporter: stringArg('file-reporter'),
|
||||
|
@ -37,7 +37,7 @@ import 'flutter_web_goldens.dart';
|
||||
import 'test_compiler.dart';
|
||||
import 'test_time_recorder.dart';
|
||||
|
||||
shelf.Handler createDirectoryHandler(Directory directory) {
|
||||
shelf.Handler createDirectoryHandler(Directory directory, { required bool crossOriginIsolated} ) {
|
||||
final mime.MimeTypeResolver resolver = mime.MimeTypeResolver();
|
||||
final FileSystem fileSystem = directory.fileSystem;
|
||||
return (shelf.Request request) async {
|
||||
@ -56,10 +56,16 @@ shelf.Handler createDirectoryHandler(Directory directory) {
|
||||
return shelf.Response.notFound('Not Found');
|
||||
}
|
||||
final String? contentType = resolver.lookup(file.path);
|
||||
final bool needsCrossOriginIsolated = crossOriginIsolated && uriPath.endsWith('.html');
|
||||
return shelf.Response.ok(
|
||||
file.openRead(),
|
||||
headers: <String, String>{
|
||||
if (contentType != null) 'Content-Type': contentType
|
||||
if (contentType != null) 'Content-Type': contentType,
|
||||
if (needsCrossOriginIsolated)
|
||||
...<String, String>{
|
||||
'Cross-Origin-Opener-Policy': 'same-origin',
|
||||
'Cross-Origin-Embedder-Policy': 'require-corp',
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
@ -74,6 +80,7 @@ class FlutterWebPlatform extends PlatformPlugin {
|
||||
required this.buildInfo,
|
||||
required this.webMemoryFS,
|
||||
required FileSystem fileSystem,
|
||||
required Directory buildDirectory,
|
||||
required File testDartJs,
|
||||
required File testHostDartJs,
|
||||
required ChromiumLauncher chromiumLauncher,
|
||||
@ -81,8 +88,10 @@ class FlutterWebPlatform extends PlatformPlugin {
|
||||
required Artifacts? artifacts,
|
||||
required ProcessManager processManager,
|
||||
required this.webRenderer,
|
||||
required this.useWasm,
|
||||
TestTimeRecorder? testTimeRecorder,
|
||||
}) : _fileSystem = fileSystem,
|
||||
_buildDirectory = buildDirectory,
|
||||
_testDartJs = testDartJs,
|
||||
_testHostDartJs = testHostDartJs,
|
||||
_chromiumLauncher = chromiumLauncher,
|
||||
@ -92,6 +101,7 @@ class FlutterWebPlatform extends PlatformPlugin {
|
||||
.add(_webSocketHandler.handler)
|
||||
.add(createDirectoryHandler(
|
||||
fileSystem.directory(fileSystem.path.join(Cache.flutterRoot!, 'packages', 'flutter_tools')),
|
||||
crossOriginIsolated: webRenderer == WebRendererMode.skwasm,
|
||||
))
|
||||
.add(_handleStaticArtifact)
|
||||
.add(_localCanvasKitHandler)
|
||||
@ -99,7 +109,8 @@ class FlutterWebPlatform extends PlatformPlugin {
|
||||
.add(_wrapperHandler)
|
||||
.add(_handleTestRequest)
|
||||
.add(createDirectoryHandler(
|
||||
fileSystem.directory(fileSystem.path.join(fileSystem.currentDirectory.path, 'test'))
|
||||
fileSystem.directory(fileSystem.path.join(fileSystem.currentDirectory.path, 'test')),
|
||||
crossOriginIsolated: webRenderer == WebRendererMode.skwasm,
|
||||
))
|
||||
.add(_packageFilesHandler);
|
||||
_server.mount(cascade.handler);
|
||||
@ -116,6 +127,7 @@ class FlutterWebPlatform extends PlatformPlugin {
|
||||
final WebMemoryFS webMemoryFS;
|
||||
final BuildInfo buildInfo;
|
||||
final FileSystem _fileSystem;
|
||||
final Directory _buildDirectory;
|
||||
final File _testDartJs;
|
||||
final File _testHostDartJs;
|
||||
final ChromiumLauncher _chromiumLauncher;
|
||||
@ -127,6 +139,7 @@ class FlutterWebPlatform extends PlatformPlugin {
|
||||
final AsyncMemoizer<void> _closeMemo = AsyncMemoizer<void>();
|
||||
final String _root;
|
||||
final WebRendererMode webRenderer;
|
||||
final bool useWasm;
|
||||
|
||||
/// Allows only one test suite (typically one test file) to be loaded and run
|
||||
/// at any given point in time. Loading more than one file at a time is known
|
||||
@ -149,11 +162,13 @@ class FlutterWebPlatform extends PlatformPlugin {
|
||||
required BuildInfo buildInfo,
|
||||
required WebMemoryFS webMemoryFS,
|
||||
required FileSystem fileSystem,
|
||||
required Directory buildDirectory,
|
||||
required Logger logger,
|
||||
required ChromiumLauncher chromiumLauncher,
|
||||
required Artifacts? artifacts,
|
||||
required ProcessManager processManager,
|
||||
required WebRendererMode webRenderer,
|
||||
required bool useWasm,
|
||||
TestTimeRecorder? testTimeRecorder,
|
||||
Uri? testPackageUri,
|
||||
Future<shelf.Server> Function() serverFactory = defaultServerFactory,
|
||||
@ -196,12 +211,14 @@ class FlutterWebPlatform extends PlatformPlugin {
|
||||
testDartJs: testDartJs,
|
||||
testHostDartJs: testHostDartJs,
|
||||
fileSystem: fileSystem,
|
||||
buildDirectory: buildDirectory,
|
||||
chromiumLauncher: chromiumLauncher,
|
||||
artifacts: artifacts,
|
||||
logger: logger,
|
||||
nullAssertions: nullAssertions,
|
||||
processManager: processManager,
|
||||
webRenderer: webRenderer,
|
||||
useWasm: useWasm,
|
||||
testTimeRecorder: testTimeRecorder,
|
||||
);
|
||||
}
|
||||
@ -254,6 +271,11 @@ class FlutterWebPlatform extends PlatformPlugin {
|
||||
'dart_stack_trace_mapper.js',
|
||||
));
|
||||
|
||||
File get _flutterJs => _fileSystem.file(_fileSystem.path.join(
|
||||
_artifacts!.getHostArtifact(HostArtifact.flutterJsDirectory).path,
|
||||
'flutter.js',
|
||||
));
|
||||
|
||||
File get _dartSdk {
|
||||
final Map<WebRendererMode, Map<NullSafetyMode, HostArtifact>> dartSdkArtifactMap = buildInfo.ddcModuleFormat == DdcModuleFormat.ddc ? kDdcDartSdkJsArtifactMap : kAmdDartSdkJsArtifactMap;
|
||||
return _fileSystem.file(_artifacts!.getHostArtifact(dartSdkArtifactMap[webRenderer]![_nullSafetyMode]!));
|
||||
@ -277,21 +299,20 @@ class FlutterWebPlatform extends PlatformPlugin {
|
||||
}
|
||||
|
||||
Future<shelf.Response> _handleTestRequest(shelf.Request request) async {
|
||||
if (request.url.path.endsWith('.dart.browser_test.dart.js')) {
|
||||
final String leadingPath = request.url.path.split('.browser_test.dart.js')[0];
|
||||
final String generatedFile = '${_fileSystem.path.split(leadingPath).join('_')}.bootstrap.js';
|
||||
return shelf.Response.ok(generateTestBootstrapFileContents('/$generatedFile', 'require.js', 'dart_stack_trace_mapper.js'), headers: <String, String>{
|
||||
HttpHeaders.contentTypeHeader: 'text/javascript',
|
||||
});
|
||||
if (request.url.path.endsWith('main.dart.browser_test.dart.js')) {
|
||||
return shelf.Response.ok(generateTestBootstrapFileContents(
|
||||
'/main.dart.bootstrap.js', 'require.js', 'dart_stack_trace_mapper.js'),
|
||||
headers: <String, String>{
|
||||
HttpHeaders.contentTypeHeader: 'text/javascript',
|
||||
}
|
||||
);
|
||||
}
|
||||
if (request.url.path.endsWith('.dart.bootstrap.js')) {
|
||||
final String leadingPath = request.url.path.split('.dart.bootstrap.js')[0];
|
||||
final String generatedFile = '${_fileSystem.path.split(leadingPath).join('_')}.dart.test.dart.js';
|
||||
if (request.url.path.endsWith('main.dart.bootstrap.js')) {
|
||||
return shelf.Response.ok(generateMainModule(
|
||||
nullAssertions: nullAssertions!,
|
||||
nativeNullAssertions: true,
|
||||
bootstrapModule: '${_fileSystem.path.basename(leadingPath)}.dart.bootstrap',
|
||||
entrypoint: '/$generatedFile'
|
||||
bootstrapModule: 'main.dart.bootstrap',
|
||||
entrypoint: '/main.dart.js'
|
||||
), headers: <String, String>{
|
||||
HttpHeaders.contentTypeHeader: 'text/javascript',
|
||||
});
|
||||
@ -349,6 +370,21 @@ class FlutterWebPlatform extends PlatformPlugin {
|
||||
_testHostDartJs.openRead(),
|
||||
headers: <String, String>{'Content-Type': 'text/javascript'},
|
||||
);
|
||||
} else if (request.requestedUri.path.contains('flutter.js')) {
|
||||
return shelf.Response.ok(
|
||||
_flutterJs.openRead(),
|
||||
headers: <String, String>{'Content-Type': 'text/javascript'},
|
||||
);
|
||||
} else if (request.requestedUri.path.contains('main.dart.mjs')) {
|
||||
return shelf.Response.ok(
|
||||
_buildDirectory.childFile('main.dart.mjs').openRead(),
|
||||
headers: <String, String>{'Content-Type': 'text/javascript'},
|
||||
);
|
||||
} else if (request.requestedUri.path.contains('main.dart.wasm')) {
|
||||
return shelf.Response.ok(
|
||||
_buildDirectory.childFile('main.dart.wasm').openRead(),
|
||||
headers: <String, String>{'Content-Type': 'application/wasm'},
|
||||
);
|
||||
} else {
|
||||
return shelf.Response.notFound('Not Found');
|
||||
}
|
||||
@ -363,7 +399,10 @@ class FlutterWebPlatform extends PlatformPlugin {
|
||||
if (fileUri != null) {
|
||||
final String dirname = _fileSystem.path.dirname(fileUri.toFilePath());
|
||||
final String basename = _fileSystem.path.basename(fileUri.toFilePath());
|
||||
final shelf.Handler handler = createDirectoryHandler(_fileSystem.directory(dirname));
|
||||
final shelf.Handler handler = createDirectoryHandler(
|
||||
_fileSystem.directory(dirname),
|
||||
crossOriginIsolated: webRenderer == WebRendererMode.skwasm,
|
||||
);
|
||||
final shelf.Request modifiedRequest = shelf.Request(
|
||||
request.method,
|
||||
request.requestedUri.replace(path: basename),
|
||||
@ -452,36 +491,66 @@ class FlutterWebPlatform extends PlatformPlugin {
|
||||
return shelf.Response.internalServerError(body: error);
|
||||
}
|
||||
|
||||
final File canvasKitFile = _canvasKitFile(relativePath);
|
||||
return shelf.Response.ok(
|
||||
_canvasKitFile(relativePath).openRead(),
|
||||
canvasKitFile.openRead(),
|
||||
headers: <String, Object>{
|
||||
HttpHeaders.contentTypeHeader: contentType,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
String _makeBuildConfigString() {
|
||||
return useWasm ? '''
|
||||
{
|
||||
compileTarget: "dart2wasm",
|
||||
renderer: "${webRenderer.name}",
|
||||
mainWasmPath: "main.dart.wasm",
|
||||
jsSupportRuntimePath: "main.dart.mjs",
|
||||
}
|
||||
''' : '''
|
||||
{
|
||||
compileTarget: "dartdevc",
|
||||
renderer: "${webRenderer.name}",
|
||||
mainJsPath: "main.dart.browser_test.dart.js",
|
||||
}
|
||||
''';
|
||||
}
|
||||
|
||||
// A handler that serves wrapper files used to bootstrap tests.
|
||||
shelf.Response _wrapperHandler(shelf.Request request) {
|
||||
final String path = _fileSystem.path.fromUri(request.url);
|
||||
if (path.endsWith('.html')) {
|
||||
final String test = '${_fileSystem.path.withoutExtension(path)}.dart';
|
||||
final String scriptBase = htmlEscape.convert(_fileSystem.path.basename(test));
|
||||
final String link = '<link rel="x-dart-test" href="$scriptBase">';
|
||||
return shelf.Response.ok('''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>${htmlEscape.convert(test)} Test</title>
|
||||
<script src="flutter.js"></script>
|
||||
<script>
|
||||
window.flutterConfiguration = {
|
||||
canvasKitBaseUrl: "/canvaskit/"
|
||||
};
|
||||
_flutter.buildConfig = {
|
||||
builds: [
|
||||
${_makeBuildConfigString()}
|
||||
]
|
||||
}
|
||||
window.testSelector = "$test";
|
||||
_flutter.loader.load({
|
||||
config: {
|
||||
canvasKitBaseUrl: "/canvaskit/",
|
||||
}
|
||||
});
|
||||
</script>
|
||||
$link
|
||||
<script src="static/dart.js"></script>
|
||||
</head>
|
||||
</html>
|
||||
''', headers: <String, String>{'Content-Type': 'text/html'});
|
||||
''', headers: <String, String>{
|
||||
'Content-Type': 'text/html',
|
||||
if (webRenderer == WebRendererMode.skwasm)
|
||||
...<String, String>{
|
||||
'Cross-Origin-Opener-Policy': 'same-origin',
|
||||
'Cross-Origin-Embedder-Policy': 'require-corp',
|
||||
}
|
||||
});
|
||||
}
|
||||
return shelf.Response.notFound('Not found.');
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ abstract class FlutterTestRunner {
|
||||
String? icudtlPath,
|
||||
Directory? coverageDirectory,
|
||||
bool web = false,
|
||||
bool useWasm = false,
|
||||
String? randomSeed,
|
||||
String? reporter,
|
||||
String? fileReporter,
|
||||
@ -117,6 +118,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner {
|
||||
String? icudtlPath,
|
||||
Directory? coverageDirectory,
|
||||
bool web = false,
|
||||
bool useWasm = false,
|
||||
String? randomSeed,
|
||||
String? reporter,
|
||||
String? fileReporter,
|
||||
@ -186,6 +188,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner {
|
||||
testFiles: testFiles.map((Uri uri) => uri.toFilePath()).toList(),
|
||||
buildInfo: debuggingOptions.buildInfo,
|
||||
webRenderer: debuggingOptions.webRenderer,
|
||||
useWasm: useWasm,
|
||||
);
|
||||
testArgs
|
||||
..add('--platform=chrome')
|
||||
@ -205,6 +208,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner {
|
||||
webMemoryFS: result,
|
||||
logger: globals.logger,
|
||||
fileSystem: globals.fs,
|
||||
buildDirectory: globals.fs.directory(tempBuildDir),
|
||||
artifacts: globals.artifacts,
|
||||
processManager: globals.processManager,
|
||||
chromiumLauncher: ChromiumLauncher(
|
||||
@ -217,6 +221,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner {
|
||||
),
|
||||
testTimeRecorder: testTimeRecorder,
|
||||
webRenderer: debuggingOptions.webRenderer,
|
||||
useWasm: useWasm,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -3,6 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:package_config/package_config.dart';
|
||||
import 'package:package_config/package_config_types.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import '../artifacts.dart';
|
||||
@ -11,6 +12,7 @@ import '../base/config.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../base/platform.dart';
|
||||
import '../base/process.dart';
|
||||
import '../build_info.dart';
|
||||
import '../bundle.dart';
|
||||
import '../cache.dart';
|
||||
@ -44,12 +46,74 @@ class WebTestCompiler {
|
||||
final ProcessManager _processManager;
|
||||
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({
|
||||
required Directory projectDirectory,
|
||||
required String testOutputDir,
|
||||
required List<String> testFiles,
|
||||
required BuildInfo buildInfo,
|
||||
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 {
|
||||
LanguageVersion languageVersion = LanguageVersion(2, 8);
|
||||
late final String platformDillName;
|
||||
@ -78,32 +142,12 @@ class WebTestCompiler {
|
||||
|
||||
final Directory outputDirectory = _fileSystem.directory(testOutputDir)
|
||||
..createSync(recursive: true);
|
||||
final List<File> generatedFiles = <File>[];
|
||||
for (final String testFilePath in testFiles) {
|
||||
final List<String> relativeTestSegments = _fileSystem.path.split(
|
||||
_fileSystem.path.relative(testFilePath, from: projectDirectory.childDirectory('test').path));
|
||||
final File generatedFile = _fileSystem.file(
|
||||
_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 File testFile = await _generateTestEntrypoint(
|
||||
testFiles: testFiles,
|
||||
projectDirectory: projectDirectory,
|
||||
outputDirectory: outputDirectory,
|
||||
languageVersion: languageVersion
|
||||
);
|
||||
|
||||
final String cachedKernelPath = getDefaultCachedKernelPath(
|
||||
trackWidgetCreation: buildInfo.trackWidgetCreation,
|
||||
@ -139,7 +183,7 @@ class WebTestCompiler {
|
||||
);
|
||||
|
||||
final CompilerOutput? output = await residentCompiler.recompile(
|
||||
Uri.parse('org-dartlang-app:///main.dart'),
|
||||
Uri.parse('org-dartlang-app:///${testFile.basename}'),
|
||||
<Uri>[],
|
||||
outputPath: outputDirectory.childFile('out').path,
|
||||
packageConfig: buildInfo.packageConfig,
|
||||
@ -157,7 +201,67 @@ class WebTestCompiler {
|
||||
final File manifestFile = outputDirectory.childFile('${output.outputFilename}.json');
|
||||
final File sourcemapFile = outputDirectory.childFile('${output.outputFilename}.map');
|
||||
final File metadataFile = outputDirectory.childFile('${output.outputFilename}.metadata');
|
||||
|
||||
return WebMemoryFS()
|
||||
..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)
|
||||
''';
|
||||
}
|
||||
|
||||
/// 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({
|
||||
required String relativeTestPath,
|
||||
required String absolutePath,
|
||||
required String? testConfigPath,
|
||||
required List<WebTestInfo> testInfos,
|
||||
required LanguageVersion languageVersion,
|
||||
}) {
|
||||
final List<String> importMainStatements = <String>[];
|
||||
final List<String> importTestConfigStatements = <String>[];
|
||||
final List<String> webTestPairs = <String>[];
|
||||
|
||||
for (int index = 0; index < testInfos.length; index++) {
|
||||
final WebTestInfo testInfo = testInfos[index];
|
||||
final String entryPointPath = testInfo.entryPoint;
|
||||
importMainStatements.add("import 'org-dartlang-app:///${Uri.file(entryPointPath)}' as test_$index show main;");
|
||||
|
||||
final String? testConfigPath = testInfo.configFile;
|
||||
String? testConfigFunction = 'null';
|
||||
if (testConfigPath != null) {
|
||||
importTestConfigStatements.add(
|
||||
"import 'org-dartlang-app:///${Uri.file(testConfigPath)}' as test_config_$index show testExecutable;"
|
||||
);
|
||||
testConfigFunction = 'test_config_$index.testExecutable';
|
||||
}
|
||||
webTestPairs.add('''
|
||||
'$entryPointPath': (
|
||||
entryPoint: test_$index.main,
|
||||
entryPointRunner: $testConfigFunction,
|
||||
goldensUri: Uri.parse('${testInfo.goldensUri}'),
|
||||
),
|
||||
''');
|
||||
}
|
||||
return '''
|
||||
// @dart = ${languageVersion.major}.${languageVersion.minor}
|
||||
import 'org-dartlang-app:///$relativeTestPath' as test;
|
||||
import 'dart:ui' as ui;
|
||||
import 'dart:ui_web' as ui_web;
|
||||
import 'dart:html';
|
||||
import 'dart:js';
|
||||
${testConfigPath != null ? "import '${Uri.file(testConfigPath)}' as test_config;" : ""}
|
||||
import 'package:stream_channel/stream_channel.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:test_api/backend.dart';
|
||||
// @dart = ${languageVersion.major}.${languageVersion.minor}
|
||||
|
||||
Future<void> main() async {
|
||||
ui_web.debugEmulateFlutterTesterEnvironment = true;
|
||||
await ui_web.bootstrapEngine();
|
||||
webGoldenComparator = DefaultWebGoldenComparator(Uri.parse('${Uri.file(absolutePath)}'));
|
||||
ui_web.debugOverrideDevicePixelRatio(3.0);
|
||||
ui.window.debugPhysicalSizeOverride = const ui.Size(2400, 1800);
|
||||
${importMainStatements.join('\n')}
|
||||
|
||||
internalBootstrapBrowserTest(() {
|
||||
return ${testConfigPath != null ? "() => test_config.testExecutable(test.main)" : "test.main"};
|
||||
});
|
||||
}
|
||||
|
||||
void internalBootstrapBrowserTest(Function getMain()) {
|
||||
var channel = serializeSuite(getMain, hidePrints: false);
|
||||
postMessageChannel().pipe(channel);
|
||||
}
|
||||
|
||||
StreamChannel serializeSuite(Function getMain(), {bool hidePrints = true}) => RemoteListener.start(getMain, hidePrints: hidePrints);
|
||||
|
||||
StreamChannel postMessageChannel() {
|
||||
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) {
|
||||
controller.local.sink.add(message.data);
|
||||
});
|
||||
controller.local.stream
|
||||
.listen(channel.port1.postMessage, onDone: portSubscription.cancel);
|
||||
|
||||
return controller.foreign;
|
||||
${importTestConfigStatements.join('\n')}
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
Map<String, WebTest> webTestMap = <String, WebTest>{
|
||||
${webTestPairs.join('\n')}
|
||||
};
|
||||
|
||||
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 @@
|
||||
// of your plugin as a separate package, instead of inlining it in the same
|
||||
// package as the core of your plugin.
|
||||
// ignore: avoid_web_libraries_in_flutter
|
||||
import 'dart:html' as html show window;
|
||||
|
||||
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
|
||||
import 'package:web/web.dart' as web;
|
||||
|
||||
import '{{projectName}}_platform_interface.dart';
|
||||
|
||||
@ -20,7 +20,7 @@ class {{pluginDartClass}}Web extends {{pluginDartClass}}Platform {
|
||||
/// Returns a [String] containing the version of the platform.
|
||||
@override
|
||||
Future<String?> getPlatformVersion() async {
|
||||
final version = html.window.navigator.userAgent;
|
||||
final version = web.window.navigator.userAgent;
|
||||
return version;
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ dependencies:
|
||||
{{#web}}
|
||||
flutter_web_plugins:
|
||||
sdk: flutter
|
||||
web: ^0.5.1
|
||||
{{/web}}
|
||||
plugin_platform_interface: ^2.0.2
|
||||
|
||||
|
@ -87,11 +87,13 @@ void main() {
|
||||
buildInfo: const BuildInfo(BuildMode.debug, '', treeShakeIcons: false),
|
||||
webMemoryFS: WebMemoryFS(),
|
||||
fileSystem: fileSystem,
|
||||
buildDirectory: fileSystem.directory('build'),
|
||||
logger: logger,
|
||||
chromiumLauncher: chromiumLauncher,
|
||||
artifacts: artifacts,
|
||||
processManager: processManager,
|
||||
webRenderer: WebRendererMode.canvaskit,
|
||||
useWasm: false,
|
||||
serverFactory: () async => server,
|
||||
testPackageUri: Uri.parse('test'),
|
||||
);
|
||||
@ -134,11 +136,13 @@ void main() {
|
||||
),
|
||||
webMemoryFS: WebMemoryFS(),
|
||||
fileSystem: fileSystem,
|
||||
buildDirectory: fileSystem.directory('build'),
|
||||
logger: logger,
|
||||
chromiumLauncher: chromiumLauncher,
|
||||
artifacts: artifacts,
|
||||
processManager: processManager,
|
||||
webRenderer: WebRendererMode.canvaskit,
|
||||
useWasm: false,
|
||||
serverFactory: () async => server,
|
||||
testPackageUri: Uri.parse('test'),
|
||||
);
|
||||
|
@ -1386,6 +1386,7 @@ class FakeFlutterTestRunner implements FlutterTestRunner {
|
||||
String? icudtlPath,
|
||||
Directory? coverageDirectory,
|
||||
bool web = false,
|
||||
bool useWasm = false,
|
||||
String? randomSeed,
|
||||
String? reporter,
|
||||
String? fileReporter,
|
||||
|
@ -91,6 +91,7 @@ void main() {
|
||||
testFiles: <String>['project/test/fake_test.dart'],
|
||||
buildInfo: buildInfo,
|
||||
webRenderer: WebRendererMode.canvaskit,
|
||||
useWasm: false,
|
||||
);
|
||||
|
||||
expect(processManager.hasRemainingExpectations, isFalse);
|
||||
|
@ -138,37 +138,18 @@ void main() {
|
||||
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(
|
||||
relativeTestPath: 'relative_path.dart',
|
||||
absolutePath: 'absolute_path.dart',
|
||||
testConfigPath: null,
|
||||
testInfos: <WebTestInfo>[
|
||||
(entryPoint: 'foo.dart', goldensUri: Uri.parse('foo.dart'), configFile: null),
|
||||
(entryPoint: 'bar.dart', goldensUri: Uri.parse('bar.dart'), configFile: 'bar_config.dart'),
|
||||
],
|
||||
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')"));
|
||||
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'"));
|
||||
});
|
||||
|
||||
group('Using the DDC module system', () {
|
||||
@ -299,38 +280,5 @@ void main() {
|
||||
|
||||
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 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:html' as html;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'package:integration_test_example/main.dart' as app;
|
||||
import 'package:web/web.dart' as web;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
@ -31,7 +30,7 @@ void main() {
|
||||
(Widget widget) =>
|
||||
widget is Text &&
|
||||
widget.data!
|
||||
.startsWith('Platform: ${html.window.navigator.platform}\n'),
|
||||
.startsWith('Platform: ${web.window.navigator.platform}\n'),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
@ -9,12 +9,12 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:html' as html;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'package:integration_test_example/main.dart' as app;
|
||||
import 'package:web/web.dart' as web;
|
||||
|
||||
void main() {
|
||||
final IntegrationTestWidgetsFlutterBinding binding =
|
||||
@ -47,7 +47,7 @@ void main() {
|
||||
(Widget widget) =>
|
||||
widget is Text &&
|
||||
widget.data!
|
||||
.startsWith('Platform: ${html.window.navigator.platform}\n'),
|
||||
.startsWith('Platform: ${web.window.navigator.platform}\n'),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
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() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
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() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
@ -2,6 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// 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();
|
||||
|
@ -2,9 +2,10 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:html' as html;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:web/web.dart' as web;
|
||||
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
void startApp() => runApp(const MyWebApp());
|
||||
@ -26,7 +27,7 @@ class _MyWebAppState extends State<MyWebApp> {
|
||||
),
|
||||
body: Center(
|
||||
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:
|
||||
sdk: flutter
|
||||
|
||||
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"
|
||||
collection: 1.18.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
@ -78,7 +79,6 @@ dev_dependencies:
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
|
@ -15,7 +15,7 @@ import 'channel.dart';
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * `_callback_web.dart`, which has the dart:html implementation
|
||||
/// * `_callback_web.dart`, which has the web implementation
|
||||
CallbackManager get callbackManager => _singletonCallbackManager;
|
||||
|
||||
/// IOCallbackManager singleton.
|
||||
|
@ -6,7 +6,7 @@ import 'dart:async';
|
||||
|
||||
import '../common.dart';
|
||||
|
||||
/// The dart:html implementation of [CallbackManager].
|
||||
/// The web implementation of [CallbackManager].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
|
@ -6,7 +6,7 @@
|
||||
///
|
||||
/// 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) {
|
||||
throw UnsupportedError('Use registerServiceExtension instead');
|
||||
|
@ -4,9 +4,11 @@
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:html' as html;
|
||||
import 'dart:js';
|
||||
import 'dart:js_util' as js_util;
|
||||
import 'dart:js_interop';
|
||||
import 'dart:js_interop_unsafe';
|
||||
|
||||
@JS('window')
|
||||
external JSObject get _window;
|
||||
|
||||
/// The web implementation of [registerWebServiceExtension].
|
||||
///
|
||||
@ -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
|
||||
// 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.
|
||||
context[r'$flutterDriverResult'] = null;
|
||||
_window.setProperty(r'$flutterDriverResult'.toJS, null);
|
||||
|
||||
js_util.setProperty(html.window, r'$flutterDriver', allowInterop((dynamic message) async {
|
||||
try {
|
||||
final Map<String, dynamic> messageJson = jsonDecode(message as String) as Map<String, dynamic>;
|
||||
final Map<String, String> params = messageJson.cast<String, String>();
|
||||
final Map<String, dynamic> result = await callback(params);
|
||||
context[r'$flutterDriverResult'] = json.encode(result);
|
||||
} catch (error, stackTrace) {
|
||||
// Encode the error in the same format the FlutterDriver extension uses.
|
||||
// See //packages/flutter_driver/lib/src/extension/extension.dart
|
||||
context[r'$flutterDriverResult'] = json.encode(<String, dynamic>{
|
||||
'isError': true,
|
||||
'response': '$error\n$stackTrace',
|
||||
});
|
||||
}
|
||||
}));
|
||||
_window.setProperty(r'$flutterDriver'.toJS, (JSAny message) {
|
||||
(() async {
|
||||
try {
|
||||
final Map<String, dynamic> messageJson = jsonDecode((message as JSString).toDart) as Map<String, dynamic>;
|
||||
final Map<String, String> params = messageJson.cast<String, String>();
|
||||
final Map<String, dynamic> result = await callback(params);
|
||||
_window.setProperty(r'$flutterDriverResult'.toJS, json.encode(result).toJS);
|
||||
} catch (error, stackTrace) {
|
||||
// Encode the error in the same format the FlutterDriver extension uses.
|
||||
// See //packages/flutter_driver/lib/src/extension/extension.dart
|
||||
_window.setProperty(r'$flutterDriverResult'.toJS,
|
||||
json.encode(<String, dynamic>{
|
||||
'isError': true,
|
||||
'response': '$error\n$stackTrace',
|
||||
}).toJS
|
||||
);
|
||||
}
|
||||
})();
|
||||
}.toJS);
|
||||
}
|
||||
|
@ -2,4 +2,4 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// 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 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// 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 @@
|
||||
@Tags(<String>['web'])
|
||||
library;
|
||||
|
||||
import 'dart:js' as js;
|
||||
import 'dart:js_interop';
|
||||
import 'dart:js_interop_unsafe';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@JS('window')
|
||||
external JSObject get _window;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding();
|
||||
|
||||
test('IntegrationTestWidgetsFlutterBinding on the web should register certain global properties', () {
|
||||
expect(js.context.hasProperty(r'$flutterDriver'), true);
|
||||
expect(js.context[r'$flutterDriver'], isNotNull);
|
||||
expect(_window.hasProperty(r'$flutterDriver'.toJS).toDart, true);
|
||||
expect(_window.getProperty(r'$flutterDriver'.toJS), isNotNull);
|
||||
|
||||
expect(js.context.hasProperty(r'$flutterDriverResult'), true);
|
||||
expect(js.context[r'$flutterDriverResult'], isNull);
|
||||
expect(_window.hasProperty(r'$flutterDriverResult'.toJS).toDart, true);
|
||||
expect(_window.getProperty(r'$flutterDriverResult'.toJS), isNull);
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user