Skip to content

Commit

Permalink
[plugin-web-app-to-rest-api] Filter invalid jump links from context r…
Browse files Browse the repository at this point in the history
…esource validation
  • Loading branch information
web-flow committed Jan 30, 2024
1 parent ddb95f2 commit 5236744
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 56 deletions.
3 changes: 2 additions & 1 deletion docs/modules/plugins/pages/plugin-web-app-to-rest-api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,8 @@ a|Reasons:

* the resource path matches the patterns specified by the `resource-checker.uri-to-ignore-regex` property;
* the resource path is equal to `#` (anchor);
* the resource is not a HTTP(S) resource.
* the resource is not a HTTP(S) resource;
* the resource is jump link which cannot be verified from the current context (if only part of the document is checked).

|`SKIPPED`
|A resource validation has already been performed, i.e. if the same resource might be present on several pages so we do not need to validate it twice.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import java.util.function.BiFunction;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;

Expand All @@ -36,14 +35,9 @@ public enum HtmlLocatorType
this.finder = finder;
}

public Elements findElements(String html, String locator)
public Elements findElements(Document document, String locator)
{
return findElements("", html, locator);
}

public Elements findElements(String baseUri, String html, String locator)
{
return finder.apply(Jsoup.parse(html, baseUri), locator);
return finder.apply(document, locator);
}

public String getDescription()
Expand Down
37 changes: 37 additions & 0 deletions vividus-plugin-html/src/main/java/org/vividus/html/JsoupUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2019-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.vividus.html;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

