jhedstrom / drupalextension

An integration layer between Behat, Mink Extension, and Drupal.
GNU General Public License v2.0
209 stars 192 forks source link

iWaitForAjaxToFinish() fails #557

Open brooke-heaton opened 4 years ago

brooke-heaton commented 4 years ago

I'm consistently getting null $result on drupal-extension/src/Drupal/DrupalExtension/Context/MinkContext.php:156 I'm not clear on where the failure is but this essentially makes any @javascript Feature impossible. I'm on Drupal v 8.7.8 using Lando with the selenium/standalone-chrome:3.141.59-oxygen chromedriver on PHP 7.2

Full error output for reference:

[Testing\Behat] Running behat  --format pretty /app/tests/behat --colors --no-interaction --stop-on-failure --strict --config /app/tests/behat/naswa.behat.yml --profile naswa --tags @conference -v
@naswa @javascript @api @release @fields @conference
Feature: Conference
  In order to verify that the conference has the correct fields and form display
  As an administrator
  I should be able to go to the conference create page and all fields

  Scenario: Create a conference page with all fields                      # features/naswa/release/ContentEditing/ConferenceFields.feature:7
    Given I am logged in as user "bheaton@naswa.org"                      # Drupal\FeatureContext::iAmLoggedInAsUser()
    Given I am on "/node/add/conference"                                  # Drupal\DrupalExtension\Context\MinkContext::visit()
    ┌─ @BeforeStep # Drupal\DrupalExtension\Context\MinkContext::beforeJavascriptStep()
    │
    ╳  RuntimeException: Unable to complete AJAX request. {"name":"step.before","feature":"Conference","step":"I click the \"Primary Info\" tab","suite":"default"} in /app/vendor/drupal/drupal-extension/src/Drupal/DrupalExtension/Context/MinkContext.php:173
    ╳  Stack trace:
    ╳  #0 /app/vendor/drupal/drupal-extension/src/Drupal/DrupalExtension/Context/MinkContext.php(107): Drupal\DrupalExtension\Context\MinkContext->iWaitForAjaxToFinish(Object(Behat\Behat\Hook\Scope\BeforeStepScope))
    ╳  #1 [internal function]: Drupal\DrupalExtension\Context\MinkContext->beforeJavascriptStep(Object(Behat\Behat\Hook\Scope\BeforeStepScope))
    ╳  #2 /app/vendor/behat/behat/src/Behat/Testwork/Call/Handler/RuntimeCallHandler.php(109): call_user_func_array(Array, Array)
    ╳  #3 /app/vendor/behat/behat/src/Behat/Testwork/Call/Handler/RuntimeCallHandler.php(64): Behat\Testwork\Call\Handler\RuntimeCallHandler->executeCall(Object(Behat\Testwork\Hook\Call\HookCall))
    ╳  #4 /app/vendor/behat/behat/src/Behat/Testwork/Call/CallCenter.php(140): Behat\Testwork\Call\Handler\RuntimeCallHandler->handleCall(Object(Behat\Testwork\Hook\Call\HookCall))
    ╳  #5 /app/vendor/behat/behat/src/Behat/Testwork/Call/CallCenter.php(96): Behat\Testwork\Call\CallCenter->handleCall(Object(Behat\Testwork\Hook\Call\HookCall))
    ╳  #6 /app/vendor/behat/behat/src/Behat/Testwork/Hook/HookDispatcher.php(74): Behat\Testwork\Call\CallCenter->makeCall(Object(Behat\Testwork\Hook\Call\HookCall))
    ╳  #7 /app/vendor/behat/behat/src/Behat/Testwork/Hook/HookDispatcher.php(58): Behat\Testwork\Hook\HookDispatcher->dispatchHook(Object(Behat\Behat\Hook\Scope\BeforeStepScope), Object(Behat\Behat\Hook\Call\BeforeStep))
    ╳  #8 /app/vendor/behat/behat/src/Behat/Behat/Hook/Tester/HookableStepTester.php(64): Behat\Testwork\Hook\HookDispatcher->dispatchScopeHooks(Object(Behat\Behat\Hook\Scope\BeforeStepScope))
    ╳  #9 /app/vendor/behat/behat/src/Behat/Behat/EventDispatcher/Tester/TickingStepTester.php(49): Behat\Behat\Hook\Tester\HookableStepTester->setUp(Object(Behat\Behat\Context\Environment\InitializedContextEnvironment), Object(Behat\Gherkin\Node\FeatureNode), Object(Behat\Gherkin\Node\StepNode), false)
    ╳  #10 /app/vendor/behat/behat/src/Behat/Behat/EventDispatcher/Tester/EventDispatchingStepTester.php(60): Behat\Behat\EventDispatcher\Tester\TickingStepTester->setUp(Object(Behat\Behat\Context\Environment\InitializedContextEnvironment), Object(Behat\Gherkin\Node\FeatureNode), Object(Behat\Gherkin\Node\StepNode), false)
    ╳  #11 /app/vendor/behat/behat/src/Behat/Behat/Tester/StepContainerTester.php(56): Behat\Behat\EventDispatcher\Tester\EventDispatchingStepTester->setUp(Object(Behat\Behat\Context\Environment\InitializedContextEnvironment), Object(Behat\Gherkin\Node\FeatureNode), Object(Behat\Gherkin\Node\StepNode), false)
    ╳  #12 /app/vendor/behat/behat/src/Behat/Behat/Tester/Runtime/RuntimeScenarioTester.php(76): Behat\Behat\Tester\StepContainerTester->test(Object(Behat\Behat\Context\Environment\InitializedContextEnvironment), Object(Behat\Gherkin\Node\FeatureNode), Object(Behat\Gherkin\Node\ScenarioNode), false)
    ╳  #13 /app/vendor/behat/behat/src/Behat/Behat/Hook/Tester/HookableScenarioTester.php(74): Behat\Behat\Tester\Runtime\RuntimeScenarioTester->test(Object(Behat\Behat\Context\Environment\InitializedContextEnvironment), Object(Behat\Gherkin\Node\FeatureNode), Object(Behat\Gherkin\Node\ScenarioNode), false)
    ╳  #14 /app/vendor/behat/behat/src/Behat/Behat/EventDispatcher/Tester/EventDispatchingScenarioTester.php(103): Behat\Behat\Hook\Tester\HookableScenarioTester->test(Object(Behat\Behat\Context\Environment\InitializedContextEnvironment), Object(Behat\Gherkin\Node\FeatureNode), Object(Behat\Gherkin\Node\ScenarioNode), false)
    ╳  #15 /app/vendor/behat/behat/src/Behat/Behat/Tester/Runtime/IsolatingScenarioTester.php(69): Behat\Behat\EventDispatcher\Tester\EventDispatchingScenarioTester->test(Object(Behat\Behat\Context\Environment\InitializedContextEnvironment), Object(Behat\Gherkin\Node\FeatureNode), Object(Behat\Gherkin\Node\ScenarioNode), false)
    ╳  #16 /app/vendor/behat/behat/src/Behat/Behat/Tester/Runtime/RuntimeFeatureTester.php(84): Behat\Behat\Tester\Runtime\IsolatingScenarioTester->test(Object(Behat\Behat\Context\Environment\UninitializedContextEnvironment), Object(Behat\Gherkin\Node\FeatureNode), Object(Behat\Gherkin\Node\ScenarioNode), false)
    ╳  #17 /app/vendor/behat/behat/src/Behat/Behat/Hook/Tester/HookableFeatureTester.php(72): Behat\Behat\Tester\Runtime\RuntimeFeatureTester->test(Object(Behat\Behat\Context\Environment\UninitializedContextEnvironment), Object(Behat\Gherkin\Node\FeatureNode), false)
    ╳  #18 /app/vendor/behat/behat/src/Behat/Behat/EventDispatcher/Tester/EventDispatchingFeatureTester.php(71): Behat\Behat\Hook\Tester\HookableFeatureTester->test(Object(Behat\Behat\Context\Environment\UninitializedContextEnvironment), Object(Behat\Gherkin\Node\FeatureNode), false)
    ╳  #19 /app/vendor/behat/behat/src/Behat/Testwork/Tester/Runtime/RuntimeSuiteTester.php(63): Behat\Behat\EventDispatcher\Tester\EventDispatchingFeatureTester->test(Object(Behat\Behat\Context\Environment\UninitializedContextEnvironment), Object(Behat\Gherkin\Node\FeatureNode), false)
    ╳  #20 /app/vendor/behat/behat/src/Behat/Testwork/Hook/Tester/HookableSuiteTester.php(73): Behat\Testwork\Tester\Runtime\RuntimeSuiteTester->test(Object(Behat\Behat\Context\Environment\UninitializedContextEnvironment), Object(Behat\Testwork\Specification\GroupedSpecificationIterator), false)
    ╳  #21 /app/vendor/behat/behat/src/Behat/Testwork/EventDispatcher/Tester/EventDispatchingSuiteTester.php(72): Behat\Testwork\Hook\Tester\HookableSuiteTester->test(Object(Behat\Behat\Context\Environment\UninitializedContextEnvironment), Object(Behat\Testwork\Specification\GroupedSpecificationIterator), false)
    ╳  #22 /app/vendor/behat/behat/src/Behat/Testwork/Tester/Runtime/RuntimeExercise.php(71): Behat\Testwork\EventDispatcher\Tester\EventDispatchingSuiteTester->test(Object(Behat\Behat\Context\Environment\UninitializedContextEnvironment), Object(Behat\Testwork\Specification\GroupedSpecificationIterator), false)
    ╳  #23 /app/vendor/behat/behat/src/Behat/Testwork/EventDispatcher/Tester/EventDispatchingExercise.php(70): Behat\Testwork\Tester\Runtime\RuntimeExercise->test(Array, false)
    ╳  #24 /app/vendor/behat/behat/src/Behat/Testwork/Ordering/OrderedExercise.php(80): Behat\Testwork\EventDispatcher\Tester\EventDispatchingExercise->test(Array, false)
    ╳  #25 /app/vendor/behat/behat/src/Behat/Testwork/Tester/Cli/ExerciseController.php(149): Behat\Testwork\Ordering\OrderedExercise->test(Array, false)
    ╳  #26 /app/vendor/behat/behat/src/Behat/Testwork/Tester/Cli/ExerciseController.php(108): Behat\Testwork\Tester\Cli\ExerciseController->testSpecifications(Object(Symfony\Component\Console\Input\ArgvInput), Array)
    ╳  #27 /app/vendor/behat/behat/src/Behat/Testwork/Cli/Command.php(63): Behat\Testwork\Tester\Cli\ExerciseController->execute(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
    ╳  #28 /app/vendor/symfony/console/Command/Command.php(255): Behat\Testwork\Cli\Command->execute(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
    ╳  #29 /app/vendor/symfony/console/Application.php(982): Symfony\Component\Console\Command\Command->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
    ╳  #30 /app/vendor/symfony/console/Application.php(255): Symfony\Component\Console\Application->doRunCommand(Object(Behat\Testwork\Cli\Command), Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
    ╳  #31 /app/vendor/behat/behat/src/Behat/Testwork/Cli/Application.php(124): Symfony\Component\Console\Application->doRun(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
    ╳  #32 /app/vendor/symfony/console/Application.php(148): Behat\Testwork\Cli\Application->doRun(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
    ╳  #33 /app/vendor/behat/behat/bin/behat(34): Symfony\Component\Console\Application->run()
    ╳  #34 {main}
    │
    And I click the "Primary Info" tab                                    # Drupal\FeatureContext::iClickTheTab()
    And I click the ".js-form-item-field-date-range-0-value-date" element # Drupal\FeatureContext::iClickTheElement()
    And I choose "January" for "Month" in the "Start date" Datepicker     # Drupal\FeatureContext::iChooseForInTheDatepicker()
