diff --git a/mechanicalsoup/browser.py b/mechanicalsoup/browser.py index db0616b0..3fb66a6d 100644 --- a/mechanicalsoup/browser.py +++ b/mechanicalsoup/browser.py @@ -194,8 +194,10 @@ def _request(self, form, url=None, **kwargs): data.append((name, tag.text)) elif tag.name == "select": + # If the value attribute is not specified, the content will + # be passed as a value instead. options = tag.select("option") - selected_values = [i.get("value", "") for i in options + selected_values = [i.get("value", i.text) for i in options if "selected" in i.attrs] if "multiple" in tag.attrs: for value in selected_values: @@ -206,7 +208,8 @@ def _request(self, form, url=None, **kwargs): data.append((name, selected_values[-1])) elif options: # Selects the first option if none are selected - data.append((name, options[0].get("value", ""))) + first_value = options[0].get("value", options[0].text) + data.append((name, first_value)) if method.lower() == "get": kwargs["params"] = data diff --git a/mechanicalsoup/form.py b/mechanicalsoup/form.py index ee6a1b3d..ab28e8cc 100644 --- a/mechanicalsoup/form.py +++ b/mechanicalsoup/form.py @@ -193,9 +193,10 @@ def set_select(self, data): :param data: Dict of ``{name: value, ...}``. Find the select element whose *name*-attribute is ``name``. Then select from among its children the option element whose - *value*-attribute is ``value``. If the select element's - *multiple*-attribute is set, then ``value`` can be a list - or tuple to select multiple options. + *value*-attribute is ``value``. If no matching *value*-attribute + is found, this will search for an option whose text matches + ``value``. If the select element's *multiple*-attribute is set, + then ``value`` can be a list or tuple to select multiple options. """ for (name, value) in data.items(): select = self.form.find("select", {"name": name}) @@ -216,6 +217,16 @@ def set_select(self, data): for choice in value: option = select.find("option", {"value": choice}) + + # try to find with text instead of value + if not option: + option = select.find("option", string=choice) + + if not option: + raise LinkNotFoundError( + 'Option %s not found for select %s' % (choice, name) + ) + option.attrs["selected"] = "selected" def __setitem__(self, name, value): diff --git a/requirements.txt b/requirements.txt index 83a69311..1ebc4d7f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ requests >= 2.0 -beautifulsoup4 +beautifulsoup4 >= 4.4 six >= 1.4 lxml diff --git a/tests/test_form.py b/tests/test_form.py index acfc43bc..8db20429 100644 --- a/tests/test_form.py +++ b/tests/test_form.py @@ -458,5 +458,40 @@ def test_choose_submit_buttons(expected_post): assert res.status_code == 200 and res.text == 'Success!' +@pytest.mark.parametrize("fail, selected, expected_post", [ + pytest.param(False, 'with_value', [('selector', 'with_value')], + id='Option with value'), + pytest.param(False, 'Without value', [('selector', 'Without value')], + id='Option without value'), + pytest.param(False, 'We have a value here', [('selector', 'with_value')], + id='Option with value selected by its text'), + pytest.param(True, 'Unknown option', None, + id='Unknown option, must raise a LinkNotFound exception') +]) +def test_option_without_value(fail, selected, expected_post): + """Option tag in select can have no value option""" + text = """ +
+ + +
+ """ + browser, url = setup_mock_browser(expected_post=expected_post, + text=text) + browser.open(url) + browser.select_form() + if fail: + with pytest.raises(mechanicalsoup.utils.LinkNotFoundError): + browser['selector'] = selected + else: + browser['selector'] = selected + + res = browser.submit_selected() + assert res.status_code == 200 and res.text == 'Success!' + + if __name__ == '__main__': pytest.main(sys.argv)