public final class JsoupUtils
{
private JsoupUtils()
{
}

public static Document getDocument(String html, String baseUri)
{
return Jsoup.parse(html, baseUri);
}

public static Document getDocument(String html)
{
return getDocument(html, "");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.jsoup.select.Elements;
import org.vividus.context.VariableContext;
import org.vividus.html.HtmlLocatorType;
import org.vividus.html.JsoupUtils;
import org.vividus.softassert.ISoftAssert;
import org.vividus.steps.ComparisonRule;
import org.vividus.variable.VariableScope;
Expand All @@ -42,7 +43,7 @@ public HtmlSteps(ISoftAssert softAssert, VariableContext variableContext)

private Optional<Element> assertElementByLocatorExists(String html, HtmlLocatorType locatorType, String locator)
{
Elements elements = locatorType.findElements(html, locator);
Elements elements = locatorType.findElements(JsoupUtils.getDocument(html), locator);
if (assertElements(locatorType, locator, ComparisonRule.EQUAL_TO, 1, elements))
{
return Optional.of(elements.first());
Expand Down Expand Up @@ -74,7 +75,7 @@ private Optional<Element> assertElementByLocatorExists(String html, HtmlLocatorT
public boolean doesElementByLocatorExist(HtmlLocatorType htmlLocatorType,
String htmlLocator, String html, ComparisonRule comparisonRule, int number)
{
Elements elements = htmlLocatorType.findElements(html, htmlLocator);
Elements elements = htmlLocatorType.findElements(JsoupUtils.getDocument(html), htmlLocator);
return assertElements(htmlLocatorType, htmlLocator, comparisonRule, number, elements);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,54 +17,25 @@
package org.vividus.html;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.MockedStatic;

class HtmlLocatorTypeTests
{
private static final String TITLE = "title";
private static final String HTML = """
<!DOCTYPE html>
<html>
<head>
<title>Title of the document</title>
</head>
<body />
</html>""";

@Test
void shouldGetElementsBySelectorHtmlWithBaseUrl()
{
try (MockedStatic<Jsoup> jsoup = mockStatic(Jsoup.class))
{
String baseUri = "base-uri";
Document document = mock();
Elements elements = mock();

jsoup.when(() -> Jsoup.parse(HTML, baseUri)).thenReturn(document);
when(document.select(TITLE)).thenReturn(elements);

assertEquals(elements, HtmlLocatorType.CSS_SELECTOR.findElements(baseUri, HTML, TITLE));

jsoup.verify(() -> Jsoup.parse(HTML, baseUri));
jsoup.verifyNoMoreInteractions();
}
}

@ParameterizedTest
@CsvSource({"CSS_SELECTOR, body", "XPATH, //body"})
void shouldFindElement(HtmlLocatorType locatorType, String locator)
{
assertEquals(1, locatorType.findElements(HTML, locator).size());
String html = """
<!DOCTYPE html>
<html>
<head>
<title>Title of the document</title>
</head>
<body />
</html>""";
assertEquals(1, locatorType.findElements(JsoupUtils.getDocument(html), locator).size());
}

@ParameterizedTest
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2019-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.vividus.html;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;

import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;

class JsoupUtilsTests
{
@Test
void shouldGetDocumentFromHtml()
{
String html = "<html></html>";
try (MockedStatic<Jsoup> jsoup = mockStatic(Jsoup.class))
{
Document document = mock();
jsoup.when(() -> Jsoup.parse(html, StringUtils.EMPTY)).thenReturn(document);

assertEquals(document, JsoupUtils.getDocument(html));
jsoup.verify(() -> Jsoup.parse(html, StringUtils.EMPTY));
jsoup.verifyNoMoreInteractions();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@
import org.apache.hc.core5.net.URIBuilder;
import org.jbehave.core.annotations.Then;
import org.jbehave.core.model.ExamplesTable;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Selector.SelectorParseException;
import org.vividus.html.HtmlLocatorType;
import org.vividus.html.JsoupUtils;
import org.vividus.http.HttpMethod;
import org.vividus.http.HttpRequestExecutor;
import org.vividus.http.HttpTestContext;
Expand All @@ -56,6 +58,7 @@ public class ResourceCheckSteps
private static final Set<String> ALLOWED_SCHEMES = Set.of("http", "https");
private static final String URL_FRAGMENT = "#";
private static final String HREF_ATTR = "href";
private static final String HTML_TITLE_TAG = "title";

private final ResourceValidator<WebPageResourceValidation> resourceValidator;
private final AttachmentPublisher attachmentPublisher;
Expand Down Expand Up @@ -106,7 +109,9 @@ public void checkResources(HtmlLocatorType htmlLocatorType, String htmlLocator,
{
softAssert.runIgnoringTestFailFast(() -> execute(() ->
{
Collection<Element> resourcesToValidate = htmlLocatorType.findElements(html, htmlLocator);
Document document = JsoupUtils.getDocument(html);
Collection<Element> resourcesToValidate = htmlLocatorType.findElements(document, htmlLocator);
boolean contextCheck = document.head().getElementsByTag(HTML_TITLE_TAG).isEmpty();
Stream<WebPageResourceValidation> validations = createResourceValidations(resourcesToValidate,
resourceValidation -> {
URI uriToCheck = resourceValidation.getUriOrError().getLeft();
Expand All @@ -120,7 +125,7 @@ public void checkResources(HtmlLocatorType htmlLocatorType, String htmlLocator,
resourceValidation.setError(message);
resourceValidation.setCheckStatus(CheckStatus.BROKEN);
}
});
}, contextCheck);
validateResources(validations);
}));
}
Expand All @@ -140,17 +145,17 @@ private WebPageResourceValidation validate(WebPageResourceValidation r)
}

private Stream<WebPageResourceValidation> createResourceValidations(Collection<Element> elements,
Consumer<WebPageResourceValidation> resourceValidator)
Consumer<WebPageResourceValidation> resourceValidator, boolean contextCheck)
{
return elements.stream()
.map(this::parseElement)
.map(e -> parseElement(e, contextCheck))
.filter(Optional::isPresent)
.map(Optional::get)
.peek(resourceValidator)
.parallel();
}

private Optional<WebPageResourceValidation> parseElement(Element element)
private Optional<WebPageResourceValidation> parseElement(Element element, boolean contextCheck)
{
String elementUriAsString = getElementUri(element).trim();
if (elementUriAsString.startsWith("data:"))
Expand Down Expand Up @@ -186,6 +191,16 @@ private Optional<WebPageResourceValidation> parseElement(Element element)
&& root.getElementsByAttributeValue("name", fragment).isEmpty();
if (targetNotPresent)
{
if (contextCheck)
{
WebPageResourceValidation contextJumpLinkValidation = new WebPageResourceValidation(
Pair.of(null, String.format(
"Validation of jump link (the target is \"%s\") is skipped as the current "
+ "context is restricted to a portion of the document.",
elementUriAsString)), elementCssSelector);
contextJumpLinkValidation.setCheckStatus(CheckStatus.FILTERED);
return Optional.of(contextJumpLinkValidation);
}
return Optional.of(ResourceValidationError.MISSING_JUMPLINK_TARGET
.onAssertion(softAssert::recordFailedAssertion, elementCssSelector, fragment)
.createValidation(null, elementCssSelector, fragment));
Expand Down Expand Up @@ -319,9 +334,10 @@ public void checkResources(HtmlLocatorType htmlLocatorType, String htmlLocator,
{
httpRequestExecutor.executeHttpRequest(HttpMethod.GET, pageUrl, Optional.empty());
return Optional.ofNullable(httpTestContext.getResponse().getResponseBodyAsString())
.map(response -> htmlLocatorType.findElements(pageUrl, response, htmlLocator))
.map(response -> htmlLocatorType
.findElements(JsoupUtils.getDocument(response, pageUrl), htmlLocator))
.map(elements -> createResourceValidations(elements,
rV -> rV.setPageURL(pageUrl)
rV -> rV.setPageURL(pageUrl), false
))
.orElseGet(() -> Stream.of(createMissingPageBodyValidation(pageUrl)));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,37 @@ void shouldFilterResourceByRegExpCheckDesiredResourcesAnPostAttachment()
}), eq(REPORT_NAME));
}

@Test
void shouldFilterJumpLinkDuringContextValidation() throws InterruptedException, ExecutionException
{
String contextHtml = "<a id='jump-link' href='#section'>Jump link</a>";
mockResourceValidator();
runExecutor();
resourceCheckSteps.setUriToIgnoreRegex(Optional.empty());
resourceCheckSteps.init();
resourceCheckSteps.checkResources(HtmlLocatorType.CSS_SELECTOR, LINK_SELECTOR, contextHtml);

verify(attachmentPublisher).publishAttachment(eq(TEMPLATE_NAME), argThat(m -> {
@SuppressWarnings(UNCHECKED)
Set<WebPageResourceValidation> validationsToReport = ((Map<String, Set<WebPageResourceValidation>>) m)
.get(RESULTS);
Iterator<WebPageResourceValidation> resourceValidations = validationsToReport.iterator();
WebPageResourceValidation validation = resourceValidations.next();
Pair<URI, String> uriOrError = validation.getUriOrError();

Assertions.assertAll(
() -> assertNull(uriOrError.getLeft()),
() -> assertEquals(
"Validation of jump link (the target is \"#section\") is skipped as the current context "
+ "is restricted to a portion of the document.",
uriOrError.getRight()),
() -> assertEquals(JUMP_LINK_SELECTOR, validation.getCssSelector()),
() -> assertSame(CheckStatus.FILTERED, validation.getCheckStatus()),
() -> assertEquals(N_A, validation.getPageURL()));
return true;
}), eq(REPORT_NAME));
}

private void runExecutor() throws InterruptedException, ExecutionException
{
doNothing().when(executor).execute(argThat(r -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ Given I am on page with URL `<pageToValidate>`
Then all resources found by xpath `//a[@href]` in ${${source-code}} are valid
!-- Deprecated
Then all resources by selector `a[href]` from ${source-code} are valid
When I change context to element located by `linkText(Link with tooltip)`
Then all resources by selector `a[href]` from ${context-source-code} are valid

Scenario: Verification of the jump link from context
When I change context to element located by `linkText(Link to unexistent element)`
Then all resources by selector `a[href]` from ${context-source-code} are valid

Expand Down

0 comments on commit 5236744

Please # to comment.