Completed
Push — master ( e3b4fb...b459f9 )
by Kevin
05:15
created

AbstractTestCase::startTimer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
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
    private $sectionTimerStart;
64
65
    public function startTimer()
66
    {
67
        $this->sectionTimerStart = microtime(true);
68
    }
69
70
    public function endTimer($name)
71
    {
72
        $endTime = microtime(true);
73
        if (!$this->sectionTimerStart) {
74
            throw new \Exception('sendTimer(name) requires startTimer() to be called previously');
75
        }
76
77
        $elapsedMs = (int)(($endTime - $this->sectionTimerStart) * 1000);
78
        $elapsedExact = $endTime - $this->sectionTimerStart;
79
80
        $message = sprintf('%s - elapsed %s', $name, $elapsedMs);
81
        $this->getLogger()->info($message, [
82
            'type' => 'timer',
83
            'name' => $name,
84
            'elapsed_ms' => $elapsedMs,
85
            'elapsed' => $elapsedExact,
86
            'start_time' => $this->sectionTimerStart,
87
            'end_time' => $endTime
88
        ]);
89
        $this->sectionTimerStart = false;
90
    }
91
92
    protected static $registrationCallbacks;
93
94
    protected function setUp()
95
    {
96
        /**
97
         * Putting this in the setup and not in the property means that an extending class can inject itself easily
98
         * before the Magium namespace, thus, taking preference over the base namespace
99
         */
100
101
        self::addBaseNamespace('Magium');
102
103
        /**
104
         * This weird little bit of code (the InitializerContainer) is done like this so we can provide a type
105
         * preference for the Initializer.  This is because Magium is a DI-based setup rigged inside a non-DI-based
106
         * setup
107
         */
108
        if (!$this->initializer instanceof InitializerContainer) {
109
            $this->initializer = Initializer::getInitializationDependencyInjectionContainer()->get(InitializerContainer::class);
110
        }
111
        $this->initializer->initialize($this);
112
    }
113
114
    /**
115
     * @return Di
116
     */
117
    public function getDi()
118
    {
119
        return $this->di;
120
    }
121
122
    /**
123
     * @param Di $di
124
     */
125
    public function setDi($di)
126
    {
127
        $this->di = $di;
128
    }
129
130
    /**
131
     * @return WebDriver
132
     */
133
    public function getWebdriver()
134
    {
135
        if (!$this->webdriver instanceof WebDriver) {
136
            $this->webdriver = $this->get(WebDriver::class);
137
        }
138
        return $this->webdriver;
139
    }
140
141
    /**
142
     * @param WebDriver $webdriver
143
     */
144
    public function setWebdriver(WebDriver $webdriver)
145
    {
146
        $this->webdriver = $webdriver;
147
    }
148
149
    public function getInitializer()
150
    {
151
        return $this->initializer;
152
    }
153
154
155
    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...
156
    {
157
        // Init at the earliest possible convenience.
158
        self::getMasterListener();
159
        parent::__construct($name, $data, $dataName);
160
    }
161
162
    public function setUseErrorHandlerFromAnnotation() {
163
        /*
164
         * This odd little function is here because the first place where you can reliably add a listener without
165
         * having to make a phpunit.xml or program argument change AND the result object is available AND there is
166
         * no incompatibility with PHPUnit 5.
167
         */
168
        $result = $this->getTestResultObject();
169
        self::getMasterListener()->bindToResult($result);
170
        return parent::setUseErrorHandlerFromAnnotation();
171
    }
172
173
    /**
174
     * @return MasterListener
175
     */
176
177
    public static function getMasterListener()
178
    {
179
        if (!self::$masterListener instanceof MasterListenerInterface) {
180
            if (interface_exists('PHPUnit_Framework_Test')) {
181
                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...
182
            } else {
183
                self::$masterListener = new MasterListener();
184
            }
185
        }
186
        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 186 which is incompatible with the return type documented by Magium\AbstractTestCase::getMasterListener of type Magium\Util\Phpunit\MasterListener.
Loading history...
187
    }
