Completed
Push — master ( 7ad456...793043 )
by Vitaly
01:13
created

GenericFeatureContext::getSymfonyService()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 6
nc 1
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 0.1 sec spin delay */
20
    const SPIN_DELAY = 100000;
21
    /** @var int UI javascript generic delay duration in milliseconds */
22
    const JS_DELAY = self::DELAY / 5;
23
    /** @var int UI spin function timeout for ex 30*0.1s = 15 sec timeout */
24
    const SPIN_TIMEOUT = 150;
25
26
    /** @var mixed */
27
    protected $session;
28
29
    /**
30
     * Initializes context.
31
     *
32
     * Every scenario gets its own context instance.
33
     * You can also pass arbitrary arguments to the
34
     * context constructor through behat.yml.
35
     *
36
     * @param mixed $session
37
     */
38
    public function __construct($session = null)
39
    {
40
        $this->session = $session;
41
42
        ini_set('xdebug.max_nesting_level', '1000');
43
    }
44
45
    /**
46
     * Get Symfony service instance.
47
     *
48
     * @param string $serviceName Service identifier
49
     * @param string $session Behat Symfony session name
50
     * @return object Symfony service instance
51
     */
52
    public function getSymfonyService(string $serviceName, string $session = 'symfony2')
53
    {
54
        return $this->getSession($session)
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Mink\Driver\DriverInterface as the method getClient() does only exist in the following implementations of said interface: Behat\Mink\Driver\BrowserKitDriver, Behat\Symfony2Extension\Driver\KernelDriver.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
55
            ->getDriver()
56
            ->getClient()
57
            ->getContainer()
58
            ->get($serviceName);
59
    }
60
61
    /**
62
     * Spin function to avoid Selenium fails.
63
     *
64
     * @param callable $lambda
65
     * @param null $data
66
     * @param int $delay
67
     * @param int $timeout
68
     *
69
     * @throws \Exception
70
     *
71
     * @return bool
72
     */
73
    public function spin(callable $lambda, $data = null, $delay = self::SPIN_DELAY, $timeout = self::SPIN_TIMEOUT)
74
    {
75
        $failedExceptions = [];
76
        for ($i = 0; $i < $timeout; $i++) {
77
            try {
78
                if ($lambda($this, $data)) {
79
                    return true;
80
                }
81
            } catch (\Exception $e) { // Gather unique exceptions
82
                $failedExceptions[$e->getMessage()] = $e->getMessage();
83
            }
84
85
            usleep($delay);
86
        }
87
88
        $backtrace = debug_backtrace();
89
90
        throw new \Exception(
91
            'Timeout thrown by '.$backtrace[1]['class'].'::'.$backtrace[1]['function']."()\n"
92
            .(array_key_exists('file', $backtrace[1]) ? $backtrace[1]['file'].', line '.$backtrace[1]['line'] : '')."\n"
93
            .implode("\n", $failedExceptions)
94
        );
95
    }
96
97
    /**
98
     * @AfterStep
99
     *
100
     * @param AfterStepScope $scope
101
     */
102
    public function takeScreenShotAfterFailedStep(AfterStepScope $scope)
103
    {
104
        if (99 === $scope->getTestResult()->getResultCode()) {
105
            $driver = $this->getSession()->getDriver();
106
107
            if (!($driver instanceof Selenium2Driver)) {
108
                return;
109
            }
110
111
            $step = $scope->getStep();
112
            $fileName = 'Fail.'.preg_replace('/[^a-zA-Z0-9-_\.]/', '_', $scope->getName().'-'.$step->getText()).'.jpg';
113
            file_put_contents($fileName, $driver->getScreenshot());
114
        }
115
    }
116
117
    /**
118
     * Find all elements 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 findAllByCssSelector(string $selector)
127
    {
128
        $session = $this->getSession();
129
130
        $elements = $session->getPage()->findAll('css', $this->fixStepArgument($selector));
131
132
        // If element with current selector is not found then print error
133
        if (count($elements) === 0) {
134
            throw new \InvalidArgumentException(sprintf('Could not evaluate CSS selector: "%s"', $selector));
135
        }
136
137
        return $elements;
138
    }
139
140
    /**
141
     * Find element by CSS selector.
142
     *
143
     * @param string $selector CSS selector
144
     *
145
     * @throws \InvalidArgumentException If element not found
146
     *
147
     * @return \Behat\Mink\Element\NodeElement
148
     */
149
    protected function findByCssSelector(string $selector) : NodeElement
150
    {
151
        return $this->findAllByCssSelector($selector)[0];
152
    }
153
154
    /**
155
     * @Given /^I set browser window size to "([^"]*)" x "([^"]*)"$/
156
     *
157
     * @param int $width  Browser window width
158
     * @param int $height Browser window height
159
     */
