├── .gitignore ├── .travis.yml ├── AUTHORS ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── lib ├── src │ ├── alert.dart │ ├── capabilities.dart │ ├── command_event.dart │ ├── common.dart │ ├── error.dart │ ├── keyboard.dart │ ├── keys.dart │ ├── logs.dart │ ├── mouse.dart │ ├── navigation.dart │ ├── options.dart │ ├── target_locator.dart │ ├── touch.dart │ ├── util.dart │ ├── web_driver.dart │ ├── web_element.dart │ └── window.dart └── sync_webdriver.dart ├── pubspec.yaml ├── test ├── frame.html ├── src │ ├── alert_test.dart │ ├── command_event_test.dart │ ├── keyboard_test.dart │ ├── logs_test.dart │ ├── mouse_test.dart │ ├── navigation_test.dart │ ├── options_test.dart │ ├── target_locator_test.dart │ ├── util_test.dart │ ├── web_driver_test.dart │ ├── web_element_test.dart │ └── window_test.dart ├── test_page.html └── test_util.dart └── tool └── travis.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | packages 3 | pubspec.lock 4 | .project 5 | .buildlog 6 | *.o 7 | *.so 8 | .idea/ 9 | .pub/ 10 | .packages 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: dart 2 | sudo: false 3 | 4 | dart: 5 | - dev 6 | - stable 7 | 8 | before_install: 9 | - export CHROMEDRIVER_BINARY=/usr/bin/chromium-browser 10 | - export CHROMEDRIVER_ARGS=--no-sandbox 11 | - /usr/bin/chromium-browser --version 12 | 13 | - export DISPLAY=:99.0 14 | - sh -e /etc/init.d/xvfb start 15 | 16 | before_script: 17 | # We use a slightly older version of chromedriver; the newer ones require a later 18 | # version of chromium than is available on travis by default. 19 | - wget http://chromedriver.storage.googleapis.com/2.12/chromedriver_linux64.zip 20 | - unzip chromedriver_linux64.zip 21 | - export PATH=$PATH:$PWD 22 | - ./packages/sync_socket/../tool/build.sh 23 | 24 | script: ./tool/travis.sh 25 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Below is a list of people and organizations that have contributed 2 | # to the Dart project. Names should be added to the list like so: 3 | # 4 | # Name/Organization 5 | 6 | Google Inc. 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at 2 | the end). 3 | 4 | ### When you file a bug 5 | 6 | Please include the following information. 7 | 8 | * The version of Dart on your system. 9 | You can do this by running `dart --version`. 10 | * The operating system you are running. 11 | * The version of the `sync_webdriver` package you are using. 12 | You can get this by looking at the `pubspec.lock` file. 13 | 14 | ```yaml 15 | test: 16 | description: sync_webdriver 17 | source: hosted 18 | version: "X.Y.Z" 19 | ``` 20 | 21 | ### Before you contribute 22 | Before we can use your code, you must sign the 23 | [Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual) 24 | (CLA), which you can do online. The CLA is necessary mainly because you own the 25 | copyright to your changes, even after your contribution becomes part of our 26 | codebase, so we need your permission to use and distribute your code. We also 27 | need to be sure of various other things—for instance that you'll tell us if you 28 | know that your code infringes on other people's patents. You don't have to sign 29 | the CLA until after you've submitted your code for review and a member has 30 | approved it, but you must do it before we can put your code into our codebase. 31 | 32 | Before you start working on a larger contribution, you should get in touch with 33 | us first through the issue tracker with your idea so that we can help out and 34 | possibly guide you. Coordinating up front makes it much easier to avoid 35 | frustration later on. 36 | 37 | ### Code reviews 38 | All submissions, including submissions by project members, require review. 39 | 40 | ### File headers 41 | All files in the project must start with the following header. 42 | 43 | // Copyright 2015 Google Inc. All Rights Reserved. 44 | // 45 | // Licensed under the Apache License, Version 2.0 (the "License"); 46 | // you may not use this file except in compliance with the License. 47 | // You may obtain a copy of the License at 48 | // 49 | // http://www.apache.org/licenses/LICENSE-2.0 50 | // 51 | // Unless required by applicable law or agreed to in writing, software 52 | // distributed under the License is distributed on an "AS IS" BASIS, 53 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 54 | // See the License for the specific language governing permissions and 55 | // limitations under the License. 56 | 57 | ### The small print 58 | Contributions made by corporations are covered by a different agreement than the 59 | one above, the 60 | [Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate). 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Dart Sync WebDriver 2 | ================ 3 | 4 | [![Build Status](https://travis-ci.org/google/dart-sync-webdriver.svg?branch=master)](https://travis-ci.org/google/dart-sync-webdriver) 5 | [![pub package](https://img.shields.io/pub/v/sync_webdriver.svg)](https://pub.dartlang.org/packages/sync_webdriver) 6 | 7 | **NOTE:** Dart Sync WebDriver is **deprecated**. Consider switching to the async [webdriver.dart](https://github.com/google/webdriver.dart). 8 | 9 | Installing 10 | ---------- 11 | 12 | This library depends on https://github.com/google/dart-sync-socket which uses 13 | a native extension. After doing a pub get or upgrade, you must build the native extension 14 | by running: 15 | ``` 16 | # ./packages/sync_socket/../tool/build.sh 17 | ``` 18 | 19 | Projects that use Sync WebDriver should include the following in their 20 | pubspec.yaml: 21 | 22 | ``` 23 | sync_webdriver: '^1.2.0' 24 | ``` 25 | -------------------------------------------------------------------------------- /lib/src/alert.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | part of sync.webdriver; 18 | 19 | /// A JavaScript alert(), confirm(), or prompt() dialog 20 | class Alert extends _WebDriverBase { 21 | /** 22 | * The text of the JavaScript alert(), confirm(), or 23 | * prompt() dialog. 24 | */ 25 | final String text; 26 | 27 | Alert._(this.text, commandProcessor) : super(commandProcessor, ''); 28 | 29 | /** 30 | * Accepts the currently displayed alert (may not be the alert for which 31 | * this object was created). 32 | * 33 | * Throws [NoAlertOpenException] if there isn't currently an alert. 34 | */ 35 | void accept() => _post('accept_alert'); 36 | 37 | /** 38 | * Dismisses the currently displayed alert (may not be the alert for which 39 | * this object was created). 40 | * 41 | * Throws [NoAlertOpenException] if there isn't currently an alert. 42 | */ 43 | void dismiss() => _post('dismiss_alert'); 44 | 45 | /** 46 | * Sends keys to the currently displayed alert (may not be the alert for which 47 | * this object was created). 48 | * 49 | * Throws [NoAlertOpenException] if there isn't currently an alert. 50 | */ 51 | void sendKeys(String keysToSend) => _post('alert_text', {'text': keysToSend}); 52 | } 53 | -------------------------------------------------------------------------------- /lib/src/capabilities.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | part of sync.webdriver; 18 | 19 | class Capabilities { 20 | static const String BROWSER_NAME = "browserName"; 21 | static const String PLATFORM = "platform"; 22 | static const String SUPPORTS_JAVASCRIPT = "javascriptEnabled"; 23 | static const String TAKES_SCREENSHOT = "takesScreenshot"; 24 | static const String VERSION = "version"; 25 | static const String SUPPORTS_ALERTS = "handlesAlerts"; 26 | static const String SUPPORTS_SQL_DATABASE = "databaseEnabled"; 27 | static const String SUPPORTS_LOCATION_CONTEXT = "locationContextEnabled"; 28 | static const String SUPPORTS_APPLICATION_CACHE = "applicationCacheEnabled"; 29 | static const String SUPPORTS_BROWSER_CONNECTION = "browserConnectionEnabled"; 30 | static const String SUPPORTS_FINDING_BY_CSS = "cssSelectorsEnabled"; 31 | static const String PROXY = "proxy"; 32 | static const String SUPPORTS_WEB_STORAGE = "webStorageEnabled"; 33 | static const String ROTATABLE = "rotatable"; 34 | static const String ACCEPT_SSL_CERTS = "acceptSslCerts"; 35 | static const String HAS_NATIVE_EVENTS = "nativeEvents"; 36 | static const String UNEXPECTED_ALERT_BEHAVIOUR = "unexpectedAlertBehaviour"; 37 | static const String LOGGING_PREFS = "loggingPrefs"; 38 | static const String ENABLE_PROFILING_CAPABILITY = 39 | "webdriver.logging.profiler.enabled"; 40 | static const String QUIET_EXCEPTIONS = "webdriver.remote.quietExceptions"; 41 | static const String CHROME_OPTIONS = "chromeOptions"; 42 | 43 | static Map get chrome => empty 44 | ..[BROWSER_NAME] = Browser.CHROME 45 | ..[VERSION] = '' 46 | ..[PLATFORM] = Platform.ANY 47 | ..[CHROME_OPTIONS] = new ChromeOptions(); 48 | 49 | static Map get firefox => empty 50 | ..[BROWSER_NAME] = Browser.FIREFOX 51 | ..[VERSION] = '' 52 | ..[PLATFORM] = Platform.ANY; 53 | 54 | static Map get android => empty 55 | ..[BROWSER_NAME] = Browser.ANDROID 56 | ..[VERSION] = '' 57 | ..[PLATFORM] = Platform.ANDROID; 58 | 59 | static Map get empty => new Map() 60 | // quiet exceptions because the Sync HTTP Client is not robust enough 61 | // to handle screenshots 62 | ..[QUIET_EXCEPTIONS] = true; 63 | } 64 | 65 | class Browser { 66 | static const String FIREFOX = "firefox"; 67 | static const String FIREFOX_2 = "firefox2"; 68 | static const String FIREFOX_3 = "firefox3"; 69 | static const String FIREFOX_PROXY = "firefoxproxy"; 70 | static const String FIREFOX_CHROME = "firefoxchrome"; 71 | static const String GOOGLECHROME = "googlechrome"; 72 | static const String SAFARI = "safari"; 73 | static const String OPERA = "opera"; 74 | static const String IEXPLORE = "iexplore"; 75 | static const String IEXPLORE_PROXY = "iexploreproxy"; 76 | static const String SAFARI_PROXY = "safariproxy"; 77 | static const String CHROME = "chrome"; 78 | static const String KONQUEROR = "konqueror"; 79 | static const String MOCK = "mock"; 80 | static const String IE_HTA = "iehta"; 81 | static const String ANDROID = "android"; 82 | static const String HTMLUNIT = "htmlunit"; 83 | static const String IE = "internet explorer"; 84 | static const String IPHONE = "iPhone"; 85 | static const String IPAD = "iPad"; 86 | static const String PHANTOMJS = "phantomjs"; 87 | } 88 | 89 | class Platform { 90 | static const String ANY = "ANY"; 91 | static const String ANDROID = "ANDROID"; 92 | } 93 | 94 | class ChromeOptions { 95 | /** 96 | * The path to the Chrome executable. This path should exist on the 97 | * machine which will launch Chrome. The path should either be absolute or 98 | * relative to the location of running ChromeDriver server. 99 | */ 100 | String binary; 101 | 102 | /** 103 | * Chrome extensions to install on browser startup. Each [File] should 104 | * specify a packed Chrome extension (CRX file) that exists locally. 105 | */ 106 | final List extensions = []; 107 | 108 | /** 109 | * Additional command line arguments to be used when starting Chrome. 110 | * 111 | * Each argument may contain an option "--" prefix: "--foo" or "foo". 112 | * Arguments with an associated value should be delimitted with an "=": 113 | * "foo=bar". 114 | */ 115 | final List arguments = []; 116 | 117 | /** 118 | * New ChromeDriver options not yet exposed through the [ChromeOptions] API. 119 | * 120 | * All values must be convertible to JSON. 121 | */ 122 | final Map experimentalOptions = {}; 123 | 124 | Map toJson() { 125 | var json = new Map.from(experimentalOptions); 126 | if (binary != null) { 127 | json['binary'] = binary; 128 | } 129 | if (arguments.isNotEmpty) { 130 | json['args'] = new List.from(arguments, growable: false); 131 | } 132 | if (extensions.isNotEmpty) { 133 | json['extensions'] = 134 | extensions.map(_encodeExtension).toList(growable: false); 135 | } 136 | 137 | return json; 138 | } 139 | 140 | String _encodeExtension(File file) => BASE64.encode(file.readAsBytesSync()); 141 | } 142 | -------------------------------------------------------------------------------- /lib/src/command_event.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | part of sync.webdriver; 18 | 19 | /// Object for holding the details of a command event. 20 | class CommandEvent { 21 | /// HTTP method for the command. 22 | final String method; 23 | 24 | /// HTTP endpoint for the command. 25 | final String endpoint; 26 | 27 | /// String representation of the params sent for the command. 28 | final String params; 29 | 30 | /// When the command started execution. 31 | final DateTime startTime; 32 | 33 | /// When the command ended execution. 34 | final DateTime endTime; 35 | 36 | /// String representation of the returned response for the command. 37 | /// If the command failed, this will be null. 38 | final String result; 39 | 40 | /// String representation of the exception thrown when the command failed. 41 | /// If the command succeeded, this will be null. 42 | final String exception; 43 | 44 | /// Stack trace at the point of the execution of the WebDriver command. 45 | final Trace stackTrace; 46 | 47 | CommandEvent( 48 | {this.method, 49 | this.endpoint, 50 | this.params, 51 | this.startTime, 52 | this.endTime, 53 | this.result, 54 | this.exception, 55 | this.stackTrace}); 56 | } 57 | -------------------------------------------------------------------------------- /lib/src/common.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | part of sync.webdriver; 18 | 19 | const String _ELEMENT = 'ELEMENT'; 20 | 21 | /** 22 | * Simple class to provide access to indexed properties such as WebElement 23 | * attributes or css styles. 24 | */ 25 | class Attributes extends _WebDriverBase { 26 | Attributes._(driver, prefix, command) : super(driver, '$prefix/$command'); 27 | 28 | String operator [](String name) => _get(name); 29 | } 30 | 31 | class Size { 32 | final num height; 33 | final num width; 34 | 35 | const Size(this.height, this.width); 36 | 37 | Size.fromJson(Map json) : this(json['height'], json['width']); 38 | 39 | Map toJson() => {'height': height, 'width': width}; 40 | 41 | @override 42 | bool operator ==(Object other) => 43 | other is Size && height == other.height && width == other.width; 44 | 45 | @override 46 | int get hashCode => width.hashCode << 3 + height.hashCode; 47 | 48 | @override 49 | String toString() => toJson().toString(); 50 | } 51 | 52 | Point _jsonToPoint(Map json) => 53 | new Point(json['x'].floor(), json['y'].floor()); 54 | 55 | Map _pointToJson(Point point) => 56 | {'x': point.x.floor(), 'y': point.y.floor()}; 57 | 58 | abstract class SearchContext { 59 | WebDriver get driver; 60 | 61 | /** 62 | * Find an element nested within this element. 63 | * 64 | * Throws [NoSuchElementException] if matching element is not found. 65 | */ 66 | WebElement findElement(Finder finder) { 67 | var element; 68 | if (finder is By) { 69 | element = _post('element', finder); 70 | } else { 71 | element = finder.findElement(this); 72 | } 73 | element._updateProvenance(this, finder); 74 | return element; 75 | } 76 | 77 | /// Find multiple elements nested within this element. 78 | List findElements(Finder finder) { 79 | var elements; 80 | if (finder is By) { 81 | elements = _post('elements', finder); 82 | } else { 83 | elements = finder.findElements(this); 84 | } 85 | 86 | for (int i = 0; i < elements.length; i++) { 87 | elements[i]._updateProvenance(this, finder, i); 88 | } 89 | 90 | return new UnmodifiableListView(elements); 91 | } 92 | 93 | dynamic _post(String command, [dynamic args]); 94 | } 95 | 96 | abstract class _WebDriverBase { 97 | final String _prefix; 98 | final WebDriver driver; 99 | 100 | _WebDriverBase(this.driver, this._prefix); 101 | 102 | dynamic _post(String command, [param]) => 103 | driver.post(_command(command), param); 104 | 105 | dynamic _get(String command) => driver.get(_command(command)); 106 | 107 | dynamic _delete(String command) => driver.delete(_command(command)); 108 | 109 | String _command(String command) { 110 | if (command == null || command.isEmpty) { 111 | return _prefix; 112 | } else if (_prefix == null || _prefix.isEmpty) { 113 | return command; 114 | } else if (command.startsWith('/')) { 115 | return '$_prefix$command'; 116 | } else 117 | return '$_prefix/$command'; 118 | } 119 | } 120 | 121 | abstract class Finder { 122 | const Finder(); 123 | 124 | WebElement findElement(SearchContext context); 125 | 126 | List findElements(SearchContext context); 127 | } 128 | 129 | class By implements Finder { 130 | final String _using; 131 | final String _value; 132 | 133 | const By._(this._using, this._value); 134 | 135 | /// Returns an element whose ID attribute matches the search value. 136 | const By.id(String id) : this._('id', id); 137 | 138 | /// Returns an element matching an XPath expression. 139 | const By.xpath(String xpath) : this._('xpath', xpath); 140 | 141 | /// Returns an anchor element whose visible text matches the search value. 142 | const By.linkText(String linkText) : this._('link text', linkText); 143 | 144 | /** 145 | * Returns an anchor element whose visible text partially matches the search 146 | * value. 147 | */ 148 | const By.partialLinkText(String partialLinkText) 149 | : this._('partial link text', partialLinkText); 150 | 151 | /// Returns an element whose NAME attribute matches the search value. 152 | const By.name(String name) : this._('name', name); 153 | 154 | /// Returns an element whose tag name matches the search value. 155 | const By.tagName(String tagName) : this._('tag name', tagName); 156 | 157 | /** 158 | * Returns an element whose class name contains the search value; compound 159 | * class names are not permitted 160 | */ 161 | const By.className(String className) : this._('class name', className); 162 | 163 | /// Returns an element matching a CSS selector. 164 | const By.cssSelector(String cssSelector) 165 | : this._('css selector', cssSelector); 166 | 167 | Map toJson() => {'using': _using, 'value': _value}; 168 | 169 | @override 170 | bool operator ==(Object other) => 171 | other is By && _using == other._using && _value == other._value; 172 | 173 | @override 174 | int get hashCode => _using.hashCode << 3 + _value.hashCode; 175 | 176 | @override 177 | WebElement findElement(SearchContext context) => context.findElement(this); 178 | 179 | @override 180 | List findElements(SearchContext context) => 181 | context.findElements(this); 182 | 183 | @override 184 | String toString() { 185 | switch (_using) { 186 | case 'link text': 187 | return 'By.linkText($_value)'; 188 | case 'partial link text': 189 | return 'By.partialLinkText($_value)'; 190 | case 'class name': 191 | return 'By.className($_value)'; 192 | case 'tag name': 193 | return 'By.tagName($_value)'; 194 | default: 195 | return 'By.$_using($_value)'; 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /lib/src/error.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | part of sync.webdriver; 18 | 19 | abstract class WebDriverException implements Exception { 20 | /** 21 | * Either the status value returned in the JSON response (preferred) or the 22 | * HTTP status code. 23 | */ 24 | final int statusCode; 25 | 26 | /** 27 | * A message describing the error. 28 | */ 29 | final String message; 30 | 31 | factory WebDriverException( 32 | {int httpStatusCode, String httpReasonPhrase, dynamic jsonResp}) { 33 | if (jsonResp is Map) { 34 | var status = jsonResp['status']; 35 | var message = jsonResp['value']['message']; 36 | 37 | switch (status) { 38 | case 0: 39 | throw new StateError( 40 | 'Not a WebDriverError Status: 0 Message: $message'); 41 | case 6: // NoSuchDriver 42 | return new NoSuchDriverException(status, message); 43 | case 7: // NoSuchElement 44 | return new NoSuchElementException(status, message); 45 | case 8: // NoSuchFrame 46 | return new NoSuchFrameException(status, message); 47 | case 9: // UnknownCommand 48 | return new UnknownCommandException(status, message); 49 | case 10: // StaleElementReferenceException 50 | return new StaleElementReferenceException(status, message); 51 | case 11: // ElementNotVisible 52 | return new ElementNotVisibleException(status, message); 53 | case 12: // InvalidElementState 54 | return new InvalidElementStateException(status, message); 55 | case 15: // ElementIsNotSelectable 56 | return new ElementIsNotSelectableException(status, message); 57 | case 17: // JavaScriptError 58 | return new JavaScriptException(status, message); 59 | case 19: // XPathLookupError 60 | return new XPathLookupException(status, message); 61 | case 21: // Timeout 62 | return new TimeoutException(status, message); 63 | case 23: // NoSuchWindow 64 | return new NoSuchWindowException(status, message); 65 | case 24: // InvalidCookieDomain 66 | return new InvalidCookieDomainException(status, message); 67 | case 25: // UnableToSetCookie 68 | return new UnableToSetCookieException(status, message); 69 | case 26: // UnexpectedAlertOpen 70 | return new UnexpectedAlertOpenException(status, message); 71 | case 27: // NoAlertOpenError 72 | return new NoAlertOpenException(status, message); 73 | case 29: // InvalidElementCoordinates 74 | return new InvalidElementCoordinatesException(status, message); 75 | case 30: // IMENotAvailable 76 | return new IMENotAvailableException(status, message); 77 | case 31: // IMEEngineActivationFailed 78 | return new IMEEngineActivationFailedException(status, message); 79 | case 32: // InvalidSelector 80 | return new InvalidSelectorException(status, message); 81 | case 33: // SessionNotCreatedException 82 | return new SessionNotCreatedException(status, message); 83 | case 34: // MoveTargetOutOfBounds 84 | return new MoveTargetOutOfBoundsException(status, message); 85 | case 13: // UnknownError 86 | default: // new error? 87 | return new UnknownException(status, message); 88 | } 89 | } 90 | if (jsonResp != null) { 91 | return new InvalidRequestException(httpStatusCode, jsonResp); 92 | } 93 | return new InvalidRequestException(httpStatusCode, httpReasonPhrase); 94 | } 95 | 96 | const WebDriverException._(this.statusCode, this.message); 97 | 98 | String toString() => '$runtimeType ($statusCode): $message'; 99 | } 100 | 101 | class InvalidRequestException extends WebDriverException { 102 | InvalidRequestException(statusCode, message) : super._(statusCode, message); 103 | } 104 | 105 | class UnknownException extends WebDriverException { 106 | UnknownException(statusCode, message) : super._(statusCode, message); 107 | } 108 | 109 | class NoSuchDriverException extends WebDriverException { 110 | NoSuchDriverException(statusCode, message) : super._(statusCode, message); 111 | } 112 | 113 | class NoSuchElementException extends WebDriverException { 114 | NoSuchElementException(statusCode, message) : super._(statusCode, message); 115 | } 116 | 117 | class NoSuchFrameException extends WebDriverException { 118 | NoSuchFrameException(statusCode, message) : super._(statusCode, message); 119 | } 120 | 121 | class UnknownCommandException extends WebDriverException { 122 | UnknownCommandException(statusCode, message) : super._(statusCode, message); 123 | } 124 | 125 | class StaleElementReferenceException extends WebDriverException { 126 | StaleElementReferenceException(statusCode, message) 127 | : super._(statusCode, message); 128 | } 129 | 130 | class ElementNotVisibleException extends WebDriverException { 131 | ElementNotVisibleException(statusCode, message) 132 | : super._(statusCode, message); 133 | } 134 | 135 | class InvalidElementStateException extends WebDriverException { 136 | InvalidElementStateException(statusCode, message) 137 | : super._(statusCode, message); 138 | } 139 | 140 | class ElementIsNotSelectableException extends WebDriverException { 141 | ElementIsNotSelectableException(statusCode, message) 142 | : super._(statusCode, message); 143 | } 144 | 145 | class JavaScriptException extends WebDriverException { 146 | JavaScriptException(statusCode, message) : super._(statusCode, message); 147 | } 148 | 149 | class XPathLookupException extends WebDriverException { 150 | XPathLookupException(statusCode, message) : super._(statusCode, message); 151 | } 152 | 153 | class TimeoutException extends WebDriverException { 154 | TimeoutException(statusCode, message) : super._(statusCode, message); 155 | } 156 | 157 | class NoSuchWindowException extends WebDriverException { 158 | NoSuchWindowException(statusCode, message) : super._(statusCode, message); 159 | } 160 | 161 | class InvalidCookieDomainException extends WebDriverException { 162 | InvalidCookieDomainException(statusCode, message) 163 | : super._(statusCode, message); 164 | } 165 | 166 | class UnableToSetCookieException extends WebDriverException { 167 | UnableToSetCookieException(statusCode, message) 168 | : super._(statusCode, message); 169 | } 170 | 171 | class UnexpectedAlertOpenException extends WebDriverException { 172 | UnexpectedAlertOpenException(statusCode, message) 173 | : super._(statusCode, message); 174 | } 175 | 176 | class NoAlertOpenException extends WebDriverException { 177 | NoAlertOpenException(statusCode, message) : super._(statusCode, message); 178 | } 179 | 180 | class InvalidElementCoordinatesException extends WebDriverException { 181 | InvalidElementCoordinatesException(statusCode, message) 182 | : super._(statusCode, message); 183 | } 184 | 185 | class IMENotAvailableException extends WebDriverException { 186 | IMENotAvailableException(statusCode, message) : super._(statusCode, message); 187 | } 188 | 189 | class IMEEngineActivationFailedException extends WebDriverException { 190 | IMEEngineActivationFailedException(statusCode, message) 191 | : super._(statusCode, message); 192 | } 193 | 194 | class InvalidSelectorException extends WebDriverException { 195 | InvalidSelectorException(statusCode, message) : super._(statusCode, message); 196 | } 197 | 198 | class SessionNotCreatedException extends WebDriverException { 199 | SessionNotCreatedException(statusCode, message) 200 | : super._(statusCode, message); 201 | } 202 | 203 | class MoveTargetOutOfBoundsException extends WebDriverException { 204 | MoveTargetOutOfBoundsException(statusCode, message) 205 | : super._(statusCode, message); 206 | } 207 | -------------------------------------------------------------------------------- /lib/src/keyboard.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | part of sync.webdriver; 18 | 19 | class Keyboard extends _WebDriverBase { 20 | Keyboard._(driver) : super(driver, 'keys'); 21 | 22 | /** 23 | * Send a [keysToSend] to the active element. 24 | */ 25 | void sendKeys(dynamic keysToSend) { 26 | if (keysToSend is String) { 27 | keysToSend = [keysToSend]; 28 | } 29 | _post('', {'value': keysToSend as List}); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/keys.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | part of sync.webdriver; 18 | 19 | class Keys { 20 | static const String NULL = '\uE000'; 21 | static const String CANCEL = '\uE001'; 22 | static const String HELP = '\uE002'; 23 | static const String BACK_SPACE = '\uE003'; 24 | static const String TAB = '\uE004'; 25 | static const String CLEAR = '\uE005'; 26 | static const String RETURN = '\uE006'; 27 | static const String ENTER = '\uE007'; 28 | static const String SHIFT = '\uE008'; 29 | static const String CONTROL = '\uE009'; 30 | static const String ALT = '\uE00A'; 31 | static const String PAUSE = '\uE00B'; 32 | static const String ESCAPE = '\uE00C'; 33 | static const String SPACE = '\uE00D'; 34 | static const String PAGE_UP = '\uE00E'; 35 | static const String PAGE_DOWN = '\uE00F'; 36 | static const String END = '\uE010'; 37 | static const String HOME = '\uE011'; 38 | static const String LEFT = '\uE012'; 39 | static const String UP = '\uE013'; 40 | static const String RIGHT = '\uE014'; 41 | static const String DOWN = '\uE015'; 42 | static const String INSERT = '\uE016'; 43 | static const String DELETE = '\uE017'; 44 | static const String SEMICOLON = '\uE018'; 45 | static const String EQUALS = '\uE019'; 46 | static const String NUMPAD_0 = '\uE01A'; 47 | static const String NUMPAD_1 = '\uE01B'; 48 | static const String NUMPAD_2 = '\uE01C'; 49 | static const String NUMPAD_3 = '\uE01D'; 50 | static const String NUMPAD_4 = '\uE01E'; 51 | static const String NUMPAD_5 = '\uE01F'; 52 | static const String NUMPAD_6 = '\uE020'; 53 | static const String NUMPAD_7 = '\uE021'; 54 | static const String NUMPAD_8 = '\uE022'; 55 | static const String NUMPAD_9 = '\uE023'; 56 | static const String MULTIPLY = '\uE024'; 57 | static const String ADD = '\uE025'; 58 | static const String SEPARATOR = '\uE026'; 59 | static const String SUBTRACT = '\uE027'; 60 | static const String DECIMAL = '\uE028'; 61 | static const String DIVIDE = '\uE029'; 62 | static const String F1 = '\uE031'; 63 | static const String F2 = '\uE032'; 64 | static const String F3 = '\uE033'; 65 | static const String F4 = '\uE034'; 66 | static const String F5 = '\uE035'; 67 | static const String F6 = '\uE036'; 68 | static const String F7 = '\uE037'; 69 | static const String F8 = '\uE038'; 70 | static const String F9 = '\uE039'; 71 | static const String F10 = '\uE03A'; 72 | static const String F11 = '\uE03B'; 73 | static const String F12 = '\uE03C'; 74 | static const String COMMAND = '\uE03D'; 75 | static const String META = COMMAND; 76 | } 77 | -------------------------------------------------------------------------------- /lib/src/logs.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2015 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | part of sync.webdriver; 18 | 19 | class Logs extends _WebDriverBase { 20 | Logs._(driver) : super(driver, 'log'); 21 | 22 | Iterable get(String logType) => 23 | _post('', {'type': logType}).map((entry) => new LogEntry.fromMap(entry)); 24 | } 25 | 26 | class LogEntry { 27 | final String message; 28 | final int timestamp; 29 | final String level; 30 | 31 | const LogEntry(this.message, this.timestamp, this.level); 32 | 33 | LogEntry.fromMap(Map map) 34 | : this(map['message'], map['timestamp'], map['level']); 35 | } 36 | 37 | class LogType { 38 | static const String BROWSER = 'browser'; 39 | static const String CLIENT = 'client'; 40 | static const String DRIVER = 'driver'; 41 | static const String PERFORMANCE = 'performance'; 42 | static const String PROFILER = 'profiler'; 43 | static const String SERVER = 'server'; 44 | } 45 | 46 | class LogLevel { 47 | static const String OFF = 'OFF'; 48 | static const String SEVERE = 'SEVERE'; 49 | static const String WARNING = 'WARNING'; 50 | static const String INFO = 'INFO'; 51 | static const String DEBUG = 'DEBUG'; 52 | static const String ALL = 'ALL'; 53 | } 54 | -------------------------------------------------------------------------------- /lib/src/mouse.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | part of sync.webdriver; 18 | 19 | class Mouse extends _WebDriverBase { 20 | static const int LEFT = 0; 21 | static const int MIDDLE = 1; 22 | static const int RIGHT = 2; 23 | 24 | Mouse._(driver) : super(driver, ''); 25 | 26 | /// Click any mouse button (at the coordinates set by the last moveTo). 27 | void click([int button]) { 28 | var json = {}; 29 | if (button is num) { 30 | json['button'] = button.clamp(0, 2).floor(); 31 | } 32 | _post('click', json); 33 | } 34 | 35 | /** 36 | * Click and hold any mouse button (at the coordinates set by the last 37 | * moveTo command). 38 | */ 39 | void down([int button]) { 40 | var json = {}; 41 | if (button is num) { 42 | json['button'] = button.clamp(0, 2).floor(); 43 | } 44 | _post('buttondown', json); 45 | } 46 | 47 | /** 48 | * Releases the mouse button previously held (where the mouse is currently 49 | * at). 50 | */ 51 | void up([int button]) { 52 | var json = {}; 53 | if (button is num) { 54 | json['button'] = button.clamp(0, 2).floor(); 55 | } 56 | _post('buttonup', json); 57 | } 58 | 59 | /// Double-clicks at the current mouse coordinates (set by moveTo). 60 | void doubleClick() => _post('doubleclick'); 61 | 62 | /** 63 | * Move the mouse. 64 | * 65 | * If [element] is specified and [xOffset] and [yOffset] are not, will move 66 | * the mouse to the center of the [element]. 67 | * 68 | * If [xOffset] and [yOffset] are specified, will move the mouse that distance 69 | * from its current location. 70 | * 71 | * If all three are specified, will move the mouse to the offset relative to 72 | * the top-left corner of the [element]. 73 | * 74 | * All other combinations of parameters are illegal. 75 | */ 76 | void moveTo({WebElement element, int xOffset, int yOffset}) { 77 | var json = {}; 78 | if (element is WebElement) { 79 | json['element'] = element.id; 80 | } 81 | if (xOffset is num && yOffset is num) { 82 | json['xoffset'] = xOffset.floor(); 83 | json['yoffset'] = yOffset.floor(); 84 | } 85 | _post('moveto', json); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/src/navigation.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | part of sync.webdriver; 18 | 19 | class Navigation extends _WebDriverBase { 20 | Navigation._(driver) : super(driver, ''); 21 | 22 | /// Navigate forwards in the browser history, if possible. 23 | void forward() => _post('forward'); 24 | 25 | /// Navigate backwards in the browser history, if possible. 26 | void back() => _post('back'); 27 | 28 | /// Refresh the current page. 29 | void refresh() => _post('refresh'); 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/options.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | part of sync.webdriver; 18 | 19 | class Cookies extends _WebDriverBase { 20 | Cookies._(driver) : super(driver, 'cookie'); 21 | 22 | /// Set a cookie. 23 | void add(Cookie cookie) => _post('', {'cookie': cookie}); 24 | 25 | /// Delete the cookie with the given [name]. 26 | void delete(String name) => _delete(name); 27 | 28 | /// Delete all cookies visible to the current page. 29 | void deleteAll() => _delete(''); 30 | 31 | /// Retrieve all cookies visible to the current page. 32 | Iterable get all => 33 | _get('').map((cookie) => new Cookie.fromJson(cookie)); 34 | } 35 | 36 | class Cookie { 37 | /// The name of the cookie. 38 | final String name; 39 | 40 | /// The cookie value. 41 | final String value; 42 | 43 | /// (Optional) The cookie path. 44 | final String path; 45 | 46 | /// (Optional) The domain the cookie is visible to. 47 | final String domain; 48 | 49 | /// (Optional) Whether the cookie is a secure cookie. 50 | final bool secure; 51 | 52 | /// (Optional) When the cookie expires. 53 | final DateTime expiry; 54 | 55 | const Cookie(this.name, this.value, 56 | {this.path, this.domain, this.secure, this.expiry}); 57 | 58 | factory Cookie.fromJson(Map json) { 59 | var expiry; 60 | if (json['expiry'] is num) { 61 | expiry = new DateTime.fromMillisecondsSinceEpoch( 62 | (json['expiry'] * 1000).round(), 63 | isUtc: true); 64 | } 65 | return new Cookie(json['name'], json['value'], 66 | path: json['path'], 67 | domain: json['domain'], 68 | secure: json['secure'], 69 | expiry: expiry); 70 | } 71 | 72 | Map toJson() { 73 | var json = {'name': name, 'value': value}; 74 | if (path is String) { 75 | json['path'] = path; 76 | } 77 | if (domain is String) { 78 | json['domain'] = domain; 79 | } 80 | if (secure is bool) { 81 | json['secure'] = secure; 82 | } 83 | if (expiry is DateTime) { 84 | json['expiry'] = (expiry.millisecondsSinceEpoch / 1000).ceil(); 85 | } 86 | return json; 87 | } 88 | 89 | @override 90 | String toString() => toJson().toString(); 91 | } 92 | 93 | class Timeouts extends _WebDriverBase { 94 | Duration _scriptTimeout; 95 | Duration _implicitWaitTimeout; 96 | Duration _pageLoadTimeout; 97 | 98 | Timeouts._(driver) : super(driver, 'timeouts'); 99 | 100 | _set(String type, Duration duration) => 101 | _post('', {'type': type, 'ms': duration.inMilliseconds}); 102 | 103 | /// Get the script timeout. 104 | Duration get scriptTimeout => _scriptTimeout; 105 | 106 | /// Set the script timeout. 107 | set scriptTimeout(Duration duration) { 108 | _set('script', duration); 109 | _scriptTimeout = duration; 110 | } 111 | 112 | /// Get the implicit timeout. 113 | Duration get implicitWaitTimeout => _implicitWaitTimeout; 114 | 115 | /// Set the implicit timeout. 116 | set implicitWaitTimeout(Duration duration) { 117 | _set('implicit', duration); 118 | _implicitWaitTimeout = duration; 119 | } 120 | 121 | /// Get the page load timeout. 122 | Duration get pageLoadTimeout => _pageLoadTimeout; 123 | 124 | /// Set the page load timeout. 125 | set pageLoadTimeout(Duration duration) { 126 | _set('page load', duration); 127 | _pageLoadTimeout = duration; 128 | } 129 | } 130 | 131 | -------------------------------------------------------------------------------- /lib/src/target_locator.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | part of sync.webdriver; 18 | 19 | class TargetLocator extends _WebDriverBase { 20 | TargetLocator._(driver) : super(driver, ''); 21 | 22 | /** 23 | * Change focus to another frame on the page. 24 | * 25 | * If [frame] is a: 26 | * int: select by its zero-based indexed 27 | * String: select frame by the name of the frame window or the id of the 28 | * frame or iframe tag. 29 | * WebElement: select the frame for a previously found frame or iframe 30 | * element. 31 | * not provided: selects the first frame on the page or the main document. 32 | * 33 | * Throws [NoSuchFrameException] if the specified frame can't be found. 34 | */ 35 | void frame([/* String | int | WebElement */ frame]) => 36 | _post('frame', {'id': frame}); 37 | 38 | /** 39 | * Switch the focus of future commands for this driver to the window with the 40 | * given name/handle. 41 | * 42 | * Throws [NoSuchWindowException] if the specified window can't be found. 43 | */ 44 | void window(/* String | Window */ window) => 45 | _post('window', {'name': window}); 46 | 47 | /** 48 | * Switches to the currently active modal dialog for this particular driver 49 | * instance. 50 | * 51 | * Throws [NoAlertOpenException] if there is not currently an alert. 52 | */ 53 | Alert get alert => new Alert._(_get('alert_text'), driver); 54 | } 55 | -------------------------------------------------------------------------------- /lib/src/touch.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | part of sync.webdriver; 18 | 19 | class Touch extends _WebDriverBase { 20 | Touch._(driver) : super(driver, 'touch'); 21 | 22 | /** 23 | * Single tap on the touch enabled device. 24 | */ 25 | void click(WebElement element) => _post('click', element); 26 | 27 | /** 28 | * Finger down on the screen. 29 | */ 30 | void down(Point point) => _post('down', _pointToJson(point)); 31 | 32 | /** 33 | * Finger up on the screen. 34 | */ 35 | void up(Point point) => _post('up', _pointToJson(point)); 36 | 37 | /** 38 | * Finger move on the screen. 39 | */ 40 | void move(Point point) => _post('move', _pointToJson(point)); 41 | 42 | /** 43 | * Scroll on the touch screen using finger based motion events. 44 | * 45 | * If start is specified, will start scrolling from that location, otherwise 46 | * will start scrolling from an arbitrary location. 47 | */ 48 | void scroll(int xOffset, int yOffset, [WebElement start]) { 49 | var json = {'xoffset': xOffset.floor(), 50 | 'yoffset': yOffset.floor()}; 51 | if (start is WebElement) { 52 | json['element'] = start.id; 53 | } 54 | _post('scroll', json); 55 | } 56 | 57 | /** 58 | * Double tap on the touch screen using finger motion events. 59 | */ 60 | void doubleClick(WebElement element) => _post('doubleclick', element); 61 | 62 | /** 63 | * Long press on the touch screen using finger motion events. 64 | */ 65 | void longClick(WebElement element) => _post('longclick', element); 66 | 67 | /** 68 | * Flick on the touch screen using finger motion events. 69 | */ 70 | void flickElement(WebElement start, int xOffset, int yOffset, int speed) => 71 | _post('flick', { 72 | 'element': start.id, 73 | 'xoffset': xOffset.floor(), 74 | 'yoffset': yOffset.floor(), 75 | 'speed': speed.floor() 76 | }); 77 | 78 | /** 79 | * Flick on the touch screen using finger motion events. 80 | */ 81 | void flick(int xSpeed, int ySpeed) => 82 | _post('flick', {'xspeed': xSpeed.floor(), 'yspeed': ySpeed.floor()}); 83 | } 84 | -------------------------------------------------------------------------------- /lib/src/util.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | part of sync.webdriver; 18 | 19 | const _DEFAULT_WAIT = const Duration(seconds: 4); 20 | const _INTERVAL = const Duration(milliseconds: 100); 21 | 22 | bool useUnittestMatchers = false; 23 | 24 | /** 25 | * Waits for [condition] to be evaluated successfully, and returns 26 | * that value. 27 | * 28 | * A successful evaluation is when the value returned by [condition] 29 | * is any value other than [null]. 30 | * 31 | * Any exceptions raised during the evauluation of [condition] are 32 | * caught and treated as an unsuccessful evaluation. 33 | */ 34 | waitForValue(condition(), 35 | {Duration timeout: _DEFAULT_WAIT, 36 | Duration interval: _INTERVAL, 37 | onError(Object error)}) { 38 | return waitFor(condition, useUnittestMatchers ? ut.isNotNull : m.isNotNull, 39 | timeout: timeout, interval: interval); 40 | } 41 | 42 | /** 43 | * Waits for [condition] to be evaluated successfully, and returns 44 | * that value. 45 | * 46 | * A successful evaluation is when the value returned by [condition] 47 | * has a value that satisfies the [matcher]. 48 | * 49 | * Any exceptions raised during the evauluation of [condition] are 50 | * caught and treated as an unsuccessful evaluation. 51 | */ 52 | waitFor(condition(), matcher, 53 | {Duration timeout: _DEFAULT_WAIT, 54 | Duration interval: _INTERVAL, 55 | onError(Object error)}) { 56 | conditionWithExpect() { 57 | expect(value, matcher) { 58 | if (matcher is! m.Matcher) { 59 | matcher = m.equals(matcher); 60 | } 61 | 62 | var matchState = {}; 63 | if (matcher.matches(value, matchState)) { 64 | return; 65 | } 66 | var desc = new m.StringDescription() 67 | ..add('Expected: ') 68 | ..addDescriptionOf(matcher) 69 | ..add('\n') 70 | ..add(' Actual: ') 71 | ..addDescriptionOf(value) 72 | ..add('\n'); 73 | 74 | var mismatchDescription = new m.StringDescription(); 75 | matcher.describeMismatch(value, mismatchDescription, matchState, true); 76 | if (mismatchDescription.length > 0) { 77 | desc.add(' Which: ${mismatchDescription}\n'); 78 | } 79 | throw new Exception(desc.toString()); 80 | } 81 | 82 | var value = condition(); 83 | if (matcher is ut.Matcher) { 84 | ut.expect(value, matcher); 85 | } else if (matcher is m.Matcher) { 86 | expect(value, matcher); 87 | } else if (useUnittestMatchers) { 88 | ut.expect(value, matcher); 89 | } else { 90 | expect(value, matcher); 91 | } 92 | return value; 93 | } 94 | return _waitFor(conditionWithExpect, timeout, interval, onError); 95 | } 96 | 97 | _waitFor( 98 | condition(), Duration timeout, Duration interval, onError(Object error)) { 99 | var endTime = new DateTime.now().add(timeout); 100 | 101 | var result; 102 | while (true) { 103 | try { 104 | return condition(); 105 | } catch (e) { 106 | if (onError != null) { 107 | onError(e); 108 | } 109 | result = e; 110 | } 111 | if (endTime.isBefore(new DateTime.now())) { 112 | break; 113 | } 114 | sleep(interval); 115 | } 116 | 117 | throw new StateError( 118 | 'Condition timeout after $timeout. It evaluated to $result'); 119 | } 120 | 121 | get isEnabled => useUnittestMatchers 122 | ? ut.wrapMatcher((WebElement e) => e.enabled) 123 | : m.wrapMatcher((WebElement e) => e.enabled); 124 | 125 | get isNotEnabled => 126 | useUnittestMatchers ? ut.isNot(isEnabled) : m.isNot(isEnabled); 127 | 128 | get isDisplayed => useUnittestMatchers 129 | ? ut.wrapMatcher((WebElement e) => e.displayed) 130 | : m.wrapMatcher((WebElement e) => e.displayed); 131 | 132 | get isNotDisplayed => 133 | useUnittestMatchers ? ut.isNot(isDisplayed) : m.isNot(isDisplayed); 134 | 135 | get isSelected => useUnittestMatchers 136 | ? ut.wrapMatcher((WebElement e) => e.selected) 137 | : m.wrapMatcher((WebElement e) => e.selected); 138 | 139 | get isNotSelected => 140 | useUnittestMatchers ? ut.isNot(isSelected) : m.isNot(isSelected); 141 | 142 | hasText(matcher) => useUnittestMatchers 143 | ? new _HasTextUnittest(ut.wrapMatcher(matcher)) 144 | : new _HasTextMatcher(m.wrapMatcher(matcher)); 145 | 146 | class _HasTextUnittest extends ut.Matcher { 147 | final ut.Matcher _matcher; 148 | const _HasTextUnittest(this._matcher); 149 | 150 | bool matches(item, Map matchState) => 151 | item is WebElement && _matcher.matches(item.text, matchState); 152 | 153 | ut.Description describe(ut.Description description) => 154 | description.add('a WebElement with text of ').addDescriptionOf(_matcher); 155 | 156 | ut.Description describeMismatch( 157 | item, ut.Description mismatchDescription, Map matchState, bool verbose) { 158 | return mismatchDescription.add('has text of ').addDescriptionOf(item.text); 159 | } 160 | } 161 | 162 | class _HasTextMatcher extends m.Matcher { 163 | final m.Matcher _matcher; 164 | const _HasTextMatcher(this._matcher); 165 | 166 | bool matches(item, Map matchState) => 167 | item is WebElement && _matcher.matches(item.text, matchState); 168 | 169 | m.Description describe(m.Description description) => 170 | description.add('a WebElement with text of ').addDescriptionOf(_matcher); 171 | 172 | m.Description describeMismatch( 173 | item, m.Description mismatchDescription, Map matchState, bool verbose) { 174 | return mismatchDescription.add('has text of ').addDescriptionOf(item.text); 175 | } 176 | } 177 | 178 | // TODO Exception management 179 | class UnexpectedTagNameException extends FormatException { 180 | UnexpectedTagNameException(String expected, String actual) 181 | : super('Element should have been "$expected" but was "$actual"'); 182 | } 183 | 184 | _setSelected(WebElement option) { 185 | if (!option.selected) { 186 | option.click(); 187 | } 188 | } 189 | 190 | _unsetSelected(WebElement option) { 191 | if (option.selected) { 192 | option.click(); 193 | } 194 | } 195 | 196 | String _escapeQuotes(String toEscape) { 197 | // Convert strings with both quotes and ticks into: 198 | // foo'"bar -> concat("foo'", '"', "bar") 199 | if (toEscape.indexOf("\"") > -1 && toEscape.indexOf("'") > -1) { 200 | var quoteIsLast = false; 201 | if (toEscape.lastIndexOf("\"") == toEscape.length - 1) { 202 | quoteIsLast = true; 203 | } 204 | var substrings = toEscape.split("\""); 205 | 206 | var quoted = new StringBuffer("concat("); 207 | for (int i = 0; i < substrings.length; i++) { 208 | quoted 209 | ..write("\"") 210 | ..write(substrings[i]) 211 | ..write("\"") 212 | ..write((i == substrings.length - 1) 213 | ? (quoteIsLast ? ", '\"')" : ")") 214 | : ", '\"', "); 215 | } 216 | return quoted.toString(); 217 | } 218 | 219 | // Escape string with just a quote into being single quoted: f"oo -> 'f"oo' 220 | if (toEscape.indexOf('"') > -1) { 221 | return "'$toEscape'"; 222 | } 223 | 224 | // Otherwise return the quoted string 225 | return '"$toEscape"'; 226 | } 227 | 228 | String _getLongestSubstringWithoutSpace(String value) { 229 | var longest = ""; 230 | value.split(" ").forEach((item) { 231 | if (item.length > longest.length) { 232 | longest = item; 233 | } 234 | }); 235 | return longest; 236 | } 237 | 238 | /** 239 | * Models a SELECT tag, providing helper methods to select and deselect options. 240 | */ 241 | @proxy 242 | class Select implements WebElement { 243 | WebElement _element; 244 | bool _isMulti; 245 | 246 | /** 247 | * A check is made that the given [element] is, indeed, a SELECT tag. 248 | * If it is not, then an [UnexpectedTagNameException] is thrown. 249 | */ 250 | Select(WebElement element) { 251 | var tagName = element.name; 252 | 253 | if (null == tagName || tagName.toLowerCase() != "select") { 254 | throw new UnexpectedTagNameException("select", tagName); 255 | } 256 | 257 | _element = element; 258 | 259 | var multi = element.attributes["multiple"]; 260 | 261 | // The atoms normalize the returned value, but check for "false" 262 | _isMulti = multi != null && multi != "false"; 263 | } 264 | 265 | /// Delegates to the WebElement 266 | noSuchMethod(Invocation msg) => reflect(_element).delegate(msg); 267 | 268 | /** 269 | * Whether this select element support selecting multiple options at the same time? 270 | * This is done by checking the value of the "multiple" attribute. 271 | */ 272 | bool get isMultiple => _isMulti; 273 | 274 | /** 275 | * All options belonging to this select tag 276 | */ 277 | List get options => 278 | _element.findElements(const By.tagName("option")); 279 | 280 | /** 281 | * All selected options belonging to this select tag 282 | */ 283 | List get allSelectedOptions => 284 | options.where((option) => option.selected).toList(growable: false); 285 | 286 | /** 287 | * The first selected option in this select tag (or the currently selected 288 | * option in a normal select) 289 | */ 290 | // TODO Exception management 291 | WebElement get firstSelectedOption => 292 | options.firstWhere((option) => option.selected); 293 | 294 | /// String representation of the current value of this select. 295 | String get value => _element.attributes['value']; 296 | 297 | /** 298 | * Select the option at the given [index]. 299 | * This is done by examing the "index" attribute of an element, and not 300 | * merely by counting. 301 | */ 302 | selectByIndex(int index) { 303 | var match = index.toString(); 304 | 305 | var matched = false; 306 | for (var option in options) { 307 | if (option.attributes["index"] == match) { 308 | _setSelected(option); 309 | if (!isMultiple) { 310 | return; 311 | } 312 | matched = true; 313 | } 314 | } 315 | 316 | if (!matched) { 317 | // TODO Exception management 318 | throw new NoSuchElementException( 319 | 0, "Cannot locate option with index: $index"); 320 | } 321 | } 322 | 323 | /** 324 | * Select all options that have a [value] matching the argument. 325 | * That is, when given "foo" this would select an option like: 326 | */ 327 | selectByValue(String value) { 328 | var matched = false; 329 | for (var option in _findByValue(value)) { 330 | _setSelected(option); 331 | if (!isMultiple) { 332 | return; 333 | } 334 | matched = true; 335 | } 336 | 337 | if (!matched) { 338 | // TODO Exception management 339 | throw new NoSuchElementException( 340 | 0, "Cannot locate option with value: $value"); 341 | } 342 | } 343 | 344 | /** 345 | * Select all options that display [text] matching the argument. 346 | * That is, when given "Bar" this would select an option like: 347 | * 348 | * <option value="foo">Bar</option> 349 | */ 350 | selectByVisibleText(String text) { 351 | var foundOptions = _findByVisibleText(text); 352 | 353 | var matched = false; 354 | for (var option in foundOptions) { 355 | _setSelected(option); 356 | if (!isMultiple) { 357 | return; 358 | } 359 | matched = true; 360 | } 361 | 362 | if (foundOptions.isEmpty && text.contains(" ")) { 363 | for (var option in _findByVisibleTextWithSpace(text)) { 364 | if (option.text == text) { 365 | _setSelected(option); 366 | if (!isMultiple) { 367 | return; 368 | } 369 | matched = true; 370 | } 371 | } 372 | } 373 | 374 | if (!matched) { 375 | // TODO Exception management 376 | throw new NoSuchElementException( 377 | 0, "Cannot locate element with text: $text"); 378 | } 379 | } 380 | 381 | /** 382 | * Clear all selected entries. 383 | * This is only valid when the SELECT supports multiple selections. 384 | */ 385 | deselectAll() { 386 | if (!isMultiple) { 387 | // TODO Exception management 388 | throw new Exception( 389 | "You may only deselect all options of a multi-select"); 390 | } 391 | 392 | for (var option in options) { 393 | _unsetSelected(option); 394 | } 395 | } 396 | 397 | /** 398 | * Deselect the option at the given [index]. 399 | * This is done by examing the "index" attribute of an element, and not 400 | * merely by counting. 401 | */ 402 | deselectByIndex(int index) { 403 | var match = index.toString(); 404 | 405 | for (var option in options) { 406 | if (option.attributes["index"] == match) { 407 | _unsetSelected(option); 408 | } 409 | } 410 | } 411 | 412 | /** 413 | * Deselect all options that have a [value] matching the argument. 414 | * That is, when given "foo" this would deselect an option like: 415 | * 416 | * <option value="foo">Bar</option> 417 | */ 418 | deselectByValue(String value) { 419 | for (var option in _findByValue(value)) { 420 | _unsetSelected(option); 421 | } 422 | } 423 | 424 | /** 425 | * Deselect all options that display [text] matching the argument. 426 | * That is, when given "Bar" this would deselect an option like: 427 | * 428 | * <option value="foo">Bar</option> 429 | */ 430 | deselectByVisibleText(String text) { 431 | var foundOptions = _findByVisibleText(text); 432 | 433 | for (var option in foundOptions) { 434 | _unsetSelected(option); 435 | } 436 | 437 | if (foundOptions.isEmpty && text.contains(" ")) { 438 | for (var option in _findByVisibleTextWithSpace(text)) { 439 | _unsetSelected(option); 440 | } 441 | } 442 | } 443 | 444 | List _findByValue(String value) { 445 | var buffer = new StringBuffer(".//option[@value = "); 446 | buffer.write(_escapeQuotes(value)); 447 | buffer.write("]"); 448 | return _element.findElements(new By.xpath(buffer.toString())); 449 | } 450 | 451 | List _findByVisibleText(String text) { 452 | var buffer = new StringBuffer(".//option[normalize-space(.) = "); 453 | buffer.write(_escapeQuotes(text)); 454 | buffer.write("]"); 455 | return _element.findElements(new By.xpath(buffer.toString())); 456 | } 457 | 458 | List _findByVisibleTextWithSpace(String text) { 459 | var candidates; 460 | var subStringWithoutSpace = _getLongestSubstringWithoutSpace(text); 461 | if (subStringWithoutSpace == "") { 462 | // hmm, text is either empty or contains only spaces - get all options ... 463 | candidates = options; 464 | } else { 465 | // get candidates via XPATH ... 466 | var buffer = new StringBuffer(".//option[contains(., "); 467 | buffer.write(_escapeQuotes(subStringWithoutSpace)); 468 | buffer.write(")]"); 469 | candidates = _element.findElements(new By.xpath(buffer.toString())); 470 | } 471 | return candidates; 472 | } 473 | } 474 | -------------------------------------------------------------------------------- /lib/src/web_driver.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | part of sync.webdriver; 18 | 19 | @deprecated 20 | 21 | /// Use [CommandEvent] instead. 22 | typedef void CommandListener(String method, String endpoint, params); 23 | 24 | class WebDriver extends SearchContext { 25 | static final Uri DEFAULT_URI = new Uri.http('127.0.0.1:4444', '/wd/hub'); 26 | static final HttpClientSync _client = new HttpClientSync(); 27 | 28 | final Uri uri; 29 | final Map capabilities; 30 | 31 | JsonCodec _jsonDecoder; 32 | Timeouts _timeouts; 33 | 34 | final StreamController _onCommand = 35 | new StreamController.broadcast(sync: true); 36 | Stream get onCommand => _onCommand.stream; 37 | 38 | factory WebDriver( 39 | {Uri uri: null, 40 | Map required: null, 41 | Map desired: const {}}) { 42 | if (uri == null) { 43 | uri = DEFAULT_URI; 44 | } 45 | var request = _client 46 | .postUrl(new Uri.http(uri.authority, path.join(uri.path, 'session'))); 47 | var jsonParams = {"desiredCapabilities": desired}; 48 | 49 | if (required != null) { 50 | jsonParams["requiredCapabilities"] = required; 51 | } 52 | 53 | request.headers.contentType = _CONTENT_TYPE_JSON; 54 | request.write(JSON.encode(jsonParams)); 55 | 56 | var resp = request.close(); 57 | 58 | var sessionUri; 59 | var capabilities = const {}; 60 | switch (resp.statusCode) { 61 | case HttpStatus.SEE_OTHER: 62 | case HttpStatus.MOVED_TEMPORARILY: 63 | case HttpStatus.MOVED_PERMANENTLY: 64 | sessionUri = Uri.parse(resp.headers.value(HttpHeaders.LOCATION)); 65 | if (sessionUri.authority == null || sessionUri.authority.isEmpty) { 66 | sessionUri = new Uri.http(uri.authority, sessionUri.path); 67 | } 68 | break; 69 | case HttpStatus.OK: 70 | var jsonResp = _parseBody(resp); 71 | if (jsonResp is! Map || jsonResp['status'] != 0) { 72 | throw new WebDriverException( 73 | httpStatusCode: resp.statusCode, 74 | httpReasonPhrase: resp.reasonPhrase, 75 | jsonResp: jsonResp); 76 | } 77 | 78 | sessionUri = _sessionUri(uri, jsonResp['sessionId']); 79 | capabilities = new UnmodifiableMapView(jsonResp['value']); 80 | break; 81 | default: 82 | throw new WebDriverException( 83 | httpStatusCode: resp.statusCode, 84 | httpReasonPhrase: resp.reasonPhrase, 85 | jsonResp: _parseBody(resp)); 86 | } 87 | 88 | return new WebDriver._(sessionUri, capabilities); 89 | } 90 | 91 | factory WebDriver.fromExistingSession(String sessionId, {Uri uri}) { 92 | if (uri == null) { 93 | uri = DEFAULT_URI; 94 | } 95 | 96 | var request = _client.getUrl(_sessionUri(uri, sessionId)); 97 | var resp = request.close(); 98 | var jsonResp = _parseBody(resp); 99 | 100 | if (jsonResp is! Map || jsonResp['status'] != 0) { 101 | throw new WebDriverException( 102 | httpStatusCode: resp.statusCode, 103 | httpReasonPhrase: resp.reasonPhrase, 104 | jsonResp: jsonResp); 105 | } 106 | 107 | var capabilities = new UnmodifiableMapView(jsonResp['value']); 108 | return new WebDriver._(_sessionUri(uri, sessionId), capabilities); 109 | } 110 | 111 | static Uri _sessionUri(Uri uri, String sessionId) => 112 | new Uri.http(uri.authority, path.join(uri.path, "session", sessionId)); 113 | 114 | WebDriver._(this.uri, this.capabilities) { 115 | _jsonDecoder = new JsonCodec.withReviver(_reviver); 116 | _timeouts = new Timeouts._(this); 117 | } 118 | 119 | @override 120 | WebDriver get driver => this; 121 | 122 | set url(String url) => post('url', {'url': url}); 123 | 124 | String get url => get('url'); 125 | 126 | String get title => get('title'); 127 | 128 | String get pageSource => get('source'); 129 | 130 | void close() { 131 | delete('window'); 132 | } 133 | 134 | void quit() { 135 | delete(''); 136 | } 137 | 138 | Iterable get windows => 139 | get('window_handles').map((handle) => new Window._(this, handle)); 140 | 141 | Window get window => new Window._(this, get('window_handle')); 142 | 143 | WebElement get activeElement => get('element/active'); 144 | 145 | TargetLocator get switchTo => new TargetLocator._(this); 146 | 147 | Navigation get navigate => new Navigation._(this); 148 | 149 | Mouse get mouse => new Mouse._(this); 150 | 151 | Keyboard get keyboard => new Keyboard._(this); 152 | 153 | Touch get touch => new Touch._(this); 154 | 155 | Cookies get cookies => new Cookies._(this); 156 | 157 | Logs get logs => new Logs._(this); 158 | 159 | Timeouts get timeouts => _timeouts; 160 | 161 | /** 162 | * Inject a snippet of JavaScript into the page for execution in the context 163 | * of the currently selected frame. The executed script is assumed to be 164 | * asynchronous and must signal that is done by invoking the provided 165 | * callback, which is always provided as the final argument to the function. 166 | * The value to this callback will be returned to the client. 167 | * 168 | * Asynchronous script commands may not span page loads. If an unload event 169 | * is fired while waiting for a script result, an error will be thrown. 170 | * 171 | * The script argument defines the script to execute in the form of a 172 | * function body. The function will be invoked with the provided args array 173 | * and the values may be accessed via the arguments object in the order 174 | * specified. The final argument will always be a callback function that must 175 | * be invoked to signal that the script has finished. 176 | * 177 | * Arguments may be any JSON-able object. WebElements will be converted to 178 | * the corresponding DOM element. Likewise, any DOM Elements in the script 179 | * result will be converted to WebElements. 180 | */ 181 | dynamic executeAsync(String script, List args) => 182 | post('execute_async', {'script': script, 'args': args}); 183 | 184 | /** 185 | * Inject a snippet of JavaScript into the page for execution in the context 186 | * of the currently selected frame. The executed script is assumed to be 187 | * synchronous and the result of evaluating the script is returned. 188 | * 189 | * The script argument defines the script to execute in the form of a 190 | * function body. The value returned by that function will be returned to the 191 | * client. The function will be invoked with the provided args array and the 192 | * values may be accessed via the arguments object in the order specified. 193 | * 194 | * Arguments may be any JSON-able object. WebElements will be converted to 195 | * the corresponding DOM element. Likewise, any DOM Elements in the script 196 | * result will be converted to WebElements. 197 | */ 198 | dynamic execute(String script, List args) => 199 | post('execute', {'script': script, 'args': args}); 200 | 201 | List captureScreenshot() => 202 | new UnmodifiableListView(BASE64.decode(captureScreenshotAsBase64())); 203 | 204 | String captureScreenshotAsBase64() => get('screenshot'); 205 | 206 | _reviver(dynamic key, dynamic value) { 207 | if (value is Map && value.containsKey('ELEMENT')) { 208 | return new WebElement._(this, value['ELEMENT']); 209 | } 210 | return value; 211 | } 212 | 213 | @override 214 | _post(String command, [params]) => post(command, params); 215 | 216 | post(String command, [params]) { 217 | var startTime = new DateTime.now(); 218 | var response; 219 | var exception; 220 | try { 221 | var path = _processCommand(command); 222 | var request = _client.postUrl(new Uri.http(uri.authority, path)); 223 | if (params != null) { 224 | request.headers.contentType = _CONTENT_TYPE_JSON; 225 | request.write(JSON.encode(params)); 226 | } 227 | response = _processResponse(request.close()); 228 | return response; 229 | } catch (e) { 230 | exception = e; 231 | rethrow; 232 | } finally { 233 | _onCommand.add(new CommandEvent( 234 | method: 'POST', 235 | endpoint: command, 236 | params: params != null ? JSON.encode(params) : null, 237 | startTime: startTime, 238 | endTime: new DateTime.now(), 239 | result: response != null ? JSON.encode(response) : null, 240 | exception: exception != null ? exception.toString() : null, 241 | stackTrace: new Trace.current(1))); 242 | } 243 | } 244 | 245 | get(String command) { 246 | var startTime = new DateTime.now(); 247 | var response; 248 | var exception; 249 | try { 250 | var path = _processCommand(command); 251 | var request = _client.getUrl(new Uri.http(uri.authority, path)); 252 | response = _processResponse(request.close()); 253 | return response; 254 | } catch (e) { 255 | exception = e; 256 | rethrow; 257 | } finally { 258 | if (command.contains('screenshot') && response != null) { 259 | response = 'screenshot'; 260 | } 261 | _onCommand.add(new CommandEvent( 262 | method: 'GET', 263 | endpoint: command, 264 | startTime: startTime, 265 | endTime: new DateTime.now(), 266 | result: response != null ? JSON.encode(response) : null, 267 | exception: exception != null ? exception.toString() : null, 268 | stackTrace: new Trace.current(1))); 269 | } 270 | } 271 | 272 | delete(String command) { 273 | var startTime = new DateTime.now(); 274 | var response; 275 | var exception; 276 | try { 277 | var path = _processCommand(command); 278 | var request = _client.deleteUrl(new Uri.http(uri.authority, path)); 279 | response = _processResponse(request.close()); 280 | return response; 281 | } catch (e) { 282 | exception = e; 283 | rethrow; 284 | } finally { 285 | _onCommand.add(new CommandEvent( 286 | method: 'DELETE', 287 | endpoint: command, 288 | startTime: startTime, 289 | endTime: new DateTime.now(), 290 | result: response != null ? JSON.encode(response) : null, 291 | exception: exception != null ? exception.toString() : null, 292 | stackTrace: new Trace.current(1))); 293 | } 294 | } 295 | 296 | String _processCommand(String command) { 297 | return path.join(uri.path, command); 298 | } 299 | 300 | _processResponse(HttpClientResponseSync resp) { 301 | if (resp.statusCode == HttpStatus.NO_CONTENT) { 302 | return null; 303 | } 304 | var jsonBody = _parseBody(resp, _jsonDecoder); 305 | 306 | if (resp.statusCode != HttpStatus.OK || 307 | jsonBody is! Map || 308 | jsonBody['status'] != 0) { 309 | throw new WebDriverException( 310 | httpStatusCode: resp.statusCode, 311 | httpReasonPhrase: resp.reasonPhrase, 312 | jsonResp: jsonBody); 313 | } 314 | 315 | return jsonBody['value']; 316 | } 317 | 318 | @override 319 | String toString() => '{WebDriver $uri}'; 320 | } 321 | 322 | final _NUL_REGEXP = new RegExp('\u{0}'); 323 | 324 | _parseBody(HttpClientResponseSync resp, [JsonCodec jsonDecoder = JSON]) { 325 | if (resp.contentLength == 0) { 326 | return null; 327 | } 328 | if (resp.body == null) { 329 | return null; 330 | } 331 | String body = resp.body.replaceAll(_NUL_REGEXP, '').trim(); 332 | 333 | if (body.isEmpty) { 334 | return null; 335 | } 336 | 337 | if (resp.headers.contentType.mimeType == _CONTENT_TYPE_JSON.mimeType) { 338 | return jsonDecoder.decode(body); 339 | } 340 | 341 | return body; 342 | } 343 | -------------------------------------------------------------------------------- /lib/src/web_element.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | part of sync.webdriver; 18 | 19 | final ContentType _CONTENT_TYPE_JSON = 20 | new ContentType("application", "json", charset: "utf-8"); 21 | 22 | class WebElement extends _WebDriverBase with SearchContext { 23 | final String id; 24 | 25 | // The following three fields identify the provenance of this element 26 | SearchContext _context; 27 | Finder _finder; 28 | int _index; 29 | 30 | WebElement._(WebDriver driver, String elementId) 31 | : id = elementId, 32 | super(driver, 'element/$elementId'); 33 | 34 | /// Click on this element. 35 | void click() { 36 | _post('click'); 37 | } 38 | 39 | /// Submit this element if it is part of a form. 40 | void submit() => _post('submit'); 41 | 42 | /// Send [keysToSend] (a [String] or [List]) to this element. 43 | void sendKeys(dynamic keysToSend) { 44 | if (keysToSend is String) { 45 | keysToSend = [keysToSend]; 46 | } 47 | _post('value', {'value': keysToSend as List}); 48 | } 49 | 50 | /// Clear the content of a text element. 51 | void clear() => _post('clear'); 52 | 53 | /// Is this radio button/checkbox/option selected? 54 | bool get selected => _get('selected'); 55 | 56 | /// Is this form element enabled? 57 | bool get enabled => _get('enabled'); 58 | 59 | /// Is this element visible in the page? 60 | bool get displayed => _get('displayed'); 61 | 62 | /// The location within the document of this element. 63 | Point get location => _jsonToPoint(_get('location')); 64 | 65 | /// The size of this element. 66 | Size get size => new Size.fromJson(_get('size')); 67 | 68 | /// The tag name for this element. 69 | String get name => _get('name'); 70 | 71 | /// Visible text within this element. 72 | String get text => _get('text'); 73 | 74 | /** 75 | * Access to the HTML attributes of this tag. 76 | * 77 | * TODO(DrMarcII): consider special handling of boolean attributes. 78 | */ 79 | Attributes get attributes => new Attributes._(driver, _prefix, 'attribute'); 80 | 81 | /** 82 | * Access to the cssProperties of this element. 83 | * 84 | * TODO(DrMarcII): consider special handling of color and possibly other 85 | * properties. 86 | */ 87 | Attributes get cssProperties => new Attributes._(driver, _prefix, 'css'); 88 | 89 | /** 90 | * Does this element represent the same element as another element? 91 | * Not the same as == 92 | */ 93 | bool equals(WebElement other) => this == other || _get('equals/${other.id}'); 94 | 95 | Map toJson() => new Map()..[_ELEMENT] = id; 96 | 97 | @override 98 | bool operator ==(Object other) => 99 | other is WebElement && driver == other.driver && id == other.id; 100 | 101 | @override 102 | int get hashCode => id.hashCode >> 3 + driver.hashCode; 103 | 104 | void _updateProvenance(SearchContext context, Finder finder, 105 | [int index = -1]) { 106 | this._context = context; 107 | this._finder = finder; 108 | this._index = index; 109 | } 110 | 111 | @override 112 | String toString() { 113 | StringBuffer result = new StringBuffer('{WebElement '); 114 | result.write(id); 115 | if (_context != null && _finder != null) { 116 | result..write(' ')..write(_context); 117 | if (_index >= 0) { 118 | result.write('.findElements('); 119 | } else { 120 | result.write('.findElement('); 121 | } 122 | result.write(_finder); 123 | if (_index >= 0) { 124 | result..write(')[')..write(_index)..write(']}'); 125 | } else { 126 | result.write(')}'); 127 | } 128 | } 129 | return result.toString(); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /lib/src/window.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | part of sync.webdriver; 18 | 19 | class Window extends _WebDriverBase { 20 | final String _handle; 21 | 22 | Window._(WebDriver driver, String handle) 23 | : this._handle = handle, 24 | super(driver, 'window/$handle'); 25 | 26 | /// The size of this window. 27 | Size get size => new Size.fromJson(_get('size')); 28 | 29 | /// The location of this window. 30 | Point get location => _jsonToPoint(_get('position')); 31 | 32 | /// Maximize this window. 33 | void maximize() => _post('maximize'); 34 | 35 | /// Set this window size. 36 | set size(Size size) => _post('size', size); 37 | 38 | /// Set this window location. 39 | set location(Point point) => _post('position', _pointToJson(point)); 40 | 41 | String toJson() => _handle; 42 | 43 | @override 44 | bool operator ==(other) => 45 | other is Window && driver == other.driver && _handle == other._handle; 46 | 47 | @override 48 | int get hashCode => _handle.hashCode >> 3 + driver.hashCode; 49 | } 50 | -------------------------------------------------------------------------------- /lib/sync_webdriver.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | library sync.webdriver; 18 | 19 | import 'dart:async' show Stream, StreamController; 20 | import 'dart:collection'; 21 | import 'dart:convert'; 22 | import 'dart:io'; 23 | import 'dart:math' show Point; 24 | import 'dart:mirrors'; 25 | 26 | import 'package:matcher/matcher.dart' as m; 27 | import 'package:path/path.dart' as path; 28 | import 'package:stack_trace/stack_trace.dart' show Trace; 29 | import 'package:sync_socket/sync_socket.dart'; 30 | import 'package:unittest/unittest.dart' as ut; 31 | 32 | part 'src/alert.dart'; 33 | part 'src/capabilities.dart'; 34 | part 'src/command_event.dart'; 35 | part 'src/common.dart'; 36 | part 'src/error.dart'; 37 | part 'src/keyboard.dart'; 38 | part 'src/keys.dart'; 39 | part 'src/logs.dart'; 40 | part 'src/mouse.dart'; 41 | part 'src/navigation.dart'; 42 | part 'src/options.dart'; 43 | part 'src/target_locator.dart'; 44 | part 'src/touch.dart'; 45 | part 'src/util.dart'; 46 | part 'src/web_driver.dart'; 47 | part 'src/web_element.dart'; 48 | part 'src/window.dart'; 49 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: sync_webdriver 2 | version: 2.0.0-pre.2 3 | author: Marc Fisher II 4 | description: > 5 | Provides WebDriver bindings for Dart. These use the WebDriver JSON interface, 6 | and as such, require the use of the WebDriver remote server. 7 | homepage: https://github.com/google/dart-sync-webdriver 8 | environment: 9 | sdk: '>=1.13.0 <2.0.0' 10 | dependencies: 11 | matcher: '^0.12.0' 12 | path: '^1.3.0' 13 | stack_trace: '^1.3.0' 14 | sync_socket: '^1.0.0' 15 | unittest: '^0.11.0' 16 | dev_dependencies: 17 | test: '^0.12.0' 18 | -------------------------------------------------------------------------------- /test/frame.html: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | frame 21 | 22 | 23 | 24 |

