4

I am in the process of upgrading from the Behat 2.x series to the Behat 3.x series. In the prior version I could load the Selenium 1 driver, which attached to PhantomJS to execute tests. When I did this I was able to hook into a function called waitForPageToLoad().

This function was provided by php-selenium (from Alexandre Salomé). It hooked into selenium and called a driver action by the same name. This worked perfectly for ensuring that Selenium waited for a page to load. At least until a timeout was reached. It made tests to go much faster.

The problem is that the Selenium 1 driver is not compatible with Behat 3.x. It looks like it has been all but abandoned and I don't see that functionality in the Selenium 2 driver for Mink.

Does anyone know of a way to make this work with Behat 3.x and Selenium 2?

Patrick
  • 3,302
  • 4
  • 28
  • 47

4 Answers4

8

Using Selenium (or any other driver for that matter), I've never had to worry about whether the page has loaded or not, with one exception: if the page finishes loading, then loads more content via AJAX.

To handle this, you can use a spin function as documented in the Behat Manual.

http://docs.behat.org/en/v2.5/cookbook/using_spin_functions.html

The benefits of this are:

  • It doesn't need you to use the selenium driver (for example, you could use PhantomJS if you want speed over looks).
  • It won't break if you stop using jQuery and switch to something else (such as Angular's $httpProvider)

I wouldn't use theirs though, the back trace is broken and who want's to wait a second between checks anyway. :)

Try this:

Assuming you are using the Mink Context (thanks Mick), you can simply check the page every second or so until the desired text has either appeared or dissapeared, or a given timeout has expired in which case we'd assume a fail.

/**
 * @When I wait for :text to appear
 * @Then I should see :text appear
 * @param $text
 * @throws \Exception
 */
public function iWaitForTextToAppear($text)
{
    $this->spin(function(FeatureContext $context) use ($text) {
        try {
            $context->assertPageContainsText($text);
            return true;
        }
        catch(ResponseTextException $e) {
            // NOOP
        }
        return false;
    });
}


/**
 * @When I wait for :text to disappear
 * @Then I should see :text disappear
 * @param $text
 * @throws \Exception
 */
public function iWaitForTextToDisappear($text)
{
    $this->spin(function(FeatureContext $context) use ($text) {
        try {
            $context->assertPageContainsText($text);
        }
        catch(ResponseTextException $e) {
            return true;
        }
        return false;
    });
}

/**
 * Based on Behat's own example
 * @see http://docs.behat.org/en/v2.5/cookbook/using_spin_functions.html#adding-a-timeout
 * @param $lambda
 * @param int $wait
 * @throws \Exception
 */
public function spin($lambda, $wait = 60)
{
    $time = time();
    $stopTime = $time + $wait;
    while (time() < $stopTime)
    {
        try {
            if ($lambda($this)) {
                return;
            }
        } catch (\Exception $e) {
            // do nothing
        }

        usleep(250000);
    }

    throw new \Exception("Spin function timed out after {$wait} seconds");
}
DanielM
  • 6,380
  • 2
  • 38
  • 57
  • 1
    Note: People, you need to extend [MinkContext](https://github.com/Behat/MinkExtension/blob/master/src/Behat/MinkExtension/Context/MinkContext.php) as it has `assertPageContainsText` – Mick Jan 27 '18 at 05:35
6

Selenium2 now has the wait($timeout, $condition) function.

You can use it like:

/**
 * @Then /^I wait for the ajax response$/
 */
public function iWaitForTheAjaxResponse()
{
    $this->getSession()->wait(5000, '(0 === jQuery.active)');
}

Other conditions that you could test for are:

  • the appearance of a certain element on the page
  • the DOM to finish loading

The reason for the change is outlined on the selenium website documentation

DanielM
  • 6,380
  • 2
  • 38
  • 57
brian_d
  • 11,190
  • 5
  • 47
  • 72
  • This is the method I used to use, it's simple and effective. However, in my opinion, this test includes constraints on ***how*** the app works, when all you should care about is ***if*** the app works. If you trade out jQuery anywhere in your app, this won't work, and you can't reuse it in projects that don't use jQuery. It is also dependent on you running Selenium when your tests should *try* to be driver independent. Though, as I said, it works and it's simple. – DanielM Apr 14 '15 at 09:40
  • I guess you could inject and include jQuery dynamically if it doesn't exist in order to have that working... – Jimbo Nov 03 '15 at 15:07
  • If you have an error telling you jQuery is unknown change the condition to: `(typeof jQuery != 'undefined' && 0 === jQuery.active)` – nickel715 Nov 02 '16 at 15:20
0

In order to help someone else, I added this method in FeatureContext.php :

 /**
 * @Then I wait :sec
 */
public function wait($sec)
{
    sleep($sec);
}

And it's working Will

William Rossier
  • 873
  • 9
  • 15
  • This should not be used anymore as there are other, more suitable functions to wait for stuff to appear on page. Currently best thing to do is call wait() as @brian_d described below. – Stormnorm Mar 06 '19 at 10:15
-1
/**
* @Then /^I wait for the ajax response$/
*/
public function iWaitForTheAjaxResponse()
{
   $this->getSession()->wait(5000, '(0 === jQuery.active)');
}

it's working