Node::next_wait_time()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 7
cts 7
cp 1
rs 9.7666
c 0
b 0
f 0
cc 4
nc 3
nop 1
crap 4
1
<?php
2
3
namespace Openbuildings\Spiderling;
4
5
/**
6
 * Node - represents HTML Dom node
7
 *
8
 * @package    Openbuildings\Spiderling
9
 * @author     Ivan Kerin
10
 * @copyright  (c) 2013 OpenBuildings Ltd.
11
 * @license    http://spdx.org/licenses/BSD-3-Clause
12
 */
13
class Node {
14
15
	const DEFAULT_WAIT_TIME = 2000;
16
17
	/**
18
	 * The driver for traversing this node and its children
19
	 * @var Driver
20
	 */
21
	protected $_driver;
22
23
	/**
24
	 * The parent node, NULL if root
25
	 * @var Node
26
	 */
27
	protected $_parent;
28
29
	/**
30
	 * The id for the current node, NULL if root
31
	 * @var string
32
	 */
33
	protected $_id = NULL;
34
35
	/**
36
	 * The time find operation will wait for the node to appear, in milliseconds
37
	 * @var integer
38
	 */
39
	protected $_next_wait_time;
40
41
	/**
42
	 * All methods not for this node will be proxied through this
43
	 * @var object
44
	 */
45
	protected $_extension;
46
47 38
	function __construct(Driver $driver = NULL, Node $parent = NULL, $id = NULL)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
48
	{
49 38
		$this->_driver = $driver;
50 38
		$this->_parent = $parent;
51 38
		$this->_id = $id;
52
53 38
		if ($parent AND $parent->_extension)
54
		{
55 1
			$this->_extension = $parent->_extension;
56
		}
57 38
	}
58
59
	/**
60
	 * Getter, get the current driver object
61
	 * @return Driver
62
	 */
63 22
	public function driver()
64
	{
65 22
		return $this->_driver;
66
	}
67
68
	/**
69
	 * Getter / Setter for the extension object
70
	 * @param  mixed $extension
71
	 * @return mixed|$this
72
	 */
73 2
	public function extension($extension = NULL)
74
	{
75 2
		if ($extension !== NULL)
76
		{
77 1
			$this->_extension = $extension;
78 1
			return $this;
79
		}
80 2
		return $this->_extension;
81
	}
82
83
	/**
84
	 * Getter - get the parent node
85
	 * @return Node
86
	 */
87 1
	public function parent()
88
	{
89 1
		return $this->_parent;
90
	}
91
92
	/**
93
	 * Setter this method is used to populate a node with a new id
94
	 * @param  string $id
95
	 * @return Node     $this
96
	 */
97 14
	public function load_new_id($id)
98
	{
99 14
		$this->_id = $id;
100 14
		return $this;
101
	}
102
103
	/**
104
	 * Setter / Getter of the next wait time
105
	 * @param  integer $next_wait_time milliseconds
106
	 * @return integer|Node
107
	 */
108 12
	public function next_wait_time($next_wait_time = NULL)
109
	{
110 12
		if ($next_wait_time !== NULL)
111
		{
112 1
			$this->_next_wait_time = $next_wait_time;
113 1
			return $this;
114
		}
115
116 12
		if ($this->_next_wait_time === NULL AND $this->_driver)
117
		{
118 12
			$this->_next_wait_time = $this->_driver->default_wait_time;
119
		}
120
121 12
		return $this->_next_wait_time;
122
	}
123
124
	/**
125
	 * Wait milliseconds
126
	 * @param  integer $milliseconds
127
	 * @return Node                $this
128
	 */
129 1
	public function wait($milliseconds = 1000)
130
	{
131 1
		usleep($milliseconds * 1000);
132
133 1
		return $this;
134
	}
135
136
	/**
137
	 * The DOMDocument or DOMElement representation of the current tag
138
	 * @return DOMDocument|DOMElement
139
	 */
140 9
	public function dom()
141
	{
142 9
		return $this->driver()->dom($this->id());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Openbuildings\Spiderling\Driver as the method dom() does only exist in the following sub-classes of Openbuildings\Spiderling\Driver: Openbuildings\Spiderling\Driver_Kohana, Openbuildings\Spiderling\Driver_Simple, Openbuildings\Spiderling\Driver_SimpleXML. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
143
	}
144
145
	/**
146
	 * GETTERS
147
	 * ===========================================
148
	 */
149
150
	/**
151
	 * is this the main html page?
152
	 * @return boolean
153
	 */
154 1
	public function is_root()
155
	{
156 1
		return ! (bool) $this->_id;
157
	}
158
159
	/**
160
	 * The current internal ID, unique to this page
161
	 * @return mixed
162
	 */
163 15
	public function id()
164
	{
165 15
		return $this->_id;
166
	}
167
168
	/**
169
	 * The html source of the current tag
170
	 * @return string
171
	 */
172 2
	public function html()
173
	{
174 2
		return $this->driver()->html($this->id());
175
	}
176
177
	/**
178
	 * The html source of the current tag
179
	 * @return string
180
	 */
181 1
	public function __toString()
182
	{
183 1
		return (string) $this->html();
184
	}
185
186
	/**
187
	 * The tag name of the current tag (body, div, input)
188
	 * @return string
189
	 */
190 2
	public function tag_name()
191
	{
192 2
		return $this->driver()->tag_name($this->id());
193
	}
194
195
	/**
196
	 * Attribute of the current tag
197
	 * @param  string $name the name of the attribute
198
	 * @return string
199
	 */
200 3
	public function attribute($name)
201
	{
202 3
		return $this->driver()->attribute($this->id(), $name);
203
	}
204
205
	/**
206
	 * The text content of the current tag (similar to javascript's innerText)
207
	 * @return string
208
	 */
209 2
	public function text()
210
	{
211 2
		return $this->driver()->text($this->id());
212
	}
213
214
	/**
215
	 * Is this element visible?
216
	 * @return boolean
217
	 */
218 1
	public function is_visible()
219
	{
220 1
		return $this->driver()->is_visible($this->id());
221
	}
222
223
	/**
224
	 * Is this option element selected?
225
	 * @return boolean
226
	 */
227 1
	public function is_selected()
228
	{
229 1
		return $this->driver()->is_selected($this->id());
230
	}
231
232
	/**
233
	 * Is this checkbox checked?
234
	 * @return boolean
235
	 */
236 1
	public function is_checked()
237
	{
238 1
		return $this->driver()->is_checked($this->id());
239
	}
240
241
	/**
242
	 * Get the value of the current form field
243
	 * @return string
244
	 */
245 3
	public function value()
246
	{
247 3
		return $this->driver()->value($this->id());
248
	}
249
250
251
	/**
252
	 * SETTERS
253
	 * ===========================================
254
	 */
255
256
	/**
257
	 * Set the value for the current form field
258
	 * @param mixed $value
259
	 * @return Node $this
260
	 */
261 3
	public function set($value)
262
	{
263 3
		$this->driver()->set($this->id(), $value);
264 3
		return $this;
265
	}
266
267
	/**
268
	 * Append to the current value - useful for textarea / input fields
269
	 * @param  string $value
270
	 * @return Node $this
271
	 */
272 1
	public function append($value)
273
	{
274 1
		$current_value = $this->driver()->value($this->id());
275
276 1
		$this->driver()->set($this->id(), $current_value.$value);
277
278 1
		return $this;
279
	}
280
281
	/**
282
	 * Click on the current html tag, either a button or a link
283
	 * @return Node $this
284
	 */
285 2
	public function click()
286
	{
287 2
		$this->driver()->click($this->id());
288 2
		return $this;
289
	}
290
291
	/**
292
	 * Select an option for the current select tag
293
	 * @return Node $this
294
	 */
295 3
	public function select_option()
296
	{
297 3
		$this->driver()->select_option($this->id(), TRUE);
298 3
		return $this;
299
	}
300
301
	/**
302
	 * Unselect an option for the current select tag
303
	 * @return Node $this
304
	 */
305 2
	public function unselect_option()
306
	{
307 2
		$this->driver()->select_option($this->id(), FALSE);
308 2
		return $this;
309
	}
310
311
	/**
312
	 * Hover over the current tag with the mouse
313
	 * @param  integer       $x offset inside the tag
314
	 * @param  integer       $y offset inside the tag
315
	 * @return Node $this
316
	 */
317 1
	public function hover($x = NULL, $y = NULL)
318
	{
319 1
		$this->driver()->move_to($this->id(), $x, $y);
320 1
		return $this;
321
	}
322
323
	/**
324
	 * Simulate drop file events on the current element
325
	 * @param  array|string $files local file filename or an array of filenames
326
	 * @return Node        $this
327
	 */
328 1
	public function drop_files($files)
329
	{
330 1
		$this->driver()->drop_files($this->id(), $files);
0 ignored issues
show
Bug introduced by
The method drop_files() does not seem to exist on object<Openbuildings\Spiderling\Driver>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
331 1
		return $this;
332
	}
333
334
335
	/**
336
	 * ACTIONS
337
	 * =======================================
338
	 */
339
340
	/**
341
	 * Click on a specifc tag child of the current tag
342
	 * @param  string|array $selector
343
	 * @param  array         $filters
344
	 * @return Node $this
345
	 */
346 1
	public function click_on($selector, array $filters = array())
347
	{
348 1
		$this->find($selector, $filters)->click();
349 1
		return $this;
350
	}
351
352
	/**
353
	 * Click on a specifc link child of the current tag
354
	 * @param  string|array  $selector
355
	 * @param  array         $filters
356
	 * @return Node $this
357
	 */
358 1
	public function click_link($selector, array $filters = array())
359
	{
360 1
		$this->find_link($selector, $filters)->click();
361 1
		return $this;
362
	}
363
364
	/**
365
	 * Click on a specifc button child of the current tag
366
	 * @param  string|array  $selector
367
	 * @param  array         $filters
368
	 * @return Node $this
369
	 */
370 1
	public function click_button($selector, array $filters = array())
371
	{
372 1
		$this->find_button($selector, $filters)->click();
373 1
		return $this;
374
	}
375
376
	/**
377
	 * Set the value of the specific form field inside the current tag
378
	 * @param  string|array  $selector
379
	 * @param  mixed         $with     the value to be set
380
	 * @param  array         $filters
381
	 * @return Node this
382
	 */
383 1
	public function fill_in($selector, $with, array $filters = array())
384
	{
385 1
		$field = $this->find_field($selector, $filters);
386
387 1
		if ( ! in_array($field->tag_name(), array('input', 'textarea')))
388
			throw new Exception('Element of type ":type" cannot be filled in! Only input and textarea elements can.');
389
390 1
		$field->set($with);
391
392 1
		return $this;
393
	}
394
395
	/**
396
	 * Choose a spesific radio tag inside the current tag
397
	 * @param  string|array   $selector
398
	 * @param  array          $filters
399
	 * @return Node  $this
400
	 */
401 1
	public function choose($selector, array $filters = array())
402
	{
403 1
		$this->find_field($selector, $filters)->set(TRUE);
404 1
		return $this;
405
	}
406
407
	/**
408
	 * Check a spesific checkbox input tag inside the current tag
409
	 * @param  string|array   $selector
410
	 * @param  array          $filters
411
	 * @return Node  $this
412
	 */
413 1
	public function check($selector, array $filters = array())
414
	{
415 1
		$this->find_field($selector, $filters)->set(TRUE);
416 1
		return $this;
417
	}
418
419
	/**
420
	 * Uncheck a spesific checkbox input tag inside the current tag
421
	 * @param  string|array   $selector
422
	 * @param  array          $filters
423
	 * @return Node  $this
424
	 */
425 1
	public function uncheck($selector, array $filters = array())
426
	{
427 1
		$this->find_field($selector, $filters)->set(FALSE);
428 1
		return $this;
429
	}
430
431
	/**
432
	 * Attach a file to a spesific file input tag inside the current tag
433
	 * @param  string|array   $selector
434
	 * @param  string         $file      the filename for the file
435
	 * @param  array          $filters
436
	 * @return Node  $this
437
	 */
438 1
	public function attach_file($selector, $file, array $filters = array())
439
	{
440 1
		$this->find_field($selector, $filters)->set($file);
441 1
		return $this;
442
	}
443
444
	/**
445
	 * Select an option of a spesific select tag inside the current tag
446
	 *
447
	 * To select the option the second parameter can be either a string of the option text
448
	 * or a filter to be applied on the options e.g. array('value' => 10)
449
	 *
450
	 * @param  string|array   $selector
451
	 * @param  array          $filters
452
	 * @param  array|string   $option_filters
453
	 * @return Node  $this
454
	 */
455 1
	public function select($selector, $option_filters, array $filters = array())
456
	{
457 1
		if ( ! is_array($option_filters))
458
		{
459 1
			$option_filters = array('text' => $option_filters);
460
		}
461
462
		$this
463 1
			->find_field($selector, $filters)
464 1
				->find('option', $option_filters)
465 1
					->select_option();
466
467 1
		return $this;
468
	}
469
470
	/**
471
	 * Unselect an option of a spesific select tag inside the current tag
472
	 *
473
	 * To select the option the second parameter can be either a string of the option text
474
	 * or a filter to be applied on the options e.g. array('value' => 10)
475
	 *
476
	 * @param  string|array   $selector
477
	 * @param  array          $filters
478
	 * @param  array|string   $option_filters
479
	 * @return Node  $this
480
	 */
481 1
	public function unselect($selector, $option_filters, array $filters = array())
0 ignored issues
show
Unused Code introduced by
The parameter $filters is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
482
	{
483 1
		if ( ! is_array($option_filters))
484
		{
485 1
			$option_filters = array('value' => $option_filters);
486
		}
487
488
		$this
489 1
			->find_field($selector)
490 1
				->find('option', $option_filters)
491 1
					->unselect_option();
492 1
		return $this;
493
	}
494
495
	/**
496
	 * Confirm a javascript alert/confirm dialog box
497
	 *
498
	 * @param  boolean|string $confirm alert/confirm - use boolean for inputs use string
499
	 * @return Node  $this
500
	 */
501 1
	public function confirm($confirm)
502
	{
503 1
		$this->driver()->confirm($confirm);
0 ignored issues
show
Bug introduced by
It seems like $confirm defined by parameter $confirm on line 501 can also be of type string; however, Openbuildings\Spiderling\Driver::confirm() does only seem to accept boolean, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
504 1
		return $this;
505
	}
506
507
	/**
508
	 * Execute arbitrary javascript on the page and get the result
509
	 *
510
	 * @param  string $script
511
	 * @return mixed
512
	 */
513 1
	public function execute($script, $callback = NULL)
514
	{
515 1
		$result = $this->driver()->execute($this->id(), $script);
516 1
		if ($callback)
517
		{
518 1
			call_user_func($callback, $result, $this);
519 1
			return $this;
520
		}
521
		else
522
		{
523 1
			return $result;
524
		}
525
	}
526
527
	/**
528
	 * Perform a screenshot of the current into the given file
529
	 * @param  string $file
530
	 * @return Node       $this
531
	 */
532 1
	public function screenshot($file)
533
	{
534 1
		$this->driver()->screenshot($file);
535 1
		return $this;
536
	}
537
538
	/**
539
	 * Hover the mouse over a specific tag child of the current tag
540
	 * @param  string|array  $selector
541
	 * @param  array         $filters
542
	 * @return Node $this
543
	 */
544 1
	public function hover_on($selector, array $filters = array())
545
	{
546 1
		$this->find($selector, $filters)->hover();
547 1
		return $this;
548
	}
549
550
	/**
551
	 * Hover the mouse over a specific link child of the current tag
552
	 * @param  string|array  $selector
553
	 * @param  array         $filters
554
	 * @return Node $this
555
	 */
556 1
	public function hover_link($selector, array $filters = array())
557
	{
558 1
		$this->find_link($selector, $filters)->hover();
559 1
		return $this;
560
	}
561
562
	/**
563
	 * Hover the mouse over a specific field child of the current tag
564
	 * @param  string|array  $selector
565
	 * @param  array         $filters
566
	 * @return Node $this
567
	 */
568 1
	public function hover_field($selector, array $filters = array())
569
	{
570 1
		$this->find_field($selector, $filters)->hover();
571 1
		return $this;
572
	}
573
574
	/**
575
	 * Hover the mouse over a specific button child of the current tag
576
	 * @param  string|array  $selector
577
	 * @param  array         $filters
578
	 * @return Node $this
579
	 */
580 1
	public function hover_button($selector, array $filters = array())
581
	{
582 1
		$this->find_button($selector, $filters)->hover();
583 1
		return $this;
584
	}
585
586
	/**
587
	 * FINDERS
588
	 * =====================================================
589
	 */
590
591
	/**
592
	 * Find an html form field child of the current tag
593
	 * @param  string|array   $selector
594
	 * @param  array          $filters
595
	 * @return Node  $this
596
	 */
597 5
	public function find_field($selector, array $filters = array())
598
	{
599 5
		return $this->find(array('field', $selector, $filters));
600
	}
601
602
	/**
603
	 * Find an html form field child of the current tag
604
	 * @param  string|array   $selector
605
	 * @param  array          $filters
606
	 * @return Node  $this
607
	 */
608 4
	public function find_link($selector, array $filters = array())
609
	{
610 4
		return $this->find(array('link', $selector, $filters));
611
	}
612
613
	/**
614
	 * Find an html button tag child of the current tag
615
	 * @param  string|array   $selector
616
	 * @param  array          $filters
617
	 * @return Node  $this
618
	 */
619 4
	public function find_button($selector, array $filters = array())
620
	{
621 4
		return $this->find(array('button', $selector, $filters));
622
	}
623
624
	/**
625
	 * Find an html tag child of the current tag
626
	 * This is the basic find method that is used by all the other finders.
627
	 * To work with ajax requests it waits a bit (defualt 2 seconds) for the content to appear on the page
628
	 * before throwing an Functest_Exception_Notfound exception
629
	 *
630
	 * @param  string|array   $selector
631
	 * @param  array          $filters
632
	 * @throws Functest_Exception_Notfound If element not found
633
	 * @return Node  $this
634
	 */
635 10
	public function find($selector, array $filters = array())
636
	{
637 10
		$locator = self::get_locator($selector, $filters);
638 10
		$self = $this;
639
640
		$node = Attempt::make(function() use ($self, $locator){
641 10
			return $self->all($locator)->first();
642 10
		}, $this->next_wait_time());
0 ignored issues
show
Bug introduced by
It seems like $this->next_wait_time() targeting Openbuildings\Spiderling\Node::next_wait_time() can also be of type this<Openbuildings\Spiderling\Node>; however, Openbuildings\Spiderling\Attempt::make() does only seem to accept integer, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
643
644 10
		$this->_next_wait_time = NULL;
645
646 10
		if ($node == NULL)
647 2
			throw new Exception_Notfound($locator, $this->driver());
648
649 10
		return $node;
650
	}
651
652
	/**
653
	 * Oposite to the find method()
654
	 *
655
	 * @param  string|array  $selector
656
	 * @param  array         $filters
657
	 * @throws Functest_Exception_Found If element is found on the page
658
	 * @return Node $this
659
	 */
660 1
	public function not_present($selector, array $filters = array())
661
	{
662 1
		$locator = self::get_locator($selector, $filters);
663 1
		$self = $this;
664
665
		$not_found = Attempt::make(function() use ($self, $locator){
666 1
			return ! $self->all($locator)->first();
667 1
		}, $this->next_wait_time());
0 ignored issues
show
Bug introduced by
It seems like $this->next_wait_time() targeting Openbuildings\Spiderling\Node::next_wait_time() can also be of type this<Openbuildings\Spiderling\Node>; however, Openbuildings\Spiderling\Attempt::make() does only seem to accept integer, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
668
669 1
		$this->_next_wait_time = NULL;
670
671 1
		if ( ! $not_found)
672 1
			throw new Exception_Found($locator, $this->driver());
673
674 1
		return TRUE;
675
	}
676
677
	/**
678
	 * Returns the parent element
679
	 *
680
	 * @return Node parent
681
	 */
682 1
	public function end()
683
	{
684 1
		return $this->_parent;
685
	}
686
687
	/**
688
	 * Find a list of elements represented by the selector / filter
689
	 *
690
	 * @param  string|array $selector
691
	 * @param  array        $filters
692
	 * @return Nodelist
693
	 */
694 18
	public function all($selector, array $filters = array())
695
	{
696 18
		$locator = self::get_locator($selector, $filters);
697
698 18
		return new Nodelist($this->driver(), $locator, $this);
699
	}
700
701
	/**
702
	 * Shortcuts for creating locators (from arrays or nested arrays)
703
	 * @param  string|Locator|array $selector
704
	 * @param  array  $filters
705
	 * @return Locator
706
	 */
707 27
	public static function get_locator($selector, array $filters = array())
708
	{
709 27
		if ($selector instanceof Locator)
710 11
			return $selector;
711
712 27
		$type = NULL;
713
714 27
		if (is_array($selector))
715
		{
716
			// Manage nested selectors
717 13
			if (is_array($selector[1]))
718
			{
719 1
				$selector = $selector[1];
720
			}
721
722 13
			$type = $selector[0];
723 13
			$filters = isset($selector[2]) ? $selector[2] : array();
724 13
			$selector = $selector[1];
725
		}
726
727 27
		return new Locator($type, $selector, $filters);
728
	}
729
730
	/**
731
	 * Pass all other methods to the extension if it is set. That way you can add additional methods
732
	 * @param  string $method
733
	 * @param  array $arguments
734
	 * @return Node $this
735
	 */
736 1
	public function __call($method, $arguments)
737
	{
738 1
		if ( ! $this->extension() OR ! method_exists($this->extension(), $method))
739
			throw new Exception_Methodmissing('Method :method does not exist on this node or node extension', array(':method' => $method));
740
741 1
		array_unshift($arguments, $this);
742
743 1
		call_user_func_array(array($this->extension(), $method), $arguments);
744
745 1
		return $this;
746
	}
747
}
748