├── .gitignore
├── LICENSE
├── README.md
├── graphics
├── Aleksandra_Shineleva.png
├── EyalAvatar.png
└── workshop_fork.mp4
├── pom.xml
└── src
└── test
├── java
└── com
│ └── demo
│ ├── Config.java
│ ├── DemoTest.java
│ ├── answers
│ ├── ex1_CreateAndroidDriver.java
│ ├── ex2_DevelopAndroidTest.java
│ ├── ex3_CreateIOSDriver.java
│ └── ex4_DevelopIOSTest.java
│ └── exercises
│ ├── ex1_CreateAndroidDriver.java
│ ├── ex2_DevelopAndroidTest.java
│ ├── ex3_CreateIOSDriver.java
│ └── ex4_DevelopIOSTest.java
└── resources
└── config
├── myDemoParallelTests.xml
└── myDemoTests.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | .idea/
3 | *.setting
4 | *.DS_store
5 | *.classpath
6 | .settings/
7 | *.project
8 | *.png
9 | test-output/
10 | build/
11 | .DS_Store
12 | test-order-details/
13 | test-results/
14 | img/
15 | perfectoReports/
16 | .git/
17 | *.log
18 | /logs/
19 | /dashboard/
20 | *.pdf
21 | /bin
22 | Drivers/
23 | target/surefire-reports/index.html
24 | apps/
25 | *.iml
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 eyaly
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Mobile testing w/ Appium + Java
2 |
3 | Mobile automated testing using Appium and Java
4 |
5 | > Before we start, open this in a new tab and let the container load.
6 |
7 | [](https://gitpod.io/#https://github.com/eyaly/mobile-appium-java-workshop)
8 |
9 | ## 🧠You will learn to
10 |
11 | * What is Appium
12 | * Create an automated Android test
13 | * Create an automated iOS test
14 | * Find element locators using Appium Inspector
15 | * Run tests in Sauce Labs real devices
16 | * Run tests in parallel
17 |
18 | ## Testing for Good
19 |
20 | ### 🌎Testing for Good enables great test automation engineering while shaping a more equitable society.👩💻
21 |
22 | Today, we're asking for donations for [Environmental Working Group](https://buy.stripe.com/9AQdU42lj9i7bHGcMN)
23 |
24 | ### [About Environmental Working Group](https://www.ewg.org/)
25 |
26 | We're advocates who won't quit. We're scientists that find solutions. We're people trying to make the safest choices for our health. At the Environmental Working Group, we believe that you should have easy access to the information you need to make smart, healthy choices. It’s this belief that inspired our president and co-founder, Ken Cook, to create EWG.
27 |
28 | Since 1993, we've worked tirelessly to protect public health. Whether it's spotlighting harmful industry standards, speaking out against outdated government legislation or empowering consumers with breakthrough education and research, we're in this fight.
29 |
30 | And we're not going anywhere.
31 |
32 | 👉 While the event is free, Sauce Labs encourages all attendees to
33 |
34 | 👉 **[donate](https://buy.stripe.com/9AQdU42lj9i7bHGcMN)**
35 |
36 | Anything helps!
37 |
38 | 100% of donations go to support the cause.
39 |
40 | ## Key
41 |
42 | 💡 this is a tip
43 |
44 | 🏋️♀️ this is an exercise for you to do
45 |
46 | ❓ this is a question for us to think and talk about. Try not to scroll beyond this question before we discuss
47 |
48 |
49 | ## Your Instructor: Eyal Yovel
50 |
51 |
52 |
53 |
54 | - 🏢 I’m a Sr Solutions Architect at Sauce Labs
55 | - 😄 Pronouns: he/him
56 | - 📫 Links:
57 | [](https://www.linkedin.com/in/eyal-yovel-9786933/)
58 | [](https://twitter.com/eyalyovel)
59 | [](https://github.com/eyaly/)
60 |
61 | ## Your TA:
62 |
63 | ---
64 | ## Setup
65 |
66 | ---
67 | ### Sauce Labs setup
68 | 1. Free [Sauce account](https://saucelabs.com/sign-up)
69 | 2. Make sure you know how to find your Sauce Labs Username and Access Key by going to the [Sauce Labs user settings page](https://app.saucelabs.com/user-settings)
70 |
71 | ---
72 | ### Appium setup
73 | 1. We will run our automated tests on Sauce Labs devices; therefore, there is no need to install Appium Server.
74 | 2. Please install [appium inspector](https://github.com/appium/appium-inspector#installation). Appium Inspector is basically just an Appium client (like WebdriverIO, Appium's Java client, etc...) with a user interface.
75 |
76 | ---
77 | ### Demo App(s)
78 | 1. The demo app that has been used for all these tests can be found [here](https://github.com/saucelabs/sample-app-mobile/releases).
79 | 2. Be aware of the fact that and iOS simulator uses a different build then a iOS real device. So please check the file you
80 | download.
81 | ---
82 | ### Gitpod setup
83 |
84 | ℹ Gitpod lets you run an entire Dev environment from a browser! You can use this approach if you don't have time or you don't know how to setup a local Java environment.
85 |
86 | 1. Sign up for a free [GitHub account](https://github.com/)
87 | 2. [Fork this repository](https://docs.github.com/en/get-started/quickstart/fork-a-repo)
88 | * Make sure you are logged into GitHub
89 | * Click the Fork in the upper right of the GitHub.
90 | * Give the repo a ⭐ while you're here 🤩
91 | 3. Click here
92 |
93 | [](https://gitpod.io/#https://github.com/nadvolod/js-code/tree/master/web-testing-2022)
94 |
95 | OR
96 | In the browser address bar, add your GitHub url (`https://github.com/USERNAME/this-repo-name`) with `https://gitpod.io/#`
97 | * The resulting url should look as follows:
98 |
99 | > https://gitpod.io/#https://github.com/USERNAME/mobile-appium-java-workshop
100 |
101 | 4. Once the Gitpod.io URL is loaded, you will need to sign in with the GitHub account you created earlier
102 | 5. Once the development environment is loaded, you should see 'Ready to test!' in the Terminal window in the lower portion of the window, run the following commands in that Terminal to set your `SAUCE_USERNAME`, `SAUCE_ACCESS_KEY`:
103 |
104 | ```bash
105 | eval $(gp env -e SAUCE_USERNAME=)
106 | eval $(gp env -e SAUCE_ACCESS_KEY=)
107 | ```
108 |
109 | > Replace , with your credentials
110 |
111 | Once you have run those 2 commands, you can run the following commands to test your environment variables:
112 |
113 | ```bash
114 | echo $SAUCE_USERNAME
115 | echo $SAUCE_ACCESS_KEY
116 | ```
117 |
118 | Run sanity tests
119 |
120 | ```bash
121 | mvn clean test -DtestngXmlFile=myDemoTests.xml
122 | ```
123 |
124 |
125 |
126 |
127 | Click here to see an example console output.
128 |
129 |
130 | [INFO] -------------------------------------------------------
131 | [INFO] T E S T S
132 | [INFO] -------------------------------------------------------
133 | [INFO] Running TestSuite
134 | *** BeforeMethod hook. Running method demoTest ***
135 | region is us
136 | *** Start demoTest test ***
137 | *** AfterMethod hook ***
138 | [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.488 s - in TestSuite
139 | [INFO]
140 | [INFO] Results:
141 | [INFO]
142 | [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
143 | [INFO]
144 | [INFO] ------------------------------------------------------------------------
145 | [INFO] BUILD SUCCESS
146 | [INFO] ------------------------------------------------------------------------
147 | [INFO] Total time: 3.579 s
148 | [INFO] Finished at: 2022-07-04T12:05:35+01:00
149 | [INFO] ------------------------------------------------------------------------
150 |
151 |
152 |
153 |
154 |
155 | #### In the next video I demo how to fork the repo and connect to Gitpod:
156 |
157 | https://user-images.githubusercontent.com/6969588/177877688-f735e152-991d-4c7e-a326-0a9a2969fea9.mp4
158 |
159 |
160 |
161 |
162 | ***✅👏Environment setup is complete if tests passed***
163 |
164 | ---
165 |
166 | ### Local environment setup
167 |
168 | 1. Sign up for a free [GitHub account](https://github.com/)
169 | 2. [Fork this repository](https://docs.github.com/en/get-started/quickstart/fork-a-repo)
170 | * Make sure you are logged into GitHub
171 | * Click the Fork in the upper right of the GitHub.
172 | * Give the repo a ⭐ while you're here 🤩
173 | 3. Clone **your fork** of the repository to your machine. Must have [Git installed](https://git-scm.com/downloads)
174 |
175 | ```bash
176 | git clone URL_OF_YOUR_FORK
177 | ```
178 |
179 | Setup environment variables on your system
180 | * [Mac/Linux](https://docs.saucelabs.com/basics/environment-variables/#setting-up-environment-variables-on-macos-and-linux-systems)
181 | * [Windows](https://docs.saucelabs.com/basics/environment-variables/#setting-up-environment-variables-on-windows-systems)
182 |
183 | Run sanity tests
184 |
185 | ```java
186 | mvn clean test -DtestngXmlFile=myDemoTests.xml
187 | ```
188 |
189 |
190 |
191 |
192 | Click here to see an example console output.
193 |
194 |
195 | [INFO] -------------------------------------------------------
196 | [INFO] T E S T S
197 | [INFO] -------------------------------------------------------
198 | [INFO] Running TestSuite
199 | *** BeforeMethod hook. Running method demoTest ***
200 | region is us
201 | *** Start demoTest test ***
202 | *** AfterMethod hook ***
203 | [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.488 s - in TestSuite
204 | [INFO]
205 | [INFO] Results:
206 | [INFO]
207 | [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
208 | [INFO]
209 | [INFO] ------------------------------------------------------------------------
210 | [INFO] BUILD SUCCESS
211 | [INFO] ------------------------------------------------------------------------
212 | [INFO] Total time: 3.579 s
213 | [INFO] Finished at: 2022-07-04T12:05:35+01:00
214 | [INFO] ------------------------------------------------------------------------
215 |
216 |
217 |
218 |
219 |
220 | ***✅👏Environment setup is complete if tests passed***
221 |
222 | ## Extra resources
223 |
224 | - [Appium options for sauce](https://docs.saucelabs.com/dev/test-configuration-options/#mobile-app-appium-capabilities-required)
225 | - [All appium capabilities](https://appium.io/docs/en/writing-running-appium/caps/)
226 | - [More Appium resources](https://github.com/saucelabs-training/demo-java/blob/main/TRAINING.md)
227 |
228 | ## Conclusions
229 | Please leave [quick and anonymous feedback on this workshop](https://docs.google.com/forms/d/e/1FAIpQLSfhKpfdRU9FuqYMfFyqD3GhjdYADzZikjes7boVErWlru4XBA/viewform)
--------------------------------------------------------------------------------
/graphics/Aleksandra_Shineleva.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eyaly/mobile-appium-java-workshop/0c2b3299b956685ca00dd97811309b2db21976fe/graphics/Aleksandra_Shineleva.png
--------------------------------------------------------------------------------
/graphics/EyalAvatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eyaly/mobile-appium-java-workshop/0c2b3299b956685ca00dd97811309b2db21976fe/graphics/EyalAvatar.png
--------------------------------------------------------------------------------
/graphics/workshop_fork.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eyaly/mobile-appium-java-workshop/0c2b3299b956685ca00dd97811309b2db21976fe/graphics/workshop_fork.mp4
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | org.example
8 | mobile-appium-java-workshop
9 | 1.0-SNAPSHOT
10 |
11 |
12 | src/test/resources/config
13 | myDemoTests.xml
14 | 7.4.0
15 | 8.2.0
16 |
17 |
18 |
19 |
20 |
21 | org.testng
22 | testng
23 | ${testng.version}
24 |
25 |
26 |
27 | io.appium
28 | java-client
29 | ${appium.version}
30 | test
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | org.apache.maven.plugins
39 | maven-surefire-plugin
40 | 3.0.0-M4
41 |
42 |
43 | ${testngXmlDir}/${testngXmlFile}
44 |
45 |
46 |
47 |
48 | maven-compiler-plugin
49 | 3.0
50 |
51 | 1.8
52 | 1.8
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/test/java/com/demo/Config.java:
--------------------------------------------------------------------------------
1 | package com.demo;
2 |
3 | public class Config {
4 | public static final String region = System.getProperty("region", "us");
5 | }
6 |
--------------------------------------------------------------------------------
/src/test/java/com/demo/DemoTest.java:
--------------------------------------------------------------------------------
1 | package com.demo;
2 |
3 | import org.testng.ITestResult;
4 | import org.testng.Reporter;
5 | import org.testng.annotations.AfterMethod;
6 | import org.testng.annotations.BeforeMethod;
7 | import org.testng.annotations.Test;
8 |
9 | import java.lang.reflect.Method;
10 | import java.net.MalformedURLException;
11 | import java.util.Map;
12 |
13 | import static com.demo.Config.region;
14 |
15 | public class DemoTest {
16 |
17 | private String SAUCE_EU_URL = "https://ondemand.eu-central-1.saucelabs.com/wd/hub";
18 | private String SAUCE_US_URL = "https://ondemand.us-west-1.saucelabs.com/wd/hub";
19 |
20 |
21 | @BeforeMethod
22 | public void setup(Method method) throws MalformedURLException {
23 |
24 | String methodName = method.getName();
25 | System.out.println("*** BeforeMethod hook. Running method " + methodName + " ***");
26 |
27 | switch (region) {
28 | case "us":
29 | System.out.println("region is us");
30 | break;
31 | case "eu":
32 | default:
33 | System.out.println("region is eu");
34 | break;
35 | }
36 |
37 | }
38 |
39 | @Test
40 | public void demoTest() {
41 | System.out.println("*** Start demoTest test ***");
42 | }
43 |
44 | @AfterMethod
45 | public void teardown(ITestResult result) {
46 | System.out.println("*** AfterMethod hook ***");
47 | }
48 |
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/test/java/com/demo/answers/ex1_CreateAndroidDriver.java:
--------------------------------------------------------------------------------
1 | package com.demo.answers;
2 |
3 | import io.appium.java_client.AppiumDriver;
4 | import io.appium.java_client.android.AndroidDriver;
5 | import org.openqa.selenium.MutableCapabilities;
6 | import org.testng.ITestResult;
7 | import org.testng.annotations.AfterMethod;
8 | import org.testng.annotations.BeforeMethod;
9 | import org.testng.annotations.Test;
10 |
11 | import java.lang.reflect.Method;
12 | import java.net.MalformedURLException;
13 | import java.net.URL;
14 | import java.util.Arrays;
15 | import java.util.List;
16 |
17 | import static com.demo.Config.region;
18 |
19 | public class ex1_CreateAndroidDriver {
20 |
21 | private String SAUCE_EU_URL = "https://ondemand.eu-central-1.saucelabs.com/wd/hub";
22 | private String SAUCE_US_URL = "https://ondemand.us-west-1.saucelabs.com/wd/hub";
23 |
24 | private AndroidDriver driver;
25 |
26 | @BeforeMethod
27 | public void setup(Method method) throws MalformedURLException {
28 |
29 | String methodName = method.getName();
30 | System.out.println("*** BeforeMethod hook. Running method " + methodName + " ***");
31 | URL url;
32 |
33 | switch (region) {
34 | case "us":
35 | System.out.println("region is us");
36 | url = new URL(SAUCE_US_URL);
37 | break;
38 | case "eu":
39 | default:
40 | System.out.println("region is eu");
41 | url = new URL(SAUCE_EU_URL);
42 | break;
43 | }
44 |
45 | MutableCapabilities capabilities = new MutableCapabilities();
46 | MutableCapabilities sauceOptions = new MutableCapabilities();
47 |
48 | //find a device in the cloud
49 | capabilities.setCapability("platformName", "android");
50 | capabilities.setCapability("automationName", "UiAutomator2");
51 | capabilities.setCapability("appium:deviceName", "Samsung.*");
52 | capabilities.setCapability("appium:platformVersion", "12");
53 |
54 | capabilities.setCapability("appium:app",
55 | "https://github.com/saucelabs/sample-app-mobile/releases/download/2.7.1/Android.SauceLabs.Mobile.Sample.app.2.7.1.apk");
56 | capabilities.setCapability("appium:appWaitActivity","com.swaglabsmobileapp.MainActivity");
57 |
58 | // Sauce capabilities
59 | sauceOptions.setCapability("name", methodName);
60 | sauceOptions.setCapability("username", System.getenv("SAUCE_USERNAME"));
61 | sauceOptions.setCapability("accessKey", System.getenv("SAUCE_ACCESS_KEY"));
62 | capabilities.setCapability("sauce:options", sauceOptions);
63 |
64 | driver = new AndroidDriver(url, capabilities);
65 |
66 | }
67 |
68 | @Test
69 | public void demoTest() {
70 | System.out.println("*** Start demoTest test ***");
71 | }
72 |
73 | @AfterMethod
74 | public void teardown(ITestResult result) {
75 | System.out.println("*** AfterMethod hook ***");
76 | driver.quit();
77 | }
78 |
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/src/test/java/com/demo/answers/ex2_DevelopAndroidTest.java:
--------------------------------------------------------------------------------
1 | package com.demo.answers;
2 |
3 | import io.appium.java_client.AppiumBy;
4 | import io.appium.java_client.android.AndroidDriver;
5 | import org.openqa.selenium.By;
6 | import org.openqa.selenium.JavascriptExecutor;
7 | import org.openqa.selenium.MutableCapabilities;
8 | import org.openqa.selenium.TimeoutException;
9 | import org.openqa.selenium.support.ui.ExpectedConditions;
10 | import org.openqa.selenium.support.ui.WebDriverWait;
11 | import org.testng.Assert;
12 | import org.testng.ITestResult;
13 | import org.testng.annotations.AfterMethod;
14 | import org.testng.annotations.BeforeMethod;
15 | import org.testng.annotations.Test;
16 |
17 | import java.lang.reflect.Method;
18 | import java.net.MalformedURLException;
19 | import java.net.URL;
20 | import java.time.Duration;
21 |
22 | import static com.demo.Config.region;
23 |
24 | public class ex2_DevelopAndroidTest {
25 |
26 | private String SAUCE_EU_URL = "https://ondemand.eu-central-1.saucelabs.com/wd/hub";
27 | private String SAUCE_US_URL = "https://ondemand.us-west-1.saucelabs.com/wd/hub";
28 |
29 | private AndroidDriver driver;
30 |
31 | @BeforeMethod
32 | public void setup(Method method) throws MalformedURLException {
33 |
34 | String methodName = method.getName();
35 | System.out.println("*** BeforeMethod hook. Running method " + methodName + " ***");
36 | URL url;
37 |
38 | switch (region) {
39 | case "us":
40 | System.out.println("region is us");
41 | url = new URL(SAUCE_US_URL);
42 | break;
43 | case "eu":
44 | default:
45 | System.out.println("region is eu");
46 | url = new URL(SAUCE_EU_URL);
47 | break;
48 | }
49 |
50 | MutableCapabilities capabilities = new MutableCapabilities();
51 | MutableCapabilities sauceOptions = new MutableCapabilities();
52 |
53 | //find a device in the cloud
54 | capabilities.setCapability("platformName", "android");
55 | capabilities.setCapability("automationName", "UiAutomator2");
56 | capabilities.setCapability("appium:deviceName", "Samsung.*");
57 | capabilities.setCapability("appium:platformVersion", "12");
58 |
59 | capabilities.setCapability("appium:app",
60 | "https://github.com/saucelabs/sample-app-mobile/releases/download/2.7.1/Android.SauceLabs.Mobile.Sample.app.2.7.1.apk");
61 | capabilities.setCapability("appium:appWaitActivity","com.swaglabsmobileapp.MainActivity");
62 |
63 | // Sauce capabilities
64 | sauceOptions.setCapability("name", methodName);
65 | sauceOptions.setCapability("username", System.getenv("SAUCE_USERNAME"));
66 | sauceOptions.setCapability("accessKey", System.getenv("SAUCE_ACCESS_KEY"));
67 | capabilities.setCapability("sauce:options", sauceOptions);
68 |
69 | driver = new AndroidDriver(url, capabilities);
70 |
71 | }
72 |
73 | @Test
74 | public void demoTest() {
75 | System.out.println("*** Start demoTest test ***");
76 |
77 | // Login
78 | // Accessibility-id test-Username
79 | // Accessibility-id test-Password
80 | // Accessibility-id test-LOGIN
81 | driver.findElement(AppiumBy.accessibilityId("test-Username")).sendKeys("standard_user");
82 | driver.findElement(AppiumBy.accessibilityId("test-Password")).sendKeys("secret_sauce");
83 | driver.findElement(AppiumBy.accessibilityId("test-LOGIN")).click();
84 |
85 | // Verify
86 | // xpath //android.widget.ScrollView[@content-desc="test-PRODUCTS"]
87 | Assert.assertTrue(isOnProductsPage());
88 |
89 | }
90 |
91 | @AfterMethod
92 | public void teardown(ITestResult result) {
93 | System.out.println("*** AfterMethod hook ***");
94 | // (3) Add code to check if test passed or failed
95 | try {
96 | ((JavascriptExecutor) driver).executeScript("sauce:job-result=" + (result.isSuccess() ? "passed" : "failed"));
97 | } finally {
98 | driver.quit();
99 | }
100 | }
101 |
102 | public boolean isOnProductsPage() {
103 |
104 | //Create an instance of an explicit wait so that we can dynamically wait for an element
105 | WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
106 |
107 | //wait for the product field to be visible and store that element into a variable
108 | try {
109 | wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//android.widget.ScrollView[@content-desc=\"test-PRODUCTS\"]")));
110 | } catch (TimeoutException e){
111 | return false;
112 | }
113 | return true;
114 | }
115 |
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/src/test/java/com/demo/answers/ex3_CreateIOSDriver.java:
--------------------------------------------------------------------------------
1 | package com.demo.answers;
2 |
3 | import io.appium.java_client.android.AndroidDriver;
4 | import io.appium.java_client.ios.IOSDriver;
5 | import org.openqa.selenium.MutableCapabilities;
6 | import org.testng.ITestResult;
7 | import org.testng.annotations.AfterMethod;
8 | import org.testng.annotations.BeforeMethod;
9 | import org.testng.annotations.Test;
10 |
11 | import java.lang.reflect.Method;
12 | import java.net.MalformedURLException;
13 | import java.net.URL;
14 |
15 | import static com.demo.Config.region;
16 |
17 | public class ex3_CreateIOSDriver {
18 |
19 | private String SAUCE_EU_URL = "https://ondemand.eu-central-1.saucelabs.com/wd/hub";
20 | private String SAUCE_US_URL = "https://ondemand.us-west-1.saucelabs.com/wd/hub";
21 |
22 | private IOSDriver driver;
23 |
24 | @BeforeMethod
25 | public void setup(Method method) throws MalformedURLException {
26 |
27 | String methodName = method.getName();
28 | System.out.println("*** BeforeMethod hook. Running method " + methodName + " ***");
29 | URL url;
30 |
31 | switch (region) {
32 | case "us":
33 | System.out.println("region is us");
34 | url = new URL(SAUCE_US_URL);
35 | break;
36 | case "eu":
37 | default:
38 | System.out.println("region is eu");
39 | url = new URL(SAUCE_EU_URL);
40 | break;
41 | }
42 |
43 | MutableCapabilities capabilities = new MutableCapabilities();
44 | MutableCapabilities sauceOptions = new MutableCapabilities();
45 |
46 | //find a device in the cloud
47 | capabilities.setCapability("platformName", "iOS");
48 | capabilities.setCapability("automationName", "XCuiTest");
49 | capabilities.setCapability("appium:deviceName", "iPhone.*");
50 | capabilities.setCapability("appium:platformVersion", "14");
51 |
52 | capabilities.setCapability("appium:app",
53 | "https://github.com/saucelabs/sample-app-mobile/releases/download/2.7.1/iOS.RealDevice.SauceLabs.Mobile.Sample.app.2.7.1.ipa");
54 |
55 | // Sauce capabilities
56 | sauceOptions.setCapability("name", methodName);
57 | sauceOptions.setCapability("username", System.getenv("SAUCE_USERNAME"));
58 | sauceOptions.setCapability("accessKey", System.getenv("SAUCE_ACCESS_KEY"));
59 | capabilities.setCapability("sauce:options", sauceOptions);
60 |
61 | driver = new IOSDriver(url, capabilities);
62 |
63 | }
64 |
65 | @Test
66 | public void demoTest() {
67 | System.out.println("*** Start demoTest test ***");
68 | }
69 |
70 | @AfterMethod
71 | public void teardown(ITestResult result) {
72 | System.out.println("*** AfterMethod hook ***");
73 | driver.quit();
74 | }
75 |
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/src/test/java/com/demo/answers/ex4_DevelopIOSTest.java:
--------------------------------------------------------------------------------
1 | package com.demo.answers;
2 |
3 | import io.appium.java_client.AppiumBy;
4 | import io.appium.java_client.ios.IOSDriver;
5 | import org.openqa.selenium.By;
6 | import org.openqa.selenium.JavascriptExecutor;
7 | import org.openqa.selenium.MutableCapabilities;
8 | import org.openqa.selenium.TimeoutException;
9 | import org.openqa.selenium.support.ui.ExpectedConditions;
10 | import org.openqa.selenium.support.ui.WebDriverWait;
11 | import org.testng.Assert;
12 | import org.testng.ITestResult;
13 | import org.testng.annotations.AfterMethod;
14 | import org.testng.annotations.BeforeMethod;
15 | import org.testng.annotations.Test;
16 |
17 | import java.lang.reflect.Method;
18 | import java.net.MalformedURLException;
19 | import java.net.URL;
20 | import java.time.Duration;
21 |
22 | import static com.demo.Config.region;
23 |
24 | public class ex4_DevelopIOSTest {
25 |
26 | private String SAUCE_EU_URL = "https://ondemand.eu-central-1.saucelabs.com/wd/hub";
27 | private String SAUCE_US_URL = "https://ondemand.us-west-1.saucelabs.com/wd/hub";
28 |
29 | private IOSDriver driver;
30 |
31 | @BeforeMethod
32 | public void setup(Method method) throws MalformedURLException {
33 |
34 | String methodName = method.getName();
35 | System.out.println("*** BeforeMethod hook. Running method " + methodName + " ***");
36 | URL url;
37 |
38 | switch (region) {
39 | case "us":
40 | System.out.println("region is us");
41 | url = new URL(SAUCE_US_URL);
42 | break;
43 | case "eu":
44 | default:
45 | System.out.println("region is eu");
46 | url = new URL(SAUCE_EU_URL);
47 | break;
48 | }
49 |
50 | MutableCapabilities capabilities = new MutableCapabilities();
51 | MutableCapabilities sauceOptions = new MutableCapabilities();
52 |
53 | //find a device in the cloud
54 | capabilities.setCapability("platformName", "iOS");
55 | capabilities.setCapability("automationName", "XCuiTest");
56 | capabilities.setCapability("appium:deviceName", "iPhone.*");
57 | capabilities.setCapability("appium:platformVersion", "14");
58 |
59 | capabilities.setCapability("appium:app",
60 | "https://github.com/saucelabs/sample-app-mobile/releases/download/2.7.1/iOS.RealDevice.SauceLabs.Mobile.Sample.app.2.7.1.ipa");
61 |
62 | // Sauce capabilities
63 | sauceOptions.setCapability("name", methodName);
64 | sauceOptions.setCapability("username", System.getenv("SAUCE_USERNAME"));
65 | sauceOptions.setCapability("accessKey", System.getenv("SAUCE_ACCESS_KEY"));
66 | capabilities.setCapability("sauce:options", sauceOptions);
67 |
68 | driver = new IOSDriver(url, capabilities);
69 |
70 | }
71 |
72 | @Test
73 | public void demoTest() {
74 | System.out.println("*** Start demoTest test ***");
75 |
76 | // Login
77 | // Accessibility-id test-Username
78 | // Accessibility-id test-Password
79 | // Accessibility-id test-LOGIN
80 | driver.findElement(AppiumBy.accessibilityId("test-Username")).sendKeys("standard_user");
81 | driver.findElement(AppiumBy.accessibilityId("test-Password")).sendKeys("secret_sauce");
82 | driver.findElement(AppiumBy.accessibilityId("test-LOGIN")).click();
83 |
84 | // Verify
85 | // xpath //android.widget.ScrollView[@content-desc="test-PRODUCTS"]
86 | Assert.assertTrue(isOnProductsPage());
87 |
88 | }
89 |
90 | @AfterMethod
91 | public void teardown(ITestResult result) {
92 | System.out.println("*** AfterMethod hook ***");
93 | try {
94 | ((JavascriptExecutor) driver).executeScript("sauce:job-result=" + (result.isSuccess() ? "passed" : "failed"));
95 | } finally {
96 | driver.quit();
97 | }
98 | }
99 |
100 | public boolean isOnProductsPage() {
101 |
102 | //Create an instance of an explicit wait so that we can dynamically wait for an element
103 | WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
104 |
105 | //wait for the product field to be visible and store that element into a variable
106 | try {
107 | wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//XCUIElementTypeStaticText[@name=\"PRODUCTS\"]")));
108 | } catch (TimeoutException e){
109 | return false;
110 | }
111 | return true;
112 | }
113 |
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/src/test/java/com/demo/exercises/ex1_CreateAndroidDriver.java:
--------------------------------------------------------------------------------
1 | package com.demo.exercises;
2 |
3 | import io.appium.java_client.android.AndroidDriver;
4 | import org.openqa.selenium.MutableCapabilities;
5 | import org.testng.ITestResult;
6 | import org.testng.annotations.AfterMethod;
7 | import org.testng.annotations.BeforeMethod;
8 | import org.testng.annotations.Test;
9 |
10 | import java.lang.reflect.Method;
11 | import java.net.MalformedURLException;
12 | import java.net.URL;
13 |
14 | import static com.demo.Config.region;
15 |
16 | public class ex1_CreateAndroidDriver {
17 |
18 | private String SAUCE_EU_URL = "https://ondemand.eu-central-1.saucelabs.com/wd/hub";
19 | private String SAUCE_US_URL = "https://ondemand.us-west-1.saucelabs.com/wd/hub";
20 |
21 | // (1) define a driver
22 | //private AndroidDriver driver;
23 |
24 | @BeforeMethod
25 | public void setup(Method method) throws MalformedURLException {
26 |
27 | String methodName = method.getName();
28 | System.out.println("*** BeforeMethod hook. Running method " + methodName + " ***");
29 | URL url;
30 |
31 | switch (region) {
32 | case "us":
33 | System.out.println("region is us");
34 | url = new URL(SAUCE_US_URL);
35 | break;
36 | case "eu":
37 | default:
38 | System.out.println("region is eu");
39 | url = new URL(SAUCE_EU_URL);
40 | break;
41 | }
42 |
43 | // (2) create the needed capabilities
44 | // MutableCapabilities capabilities = new MutableCapabilities();
45 | // MutableCapabilities sauceOptions = new MutableCapabilities();
46 |
47 | // Capabilities: platformName , automationName, appium:deviceName , appium:platformVersion
48 | // Values: Samsung.* , UiAutomator2, 12, android
49 |
50 |
51 | // capabilities.setCapability("appium:app",
52 | // "https://github.com/saucelabs/sample-app-mobile/releases/download/2.7.1/Android.SauceLabs.Mobile.Sample.app.2.7.1.apk");
53 | // capabilities.setCapability("appium:appWaitActivity","com.swaglabsmobileapp.MainActivity");
54 |
55 | // Sauce capabilities
56 | // sauceOptions.setCapability("name", methodName);
57 | // sauceOptions.setCapability("username", System.getenv("SAUCE_USERNAME"));
58 | // sauceOptions.setCapability("accessKey", System.getenv("SAUCE_ACCESS_KEY"));
59 | // capabilities.setCapability("sauce:options", sauceOptions);
60 | //
61 | // driver = new AndroidDriver(url, capabilities);
62 |
63 | }
64 |
65 | @Test
66 | public void demoTest() {
67 | System.out.println("*** Start demoTest test ***");
68 | }
69 |
70 | @AfterMethod
71 | public void teardown(ITestResult result) {
72 | System.out.println("*** AfterMethod hook ***");
73 |
74 | // (3) quit the driver
75 | // driver.quit();
76 | }
77 |
78 | // (4) in resources -> config -> myDemoTests.xml point to this class
79 | //
80 |
81 | // (5) In the terminal run the cmd : "mvn clean test"
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/src/test/java/com/demo/exercises/ex2_DevelopAndroidTest.java:
--------------------------------------------------------------------------------
1 | package com.demo.exercises;
2 |
3 | import io.appium.java_client.AppiumBy;
4 | import io.appium.java_client.android.AndroidDriver;
5 | import org.openqa.selenium.By;
6 | import org.openqa.selenium.JavascriptExecutor;
7 | import org.openqa.selenium.MutableCapabilities;
8 | import org.openqa.selenium.TimeoutException;
9 | import org.openqa.selenium.support.ui.ExpectedConditions;
10 | import org.openqa.selenium.support.ui.WebDriverWait;
11 | import org.testng.Assert;
12 | import org.testng.ITestResult;
13 | import org.testng.annotations.AfterMethod;
14 | import org.testng.annotations.BeforeMethod;
15 | import org.testng.annotations.Test;
16 |
17 | import java.lang.reflect.Method;
18 | import java.net.MalformedURLException;
19 | import java.net.URL;
20 | import java.time.Duration;
21 |
22 | import static com.demo.Config.region;
23 |
24 | public class ex2_DevelopAndroidTest {
25 |
26 | private String SAUCE_EU_URL = "https://ondemand.eu-central-1.saucelabs.com/wd/hub";
27 | private String SAUCE_US_URL = "https://ondemand.us-west-1.saucelabs.com/wd/hub";
28 |
29 | private AndroidDriver driver;
30 |
31 | @BeforeMethod
32 | public void setup(Method method) throws MalformedURLException {
33 |
34 | String methodName = method.getName();
35 | System.out.println("*** BeforeMethod hook. Running method " + methodName + " ***");
36 | URL url;
37 |
38 | switch (region) {
39 | case "us":
40 | System.out.println("region is us");
41 | url = new URL(SAUCE_US_URL);
42 | break;
43 | case "eu":
44 | default:
45 | System.out.println("region is eu");
46 | url = new URL(SAUCE_EU_URL);
47 | break;
48 | }
49 |
50 | MutableCapabilities capabilities = new MutableCapabilities();
51 | MutableCapabilities sauceOptions = new MutableCapabilities();
52 |
53 | //find a device in the cloud
54 | capabilities.setCapability("platformName", "android");
55 | capabilities.setCapability("automationName", "UiAutomator2");
56 | capabilities.setCapability("appium:deviceName", "Samsung.*");
57 | capabilities.setCapability("appium:platformVersion", "12");
58 |
59 | capabilities.setCapability("appium:app",
60 | "https://github.com/saucelabs/sample-app-mobile/releases/download/2.7.1/Android.SauceLabs.Mobile.Sample.app.2.7.1.apk");
61 | capabilities.setCapability("appium:appWaitActivity","com.swaglabsmobileapp.MainActivity");
62 |
63 | // Sauce capabilities
64 | sauceOptions.setCapability("name", methodName);
65 | sauceOptions.setCapability("username", System.getenv("SAUCE_USERNAME"));
66 | sauceOptions.setCapability("accessKey", System.getenv("SAUCE_ACCESS_KEY"));
67 | capabilities.setCapability("sauce:options", sauceOptions);
68 |
69 | driver = new AndroidDriver(url, capabilities);
70 |
71 | }
72 |
73 | @Test
74 | public void demoTest() {
75 | System.out.println("*** Start demoTest test ***");
76 |
77 | // (1) Add code to set username and password, click on the login button
78 | // Login
79 | // Accessibility-id test-Username sendKeys standard_user
80 | // Accessibility-id test-Password sendKeys secret_sauce
81 | // Accessibility-id test-LOGIN click
82 | driver.findElement(AppiumBy.accessibilityId("test-Username")).sendKeys("standard_user");
83 |
84 | // (2) add code to verify we are on the next page
85 | // Verify using Assert.assertTrue
86 | // xpath -> //android.widget.ScrollView[@content-desc="test-PRODUCTS"]
87 | //Assert.assertTrue(isOnProductsPage());
88 |
89 | }
90 |
91 | @AfterMethod
92 | public void teardown(ITestResult result) {
93 | System.out.println("*** AfterMethod hook ***");
94 | // (3) Add code to check if test passed or failed
95 | try {
96 | // ((JavascriptExecutor) driver).executeScript("sauce:job-result=" + (result.isSuccess() ? "passed" : "failed"));
97 | } finally {
98 | driver.quit();
99 | }
100 | }
101 |
102 | // public boolean isOnProductsPage() {
103 | //
104 | // //Create an instance of an explicit wait so that we can dynamically wait for an element
105 | // WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
106 | //
107 | // //wait for the product field to be visible and store that element into a variable
108 | // try {
109 | // wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//android.widget.ScrollView[@content-desc=\"test-PRODUCTS\"]")));
110 | // } catch (TimeoutException e){
111 | // return false;
112 | // }
113 | // return true;
114 | // }
115 |
116 | // (4) in resources -> config -> myDemoTests.xml point to this class
117 | //
118 |
119 | // (5) In the terminal run the cmd : "mvn clean test"
120 | }
121 |
--------------------------------------------------------------------------------
/src/test/java/com/demo/exercises/ex3_CreateIOSDriver.java:
--------------------------------------------------------------------------------
1 | package com.demo.exercises;
2 |
3 | import io.appium.java_client.ios.IOSDriver;
4 | import org.openqa.selenium.MutableCapabilities;
5 | import org.testng.ITestResult;
6 | import org.testng.annotations.AfterMethod;
7 | import org.testng.annotations.BeforeMethod;
8 | import org.testng.annotations.Test;
9 |
10 | import java.lang.reflect.Method;
11 | import java.net.MalformedURLException;
12 | import java.net.URL;
13 |
14 | import static com.demo.Config.region;
15 |
16 | public class ex3_CreateIOSDriver {
17 |
18 | private String SAUCE_EU_URL = "https://ondemand.eu-central-1.saucelabs.com/wd/hub";
19 | private String SAUCE_US_URL = "https://ondemand.us-west-1.saucelabs.com/wd/hub";
20 |
21 | // (1) define a driver
22 | // private IOSDriver driver;
23 |
24 | @BeforeMethod
25 | public void setup(Method method) throws MalformedURLException {
26 |
27 | String methodName = method.getName();
28 | System.out.println("*** BeforeMethod hook. Running method " + methodName + " ***");
29 | URL url;
30 |
31 | switch (region) {
32 | case "us":
33 | System.out.println("region is us");
34 | url = new URL(SAUCE_US_URL);
35 | break;
36 | case "eu":
37 | default:
38 | System.out.println("region is eu");
39 | url = new URL(SAUCE_EU_URL);
40 | break;
41 | }
42 |
43 | // (2) create the needed capabilities
44 | // MutableCapabilities capabilities = new MutableCapabilities();
45 | // MutableCapabilities sauceOptions = new MutableCapabilities();
46 |
47 | // Capabilities: platformName , automationName, appium:deviceName , appium:platformVersion
48 | // Values: iPhone.* , XCuiTest, 14, iOS
49 |
50 | // capabilities.setCapability("appium:app",
51 | // "https://github.com/saucelabs/sample-app-mobile/releases/download/2.7.1/iOS.RealDevice.SauceLabs.Mobile.Sample.app.2.7.1.ipa");
52 |
53 | // Sauce capabilities
54 | // sauceOptions.setCapability("name", methodName);
55 | // sauceOptions.setCapability("username", System.getenv("SAUCE_USERNAME"));
56 | // sauceOptions.setCapability("accessKey", System.getenv("SAUCE_ACCESS_KEY"));
57 | // capabilities.setCapability("sauce:options", sauceOptions);
58 |
59 | // driver = new IOSDriver(url, capabilities);
60 |
61 | }
62 |
63 | @Test
64 | public void demoTest() {
65 | System.out.println("*** Start demoTest test ***");
66 | }
67 |
68 | @AfterMethod
69 | public void teardown(ITestResult result) {
70 | System.out.println("*** AfterMethod hook ***");
71 | // (3) quit the driver
72 | // driver.quit();
73 | }
74 |
75 | // (4) in resources -> config -> myDemoTests.xml point to this class
76 | //
77 |
78 | // (5) In the terminal run the cmd : "mvn clean test"
79 |
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/test/java/com/demo/exercises/ex4_DevelopIOSTest.java:
--------------------------------------------------------------------------------
1 | package com.demo.exercises;
2 |
3 | import io.appium.java_client.AppiumBy;
4 | import io.appium.java_client.ios.IOSDriver;
5 | import org.openqa.selenium.By;
6 | import org.openqa.selenium.JavascriptExecutor;
7 | import org.openqa.selenium.MutableCapabilities;
8 | import org.openqa.selenium.TimeoutException;
9 | import org.openqa.selenium.support.ui.ExpectedConditions;
10 | import org.openqa.selenium.support.ui.WebDriverWait;
11 | import org.testng.Assert;
12 | import org.testng.ITestResult;
13 | import org.testng.annotations.AfterMethod;
14 | import org.testng.annotations.BeforeMethod;
15 | import org.testng.annotations.Test;
16 |
17 | import java.lang.reflect.Method;
18 | import java.net.MalformedURLException;
19 | import java.net.URL;
20 | import java.time.Duration;
21 |
22 | import static com.demo.Config.region;
23 |
24 | public class ex4_DevelopIOSTest {
25 |
26 | private String SAUCE_EU_URL = "https://ondemand.eu-central-1.saucelabs.com/wd/hub";
27 | private String SAUCE_US_URL = "https://ondemand.us-west-1.saucelabs.com/wd/hub";
28 |
29 | private IOSDriver driver;
30 |
31 | @BeforeMethod
32 | public void setup(Method method) throws MalformedURLException {
33 |
34 | String methodName = method.getName();
35 | System.out.println("*** BeforeMethod hook. Running method " + methodName + " ***");
36 | URL url;
37 |
38 | switch (region) {
39 | case "us":
40 | System.out.println("region is us");
41 | url = new URL(SAUCE_US_URL);
42 | break;
43 | case "eu":
44 | default:
45 | System.out.println("region is eu");
46 | url = new URL(SAUCE_EU_URL);
47 | break;
48 | }
49 |
50 | MutableCapabilities capabilities = new MutableCapabilities();
51 | MutableCapabilities sauceOptions = new MutableCapabilities();
52 |
53 | //find a device in the cloud
54 | capabilities.setCapability("platformName", "iOS");
55 | capabilities.setCapability("automationName", "XCuiTest");
56 | capabilities.setCapability("appium:deviceName", "iPhone.*");
57 | capabilities.setCapability("appium:platformVersion", "14");
58 |
59 | capabilities.setCapability("appium:app",
60 | "https://github.com/saucelabs/sample-app-mobile/releases/download/2.7.1/iOS.RealDevice.SauceLabs.Mobile.Sample.app.2.7.1.ipa");
61 |
62 | // Sauce capabilities
63 | sauceOptions.setCapability("name", methodName);
64 | sauceOptions.setCapability("username", System.getenv("SAUCE_USERNAME"));
65 | sauceOptions.setCapability("accessKey", System.getenv("SAUCE_ACCESS_KEY"));
66 | capabilities.setCapability("sauce:options", sauceOptions);
67 |
68 | driver = new IOSDriver(url, capabilities);
69 |
70 | }
71 |
72 | @Test
73 | public void demoTest() {
74 | System.out.println("*** Start demoTest test ***");
75 |
76 | // (1) Add code to set username and password, click on the login button
77 | // Login
78 | // Accessibility-id test-Username sendKeys standard_user
79 | // Accessibility-id test-Password sendKeys secret_sauce
80 | // Accessibility-id test-LOGIN click
81 | driver.findElement(AppiumBy.accessibilityId("test-Username")).sendKeys("standard_user");
82 |
83 | // (2) add code to verify we are on the next page
84 | // Verify using Assert.assertTrue
85 | // xpath -> //XCUIElementTypeStaticText[@name="PRODUCTS"]
86 | //Assert.assertTrue(isOnProductsPage());
87 |
88 | }
89 |
90 | @AfterMethod
91 | public void teardown(ITestResult result) {
92 | System.out.println("*** AfterMethod hook ***");
93 | // (3) Add code to check if test passed or failed
94 | try {
95 | // ((JavascriptExecutor) driver).executeScript("sauce:job-result=" + (result.isSuccess() ? "passed" : "failed"));
96 | } finally {
97 | driver.quit();
98 | }
99 | }
100 |
101 | // public boolean isOnProductsPage() {
102 | //
103 | // //Create an instance of an explicit wait so that we can dynamically wait for an element
104 | // WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
105 | //
106 | // //wait for the product field to be visible and store that element into a variable
107 | // try {
108 | // wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//XCUIElementTypeStaticText[@name=\"PRODUCTS\"]")));
109 | // } catch (TimeoutException e){
110 | // return false;
111 | // }
112 | // return true;
113 | // }
114 |
115 | // (4) in resources -> config -> myDemoTests.xml point to this class
116 | //
117 |
118 | // (5) In the terminal run the cmd : "mvn clean test"
119 |
120 |
121 | }
122 |
--------------------------------------------------------------------------------
/src/test/resources/config/myDemoParallelTests.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/test/resources/config/myDemoTests.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------