188
189
    protected function tearDown()
190
    {
191
        foreach ($this->postCallbacks as $callback) {
192
            if (is_callable($callback)) {
193
                call_user_func($callback);
194
            }
195
        }
196
        parent::tearDown();
197
        if ($this->webdriver instanceof WebDriver) {
198
            try {
199
                $this->webdriver->close();
200
            } catch (\Exception $e) {
201
                // All closed
202
            }
203
204
            $this->webdriver->quit();
205
            $this->webdriver = null;
206
        }
207
    }
208
209
210
    public function filterWebDriverAction($by)
211
    {
212
        switch ($by) {
213
            case WebDriver::BY_XPATH:
214
                return 'xpath';
215
            case WebDriver::BY_CSS_SELECTOR:
216
                return 'css_selector';
217
            case WebDriver::BY_ID:
218
                return 'id';
219
            default:
220
                return $by;
221
        }
222
    }
223
224
    public function assertElementClickable($selector, $by = WebDriver::BY_ID)
225
    {
226
        $this->elementAssertion($selector, $by, Clickable::ASSERTION);
227
    }
228
229
230
    public function assertElementNotClickable($selector, $by = WebDriver::BY_ID)
231
    {
232
        $this->elementAssertion($selector, $by, NotClickable::ASSERTION);
233
234
    }
235
236
    public static function addBaseNamespace($namespace)
237
    {
238
        if (!in_array($namespace, self::$baseNamespaces)) {
239
            self::$baseNamespaces[] = trim($namespace, '\\');
240
        }
241
    }
242
243
    public static function resolveClass( $class, $prefix = null)
244
    {
245
        $origClass = $class;
246
        if ($prefix !== null) {
247
            $class = "{$prefix}\\{$class}";
248
        }
249
        foreach (self::$baseNamespaces as $namespace) {
250
            if (strpos($class, $namespace) === 0) {
251
                // We have a fully qualified class name
252
                return $class;
253
            }
254
        }
255
256
        foreach (self::$baseNamespaces as $namespace) {
257
            $fqClass = $namespace . '\\' . $class;
258
            try {
259
                if (class_exists($fqClass)) {
260
                    return $fqClass;
261
                }
262
            } catch (\Exception $e) {
263
                /*
264
                 * Nothing to see here
265
                 * http://www.reactiongifs.us/wp-content/uploads/2015/04/nothing_to_see_here_naked_gun.gif
266
                 *
267
                 * This is necessary because, in Magento, when developer mode is turned on, Magento will throw an
268
                 * exception if the autoloader can't find a file.
269
                 */
270
            }
271
        }
272
        return $origClass;
273
    }
274
275
    public function setTypePreference($type, $preference)
276
    {
277
        $type = self::resolveClass($type);
278
        $preference = self::resolveClass($preference);
279
        $this->di->instanceManager()->unsetTypePreferences($type);
280
        $this->di->instanceManager()->setTypePreference($type, [$preference]);
281
282
    }
283
284
    protected function normalizeClassRequest($class)
285
    {
286
        return str_replace('/', '\\', $class);
287
    }
288
289
    public function addPostTestCallback($callback)
290
    {
291
        if (!is_callable($callback)) {
292
            throw new InvalidConfigurationException('Callback is not callable');
293
        }
294
        $this->postCallbacks[] = $callback;
295
    }
296
297
    /**
298
299
     * @param string $theme
300
     * @return \Magium\Themes\ThemeConfigurationInterface
301
     */
302
303
    public function getTheme($theme = null)
304
    {
305
        if ($theme === null) {
306
            return $this->get($this->baseThemeClass);
307
        }
308
        $theme = self::resolveClass($theme, 'Themes');
309
        return $this->get($theme);
310
    }
