mirror of
https://github.com/Second-Hand-Friends/kleinanzeigen-bot.git
synced 2026-03-12 02:31:45 +01:00
feat: Improved WebSelect Handling: Added Combobox Support, Enhanced Element Detection, and Smarter Option Matching (#679)
## ℹ️ Description Added Webselect-Function for Input/Dropdown Combobox PR for issue/missing feature #677 # Fixes / Enhancements Finding Special Attributes Elements can fail because they are currently only selected using the name="..." attributes of the HTML elements. If it fails, ALSO fallback-handle selecting special attribute HTML elements by ID instead / additionally. (For example the "brands" Input/Combobox for Mens Shoes... When trying to select a Value in a <select>, it does not only rely on the actual Option value (xxx in the example <options value="xxx">yyy</...>) but instead also on the displayed HTML value (i.e. yyy in above example). This improves UX because the User doesnt have to check the actual "value" of the Option but instead can check the displayed Value from the Browsers Display directly. Testcases for Webselect_Combobox were not added due to missing knowledge about Async Mocking properly. ## 📋 Changes Summary ✅ Fixes & Enhancements - New WebSelect Functionality - Improved Element Detection for Special Attributes - Enhanced <select> Option Matching Logic This improves UX and test robustness — users no longer need to know the exact underlying value, as matching also works with the visible label shown in the browser. 🧩 Result These updates make dropdown and combobox interactions more intuitive, resilient, and user-friendly across diverse HTML structures. ### ⚙️ Type of Change Select the type(s) of change(s) included in this pull request: - [x] 🐞 Bug fix (non-breaking change which fixes an issue) - [x] ✨ New feature (adds new functionality without breaking existing usage) - [ ] 💥 Breaking change (changes that might break existing user setups, scripts, or configurations) ## ✅ Checklist Before requesting a review, confirm the following: - [x] I have reviewed my changes to ensure they meet the project's standards. - [ ] I have tested my changes and ensured that all tests pass (`pdm run test`). - [x] I have formatted the code (`pdm run format`). - [x] I have verified that linting passes (`pdm run lint`). - [x] I have updated documentation where necessary. By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Field lookup now falls back to locating by ID when name lookup times out. * Option selection uses a two-pass match (value then displayed text); JS-path failures now surface as timeouts. * Error and log messages localized and clarified. * **New Features** * Support for combobox-style inputs: type into the input, open dropdown, and select by visible text (handles special characters). * **Tests** * Added tests for combobox selection, missing dropdowns, no-match errors, value-path selection, and special-character handling. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Jens <1742418+1cu@users.noreply.github.com> Co-authored-by: Claude <claude@anthropic.com>
This commit is contained in:
@@ -1068,24 +1068,31 @@ class KleinanzeigenBot(WebScrapingMixin):
|
||||
try:
|
||||
# finding element by name cause id are composed sometimes eg. autos.marke_s+autos.model_s for Modell by cars
|
||||
special_attr_elem = await self.web_find(By.XPATH, f"//*[contains(@name, '{special_attribute_key}')]")
|
||||
except TimeoutError as ex:
|
||||
LOG.debug("Attribute field '%s' could not be found.", special_attribute_key)
|
||||
raise TimeoutError(f"Failed to set special attribute [{special_attribute_key}] (not found)") from ex
|
||||
except TimeoutError:
|
||||
# Trying to find element by ID instead cause sometimes there is NO name attribute...
|
||||
try:
|
||||
special_attr_elem = await self.web_find(By.ID, special_attribute_key)
|
||||
except TimeoutError as ex:
|
||||
LOG.debug(_("Attribute field '%s' could not be found."), special_attribute_key)
|
||||
raise TimeoutError(_("Failed to set attribute '%s'") % special_attribute_key) from ex
|
||||
|
||||
try:
|
||||
elem_id:str = str(special_attr_elem.attrs.id)
|
||||
if special_attr_elem.local_name == "select":
|
||||
LOG.debug("Attribute field '%s' seems to be a select...", special_attribute_key)
|
||||
LOG.debug(_("Attribute field '%s' seems to be a select..."), special_attribute_key)
|
||||
await self.web_select(By.ID, elem_id, special_attribute_value_str)
|
||||
elif special_attr_elem.attrs.type == "checkbox":
|
||||
LOG.debug("Attribute field '%s' seems to be a checkbox...", special_attribute_key)
|
||||
LOG.debug(_("Attribute field '%s' seems to be a checkbox..."), special_attribute_key)
|
||||
await self.web_click(By.ID, elem_id)
|
||||
elif special_attr_elem.attrs.type == "text" and special_attr_elem.attrs.get("role") == "combobox":
|
||||
LOG.debug(_("Attribute field '%s' seems to be a Combobox (i.e. text input with filtering dropdown)..."), special_attribute_key)
|
||||
await self.web_select_combobox(By.ID, elem_id, special_attribute_value_str)
|
||||
else:
|
||||
LOG.debug("Attribute field '%s' seems to be a text input...", special_attribute_key)
|
||||
LOG.debug(_("Attribute field '%s' seems to be a text input..."), special_attribute_key)
|
||||
await self.web_input(By.ID, elem_id, special_attribute_value_str)
|
||||
except TimeoutError as ex:
|
||||
LOG.debug("Attribute field '%s' is not of kind radio button.", special_attribute_key)
|
||||
raise TimeoutError(f"Failed to set special attribute [{special_attribute_key}]") from ex
|
||||
LOG.debug(_("Failed to set attribute field '%s' via known input types."), special_attribute_key)
|
||||
raise TimeoutError(_("Failed to set attribute '%s'") % special_attribute_key) from ex
|
||||
LOG.debug("Successfully set attribute field [%s] to [%s]...", special_attribute_key, special_attribute_value_str)
|
||||
|
||||
async def __set_shipping(self, ad_cfg:Ad, mode:AdUpdateStrategy = AdUpdateStrategy.REPLACE) -> None:
|
||||
|
||||
Reference in New Issue
Block a user