Problem/Motivation

\Drupal\book\Tests\BookTest::testBookOrdering() is simulating Javascript behavior by manipulating hidden fields on the form. #2809161: Convert Javascript/AJAX testing to use JavascriptTestBase suggests to convert them to JavascriptTestBase.

Proposed resolution

Create a BookJavascriptTest::testBookOrdering() and remove the current test from \Drupal\book\Tests\BookTest.

Remaining tasks

None.

User interface changes

None.

API changes

None.

Data model changes

None.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

claudiu.cristea created an issue. See original summary.

claudiu.cristea’s picture

Title: Convert \Drupal\book\Tests\BookTest::testBookOrdering() to JavascriptTestBase » Convert \Drupal\book\Tests\BookTest::testBookOrdering() to a JavascriptTestBase test

.

claudiu.cristea’s picture

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

It is super nice to expand the test coverage here!

+++ b/core/modules/book/tests/src/FunctionalJavascript/BookJavascriptTest.php
@@ -0,0 +1,141 @@
+    $this->assertGreaterThan($page2->book['weight'], $page1->book['weight']);

Super nice!

droplet’s picture

Status: Reviewed & tested by the community » Needs review

Should we document it better what is it testing? Or split it into few test cases.

  1. +++ b/core/modules/book/tests/src/FunctionalJavascript/BookJavascriptTest.php
    @@ -0,0 +1,141 @@
    +  public function testBookOrdering() {
    

    Reading the title, I just expected this is testing re-order with dragging

  2. +++ b/core/modules/book/tests/src/FunctionalJavascript/BookJavascriptTest.php
    @@ -0,0 +1,141 @@
    +    $page->findButton('Show row weights')->click();
    

    Until I read Line 8x, I don't know it's also tested "Show Weight" feature.

Missing?
- test Parent-Child

droplet’s picture

DEL

claudiu.cristea’s picture

Until I read Line 8x, I don't know it's also tested "Show Weight" feature.

Yes, that is not exactly in the scope. It's a bonus :)

Also, could we verify it in UI than DB? It could be cached wrongly.

Everything is tested in the UI. The backend test is only an extra precaution.

droplet’s picture

Everything is tested in the UI. The backend test is only an extra precaution.

Thanks. I just read the code again and see it :)

Yes, that is not exactly in the scope. It's a bonus :)

Will another test case for it? If so, should we remove it in the current test? Better Performance :)

claudiu.cristea’s picture

Well, it's not 100% out-of-scope. A sortable table can be used either with drag-n-drop or using the weight selectors. I want to test here that the selectors were synced when I'm doing the drag and drop. So, I would keep it here for now.

droplet’s picture

+++ b/core/modules/book/tests/src/FunctionalJavascript/BookJavascriptTest.php
@@ -0,0 +1,141 @@
+    $page->findButton('Hide row weights')->click();
...
+    $this->submitForm([], 'Save book pages');

Umm. It looks right.. and not so right.

If we clicked the weight column before saving. How can we tell it's changed by drag-n-drop or changed by "Weight" column JS (maybe there's a `change` event from "Weight" click triggered re-ordering)

claudiu.cristea’s picture

I'm not reordering by weight selector. I'm only checking the weights before and after drag-and-drop. But in order to check their values, I need to make them visible because Mink is not able to locate invisible elements on the page. Mink act like an human that is using a real browser.

droplet’s picture

Thanks.

I'm not reordering by weight selector.

I understand it. But any other actions could be the side-effect to the test. Although we know that no changes from the "Show Weight" click in the current code, refactoring code could change the behavior. If we re-run all these testing with Human on refactoring, then unit test is useless.

Mink able to execute JS. But I think it's not necessary in this test case. An human just drag it and click save, and then see correct ordering.

EDITED:

Although we know that no changes from the "Show Weight" click in the current code,

In fact, until I actually reading the JS code carefully. I don't know if it's true or not. My eyes looking at UI, it's not. But it could be work in a way I don't expect.

claudiu.cristea’s picture

But I think it's not necessary in this test case. An human just drag it and click save, and then see correct ordering.

Well, I wanted to test the values invisibly stored by JS in the selectors. I'm testing here JS drag-and-drop and then i'm checking if everything was correct first in UI, by checking the values of weight selectors and, second, in backend by submitting the form and loading the pages. What's wrong with that?

droplet’s picture

It's wrong in Browser Acceptance test. Let me try to explain it.

Test is guaranteed the feature works as expected, no matter what we changed the code in other places.