ptmkenny commented 4 years ago

iWaitForAjaxtoFinish() is also failing for me.

I'm using Drupal Extension 4.1.0 on Drupal 8.9.1.

I have my site set up locally using lando; when I run the tests there, "I wait for AJAX to finish" works as expected.

Lando is using the selenium/standalone-chrome-debug docker image.

However, when I push my changes to github and a build is triggered via CircleCI on Pantheon, the "I wait for AJAX to finish" step gives me the following error:

Pantheon CircleCI uses DMore\Chromedriver instead of selenium standalone chrome:

# Dynamically set Behat configuration parameters
export BEHAT_PARAMS='{"extensions" : {"Behat\\MinkExtension" : {"base_url" : "https://'$TERMINUS_ENV'-'$TERMINUS_SITE'.pantheonsite.io/"}, "Drupal\\DrupalExtension" : {"drush" :   {  "alias":  "@pantheon.'$TERMINUS_SITE'.'$TERMINUS_ENV'" }, { "root": "/app/web" }}}}'

# Start headless Chrome
echo "\n Starting Chrome in headless mode ..."
google-chrome --disable-gpu --headless --remote-debugging-address=0.0.0.0 --remote-debugging-port=9222 --no-sandbox </dev/null &>/dev/null &
      Browser crashed (Behat\Mink\Exception\DriverException)

    │

    ╳  Fatal error: Call to a member function call() on null (Behat\Testwork\Call\Exception\FatalThrowableError)

    │

    └─ @AfterStep # FailAid\Context\FailureContext::gatherStateFactsAfterFailedStep()

      Internal error

