Skip to content

Instantly share code, notes, and snippets.

@JeffML
Last active August 29, 2015 14:22
Show Gist options
  • Save JeffML/d8b82975b27b4adf615f to your computer and use it in GitHub Desktop.
Save JeffML/d8b82975b27b4adf615f to your computer and use it in GitHub Desktop.
Example of using threaded Geb test for Broken Links
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