Repository: vividus-framework/vividus
Pull request #5766: [plugin-applitools] Add properties for applitools tests
Author: login: uarlouski
Created: 2025-03-10T09:58:57Z | Merged: 2025-03-10T11:38:29Z



Diff:
diff --git a/docs/modules/plugins/partials/applitools-configuration.adoc b/docs/modules/plugins/partials/applitools-configuration.adoc
index f0f60ab021..56d48adc15 100644
--- a/docs/modules/plugins/partials/applitools-configuration.adoc
+++ b/docs/modules/plugins/partials/applitools-configuration.adoc
@@ -20,3 +20,4 @@
 ** `disableBrowserFetching` - By default, is `false`.  If `true`, page resources for rendering on the UFG will be fetched from outside of the browser.
 ** `layoutBreakpoints` - By default, is `true`.  If `true`, the viewport width and height are changed to the specified device's values prior to capturing the page source for rendering in UFG.
 ** `scaleRatio` - The scale ratio of the image, the less the ratio the smaller the image and vice versa, for example if we want to make the image 25% smaller the ration should be set to `0.75`, this can be useful if image height exceeds the Applitools height limit (15000 pixels).
+** `properties` - The list of key value pairs used as a metadata for visual test in Applitools. The required value format is `key1=value1, key2=value2, ...` where the key must not be empty e.g. `Component name=Menu Item, State=Open, Version=1`.
diff --git a/vividus-plugin-applitools/src/main/java/org/vividus/visual/eyes/converter/ExamplesTableToApplitoolsVisualChecksConverter.java b/vividus-plugin-applitools/src/main/java/org/vividus/visual/eyes/converter/ExamplesTableToApplitoolsVisualChecksConverter.java
index a05a7ca552..b0c7f3fa2a 100644
--- a/vividus-plugin-applitools/src/main/java/org/vividus/visual/eyes/converter/ExamplesTableToApplitoolsVisualChecksConverter.java
+++ b/vividus-plugin-applitools/src/main/java/org/vividus/visual/eyes/converter/ExamplesTableToApplitoolsVisualChecksConverter.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019-2024 the original author or authors.
+ * Copyright 2019-2025 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.visual.eyes.converter;
 
+import static org.apache.commons.lang3.Validate.isTrue;
+
 import java.lang.reflect.Type;
 import java.net.URI;
 import java.util.List;
@@ -64,12 +66,13 @@ public class ExamplesTableToApplitoolsVisualChecksConverter extends
     private static final String AREAS_TO_IGNORE_OPTION = "areasToIgnore";
     private static final String ACCESSIBILITY_STANDARD_OPTION = "accessibilityStandard";
     private static final String SCALE_RATIO = "scaleRatio";
+    private static final String PROPERTIES = "properties";
 
     private static final List<String> SUPPORTED_OPTIONS = List.of(BASELINE_NAME_OPTION, ACTION_OPTION,
             EXECUTE_API_KEY_OPTION, READ_API_KEY_OPTION, HOST_APP_OPTION, HOST_OS_OPTION, VIEWPORT_SIZE_OPTION,
             MATCH_LEVEL_OPTION, DISABLE_BROWSER_FETCHING, SERVER_URI_OPTION, APP_NAME_OPTION, BATCH_NAME_OPTION,
             BASELINE_ENV_NAME_OPTION, ELEMENTS_TO_IGNORE_OPTION, AREAS_TO_IGNORE_OPTION, ACCESSIBILITY_STANDARD_OPTION,
-            LAYOUT_BREAKPOINTS, SCALE_RATIO);
+            LAYOUT_BREAKPOINTS, SCALE_RATIO, PROPERTIES);
 
     private String executeApiKey;
     private String readApiKey;
@@ -112,7 +115,8 @@ public List<ApplitoolsVisualCheck> convertValue(ExamplesTable table, Type type)
             VisualActionType action = params.valueAs(ACTION_OPTION, VisualActionType.class);
 
             ApplitoolsVisualCheck check = createCheck(batchName, baselineName, action);
