-
-
Notifications
You must be signed in to change notification settings - Fork 13
/
VisibilityTrait.php
139 lines (120 loc) · 4.92 KB
/
VisibilityTrait.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
<?php
declare(strict_types=1);
namespace DrevOps\BehatSteps;
/**
* Trait VisibilityTrait.
*
* Visibility-related steps.
*
* @package DrevOps\BehatSteps
*/
trait VisibilityTrait {
/**
* Assert that element with specified CSS is visible on page.
*
* @Then /^(?:|I )should see a visible "(?P<selector>[^"]*)" element$/
*/
public function visibilityAssertElementIsVisible(string $selector): void {
$element = $this->getSession()->getPage();
$nodes = $element->findAll('css', $selector);
if (empty($nodes)) {
throw new \Exception(sprintf('Element defined by "%s" selector is not present on the page.', $selector));
}
foreach ($nodes as $node) {
if (!$node->isVisible()) {
throw new \Exception(sprintf('Element defined by "%s" selector is not visible on the page.', $selector));
}
}
}
/**
* Assert that element with specified CSS is visible on page.
*
* @Then /^(?:|I )should not see a visible "(?P<selector>[^"]*)" element$/
*/
public function visibilityAssertElementIsNotVisible(string $selector): void {
$element = $this->getSession()->getPage();
$nodes = $element->findAll('css', $selector);
foreach ($nodes as $node) {
if ($node->isVisible()) {
throw new \Exception(sprintf('Element defined by "%s" selector is visible on the page, but should not be.', $selector));
}
}
}
/**
* Assert that element with specified CSS is visually visible on page.
*
* @Then /^(?:|I )should see a visually visible "(?P<selector>[^"]*)" element(?: with top offset of "([^"]*)" pixels)?$/
*/
public function visibilityAssertElementIsVisuallyVisible(string $selector, int $offset = 0): void {
$this->visibilityAssertElementIsVisible($selector);
if (!$this->visibilityElementIsVisuallyVisible($selector, $offset)) {
throw new \Exception(sprintf('Element(s) defined by "%s" selector is not visually visible on the page.', $selector));
}
}
/**
* Assert that element with specified CSS is visually hidden on page.
*
* Visually hidden means either:
* - element is not rendered in the layout (i.e., CSS is "display: none").
* - element is rendered in the layout, but not visible to the viewer (i.e.,
* when one of the screen reader-only techniques is used).
*
* @Then /^(?:|I )should not see a visually hidden "(?P<selector>[^"]*)" element(?: with top offset of "([^"]*)" pixels)?$/
*/
public function visibilityAssertElementIsVisuallyHidden(string $selector, int $offset = 0): void {
if ($this->visibilityElementIsVisuallyVisible($selector, $offset)) {
throw new \Exception(sprintf('Element(s) defined by "%s" selector is visually visible on the page, but should not be.', $selector));
}
}
/**
* Check if an element is visually visible using different FE techniques.
*
* @param string $selector
* CSS query selector.
* @param int $offset
* (optional) Vertical element offset in pixels. Defaults to 0.
*
* @return bool
* TRUE if an element is visually visible, FALSE if not.
*/
protected function visibilityElementIsVisuallyVisible(string $selector, int $offset) {
// The contents of this JS function should be copied as-is from the <script>
// section in the bottom of the tests/behat/fixtures/relative.html file.
$scriptFunction = <<<JS
function isElemVisible(selector, offset = 0) {
var failures = [];
document.querySelectorAll(selector).forEach(function (el) {
// Inject a style to disable scrollbars for more consistent results.
if (document.querySelectorAll('head #relative_style').length === 0) {
document.querySelector('head').insertAdjacentHTML('beforeend', '<style id="relative_style" type="text/css">::-webkit-scrollbar{display: none;}</style>');
}
// Scroll to the element top, accounting for an offset.
window.scroll({top: el.offsetTop - offset});
// Gather visibility constraints.
isVisible = !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length);
hasHeight = el.clientHeight > 1 || el.offsetHeight > 1;
notClipped = !(getComputedStyle(el).clip === 'rect(0px 0px 0px 0px)' && getComputedStyle(el).position === 'absolute');
rect = el.getBoundingClientRect();
onScreen = !(
rect.left + rect.width <= 0
|| rect.top + rect.height <= 0
|| rect.left >= window.innerWidth
|| rect.top >= window.innerHeight
);
if (!isVisible || !hasHeight || !notClipped || !onScreen) {
failures.push(el);
}
});
return failures.length === 0;
}
JS;
// Include and call visibility assertion function.
$script = <<<JS
(function() {
{$scriptFunction}
return isElemVisible('{$selector}', {$offset});
})();
JS;
return $this->getSession()->getDriver()->evaluateScript($script);
}
}