View Javadoc
1   /*
2    * Copyright 2023 Adrian Herscu
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  package dev.aherscu.qa.testing.example.scenarios.tutorial4;
18  
19  import static dev.aherscu.qa.testing.example.scenarios.tutorial4.TestingAndroidOnSauceLabs.*;
20  import static java.lang.Thread.*;
21  import static java.util.concurrent.TimeUnit.*;
22  import static org.hamcrest.MatcherAssert.*;
23  import static org.hamcrest.Matchers.*;
24  
25  import java.lang.SuppressWarnings;
26  
27  import org.openqa.selenium.*;
28  import org.testng.annotations.*;
29  
30  import com.tngtech.jgiven.*;
31  import com.tngtech.jgiven.annotation.*;
32  import com.tngtech.jgiven.testng.*;
33  
34  import edu.umd.cs.findbugs.annotations.*;
35  import io.appium.java_client.*;
36  import lombok.*;
37  import lombok.extern.slf4j.*;
38  
39  /**
40   * TODO should run against one of the Android applications, e.g. Calculator
41   */
42  @As("Testing with JGiven")
43  @Slf4j
44  public class TestingAndroidWithJGiven extends
45      ScenarioTest<TestingAndroidWithJGiven.Fixtures, TestingAndroidWithJGiven.Actions, TestingAndroidWithJGiven.Verifications> {
46  
47      private final ThreadLocal<WebDriver> webDriver = new ThreadLocal<>();
48  
49      @DataProvider(parallel = true)
50      static Object[][] environmentLabels() {
51          return new Object[][] {
52              { new Label("TBD") },
53              { new Label("TBD") },
54              { new Label("TBD") },
55              { new Label("TBD") },
56              { new Label("TBD") },
57              { new Label("TBD") },
58              { new Label("TBD") },
59              { new Label("TBD") }
60          };
61      }
62  
63      @Test(dataProvider = "environmentLabels")
64      public void shouldAllowLogin(final Label label) {
65          given()
66              .application_installed(webDriver.get());
67  
68          then()
69              .valid_email_required();
70  
71          when()
72              .entering_environment(label)
73              .and().hiding_keyboard();
74  
75          then()
76              .can_tap_to_login();
77      }
78  
79      @SuppressFBWarnings(
80          value = "UPM_UNCALLED_PRIVATE_METHOD",
81          justification = "called by testng framework")
82      @AfterMethod(alwaysRun = true) // important, otherwise we may leak resources
83      private void afterMethodCloseWebDriver() {
84          log.debug("quitting");
85          webDriver.get().quit();
86      }
87  
88      @SuppressFBWarnings(
89          value = "UPM_UNCALLED_PRIVATE_METHOD",
90          justification = "called by testng framework")
91      @BeforeMethod
92      @SneakyThrows
93      private void beforeMethodOpenWebDriver() {
94          // NOTE: ensure you have app.apk uploaded to SauceLabs
95          // curl -u "TBD:TBD" -X POST
96          // https://saucelabs.com/rest/v1/storage/TBD --data-binary
97          // @TBD.apk
98          log.debug("opening web driver");
99          webDriver.set(saucelabsApp(getClass().getSimpleName()
100             + "#" + currentThread().getId()));
101         webDriver.get().manage().timeouts().implicitlyWait(5, SECONDS);
102     }
103 
104     static class Actions extends Stage<Actions> {
105         @ScenarioState
106         private final ThreadLocal<WebDriver> webDriver = new ThreadLocal<>();
107 
108         Actions entering_environment(final Label label) {
109             log.debug("entering environment {}", label);
110             webDriver.get().findElement(By.xpath("//input"))
111                 .sendKeys(label.value);
112 
113             return self();
114         }
115 
116         Actions hiding_keyboard() {
117             log.debug("hiding keyboard");
118             ((HidesKeyboard) webDriver.get()).hideKeyboard();
119             return self();
120         }
121     }
122 
123     static class Fixtures extends Stage<Fixtures> {
124         @ScenarioState
125         private final ThreadLocal<WebDriver> webDriver = new ThreadLocal<>();
126 
127         Fixtures application_installed(
128             @SuppressWarnings("hiding") @Hidden final WebDriver webDriver) {
129             log.debug("application installed {}", webDriver);
130             this.webDriver.set(webDriver);
131             return self();
132         }
133     }
134 
135     @AllArgsConstructor
136     static class Label {
137         final String value;
138 
139         @Override
140         public String toString() {
141             return value;
142         }
143     }
144 
145     static class Verifications extends Stage<Verifications> {
146         @ScenarioState
147         private final ThreadLocal<WebDriver> webDriver = new ThreadLocal<>();
148 
149         Verifications can_tap_to_login() {
150             log.debug("can tap to login");
151             assertThat(webDriver.get()
152                 .findElement(By.xpath("//*[text()='Tap anywhere to Login']"))
153                 .isDisplayed(),
154                 is(true));
155 
156             return self();
157         }
158 
159         Verifications valid_email_required() {
160             log.debug("valid email required");
161             assertThat(webDriver.get()
162                 .findElements(By
163                     .xpath("//*[text()='Please enter valid email']")),
164                 not(empty()));
165             return self();
166         }
167     }
168 }