moy: Looks good to me. 0-based index shouldn't be a problem for programmers. 
diff --git a/docs/ChangeLog.rst b/docs/ChangeLog.rst
index 1fd119d..cbaa569 100644
--- a/docs/ChangeLog.rst
+++ b/docs/ChangeLog.rst
@@ -23,7 +23,9 @@ Main changes:
 * We now have a documentation: https://mechanicalsoup.readthedocs.io/
 
 * ``StatefulBrowser.select_form`` can now be called without argument,
-  and defaults to ``"form"`` in this case.
+  and defaults to ``"form"`` in this case. It also has a new argument,
+  ``nr`` (defaults to 0), which can be used to specify the index of
+  the form to select if multiple forms match the selection criteria.
 
 * We now use requirement files. You can install the dependencies of
   Mechanicalsoup with e.g.::
diff --git a/mechanicalsoup/stateful_browser.py b/mechanicalsoup/stateful_browser.py
index 8d27bbc..5baf098 100644
--- a/mechanicalsoup/stateful_browser.py
+++ b/mechanicalsoup/stateful_browser.py
@@ -147,31 +147,32 @@ class StatefulBrowser(Browser):
         """
         return self.open(self.absolute_url(url), *args, **kwargs)
 
-    def select_form(self, selector="form", *args, **kwargs):
+    def select_form(self, selector="form", nr=0):
         """Select a form in the current page.
 
+        :param selector: CSS selector to identify the form to select.
+            If not specified, ``selector`` defaults to "form", which is
+            useful if, e.g., there is only one form on the page.
+            For ``selector`` syntax, see the `.select() method in BeautifulSoup
+            <https://www.crummy.com/software/BeautifulSoup/bs4/doc/#css-selectors>`__.
+        :param nr: A zero-based index specifying which form among those that
+            match ``selector`` will be selected. Useful when one or more forms
+            have the same attributes as the form you want to select, and its
+            position on the page is the only way to uniquely identify it.
+            Default is the first matching form (``nr=0``).
+
         :return: The selected form as a soup object. It can also be
             retrieved later with :func:`get_current_form`.
