MDL-66378 Behat: Speed up 'before/after' step in Chrome

On some Chrome versions the xpath used for before/after is slow
and (sometimes) uses a lot of memory. This change uses pure
JavaScript to do the before/after calculation, which should always
be fast.
This commit is contained in:
sam marshall 2019-08-15 13:10:13 +01:00 committed by Andrew Nicols
parent a157baa65a
commit 9f1149ac13

View file

@ -771,20 +771,9 @@ class behat_general extends behat_base {
* @param string $postselectortype The selector type of the latest element * @param string $postselectortype The selector type of the latest element
*/ */
public function should_appear_before($preelement, $preselectortype, $postelement, $postselectortype) { public function should_appear_before($preelement, $preselectortype, $postelement, $postselectortype) {
$msg = '"' . $preelement . '" "' . $preselectortype .
// We allow postselectortype as a non-text based selector. '" does not appear before "' . $postelement . '" "' . $postselectortype . '"';
list($preselector, $prelocator) = $this->transform_selector($preselectortype, $preelement); $this->check_element_order($preelement, $preselectortype, $postelement, $postselectortype, $msg);
list($postselector, $postlocator) = $this->transform_selector($postselectortype, $postelement);
$prexpath = $this->find($preselector, $prelocator)->getXpath();
$postxpath = $this->find($postselector, $postlocator)->getXpath();
// Using following xpath axe to find it.
$msg = '"'.$preelement.'" "'.$preselectortype.'" does not appear before "'.$postelement.'" "'.$postselectortype.'"';
$xpath = $prexpath.'/following::*[contains(., '.$postxpath.')]';
if (!$this->getSession()->getDriver()->find($xpath)) {
throw new ExpectationException($msg, $this->getSession());
}
} }
/** /**
@ -798,18 +787,37 @@ class behat_general extends behat_base {
* @param string $preselectortype The locator of the preceding element * @param string $preselectortype The locator of the preceding element
*/ */
public function should_appear_after($postelement, $postselectortype, $preelement, $preselectortype) { public function should_appear_after($postelement, $postselectortype, $preelement, $preselectortype) {
$msg = '"' . $postelement . '" "' . $postselectortype .
'" does not appear after "' . $preelement . '" "' . $preselectortype . '"';
$this->check_element_order($preelement, $preselectortype, $postelement, $postselectortype, $msg);
}
// We allow postselectortype as a non-text based selector. /**
list($postselector, $postlocator) = $this->transform_selector($postselectortype, $postelement); * Shared code to check whether an element is before or after another one.
*
* @param string $preelement The locator of the preceding element
* @param string $preselectortype The locator of the preceding element
* @param string $postelement The locator of the following element
* @param string $postselectortype The selector type of the following element
* @param string $msg Message to output if this fails
*/
protected function check_element_order(string $preelement, string $preselectortype,
string $postelement, string $postselectortype, string $msg) {
list($preselector, $prelocator) = $this->transform_selector($preselectortype, $preelement); list($preselector, $prelocator) = $this->transform_selector($preselectortype, $preelement);
list($postselector, $postlocator) = $this->transform_selector($postselectortype, $postelement);
$postxpath = $this->find($postselector, $postlocator)->getXpath();
$prexpath = $this->find($preselector, $prelocator)->getXpath(); $prexpath = $this->find($preselector, $prelocator)->getXpath();
$postxpath = $this->find($postselector, $postlocator)->getXpath();
// Using preceding xpath axe to find it. // The xpath to do this was running really slowly on certain Chrome versions so we are using
$msg = '"'.$postelement.'" "'.$postselectortype.'" does not appear after "'.$preelement.'" "'.$preselectortype.'"'; // this DOM method instead.
$xpath = $postxpath.'/preceding::*[contains(., '.$prexpath.')]'; $ok = $this->getSession()->getDriver()->evaluateScript('return (function() { ' .
if (!$this->getSession()->getDriver()->find($xpath)) { 'var a = document.evaluate("' . $prexpath . '", document).iterateNext();' .
'var b = document.evaluate("' . $postxpath . '", document).iterateNext();' .
'return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_PRECEDING;})()'
);
if (!$ok) {
throw new ExpectationException($msg, $this->getSession()); throw new ExpectationException($msg, $this->getSession());
} }
} }