Completed
Push — master ( fdf082...9ad6b1 )
by Kevin
08:56 queued 05:59
created

AbstractTestCase::byCssSelector()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
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\Themes\BaseThemeInterface;
14
use Magium\Themes\ThemeConfigurationInterface;
15
use Magium\Util\Configuration\ClassConfigurationReader;
16
use Magium\Util\Configuration\ConfigurationCollector\DefaultPropertyCollector;
17
use Magium\Util\Configuration\ConfigurationReader;
18
use Magium\Util\Configuration\EnvironmentConfigurationReader;
19
use Magium\Util\Configuration\StandardConfigurationProvider;
20
use Magium\Util\Log\Logger;
21
use Magium\Util\Api\Clairvoyant\Clairvoyant;
22
use Magium\Util\Phpunit\MasterListener;
23
use Magium\Util\TestCase\RegistrationListener;
24
use Magium\WebDriver\WebDriver;
25
use PHPUnit_Framework_TestResult;
26
use Zend\Di\Config;
27
use Zend\Di\Di;
28
29
abstract class AbstractTestCase extends \PHPUnit_Framework_TestCase
30
{
31
32
    protected static $baseNamespaces = [];
33
34
    protected $baseThemeClass = 'Magium\Themes\ThemeConfigurationInterface';
35
36
    protected $postCallbacks = [];
37
38
    /**
39
     * @var MasterListener
40
     */
41
42
    protected static $masterListener;
43
44
    /**
45
     * @var \Magium\WebDriver\WebDriver
46
     */
47
    protected $webdriver;
48
49
    /**
50
     * @var Di
51
     */
52
53
    protected $di;
54
55
    protected $textElementNodeSearch = [
56
        'button', 'span', 'a', 'li', 'label', 'option', 'h1', 'h2', 'h3', 'td'
57
    ];
58
59
    protected $testCaseConfiguration = 'Magium\TestCaseConfiguration';
60
61
    protected $testCaseConfigurationObject;
62
63
    const BY_XPATH = 'byXpath';
64
    const BY_ID    = 'byId';
65
    const BY_CSS_SELECTOR = 'byCssSelector';
66
    const BY_TEXT = 'byText';
67
68
    protected static $registrationCallbacks;
69
70
    protected function setUp()
71
    {
72
        /*
73
         * Putting this in the setup and not in the property means that an extending class can inject itself easily
74
         * before the Magium namespace, thus, taking preference over the base namespace
75
         */
76
        self::addBaseNamespace('Magium');
77
        $this->configureDi();
78
        $this->di->instanceManager()->addSharedInstance(self::$masterListener, 'Magium\Util\Phpunit\MasterListener');
79
80
        $rc = new \ReflectionClass($this);
81
        while ($rc->getParentClass()) {
82
            $class = $rc->getParentClass()->getName();
83
            $this->di->instanceManager()->addSharedInstance($this, $class);
84
            $rc = new \ReflectionClass($class);
85
        }
86
        $this->webdriver = $this->di->get('Magium\WebDriver\WebDriver');
87
88
        $this->webdriver->setRemoteExecuteMethod($this->di->get('Magium\WebDriver\LoggingRemoteExecuteMethod'));
89
90
        // This is going to be refactored in a completely backwards compatible way.  Currently, because the DiC is
91
        // rebuilt for each request it doesn't maintain state between tests.  This is a good thing... except when
92
        // something that understands it (the MasterListener) does restain state.
93
94
        $clairvoyant = self::getMasterListener()->getListener('Magium\Util\Api\Clairvoyant\Clairvoyant');
95
        if ($clairvoyant instanceof Clairvoyant) {
96
            $this->di->instanceManager()->addSharedInstance($clairvoyant, get_class($clairvoyant));
97
        } else {
98
            $clairvoyant = $this->get('Magium\Util\Api\Clairvoyant\Clairvoyant');
99
            self::getMasterListener()->addListener($clairvoyant);
0 ignored issues
show
Documentation introduced by
$clairvoyant is of type object|null, but the function expects a object<PHPUnit_Framework_TestListener>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
100
        }
101
102
        /* @var $clairvoyant \Magium\Util\Api\Clairvoyant\Clairvoyant */
103
        $clairvoyant->setApiRequest($this->get('Magium\Util\Api\Request'));
0 ignored issues
show
Documentation introduced by
$this->get('Magium\\Util\\Api\\Request') is of type object|null, but the function expects a object<Magium\Util\Api\Request>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
104
        $clairvoyant->reset();
105
        $clairvoyant->setSessionId($this->webdriver->getSessionID());
106
        $clairvoyant->setCapability($this->testCaseConfigurationObject->getCapabilities());
107
        $this->getLogger()->addWriter($clairvoyant);
108
        $this->getLogger()->addCharacteristic(Logger::CHARACTERISTIC_BROWSER, $this->webdriver->getBrowser());
109
        $this->getLogger()->addCharacteristic(Logger::CHARACTERISTIC_OPERATING_SYSTEM, $this->webdriver->getPlatform());
110
111
112
        RegistrationListener::executeCallbacks($this);
113
    }
114
115
    public function __construct($name = null, array $data = [], $dataName = null, TestCaseConfiguration $configuration = null)
116
    {
117
        $this->testCaseConfigurationObject = $configuration;
118
        self::getMasterListener();
119
        parent::__construct($name, $data, $dataName);
120
121
    }
122
123
    protected function getDefaultConfiguration()
124
    {
125
        return [
126
            'definition' => [
127
                'class' => [
128
                    'Magium\WebDriver\WebDriver' => [
129
                        'instantiator' => 'Magium\WebDriver\WebDriverFactory::create'
130
                    ],
131
132
                    'Magium\WebDriver\WebDriverFactory' => [
133
                        'create'       => $this->testCaseConfigurationObject->getWebDriverConfiguration()
134
                    ]
135
                ]
136
            ],
137
            'instance'  => [
138
                'preference' => [
139
                    'Zend\I18n\Translator\Translator' => ['Magium\Util\Translator\Translator']
140
                ],
141
                'Magium\Util\Translator\Translator' => [
142
                    'parameters'    => [
143
                        'locale'    => 'en_US'
144
                    ]
145
                ],
146
                'Magium\Util\Log\Logger'   => [
147
                    'parameters'    => [
148
                        'options'   => [
149
                            'writers' => [
150
                                [
151
                                    'name' => 'Zend\Log\Writer\Noop',
152
                                    'options' => []
153
                                ]
154
                            ]
155
                        ]
156
                    ]
157
                ]
158
            ]
159
        ];
160
    }
161
162
    public function configureDi()
163
    {
164
        if (!$this->testCaseConfigurationObject instanceof TestCaseConfiguration) {
165
            if ($this->di instanceof Di) {
166
                $this->testCaseConfigurationObject = $this->get($this->testCaseConfiguration);
167
            } else {
168
                $this->testCaseConfigurationObject = new $this->testCaseConfiguration(
169
                    new StandardConfigurationProvider(
170
                        new ConfigurationReader(),
171
                        new ClassConfigurationReader(),
172
                        new EnvironmentConfigurationReader()
173
                    )
174
                    , new DefaultPropertyCollector()
175
                );
176
            }
177
        }
178
        /* @var $configuration TestCaseConfiguration */
179
        $configArray = $this->getDefaultConfiguration();
180
181
        $count = 0;
182
183
        $path = realpath(__DIR__ . '/../');
184
185
        while ($count++ < 5) {
186
            $dir = "{$path}/configuration/";
187
            if (is_dir($dir)) {
188
                foreach (glob($dir . '*.php') as $file) {
189
                    $configArray = array_merge_recursive($configArray, include $file);
190
                }
191
                break;
192
            }
193
            $path .= '/../';
194
        }
195
196
197
        $configArray = $this->testCaseConfigurationObject->reprocessConfiguration($configArray);
198
        $configuration = new Config($configArray);
199
200
        $this->di = new Di();
201
        $configuration->configure($this->di);
202
    }
203
204
    public function setTestResultObject(PHPUnit_Framework_TestResult $result)
205
    {
206
        // This odd little function is here because the first place where you can reliably add a listener without
207
        // having to make a phpunit.xml or program argument change
208
209
        self::getMasterListener()->bindToResult($result);
210
        parent::setTestResultObject($result);
211
    }
212
213
    /**
214
     * @return MasterListener
215
     */
216
217
    public static function getMasterListener()
218
    {
219
        if (!self::$masterListener instanceof MasterListener) {
220
            self::$masterListener = new MasterListener();
221
        }
222
        return self::$masterListener;
223
    }
224
225
    protected function tearDown()
226
    {
227
        foreach ($this->postCallbacks as $callback) {
228
            if (is_callable($callback)) {
229
                call_user_func($callback);
230
            }
231
        }
232
        parent::tearDown();
233
        if ($this->webdriver instanceof WebDriver) {
234
            $this->webdriver->quit();
235
            $this->webdriver = null;
236
        }
237
    }
238
239
240
    public function filterWebDriverAction($by)
241
    {
242
        switch ($by) {
243
            case WebDriver::BY_XPATH:
244
                return 'xpath';
245
            case WebDriver::BY_CSS_SELECTOR:
246
                return 'css_selector';
247
            case WebDriver::BY_ID:
248
                return 'id';
249
            default:
250
                return $by;
251
        }
252
    }
253
254
    public function assertElementClickable($selector, $by = WebDriver::BY_ID)
255
    {
256
        $this->elementAssertion($selector, $by, Clickable::ASSERTION);
257
    }
258
259
260
    public function assertElementNotClickable($selector, $by = WebDriver::BY_ID)
261
    {
262
        $this->elementAssertion($selector, $by, NotClickable::ASSERTION);
263
264
    }
265
266
    public function setTestCaseConfigurationClass($class)
267
    {
268
        $this->testCaseConfiguration = $class;
269
    }
270
271
    public static function addBaseNamespace($namespace)
272
    {
273
        if (!in_array($namespace, self::$baseNamespaces)) {
274
            self::$baseNamespaces[] = trim($namespace, '\\');
275
        }
276
    }
277
278
    public static function resolveClass( $class, $prefix = null)
279
    {
280
        $origClass = $class;
281
        if ($prefix !== null) {
282
            $class = "{$prefix}\\{$class}";
283
        }
284
        foreach (self::$baseNamespaces as $namespace) {
285
            if (strpos($namespace, $class) === 0) {
286
                // We have a fully qualified class name
287
                return $class;
288
            }
289
        }
290
291
        foreach (self::$baseNamespaces as $namespace) {
292
            $fqClass = $namespace . '\\' . $class;
293
            if (class_exists($fqClass)) {
294
                return $fqClass;
295
            }
296
        }
297
        return $origClass;
298
    }
299
300
    public function setTypePreference($type, $preference)
301
    {
302
        $type = self::resolveClass($type);
303
        $preference = self::resolveClass($preference);
304
        $this->di->instanceManager()->unsetTypePreferences($type);
305
        $this->di->instanceManager()->setTypePreference($type, [$preference]);
306
307
    }
308
309
    protected function normalizeClassRequest($class)
310
    {
311
        return str_replace('/', '\\', $class);
312
    }
313
314
    public function addPostTestCallback($callback)
315
    {
316
        if (!is_callable($callback)) {
317
            throw new InvalidConfigurationException('Callback is not callable');
318
        }
319
        $this->postCallbacks[] = $callback;
320
    }
321
322
    /**
323
324
     * @param string $theme
325
     * @return \Magium\Themes\ThemeConfigurationInterface
326
     */
327
328
    public function getTheme($theme = null)
329
    {
330
        if ($theme === null) {
331
            return $this->get($this->baseThemeClass);
332
        }
333
        $theme = self::resolveClass($theme, 'Themes');
334
        return $this->get($theme);
335
    }
336
337
    /**
338
     *
339
     * @param string $action
340
     * @return mixed
341
     */
342
343
    public function getAction($action)
344
    {
345
        $action = self::resolveClass($action, 'Actions');
346
347
        return $this->get($action);
348
    }
349
350
351
    /**
352
     * @param string $name
353
     * @return \Magium\Magento\Identities\AbstractEntity
354
     */
355
356
    public function getIdentity($name = 'Customer')
357
    {
358
        $name = self::resolveClass($name, 'Identities');
359
360
        return $this->get($name);
361
    }
362
363
    /**
364
     *
365
     * @param string $navigator
366
     * @return \Magium\Magento\Navigators\BaseMenuNavigator
367
     */
368
369
    public function getNavigator($navigator = 'BaseMenu')
370
    {
371
        $navigator = self::resolveClass($navigator, 'Navigators');
372
373
        return $this->get($navigator);
374
    }
375
376
    public function getAssertion($assertion)
377
    {
378
        $assertion = self::resolveClass($assertion, 'Assertions');
379
380
        return $this->get($assertion);
381
    }
382
383
    /**
384
     *
385
     * @param string $extractor
386
     * @return \Magium\Extractors\AbstractExtractor
387
     */
388
389
    public function getExtractor($extractor)
390
    {
391
        $extractor = self::resolveClass($extractor, 'Extractors');
392
393
        return $this->get($extractor);
394
    }
395
396
    /**
397
     * Sleep the specified amount of time.
398
     *
399
     * Options: 1s (1 second), 1ms (1 millisecond), 1us (1 microsecond), 1ns (1 nanosecond)
400
     *
401
     * @param $time
402
     */
403
404
    public function sleep($time)
405
    {
406
        $length = (int)$time;
407
408
        if (strpos($time, 'ms') !== false) {
409
            usleep($length * 1000);
410
        } else if (strpos($time, 'us') !== false) {
411
            usleep($length);
412
        } else if (strpos($time, 'ns') !== false) {
413
            time_nanosleep(0, $length);
414
        } else {
415
            sleep($length);
416
        }
417
    }
418
419
420
    public function commandOpen($url)
421
    {
422
        $this->get('Magium\Commands\Open')->open($url);
423
    }
424
425
426
    /**
427
     * @return \Magium\Util\Log\Logger
428
     */
429
430
    public function getLogger()
431
    {
432
        return $this->get('Magium\Util\Log\Logger');
433
    }
434
435
    public function get($class)
436
    {
437
        $class = $this->normalizeClassRequest($class);
438
        $preferredClass = $this->di->instanceManager()->getTypePreferences($class);
439
        if (is_array($preferredClass) && count($preferredClass) > 0) {
440
            $class = array_shift($preferredClass);
441
        }
442
        return $this->di->get($class);
443
    }
444
445
446
    public function assertElementExists($selector, $by = 'byId')
447
    {
448
        $this->elementAssertion($selector, $by, Exists::ASSERTION);
449
    }
450
451
    public function assertTitleEquals($title)
452
    {
453
        $webTitle = $this->webdriver->getTitle();
454
        self::assertEquals($title, $webTitle);
455
    }
456
457
458
    public function assertTitleContains($title)
459
    {
460
        $webTitle = $this->webdriver->getTitle();
461
        self::assertContains($title, $webTitle);
462
    }
463
464
465
    public function assertNotTitleEquals($title)
466
    {
467
        $webTitle = $this->webdriver->getTitle();
468
        self::assertNotEquals($title, $webTitle);
469
    }
470
471
472
    public function assertNotTitleContains($title)
473
    {
474
        $webTitle = $this->webdriver->getTitle();
475
        self::assertNotContains($title, $webTitle);
476
    }
477
478
    public function assertURLEquals($url)
479
    {
480
        $webUrl = $this->webdriver->getCurrentURL();
481
        self::assertEquals($url, $webUrl);
482
    }
483
484
    public function assertURLContains($url)
485
    {
486
        $webUrl = $this->webdriver->getCurrentURL();
487
        self::assertContains($url, $webUrl);
488
    }
489
490
491
    public function assertURLNotEquals($url)
492
    {
493
        $webUrl = $this->webdriver->getCurrentURL();
494
        self::assertNotEquals($url, $webUrl);
495
    }
496
497
    public function assertURLNotContains($url)
498
    {
499
        $webUrl = $this->webdriver->getCurrentURL();
500
        self::assertNotContains($url, $webUrl);
501
    }
502
503
    protected function elementAssertion($selector, $by, $name)
504
    {
505
        $executor = $this->getAssertion(LoggingAssertionExecutor::ASSERTION);
506
        $assertion = $this->getAssertion($name);
507
        $assertion->setSelector($selector)->setBy($by);
508
        $executor->execute($assertion);
509
    }
510
511
    public function assertElementDisplayed($selector, $by = 'byId')
512
    {
513
        $this->elementAssertion($selector, $by, NotDisplayed::ASSERTION);
514
    }
515
516
    public function assertElementNotDisplayed($selector, $by = 'byId')
517
    {
518
        try {
519
            $this->assertElementExists($selector, $by);
520
            self::assertFalse(
521
                $this->webdriver->$by($selector)->isDisplayed(),
522
                sprintf('The element: %s, is displayed and it should not have been', $selector)
523
            );
524
        } catch (\Exception $e) {
525
            $this->fail(sprintf('Element "%s" cannot be found using selector "%s"', $selector, $by));
526
        }
527
    }
528
529
    public function assertElementNotExists($selector, $by = 'byId')
530
    {
531
        $this->elementAssertion($selector, $by, NotExists::ASSERTION);
532
    }
533
534
    /**
535
     * @return LoggingAssertionExecutor
536
     */
537
538
    public function getAssertionLogger()
539
    {
540
        return $this->getAssertion(LoggingAssertionExecutor::ASSERTION);
541
    }
542
543
    public function switchThemeConfiguration($fullyQualifiedClassName)
544
    {
545
546
        $reflection = new \ReflectionClass($fullyQualifiedClassName);
547
548
        if ($reflection->implementsInterface('Magium\Themes\ThemeConfigurationInterface')) {
549
            $this->baseThemeClass = $fullyQualifiedClassName;
550
            $this->di->instanceManager()->unsetTypePreferences('Magium\Themes\ThemeConfigurationInterface');
551
            $this->di->instanceManager()->setTypePreference('Magium\Themes\ThemeConfigurationInterface', [$fullyQualifiedClassName]);
552
553
            if ($reflection->implementsInterface('Magium\Themes\BaseThemeInterface')) {
554
                $this->di->instanceManager()->unsetTypePreferences('Magium\Themes\BaseThemeInterface');
555
                $this->di->instanceManager()->setTypePreference('Magium\Themes\BaseThemeInterface', [$fullyQualifiedClassName]);
556
            }
557
            $theme = $this->getTheme();
558
            if ($theme instanceof BaseThemeInterface) {
559
                $theme->configure($this);
560
            }
561
        } else {
562
            throw new InvalidConfigurationException('The theme configuration implement Magium\Themes\ThemeConfigurationInterface');
563
        }
564
        $this->getLogger()->addCharacteristic(Logger::CHARACTERISTIC_THEME, $fullyQualifiedClassName);
565
    }
566
567
    public static function assertWebDriverElement($element)
568
    {
569
        self::assertInstanceOf('Facebook\WebDriver\WebDriverElement', $element);
570
    }
571
572
    public function assertElementHasText($node, $text)
573
    {
574
        try {
575
            $this->byXpath(sprintf('//%s[contains(., "%s")]', $node, addslashes($text)));
576
        } catch (\Exception $e) {
577
            $this->fail('The body did not contain the text: ' . $text);
578
        }
579
    }
580
581
    public function assertPageHasText($text)
582
    {
583
        try {
584
            $this->webdriver->byXpath(sprintf('//body[contains(., "%s")]', $text));
585
            // If the element is not found then an exception will be thrown
586
        } catch (\Exception $e) {
587
            $this->fail('The body did not contain the text: ' . $text);
588
        }
589
590
    }
591
592
    public function assertPageNotHasText($text)
593
    {
594
        try {
595
            $this->webdriver->byXpath(sprintf('//body[contains(., "%s")]', $text));
596
            $this->fail('The page contains the words: ' . $text);
597
        } catch (NoSuchElementException $e) {
598
            // Exception thrown is a success
599
        }
600
    }
601
602
    /**
603
     * @param $xpath
604
     * @return \Facebook\WebDriver\Remote\RemoteWebElement
605
     */
606
607
    public function byXpath($xpath)
608
    {
609
        return $this->webdriver->byXpath($xpath);
610
    }
611
612
    /**
613
     * @param $id
614
     * @return \Facebook\WebDriver\Remote\RemoteWebElement
615
     */
616
617
    public function byId($id)
618
    {
619
        return $this->webdriver->byId($id);
620
    }
621
622
    /**
623
     * @param $selector
624
     * @return \Facebook\WebDriver\Remote\RemoteWebElement
625
     */
626
627
    public function byCssSelector($selector)
628
    {
629
        return $this->webdriver->byCssSelector($selector);
630
    }
631
632
    protected function getElementByTextXpath($xpathTemplate, $text, $specificNodeType = null, $parentElementSelector = null)
633
    {
634
635
        if ($parentElementSelector !== null) {
636
            $xpathTemplate = $parentElementSelector . $xpathTemplate;
637
        }
638
        if ($specificNodeType !== null) {
639
            return $this->byXpath(sprintf($xpathTemplate, $specificNodeType, $this->getTranslator()->translatePlaceholders($text)));
640
        }
641
642
        foreach ($this->textElementNodeSearch as $nodeName) {
643
            $xpath = sprintf($xpathTemplate, $nodeName, $this->getTranslator()->translatePlaceholders($text));
644
            if ($this->webdriver->elementExists($xpath, WebDriver::BY_XPATH)) {
645
                return $this->webdriver->byXpath($xpath);
646
            }
647
        }
648
        // This is here for consistency with the other by* methods
649
        WebDriverException::throwException(7, 'Could not find element with text: ' . $this->getTranslator()->translatePlaceholders($text), []);
650
    }
651
652
    /**
653
     * @param string $text
654
     * @param string $specificNodeType
655
     * @param string $parentElementSelector
656
     * @return \Facebook\WebDriver\Remote\RemoteWebElement
657
     */
658
    public function byText($text, $specificNodeType = null, $parentElementSelector = null)
659
    {
660
        $xpathTemplate = '//%s[concat(" ",normalize-space(.)," ") = " %s "]';
661
        return $this->getElementByTextXpath($xpathTemplate, $text, $specificNodeType, $parentElementSelector);
662
    }
663
664
665
    /**
666
     * @param string $text
667
     * @param string $specificNodeType
668
     * @param string $parentElementSelector
669
     * @return \Facebook\WebDriver\Remote\RemoteWebElement
670
     */
671
    public function byContainsText($text, $specificNodeType = null, $parentElementSelector = null)
672
    {
673
        $xpathTemplate = '//%s[contains(., "%s")]';
674
        return $this->getElementByTextXpath($xpathTemplate, $text, $specificNodeType, $parentElementSelector);
675
    }
676
677
    /**
678
     * @return \Magium\Util\Translator\Translator
679
     */
680
681
    public function getTranslator()
682
    {
683
        return $this->get('Magium\Util\Translator\Translator');
684
    }
685
686
    public function addTranslationCsvFile($file, $locale)
687
    {
688
        $this->getTranslator()->addTranslationCsvFile($file, $locale);
689
    }
690
}
691