Problem/Motivation

The AssertContentTrait::assertOptionSelected() method is useless at the moment.
There is a @todo in the comments that suggests switching from id to name.

An example from Paragraphs:

$this->assertOptionSelected('edit-fields-field-paragraphs-settings-edit-form-settings-add-mode', 'button', 'Updated value is correct!.');

This line tries to assert that the button option on the current form has been selected with the previous drupalPostAjaxForm() call. It currently fails because an xpath query with //select[@id='edit-fields-field-paragraphs-settings-edit-form-settings-add-mode']//option[@value='button'] doesn't return any elements. It is missing some random string at the end:

'edit-fields-field-paragraphs-settings-edit-form-settings-add-mode--XYZXYZXYZX'

A couple of screenshots - the element id:

The query drupal tries to use:

The query that is necessary (with two dashes and the random string):

Proposed resolution

Switch from using the ID to find a field in a form to using the name of the field. This would be an API change. One possible alternative would be to change the xpath query to a partial match.

Remaining tasks

Discuss, provide patch, review and commit.

Provide patch: There is one in the comments, it only changes the assertOption methods, no tests yet.

User interface changes

None

API changes

assertOptionSelected(), assertNoOption(), assertNoOptionSelected() use the name of the field instead of the id.
OR
None / behaviour of assertOption...() changes.

Data model changes

None

Comments

LKS90’s picture

Issue summary: View changes
Status: Active » Needs review
StatusFileSize
new5.19 KB

Here is a patch that implements the @todo from the comment.

It switches from using the id of the form field to the name. No random characters in the field name like in the ID.

I made the paragraphs test pass easily by implementing this and then switching the id with the name in the tests.

Edit: I also provided a patch for the paragraphs module which depends on this issue. You can find it here.

LKS90’s picture

Issue summary: View changes

Status: Needs review » Needs work

The last submitted patch, 1: assertoptionselected-2530092-1.patch, failed testing.

LKS90’s picture

Status: Needs work » Needs review
StatusFileSize
new5.19 KB

Forgot one @id in the SafeMarkup::format call.

Status: Needs review » Needs work

The last submitted patch, 4: assertoptionselected-2530092-4.patch, failed testing.

LKS90’s picture

Title: AssertOptionSelected() is useless for testing. » AssertOptionSelected() does not work for dynamic html ids
Issue summary: View changes
LKS90’s picture

Title: AssertOptionSelected() does not work for dynamic html ids » AssertOptionSelected() does not work for dynamic HTML IDs
Issue summary: View changes
LKS90’s picture

StatusFileSize
new0 bytes

New version, this one replaces the select[@id=:id] with select[contains(@id, :id)], so a partial match is also returned (and the random characters are not necessary.). Paragraphs tests pass with this one, should also pass the core tests.

LKS90’s picture

Status: Needs work » Needs review
StatusFileSize
new3.24 KB

A non-empty patch this time.

berdir’s picture

+++ b/core/modules/simpletest/src/AssertContentTrait.php
@@ -1160,7 +1160,7 @@ protected function assertNoFieldChecked($id, $message = '', $group = 'Browser')
   protected function assertOption($id, $option, $message = '', $group = 'Browser') {
-    $options = $this->xpath('//select[@id=:id]//option[@value=:option]', array(':id' => $id, ':option' => $option));
+    $options = $this->xpath('//select[contains(@id, :id)]//option[@value=:option]', array(':id' => $id, ':option' => $option));
     return $this->assertTrue(isset($options[0]), $message ? $message : SafeMarkup::format('Option @option for field @id exists.', array('@option' => $option, '@id' => $id)), $group);
   }

Starts with might be a better option, to avoid false positives?

Maybe even an option to explicitly make it starts-with, not sure...

dawehner’s picture

Instead of using IDs we should promote assertion methods which deal which use the data-drupal-selector bits.

LKS90’s picture

StatusFileSize
new3.78 KB
new3.76 KB

Switched to starts-with, also removed an additional @todo that was in the drupal-selector method and basically just wrong.

berdir’s picture

Status: Needs review » Needs work

Can we try what happens if we just switch to data-drupal-selector?

LKS90’s picture

Status: Needs work » Needs review
StatusFileSize
new3.6 KB

This patch is made from scratch, no interdiff.
For the no versions, I copied the function itself and replaced assertTrue with assertFalse, otherwise I just call the data-drupal-selector variant. Works in Paragraphs.

Status: Needs review » Needs work

The last submitted patch, 14: assertoptionselected-2530092-14.patch, failed testing.

LKS90’s picture

Status: Needs work » Needs review
StatusFileSize
new13.66 KB
new10.06 KB

