Completed
Branch master (0f91be)
by Kevin
05:55 queued 03:21
created

AbstractTestCase::getElementByTextXpath()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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