Behat / MinkExtension

Mink extension (tight integration and configuration) for Behat
MIT License
636 stars 279 forks source link

Element matching xpath "./ancestor::form" not found. #368

Closed josegamboa closed 4 years ago

josegamboa commented 4 years ago

Hi Guys,

I am having an issue at the moment:

Drupal\Tests\PhpUnit\ExistingSiteJavascript\TestingTest::testPage Behat\Mink\Exception\ElementNotFoundException: Element matching xpath "./ancestor::form" not found.

/var/www/site/vendor/behat/mink/src/WebAssert.php:418 /var/www/site/docroot/core/tests/Drupal/Tests/UiHelperTrait.php:78 /var/www/site/docroot/core/tests/Drupal/Tests/UiHelperTrait.php:250 /var/www/site/tests/phpunit/src/ExistingSiteJavascript/JavascriptTestBase.php:23

Any ideas?

ciaranmcnulty commented 4 years ago

It's hard to tell without knowing your project and what's in those helpers, but the generic message means you're addressing a form element on a page that is not part of a form

josegamboa commented 4 years ago

Hi @ciaranmcnulty, Thanks a lot for helping me. I am still doing some investigation about it and this is what I have so far:

I just did a basic test:

` namespace Drupal\Tests\PhpUnit\ExistingSiteJavascript;

use weitzman\DrupalTestTraits\ExistingSiteWebDriverTestBase;

class ExampleWebDriverTest extends ExistingSiteWebDriverTestBase {

public function testContentCreation() { $web_assert = $this->assertSession(); $this->drupalLogin($this->createUser([], NULL, TRUE)); $this->drupalGet('/admin/content'); $web_assert->statusCodeEquals(200); }

}

` This issue seems to be happening when calling $this->drupalLogin ():

file: /core/tests/Drupal/Tests/UiHelperTrait.php

` protected function drupalLogin(AccountInterface $account) { if ($this->loggedInUser) { $this->drupalLogout(); }

$this->drupalGet(Url::fromRoute('user.login'));
$this->submitForm([
  'name' => $account->getAccountName(),
  'pass' => $account->passRaw,
], t('Log in'));

// @see ::drupalUserIsLoggedIn()
$account->sessionId = $this->getSession()->getCookie(\Drupal::service('session_configuration')->getOptions(\Drupal::request())['name']);
$this->assertTrue($this->drupalUserIsLoggedIn($account), new FormattableMarkup('User %name successfully logged in.', ['%name' => $account->getAccountName()]));

$this->loggedInUser = $account;
$this->container->get('current_user')->setAccount($account);

} `

Then submit form is doing the login process:

` protected function submitForm(array $edit, $submit, $form_html_id = NULL) { $assert_session = $this->assertSession();

// Get the form.
if (isset($form_html_id)) {
  $form = $assert_session->elementExists('xpath', "//form[@id='$form_html_id']");
  $submit_button = $assert_session->buttonExists($submit, $form);
  $action = $form->getAttribute('action');
}
else {
  $submit_button = $assert_session->buttonExists($submit);
  $form = $assert_session->elementExists('xpath', './ancestor::form', $submit_button);
  $action = $form->getAttribute('action');
}

// Edit the form values.
foreach ($edit as $name => $value) {
  $field = $assert_session->fieldExists($name, $form);

  // Provide support for the values '1' and '0' for checkboxes instead of
  // TRUE and FALSE.
  // @todo Get rid of supporting 1/0 by converting all tests cases using
  // this to boolean values.
  $field_type = $field->getAttribute('type');
  if ($field_type === 'checkbox') {
    $value = (bool) $value;
  }

  $field->setValue($value);
}

// Submit form.
$this->prepareRequest();
$submit_button->press();

// Ensure that any changes to variables in the other thread are picked up.
$this->refreshVariables();

// Check if there are any meta refresh redirects (like Batch API pages).
if ($this->checkForMetaRefresh()) {
  // We are finished with all meta refresh redirects, so reset the counter.
  $this->metaRefreshCount = 0;
}

// Log only for JavascriptTestBase tests because for Goutte we log with
// ::getResponseLogHandler.
if ($this->htmlOutputEnabled && !($this->getSession()->getDriver() instanceof GoutteDriver)) {
  $out = $this->getSession()->getPage()->getContent();
  $html_output = 'POST request to: ' . $action .
    '<hr />Ending URL: ' . $this->getSession()->getCurrentUrl();
  $html_output .= '<hr />' . $out;
  $html_output .= $this->getHtmlOutputHeaders();
  $this->htmlOutput($html_output);
}

} `

It seems like somehow the login process is not able to find the button (Log in), even though the button is there and the value is: "Log in", but, if I do hard-code the id of the form in $form_html_id = NULL param, it just works, I did some debugging and the login form seems to be fine and it has the button too, I am not sure how is this happening.

`

$submit_button = $assert_session->buttonExists($submit); $form = $assert_session->elementExists('xpath', './ancestor::form', $submit_button); `

Thanks...

ciaranmcnulty commented 4 years ago

Is the button inside the form in the markup?

josegamboa commented 4 years ago

Hi @ciaranmcnulty ,

Yes, it is: `

josegamboa commented 4 years ago

this is the logic for file: /core/tests/Drupal/Tests/UiHelperTrait.php

` public function buttonExists($button, TraversableElement $container = NULL) { $container = $container ?: $this->session->getPage(); $node = $container->findButton($button);

if ($node === NULL) {
  throw new ElementNotFoundException($this->session->getDriver(), 'button', 'id|name|label|value', $button);
}

return $node;

}

`

ciaranmcnulty commented 4 years ago

Not sure I can offer much more help; the error seems to be coming from inside UiHelperTrait which isn't part of this project

josegamboa commented 4 years ago

@ciaranmcnulty , Thanks a lot for your help, after digging a little bit more it was a Drupal core issue when authenticating admin users. if the page has two elements with the value Log in, it takes the first one to find the login form, it explains the error above, I have created a patch for fixing it in case someone is having the same issue: https://www.drupal.org/project/drupal/issues/3169604#comment-13816168