Java tutorial
/* * Copyright (c) 2010 INSciTE. All rights reserved * INSciTE is on the web at: http://www.hightechkids.org * This code is released under GPL; see LICENSE.txt for details. */ package fll.web; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.io.Writer; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.Charset; import java.nio.file.DirectoryIteratorException; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.concurrent.TimeUnit; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.protocol.BasicHttpContext; import org.apache.log4j.Logger; import org.junit.Assert; import org.openqa.selenium.Alert; import org.openqa.selenium.By; import org.openqa.selenium.NoAlertPresentException; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.OutputType; import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.support.ui.Select; import org.w3c.dom.Document; import com.fasterxml.jackson.databind.ObjectMapper; import fll.TestUtils; import fll.Tournament; import fll.util.FLLInternalException; import fll.util.LogUtils; import fll.web.api.TournamentsServlet; import fll.xml.BracketSortType; import io.github.bonigarcia.wdm.ChromeDriverManager; import io.github.bonigarcia.wdm.FirefoxDriverManager; import net.mtu.eggplant.xml.XMLUtils; /** * Some utilities for integration tests. */ public final class IntegrationTestUtils { private static final Logger LOGGER = LogUtils.getLogger(); public static final String TEST_USERNAME = "fll"; public static final String TEST_PASSWORD = "Lego"; /** * How long to wait for pages to load before checking for elements. */ public static final long WAIT_FOR_PAGE_LOAD_MS = 2500; private IntegrationTestUtils() { // no instances } /** * Check if an element exists. */ public static boolean isElementPresent(final WebDriver selenium, final By search) { boolean elementFound = false; try { selenium.findElement(search); elementFound = true; } catch (NoSuchElementException e) { elementFound = false; } return elementFound; } /** * Load a page and check to make sure the page didn't crash. * * @param selenium the test controller * @param url the page to load * @throws IOException * @throws InterruptedException */ public static void loadPage(final WebDriver selenium, final String url) throws IOException, InterruptedException { selenium.get(url); Thread.sleep(WAIT_FOR_PAGE_LOAD_MS); assertNoException(selenium); } /** * Assert that the current page is not the error handler page. */ public static void assertNoException(final WebDriver selenium) { Assert.assertFalse("Error loading page", isElementPresent(selenium, By.id("exception-handler"))); } /** * Initialize the database using the given challenge document. * * @param driver the test controller * @param challengeDocument the challenge descriptor * @throws IOException * @throws InterruptedException */ public static void initializeDatabase(final WebDriver driver, final Document challengeDocument) throws IOException, InterruptedException { Assert.assertNotNull(challengeDocument); final Path challengeFile = Files.createTempFile("fll", ".xml"); try (final Writer writer = new FileWriter(challengeFile.toFile())) { XMLUtils.writeXML(challengeDocument, writer); } try { initializeDatabase(driver, challengeFile); } finally { Files.delete(challengeFile); } } /** * Initialize the database using the given challenge descriptor. * * @param driver the test controller * @param challengeStream the challenge descriptor * @throws IOException * @throws InterruptedException */ public static void initializeDatabase(final WebDriver driver, final InputStream challengeStream) throws IOException, InterruptedException { Assert.assertNotNull(challengeStream); final Path challengeFile = Files.createTempFile("fll", ".xml"); Files.copy(challengeStream, challengeFile, StandardCopyOption.REPLACE_EXISTING); try { initializeDatabase(driver, challengeFile); } finally { Files.delete(challengeFile); } } /** * Initialize the database using the given challenge descriptor. * * @param driver the test controller * @param challengeFile a file to read the challenge description from. This * file will not be deleted. * @throws InterruptedException * @throws IOException */ public static void initializeDatabase(final WebDriver driver, final Path challengeFile) throws InterruptedException { driver.get(TestUtils.URL_ROOT + "setup/"); Thread.sleep(WAIT_FOR_PAGE_LOAD_MS); if (isElementPresent(driver, By.name("submit_login"))) { login(driver); driver.get(TestUtils.URL_ROOT + "setup/"); Thread.sleep(WAIT_FOR_PAGE_LOAD_MS); } final WebElement fileEle = driver.findElement(By.name("xmldocument")); fileEle.sendKeys(challengeFile.toAbsolutePath().toString()); final WebElement reinitDB = driver.findElement(By.name("reinitializeDatabase")); reinitDB.click(); Thread.sleep(WAIT_FOR_PAGE_LOAD_MS); try { final Alert confirmCreateDB = driver.switchTo().alert(); LOGGER.info("Confirmation text: " + confirmCreateDB.getText()); confirmCreateDB.accept(); } catch (final NoAlertPresentException e) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("No alert found, assuming the database was empty and didn't need an alert."); } } Thread.sleep(2 * WAIT_FOR_PAGE_LOAD_MS); driver.findElement(By.id("success")); // setup user final WebElement userElement = driver.findElement(By.name("user")); userElement.sendKeys(TEST_USERNAME); final WebElement passElement = driver.findElement(By.name("pass")); passElement.sendKeys(TEST_PASSWORD); final WebElement passCheckElement = driver.findElement(By.name("pass_check")); passCheckElement.sendKeys(TEST_PASSWORD); final WebElement submitElement = driver.findElement(By.name("submit_create_user")); submitElement.click(); Thread.sleep(2 * WAIT_FOR_PAGE_LOAD_MS); driver.findElement(By.id("success-create-user")); login(driver); } /** * Initialize a database from a zip file. * * @param selenium the test controller * @param inputStream input stream that has database to load in it, this input * stream is closed by this method upon successful completion * @throws IOException * @throws InterruptedException */ public static void initializeDatabaseFromDump(final WebDriver selenium, final InputStream inputStream) throws IOException, InterruptedException { Assert.assertNotNull(inputStream); final File dumpFile = IntegrationTestUtils.storeInputStreamToFile(inputStream); try { selenium.get(TestUtils.URL_ROOT + "setup/"); if (isElementPresent(selenium, By.name("submit_login"))) { login(selenium); selenium.get(TestUtils.URL_ROOT + "setup/"); } final WebElement dbEle = selenium.findElement(By.name("dbdump")); dbEle.sendKeys(dumpFile.getAbsolutePath()); final WebElement createEle = selenium.findElement(By.name("createdb")); createEle.click(); try { final Alert confirmCreateDB = selenium.switchTo().alert(); LOGGER.info("Confirmation text: " + confirmCreateDB.getText()); confirmCreateDB.accept(); } catch (final NoAlertPresentException e) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("No alert found, assuming the database was empty and didn't need an alert."); } } selenium.findElement(By.id("success")); // setup user final WebElement userElement = selenium.findElement(By.name("user")); userElement.sendKeys(TEST_USERNAME); final WebElement passElement = selenium.findElement(By.name("pass")); passElement.sendKeys(TEST_PASSWORD); final WebElement passCheckElement = selenium.findElement(By.name("pass_check")); passCheckElement.sendKeys(TEST_PASSWORD); final WebElement submitElement = selenium.findElement(By.name("submit_create_user")); submitElement.click(); selenium.findElement(By.id("success-create-user")); login(selenium); } finally { if (!dumpFile.delete()) { dumpFile.deleteOnExit(); } } login(selenium); } /** * Defaults filePrefix to "fll". * * @see #storeScreenshot(String, WebDriver) */ public static void storeScreenshot(final WebDriver driver) throws IOException { storeScreenshot("fll", driver); } /** * Store screenshot and other information for debugging the error. * * @param filePrefix prefix for the files that are created * @param driver * @throws IOException */ public static void storeScreenshot(final String filePrefix, final WebDriver driver) throws IOException { final Path tempDir = Files.createTempDirectory(Paths.get("screenshots"), filePrefix); if (driver instanceof TakesScreenshot) { final Path screenshot = tempDir.resolve("screenshot.png"); final File scrFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE); Files.copy(scrFile.toPath(), screenshot); LOGGER.info("Screenshot saved to " + screenshot.toAbsolutePath().toString()); } else { LOGGER.warn("Unable to get screenshot"); } final Path htmlFile = tempDir.resolve("page.html"); final String html = driver.getPageSource(); final BufferedWriter writer = Files.newBufferedWriter(htmlFile); writer.write(html); writer.close(); LOGGER.info("HTML saved to " + htmlFile.toAbsolutePath().toString()); // get the database final Path dbroot = Paths.get("tomcat", "webapps", "fll-sw", "WEB-INF"); LOGGER.info("Copying database files from " + dbroot.toAbsolutePath() + " to " + tempDir.toAbsolutePath()); try (final DirectoryStream<Path> stream = Files.newDirectoryStream(dbroot, "flldb*")) { for (final Path entry : stream) { if (Files.isRegularFile(entry)) { Files.copy(entry, tempDir.resolve(dbroot.relativize(entry))); LOGGER.info("Copied database file " + entry.toString()); } } } catch (final DirectoryIteratorException ex) { LOGGER.error("Unable to get database files", ex); } LOGGER.info("Finished copying database files"); } /** * Copy the contents of a stream to a temporary file. * * @param inputStream the data to store in the temporary file * @return the temporary file, you need to delete it * @throws IOException */ public static File storeInputStreamToFile(final InputStream inputStream) throws IOException { final File tempFile = File.createTempFile("fll", null); final FileOutputStream outputStream = new FileOutputStream(tempFile); final byte[] buffer = new byte[1042]; int bytesRead; while (-1 != (bytesRead = inputStream.read(buffer))) { outputStream.write(buffer, 0, bytesRead); } outputStream.close(); return tempFile; } /** * Login to fll * @throws InterruptedException */ public static void login(final WebDriver driver) throws InterruptedException { driver.get(TestUtils.URL_ROOT + "login.jsp"); final WebElement userElement = driver.findElement(By.name("user")); userElement.sendKeys(TEST_USERNAME); final WebElement passElement = driver.findElement(By.name("pass")); passElement.sendKeys(TEST_PASSWORD); final WebElement submitElement = driver.findElement(By.name("submit_login")); submitElement.click(); Thread.sleep(WAIT_FOR_PAGE_LOAD_MS); } private static String readAll(final Reader rd) throws IOException { StringBuilder sb = new StringBuilder(); int cp; while ((cp = rd.read()) != -1) { sb.append((char) cp); } return sb.toString(); } private static String readJSON(final String url) throws MalformedURLException, IOException { InputStream is = new URL(url).openStream(); try { final BufferedReader rd = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8"))); final String jsonText = readAll(rd); return jsonText; } finally { is.close(); } } /** * Find a tournament by name using the JSON API. * * @param tournamentName name of tournament * @return the tournament or null if not found */ public static Tournament getTournamentByName(final String tournamentName) throws IOException { final String json = readJSON(TestUtils.URL_ROOT + "api/Tournaments"); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Tournaments json: " + json); } // get the JSON final ObjectMapper jsonMapper = new ObjectMapper(); final Reader reader = new StringReader(json); final Collection<Tournament> tournaments = jsonMapper.readValue(reader, TournamentsServlet.TournamentsTypeInformation.INSTANCE); for (final Tournament tournament : tournaments) { if (tournament.getName().equals(tournamentName)) { return tournament; } } return null; } /** * Add a team to a tournament. * @throws InterruptedException */ public static void addTeam(final WebDriver selenium, final int teamNumber, final String teamName, final String organization, final String division, final String tournamentName) throws IOException, InterruptedException { final Tournament tournament = getTournamentByName(tournamentName); loadPage(selenium, TestUtils.URL_ROOT + "admin/index.jsp"); selenium.findElement(By.linkText("Add a team")).click(); Thread.sleep(WAIT_FOR_PAGE_LOAD_MS); selenium.findElement(By.name("teamNumber")).sendKeys(String.valueOf(teamNumber)); selenium.findElement(By.name("teamName")).sendKeys(teamName); selenium.findElement(By.name("organization")).sendKeys(organization); selenium.findElement(By.id("tournament_" + tournament.getTournamentID())).click(); Thread.sleep(WAIT_FOR_PAGE_LOAD_MS); final WebElement eventDivision = selenium .findElement(By.id("event_division_" + tournament.getTournamentID())); final Select eventDivisionSel = new Select(eventDivision); eventDivisionSel.selectByValue(division); final WebElement judgingStation = selenium .findElement(By.id("judging_station_" + tournament.getTournamentID())); final Select judgingStationSel = new Select(judgingStation); judgingStationSel.selectByValue(division); selenium.findElement(By.name("commit")).click(); Thread.sleep(WAIT_FOR_PAGE_LOAD_MS); selenium.findElement(By.id("success")); } /** * Set the current tournament by name. * * @param tournamentName the name of the tournament to make the current * tournament * @throws IOException * @throws InterruptedException */ public static void setTournament(final WebDriver selenium, final String tournamentName) throws IOException, InterruptedException { loadPage(selenium, TestUtils.URL_ROOT + "admin/index.jsp"); final WebElement currentTournament = selenium.findElement(By.id("currentTournamentSelect")); final Select currentTournamentSel = new Select(currentTournament); String tournamentID = null; for (final WebElement option : currentTournamentSel.getOptions()) { final String text = option.getText(); if (LOGGER.isTraceEnabled()) { LOGGER.trace("setTournament option: " + text); } if (text.endsWith("[ " + tournamentName + " ]")) { tournamentID = option.getAttribute("value"); } } Assert.assertNotNull("Could not find tournament with name: " + tournamentName, tournamentID); currentTournamentSel.selectByValue(tournamentID); final WebElement changeTournament = selenium.findElement(By.name("change_tournament")); changeTournament.click(); Assert.assertNotNull(selenium.findElement(By.id("success"))); } /** * Create firefox web driver used for most integration tests. * * @see #createWebDriver(WebDriverType) */ public static WebDriver createWebDriver() { return createWebDriver(WebDriverType.FIREFOX); } public enum WebDriverType { FIREFOX, CHROME } private static Set<WebDriverType> mInitializedWebDrivers = new HashSet<>(); /** * Create a web driver and set appropriate timeouts on it. */ public static WebDriver createWebDriver(final WebDriverType type) { final WebDriver selenium; switch (type) { case FIREFOX: selenium = createFirefoxWebDriver(); break; case CHROME: selenium = createChromeWebDriver(); break; default: throw new IllegalArgumentException("Unknown web driver type: " + type); } selenium.manage().timeouts().implicitlyWait(WAIT_FOR_PAGE_LOAD_MS, TimeUnit.MILLISECONDS); selenium.manage().timeouts().pageLoadTimeout(60, TimeUnit.SECONDS); return selenium; } private static WebDriver createFirefoxWebDriver() { if (!mInitializedWebDrivers.contains(WebDriverType.FIREFOX)) { FirefoxDriverManager.getInstance().setup(); mInitializedWebDrivers.add(WebDriverType.FIREFOX); } // final DesiredCapabilities capabilities = DesiredCapabilities.firefox(); // capabilities.setCapability("marionette", true); // final WebDriver selenium = new FirefoxDriver(capabilities); final WebDriver selenium = new FirefoxDriver(); return selenium; } private static WebDriver createChromeWebDriver() { if (!mInitializedWebDrivers.contains(WebDriverType.CHROME)) { ChromeDriverManager.getInstance().setup(); mInitializedWebDrivers.add(WebDriverType.CHROME); } final WebDriver selenium = new ChromeDriver(); return selenium; } public static void initializePlayoffsForAwardGroup(final WebDriver selenium, final String awardGroup) throws IOException, InterruptedException { initializePlayoffsForAwardGroup(selenium, awardGroup, BracketSortType.SEEDING); } public static void initializePlayoffsForAwardGroup(final WebDriver selenium, final String awardGroup, final BracketSortType bracketSort) throws IOException, InterruptedException { loadPage(selenium, TestUtils.URL_ROOT + "playoff"); selenium.findElement(By.id("create-bracket")).click(); Thread.sleep(WAIT_FOR_PAGE_LOAD_MS); selenium.findElement( By.xpath("//input[@value='Create Head to Head Bracket for Award Group " + awardGroup + "']")) .click(); Assert.assertTrue("Error creating bracket for award group: " + awardGroup, isElementPresent(selenium, By.id("success"))); final Select initDiv = new Select(selenium.findElement(By.id("initialize-division"))); initDiv.selectByValue(awardGroup); selenium.findElement(By.id("initialize_brackets")).click(); Thread.sleep(WAIT_FOR_PAGE_LOAD_MS); Assert.assertFalse("Error loading page", isElementPresent(selenium, By.id("exception-handler"))); final Select sort = new Select(selenium.findElement(By.id("sort"))); sort.selectByValue(bracketSort.name()); selenium.findElement(By.id("submit")).click(); Thread.sleep(WAIT_FOR_PAGE_LOAD_MS); Assert.assertFalse("Error loading page", isElementPresent(selenium, By.id("exception-handler"))); } /** * Try harder to find elements. */ public static WebElement findElement(final WebDriver selenium, final By by, final int maxAttempts) { int attempts = 0; WebElement e = null; while (e == null && attempts <= maxAttempts) { try { e = selenium.findElement(by); } catch (final NoSuchElementException ex) { ++attempts; e = null; if (attempts >= maxAttempts) { throw ex; } else { LOGGER.warn("Trouble finding element, trying again", ex); } } } return e; } /** * Change the number of seeding rounds for the specified tournament. * * @param selenium * @param newValue * @throws NoSuchElementException if there was a problem changing the value * @throws IOException if there is an error talking to selenium * @throws InterruptedException */ public static void changeNumSeedingRounds(final WebDriver selenium, final int tournamentId, final int newValue) throws NoSuchElementException, IOException, InterruptedException { IntegrationTestUtils.loadPage(selenium, TestUtils.URL_ROOT + "admin/edit_all_parameters.jsp"); final Select seedingRoundsSelection = new Select( selenium.findElement(By.name("seeding_rounds_" + tournamentId))); seedingRoundsSelection.selectByValue(Integer.toString(newValue)); selenium.findElement(By.id("submit")).click(); selenium.findElement(By.id("success")); } /** * Get the id of the current tournament * * @throws IOException * @throws InterruptedException */ public static int getCurrentTournamentId(final WebDriver selenium) throws IOException, InterruptedException { loadPage(selenium, TestUtils.URL_ROOT + "admin/index.jsp"); final WebElement currentTournament = selenium.findElement(By.id("currentTournamentSelect")); final Select currentTournamentSel = new Select(currentTournament); for (final WebElement option : currentTournamentSel.getOptions()) { if (option.isSelected()) { final String idStr = option.getAttribute("value"); return Integer.valueOf(idStr); } } throw new FLLInternalException("Cannot find default tournament"); } /** * Download the specified file and check the content type. * If the content type doesn't match an assertion violation will be thrown. * * @param urlToLoad the page to load * @param destination where to save the file, may be null to not save the file * and just check the content type. Any existing file will be * overwritten. */ public static void downloadFile(final URL urlToLoad, final String expectedContentType, final Path destination) throws ClientProtocolException, IOException { try (final CloseableHttpClient client = HttpClientBuilder.create().build()) { final BasicHttpContext localContext = new BasicHttpContext(); // if (this.mimicWebDriverCookieState) { // localContext.setAttribute(ClientContext.COOKIE_STORE, // mimicCookieState(selenium.manage().getCookies())); // } final HttpRequestBase requestMethod = new HttpGet(); requestMethod.setURI(urlToLoad.toURI()); // HttpParams httpRequestParameters = requestMethod.getParams(); // httpRequestParameters.setParameter(ClientPNames.HANDLE_REDIRECTS, // this.followRedirects); // requestMethod.setParams(httpRequestParameters); final HttpResponse response = client.execute(requestMethod, localContext); final Header contentTypeHeader = response.getFirstHeader("Content-type"); Assert.assertNotNull("Null content type header: " + urlToLoad.toString(), contentTypeHeader); final String contentType = contentTypeHeader.getValue().split(";")[0].trim(); Assert.assertEquals("Unexpected content type from: " + urlToLoad.toString(), expectedContentType, contentType); if (null != destination) { try (final InputStream stream = response.getEntity().getContent()) { Files.copy(stream, destination, StandardCopyOption.REPLACE_EXISTING); } // try create stream } // non-null destination } catch (final URISyntaxException e) { throw new FLLInternalException("Got exception turning URL into URI, this shouldn't happen", e); } } }