-            Type targetType = new TypeLiteral<Set<Locator>>() { }.value;
+            Type locatorsType = new TypeLiteral<Set<Locator>>() { }.value;
+            Type propertiesType = new TypeLiteral<List<String>>() { }.value;
 
             configureEyes(
                 check,
@@ -127,10 +131,11 @@ public List<ApplitoolsVisualCheck> convertValue(ExamplesTable table, Type type)
                 params.valueAs(APP_NAME_OPTION, String.class, appName),
                 params.valueAs(BASELINE_ENV_NAME_OPTION, String.class, baselineEnvName),
                 params.valueAs(READ_API_KEY_OPTION, String.class, readApiKey),
-                params.valueAs(ELEMENTS_TO_IGNORE_OPTION, targetType, Set.of()),
-                params.valueAs(AREAS_TO_IGNORE_OPTION, targetType, Set.of()),
+                params.valueAs(ELEMENTS_TO_IGNORE_OPTION, locatorsType, Set.of()),
+                params.valueAs(AREAS_TO_IGNORE_OPTION, locatorsType, Set.of()),
                 params.valueAs(ACCESSIBILITY_STANDARD_OPTION, AccessibilitySettings.class, null),
-                params.valueAs(SCALE_RATIO, Double.class, null)
+                params.valueAs(SCALE_RATIO, Double.class, null),
+                params.valueAs(PROPERTIES, propertiesType, List.of())
             );
 
             return check;
@@ -142,7 +147,8 @@ public ApplitoolsVisualCheck create(String batchName, String baselineName, Visua
     {
         ApplitoolsVisualCheck check = createCheck(batchName, baselineName, action);
         configureEyes(check, executeApiKey, hostApp, hostOS, viewportSize, matchLevel, disableBrowserFetching,
-                layoutBreakpoints, serverUri, appName, baselineEnvName, readApiKey, Set.of(), Set.of(), null, null);
+                layoutBreakpoints, serverUri, appName, baselineEnvName, readApiKey, Set.of(), Set.of(), null, null,
+                List.of());
         return check;
     }
 
@@ -155,8 +161,11 @@ private ApplitoolsVisualCheck createCheck(String batchName, String baselineName,
     private void configureEyes(ApplitoolsVisualCheck check, String executeApiKey, String hostApp, String hostOS,
             Dimension viewportSize, MatchLevel matchLevel, Boolean disableBrowserFetching, Boolean layoutBreakpoints,
             URI serverUri, String appName, String baselineEnvName, String readApiKey, Set<Locator> elements,
-            Set<Locator> areas, AccessibilitySettings settings, Double scaleRatio)
+            Set<Locator> areas, AccessibilitySettings settings, Double scaleRatio, List<String> properties)
     {
+        properties.forEach(prop -> isTrue(prop.matches("^(?!\s*=).+?=.*"), "The property defined in 'properties' "
+                + "field has invalid format, expected <not empty key>=<value> but got %s", prop));
+
         check.setScreenshotParameters(screenshotParametersFactory.create());
         check.setReadApiKey(readApiKey);
         check.setElementsToIgnore(elements);
@@ -180,6 +189,12 @@ private void configureEyes(ApplitoolsVisualCheck check, String executeApiKey, St
                 .setAccessibilityValidation(settings)
                 .setScaleRatio(scaleRatio);
 
+        properties.forEach(prop ->
+        {
+            String[] keyAndValue = prop.split("=", 2);
+            configuration.addProperty(keyAndValue[0], keyAndValue[1]);
+        });
+
         boolean saveTests = check.getAction() == VisualActionType.ESTABLISH;
         configuration.setSaveFailedTests(saveTests);
         configuration.setSaveNewTests(saveTests);
diff --git a/vividus-plugin-applitools/src/test/java/org/vividus/visual/eyes/converter/ExamplesTableToApplitoolsVisualChecksConverterTests.java b/vividus-plugin-applitools/src/test/java/org/vividus/visual/eyes/converter/ExamplesTableToApplitoolsVisualChecksConverterTests.java
index 4270efc448..bef19812e0 100644
--- a/vividus-plugin-applitools/src/test/java/org/vividus/visual/eyes/converter/ExamplesTableToApplitoolsVisualChecksConverterTests.java
+++ b/vividus-plugin-applitools/src/test/java/org/vividus/visual/eyes/converter/ExamplesTableToApplitoolsVisualChecksConverterTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019-2024 the original author or authors.
+ * Copyright 2019-2025 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.
@@ -36,6 +36,7 @@
 import com.applitools.eyes.AccessibilityLevel;
 import com.applitools.eyes.AccessibilitySettings;
 import com.applitools.eyes.MatchLevel;
+import com.applitools.eyes.PropertyData;
 import com.applitools.eyes.RectangleSize;
 import com.applitools.eyes.config.Configuration;
 
@@ -49,6 +50,8 @@
 import org.jbehave.core.steps.ParameterConverters.FunctionalParameterConverter;
 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.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
@@ -83,8 +86,8 @@ void shouldConvertExamplesTableIntoApplitoolsVisualChecksAllParameters()
         when(baselineIndexer.createIndexedBaseline(BASELINE)).thenReturn(BASELINE);
 
          // CHECKSTYLE:OFF
-        String table = "|executeApiKey  |readApiKey  |hostApp |hostOS |viewportSize|matchLevel|disableBrowserFetching|layoutBreakpoints|serverUri          |appName |batchName |baselineEnvName  |elementsToIgnore|areasToIgnore|baselineName |action   |accessibilityStandard|scaleRatio|" + System.lineSeparator()
-                     + "|execute-api-key|read-api-key|host-app|host-os|1x1         |EXACT     |true                  |true             |https://example.com|app-name|batch-name|baseline-env-name|elements        |areas        |baseline-name|ESTABLISH|WCAG 2.1 - AA        |0.75      |";
+        String table = "|executeApiKey  |readApiKey  |hostApp |hostOS |viewportSize|matchLevel|disableBrowserFetching|layoutBreakpoints|serverUri          |appName |batchName |baselineEnvName  |elementsToIgnore|areasToIgnore|baselineName |action   |accessibilityStandard|scaleRatio|properties                            |" + System.lineSeparator()
+                     + "|execute-api-key|read-api-key|host-app|host-os|1x1         |EXACT     |true                  |true             |https://example.com|app-name|batch-name|baseline-env-name|elements        |areas        |baseline-name|ESTABLISH|WCAG 2.1 - AA        |0.75      |Test type=Unit test, Plugin=Applitools|";
         // CHECKSTYLE:ON
         ExamplesTable applitoolsCheckTable = createTable(table);
 
@@ -116,10 +119,23 @@ void shouldConvertExamplesTableIntoApplitoolsVisualChecksAllParameters()
             () -> assertEquals(BATCH, configuration.getBatch().getName()),
             () -> assertEquals(0.75d, configuration.getScaleRatio()),
             () -> assertEquals(AccessibilityLevel.AA, settings.getLevel()),
-            () -> assertEquals(AccessibilityGuidelinesVersion.WCAG_2_1, settings.getGuidelinesVersion())
+            () -> assertEquals(AccessibilityGuidelinesVersion.WCAG_2_1, settings.getGuidelinesVersion()),
+            () ->
+            {
+                List<PropertyData> propsData = configuration.getProperties();
+                assertThat(propsData, hasSize(2));
+                assertPropertyData(propsData.get(0), "Test type", "Unit test");
+                assertPropertyData(propsData.get(1), "Plugin", "Applitools");
+            }
         );
     }
 
