View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
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    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.junit;
16  
17  import static org.junit.Assert.assertArrayEquals;
18  import static org.junit.Assert.assertTrue;
19  import static org.junit.Assert.fail;
20  
21  import java.lang.reflect.Method;
22  import java.util.List;
23  
24  import org.apache.commons.lang3.SystemUtils;
25  import org.htmlunit.BrowserVersion;
26  import org.htmlunit.WebDriverTestCase;
27  import org.htmlunit.WebTestCase;
28  import org.htmlunit.junit.annotation.Alerts;
29  import org.htmlunit.junit.annotation.BuggyWebDriver;
30  import org.htmlunit.junit.annotation.HtmlUnitNYI;
31  import org.htmlunit.junit.annotation.NotYetImplemented;
32  import org.htmlunit.junit.annotation.OS;
33  import org.htmlunit.junit.annotation.TestedBrowser;
34  import org.htmlunit.junit.annotation.Tries;
35  import org.junit.Test;
36  import org.junit.internal.runners.model.ReflectiveCallable;
37  import org.junit.internal.runners.statements.Fail;
38  import org.junit.rules.RunRules;
39  import org.junit.rules.TestRule;
40  import org.junit.runners.BlockJUnit4ClassRunner;
41  import org.junit.runners.model.FrameworkMethod;
42  import org.junit.runners.model.InitializationError;
43  import org.junit.runners.model.Statement;
44  
45  /**
46   * The runner for test methods that run with a specific browser ({@link TestedBrowser}).
47   *
48   * @author Ahmed Ashour
49   * @author Frank Danek
50   * @author Ronald Brill
51   * @author cd alexndr
52   */
53  public class BrowserVersionClassRunner extends BlockJUnit4ClassRunner {
54  
55      /** If no alerts defined. */
56      public static final String[] NO_ALERTS_DEFINED = {"no alerts defined"};
57  
58      private final BrowserVersion browserVersion_;
59      private final boolean realBrowser_;
60      static final boolean MAVEN = System.getProperty("htmlunit.maven") != null;
61  
62      /**
63       * Constructs a new instance.
64       * @param klass the class
65       * @param browserVersion the browser version
66       * @param realBrowser use real browser or not
67       * @throws InitializationError if an error occurs
68       */
69      public BrowserVersionClassRunner(final Class<WebTestCase> klass,
70          final BrowserVersion browserVersion, final boolean realBrowser) throws InitializationError {
71          super(klass);
72          browserVersion_ = browserVersion;
73          realBrowser_ = realBrowser;
74      }
75  
76      private void setAlerts(final WebTestCase testCase, final Method method) {
77          final Alerts alerts = method.getAnnotation(Alerts.class);
78          String[] expectedAlerts = {};
79          if (alerts != null) {
80              expectedAlerts = NO_ALERTS_DEFINED;
81              if (isDefined(alerts.value())) {
82                  expectedAlerts = alerts.value();
83              }
84              else {
85                  if (browserVersion_ == BrowserVersion.EDGE) {
86                      expectedAlerts = firstDefined(alerts.EDGE(), alerts.DEFAULT());
87                  }
88                  else if (browserVersion_ == BrowserVersion.FIREFOX_ESR) {
89                      expectedAlerts = firstDefined(alerts.FF_ESR(), alerts.DEFAULT());
90                  }
91                  else if (browserVersion_ == BrowserVersion.FIREFOX) {
92                      expectedAlerts = firstDefined(alerts.FF(), alerts.DEFAULT());
93                  }
94                  else if (browserVersion_ == BrowserVersion.CHROME) {
95                      expectedAlerts = firstDefined(alerts.CHROME(), alerts.DEFAULT());
96                  }
97              }
98          }
99  
100         if (isRealBrowser()) {
101             final BuggyWebDriver buggyWebDriver = method.getAnnotation(BuggyWebDriver.class);
102             if (buggyWebDriver != null) {
103                 if (isDefined(buggyWebDriver.value())) {
104                     expectedAlerts = buggyWebDriver.value();
105                 }
106                 else {
107                     if (browserVersion_ == BrowserVersion.EDGE) {
108                         expectedAlerts = firstDefinedOrGiven(expectedAlerts,
109                                             buggyWebDriver.EDGE(), buggyWebDriver.DEFAULT());
110                     }
111                     else if (browserVersion_ == BrowserVersion.FIREFOX_ESR) {
112                         expectedAlerts = firstDefinedOrGiven(expectedAlerts,
113                                             buggyWebDriver.FF_ESR(), buggyWebDriver.DEFAULT());
114                     }
115                     else if (browserVersion_ == BrowserVersion.FIREFOX) {
116                         expectedAlerts = firstDefinedOrGiven(expectedAlerts,
117                                             buggyWebDriver.FF(), buggyWebDriver.DEFAULT());
118                     }
119                     else if (browserVersion_ == BrowserVersion.CHROME) {
120                         expectedAlerts = firstDefinedOrGiven(expectedAlerts,
121                                             buggyWebDriver.CHROME(), buggyWebDriver.DEFAULT());
122                     }
123                 }
124             }
125         }
126         else {
127             final HtmlUnitNYI htmlUnitNYI = method.getAnnotation(HtmlUnitNYI.class);
128             if (htmlUnitNYI != null) {
129                 if (browserVersion_ == BrowserVersion.EDGE) {
130                     expectedAlerts = firstDefinedOrGiven(expectedAlerts, htmlUnitNYI.EDGE());
131                 }
132                 else if (browserVersion_ == BrowserVersion.FIREFOX_ESR) {
133                     expectedAlerts = firstDefinedOrGiven(expectedAlerts, htmlUnitNYI.FF_ESR());
134                 }
135                 else if (browserVersion_ == BrowserVersion.FIREFOX) {
136                     expectedAlerts = firstDefinedOrGiven(expectedAlerts, htmlUnitNYI.FF());
137                 }
138                 else if (browserVersion_ == BrowserVersion.CHROME) {
139                     expectedAlerts = firstDefinedOrGiven(expectedAlerts, htmlUnitNYI.CHROME());
140                 }
141             }
142         }
143 
144         testCase.setExpectedAlerts(expectedAlerts);
145     }
146 
147     private static String[] firstDefined(final String[]... variants) {
148         for (final String[] var : variants) {
149             if (isDefined(var)) {
150                 return var;
151             }
152         }
153         return NO_ALERTS_DEFINED;
154     }
155 
156     /**
157      * @param given the default
158      * @param variants variants
159      * @return a string array containing the first defined value or the provided one
160      */
161     public static String[] firstDefinedOrGiven(final String[] given, final String[]... variants) {
162         for (final String[] var : variants) {
163             if (isDefined(var)) {
164                 try {
165                     assertArrayEquals(var, given);
166                     fail("BuggyWebDriver duplicates expectations");
167                 }
168                 catch (final AssertionError e) {
169                     // ok
170                 }
171                 return var;
172             }
173         }
174         return given;
175     }
176 
177     /**
178      * {@inheritDoc}
179      */
180     @Override
181     protected Object createTest() throws Exception {
182         final Object test = super.createTest();
183         assertTrue("Test class must inherit WebTestCase", test instanceof WebTestCase);
184         final WebTestCase object = (WebTestCase) test;
185         object.setBrowserVersion(browserVersion_);
186         if (test instanceof WebDriverTestCase) {
187             ((WebDriverTestCase) test).setUseRealBrowser(realBrowser_);
188         }
189         return object;
190     }
191 
192     @Override
193     protected String getName() {
194         String browserString = browserVersion_.getNickname();
195         if (realBrowser_) {
196             browserString = "Real " + browserString;
197         }
198         return String.format("[%s]", browserString);
199     }
200 
201     @Override
202     protected String testName(final FrameworkMethod method) {
203         String prefix = "";
204         if (isNotYetImplemented(method) && !realBrowser_) {
205             prefix = "(NYI) ";
206         }
207 
208         String browserString = browserVersion_.getNickname();
209         if (realBrowser_) {
210             browserString = "Real " + browserString;
211         }
212         if (!MAVEN) {
213             return String.format("%s [%s]", method.getName(), browserString);
214         }
215         String className = method.getMethod().getDeclaringClass().getName();
216         className = className.substring(className.lastIndexOf('.') + 1);
217         return String.format("%s%s [%s]", prefix, className + '.' + method.getName(), browserString);
218     }
219 
220     /**
221      * Does the test class contains test methods.
222      *
223      * @param klass the class
224      * @return whether it contains test methods or not
225      */
226     public static boolean containsTestMethods(final Class<WebTestCase> klass) {
227         for (final Method method : klass.getMethods()) {
228             if (method.getAnnotation(Test.class) != null) {
229                 return true;
230             }
231         }
232         return false;
233     }
234 
235     /**
236      * @param alerts the alerst to check
237      * @return true if there is exactly one alert defined
238      */
239     public static boolean isDefined(final String[] alerts) {
240         return alerts.length != 1 || !alerts[0].equals(BrowserRunner.EMPTY_DEFAULT);
241     }
242 
243     /**
244      * Returns true if current operating system is contained in the specific <tt>oses</tt>.
245      */
246     private boolean isDefinedIn(final OS[] oses) {
247         for (final OS os : oses) {
248             switch (os) {
249                 case Linux:
250                     return SystemUtils.IS_OS_LINUX;
251                 case Windows:
252                     return SystemUtils.IS_OS_WINDOWS;
253                 default:
254                     break;
255             }
256         }
257         return false;
258     }
259 
260     /**
261      * Returns true if current {@link #browserVersion_} is contained in the specific <tt>browsers</tt>.
262      */
263     private boolean isDefinedIn(final TestedBrowser[] browsers) {
264         for (final TestedBrowser browser : browsers) {
265             switch (browser) {
266                 case EDGE:
267                     if (browserVersion_ == BrowserVersion.EDGE) {
268                         return true;
269                     }
270                     break;
271 
272                 case FF_ESR:
273                     if (browserVersion_ == BrowserVersion.FIREFOX_ESR) {
274                         return true;
275                     }
276                     break;
277 
278                 case FF:
279                     if (browserVersion_ == BrowserVersion.FIREFOX) {
280                         return true;
281                     }
282                     break;
283 
284                 case CHROME:
285                     if (browserVersion_ == BrowserVersion.CHROME) {
286                         return true;
287                     }
288                     break;
289 
290                 default:
291             }
292         }
293         return false;
294     }
295 
296     @Override
297     @SuppressWarnings("deprecation")
298     protected Statement methodBlock(final FrameworkMethod method) {
299         final Object test;
300         final WebTestCase testCase;
301         try {
302             testCase = (WebTestCase) createTest();
303             test = new ReflectiveCallable() {
304                 @Override
305                 protected Object runReflectiveCall() throws Throwable {
306                     return testCase;
307                 }
308             } .run();
309         }
310         catch (final Throwable e) {
311             return new Fail(e);
312         }
313 
314         Statement statement = methodInvoker(method, test);
315         statement = possiblyExpectingExceptions(method, test, statement);
316         statement = withPotentialTimeout(method, test, statement);
317         statement = withBefores(method, test, statement);
318         statement = withAfters(method, test, statement);
319         statement = withRules(method, test, statement);
320         statement = withInterruptIsolation(statement);
321 
322         //  End of copy & paste from super.methodBlock()  //
323 
324         boolean notYetImplemented = false;
325         final int tries;
326 
327         if (testCase instanceof WebDriverTestCase && realBrowser_) {
328             tries = 1;
329         }
330         else {
331             notYetImplemented = isNotYetImplemented(method);
332             tries = getTries(method);
333         }
334         setAlerts(testCase, method.getMethod());
335         statement = new BrowserStatement(statement, method, realBrowser_,
336                 notYetImplemented, tries, browserVersion_);
337         return statement;
338     }
339 
340     private Statement withRules(final FrameworkMethod method, final Object target, final Statement statement) {
341         Statement result = statement;
342         result = withMethodRules(method, target, result);
343         result = withTestRules(method, target, result);
344         return result;
345     }
346 
347     private Statement withMethodRules(final FrameworkMethod method, final Object target, Statement result) {
348         final List<TestRule> testRules = getTestRules(target);
349         for (final org.junit.rules.MethodRule each : getMethodRules(target)) {
350             if (!testRules.contains(each)) {
351                 result = each.apply(result, method, target);
352             }
353         }
354         return result;
355     }
356 
357     private List<org.junit.rules.MethodRule> getMethodRules(final Object target) {
358         return rules(target);
359     }
360 
361     /**
362      * Returns a Statement.
363      *
364      * @param statement The base statement
365      * @return a RunRules statement if any class-level Rule are found, or the base statement
366      */
367     private Statement withTestRules(final FrameworkMethod method, final Object target, final Statement statement) {
368         final List<TestRule> testRules = getTestRules(target);
369         return testRules.isEmpty() ? statement : new RunRules(statement, testRules, describeChild(method));
370     }
371 
372     /**
373      * Returns if not yet implemented.
374      * @param method the method
375      * @return if not yet implemented
376      */
377     protected boolean isNotYetImplemented(final FrameworkMethod method) {
378         final NotYetImplemented notYetImplementedBrowsers = method.getAnnotation(NotYetImplemented.class);
379         if (notYetImplementedBrowsers == null) {
380             return false;
381         }
382         return isDefinedIn(notYetImplementedBrowsers.value()) || isDefinedIn(notYetImplementedBrowsers.os());
383     }
384 
385     private static int getTries(final FrameworkMethod method) {
386         final Tries tries = method.getAnnotation(Tries.class);
387         return tries != null ? tries.value() : 1;
388     }
389 
390     /**
391      * Returns the browser version.
392      * @return the browser version
393      */
394     protected BrowserVersion getBrowserVersion() {
395         return browserVersion_;
396     }
397 
398     /**
399      * Is real browser.
400      * @return whether we are using real browser or not
401      */
402     protected boolean isRealBrowser() {
403         return realBrowser_;
404     }
405 }