From d9eb302325d7353ef62d00dc1e8a96d38596d885 Mon Sep 17 00:00:00 2001 From: Ivan Kalinin Date: Wed, 24 Aug 2022 23:17:13 +0300 Subject: [PATCH] [plugin-web-app] Add step waiting for the console message --- .../modules/plugins/pages/plugin-web-app.adoc | 22 +++++ .../plugins/partials/selenium-properties.adoc | 10 +++ .../steps/ui/web/JsValidationSteps.java | 90 +++++++++++++++++-- .../steps/ui/web/JsValidationStepsTests.java | 62 +++++++++++-- .../story/integration/Browser Console.story | 9 ++ 5 files changed, 177 insertions(+), 16 deletions(-) diff --git a/docs/modules/plugins/pages/plugin-web-app.adoc b/docs/modules/plugins/pages/plugin-web-app.adoc index 2b8c8d7c82..c07529cd51 100644 --- a/docs/modules/plugins/pages/plugin-web-app.adoc +++ b/docs/modules/plugins/pages/plugin-web-app.adoc @@ -1193,3 +1193,25 @@ Then there are browser console $logLevels by regex '$pattern' Given I am on a page with the URL 'https://vividus-test-site.herokuapp.com/' Then there are browser console ERRORS by regex '.*user.*' ---- + +==== Wait for console log entries and save them + +Waits for the appearance of the console log entries with the expected level and which match regular expression and saves all the entries (including awaited ones) of the expected level gathered during the wait to the scoped variable. + +NOTE: Wait uses generic UI timeouts specified by the properties `ui.wait.timeout` and `ui.wait.polling-period`. See <<_properties>> section for more details. + +[source,gherkin] +---- +When I wait until browser console $logEntries by regex `$regex` appear and save all entries into $scopes variable `$variableName` +---- +* `$logLevels` - {log-levels} +* `$pattern` - The regular expression to match log entry messages. +* `$scopes` - xref:commons:variables.adoc#_scopes[The comma-separated set of the variables scopes]. +* `$variableName` - The name of the variable to save the value of the barcode. + +.Wait for application readiness +---- +Given I am on a page with the URL 'https://vividus-test-site.herokuapp.com/' +When I wait until browser console infos by regex `.*Application ready.*` appear and save all entries into scenario variable `logs` +Then `${logs}` matches `.*Application ready in \d+ seconds.*` +---- diff --git a/docs/modules/plugins/partials/selenium-properties.adoc b/docs/modules/plugins/partials/selenium-properties.adoc index 3a81097a75..ee3765ea8a 100644 --- a/docs/modules/plugins/partials/selenium-properties.adoc +++ b/docs/modules/plugins/partials/selenium-properties.adoc @@ -39,3 +39,13 @@ If the value is set to `true`, an attempt to reset context will be performed whe |password |`` |Password to be used to create a new session + +|`ui.wait.timeout` +||{iso-date-format-link} format +|`PT1M` +|Total duration to wait for UI condition + +|`ui.wait.polling-period` +||{iso-date-format-link} format +|`PT2S` +|Used together with `ui.wait.timeout`. Total duration of time between two iterations of UI condition check diff --git a/vividus-plugin-web-app/src/main/java/org/vividus/steps/ui/web/JsValidationSteps.java b/vividus-plugin-web-app/src/main/java/org/vividus/steps/ui/web/JsValidationSteps.java index 71f21e6a32..3dca30cbae 100644 --- a/vividus-plugin-web-app/src/main/java/org/vividus/steps/ui/web/JsValidationSteps.java +++ b/vividus-plugin-web-app/src/main/java/org/vividus/steps/ui/web/JsValidationSteps.java @@ -16,31 +16,49 @@ package org.vividus.steps.ui.web; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Collectors; -import javax.inject.Inject; - import org.jbehave.core.annotations.Then; +import org.jbehave.core.annotations.When; import org.openqa.selenium.WebDriver; import org.openqa.selenium.logging.LogEntry; +import org.vividus.context.VariableContext; import org.vividus.reporter.event.IAttachmentPublisher; import org.vividus.selenium.IWebDriverProvider; import org.vividus.selenium.logging.BrowserLogLevel; import org.vividus.selenium.logging.BrowserLogManager; import org.vividus.softassert.ISoftAssert; +import org.vividus.ui.action.WaitActions; +import org.vividus.ui.action.WaitResult; +import org.vividus.variable.VariableScope; public class JsValidationSteps { - @Inject private IWebDriverProvider webDriverProvider; - @Inject private IAttachmentPublisher attachmentPublisher; - @Inject private ISoftAssert softAssert; + private final IWebDriverProvider webDriverProvider; + private final ISoftAssert softAssert; + private final IAttachmentPublisher attachmentPublisher; + private final WaitActions waitActions; + private final VariableContext variableContext; private boolean includeBrowserExtensionLogEntries; + public JsValidationSteps(IWebDriverProvider webDriverProvider, ISoftAssert softAssert, + IAttachmentPublisher attachmentPublisher, WaitActions waitActions, VariableContext variableContext) + { + this.webDriverProvider = webDriverProvider; + this.softAssert = softAssert; + this.attachmentPublisher = attachmentPublisher; + this.waitActions = waitActions; + this.variableContext = variableContext; + } + /** * Checks that opened page contains JavaScript browser console logs that matches regex. *