160
    public function iSetBrowserWindowSizeToX($width, $height)
161
    {
162
        $this->getSession()->resizeWindow((int) $width, (int) $height, 'current');
163
    }
164
165
    /**
166
     * @Given /^I wait "([^"]*)" milliseconds for response$/
167
     *
168
     * @param int $delay Amount of milliseconds to wait
169
     */
170
    public function iWaitMillisecondsForResponse($delay = self::DELAY)
171
    {
172
        $this->getSession()->wait((int) $delay);
173
    }
174
175
    /**
176
     * Click on the element with the provided xpath query.
177
     *
178
     * @When I click on the element :arg1
179
     *
180
     * @param string $selector CSS element selector
181
     *
182
     * @throws \InvalidArgumentException
183
     */
184
    public function iClickOnTheElement(string $selector)
185
    {
186
        // Click on the founded element
187
        $this->findByCssSelector($selector)->click();
188
    }
189
190
    /**
191
     * @When /^I hover over the element "([^"]*)"$/
192
     *
193
     * @param string $selector CSS element selector
194
     *
195
     * @throws \InvalidArgumentException
196
     */
197
    public function iHoverOverTheElement(string $selector)
198
    {
199
        $this->findByCssSelector($selector)->mouseOver();
200
    }
201
202
    /**
203
     * Fill in input with the provided info.
204
     *
205
     * @When I fill in the input :arg1 with :arg2
206
     *
207
     * @param string $selector CSS element selector
208
     * @param string $value    Element value for filling in
209
     *
210
     * @throws \InvalidArgumentException
211
     */
212
    public function iFillInTheElement(string $selector, string $value)
213
    {
214
        $this->findByCssSelector($selector)->setValue($this->fixStepArgument($value));
215
    }
216
217
    /**
218
     * @When I scroll vertically to :arg1 px
219
     *
220
     * @param mixed $yPos Vertical scrolling position in pixels
221
     */
222
    public function iScrollVerticallyToPx($yPos)
223
    {
224
        $this->getSession()->executeScript('window.scrollTo(0, Math.min(document.documentElement.scrollHeight, document.body.scrollHeight, '.((int) $yPos).'));');
225
    }
226
227
    /**
228
     * @When I scroll horizontally to :arg1 px
229
     *
230
     * @param mixed $xPos Horizontal scrolling position in pixels
231
     */
232
    public function iScrollHorizontallyToPx($xPos)
233
    {
234
        $this->getSession()->executeScript('window.scrollTo('.((int) $xPos).', 0);');
235
    }
236
237
    /**
238
     * @Given /^I fill hidden field "([^"]*)" with "([^"]*)"$/
239
     *
240
     * @param string $field Field name
241
     * @param string $value Field value
242
     */
243
    public function iFillHiddenFieldWith(string $field, string $value)
244
    {
245
        // TODO: Change to Mink implementation
246
        $this->getSession()->executeScript("
247
            $('input[name=".$field."]').val('".$value."');
248
        ");
249
    }
250
251
    /**
252
     * @Then I check custom checkbox with :id
253
     *
254
     * @param string $id Checkbox identifier
255
     *
256
     * @throws \InvalidArgumentException If checkbox with provided identifier does not exists
257
     */
258
    public function iCheckCustomCheckboxWith(string $id)
259
    {
260
        // Find label for checkbox by chekbox identifier
261
        $element = null;
262
        foreach ($this->findAllByCssSelector('label') as $label) {
263
            if ($label->getAttribute('for') === $id) {
264
                $element = $label;
265
            }
266
        }
267
268
        // Imitate checkbox checking by clicking its label
269
        $element->click();
270
    }
271
272
    /**
273
     * @Then I drag element :selector to :target
274
     *
275
     * @param string $selector Source element for dragging
276
     * @param string $target   Target element to drag to
277
     *
278
     * @throws \InvalidArgumentException
279
     */
280
    public function dragElementTo(string $selector, string $target)
281
    {
282
        $this->findByCssSelector($selector)->dragTo($this->findByCssSelector($target));
283
284
        $this->iWaitMillisecondsForResponse(self::JS_DELAY);
285
    }
286
287
    /**
288
     * Fill in input with the provided info.
289
     *
290
     * @When I fill in the element :arg1 with value :arg2 using js
291
     *
292
     * @param string $selector CSS element selector
293
     * @param string $value    Element value for filling in
294
     */
295
    public function iFillInTheElementUsingJs(string $selector, string $value)
296
    {
297
        $this->getSession()->executeScript('document.querySelectorAll("'.$selector.'")[0].value="'.$value.'";');
298
    }
299
}
300