Completed
Push — master ( 570bd3...e9ea8a )
by Vitaly
07:33
created

GenericFeatureContext::spin()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 9.0534
c 0
b 0
f 0
cc 4
eloc 11
nc 4
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace samsonframework\behatextension;
6
7
use Behat\Behat\Hook\Scope\AfterStepScope;
8
use Behat\Mink\Driver\Selenium2Driver;
9
use Behat\Mink\Element\NodeElement;
10
use Behat\MinkExtension\Context\MinkContext;
11
12
/**
13
 * Defines generic feature steps.
14
 */
15
class GenericFeatureContext extends MinkContext
16
{
17
    /** @var int UI generic delay duration in milliseconds */
18
    const DELAY = 1000;
19
    /** @var int UI javascript generic delay duration in milliseconds */
20
    const JS_DELAY = self::DELAY / 5;
21
    /** @var int UI spin function delay */
22
    const SPIN_TIMEOUT = self::DELAY / 20;
23
24
    /** @var mixed */
25
    protected $session;
26
27
    /**
28
     * Initializes context.
29
     *
30
     * Every scenario gets its own context instance.
31
     * You can also pass arbitrary arguments to the
32
     * context constructor through behat.yml.
33
     *
34
     * @param mixed $session
35
     */
36
    public function __construct($session = null)
37
    {
38
        $this->session = $session;
39
40
        ini_set('xdebug.max_nesting_level', '1000');
41
    }
42
43
    /**
44
     * Spin function to avoid Selenium fails.
45
     *
46
     * @param callable $lambda
47
     * @param int $wait
48
     *
49
     * @return bool
50
     * @throws \Exception
51
     */
52
    public function spin(callable $lambda, $wait = self::SPIN_TIMEOUT)
53
    {
54
        for ($i = 0; $i < $wait; $i++) {
55
            try {
56
                if ($lambda($this)) {
57
                    return true;
58
                }
59
            } catch (\Exception $e) {
60
                // do nothing
61
            }
62
63
            sleep(1);
64
        }
65
66
        $backtrace = debug_backtrace();
67
68
        throw new \Exception(
69
            "Timeout thrown by " . $backtrace[1]['class'] . "::" . $backtrace[1]['function'] . "()\n" .
70
            $backtrace[1]['file'] . ", line " . $backtrace[1]['line']
71
        );
72
    }
73
74
    /**
75
     * @AfterStep
76
     *
77
     * @param AfterStepScope $scope
78
     */
79
    public function takeScreenShotAfterFailedStep(AfterStepScope $scope)
80
    {
81
        if (99 === $scope->getTestResult()->getResultCode()) {
82
            $driver = $this->getSession()->getDriver();
83
84
            if (!($driver instanceof Selenium2Driver)) {
85
                return;
86
            }
87
88
            $step = $scope->getStep();
89
            $fileName = 'Fail.'.preg_replace('/[^a-zA-Z0-9-_\.]/', '_', $scope->getName().'-'.$step->getText()).'.jpg';
90
            file_put_contents($fileName, $driver->getScreenshot());
91
        }
92
    }
93
94
    /**
95
     * Find all elements by CSS selector.
96
     *
97
     * @param string $selector CSS selector
98
     *
99
     * @throws \InvalidArgumentException If element not found
100
     *
101
     * @return \Behat\Mink\Element\NodeElement[]
102
     */
103
    protected function findAllByCssSelector(string $selector)
104
    {
105
        $session = $this->getSession();
106
107
        $elements = $session->getPage()->findAll('css', $this->fixStepArgument($selector));
108
109
        // If element with current selector is not found then print error
110
        if (count($elements) === 0) {
111
            throw new \InvalidArgumentException(sprintf('Could not evaluate CSS selector: "%s"', $selector));
112
        }
113
114
        return $elements;
115
    }
116
117
    /**
118
     * Find element by CSS selector.
119
     *
120
     * @param string $selector CSS selector
121
     *
122
     * @throws \InvalidArgumentException If element not found
123
     *
124
     * @return \Behat\Mink\Element\NodeElement
125
     */
126
    protected function findByCssSelector(string $selector) : NodeElement
127
    {
128
        return $this->findAllByCssSelector($selector)[0];
129
    }
130
131
    /**
132
     * @Given /^I set browser window size to "([^"]*)" x "([^"]*)"$/
133
     *
134
     * @param int $width  Browser window width
135
     * @param int $height Browser window height
136
     */
137
    public function iSetBrowserWindowSizeToX($width, $height)
138
    {
139
        $this->getSession()->resizeWindow((int) $width, (int) $height, 'current');
140
    }
141
142
    /**
143
     * @Given /^I wait "([^"]*)" milliseconds for response$/
144
     *
145
     * @param int $delay Amount of milliseconds to wait
146
     */
147
    public function iWaitMillisecondsForResponse($delay = self::DELAY)
148
    {
149
        $this->getSession()->wait((int) $delay);
150
    }
151
152
    /**
153
     * Click on the element with the provided xpath query.
154
     *
155
     * @When I click on the element :arg1
156
     *
157
     * @param string $selector CSS element selector
158
     *
159
     * @throws \InvalidArgumentException
160
     */
161
    public function iClickOnTheElement(string $selector)
162
    {
163
        // Click on the founded element
164
        $this->findByCssSelector($selector)->click();
165
    }
166
167
    /**
168
     * @When /^I hover over the element "([^"]*)"$/
169
     *
170
     * @param string $selector CSS element selector
171
     *
172
     * @throws \InvalidArgumentException
173
     */
174
    public function iHoverOverTheElement(string $selector)
175
    {
176
        $this->findByCssSelector($selector)->mouseOver();
177
    }
178
179
    /**
180
     * Fill in input with the provided info.
181
     *
182
     * @When I fill in the input :arg1 with :arg2
183
     *
184
     * @param string $selector CSS element selector
185
     * @param string $value    Element value for filling in
186
     *
187
     * @throws \InvalidArgumentException
188
     */
189
    public function iFillInTheElement(string $selector, string $value)
190
    {
191
        $this->findByCssSelector($selector)->setValue($this->fixStepArgument($value));
192
    }
193
194
    /**
195
     * @When I scroll vertically to :arg1 px
196
     *
197
     * @param mixed $yPos Vertical scrolling position in pixels
198
     */
199
    public function iScrollVerticallyToPx($yPos)
200
    {
201
        $this->getSession()->executeScript('window.scrollTo(0, Math.min(document.documentElement.scrollHeight, document.body.scrollHeight, '.((int) $yPos).'));');
202
    }
203
204
    /**
205
     * @When I scroll horizontally to :arg1 px
206
     *
207
     * @param mixed $xPos Horizontal scrolling position in pixels
208
     */
209
    public function iScrollHorizontallyToPx($xPos)
210
    {
211
        $this->getSession()->executeScript('window.scrollTo('.((int) $xPos).', 0);');
212
    }
213
214
    /**
215
     * @Given /^I fill hidden field "([^"]*)" with "([^"]*)"$/
216
     *
217
     * @param string $field Field name
218
     * @param string $value Field value
219
     */
220
    public function iFillHiddenFieldWith(string $field, string $value)
221
    {
222
        // TODO: Change to Mink implementation
223
        $this->getSession()->executeScript("
224
            $('input[name=".$field."]').val('".$value."');
225
        ");
226
    }
227
228
    /**
229
     * @Then I check custom checkbox with :id
230
     *
231
     * @param string $id Checkbox identifier
232
     *
233
     * @throws \InvalidArgumentException If checkbox with provided identifier does not exists
234
     */
235
    public function iCheckCustomCheckboxWith(string $id)
236
    {
237
        // Find label for checkbox by chekbox identifier
238
        $element = null;
239
        foreach ($this->findAllByCssSelector('label') as $label) {
240
            if ($label->getAttribute('for') === $id) {
241
                $element = $label;
242
            }
243
        }
244
245
        // Imitate checkbox checking by clicking its label
246
        $element->click();
247
    }
248
249
    /**
250
     * @Then I drag element :selector to :target
251
     *
252
     * @param string $selector Source element for dragging
253
     * @param string $target   Target element to drag to
254
     *
255
     * @throws \InvalidArgumentException
256
     */
257
    public function dragElementTo(string $selector, string $target)
258
    {
259
        $this->findByCssSelector($selector)->dragTo($this->findByCssSelector($target));
260
261
        $this->iWaitMillisecondsForResponse(self::JS_DELAY);
262
    }
263
264
    /**
265
     * Fill in input with the provided info.
266
     *
267
     * @When I fill in the element :arg1 with value :arg2 using js
268
     *
269
     * @param string $selector CSS element selector
270
     * @param string $value    Element value for filling in
271
     */
272
    public function iFillInTheElementUsingJs(string $selector, string $value)
273
    {
274
        $this->getSession()->executeScript('document.querySelectorAll("'.$selector.'")[0].value="'.$value.'";');
275
    }
276
}
277