GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

DomElementSearch::getElementsByTitle()   B
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 26
Code Lines 11

Duplication

Lines 26
Ratio 100 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 26
loc 26
rs 8.8571
cc 3
eloc 11
nc 4
nop 2
1
<?php
2
3
/**
4
 * Copyright (c) 2011-present Mediasift Ltd
5
 * All rights reserved.
6
 *
7
 * Redistribution and use in source and binary forms, with or without
8
 * modification, are permitted provided that the following conditions
9
 * are met:
10
 *
11
 *   * Redistributions of source code must retain the above copyright
12
 *     notice, this list of conditions and the following disclaimer.
13
 *
14
 *   * Redistributions in binary form must reproduce the above copyright
15
 *     notice, this list of conditions and the following disclaimer in
16
 *     the documentation and/or other materials provided with the
17
 *     distribution.
18
 *
19
 *   * Neither the names of the copyright holders nor the names of his
20
 *     contributors may be used to endorse or promote products derived
21
 *     from this software without specific prior written permission.
22
 *
23
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34
 * POSSIBILITY OF SUCH DAMAGE.
35
 *
36
 * @category  Libraries
37
 * @package   Storyplayer/Modules/Browser
38
 * @author    Stuart Herbert <[email protected]>
39
 * @copyright 2011-present Mediasift Ltd www.datasift.com
40
 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
41
 * @link      http://datasift.github.io/storyplayer
42
 */
43
44
namespace Storyplayer\SPv3\Modules\Browser;
45
46
use Exception;
47
use DataSift\Storyplayer\PlayerLib\StoryTeller;
48
use Storyplayer\SPv3\Modules\Exceptions;
49
50
/**
51
 * Retrieve element(s) from the DOM
52
 *
53
 * This class is effectively the 'map' part of our map/reduce search pattern:
54
 *
55
 * - this class is used to retrieve a list of DOM elements that match our
56
 *   search criteria
57
 * - the caller then reduces the list to the single element that is required
58
 *
59
 * @category  Libraries
60
 * @package   Storyplayer/Modules/Browser
61
 * @author    Stuart Herbert <[email protected]>
62
 * @copyright 2011-present Mediasift Ltd www.datasift.com
63
 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
64
 * @link      http://datasift.github.io/storyplayer
65
 */