Here is a patch that also updates one test (FilterFormTest) which fails with the changes from above.
The changes are of the same nature as the initial patch, switching from using $id/@id/:id to the data-drupal-selector.

LKS90’s picture

StatusFileSize
new13.98 KB
new1.66 KB

Here is an updated version, I forgot to update some of the documentation and one of the replacement variable names.

No functional changes, so the testbot run of the previous patch still applies.

sasanikolic’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/filter/src/Tests/FilterFormTest.php
    @@ -253,18 +253,16 @@ protected function assertOptions($id, array $expected_options, $selected) {
    +    $select = $this->xpath('//select[@data-drupal-selector=:data_drupal_selector]', [':data_drupal_selector' => $drupal_selector]);
    

    Here we don't need to check if it contains 'required'?

  2. +++ b/core/modules/simpletest/src/AssertContentTrait.php
    @@ -1163,8 +1163,7 @@ protected function assertNoFieldChecked($id, $message = '', $group = 'Browser')
    +    return $this->assertOptionWithDrupalSelector($id, $option, $message, $group);
    
    @@ -1213,9 +1212,9 @@ protected function assertOptionWithDrupalSelector($drupal_selector, $option, $me
    +    $options = $this->xpath('//select[@data-drupal-selector=:data_drupal_selector]//option[@value=:option]', array(':data_drupal_selector' => $id, ':option' => $option));
    +    return $this->assertFalse(isset($options[0]), $message ? $message : SafeMarkup::format('Option @option for field @data_drupal_selector exists.', array('@option' => $option, '@data_drupal_selector' => $id)), $group);
    
    @@ -1241,8 +1240,7 @@ protected function assertNoOption($id, $option, $message = '', $group = 'Browser
    +    return $this->assertOptionSelectedWithDrupalSelector($id, $option, $message, $group);
    
    @@ -1293,8 +1291,8 @@ protected function assertOptionSelectedWithDrupalSelector($drupal_selector, $opt
    +    $elements = $this->xpath('//select[@data-drupal-selector=:data_drupal_selector]//option[@value=:option]', array(':data_drupal_selector' => $id, ':option' => $option));
    +    return $this->assertFalse(isset($elements[0]) && !empty($elements[0]['selected']), $message ? $message : SafeMarkup::format('Option @option for field @data_drupal_selector is selected.', array('@option' => $option, '@data_drupal_selector' => $id)), $group);
    

    We should change the $id also here to $drupal_selector, i guess?

  3. +++ b/core/modules/simpletest/src/AssertContentTrait.php
    @@ -1213,9 +1212,9 @@ protected function assertOptionWithDrupalSelector($drupal_selector, $option, $me
    +
    

    Empty line.

LKS90’s picture

Status: Needs work » Needs review
StatusFileSize
new16.41 KB
new6.67 KB

Readded the check for the required attribute, switched $ids which were data-drupal-selectors to $drupal_selectors, removed useless empty line. The interdiff is a bit different, it's a diff of patch 17 and 19 instead of using git diff.

Now I'm starting to wonder why we have those functions twice, except to keep contrib modules from failing. I'd rather switch to $name instead of $id, and update all core tests to only use the ...WithDrupalSelector() variant to lead as a good example. Or we replace the current assertOption() method with the ...WithDrupalSelector() variant and force contrib modules to update as well?

LKS90’s picture

StatusFileSize
new17.54 KB
new1.49 KB

Spotted another change that modifies the behaviour of a function instead of replacing the function as I meant to do.

Status: Needs review » Needs work

The last submitted patch, 20: assertoptionselected-2530092-20.patch, failed testing.

LKS90’s picture

Status: Needs work » Needs review
StatusFileSize
new119.04 KB
new2.58 KB

Accidentally reverted the wrong function, fixed now.

LKS90’s picture

StatusFileSize
new17.55 KB
new2.58 KB

Only pulled, forgot to actually rebase the branch... :D

juanse254’s picture

Looks good to me :).

giancarlosotelo’s picture

Status: Needs review » Reviewed & tested by the community

The last submitted patch, 8: assertoptionselected-2530092-8.patch, failed testing.

xjm’s picture

Status: Reviewed & tested by the community » Needs work

Nice find!

  1. +++ b/core/modules/simpletest/src/AssertContentTrait.php
    @@ -1216,15 +1215,15 @@ protected function assertOption($id, $option, $message = '', $group = 'Browser')
    -    $options = $this->xpath('//select[@data-drupal-selector=:data_drupal_selector]//option[@value=:option]', array(':data_drupal_selector' => $drupal_selector, ':option' => $option));
    -    return $this->assertTrue(isset($options[0]), $message ? $message : SafeMarkup::format('Option @option for field @data_drupal_selector exists.', array('@option' => $option, '@data_drupal_selector' => $drupal_selector)), $group);
    +    $options = $this->xpath('//select[@data-drupal-selector=:data_drupal_selector]//option[@value=:option]', [':data_drupal_selector' => $drupal_selector, ':option' => $option]);
    +    return $this->assertTrue(!empty((string) $options[0]), $message ? $message : SafeMarkup::format('Option @option for field @data_drupal_selector exists.', array('@option' => $option, '@data_drupal_selector' => $drupal_selector)), $group);
    

    Why is it necessary/correct to switch to the string cast here instead of the isset check? We didn't do that elsewhere in the patch. An inline comment would be good if there is a reason it's different.

  2. +++ b/core/modules/simpletest/src/AssertContentTrait.php
    @@ -1320,9 +1318,9 @@ protected function assertOptionSelectedWithDrupalSelector($drupal_selector, $opt
    -  protected function assertNoOptionSelected($id, $option, $message = '', $group = 'Browser') {
    -    $elements = $this->xpath('//select[@id=:id]//option[@value=:option]', array(':id' => $id, ':option' => $option));
    -    return $this->assertTrue(isset($elements[0]) && empty($elements[0]['selected']), $message ? $message : SafeMarkup::format('Option @option for field @id is not selected.', array('@option' => $option, '@id' => $id)), $group);
    +  protected function assertNoOptionSelected($drupal_selector, $option, $message = '', $group = 'Browser') {
    +    $elements = $this->xpath('//select[@data-drupal-selector=:data_drupal_selector]//option[@value=:option]', array(':data_drupal_selector' => $drupal_selector, ':option' => $option));
    +    return $this->assertFalse(isset($elements[0]) && !empty($elements[0]['selected']), $message ? $message : SafeMarkup::format('Option @option for field @data_drupal_selector is selected.', array('@option' => $option, '@data_drupal_selector' => $drupal_selector)), $group);
    

    This is changing the logic of the assertion in a pretty confusing way... why? I think the result is also changing for the condition where the element is not set. Before, that would trigger a fail; now, it will trigger a pass. I think; I could have gotten confused though while drawing the truth table in my head because of the compound negatives. An inline comment would be good and I feel like we also need more test coverage for this assertion's behavior itself for all four possible combinations. (Well, all three that is; the 'selected' obviously must be empty if the parent element is not set.)

I think this would need a small change record for those using this assertion, also.

xjm’s picture

BTW, for reviewers, I highly recommending doing a word diff on this one.

xjm’s picture

Issue tags: +Needs change record

Forgot the tag.

LKS90’s picture

Status: Needs work » Needs review
StatusFileSize
new16.55 KB
new3.53 KB

Hey there!
Thanks for the review, here is an updated patch.
I reverted both changes as they are unnecessary. The first change (with the string cast) is pointless, the xpath query already looks for the value of the form element, so if the element doesn't exist, the options array/SimpleXMLElement would be empty. I checked for the value, which would be (an) empty (string) in case the element doesn't exist, a bit too much probably. The second change is simply switching the logic around, I reverted that one as well.

I also found some text which I updated incorrectly, corrected that as well.
The patch should be easier to review now :).

I also made a change record, it's not published yet and probably needs some feedback as well (my first change record).

Status: Needs review » Needs work

The last submitted patch, 30: assertoptionselected-2530092-30.patch, failed testing.

The last submitted patch, 30: assertoptionselected-2530092-30.patch, failed testing.

LKS90’s picture

StatusFileSize
new16.56 KB
new1.12 KB

I broke the xpath query in the last patch. Here is the fix for that. This patch should pass the tests, should be easy to review and the scope is minimized to only modify things that are necessary.

juanse254’s picture

Status: Needs work » Needs review

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

johnchque’s picture

Assigned: Unassigned » johnchque
StatusFileSize
new2.29 KB

It seems we had a @todo there. I tried using the function in a contrib module but it does not work with dynamic html ids. Added the assertOptionSelectedWithName function. Should we also add something like assertOptionSelectedWithId? Also not sure if this should go to 8.2.x.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

quietone’s picture

Component: simpletest.module » phpunit

Triaging issues in simpletest.module as part of the Bug Smash Initiative to determine if they should be in the Simpletest Project or core.

This looks like it a Phpunit issue, changing component.

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

smustgrave’s picture

Status: Needs review » Postponed (maintainer needs more info)

This looks like it was already deprecated in https://www.drupal.org/project/drupal/issues/3114617 in favor of

$this->assertSession()->optionExists() instead and check the "selected" attribute yourself.

Should this be closed as a duplicate?

berdir’s picture

Status: Postponed (maintainer needs more info) » Closed (outdated)
Issue tags: -Needs change record

I'd say outdated it is, we didn't fix the problem, we just removed the problematic method.