ux icon indicating copy to clipboard operation
ux copied to clipboard

[Autocomplete] How to submit data in functional tests

Open benr77 opened this issue 2 years ago • 9 comments

If I have a functional test using WebTestCase, I can submit a form from the test. This was working fine for my own home-grown autocomplete solution, but today I upgraded and replaced my antiquated solution with UX Autocomplete.

The first hurdle was changing the form field in the test from:

enquiry[contact]

to

enquiry[contact][autocomplete]

I understand that there is a subscriber which re-maps the child back on to the parent, so this makes sense.

However, when I then run my tests again, with valid ID values for entities that can be returned by the autocomplete, I get errors such as:

InvalidArgumentException: Input "enquiry[contact][autocomplete]" cannot take "1" as a value (possible values: "")

Clearly this is something to do with the form not knowing what values are permissible, but how do I get around this and allow the test to submit the form successfully without Javascript?

Thanks

benr77 avatar Feb 01 '23 18:02 benr77

Hi, this is for easy admin, but it should work here too. You need to disable validation for the autocomplete field before submitting the form.

$location = LocationFactory::createOne(['name' => 'My new location']);

$form = $crawler->filter('button[type="submit"][value="saveAndReturn"]')->form([], 'POST');
// without js autocomplete selects are empty, so we need to disable validation
$form['entity[location][autocomplete]']->disableValidation();
self::$client->submit($form, [
    'entity[location][autocomplete]' => $location->getId(),
]);

1ed avatar Feb 03 '23 13:02 1ed

I'm curious is that works. But also, if that's what's needed, that's gross (this is not a comment about your code @1ed - just the fact that you would NEED that code).

It's weird enough that we need to change from to add the [autocomplete], though that is likely unavoidable (but should probably be documented). But you should be able to, I would think, just put "1" into the field... as that's exactly what the frontend JS does. So I would have tried exactly what @benr77 did... and so I'm surprised it didn't work.

Btw, we actually DO have a test case in core that, I believe, does exactly what @benr77 is doing, and that works fine. Here it is: https://github.com/symfony/ux/blob/5315ca00c42c035d89a6db5b33114dfab896add5/src/Autocomplete/tests/Functional/AutocompleteFormRenderingTest.php#L56-L60

Cheers!

weaverryan avatar Feb 05 '23 22:02 weaverryan

@weaverryan the validation done the Crawler (which validates that the user input is supported by the HTML, or something like that), but $browser uses a different method (just creates a POST request). Why the [autocomplete] field is needed? I don't remember.

P.S. ->disableValidation() can be called on the form too, maybe a little bit better.

1ed avatar Feb 05 '23 22:02 1ed

I can get it working by following the same method as used in the test that Ryan has pointed out. However, then I'm immediately falling foul of CSRF protection as we're not sending the token in the post() request.

This also means I can't use any of the default form field values, and have to specify all of them in the test. This is not ideal with a big form - I only want to specify the fields that the test is interested in.

I have managed to get it working by using the DOM to change the value of the placeholder option to the one I want to submit, and then submitting the form (which then automatically sends all the other default values).

->use(function(Browser $browser, Crawler $crawler) use ($newId)
{
    $crawler->filter('#id-of-select-field')->each(function (Crawler $crawler) use ($newId)
    {
        /** @var DOMElement $option */
        foreach ($crawler->children() as $option)
        {
            $option->setAttribute('value', $newId);
        }
    });
})
->click('Save Changes')

I guess you could also add a new <option> and give it the selected attribute, rather than change the placeholder option.

I have no idea if this is the best way of doing it but it seems to work.

Any thoughts?

benr77 avatar Feb 23 '23 09:02 benr77

BTW I'm using AJAX lookups for this - so all the options are not presented in the HTML on page load.

benr77 avatar Feb 23 '23 09:02 benr77

Thank you for this issue. There has not been a lot of activity here for a while. Has this been resolved?

