Completed
Push — master ( c25c8e...44516d )
by Kevin
06:39
created

setUseErrorHandlerFromAnnotation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 0
1
<?php
2
3
namespace Magium;
4
5
use Facebook\WebDriver\Exception\NoSuchElementException;
6
use Facebook\WebDriver\Exception\WebDriverException;
7
use Facebook\WebDriver\WebDriverElement;
8
use Magium\Assertions\Element\Clickable;
9
use Magium\Assertions\Element\Displayed;
10
use Magium\Assertions\Element\Exists;
11
use Magium\Assertions\Element\NotClickable;
12
use Magium\Assertions\Element\NotDisplayed;
13
use Magium\Assertions\Element\NotExists;
14
use Magium\Assertions\LoggingAssertionExecutor;
15
use Magium\Commands\Open;
16
use Magium\TestCase\Initializer;
17
use Magium\TestCase\InitializerContainer;
18
use Magium\Themes\BaseThemeInterface;
19
use Magium\Util\Log\LoggerInterface;
20
use Magium\Util\Phpunit\MasterListener;
21
use Magium\Util\Phpunit\MasterListenerInterface;
22
use Magium\WebDriver\WebDriver;
23
use PHPUnit\Framework\TestCase;
24
use Zend\Di\Di;
25
26
abstract class AbstractTestCase extends TestCase
27
{
28
29
    protected static $baseNamespaces = [];
30
31
    protected $baseThemeClass = 'Magium\Themes\ThemeConfigurationInterface';
32
33
    protected $postCallbacks = [];
34
35
    /**
36
     * @var MasterListener
37
     */
38
39
    protected static $masterListener;
40
41
    /**
42
     * @var \Magium\WebDriver\WebDriver
43
     */
44
    protected $webdriver;
45
46
    /**
47
     * @var Di
48
     */
49
50
    protected $di;
51
52
    protected $textElementNodeSearch = [
53
        'button', 'span', 'a', 'li', 'label', 'option', 'h1', 'h2', 'h3', 'td'
54
    ];
55
56
    protected $initializer;
57
58
    const BY_XPATH = 'byXpath';
59
    const BY_ID    = 'byId';
60
    const BY_CSS_SELECTOR = 'byCssSelector';
61
    const BY_TEXT = 'byText';
62
63
    protected static $registrationCallbacks;
64
65
    protected function setUp()
66
    {
67
        /**
68
         * Putting this in the setup and not in the property means that an extending class can inject itself easily
69
         * before the Magium namespace, thus, taking preference over the base namespace
70
         */
71
72
        self::addBaseNamespace('Magium');
73
74
        /**
75
         * This weird little bit of code (the InitializerContainer) is done like this so we can provide a type
76
         * preference for the Initializer.  This is because Magium is a DI-based setup rigged inside a non-DI-based
77
         * setup
78
         */
79
        if (!$this->initializer instanceof InitializerContainer) {
80
            $this->initializer = Initializer::getInitializationDependencyInjectionContainer()->get(InitializerContainer::class);
81
        }
82
        $this->initializer->initialize($this);
83
    }
84
85
    /**
86
     * @return Di
87
     */
88
    public function getDi()
89
    {
90
        return $this->di;
91
    }
92
93
    /**
94
     * @param Di $di
95
     */
96
    public function setDi($di)
97
    {
98
        $this->di = $di;
99
    }
100
101
    /**
102
     * @return WebDriver
103
     */
104
    public function getWebdriver()
105
    {
106
        if (!$this->webdriver instanceof WebDriver) {
107
            $this->webdriver = $this->get(WebDriver::class);
108
        }
109
        return $this->webdriver;
110
    }
111
112
    /**
113
     * @param WebDriver $webdriver
114
     */
115
    public function setWebdriver(WebDriver $webdriver)
116
    {
117
        $this->webdriver = $webdriver;
118
    }
119
120
    public function getInitializer()
121
    {
122
        return $this->initializer;
123
    }
124
125
126
    public function __construct($name = null, array $data = [], $dataName = null, Initializer $initializer = null)
0 ignored issues
show
Unused Code introduced by
The parameter $initializer is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
127
    {
128
        // Init at the earliest possible convenience.
129
        self::getMasterListener();
130
        parent::__construct($name, $data, $dataName);
131
    }
132
133
    public function setUseErrorHandlerFromAnnotation() {
134
        /*
135
         * This odd little function is here because the first place where you can reliably add a listener without
136
         * having to make a phpunit.xml or program argument change AND the result object is available AND there is
137
         * no incompatibility with PHPUnit 5.
138
         */
139
        $result = $this->getTestResultObject();
140
        self::getMasterListener()->bindToResult($result);
141
        return parent::setUseErrorHandlerFromAnnotation();
142
    }
143
144
    /**
145
     * @return MasterListener
146
     */
147
148
    public static function getMasterListener()
149
    {
150
        if (!self::$masterListener instanceof MasterListenerInterface) {
151
            if (interface_exists('PHPUnit_Framework_Test')) {
152
                self::$masterListener = new \Magium\Util\Phpunit5\MasterListener();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Magium\Util\Phpunit5\MasterListener() of type object<Magium\Util\Phpunit5\MasterListener> is incompatible with the declared type object<Magium\Util\Phpunit\MasterListener> of property $masterListener.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
153
            } else {
154
                self::$masterListener = new MasterListener();
155
            }
156
        }
157
        return self::$masterListener;
0 ignored issues
show
Bug Compatibility introduced by
The expression self::$masterListener; of type Magium\Util\Phpunit5\Mas...\Phpunit\MasterListener adds the type Magium\Util\Phpunit5\MasterListener to the return on line 157 which is incompatible with the return type documented by Magium\AbstractTestCase::getMasterListener of type Magium\Util\Phpunit\MasterListener.
Loading history...
158
    }
159
160
    protected function tearDown()
161
    {
162
        foreach ($this->postCallbacks as $callback) {
163
            if (is_callable($callback)) {
164
                call_user_func($callback);
165
            }
166
        }
167
        parent::tearDown();
168
        if ($this->webdriver instanceof WebDriver) {
169
            try {
170
                $this->webdriver->close();
171
            } catch (\Exception $e) {
172
                // All closed
173
            }
174
175
            $this->webdriver->quit();
176
            $this->webdriver = null;
177
        }
178
    }
179
180
181
    public function filterWebDriverAction($by)
182
    {
183
        switch ($by) {
184
            case WebDriver::BY_XPATH:
185
                return 'xpath';
186
            case WebDriver::BY_CSS_SELECTOR:
187
                return 'css_selector';
188
            case WebDriver::BY_ID:
189
                return 'id';
190
            default:
191
                return $by;
192
        }
193
    }
194
195
    public function assertElementClickable($selector, $by = WebDriver::BY_ID)
196
    {
197
        $this->elementAssertion($selector, $by, Clickable::ASSERTION);
198
    }
199
200
201
    public function assertElementNotClickable($selector, $by = WebDriver::BY_ID)
202
    {
203
        $this->elementAssertion($selector, $by, NotClickable::ASSERTION);
204
205
    }
206
207
    public static function addBaseNamespace($namespace)
208
    {
209
        if (!in_array($namespace, self::$baseNamespaces)) {
210
            self::$baseNamespaces[] = trim($namespace, '\\');
211
        }
212
    }
213
214
    public static function resolveClass( $class, $prefix = null)
215
    {
216
        $origClass = $class;
217
        if ($prefix !== null) {
218
            $class = "{$prefix}\\{$class}";
219
        }
220
        foreach (self::$baseNamespaces as $namespace) {
221
            if (strpos($class, $namespace) === 0) {
222
                // We have a fully qualified class name
223
                return $class;
224
            }
225
        }
226
227
        foreach (self::$baseNamespaces as $namespace) {
228
            $fqClass = $namespace . '\\' . $class;
229
            try {
230
                if (class_exists($fqClass)) {
231
                    return $fqClass;
232
                }
233
            } catch (\Exception $e) {
234
                /*
235
                 * Nothing to see here
236
                 * http://www.reactiongifs.us/wp-content/uploads/2015/04/nothing_to_see_here_naked_gun.gif
237
                 *
238
                 * This is necessary because, in Magento, when developer mode is turned on, Magento will throw an
239
                 * exception if the autoloader can't find a file.
240
                 */
241
            }
242
        }
243
        return $origClass;
244
    }
245
246
    public function setTypePreference($type, $preference)
247
    {
248
        $type = self::resolveClass($type);
249
        $preference = self::resolveClass($preference);
250
        $this->di->instanceManager()->unsetTypePreferences($type);
251
        $this->di->instanceManager()->setTypePreference($type, [$preference]);
252
253
    }
254
255
    protected function normalizeClassRequest($class)
256
    {
257
        return str_replace('/', '\\', $class);
258
    }
259
260
    public function addPostTestCallback($callback)
261
    {
262
        if (!is_callable($callback)) {
263
            throw new InvalidConfigurationException('Callback is not callable');
264
        }
265
        $this->postCallbacks[] = $callback;
266
    }
267
268
    /**
269
270
     * @param string $theme
271
     * @return \Magium\Themes\ThemeConfigurationInterface
272
     */
273
274
    public function getTheme($theme = null)
275
    {
276
        if ($theme === null) {
277
            return $this->get($this->baseThemeClass);
278
        }
279
        $theme = self::resolveClass($theme, 'Themes');
280
        return $this->get($theme);
281
    }
282
283
    /**
284
     *
285
     * @param string $action
286
     * @return mixed
287
     */
288
289
    public function getAction($action)
290
    {
291
        $action = self::resolveClass($action, 'Actions');
292
293
        return $this->get($action);
294
    }
295
296
297
    /**
298
     * @param string $name
299
     * @return \Magium\Identities\NameInterface
300
     */
301
302
    public function getIdentity($name = 'Customer')
303
    {
304
        $name = self::resolveClass($name, 'Identities');
305
306
        return $this->get($name);
307
    }
308
309
    /**
310
     *
311
     * @param string $navigator
312
     * @return \Magium\Navigators\NavigatorInterface
313
     */
314
315
    public function getNavigator($navigator = 'BaseMenu')
316
    {
317
        $navigator = self::resolveClass($navigator, 'Navigators');
318
319
        return $this->get($navigator);
320
    }
321
322
    public function getAssertion($assertion)
323
    {
324
        $assertion = self::resolveClass($assertion, 'Assertions');
325
326
        return $this->get($assertion);
327
    }
328
329
    /**
330
     *
331
     * @param string $extractor
332
     * @return \Magium\Extractors\AbstractExtractor
333
     */
334
335
    public function getExtractor($extractor)
336
    {
337
        $extractor = self::resolveClass($extractor, 'Extractors');
338
339
        return $this->get($extractor);
340
    }
341
342
    /**
343
     * Sleep the specified amount of time.
344
     *
345
     * Options: 1s (1 second), 1ms (1 millisecond), 1us (1 microsecond), 1ns (1 nanosecond)
346
     *
347
     * @param $time
348
     */
349
350
    public function sleep($time)
351
    {
352
        $length = (int)$time;
353
354
        if (strpos($time, 'ms') !== false) {
355
            usleep($length * 1000);
356
        } else if (strpos($time, 'us') !== false) {
357
            usleep($length);
358
        } else if (strpos($time, 'ns') !== false) {
359
            time_nanosleep(0, $length);
360
        } else {
361
            sleep($length);
362
        }
363
    }
364
365
366
    public function commandOpen($url)
367
    {
368
        $this->get(Open::class)->open($url);
369
    }
370
371
372
    /**
373
     * @return LoggerInterface
374
     */
375
376
    public function getLogger()
377
    {
378
        return $this->get(LoggerInterface::class);
379
    }
380
381
    public function get($class)
382
    {
383
        $class = $this->normalizeClassRequest($class);
384
        $preferredClass = $this->di->instanceManager()->getTypePreferences($class);
385
        if (is_array($preferredClass) && count($preferredClass) > 0) {
386
            $class = array_shift($preferredClass);
387
        }
388
        return $this->di->get($class);
389
    }
390
391
    public static function isPHPUnit5()
392
    {
393
        return class_exists('PHPUnit_Framework_TestSuite');
394
    }
395
396
    public function assertElementExists($selector, $by = 'byId')
397
    {
398
        $this->elementAssertion($selector, $by, Exists::ASSERTION);
399
    }
400
401
    public function assertTitleEquals($title)
402
    {
403
        $webTitle = $this->webdriver->getTitle();
404
        self::assertEquals($title, $webTitle);
405
    }
406
407
408
    public function assertTitleContains($title)
409
    {
410
        $webTitle = $this->webdriver->getTitle();
411
        self::assertContains($title, $webTitle);
412
    }
413
414
415
    public function assertNotTitleEquals($title)
416
    {
417
        $webTitle = $this->webdriver->getTitle();
418
        self::assertNotEquals($title, $webTitle);
419
    }
420
421
422
    public function assertNotTitleContains($title)
423
    {
424
        $webTitle = $this->webdriver->getTitle();
425
        self::assertNotContains($title, $webTitle);
426
    }
427
428
    public function assertURLEquals($url)
429
    {
430
        $webUrl = $this->webdriver->getCurrentURL();
431
        self::assertEquals($url, $webUrl);
432
    }
433
434
    public function assertURLContains($url)
435
    {
436
        $webUrl = $this->webdriver->getCurrentURL();
437
        self::assertContains($url, $webUrl);
438
    }
439
440
441
    public function assertURLNotEquals($url)
442
    {
443
        $webUrl = $this->webdriver->getCurrentURL();
444
        self::assertNotEquals($url, $webUrl);
445
    }
446
447
    public function assertURLNotContains($url)
448
    {
449
        $webUrl = $this->webdriver->getCurrentURL();
450
        self::assertNotContains($url, $webUrl);
451
    }
452
453
    protected function elementAssertion($selector, $by, $name)
454
    {
455
        $executor = $this->getAssertion(LoggingAssertionExecutor::ASSERTION);
456
        $assertion = $this->getAssertion($name);
457
        $assertion->setSelector($selector)->setBy($by);
458
        $executor->execute($assertion);
459
    }
460
461
    public function assertElementDisplayed($selector, $by = 'byId')
462
    {
463
        $this->elementAssertion($selector, $by, Displayed::ASSERTION);
464
    }
465
466
    public function assertElementNotDisplayed($selector, $by = 'byId')
467
    {
468
        $this->elementAssertion($selector, $by, NotDisplayed::ASSERTION);
469
    }
470
471
    public function assertElementNotExists($selector, $by = 'byId')
472
    {
473
        $this->elementAssertion($selector, $by, NotExists::ASSERTION);
474
    }
475
476
    /**
477
     * @return LoggingAssertionExecutor
478
     */
479
480
    public function getAssertionLogger()
481
    {
482
        return $this->getAssertion(LoggingAssertionExecutor::ASSERTION);
483
    }
484
485
    public function switchThemeConfiguration($fullyQualifiedClassName)
486
    {
487
488
        $reflection = new \ReflectionClass($fullyQualifiedClassName);
489
490
        if ($reflection->implementsInterface('Magium\Themes\ThemeConfigurationInterface')) {
491
            $this->baseThemeClass = $fullyQualifiedClassName;
492
            $this->di->instanceManager()->unsetTypePreferences('Magium\Themes\ThemeConfigurationInterface');
493
            $this->di->instanceManager()->setTypePreference('Magium\Themes\ThemeConfigurationInterface', [$fullyQualifiedClassName]);
494
495
            if ($reflection->implementsInterface('Magium\Themes\BaseThemeInterface')) {
496
                $this->di->instanceManager()->unsetTypePreferences('Magium\Themes\BaseThemeInterface');
497
                $this->di->instanceManager()->setTypePreference('Magium\Themes\BaseThemeInterface', [$fullyQualifiedClassName]);
498
            }
499
            $theme = $this->getTheme();
500
            if ($theme instanceof BaseThemeInterface) {
501
                $theme->configure($this);
502
            }
503
        } else {
504
            throw new InvalidConfigurationException('The theme configuration implement Magium\Themes\ThemeConfigurationInterface');
505
        }
506
        $this->getLogger()->addCharacteristic(LoggerInterface::CHARACTERISTIC_THEME, $fullyQualifiedClassName);
507
    }
508
509
    public static function assertWebDriverElement($element)
510
    {
511
        self::assertInstanceOf('Facebook\WebDriver\WebDriverElement', $element);
512
    }
513
514 View Code Duplication
    public function assertElementHasText($node, $text)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
515
    {
516
        try {
517
            $element = $this->byXpath(sprintf('//%s[contains(., "%s")]', $node, addslashes($text)));
518
            self::assertWebDriverElement($element);
519
        } catch (\Exception $e) {
520
            $this->fail('The body did not contain the text: ' . $text);
521
        }
522
    }
523
524 View Code Duplication
    public function assertPageHasText($text)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
525
    {
526
        try {
527
            $element = $this->webdriver->byXpath(sprintf('//body[contains(., "%s")]', $text));
528
            // If the element is not found then an exception will be thrown
529
            self::assertWebDriverElement($element);
530
        } catch (\Exception $e) {
531
            $this->fail('The body did not contain the text: ' . $text);
532
        }
533
    }
534
535
    public function assertPageNotHasText($text)
536
    {
537
        try {
538
            $this->webdriver->byXpath(sprintf('//body[contains(., "%s")]', $text));
539
            $this->fail('The page contains the words: ' . $text);
540
        } catch (NoSuchElementException $e) {
541
            // Exception thrown is a success
542
            self::assertInstanceOf(NoSuchElementException::class, $e);
543
        }
544
    }
545
546
    /**
547
     * @param $xpath
548
     * @return \Facebook\WebDriver\Remote\RemoteWebElement
549
     */
550
551
    public function byXpath($xpath)
552
    {
553
        return $this->webdriver->byXpath($xpath);
554
    }
555
556
    /**
557
     * @param $id
558
     * @return \Facebook\WebDriver\Remote\RemoteWebElement
559
     */
560
561
    public function byId($id)
562
    {
563
        return $this->webdriver->byId($id);
564
    }
565
566
    /**
567
     * @param $selector
568
     * @return \Facebook\WebDriver\Remote\RemoteWebElement
569
     */
570
571
    public function byCssSelector($selector)
572
    {
573
        return $this->webdriver->byCssSelector($selector);
574
    }
575
576
    protected function getElementByTextXpath($xpathTemplate, $text, $specificNodeType = null, $parentElementSelector = null)
577
    {
578
579
        if ($parentElementSelector !== null) {
580
            $xpathTemplate = $parentElementSelector . $xpathTemplate;
581
        }
582
        if ($specificNodeType !== null) {
583
            return $this->byXpath(sprintf($xpathTemplate, $specificNodeType, $this->getTranslator()->translatePlaceholders($text)));
584
        }
585
586
        foreach ($this->textElementNodeSearch as $nodeName) {
587
            $xpath = sprintf($xpathTemplate, $nodeName, $this->getTranslator()->translatePlaceholders($text));
588
            if ($this->webdriver->elementExists($xpath, WebDriver::BY_XPATH)) {
589
                return $this->webdriver->byXpath($xpath);
590
            }
591
        }
592
        // This is here for consistency with the other by* methods
593
        WebDriverException::throwException(7, 'Could not find element with text: ' . $this->getTranslator()->translatePlaceholders($text), []);
594
    }
595
596
    /**
597
     * @param string $text
598
     * @param string $specificNodeType
599
     * @param string $parentElementSelector
600
     * @return \Facebook\WebDriver\Remote\RemoteWebElement
601
     */
602
    public function byText($text, $specificNodeType = null, $parentElementSelector = null)
603
    {
604
        $xpathTemplate = '//%s[concat(" ",normalize-space(.)," ") = " %s "]';
605
        return $this->getElementByTextXpath($xpathTemplate, $text, $specificNodeType, $parentElementSelector);
606
    }
607
608
609
    /**
610
     * @param string $text
611
     * @param string $specificNodeType
612
     * @param string $parentElementSelector
613
     * @return \Facebook\WebDriver\Remote\RemoteWebElement
614
     */
615
    public function byContainsText($text, $specificNodeType = null, $parentElementSelector = null)
616
    {
617
        $xpathTemplate = '//%s[contains(., "%s")]';
618
        return $this->getElementByTextXpath($xpathTemplate, $text, $specificNodeType, $parentElementSelector);
619
    }
620
621
    /**
622
     * @return \Magium\Util\Translator\Translator
623
     */
624
625
    public function getTranslator()
626
    {
627
        return $this->get('Magium\Util\Translator\Translator');
628
    }
629
630
    public function addTranslationCsvFile($file, $locale)
631
    {
632
        $this->getTranslator()->addTranslationCsvFile($file, $locale);
633
    }
634
}
635