311
312
    /**
313
     *
314
     * @param string $action
315
     * @return mixed
316
     */
317
318
    public function getAction($action)
319
    {
320
        $action = self::resolveClass($action, 'Actions');
321
322
        return $this->get($action);
323
    }
324
325
326
    /**
327
     * @param string $name
328
     * @return \Magium\Identities\NameInterface
329
     */
330
331
    public function getIdentity($name = 'Customer')
332
    {
333
        $name = self::resolveClass($name, 'Identities');
334
335
        return $this->get($name);
336
    }
337
338
    /**
339
     *
340
     * @param string $navigator
341
     * @return \Magium\Navigators\NavigatorInterface
342
     */
343
344
    public function getNavigator($navigator = 'BaseMenu')
345
    {
346
        $navigator = self::resolveClass($navigator, 'Navigators');
347
348
        return $this->get($navigator);
349
    }
350
351
    public function getAssertion($assertion)
352
    {
353
        $assertion = self::resolveClass($assertion, 'Assertions');
354
355
        return $this->get($assertion);
356
    }
357
358
    /**
359
     *
360
     * @param string $extractor
361
     * @return \Magium\Extractors\AbstractExtractor
362
     */
363
364
    public function getExtractor($extractor)
365
    {
366
        $extractor = self::resolveClass($extractor, 'Extractors');
367
368
        return $this->get($extractor);
369
    }
370
371
    /**
372
     * Sleep the specified amount of time.
373
     *
374
     * Options: 1s (1 second), 1ms (1 millisecond), 1us (1 microsecond), 1ns (1 nanosecond)
375
     *
376
     * @param $time
377
     */
378
379
    public function sleep($time)
380
    {
381
        $length = (int)$time;
382
383
        if (strpos($time, 'ms') !== false) {
384
            usleep($length * 1000);
385
        } else if (strpos($time, 'us') !== false) {
386
            usleep($length);
387
        } else if (strpos($time, 'ns') !== false) {
388
            time_nanosleep(0, $length);
389
        } else {
390
            sleep($length);
391
        }
392
    }
393
394
395
    public function commandOpen($url)
396
    {
397
        $this->get(Open::class)->open($url);
398
    }
399
400
401
    /**
402
     * @return LoggerInterface
403
     */
404
405
    public function getLogger()
406
    {
407
        return $this->get(LoggerInterface::class);
408
    }
409
410
    public function get($class)
411
    {
412
        $class = $this->normalizeClassRequest($class);
413
        $preferredClass = $this->di->instanceManager()->getTypePreferences($class);
414
        if (is_array($preferredClass) && count($preferredClass) > 0) {
415
            $class = array_shift($preferredClass);
416
        }
417
        return $this->di->get($class);
418
    }
419
420
    public static function isPHPUnit5()
421
    {
422
        return class_exists('PHPUnit_Framework_TestSuite');
423
    }
424
425
    public function assertElementExists($selector, $by = 'byId')
426
    {
427
        $this->elementAssertion($selector, $by, Exists::ASSERTION);
428
    }
429
430
    public function assertTitleEquals($title)
431
    {
432
        $webTitle = $this->webdriver->getTitle();
433
        self::assertEquals($title, $webTitle);
434
    }
435
436
437
    public function assertTitleContains($title)
438
    {
439
        $webTitle = $this->webdriver->getTitle();
440
        self::assertContains($title, $webTitle);
441
    }
442
443
444
    public function assertNotTitleEquals($title)
445
    {
446
        $webTitle = $this->webdriver->getTitle();
447
        self::assertNotEquals($title, $webTitle);
448
    }
449
450
451
    public function assertNotTitleContains($title)
452
    {
453
        $webTitle = $this->webdriver->getTitle();
454
        self::assertNotContains($title, $webTitle);
455
    }
456
457
    public function assertURLEquals($url)
