Completed
Pull Request — master (#16)
by Sergii
04:39
created

RawTqContext::getWebDriverSession()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 0
cts 1
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
/**
3
 * @author Sergii Bondarenko, <[email protected]>
4
 */
5
namespace Drupal\TqExtension\Context;
6
7
// Contexts.
8
use Behat\Behat\Context\SnippetAcceptingContext;
9
use Drupal\DrupalExtension\Context as DrupalContexts;
10
// Exceptions.
11
use Behat\Behat\Context\Exception\ContextNotFoundException;
12
use Behat\DebugExtension\Debugger;
13
// Helpers.
14
use WebDriver\Session;
15
use Drupal\Driver\DrushDriver;
16
use Drupal\Driver\DriverInterface as DrupalDriverInterface;
17
use Drupal\Component\Utility\Random;
18
use Behat\Mink\Element\NodeElement;
19
use Behat\Mink\Driver\GoutteDriver;
20
use Behat\Mink\Driver\Selenium2Driver;
21
use Behat\Mink\Driver\DriverInterface as SessionDriverInterface;
22
use Behat\Behat\Hook\Scope\StepScope;
23
use Behat\Behat\Context\Environment\InitializedContextEnvironment;
24
use Behat\Testwork\Environment\Environment;
25
// Utils.
26
use Drupal\TqExtension\Utils\Url;
27
use Drupal\TqExtension\Utils\Tags;
28
use Drupal\TqExtension\Utils\JavaScript;
29
use Drupal\TqExtension\Cores\DrupalKernelPlaceholder;
30
31
/**
32
 * @see RawTqContext::__call()
33
 *
34
 * @method User\UserContext getUserContext()
35
 * @method Node\NodeContext getNodeContext()
36
 * @method Form\FormContext getFormContext()
37
 * @method Email\EmailContext getEmailContext()
38
 * @method Drush\DrushContext getDrushContext()
39
 * @method Wysiwyg\WysiwygContext getWysiwygContext()
40
 * @method Message\MessageContext getMessageContext()
41
 * @method Redirect\RedirectContext getRedirectContext()
42
 * @method TqContext getTqContext()
43
 * @method DrupalContexts\MinkContext getMinkContext()
44
 * @method DrupalContexts\DrupalContext getDrupalContext()
45
 * @method Random getRandom()
46
 */
47
class RawTqContext extends RawPageContext implements TqContextInterface
48
{
49
    use JavaScript;
50
    use Debugger;
51
    use Tags;
52
53
    /**
54
     * Parameters of TqExtension.
55
     *
56
     * @var array
57
     */
58
    private $parameters = [];
59
    /**
60
     * @var string
61
     */
62
    protected static $pageUrl = '';
63
64
    /**
65
     * @param string $method
66
     * @param array $arguments
67
     *
68
     * @throws \Exception
69
     * @throws ContextNotFoundException
70
     *   When context class cannot be loaded.
71
     *
72
     * @return SnippetAcceptingContext
73
     */
74
    public function __call($method, array $arguments)
75
    {
76
        $environment = $this->getEnvironment();
77
        // @example
78
        // The "getFormContext" method is not declared and his name will be split by capital
79
        // letters, creating an array with three items: "get", "Form" and "Context".
80
        list(, $base, $context) = preg_split('/(?=[A-Z])/', $method);
81
82
        foreach ([
83
            [$this->getTqParameter('namespace'), 'Context', $base],
84
            ['Drupal', 'DrupalExtension', 'Context'],
85
        ] as $class) {
86
            $class[] = "$base$context";
87
            $class = implode('\\', $class);
88
89
            if ($environment->hasContextClass($class)) {
90
                return $environment->getContext($class);
91
            }
92
        }
93
94
        throw new \Exception(sprintf('Method "%s" does not exist', $method));
95
    }
96
97
    /**
98
     * Get selector by name.
99
     *
100
     * @param string $name
101
     *   Selector name from the configuration file.
102
     *
103
     * @return string
104
     *   CSS selector.
105
     *
106
     * @throws \Exception
107
     *   If selector does not exits.
108
     */
109
    public function getDrupalSelector($name)
110
    {
111
        $selectors = $this->getDrupalParameter('selectors');
112
113
        if (!isset($selectors[$name])) {
114
            throw new \Exception(sprintf('No such selector configured: %s', $name));
115
        }
116
117
        return $selectors[$name];
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123
    public function getDrupalText($name)
124
    {
125
        // Make text selectors translatable.
126
        return DrupalKernelPlaceholder::t(parent::getDrupalText($name));
127
    }
128
129
    /**
130
     * @param string $site
131
     *   Drupal site folder.
132
     *
133
     * @return string
134
     *   URL to files directory.
135
     */
136
    public function getFilesUrl($site = 'default')
137
    {
138
        return $this->locatePath("sites/$site/files");
139
    }
140
141
    /**
142
     * @return Environment|InitializedContextEnvironment
143
     */
144
    public function getEnvironment()
145
    {
146
        return $this->getDrupal()->getEnvironment();
147
    }
148
149
    /**
150
     * @return SessionDriverInterface|Selenium2Driver|GoutteDriver
151
     */
152
    public function getSessionDriver()
153
    {
154
        return $this->getSession()->getDriver();
155
    }
156
157
    /**
158
     * @return Session
159
     */
160
    public function getWebDriverSession()
161
    {
162
        return $this->getSessionDriver()->getWebDriverSession();
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 getWebDriverSession() does only exist in the following implementations of said interface: Behat\Mink\Driver\Selenium2Driver.

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...
163
    }
164
165
    /**
166
     * @todo Remove this when DrupalExtension will be used Mink >=1.6 and use $this->getSession->getWindowNames();
167
     *
168
     * @return string[]
169
     */
170
    public function getWindowNames()
171
    {
172
        return $this->getWebDriverSession()->window_handles();
173
    }
174
175
    /**
176
     * @param NodeElement $element
177
     * @param string $script
178
     *
179
     * @example
180
     * $this->executeJsOnElement($this->element('*', 'Meta tags'), 'return jQuery({{ELEMENT}}).text();');
181
     * $this->executeJsOnElement($this->element('*', '#menu'), '{{ELEMENT}}.focus();');
182
     *
183
     * @throws \Exception
184
     *
185
     * @return mixed
186
     */
187
    public function executeJsOnElement(NodeElement $element, $script)
188
    {
189
        $session = $this->getWebDriverSession();
190
        // We need to trigger something with "withSyn" method, because, otherwise an element won't be found.
191
        $element->focus();
192
193
        self::debug(['%s'], [$script]);
194
195
        return $session->execute([
196
            'script' => str_replace('{{ELEMENT}}', 'arguments[0]', $script),
197
            'args' => [['ELEMENT' => $session->element('xpath', $element->getXpath())->getID()]],
198
        ]);
199
    }
200
201
    /**
202
     * @param string $javascript
203
     *   JS code for execution.
204
     * @param array $args
205
     *   Placeholder declarations.
206
     *
207
     * @return mixed
208
     */
209
    public function executeJs($javascript, array $args = [])
210
    {
211
        $javascript = DrupalKernelPlaceholder::formatString($javascript, $args);
212
213
        self::debug([$javascript]);
214
215
        return $this->getSession()->evaluateScript($javascript);
216
    }
217
218
    /**
219
     * Check JS events in step definition.
220
     *
221
     * @param StepScope $event
222
     *
223
     * @return int
224
     */
225
    public static function isStepImpliesJsEvent(StepScope $event)
226
    {
227
        return self::hasTag('javascript') && preg_match('/(follow|press|click|submit)/i', $event->getStep()->getText());
228
    }
229
230
    /**
231
     * @return DrupalDriverInterface|DrushDriver
232
     */
233
    public function getDrushDriver()
234
    {
235
        return $this->getDriver('drush');
236
    }
237
238
    /**
239
     * Wait for all AJAX requests and jQuery animations.
240
     */
241
    public function waitAjaxAndAnimations()
242
    {
243
        $script = [];
244
        $script[] = '!window.__ajaxRequestsInProcess';
245
        $script[] = "(window.jQuery ? !jQuery(':animated').length && !jQuery.active : true)";
246
247
        $this->getSession()->wait(1000, implode(' && ', $script));
248
    }
249
250
    /**
251
     * {@inheritdoc}
252
     */
253
    public function setTqParameters(array $parameters)
254
    {
255
        if (empty($this->parameters)) {
256
            $this->parameters = $parameters;
257
        }
258
    }
259
260
    /**
261
     * {@inheritdoc}
262
     */
263
    public function getTqParameter($name)
264
    {
265
        return isset($this->parameters[$name]) ? $this->parameters[$name] : false;
266
    }
267
268
    /**
269
     * {@inheritdoc}
270
     */
271
    public function locatePath($path = '')
272
    {
273
        return (string) new Url($this->getMinkParameter('base_url'), $path);
274
    }
275
276
    /**
277
     * @return string
278
     *   Absolute URL.
279
     */
280
    public function getCurrentUrl()
281
    {
282
        return $this->locatePath($this->getSession()->getCurrentUrl());
283
    }
284
}
285