From 3d313ab9ac7069fe5d32e1febbc219603bf90169 Mon Sep 17 00:00:00 2001 From: avinBar Date: Thu, 18 Jul 2024 16:56:53 +0300 Subject: [PATCH] [plugin-web-app-playwright] Add element wait steps (#5224) --- .../pages/plugin-web-app-playwright.adoc | 34 ++----- .../plugins/partials/generic-ui-steps.adoc | 64 +------------ .../partials/ui-wait-element-state-steps.adoc | 60 ++++++++++++ .../ui/web/playwright/action/WaitActions.java | 21 ++++- .../assertions/PlaywrightSoftAssert.java | 11 ++- .../ui/web/playwright/steps/ElementState.java | 83 +++++++++++++++++ .../ui/web/playwright/steps/WaitSteps.java | 74 +++++++++++++-- .../playwright/action/WaitActionsTests.java | 25 ++++- .../playwright/steps/ElementStateTests.java | 81 +++++++++++++++- .../web/playwright/steps/WaitStepsTests.java | 93 ++++++++++++++++--- 10 files changed, 425 insertions(+), 121 deletions(-) create mode 100644 docs/modules/plugins/partials/ui-wait-element-state-steps.adoc diff --git a/docs/modules/plugins/pages/plugin-web-app-playwright.adoc b/docs/modules/plugins/pages/plugin-web-app-playwright.adoc index 21c9f165ef..37d8002db5 100644 --- a/docs/modules/plugins/pages/plugin-web-app-playwright.adoc +++ b/docs/modules/plugins/pages/plugin-web-app-playwright.adoc @@ -250,41 +250,25 @@ include::plugins:partial$ui-size-and-coordinates-steps.adoc[] include::plugins:partial$common-web-app-text-steps.adoc[] -=== Wait for element appearance +include::plugins:partial$ui-wait-element-state-steps.adoc[] -Waits for appearance of the element by the locator. +=== Wait for element state -[source,gherkin] ----- -When I wait until element located by `$locator` appears ----- - -* `$locator` - <<_locator>>. - -.Wait for appearance of the element with the specified id -[source,gherkin] ----- -When I wait until element located by `id(welcome-image)` appears ----- - - -=== Wait for element disappearance - -Waits for disappearance of the element by the locator. - -NOTE: If the element doesn't exist on the page/context, the step will immediately complete successfully. +Waits for an element, located by the specified locator in the given search context, to change to the specified state. [source,gherkin] ---- -When I wait until element located by `$locator` disappears +When I wait until state of element located by `$locator` is $state ---- -* `$locator` - <<_locator>>. +* `$locator` - The <<_locator,locator>> used to find element. +* `$state` - The expected element xref:parameters:state.adoc[state]. -.Wait for disappearance of the element with the specified id +.Click on the button and wait until the element becomes invisible [source,gherkin] ---- -When I wait until element located by `id(welcome-image)` disappears +When I click on element located by `id(signInButton)` +When I wait until state of element located by `id(signInButton):a` is NOT VISIBLE ---- === Wait for expected elements number diff --git a/docs/modules/plugins/partials/generic-ui-steps.adoc b/docs/modules/plugins/partials/generic-ui-steps.adoc index 45ba511cd6..1fb8500a0c 100644 --- a/docs/modules/plugins/partials/generic-ui-steps.adoc +++ b/docs/modules/plugins/partials/generic-ui-steps.adoc @@ -36,69 +36,7 @@ When I take screenshot and save it to folder `$screenshotFilePath` When I take screenshot and save it to file at path `${screenshot-directory}/start-page.png` ---- -=== Wait for element appearance - -Waits for appearance of the element by the locator. - -WARNING: It's forbidden to use <<_visibility_types>> in the locator. - -[source,gherkin] ----- -When I wait until element located by `$locator` appears ----- - -* `$locator` - <<_locator>>. - -.Wait for appearance of the element with the specified name -[source,gherkin] ----- -When I wait until element located by `name(welcome-image)` appears ----- - - -=== Wait for element disappearance - -Waits for disappearance of the element by the locator. - -NOTE: If the element doesn't exist on the page/context, the step will immediately complete successfully. -Checking the element on the page (if needed) should be done in a separate step (e.g. <<_wait_for_element_appearance>> or xref:plugin-html.adoc#_validate_elements[Validate elements]). - -WARNING: It's forbidden to use <<_visibility_types>> in the locator. - -[source,gherkin] ----- -When I wait until element located by `$locator` disappears ----- - -* `$locator` - <<_locator>>. - -.Wait for disappearance of the element with the specified name -[source,gherkin] ----- -When I wait until element located by `name(welcome-image)` disappears ----- - - -=== Wait for element with specified state using polling interval - -Waits for an element with the specified state to be found using the specified timeout with polling interval. - -[source,gherkin] ----- -When I wait `$duration` with `$pollingDuration` polling until element located by `$locator` becomes $state ----- - -* `$duration` - Total duration to wait in the {iso-date-format-link} format. -* `$pollingDuration` - The duration to wait between search retries in the {iso-date-format-link} format. -* `$locator` - The <<_locator,locator>> of the element to wait for state change. -* `$state` - The element xref:parameters:state.adoc[state]. - -.Verify that the element become invisible up to 10 times -[source,gherkin] ----- -When I wait `PT10S` with `PT1S` polling until element located by `id(element-to-hide)` becomes not visible ----- - +include::plugins:partial$ui-wait-element-state-steps.adoc[] === Wait for expected elements number diff --git a/docs/modules/plugins/partials/ui-wait-element-state-steps.adoc b/docs/modules/plugins/partials/ui-wait-element-state-steps.adoc new file mode 100644 index 0000000000..9fcf28982b --- /dev/null +++ b/docs/modules/plugins/partials/ui-wait-element-state-steps.adoc @@ -0,0 +1,60 @@ +=== Wait for element appearance + +Waits for appearance of the element by the locator. + +WARNING: It's forbidden to use <<_visibility_types>> in the locator. + +[source,gherkin] +---- +When I wait until element located by `$locator` appears +---- + +* `$locator` - <<_locator>>. + +.Wait for appearance of the element with the specified name +[source,gherkin] +---- +When I wait until element located by `id(welcome-image)` appears +---- + +=== Wait for element disappearance + +Waits for disappearance of the element by the locator. + +NOTE: If the element doesn't exist on the page/context, the step will immediately complete successfully. +Checking the element on the page (if needed) should be done in a separate step (e.g. <<_wait_for_element_appearance>> or xref:plugin-html.adoc#_validate_elements[Validate elements]). + +WARNING: It's forbidden to use <<_visibility_types>> in the locator. + +[source,gherkin] +---- +When I wait until element located by `$locator` disappears +---- + +* `$locator` - <<_locator>>. + +.Wait for disappearance of the element with the specified name +[source,gherkin] +---- +When I wait until element located by `id(welcome-image)` disappears +---- + +=== Wait for element with specified state using polling interval + +Waits for an element with the specified state to be found using the specified timeout with polling interval. + +[source,gherkin] +---- +When I wait `$duration` with `$pollingDuration` polling until element located by `$locator` becomes $state +---- + +* `$duration` - Total duration to wait in the {iso-date-format-link} format. +* `$pollingDuration` - The duration to wait between search retries in the {iso-date-format-link} format. +* `$locator` - The <<_locator,locator>> of the element to wait for state change. +* `$state` - The element xref:parameters:state.adoc[state]. + +.Verify that the element become invisible up to 10 times +[source,gherkin] +---- +When I wait `PT10S` with `PT1S` polling until element located by `id(element-to-hide)` becomes not visible +---- diff --git a/vividus-plugin-web-app-playwright/src/main/java/org/vividus/ui/web/playwright/action/WaitActions.java b/vividus-plugin-web-app-playwright/src/main/java/org/vividus/ui/web/playwright/action/WaitActions.java index e0ba90c161..9375791861 100644 --- a/vividus-plugin-web-app-playwright/src/main/java/org/vividus/ui/web/playwright/action/WaitActions.java +++ b/vividus-plugin-web-app-playwright/src/main/java/org/vividus/ui/web/playwright/action/WaitActions.java @@ -21,14 +21,20 @@ import com.microsoft.playwright.TimeoutError; import org.vividus.softassert.ISoftAssert; +import org.vividus.ui.web.playwright.assertions.PlaywrightSoftAssert; public class WaitActions { + private static final String PASSED_CONDITION = "Passed wait condition: "; + private static final String FAILED_CONDITION = "Failed wait condition: "; + private final ISoftAssert softAssert; + private final PlaywrightSoftAssert playwrightSoftAssert; - public WaitActions(ISoftAssert softAssert) + public WaitActions(ISoftAssert softAssert, PlaywrightSoftAssert playwrightSoftAssert) { this.softAssert = softAssert; + this.playwrightSoftAssert = playwrightSoftAssert; } public void runWithTimeoutAssertion(String conditionDescription, Runnable timeoutOperation) @@ -41,12 +47,19 @@ public void runWithTimeoutAssertion(Supplier conditionDescription, Runna try { timeoutOperation.run(); - softAssert.recordPassedAssertion("Passed wait condition: " + conditionDescription.get()); + softAssert.recordPassedAssertion(PASSED_CONDITION + conditionDescription.get()); } catch (TimeoutError e) { - softAssert.recordFailedAssertion( - "Failed wait condition: " + conditionDescription.get() + ". " + e.getMessage(), e); + softAssert.recordFailedAssertion(FAILED_CONDITION + conditionDescription.get() + ". " + e.getMessage(), e); } } + + public void runTimeoutPlaywrightAssertion(Supplier conditionDescription, Runnable timeoutAssertOperation) + { + playwrightSoftAssert.runAssertion(() -> FAILED_CONDITION + conditionDescription.get(), () -> { + timeoutAssertOperation.run(); + softAssert.recordPassedAssertion(PASSED_CONDITION + conditionDescription.get()); + }); + } } diff --git a/vividus-plugin-web-app-playwright/src/main/java/org/vividus/ui/web/playwright/assertions/PlaywrightSoftAssert.java b/vividus-plugin-web-app-playwright/src/main/java/org/vividus/ui/web/playwright/assertions/PlaywrightSoftAssert.java index 0874762f6a..ba24589e3b 100644 --- a/vividus-plugin-web-app-playwright/src/main/java/org/vividus/ui/web/playwright/assertions/PlaywrightSoftAssert.java +++ b/vividus-plugin-web-app-playwright/src/main/java/org/vividus/ui/web/playwright/assertions/PlaywrightSoftAssert.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 the original author or authors. + * 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. @@ -16,6 +16,8 @@ package org.vividus.ui.web.playwright.assertions; +import java.util.function.Supplier; + import org.opentest4j.AssertionFailedError; import org.vividus.softassert.ISoftAssert; @@ -29,6 +31,11 @@ public PlaywrightSoftAssert(ISoftAssert softAssert) } public void runAssertion(String messageOnFailure, Runnable assertion) + { + runAssertion(() -> messageOnFailure, assertion); + } + + public void runAssertion(Supplier messageOnFailure, Runnable assertion) { try { @@ -36,7 +43,7 @@ public void runAssertion(String messageOnFailure, Runnable assertion) } catch (AssertionFailedError e) { - softAssert.recordFailedAssertion(messageOnFailure + ". " + e.getMessage(), e); + softAssert.recordFailedAssertion(messageOnFailure.get() + ". " + e.getMessage(), e); } } } diff --git a/vividus-plugin-web-app-playwright/src/main/java/org/vividus/ui/web/playwright/steps/ElementState.java b/vividus-plugin-web-app-playwright/src/main/java/org/vividus/ui/web/playwright/steps/ElementState.java index 2d12666183..8aa31dffdb 100644 --- a/vividus-plugin-web-app-playwright/src/main/java/org/vividus/ui/web/playwright/steps/ElementState.java +++ b/vividus-plugin-web-app-playwright/src/main/java/org/vividus/ui/web/playwright/steps/ElementState.java @@ -29,6 +29,18 @@ public void assertElementState(Locator locator) { PlaywrightLocatorAssertions.assertElementEnabled(locator, false); } + + @Override + public void waitForElementState(Locator locator) + { + PlaywrightLocatorAssertions.assertElementEnabled(locator, true); + } + + @Override + public boolean isElementState(Locator locator) + { + return locator.isEnabled(new Locator.IsEnabledOptions().setTimeout(NO_WAIT_TIMEOUT)); + } }, DISABLED { @@ -37,6 +49,18 @@ public void assertElementState(Locator locator) { PlaywrightLocatorAssertions.assertElementDisabled(locator, false); } + + @Override + public void waitForElementState(Locator locator) + { + PlaywrightLocatorAssertions.assertElementDisabled(locator, true); + } + + @Override + public boolean isElementState(Locator locator) + { + return locator.isDisabled(new Locator.IsDisabledOptions().setTimeout(NO_WAIT_TIMEOUT)); + } }, SELECTED { @@ -45,6 +69,18 @@ public void assertElementState(Locator locator) { PlaywrightLocatorAssertions.assertElementSelected(locator, false); } + + @Override + public void waitForElementState(Locator locator) + { + PlaywrightLocatorAssertions.assertElementSelected(locator, true); + } + + @Override + public boolean isElementState(Locator locator) + { + return isElementSelected(locator); + } }, NOT_SELECTED { @@ -53,6 +89,18 @@ public void assertElementState(Locator locator) { PlaywrightLocatorAssertions.assertElementNotSelected(locator, false); } + + @Override + public void waitForElementState(Locator locator) + { + PlaywrightLocatorAssertions.assertElementNotSelected(locator, true); + } + + @Override + public boolean isElementState(Locator locator) + { + return !isElementSelected(locator); + } }, VISIBLE { @@ -61,6 +109,18 @@ public void assertElementState(Locator locator) { PlaywrightLocatorAssertions.assertElementVisible(locator, false); } + + @Override + public void waitForElementState(Locator locator) + { + PlaywrightLocatorAssertions.assertElementVisible(locator, true); + } + + @Override + public boolean isElementState(Locator locator) + { + return locator.isVisible(); + } }, NOT_VISIBLE { @@ -69,7 +129,30 @@ public void assertElementState(Locator locator) { PlaywrightLocatorAssertions.assertElementHidden(locator, false); } + + @Override + public void waitForElementState(Locator locator) + { + PlaywrightLocatorAssertions.assertElementHidden(locator, true); + } + + @Override + public boolean isElementState(Locator locator) + { + return locator.isHidden(); + } }; + private static final int NO_WAIT_TIMEOUT = 0; + public abstract void assertElementState(Locator locator); + + public abstract void waitForElementState(Locator locator); + + public abstract boolean isElementState(Locator locator); + + private static boolean isElementSelected(Locator locator) + { + return locator.isChecked(new Locator.IsCheckedOptions().setTimeout(NO_WAIT_TIMEOUT)); + } } diff --git a/vividus-plugin-web-app-playwright/src/main/java/org/vividus/ui/web/playwright/steps/WaitSteps.java b/vividus-plugin-web-app-playwright/src/main/java/org/vividus/ui/web/playwright/steps/WaitSteps.java index e238939c51..3e1015f33e 100644 --- a/vividus-plugin-web-app-playwright/src/main/java/org/vividus/ui/web/playwright/steps/WaitSteps.java +++ b/vividus-plugin-web-app-playwright/src/main/java/org/vividus/ui/web/playwright/steps/WaitSteps.java @@ -16,53 +16,64 @@ package org.vividus.ui.web.playwright.steps; +import java.time.Duration; import java.util.function.BooleanSupplier; import java.util.function.Supplier; import com.microsoft.playwright.Locator; import com.microsoft.playwright.Page; -import com.microsoft.playwright.options.WaitForSelectorState; +import org.apache.commons.lang3.Validate; import org.hamcrest.Matcher; import org.jbehave.core.annotations.When; +import org.vividus.softassert.ISoftAssert; import org.vividus.steps.ComparisonRule; import org.vividus.steps.StringComparisonRule; import org.vividus.ui.web.playwright.BrowserContextProvider; import org.vividus.ui.web.playwright.UiContext; import org.vividus.ui.web.playwright.action.WaitActions; import org.vividus.ui.web.playwright.locator.PlaywrightLocator; +import org.vividus.ui.web.playwright.locator.Visibility; +import org.vividus.util.wait.DurationBasedWaiter; public class WaitSteps { private final UiContext uiContext; private final BrowserContextProvider browserContextProvider; private final WaitActions waitActions; + private final ISoftAssert softAssert; - public WaitSteps(UiContext uiContext, BrowserContextProvider browserContextProvider, WaitActions waitActions) + public WaitSteps(UiContext uiContext, BrowserContextProvider browserContextProvider, WaitActions waitActions, + ISoftAssert softAssert) { this.uiContext = uiContext; this.browserContextProvider = browserContextProvider; this.waitActions = waitActions; + this.softAssert = softAssert; } /** * Waits for appearance of an element with the specified locator + * Step supports only VISIBLE elements waiting. If locator will be configured to ALL exception will + * be thrown. * @param locator locator to locate element */ @When("I wait until element located by `$locator` appears") public void waitForElementAppearance(PlaywrightLocator locator) { - waitForElementState(locator, WaitForSelectorState.VISIBLE); + waitForElementStateValidatingVisibility(locator, ElementState.VISIBLE); } /** * Waits for disappearance of an element with the specified locator + * Step supports only VISIBLE elements waiting. If locator will be configured to ALL exception will + * be thrown. * @param locator locator to locate element */ @When("I wait until element located by `$locator` disappears") public void waitForElementDisappearance(PlaywrightLocator locator) { - waitForElementState(locator, WaitForSelectorState.HIDDEN); + waitForElementStateValidatingVisibility(locator, ElementState.NOT_VISIBLE); } /** @@ -106,12 +117,55 @@ public void waitUntilPageTitleIs(StringComparisonRule comparisonRule, String pat waitActions.runWithTimeoutAssertion(assertionMessage, () -> currentPage.waitForCondition(waitCondition)); } - private void waitForElementState(PlaywrightLocator locator, WaitForSelectorState state) + /** + * Waits for an element with the specified locator to take a certain state + * in the specified search context + * @param locator to locate element + * @param state State value of the element + * (Possible values: ENABLED, DISABLED, SELECTED, NOT_SELECTED, VISIBLE, NOT_VISIBLE) + */ + @When("I wait until state of element located by `$locator` is $state") + public void waitForElementState(PlaywrightLocator locator, ElementState state) { - Locator.WaitForOptions waitOption = new Locator.WaitForOptions().setState(state); - String assertionMessage = String.format("element located by '%s' to be %s", locator, - state.toString().toLowerCase()); - waitActions.runWithTimeoutAssertion(assertionMessage, - () -> uiContext.locateElement(locator).waitFor(waitOption)); + Supplier conditionDescription = () -> String.format("element located by `%s` to be %s", locator, state); + waitActions.runTimeoutPlaywrightAssertion(conditionDescription, () -> { + Locator element = uiContext.locateElement(locator); + state.waitForElementState(element); + }); + } + + /** + * Waits duration with pollingDuration until an element by the specified locator + * becomes a state in the specified search context + *
Example:
+ * When I wait 'PT30S' with 'PT10S' polling until element located `id(text)` becomes NOT_VISIBLE + * - wait until all elements with id=text becomes not visible for 30 seconds, polling every 10 seconds + * + * @param duration Total waiting time according to + * ISO 8601 standard + * @param pollingDuration Defines the timeout between attempts according to + * ISO 8601 standard + * @param locator Locator to search for elements + * @param state State value of the element + * (Possible values: ENABLED, DISABLED, SELECTED, NOT_SELECTED, VISIBLE, NOT_VISIBLE) + */ + @When("I wait `$duration` with `$pollingDuration` polling until element located by `$locator` becomes $state") + public void waitDurationWithPollingTillElementState(Duration duration, Duration pollingDuration, + PlaywrightLocator locator, ElementState state) + { + Locator element = uiContext.locateElement(locator); + boolean isElementInState = new DurationBasedWaiter(duration, pollingDuration).wait( + () -> state.isElementState(element), result -> result); + softAssert.assertTrue(String.format("The element located by `%s` has become %s", locator, state), + isElementInState); + } + + private void waitForElementStateValidatingVisibility(PlaywrightLocator locator, + ElementState state) + { + Validate.isTrue(Visibility.VISIBLE == locator.getVisibility(), + "The step supports locators with VISIBLE visibility settings only, but the locator is `%s`", + locator.toString()); + waitForElementState(locator, state); } } diff --git a/vividus-plugin-web-app-playwright/src/test/java/org/vividus/ui/web/playwright/action/WaitActionsTests.java b/vividus-plugin-web-app-playwright/src/test/java/org/vividus/ui/web/playwright/action/WaitActionsTests.java index ebf168df84..e1e26b8047 100644 --- a/vividus-plugin-web-app-playwright/src/test/java/org/vividus/ui/web/playwright/action/WaitActionsTests.java +++ b/vividus-plugin-web-app-playwright/src/test/java/org/vividus/ui/web/playwright/action/WaitActionsTests.java @@ -16,10 +16,14 @@ package org.vividus.ui.web.playwright.action; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import java.util.function.Supplier; + import com.microsoft.playwright.TimeoutError; import org.junit.jupiter.api.Test; @@ -28,16 +32,20 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.vividus.softassert.SoftAssert; +import org.vividus.ui.web.playwright.assertions.PlaywrightSoftAssert; @ExtendWith(MockitoExtension.class) class WaitActionsTests { private static final String CHECKED_CONDITION = "checked condition"; private static final String TIMEOUT_ERROR_MESSAGE = "Timeout error message"; + private static final String FAILED_ASSERTION_DESCRIPTION = "Failed wait condition: " + CHECKED_CONDITION; + private static final String PASSED_ASSERTION_DESCRIPTION = "Passed wait condition: " + CHECKED_CONDITION; @Mock private Runnable timeoutOperation; @Mock private SoftAssert softAssert; + @Mock private PlaywrightSoftAssert playwrightSoftAssert; @InjectMocks private WaitActions waitActions; @Test @@ -45,7 +53,7 @@ void shouldRunWithTimeoutAssertionWithSuccessfullyAssertion() { waitActions.runWithTimeoutAssertion(CHECKED_CONDITION, timeoutOperation); verify(timeoutOperation).run(); - verify(softAssert).recordPassedAssertion("Passed wait condition: " + CHECKED_CONDITION); + verify(softAssert).recordPassedAssertion(PASSED_ASSERTION_DESCRIPTION); } @Test @@ -56,7 +64,20 @@ void shouldRunWithTimeoutAssertionWithFailedAssertion() waitActions.runWithTimeoutAssertion(CHECKED_CONDITION, timeoutOperation); verify(timeoutOperation).run(); verify(softAssert).recordFailedAssertion( - String.format("Failed wait condition: %s. %s", CHECKED_CONDITION, TIMEOUT_ERROR_MESSAGE), timeoutError); + String.format("%s. %s", FAILED_ASSERTION_DESCRIPTION, TIMEOUT_ERROR_MESSAGE), timeoutError); verifyNoMoreInteractions(softAssert); } + + @Test + void shouldRunTimeoutPlaywrightAssertion() + { + doNothing().when(playwrightSoftAssert).runAssertion( + argThat((Supplier description) -> description.get().equals(FAILED_ASSERTION_DESCRIPTION)), + argThat(runnable -> { + runnable.run(); + return true; + })); + waitActions.runTimeoutPlaywrightAssertion(() -> CHECKED_CONDITION, timeoutOperation); + verify(softAssert).recordPassedAssertion(PASSED_ASSERTION_DESCRIPTION); + } } diff --git a/vividus-plugin-web-app-playwright/src/test/java/org/vividus/ui/web/playwright/steps/ElementStateTests.java b/vividus-plugin-web-app-playwright/src/test/java/org/vividus/ui/web/playwright/steps/ElementStateTests.java index 8f72100552..0b195f9ae8 100644 --- a/vividus-plugin-web-app-playwright/src/test/java/org/vividus/ui/web/playwright/steps/ElementStateTests.java +++ b/vividus-plugin-web-app-playwright/src/test/java/org/vividus/ui/web/playwright/steps/ElementStateTests.java @@ -16,7 +16,11 @@ package org.vividus.ui.web.playwright.steps; +import static org.junit.jupiter.api.Assertions.assertEquals; + import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Stream; import com.microsoft.playwright.Locator; @@ -24,7 +28,10 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -34,6 +41,8 @@ @ExtendWith(MockitoExtension.class) class ElementStateTests { + private static final int NO_WAIT_TIMEOUT = 0; + @Mock private Locator locator; @@ -53,14 +62,82 @@ private static Stream stateProvider() (BiConsumer) PlaywrightLocatorAssertions::assertElementHidden)); } + private static Stream checkVisibilityStateProvider() + { + return Stream.of( + Arguments.of(ElementState.VISIBLE, true, (Function) Locator::isVisible), + Arguments.of(ElementState.VISIBLE, false, (Function) Locator::isVisible), + Arguments.of(ElementState.NOT_VISIBLE, true, (Function) Locator::isHidden), + Arguments.of(ElementState.NOT_VISIBLE, false, (Function) Locator::isHidden) + ); + } + @ParameterizedTest @MethodSource("stateProvider") void shouldAssertElementState(ElementState state, BiConsumer assertion) + { + shouldAssertElementState(state::assertElementState, false, assertion); + } + + @ParameterizedTest + @MethodSource("stateProvider") + void shouldWaitForElementState(ElementState state, BiConsumer assertion) + { + shouldAssertElementState(state::waitForElementState, true, assertion); + } + + @ParameterizedTest + @MethodSource("checkVisibilityStateProvider") + void shouldCheckElementVisibility(ElementState state, boolean isInState, Function isState) + { + Mockito.when(isState.apply(locator)).thenReturn(isInState); + assertEquals(isInState, state.isElementState(locator)); + } + + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void shouldCheckIsElementEnabled(boolean isEnable) + { + var state = ElementState.ENABLED; + var optionsCaptor = ArgumentCaptor.forClass(Locator.IsEnabledOptions.class); + Mockito.when(locator.isEnabled(optionsCaptor.capture())).thenReturn(isEnable); + assertEquals(isEnable, state.isElementState(locator)); + assertEquals(NO_WAIT_TIMEOUT, optionsCaptor.getValue().timeout); + } + + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void shouldCheckIsElementDisabled(boolean isDisabled) + { + var state = ElementState.DISABLED; + var optionsCaptor = ArgumentCaptor.forClass(Locator.IsDisabledOptions.class); + Mockito.when(locator.isDisabled(optionsCaptor.capture())).thenReturn(isDisabled); + assertEquals(isDisabled, state.isElementState(locator)); + assertEquals(NO_WAIT_TIMEOUT, optionsCaptor.getValue().timeout); + } + + @ParameterizedTest + @CsvSource({ + "SELECTED, true, true", + "SELECTED, false, false", + "NOT_SELECTED, false, true", + "NOT_SELECTED, true, false" + }) + void shouldCheckIsElementSelected(ElementState state, boolean isChecked, boolean isInState) + { + var optionsCaptor = ArgumentCaptor.forClass(Locator.IsCheckedOptions.class); + Mockito.when(locator.isChecked(optionsCaptor.capture())).thenReturn(isChecked); + assertEquals(isInState, state.isElementState(locator)); + assertEquals(NO_WAIT_TIMEOUT, optionsCaptor.getValue().timeout); + } + + private void shouldAssertElementState(Consumer stateConsumer, boolean waitForState, + BiConsumer assertion) { try (MockedStatic mocked = Mockito.mockStatic(PlaywrightLocatorAssertions.class)) { - state.assertElementState(locator); - mocked.verify(() -> assertion.accept(locator, false)); + stateConsumer.accept(locator); + mocked.verify(() -> assertion.accept(locator, waitForState)); } } } diff --git a/vividus-plugin-web-app-playwright/src/test/java/org/vividus/ui/web/playwright/steps/WaitStepsTests.java b/vividus-plugin-web-app-playwright/src/test/java/org/vividus/ui/web/playwright/steps/WaitStepsTests.java index b83b17ee4f..568214c8fc 100644 --- a/vividus-plugin-web-app-playwright/src/test/java/org/vividus/ui/web/playwright/steps/WaitStepsTests.java +++ b/vividus-plugin-web-app-playwright/src/test/java/org/vividus/ui/web/playwright/steps/WaitStepsTests.java @@ -16,35 +16,48 @@ package org.vividus.ui.web.playwright.steps; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.time.Duration; +import java.util.function.BiConsumer; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.stream.Stream; import com.microsoft.playwright.BrowserContext; import com.microsoft.playwright.Locator; import com.microsoft.playwright.Page; -import com.microsoft.playwright.options.WaitForSelectorState; 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.Arguments; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; +import org.vividus.softassert.ISoftAssert; import org.vividus.steps.ComparisonRule; import org.vividus.steps.StringComparisonRule; import org.vividus.ui.web.playwright.BrowserContextProvider; import org.vividus.ui.web.playwright.UiContext; import org.vividus.ui.web.playwright.action.WaitActions; +import org.vividus.ui.web.playwright.assertions.PlaywrightLocatorAssertions; import org.vividus.ui.web.playwright.locator.PlaywrightLocator; +import org.vividus.ui.web.playwright.locator.Visibility; @ExtendWith(MockitoExtension.class) class WaitStepsTests @@ -54,20 +67,52 @@ class WaitStepsTests @Mock private WaitActions waitActions; @Mock private Locator locator; @Mock private BrowserContext context; + @Mock private ISoftAssert softAssert; @InjectMocks private WaitSteps waitSteps; private final PlaywrightLocator playwrightLocator = new PlaywrightLocator("css", "div"); + private static Stream visibilityTestProvider() + { + return Stream.of( + Arguments.of((BiConsumer) WaitSteps::waitForElementAppearance), + Arguments.of((BiConsumer) WaitSteps::waitForElementDisappearance) + ); + } + @Test void shouldWaitForElementAppearance() { - testWaitForElementStateStep(waitSteps::waitForElementAppearance, WaitForSelectorState.VISIBLE); + shouldWaitForElementState(ElementState.VISIBLE, steps -> steps.waitForElementAppearance(playwrightLocator), + () -> PlaywrightLocatorAssertions.assertElementVisible(locator, true)); } @Test void shouldWaitForElementDisappearance() { - testWaitForElementStateStep(waitSteps::waitForElementDisappearance, WaitForSelectorState.HIDDEN); + shouldWaitForElementState(ElementState.NOT_VISIBLE, + steps -> steps.waitForElementDisappearance(playwrightLocator), + () -> PlaywrightLocatorAssertions.assertElementHidden(locator, true)); + } + + @Test + void shouldWaitForElementState() + { + shouldWaitForElementState(ElementState.ENABLED, + steps -> steps.waitForElementState(playwrightLocator, ElementState.ENABLED), + () -> PlaywrightLocatorAssertions.assertElementEnabled(locator, true)); + } + + @ParameterizedTest + @MethodSource("visibilityTestProvider") + void shouldThrowExceptionWhenLocatorVisibilityIsNotVisible(BiConsumer test) + { + var locator = new PlaywrightLocator("id", "value"); + locator.setVisibility(Visibility.ALL); + var exception = assertThrows(IllegalArgumentException.class, () -> test.accept(waitSteps, locator)); + var expectedExceptionMessage = String.format( + "The step supports locators with VISIBLE visibility settings only, but the locator is `%s`", locator); + assertEquals(expectedExceptionMessage, exception.getMessage()); } @Test @@ -121,17 +166,39 @@ void shouldWaitUntilPageTitleIs() assertTrue(condition.getAsBoolean()); } - private void testWaitForElementStateStep(Consumer step, WaitForSelectorState selectorState) + @ParameterizedTest + @CsvSource({ "false,true", "false, false" }) + void shouldWaitDurationWithPollingTillElementState(boolean initialStateResult, boolean finalStateResult) { + var duration = Duration.ofSeconds(1); + var pollingDuration = Duration.ofMillis(100); + var state = ElementState.VISIBLE; + when(uiContext.locateElement(playwrightLocator)).thenReturn(locator); - doNothing().when(waitActions) - .runWithTimeoutAssertion(eq("element located by 'css(div) with visibility: visible' to be " - + selectorState.toString().toLowerCase()), argThat(runnable -> - { - runnable.run(); - return true; - })); - step.accept(playwrightLocator); - verify(locator).waitFor(argThat(arg -> arg.state == selectorState)); + when(locator.isVisible()).thenReturn(initialStateResult, finalStateResult); + + waitSteps.waitDurationWithPollingTillElementState(duration, pollingDuration, playwrightLocator, state); + var assertionDescription = String.format("The element located by `%s` has become VISIBLE", playwrightLocator); + verify(softAssert).assertTrue(eq(assertionDescription), + eq(finalStateResult)); + } + + private void shouldWaitForElementState(ElementState state, Consumer test, + MockedStatic.Verification verification) + { + when(uiContext.locateElement(playwrightLocator)).thenReturn(locator); + try (var playwrightLocatorAssertions = mockStatic(PlaywrightLocatorAssertions.class)) + { + test.accept(waitSteps); + var conditionDescription = String.format("element located by `%s` to be %s", playwrightLocator, + state); + var timeoutOperationCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(waitActions).runTimeoutPlaywrightAssertion( + argThat((Supplier s) -> s.get().equals(conditionDescription)), + timeoutOperationCaptor.capture()); + + timeoutOperationCaptor.getValue().run(); + playwrightLocatorAssertions.verify(verification); + } } }