Note that log buffers are reset after step invocation, meaning that available log entries correspond to those @@ -94,6 +112,54 @@ public void checkJsLogEntriesOnOpenedPageFilteredByRegExp(List checkLogMessagesAbsence(getLogEntries(logEntries, regex), logEntries); } + /** + * Waits for the appearance of the console log entries with the expected level and which match regular expression + * and saves all the entries (including awaited ones) of the expected level gathered during the wait to the + * scoped variable. + * + * @param logEntries Comma-separated list of entries to check. Possible values: "errors", "warnings", "infos". + * @param regex Regular expression to filter log entries. + * @param scopes The set (comma-separated list of scopes e.g.: STORY, NEXT_BATCHES) of variable's scope
+ * Available scopes: + *

. + * @param variableName The name of the variable to save the SFTP commands execution result. + */ + @When("I wait until browser console $logEntries by regex `$regex` appear and save all entries into $scopes " + + "variable `$variableName`") + public void waitForMessageAndSave(List logEntries, Pattern regex, Set scopes, + String variableName) + { + WebDriver webDriver = webDriverProvider.get(); + List messages = new ArrayList<>(); + WaitResult waitResult = waitActions.wait(webDriver, new Function<>() + { + @Override + public Boolean apply(WebDriver driver) + { + Set newMessages = getLogEntries(logEntries, driver); + messages.addAll(newMessages); + return newMessages.stream().map(LogEntry::getMessage).anyMatch(regex.asMatchPredicate()); + } + + @Override + public String toString() + { + return String.format("appearance of JavaScript %s matching `%s` regex", + JsValidationSteps.toString(logEntries), regex); + } + }); + if (waitResult.isWaitPassed()) + { + variableContext.putVariable(scopes, variableName, messages); + } + publishAttachment(webDriver, messages); + } + private void checkLogMessagesAbsence(Set logEntries, List logLevels) { softAssert.assertEquals("Current page contains no JavaScript " + toString(logLevels), 0, @@ -103,23 +169,29 @@ private void checkLogMessagesAbsence(Set logEntries, List getLogEntries(List logEntries, Predicate filter) { WebDriver webDriver = webDriverProvider.get(); - Set filteredLogEntries = BrowserLogManager.getFilteredLog(webDriver, logEntries).stream() + Set filteredLogEntries = getLogEntries(logEntries, webDriver).stream() .filter(filter::test) .collect(Collectors.toSet()); - publishAttachment(Map.of(webDriver.getCurrentUrl(), filteredLogEntries)); + publishAttachment(webDriver, filteredLogEntries); return filteredLogEntries; } + private Set getLogEntries(List logEntries, WebDriver webDriver) + { + return BrowserLogManager.getFilteredLog(webDriver, logEntries); + } + private Set getLogEntries(List logEntries, Pattern regex) { return getLogEntries(logEntries, message -> regex.matcher(message.getMessage()).matches()); } - private void publishAttachment(Map> results) + private void publishAttachment(WebDriver webDriver, Collection filteredLogEntries) { attachmentPublisher.publishAttachment("/org/vividus/steps/ui/web/js-console-validation-result-table.ftl", - Map.of("results", results), "JavaScript console validation results"); + Map.of("results", Map.of(webDriver.getCurrentUrl(), filteredLogEntries)), + "JavaScript console validation results"); } private static String toString(List logLevels) diff --git a/vividus-plugin-web-app/src/test/java/org/vividus/steps/ui/web/JsValidationStepsTests.java b/vividus-plugin-web-app/src/test/java/org/vividus/steps/ui/web/JsValidationStepsTests.java index 06da943b88..59121292dc 100644 --- a/vividus-plugin-web-app/src/test/java/org/vividus/steps/ui/web/JsValidationStepsTests.java +++ b/vividus-plugin-web-app/src/test/java/org/vividus/steps/ui/web/JsValidationStepsTests.java @@ -17,12 +17,16 @@ package org.vividus.steps.ui.web; import static java.util.Collections.singletonList; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -32,9 +36,12 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.mockito.InOrder; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.openqa.selenium.HasCapabilities; import org.openqa.selenium.WebDriver; @@ -43,10 +50,14 @@ import org.openqa.selenium.logging.LogEntry; import org.openqa.selenium.logging.LogType; import org.openqa.selenium.logging.Logs; +import org.vividus.context.VariableContext; import org.vividus.reporter.event.IAttachmentPublisher; import org.vividus.selenium.IWebDriverProvider; import org.vividus.selenium.logging.BrowserLogLevel; import org.vividus.softassert.ISoftAssert; +import org.vividus.ui.action.WaitActions; +import org.vividus.ui.action.WaitResult; +import org.vividus.variable.VariableScope; @ExtendWith(MockitoExtension.class) class JsValidationStepsTests @@ -67,13 +78,19 @@ class JsValidationStepsTests @Mock private ISoftAssert softAssert; + @Mock + private WaitActions waitActions; + + @Mock + private VariableContext variableContext; + @InjectMocks private JsValidationSteps jsValidationSteps; @Test void testCheckThereAreLogEntriesOnOpenedPageFilteredByRegExp() { - Map> expectedResults = testCheckJsErrors(ERROR_MESSAGE, () -> jsValidationSteps + Map> expectedResults = testCheckJsErrors(ERROR_MESSAGE, () -> jsValidationSteps .checkThereAreLogEntriesOnOpenedPageFilteredByRegExp(singletonList(BrowserLogLevel.ERRORS), ERROR_MESSAGE_PATTERN)); InOrder inOrder = inOrder(attachmentPublisher, softAssert); @@ -85,7 +102,7 @@ void testCheckThereAreLogEntriesOnOpenedPageFilteredByRegExp() @Test void testCheckJsErrorsOnPage() { - Map> expectedResults = testCheckJsErrors(ERROR_MESSAGE, + Map> expectedResults = testCheckJsErrors(ERROR_MESSAGE, () -> jsValidationSteps.checkJsLogEntriesOnOpenedPage(singletonList(BrowserLogLevel.ERRORS))); verifyTestActions(expectedResults, ERRORS_ASSERTION_DESCRIPTION); } @@ -103,7 +120,7 @@ void testCheckJsErrorsOnPageByRegExpNoMatch() void testCheckJsErrorsAndWarningsOnPage() { List logLevels = Arrays.asList(BrowserLogLevel.ERRORS, BrowserLogLevel.WARNINGS); - Map> expectedResults = testCheckJsErrors(ERROR_MESSAGE, + Map> expectedResults = testCheckJsErrors(ERROR_MESSAGE, () -> jsValidationSteps.checkJsLogEntriesOnOpenedPage(logLevels)); verifyTestActions(expectedResults, "Current page contains no JavaScript errors, warnings"); } @@ -112,7 +129,7 @@ void testCheckJsErrorsAndWarningsOnPage() void testCheckJsErrorsContainsBrowserExtensionErrors() { jsValidationSteps.setIncludeBrowserExtensionLogEntries(true); - Map> expectedResults = testCheckJsErrors(EXTENSION + ERROR_MESSAGE, + Map> expectedResults = testCheckJsErrors(EXTENSION + ERROR_MESSAGE, () -> jsValidationSteps.checkJsLogEntriesOnOpenedPage(singletonList(BrowserLogLevel.ERRORS))); verifyTestActions(expectedResults, ERRORS_ASSERTION_DESCRIPTION); } @@ -126,7 +143,38 @@ void testCheckJsErrorsExcludesBrowserExtensionErrors() verifyTestActions(Map.of(URL, Collections.emptySet()), ERRORS_ASSERTION_DESCRIPTION); } - private Map> testCheckJsErrors(String logErrorMessage, Runnable action) + @ParameterizedTest + @CsvSource({"true, 1", "false, 0"}) + void shouldWaitForMessagesAndSaveResultIntoScopedVariable(boolean waitPassed, int savesVariable) + { + var webDriver = mock(WebDriver.class, withSettings().extraInterfaces(HasCapabilities.class)); + when(webDriverProvider.get()).thenReturn(webDriver); + when(webDriver.getCurrentUrl()).thenReturn(URL); + var options = mock(Options.class); + when(webDriver.manage()).thenReturn(options); + var logs = mock(Logs.class); + when(options.logs()).thenReturn(logs); + var severeEntry = new LogEntry(Level.SEVERE, 1L, "severe"); + var infoEntry = new LogEntry(Level.INFO, 1L, "info"); + when(logs.get(LogType.BROWSER)).thenReturn(new LogEntries(List.of(severeEntry)), + new LogEntries(List.of(infoEntry))); + var result = new WaitResult<>(); + result.setWaitPassed(waitPassed); + when(waitActions.wait(eq(webDriver), argThat(f -> { + f.apply(webDriver); + f.apply(webDriver); + return "appearance of JavaScript infos matching `.*info.*` regex".equals(f.toString()); + }))).thenReturn(result); + var variableName = "variableName"; + var scopes = Set.of(VariableScope.SCENARIO); + jsValidationSteps.waitForMessageAndSave(List.of(BrowserLogLevel.INFOS), Pattern.compile(".*info.*"), scopes, + variableName); + var ordered = Mockito.inOrder(attachmentPublisher, variableContext); + ordered.verify(variableContext, times(savesVariable)).putVariable(scopes, variableName, List.of(infoEntry)); + verifyAttachmentPublisher(Map.of(URL, List.of(infoEntry)), ordered); + } + + private Map> testCheckJsErrors(String logErrorMessage, Runnable action) { LogEntry entry = mockGetLogEntry(logErrorMessage); action.run(); @@ -149,7 +197,7 @@ private LogEntry mockGetLogEntry(String logErrorMessage) return severeEntry; } - private void verifyTestActions(Map> expectedResults, String assertionDescription) + private void verifyTestActions(Map> expectedResults, String assertionDescription) { InOrder inOrder = inOrder(attachmentPublisher, softAssert); verifyAttachmentPublisher(expectedResults, inOrder); @@ -157,7 +205,7 @@ private void verifyTestActions(Map> expectedResults, Strin expectedResults.entrySet().iterator().next().getValue().size()); } - private void verifyAttachmentPublisher(Map> expectedResults, InOrder order) + private void verifyAttachmentPublisher(Map> expectedResults, InOrder order) { order.verify(attachmentPublisher).publishAttachment( "/org/vividus/steps/ui/web/js-console-validation-result-table.ftl", diff --git a/vividus-tests/src/main/resources/story/integration/Browser Console.story b/vividus-tests/src/main/resources/story/integration/Browser Console.story index 3aabcbd03a..33ecae73f6 100644 --- a/vividus-tests/src/main/resources/story/integration/Browser Console.story +++ b/vividus-tests/src/main/resources/story/integration/Browser Console.story @@ -20,3 +20,12 @@ Then there are no browser console ERRORS Scenario: Verify step: Then there are no browser console $logEntries by regex '$regex' When I execute javascript `console.error('error')` with arguments: Then there are no browser console ERRORS by regex '.*message.*' + + +Scenario: Verify step: When I wait until browser console $logEntries by regex `$regex` appear and save all entries into $scopes variable `$variable` +When I execute javascript `console.log('immediate')` with arguments: +When I execute javascript `setTimeout(() => console.log("delayed"), 5000);` with arguments: +When I wait until browser console infos by regex `.*delayed.*` appear and save all entries into scenario variable `logs` +Then there are no browser console INFOS by regex '.*immediate.*' +Then `${logs}` matches `.*immediate.*` +Then `${logs}` matches `.*delayed.*`