Problem/Motivation

Sometimes we need to inspect hidden fields on forms but Mink fail to recognise those fields in \Behat\Mink\WebAssert::fieldExists(). It's just throwing an ElementNotFoundException. This happens because Mink observe fields like a human using the browser. His search is reaching only human visible fields and WebAssert::fieldExists() simply filters out hidden fields from the search. While manipulating hidden fields can be done in JavascriptTestBase with JavaScript, simply inspecting them should be made possible in BrowserTestBase.

Proposed resolution

While keeping the WebAssert::fieldExists() at his current behaviour, implement dedicated methods foe hidden fields inspeection.

Remaining tasks

None.

User interface changes

None.

API changes

New methods dedicated to hidden fields in WebAssert:

  • ::hiddenFieldExists()
  • ::hiddenFieldNotExists()
  • ::hiddenFieldValueEquals()
  • ::hiddenFieldValueNotEquals()

Data model changes

None.

Comments

claudiu.cristea created an issue. See original summary.

claudiu.cristea’s picture

Status: Active » Needs review
claudiu.cristea’s picture

StatusFileSize
new0 bytes

Status: Needs review » Needs work

The last submitted patch, 3: browsertestbase_hiddenfields-test-only.patch, failed testing.

claudiu.cristea’s picture

Status: Needs work » Needs review
StatusFileSize
new1.39 KB

Ups.

claudiu.cristea’s picture

Status: Needs review » Needs work

The last submitted patch, 5: browsertestbase_hiddenfields-test-only.patch, failed testing.

claudiu.cristea’s picture

Status: Needs work » Needs review
StatusFileSize
new4.42 KB
new4.44 KB

Patch.

claudiu.cristea’s picture

Priority: Normal » Major

I guess this is Major for testing framework as it blocks other test conversions.

claudiu.cristea’s picture

StatusFileSize
new4.42 KB
new804 bytes

Small naming fix.

The last submitted patch, 8: 2767655-8.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 10: 2767655-10.patch, failed testing.

claudiu.cristea’s picture

Status: Needs work » Needs review
StatusFileSize
new1.45 KB
new4.55 KB

Refined our implementation of fieldExists() letting also 'named_exact' selector to run.

Status: Needs review » Needs work

The last submitted patch, 13: 2767655-13.patch, failed testing.

claudiu.cristea’s picture

Status: Needs work » Needs review
StatusFileSize
new4.61 KB

Hm. Interdiff is from #13.

claudiu.cristea’s picture

StatusFileSize
new4.51 KB
new1.47 KB

Optimize more fieldExists().

jibran’s picture

