Completed
Branch master (a7a328)
by Kevin
04:49 queued 02:08
created

Menu::getBaseMatches()   C

Complexity

Conditions 8
Paths 13

Size

Total Lines 34
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 34
rs 5.3846
cc 8
eloc 22
nc 13
nop 2
1
<?php
2
3
namespace Magium\Extractors\Navigation;
4
5
use Magium\AbstractTestCase;
6
use Magium\Extractors\AbstractExtractor;
7
use Magium\Navigators\InstructionNavigator;
8
use Magium\Themes\ThemeConfigurationInterface;
9
use Magium\WebDriver\WebDriver;
10
11
class Menu extends AbstractExtractor
12
{
13
    const EXTRACTOR = 'Navigation\Menu';
14
15
    protected $translator;
16
    protected $instructionNavigator;
17
18
    protected $path;
19
20
    protected $baseXpath;
21
    protected $childXpath;
22
23
    protected $childNodeSearch = '*[concat(" ", normalize-space(.)," ") = " %s "]';
24
25
    protected $childSearchOrder = [
26
        'span[concat(" ",normalize-space(.)," ") = " %s "]/ancestor::li[1]',
27
        'a[concat(" ",normalize-space(.)," ") = " %s "]/ancestor::li[1]',
28
        'li[concat(" ",normalize-space(.)," ") = " %s "][1]',
29
        '*[concat(" ",normalize-space(.)," ") = " %s "]/ancestor::li[1]',
30
    ];
31
32
    protected $baseSearchOrder = [
33
        'nav',
34
        'ul[@class="nav"]',
35
        'ul[@class="navigation"]',
36
        'ul[@id="nav"]',
37
        'ul[contains(concat(" ",normalize-space(@id)," "), " nav ")]',
38
        'ul[contains(@id, "nav")]',
39
        'ul[contains(concat(" ",normalize-space(@class)," "), " nav ")]',
40
        'ul[contains(concat(" ",normalize-space(class)," "), " Nav ")]',
41
        'ul[contains(concat(" ",normalize-space(@id)," "), " navigation ")]',
42
        'ul[contains(concat(" ",normalize-space(@class)," "), " navigation ")]',
43
        'ul[contains(concat(" ",normalize-space(class)," "), " Navigation ")]',
44
        'ul[contains(@class, "nav")]',
45
        'ol[@class="nav"]',
46
        'ol[@class="navigation"]',
47
        'ol[@id="nav"]',
48
        'ol[contains(concat(" ",normalize-space(@id)," "), " nav ")]',
49
        'ol[contains(@id, "nav")]',
50
        'ol[contains(concat(" ",normalize-space(@class)," "), " nav ")]',
51
        'ol[contains(concat(" ",normalize-space(class)," "), " Nav ")]',
52
        'ol[contains(concat(" ",normalize-space(@id)," "), " navigation ")]',
53
        'ol[contains(concat(" ",normalize-space(@class)," "), " navigation ")]',
54
        'ol[contains(concat(" ",normalize-space(class)," "), " Navigation ")]',
55
        'ol[contains(@class, "nav")]',
56
        'ul',
57
        'ol'
58
    ];
59
60
    public function __construct(
61
        WebDriver $webDriver,
62
        AbstractTestCase $testCase,
63
        ThemeConfigurationInterface $theme,
64
        InstructionNavigator $instructionNavigator
65
    )
66
    {
67
        parent::__construct($webDriver, $testCase, $theme);
68
        $this->instructionNavigator = $instructionNavigator;
69
    }
70
71
    public function setPath($path)
72
    {
73
        $this->path = $path;
74
    }
75
76
    public function getBaseXpath()
77
    {
78
        return $this->baseXpath;
79
    }
80
81
    public function getChildXpath()
82
    {
83
        return $this->childXpath;
84
    }
85
86
    /**
87
     * Attempts to extract an Xpath pattern to use for category navigation.
88
     *
89
     * Because this approach is somewhat manual translation needs to be done prior to executing this method.
90
     *
91
     * @throws MissingNavigationSchemeException
92
     * @throws UnableToExtractMenuXpathException
93
     */
94
95
    public function extract()
96
    {
97
        if (!$this->path) {
98
            throw new MissingNavigationSchemeException('Missing the (translatable) navigation scheme in the format of "part1/part2."');
99
        }
100
101
        $parts = explode('/', $this->path);
102
103
        if (count($parts) < 1) {
104
            throw new MissingNavigationSchemeException('Invalid navigation scheme."');
105
        }
106
107
        $baseChild = $parts[0];
108
        $html = $this->webDriver->byXpath('//body')->getAttribute('innerHTML');
109
        $doc = new \DOMDocument();
110
        libxml_use_internal_errors(true);
111
        $doc->loadHTML('<html><body>' . $html . '</body></html>');
112
        $xpath = new \DOMXPath($doc);
113
114
        $baseMatches = $this->getBaseMatches($xpath, $baseChild);
115
116
        $finalMatches = $this->getFinalMatches($baseMatches, $parts);
117
118
        if ($finalMatches === true) {
119
            return;
120
        }
121
122
        if (!count($finalMatches)) {
123
            throw new UnableToExtractMenuXpathException('Could not extract menu Xpaths for the category path: ' . $this->path);
124
        }
125
126
        $this->baseXpath = $finalMatches[0]['baseNodePath'];
127
        $this->childXpath = $finalMatches[0]['childNodePath'];
128
    }
129
130
    /**
131
     * @param $baseMatches
132
     * @param $parts
133
     * @return bool|array Boolean if no further processing is needed, array if there is
134
     */
135
136
    protected function getFinalMatches($baseMatches, $parts)
137
    {
138
        foreach ($baseMatches as $match) {
139
            $instructions = [
140
                [WebDriver::INSTRUCTION_MOUSE_MOVETO, '//body']
141
            ];
142
            $template = $match['baseNodePath'];
143
            $lastXpath = null;
144
            foreach ($parts as $part) {
145
                $childXpath = sprintf($match['childNodePath'], $part);
146
                $template .= sprintf('/descendant::%s', $childXpath);
147
                $instructions[] = [
148
                    WebDriver::INSTRUCTION_MOUSE_MOVETO, $template
149
                ];
150
                $lastXpath = $template;
151
            }
152
153
            try {
154
                foreach ($instructions as $instruction) {
155
                    if (!$this->webDriver->elementExists($instruction[1], WebDriver::BY_XPATH)) {
156
                        continue 2;
157
                    }
158
                }
159
                $this->instructionNavigator->navigateTo($instructions);
160
161
                // Will throw an exception on error
162
                $this->testCase->assertElementClickable($lastXpath, WebDriver::BY_XPATH);
163
164
                $this->baseXpath = $match['baseNodePath'];
165
                $this->childXpath = $match['childNodePath'];
166
                return true;
167
168
            } catch (\Exception $e) {
169
                // If an exception is thrown it just means we try the next template pattern
170
            }
171
172
        }
173
        return false;
174
    }
175
176
177
    protected function getBaseMatches($xpath, $baseChild)
178
    {
179
        $baseMatches = [];
180
        foreach ($this->baseSearchOrder as $base) {
181
            $baseQuery = '//' . $base;
182
            $nodeList = $xpath->query($baseQuery);
183
            foreach ($nodeList as $index => $node) {
184
                /* @var $node \DOMElement */
185
                $path = $node->getNodePath();
186
                try {
187
                    $element = $this->webDriver->byXpath($path);
188
189
                    if ($element->isDisplayed()) {
190
                        foreach ($this->childSearchOrder as $order) {
191
                            $childQueryXpath = sprintf($order, $baseChild);
192
                            $childQuery = $path . '/descendant::' . $childQueryXpath;
193
                            $childNodeList = $xpath->query($childQuery);
194
                            if ($childNodeList instanceof \DOMNodeList) {
195
                                foreach ($childNodeList as $node) {
196
                                    $baseMatches[] = [
197
                                        'baseNodePath' => $path,
198
                                        'childNodePath' => $order
199
                                    ];
200
                                }
201
                            }
202
                        }
203
                    }
204
                } catch (\Exception $e) {
205
                    continue;
206
                }
207
            }
208
        }
209
        return $baseMatches;
210
    }
211
212
}