diff --git a/.gitmodules b/.gitmodules index 7e3505a206..546e66fa42 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "vividus-build-system"] path = vividus-build-system url = https://github.com/vividus-framework/vividus-build-system.git + branch = full-page-screenshot diff --git a/docs/modules/plugins/pages/plugin-visual.adoc b/docs/modules/plugins/pages/plugin-visual.adoc index 377aae9cae..249e1c9d51 100644 --- a/docs/modules/plugins/pages/plugin-visual.adoc +++ b/docs/modules/plugins/pages/plugin-visual.adoc @@ -267,6 +267,27 @@ When I baseline with `scrollable-element-context` using screenshot conf |51 |SIMPLE | ---- +==== Strategies + +[cols="1,3", options="header"] +|=== + +|Name +|Description + +|`SIMPLE` +|Used to take a screenshot of current viewport. + +|`FULL` +|Used to take a screenshot of whole application view. The strategy starts shooting at the top position +of the application view and ends at the bottom position, once the shooting is done the initial top position +gets restored. + +For iOS platforrm make sure to set the `screenshotQuality` property to `0` to ensure screenshot pixels +consistensy, please see https://github.com/appium/appium-xcuitest-driver[XCUI capabililties] for more details. + +|=== + == Steps === Run simple visual test diff --git a/vividus-build-system b/vividus-build-system index 983860b6bd..b023ceb959 160000 --- a/vividus-build-system +++ b/vividus-build-system @@ -1 +1 @@ -Subproject commit 983860b6bdc2f82ce3718e954a081620e70eb59a +Subproject commit b023ceb959f01fecb4f37e5c4af9b2f25e80cdd0 diff --git a/vividus-extension-selenium/src/main/java/org/vividus/selenium/screenshot/AbstractScreenshotTaker.java b/vividus-extension-selenium/src/main/java/org/vividus/selenium/screenshot/AbstractScreenshotTaker.java index cc00af9b65..8b8b002516 100644 --- a/vividus-extension-selenium/src/main/java/org/vividus/selenium/screenshot/AbstractScreenshotTaker.java +++ b/vividus-extension-selenium/src/main/java/org/vividus/selenium/screenshot/AbstractScreenshotTaker.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 the original author or authors. + * Copyright 2019-2022 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,20 +16,13 @@ package org.vividus.selenium.screenshot; -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.Optional; -import javax.imageio.ImageIO; - import org.apache.commons.io.FileUtils; -import org.openqa.selenium.OutputType; import org.openqa.selenium.SearchContext; -import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.slf4j.Logger; @@ -58,15 +51,6 @@ protected AbstractScreenshotTaker(IWebDriverProvider webDriverProvider, this.screenshotDebugger = screenshotDebugger; } - @Override - public BufferedImage takeViewportScreenshot() throws IOException - { - try (InputStream inputStream = new ByteArrayInputStream(takeScreenshotAsByteArray())) - { - return ImageIO.read(inputStream); - } - } - @Override public Path takeScreenshot(Path screenshotFilePath) throws IOException { @@ -116,7 +100,7 @@ protected String generateScreenshotFileName(String screenshotName) protected byte[] takeScreenshotAsByteArray() { - return webDriverProvider.getUnwrapped(TakesScreenshot.class).getScreenshotAs(OutputType.BYTES); + return ScreenshotUtils.takeScreenshotAsByteArray(getWebDriverProvider().get()); } protected IWebDriverProvider getWebDriverProvider() diff --git a/vividus-extension-selenium/src/main/java/org/vividus/selenium/screenshot/ScreenshotTaker.java b/vividus-extension-selenium/src/main/java/org/vividus/selenium/screenshot/ScreenshotTaker.java index cec5a78b65..e59e0addb1 100644 --- a/vividus-extension-selenium/src/main/java/org/vividus/selenium/screenshot/ScreenshotTaker.java +++ b/vividus-extension-selenium/src/main/java/org/vividus/selenium/screenshot/ScreenshotTaker.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 the original author or authors. + * Copyright 2019-2022 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,7 +16,6 @@ package org.vividus.selenium.screenshot; -import java.awt.image.BufferedImage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; @@ -25,7 +24,5 @@ public interface ScreenshotTaker { Optional takeScreenshot(String screenshotName); - BufferedImage takeViewportScreenshot() throws IOException; - Path takeScreenshot(Path screenshotFilePath) throws IOException; } diff --git a/vividus-extension-selenium/src/main/java/org/vividus/selenium/screenshot/ScreenshotUtils.java b/vividus-extension-selenium/src/main/java/org/vividus/selenium/screenshot/ScreenshotUtils.java new file mode 100644 index 0000000000..0475253055 --- /dev/null +++ b/vividus-extension-selenium/src/main/java/org/vividus/selenium/screenshot/ScreenshotUtils.java @@ -0,0 +1,49 @@ +/* + * Copyright 2019-2022 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.selenium.screenshot; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import javax.imageio.ImageIO; + +import org.openqa.selenium.OutputType; +import org.openqa.selenium.TakesScreenshot; +import org.openqa.selenium.WebDriver; +import org.vividus.selenium.WebDriverUtil; + +public final class ScreenshotUtils +{ + private ScreenshotUtils() + { + } + + public static BufferedImage takeViewportScreenshot(WebDriver webDriver) throws IOException + { + try (InputStream inputStream = new ByteArrayInputStream(takeScreenshotAsByteArray(webDriver))) + { + return ImageIO.read(inputStream); + } + } + + public static byte[] takeScreenshotAsByteArray(WebDriver webDriver) + { + return WebDriverUtil.unwrap(webDriver, TakesScreenshot.class).getScreenshotAs(OutputType.BYTES); + } +} diff --git a/vividus-extension-selenium/src/main/java/org/vividus/steps/ui/BarcodeSteps.java b/vividus-extension-selenium/src/main/java/org/vividus/steps/ui/BarcodeSteps.java index 40876f23cc..503059277e 100644 --- a/vividus-extension-selenium/src/main/java/org/vividus/steps/ui/BarcodeSteps.java +++ b/vividus-extension-selenium/src/main/java/org/vividus/steps/ui/BarcodeSteps.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 the original author or authors. + * Copyright 2019-2022 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. @@ -27,7 +27,8 @@ import org.vividus.context.VariableContext; import org.vividus.reporter.event.AttachmentPublishEvent; import org.vividus.reporter.model.Attachment; -import org.vividus.selenium.screenshot.ScreenshotTaker; +import org.vividus.selenium.IWebDriverProvider; +import org.vividus.selenium.screenshot.ScreenshotUtils; import org.vividus.softassert.ISoftAssert; import org.vividus.ui.action.BarcodeActions; import org.vividus.variable.VariableScope; @@ -36,16 +37,16 @@ public class BarcodeSteps { - private final ScreenshotTaker screenshotTaker; + private final IWebDriverProvider webDriverProvider; private final BarcodeActions barcodeActions; private final VariableContext variableContext; private final ISoftAssert softAssert; private final EventBus eventBus; - public BarcodeSteps(ScreenshotTaker screenshotTaker, BarcodeActions barcodeActions, + public BarcodeSteps(IWebDriverProvider webDriverProvider, BarcodeActions barcodeActions, VariableContext variableContext, ISoftAssert softAssert, EventBus eventBus) { - this.screenshotTaker = screenshotTaker; + this.webDriverProvider = webDriverProvider; this.barcodeActions = barcodeActions; this.variableContext = variableContext; this.softAssert = softAssert; @@ -74,7 +75,7 @@ public BarcodeSteps(ScreenshotTaker screenshotTaker, BarcodeActions barcodeActio @When("I scan barcode from screen and save result to $scopes variable `$variableName`") public void scanBarcode(Set scopes, String variableName) throws IOException { - BufferedImage viewportScreenshot = screenshotTaker.takeViewportScreenshot(); + BufferedImage viewportScreenshot = ScreenshotUtils.takeViewportScreenshot(webDriverProvider.get()); try { String result = barcodeActions.scanBarcode(viewportScreenshot); diff --git a/vividus-extension-selenium/src/main/java/org/vividus/ui/util/ImageUtils.java b/vividus-extension-selenium/src/main/java/org/vividus/ui/util/ImageUtils.java index 192fd43016..3cd82680e3 100644 --- a/vividus-extension-selenium/src/main/java/org/vividus/ui/util/ImageUtils.java +++ b/vividus-extension-selenium/src/main/java/org/vividus/ui/util/ImageUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 the original author or authors. + * Copyright 2019-2022 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. @@ -17,6 +17,7 @@ package org.vividus.ui.util; import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; @@ -24,12 +25,23 @@ public final class ImageUtils { + private static final String PNG_EXT = "png"; + private ImageUtils() { } - public static void writeAsPng(BufferedImage toWrite, File location) throws IOException + public static void writeAsPng(BufferedImage image, File location) throws IOException + { + ImageIO.write(image, PNG_EXT, new File(location.getAbsolutePath() + '.' + PNG_EXT)); + } + + public static byte[] encodeAsPng(BufferedImage image) throws IOException { - ImageIO.write(toWrite, "png", new File(location.getAbsolutePath() + ".png")); + try (ByteArrayOutputStream output = new ByteArrayOutputStream()) + { + ImageIO.write(image, PNG_EXT, output); + return output.toByteArray(); + } } } diff --git a/vividus-extension-selenium/src/test/java/org/vividus/selenium/screenshot/AbstractScreenshotTakerTests.java b/vividus-extension-selenium/src/test/java/org/vividus/selenium/screenshot/AbstractScreenshotTakerTests.java index 5d729a1518..2d46971df7 100644 --- a/vividus-extension-selenium/src/test/java/org/vividus/selenium/screenshot/AbstractScreenshotTakerTests.java +++ b/vividus-extension-selenium/src/test/java/org/vividus/selenium/screenshot/AbstractScreenshotTakerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 the original author or authors. + * Copyright 2019-2022 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. @@ -21,17 +21,16 @@ import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; -import java.awt.image.BufferedImage; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -48,14 +47,13 @@ import org.junit.jupiter.api.io.TempDir; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; -import org.openqa.selenium.OutputType; import org.openqa.selenium.SearchContext; import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.vividus.selenium.IWebDriverProvider; -import org.vividus.util.ResourceUtils; import ru.yandex.qatools.ashot.AShot; @@ -81,25 +79,6 @@ void afterEach() verifyNoMoreInteractions(webDriverProvider, takesScreenshot); } - private void mockScreenshotTaking() - { - byte[] bytes = ResourceUtils.loadResourceAsByteArray(getClass(), IMAGE_PNG); - - when(webDriverProvider.getUnwrapped(TakesScreenshot.class)).thenReturn(takesScreenshot); - when(takesScreenshot.getScreenshotAs(OutputType.BYTES)).thenReturn(bytes); - } - - @Test - void shouldTakeViewportScreenshot() throws IOException - { - mockScreenshotTaking(); - - BufferedImage image = testScreenshotTaker.takeViewportScreenshot(); - - assertEquals(400, image.getWidth()); - assertEquals(600, image.getHeight()); - } - @Test void shouldGenerateScreenshotFileName() { @@ -111,20 +90,38 @@ void shouldGenerateScreenshotFileName() @Test void shouldNotPublishEmptyScreenshotData(@TempDir Path path) throws IOException { - testScreenshotTaker.screenshot = new byte[0]; - assertNull(testScreenshotTaker.takeScreenshot(path.resolve(IMAGE_PNG))); - assertThat(testLogger.getLoggingEvents(), empty()); + try (MockedStatic utils = mockStatic(ScreenshotUtils.class)) + { + testScreenshotTaker.screenshot = new byte[0]; + + WebDriver webDriver = mock(WebDriver.class); + when(webDriverProvider.get()).thenReturn(webDriver); + utils.when(() -> ScreenshotUtils.takeScreenshotAsByteArray(webDriver)) + .thenReturn(testScreenshotTaker.screenshot); + + assertNull(testScreenshotTaker.takeScreenshot(path.resolve(IMAGE_PNG))); + assertThat(testLogger.getLoggingEvents(), empty()); + } } @Test void shouldPublishScreenshot(@TempDir Path path) throws IOException { - testScreenshotTaker.screenshot = new byte[1]; - Path screenshotPath = testScreenshotTaker.takeScreenshot(path.resolve(IMAGE_PNG)); - assertTrue(Files.exists(screenshotPath)); - assertArrayEquals(testScreenshotTaker.screenshot, Files.readAllBytes(screenshotPath)); - assertThat(testLogger.getLoggingEvents(), equalTo(List.of( - info("Screenshot was taken: {}", screenshotPath.toAbsolutePath())))); + try (MockedStatic utils = mockStatic(ScreenshotUtils.class)) + { + testScreenshotTaker.screenshot = new byte[1]; + + WebDriver webDriver = mock(WebDriver.class); + when(webDriverProvider.get()).thenReturn(webDriver); + utils.when(() -> ScreenshotUtils.takeScreenshotAsByteArray(webDriver)) + .thenReturn(testScreenshotTaker.screenshot); + + Path screenshotPath = testScreenshotTaker.takeScreenshot(path.resolve(IMAGE_PNG)); + assertTrue(Files.exists(screenshotPath)); + assertArrayEquals(testScreenshotTaker.screenshot, Files.readAllBytes(screenshotPath)); + assertThat(testLogger.getLoggingEvents(), equalTo(List.of( + info("Screenshot was taken: {}", screenshotPath.toAbsolutePath())))); + } } @Test @@ -177,11 +174,5 @@ public Optional takeScreenshot(String screenshotName) { throw new UnsupportedOperationException(); } - - @Override - protected byte[] takeScreenshotAsByteArray() - { - return screenshot == null ? super.takeScreenshotAsByteArray() : screenshot; - } } } diff --git a/vividus-extension-selenium/src/test/java/org/vividus/selenium/screenshot/ScreenshotUtilsTests.java b/vividus-extension-selenium/src/test/java/org/vividus/selenium/screenshot/ScreenshotUtilsTests.java new file mode 100644 index 0000000000..dd030d75ef --- /dev/null +++ b/vividus-extension-selenium/src/test/java/org/vividus/selenium/screenshot/ScreenshotUtilsTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2019-2022 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.selenium.screenshot; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +import java.awt.image.BufferedImage; +import java.io.IOException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openqa.selenium.OutputType; +import org.openqa.selenium.TakesScreenshot; +import org.openqa.selenium.WebDriver; +import org.vividus.util.ResourceUtils; + +@ExtendWith(MockitoExtension.class) +class ScreenshotUtilsTests +{ + @Mock(extraInterfaces = TakesScreenshot.class) + private WebDriver webDriver; + + @Test + void shouldTakeScreenshotAsByteArray() + { + byte[] bytes = mockScreenshotTaking(); + + assertArrayEquals(bytes, ScreenshotUtils.takeScreenshotAsByteArray(webDriver)); + } + + @Test + void shouldTakeViewportScreenshot() throws IOException + { + mockScreenshotTaking(); + + BufferedImage image = ScreenshotUtils.takeViewportScreenshot(webDriver); + + assertEquals(400, image.getWidth()); + assertEquals(600, image.getHeight()); + } + + private byte[] mockScreenshotTaking() + { + byte[] bytes = ResourceUtils.loadResourceAsByteArray(getClass(), "image.png"); + + TakesScreenshot takesScreenshot = (TakesScreenshot) webDriver; + when(takesScreenshot.getScreenshotAs(OutputType.BYTES)).thenReturn(bytes); + + return bytes; + } +} diff --git a/vividus-extension-selenium/src/test/java/org/vividus/steps/ui/BarcodeStepsTests.java b/vividus-extension-selenium/src/test/java/org/vividus/steps/ui/BarcodeStepsTests.java index ac99c1e496..90e2431cc0 100644 --- a/vividus-extension-selenium/src/test/java/org/vividus/steps/ui/BarcodeStepsTests.java +++ b/vividus-extension-selenium/src/test/java/org/vividus/steps/ui/BarcodeStepsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 the original author or authors. + * Copyright 2019-2022 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. @@ -21,6 +21,7 @@ import static org.hamcrest.Matchers.instanceOf; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; @@ -32,16 +33,21 @@ import com.google.common.eventbus.EventBus; import com.google.zxing.NotFoundException; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; +import org.openqa.selenium.WebDriver; import org.vividus.context.VariableContext; import org.vividus.reporter.event.AttachmentPublishEvent; import org.vividus.reporter.model.Attachment; +import org.vividus.selenium.IWebDriverProvider; import org.vividus.selenium.screenshot.ScreenshotTaker; +import org.vividus.selenium.screenshot.ScreenshotUtils; import org.vividus.softassert.ISoftAssert; import org.vividus.ui.action.BarcodeActions; import org.vividus.variable.VariableScope; @@ -61,37 +67,51 @@ class BarcodeStepsTests @Mock private VariableContext variableContext; @Mock private ISoftAssert softAssert; @Mock private EventBus eventBus; + @Mock private IWebDriverProvider webDriverProvider; + @Mock private WebDriver webDriver; @InjectMocks private BarcodeSteps barCodeSteps; + @BeforeEach + void init() + { + when(webDriverProvider.get()).thenReturn(webDriver); + } + @Test void shouldScanBarcodeSuccessfully() throws IOException, NotFoundException { - when(screenshotTaker.takeViewportScreenshot()).thenReturn(QR_CODE_IMAGE); - when(barcodeActions.scanBarcode(QR_CODE_IMAGE)).thenReturn(QR_CODE_VALUE); + try (MockedStatic utils = mockStatic(ScreenshotUtils.class)) + { + utils.when(() -> ScreenshotUtils.takeViewportScreenshot(webDriver)).thenReturn(QR_CODE_IMAGE); + when(barcodeActions.scanBarcode(QR_CODE_IMAGE)).thenReturn(QR_CODE_VALUE); - barCodeSteps.scanBarcode(VARIABLE_SCOPE, VARIABLE_NAME); + barCodeSteps.scanBarcode(VARIABLE_SCOPE, VARIABLE_NAME); - verify(variableContext).putVariable(VARIABLE_SCOPE, VARIABLE_NAME, QR_CODE_VALUE); + verify(variableContext).putVariable(VARIABLE_SCOPE, VARIABLE_NAME, QR_CODE_VALUE); + } } @Test void whenIScanBarcodeAndBarcodeIsAbsent() throws IOException, NotFoundException { - when(screenshotTaker.takeViewportScreenshot()).thenReturn(QR_CODE_IMAGE); - var exception = NotFoundException.getNotFoundInstance(); - when(barcodeActions.scanBarcode(QR_CODE_IMAGE)).thenThrow(exception); + try (MockedStatic utils = mockStatic(ScreenshotUtils.class)) + { + utils.when(() -> ScreenshotUtils.takeViewportScreenshot(webDriver)).thenReturn(QR_CODE_IMAGE); + var exception = NotFoundException.getNotFoundInstance(); + when(barcodeActions.scanBarcode(QR_CODE_IMAGE)).thenThrow(exception); - barCodeSteps.scanBarcode(VARIABLE_SCOPE, VARIABLE_NAME); + barCodeSteps.scanBarcode(VARIABLE_SCOPE, VARIABLE_NAME); - verify(softAssert).recordFailedAssertion("There is no barcode on the screen", exception); - var eventCaptor = ArgumentCaptor.forClass(Object.class); - verify(eventBus).post(eventCaptor.capture()); - Object event = eventCaptor.getValue(); - assertThat(event, instanceOf(AttachmentPublishEvent.class)); - Attachment attachment = ((AttachmentPublishEvent) event).getAttachment(); - assertEquals("Viewport Screenshot", attachment.getTitle()); - assertEquals("image/png", attachment.getContentType()); - assertArrayEquals(ImageTool.toByteArray(QR_CODE_IMAGE), attachment.getContent()); - verifyNoInteractions(variableContext); + verify(softAssert).recordFailedAssertion("There is no barcode on the screen", exception); + var eventCaptor = ArgumentCaptor.forClass(Object.class); + verify(eventBus).post(eventCaptor.capture()); + Object event = eventCaptor.getValue(); + assertThat(event, instanceOf(AttachmentPublishEvent.class)); + Attachment attachment = ((AttachmentPublishEvent) event).getAttachment(); + assertEquals("Viewport Screenshot", attachment.getTitle()); + assertEquals("image/png", attachment.getContentType()); + assertArrayEquals(ImageTool.toByteArray(QR_CODE_IMAGE), attachment.getContent()); + verifyNoInteractions(variableContext); + } } } diff --git a/vividus-extension-selenium/src/test/java/org/vividus/ui/util/ImageUtilsTests.java b/vividus-extension-selenium/src/test/java/org/vividus/ui/util/ImageUtilsTests.java index 53e608852f..1c62af768e 100644 --- a/vividus-extension-selenium/src/test/java/org/vividus/ui/util/ImageUtilsTests.java +++ b/vividus-extension-selenium/src/test/java/org/vividus/ui/util/ImageUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 the original author or authors. + * Copyright 2019-2022 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,7 @@ package org.vividus.ui.util; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import java.awt.image.BufferedImage; @@ -38,4 +39,15 @@ void shouldWriteImageAsPng(@TempDir File tempDir) throws IOException assertEquals(image.getHeight(), imageFromDisk.getHeight()); assertEquals(image.getWidth(), imageFromDisk.getWidth()); } + + @Test + void shouldEncodeAsPng() throws IOException + { + BufferedImage image = new BufferedImage(1, 1, 1); + byte[] bytes = ImageUtils.encodeAsPng(image); + byte[] expected = { -119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 1, 0, 0, 0, 1, 8, 2, + 0, 0, 0, -112, 119, 83, -34, 0, 0, 0, 12, 73, 68, 65, 84, 120, 94, 99, 96, 96, 96, 0, 0, 0, 4, 0, 1, 15, + -46, -83, -28, 0, 0, 0, 0, 73, 69, 78, 68, -82, 66, 96, -126 }; + assertArrayEquals(expected, bytes); + } } diff --git a/vividus-extension-visual-testing/src/main/java/org/vividus/visual/steps/AbstractVisualSteps.java b/vividus-extension-visual-testing/src/main/java/org/vividus/visual/steps/AbstractVisualSteps.java index 3271c0ef88..59b1a576f8 100644 --- a/vividus-extension-visual-testing/src/main/java/org/vividus/visual/steps/AbstractVisualSteps.java +++ b/vividus-extension-visual-testing/src/main/java/org/vividus/visual/steps/AbstractVisualSteps.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 the original author or authors. + * Copyright 2019-2022 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. diff --git a/vividus-plugin-mobile-app/build.gradle b/vividus-plugin-mobile-app/build.gradle index cda530aa33..94a171b1b9 100644 --- a/vividus-plugin-mobile-app/build.gradle +++ b/vividus-plugin-mobile-app/build.gradle @@ -9,6 +9,7 @@ project.description = 'Vividus plugin for testing mobile applications' implementation(group: 'org.slf4j', name: 'slf4j-api', version: versions.slf4j) implementation(group: 'javax.inject', name: 'javax.inject', version: versions.javaxInject) + implementation(group: 'org.boofcv', name: 'boofcv-core', version: '0.40.1') testImplementation platform(group: 'org.junit', name: 'junit-bom', version: versions.junit) testImplementation(group: 'org.junit.jupiter', name: 'junit-jupiter') diff --git a/vividus-plugin-mobile-app/src/main/java/org/vividus/mobileapp/action/TouchActions.java b/vividus-plugin-mobile-app/src/main/java/org/vividus/mobileapp/action/TouchActions.java index 9a20dd3178..9a3c1e7194 100644 --- a/vividus-plugin-mobile-app/src/main/java/org/vividus/mobileapp/action/TouchActions.java +++ b/vividus-plugin-mobile-app/src/main/java/org/vividus/mobileapp/action/TouchActions.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 the original author or authors. + * Copyright 2019-2022 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. @@ -39,7 +39,7 @@ import org.vividus.selenium.IWebDriverProvider; import org.vividus.selenium.WebDriverUtil; import org.vividus.selenium.manager.GenericWebDriverManager; -import org.vividus.selenium.screenshot.ScreenshotTaker; +import org.vividus.selenium.screenshot.ScreenshotUtils; import org.vividus.util.Sleeper; import io.appium.java_client.PerformsTouchActions; @@ -55,15 +55,13 @@ public class TouchActions private final IWebDriverProvider webDriverProvider; private final GenericWebDriverManager genericWebDriverManager; - private final ScreenshotTaker screenshotTaker; private final MobileApplicationConfiguration mobileApplicationConfiguration; public TouchActions(IWebDriverProvider webDriverProvider, GenericWebDriverManager genericWebDriverManager, - ScreenshotTaker screenshotTaker, MobileApplicationConfiguration mobileApplicationConfiguration) + MobileApplicationConfiguration mobileApplicationConfiguration) { this.webDriverProvider = webDriverProvider; this.genericWebDriverManager = genericWebDriverManager; - this.screenshotTaker = screenshotTaker; this.mobileApplicationConfiguration = mobileApplicationConfiguration; } @@ -185,7 +183,19 @@ public void swipeUntil(SwipeDirection direction, Duration swipeDuration, Rectang } /** - * Performs vertical swipe from startY to endY with swipeDuration + * Performs vertical swipe from startY to endY with swipeDuration. + * + * @param startY start Y coordinate + * @param endY end Y coordinate + * @param swipeDuration swipe duration in ISO 8601 format + */ + public void performVerticalSwipe(int startY, int endY, Duration swipeDuration) + { + performVerticalSwipe(startY, endY, defaultSwipeArea(), swipeDuration); + } + + /** + * Performs vertical swipe from startY to endY with swipeDuration within the area. * * @param startY start Y coordinate * @param endY end Y coordinate @@ -198,11 +208,27 @@ public void performVerticalSwipe(int startY, int endY, Rectangle swipeArea, Dura mobileApplicationConfiguration.getSwipeVerticalXPosition(), swipeArea.getPoint()), swipeDuration); } + /** + * Swipes in swipeDuration direction until the edge of the application is reached + * + * @param direction direction to swipe, either UP or DOWN + * @param swipeDuration duration between a pointer moves from the start to the end of the swipe coordinates + */ + public void swipeUntilEdge(SwipeDirection direction, Duration swipeDuration) + { + swipeUntil(direction, swipeDuration, defaultSwipeArea(), () -> false); + } + + private Rectangle defaultSwipeArea() + { + return new Rectangle(new Point(0, 0), genericWebDriverManager.getSize()); + } + private BufferedImage takeScreenshot() { try { - return screenshotTaker.takeViewportScreenshot(); + return ScreenshotUtils.takeViewportScreenshot(webDriverProvider.get()); } catch (IOException e) { diff --git a/vividus-plugin-mobile-app/src/main/java/org/vividus/mobileapp/steps/TouchSteps.java b/vividus-plugin-mobile-app/src/main/java/org/vividus/mobileapp/steps/TouchSteps.java index 0bf092e852..8b7b17010d 100644 --- a/vividus-plugin-mobile-app/src/main/java/org/vividus/mobileapp/steps/TouchSteps.java +++ b/vividus-plugin-mobile-app/src/main/java/org/vividus/mobileapp/steps/TouchSteps.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 the original author or authors. + * Copyright 2019-2022 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. diff --git a/vividus-plugin-mobile-app/src/main/java/org/vividus/selenium/mobileapp/MobileAppScreenshotTaker.java b/vividus-plugin-mobile-app/src/main/java/org/vividus/selenium/mobileapp/MobileAppScreenshotTaker.java index 3d2d7ab4e0..03752860d8 100644 --- a/vividus-plugin-mobile-app/src/main/java/org/vividus/selenium/mobileapp/MobileAppScreenshotTaker.java +++ b/vividus-plugin-mobile-app/src/main/java/org/vividus/selenium/mobileapp/MobileAppScreenshotTaker.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 the original author or authors. + * Copyright 2019-2022 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. @@ -25,6 +25,7 @@ import org.vividus.selenium.screenshot.Screenshot; import org.vividus.selenium.screenshot.ScreenshotConfiguration; import org.vividus.selenium.screenshot.ScreenshotDebugger; +import org.vividus.selenium.screenshot.ScreenshotUtils; public class MobileAppScreenshotTaker extends AbstractScreenshotTaker { @@ -39,6 +40,7 @@ public MobileAppScreenshotTaker(IWebDriverProvider webDriverProvider, public Optional takeScreenshot(String screenshotName) { String fileName = generateScreenshotFileName(screenshotName); - return Optional.of(new Screenshot(fileName, takeScreenshotAsByteArray())); + byte [] screenshotBody = ScreenshotUtils.takeScreenshotAsByteArray(getWebDriverProvider().get()); + return Optional.of(new Screenshot(fileName, screenshotBody)); } } diff --git a/vividus-plugin-mobile-app/src/main/java/org/vividus/selenium/mobileapp/screenshot/strategies/MobileAppFullShootingStrategy.java b/vividus-plugin-mobile-app/src/main/java/org/vividus/selenium/mobileapp/screenshot/strategies/MobileAppFullShootingStrategy.java new file mode 100644 index 0000000000..ff46d24f73 --- /dev/null +++ b/vividus-plugin-mobile-app/src/main/java/org/vividus/selenium/mobileapp/screenshot/strategies/MobileAppFullShootingStrategy.java @@ -0,0 +1,341 @@ +/* + * Copyright 2019-2022 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.selenium.mobileapp.screenshot.strategies; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.time.Duration; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.ToIntFunction; +import java.util.stream.IntStream; + +import javax.inject.Named; + +import org.apache.commons.lang3.function.FailableSupplier; +import org.ddogleg.struct.DogArray; +import org.openqa.selenium.WebDriver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.vividus.mobileapp.action.TouchActions; +import org.vividus.mobileapp.configuration.MobileApplicationConfiguration; +import org.vividus.mobileapp.model.SwipeDirection; +import org.vividus.reporter.event.AttachmentPublisher; +import org.vividus.selenium.mobileapp.MobileAppWebDriverManager; +import org.vividus.selenium.screenshot.ScreenshotUtils; +import org.vividus.selenium.screenshot.strategies.ScreenshotShootingStrategy; +import org.vividus.ui.util.ImageUtils; +import org.vividus.util.Sleeper; + +import boofcv.alg.template.TemplateMatching; +import boofcv.factory.template.FactoryTemplateMatching; +import boofcv.factory.template.TemplateScoreType; +import boofcv.io.image.ConvertBufferedImage; +import boofcv.struct.feature.Match; +import boofcv.struct.image.GrayF32; +import ru.yandex.qatools.ashot.coordinates.Coords; +import ru.yandex.qatools.ashot.shooting.ShootingStrategy; +import ru.yandex.qatools.ashot.util.ImageTool; + +@Named("FULL") +public class MobileAppFullShootingStrategy implements ShootingStrategy, ScreenshotShootingStrategy +{ + private static final long serialVersionUID = -1367316946809918634L; + + private static final Logger LOGGER = LoggerFactory.getLogger(MobileAppFullShootingStrategy.class); + + private static final Duration SWIPE_DURATION = Duration.ofSeconds(2); + private static final int DEFAULT_SHIFT = 100; + private static final int DIVIDER = 3; + private static final double CUT_PERCENTAGE = 0.15; + private static final String UNSUPPORTED_MESSAGE = "Element screenshot is not supported"; + private static final double MATCH_SCORE_THRESHOLD = 0.99d; + + private final transient TouchActions touchActions; + private final transient MobileAppWebDriverManager genericWebDriverManager; + private final transient MobileApplicationConfiguration mobileApplicationConfiguration; + private final transient AttachmentPublisher attachmentPublisher; + + public MobileAppFullShootingStrategy(TouchActions touchActions, + MobileAppWebDriverManager genericWebDriverManager, + MobileApplicationConfiguration mobileApplicationConfiguration, AttachmentPublisher attachmentPublisher) + { + this.touchActions = touchActions; + this.genericWebDriverManager = genericWebDriverManager; + this.mobileApplicationConfiguration = mobileApplicationConfiguration; + this.attachmentPublisher = attachmentPublisher; + } + + @Override + public ShootingStrategy getDecoratedShootingStrategy(ShootingStrategy shootingStrategy) + { + return this; + } + + @Override + public BufferedImage getScreenshot(WebDriver webDriver) + { + touchActions.swipeUntilEdge(SwipeDirection.DOWN, SWIPE_DURATION); + int height = genericWebDriverManager.getSize().getHeight(); + int cutSize = (int) (height * CUT_PERCENTAGE); + return getScreenshot(webDriver, ImageArea.from(cutSize, height - cutSize * 2), DEFAULT_SHIFT); + } + + @Override + public BufferedImage getScreenshot(WebDriver webDriver, Set coords) + { + throw new UnsupportedOperationException(UNSUPPORTED_MESSAGE); + } + + @Override + public Set prepareCoords(Set coordsSet) + { + throw new UnsupportedOperationException(UNSUPPORTED_MESSAGE); + } + + private BufferedImage getScreenshot(WebDriver webDriver, ImageArea targetArea, int scrollY) + { + int dpr = (int) genericWebDriverManager.getDpr(); + ImageArea imageArea = ImageArea.scaled(targetArea, dpr); + + int swipeRate = targetArea.getScrollableHeight() / DIVIDER; + int startY = scrollY + swipeRate * 2; + int endY = scrollY + swipeRate; + + Runnable swipeUp = () -> + { + touchActions.performVerticalSwipe(startY, endY, SWIPE_DURATION); + Sleeper.sleep(mobileApplicationConfiguration.getSwipeStabilizationDuration()); + }; + + try + { + BufferedImage fullPageImage = takeFullPageScreenshot(swipeUp, webDriver, imageArea, swipeRate * dpr); + touchActions.swipeUntilEdge(SwipeDirection.DOWN, SWIPE_DURATION); + return fullPageImage; + } + catch (TemplateMissmatchException thrown) + { + touchActions.swipeUntilEdge(SwipeDirection.DOWN, SWIPE_DURATION); + + publishImage("targetImage", thrown.getImage()); + publishImage("template", thrown.getTemplate()); + throw new IllegalStateException(thrown); + } + } + + private BufferedImage takeFullPageScreenshot(Runnable swipe, WebDriver webDriver, + ImageArea imageArea, int swipeRate) throws TemplateMissmatchException + { + BufferedImage viewport = takeViewportScreenshot(webDriver); + BufferedImage output = cropImage(viewport, 0, imageArea.getBottomStartY()); + + int screenSwipeLimit = mobileApplicationConfiguration.getSwipeLimit(); + BufferedImage previousFrame = null; + + for (int count = 0; count <= screenSwipeLimit; count++) + { + swipe.run(); + + viewport = takeViewportScreenshot(webDriver); + + BufferedImage area = cropArea(viewport, imageArea); + int areaHeight = area.getHeight(); + + BufferedImage currentFrame = cropImage(area, areaHeight - swipeRate * 2 + DEFAULT_SHIFT, + swipeRate - DEFAULT_SHIFT); + if (previousFrame != null && ImageTool.equalImage(currentFrame).matches(previousFrame)) + { + BufferedImage bottomFrame = cropImage(viewport, imageArea.getBottomStartY(), + viewport.getHeight() - imageArea.getBottomStartY()); + return concatImages(List.of(output, bottomFrame)); + } + previousFrame = currentFrame; + + output = concatByTemplate(output, area, currentFrame); + + if (count == screenSwipeLimit) + { + LOGGER.warn("Taking of full page screenshot is stopped due to exceeded swipe limit '{}'", + screenSwipeLimit); + } + } + + return output; + } + + private BufferedImage cropArea(BufferedImage image, ImageArea area) + { + return cropImage(image, area.getTopIndent(), area.getScrollableHeight()); + } + + private void publishImage(String name, BufferedImage image) + { + wrap(() -> + { + String imageName = name + ".png"; + attachmentPublisher.publishAttachment(ImageUtils.encodeAsPng(image), imageName); + return null; + }); + } + + private BufferedImage takeViewportScreenshot(WebDriver webDriver) + { + return wrap(() -> ScreenshotUtils.takeViewportScreenshot(webDriver)).get(); + } + + private Optional wrap(FailableSupplier supplier) + { + try + { + return Optional.ofNullable(supplier.get()); + } + catch (IOException thrown) + { + throw new UncheckedIOException(thrown); + } + } + + public static BufferedImage concatImages(List images) + { + int width = asInts(images, BufferedImage::getWidth).max().getAsInt(); + int height = asInts(images, BufferedImage::getHeight).sum(); + + BufferedImage output = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR); + Graphics2D graphic = output.createGraphics(); + graphic.setPaint(Color.RED); + graphic.setColor(Color.BLUE); + graphic.fillRect(0, 0, width, height); + int drawPosition = 0; + for (BufferedImage image : images) + { + graphic.drawImage(image, null, 0, drawPosition); + drawPosition += image.getHeight(); + } + graphic.dispose(); + return output; + } + + public static BufferedImage concatByTemplate(BufferedImage first, BufferedImage second, BufferedImage template) + throws TemplateMissmatchException + { + int firstY = getOverlapY(first, template); + int secondY = getOverlapY(second, template); + return concatImages(List.of( + cropImage(first, 0, firstY), + cropImage(second, secondY, second.getHeight() - secondY))); + } + + public static BufferedImage cropImage(BufferedImage image, int fromY, int toY) + { + return image.getSubimage(0, fromY, image.getWidth(), toY); + } + + private static int getOverlapY(BufferedImage image, BufferedImage template) throws TemplateMissmatchException + { + GrayF32 imageGray = ConvertBufferedImage.convertFromSingle(image, null, GrayF32.class); + GrayF32 templateGray = ConvertBufferedImage.convertFromSingle(template, null, GrayF32.class); + DogArray results = match(imageGray, templateGray); + + if (results.getSize() == 0 || results.getTail().score < MATCH_SCORE_THRESHOLD) + { + throw new TemplateMissmatchException(image, template); + } + + return results.get(results.getSize() - 1).getY(); + } + + private static DogArray match(GrayF32 image, GrayF32 template) + { + TemplateMatching matcher = FactoryTemplateMatching.createMatcher(TemplateScoreType.NCC, GrayF32.class); + matcher.setImage(image); + matcher.setTemplate(template.subimage(0, 0, template.getWidth(), template.getHeight()), null, 1); + matcher.process(); + return matcher.getResults(); + } + + private static IntStream asInts(List images, ToIntFunction function) + { + return images.stream().mapToInt(function); + } + + public static class TemplateMissmatchException extends Exception + { + private static final long serialVersionUID = 4945579279267119038L; + + private final transient BufferedImage image; + private final transient BufferedImage template; + + public TemplateMissmatchException(BufferedImage image, BufferedImage template) + { + super("Unable to match the template in the target image"); + this.image = image; + this.template = template; + } + + public BufferedImage getImage() + { + return image; + } + + public BufferedImage getTemplate() + { + return template; + } + } + + private static final class ImageArea + { + private final int topIndent; + private final int scrollableHeight; + + private ImageArea(int topIndent, int scrollableHeight) + { + this.topIndent = topIndent; + this.scrollableHeight = scrollableHeight; + } + + private static ImageArea from(int topIndent, int scrollableHeight) + { + return new ImageArea(topIndent, scrollableHeight); + } + + private static ImageArea scaled(ImageArea imageArea, int dpr) + { + return new ImageArea(imageArea.getTopIndent() * dpr, imageArea.getScrollableHeight() * dpr); + } + + private int getTopIndent() + { + return topIndent; + } + + private int getBottomStartY() + { + return getTopIndent() + getScrollableHeight(); + } + + private int getScrollableHeight() + { + return scrollableHeight; + } + } +} diff --git a/vividus-plugin-mobile-app/src/test/java/io/appium/java_client/TouchActionsTests.java b/vividus-plugin-mobile-app/src/test/java/io/appium/java_client/TouchActionsTests.java index d69f50e2c3..8c9ee20b90 100644 --- a/vividus-plugin-mobile-app/src/test/java/io/appium/java_client/TouchActionsTests.java +++ b/vividus-plugin-mobile-app/src/test/java/io/appium/java_client/TouchActionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 the original author or authors. + * Copyright 2019-2022 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. @@ -19,8 +19,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.doThrow; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -44,11 +48,13 @@ import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.openqa.selenium.Dimension; import org.openqa.selenium.Point; import org.openqa.selenium.Rectangle; +import org.openqa.selenium.WebDriver; import org.openqa.selenium.remote.RemoteWebElement; import org.vividus.mobileapp.action.TouchActions; import org.vividus.mobileapp.configuration.MobileApplicationConfiguration; @@ -56,6 +62,7 @@ import org.vividus.selenium.IWebDriverProvider; import org.vividus.selenium.manager.GenericWebDriverManager; import org.vividus.selenium.screenshot.ScreenshotTaker; +import org.vividus.selenium.screenshot.ScreenshotUtils; import org.vividus.util.ResourceUtils; @ExtendWith(MockitoExtension.class) @@ -94,7 +101,7 @@ class TouchActionsTests @BeforeEach void init() { - when(webDriverProvider.getUnwrapped(PerformsTouchActions.class)).thenReturn(performsTouchActions); + lenient().when(webDriverProvider.getUnwrapped(PerformsTouchActions.class)).thenReturn(performsTouchActions); } @Test @@ -174,85 +181,135 @@ void shouldTapOnVisibleElementWithoutWaitIfDurationIsZero() } @Test - void shouldSwipeUntilConditionIsTrue() throws IOException + void shouldSwipeUntilConditionIsTrue() { - when(genericWebDriverManager.getSize()).thenReturn(DIMENSION); - when(stopCondition.getAsBoolean()).thenReturn(false) - .thenReturn(false) - .thenReturn(true); - when(screenshotTaker.takeViewportScreenshot()).thenReturn(getImage(BLACK_IMAGE)) - .thenReturn(getImage(WHITE_IMAGE)); + try (MockedStatic utils = mockStatic(ScreenshotUtils.class)) + { + WebDriver driver = mock(WebDriver.class); + when(webDriverProvider.get()).thenReturn(driver); + + utils.when(() -> ScreenshotUtils.takeViewportScreenshot(driver)).thenReturn(getImage(BLACK_IMAGE)) + .thenReturn(getImage(WHITE_IMAGE)); - touchActions.swipeUntil(SwipeDirection.UP, DURATION, SWIPE_AREA, stopCondition); + when(genericWebDriverManager.getSize()).thenReturn(DIMENSION); + when(stopCondition.getAsBoolean()).thenReturn(false) + .thenReturn(false) + .thenReturn(true); - verifySwipe(3); - verifyConfiguration(); + touchActions.swipeUntil(SwipeDirection.UP, DURATION, SWIPE_AREA, stopCondition); + + verifySwipe(3); + verifyConfiguration(); + } } @Test void shouldNotExceedSwipeLimit() throws IOException { - when(genericWebDriverManager.getSize()).thenReturn(DIMENSION); - when(stopCondition.getAsBoolean()).thenReturn(false); - when(screenshotTaker.takeViewportScreenshot()).thenReturn(getImage(BLACK_IMAGE)) - .thenReturn(getImage(WHITE_IMAGE)) - .thenReturn(getImage(BLACK_IMAGE)) - .thenReturn(getImage(WHITE_IMAGE)) - .thenReturn(getImage(BLACK_IMAGE)) - .thenReturn(getImage(WHITE_IMAGE)); - - IllegalStateException exception = assertThrows(IllegalStateException.class, - () -> touchActions.swipeUntil(SwipeDirection.UP, DURATION, SWIPE_AREA, stopCondition)); - - assertEquals("Swiping is stopped due to exceeded swipe limit '5'", exception.getMessage()); - verifySwipe(6); - verifyConfiguration(); + try (MockedStatic utils = mockStatic(ScreenshotUtils.class)) + { + WebDriver driver = mock(WebDriver.class); + when(webDriverProvider.get()).thenReturn(driver); + + utils.when(() -> ScreenshotUtils.takeViewportScreenshot(driver)).thenReturn(getImage(BLACK_IMAGE)) + .thenReturn(getImage(WHITE_IMAGE)) + .thenReturn(getImage(BLACK_IMAGE)) + .thenReturn(getImage(WHITE_IMAGE)) + .thenReturn(getImage(BLACK_IMAGE)) + .thenReturn(getImage(WHITE_IMAGE)); + + when(genericWebDriverManager.getSize()).thenReturn(DIMENSION); + when(stopCondition.getAsBoolean()).thenReturn(false); + + IllegalStateException exception = assertThrows(IllegalStateException.class, + () -> touchActions.swipeUntil(SwipeDirection.UP, DURATION, SWIPE_AREA, stopCondition)); + + assertEquals("Swiping is stopped due to exceeded swipe limit '5'", exception.getMessage()); + verifySwipe(6); + verifyConfiguration(); + } } @Test void shouldStopSwipeOnceEndOfPageIsReached() throws IOException { - when(genericWebDriverManager.getSize()).thenReturn(DIMENSION); - when(stopCondition.getAsBoolean()).thenReturn(false); - when(screenshotTaker.takeViewportScreenshot()).thenReturn(getImage(BLACK_IMAGE)) - .thenReturn(getImage(WHITE_IMAGE)) - .thenReturn(getImage(BLACK_IMAGE)) - .thenReturn(getImage(BLACK_IMAGE)); + try (MockedStatic utils = mockStatic(ScreenshotUtils.class)) + { + WebDriver driver = mock(WebDriver.class); + when(webDriverProvider.get()).thenReturn(driver); - touchActions.swipeUntil(SwipeDirection.UP, DURATION, SWIPE_AREA, stopCondition); + utils.when(() -> ScreenshotUtils.takeViewportScreenshot(driver)).thenReturn(getImage(BLACK_IMAGE)) + .thenReturn(getImage(WHITE_IMAGE)) + .thenReturn(getImage(BLACK_IMAGE)) + .thenReturn(getImage(BLACK_IMAGE)); - verifySwipe(4); - verifyConfiguration(); + when(genericWebDriverManager.getSize()).thenReturn(DIMENSION); + when(stopCondition.getAsBoolean()).thenReturn(false); + + touchActions.swipeUntil(SwipeDirection.UP, DURATION, SWIPE_AREA, stopCondition); + + verifySwipe(4); + verifyConfiguration(); + } } @Test void shouldWrapIOException() throws IOException { - when(genericWebDriverManager.getSize()).thenReturn(DIMENSION); - IOException exception = mock(IOException.class); + try (MockedStatic utils = mockStatic(ScreenshotUtils.class)) + { + WebDriver driver = mock(WebDriver.class); + when(webDriverProvider.get()).thenReturn(driver); + + IOException exception = mock(IOException.class); + utils.when(() -> ScreenshotUtils.takeViewportScreenshot(driver)).thenThrow(exception); + + when(genericWebDriverManager.getSize()).thenReturn(DIMENSION); + when(stopCondition.getAsBoolean()).thenReturn(false); - when(stopCondition.getAsBoolean()).thenReturn(false); - doThrow(exception).when(screenshotTaker).takeViewportScreenshot(); + UncheckedIOException wrapper = assertThrows(UncheckedIOException.class, + () -> touchActions.swipeUntil(SwipeDirection.UP, DURATION, SWIPE_AREA, stopCondition)); - UncheckedIOException wrapper = assertThrows(UncheckedIOException.class, - () -> touchActions.swipeUntil(SwipeDirection.UP, DURATION, SWIPE_AREA, stopCondition)); + assertEquals(exception, wrapper.getCause()); + verifySwipe(1); + verifyConfiguration(); + } + } - assertEquals(exception, wrapper.getCause()); + @Test + void shouldPerformVerticalSwipeWithinArea() + { + touchActions.performVerticalSwipe(640, 160, SWIPE_AREA, DURATION); verifySwipe(1); - verifyConfiguration(); } @Test void shouldPerformVerticalSwipe() { - touchActions.performVerticalSwipe(640, 160, SWIPE_AREA, DURATION); + when(genericWebDriverManager.getSize()).thenReturn(DIMENSION); + touchActions.performVerticalSwipe(640, 160, DURATION); verifySwipe(1); } + @Test + void shouldSwipeUntilEdge() + { + when(genericWebDriverManager.getSize()).thenReturn(DIMENSION); + TouchActions spy = spy(touchActions); + doNothing().when(spy).swipeUntil(eq(SwipeDirection.UP), eq(DURATION), eq(SWIPE_AREA), + argThat(arg -> !((BooleanSupplier) arg).getAsBoolean())); + + spy.swipeUntilEdge(SwipeDirection.UP, DURATION); + + verify(spy).swipeUntil(eq(SwipeDirection.UP), eq(DURATION), eq(SWIPE_AREA), + argThat(arg -> !((BooleanSupplier) arg).getAsBoolean())); + } + private void verifySwipe(int times) { verify(performsTouchActions, times(times)) .performTouchAction(argThat(arg -> SCROLL_UP.equals(arg.getParameters().toString()))); + verify(webDriverProvider, times(times)).getUnwrapped(PerformsTouchActions.class); verifyNoMoreInteractions(stopCondition, performsTouchActions, screenshotTaker, genericWebDriverManager, webDriverProvider); } diff --git a/vividus-plugin-mobile-app/src/test/java/org/vividus/selenium/mobileapp/MobileAppScreenshotTakerTests.java b/vividus-plugin-mobile-app/src/test/java/org/vividus/selenium/mobileapp/MobileAppScreenshotTakerTests.java index 009411be21..7f8bf557e6 100644 --- a/vividus-plugin-mobile-app/src/test/java/org/vividus/selenium/mobileapp/MobileAppScreenshotTakerTests.java +++ b/vividus-plugin-mobile-app/src/test/java/org/vividus/selenium/mobileapp/MobileAppScreenshotTakerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 the original author or authors. + * Copyright 2019-2022 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. @@ -19,6 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -27,19 +28,20 @@ import java.nio.file.Path; import java.util.Optional; -import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; -import org.openqa.selenium.OutputType; -import org.openqa.selenium.TakesScreenshot; +import org.openqa.selenium.WebDriver; import org.vividus.selenium.IWebDriverProvider; import org.vividus.selenium.mobileapp.screenshot.MobileAppAshotFactory; import org.vividus.selenium.screenshot.IScreenshotFileNameGenerator; import org.vividus.selenium.screenshot.Screenshot; +import org.vividus.selenium.screenshot.ScreenshotUtils; @ExtendWith(MockitoExtension.class) class MobileAppScreenshotTakerTests @@ -50,39 +52,44 @@ class MobileAppScreenshotTakerTests @Mock private IWebDriverProvider webDriverProvider; @Mock private IScreenshotFileNameGenerator screenshotFileNameGenerator; - @Mock private TakesScreenshot takesScreenshot; @Mock private MobileAppAshotFactory ashotFactory; + @Mock private WebDriver webDriver; @InjectMocks private MobileAppScreenshotTaker screenshotTaker; - @AfterEach - void afterEach() + @BeforeEach + void beforeEach() { - verifyNoMoreInteractions(webDriverProvider, screenshotFileNameGenerator, takesScreenshot); + when(webDriverProvider.get()).thenReturn(webDriver); } @Test void shouldTakeScreenshot() { - when(screenshotFileNameGenerator.generateScreenshotFileName(SCREENSHOT_NAME)).thenReturn(FILE_NAME); - when(webDriverProvider.getUnwrapped(TakesScreenshot.class)).thenReturn(takesScreenshot); - when(takesScreenshot.getScreenshotAs(OutputType.BYTES)).thenReturn(DATA); + try (MockedStatic utils = mockStatic(ScreenshotUtils.class)) + { + utils.when(() -> ScreenshotUtils.takeScreenshotAsByteArray(webDriver)).thenReturn(DATA); - Optional takenScreenshot = screenshotTaker.takeScreenshot(SCREENSHOT_NAME); - assertTrue(takenScreenshot.isPresent()); - Screenshot screenshot = takenScreenshot.get(); - assertEquals(FILE_NAME, screenshot.getFileName()); - assertArrayEquals(DATA, screenshot.getData()); - verifyNoMoreInteractions(screenshotFileNameGenerator, webDriverProvider, takesScreenshot); + when(screenshotFileNameGenerator.generateScreenshotFileName(SCREENSHOT_NAME)).thenReturn(FILE_NAME); + + Optional takenScreenshot = screenshotTaker.takeScreenshot(SCREENSHOT_NAME); + assertTrue(takenScreenshot.isPresent()); + Screenshot screenshot = takenScreenshot.get(); + assertEquals(FILE_NAME, screenshot.getFileName()); + assertArrayEquals(DATA, screenshot.getData()); + verifyNoMoreInteractions(screenshotFileNameGenerator, webDriverProvider); + } } @Test void shouldSaveScreenshotToAPath(@TempDir Path path) throws IOException { - when(webDriverProvider.getUnwrapped(TakesScreenshot.class)).thenReturn(takesScreenshot); - when(takesScreenshot.getScreenshotAs(OutputType.BYTES)).thenReturn(DATA); + try (MockedStatic utils = mockStatic(ScreenshotUtils.class)) + { + utils.when(() -> ScreenshotUtils.takeScreenshotAsByteArray(webDriver)).thenReturn(DATA); - Path takenScreenshot = screenshotTaker.takeScreenshot(path.resolve(FILE_NAME)); - assertArrayEquals(DATA, Files.readAllBytes(takenScreenshot)); - verifyNoMoreInteractions(screenshotFileNameGenerator, webDriverProvider, takesScreenshot); + Path takenScreenshot = screenshotTaker.takeScreenshot(path.resolve(FILE_NAME)); + assertArrayEquals(DATA, Files.readAllBytes(takenScreenshot)); + verifyNoMoreInteractions(screenshotFileNameGenerator, webDriverProvider); + } } } diff --git a/vividus-plugin-mobile-app/src/test/java/org/vividus/selenium/mobileapp/screenshot/strategies/MobileAppFullShootingStrategyTests.java b/vividus-plugin-mobile-app/src/test/java/org/vividus/selenium/mobileapp/screenshot/strategies/MobileAppFullShootingStrategyTests.java new file mode 100644 index 0000000000..c5dc188e4c --- /dev/null +++ b/vividus-plugin-mobile-app/src/test/java/org/vividus/selenium/mobileapp/screenshot/strategies/MobileAppFullShootingStrategyTests.java @@ -0,0 +1,202 @@ +/* + * Copyright 2019-2022 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.selenium.mobileapp.screenshot.strategies; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.time.Duration; +import java.util.Set; + +import javax.imageio.ImageIO; + +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.ValueSource; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openqa.selenium.Dimension; +import org.openqa.selenium.WebDriver; +import org.vividus.mobileapp.action.TouchActions; +import org.vividus.mobileapp.configuration.MobileApplicationConfiguration; +import org.vividus.reporter.event.AttachmentPublisher; +import org.vividus.selenium.mobileapp.MobileAppWebDriverManager; +import org.vividus.selenium.mobileapp.screenshot.strategies.MobileAppFullShootingStrategy.TemplateMissmatchException; +import org.vividus.selenium.screenshot.ScreenshotUtils; +import org.vividus.util.ResourceUtils; + +import ru.yandex.qatools.ashot.coordinates.Coords; +import ru.yandex.qatools.ashot.shooting.ShootingStrategy; +import ru.yandex.qatools.ashot.util.ImageTool; + +@ExtendWith(MockitoExtension.class) +class MobileAppFullShootingStrategyTests +{ + private static final String UNSUPPORTED_MESSAGE = "Element screenshot is not supported"; + private static final String TOP_IMAGE = "top-image.png"; + private static final String FIRST_IMAGE = "1st-scroll-image.png"; + private static final String SECOND_IMAGE = "2nd-scroll-image.png"; + + @Mock private TouchActions touchActions; + @Mock private MobileAppWebDriverManager genericWebDriverManager; + @Mock private AttachmentPublisher attachmentPublisher; + @Mock private WebDriver webDriver; + + private MobileAppFullShootingStrategy strategy; + + @Test + void shouldReturnFullScreenshot() + { + defaultInit(); + + Dimension dimension = new Dimension(375, 812); + when(genericWebDriverManager.getSize()).thenReturn(dimension); + when(genericWebDriverManager.getDpr()).thenReturn(3D); + + try (MockedStatic utils = mockStatic(ScreenshotUtils.class)) + { + utils.when(() -> ScreenshotUtils.takeViewportScreenshot(webDriver)) + .thenReturn(getImage(TOP_IMAGE)) + .thenReturn(getImage(FIRST_IMAGE)) + .thenReturn(getImage(SECOND_IMAGE)) + .thenReturn(getImage("3rd-scroll-image.png")); + + BufferedImage fullImage = strategy.getScreenshot(webDriver); + + assertThat(getImage("full-image.png"), ImageTool.equalImage(fullImage)); + } + } + + @Test + void shouldReturnScreenshotLimitedBySwipeLimit() + { + init(1); + + Dimension dimension = new Dimension(375, 812); + when(genericWebDriverManager.getSize()).thenReturn(dimension); + when(genericWebDriverManager.getDpr()).thenReturn(3D); + + try (MockedStatic utils = mockStatic(ScreenshotUtils.class)) + { + utils.when(() -> ScreenshotUtils.takeViewportScreenshot(webDriver)) + .thenReturn(getImage(TOP_IMAGE)) + .thenReturn(getImage(FIRST_IMAGE)) + .thenReturn(getImage(SECOND_IMAGE)); + + BufferedImage fullImage = strategy.getScreenshot(webDriver); + + assertThat(getImage("swipe-limit-image.png"), ImageTool.equalImage(fullImage)); + } + } + + @ParameterizedTest + @ValueSource(strings = { "full-missmatch.png", "threshold-missmatch.png" }) + void shouldFailWithTemplateMissmatch(String image) + { + defaultInit(); + + Dimension dimension = new Dimension(375, 812); + when(genericWebDriverManager.getSize()).thenReturn(dimension); + when(genericWebDriverManager.getDpr()).thenReturn(3D); + + try (MockedStatic utils = mockStatic(ScreenshotUtils.class)) + { + utils.when(() -> ScreenshotUtils.takeViewportScreenshot(webDriver)) + .thenReturn(getImage(TOP_IMAGE)) + .thenReturn(getImage(FIRST_IMAGE)) + .thenReturn(getImage(image)); + + IllegalStateException thrown = assertThrows(IllegalStateException.class, + () -> strategy.getScreenshot(webDriver)); + assertThat(thrown.getCause(), instanceOf(TemplateMissmatchException.class)); + TemplateMissmatchException cause = (TemplateMissmatchException) thrown.getCause(); + assertEquals("Unable to match the template in the target image", cause.getMessage()); + } + } + + @Test + void shouldReturnStrategy() + { + defaultInit(); + ShootingStrategy shootingStrategy = mock(ShootingStrategy.class); + + assertEquals(strategy, strategy.getDecoratedShootingStrategy(shootingStrategy)); + + verifyNoInteractions(shootingStrategy); + } + + @Test + void shouldFailOnElementScreenshot() + { + defaultInit(); + Set coords = Set.of(); + + UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class, + () -> strategy.getScreenshot(webDriver, coords)); + assertEquals(UNSUPPORTED_MESSAGE, thrown.getMessage()); + } + + @Test + void shouldFailOnPrepareCoords() + { + defaultInit(); + Set coords = Set.of(); + + UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class, + () -> strategy.prepareCoords(coords)); + assertEquals(UNSUPPORTED_MESSAGE, thrown.getMessage()); + } + + private void defaultInit() + { + init(10); + } + + private void init(int swipeLimit) + { + MobileApplicationConfiguration mobileAppConfig = new MobileApplicationConfiguration(Duration.ZERO, + swipeLimit, 0, 0); + strategy = new MobileAppFullShootingStrategy(touchActions, genericWebDriverManager, mobileAppConfig, + attachmentPublisher); + } + + private BufferedImage getImage(String image) + { + byte[] bytes = ResourceUtils.loadResourceAsByteArray(getClass(), image); + try (InputStream inputStream = new ByteArrayInputStream(bytes)) + { + return ImageIO.read(inputStream); + } + catch (IOException e) + { + throw new UncheckedIOException(e); + } + } +} diff --git a/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/1st-scroll-image.png b/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/1st-scroll-image.png new file mode 100644 index 0000000000..e9778f68ab Binary files /dev/null and b/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/1st-scroll-image.png differ diff --git a/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/2nd-scroll-image.png b/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/2nd-scroll-image.png new file mode 100644 index 0000000000..94202c8c4e Binary files /dev/null and b/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/2nd-scroll-image.png differ diff --git a/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/3rd-scroll-image.png b/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/3rd-scroll-image.png new file mode 100644 index 0000000000..191687ff6f Binary files /dev/null and b/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/3rd-scroll-image.png differ diff --git a/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/full-image.png b/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/full-image.png new file mode 100644 index 0000000000..41646eee48 Binary files /dev/null and b/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/full-image.png differ diff --git a/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/full-missmatch.png b/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/full-missmatch.png new file mode 100644 index 0000000000..0e6d12576f Binary files /dev/null and b/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/full-missmatch.png differ diff --git a/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/swipe-limit-image.png b/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/swipe-limit-image.png new file mode 100644 index 0000000000..b3a88799c4 Binary files /dev/null and b/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/swipe-limit-image.png differ diff --git a/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/threshold-missmatch.png b/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/threshold-missmatch.png new file mode 100644 index 0000000000..1d8c239c30 Binary files /dev/null and b/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/threshold-missmatch.png differ diff --git a/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/top-image.png b/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/top-image.png new file mode 100644 index 0000000000..2fadca9cce Binary files /dev/null and b/vividus-plugin-mobile-app/src/test/resources/org/vividus/selenium/mobileapp/screenshot/strategies/top-image.png differ diff --git a/vividus-tests/src/main/resources/baselines/android-full-app-view.png b/vividus-tests/src/main/resources/baselines/android-full-app-view.png new file mode 100644 index 0000000000..425a7fabcf Binary files /dev/null and b/vividus-tests/src/main/resources/baselines/android-full-app-view.png differ diff --git a/vividus-tests/src/main/resources/baselines/ios-AREA-ignore.png b/vividus-tests/src/main/resources/baselines/ios-AREA-ignore.png index 4487b2903f..719fac1904 100644 Binary files a/vividus-tests/src/main/resources/baselines/ios-AREA-ignore.png and b/vividus-tests/src/main/resources/baselines/ios-AREA-ignore.png differ diff --git a/vividus-tests/src/main/resources/baselines/ios-ELEMENT-ignore.png b/vividus-tests/src/main/resources/baselines/ios-ELEMENT-ignore.png index 3c52425a49..65a7f05fff 100644 Binary files a/vividus-tests/src/main/resources/baselines/ios-ELEMENT-ignore.png and b/vividus-tests/src/main/resources/baselines/ios-ELEMENT-ignore.png differ diff --git a/vividus-tests/src/main/resources/baselines/ios-context-with-ignore.png b/vividus-tests/src/main/resources/baselines/ios-context-with-ignore.png index 8a0a785933..6edca572d2 100644 Binary files a/vividus-tests/src/main/resources/baselines/ios-context-with-ignore.png and b/vividus-tests/src/main/resources/baselines/ios-context-with-ignore.png differ diff --git a/vividus-tests/src/main/resources/baselines/ios-context.png b/vividus-tests/src/main/resources/baselines/ios-context.png index dd945171d3..e31c54ad01 100644 Binary files a/vividus-tests/src/main/resources/baselines/ios-context.png and b/vividus-tests/src/main/resources/baselines/ios-context.png differ diff --git a/vividus-tests/src/main/resources/baselines/ios-custom-config-AREA-ignore.png b/vividus-tests/src/main/resources/baselines/ios-custom-config-AREA-ignore.png index f9e046209f..757eeed0a5 100644 Binary files a/vividus-tests/src/main/resources/baselines/ios-custom-config-AREA-ignore.png and b/vividus-tests/src/main/resources/baselines/ios-custom-config-AREA-ignore.png differ diff --git a/vividus-tests/src/main/resources/baselines/ios-custom-config-ELEMENT-ignore.png b/vividus-tests/src/main/resources/baselines/ios-custom-config-ELEMENT-ignore.png index ed412589a7..e7b943c901 100644 Binary files a/vividus-tests/src/main/resources/baselines/ios-custom-config-ELEMENT-ignore.png and b/vividus-tests/src/main/resources/baselines/ios-custom-config-ELEMENT-ignore.png differ diff --git a/vividus-tests/src/main/resources/baselines/ios-custom-config.png b/vividus-tests/src/main/resources/baselines/ios-custom-config.png index b1e35b24ce..142fe473a8 100644 Binary files a/vividus-tests/src/main/resources/baselines/ios-custom-config.png and b/vividus-tests/src/main/resources/baselines/ios-custom-config.png differ diff --git a/vividus-tests/src/main/resources/baselines/ios-full-app-view.png b/vividus-tests/src/main/resources/baselines/ios-full-app-view.png new file mode 100644 index 0000000000..1acb69708b Binary files /dev/null and b/vividus-tests/src/main/resources/baselines/ios-full-app-view.png differ diff --git a/vividus-tests/src/main/resources/baselines/ios-full-page.png b/vividus-tests/src/main/resources/baselines/ios-full-page.png index 2a655f4b09..b5d091e499 100644 Binary files a/vividus-tests/src/main/resources/baselines/ios-full-page.png and b/vividus-tests/src/main/resources/baselines/ios-full-page.png differ diff --git a/vividus-tests/src/main/resources/baselines/original/android-full-app-view.png b/vividus-tests/src/main/resources/baselines/original/android-full-app-view.png new file mode 100644 index 0000000000..7a0dabc3dc Binary files /dev/null and b/vividus-tests/src/main/resources/baselines/original/android-full-app-view.png differ diff --git a/vividus-tests/src/main/resources/baselines/original/ios-AREA-ignore.png b/vividus-tests/src/main/resources/baselines/original/ios-AREA-ignore.png index 5b15714405..ded6680c6f 100644 Binary files a/vividus-tests/src/main/resources/baselines/original/ios-AREA-ignore.png and b/vividus-tests/src/main/resources/baselines/original/ios-AREA-ignore.png differ diff --git a/vividus-tests/src/main/resources/baselines/original/ios-ELEMENT-ignore.png b/vividus-tests/src/main/resources/baselines/original/ios-ELEMENT-ignore.png index 7d9a9ff549..e1de0e33fe 100644 Binary files a/vividus-tests/src/main/resources/baselines/original/ios-ELEMENT-ignore.png and b/vividus-tests/src/main/resources/baselines/original/ios-ELEMENT-ignore.png differ diff --git a/vividus-tests/src/main/resources/baselines/original/ios-context-with-ignore.png b/vividus-tests/src/main/resources/baselines/original/ios-context-with-ignore.png index b6f7742527..536321b4c3 100644 Binary files a/vividus-tests/src/main/resources/baselines/original/ios-context-with-ignore.png and b/vividus-tests/src/main/resources/baselines/original/ios-context-with-ignore.png differ diff --git a/vividus-tests/src/main/resources/baselines/original/ios-context.png b/vividus-tests/src/main/resources/baselines/original/ios-context.png index 6282c3c3d8..5c6db7ab5d 100644 Binary files a/vividus-tests/src/main/resources/baselines/original/ios-context.png and b/vividus-tests/src/main/resources/baselines/original/ios-context.png differ diff --git a/vividus-tests/src/main/resources/baselines/original/ios-custom-config-AREA-ignore.png b/vividus-tests/src/main/resources/baselines/original/ios-custom-config-AREA-ignore.png index f819c81e90..c14ff8d1e7 100644 Binary files a/vividus-tests/src/main/resources/baselines/original/ios-custom-config-AREA-ignore.png and b/vividus-tests/src/main/resources/baselines/original/ios-custom-config-AREA-ignore.png differ diff --git a/vividus-tests/src/main/resources/baselines/original/ios-custom-config-ELEMENT-ignore.png b/vividus-tests/src/main/resources/baselines/original/ios-custom-config-ELEMENT-ignore.png index 99bf9defac..7d040a9210 100644 Binary files a/vividus-tests/src/main/resources/baselines/original/ios-custom-config-ELEMENT-ignore.png and b/vividus-tests/src/main/resources/baselines/original/ios-custom-config-ELEMENT-ignore.png differ diff --git a/vividus-tests/src/main/resources/baselines/original/ios-custom-config.png b/vividus-tests/src/main/resources/baselines/original/ios-custom-config.png index dc21d5e8e6..9f7cfd2d1a 100644 Binary files a/vividus-tests/src/main/resources/baselines/original/ios-custom-config.png and b/vividus-tests/src/main/resources/baselines/original/ios-custom-config.png differ diff --git a/vividus-tests/src/main/resources/baselines/original/ios-full-app-view.png b/vividus-tests/src/main/resources/baselines/original/ios-full-app-view.png new file mode 100644 index 0000000000..eabbe607f2 Binary files /dev/null and b/vividus-tests/src/main/resources/baselines/original/ios-full-app-view.png differ diff --git a/vividus-tests/src/main/resources/baselines/original/ios-full-page.png b/vividus-tests/src/main/resources/baselines/original/ios-full-page.png index eb72af56dc..1c2f14b49b 100644 Binary files a/vividus-tests/src/main/resources/baselines/original/ios-full-page.png and b/vividus-tests/src/main/resources/baselines/original/ios-full-page.png differ diff --git a/vividus-tests/src/main/resources/data/tables/system/mobile_app/locators/android.table b/vividus-tests/src/main/resources/data/tables/system/mobile_app/locators/android.table index 77493d72fa..e37fe43cd2 100644 --- a/vividus-tests/src/main/resources/data/tables/system/mobile_app/locators/android.table +++ b/vividus-tests/src/main/resources/data/tables/system/mobile_app/locators/android.table @@ -6,6 +6,7 @@ |menuQrCodeXpath |//android.widget.TextView[@text='QR Code'] | |menuWaitXpath |//android.widget.TextView[@text='Wait'] | |menuScrollViewXpath |//android.widget.TextView[@text='Scroll View'] | +|menuShortScrollViewXpath |//android.widget.TextView[@text='Short Scroll View'] | |menuSliderXpath |//android.widget.TextView[@text='Slider'] | |nameInputXpath |//android.widget.EditText[@text='Enter your name...']| |action |COMPARE_AGAINST | diff --git a/vividus-tests/src/main/resources/data/tables/system/mobile_app/locators/ios.table b/vividus-tests/src/main/resources/data/tables/system/mobile_app/locators/ios.table index 6077ac427c..a7336caa50 100644 --- a/vividus-tests/src/main/resources/data/tables/system/mobile_app/locators/ios.table +++ b/vividus-tests/src/main/resources/data/tables/system/mobile_app/locators/ios.table @@ -6,6 +6,7 @@ |menuQrCodeXpath |//XCUIElementTypeButton[@name="QR Code"] | |menuWaitXpath |//XCUIElementTypeButton[@name="Wait"] | |menuScrollViewXpath |//XCUIElementTypeButton[@name="Scroll View"] | +|menuShortScrollViewXpath |//XCUIElementTypeButton[@name="Short Scroll View"] | |menuSliderXpath |//XCUIElementTypeButton[@name="Slider"] | |nameInputXpath |//XCUIElementTypeTextField[@value='Enter your name...']| |action |COMPARE_AGAINST | diff --git a/vividus-tests/src/main/resources/properties/suite/system/mobile_app/ios/suite.properties b/vividus-tests/src/main/resources/properties/suite/system/mobile_app/ios/suite.properties index 53c7790285..e7a6cb7df2 100644 --- a/vividus-tests/src/main/resources/properties/suite/system/mobile_app/ios/suite.properties +++ b/vividus-tests/src/main/resources/properties/suite/system/mobile_app/ios/suite.properties @@ -1,3 +1,5 @@ bdd.batch-1.meta-filters=groovy: !targetPlatform || targetPlatform.contains('ios') bdd.story-loader.batch-1.resource-include-patterns=MobileAppStepsTests.story,VisualTesting.story + +selenium.grid.capabilities.appium\:screenshotQuality=0 diff --git a/vividus-tests/src/main/resources/story/system/mobile_app/VisualTesting.story b/vividus-tests/src/main/resources/story/system/mobile_app/VisualTesting.story index 9ca7ad9c9b..a659b09aae 100644 --- a/vividus-tests/src/main/resources/story/system/mobile_app/VisualTesting.story +++ b/vividus-tests/src/main/resources/story/system/mobile_app/VisualTesting.story @@ -61,3 +61,13 @@ When I change context to element located `xpath()` When I baseline with `${target-platform}-context-with-ignore` ignoring: |ELEMENT | |By.accessibilityId(header)| +When I reset context + + +Scenario: Verify full application view check +When I tap on element located `accessibilityId(menuToggler)` +When I tap on element located `xpath()` +When I wait until element located `accessibilityId(body)` appears +When I baseline with `${target-platform}-full-app-view` using screenshot configuration: +|shootingStrategy| +|FULL |