Skip to content

Commit 489be47

Browse files
committed
Add step to verify element CSS properties
1 parent f805340 commit 489be47

File tree

14 files changed

+901
-246
lines changed

14 files changed

+901
-246
lines changed

docs/modules/plugins/partials/ui-element-css-property-steps.adoc

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,30 @@ Then context element has CSS property `$cssName` with value that $comparisonRule
1919
When I change context to element located by `id(rainbow)`
2020
Then context element has CSS property `color` with value that is equal to `rgba(0, 0, 0, 1)`
2121
----
22+
23+
=== Check an element CSS properties
24+
25+
Checks the context element has the expected CSS properties.
26+
27+
The context can be set by the <<_change_context,corresponding steps>>.
28+
29+
[source,gherkin]
30+
----
31+
Then context element has CSS properties:$parameters
32+
----
33+
34+
* `$parameters` - The xref:ROOT:glossary.adoc#_examplestable[ExamplesTable] containing the following columns:
35+
** [subs=+quotes]`*cssName*` - A name of the CSS property.
36+
** [subs=+quotes]`*comparisonRule*` - CSS property xref:parameters:string-comparison-rule.adoc[comparison rule].
37+
** [subs=+quotes]`*expectedValue*` - The expected value of the CSS property.
38+
39+
.Change context to element and verify several CSS properties
40+
[source,gherkin]
41+
----
42+
When I change context to element located by `id(block)`
43+
Then context element has CSS properties:
44+
|cssName |comparisonRule |expectedValue |
45+
|align-items |is equal to |center |
46+
|border |contains |solid |
47+
|font-size |contains |20 |
48+
----
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2019-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.vividus.steps.ui.web;
18+
19+
import org.vividus.steps.StringComparisonRule;
20+
21+
public class CssValidationResult
22+
{
23+
private String cssName;
24+
private String cssActualValue;
25+
private StringComparisonRule comparisonRule;
26+
private String cssExpectedValue;
27+
private boolean passed;
28+
29+
public CssValidationResult(String cssName, String cssActualValue, StringComparisonRule comparisonRule,
30+
String cssExpectedValue, boolean passed)
31+
{
32+
this.cssName = cssName;
33+
this.cssActualValue = cssActualValue;
34+
this.comparisonRule = comparisonRule;
35+
this.cssExpectedValue = cssExpectedValue;
36+
this.passed = passed;
37+
}
38+
39+
public String getCssName()
40+
{
41+
return cssName;
42+
}
43+
44+
public void setCssName(String cssName)
45+
{
46+
this.cssName = cssName;
47+
}
48+
49+
public String getCssActualValue()
50+
{
51+
return cssActualValue;
52+
}
53+
54+
public void setCssActualValue(String cssActualValue)
55+
{
56+
this.cssActualValue = cssActualValue;
57+
}
58+
59+
public StringComparisonRule getComparisonRule()
60+
{
61+
return comparisonRule;
62+
}
63+
64+
public void setComparisonRule(StringComparisonRule comparisonRule)
65+
{
66+
this.comparisonRule = comparisonRule;
67+
}
68+
69+
public String getCssExpectedValue()
70+
{
71+
return cssExpectedValue;
72+
}
73+
74+
public void setCssExpectedValue(String cssExpectedValue)
75+
{
76+
this.cssExpectedValue = cssExpectedValue;
77+
}
78+
79+
public boolean isPassed()
80+
{
81+
return passed;
82+
}
83+
84+
public void setPassed(boolean passed)
85+
{
86+
this.passed = passed;
87+
}
88+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
function getComputedStyleAsMap(element) {
2+
var computedStyle = getComputedStyle(element);
3+
var computedStyleMap = new Map(Object.entries(computedStyle));
4+
for (const [key] of computedStyleMap) {
5+
if (/^\d+$/.test(key)) {
6+
computedStyleMap.delete(key);
7+
}
8+
}
9+
return Object.fromEntries(computedStyleMap);
10+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<#ftl strip_whitespace=true>
2+
3+
<!doctype html>
4+
<html lang="en">
5+
<head>
6+
<meta charset="utf-8">
7+
<title>CSS properties validation results table</title>
8+
<link rel="stylesheet" href="../../styles.css"/>
9+
<link rel="stylesheet" href="../../webjars/bootstrap/3.4.1/css/bootstrap.min.css"/>
10+
</head>
11+
<body>
12+
<style>
13+
table {
14+
border-collapse: collapse;
15+
width: 100%;
16+
}
17+
table th {
18+
text-align: center;
19+
font-family: Arial, Helvetica, sans-serif;
20+
}
21+
thead th {
22+
position: sticky;
23+
position: -webkit-sticky;
24+
top: 0;
25+
z-index: 10;
26+
background: #dfe2e4;
27+
}
28+
tr.pass {
29+
background-color: #d4edda;
30+
}
31+
tr.fail {
32+
background-color: #f8d7da;
33+
}
34+
.icon {
35+
display: inline-block;
36+
width: 16px;
37+
height: 16px;
38+
text-align: center;
39+
vertical-align: middle;
40+
}
41+
.icon.pass {
42+
color: #28a745;
43+
}
44+
.icon.fail {
45+
color: #dc3545;
46+
}
47+
</style>
48+
49+
<table class="table table-hover table-bordered table-condensed fixedHeader">
50+
<thead>
51+
<tr>
52+
<th>Css Property</th>
53+
<th>Actual Value</th>
54+
<th>Comparison Rule</th>
55+
<th>Expected</th>
56+
<th>Result</th>
57+
</tr>
58+
</thead>
59+
<tbody>
60+
<#list cssResults as cssResult>
61+
<tr class="${cssResult.passed?string('pass', 'fail')}">
62+
<td>${cssResult.cssName}</td>
63+
<td>${cssResult.cssActualValue!""}</td>
64+
<td>${cssResult.comparisonRule}</td>
65+
<td>${cssResult.cssExpectedValue}</td>
66+
<td>
67+
<span class="icon ${cssResult.passed?string('pass', 'fail')}">
68+
${cssResult.passed?string('✔', '✖')}
69+
</span>
70+
</td>
71+
</tr>
72+
</#list>
73+
</tbody>
74+
</table>
75+
<script src="../../webjars/bootstrap/3.4.1/js/bootstrap.min.js"></script>
76+
</body>
77+
</html>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2019-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.vividus.steps.ui.web;
18+
19+
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertFalse;
21+
22+
import org.junit.jupiter.api.Test;
23+
import org.vividus.steps.StringComparisonRule;
24+
25+
class CssValidationResultTests
26+
{
27+
@Test
28+
void testToCoverage()
29+
{
30+
final String newKey = "new_key";
31+
final String newValue = "new_value";
32+
final String newExpected = "new_expected";
33+
CssValidationResult result = new CssValidationResult("key", "value", StringComparisonRule.CONTAINS,
34+
"expected", true);
35+
result.setCssName(newKey);
36+
result.setCssActualValue(newValue);
37+
result.setComparisonRule(StringComparisonRule.IS_EQUAL_TO);
38+
result.setCssExpectedValue(newExpected);
39+
result.setPassed(false);
40+
assertEquals(newKey, result.getCssName());
41+
assertEquals(newValue, result.getCssActualValue());
42+
assertEquals(StringComparisonRule.IS_EQUAL_TO, result.getComparisonRule());
43+
assertEquals(newExpected, result.getCssExpectedValue());
44+
assertFalse(result.isPassed());
45+
}
46+
}

vividus-plugin-web-app-playwright/src/main/java/org/vividus/ui/web/playwright/steps/ElementSteps.java

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.io.File;
2020
import java.io.IOException;
21+
import java.util.ArrayList;
22+
import java.util.List;
2123
import java.util.Map;
2224
import java.util.Optional;
2325
import java.util.Set;
@@ -26,13 +28,20 @@
2628
import com.microsoft.playwright.assertions.PlaywrightAssertions;
2729
import com.microsoft.playwright.options.BoundingBox;
2830

31+
import org.apache.commons.lang3.StringUtils;
32+
import org.apache.commons.text.CaseUtils;
2933
import org.hamcrest.Matcher;
3034
import org.jbehave.core.annotations.Then;
3135
import org.jbehave.core.annotations.When;
36+
import org.jbehave.core.model.ExamplesTable;
37+
import org.jbehave.core.steps.Parameters;
3238
import org.vividus.context.VariableContext;
39+
import org.vividus.reporter.event.IAttachmentPublisher;
3340
import org.vividus.softassert.ISoftAssert;
3441
import org.vividus.steps.ComparisonRule;
3542
import org.vividus.steps.StringComparisonRule;
43+
import org.vividus.steps.ui.web.CssValidationResult;
44+
import org.vividus.ui.web.action.JavascriptActions;
3645
import org.vividus.ui.web.action.ResourceFileLoader;
3746
import org.vividus.ui.web.playwright.UiContext;
3847
import org.vividus.ui.web.playwright.action.ElementActions;
@@ -43,23 +52,30 @@
4352

4453
public class ElementSteps
4554
{
55+
private static final String ELEMENT_CSS_CONTAINING_VALUE = "Element has CSS property '%s' containing value '%s'";
56+
4657
private final UiContext uiContext;
4758
private final ISoftAssert softAssert;
4859
private final VariableContext variableContext;
4960
private final ElementActions elementActions;
5061
private final PlaywrightSoftAssert playwrightSoftAssert;
5162
private final ResourceFileLoader resourceFileLoader;
63+
private final JavascriptActions javascriptActions;
64+
private final IAttachmentPublisher attachmentPublisher;
5265

5366
public ElementSteps(UiContext uiContext, ISoftAssert softAssert, VariableContext variableContext,
5467
ElementActions elementActions, PlaywrightSoftAssert playwrightSoftAssert,
55-
ResourceFileLoader resourceFileLoader)
68+
ResourceFileLoader resourceFileLoader, JavascriptActions javascriptActions,
69+
IAttachmentPublisher attachmentPublisher)
5670
{
5771
this.uiContext = uiContext;
5872
this.softAssert = softAssert;
5973
this.variableContext = variableContext;
6074
this.elementActions = elementActions;
6175
this.playwrightSoftAssert = playwrightSoftAssert;
6276
this.resourceFileLoader = resourceFileLoader;
77+
this.javascriptActions = javascriptActions;
78+
this.attachmentPublisher = attachmentPublisher;
6379
}
6480

6581
/**
@@ -327,4 +343,63 @@ private void saveAttributeValueOfElement(Locator element, String attributeName,
327343
() -> softAssert.recordFailedAssertion(
328344
String.format("The '%s' attribute does not exist", attributeName)));
329345
}
346+
347+
/**
348+
* Checks that the context <b>element</b> has an expected <b>CSS properties</b>
349+
* <p>The expected CSS parameters to be defined in the ExamplesTable:</p>
350+
* <ul>
351+
* <li><b>cssName</b> - the name of the CSS property</li>
352+
* <li><b>comparisonRule</b> - String comparison rule: "is equal to", "contains", "does not contain",
353+
* "matches".</li>
354+
* <li><b>expectedValue</b> - expected CSS property value</li>
355+
* </ul>
356+
* <p>Usage example:</p>
357+
* <code>
358+
* <br>Then context element has CSS properties:
359+
* <br>|cssName |comparisonRule |expectedValue |
360+
* <br>|border |contains |solid |
361+
* </code>
362+
*
363+
* @param parameters The parameters of the expected CSS properties to set as ExamplesTable
364+
*/
365+
@Then("context element has CSS properties:$parameters")
366+
public void doesElementHasCssProperties(ExamplesTable parameters)
367+
{
368+
String getAllCssScript =
369+
org.vividus.util.ResourceUtils.loadResource("org/vividus/ui/web/get-element-computed-css-func.js");
370+
String script = "([el]) => {" + getAllCssScript + "return getComputedStyleAsMap(el)}";
371+
Locator element = uiContext.getCurrentContexOrPageRoot();
372+
Map<String, String> elementCss = javascriptActions.executeScript(script, element.elementHandle());
373+
374+
List<CssValidationResult> cssResults = validateElementCss(parameters, elementCss);
375+
attachmentPublisher.publishAttachment("templates/css_validation_result.ftl",
376+
Map.of("cssResults", cssResults), "Css validation results");
377+
}
378+
379+
private List<CssValidationResult> validateElementCss(ExamplesTable parameters, Map<String, String> elementCss)
380+
{
381+
List<Parameters> rowsAsParameters = parameters.getRowsAsParameters();
382+
List<CssValidationResult> cssResults = new ArrayList<>();
383+
rowsAsParameters.forEach(params ->
384+
{
385+
Map<String, String> values = params.values();
386+
String cssName = values.get("cssName");
387+
String expectedValue = values.get("expectedValue");
388+
StringComparisonRule comparisonRule = params.valueAs("comparisonRule", StringComparisonRule.class);
389+
390+
String actualCssValue = getCssValue(elementCss, cssName);
391+
boolean passed = softAssert.assertThat(String.format(ELEMENT_CSS_CONTAINING_VALUE, cssName, expectedValue),
392+
actualCssValue, comparisonRule.createMatcher(expectedValue));
393+
cssResults.add(new CssValidationResult(cssName, actualCssValue, comparisonRule, expectedValue, passed));
394+
});
395+
return cssResults;
396+
}
397+
398+
private String getCssValue(Map<String, String> cssMap, String cssName)
399+
{
400+
return Optional.ofNullable(cssMap.get(cssName)).orElseGet(() -> {
401+
String cssValueAsCamelCase = CaseUtils.toCamelCase(StringUtils.removeStart(cssName, '-'), false, '-');
402+
return cssMap.get(cssValueAsCamelCase);
403+
});
404+
}
330405
}

0 commit comments

Comments
 (0)