/src/Codeception/Util/Locator.php
PHP | 400 lines | 140 code | 17 blank | 243 comment | 15 complexity | 51a7fe7e6e6374b2460d95c2e85f8099 MD5 | raw file
- <?php
- namespace Codeception\Util;
- use Facebook\WebDriver\WebDriverBy;
- use Symfony\Component\CssSelector\CssSelectorConverter;
- use Symfony\Component\CssSelector\Exception\ParseException;
- use Symfony\Component\CssSelector\XPath\Translator;
- /**
- * Set of useful functions for using CSS and XPath locators.
- * Please check them before writing complex functional or acceptance tests.
- *
- */
- class Locator
- {
- /**
- * Applies OR operator to any number of CSS or XPath selectors.
- * You can mix up CSS and XPath selectors here.
- *
- * ```php
- * <?php
- * use \Codeception\Util\Locator;
- *
- * $I->see('Title', Locator::combine('h1','h2','h3'));
- * ?>
- * ```
- *
- * This will search for `Title` text in either `h1`, `h2`, or `h3` tag.
- * You can also combine CSS selector with XPath locator:
- *
- * ```php
- * <?php
- * use \Codeception\Util\Locator;
- *
- * $I->fillField(Locator::combine('form input[type=text]','//form/textarea[2]'), 'qwerty');
- * ?>
- * ```
- *
- * As a result the Locator will produce a mixed XPath value that will be used in fillField action.
- *
- * @static
- *
- * @param $selector1
- * @param $selector2
- *
- * @throws \Exception
- *
- * @return string
- */
- public static function combine($selector1, $selector2)
- {
- $selectors = func_get_args();
- foreach ($selectors as $k => $v) {
- $selectors[$k] = self::toXPath($v);
- if (!$selectors[$k]) {
- throw new \Exception("$v is invalid CSS or XPath");
- }
- }
- return implode(' | ', $selectors);
- }
- /**
- * Matches the *a* element with given URL
- *
- * ```php
- * <?php
- * use \Codeception\Util\Locator;
- *
- * $I->see('Log In', Locator::href('/login.php'));
- * ?>
- * ```
- *
- * @static
- *
- * @param $url
- *
- * @return string
- */
- public static function href($url)
- {
- return sprintf('//a[@href=normalize-space(%s)]', Translator::getXpathLiteral($url));
- }
- /**
- * Matches the element with given tab index
- *
- * Do you often use the `TAB` key to navigate through the web page? How do your site respond to this navigation?
- * You could try to match elements by their tab position using `tabIndex` method of `Locator` class.
- * ```php
- * <?php
- * use \Codeception\Util\Locator;
- *
- * $I->fillField(Locator::tabIndex(1), 'davert');
- * $I->fillField(Locator::tabIndex(2) , 'qwerty');
- * $I->click('Login');
- * ?>
- * ```
- *
- * @static
- *
- * @param $index
- *
- * @return string
- */
- public static function tabIndex($index)
- {
- return sprintf('//*[@tabindex = normalize-space(%d)]', $index);
- }
- /**
- * Matches option by text:
- *
- * ```php
- * <?php
- * use Codeception\Util\Locator;
- *
- * $I->seeElement(Locator::option('Male'), '#select-gender');
- * ```
- *
- * @param $value
- *
- * @return string
- */
- public static function option($value)
- {
- return sprintf('//option[.=normalize-space("%s")]', $value);
- }
- protected static function toXPath($selector)
- {
- try {
- $xpath = (new CssSelectorConverter())->toXPath($selector);
- return $xpath;
- } catch (ParseException $e) {
- if (self::isXPath($selector)) {
- return $selector;
- }
- }
- return null;
- }
- /**
- * Finds element by it's attribute(s)
- *
- * ```php
- * <?php
- * use \Codeception\Util\Locator;
- *
- * $I->seeElement(Locator::find('img', ['title' => 'diagram']));
- * ```
- *
- * @static
- *
- * @param $element
- * @param $attributes
- *
- * @return string
- */
- public static function find($element, array $attributes)
- {
- $operands = [];
- foreach ($attributes as $attribute => $value) {
- if (is_int($attribute)) {
- $operands[] = '@' . $value;
- } else {
- $operands[] = '@' . $attribute . ' = ' . Translator::getXpathLiteral($value);
- }
- }
- return sprintf('//%s[%s]', $element, implode(' and ', $operands));
- }
- /**
- * Checks that provided string is CSS selector
- *
- * ```php
- * <?php
- * Locator::isCSS('#user .hello') => true
- * Locator::isCSS('body') => true
- * Locator::isCSS('//body/p/user') => false
- * ```
- *
- * @param $selector
- *
- * @return bool
- */
- public static function isCSS($selector)
- {
- try {
- (new CssSelectorConverter())->toXPath($selector);
- } catch (ParseException $e) {
- return false;
- }
- return true;
- }
- /**
- * Checks that locator is an XPath
- *
- * ```php
- * <?php
- * Locator::isXPath('#user .hello') => false
- * Locator::isXPath('body') => false
- * Locator::isXPath('//body/p/user') => true
- * ```
- *
- * @param $locator
- *
- * @return bool
- */
- public static function isXPath($locator)
- {
- $document = new \DOMDocument('1.0', 'UTF-8');
- $xpath = new \DOMXPath($document);
- return @$xpath->evaluate($locator, $document) !== false;
- }
- /**
- * @param $locator
- * @return bool
- */
- public static function isPrecise($locator)
- {
- if (is_array($locator)) {
- return true;
- }
- if ($locator instanceof WebDriverBy) {
- return true;
- }
- if (Locator::isID($locator)) {
- return true;
- }
- if (strpos($locator, '//') === 0) {
- return true; // simple xpath check
- }
- return false;
- }
- /**
- * Checks that a string is valid CSS ID
- *
- * ```php
- * <?php
- * Locator::isID('#user') => true
- * Locator::isID('body') => false
- * Locator::isID('//body/p/user') => false
- * ```
- *
- * @param $id
- *
- * @return bool
- */
- public static function isID($id)
- {
- return (bool)preg_match('~^#[\w\.\-\[\]\=\^\~\:]+$~', $id);
- }
- /**
- * Checks that a string is valid CSS class
- *
- * ```php
- * <?php
- * Locator::isClass('.hello') => true
- * Locator::isClass('body') => false
- * Locator::isClass('//body/p/user') => false
- * ```
- *
- * @param $class
- * @return bool
- */
- public static function isClass($class)
- {
- return (bool)preg_match('~^\.[\w\.\-\[\]\=\^\~\:]+$~', $class);
- }
- /**
- * Locates an element containing a text inside.
- * Either CSS or XPath locator can be passed, however they will be converted to XPath.
- *
- * ```php
- * <?php
- * use Codeception\Util\Locator;
- *
- * Locator::contains('label', 'Name'); // label containing name
- * Locator::contains('div[@contenteditable=true]', 'hello world');
- * ```
- *
- * @param $element
- * @param $text
- *
- * @return string
- */
- public static function contains($element, $text)
- {
- $text = Translator::getXpathLiteral($text);
- return sprintf('%s[%s]', self::toXPath($element), "contains(., $text)");
- }
- /**
- * Locates element at position.
- * Either CSS or XPath locator can be passed as locator,
- * position is an integer. If a negative value is provided, counting starts from the last element.
- * First element has index 1
- *
- * ```php
- * <?php
- * use Codeception\Util\Locator;
- *
- * Locator::elementAt('//table/tr', 2); // second row
- * Locator::elementAt('//table/tr', -1); // last row
- * Locator::elementAt('table#grind>tr', -2); // previous than last row
- * ```
- *
- * @param string $element CSS or XPath locator
- * @param int $position xpath index
- *
- * @return mixed
- */
- public static function elementAt($element, $position)
- {
- if (is_int($position) && $position < 0) {
- $position++; // -1 points to the last element
- $position = 'last()-'.abs($position);
- }
- if ($position === 0) {
- throw new \InvalidArgumentException(
- '0 is not valid element position. XPath expects first element to have index 1'
- );
- }
- return sprintf('(%s)[position()=%s]', self::toXPath($element), $position);
- }
- /**
- * Locates first element of group elements.
- * Either CSS or XPath locator can be passed as locator,
- * Equal to `Locator::elementAt($locator, 1)`
- *
- * ```php
- * <?php
- * use Codeception\Util\Locator;
- *
- * Locator::firstElement('//table/tr');
- * ```
- *
- * @param $element
- *
- * @return mixed
- */
- public static function firstElement($element)
- {
- return self::elementAt($element, 1);
- }
- /**
- * Locates last element of group elements.
- * Either CSS or XPath locator can be passed as locator,
- * Equal to `Locator::elementAt($locator, -1)`
- *
- * ```php
- * <?php
- * use Codeception\Util\Locator;
- *
- * Locator::lastElement('//table/tr');
- * ```
- *
- * @param $element
- *
- * @return mixed
- */
- public static function lastElement($element)
- {
- return self::elementAt($element, 'last()');
- }
- /**
- * Transforms strict locator, \Facebook\WebDriver\WebDriverBy into a string represenation
- *
- * @param $selector
- *
- * @return string
- */
- public static function humanReadableString($selector)
- {
- if (is_string($selector)) {
- return "'$selector'";
- }
- if (is_array($selector)) {
- $type = strtolower(key($selector));
- $locator = $selector[$type];
- return "$type '$locator'";
- }
- if (class_exists('\Facebook\WebDriver\WebDriverBy')) {
- if ($selector instanceof WebDriverBy) {
- $type = $selector->getMechanism();
- $locator = $selector->getValue();
- return "$type '$locator'";
- }
- }
- throw new \InvalidArgumentException("Unrecognized selector");
- }
- }