I also get the same browser crash with code like this:

  /**
   * Wait for a element.
   *
   * https://stackoverflow.com/a/49117368/1209486
   *
   * @When (I )wait :count second(s) until I see the :element element
   */
  public function iWaitSecondsForElement($seconds, $element) {
    $timeout = $seconds * 1000;
    $page = $this->getSession()->getPage();

    $page->waitFor("$timeout",
      function () use ($page, $element) {
        return $page->find('css', "$element");
      }
    );
  }

In both cases, this error only occurs when testing the Pantheon site via CircleCI; it does not occur locally in lando.

shagel7984 commented 3 years ago

Any news here?

michaellenahan commented 2 years ago

Using drupal/drupal-extension v4.1.0 with Drupal 9.2.9

iWaitForAjaxtoFinish() is failing for us on our local VMs, but, strangely, not on our gitlab-ci.

On the VM, we get many "Unable to complete AJAX request" errors.

Taking a look at MinkContext.php ...

https://github.com/jhedstrom/drupalextension/blob/master/src/Drupal/DrupalExtension/Context/MinkContext.php#L147

... we see that iWaitForAjaxToFinish() contains a javascript function which returns false if Ajax is still running, and returns true if Ajax is no longer running.

The problem I am having is that the jQuery.active property is not always available, and, in the cases where jQuery.active is not available, jQuery.active === 0 is evaluating to false. Therefore, the javascript function is returning false, and the $this->getSession()->wait() times out.