458
    {
459
        $webUrl = $this->webdriver->getCurrentURL();
460
        self::assertEquals($url, $webUrl);
461
    }
462
463
    public function assertURLContains($url)
464
    {
465
        $webUrl = $this->webdriver->getCurrentURL();
466
        self::assertContains($url, $webUrl);
467
    }
468
469
470
    public function assertURLNotEquals($url)
471
    {
472
        $webUrl = $this->webdriver->getCurrentURL();
473
        self::assertNotEquals($url, $webUrl);
474
    }
475
476
    public function assertURLNotContains($url)
477
    {
478
        $webUrl = $this->webdriver->getCurrentURL();
479
        self::assertNotContains($url, $webUrl);
480
    }
481
482
    protected function elementAssertion($selector, $by, $name)
483
    {
484
        $executor = $this->getAssertion(LoggingAssertionExecutor::ASSERTION);
485
        $assertion = $this->getAssertion($name);
486
        $assertion->setSelector($selector)->setBy($by);
487
        $executor->execute($assertion);
488
    }
489
490
    public function assertElementDisplayed($selector, $by = 'byId')
491
    {
492
        $this->elementAssertion($selector, $by, Displayed::ASSERTION);
493
    }
494
495
    public function assertElementNotDisplayed($selector, $by = 'byId')
496
    {
497
        $this->elementAssertion($selector, $by, NotDisplayed::ASSERTION);
498
    }
499
500
    public function assertElementNotExists($selector, $by = 'byId')
501
    {
502
        $this->elementAssertion($selector, $by, NotExists::ASSERTION);
503
    }
504
505
    /**
506
     * @return LoggingAssertionExecutor
507
     */
508
509
    public function getAssertionLogger()
510
    {
511
        return $this->getAssertion(LoggingAssertionExecutor::ASSERTION);
512
    }
513
514
    public function switchThemeConfiguration($fullyQualifiedClassName)
515
    {
516
517
        $reflection = new \ReflectionClass($fullyQualifiedClassName);
518
519
        if ($reflection->implementsInterface('Magium\Themes\ThemeConfigurationInterface')) {
520
            $this->baseThemeClass = $fullyQualifiedClassName;
521
            $this->di->instanceManager()->unsetTypePreferences('Magium\Themes\ThemeConfigurationInterface');
522
            $this->di->instanceManager()->setTypePreference('Magium\Themes\ThemeConfigurationInterface', [$fullyQualifiedClassName]);
523
524
            if ($reflection->implementsInterface('Magium\Themes\BaseThemeInterface')) {
525
                $this->di->instanceManager()->unsetTypePreferences('Magium\Themes\BaseThemeInterface');
526
                $this->di->instanceManager()->setTypePreference('Magium\Themes\BaseThemeInterface', [$fullyQualifiedClassName]);
527
            }
528
            $theme = $this->getTheme();
529
            if ($theme instanceof BaseThemeInterface) {
530
                $theme->configure($this);
531
            }
532
        } else {
533
            throw new InvalidConfigurationException('The theme configuration implement Magium\Themes\ThemeConfigurationInterface');
534
        }
535
        $this->getLogger()->addCharacteristic(LoggerInterface::CHARACTERISTIC_THEME, $fullyQualifiedClassName);
536
    }
537
538
    public static function assertWebDriverElement($element)
539
    {
540
        self::assertInstanceOf('Facebook\WebDriver\WebDriverElement', $element);
541
    }
542
543 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...
544
    {
545
        try {
546
            $element = $this->byXpath(sprintf('//%s[contains(., "%s")]', $node, addslashes($text)));
547
            self::assertWebDriverElement($element);
548
        } catch (\Exception $e) {
549
            $this->fail('The body did not contain the text: ' . $text);
550
        }
551
    }
