Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
drupalPostAjaxForm()
is simulating the behaviour of ajax.js
, so using it, doesn't really provide fundamental guarantees.
#2809161: Convert Javascript/AJAX testing to use JavascriptTestBase suggests to convert them to JavascriptTestBase
Proposed resolution
- Figure out which part of the test is testing PHP code and which part ajax behaviour
- Extract the ajax behaviour into a test that extends JavascriptTestBase
Remaining tasks
User interface changes
API changes
Data model changes
Comment | File | Size | Author |
---|---|---|---|
#25 | interdiff-23-25.txt | 564 bytes | gaurav.kapoor |
#25 | comments-2809519-25.patch | 11.15 KB | gaurav.kapoor |
#23 | interdiff-19-23.txt | 1.77 KB | gaurav.kapoor |
#23 | comments-2809519-23.patch | 11.15 KB | gaurav.kapoor |
#19 | interdiff-2809519-12-19.txt | 2.91 KB | martin107 |
Comments
Comment #3
michielnugter CreditAttribution: michielnugter as a volunteer and at Synetic commentedComment #4
martin107 CreditAttribution: martin107 as a volunteer commentedI am making a start on this
what I have is mostly complete... just a little bug I need to resolve.. but in the spirit of post early post often... the basic structure and general solution is in the place.
1) I have converted AjaxFormPageCacheTest into something that extends JavascriptTestBase and moved it into the Drupal\FunctionalJavascriptTests\Ajax namespace.
Previously we asserted what the text of the AJAX response should be - I have cut those out and just the assertions about how the DOM has been updated remain in the test.
2) The place to make assertions about the text of the AJAX response in now covered in a new unit test - see AjaxCommandsTest::testUpdateBuildIdCommand()
Comment #6
martin107 CreditAttribution: martin107 as a volunteer commentedAh so to highlight a difference between classes that extend
Drupal\simpletest\WebTestBase
and things that extend
Drupal\FunctionalJavascriptTests\JavascriptTestBase
When using drupalGet() - it is not equivalent.
the new call clears the drupal cache with a call to refreshVariables()
Hence the failing test
Comment #7
martin107 CreditAttribution: martin107 as a volunteer commentedHere is the fix.
Comment #8
michielnugter CreditAttribution: michielnugter as a volunteer and at Synetic commentedThanks for the good start on this issue and nice find on the drupalGet()!
Some review comments:
Let's not use deprecated methods in the JavascriptTestBase tests. The conversions are extensive and more or less complete rewrites most of the times anyway.
You don't need the string cast anymore.
I think we can wait for something to happen in the DOM to assert the change has been made? We should always try to avoid this method if possible.
Nice find! Good to keep this one in mind.
It states that it is tested that a AJAX request is done but this test actually doesn't test it. The assert part in assertWaitOnAjaxRequest is misleading, it doesn't assert anything. For the test to be valid we need to check something has changed in the DOM.
Awesome!
Comment #9
dawehnerThis test is sooo much better as it used to be. Thank you for working on it!
Can we use
{@inheritdoc}
here?Can we document this line of change?
Comment #10
martin107 CreditAttribution: martin107 as a volunteer commentedThanks for the expert review and the encouragement - it is much appreciated.
#8.1 fixed - In many places assertEqual becomes assertEquals etc
#8.2 fixed
#8.3
I need to ask a question
Programmatically we drive mink into changing the pull down box - There is a AJAX request/response exchange and a DOM element updated.
That means a $this->waitForElement() call will not work because the waitForXYZ method calls poll the page until a new element appears -- in this case we are updating an existing DOM element ... and the new id is not predictable..
I would like get rid of the assertWaitOnAjaxRequest() but I must be missing something?
#8.5 Fixed .... the new element is predictable so the waitFor is simple
#9.1 Fixed.. thanks when I rescan that section of the code and "Question all the things" I see "node" is not a required module.
#9.2 I have updated the existing comments the emphasis that we are pressing the reload button on our virtual browser.
Comment #11
michielnugter CreditAttribution: michielnugter as a volunteer and at Synetic commentedChanges look good!
On #8.3
There are multiple issues with assertWaitOnAjaxRequest which make it prone to random failures in the tests. Some examples:
- Events are debounced, the ajax request starts ~300ms later than expected.
- Postprocessing is done separate from the ajax callback handler.
- Some browser event is interfering with the DOM (e.g. resize event).
When asserting the change in the DOM you work around all these problems as the test waits untill the expected change has happened.
There should always be something different in the DOM we can check on.
For example:
This could be solved with the following:
Although in this case I doubt the performed check. I'd expect a change somewhere in the DOM and not only the form beeing rebuild. I would check the actual change to the form in the waitFor() method. Just make sure no exception is uncaught in the waitFor callback and add an assertion after the wait to really assert the expected state (waitFor is not an assertion and will therefore not make the test fail if it never passes.
On the used timeout (this one is in seconds): please always use 10 seconds or more if you have a good reason for it but never less. Up till now it's been my experience that this is sufficient to never cause random failures.
Comment #12
martin107 CreditAttribution: martin107 as a volunteer commentedThanks for taking my thinking apart - rather than just simply moving the issue forward...
With hindsight the solution looks trivial/natural
Comment #13
michielnugter CreditAttribution: michielnugter as a volunteer and at Synetic commentedSure! It's awesome to see others working on these tests :) I've really been working on finding the best way of handling the javascript issues and am very glad that I can share them!
So nice to see it's actually rather easy to find an alternative!
Review comments:
Didn't even think of it in this way :) But unfortunately the :contains selector is a jQuery specific selector. Earlier it was stated by @droplet (can't remember where though) that we shouldn't use jQuery only selectors to make sure we're not dependent on jQuery in our tests.
I think therefore we have to check this manually (a waitFor with callback that checks the value).
Shame that there's no equivalent in css though, it's such a nice way of doing it..
Comment #14
dawehnerThis is really valueable information we should document somewhere.
I wonder whether this actually also speeds up things a bit when we use just native APIs of the browser.
Ensure to also wrap the comments on 80 chars
Comment #15
michielnugter CreditAttribution: michielnugter as a volunteer and at Synetic commentedI'm going to update the documentation on the Javascript tests soon if I have my head wrapped around all these points completely.
As the alternative implementation is a bit more bulky I don't think there will be an overall performance gain. If there would be one I don't think it will be measurable though.
Comment #16
dawehnerThat is really great!
Its fair, well one could always have some hope about faster test runs :)
Comment #17
martin107 CreditAttribution: martin107 as a volunteer commentedFrom #13
I am not ready to give up yet!
When I watch the phantomjs debug output I see the command is auto converted into the equivalent xpath selector.
it is the ugly child of the css version but this is the equivalent
I have chased this down through a few layer of function calls and have identified the place where it happens
Behat\Mink\Element::findAll() - has a call to selectorToXpath()
IF we can say the testbot will always rely on the xpath equivalent -- Are we safe to proceed as is?
The fallback is to convert to xpath... I hope.
"Needs work" As I still have to correct the 80 chars thing from #14
Comment #18
michielnugter CreditAttribution: michielnugter as a volunteer and at Synetic commented@martin2017:
Thanks for the extensive research and reply!
I've been looking into this question this morning quite a lot and to me the usage of :contains in a Mink css selector is not a problem and fine by me.
My reasoning for this:
- Yes it's a custom css selector introduced by jQuery but it is fully supported by the Symfony CssSelector component. It does not rely on any additional component.
- It is currently used on several of the old Simpletests. We'll be converting these tests ofcourse but I don't see any reason for changing the css selectors there.
- The :contains is better readable and understandable.
The only con I could think of was that we introduce a non-standard css selector in our tests. Though I don't think you'll need to look far to discover another non-standard css selector in core..
I'm not a huge fan of converting to xpath just for the sake of xpath and a standard selector, these selectors are generally hard to read..
If the comment issues are fixed then I think it's good to go! Thanks very much for all the effort and sticking with it!
Comment #19
martin107 CreditAttribution: martin107 as a volunteer commentedThanks ... that is great news.
I don't think we will kill too many kitten with the css thing....
I have adjust the comments to fit inside the 80 chars limit.
Comment #20
michielnugter CreditAttribution: michielnugter as a volunteer and at Synetic commentedThanks a lot and it's looking good!
Comment #21
alexpottEmpty comment lines like this are against coding standards. I think the comment on the next line is just a continuation from the comment on the previous line.
This is not formatted the way a list should be. See https://www.drupal.org/docs/develop/coding-standards/api-documentation-a...
These comments should flow together.
Over 80 chars.
Comment #22
gaurav.kapoor CreditAttribution: gaurav.kapoor at OpenSense Labs commentedComment #23
gaurav.kapoor CreditAttribution: gaurav.kapoor at OpenSense Labs commentedComment #24
michielnugter CreditAttribution: michielnugter as a volunteer and at Synetic commentedSorry for missing those.
Last one that needs a fix:
Empty line still needs to be dropped.
Comment #25
gaurav.kapoor CreditAttribution: gaurav.kapoor at OpenSense Labs commentedComment #26
martin107 CreditAttribution: martin107 as a volunteer commentedAll the comments from #21 and #24 have been address.... all my mistakes... thank you for such a rapid response.
@alexptott thanks for #21.3 that is good for me to know.
Comment #27
alexpottCommitted and pushed 3b22e70 to 8.4.x and b636bb7 to 8.3.x. Thanks!
Committed to 8.3.x as this is a test-only change.
Everyone received credit because every contributor on the issue provided a patch or a helpful review.
Small fix on commit.