+++ b/core/tests/Drupal/Tests/PartialNamedIncludingHiddenSelector.php
@@ -0,0 +1,22 @@
+class PartialNamedIncludingHiddenSelector extends PartialNamedSelector {

Can we push this change upstream?

claudiu.cristea’s picture

@jibran

Push where, In Mink repo? Their philosophy is to search for visible fields. I don't think such a selector will be accepted. But, by architecture. they are flexible. That's way we could add the new selector.

claudiu.cristea’s picture

Status: Needs review » Needs work

Hm. It's not enough to allow BrowserTestBase::drupalPostForm() to use hidden fields. Other assertions are suffering too. For example $this->assertSession()->fieldValueEquals() & Co. They need to be able to search for hidden fields too.

EDIT: Shouldn't we make WebAssert::fieldExists() and WebAssert::fieldNotExists() take into account hidden fields without any condition (w/o passing the flag as parameter)?

claudiu.cristea’s picture

StatusFileSize
new4.5 KB
new2.48 KB

I guess we need to expand the scope of this issue by extending WebAssert::fieldExists() and WebAssert::fieldNotExists() to hidden fields. Lets' see if is working

claudiu.cristea’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 20: 2767655-20.patch, failed testing.

claudiu.cristea’s picture

Status: Needs work » Needs review
StatusFileSize
new4.56 KB

Sorry, bad patch. Interdiff correct in #20.

claudiu.cristea’s picture

@jibran, I published also a PR upstream https://github.com/minkphp/Mink/pull/711. But this should not be a blocker for this issue.

larowlan’s picture

I was kind of against this, given there is no valid use-case for editing a hidden field except in a JavaScript scenario.

But the code is low impact and helps us with writing tests for things like nefarious dom manipulation.

+1 RTBC from me.

claudiu.cristea’s picture

Title: BrowserTestBase::drupalPostForm() cannot post hidden fields » Allow WebAssert field finder to catch also hidden fields

The patch was expanded to field finders (WebAssert::fieldExists() and WebAssert::fieldNotExists()). Changed the title to reflect that.

dawehner’s picture

Component: base system » phpunit

Thank you for fixing this problem! I moved it to the phpunit component, as it kinda belongs more into that area.

  1. +++ b/core/tests/Drupal/Tests/BrowserTestBase.php
    @@ -313,7 +314,10 @@ protected function initMink() {
    +    $selectors_handler = new SelectorsHandler([
    +      'named_partial_including_hidden' => new PartialNamedIncludingHiddenSelector()
    +    ]);
    +    $session = new Session($driver, $selectors_handler);
    

    Could we maybe document with a one liner, why we need it? "Per default hidden fields are not available".

  2. +++ b/core/tests/Drupal/Tests/PartialNamedIncludingHiddenSelector.php
    @@ -0,0 +1,22 @@
    +/**
    + * Extends PartialNamedSelector to allow retrieval of hidden fields.
    + *
    + * @see \Behat\Mink\Selector\PartialNamedSelector
    + */
    +class PartialNamedIncludingHiddenSelector extends PartialNamedSelector {
    +
    

    Just in general, did someone checked out whether this is something mink would consider to have upstream, or maybe even be fixed as default behaviour upstream?

  3. +++ b/core/tests/Drupal/Tests/WebAssert.php
    @@ -422,4 +422,39 @@ public function fieldDisabled($field, TraversableElement $container = NULL)  {
    +   * {@inheritdoc}
    +   */
    +  public function fieldExists($field, TraversableElement $container = NULL) {
    +    try {
    +      return parent::fieldExists($field, $container);
    +    }
    +    catch (ElementNotFoundException $exception) {
    +      // Try once again with the 'named_partial_including_hidden' selector to
    +      // get potential hidden fields.
    +      $container = $container ?: $this->session->getPage();
    +      if ($node = $container->find('named_partial_including_hidden', ['field', $field])) {
    +        return $node;
    +      }
    +      throw $exception;
    +    }
    +  }
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function fieldNotExists($field, TraversableElement $container = NULL) {
    +    try {
    +      parent::fieldNotExists($field, $container);
    +    }
    +    catch (ExpectationException $exception) {
    +      // Try once again with the 'named_partial_including_hidden' selector to
    +      // get potential hidden fields.
    +      $container = $container ?: $this->session->getPage();
    +      if ($container->find('named_partial_including_hidden', ['field', $field])) {
    +        throw $exception;
    +      }
    +    }
    +  }
    +
    

    I'm wondering whether we should instead change out the existing selectors, by passing along ['named_partial' => new PartialNamedIncludingHiddenSelector()] above? This would allow us potentially to not add all this custom code below.

claudiu.cristea’s picture

StatusFileSize
new3.32 KB
new2.38 KB

@dawehner, thank you for review

#27.1: Done,

#27.2: I opened a PR upstream https://github.com/minkphp/Mink/pull/711. There's a discussion about the opportunity to have that in Mink. They seem reluctant.

#27.3: Yes, we can. The reason why I didn't went to that solution from the beginning (it would have been the straight way) is that I wanted to keep also the native Mink ability to filter out hidden fields. I don't know, maybe we need to create our own methods in WebAssert for searching also hidden fields and let those 2 methods untouched so we have both alternatives. Their way is based (I think) on the assumption that a field must be a human visible field.

dawehner’s picture

Ideally though I agree with the points that we should not expose a feature normal browser users don't have per default to our tests? Maybe just adding this capability to javascript tests seems to be a better approach. I also left a small comment on the github issue.

claudiu.cristea’s picture

@dawehner, hm, but what about BrowserTestBase tests? They are coming from WebTestBase. The book pages ordering is such a case where we are using a non-JS browser but we are emulating a JS behaviour by manipulating (in behalf of JS) the hidden fields. Probably we'll have to move those tests as JS tests?

dawehner’s picture

@claudiu.cristea
I mean in this example its kind of obvious. Reordering books is a clear JS feature, so they should be tested in JS, rather than faked using PHP.

klausi’s picture

I'm not sure we should do this. If the use case is only faking Javascript by manipulating hidden fields, then we should not do this and convert the test to a proper javascript test instead.

Can you provide some other use cases in the issue summary where we would want to manipulate hidden fields? @larowlan mentioned nefarious DOM manipulation, but why would you do that in a functional test?

dawehner’s picture

@klausi
Yeah I still believe having a WebAssert for JS, as we actually already have in \Drupal\FunctionalJavascriptTests\JSWebAssert and put it on there is conceptually better.

alexpott’s picture

There's another related issue - how about you want to assert on the value of a hidden input? $this->assertFieldById() does not work on hidden fields because of this.

alexpott’s picture

So $this->assertSession()->fieldValueEquals() does not work either

claudiu.cristea’s picture

@alexpott, Probably we want some dedicated assert methods only for hidden fields to cover such cases. ::assertHiddenFieldById(), $this->assertSession()->hiddenFieldValueEquals()? Then we can keep the actual behaviour for existing methods

EDIT: @alexpott, The patch from #23 can be a starting point.

claudiu.cristea’s picture

Version: 8.2.x-dev » 8.1.x-dev
StatusFileSize
new7.25 KB
new8.36 KB

Here's a try that provides dedicated methods in WebAssert that work only for hidden fields inspection. Given to #34, I think this is reasonable to have in Drupal BrowserTestBase. Inspection not manipulation.

claudiu.cristea’s picture

Title: Allow WebAssert field finder to catch also hidden fields » Allow WebAssert to inspect also hidden fields
Issue summary: View changes
StatusFileSize
new7.27 KB
new1.98 KB

Typo, nits, IS.

dawehner’s picture

Strictly speaking this is no BC layer, should we try to add one or talk about that in another issue?

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

@alexpott, Probably we want some dedicated assert methods only for hidden fields to cover such cases. ::assertHiddenFieldById(), $this->assertSession()->hiddenFieldValueEquals()? Then we can keep the actual behaviour for existing methods

I like this approach, less magic and more explicit code!

effulgentsia’s picture

Issue tags: +rc eligible

Discussed with @xjm and @catch, and we decided this is "rc eligible". The one hesitation we have about that is that it's not only changing tests, but also making an addition to a testing API: WebAssert could in theory be subclassed, and contrib/custom code might have such a subclass with these method names and collide. But, we think the risk of this actually being the case is sufficiently low, and the disruption for such a project/site wouldn't be breaking live code, but only a CI workflow, until fixed.

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.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs work
+++ b/core/modules/simpletest/tests/src/Functional/BrowserTestBaseTest.php
@@ -61,6 +61,17 @@ public function testForm() {
+    $this->assertSession()->hiddenFieldExists('strawberry'); // By name.
+    $this->assertSession()->hiddenFieldExists('red'); // By value.
+    $this->assertSession()->hiddenFieldExists('redstrawberryhiddenfield'); // By id.

I'm pretty sure that this types of comments are no allowed in our coding standards - also 80 chars...

Needs a reroll - doesn't apply to 8.3.x - which is where the patch needs to be committed first.

claudiu.cristea’s picture

Version: 8.2.x-dev » 8.3.x-dev
Status: Needs work » Reviewed & tested by the community
StatusFileSize
new7.18 KB
new1.01 KB

As is only a tiny docs change and reroll, I set it back to RTBC as per #41.

  • catch committed 5c93d7e on 8.3.x
    Issue #2767655 by claudiu.cristea, dawehner, alexpott: Allow WebAssert...
catch’s picture

Version: 8.3.x-dev » 8.2.x-dev
Status: Reviewed & tested by the community » Patch (to be ported)

Fixed this on commit:

core/tests/Drupal/Tests/WebAssert.php
----------------------------------------------------------------------
FOUND 4 ERRORS AFFECTING 2 LINES
----------------------------------------------------------------------
 478 | ERROR | [x] Concat operator must be surrounded by a single
     |       |     space
 478 | ERROR | [x] Concat operator must be surrounded by a single
     |       |     space
 499 | ERROR | [x] Concat operator must be surrounded by a single
     |       |     space
 499 | ERROR | [x] Concat operator must be surrounded by a single
     |       |     space
----------------------------------------------------------------------
PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------

Time: 66ms; Memory: 6Mb

We should discuss whether to cherry-pick this to 8.2.x, so leaving at 'to be ported' there.

larowlan’s picture

Status: Patch (to be ported) » Needs review
StatusFileSize
new7.2 KB

8.2.x backport

claudiu.cristea’s picture

Status: Needs review » Reviewed & tested by the community

Here I'm RTBCing only the port to 8.2.x, not the patch itself. The patch has been already reviewed.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 48: 2767655-hidden-fields.48.patch, failed testing.

  • catch committed 5c93d7e on 8.4.x
    Issue #2767655 by claudiu.cristea, dawehner, alexpott: Allow WebAssert...

  • catch committed 5c93d7e on 8.4.x
    Issue #2767655 by claudiu.cristea, dawehner, alexpott: Allow WebAssert...

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.

berdir’s picture

Status: Needs work » Fixed

I have a feeling that this is not going to be backported to 8.2.x anymore :)

Struggled a bit to get this working with drupalPostForm(), what worked is using hiddenFieldExists() to get the hidden form elements and then setValue() on them, and then doing a drupalPostForm() only with the remaining visible form elements.

Status: Fixed » Closed (fixed)

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

hermann77’s picture

Issue tags: -

I have no luck with drupalPostForm() so my way to test forms with hidden fields is like this:

$this->getSession()
          ->getPage()
          ->find('css', "input[name='tagging_my_tags_hidden']")
          ->setValue('TestTag1, TestTag2');

$this->getSession()
      ->getPage()
      ->find('css', "input[name='title']")
      ->setValue('TestTitle111');

$this->getSession()->getPage()->pressButton(t('Save'));