this is a frame

25 | 26 | 27 | -------------------------------------------------------------------------------- /test/src/alert_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | @TestOn('vm') 18 | library webdriver_test.alert; 19 | 20 | import 'package:sync_webdriver/sync_webdriver.dart'; 21 | import 'package:test/test.dart'; 22 | 23 | import '../test_util.dart'; 24 | 25 | void main() { 26 | group('Alert', () { 27 | WebDriver driver; 28 | WebElement button; 29 | WebElement output; 30 | 31 | setUp(() { 32 | driver = createTestDriver(); 33 | driver.url = testPagePath; 34 | button = driver.findElement(new By.tagName('button')); 35 | output = driver.findElement(new By.id('settable')); 36 | }); 37 | 38 | tearDown(() { 39 | driver.quit(); 40 | driver = null; 41 | button = null; 42 | output = null; 43 | }); 44 | 45 | test('no alert', () { 46 | expect(() => driver.switchTo.alert, 47 | throwsA(new isInstanceOf())); 48 | }); 49 | 50 | test('text', () { 51 | button.click(); 52 | var alert = driver.switchTo.alert; 53 | expect(alert.text, 'button clicked'); 54 | alert.dismiss(); 55 | }); 56 | 57 | test('accept', () { 58 | button.click(); 59 | var alert = driver.switchTo.alert; 60 | alert.accept(); 61 | expect(output.text, startsWith('accepted')); 62 | }); 63 | 64 | test('dismiss', () { 65 | button.click(); 66 | var alert = driver.switchTo.alert; 67 | alert.dismiss(); 68 | expect(output.text, startsWith('dismissed')); 69 | }); 70 | 71 | test('sendKeys', () { 72 | button.click(); 73 | var alert = driver.switchTo.alert; 74 | alert.sendKeys('some keys'); 75 | alert.accept(); 76 | expect(output.text, endsWith('some keys')); 77 | }); 78 | }); 79 | } 80 | -------------------------------------------------------------------------------- /test/src/command_event_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2015 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | @TestOn('vm') 18 | library webdriver_test.command_event; 19 | 20 | import 'dart:async'; 21 | 22 | import 'package:stack_trace/stack_trace.dart'; 23 | import 'package:sync_webdriver/sync_webdriver.dart'; 24 | import 'package:test/test.dart'; 25 | 26 | import '../test_util.dart'; 27 | 28 | void main() { 29 | group('CommandListener', () { 30 | WebDriver driver; 31 | List commands = []; 32 | StreamSubscription subscription; 33 | 34 | setUp(() { 35 | driver = createTestDriver(); 36 | driver.url = testPagePath; 37 | subscription = driver.onCommand.listen((evt) { 38 | commands.add(evt); 39 | }); 40 | }); 41 | 42 | tearDown(() { 43 | subscription.cancel(); 44 | subscription = null; 45 | commands.clear(); 46 | driver.quit(); 47 | driver = null; 48 | }); 49 | 50 | test('listens to commands on driver', () { 51 | driver.findElements(const By.id('an-id')); 52 | driver.captureScreenshot(); 53 | 54 | expect(commands, hasLength(2)); 55 | _checkCommand(commands[0], 'POST', 'elements', isNotNull); 56 | _checkCommand(commands[1], 'GET', 'screenshot', isNull); 57 | }); 58 | 59 | test('listens to commands on element', () { 60 | WebElement el = driver.findElement(const By.id('table1')); 61 | el.name; 62 | el.displayed; 63 | el.findElements(const By.tagName('tr')); 64 | 65 | expect(commands, hasLength(4)); 66 | _checkCommand(commands[0], 'POST', 'element', isNotNull); 67 | _checkCommand(commands[1], 'GET', matches('element/.+/name'), isNull); 68 | _checkCommand( 69 | commands[2], 'GET', matches('element/.+/displayed'), isNull); 70 | _checkCommand( 71 | commands[3], 'POST', matches('element/.+/elements'), isNotNull); 72 | }); 73 | 74 | test('listens to commands on timeouts', () { 75 | driver.timeouts.implicitWaitTimeout = new Duration(seconds: 1); 76 | driver.timeouts.implicitWaitTimeout = new Duration(); 77 | 78 | expect(commands, hasLength(2)); 79 | _checkCommand(commands[0], 'POST', 'timeouts', isNotNull, isNull); 80 | _checkCommand(commands[0], 'POST', 'timeouts', isNotNull, isNull); 81 | }); 82 | 83 | test('fires multiple listeners', () { 84 | var localCommands = []; 85 | var localSubscription = driver.onCommand.listen((evt) { 86 | localCommands.add(evt); 87 | }); 88 | driver.findElements(const By.id('an-id')); 89 | driver.captureScreenshot(); 90 | 91 | expect(commands, hasLength(2)); 92 | _checkCommand(commands[0], 'POST', 'elements', isNotNull); 93 | _checkCommand(commands[1], 'GET', 'screenshot', isNull); 94 | expect(localCommands, hasLength(2)); 95 | _checkCommand(localCommands[0], 'POST', 'elements', isNotNull); 96 | _checkCommand(localCommands[1], 'GET', 'screenshot', isNull); 97 | localSubscription.cancel(); 98 | }); 99 | 100 | test('errors are propagated', () { 101 | try { 102 | driver.findElement(new By.id('non-existant')); 103 | } catch (e) {} 104 | _checkCommand( 105 | commands[0], 'POST', 'element', isNotNull, isNull, isNotNull); 106 | }); 107 | }); 108 | } 109 | 110 | _checkCommand(CommandEvent log, method, command, params, 111 | [response, exception]) { 112 | if (response == null) { 113 | response = isNotNull; 114 | } 115 | if (exception == null) { 116 | exception = isNull; 117 | } 118 | expect(log.method, method); 119 | expect(log.endpoint, command); 120 | expect(log.params, params); 121 | expect( 122 | log.endTime, 123 | predicate(log.startTime.isBefore, 124 | 'event endTime is not after event startTime')); 125 | expect(log.result, response); 126 | expect(log.exception, exception); 127 | 128 | var trace = new Trace.current(2).frames.map((f) => f.toString()).toList(); 129 | expect( 130 | log.stackTrace.frames 131 | .map((f) => f.toString()) 132 | .skipWhile((f) => f != trace[0]) 133 | .toList(), 134 | trace); 135 | } 136 | -------------------------------------------------------------------------------- /test/src/keyboard_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | @TestOn('vm') 18 | library webdriver_test.keyboard; 19 | 20 | import 'package:sync_webdriver/sync_webdriver.dart'; 21 | import 'package:test/test.dart'; 22 | 23 | import '../test_util.dart'; 24 | 25 | void main() { 26 | group('Keyboard', () { 27 | WebDriver driver; 28 | WebElement textInput; 29 | 30 | setUp(() { 31 | driver = createTestDriver(); 32 | driver.url = testPagePath; 33 | textInput = driver.findElement(new By.cssSelector('input[type=text]')); 34 | textInput.click(); 35 | }); 36 | 37 | tearDown(() { 38 | driver.quit(); 39 | driver = null; 40 | textInput = null; 41 | }); 42 | 43 | test('sendKeys -- once', () { 44 | driver.keyboard.sendKeys('abcdef'); 45 | expect(textInput.attributes['value'], 'abcdef'); 46 | }); 47 | 48 | test('sendKeys -- twice', () { 49 | driver.keyboard..sendKeys('abc')..sendKeys('def'); 50 | expect(textInput.attributes['value'], 'abcdef'); 51 | }); 52 | 53 | test('sendKeys -- list', () { 54 | driver.keyboard.sendKeys(['a', 'b', 'c', 'd', 'e', 'f']); 55 | expect(textInput.attributes['value'], 'abcdef'); 56 | }); 57 | 58 | // doesn't work with chromedriver 59 | // https://code.google.com/p/chromedriver/issues/detail?id=443 60 | test('sendKeys -- with tab', () { 61 | driver.keyboard.sendKeys(['abc', Keys.TAB, 'def']); 62 | expect(textInput.attributes['value'], 'abc'); 63 | }); 64 | }); 65 | } 66 | -------------------------------------------------------------------------------- /test/src/logs_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2015 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | @TestOn('vm') 18 | library webdriver_test.logs; 19 | 20 | import 'package:sync_webdriver/sync_webdriver.dart'; 21 | import 'package:test/test.dart'; 22 | 23 | import '../test_util.dart'; 24 | 25 | void main() { 26 | group('Logs', () { 27 | WebDriver driver; 28 | 29 | setUp(() { 30 | driver = createTestDriver(additionalCapabilities: { 31 | Capabilities.LOGGING_PREFS: {LogType.PERFORMANCE: LogLevel.INFO} 32 | }); 33 | driver.url = testPagePath; 34 | }); 35 | 36 | tearDown(() { 37 | driver.quit(); 38 | driver = null; 39 | }); 40 | 41 | test('get logs', () { 42 | Iterable logs = driver.logs.get(LogType.PERFORMANCE); 43 | expect(logs.length, greaterThan(0)); 44 | logs.forEach((entry) { 45 | expect(entry.level, equals(LogLevel.INFO)); 46 | }); 47 | }); 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /test/src/mouse_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | @TestOn('vm') 18 | library webdriver_test.mouse; 19 | 20 | import 'package:sync_webdriver/sync_webdriver.dart'; 21 | import 'package:test/test.dart'; 22 | 23 | import '../test_util.dart'; 24 | 25 | void main() { 26 | group('Mouse', () { 27 | WebDriver driver; 28 | WebElement button; 29 | 30 | setUp(() { 31 | driver = createTestDriver(); 32 | driver.url = testPagePath; 33 | button = driver.findElement(new By.tagName('button')); 34 | }); 35 | 36 | tearDown(() { 37 | driver.quit(); 38 | driver = null; 39 | button = null; 40 | }); 41 | 42 | test('moveTo element/click', () { 43 | driver.mouse 44 | ..moveTo(element: button) 45 | ..click(); 46 | driver.switchTo.alert.dismiss(); 47 | }); 48 | 49 | test('moveTo coordinates/click', () { 50 | var pos = button.location; 51 | driver.mouse 52 | ..moveTo(xOffset: pos.x + 5, yOffset: pos.y + 5) 53 | ..click(); 54 | driver.switchTo.alert.dismiss(); 55 | }); 56 | 57 | test('moveTo element coordinates/click', () { 58 | driver.mouse 59 | ..moveTo(element: button, xOffset: 5, yOffset: 5) 60 | ..click(); 61 | driver.switchTo.alert.dismiss(); 62 | }); 63 | 64 | // TODO(DrMarcII): Better up/down tests 65 | test('down/up', () { 66 | driver.mouse 67 | ..moveTo(element: button) 68 | ..down() 69 | ..up(); 70 | driver.switchTo.alert.dismiss(); 71 | }); 72 | 73 | // TODO(DrMarcII): Better double click test 74 | test('doubleClick', () { 75 | driver.mouse 76 | ..moveTo(element: button) 77 | ..doubleClick(); 78 | driver.switchTo.alert.dismiss(); 79 | }); 80 | }); 81 | } 82 | -------------------------------------------------------------------------------- /test/src/navigation_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | @TestOn('vm') 18 | library webdriver_test.navigation; 19 | 20 | import 'package:sync_webdriver/sync_webdriver.dart'; 21 | import 'package:test/test.dart'; 22 | 23 | import '../test_util.dart'; 24 | 25 | void main() { 26 | group('Navigation', () { 27 | WebDriver driver; 28 | 29 | setUp(() { 30 | driver = createTestDriver(); 31 | driver.url = 'http://www.google.com/ncr'; 32 | }); 33 | 34 | tearDown(() { 35 | driver.quit(); 36 | driver = null; 37 | }); 38 | 39 | test('forward/back', () { 40 | driver.url = testPagePath; 41 | driver.navigate.back(); 42 | waitFor(() => driver.title, contains('Google')); 43 | driver.navigate.forward(); 44 | waitFor(() => driver.title, contains('test_page')); 45 | }, skip: 'TODO(DrMarcII): figure out why this test is not finishing'); 46 | 47 | test('refresh', () { 48 | var element = driver.findElement(new By.name('q')); 49 | driver.navigate.refresh(); 50 | waitFor(() => () => element.name, 51 | throwsA(new isInstanceOf())); 52 | }); 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /test/src/options_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | @TestOn('vm') 18 | library webdriver_test.options; 19 | 20 | import 'package:sync_webdriver/sync_webdriver.dart'; 21 | import 'package:test/test.dart'; 22 | 23 | import '../test_util.dart'; 24 | 25 | void main() { 26 | group('Cookies', () { 27 | WebDriver driver; 28 | 29 | setUp(() { 30 | driver = createTestDriver(); 31 | driver.url = 'http://www.google.com/ncr'; 32 | }); 33 | 34 | tearDown(() { 35 | driver.quit(); 36 | driver = null; 37 | }); 38 | 39 | test('add simple cookie', () { 40 | driver.cookies.add(new Cookie('mycookie', 'myvalue')); 41 | var found = false; 42 | for (var cookie in driver.cookies.all) { 43 | if (cookie.name == 'mycookie') { 44 | found = true; 45 | expect(cookie.value, 'myvalue'); 46 | break; 47 | } 48 | } 49 | expect(found, isTrue); 50 | }); 51 | 52 | test('add complex cookie', () { 53 | var date = new DateTime.utc(2099); 54 | driver.cookies.add(new Cookie('mycomplexcookie', 'myvalue', 55 | path: '/', domain: '.google.com', secure: false, expiry: date)); 56 | var found = false; 57 | for (var cookie in driver.cookies.all) { 58 | if (cookie.name == 'mycomplexcookie') { 59 | found = true; 60 | expect(cookie.value, 'myvalue'); 61 | expect(cookie.expiry, date); 62 | break; 63 | } 64 | } 65 | expect(found, isTrue); 66 | }); 67 | 68 | test('delete cookie', () { 69 | driver.cookies.add(new Cookie('mycookie', 'myvalue')); 70 | driver.cookies.delete('mycookie'); 71 | bool found = false; 72 | for (var cookie in driver.cookies.all) { 73 | if (cookie.name == 'mycookie') { 74 | found = true; 75 | } 76 | } 77 | expect(found, isFalse); 78 | }); 79 | 80 | test('delete all cookies', () { 81 | driver.cookies.add(new Cookie('mycookie', 'myvalue')); 82 | waitFor(() => driver.cookies.all, hasLength(isPositive)); 83 | driver.cookies.deleteAll(); 84 | waitFor(() => driver.cookies.all, isEmpty); 85 | }); 86 | }); 87 | 88 | group('TimeOuts', () { 89 | WebDriver driver; 90 | 91 | setUp(() { 92 | driver = createTestDriver(); 93 | }); 94 | 95 | tearDown(() { 96 | driver.quit(); 97 | driver = null; 98 | }); 99 | 100 | test('set all timeouts', () { 101 | expect(driver.timeouts.scriptTimeout, isNull); 102 | var timeout = new Duration(seconds: 5); 103 | driver.timeouts.scriptTimeout = timeout; 104 | expect(driver.timeouts.scriptTimeout, equals(timeout)); 105 | 106 | expect(driver.timeouts.pageLoadTimeout, isNull); 107 | timeout = new Duration(seconds: 10); 108 | driver.timeouts.pageLoadTimeout = timeout; 109 | expect(driver.timeouts.pageLoadTimeout, equals(timeout)); 110 | 111 | timeout = new Duration(seconds: 2); 112 | driver.timeouts.implicitWaitTimeout = timeout; 113 | expect(driver.timeouts.implicitWaitTimeout, equals(timeout)); 114 | }); 115 | }); 116 | } 117 | -------------------------------------------------------------------------------- /test/src/target_locator_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | @TestOn('vm') 18 | library webdriver_test.target_locator; 19 | 20 | import 'package:sync_webdriver/sync_webdriver.dart'; 21 | import 'package:test/test.dart'; 22 | 23 | import '../test_util.dart'; 24 | 25 | /** 26 | * Tests for switchTo.frame(). switchTo.window() and switchTo.alert are tested 27 | * in other classes. 28 | */ 29 | void main() { 30 | group('TargetLocator', () { 31 | WebDriver driver; 32 | 33 | setUp(() { 34 | driver = createTestDriver(); 35 | driver.url = testPagePath; 36 | }); 37 | 38 | tearDown(() { 39 | driver.quit(); 40 | driver = null; 41 | }); 42 | 43 | test('frame index', () { 44 | driver.switchTo.frame(0); 45 | expect(driver.pageSource, contains('this is a frame')); 46 | }); 47 | 48 | test('frame name', () { 49 | driver.switchTo.frame('frame'); 50 | expect(driver.pageSource, contains('this is a frame')); 51 | }); 52 | 53 | test('frame element', () { 54 | var frame = driver.findElement(new By.name('frame')); 55 | driver.switchTo.frame(frame); 56 | expect(driver.pageSource, contains('this is a frame')); 57 | }); 58 | 59 | test('root frame', () { 60 | driver.switchTo.frame(0); 61 | expect(driver.pageSource, contains('this is a frame')); 62 | driver.switchTo.frame(); 63 | expect( 64 | () => driver.findElement(new By.tagName('button')), returnsNormally); 65 | }); 66 | 67 | test('window object', () { 68 | driver.findElement(new By.partialLinkText('Open copy')).click(); 69 | for (var window in driver.windows) { 70 | driver.switchTo.window(window); 71 | expect(driver.window, window); 72 | } 73 | }); 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /test/src/util_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | @TestOn('vm') 18 | library webdriver_test.util; 19 | 20 | import 'package:sync_webdriver/sync_webdriver.dart'; 21 | import 'package:test/test.dart'; 22 | import 'package:unittest/unittest.dart' as unittest; 23 | 24 | import '../test_util.dart'; 25 | 26 | void main() { 27 | group('waitFor()', () { 28 | setUp(() { 29 | useUnittestMatchers = false; 30 | }); 31 | 32 | test('that returns a string', () { 33 | var count = 0; 34 | var result = waitFor(() { 35 | if (count == 2) return 'webdriver - Google Search'; 36 | count++; 37 | return count; 38 | }, equals('webdriver - Google Search')); 39 | 40 | expect(result, equals('webdriver - Google Search')); 41 | }); 42 | 43 | test('that returns null', () { 44 | var count = 0; 45 | var result = waitFor(() { 46 | if (count == 2) return null; 47 | count++; 48 | return count; 49 | }, isNull); 50 | expect(result, isNull); 51 | }); 52 | 53 | test('that returns false', () { 54 | var count = 0; 55 | var result = waitFor(() { 56 | if (count == 2) return false; 57 | count++; 58 | return count; 59 | }, isFalse); 60 | expect(result, isFalse); 61 | }); 62 | }); 63 | 64 | group('waitForValue()', () { 65 | setUp(() { 66 | useUnittestMatchers = false; 67 | }); 68 | 69 | test('that returns a string', () { 70 | var count = 0; 71 | var result = waitForValue(() { 72 | if (count == 2) return 'Google'; 73 | count++; 74 | return null; 75 | }); 76 | expect(result, equals('Google')); 77 | }); 78 | 79 | test('that returns false', () { 80 | var count = 0; 81 | var result = waitForValue(() { 82 | expect(count, lessThanOrEqualTo(2)); 83 | if (count == 2) { 84 | count++; 85 | return false; 86 | } 87 | count++; 88 | return null; 89 | }); 90 | expect(result, isFalse); 91 | }); 92 | }); 93 | 94 | group('custom Matcher', () { 95 | WebDriver driver; 96 | setUp(() { 97 | driver = createTestDriver(); 98 | driver.url = testPagePath; 99 | }); 100 | 101 | tearDown(() { 102 | driver.quit(); 103 | driver = null; 104 | }); 105 | 106 | test('isDisplayed', () { 107 | var body = driver.findElement(const By.tagName('body')); 108 | expect(body, isDisplayed); 109 | }); 110 | 111 | test('isNotDisplayed', () { 112 | var div = driver.findElement(const By.tagName('div')); 113 | expect(div, isNotDisplayed); 114 | }); 115 | 116 | test('isEnabled', () { 117 | var body = driver.findElement(const By.tagName('body')); 118 | expect(body, isEnabled); 119 | }); 120 | 121 | test('isNotEnabled', () { 122 | var input = 123 | driver.findElement(const By.cssSelector('input[type=password]')); 124 | expect(input, isNotEnabled); 125 | }); 126 | 127 | test('hasText', () { 128 | var button = driver.findElement(const By.tagName('button')); 129 | expect(button, hasText('button')); 130 | expect(button, hasText(equalsIgnoringCase('BUTTON'))); 131 | }); 132 | }); 133 | 134 | group('waitFor() -- unittest', () { 135 | setUp(() { 136 | useUnittestMatchers = true; 137 | }); 138 | 139 | tearDown(() { 140 | useUnittestMatchers = false; 141 | }); 142 | 143 | test('that returns a string', () { 144 | var count = 0; 145 | var result = waitFor(() { 146 | if (count == 2) return 'webdriver - Google Search'; 147 | count++; 148 | return count; 149 | }, unittest.equals('webdriver - Google Search')); 150 | 151 | unittest.expect(result, unittest.equals('webdriver - Google Search')); 152 | }); 153 | 154 | test('that returns null', () { 155 | var count = 0; 156 | var result = waitFor(() { 157 | if (count == 2) return null; 158 | count++; 159 | return count; 160 | }, unittest.isNull); 161 | unittest.expect(result, unittest.isNull); 162 | }); 163 | 164 | test('that returns false', () { 165 | var count = 0; 166 | var result = waitFor(() { 167 | if (count == 2) return false; 168 | count++; 169 | return count; 170 | }, unittest.isFalse); 171 | unittest.expect(result, unittest.isFalse); 172 | }); 173 | }); 174 | 175 | group('waitForValue() -- unittest', () { 176 | setUp(() { 177 | useUnittestMatchers = true; 178 | }); 179 | 180 | tearDown(() { 181 | useUnittestMatchers = false; 182 | }); 183 | 184 | test('that returns a string', () { 185 | var count = 0; 186 | var result = waitForValue(() { 187 | if (count == 2) return 'Google'; 188 | count++; 189 | return null; 190 | }); 191 | unittest.expect(result, unittest.equals('Google')); 192 | }); 193 | 194 | test('that returns false', () { 195 | var count = 0; 196 | var result = waitForValue(() { 197 | unittest.expect(count, unittest.lessThanOrEqualTo(2)); 198 | if (count == 2) { 199 | count++; 200 | return false; 201 | } 202 | count++; 203 | return null; 204 | }); 205 | unittest.expect(result, unittest.isFalse); 206 | }); 207 | }); 208 | 209 | group('custom Matcher -- unittest', () { 210 | WebDriver driver; 211 | setUp(() { 212 | driver = createTestDriver(); 213 | driver.url = testPagePath; 214 | useUnittestMatchers = true; 215 | }); 216 | 217 | tearDown(() { 218 | driver.quit(); 219 | driver = null; 220 | useUnittestMatchers = false; 221 | }); 222 | 223 | test('isDisplayed', () { 224 | var body = driver.findElement(const By.tagName('body')); 225 | unittest.expect(body, isDisplayed); 226 | }); 227 | 228 | test('isNotDisplayed', () { 229 | var div = driver.findElement(const By.tagName('div')); 230 | unittest.expect(div, isNotDisplayed); 231 | }); 232 | 233 | test('isEnabled', () { 234 | var body = driver.findElement(const By.tagName('body')); 235 | unittest.expect(body, isEnabled); 236 | }); 237 | 238 | test('isNotEnabled', () { 239 | var input = 240 | driver.findElement(const By.cssSelector('input[type=password]')); 241 | unittest.expect(input, isNotEnabled); 242 | }); 243 | 244 | test('hasText', () { 245 | var button = driver.findElement(const By.tagName('button')); 246 | unittest.expect(button, hasText('button')); 247 | unittest.expect(button, hasText(unittest.equalsIgnoringCase('BUTTON'))); 248 | }); 249 | }); 250 | 251 | group('Select class', () { 252 | WebDriver driver; 253 | WebElement selectSimple; 254 | WebElement selectMulti; 255 | 256 | setUp(() { 257 | driver = createTestDriver(); 258 | driver.url = testPagePath; 259 | selectSimple = driver.findElement(const By.id('select-simple')); 260 | selectMulti = driver.findElement(const By.id('select-multi')); 261 | }); 262 | 263 | tearDown(() { 264 | driver.quit(); 265 | driver = null; 266 | selectSimple = null; 267 | selectMulti = null; 268 | }); 269 | 270 | test('isMultiple', () { 271 | expect(new Select(selectSimple).isMultiple, false); 272 | expect(new Select(selectMulti).isMultiple, true); 273 | }); 274 | 275 | test('selectByIndex simple', () { 276 | var select = new Select(selectSimple)..selectByIndex(2); 277 | var options = select.options; 278 | 279 | expect(options[0].selected, false); 280 | expect(options[1].selected, false); 281 | expect(options[2].selected, true); 282 | expect(options[3].selected, false); 283 | expect(options[2], select.firstSelectedOption); 284 | expect(options[2].text, "Apple"); 285 | expect(options[2].attributes["value"], "appleValue"); 286 | expect(select.allSelectedOptions, [options[2]]); 287 | expect(select.value, "appleValue"); 288 | }); 289 | 290 | test('[de]selectByIndex multiple', () { 291 | var select = new Select(selectMulti)..selectByIndex(1)..selectByIndex(3); 292 | var options = select.options; 293 | 294 | expect(options[0].selected, false); 295 | expect(options[1].selected, true); 296 | expect(options[2].selected, false); 297 | expect(options[3].selected, true); 298 | expect(options[1], select.firstSelectedOption); 299 | expect(options[1].text, "Green"); 300 | expect(options[1].attributes["value"], "greenValue"); 301 | expect(select.allSelectedOptions, [options[1], options[3]]); 302 | expect(select.value, "greenValue"); 303 | 304 | select.deselectAll(); 305 | expect(options[0].selected, false); 306 | expect(options[1].selected, false); 307 | expect(options[2].selected, false); 308 | expect(options[3].selected, false); 309 | 310 | select 311 | ..selectByIndex(1) 312 | ..selectByIndex(3) 313 | ..deselectByIndex(3); 314 | expect(options[0].selected, false); 315 | expect(options[1].selected, true); 316 | expect(options[2].selected, false); 317 | expect(options[3].selected, false); 318 | }); 319 | 320 | test('selectByValue simple', () { 321 | var select = new Select(selectSimple)..selectByValue("appleValue"); 322 | var options = select.options; 323 | 324 | expect(options[2].selected, true); 325 | expect(options[2], select.firstSelectedOption); 326 | expect(options[2].text, "Apple"); 327 | expect(options[2].attributes["value"], "appleValue"); 328 | expect(select.allSelectedOptions, [options[2]]); 329 | expect(select.value, "appleValue"); 330 | }); 331 | 332 | test('[de]selectByValue multiple', () { 333 | var select = new Select(selectMulti) 334 | ..selectByValue("greenValue") 335 | ..selectByValue("yellowValue"); 336 | var options = select.options; 337 | 338 | expect(options[1].selected, true); 339 | expect(options[1], select.firstSelectedOption); 340 | expect(options[1].text, "Green"); 341 | expect(options[1].attributes["value"], "greenValue"); 342 | expect(select.allSelectedOptions, [options[1], options[3]]); 343 | expect(select.value, "greenValue"); 344 | 345 | select.deselectAll(); 346 | expect(options[0].selected, false); 347 | expect(options[1].selected, false); 348 | expect(options[2].selected, false); 349 | expect(options[3].selected, false); 350 | 351 | select 352 | ..selectByValue("greenValue") 353 | ..selectByValue("yellowValue") 354 | ..deselectByValue("yellowValue"); 355 | expect(options[0].selected, false); 356 | expect(options[1].selected, true); 357 | expect(options[2].selected, false); 358 | expect(options[3].selected, false); 359 | }); 360 | 361 | test('selectByVisibleText simple', () { 362 | var select = new Select(selectSimple)..selectByVisibleText("Apple"); 363 | var options = select.options; 364 | 365 | expect(options[2].selected, true); 366 | expect(options[2], select.firstSelectedOption); 367 | expect(options[2].text, "Apple"); 368 | expect(options[2].attributes["value"], "appleValue"); 369 | expect(select.allSelectedOptions, [options[2]]); 370 | expect(select.value, "appleValue"); 371 | }); 372 | 373 | test('[de]selectByVisibleText multiple', () { 374 | var select = new Select(selectMulti) 375 | ..selectByVisibleText("Green") 376 | ..selectByVisibleText("Yellow"); 377 | var options = select.options; 378 | 379 | expect(options[1].selected, true); 380 | expect(options[1], select.firstSelectedOption); 381 | expect(options[1].text, "Green"); 382 | expect(options[1].attributes["value"], "greenValue"); 383 | expect(select.allSelectedOptions, [options[1], options[3]]); 384 | expect(select.value, "greenValue"); 385 | 386 | select.deselectAll(); 387 | expect(options[0].selected, false); 388 | expect(options[1].selected, false); 389 | expect(options[2].selected, false); 390 | expect(options[3].selected, false); 391 | 392 | select 393 | ..selectByVisibleText("Green") 394 | ..selectByVisibleText("Yellow") 395 | ..deselectByVisibleText("Yellow"); 396 | expect(options[0].selected, false); 397 | expect(options[1].selected, true); 398 | expect(options[2].selected, false); 399 | expect(options[3].selected, false); 400 | }); 401 | }); 402 | } 403 | -------------------------------------------------------------------------------- /test/src/web_driver_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | @TestOn('vm') 18 | library webdriver_test.web_driver; 19 | 20 | import 'package:sync_webdriver/sync_webdriver.dart'; 21 | import 'package:test/test.dart'; 22 | 23 | import '../test_util.dart'; 24 | 25 | void main() { 26 | group('WebDriver', () { 27 | WebDriver driver; 28 | 29 | setUp(() { 30 | driver = createTestDriver(); 31 | driver.url = testPagePath; 32 | }); 33 | 34 | tearDown(() { 35 | driver.quit(); 36 | driver = null; 37 | }); 38 | 39 | test('set url', () { 40 | driver.url = 'http://www.google.com/ncr'; 41 | driver.findElement(new By.name('q')); 42 | driver.url = testPagePath; 43 | driver.findElement(new By.id('table1')); 44 | }); 45 | 46 | test('get url', () { 47 | var url = driver.url; 48 | expect(url, startsWith('file:')); 49 | expect(url, endsWith('test_page.html')); 50 | driver.url = 'http://www.google.com/ncr'; 51 | url = driver.url; 52 | expect(url, contains('www.google.com')); 53 | }); 54 | 55 | test('findElement -- success', () { 56 | expect(driver.findElement(new By.tagName('tr')), isWebElement); 57 | }); 58 | 59 | test('findElement -- failure', () { 60 | expect(() => driver.findElement(new By.id('non-existent-id')), 61 | throwsA(new isInstanceOf())); 62 | }); 63 | 64 | test('findElements -- 1 found', () { 65 | var elements = 66 | driver.findElements(new By.cssSelector('input[type=text]')); 67 | expect(elements, hasLength(1)); 68 | expect(elements, everyElement(isWebElement)); 69 | }); 70 | 71 | test('findElements -- 4 found', () { 72 | var elements = driver.findElements(new By.tagName('td')); 73 | expect(elements, hasLength(4)); 74 | expect(elements, everyElement(isWebElement)); 75 | }); 76 | 77 | test('findElements -- 0 found', () { 78 | expect(driver.findElements(new By.id('non-existent-id')), isEmpty); 79 | }); 80 | 81 | test('pageSource', () { 82 | expect(driver.pageSource, contains('test_page')); 83 | }); 84 | 85 | test('close/windows', () { 86 | int numHandles = driver.windows.length; 87 | driver.findElement(new By.partialLinkText('Open copy')).click(); 88 | expect(driver.windows.length, numHandles + 1); 89 | driver.close(); 90 | expect(driver.windows.length, numHandles); 91 | }); 92 | 93 | test('windows', () { 94 | var windows = driver.windows; 95 | expect(windows, hasLength(isPositive)); 96 | expect(windows, everyElement(isWindow)); 97 | }); 98 | 99 | test('window', () { 100 | expect(driver.window, isWindow); 101 | }); 102 | 103 | test('execute', () { 104 | WebElement button = driver.findElement(new By.tagName('button')); 105 | String script = ''' 106 | arguments[1].textContent = arguments[0]; 107 | return arguments[1];'''; 108 | var e = driver.execute(script, ['new text', button]); 109 | expect(e.text, 'new text'); 110 | }); 111 | 112 | test('executeAsync', () { 113 | WebElement button = driver.findElement(new By.tagName('button')); 114 | String script = ''' 115 | arguments[1].textContent = arguments[0]; 116 | arguments[2](arguments[1]);'''; 117 | var e = driver.executeAsync(script, ['new text', button]); 118 | expect(e.text, 'new text'); 119 | }); 120 | 121 | test('captureScreenshot', () { 122 | var screenshot = driver.captureScreenshot(); 123 | expect(screenshot, hasLength(isPositive)); 124 | expect(screenshot, everyElement(new isInstanceOf())); 125 | expect(screenshot, everyElement(inInclusiveRange(0, 255))); 126 | }); 127 | 128 | test('fromExistingSession', () { 129 | List path = driver.uri.pathSegments; 130 | Uri uri = new Uri.http( 131 | driver.uri.authority, path.sublist(0, path.length - 2).join('/')); 132 | String session = path.last; 133 | var newDriver = new WebDriver.fromExistingSession(session, uri: uri); 134 | expect(newDriver.capabilities, driver.capabilities); 135 | var url = newDriver.url; 136 | expect(url, startsWith('file:')); 137 | expect(url, endsWith('test_page.html')); 138 | newDriver.url = 'http://www.google.com/ncr'; 139 | url = driver.url; 140 | expect(url, contains('www.google.com')); 141 | }); 142 | }); 143 | } 144 | -------------------------------------------------------------------------------- /test/src/web_element_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | @TestOn('vm') 18 | library webdriver_test.web_element; 19 | 20 | import 'package:sync_webdriver/sync_webdriver.dart'; 21 | import 'package:test/test.dart'; 22 | 23 | import '../test_util.dart'; 24 | 25 | void main() { 26 | group('WebElement', () { 27 | WebDriver driver; 28 | WebElement table; 29 | WebElement button; 30 | WebElement form; 31 | WebElement textInput; 32 | WebElement checkbox; 33 | WebElement disabled; 34 | WebElement invisible; 35 | 36 | setUp(() { 37 | driver = createTestDriver(); 38 | driver.url = testPagePath; 39 | table = driver.findElement(const By.tagName('table')); 40 | button = driver.findElement(const By.tagName('button')); 41 | form = driver.findElement(const By.tagName('form')); 42 | textInput = driver.findElement(const By.cssSelector('input[type=text]')); 43 | checkbox = 44 | driver.findElement(const By.cssSelector('input[type=checkbox]')); 45 | disabled = 46 | driver.findElement(const By.cssSelector('input[type=password]')); 47 | invisible = driver.findElement(const By.tagName('div')); 48 | }); 49 | 50 | tearDown(() { 51 | driver.quit(); 52 | driver = null; 53 | table = null; 54 | button = null; 55 | form = null; 56 | textInput = null; 57 | checkbox = null; 58 | disabled = null; 59 | invisible = null; 60 | }); 61 | 62 | test('click', () { 63 | button.click(); 64 | driver.switchTo.alert.accept(); 65 | }); 66 | 67 | test('submit', () { 68 | form.submit(); 69 | var alert = driver.switchTo.alert; 70 | expect(alert.text, 'form submitted'); 71 | alert.accept(); 72 | }); 73 | 74 | test('sendKeys', () { 75 | textInput.sendKeys('some keys'); 76 | expect(textInput.attributes['value'], 'some keys'); 77 | }); 78 | 79 | test('clear', () { 80 | textInput.sendKeys('some keys'); 81 | textInput.clear(); 82 | expect(textInput.attributes['value'], ''); 83 | }); 84 | 85 | test('enabled', () { 86 | expect(table.enabled, isTrue); 87 | expect(button.enabled, isTrue); 88 | expect(form.enabled, isTrue); 89 | expect(textInput.enabled, isTrue); 90 | expect(checkbox.enabled, isTrue); 91 | expect(disabled.enabled, isFalse); 92 | }); 93 | 94 | test('displayed', () { 95 | expect(table.displayed, isTrue); 96 | expect(button.displayed, isTrue); 97 | expect(form.displayed, isTrue); 98 | expect(textInput.displayed, isTrue); 99 | expect(checkbox.displayed, isTrue); 100 | expect(disabled.displayed, isTrue); 101 | expect(invisible.displayed, isFalse); 102 | }); 103 | 104 | test('location -- table', () { 105 | var location = table.location; 106 | expect(location, isPoint); 107 | // TODO(DrMarcII): Switch to hasProperty matchers 108 | expect(location.x, isNonNegative); 109 | expect(location.y, isNonNegative); 110 | }); 111 | 112 | test('location -- invisible', () { 113 | var location = invisible.location; 114 | expect(location, isPoint); 115 | // TODO(DrMarcII): Switch to hasProperty matchers 116 | expect(location.x, 0); 117 | expect(location.y, 0); 118 | }); 119 | 120 | test('size -- table', () { 121 | var size = table.size; 122 | expect(size, isSize); 123 | // TODO(DrMarcII): Switch to hasProperty matchers 124 | expect(size.width, isNonNegative); 125 | expect(size.height, isNonNegative); 126 | }); 127 | 128 | test('size -- invisible', () { 129 | var size = invisible.size; 130 | expect(size, isSize); 131 | // TODO(DrMarcII): I thought these should be 0 132 | // TODO(DrMarcII): Switch to hasProperty matchers 133 | expect(size.width, isNonNegative); 134 | expect(size.height, isNonNegative); 135 | }); 136 | 137 | test('name', () { 138 | expect(table.name, 'table'); 139 | expect(button.name, 'button'); 140 | expect(form.name, 'form'); 141 | expect(textInput.name, 'input'); 142 | }); 143 | 144 | test('text', () { 145 | expect(table.text, 'r1c1 r1c2\nr2c1 r2c2'); 146 | expect(button.text, 'button'); 147 | expect(invisible.text, ''); 148 | }); 149 | 150 | test('findElement -- success', () { 151 | expect(table.findElement(const By.tagName('tr')), isWebElement); 152 | }); 153 | 154 | test('findElement -- failure', () { 155 | expect(() => button.findElement(const By.tagName('tr')), 156 | throwsA(new isInstanceOf())); 157 | }); 158 | 159 | test('findElements -- 1 found', () { 160 | var elements = 161 | form.findElements(const By.cssSelector('input[type=text]')); 162 | expect(elements, hasLength(1)); 163 | expect(elements, everyElement(isWebElement)); 164 | }); 165 | 166 | test('findElements -- 4 found', () { 167 | var elements = table.findElements(const By.tagName('td')); 168 | expect(elements, hasLength(4)); 169 | expect(elements, everyElement(isWebElement)); 170 | }); 171 | 172 | test('findElements -- 0 found', () { 173 | expect(form.findElements(const By.tagName('td')), isEmpty); 174 | }); 175 | 176 | test('attributes', () { 177 | expect(table.attributes['id'], 'table1'); 178 | expect(table.attributes['non-standard'], 'a non standard attr'); 179 | expect(table.attributes['disabled'], isNull); 180 | expect(disabled.attributes['disabled'], 'true'); 181 | }); 182 | 183 | test('cssProperties', () { 184 | expect(invisible.cssProperties['display'], 'none'); 185 | expect(invisible.cssProperties['background-color'], 'rgba(255, 0, 0, 1)'); 186 | expect(invisible.cssProperties['direction'], 'ltr'); 187 | }); 188 | 189 | test('equals', () { 190 | expect(invisible.equals(disabled), isFalse); 191 | var element = driver.findElement(new By.cssSelector('table')); 192 | expect(element.equals(table), isTrue); 193 | }); 194 | 195 | test('toString includes provenance info', () { 196 | expect(table.toString(), contains(driver.toString())); 197 | expect(table.toString(), contains(const By.tagName('table').toString())); 198 | expect(table.toString(), contains('findElement(')); 199 | 200 | var elements = table.findElements(const By.tagName('td')); 201 | expect(elements[0].toString(), contains(table.toString())); 202 | expect( 203 | elements[0].toString(), contains(const By.tagName('td').toString())); 204 | expect(elements[0].toString(), contains('findElements(')); 205 | expect(elements[0].toString(), contains('[0]')); 206 | expect(elements[1].toString(), contains('[1]')); 207 | expect(elements[2].toString(), contains('[2]')); 208 | expect(elements[3].toString(), contains('[3]')); 209 | }); 210 | }); 211 | } 212 | -------------------------------------------------------------------------------- /test/src/window_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | @TestOn('vm') 18 | library webdriver_test.window; 19 | 20 | import 'dart:math' show Point; 21 | 22 | import 'package:sync_webdriver/sync_webdriver.dart'; 23 | import 'package:test/test.dart'; 24 | 25 | import '../test_util.dart'; 26 | 27 | void main() { 28 | group('Window', () { 29 | WebDriver driver; 30 | 31 | setUp(() { 32 | driver = createTestDriver(); 33 | }); 34 | 35 | tearDown(() { 36 | driver.quit(); 37 | driver = null; 38 | }); 39 | 40 | test('size', () { 41 | driver.window.size = new Size(400, 600); 42 | var size = driver.window.size; 43 | expect(size, isSize); 44 | expect(size.height, 400); 45 | expect(size.width, 600); 46 | }); 47 | 48 | test('location', () { 49 | driver.window.location = new Point(10, 20); 50 | var point = driver.window.location; 51 | expect(point, isPoint); 52 | expect(point.x, 10); 53 | expect(point.y, 20); 54 | }); 55 | 56 | // fails in some cases with multiple monitors 57 | test('maximize', () { 58 | driver.window.maximize(); 59 | var point = driver.window.location; 60 | expect(point, isPoint); 61 | expect(point.x, 0); 62 | expect(point.y, 0); 63 | }, skip: true); 64 | }); 65 | } 66 | -------------------------------------------------------------------------------- /test/test_page.html: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | test_page 21 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
r1c1r1c2
r2c1r2c2
44 | 45 |
46 | 47 | 48 | 49 |
50 | 56 | 62 | 64 | 65 | Open copy in other window 66 |
67 |