+    private void assertPropertyData(PropertyData propData, String key, String value)
+    {
+        assertEquals(key, propData.getName());
+        assertEquals(value, propData.getValue());
+    }
+
     @Test
     void shouldCreateApplitoolsVisualChecksWithDefaultParameters()
     {
@@ -136,6 +152,20 @@ void shouldConvertExamplesTableIntoApplitoolsVisualChecksWithDefaultParameters()
         testDefaultParameters(() -> converter.convertValue(applitoolsCheckTable, null));
     }
 
+    @ParameterizedTest
+    @ValueSource(strings = { "=value", "parampampam" })
+    void shouldFailOnInvalidProperties(String invalidProp)
+    {
+        String table = "|batchName |baselineName |action         |properties|" + System.lineSeparator()
+                + "|batch-name|baseline-name|COMPARE_AGAINST|%s|".formatted(invalidProp);
+        ExamplesTable applitoolsCheckTable = createTable(table);
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+                () -> converter.convertValue(applitoolsCheckTable, null));
+        assertEquals("The property defined in 'properties' field has invalid format, expected <not empty key>=<value>"
+                + " but got %s".formatted(invalidProp), thrown.getMessage());
+    }
+
     private void testDefaultParameters(Supplier<List<ApplitoolsVisualCheck>> checksSupplier)
     {
         when(screenshotParametersFactory.create()).thenReturn(Optional.of(screenshotParameters));
@@ -186,7 +216,8 @@ private void testDefaultParameters(Supplier<List<ApplitoolsVisualCheck>> checksS
             () -> assertFalse(configuration.isDefaultLayoutBreakpointsSet()),
             () -> assertEquals(BATCH, configuration.getBatch().getName()),
             () -> assertNull(configuration.getAccessibilityValidation()),
-            () -> assertNull(configuration.getScaleRatio())
+            () -> assertNull(configuration.getScaleRatio()),
+            () -> assertEquals(List.of(), configuration.getProperties())
         );
     }
 


Reviews:
user: login: valfirst
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T10:17:21Z
user: login: uarlouski
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T10:39:18Z
user: login: valfirst
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-10T11:37:04Z

Review comments:
diff_hunk: @@ -20,3 +20,4 @@
 ** `disableBrowserFetching` - By default, is `false`.  If `true`, page resources for rendering on the UFG will be fetched from outside of the browser.
 ** `layoutBreakpoints` - By default, is `true`.  If `true`, the viewport width and height are changed to the specified device's values prior to capturing the page source for rendering in UFG.
 ** `scaleRatio` - The scale ratio of the image, the less the ratio the smaller the image and vice versa, for example if we want to make the image 25% smaller the ration should be set to `0.75`, this can be useful if image height exceeds the Applitools height limit (15000 pixels).
