Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

ScreenshotTB3Test.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. /*
  2. * Copyright 2000-2013 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * 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, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.tests.tb3;
  17. import java.awt.image.BufferedImage;
  18. import java.io.ByteArrayInputStream;
  19. import java.io.File;
  20. import java.io.FileFilter;
  21. import java.io.IOException;
  22. import java.util.ArrayList;
  23. import java.util.List;
  24. import javax.imageio.ImageIO;
  25. import org.junit.After;
  26. import org.junit.Before;
  27. import org.junit.Rule;
  28. import org.junit.rules.TestRule;
  29. import org.junit.rules.TestWatcher;
  30. import org.openqa.selenium.OutputType;
  31. import org.openqa.selenium.TakesScreenshot;
  32. import org.openqa.selenium.remote.DesiredCapabilities;
  33. import com.vaadin.testbench.Parameters;
  34. import com.vaadin.testbench.commands.TestBenchCommands;
  35. /**
  36. * Base class which provides functionality for tests which use the automatic
  37. * screenshot comparison function.
  38. *
  39. * @author Vaadin Ltd
  40. */
  41. public abstract class ScreenshotTB3Test extends AbstractTB3Test {
  42. private String screenshotBaseName;
  43. @Rule
  44. public TestRule watcher = new TestWatcher() {
  45. @Override
  46. protected void starting(org.junit.runner.Description description) {
  47. Class<?> testClass = description.getTestClass();
  48. // Runner adds [BrowserName] which we do not want to use in the
  49. // screenshot name
  50. String testMethod = description.getMethodName();
  51. testMethod = testMethod.replaceAll("\\[.*\\]", "");
  52. String className = testClass.getSimpleName();
  53. screenshotBaseName = className + "-" + testMethod;
  54. }
  55. };
  56. /**
  57. * Contains a list of screenshot identifiers for which
  58. * {@link #compareScreen(String)} has failed during the test
  59. */
  60. private List<String> screenshotFailures = new ArrayList<String>();
  61. /**
  62. * Defines TestBench screen comparison parameters before each test run
  63. */
  64. @Before
  65. public void setupScreenComparisonParameters() {
  66. Parameters.setScreenshotErrorDirectory(getScreenshotErrorDirectory());
  67. Parameters
  68. .setScreenshotReferenceDirectory(getScreenshotReferenceDirectory());
  69. }
  70. /**
  71. * Grabs a screenshot and compares with the reference image with the given
  72. * identifier. Supports alternative references and will succeed if the
  73. * screenshot matches at least one of the references.
  74. *
  75. * In case of a failed comparison this method stores the grabbed screenshots
  76. * in the error directory as defined by
  77. * {@link #getScreenshotErrorDirectory()}. It will also generate a html file
  78. * in the same directory, comparing the screenshot with the first found
  79. * reference.
  80. *
  81. * @param identifier
  82. * @throws IOException
  83. */
  84. protected void compareScreen(String identifier) throws IOException {
  85. if (identifier == null || identifier.isEmpty()) {
  86. throw new IllegalArgumentException("Empty identifier not supported");
  87. }
  88. File mainReference = getScreenshotReferenceFile(identifier);
  89. List<File> referenceFiles = findReferenceAndAlternatives(mainReference);
  90. List<File> failedReferenceFiles = new ArrayList<File>();
  91. for (File referenceFile : referenceFiles) {
  92. if (testBench(driver).compareScreen(referenceFile)) {
  93. // There might be failure files because of retries in TestBench.
  94. deleteFailureFiles(getErrorFileFromReference(referenceFile));
  95. break;
  96. } else {
  97. failedReferenceFiles.add(referenceFile);
  98. }
  99. }
  100. File referenceToKeep = null;
  101. if (failedReferenceFiles.size() == referenceFiles.size()) {
  102. // Ensure we use the correct browser version (e.g. if running IE11
  103. // and only an IE 10 reference was available, then mainReference
  104. // will be for IE 10, not 11)
  105. String originalName = getScreenshotReferenceName(identifier);
  106. File exactVersionFile = new File(originalName);
  107. if (!exactVersionFile.equals(mainReference)) {
  108. // Rename png+html to have the correct version
  109. File correctPng = getErrorFileFromReference(exactVersionFile);
  110. File producedPng = getErrorFileFromReference(mainReference);
  111. File correctHtml = htmlFromPng(correctPng);
  112. File producedHtml = htmlFromPng(producedPng);
  113. producedPng.renameTo(correctPng);
  114. producedHtml.renameTo(correctHtml);
  115. referenceToKeep = exactVersionFile;
  116. screenshotFailures.add(exactVersionFile.getName());
  117. } else {
  118. // All comparisons failed, keep the main error image + HTML
  119. screenshotFailures.add(mainReference.getName());
  120. referenceToKeep = mainReference;
  121. }
  122. }
  123. // Remove all PNG/HTML files we no longer need (failed alternative
  124. // references or all error files (PNG/HTML) if comparison succeeded)
  125. for (File failedAlternative : failedReferenceFiles) {
  126. File failurePng = getErrorFileFromReference(failedAlternative);
  127. if (failedAlternative != referenceToKeep) {
  128. // Delete png + HTML
  129. deleteFailureFiles(failurePng);
  130. }
  131. }
  132. }
  133. private void deleteFailureFiles(File failurePng) {
  134. File failureHtml = htmlFromPng(failurePng);
  135. failurePng.delete();
  136. failureHtml.delete();
  137. }
  138. /**
  139. * Returns a new File which points to a .html file instead of the given .png
  140. * file
  141. *
  142. * @param png
  143. * @return
  144. */
  145. private static File htmlFromPng(File png) {
  146. return new File(png.getParentFile(), png.getName().replaceAll(
  147. "\\.png$", ".png.html"));
  148. }
  149. /**
  150. *
  151. * @param referenceFile
  152. * The reference image file (in the directory defined by
  153. * {@link #getScreenshotReferenceDirectory()})
  154. * @return the file name of the file generated in the directory defined by
  155. * {@link #getScreenshotErrorDirectory()} if comparison with the
  156. * given reference image fails.
  157. */
  158. private File getErrorFileFromReference(File referenceFile) {
  159. return new File(referenceFile.getAbsolutePath().replace(
  160. getScreenshotReferenceDirectory(),
  161. getScreenshotErrorDirectory()));
  162. }
  163. /**
  164. * Finds alternative references for the given files
  165. *
  166. * @param reference
  167. * @return all references which should be considered when comparing with the
  168. * given files, including the given reference
  169. */
  170. private List<File> findReferenceAndAlternatives(File reference) {
  171. List<File> files = new ArrayList<File>();
  172. files.add(reference);
  173. File screenshotDir = reference.getParentFile();
  174. String name = reference.getName();
  175. // Remove ".png"
  176. String nameBase = name.substring(0, name.length() - 4);
  177. for (int i = 1;; i++) {
  178. File file = new File(screenshotDir, nameBase + "_" + i + ".png");
  179. if (file.exists()) {
  180. files.add(file);
  181. } else {
  182. break;
  183. }
  184. }
  185. return files;
  186. }
  187. /**
  188. * @param testName
  189. * @return the reference file name to use for the given browser, as
  190. * described by {@literal capabilities}, and identifier
  191. */
  192. private File getScreenshotReferenceFile(String identifier) {
  193. DesiredCapabilities capabilities = getDesiredCapabilities();
  194. String originalName = getScreenshotReferenceName(identifier);
  195. File exactVersionFile = new File(originalName);
  196. if (exactVersionFile.exists()) {
  197. return exactVersionFile;
  198. }
  199. String browserVersion = capabilities.getVersion();
  200. if (browserVersion.matches("\\d+")) {
  201. for (int version = Integer.parseInt(browserVersion); version > 0; version--) {
  202. String fileName = getScreenshotReferenceName(identifier,
  203. version);
  204. File oldVersionFile = new File(fileName);
  205. if (oldVersionFile.exists()) {
  206. return oldVersionFile;
  207. }
  208. }
  209. }
  210. return exactVersionFile;
  211. }
  212. /**
  213. * @return the base directory of 'reference' and 'errors' screenshots
  214. */
  215. protected abstract String getScreenshotDirectory();
  216. /**
  217. * @return the directory where reference images are stored (the 'reference'
  218. * folder inside the screenshot directory)
  219. */
  220. private String getScreenshotReferenceDirectory() {
  221. return getScreenshotDirectory() + "/reference";
  222. }
  223. /**
  224. * @return the directory where comparison error images should be created
  225. * (the 'errors' folder inside the screenshot directory)
  226. */
  227. private String getScreenshotErrorDirectory() {
  228. return getScreenshotDirectory() + "/errors";
  229. }
  230. /**
  231. * Checks if any screenshot comparisons failures occurred during the test
  232. * and combines all comparison errors into one exception
  233. *
  234. * @throws IOException
  235. * If there were failures during the test
  236. */
  237. @After
  238. public void checkCompareFailures() throws IOException {
  239. if (!screenshotFailures.isEmpty()) {
  240. throw new IOException(
  241. "The following screenshots did not match the reference: "
  242. + screenshotFailures.toString());
  243. }
  244. }
  245. /*
  246. * (non-Javadoc)
  247. *
  248. * @see
  249. * com.vaadin.tests.tb3.AbstractTB3Test#onUncaughtException(java.lang.Throwable
  250. * )
  251. */
  252. @Override
  253. public void onUncaughtException(Throwable cause) {
  254. super.onUncaughtException(cause);
  255. // Grab a "failure" screenshot and store in the errors folder for later
  256. // analysis
  257. try {
  258. TestBenchCommands testBench = testBench();
  259. if (testBench != null) {
  260. testBench.disableWaitForVaadin();
  261. }
  262. } catch (Throwable t) {
  263. t.printStackTrace();
  264. }
  265. try {
  266. if (driver != null) {
  267. BufferedImage screenshotImage = ImageIO
  268. .read(new ByteArrayInputStream(
  269. ((TakesScreenshot) driver)
  270. .getScreenshotAs(OutputType.BYTES)));
  271. ImageIO.write(screenshotImage, "png", new File(
  272. getScreenshotFailureName()));
  273. }
  274. } catch (Throwable t) {
  275. t.printStackTrace();
  276. }
  277. }
  278. /**
  279. * @return the name of a "failure" image which is stored in the folder
  280. * defined by {@link #getScreenshotErrorDirectory()} when the test
  281. * fails
  282. */
  283. private String getScreenshotFailureName() {
  284. return getScreenshotErrorBaseName() + "-failure.png";
  285. }
  286. /**
  287. * @return the base name used for screenshots. This is the first part of the
  288. * screenshot file name, typically created as "testclass-testmethod"
  289. */
  290. public String getScreenshotBaseName() {
  291. return screenshotBaseName;
  292. }
  293. /**
  294. * Returns the name of the reference file based on the given parameters.
  295. *
  296. * @param testName
  297. * @param capabilities
  298. * @param identifier
  299. * @return the full path of the reference
  300. */
  301. private String getScreenshotReferenceName(String identifier) {
  302. return getScreenshotReferenceName(identifier, null);
  303. }
  304. /**
  305. * Returns the name of the reference file based on the given parameters. The
  306. * version given in {@literal capabilities} is used unless it is overridden
  307. * by the {@literal versionOverride} parameter.
  308. *
  309. * @param testName
  310. * @param capabilities
  311. * @param identifier
  312. * @return the full path of the reference
  313. */
  314. private String getScreenshotReferenceName(String identifier,
  315. Integer versionOverride) {
  316. String uniqueBrowserIdentifier;
  317. if (versionOverride == null) {
  318. uniqueBrowserIdentifier = BrowserUtil
  319. .getUniqueIdentifier(getDesiredCapabilities());
  320. } else {
  321. uniqueBrowserIdentifier = BrowserUtil.getUniqueIdentifier(
  322. getDesiredCapabilities(), "" + versionOverride);
  323. }
  324. // WindowMaximizeRestoreTest_Windows_InternetExplorer_8_window-1-moved-maximized-restored.png
  325. return getScreenshotReferenceDirectory() + "/"
  326. + getScreenshotBaseName() + "_" + uniqueBrowserIdentifier + "_"
  327. + identifier + ".png";
  328. }
  329. /**
  330. * Returns the base name of the screenshot in the error directory. This is a
  331. * name so that all files matching {@link #getScreenshotErrorBaseName()}*
  332. * are owned by this test instance (taking into account
  333. * {@link #getDesiredCapabilities()}) and can safely be removed before
  334. * running this test.
  335. */
  336. private String getScreenshotErrorBaseName() {
  337. return getScreenshotReferenceName("dummy", null).replace(
  338. getScreenshotReferenceDirectory(),
  339. getScreenshotErrorDirectory()).replace("_dummy.png", "");
  340. }
  341. /**
  342. * Removes any old screenshots related to this test from the errors
  343. * directory before running the test
  344. */
  345. @Before
  346. public void cleanErrorDirectory() {
  347. // Remove any screenshots for this test from the error directory
  348. // before running it. Leave unrelated files as-is
  349. File errorDirectory = new File(getScreenshotErrorDirectory());
  350. // Create errors directory if it does not exist
  351. if (!errorDirectory.exists()) {
  352. errorDirectory.mkdirs();
  353. }
  354. final String errorBase = getScreenshotErrorBaseName()
  355. .replace("\\", "/");
  356. File[] files = errorDirectory.listFiles(new FileFilter() {
  357. @Override
  358. public boolean accept(File pathname) {
  359. String thisFile = pathname.getAbsolutePath().replace("\\", "/");
  360. if (thisFile.startsWith(errorBase)) {
  361. return true;
  362. }
  363. return false;
  364. }
  365. });
  366. for (File f : files) {
  367. f.delete();
  368. }
  369. }
  370. }