-
-        Arguments are the same as the select() method for a soup
-        object. ``selector`` is a string containing a CSS selector.
-
-        If the ``selector`` argument is not given, it defaults
-        to "form", so one can
-        call :func:`~mechanicalsoup.Browser.select_form()` to select
-        the form if there is only one form in the page.
-
-        See also: `.select() method in BeautifulSoup
-        <https://www.crummy.com/software/BeautifulSoup/bs4/doc/#css-selectors>`__
         """
-        found_forms = self.__current_page.select(selector, *args, **kwargs)
-        if len(found_forms) < 1:
+        # nr is a 0-based index for consistency with mechanize
+        found_forms = self.__current_page.select(selector, limit=nr + 1)
+        if len(found_forms) != nr + 1:
             if self.__debug:
-                print('select_form failed for', *args)
+                print('select_form failed for', selector)
                 self.launch_browser()
             raise LinkNotFoundError()
 
-        self.__current_form = Form(found_forms[0])
+        self.__current_form = Form(found_forms[-1])
         return self.__current_form
 
     def submit_selected(self, btnName=None, *args, **kwargs):

Implement nr option?
In MechanicalSoup, almost all of our methods that find some element return or operate on the first one that is found (`Form.choose_submit` appears to be the recently-modified exception). If, for example, you have a page with two `<form>...</form>` with no unique attributes, then you cannot select the second form with `StatefulBrowser.select_form`.

This problem is solved in Mechanize by giving these methods an `nr=1` argument, which selects the _nr_-th match, and the first by default. We could certainly add this to many of the methods and retain the current default behavior entirely. I think this alone would be a worthwhile enhancement.

One related (but perhaps separate) issue is the lack of feedback when you omit the `nr` option and get an element that you don't expect, likely resulting in an indecipherable error, or worse, silently incorrect behavior. In my ideal world, you'd specify `nr` when you know you want the _nr_-th match, and if `nr` is omitted you demand that there is only one match. This seems like the safest option, but may be too draconian. Perhaps it would be suitable for a "debug" or "strict" mode?
diff --git a/tests/test_stateful_browser.py b/tests/test_stateful_browser.py
index 58eb88d..19febf0 100644
--- a/tests/test_stateful_browser.py
+++ b/tests/test_stateful_browser.py
@@ -326,5 +326,20 @@ def test_with():
     assert browser.session is None
 
 
+def test_select_form_nr():
+    """Test the nr option of select_form."""
+    forms = """<form id="a"></form><form id="b"></form><form id="c"></form>"""
+    with mechanicalsoup.StatefulBrowser() as browser:
+        browser.open_fake_page(forms)
+        form = browser.select_form()
+        assert form.form['id'] == "a"
+        form = browser.select_form(nr=1)
+        assert form.form['id'] == "b"
+        form = browser.select_form(nr=2)
+        assert form.form['id'] == "c"
+        with pytest.raises(mechanicalsoup.LinkNotFoundError):
+            browser.select_form(nr=3)
+
+
 if __name__ == '__main__':
     pytest.main(sys.argv)

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-flake8 pytest-mock requests_mock
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
beautifulsoup4==4.12.3
certifi==2021.5.30
charset-normalizer==2.0.12
coverage==6.2
flake8==5.0.4
idna==3.10
importlib-metadata==4.2.0
iniconfig==1.1.1
lxml==5.3.1
mccabe==0.7.0
-e git+https://github.com/MechanicalSoup/MechanicalSoup.git@0d147eb88b3bffb4853b841b418924cbb569c4ea#egg=MechanicalSoup
packaging==21.3
pluggy==1.0.0
py==1.11.0
pycodestyle==2.9.1
pyflakes==2.5.0
pyparsing==3.1.4
pytest==7.0.1
pytest-cov==4.0.0
pytest-flake8==1.1.1
pytest-mock==3.6.1
requests==2.27.1
requests-mock==1.12.1
six==1.17.0
soupsieve==2.3.2.post1
tomli==1.2.3
typing_extensions==4.1.1
urllib3==1.26.20
zipp==3.6.0

name: MechanicalSoup
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - beautifulsoup4==4.12.3
      - charset-normalizer==2.0.12
      - coverage==6.2
      - flake8==5.0.4
      - idna==3.10
      - importlib-metadata==4.2.0
      - iniconfig==1.1.1
      - lxml==5.3.1
      - mccabe==0.7.0
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pycodestyle==2.9.1
      - pyflakes==2.5.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytest-cov==4.0.0
      - pytest-flake8==1.1.1
      - pytest-mock==3.6.1
      - requests==2.27.1
      - requests-mock==1.12.1
      - six==1.17.0
      - soupsieve==2.3.2.post1
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - zipp==3.6.0
prefix: /opt/conda/envs/MechanicalSoup

tests/test_stateful_browser.py::test_select_form_nr
tests/test_stateful_browser.py::flake-8::FLAKE8 tests/test_stateful_browser.py::test_submit_online tests/test_stateful_browser.py::test_form_noaction
tests/test_stateful_browser.py::test_request_forward tests/test_stateful_browser.py::test_no_404 tests/test_stateful_browser.py::test_404 tests/test_stateful_browser.py::test_user_agent tests/test_stateful_browser.py::test_open_relative tests/test_stateful_browser.py::test_links tests/test_stateful_browser.py::test_submit_btnName[input] tests/test_stateful_browser.py::test_submit_btnName[button] tests/test_stateful_browser.py::test_get_set_debug tests/test_stateful_browser.py::test_list_links tests/test_stateful_browser.py::test_launch_browser tests/test_stateful_browser.py::test_find_link tests/test_stateful_browser.py::test_verbose tests/test_stateful_browser.py::test_new_control tests/test_stateful_browser.py::test_form_noname tests/test_stateful_browser.py::test_form_multiple tests/test_stateful_browser.py::test_with
docs/ChangeLog.rst mechanicalsoup/stateful_browser.py
docs/ChangeLog.rst mechanicalsoup/stateful_browser.py