webview_flutter_e2e.dart 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  1. // Copyright 2019, the Chromium project authors. Please see the AUTHORS file
  2. // for details. All rights reserved. Use of this source code is governed by a
  3. // BSD-style license that can be found in the LICENSE file.
  4. import 'dart:async';
  5. import 'dart:convert';
  6. import 'dart:typed_data';
  7. import 'package:flutter/foundation.dart';
  8. import 'package:flutter/services.dart';
  9. import 'package:flutter/widgets.dart';
  10. import 'package:flutter_test/flutter_test.dart';
  11. import 'package:webview_flutter/webview_flutter.dart';
  12. import 'package:e2e/e2e.dart';
  13. void main() {
  14. E2EWidgetsFlutterBinding.ensureInitialized();
  15. testWidgets('initalUrl', (WidgetTester tester) async {
  16. final Completer<WebViewController> controllerCompleter =
  17. Completer<WebViewController>();
  18. await tester.pumpWidget(
  19. Directionality(
  20. textDirection: TextDirection.ltr,
  21. child: WebView(
  22. key: GlobalKey(),
  23. initialUrl: 'https://flutter.dev/',
  24. onWebViewCreated: (WebViewController controller) {
  25. controllerCompleter.complete(controller);
  26. },
  27. ),
  28. ),
  29. );
  30. final WebViewController controller = await controllerCompleter.future;
  31. final String currentUrl = await controller.currentUrl();
  32. expect(currentUrl, 'https://flutter.dev/');
  33. });
  34. testWidgets('loadUrl', (WidgetTester tester) async {
  35. final Completer<WebViewController> controllerCompleter =
  36. Completer<WebViewController>();
  37. await tester.pumpWidget(
  38. Directionality(
  39. textDirection: TextDirection.ltr,
  40. child: WebView(
  41. key: GlobalKey(),
  42. initialUrl: 'https://flutter.dev/',
  43. onWebViewCreated: (WebViewController controller) {
  44. controllerCompleter.complete(controller);
  45. },
  46. ),
  47. ),
  48. );
  49. final WebViewController controller = await controllerCompleter.future;
  50. await controller.loadUrl('https://www.google.com/');
  51. final String currentUrl = await controller.currentUrl();
  52. expect(currentUrl, 'https://www.google.com/');
  53. });
  54. // enable this once https://github.com/flutter/flutter/issues/31510
  55. // is resolved.
  56. testWidgets('loadUrl with headers', (WidgetTester tester) async {
  57. final Completer<WebViewController> controllerCompleter =
  58. Completer<WebViewController>();
  59. final StreamController<String> pageStarts = StreamController<String>();
  60. final StreamController<String> pageLoads = StreamController<String>();
  61. await tester.pumpWidget(
  62. Directionality(
  63. textDirection: TextDirection.ltr,
  64. child: WebView(
  65. key: GlobalKey(),
  66. initialUrl: 'https://flutter.dev/',
  67. onWebViewCreated: (WebViewController controller) {
  68. controllerCompleter.complete(controller);
  69. },
  70. javascriptMode: JavascriptMode.unrestricted,
  71. onPageStarted: (String url) {
  72. pageStarts.add(url);
  73. },
  74. onPageFinished: (String url) {
  75. pageLoads.add(url);
  76. },
  77. ),
  78. ),
  79. );
  80. final WebViewController controller = await controllerCompleter.future;
  81. final Map<String, String> headers = <String, String>{
  82. 'test_header': 'flutter_test_header'
  83. };
  84. await controller.loadUrl('https://flutter-header-echo.herokuapp.com/',
  85. headers: headers);
  86. final String currentUrl = await controller.currentUrl();
  87. expect(currentUrl, 'https://flutter-header-echo.herokuapp.com/');
  88. await pageStarts.stream.firstWhere((String url) => url == currentUrl);
  89. await pageLoads.stream.firstWhere((String url) => url == currentUrl);
  90. final String content = await controller
  91. .evaluateJavascript('document.documentElement.innerText');
  92. expect(content.contains('flutter_test_header'), isTrue);
  93. });
  94. testWidgets('JavaScriptChannel', (WidgetTester tester) async {
  95. final Completer<WebViewController> controllerCompleter =
  96. Completer<WebViewController>();
  97. final Completer<void> pageStarted = Completer<void>();
  98. final Completer<void> pageLoaded = Completer<void>();
  99. final List<String> messagesReceived = <String>[];
  100. await tester.pumpWidget(
  101. Directionality(
  102. textDirection: TextDirection.ltr,
  103. child: WebView(
  104. key: GlobalKey(),
  105. // This is the data URL for: '<!DOCTYPE html>'
  106. initialUrl:
  107. 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+',
  108. onWebViewCreated: (WebViewController controller) {
  109. controllerCompleter.complete(controller);
  110. },
  111. javascriptMode: JavascriptMode.unrestricted,
  112. // TODO(iskakaushik): Remove this when collection literals makes it to stable.
  113. // ignore: prefer_collection_literals
  114. javascriptChannels: <JavascriptChannel>[
  115. JavascriptChannel(
  116. name: 'Echo',
  117. onMessageReceived: (JavascriptMessage message) {
  118. messagesReceived.add(message.message);
  119. },
  120. ),
  121. ].toSet(),
  122. onPageStarted: (String url) {
  123. pageStarted.complete(null);
  124. },
  125. onPageFinished: (String url) {
  126. pageLoaded.complete(null);
  127. },
  128. ),
  129. ),
  130. );
  131. final WebViewController controller = await controllerCompleter.future;
  132. await pageStarted.future;
  133. await pageLoaded.future;
  134. expect(messagesReceived, isEmpty);
  135. await controller.evaluateJavascript('Echo.postMessage("hello");');
  136. expect(messagesReceived, equals(<String>['hello']));
  137. });
  138. testWidgets('resize webview', (WidgetTester tester) async {
  139. final String resizeTest = '''
  140. <!DOCTYPE html><html>
  141. <head><title>Resize test</title>
  142. <script type="text/javascript">
  143. function onResize() {
  144. Resize.postMessage("resize");
  145. }
  146. function onLoad() {
  147. window.onresize = onResize;
  148. }
  149. </script>
  150. </head>
  151. <body onload="onLoad();" bgColor="blue">
  152. </body>
  153. </html>
  154. ''';
  155. final String resizeTestBase64 =
  156. base64Encode(const Utf8Encoder().convert(resizeTest));
  157. final Completer<void> resizeCompleter = Completer<void>();
  158. final Completer<void> pageStarted = Completer<void>();
  159. final Completer<void> pageLoaded = Completer<void>();
  160. final Completer<WebViewController> controllerCompleter =
  161. Completer<WebViewController>();
  162. final GlobalKey key = GlobalKey();
  163. final WebView webView = WebView(
  164. key: key,
  165. initialUrl: 'data:text/html;charset=utf-8;base64,$resizeTestBase64',
  166. onWebViewCreated: (WebViewController controller) {
  167. controllerCompleter.complete(controller);
  168. },
  169. // TODO(iskakaushik): Remove this when collection literals makes it to stable.
  170. // ignore: prefer_collection_literals
  171. javascriptChannels: <JavascriptChannel>[
  172. JavascriptChannel(
  173. name: 'Resize',
  174. onMessageReceived: (JavascriptMessage message) {
  175. resizeCompleter.complete(true);
  176. },
  177. ),
  178. ].toSet(),
  179. onPageStarted: (String url) {
  180. pageStarted.complete(null);
  181. },
  182. onPageFinished: (String url) {
  183. pageLoaded.complete(null);
  184. },
  185. javascriptMode: JavascriptMode.unrestricted,
  186. );
  187. await tester.pumpWidget(
  188. Directionality(
  189. textDirection: TextDirection.ltr,
  190. child: Column(
  191. children: <Widget>[
  192. SizedBox(
  193. width: 200,
  194. height: 200,
  195. child: webView,
  196. ),
  197. ],
  198. ),
  199. ),
  200. );
  201. await controllerCompleter.future;
  202. await pageStarted.future;
  203. await pageLoaded.future;
  204. expect(resizeCompleter.isCompleted, false);
  205. await tester.pumpWidget(
  206. Directionality(
  207. textDirection: TextDirection.ltr,
  208. child: Column(
  209. children: <Widget>[
  210. SizedBox(
  211. width: 400,
  212. height: 400,
  213. child: webView,
  214. ),
  215. ],
  216. ),
  217. ),
  218. );
  219. await resizeCompleter.future;
  220. });
  221. testWidgets('set custom userAgent', (WidgetTester tester) async {
  222. final Completer<WebViewController> controllerCompleter1 =
  223. Completer<WebViewController>();
  224. final GlobalKey _globalKey = GlobalKey();
  225. await tester.pumpWidget(
  226. Directionality(
  227. textDirection: TextDirection.ltr,
  228. child: WebView(
  229. key: _globalKey,
  230. initialUrl: 'about:blank',
  231. javascriptMode: JavascriptMode.unrestricted,
  232. userAgent: 'Custom_User_Agent1',
  233. onWebViewCreated: (WebViewController controller) {
  234. controllerCompleter1.complete(controller);
  235. },
  236. ),
  237. ),
  238. );
  239. final WebViewController controller1 = await controllerCompleter1.future;
  240. final String customUserAgent1 = await _getUserAgent(controller1);
  241. expect(customUserAgent1, 'Custom_User_Agent1');
  242. // rebuild the WebView with a different user agent.
  243. await tester.pumpWidget(
  244. Directionality(
  245. textDirection: TextDirection.ltr,
  246. child: WebView(
  247. key: _globalKey,
  248. initialUrl: 'about:blank',
  249. javascriptMode: JavascriptMode.unrestricted,
  250. userAgent: 'Custom_User_Agent2',
  251. ),
  252. ),
  253. );
  254. final String customUserAgent2 = await _getUserAgent(controller1);
  255. expect(customUserAgent2, 'Custom_User_Agent2');
  256. });
  257. testWidgets('use default platform userAgent after webView is rebuilt',
  258. (WidgetTester tester) async {
  259. final Completer<WebViewController> controllerCompleter =
  260. Completer<WebViewController>();
  261. final GlobalKey _globalKey = GlobalKey();
  262. // Build the webView with no user agent to get the default platform user agent.
  263. await tester.pumpWidget(
  264. Directionality(
  265. textDirection: TextDirection.ltr,
  266. child: WebView(
  267. key: _globalKey,
  268. initialUrl: 'https://flutter.dev/',
  269. javascriptMode: JavascriptMode.unrestricted,
  270. onWebViewCreated: (WebViewController controller) {
  271. controllerCompleter.complete(controller);
  272. },
  273. ),
  274. ),
  275. );
  276. final WebViewController controller = await controllerCompleter.future;
  277. final String defaultPlatformUserAgent = await _getUserAgent(controller);
  278. // rebuild the WebView with a custom user agent.
  279. await tester.pumpWidget(
  280. Directionality(
  281. textDirection: TextDirection.ltr,
  282. child: WebView(
  283. key: _globalKey,
  284. initialUrl: 'about:blank',
  285. javascriptMode: JavascriptMode.unrestricted,
  286. userAgent: 'Custom_User_Agent',
  287. ),
  288. ),
  289. );
  290. final String customUserAgent = await _getUserAgent(controller);
  291. expect(customUserAgent, 'Custom_User_Agent');
  292. // rebuilds the WebView with no user agent.
  293. await tester.pumpWidget(
  294. Directionality(
  295. textDirection: TextDirection.ltr,
  296. child: WebView(
  297. key: _globalKey,
  298. initialUrl: 'about:blank',
  299. javascriptMode: JavascriptMode.unrestricted,
  300. ),
  301. ),
  302. );
  303. final String customUserAgent2 = await _getUserAgent(controller);
  304. expect(customUserAgent2, defaultPlatformUserAgent);
  305. });
  306. group('Media playback policy', () {
  307. String audioTestBase64;
  308. setUpAll(() async {
  309. final ByteData audioData =
  310. await rootBundle.load('assets/sample_audio.ogg');
  311. final String base64AudioData =
  312. base64Encode(Uint8List.view(audioData.buffer));
  313. final String audioTest = '''
  314. <!DOCTYPE html><html>
  315. <head><title>Audio auto play</title>
  316. <script type="text/javascript">
  317. function play() {
  318. var audio = document.getElementById("audio");
  319. audio.play();
  320. }
  321. function isPaused() {
  322. var audio = document.getElementById("audio");
  323. return audio.paused;
  324. }
  325. </script>
  326. </head>
  327. <body onload="play();">
  328. <audio controls id="audio">
  329. <source src="data:audio/ogg;charset=utf-8;base64,$base64AudioData">
  330. </audio>
  331. </body>
  332. </html>
  333. ''';
  334. audioTestBase64 = base64Encode(const Utf8Encoder().convert(audioTest));
  335. });
  336. testWidgets('Auto media playback', (WidgetTester tester) async {
  337. Completer<WebViewController> controllerCompleter =
  338. Completer<WebViewController>();
  339. Completer<void> pageStarted = Completer<void>();
  340. Completer<void> pageLoaded = Completer<void>();
  341. await tester.pumpWidget(
  342. Directionality(
  343. textDirection: TextDirection.ltr,
  344. child: WebView(
  345. key: GlobalKey(),
  346. initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64',
  347. onWebViewCreated: (WebViewController controller) {
  348. controllerCompleter.complete(controller);
  349. },
  350. javascriptMode: JavascriptMode.unrestricted,
  351. onPageStarted: (String url) {
  352. pageStarted.complete(null);
  353. },
  354. onPageFinished: (String url) {
  355. pageLoaded.complete(null);
  356. },
  357. initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
  358. ),
  359. ),
  360. );
  361. WebViewController controller = await controllerCompleter.future;
  362. await pageStarted.future;
  363. await pageLoaded.future;
  364. String isPaused = await controller.evaluateJavascript('isPaused();');
  365. expect(isPaused, _webviewBool(false));
  366. controllerCompleter = Completer<WebViewController>();
  367. pageStarted = Completer<void>();
  368. pageLoaded = Completer<void>();
  369. // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy
  370. await tester.pumpWidget(
  371. Directionality(
  372. textDirection: TextDirection.ltr,
  373. child: WebView(
  374. key: GlobalKey(),
  375. initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64',
  376. onWebViewCreated: (WebViewController controller) {
  377. controllerCompleter.complete(controller);
  378. },
  379. javascriptMode: JavascriptMode.unrestricted,
  380. onPageStarted: (String url) {
  381. pageStarted.complete(null);
  382. },
  383. onPageFinished: (String url) {
  384. pageLoaded.complete(null);
  385. },
  386. initialMediaPlaybackPolicy:
  387. AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
  388. ),
  389. ),
  390. );
  391. controller = await controllerCompleter.future;
  392. await pageStarted.future;
  393. await pageLoaded.future;
  394. isPaused = await controller.evaluateJavascript('isPaused();');
  395. expect(isPaused, _webviewBool(true));
  396. });
  397. testWidgets('Changes to initialMediaPlaybackPolocy are ignored',
  398. (WidgetTester tester) async {
  399. final Completer<WebViewController> controllerCompleter =
  400. Completer<WebViewController>();
  401. Completer<void> pageStarted = Completer<void>();
  402. Completer<void> pageLoaded = Completer<void>();
  403. final GlobalKey key = GlobalKey();
  404. await tester.pumpWidget(
  405. Directionality(
  406. textDirection: TextDirection.ltr,
  407. child: WebView(
  408. key: key,
  409. initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64',
  410. onWebViewCreated: (WebViewController controller) {
  411. controllerCompleter.complete(controller);
  412. },
  413. javascriptMode: JavascriptMode.unrestricted,
  414. onPageStarted: (String url) {
  415. pageStarted.complete(null);
  416. },
  417. onPageFinished: (String url) {
  418. pageLoaded.complete(null);
  419. },
  420. initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
  421. ),
  422. ),
  423. );
  424. final WebViewController controller = await controllerCompleter.future;
  425. await pageStarted.future;
  426. await pageLoaded.future;
  427. String isPaused = await controller.evaluateJavascript('isPaused();');
  428. expect(isPaused, _webviewBool(false));
  429. pageStarted = Completer<void>();
  430. pageLoaded = Completer<void>();
  431. await tester.pumpWidget(
  432. Directionality(
  433. textDirection: TextDirection.ltr,
  434. child: WebView(
  435. key: key,
  436. initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64',
  437. onWebViewCreated: (WebViewController controller) {
  438. controllerCompleter.complete(controller);
  439. },
  440. javascriptMode: JavascriptMode.unrestricted,
  441. onPageStarted: (String url) {
  442. pageStarted.complete(null);
  443. },
  444. onPageFinished: (String url) {
  445. pageLoaded.complete(null);
  446. },
  447. initialMediaPlaybackPolicy:
  448. AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
  449. ),
  450. ),
  451. );
  452. await controller.reload();
  453. await pageStarted.future;
  454. await pageLoaded.future;
  455. isPaused = await controller.evaluateJavascript('isPaused();');
  456. expect(isPaused, _webviewBool(false));
  457. });
  458. });
  459. testWidgets('getTitle', (WidgetTester tester) async {
  460. final String getTitleTest = '''
  461. <!DOCTYPE html><html>
  462. <head><title>Some title</title>
  463. </head>
  464. <body>
  465. </body>
  466. </html>
  467. ''';
  468. final String getTitleTestBase64 =
  469. base64Encode(const Utf8Encoder().convert(getTitleTest));
  470. final Completer<void> pageStarted = Completer<void>();
  471. final Completer<void> pageLoaded = Completer<void>();
  472. final Completer<WebViewController> controllerCompleter =
  473. Completer<WebViewController>();
  474. await tester.pumpWidget(
  475. Directionality(
  476. textDirection: TextDirection.ltr,
  477. child: WebView(
  478. initialUrl: 'data:text/html;charset=utf-8;base64,$getTitleTestBase64',
  479. onWebViewCreated: (WebViewController controller) {
  480. controllerCompleter.complete(controller);
  481. },
  482. onPageStarted: (String url) {
  483. pageStarted.complete(null);
  484. },
  485. onPageFinished: (String url) {
  486. pageLoaded.complete(null);
  487. },
  488. ),
  489. ),
  490. );
  491. final WebViewController controller = await controllerCompleter.future;
  492. await pageStarted.future;
  493. await pageLoaded.future;
  494. final String title = await controller.getTitle();
  495. expect(title, 'Some title');
  496. });
  497. group('NavigationDelegate', () {
  498. final String blankPage = "<!DOCTYPE html><head></head><body></body></html>";
  499. final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' +
  500. base64Encode(const Utf8Encoder().convert(blankPage));
  501. testWidgets('can allow requests', (WidgetTester tester) async {
  502. final Completer<WebViewController> controllerCompleter =
  503. Completer<WebViewController>();
  504. final StreamController<String> pageLoads =
  505. StreamController<String>.broadcast();
  506. await tester.pumpWidget(
  507. Directionality(
  508. textDirection: TextDirection.ltr,
  509. child: WebView(
  510. key: GlobalKey(),
  511. initialUrl: blankPageEncoded,
  512. onWebViewCreated: (WebViewController controller) {
  513. controllerCompleter.complete(controller);
  514. },
  515. javascriptMode: JavascriptMode.unrestricted,
  516. navigationDelegate: (NavigationRequest request) {
  517. return (request.url.contains('youtube.com'))
  518. ? NavigationDecision.prevent
  519. : NavigationDecision.navigate;
  520. },
  521. onPageFinished: (String url) => pageLoads.add(url),
  522. ),
  523. ),
  524. );
  525. await pageLoads.stream.first; // Wait for initial page load.
  526. final WebViewController controller = await controllerCompleter.future;
  527. await controller
  528. .evaluateJavascript('location.href = "https://www.google.com/"');
  529. await pageLoads.stream.first; // Wait for the next page load.
  530. final String currentUrl = await controller.currentUrl();
  531. expect(currentUrl, 'https://www.google.com/');
  532. });
  533. testWidgets('can block requests', (WidgetTester tester) async {
  534. final Completer<WebViewController> controllerCompleter =
  535. Completer<WebViewController>();
  536. final StreamController<String> pageLoads =
  537. StreamController<String>.broadcast();
  538. await tester.pumpWidget(
  539. Directionality(
  540. textDirection: TextDirection.ltr,
  541. child: WebView(
  542. key: GlobalKey(),
  543. initialUrl: blankPageEncoded,
  544. onWebViewCreated: (WebViewController controller) {
  545. controllerCompleter.complete(controller);
  546. },
  547. javascriptMode: JavascriptMode.unrestricted,
  548. navigationDelegate: (NavigationRequest request) {
  549. return (request.url.contains('youtube.com'))
  550. ? NavigationDecision.prevent
  551. : NavigationDecision.navigate;
  552. },
  553. onPageFinished: (String url) => pageLoads.add(url),
  554. ),
  555. ),
  556. );
  557. await pageLoads.stream.first; // Wait for initial page load.
  558. final WebViewController controller = await controllerCompleter.future;
  559. await controller
  560. .evaluateJavascript('location.href = "https://www.youtube.com/"');
  561. // There should never be any second page load, since our new URL is
  562. // blocked. Still wait for a potential page change for some time in order
  563. // to give the test a chance to fail.
  564. await pageLoads.stream.first
  565. .timeout(const Duration(milliseconds: 500), onTimeout: () => null);
  566. final String currentUrl = await controller.currentUrl();
  567. expect(currentUrl, isNot(contains('youtube.com')));
  568. });
  569. testWidgets('supports asynchronous decisions', (WidgetTester tester) async {
  570. final Completer<WebViewController> controllerCompleter =
  571. Completer<WebViewController>();
  572. final StreamController<String> pageLoads =
  573. StreamController<String>.broadcast();
  574. await tester.pumpWidget(
  575. Directionality(
  576. textDirection: TextDirection.ltr,
  577. child: WebView(
  578. key: GlobalKey(),
  579. initialUrl: blankPageEncoded,
  580. onWebViewCreated: (WebViewController controller) {
  581. controllerCompleter.complete(controller);
  582. },
  583. javascriptMode: JavascriptMode.unrestricted,
  584. navigationDelegate: (NavigationRequest request) async {
  585. NavigationDecision decision = NavigationDecision.prevent;
  586. decision = await Future<NavigationDecision>.delayed(
  587. const Duration(milliseconds: 10),
  588. () => NavigationDecision.navigate);
  589. return decision;
  590. },
  591. onPageFinished: (String url) => pageLoads.add(url),
  592. ),
  593. ),
  594. );
  595. await pageLoads.stream.first; // Wait for initial page load.
  596. final WebViewController controller = await controllerCompleter.future;
  597. await controller
  598. .evaluateJavascript('location.href = "https://www.google.com"');
  599. await pageLoads.stream.first; // Wait for second page to load.
  600. final String currentUrl = await controller.currentUrl();
  601. expect(currentUrl, 'https://www.google.com/');
  602. });
  603. });
  604. testWidgets('launches with gestureNavigationEnabled on iOS',
  605. (WidgetTester tester) async {
  606. final Completer<WebViewController> controllerCompleter =
  607. Completer<WebViewController>();
  608. await tester.pumpWidget(
  609. Directionality(
  610. textDirection: TextDirection.ltr,
  611. child: SizedBox(
  612. width: 400,
  613. height: 300,
  614. child: WebView(
  615. key: GlobalKey(),
  616. initialUrl: 'https://flutter.dev/',
  617. gestureNavigationEnabled: true,
  618. onWebViewCreated: (WebViewController controller) {
  619. controllerCompleter.complete(controller);
  620. },
  621. ),
  622. ),
  623. ),
  624. );
  625. final WebViewController controller = await controllerCompleter.future;
  626. final String currentUrl = await controller.currentUrl();
  627. expect(currentUrl, 'https://flutter.dev/');
  628. });
  629. testWidgets('target _blank opens in same window',
  630. (WidgetTester tester) async {
  631. final Completer<WebViewController> controllerCompleter =
  632. Completer<WebViewController>();
  633. final Completer<void> pageLoaded = Completer<void>();
  634. await tester.pumpWidget(
  635. Directionality(
  636. textDirection: TextDirection.ltr,
  637. child: WebView(
  638. key: GlobalKey(),
  639. onWebViewCreated: (WebViewController controller) {
  640. controllerCompleter.complete(controller);
  641. },
  642. javascriptMode: JavascriptMode.unrestricted,
  643. onPageFinished: (String url) {
  644. pageLoaded.complete(null);
  645. },
  646. ),
  647. ),
  648. );
  649. final WebViewController controller = await controllerCompleter.future;
  650. await controller.evaluateJavascript('window.open("about:blank", "_blank")');
  651. await pageLoaded.future;
  652. final String currentUrl = await controller.currentUrl();
  653. expect(currentUrl, 'about:blank');
  654. });
  655. }
  656. // JavaScript booleans evaluate to different string values on Android and iOS.
  657. // This utility method returns the string boolean value of the current platform.
  658. String _webviewBool(bool value) {
  659. if (defaultTargetPlatform == TargetPlatform.iOS) {
  660. return value ? '1' : '0';
  661. }
  662. return value ? 'true' : 'false';
  663. }
  664. /// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests.
  665. Future<String> _getUserAgent(WebViewController controller) async {
  666. if (defaultTargetPlatform == TargetPlatform.iOS) {
  667. return await controller.evaluateJavascript('navigator.userAgent;');
  668. }
  669. return jsonDecode(
  670. await controller.evaluateJavascript('navigator.userAgent;'));
  671. }