How to handle Race Conditions in Behat/Mink

165 Views Asked by At

So I have the following, very simply test in Behat/Mink. A button, when pressed, shows an element, which I then fill with some text. Sometimes this test works, sometimes it doesn't. Here's the basic structure:

file.blade.php

<button id="show-inputs">Show</button>
<div id="inputs" class="hidden">
  <span>Input</span>
  <input type="text" id="test-input"/>
</div>

script.js

jQuery(function($){
  $('#show-inputs').on('click', function() {
    $('#inputs').removeClass('hidden');
  });
});

TestingFeature.feature

@javascript
Scenario: Testing Input Show/Fill
  Given I am on "/"
  Then I press "show-inputs"
  And I wait for javascript to finish loading
  Then I fill in "test-input" with "This is a Test"
  ...

FeatureContext.php

public function iWaitForJavascriptToFinishLoading() {
  $this->getSession()->wait(10000, '$.active === 0');
}

As you can see, it's a simple test; after navigating to /, trigger click of #show-inputs, wait for JS to finish (i.e. give time to show #inputs div), then fill in #test-input with a simple test.

Running these tests, sometimes it works, other times it doesn't. Here's the output of behat:

...
Then I press "show-inputs"                        # FeatureContext::pressButton()
And I wait for javascript to finish loading       # FeatureContext::iWaitForJavascriptToFinishLoading()
Then I fill in "test-input" with "This is a Test" # FeatureContext::fillField()
  Element is not visible and can not be focused

The error Element is not visible and can not be focused only shows up sometimes, suggesting a race-condition on Show Element > Fill Element, happening in the incorrect order. I thought that And I wait for javascript to finish loading, which waits until $.active is 0 would handle this, but apparently it doesn't. I'm wondering if it has something to do with using a class hidden (display:none) vs a simple $('{$selector}').show(), but I'm not sure.

I can add a new check for something along the lines of And I wait for "inputs" to be "visible", which waits until $('{$selector}').is(':visible') === true, but that doesn't feel right.

Has anyone come across this issue before?

1

There are 1 best solutions below

0
Tim Lewis On

I ended up going the way I figured I would have to, using And I wait for "test-input" to be "visible", with a custom definition in FeatureContext.php:

/**
 * @Then I wait for :elementId to be :visibility
 */
public function iWaitForElementToBe($elementId, $visibility) {
  if ($visibility != 'visible' && $visibility != 'hidden') {
    throw new Exception("Invalid visibility pseudo `{$visibility}` supplied: Must be one of `visible`, `hidden`");
  }
  $this->getSession()->wait(1000, "\$('#{$elementId}').is(':{$visibility}') === true");
}

Then in TestingFeature.feature:

@javascript
Scenario: Testing Input Show/Fill
  Given I am on "/"
  Then I press "show-inputs"
  And I wait for "test-input" to be "visible"
  Then I fill in "test-input" with "This is a Test"

The only caveat is that if multiple elements are shown the same time, the And I wait for ... to be ... needs to be repeated for each element, even if they're toggled visible/hidden in the same line of code (such as $('#id, #id2').removeClass('hidden')).