# pages/calculation_page.py from selenium.webdriver.common.by import By from pages.base_page import BasePage import logging logger = logging.getLogger(__name__) class CalculationPage(BasePage): """Page Object für die Berechnungsformulare""" # WICHTIG: Verwende data-v-* Attribute NUR wenn sie WIRKLICH stabil sind # Besser: Positionsbasierte Selektoren mit aussagekräftigen Parent-Elementen FIELD_MAPPING = { # Material-Sektion (erste Box) "HS_CODE": ( By.XPATH, "//div[contains(@class, 'master-data-item')][1]//div[contains(@class, 'caption-column')][text()='HS code']" "/following-sibling::div//input[@class='input-field']" ), "TARIFF_RATE": ( By.XPATH, "//div[contains(@class, 'master-data-item')][1]//div[contains(@class, 'caption-column')][contains(text(), 'Tariff rate')]" "/following-sibling::div//input[@class='input-field']" ), # Price-Sektion (zweite Box) "PRICE": ( By.XPATH, "//div[contains(@class, 'master-data-item')][2]//div[contains(@class, 'caption-column')][text()='MEK_A [EUR]']" "/following-sibling::div//input[@class='input-field']" ), "OVERSEA_SHARE": ( By.XPATH, "//div[contains(@class, 'master-data-item')][2]//div[contains(@class, 'caption-column')][contains(text(), 'Oversea share')]" "/following-sibling::div//input[@class='input-field']" ), # Handling Unit-Sektion (dritte Box) "LENGTH": ( By.XPATH, "//div[contains(@class, 'master-data-item')][3]//div[contains(@class, 'caption-column')][text()='HU length']" "/following-sibling::div//input[@class='input-field']" ), "WIDTH": ( By.XPATH, "//div[contains(@class, 'master-data-item')][3]//div[contains(@class, 'caption-column')][text()='HU width']" "/following-sibling::div//input[@class='input-field']" ), "HEIGHT": ( By.XPATH, "//div[contains(@class, 'master-data-item')][3]//div[contains(@class, 'caption-column')][text()='HU height']" "/following-sibling::div//input[@class='input-field']" ), "WEIGHT": ( By.XPATH, "//div[contains(@class, 'master-data-item')][3]//div[contains(@class, 'caption-column')][text()='HU weight']" "/following-sibling::div//input[@class='input-field']" ), "PIECES_UNIT": ( By.XPATH, "//div[contains(@class, 'master-data-item')][3]//div[contains(@class, 'caption-column')][text()='Pieces per HU']" "/following-sibling::div//input[@class='input-field']" ), # Dropdowns "DIMENSION_UNIT": ( By.XPATH, "//div[contains(@class, 'master-data-item')][3]//div[contains(@class, 'caption-column')][text()='Dimension unit']" "/following-sibling::div//button[contains(@class, 'dropdown-trigger')]" ), "WEIGHT_UNIT": ( By.XPATH, "//div[contains(@class, 'master-data-item')][3]//div[contains(@class, 'caption-column')][text()='Weight unit']" "/following-sibling::div//button[contains(@class, 'dropdown-trigger')]" ), # Checkboxen "FBA_FEE": ( By.XPATH, "//div[contains(@class, 'master-data-item')][2]" "//label[contains(@class, 'checkbox-item')]" "[.//span[contains(@class, 'checkbox-label')][normalize-space(text())='']]" ), "MIXED": ( By.XPATH, "//label[contains(@class, 'checkbox-item')]" "[.//span[contains(@class, 'checkbox-label')][text()='Mixable']]" ), "STACKED": ( By.XPATH, "//label[contains(@class, 'checkbox-item')]" "[.//span[contains(@class, 'checkbox-label')][text()='Stackable']]" ), } DEST_FIELD_MAPPING = { "NAME": (By.XPATH, "//input[@placeholder='Add new Destination ...']"), "QUANTITY": (By.XPATH, "//div[contains(@class, 'destination-edit-column-caption') and contains(text(), 'Annual quantity')]/following-sibling::div[1]//input[@class='input-field']") "ROUTING": (By.XPATH, "//input[@type='radio' and @name='model' and @value='routing']"), "D2D": (By.XPATH, "//input[@type='radio' and @name='model' and @value='d2d']"), "ROUTE": (By.XPATH, "//div[@class='destination-route-container']//div[contains(@class, 'destination-route-inner-container')][.//span[contains(text(), 'Ireland Su')] and .//span[contains(text(), 'WH ULHA')] and .//span[contains(text(), 'AB')]]") "HANDLING_TAB": (By.XPATH, "//button[@class='tab-header' and text()='Handling & Repackaging']"), "CUSTOM_HANDLING": (By.XPATH, "//div[@class='destination-edit-handling-cost']//label[@class='checkbox-item']/input[@type='checkbox']"), "REPACKING": (By.XPATH, "//div[@class='destination-edit-column-caption' and contains(text(), 'Repackaging cost')]/following-sibling::div[@class='destination-edit-column-data'][1]//input[@class='input-field']"), "HANDLING": (By.XPATH, "//div[@class='destination-edit-column-caption' and contains(text(), 'Handling cost')]/following-sibling::div[@class='destination-edit-column-data'][1]//input[@class='input-field']"), "DISPOSAL": (By.XPATH, "//div[@class='destination-edit-column-caption' and contains(text(), 'Disposal cost')]/following-sibling::div[@class='destination-edit-column-data'][1]//input[@class='input-field']"), } # Buttons CALCULATE_CLOSE_BUTTON = (By.XPATH, "//button[contains(., 'Calculate & close')]") CLOSE_BUTTON = (By.XPATH, "//button[contains(., 'Close') and not(contains(., 'Calculate'))]") def fill_form(self, data_dict): """Füllt das Formular mit Daten aus dem Excel""" for field_name, locator in self.FIELD_MAPPING.items(): value = data_dict[field_name] logger.info(f"Filling field: {field_name} = {value}") try: if field_name in ["FBA_FEE", "STACKED", "MIXED"]: self.set_checkbox(*locator, str(value) == 'True') elif field_name in ["DIMENSION_UNIT", "WEIGHT_UNIT"]: self.select_dropdown_option(*locator, str(value)) else: self.fill_input(*locator, str(value), check_existence=field_name in ["HS_CODE", "TARIFF_RATE"]) except Exception as e: logger.error(f"Failed to fill field {field_name}: {e}") self.driver.save_screenshot(f"failed_field_{field_name}.png") raise Exception(f"Could not fill field '{field_name}': {e}") from e def add_destination(self, data_dict): self.search_and_select_autosuggest(*self.DEST_FIELD_MAPPING["NAME"], data_dict["NAME"]) def fill_destination(self, data_dict): self.wait_for_element(*self.DEST_FIELD_MAPPING["QUANTITY"]) pass def click_calculate_and_close(self): """Klickt auf 'Calculate & close' Button""" self.click_button(*self.CALCULATE_CLOSE_BUTTON) def click_close(self): """Klickt auf 'Close' Button""" self.click_button(*self.CLOSE_BUTTON)