Last active
August 29, 2015 14:22
-
-
Save JeffML/d8b82975b27b4adf615f to your computer and use it in GitHub Desktop.
Example of using threaded Geb test for Broken Links
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package tests | |
import static groovy.json.JsonOutput.* | |
import static org.junit.Assert.* | |
import geb.Browser | |
import java.util.concurrent.* | |
import org.junit.Before | |
import org.junit.Test | |
import org.junit.runner.RunWith | |
import org.junit.runners.Parameterized | |
import org.junit.runners.Parameterized.Parameters | |
import page.AnyPage | |
@RunWith(Parameterized.class) | |
//@Ignore | |
class BrokenLinkTest extends BaseTest { | |
static def links=[:].withDefault{[]} // collection of links from all pages visited | |
static def pageCt = 0; // # of pages to check | |
static def currPageCt = 0; // current page index | |
static def testTimeoutSecs = 600; // 10 minutes should do it | |
static def THREADS = 40; | |
/* a borken link for testing */ | |
def BORKED; | |
def page; | |
/** | |
* Determines the parameters for this test. | |
* @return The parameters for each iteration of the test | |
*/ | |
@Parameters(name = "Broken Link Test") | |
public static Collection<Object[]> getPages() { | |
def pages = AllPagesTests.getPages() // a list of URLs | |
pageCt = pages.size(); | |
return pages; | |
} | |
BrokenLinkTest(url, title) { | |
page = [url: url, title: title] | |
} | |
@Before | |
void setup() { | |
BORKED = "${browser.baseUrl}/bork/bork/bork.html" //see collectLinks() | |
AnyPage.url = this.page.url | |
AnyPage.titleText = this.page.title | |
} | |
@Test | |
void collectLinks() { | |
//for all pages but the last, we merely collect links; see LAST comment | |
def missing = [:] | |
try { | |
to AnyPage | |
waitFor 5, {at AnyPage} | |
} catch (org.openqa.selenium.TimeoutException e) { | |
System.err << "Page load timeout occurred on ${this.page.url}" | |
return; | |
} | |
def anchors; | |
try { | |
anchors = $('a', href: notContains('location.href#')) | |
} catch (org.openqa.selenium.StaleElementReferenceException e) { | |
println "$e.message" | |
anchors = []; | |
} | |
anchors.each{ it -> | |
def link = it.@href | |
if (link =~ /^(javascript:|mailto:|tel:).*/ ) return; | |
if (!link.startsWith('http')) { | |
link = "$browser.baseUrl/$link"; | |
} | |
links[link].push page.url | |
} | |
// add a broken test link (for testing only) | |
// links[BORKED].push page.url | |
// LAST: if this is the last page, we have all our links and can start checking them | |
if (++currPageCt == pageCt) { | |
reportBorkedLinks(browser); | |
} | |
} | |
/** | |
* check for broken links on all pages | |
* | |
* This is a threaded operation: | |
* <ol> | |
* <li> create a missing map to hold error data; it must be synchronized | |
* <li> set up a thread pool | |
* <li> create defer method to add Callables (closures) to thread pool | |
* <li> create a linkCheck method (self-explanatory) | |
* <li> for each link, add task to thread pool (which calls linkCheck) | |
* <li> tell pool we're done submitting (pool.shutdown) | |
* <li> wait for termination (all threads complete) | |
* <li> report any missing links | |
* </ol> | |
*/ | |
static void reportBorkedLinks(browser) { | |
println "# of links found in pages = ${links.size()}" | |
def missing = [:].asSynchronized() | |
def pool = Executors.newFixedThreadPool(THREADS) | |
def defer = { | |
c -> pool.submit(c as Callable) | |
} | |
def linkCheck = { link, pageURLs -> | |
// println "checking link: $link" | |
URL url = new URL(link); | |
// for this one page (and one page only), HEAD request returns a 501; GET gets 200: | |
def requestType = link == "http://www.cdw.com/shop/products/PANTONE-Process-Color-Guide-Coated/212012.aspx"? 'GET' : 'HEAD' | |
def connection = url.openConnection() as HttpURLConnection; | |
connection.setConnectTimeout(5000) | |
connection.setRequestMethod(requestType); | |
connection.connect(); | |
def code = connection.getResponseCode(); | |
connection.disconnect(); | |
connection = null; // try to help GC out a little | |
def okay = code == 200 || (300..<400).contains(code) | |
if (!okay) { | |
if (!missing[code]) missing[code] = [] | |
if (pageURLs.size() > 5) { | |
pageURLs = pageURLs[0..4] + ["..."] | |
} | |
missing[code].push([brokenLink: link, urls: pageURLs]) | |
} | |
} | |
links.each { link, urls -> | |
defer {linkCheck(link, urls.unique())} // passes this closure (which calls linkCheck) to the thread pool | |
} | |
pool.shutdown(); | |
try { | |
// Wait a while for existing tasks to terminate | |
if (!pool.awaitTermination(testTimeoutSecs, TimeUnit.SECONDS)) { | |
pool.shutdownNow(); // Cancel currently executing tasks | |
// Wait a while for tasks to respond to being cancelled | |
if (!pool.awaitTermination(testTimeoutSecs, TimeUnit.SECONDS)) | |
fail("Pool did not terminate"); | |
} | |
} catch (InterruptedException ie) { | |
// (Re-)Cancel if current thread also interrupted | |
pool.shutdownNow(); | |
// Preserve interrupt status | |
Thread.currentThread().interrupt(); | |
} | |
def jsonStr = prettyPrint(toJson(missing)); | |
assertEquals("Borked links: $jsonStr", 0, missing.size()) | |
println '' | |
println "No borken links" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment