Menu::getBaseMatches()   C
last analyzed

Complexity

Conditions 8
Paths 13

Size

Total Lines 34
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 34
rs 5.3846
c 0
b 0
f 0
nc 13
cc 8
eloc 22
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
        $this->baseXpath = $this->childXpath = null;
98
        if (!$this->path) {
99
            throw new MissingNavigationSchemeException('Missing the (translatable) navigation scheme in the format of "part1/part2."');
100
        }
101
102
        $parts = explode('/', $this->path);
103
104
        if (count($parts) < 1) {
105
            throw new MissingNavigationSchemeException('Invalid navigation scheme."');
106
        }
107
108
        $baseChild = $parts[0];
109
        $html = $this->webDriver->byXpath('//body')->getAttribute('innerHTML');
110
        $doc = new \DOMDocument();
111
        libxml_use_internal_errors(true);
112
        $doc->loadHTML('<html><body>' . $html . '</body></html>');
113
        $xpath = new \DOMXPath($doc);
114
115
        $baseMatches = $this->getBaseMatches($xpath, $baseChild);
116
117
        $finalMatches = $this->getFinalMatches($baseMatches, $parts);
118
119
        if ($finalMatches === true) {
120
            return;
121
        }
122
123
        if (!count($finalMatches)) {
124
            throw new UnableToExtractMenuXpathException('Could not extract menu Xpaths for the category path: ' . $this->path);
125
        }
126
127
        $this->baseXpath = $finalMatches[0]['baseNodePath'];
128
        $this->childXpath = $finalMatches[0]['childNodePath'];
129
    }
130
131
    /**
132
     * @param $baseMatches
133
     * @param $parts
134
     * @return bool|array Boolean if no further processing is needed, array if there is
135
     */
136
137
    protected function getFinalMatches($baseMatches, $parts)
138
    {
139
        foreach ($baseMatches as $match) {
140
            $instructions = [
141
                [WebDriver::INSTRUCTION_MOUSE_MOVETO, '//body']
142
            ];
143
            $template = $match['baseNodePath'];
144
            $lastXpath = null;
145
            foreach ($parts as $part) {
146
                $childXpath = sprintf($match['childNodePath'], $part);
147
                $template .= sprintf('/descendant::%s', $childXpath);
148
                $instructions[] = [
149
                    WebDriver::INSTRUCTION_MOUSE_MOVETO, $template
150
                ];
151
                $lastXpath = $template;
152
            }
153
154
            try {
155
                foreach ($instructions as $instruction) {
156
                    if (!$this->webDriver->elementExists($instruction[1], WebDriver::BY_XPATH)) {
157
                        continue 2;
158
                    }
159
                }
160
                $this->instructionNavigator->navigateTo($instructions);
161
162
                // Will throw an exception on error
163
                $this->testCase->assertElementClickable($lastXpath, WebDriver::BY_XPATH);
164
165
                $this->baseXpath = $match['baseNodePath'];
166
                $this->childXpath = $match['childNodePath'];
167
                return true;
168
169
            } catch (\Exception $e) {
170
                // If an exception is thrown it just means we try the next template pattern
171
            }
172
173
        }
174
        return false;
175
    }
176
177
178
    protected function getBaseMatches($xpath, $baseChild)
179
    {
180
        $baseMatches = [];
181
        foreach ($this->baseSearchOrder as $base) {
182
            $baseQuery = '//' . $base;
183
            $nodeList = $xpath->query($baseQuery);
184
            foreach ($nodeList as $index => $node) {
185
                /* @var $node \DOMElement */
186
                $path = $node->getNodePath();
187
                try {
188
                    $element = $this->webDriver->byXpath($path);
189
190
                    if ($element->isDisplayed()) {
191
                        foreach ($this->childSearchOrder as $order) {
192
                            $childQueryXpath = sprintf($order, $baseChild);
193
                            $childQuery = $path . '/descendant::' . $childQueryXpath;
194
                            $childNodeList = $xpath->query($childQuery);
195
                            if ($childNodeList instanceof \DOMNodeList) {
196
                                foreach ($childNodeList as $node) {
197
                                    $baseMatches[] = [
198
                                        'baseNodePath' => $path,
199
                                        'childNodePath' => $order
200
                                    ];
201
                                }
202
                            }
203
                        }
204
                    }
205
                } catch (\Exception $e) {
206
                    continue;
207
                }
208
            }
209
        }
210
        return $baseMatches;
211
    }
212
213
}