Here is the pull request: https://github.com/jhedstrom/drupalextension/pull/603

In the PR I made this change to the javascript function, to add the check to see if the jQuery.active property was available: jQuery.hasOwnProperty('active') === false (admittedly, this is verbose ... it does not have to stay that way ... I did it like this for my own clarity while debugging).

      return (
        // Assert no AJAX request is running (via jQuery or Drupal) and no
        // animation is running.
        (typeof jQuery === 'undefined' || jQuery.hasOwnProperty('active') === false || (jQuery.active === 0 && jQuery(':animated').length === 0)) &&
        d7_not_ajaxing && d8_not_ajaxing
      );

Here is some debug code, this helped me investigate what jQuery.active was doing.

    public function iWaitForAjaxToFinish($event = null)
    {
        // DEBUG
        $active = <<<JS
    (function() {
        if (typeof jQuery === 'undefined') {
            return "jQuery === 'undefined'";
        }
        if (jQuery.hasOwnProperty('active') === false) {
            return 'jQuery.hasOwnProperty(\'active\') === false';
        }
        return jQuery.active;
    }());
JS;
        $active_result = $this->getSession()->evaluateScript($active);
        echo $active_result;
        // END DEBUG

Running the behat command I now get output like this:

Sometimes, jQuery.active returns an integer (0). Sometimes, jQuery itself is undefined. Sometimes, jQuery is defined but it has no 'active' property <-- this is the case which was not handled properly and was causing the error.

$ ${BEHAT} --config ${CODEBASE_DIR}/tests/behat/behat.gitlab.yml ${BEHAT_OPTIONS} --tags=comments -vvv
@javascript @api @comments @coretest
Feature: Test comments.

  Scenario: I disable Google Recaptcha for testing comments.          # features/comments.feature:7
    Given I am logged in as a user with the "administrator" role      # FeatureContext::assertAuthenticatedByRole()
    And I am on "/admin/config/services/vue_comments"                 # Drupal\DrupalExtension\Context\MinkContext::visit()
    Then I uncheck the box "Enable Google Captcha"                    # Drupal\DrupalExtension\Context\MinkContext::assertUncheckBox()
    ┌─ @BeforeStep # Drupal\DrupalExtension\Context\MinkContext::beforeJavascriptStep()
    │
    │  0
    │
    Then I press "edit-submit"                                        # Drupal\DrupalExtension\Context\MinkContext::pressButton()
    │
    │  0
    │
    └─ @AfterStep # Drupal\DrupalExtension\Context\MinkContext::afterJavascriptStep()
    Then I should see "Die Konfigurationsoptionen wurden gespeichert" # Drupal\DrupalExtension\Context\MinkContext::assertPageContainsText()

  Scenario: I comment on a recipe.                                                 # features/comments.feature:14
    Given I am on "/rezepte/rocher-torte-rezept-mit-nutella"                       # Drupal\DrupalExtension\Context\MinkContext::visit()
    And I wait for "Kommentare zu"                                                 # Devinci\DevinciExtension\Context\JavascriptContext::iWaitFor()
    And I wait for "Kommentare lesen oder abgeben"                                 # Devinci\DevinciExtension\Context\JavascriptContext::iWaitFor()
    ┌─ @BeforeStep # Drupal\DrupalExtension\Context\MinkContext::beforeJavascriptStep()
    │
    │  jQuery === 'undefined'
    │
    Then I click "Kommentare lesen oder abgeben"                                   # Drupal\DrupalExtension\Context\MinkContext::assertClick()
    │
    │  jQuery === 'undefined'
    │
    └─ @AfterStep # Drupal\DrupalExtension\Context\MinkContext::afterJavascriptStep()
    And I fill in "comment-name" with "Kommentar Name Behat 1"                     # Drupal\DrupalExtension\Context\MinkContext::fillField()
    And I fill in "comment-text" with "Kommentar Text Behat 1"                     # Drupal\DrupalExtension\Context\MinkContext::fillField()
    ┌─ @BeforeStep # Drupal\DrupalExtension\Context\MinkContext::beforeJavascriptStep()
    │
    │  jQuery.hasOwnProperty('active') === false
    │
    And I click on ".lupus-comments__top__rating .lupus-rating-star:first-of-type" # ContentContext::clickElement()
    │
    │  jQuery.hasOwnProperty('active') === false
    │
    └─ @AfterStep # Drupal\DrupalExtension\Context\MinkContext::afterJavascriptStep()
jhedstrom commented 2 years ago

This looks great! Thanks for diving into that. I can merge the PR as it looks good to me.

Natshah commented 1 year ago

Facing the same issue

Aporie commented 1 year ago

I'm still facing the same issue even with michaellenahan comment.

In my case, the error happens when I return a 403 or a 500 on purpose. I'd like Behat not to stop there, but to actually validate that a message is displayed to the user.

I did a quick digging around but couldn't figure it out. I actually don't really understand how this test works, basically we wait until the result is false and then throw an error. How does it work when the AJAX callback is actually returning a 200? What is stopping the execution of this function not to throw the \RuntimeException?

From a rough guess, I think what triggers my error is that all Drupal.ajax.instances are done but Behat didn't get a 200 response so continue the execution of iWaitForAjaxToFinish which throws the error and break the scenario execution ...

AliagaDev commented 4 months ago

Created pull request

jQuery.active internal value may have negative value in some cases causing inconsistent test results, as jquery supposedly increments the value when an ajax request starts and decrements it when it finishes, it should not influence the identification of active ajax requests

Hope it helps!