diff --git a/docs/modules/plugins/pages/plugin-web-app-to-rest-api.adoc b/docs/modules/plugins/pages/plugin-web-app-to-rest-api.adoc index 09849f2510..d23aa68ce9 100644 --- a/docs/modules/plugins/pages/plugin-web-app-to-rest-api.adoc +++ b/docs/modules/plugins/pages/plugin-web-app-to-rest-api.adoc @@ -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. diff --git a/vividus-plugin-html/src/main/java/org/vividus/html/HtmlLocatorType.java b/vividus-plugin-html/src/main/java/org/vividus/html/HtmlLocatorType.java index ae639408a5..fed3739375 100644 --- a/vividus-plugin-html/src/main/java/org/vividus/html/HtmlLocatorType.java +++ b/vividus-plugin-html/src/main/java/org/vividus/html/HtmlLocatorType.java @@ -18,7 +18,6 @@ import java.util.function.BiFunction; -import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.select.Elements; @@ -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() diff --git a/vividus-plugin-html/src/main/java/org/vividus/html/JsoupUtils.java b/vividus-plugin-html/src/main/java/org/vividus/html/JsoupUtils.java new file mode 100644 index 0000000000..1632b16b4e --- /dev/null +++ b/vividus-plugin-html/src/main/java/org/vividus/html/JsoupUtils.java @@ -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, ""); + } +} diff --git a/vividus-plugin-html/src/main/java/org/vividus/steps/html/HtmlSteps.java b/vividus-plugin-html/src/main/java/org/vividus/steps/html/HtmlSteps.java index cfb9ade560..edea1096b2 100644 --- a/vividus-plugin-html/src/main/java/org/vividus/steps/html/HtmlSteps.java +++ b/vividus-plugin-html/src/main/java/org/vividus/steps/html/HtmlSteps.java @@ -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; @@ -42,7 +43,7 @@ public HtmlSteps(ISoftAssert softAssert, VariableContext variableContext) private Optional 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()); @@ -74,7 +75,7 @@ private Optional 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); } diff --git a/vividus-plugin-html/src/test/java/org/vividus/html/HtmlLocatorTypeTests.java b/vividus-plugin-html/src/test/java/org/vividus/html/HtmlLocatorTypeTests.java index 8658a1d37c..64fd241a8b 100644 --- a/vividus-plugin-html/src/test/java/org/vividus/html/HtmlLocatorTypeTests.java +++ b/vividus-plugin-html/src/test/java/org/vividus/html/HtmlLocatorTypeTests.java @@ -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 = """ - - - - Title of the document - - - """; - - @Test - void shouldGetElementsBySelectorHtmlWithBaseUrl() - { - try (MockedStatic 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 = """ + + + + Title of the document + + + """; + assertEquals(1, locatorType.findElements(JsoupUtils.getDocument(html), locator).size()); } @ParameterizedTest diff --git a/vividus-plugin-html/src/test/java/org/vividus/html/JsoupUtilsTests.java b/vividus-plugin-html/src/test/java/org/vividus/html/JsoupUtilsTests.java new file mode 100644 index 0000000000..c4e2cc5f11 --- /dev/null +++ b/vividus-plugin-html/src/test/java/org/vividus/html/JsoupUtilsTests.java @@ -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 = ""; + try (MockedStatic 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(); + } + } +} diff --git a/vividus-plugin-web-app-to-rest-api/src/main/java/org/vividus/steps/integration/ResourceCheckSteps.java b/vividus-plugin-web-app-to-rest-api/src/main/java/org/vividus/steps/integration/ResourceCheckSteps.java index 1dc65e71a0..0f78b7965a 100644 --- a/vividus-plugin-web-app-to-rest-api/src/main/java/org/vividus/steps/integration/ResourceCheckSteps.java +++ b/vividus-plugin-web-app-to-rest-api/src/main/java/org/vividus/steps/integration/ResourceCheckSteps.java @@ -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; @@ -56,6 +58,7 @@ public class ResourceCheckSteps private static final Set 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 resourceValidator; private final AttachmentPublisher attachmentPublisher; @@ -106,7 +109,9 @@ public void checkResources(HtmlLocatorType htmlLocatorType, String htmlLocator, { softAssert.runIgnoringTestFailFast(() -> execute(() -> { - Collection resourcesToValidate = htmlLocatorType.findElements(html, htmlLocator); + Document document = JsoupUtils.getDocument(html); + Collection resourcesToValidate = htmlLocatorType.findElements(document, htmlLocator); + boolean contextCheck = document.head().getElementsByTag(HTML_TITLE_TAG).isEmpty(); Stream validations = createResourceValidations(resourcesToValidate, resourceValidation -> { URI uriToCheck = resourceValidation.getUriOrError().getLeft(); @@ -120,7 +125,7 @@ public void checkResources(HtmlLocatorType htmlLocatorType, String htmlLocator, resourceValidation.setError(message); resourceValidation.setCheckStatus(CheckStatus.BROKEN); } - }); + }, contextCheck); validateResources(validations); })); } @@ -140,17 +145,17 @@ private WebPageResourceValidation validate(WebPageResourceValidation r) } private Stream createResourceValidations(Collection elements, - Consumer resourceValidator) + Consumer 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 parseElement(Element element) + private Optional parseElement(Element element, boolean contextCheck) { String elementUriAsString = getElementUri(element).trim(); if (elementUriAsString.startsWith("data:")) @@ -186,6 +191,16 @@ private Optional 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)); @@ -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))); } diff --git a/vividus-plugin-web-app-to-rest-api/src/test/java/org/vividus/steps/integration/ResourceCheckStepsTests.java b/vividus-plugin-web-app-to-rest-api/src/test/java/org/vividus/steps/integration/ResourceCheckStepsTests.java index 4689733b21..95a5d0f38c 100644 --- a/vividus-plugin-web-app-to-rest-api/src/test/java/org/vividus/steps/integration/ResourceCheckStepsTests.java +++ b/vividus-plugin-web-app-to-rest-api/src/test/java/org/vividus/steps/integration/ResourceCheckStepsTests.java @@ -573,6 +573,37 @@ void shouldFilterResourceByRegExpCheckDesiredResourcesAnPostAttachment() }), eq(REPORT_NAME)); } + @Test + void shouldFilterJumpLinkDuringContextValidation() throws InterruptedException, ExecutionException + { + String contextHtml = "Jump link"; + 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 validationsToReport = ((Map>) m) + .get(RESULTS); + Iterator resourceValidations = validationsToReport.iterator(); + WebPageResourceValidation validation = resourceValidations.next(); + Pair 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 -> { diff --git a/vividus-tests/src/main/resources/story/integration/ResourceCheckSteps.story b/vividus-tests/src/main/resources/story/integration/ResourceCheckSteps.story index 4046d4f633..e2f3b7dd7f 100644 --- a/vividus-tests/src/main/resources/story/integration/ResourceCheckSteps.story +++ b/vividus-tests/src/main/resources/story/integration/ResourceCheckSteps.story @@ -28,6 +28,10 @@ Given I am on page with URL `` 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