ConditionsElement.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Mtf\Client\Element;
  7. use Magento\Mtf\Client\ElementInterface;
  8. use Magento\Mtf\Client\Locator;
  9. use Magento\Mtf\ObjectManager;
  10. /**
  11. * Typified element class for conditions.
  12. *
  13. * Format value.
  14. * Add slash to symbols: "{", "}", "[", "]", ":".
  15. * 1. Single condition:
  16. * [Type|Param|Param|...|Param]
  17. * 2. List conditions:
  18. * [Type|Param|Param|...|Param]
  19. * [Type|Param|Param|...|Param]
  20. * [Type|Param|Param|...|Param]
  21. * 3. Combination condition with single condition
  22. * {Type|Param|Param|...|Param:[Type|Param|Param|...|Param]}
  23. * 4. Combination condition with list conditions
  24. * {Type|Param|Param|...|Param:[[Type|Param|...|Param][Type|Param|...|Param]...[Type|Param|...|Param]]}
  25. * 5. Top level condition
  26. * {TopLevelCondition:[ANY|FALSE]}{Type|Param|Param|...|Param:[[Type|Param|...|Param]...[Type|Param|...|Param]]}
  27. *
  28. * Example value:
  29. * {Products subselection|total amount|greater than|135|ANY:[[Price in cart|is|100][Quantity in cart|is|100]]}
  30. * {Conditions combination:[
  31. * [Subtotal|is|100]
  32. * {Product attribute combination|NOT FOUND|ANY:[[Attribute Set|is|Default][Attribute Set|is|Default]]}
  33. * ]}
  34. *
  35. * @SuppressWarnings(PHPMD.TooManyFields)
  36. */
  37. class ConditionsElement extends SimpleElement
  38. {
  39. /**
  40. * Count for trying fill condition element.
  41. */
  42. const TRY_COUNT = 3;
  43. /**
  44. * Main condition.
  45. *
  46. * @var string
  47. */
  48. protected $mainCondition = './/ul[contains(@id,"__1__children")]/..';
  49. /**
  50. * Identification for chooser grid.
  51. *
  52. * @var string
  53. */
  54. protected $chooserLocator = '.rule-chooser-trigger';
  55. /**
  56. * Button add condition.
  57. *
  58. * @var string
  59. */
  60. protected $addNew = './/*[contains(@class,"rule-param-new-child")]/a';
  61. /**
  62. * Button remove condition.
  63. *
  64. * @var string
  65. */
  66. protected $remove = './/*/a[@class="rule-param-remove"]';
  67. /**
  68. * New condition.
  69. *
  70. * @var string
  71. */
  72. protected $newCondition = './ul/li/span[contains(@class,"rule-param-new-child")]/..';
  73. /**
  74. * Type of new condition.
  75. *
  76. * @var string
  77. */
  78. protected $typeNew = './/*[@class="element"]/select';
  79. /**
  80. * Created condition.
  81. *
  82. * @var string
  83. */
  84. protected $created = './ul/li[span[contains(@class,"rule-param-new-child")]]/preceding-sibling::li[1]';
  85. /**
  86. * Children condition.
  87. *
  88. * @var string
  89. */
  90. protected $children = './/ul[contains(@id,"conditions__")]';
  91. /**
  92. * Parameter of condition.
  93. *
  94. * @var string
  95. */
  96. protected $param = './span[span[*[substring(@id,(string-length(@id)-%d+1))="%s"]]]';
  97. /**
  98. * Rule param wait locator.
  99. *
  100. * @var string
  101. */
  102. protected $ruleParamWait = './/*[@class="rule-param-wait"]';
  103. /**
  104. * Rule param input selector.
  105. *
  106. * @var string
  107. */
  108. protected $ruleParamInput = '[name^="rule"]';
  109. /**
  110. * Apply rule param link.
  111. *
  112. * @var string
  113. */
  114. protected $applyRuleParam = './/*[@class="rule-param-apply"]';
  115. /**
  116. * Chooser grid locator.
  117. *
  118. * @var string
  119. */
  120. protected $chooserGridLocator = 'div[id*=chooser]';
  121. /**
  122. * Datepicker xpath.
  123. *
  124. * @var string
  125. */
  126. private $datepicker = './/*[contains(@class,"ui-datepicker-trigger")]';
  127. /**
  128. * Key of last find param.
  129. *
  130. * @var int
  131. */
  132. protected $findKeyParam = 0;
  133. /**
  134. * Map of parameters.
  135. *
  136. * @var array
  137. */
  138. protected $mapParams = [
  139. 'attribute',
  140. 'operator',
  141. 'value_type',
  142. 'value',
  143. 'aggregator',
  144. ];
  145. /**
  146. * Map encode special chars.
  147. *
  148. * @var array
  149. */
  150. protected $encodeChars = [
  151. '\{' => '&lbrace;',
  152. '\}' => '&rbrace;',
  153. '\[' => '&lbracket;',
  154. '\]' => '&rbracket;',
  155. '\:' => '&colon;',
  156. ];
  157. /**
  158. * Map decode special chars.
  159. *
  160. * @var array
  161. */
  162. protected $decodeChars = [
  163. '&lbrace;' => '{',
  164. '&rbrace;' => '}',
  165. '&lbracket;' => '[',
  166. '&rbracket;' => ']',
  167. '&colon;' => ':',
  168. ];
  169. /**
  170. * Latest occurred exception.
  171. *
  172. * @var \Exception
  173. */
  174. protected $exception;
  175. /**
  176. * @inheritdoc
  177. */
  178. public function setValue($value)
  179. {
  180. $this->eventManager->dispatchEvent(['set_value'], [__METHOD__, $this->getAbsoluteSelector()]);
  181. $this->clear();
  182. $conditions = $this->decodeValue($value);
  183. $context = $this->find($this->mainCondition, Locator::SELECTOR_XPATH);
  184. if (!empty($conditions[0]['TopLevelCondition'])) {
  185. array_unshift($this->mapParams, 'aggregator');
  186. $condition = $this->parseTopLevelCondition($conditions[0]['TopLevelCondition']);
  187. $this->fillCondition($condition['rules'], $context);
  188. unset($conditions[0]);
  189. array_shift($this->mapParams);
  190. }
  191. $this->addMultipleCondition($conditions, $context);
  192. }
  193. /**
  194. * Add conditions combination.
  195. *
  196. * @param string $condition
  197. * @param ElementInterface $context
  198. * @return ElementInterface
  199. */
  200. protected function addConditionsCombination($condition, ElementInterface $context)
  201. {
  202. $condition = $this->parseCondition($condition);
  203. $this->addCondition($condition['type'], $context);
  204. $createdCondition = $context->find($this->created, Locator::SELECTOR_XPATH);
  205. $this->waitForCondition($createdCondition);
  206. if (!empty($condition['rules'])) {
  207. $this->fillCondition($condition['rules'], $createdCondition);
  208. }
  209. return $createdCondition;
  210. }
  211. /**
  212. * Add conditions.
  213. *
  214. * @param array $conditions
  215. * @param ElementInterface $context
  216. * @return void
  217. */
  218. protected function addMultipleCondition(array $conditions, ElementInterface $context)
  219. {
  220. foreach ($conditions as $key => $condition) {
  221. $elementContext = is_numeric($key) ? $context : $this->addConditionsCombination($key, $context);
  222. if (is_string($condition)) {
  223. $this->addSingleCondition($condition, $elementContext);
  224. } else {
  225. $this->addMultipleCondition($condition, $elementContext);
  226. }
  227. }
  228. }
  229. /**
  230. * Add single Condition.
  231. *
  232. * @param string $condition
  233. * @param ElementInterface $context
  234. * @return void
  235. */
  236. protected function addSingleCondition($condition, ElementInterface $context)
  237. {
  238. $condition = $this->parseCondition($condition);
  239. $this->addCondition($condition['type'], $context);
  240. $createdCondition = $context->find($this->created, Locator::SELECTOR_XPATH);
  241. $this->waitForCondition($createdCondition);
  242. $this->fillCondition($condition['rules'], $createdCondition);
  243. }
  244. /**
  245. * Click to add condition button and set type.
  246. *
  247. * @param string $type
  248. * @param ElementInterface $context
  249. * @return void
  250. * @throws \Exception
  251. */
  252. protected function addCondition($type, ElementInterface $context)
  253. {
  254. $newCondition = $context->find($this->newCondition, Locator::SELECTOR_XPATH);
  255. $count = 0;
  256. do {
  257. $newCondition->find($this->addNew, Locator::SELECTOR_XPATH)->click();
  258. try {
  259. $newCondition->find($this->typeNew, Locator::SELECTOR_XPATH, 'select')->setValue($type);
  260. $isSetType = true;
  261. } catch (\PHPUnit_Extensions_Selenium2TestCase_WebDriverException $e) {
  262. $isSetType = false;
  263. $this->exception = $e;
  264. $this->eventManager->dispatchEvent(['exception'], [__METHOD__, $this->getAbsoluteSelector()]);
  265. }
  266. $count++;
  267. } while (!$isSetType && $count < self::TRY_COUNT);
  268. if (!$isSetType) {
  269. $exception = $this->exception ? $this->exception : (new \Exception("Can not add condition: {$type}"));
  270. throw $exception;
  271. }
  272. }
  273. /**
  274. * Fill single condition.
  275. *
  276. * @param array $rules
  277. * @param ElementInterface $element
  278. * @return void
  279. * @throws \Exception
  280. *
  281. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  282. * @SuppressWarnings(PHPMD.NPathComplexity)
  283. */
  284. protected function fillCondition(array $rules, ElementInterface $element)
  285. {
  286. $this->resetKeyParam();
  287. foreach ($rules as $rule) {
  288. /** @var ElementInterface $param */
  289. $param = $this->findNextParam($element);
  290. $isSet = false;
  291. $count = 0;
  292. do {
  293. try {
  294. $openParamLink = $param->find('a');
  295. if ($openParamLink->isVisible()) {
  296. $openParamLink->click();
  297. }
  298. $this->waitUntil(function () use ($param) {
  299. return $param->find($this->ruleParamInput)->isVisible() ? true : null;
  300. });
  301. if ($this->fillGrid($rule, $param)) {
  302. $isSet = true;
  303. } elseif ($this->fillSelect($rule, $param)) {
  304. $isSet = true;
  305. } elseif ($this->fillText($rule, $param)) {
  306. $isSet = true;
  307. }
  308. } catch (\PHPUnit_Extensions_Selenium2TestCase_WebDriverException $e) {
  309. $isSet = false;
  310. $this->exception = $e;
  311. $this->eventManager->dispatchEvent(['exception'], [__METHOD__, $this->getAbsoluteSelector()]);
  312. }
  313. $count++;
  314. } while (!$isSet && $count < self::TRY_COUNT);
  315. if (!$isSet) {
  316. $exception = $this->exception ? $this->exception : (new \Exception('Can not set value: ' . $rule));
  317. throw $exception;
  318. }
  319. }
  320. }
  321. /**
  322. * Fill grid element.
  323. *
  324. * @param string $rule
  325. * @param ElementInterface $param
  326. * @return bool
  327. */
  328. protected function fillGrid($rule, ElementInterface $param)
  329. {
  330. if (preg_match('`%(.*?)%`', $rule, $chooserGrid)) {
  331. $chooserConfig = explode('#', $chooserGrid[1]);
  332. $rule = preg_replace('`%(.*?)%`', '', $rule);
  333. $param->find($this->chooserLocator)->click();
  334. $grid = ObjectManager::getInstance()->create(
  335. str_replace('/', '\\', $chooserConfig[0]),
  336. [
  337. 'element' => $this->find($this->chooserGridLocator)
  338. ]
  339. );
  340. $grid->searchAndSelect([$chooserConfig[1] => $rule]);
  341. $apply = $param->find($this->applyRuleParam, Locator::SELECTOR_XPATH);
  342. if ($apply->isVisible()) {
  343. $apply->click();
  344. }
  345. return true;
  346. }
  347. return false;
  348. }
  349. /**
  350. * Fill select element.
  351. *
  352. * @param string $rule
  353. * @param ElementInterface $param
  354. * @return bool
  355. */
  356. protected function fillSelect($rule, ElementInterface $param)
  357. {
  358. $value = $param->find('select', Locator::SELECTOR_TAG_NAME, 'select');
  359. if ($value->isVisible()) {
  360. $value->setValue($rule);
  361. $this->click();
  362. return true;
  363. }
  364. return false;
  365. }
  366. /**
  367. * Fill text element.
  368. *
  369. * @param string $rule
  370. * @param ElementInterface $param
  371. * @return bool
  372. */
  373. protected function fillText($rule, ElementInterface $param)
  374. {
  375. $value = $param->find('input', Locator::SELECTOR_TAG_NAME);
  376. if ($value->isVisible()) {
  377. if (!$value->getAttribute('readonly')) {
  378. $value->setValue($rule);
  379. } else {
  380. $datepicker = $param->find(
  381. $this->datepicker,
  382. Locator::SELECTOR_XPATH,
  383. DatepickerElement::class
  384. );
  385. $datepicker->setValue($rule);
  386. }
  387. $apply = $param->find('.//*[@class="rule-param-apply"]', Locator::SELECTOR_XPATH);
  388. if ($apply->isVisible()) {
  389. $apply->click();
  390. }
  391. return true;
  392. }
  393. return false;
  394. }
  395. /**
  396. * Decode value.
  397. *
  398. * @param string $value
  399. * @return array
  400. * @throws \Exception
  401. */
  402. protected function decodeValue($value)
  403. {
  404. $value = str_replace(array_keys($this->encodeChars), $this->encodeChars, $value);
  405. $value = preg_replace('/(\]|})({|\[)/', '$1,$2', $value);
  406. $value = preg_replace('/{([^:]+):/', '{"$1":', $value);
  407. $value = preg_replace('/\[([^\[{])/', '"$1', $value);
  408. $value = preg_replace('/([^\]}])\]/', '$1"', $value);
  409. $value = str_replace(array_keys($this->decodeChars), $this->decodeChars, $value);
  410. $value = "[{$value}]";
  411. $value = json_decode($value, true);
  412. if (null === $value) {
  413. throw new \Exception('Bad format value.');
  414. }
  415. return $value;
  416. }
  417. /**
  418. * Parse condition.
  419. *
  420. * @param string $condition
  421. * @return array
  422. * @throws \Exception
  423. */
  424. protected function parseCondition($condition)
  425. {
  426. if (!preg_match_all('/([^|]+\|?)/', $condition, $match)) {
  427. throw new \Exception('Bad format condition');
  428. }
  429. foreach ($match[1] as $key => $value) {
  430. $match[1][$key] = rtrim($value, '|');
  431. }
  432. return [
  433. 'type' => array_shift($match[1]),
  434. 'rules' => array_values($match[1]),
  435. ];
  436. }
  437. /**
  438. * Parse top level condition.
  439. *
  440. * @param string $condition
  441. * @return array
  442. * @throws \Exception
  443. */
  444. protected function parseTopLevelCondition($condition)
  445. {
  446. if (preg_match_all('/([^|]+)\|?/', $condition, $match) === false) {
  447. throw new \Exception('Bad format condition');
  448. }
  449. return [
  450. 'rules' => $match[1],
  451. ];
  452. }
  453. /**
  454. * Find next param of condition for fill.
  455. *
  456. * @param ElementInterface $context
  457. * @return ElementInterface
  458. * @throws \Exception
  459. */
  460. protected function findNextParam(ElementInterface $context)
  461. {
  462. do {
  463. if (!isset($this->mapParams[$this->findKeyParam])) {
  464. throw new \Exception("Empty map of params");
  465. }
  466. $param = $this->mapParams[$this->findKeyParam];
  467. $element = $context->find(sprintf($this->param, strlen($param), $param), Locator::SELECTOR_XPATH);
  468. $this->findKeyParam += 1;
  469. } while (!$element->isVisible());
  470. return $element;
  471. }
  472. /**
  473. * Reset key of last find param.
  474. *
  475. * @return void
  476. */
  477. protected function resetKeyParam()
  478. {
  479. $this->findKeyParam = 0;
  480. }
  481. /**
  482. * Param wait loader.
  483. *
  484. * @param ElementInterface $element
  485. * @return void
  486. */
  487. protected function waitForCondition(ElementInterface $element)
  488. {
  489. $this->waitUntil(
  490. function () use ($element) {
  491. return $element->getAttribute('class') == 'rule-param-wait' ? null : true;
  492. }
  493. );
  494. }
  495. /**
  496. * Clear conditions.
  497. *
  498. * @return void
  499. */
  500. protected function clear()
  501. {
  502. $remote = $this->find($this->remove, Locator::SELECTOR_XPATH);
  503. while ($remote->isVisible()) {
  504. $remote->click();
  505. $remote = $this->find($this->remove, Locator::SELECTOR_XPATH);
  506. }
  507. }
  508. /**
  509. * Get value from conditions.
  510. *
  511. * @return null
  512. */
  513. public function getValue()
  514. {
  515. return null;
  516. }
  517. }