+** `properties` - The list of key value pairs used as a metadata for visual test in Applitools. The required value format is `key1=value1, key2=value2`, where the key must not be empty.
path: docs/modules/plugins/partials/applitools-configuration.adoc
user: login: valfirst
body: please add some relevant examples
created_at: 2025-03-10T10:16:59Z
author_association: COLLABORATOR
diff_hunk: @@ -20,3 +20,4 @@
 ** `disableBrowserFetching` - By default, is `false`.  If `true`, page resources for rendering on the UFG will be fetched from outside of the browser.
 ** `layoutBreakpoints` - By default, is `true`.  If `true`, the viewport width and height are changed to the specified device's values prior to capturing the page source for rendering in UFG.
 ** `scaleRatio` - The scale ratio of the image, the less the ratio the smaller the image and vice versa, for example if we want to make the image 25% smaller the ration should be set to `0.75`, this can be useful if image height exceeds the Applitools height limit (15000 pixels).
+** `properties` - The list of key value pairs used as a metadata for visual test in Applitools. The required value format is `key1=value1, key2=value2`, where the key must not be empty.
path: docs/modules/plugins/partials/applitools-configuration.adoc
user: login: valfirst
body: how to escape comma?
created_at: 2025-03-10T10:17:18Z
author_association: COLLABORATOR
diff_hunk: @@ -20,3 +20,4 @@
 ** `disableBrowserFetching` - By default, is `false`.  If `true`, page resources for rendering on the UFG will be fetched from outside of the browser.
 ** `layoutBreakpoints` - By default, is `true`.  If `true`, the viewport width and height are changed to the specified device's values prior to capturing the page source for rendering in UFG.
 ** `scaleRatio` - The scale ratio of the image, the less the ratio the smaller the image and vice versa, for example if we want to make the image 25% smaller the ration should be set to `0.75`, this can be useful if image height exceeds the Applitools height limit (15000 pixels).
+** `properties` - The list of key value pairs used as a metadata for visual test in Applitools. The required value format is `key1=value1, key2=value2`, where the key must not be empty.
path: docs/modules/plugins/partials/applitools-configuration.adoc
user: login: uarlouski
body: not supported by jbehave
created_at: 2025-03-10T10:39:18Z
author_association: MEMBER

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-10T10:04:14Z
body: # [Qodana](https://qodana.cloud/projects/3eQN1/reports/jgMRv) for JVM

**It seems all right 👌**

No new problems were found according to the checks applied

💡 Qodana analysis was run in the pull request mode: only the changed files were checked
☁️ [View the detailed Qodana report](https://qodana.cloud/projects/3eQN1/reports/jgMRv)

<details>
<summary>Contact Qodana team</summary>

Contact us at [qodana-support@jetbrains.com](mailto:qodana-support@jetbrains.com)
  - Or via our issue tracker: https://jb.gg/qodana-issue
  - Or share your feedback: https://jb.gg/qodana-discussions
</details>
<!-- JetBrains/qodana-action@v2024.3.4 : Qodana for JVM -->
author_association: NONE
user: login: codecov[bot]
created_at: 2025-03-10T10:07:01Z
body: ## [Codecov](https://app.codecov.io/gh/vividus-framework/vividus/pull/5766?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vividus-framework) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 97.74%. Comparing base [(`81dba42`)](https://app.codecov.io/gh/vividus-framework/vividus/commit/81dba42e905ff0c774dd401df3213885810c2b83?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vividus-framework) to head [(`bbb38f7`)](https://app.codecov.io/gh/vividus-framework/vividus/commit/bbb38f719fa986d90e45efbf7b7d7a42d90b38d7?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vividus-framework).
> Report is 2 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##             master    #5766   +/-   ##
=========================================
  Coverage     97.73%   97.74%           
  Complexity     7199     7199           
=========================================
  Files           993      993           
  Lines         20881    20889    +8     
  Branches       1373     1373           
=========================================
+ Hits          20409    20417    +8     
  Misses          362      362           
  Partials        110      110           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/vividus-framework/vividus/pull/5766?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vividus-framework).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vividus-framework).

<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- 📦 [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-03-10T10:45:57Z
body: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=vividus-framework_vividus&pullRequest=5766) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [1 Security Hotspot](https://sonarcloud.io/project/security_hotspots?id=vividus-framework_vividus&pullRequest=5766&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=vividus-framework_vividus&pullRequest=5766)


author_association: NONE