carsonbot avatar Apr 25 '24 12:04 carsonbot

As far as I am aware this issue is not yet resolved.

benr77 avatar Apr 25 '24 12:04 benr77

Thank you for this issue. There has not been a lot of activity here for a while. Has this been resolved?

carsonbot avatar Oct 26 '24 12:10 carsonbot

I think the CSRF issue has been resolved in a recent release, but as for the general issue I think it's still unresolved.

Please keep the issue open for now.

benr77 avatar Oct 26 '24 13:10 benr77

@benr77 Check https://github.com/symfony/ux/issues/1334#issuecomment-1896659252 for a working example

simondaigre avatar Nov 30 '24 20:11 simondaigre

If you're experiencing issues with submitForm in functional tests using Symfony's KernelBrowser, you can work around it by directly interacting with the form fields and submitting the form manually. Below is an example:

$form = $this->client->getCrawler()->selectButton('transaction_form')->form();

// Disabling validation for the autocomplete field
$form['transaction_form[user]']->disableValidation()->setValue($user->id->toString());
$form['transaction_form[phoneNumber]']->setValue('some phone number');
$form['transaction_form[amount]']->setValue('5');

// Submit the form manually
$this->client->submit($form);

The disableValidation method is marked as internal. Your editor may not detect it, and tools like PHPStan might flag it as an error. However, it works in practice and can be safely used in this context. If necessary, you can suppress the PHPStan error for this specific line or adjust your PHPStan configuration.

bernard-ng avatar Jan 26 '25 05:01 bernard-ng

I have some other ajax based fields where I workaround with adding hidden fields to the DOM:

trait CrawlerFormAddHiddenFieldsTrait
{
    /**
     * @param string[] $additionalHiddenFields
     */
    protected static function selectFormWithAdditionalHiddenFields(Crawler $crawler, string $selectButtonValue, array $additionalHiddenFields): Form
    {
        $form = $crawler->selectButton($selectButtonValue)->form();

        foreach ($additionalHiddenFields as $name) {
            /** @var \DOMDocument $document */
            $document = $form->getFormNode()->ownerDocument;
            $hiddenField = $document->createElement('input');
            Assert::notFalse($hiddenField);
            $hiddenField->setAttribute('type', 'hidden');
            $hiddenField->setAttribute('name', $name);
            $hiddenField->setAttribute('value', '');
            $form->getFormNode()->appendChild($hiddenField);
        }

        return $crawler->selectButton($selectButtonValue)->form(); // reselect form to get updated DOM with hidden fields
    }
}

Usage:

       $form = static::selectFormWithAdditionalHiddenFields($crawler, 'Weiter', [
            'my_form[my_field]',
        ]);

        $client->submit($form, [
            'my_form[my_field]' => $uuid,
        ]);

Arrays:

        $form = static::selectFormWithAdditionalHiddenFields($crawler, 'Speichern', [
            'my_form[my_field][contacts][0]',
            'my_form[my_field][contacts][1]',
            'my_form[my_field][contacts][2]',
            'my_form[my_field][contacts][3]',
        ]);

        $client->submit($form, [
            'my_form[my_field][contacts][0]' => $uuid1,
            'my_form[my_field][contacts][1]' => $uuid2,
            'my_form[my_field][contacts][2]' => $uuid3,
            'my_form[my_field][contacts][3]' => $uuid4,
        ]);

alexander-schranz avatar Mar 21 '25 13:03 alexander-schranz

Thank you for this issue. There has not been a lot of activity here for a while. Has this been resolved?

carsonbot avatar Sep 22 '25 12:09 carsonbot

Friendly ping? Should this still be open? I will close if I don't hear anything.

carsonbot avatar Oct 06 '25 12:10 carsonbot

Hey,

I didn't hear anything so I'm going to close it. Feel free to comment if this is still relevant, I can always reopen!

carsonbot avatar Oct 20 '25 12:10 carsonbot