552
553 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...
554
    {
555
        try {
556
            $element = $this->webdriver->byXpath(sprintf('//body[contains(., "%s")]', $text));
557
            // If the element is not found then an exception will be thrown
558
            self::assertWebDriverElement($element);
559
        } catch (\Exception $e) {
560
            $this->fail('The body did not contain the text: ' . $text);
561
        }
562
    }
563
564
    public function assertPageNotHasText($text)
565
    {
566
        try {
567
            $this->webdriver->byXpath(sprintf('//body[contains(., "%s")]', $text));
568
            $this->fail('The page contains the words: ' . $text);
569
        } catch (NoSuchElementException $e) {
570
            // Exception thrown is a success
571
            self::assertInstanceOf(NoSuchElementException::class, $e);
572
        }
573
    }
574
575
    /**
576
     * @param $xpath
577
     * @return \Facebook\WebDriver\Remote\RemoteWebElement
578
     */
579
580
    public function byXpath($xpath)
581
    {
582
        return $this->webdriver->byXpath($xpath);
583
    }
584
585
    /**
586
     * @param $id
587
     * @return \Facebook\WebDriver\Remote\RemoteWebElement
588
     */
589
590
    public function byId($id)
591
    {
592
        return $this->webdriver->byId($id);
593
    }
594
595
    /**
596
     * @param $selector
597
     * @return \Facebook\WebDriver\Remote\RemoteWebElement
598
     */
599
600
    public function byCssSelector($selector)
601
    {
602
        return $this->webdriver->byCssSelector($selector);
603
    }
604
605
    protected function getElementByTextXpath($xpathTemplate, $text, $specificNodeType = null, $parentElementSelector = null)
606
    {
607
608
        if ($parentElementSelector !== null) {
609
            $xpathTemplate = $parentElementSelector . $xpathTemplate;
610
        }
611
        if ($specificNodeType !== null) {
612
            return $this->byXpath(sprintf($xpathTemplate, $specificNodeType, $this->getTranslator()->translatePlaceholders($text)));
613
        }
614
615
        foreach ($this->textElementNodeSearch as $nodeName) {
616
            $xpath = sprintf($xpathTemplate, $nodeName, $this->getTranslator()->translatePlaceholders($text));
617
            if ($this->webdriver->elementExists($xpath, WebDriver::BY_XPATH)) {
618
                return $this->webdriver->byXpath($xpath);
619
            }
620
        }
621
        // This is here for consistency with the other by* methods
622
        WebDriverException::throwException(7, 'Could not find element with text: ' . $this->getTranslator()->translatePlaceholders($text), []);
623
    }
624
625
    /**
626
     * @param string $text
627
     * @param string $specificNodeType
628
     * @param string $parentElementSelector
629
     * @return \Facebook\WebDriver\Remote\RemoteWebElement
630
     */
631
    public function byText($text, $specificNodeType = null, $parentElementSelector = null)
632
    {
633
        $xpathTemplate = '//%s[concat(" ",normalize-space(.)," ") = " %s "]';
634
        return $this->getElementByTextXpath($xpathTemplate, $text, $specificNodeType, $parentElementSelector);
635
    }
636
637
638
    /**
639
     * @param string $text
640
     * @param string $specificNodeType
641
     * @param string $parentElementSelector
642
     * @return \Facebook\WebDriver\Remote\RemoteWebElement
643
     */
644
    public function byContainsText($text, $specificNodeType = null, $parentElementSelector = null)
645
    {
646
        $xpathTemplate = '//%s[contains(., "%s")]';
647
        return $this->getElementByTextXpath($xpathTemplate, $text, $specificNodeType, $parentElementSelector);
648
    }
649
650
    /**
651
     * @return \Magium\Util\Translator\Translator
652
     */
653
654
    public function getTranslator()
655
    {
656
        return $this->get('Magium\Util\Translator\Translator');
657
    }
658
659
    public function addTranslationCsvFile($file, $locale)
660
    {
661
        $this->getTranslator()->addTranslationCsvFile($file, $locale);
662
    }
663
}
664