Completed
Push — master ( 5c9c0e...c8973f )
by Kevin
05:21 queued 02:40
created

AbstractTestCase::tearDown()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 9
Bugs 0 Features 0
Metric Value
c 9
b 0
f 0
dl 0
loc 19
rs 8.8571
cc 5
eloc 11
nc 9
nop 0
1
<?php
2
3
namespace Magium;
4
5
use Facebook\WebDriver\Exception\NoSuchElementException;
6
use Facebook\WebDriver\Exception\WebDriverException;
7
use Magium\Assertions\Element\Clickable;
8
use Magium\Assertions\Element\Exists;
9
use Magium\Assertions\Element\NotClickable;
10
use Magium\Assertions\Element\NotDisplayed;
11
use Magium\Assertions\Element\NotExists;
12
use Magium\Assertions\LoggingAssertionExecutor;
13
use Magium\TestCase\Initializer;
14
use Magium\Themes\BaseThemeInterface;
15
use Magium\Util\Log\Logger;
16
use Magium\Util\Phpunit\MasterListener;
17
use Magium\WebDriver\WebDriver;
18
use PHPUnit_Framework_TestResult;
19
use Zend\Di\Di;
20
21
abstract class AbstractTestCase extends \PHPUnit_Framework_TestCase
22
{
23
24
    protected static $baseNamespaces = [];
25
26
    protected $baseThemeClass = 'Magium\Themes\ThemeConfigurationInterface';
27
28
    protected $postCallbacks = [];
29
30
    /**
31
     * @var MasterListener
32
     */
33
34
    protected static $masterListener;
35
36
    /**
37
     * @var \Magium\WebDriver\WebDriver
38
     */
39
    protected $webdriver;
40
41
    /**
42
     * @var Di
43
     */
44
45
    protected $di;
46
47
    protected $textElementNodeSearch = [
48
        'button', 'span', 'a', 'li', 'label', 'option', 'h1', 'h2', 'h3', 'td'
49
    ];
50
51
    protected $initializer;
52
53
    const BY_XPATH = 'byXpath';
54
    const BY_ID    = 'byId';
55
    const BY_CSS_SELECTOR = 'byCssSelector';
56
    const BY_TEXT = 'byText';
57
58
    protected static $registrationCallbacks;
59
60
    protected function setUp()
61
    {
62
        /*
63
         * Putting this in the setup and not in the property means that an extending class can inject itself easily
64
         * before the Magium namespace, thus, taking preference over the base namespace
65
         */
66
        self::addBaseNamespace('Magium');
67
        if (!$this->initializer instanceof Initializer) {
68
            $this->initializer = new Initializer();
69
        }
70
        $this->initializer->initialize($this);
71
    }
72
73
    /**
74
     * @return Di
75
     */
76
    public function getDi()
77
    {
78
        return $this->di;
79
    }
80
81
    /**
82
     * @param Di $di
83
     */
84
    public function setDi($di)
85
    {
86
        $this->di = $di;
87
    }
88
89
    /**
90
     * @return WebDriver
91
     */
92
    public function getWebdriver()
93
    {
94
        return $this->webdriver;
95
    }
96
97
    /**
98
     * @param WebDriver $webdriver
99
     */
100
    public function setWebdriver(WebDriver $webdriver)
101
    {
102
        $this->webdriver = $webdriver;
103
    }
104
105
    public function getInitializer()
106
    {
107
        return $this->initializer;
108
    }
109
110
111
    public function __construct($name = null, array $data = [], $dataName = null, Initializer $initializer = null)
112
    {
113
        if (!$initializer instanceof Initializer) {
114
            $initializer = new Initializer();
115
        }
116
        $this->initializer = $initializer;
117
        self::getMasterListener();
118
        parent::__construct($name, $data, $dataName);
119
120
    }
121
122
    public function setTestResultObject(PHPUnit_Framework_TestResult $result)
123
    {
124
        // This odd little function is here because the first place where you can reliably add a listener without
125
        // having to make a phpunit.xml or program argument change
126
127
        self::getMasterListener()->bindToResult($result);
128
        parent::setTestResultObject($result);
129
    }
130
131
    /**
132
     * @return MasterListener
133
     */
134
135
    public static function getMasterListener()
136
    {
137
        if (!self::$masterListener instanceof MasterListener) {
138
            self::$masterListener = new MasterListener();
139
        }
140
        return self::$masterListener;
141
    }
142
143
    protected function tearDown()
144
    {
145
        foreach ($this->postCallbacks as $callback) {
146
            if (is_callable($callback)) {
147
                call_user_func($callback);
148
            }
149
        }
150
        parent::tearDown();
151
        if ($this->webdriver instanceof WebDriver) {
152
            try {
153
                $this->webdriver->close();
154
            } catch (\Exception $e) {
155
                // All closed
156
            }
157
158
            $this->webdriver->quit();
159
            $this->webdriver = null;
160
        }
161
    }
162
163
164
    public function filterWebDriverAction($by)
165
    {
166
        switch ($by) {
167
            case WebDriver::BY_XPATH:
168
                return 'xpath';
169
            case WebDriver::BY_CSS_SELECTOR:
170
                return 'css_selector';
171
            case WebDriver::BY_ID:
172
                return 'id';
173
            default:
174
                return $by;
175
        }
176
    }
177
178
    public function assertElementClickable($selector, $by = WebDriver::BY_ID)
179
    {
180
        $this->elementAssertion($selector, $by, Clickable::ASSERTION);
181
    }
182
183
184
    public function assertElementNotClickable($selector, $by = WebDriver::BY_ID)
185
    {
186
        $this->elementAssertion($selector, $by, NotClickable::ASSERTION);
187
188
    }
189
190
    public static function addBaseNamespace($namespace)
191
    {
192
        if (!in_array($namespace, self::$baseNamespaces)) {
193
            self::$baseNamespaces[] = trim($namespace, '\\');
194
        }
195
    }
196
197
    public static function resolveClass( $class, $prefix = null)
198
    {
199
        $origClass = $class;
200
        if ($prefix !== null) {
201
            $class = "{$prefix}\\{$class}";
202
        }
203
        foreach (self::$baseNamespaces as $namespace) {
204
            if (strpos($namespace, $class) === 0) {
205
                // We have a fully qualified class name
206
                return $class;
207
            }
208
        }
209
210
        foreach (self::$baseNamespaces as $namespace) {
211
            $fqClass = $namespace . '\\' . $class;
212
            if (class_exists($fqClass)) {
213
                return $fqClass;
214
            }
215
        }
216
        return $origClass;
217
    }
218
219
    public function setTypePreference($type, $preference)
220
    {
221
        $type = self::resolveClass($type);
222
        $preference = self::resolveClass($preference);
223
        $this->di->instanceManager()->unsetTypePreferences($type);
224
        $this->di->instanceManager()->setTypePreference($type, [$preference]);
225
226
    }
227
228
    protected function normalizeClassRequest($class)
229
    {
230
        return str_replace('/', '\\', $class);
231
    }
232
233
    public function addPostTestCallback($callback)
234
    {
235
        if (!is_callable($callback)) {
236
            throw new InvalidConfigurationException('Callback is not callable');
237
        }
238
        $this->postCallbacks[] = $callback;
239
    }
240
241
    /**
242
243
     * @param string $theme
244
     * @return \Magium\Themes\ThemeConfigurationInterface
245
     */
246
247
    public function getTheme($theme = null)
248
    {
249
        if ($theme === null) {
250
            return $this->get($this->baseThemeClass);
251
        }
252
        $theme = self::resolveClass($theme, 'Themes');
253
        return $this->get($theme);
254
    }
255
256
    /**
257
     *
258
     * @param string $action
259
     * @return mixed
260
     */
261
262
    public function getAction($action)
263
    {
264
        $action = self::resolveClass($action, 'Actions');
265
266
        return $this->get($action);
267
    }
268
269
270
    /**
271
     * @param string $name
272
     * @return \Magium\Magento\Identities\AbstractEntity
273
     */
274
275
    public function getIdentity($name = 'Customer')
276
    {
277
        $name = self::resolveClass($name, 'Identities');
278
279
        return $this->get($name);
280
    }
281
282
    /**
283
     *
284
     * @param string $navigator
285
     * @return \Magium\Magento\Navigators\BaseMenuNavigator
286
     */
287
288
    public function getNavigator($navigator = 'BaseMenu')
289
    {
290
        $navigator = self::resolveClass($navigator, 'Navigators');
291
292
        return $this->get($navigator);
293
    }
294
295
    public function getAssertion($assertion)
296
    {
297
        $assertion = self::resolveClass($assertion, 'Assertions');
298
299
        return $this->get($assertion);
300
    }
301
302
    /**
303
     *
304
     * @param string $extractor
305
     * @return \Magium\Extractors\AbstractExtractor
306
     */
307
308
    public function getExtractor($extractor)
309
    {
310
        $extractor = self::resolveClass($extractor, 'Extractors');
311
312
        return $this->get($extractor);
313
    }
314
315
    /**
316
     * Sleep the specified amount of time.
317
     *
318
     * Options: 1s (1 second), 1ms (1 millisecond), 1us (1 microsecond), 1ns (1 nanosecond)
319
     *
320
     * @param $time
321
     */
322
323
    public function sleep($time)
324
    {
325
        $length = (int)$time;
326
327
        if (strpos($time, 'ms') !== false) {
328
            usleep($length * 1000);
329
        } else if (strpos($time, 'us') !== false) {
330
            usleep($length);
331
        } else if (strpos($time, 'ns') !== false) {
332
            time_nanosleep(0, $length);
333
        } else {
334
            sleep($length);
335
        }
336
    }
337
338
339
    public function commandOpen($url)
340
    {
341
        $this->get('Magium\Commands\Open')->open($url);
342
    }
343
344
345
    /**
346
     * @return \Magium\Util\Log\Logger
347
     */
348
349
    public function getLogger()
350
    {
351
        return $this->get('Magium\Util\Log\Logger');
352
    }
353
354
    public function get($class)
355
    {
356
        $class = $this->normalizeClassRequest($class);
357
        $preferredClass = $this->di->instanceManager()->getTypePreferences($class);
358
        if (is_array($preferredClass) && count($preferredClass) > 0) {
359
            $class = array_shift($preferredClass);
360
        }
361
        return $this->di->get($class);
362
    }
363
364
365
    public function assertElementExists($selector, $by = 'byId')
366
    {
367
        $this->elementAssertion($selector, $by, Exists::ASSERTION);
368
    }
369
370
    public function assertTitleEquals($title)
371
    {
372
        $webTitle = $this->webdriver->getTitle();
373
        self::assertEquals($title, $webTitle);
374
    }
375
376
377
    public function assertTitleContains($title)
378
    {
379
        $webTitle = $this->webdriver->getTitle();
380
        self::assertContains($title, $webTitle);
381
    }
382
383
384
    public function assertNotTitleEquals($title)
385
    {
386
        $webTitle = $this->webdriver->getTitle();
387
        self::assertNotEquals($title, $webTitle);
388
    }
389
390
391
    public function assertNotTitleContains($title)
392
    {
393
        $webTitle = $this->webdriver->getTitle();
394
        self::assertNotContains($title, $webTitle);
395
    }
396
397
    public function assertURLEquals($url)
398
    {
399
        $webUrl = $this->webdriver->getCurrentURL();
400
        self::assertEquals($url, $webUrl);
401
    }
402
403
    public function assertURLContains($url)
404
    {
405
        $webUrl = $this->webdriver->getCurrentURL();
406
        self::assertContains($url, $webUrl);
407
    }
408
409
410
    public function assertURLNotEquals($url)
411
    {
412
        $webUrl = $this->webdriver->getCurrentURL();
413
        self::assertNotEquals($url, $webUrl);
414
    }
415
416
    public function assertURLNotContains($url)
417
    {
418
        $webUrl = $this->webdriver->getCurrentURL();
419
        self::assertNotContains($url, $webUrl);
420
    }
421
422
    protected function elementAssertion($selector, $by, $name)
423
    {
424
        $executor = $this->getAssertion(LoggingAssertionExecutor::ASSERTION);
425
        $assertion = $this->getAssertion($name);
426
        $assertion->setSelector($selector)->setBy($by);
427
        $executor->execute($assertion);
428
    }
429
430
    public function assertElementDisplayed($selector, $by = 'byId')
431
    {
432
        $this->elementAssertion($selector, $by, NotDisplayed::ASSERTION);
433
    }
434
435
    public function assertElementNotDisplayed($selector, $by = 'byId')
436
    {
437
        try {
438
            $this->assertElementExists($selector, $by);
439
            self::assertFalse(
440
                $this->webdriver->$by($selector)->isDisplayed(),
441
                sprintf('The element: %s, is displayed and it should not have been', $selector)
442
            );
443
        } catch (\Exception $e) {
444
            $this->fail(sprintf('Element "%s" cannot be found using selector "%s"', $selector, $by));
445
        }
446
    }
447
448
    public function assertElementNotExists($selector, $by = 'byId')
449
    {
450
        $this->elementAssertion($selector, $by, NotExists::ASSERTION);
451
    }
452
453
    /**
454
     * @return LoggingAssertionExecutor
455
     */
456
457
    public function getAssertionLogger()
458
    {
459
        return $this->getAssertion(LoggingAssertionExecutor::ASSERTION);
460
    }
461
462
    public function switchThemeConfiguration($fullyQualifiedClassName)
463
    {
464
465
        $reflection = new \ReflectionClass($fullyQualifiedClassName);
466
467
        if ($reflection->implementsInterface('Magium\Themes\ThemeConfigurationInterface')) {
468
            $this->baseThemeClass = $fullyQualifiedClassName;
469
            $this->di->instanceManager()->unsetTypePreferences('Magium\Themes\ThemeConfigurationInterface');
470
            $this->di->instanceManager()->setTypePreference('Magium\Themes\ThemeConfigurationInterface', [$fullyQualifiedClassName]);
471
472
            if ($reflection->implementsInterface('Magium\Themes\BaseThemeInterface')) {
473
                $this->di->instanceManager()->unsetTypePreferences('Magium\Themes\BaseThemeInterface');
474
                $this->di->instanceManager()->setTypePreference('Magium\Themes\BaseThemeInterface', [$fullyQualifiedClassName]);
475
            }
476
            $theme = $this->getTheme();
477
            if ($theme instanceof BaseThemeInterface) {
478
                $theme->configure($this);
479
            }
480
        } else {
481
            throw new InvalidConfigurationException('The theme configuration implement Magium\Themes\ThemeConfigurationInterface');
482
        }
483
        $this->getLogger()->addCharacteristic(Logger::CHARACTERISTIC_THEME, $fullyQualifiedClassName);
484
    }
485
486
    public static function assertWebDriverElement($element)
487
    {
488
        self::assertInstanceOf('Facebook\WebDriver\WebDriverElement', $element);
489
    }
490
491
    public function assertElementHasText($node, $text)
492
    {
493
        try {
494
            $this->byXpath(sprintf('//%s[contains(., "%s")]', $node, addslashes($text)));
495
        } catch (\Exception $e) {
496
            $this->fail('The body did not contain the text: ' . $text);
497
        }
498
    }
499
500
    public function assertPageHasText($text)
501
    {
502
        try {
503
            $this->webdriver->byXpath(sprintf('//body[contains(., "%s")]', $text));
504
            // If the element is not found then an exception will be thrown
505
        } catch (\Exception $e) {
506
            $this->fail('The body did not contain the text: ' . $text);
507
        }
508
509
    }
510
511
    public function assertPageNotHasText($text)
512
    {
513
        try {
514
            $this->webdriver->byXpath(sprintf('//body[contains(., "%s")]', $text));
515
            $this->fail('The page contains the words: ' . $text);
516
        } catch (NoSuchElementException $e) {
517
            // Exception thrown is a success
518
        }
519
    }
520
521
    /**
522
     * @param $xpath
523
     * @return \Facebook\WebDriver\Remote\RemoteWebElement
524
     */
525
526
    public function byXpath($xpath)
527
    {
528
        return $this->webdriver->byXpath($xpath);
529
    }
530
531
    /**
532
     * @param $id
533
     * @return \Facebook\WebDriver\Remote\RemoteWebElement
534
     */
535
536
    public function byId($id)
537
    {
538
        return $this->webdriver->byId($id);
539
    }
540
541
    /**
542
     * @param $selector
543
     * @return \Facebook\WebDriver\Remote\RemoteWebElement
544
     */
545
546
    public function byCssSelector($selector)
547
    {
548
        return $this->webdriver->byCssSelector($selector);
549
    }
550
551
    protected function getElementByTextXpath($xpathTemplate, $text, $specificNodeType = null, $parentElementSelector = null)
552
    {
553
554
        if ($parentElementSelector !== null) {
555
            $xpathTemplate = $parentElementSelector . $xpathTemplate;
556
        }
557
        if ($specificNodeType !== null) {
558
            return $this->byXpath(sprintf($xpathTemplate, $specificNodeType, $this->getTranslator()->translatePlaceholders($text)));
559
        }
560
561
        foreach ($this->textElementNodeSearch as $nodeName) {
562
            $xpath = sprintf($xpathTemplate, $nodeName, $this->getTranslator()->translatePlaceholders($text));
563
            if ($this->webdriver->elementExists($xpath, WebDriver::BY_XPATH)) {
564
                return $this->webdriver->byXpath($xpath);
565
            }
566
        }
567
        // This is here for consistency with the other by* methods
568
        WebDriverException::throwException(7, 'Could not find element with text: ' . $this->getTranslator()->translatePlaceholders($text), []);
569
    }
570
571
    /**
572
     * @param string $text
573
     * @param string $specificNodeType
574
     * @param string $parentElementSelector
575
     * @return \Facebook\WebDriver\Remote\RemoteWebElement
576
     */
577
    public function byText($text, $specificNodeType = null, $parentElementSelector = null)
578
    {
579
        $xpathTemplate = '//%s[concat(" ",normalize-space(.)," ") = " %s "]';
580
        return $this->getElementByTextXpath($xpathTemplate, $text, $specificNodeType, $parentElementSelector);
581
    }
582
583
584
    /**
585
     * @param string $text
586
     * @param string $specificNodeType
587
     * @param string $parentElementSelector
588
     * @return \Facebook\WebDriver\Remote\RemoteWebElement
589
     */
590
    public function byContainsText($text, $specificNodeType = null, $parentElementSelector = null)
591
    {
592
        $xpathTemplate = '//%s[contains(., "%s")]';
593
        return $this->getElementByTextXpath($xpathTemplate, $text, $specificNodeType, $parentElementSelector);
594
    }
595
596
    /**
597
     * @return \Magium\Util\Translator\Translator
598
     */
599
600
    public function getTranslator()
601
    {
602
        return $this->get('Magium\Util\Translator\Translator');
603
    }
604
605
    public function addTranslationCsvFile($file, $locale)
606
    {
607
        $this->getTranslator()->addTranslationCsvFile($file, $locale);
608
    }
609
}
610