66
class DomElementSearch
67
{
68
    use VisibleElementFinder;
69
70
    protected $topElement;
71
    protected $topXpath;
72
73
    public function __construct($topElement)
74
    {
75
        $this->setTopElement($topElement);
76
    }
77
78
    // ==================================================================
79
    //
80
    // Element finders go here
81
    //
82
    // ------------------------------------------------------------------
83
84
    /**
85
     * @param string $tags
86
     */
87
    protected function convertTagsToString($tags)
88
    {
89
        if (is_string($tags)) {
90
            return $tags;
91
        }
92
93
        return implode('|', $tags);
94
    }
95
96
    // ==================================================================
97
    //
98
    // Different ways to find elements in the DOM
99
    //
100
    // ------------------------------------------------------------------
101
102
    /**
103
     * @param  string $text
104
     * @param  string $tags
105
     * @return array<\DataSift\WebDriver\WebDriverElement>
106
     */
107 View Code Duplication
    public function getElementsByAltText($text, $tags = '*')
108
    {
109
        // what are we doing?
110
        $tag = $this->convertTagsToString($tags);
111
        $log = usingLog()->startAction("get '{$tag}' elements with alt text '{$text}'");
112
113
        // prepare the list of tags
114
        if (is_string($tags)) {
115
            $tags = array($tags);
116
        }
117
118
        // build up the xpath to use
119
        $xpathList = array();
120
        foreach ($tags as $tag) {
121
            $xpathList[] = 'descendant::' . $tag . '[@alt = "' . $text . '"]';
122
        }
123
124
        // get the possibly matching elements
125
        $elements = $this->getElementsByXpath($xpathList);
126
127
        // log the result
128
        $log->endAction(count($elements) . " element(s) found");
129
130
        // return the elements
131
        return $elements;
132
    }
133
134
    /**
135
     * @param  string $class
136
     * @param  string $tags
137
     * @return array<\DataSift\WebDriver\WebDriverElement>
138
     */
139 View Code Duplication
    public function getElementsByClass($class, $tags = '*')
140
    {
141
        // what are we doing?
142
        $tag = $this->convertTagsToString($tags);
143
        $log = usingLog()->startAction("get '{$tag}' elements with CSS class '{$class}'");
144
145
        // prepare the list of tags
146
        if (is_string($tags)) {
147
            $tags = array($tags);
148
        }
149
150
        // build up the xpath to use
151
        $xpathList = array();
152
        foreach ($tags as $tag) {
153
            $xpathList[] = 'descendant::' . $tag . '[contains(concat(" ", normalize-space(@class), " "), " ' . $class . ' ")]';
154
        }
155
156
        // find the matches
157
        $elements = $this->getElementsByXpath($xpathList);
158
159
        // log the result
160
        $log->endAction(count($elements) . " element(s) found");
161
162
        // return the elements
163
        return $elements;
164
    }
165
166
    /**
167
     * @param  string $id
168
     * @param  string $tags
169
     * @return array<\DataSift\WebDriver\WebDriverElement>
170
     */
171 View Code Duplication
    public function getElementsById($id, $tags = '*')
172
    {
173
        // what are we doing?
174
        $tag = $this->convertTagsToString($tags);
175
        $log = usingLog()->startAction("get '{$tag}' elements with id '{$id}'");
176
177
        // prepare the list of tags
178
        if (is_string($tags)) {
179
            $tags = array($tags);
180
        }
181
182
        // build up the xpath to use
183
        $xpathList = array();
184
        foreach ($tags as $tag) {
185
            $xpathList[] = 'descendant::' . $tag . '[@id = "' . $id . '"]';
186
        }
187
188
        // find the matches
189
        $elements = $this->getElementsByXpath($xpathList);
190
191
        // log the result
192
        $log->endAction(count($elements) . " element(s) found");
193
194
        // return the elements
195
        return $elements;
196
    }
197
198
    /**
199
     * @param  string $labelText
200
     * @return array<\DataSift\WebDriver\WebDriverElement>
201
     */
202
    public function getElementsByLabel($labelText)
203
    {
204
        // what are we doing?
205
        $log = usingLog()->startAction("get elements for label '{$labelText}'");
206
207
        // our return value
208
        $retval = [];
209
210
        try {
211
            // build up the xpath to use
212
            $xpathList = [
213
                'descendant::*[text()]//parent::label',
214
                'descendant::label[normalize-space(text()) = "' . $labelText . '"]'
215
            ];
216
217
            // search using the xpath
218
            $tmpElements = $this->getElementsByXpath($xpathList);
219
220
            // we're going to search in a case-insensitive manner
221
            $searchText = trim(rtrim(strtolower($labelText)));
222
223
            // filter out any labels that do not have the text we want
224
            $labelElements = [];
225
            foreach ($tmpElements as $tmpElement) {
226
                $elementText = strtolower(trim(rtrim($tmpElement->text())));
227
                $elementText = str_replace(["\n", "\r\n"], " ", $elementText);
228
                if ($elementText == $searchText) {
229
                    $labelElements[] = $tmpElement;
230
                }
231
                else {
232
                    usingLog()->writeToLog("rejecting element containing text '{$elementText}'");
233
                }
234
            }
235
236
237
            // we cannot filter by visibility here - the <label> may be
238
            // visible but the <input> may be invisible :(
239
        }
240
        catch (Exception $e) {
241
            $log->endAction("did not find label '{$labelText}'");
242
            throw $e;
243
        }
244
245
        // search all of the label elements to find an associated input
246
        // element that we can safely use
247
        foreach ($labelElements as $labelElement)
248
        {
249
            try {
250
                // add each element that matches this label
251
                $retval[] = $this->getElementAssociatedWithLabelElement($labelElement, $labelText);
252
            }
253
            catch (Exception $e) {
254
                // do nothing
255
            }
256
        }
257
258
        // log the result
259
        $log->endAction(count($retval) . " element(s) found");
260
261
        // return the elements
262
        return $retval;
263
    }
264
265
    /**
266
     * @param  \DataSift\WebDriver\WebDriverElement $labelElement
267
     * @param  string $labelText
268
     * @return \DataSift\WebDriver\WebDriverElement
269
     */
270
    protected function getElementAssociatedWithLabelElement($labelElement, $labelText)
271
    {
272
        // shorthand
273
        $topElement = $this->getTopElement();
274
275
        // what are we doing?
276
        $log = usingLog()->startAction("find elements associated with label '$labelText'");
277
278
        $inputElementId = null;
279
        try {
280
            $inputElementId = $log->addStep("determine id of corresponding input element", function() use($labelElement) {
281
                return $labelElement->attribute('for');
282
            });
283
        }
284
        catch (Exception $e) {
285
            usingLog()->writeToLog("label '{$labelText}' is missing the 'for' attribute");
286
287
            // this is NOT fatal - the element might be nested
288
        }
289
290
        // what do we do next?
291
        if ($inputElementId !== null)
292
        {
293
            // where does the 'for' attribute go?
294
            try {
295
                $inputElement = $log->addStep("find the input element with the id '{$inputElementId}'", function() use($topElement, $inputElementId) {
296
                    return $topElement->getElement('id', $inputElementId);
297
                });
298
299
                // all done
300
                $log->endAction();
301
                return $inputElement;
302
            }
303
            catch (Exception $e) {
304
305
                $log->endAction("could not find element with id '{$inputElementId}'; does markup use 'name' when it should 'id'?");
306
                // report the failure
307
                throw Exceptions::newActionFailedException(__METHOD__);
308
            }
309
        }
310
311
        // if we get here, then the label doesn't say which element it is 'for'
312
        //
313
        // let's hope (assume?) that the input is inside the element
314
        try {
315
            // build up the xpath to use
316
            $xpathList = [
317
                'descendant::label[normalize-space(text()) = "' . $labelText . '"]/input'
318
            ];
319
320
            // search using the xpath
321
            $elements = $this->getElementsByXpath($xpathList);
322
323
            // find the first one that the user can see
324
            $inputElement = $this->returnNthVisibleElement(0, $elements);
325
326
            // if we get here, we're good
327
            $log->endAction();
328
            return $inputElement;
329
        }
330
        catch (Exception $e) {
331
            $log->endAction("cound not find input element associated with label '{$labelText}'");
332
            throw Exceptions::newActionFailedException(__METHOD__);
333
        }
334
    }
335
336
    /**
337
     * @param  string $searchTerm
338
     * @param  string $tags
339
     * @return array<\DataSift\WebDriver\WebDriverElement>
340
     */
341
    public function getElementsByLabelIdOrName($searchTerm, $tags = '*')
342
    {
343
        // what are we doing?
344
        $tag = $this->convertTagsToString($tags);
345
        $log = usingLog()->startAction("get '{$tag}' with label, id or name '{$searchTerm}'");
346
347
        // our return value
348
        $retval = [];
349
350
        // can we find this puppy by its label?
351
        try {
352
            $retval = array_merge($retval, $this->getElementsByLabel($searchTerm));
353
        }
354
        catch (Exception $e) {
355
            // do nothing
356
        }
357
358
        // are there any with the ID?
359
        try {
360
            $retval = array_merge($this->getElementsById($searchTerm, $tags));
361
        }
362
        catch (Exception $e) {
363
            // do nothing
364
        }
365
366
        // and what about finding it by its text?
367
        $retval = array_merge($retval, $this->getElementsByName($searchTerm, $tags));
368
369
        // log the result
370
        $log->endAction(count($retval) . " element(s) found");
371
372
        // return the elements
373
        return $retval;
374
    }
375
376
    /**
377
     * @param  string $name
378
     * @param  string $tags
379
     * @return array<\DataSift\WebDriver\WebDriverElement>
380
     */
381 View Code Duplication
    public function getElementsByName($name, $tags = '*')
382
    {
383
        // what are we doing?
384
        $tag = $this->convertTagsToString($tags);
385
        $log = usingLog()->startAction("get '{$tag}' elements with name '{$name}'");
386
387
        // prepare the list of tags
388
        if (is_string($tags)) {
389
            $tags = array($tags);
390
        }
391
392
        // build up the xpath to use
393
        $xpathList = array();
394
        foreach ($tags as $tag) {
395
            $xpathList[] = 'descendant::' . $tag . '[@name = "' . $name . '"]';
396
        }
397
398
        // find the matches
399
        $elements = $this->getElementsByXpath($xpathList);
400
401
        // log the result
402
        $log->endAction(count($elements) . " element(s) found");
403
404
        // return the elements
405
        return $elements;
406
    }
407
408
    /**
409
     * @param  string $text
410
     * @param  string $tags
411
     * @return array<\DataSift\WebDriver\WebDriverElement>
412
     */
413 View Code Duplication
    public function getElementsByPlaceholder($text, $tags = '*')
414
    {
415
        // what are we doing?
416
        $tag = $this->convertTagsToString($tags);
417
        $log = usingLog()->startAction("get '{$tag}' element with placeholder '{$text}'");
418
419
        // prepare the list of tags
420
        if (is_string($tags)) {
421
            $tags = array($tags);
422
        }
423
424
        // build up the xpath to use
425
        $xpathList = array();
426
        foreach ($tags as $tag) {
427
            $xpathList[] = 'descendant::' . $tag . '[@placeholder = "' . $text . '"]';
428
        }
429
430
        // get the possibly matching elements
431
        $elements = $this->getElementsByXpath($xpathList);
432
433
        // log the result
434
        $log->endAction(count($elements) . " element(s) found");
435
436
        // return the elements
437
        return $elements;
438
    }
439
440
    /**
441
     * @param  string $text
442
     * @param  string $tags
443
     * @return array<\DataSift\WebDriver\WebDriverElement>
444
     */
445
    public function getElementsByText($text, $tags = '*')
446
    {
447
        // what are we doing?
448
        $tag = $this->convertTagsToString($tags);
449
        $log = usingLog()->startAction("get '{$tag}' element with text '{$text}'");
450
451
        // prepare the list of tags
452
        if (is_string($tags)) {
453
            $tags = array($tags);
454
        }
455
456
        // build up the xpath to use
457
        $xpathList = array();
458
        foreach ($tags as $tag) {
459
            $xpathList[] = 'descendant::' . $tag . '[normalize-space(text()) = "' . $text . '"]';
460
            $xpathList[] = 'descendant::' . $tag . '[normalize-space(string(.)) = "' . $text . '"]';
461
            $xpathList[] = 'descendant::' . $tag . '/*[normalize-space(string(.)) = "' . $text . '"]/parent::' . $tag;
462
463
            // special cases
464
            if ($tag == '*' || $tag == 'input' || $tag == 'button') {
465
                $xpathList[] = 'descendant::input[normalize-space(@value) = "' . $text . '"]';
466
                $xpathList[] = 'descendant::input[normalize-space(@placeholder) = "' . $text . '"]';
467
            }
468
        }
469
470
        // get the possibly matching elements
471
        $elements = $this->getElementsByXpath($xpathList);
472
473
        // log the result
474
        $log->endAction(count($elements) . " element(s) found");
475
476
        // return the elements
477
        return $elements;
478
    }
479
480
    /**
481
     * @param  string $title
482
     * @param  string $tags
483
     * @return array<\DataSift\WebDriver\WebDriverElement>
484
     */
485 View Code Duplication
    public function getElementsByTitle($title, $tags = '*')
486
    {
487
        // what are we doing?
488
        $tag = $this->convertTagsToString($tags);
489
        $log = usingLog()->startAction("get '{$tag}' element with title '{$title}'");
490
491
        // prepare the list of tags
492
        if (is_string($tags)) {
493
            $tags = array($tags);
494
        }
495
496
        // build up the xpath to use
497
        $xpathList = array();
498
        foreach ($tags as $tag) {
499
            $xpathList[] = 'descendant::' . $tag . '[@title = "' . $title . '"]';
500
        }
501
502
        // search using the xpath
503
        $elements = $this->getElementsByXpath($xpathList);
504
505
        // log the result
506
        $log->endAction(count($elements) . " element(s) found");
507
508
        // return the elements
509
        return $elements;
510
    }
511
512
    /**
513
     * @param  array<string> $xpathList
514
     * @return array<\DataSift\WebDriver\WebDriverElement>
515
     */
516
    public function getElementsByXpath($xpathList)
517
    {
518
        // shorthand
519
        $topElement = $this->getTopElement();
520
521
        // what are we doing?
522
        $log = usingLog()->startAction("search the browser's DOM using a list of XPath queries");
523
524
        // our set of elements to return
525
        $return = array();
526
527
        try {
528
            foreach ($xpathList as $xpath) {
529
                $elements = $log->addStep("find elements using xpath '{$xpath}'", function() use($topElement, $xpath) {
530
                    return $topElement->getElements('xpath', $xpath);
531
                });
532
533
                if (count($elements) > 0) {
534
                    // add these elements to the total list
535
                    $return = array_merge($return, $elements);
536
                }
537
            }
538
        }
539
        catch (Exception $e) {
540
            // log the result
541
            $log->endAction("no matching elements");
542
543
            // report the failure
544
            throw Exceptions::newActionFailedException(__METHOD__);
545
        }
546
547
        // if we get here, we found a match
548
        $log->endAction("found " . count($return) . " element(s)");
549
        return $return;
550
    }
551
552
    // ==================================================================
553
    //
554
    // Support for restricting where we look in the DOM
555
    //
556
    // ------------------------------------------------------------------
557
558
    /**
559
     * @return \DataSift\WebDriver\WebDriverElement
560
     */
561
    public function getTopElement()
562
    {
563
        return $this->topElement;
564
    }
565
566
    /**
567
     * @param \DataSift\WebDriver\WebDriverElement $element
568
     */
569
    public function setTopElement($element)
570
    {
571
        $this->topElement = $element;
572
    }
573
574
    /**
575
     * @return string
576
     */
577
    protected function getTopXpath()
578
    {
579
        return $this->topXpath;
580
    }
581
582
    /**
583
     * @param string $xpath
584
     */
585
    protected function setTopXpath($xpath)
586
    {
587
        $this->topXpath = $xpath;
588
    }
589
}
590