├── .gitignore ├── LICENSE.md ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── ai │ └── test │ └── sdk │ └── demo │ └── Example.java └── resources └── log4j.properties /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | build/ 3 | 4 | # OS Generated files 5 | .DS_Store 6 | ._* 7 | .Spotlight-V100 8 | .Trashes 9 | Icon? 10 | ehthumbs.db 11 | Thumbs.db 12 | 13 | # Eclipse 14 | .project 15 | .classpath 16 | .settings 17 | 18 | # Gradle 19 | .gradle/ 20 | /bin/ 21 | 22 | Scratch*.java -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 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, and distribution as defined by Sections 1 through 9 of this document. 10 | 11 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 12 | 13 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 14 | 15 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 16 | 17 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 18 | 19 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 20 | 21 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 22 | 23 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 24 | 25 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 26 | 27 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 28 | 29 | 2. Grant of Copyright License. 30 | 31 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 32 | 33 | 3. Grant of Patent License. 34 | 35 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 36 | 37 | 4. Redistribution. 38 | 39 | You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 40 | 41 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 42 | You must cause any modified files to carry prominent notices stating that You changed the files; and 43 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 44 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 45 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 46 | 47 | 5. Submission of Contributions. 48 | 49 | Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 50 | 51 | 6. Trademarks. 52 | 53 | This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 54 | 55 | 7. Disclaimer of Warranty. 56 | 57 | Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 58 | 59 | 8. Limitation of Liability. 60 | 61 | In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 62 | 63 | 9. Accepting Warranty or Additional Liability. 64 | 65 | While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 66 | 67 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # test-ai-selenium-demo 2 | [![JDK-11+](https://img.shields.io/badge/JDK-11%2B-blue)](https://adoptium.net) 3 | [![Apache 2.0](https://img.shields.io/badge/Apache-2.0-blue)](https://www.apache.org/licenses/LICENSE-2.0) 4 | [![Discord](https://img.shields.io/discord/853669216880295946?&logo=discord)](https://sdk.test.ai/discord) 5 | 6 | Welcome to the test.ai Selenium Java demo! 7 | 8 | This repository contains a pre-configured project and basic tutorial, so you can hit the ground running with [test.ai enhanced Selenium](https://github.com/testdotai/java-selenium-sdk)! 9 | 10 | 👉 This tutorial uses Google Chrome, but you will be able to use `test-ai-selenium` with any other browser that supports Selenium. 11 | 12 | ## Pre-requisites 13 | Before we get started, please ensure that you have installed the following software on your computer: 14 | 15 | * [Java](https://adoptium.net) - version `11` or newer 16 | * [Google Chrome](https://www.google.com/chrome/) 17 | 18 | You will also need a test.ai account, please visit https://sdk.test.ai (it's free!) to register. 19 | 20 | ## An Introduction to the test.ai SDK 21 | In the following tutorial, you will learn how to set up and run AI-enhanced Selenium with test.ai. 22 | 23 | Now that you have the pre-requisites installed, let's get started. 24 | 25 | Please begin by cloning this repository to your computer. 26 | ```bash 27 | git clone https://github.com/testdotai/java-selenium-sdk-demo.git 28 | ``` 29 | 30 | ### Run the demo 31 | Please visit https://sdk.test.ai, and log into your account. Please copy your `API key` (in the upper right corner of your screen), you will need this for the next step. 32 | 33 | ![Example API Key](https://testdotai.github.io/static-assets/appium-demo/api_key.png) 34 | 35 | Next, `cd` into the root directory of this repository and run the following command, replacing the text `YOUR_API_KEY` with your test.ai API key. 36 | ```bash 37 | ./gradlew run --args=YOUR_API_KEY 38 | ``` 39 | 40 | If using Windows, please run 41 | ```powershell 42 | gradlew.bat run --args=YOUR_API_KEY 43 | ``` 44 | 45 | If everything worked, an instance of the Chrome browser will be started and open to the [test.ai Store](https://testaistore.com) (no, it's not a real store!). Then, the sample code in this demo will click the `Store` navbar link, search for `Shoes`, and add a pair of green shoes to the cart. 46 | 47 | 🎥 Click [here](https://testdotai.github.io/static-assets/selenium-demo/no_ai_example_flow.mov) to see a video of the expected behavior. 48 | 49 | Thus far, the demo is using the standard Selenium selectors without any AI, which is how apps today are commonly tested. However, selectors such as these are fragile and break easily, as even minor changes to a website may cause them to *immediately* stop working. Fortunately, the test.ai SDK is equipped to help you avoid this unecessary hassle. 50 | 51 | ### Using test.ai with Selenium 52 | 53 | Please visit https://sdk.test.ai (and log in to your test.ai account if you've been logged out). 54 | 55 | You should see the following new entries on this page: 56 | 57 | 58 | 59 | namely, 60 | * `store_nav_link` 61 | * `search_products_input` 62 | * `search_button` 63 | * `green_shoes` 64 | * `add_to_cart` 65 | 66 | Start by clicking on the link in the `Element` column for `store_nav_link`. 67 | 68 | On this new page, find the navbar link `Store`. Using your mouse, click and drag a box around the link. A green box will appear as you drag your mouse. Release your mouse button to save the selection. 69 | 70 | ![store nav link demo](https://testdotai.github.io/static-assets/selenium-demo/store_nav_link.gif) 71 | 72 | Believe it or not, you just used AI! test.ai is visual-based, so there's no need to mess around with Selenium selectors. The test.ai classifier will train itself using the element inside the box you just drew with your mouse, and now, when it encounters this element in the future, it will be able to recognize it! 73 | 74 | Let's do the same thing for the other elements. 75 | 76 | #### `search_products_input` 77 |
78 | (click to expand) 79 | 80 | ![search products input demo](https://testdotai.github.io/static-assets/selenium-demo/search_products_input.gif) 81 |
82 | 83 | #### `search_button` 84 |
85 | (click to expand) 86 | 87 | ![search button demo](https://testdotai.github.io/static-assets/selenium-demo/search_button.gif) 88 |
89 | 90 | #### `green_shoes` 91 |
92 | (click to expand) 93 | 94 | ![green shoes demo](https://testdotai.github.io/static-assets/selenium-demo/green_shoes.gif) 95 |
96 | 97 | #### `add_to_cart` 98 |
99 | (click to expand) 100 | 101 | ![add to cart demo](https://testdotai.github.io/static-assets/selenium-demo/add_to_cart.gif) 102 |
103 | 104 | 👉 Training takes a few minutes, you can check training status by visiting https://sdk.test.ai/training_status 105 | 106 | ![training status](https://testdotai.github.io/static-assets/selenium-demo/training_status.png) 107 | 108 | Next, let's simulate what happens when a developer changes a web application's code. 109 | 110 | In the IDE of your choice, please open [src/main/java/ai/test/sdk/demo/Example.java](src/main/java/ai/test/sdk/demo/Example.java). This Java file contains an abridged form of what you might find in typical Selenium-based test suite. 111 | 112 | A few noteworthy items: 113 | * The `ChromeDriver` gets passed as a parameter to a `TestAiDriver`, along with your API key. 114 | * Each call to a "`findElementsBy`" method contains a second parameter, which is used to give the element an optional, human-readable label for use at https://sdk.test.ai 115 | 116 | As you can see, it is very easy to integrate test.ai into your existing Selenium-based test cases. 117 | 118 | Now, let's change a couple of the XPath selectors: 119 | 120 | ```java 121 | // Change this line: 122 | WebElement storeNavLink = driver.findElementById("menu-item-45", "store_nav_link"); 123 | 124 | //to: 125 | WebElement storeNavLink = driver.findElementById("my-developers-changed-this", "store_nav_link"); 126 | ``` 127 | 128 | ```java 129 | // Change this line: 130 | WebElement searchProductsInput = driver.findElementByCssSelector("#woocommerce-product-search-field-0", "search_products_input"); 131 | 132 | // to: 133 | WebElement searchProductsInput = driver.findElementByCssSelector("#my-developers-also-changed-this", "search_products_input"); 134 | ``` 135 | 136 | ```java 137 | // Change this line: 138 | WebElement addToCart = driver.findElementByXPath("//*[@id=\"product-2719\"]/div[2]/form/button", "add_to_cart"); 139 | 140 | // to: 141 | WebElement addToCart = driver.findElementByXPath("//*[wow/these/developers/have/been/busy]", "add_to_cart"); 142 | ``` 143 | 144 | As you may have already guessed, a standard Selenium test would certainly fail, but test.ai won't! 145 | 146 | ### Re-run using AI selectors 147 | Using your terminal, `cd` into the root directory of this project, and run the following command, replacing the text `YOUR_API_KEY` with your test.ai API key. 148 | 149 | ```bash 150 | ./gradlew run --args=YOUR_API_KEY 151 | ``` 152 | 153 | If using Windows, please run 154 | ```powershell 155 | gradlew.bat run --args=YOUR_API_KEY 156 | ``` 157 | 158 | The demo will repeat the same steps that it performed previously, only this time, it's using test.ai! The "developer's breaking changes" we introduced in the code have no effect on the test's ability to run, all thanks to test.ai AI. 159 | 160 | And that's all folks, you've made it to the end of the tutorial! 🎉 161 | 162 | As far as next steps go, please feel free to experiment more with this code and try out some of the other selectors. Good luck and have fun! 163 | 164 | ## Additional Resources 165 | * [API docs](https://www.javadoc.io/doc/ai.test.sdk/test-ai-selenium) 166 | * [Another Basic Tutorial](https://sdk.test.ai/tutorial) 167 | 168 | ## Contact 169 | Questions? Comments? We'd love to hear from you! 170 | 171 | * ✉️ Email us: `sdk {at} test.ai` 172 | * 💬 Chat with us on Discord: https://sdk.test.ai/discord -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | repositories { 6 | mavenLocal() 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | implementation 'ai.test.sdk:test-ai-selenium:0.1.0' 12 | implementation 'io.github.bonigarcia:webdrivermanager:5.1.0' 13 | implementation 'org.slf4j:slf4j-reload4j:1.7.36' 14 | implementation 'org.apache.logging.log4j:log4j-api:2.17.1' 15 | } 16 | 17 | application { 18 | mainClass = 'ai.test.sdk.demo.Example' 19 | } 20 | 21 | tasks.named('test') { 22 | 23 | } 24 | 25 | wrapper { 26 | gradleVersion = '7.4' 27 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdotai/java-selenium-sdk-demo/529dc3f59b52125837a2c7e7dc55e9c523c3cf84/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'test-ai-selenium-demo' 2 | -------------------------------------------------------------------------------- /src/main/java/ai/test/sdk/demo/Example.java: -------------------------------------------------------------------------------- 1 | package ai.test.sdk.demo; 2 | 3 | import org.openqa.selenium.WebElement; 4 | import org.openqa.selenium.chrome.ChromeDriver; 5 | 6 | import ai.test.sdk.TestAiDriver; 7 | import io.github.bonigarcia.wdm.WebDriverManager; 8 | 9 | /** 10 | * A brief example demonstrating the capabilities of test.ai enhanced Selenium. 11 | * 12 | * @author Alexander Wu (alec@test.ai) 13 | * 14 | */ 15 | public class Example 16 | { 17 | /** 18 | * Main driver 19 | * 20 | * @param args Program arguments, should be an Array of length 1 containing a String with the user's API key. 21 | * @throws Throwable if anything went wrong 22 | */ 23 | public static void main(String[] args) throws Throwable 24 | { 25 | WebDriverManager.chromedriver().setup(); 26 | ChromeDriver chromeDriver = new ChromeDriver(); 27 | 28 | try 29 | { 30 | TestAiDriver driver = new TestAiDriver(chromeDriver, args[0]); 31 | driver.get("https://testaistore.com/"); 32 | 33 | Thread.sleep(2000); 34 | 35 | WebElement storeNavLink = driver.findElementById("menu-item-45", "store_nav_link"); 36 | storeNavLink.click(); 37 | 38 | Thread.sleep(2000); 39 | 40 | WebElement searchProductsInput = driver.findElementByCssSelector("#woocommerce-product-search-field-0", "search_products_input"); 41 | searchProductsInput.sendKeys("Shoes"); 42 | 43 | Thread.sleep(2000); 44 | 45 | WebElement searchButton = driver.findElementByXPath("//*[@id=\"woocommerce_product_search-4\"]/form/button", "search_button"); 46 | searchButton.click(); 47 | 48 | Thread.sleep(2000); 49 | 50 | WebElement greenShoes = driver.findElementByXPath("//*[@id=\"main\"]/div/ul/li[2]/div[1]/a", "green_shoes"); 51 | greenShoes.click(); 52 | 53 | Thread.sleep(2000); 54 | 55 | WebElement addToCart = driver.findElementByXPath("//*[@id=\"product-2719\"]/div[2]/form/button", "add_to_cart"); 56 | addToCart.click(); 57 | 58 | Thread.sleep(5000); 59 | } 60 | finally 61 | { 62 | chromeDriver.quit(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=INFO, stdout 2 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 3 | log4j.appender.stdout.Target=System.out 4 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 5 | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n --------------------------------------------------------------------------------