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  package dev.aherscu.qa.jgiven.webdriver.steps;
17  
18  import static dev.aherscu.qa.jgiven.commons.utils.WebDriverEx.*;
19  import static dev.aherscu.qa.testing.utils.UrlUtils.*;
20  import static java.util.Objects.*;
21  
22  import java.lang.SuppressWarnings;
23  import java.util.function.*;
24  
25  import javax.annotation.concurrent.*;
26  
27  import org.openqa.selenium.*;
28  
29  import com.tngtech.jgiven.annotation.*;
30  import com.tngtech.jgiven.attachment.*;
31  
32  import dev.aherscu.qa.jgiven.commons.steps.*;
33  import dev.aherscu.qa.jgiven.commons.utils.*;
34  import dev.aherscu.qa.jgiven.webdriver.formatters.*;
35  import dev.aherscu.qa.jgiven.webdriver.model.*;
36  import edu.umd.cs.findbugs.annotations.*;
37  import lombok.*;
38  import lombok.extern.slf4j.*;
39  
40  /**
41   * Generic web driver fixtures. If WebDriver's capabilities contain
42   * {@link WebDriverFixtures#AUTO_QUIT}, then closes the WebDriver by end of
43   * scenario.
44   *
45   * @param <SELF>
46   *            the type of the subclass
47   * @author aherscu
48   */
49  @ThreadSafe
50  @Slf4j
51  public class WebDriverFixtures<SELF extends WebDriverFixtures<SELF>>
52      extends GenericFixtures<WebDriverScenarioType, SELF> {
53  
54      /**
55       * Add to capabilities in order to automatically quit the web driver by end
56       * of scenario; value does not matter.
57       */
58      public static final String               AUTO_QUIT = "autoQuit";
59  
60      /**
61       * The given Web Driver.
62       */
63      @ProvidedScenarioState
64      protected final ThreadLocal<WebDriverEx> webDriver =
65          new ThreadLocal<>();
66  
67      /**
68       * Sets a web driver for this scenario. If it has has a capability named
69       * {@link WebDriverFixtures#AUTO_QUIT}, then it will automatically quit
70       * after scenario.
71       *
72       * @param webDriver
73       *            the driver
74       * @return {@link #self()}
75       * @throws NullPointerException
76       *             if the driver reference was null
77       */
78      @NestedSteps
79      public SELF a_web_driver(
80          @SuppressWarnings("hiding") @WebDriverFormatter.Annotation final WebDriverEx webDriver) {
81          log.debug("setting web driver {}", webDriver);
82          // TODO pretty print
83          currentStep
84              .addAttachment(Attachment.plainText(webDriver.originalCapabilities
85                  .asMap()
86                  .toString()));
87          this.webDriver.set(requireNonNull(webDriver,
88              "must provide a web driver"));
89          return self();
90      }
91  
92      /**
93       * Opens Web application at specified host if not already open.
94       *
95       * @param applicationUrl
96       *            the location of the application
97       * @return {@link #self()}
98       */
99      public SELF at(final String applicationUrl) {
100         val currentUrl = thisWebDriver().asGeneric().getCurrentUrl();
101 
102         log.trace("currently at {} asking for {}",
103             currentUrl, applicationUrl);
104 
105         // NOTE: browsers opened by Selenium may point to an invalid URL
106         // e.g. Chrome on SauceLabs points at data: which is not a valid URL
107         if (isUrl(currentUrl)
108             && hostOf(currentUrl)
109                 .equals(hostOf(applicationUrl))) {
110             log.debug("already at {}", applicationUrl);
111         } else {
112             log.debug("opening {}", applicationUrl);
113             thisWebDriver().asGeneric().get(applicationUrl);
114         }
115         return self();
116     }
117 
118     /**
119      * Switches to specified Appium context.
120      *
121      * @param byRule
122      *            naming rule of Appium context
123      * @return {@link #self()}
124      * @throws NoSuchContextException
125      *             if no such context exists
126      */
127     @Hidden
128     protected SELF context(final Predicate<String> byRule) {
129         return context(byRule, (ContextAware) thisWebDriver());
130     }
131 
132     /**
133      * Scrolls specified element into view.
134      *
135      * <p>
136      * {@code scrollIntoView} metric will be updated.
137      * </p>
138      *
139      * @param element
140      *            the element to scroll into view
141      * @return the element
142      */
143     @Override
144     protected WebElement scrollIntoView(final WebElement element) {
145         try (val scrollIntoViewTimerContext = scrollIntoViewTimer.time()) {
146             log.debug("scrolling to {}", descriptionOf(element));
147             thisWebDriver().scrollIntoView(element);
148         }
149         return element;
150     }
151 
152     protected final WebDriverEx thisWebDriver() {
153         return requireNonNull(webDriver.get(), "web driver not initialized");
154     }
155 
156     @SuppressFBWarnings(
157         value = "UPM_UNCALLED_PRIVATE_METHOD",
158         justification = "called by testng framework")
159     @AfterScenario
160     private void afterScenarioQuitWebDriver() {
161         if (nonNull(thisWebDriver().originalCapabilities
162             .getCapability(AUTO_QUIT))) {
163             log.debug("automatically quitting after scenario");
164             thisWebDriver().safelyQuit();
165         }
166     }
167 }