// Copyright � 2002-2005 Canoo Engineering AG, Switzerland. package com.canoo.webtest.steps.verify; import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import com.canoo.webtest.self.ContextStub; import com.canoo.webtest.self.ThrowAssert; import com.canoo.webtest.steps.BaseStepTestCase; import com.canoo.webtest.steps.Step; import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; import com.gargoylesoftware.htmlunit.HttpMethod; import com.gargoylesoftware.htmlunit.MockWebConnection; import com.gargoylesoftware.htmlunit.Page; import com.gargoylesoftware.htmlunit.ScriptException; import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.WebResponse; import com.gargoylesoftware.htmlunit.WebResponseData; import com.gargoylesoftware.htmlunit.html.HtmlPage; import com.gargoylesoftware.htmlunit.util.NameValuePair; /** * @author Dierk K�nig, Urs-Peter H�ss * @author Marc Guillemot, Paul King */ // todo: dk: test missing for already visited pages -> performance issue // todo: dk: how about .doc .pdf links? -> stopHunting // todo: StepFailedException for all Broken Links and processing does not stop public class VerifyLinksTest extends BaseStepTestCase { private static final String DUMMY_HREF = "any.htm"; private static final String DUMMY_LABEL = "Homepage"; private static final HtmlPage DUMMY_PAGE = getResponseForText(samplePageWithAnchor()); private static final int THROW_NO_EXCEPTION = 0; private static final int THROW_MALFORMEDURLEXCEPTION = 1; private static final int THROW_IOEXCEPTION = 2; protected Step createStep() { return new VerifyLinks(); } public static void testFindNoLinksOnEmptyPage() { checkLinkCountText("", 0); checkLinkCountText(wrapContent(""), 0); } // public void pendingTestFindNoLinksOnNameAttribute() { // checkLinkCountText(wrapContent("<A NAME='bla'>Content</A>"), 0); // checkLinkCountText(wrapContent("<A NAME='bla'></A>"), 0); // } public void testLinksValid() throws Exception { checkLinkCountDummyPage("<a href='#me'>dummy</a>" + "<a name='me' href='http://www.yahoo.com'>dummy</a>" + "<a href='http://www.google.com'>dummy</a>" + "<a href='/trafficlight.html'>dummy</a>", 4); } public void testLinksBroken() throws Exception { checkLinkCountDummyPage("<a href='notExist.jsp'>dummy</a>" + "<a href='ftp://dummy.org'>dummy</a>" + "<a href='crazy://badUrl.org'>dummy</a>", 1); } // public void pendingTestClientSideImageMap() { // checkLinkCountText(wrapContent("<MAP NAME=\"xxx\">" + // "<AREA shape=\"rect\" Coords=\"572,5,605,50\" HREF=\"bla\">" + // "<AREA shape=\"rect\" Coords=\"572,5,605,50\" HREF=\"bla\"></MAP>" + // "<IMG USEMAP=\"#XXX\" SRC=\"x.gif\">"), 2); // } public static void testFindOneLinkOnPage() { checkLinkCountText(samplePageWithAnchor(), 1); checkLinkCountText(wrapContent(anchor(DUMMY_HREF, "<IMG src=\"bla\">")), 1); checkLinkCountText(wrapContent("<A CLASS=\"x\" HREF=\"home.htm\"><IMG source=\"bla\"></A>"), 1); checkLinkCountText(wrapContent("<A HREF=\"home.htm\" TARGET=\"FRAME\">Homepage</A>"), 1); checkLinkCountText(wrapContent(anchor(DUMMY_HREF, DUMMY_LABEL) + anchor(DUMMY_HREF, DUMMY_LABEL)), 1); } public static void testIgnoreSpecialLinks() { checkLinkCountText(wrapContent(anchor("ftp://ftp.whateveritis.com", DUMMY_LABEL)), 0); checkLinkCountText(wrapContent(anchor("mailto:feedback-online@canoo.com", "Feedback")), 0); checkLinkCountText(wrapContent(anchor("www.ftp.com", "bla")), 1); } public static void testRelativeHrefExpansion() throws MalformedURLException { Set foundGoodLinks = getGoodLinks(samplePageWithAnchor()); assertTrue(foundGoodLinks.contains(new URL(ContextStub.SOME_BASE_URL + "/" + DUMMY_HREF))); } private static class VerifyLinksExposeVisitsStub extends VerifyLinks { public void checkVisits(WebClient webClient, HtmlPage response) { super.checkVisits(webClient, response); } } public void testBrokenLinkDoesNotInterrupt() { WebClient client = new WebClient(); VerifyLinks step = new VerifyLinksExposeVisitsStub(); HtmlPage page = getResponseForText(wrapContent(anchor("a", "x") + anchor("b", "y"))); step.checkVisits(client, page); assertEquals(2, step.getFailedVisits().size()); } private static Set getGoodLinks(final String text) { HtmlPage response = getResponseForText(text); return VerifyLinks.getGoodLinks(response); } private static HtmlPage getResponseForText(final String text) { ContextStub context = new ContextStub(text); return (HtmlPage) context.getCurrentResponse(); } public static void testSameHRefNotToBeFollowedTwice() { WebClient client = new WebClient(); VerifyLinks step = new VerifyLinksExposeVisitsStub(); String href = "a"; HtmlPage page = getResponseForText(wrapContent(anchor(href, "LabelX") + anchor(href, "LabelY"))); step.checkVisits(client, page); assertEquals(1, step.getFailedVisits().size()); } private static class VerifyLinksExposeDepthStub extends VerifyLinks { private int fCount; protected void visit(HtmlPage page, URL ignoreUrl, WebClient client) { fCount++; followRecursively(page, client); } protected boolean stopHunting(HtmlPage htmlPage) { return false; } } public static void testDepth() { assertDepth(0); assertDepth(1); assertDepth(10); } private static void assertDepth(int depth) { VerifyLinksExposeDepthStub verifyLinks = new VerifyLinksExposeDepthStub(); verifyLinks.setDepth(Integer.toString(depth)); verifyLinks.verifyProperties(); verifyLinks.followRecursively(DUMMY_PAGE, new WebClient()); assertEquals(depth, verifyLinks.fCount); } public void testDepthWithBrokenUrl() { WebClient client = new WebClient(); VerifyLinks verifyLinks = (VerifyLinks) getStep(); HtmlPage page = getResponseForText(wrapContent(anchor("youwillnotfindthis", "x"))); verifyLinks.setDepth("2"); verifyLinks.verifyProperties(); verifyLinks.checkVisits(client, page); boolean containsExactlyOneSemicolon = verifyLinks.brokenLinksToString().indexOf(";") == verifyLinks.brokenLinksToString().lastIndexOf(";") && verifyLinks.brokenLinksToString().indexOf(";") != -1; assertTrue(containsExactlyOneSemicolon); } private static class VerifyLinksExposeForeignHostStub extends VerifyLinks { private boolean fIsforeignHost = true; protected boolean isForeignHost(URL ignore) { return fIsforeignHost; } } public static void testOnSiteOnly() { VerifyLinksExposeForeignHostStub verifyLinks = new VerifyLinksExposeForeignHostStub(); verifyLinks.setOnsiteonly(true); verifyLinks.verifyProperties(); assertTrue("onsiteonly and foreign host: stop", verifyLinks.stopHunting(DUMMY_PAGE)); verifyLinks.fIsforeignHost = false; assertTrue("onsiteonly and same host: don't stop", !verifyLinks.stopHunting(DUMMY_PAGE)); verifyLinks.fIsforeignHost = true; verifyLinks.setOnsiteonly(false); verifyLinks.verifyProperties(); assertTrue("not onsiteonly and foreign host: don't stop", !verifyLinks.stopHunting(DUMMY_PAGE)); verifyLinks.fIsforeignHost = false; assertTrue("not onsiteonly and same host: don't stop", !verifyLinks.stopHunting(DUMMY_PAGE)); } private static void checkLinkCountText(final String text, final int expectedLinkCount) { ContextStub context = new ContextStub(text); assertEquals(expectedLinkCount, VerifyLinks.getLinkCount((HtmlPage) context.getCurrentResponse())); } private void checkLinkCountDummyPage(final String content, final int expectedLinkCount) { final ContextStub context = new ContextStub(); final String htmlContent = wrapContent(content); final HtmlPage page = getDummyPage(htmlContent); context.saveResponseAsCurrent(page); assertEquals(expectedLinkCount, VerifyLinks.getLinkCount(page)); } public void testNbLinksInReport() throws Exception { final String content = "<a href='page2.html'>dummy</a>" + "<a href='page3.html'>dummy</a>" + "<a href='/trafficlight.html'>dummy</a>" + "<a href='https://foo/secure.html'>secure</a>"; final MockWebConnection webConnection = (MockWebConnection) getContext().getWebClient().getWebConnection(); webConnection.setDefaultResponse("<html></html>"); final URL startUrl = new URL("http://www.foo.foo/"); webConnection.setResponse(startUrl, wrapContent(content)); getContext().getWebClient().getPage(startUrl); final VerifyLinks step = (VerifyLinks) getStep(); step.setDepth("5"); step.execute(); final Map properties = step.getParameterDictionary(); assertEquals("4", properties.get("-> valid links")); } public void testNotBrokenStateRequest() { assertNotBrokenStateWithHttpReturnCode(399, true); assertNotBrokenStateWithHttpReturnCode(400, false); assertNotBrokenStateWithHttpReturnCode(401, false); assertNotBrokenStateWithHttpReturnCode(HttpURLConnection.HTTP_BAD_GATEWAY, false); assertNotBrokenStateWithHttpReturnCode(HttpURLConnection.HTTP_BAD_REQUEST, false); assertNotBrokenStateWithHttpReturnCode(HttpURLConnection.HTTP_NOT_FOUND, false); assertNotBrokenStateWithHttpReturnCode(HttpURLConnection.HTTP_OK, true); } public void testNotBrokenStateWithException() { assertNotBrokenStateWithException(THROW_MALFORMEDURLEXCEPTION, false); assertNotBrokenStateWithException(THROW_IOEXCEPTION, false); } private void assertNotBrokenStateWithHttpReturnCode(int returncode, boolean expected) { assertNotBrokenState(returncode, THROW_NO_EXCEPTION, expected); } private void assertNotBrokenStateWithException(int exceptionCode, boolean expected) { assertNotBrokenState(0, exceptionCode, expected); } private void assertNotBrokenState(int returncode, int exceptionCode, boolean expected) { final VerifyLinks step = (VerifyLinks) createAndConfigureStep(); final HtmlPage page = getResponseForText(samplePageWithAnchor()); final WebClient badClient = makeClient(returncode, exceptionCode); getContext().setWebClient(badClient); step.checkVisits(badClient, page); assertEquals(expected, step.getFailedVisits().size() == 0); } private static WebClient makeClient(final int httpReturnCode, final int exceptionCode) { return new WebClient() { @SuppressWarnings("unchecked") public Page getPage(final URL url) throws IOException, FailingHttpStatusCodeException { switch (exceptionCode) { case THROW_MALFORMEDURLEXCEPTION: throw new MalformedURLException(); case THROW_IOEXCEPTION: throw new IOException(); default: } if (httpReturnCode < 400) { return super.getPage(WebClient.URL_ABOUT_BLANK); } final List<NameValuePair> emptyList = Collections.emptyList(); final WebResponseData responseData = new WebResponseData(new byte[]{}, httpReturnCode, "blah", emptyList); WebResponse webResponse = new WebResponse(responseData, url, HttpMethod.GET, 1); throw new FailingHttpStatusCodeException(webResponse); } }; } private static String samplePageWithAnchor() { return wrapContent(anchor(DUMMY_HREF, DUMMY_LABEL)); } private static String anchor(String href, String label) { return "<A HREF=\"" + href + "\">" + label + "</A>"; } private static String wrapContent(String content) { return "<html><head><title>foo</title></head><body>" + content + "</body></html>"; } public void testJSErrorsOnForeignPages() throws Exception { final String htmlContent = wrapContent("<a href='http://htmlunit.sf.net/jserror.html'>other</a>"); final HtmlPage page = getDummyPage(htmlContent); getContext().saveResponseAsCurrent(page); final WebClient webClient = page.getWebClient(); final MockWebConnection mockConnection = new MockWebConnection(); mockConnection.setResponse(new URL("http://htmlunit.sf.net/jserror.html"), "<html><body onload='alert('></body></html>"); webClient.setWebConnection(mockConnection); final VerifyLinks step = (VerifyLinks) getStep(); // throws exception as default is false ThrowAssert.assertThrows(ScriptException.class, getExecuteStepTestBlock()); // ignore exceptions step.setIgnoreForeignJSErrors(true); executeStep(step); } }