Repository: MechanicalSoup/MechanicalSoup

Issue:
[BUG] Browser does not follow `formaction` URLs found in buttons inside forms
As per the [w3/MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#formaction), browsers should use the URL found in the `formaction` element in buttons nested inside forms as opposed to the `action` element in said forms.

For a repro -- check the test in #455 


Discussion:

Patch:
diff --git a/mechanicalsoup/browser.py b/mechanicalsoup/browser.py
index dda98d97..771e080d 100644
--- a/mechanicalsoup/browser.py
+++ b/mechanicalsoup/browser.py
@@ -190,6 +190,13 @@ def get_request_kwargs(cls, form, url=None, **kwargs):
         """Extract input data from the form."""
         method = str(form.get("method", "get"))
         action = form.get("action")
+
+        # If the form has a submit button, use its form action
+        # https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#formaction.
+        button_submit_element = form.find("button")
+        if button_submit_element:
+            action = button_submit_element.get("formaction", action)
+
         url = urllib.parse.urljoin(url, action)
         if url is None:  # This happens when both `action` and `url` are None.
             raise ValueError('no URL to submit to')
diff --git a/mechanicalsoup/form.py b/mechanicalsoup/form.py
index 42b69498..39548ed2 100644
--- a/mechanicalsoup/form.py
+++ b/mechanicalsoup/form.py
@@ -359,12 +359,12 @@ def choose_submit(self, submit):
                 if found:
                     # Ignore submit element since it is an exact
                     # duplicate of the one we're looking at.
-                    del inp['name']
+                    inp.decompose()
                 found = True
             else:
                 # Delete any non-matching element's name so that it will be
                 # omitted from the submitted form data.
-                del inp['name']
+                inp.decompose()
 
         if not found and submit is not None and submit is not False:
             raise LinkNotFoundError(


Test patch:
diff --git a/tests/test_browser.py b/tests/test_browser.py
index a8047d29..013f1ef3 100644
--- a/tests/test_browser.py
+++ b/tests/test_browser.py
@@ -76,6 +76,43 @@ def test_get_request_kwargs_when_url_is_in_kwargs(httpbin):
         browser.get_request_kwargs(form, page.url, **kwargs)
 
 
+def test_get_request_kwargs_when_submit_element_has_formaction_attribute():
+    form_html = """
+        <form method="post" action="https://example.com/submit">
+            <button formaction="https://example.com/submit1"></button>
+        </form>
+    """
+    form = BeautifulSoup(form_html, "lxml").form
+    request_kwargs = mechanicalsoup.Browser.get_request_kwargs(
+        form, "https://example.com"
+    )
+    assert request_kwargs["url"] == "https://example.com/submit1"
+
+
+def test_get_request_kwargs_many_submit_elements_have_formaction_attribute():
+    form_html = """
+        <form method="post" action="https://example.com/submit">
+            <button formaction="https://example.com/submit1"></button>
+            <button formaction="https://example.com/submit2"></button>
+            <button formaction="https://example.com/submit3"></button>
+        </form>
+    """
+    browser = mechanicalsoup.StatefulBrowser()
+    browser.open_fake_page(form_html, url="https://example.com")
+    browser.select_form()
+    chosen_button = browser.form.form.find_all(
+        "button"
+    )[1]  # Choose the second button
+    browser.form.choose_submit(
+        submit=chosen_button
+    )
+    assert len(browser.form.form.find_all("button")) == 1
+    request_kwargs = mechanicalsoup.Browser.get_request_kwargs(
+        browser.form.form, "https://example.com"
+    )
+    assert request_kwargs["url"] == "https://example.com/submit2"
+
+
 def test__request(httpbin):
     form_html = f"""
     <form method="post" action="{httpbin.url}/post">