Do we agree with it? If not, I think too much :(

Above test catches the drag-n-drop ordering.

Now, we refactoring the "Weight Column" to RE-SYNC the value to more readability (e.g. From 1 to 10 instead of -15, -9 -6) on clicking.

We'd sure that ABOVE TEST is covered the drag-n-drop. We run the Patch, it's GREEN. It's passed! It committed!

However, we introduced a new bug on refactoring. It broke the drag-n-drop to SYNC THE VALUE.

In this case, above test case still passed!! Because it clicked the "Weight Column" to trigger the event fired and changing the value correctly!

Current:
DRAG -> VALUE changed by DRAG -> click the weight column -> save

Then:
I introduced a new feature to weight click and a bug to drag-n-drop.
CLICK WEIGHT COLUMN -> VALUE change by NEW Feature -> save
(We know that drag-n-drop has test covered, we won't spend time on testing with HUMAN.)

After this bad refactoring
DRAG -> (NO CHANGE TO VALUE) -> CLICK WEIGHT COLUMN -> CHANGED VALUE FROM WEIGHT -> SAVE

Is the drag-n-drop really working? No!

claudiu.cristea’s picture

Status: Needs review » Reviewed & tested by the community

Sorry, you lost me. Instead of all big explanation, please point out what additional test do we need here, but strictly in the scope of this issue which is book reordering or what's wrong with this test. Till you provide me with that, I'll set back this to RTBC as it has been RTBCed several times in #2757007: Convert all book web tests to BrowserTestBase and also here, in #4.

droplet’s picture

No extra tests but removing the extra `Show rows weight` clicking

I reviewed the patch again. It's only ONE SAVE at the end of tests. I hope I don't miss anything.

This is how above patch does:
Drag -> [Change Value] -> Click `Show rows weight` -> [Change test factors (SIDE-EFFECT)] -> Click Save

We should remove the side-effect in tests.

Best example:
Drag -> [Change Value] -> Click Save -> [Perform checking with `document.querySelectorAll` like selectors, Mink's assertions]

Good example:
Drag -> [Change Value] -> [Checking the result with `document.querySelectorAll` like selectors which will not modify the HTML] -> Click Save

** I will keep it as RTBC. But I hope the committers/maintainers give me one more chance to explain it if you disagree/don't get my point also. Don't think in PHP server-side testing with generated HTML output.

Thanks ALL

pfrenssen’s picture

@droplet I don't get the reservations you have, I have reviewed the test and it does multiple checks to see whether the reordering was successful.

  1. +++ b/core/modules/book/tests/src/FunctionalJavascript/BookJavascriptTest.php
    @@ -0,0 +1,141 @@
    +    // Check that '2nd page' page precedes the '1st page'.
    +    $this->assertOrderInPage(['2nd page', '1st page']);
    

    It first checks to see if the dragging actually results in the items being placed in the right order in the DOM.

  2. +++ b/core/modules/book/tests/src/FunctionalJavascript/BookJavascriptTest.php
    @@ -0,0 +1,141 @@
    +    // Check that rows weight selects are visible.
    +    $this->assertTrue($weight_select1->isVisible());
    +    $this->assertTrue($weight_select2->isVisible());
    +
    +    // Check that '1st page' row became heavier than '2nd page' row.
    +    $this->assertGreaterThan($weight_select2->getValue(), $weight_select1->getValue());
    

    Then it checks that the weights have been correctly updated in the DOM. The actual numbers don't matter, but the dragged item should now have a higher weight.

  3. +++ b/core/modules/book/tests/src/FunctionalJavascript/BookJavascriptTest.php
    @@ -0,0 +1,141 @@
    +    $this->submitForm([], 'Save book pages');
    +    $this->assertSession()->pageTextContains(new FormattableMarkup('Updated book @book.', ['@book' => $book->getTitle()]));
    +
    +    // Check again that '2nd page' is on top after form submit.
    +    $this->assertOrderInPage(['2nd page', '1st page']);
    

    It submits the form and checks again that the correct order is retained, this already proves that the weights have been correctly updated and saved.

  4. +++ b/core/modules/book/tests/src/FunctionalJavascript/BookJavascriptTest.php
    @@ -0,0 +1,141 @@
    +    // Check that page reordering was done in the backend.
    +    $page1 = Node::load($page1->id());
    +    $page2 = Node::load($page2->id());
    +    $this->assertGreaterThan($page2->book['weight'], $page1->book['weight']);
    

    Then it does an additional check to see that the weights that are saved in the database are in the correct order. This is not strictly needed, but it *REALLY* proves it now.

For me this looks really sufficient for the current implementation.

If we would implement the weight re-syncing example that you give, then that should have an additional javascript test that proves that the weight re-syncing does not affect previous unsaved reordering by drag-and-drop.

dawehner’s picture

@droplet
This patch already improves the status a lot. We could though, as you know, always iterate on top of it. Creating issues is easy.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 3: 2820079-3.patch, failed testing.

claudiu.cristea’s picture

Status: Needs work » Reviewed & tested by the community

There was failure on a test that made an external HTTP request:

cURL error 28: Operation timed out after 30001 milliseconds with 0 bytes received (see http://curl.haxx.se/libcurl/c/libcurl-errors.html)

Looks like is caused by http://thenextweb.com/insider/2016/10/21/massive-ddos-attack-dyn-dns-cau...

droplet’s picture

Then it checks that the weights have been correctly updated in the DOM. The actual numbers don't matter, but the dragged item should now have a higher weight.

You skipped the most important point:
It can check the DOM with test assertions (result, the DOM is snapshoted). But it should not modify the DOM and then check it.

What it should be tested is a single action: Drag => GREEN result
Above test is testing a combined action:Drag + Weight Click => GREEN result

It's lucky! The DOM changes do not make any differences to test assertions above.

@pfrenssen comment about re-syncing example isn't true all time. Any new test on weight is testing weight features or a drag + weight combination. But tests will not test drag-n-drop alone again.

@dawehner,
OK.

Somehow, I agreed my suggestions is silly. We almost never make an extra change to `Weight` link in CORE.

Thanks All. Have A Nice Weekend!

michielnugter’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
3.53 KB
7.78 KB

@droplet

If I understand correctly:

The test now tests the following:

  1. Use drag-n-drop to change the order
  2. Validate the UI (checks if reordering works)
  3. Enable the weight select boxes
  4. Validate the changed values in the weight selects
  5. Hide the weight fields and check they've been hidden
  6. Submit the page
  7. Validate the order on the server

The problem is that the server check relies on the drag-n-drop AND the switch to the manual input. Drag-n-drop is not checked isolated from the weight fields.

I changed the patch to submit the page directly after the drag-n-drop action and then validate on the server. After that, I use the weight fields directly and submit the page. Again I check the values on the server. This way the drag-n-drop is tested separately from the weight fields.

Other small change: assertTrue / assertFalse is deprecated in 9.x (should a patch be posted to mark each method deprecated to make the IDE pick it up?). I changed them to assertEquals.

droplet’s picture

@michielnugter,

Step 1-7 in your comment #22 is how above patch is tested.

But with this test scope, this is only test drag-n-drop. Correct order should be:
1. Use drag-n-drop to change the order
2. Validate the UI (checks if reordering works)
3. Validate the Value in Hidden Weight field (without interactive click, use getHTML, test assertions)

4. Submit the page
5. Validate the order on the server

+++ b/core/modules/book/tests/src/FunctionalJavascript/BookJavascriptTest.php
@@ -0,0 +1,160 @@
+    // Toggle row weight selects as visible.
+    $page->findButton('Show row weights')->click();

Point #3 is important. "An action interacts with UI to validate the result" vs "Validate with snapshotted result (->getHTML)"

Ideally, a strict test should validate with getHTML before interact with UI `->click` again. (Good test won't make assumptions)

droplet’s picture

I read the full #22 patch again. Yes. it's improved.

It performed `submitForm & validate` the order before clicking `weight` link.

claudiu.cristea’s picture

  1. +++ b/core/modules/book/tests/src/FunctionalJavascript/BookJavascriptTest.php
    @@ -0,0 +1,160 @@
    +    $this->assertOrderInPage(['2nd page', '1st page'], 'Order is changed using drag-n-drop.');
    

    Please drop the message. We don't use messages anymore and the function doesn't have a message argument in signature.

  2. +++ b/core/modules/book/tests/src/FunctionalJavascript/BookJavascriptTest.php
    @@ -81,30 +81,49 @@
    +    // Check that the 1st page is first again.
    

    Please use quotes around '1st page'.

  3. +++ b/core/modules/book/tests/src/FunctionalJavascript/BookJavascriptTest.php
    @@ -52,8 +52,8 @@
    -    $this->assertFalse($weight_select1->isVisible());
    -    $this->assertFalse($weight_select2->isVisible());
    +    $this->assertEquals(FALSE, $weight_select1->isVisible());
    +    $this->assertEquals(FALSE, $weight_select2->isVisible());
    
    @@ -81,30 +81,49 @@
    -    $this->assertTrue($weight_select1->isVisible());
    -    $this->assertTrue($weight_select2->isVisible());
    +    $this->assertEquals(TRUE, $weight_select1->isVisible());
    +    $this->assertEquals(TRUE, $weight_select2->isVisible());
    ...
    -    $this->assertFalse($weight_select1->isVisible());
    -    $this->assertFalse($weight_select2->isVisible());
    +    $this->assertEquals(FALSE, $weight_select1->isVisible());
    +    $this->assertEquals(FALSE, $weight_select2->isVisible());
    

    When were assertTrue() and assertFalse() deprecated? Can you point me there? If not, please revert these changes.

michielnugter’s picture

Didn't know about the messages, I removed them. Also put quotes around the 1st page text.

As for assertTrue, the method is defined in the 'AssertLegacyTrait' trait. This was deprecated in #2304461.

@droplet: The hidden field value is now tested implicitly by posting it to the server and validating in there, is that enough?

droplet’s picture

@michielnugter #26,

That's fine. I was read the interdiff and doesn't realize that in my first response. Thanks!

claudiu.cristea’s picture

Status: Needs review » Needs work

@michielnugter,

As for assertTrue, the method is defined in the 'AssertLegacyTrait' trait. This was deprecated in #2304461.

No, sorry, it was not deprecated. Please check again. Even AssertLegacyTrait will be removed, JavascriptTestBase has its own assertTrue()/assertFalse() method.

droplet’s picture

Now the assertTrue extend from:
https://github.com/drupal/drupal/blob/b33af7a964ee34171e3328c3276d63551a...

When it removed, it will be used function from PHPUnit directly (if I read the source correctly. JavascriptTestBase doesn't involve TestBase)

(I don't know the preferred method anyway)

michielnugter’s picture

Status: Needs work » Needs review
FileSize
1.88 KB
7.66 KB

@claudiu.cristea

JavascriptTestBase doesn't implement the assertTrue but the PHPUnit_Framework_Assert class does define a (static?) assertTrue, this class is extended via-via. As to why the Trait is still included is not clear to me then.

Sorry about the mixup, I reverted the method calls back.

Edit:
... Now it's clear:

$this->assertTrue() is deprecated, it actually does call the method in AssertLegacyTrait. The documentation states we should use the following:

self::assertTrue()

Which conforms to the static definition of the assertTrue method in PHPUnit_Framework_Assert. It seems the recommended way is self:: for every assert?

claudiu.cristea’s picture

JavascriptTestBase doesn't implement the assertTrue

Sure, it's not implemented directly but it's inherited from ancestors.

$this->assertTrue() is deprecated,

The trait method is deprecated, not explicitly, but together with the trait. So when the trait will be removed there will still be a valid $this->assertTrue() inherited from class ancestors.

@droplet, if everything is OK, please set the issue back to RTBC.

droplet’s picture

Status: Needs review » Reviewed & tested by the community
michielnugter’s picture

@claudiu.cristea

JavascriptTestBase extends via-via PHPUnit_Framework_Assert method. This is the only ancestor that defines the assertTrue method, statically.

So without the deprecated Trait, the method is only callable statically. $this->assertTrue() is really deprecated in favor of self::assertTrue().

As the $this->assertTrue() style is still used all over the place, even in the BrowserTestBase class I don't think it should stop this patch. I however do think we should try to avoid deprecated methods when writing new patches.

dawehner’s picture

So without the deprecated Trait, the method is only callable statically.

Well, this is kinda wrong ... static means it can be also called statically, but it is still a normal method.

. $this->assertTrue() is really deprecated in favor of self::assertTrue().

That change for itself should be discussed in its issue, please create one. Note: Sebastian Bergmann though wrote an response there: https://github.com/sebastianbergmann/phpunit/issues/1914#issuecomment-14...

michielnugter’s picture

@dawehner

Thanks for explaining. It feels really weird to not call a static method statically but if it's the way to go I'll follow.

Sorry about the confusion and setting the issue back unnecessarily.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

I think the test is a bit heavy on testing tabledrag functionality rather than the book ordering but we can refactor that once a tabledrag test exists. Nice to see proper JS testing happening.

Committed and pushed 7522a3d to 8.3.x and dc329cc to 8.2.x. Thanks!

Committed to 8.2.x to keep test coverage the same.

  • alexpott committed 7522a3d on 8.3.x
    Issue #2820079 by michielnugter, claudiu.cristea, droplet, dawehner,...

  • alexpott committed dc329cc on 8.2.x
    Issue #2820079 by michielnugter, claudiu.cristea, droplet, dawehner,...
dawehner’s picture

Sorry about the confusion and setting the issue back unnecessarily.

No worries. Its always good to talk about stuff.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.