def is_chart_displayed(self, chart: str) -> bool: """Boolean check for whether a specific chart is displayed on the Reports page. :param chart: The name of a chart on the Reports page. """ groomed_chart: str if chart == 'Origins & Destinations': groomed_chart = f'chart-ride-{chart.lower().replace(" ", "-")}' else: groomed_chart = f'chart-{chart.lower().replace(" ", "-")}' element: WebElement = self.driver.wait_until_visible( *Selectors.data_id(groomed_chart)) return element.is_displayed()
class PassengerDetailsModal(Component): """Objects and methods for the Passenger Details Modal component.""" ROOT_LOCATOR: Selector = Selectors.data_id('passenger-details-container') _next_button: Selector = Selectors.data_id('next-button') _capacity_input: Selector = Selectors.data_id('capacity-select') _wheelchair_container: Selector = Selectors.data_id('wheelchair-container') @property def next_button(self) -> WebElement: return self.container.find_element(*self._next_button) @property def capacity_input(self) -> WebElement: return self.container.find_element(*self._capacity_input) @property def wheelchair_container(self) -> WebElement: return self.container.find_element(*self._wheelchair_container) def wheelchair_select(self) -> None: """Select the wheelchair input for a ride.""" self.wheelchair_container.click()
class KebabMenu(Component): """Objects and methods for the Kebab Menu component.""" ROOT_LOCATOR: Selector = Selectors.data_id('kebab-menu-container') _delete_button: Selector = Selectors.data_id('delete-group') _edit_button: Selector = Selectors.data_id('edit-group') _manage_button: Selector = Selectors.data_id('manage-users-item') @property def delete_button(self) -> WebElement: return self.container.find_element(*self._delete_button) @property def edit_button(self) -> WebElement: return self.container.find_element(*self._edit_button) @property def manage_button(self) -> WebElement: return self.container.find_element(*self._manage_button) def delete_group(self) -> object: """Open the deletion modal for a group row.""" self.delete_button.click() return DeletionModal(self).wait_for_component_to_be_visible() def edit_group(self) -> object: """Open the edit group form for a group row.""" self.edit_button.click() return GroupForm(self).wait_for_component_to_be_visible() def manage_group_users(self) -> object: """Open the edit group form for a group row.""" self.manage_button.click() return GroupUserForm(self).wait_for_component_to_be_visible()
class Rides(Base): """Rides Page objects and methods for the OnDemand Web application.""" URL_PATH = f'{Base.URL_PATH}/rides' ROOT_LOCATOR: Selector = Selectors.data_id('rides-page-container') _past_tab: Selector = Selectors.data_id('past') _tabs_container: Selector = Selectors.data_id('tabs-container') _upcoming_tab: Selector = Selectors.data_id('upcoming') @property def past_rides_tab(self) -> WebElement: return self.tabs_container.find_element(*self._past_tab) @property def rides_list(self) -> RidesList: return RidesList(self) @property def tabs_container(self) -> WebElement: return self.driver.find_element(*self._tabs_container) @property def upcoming_rides_tab(self) -> WebElement: return self.tabs_container.find_element(*self._upcoming_tab)
class LocationCard(Component): """Objects and methods for the Location Card component.""" ROOT_LOCATOR: Selector = Selectors.data_id('location-card') _card_id: Selector = Selectors.data_id('card-id') _drop_off_field: Selector = Selectors.data_id('dropoff-checkbox') _hub_field: Selector = Selectors.data_id('hub-checkbox') _input: Selector = Selectors.tag('input') _label: Selector = Selectors.data_id('location-card-label') _pick_up_field: Selector = Selectors.data_id('pickup-checkbox') @property def card_id(self) -> str: """Return the unique address ID for a location card. The get_attribute method is used instead of .text as the service_id is a hidden attribute. """ return self.container.find_element( *self._card_id).get_attribute('innerText') @property def drop_off_checkbox(self) -> WebElement: return self.drop_off_field.find_element(*self._input) @property def drop_off_field(self) -> WebElement: return self.container.find_element(*self._drop_off_field) @property def hub_checkbox(self) -> WebElement: return self.hub_field.find_element(*self._input) @property def hub_field(self) -> WebElement: return self.container.find_element(*self._hub_field) @property def label(self) -> str: return self.container.find_element(*self._label).text @property def pick_up_checkbox(self) -> WebElement: return self.pick_up_field.find_element(*self._input) @property def pick_up_field(self) -> WebElement: return self.container.find_element(*self._pick_up_field) def hub_card(self) -> WebElement: """Run a check to see whether a card is a hub card or not.""" return self.container.wait_until_present(*self._hub_field)
class ArchiveModal(Component): """Objects and methods for the Service Archive Modal.""" ROOT_LOCATOR: Selector = Selectors.data_id( 'archive-service-modal-container') _cancel_button: Selector = Selectors.data_id( 'cancel-archive-service-button') _confirm_button: Selector = Selectors.data_id( 'confirm-archive-service-button') _message: Selector = Selectors.data_id('archive-service-message') @property def archive_message(self) -> WebElement: return self.container.find_element(*self._message) @property def cancel_button(self) -> WebElement: return self.container.find_element(*self._cancel_button) @property def confirm_button(self) -> WebElement: return self.container.find_element(*self._confirm_button) def confirm_service_archive(self) -> bool: """Confirm a service archive. Returns True if the container is no longer displayed and False if an Alert is raised. This method should be used with an assert statement to verify its boolean return. """ self.confirm_button.click() try: self.page.driver.wait_until_not_visible(*self.ROOT_LOCATOR) return True except UnexpectedAlertPresentException: return False
class ScheduleForm(Component): """Objects and methods for the Schedule form.""" ROOT_LOCATOR: Selector = Selectors.data_id('ride-schedule-container') _asap_ride_button: Selector = Selectors.data_id('asap-ride-button') _future_ride_button: Selector = Selectors.data_id('future-ride-button') _recurring_ride_button: Selector = Selectors.data_id( 'recurring-ride-button') @property def asap_ride_button(self) -> WebElement: return self.container.find_element(*self._asap_ride_button) @property def future_ride_button(self) -> WebElement: return self.container.find_element(*self._future_ride_button) @property def recurring_ride_button(self) -> WebElement: return self.container.find_element(*self._recurring_ride_button) def select_ride_type(self, ride_type: str) -> None: """Select a ride type. Ride type defaults to ASAP within the OnDemand Admin application. :param ride_type: The type of ride to be submitted. """ if ride_type == 'future': self.future_ride_button.click() elif ride_type == 'recurring': self.recurring_ride_button.click() elif ride_type != 'asap': raise RideTypeException( f'The ride type: {ride_type} is not supported.\n' f'Expected "asap", "future", or "recurring".', )
class PassengerForm(Component): """Objects and methods for the Passenger form.""" ROOT_LOCATOR: Selector = Selectors.data_id('user-details-container') _first_name_field: Selector = Selectors.name('first_name') _last_name_field: Selector = Selectors.name('last_name') _phone_field: Selector = Selectors.name('phone') @property def first_name_field(self) -> WebElement: return self.container.find_element(*self._first_name_field) @property def last_name_field(self) -> WebElement: return self.container.find_element(*self._last_name_field) @property def phone_field(self) -> WebElement: return self.container.find_element(*self._phone_field) def fill_passenger_info_form(self, ride: dict) -> None: """Fill out the passenger information form. The ride param may be of type RecurringRide or Ride depending on the test which is being ran. The default type will be Ride as it is the most common data type for testing. :param ride: A ride yielded from a ride fixture. """ try: rider = ride['ride']['rider'] except KeyError: rider = ride['rider'] self.first_name_field.fill(rider['first_name']) self.last_name_field.fill(rider['last_name']) self.phone_field.fill(rider['phone'])
class AddressesList(Component): """Objects and methods for the Addresses List component.""" ROOT_LOCATOR: Selector = Selectors.data_id('address-list-container') @property def address_rows(self) -> Tuple[AddressRow, ...]: """Return a tuple of address rows within the list container. A conditional check for whether the URL contains 'admin' allows this method to be used in both OnDemand Web and OnDemand Admin. """ rows: WebElements if 'admin' not in self.driver.current_url: rows = self.container.find_elements(*WebAddressRow.ROOT_LOCATOR) return tuple(WebAddressRow(self, element) for element in rows) rows = self.container.find_elements(*AdminAddressRow.ROOT_LOCATOR) return tuple(AdminAddressRow(self, element) for element in rows) def filter_rows(self, address: dict, address_label: str = None) -> Union[AddressRow, None]: """Filter all address rows for a match with an address label. :param address_label: An optional label for an address. :param address: An address object yielded from an address fixture. """ name: str = address['name'] if address_label is None else address_label address_list: Tuple[AddressRow, ...] = tuple(row for row in self.address_rows if name in row.data) if not address_list: return None return address_list[0] def surface_address_row(self, address: dict, address_label: str = None) -> AddressRow: """Raise a address row that matches an address label. :param address_label: An optional label for an address. :param address: An address object yielded from an address fixture. """ return self.filter_rows(address, address_label=address_label)
class ServiceCardList(Component): """Objects and methods for the Service Card list.""" ROOT_LOCATOR: Selector = Selectors.data_id('service-card-container') @property def service_cards(self) -> Tuple[ServiceCard, ...]: return tuple( ServiceCard(self.page, item) for item in self.container.find_elements( *ServiceCard.ROOT_LOCATOR)) def card_archived(self, service: dict) -> bool: """Confirm a card has been archived. Returns False if the card is still displayed and True if it is no longer within the DOM. This method should be used with an assert statement to verify its boolean return. :param service: The service intended to be archived. """ try: self.surface_service_card(service) return False except NoSuchElementException: return True except StaleElementReferenceException: return True def filter_cards(self, service: dict) -> ServiceCard: """Filter all service cards for a match with a service id. :param service: The service intended to be raised. """ card_list: Tuple[ServiceCard, ...] = tuple( card for card in self.service_cards if card.service_id == service['service_id']) if not card_list: raise NoSuchElementException return card_list[0] def surface_service_card(self, service: dict) -> ServiceCard: """Raise a service card for later use. :param service: The service intended to be raised. """ return self.filter_cards(service)
class RideCardPanel(Component): """Objects and methods for the ride cards panel.""" ROOT_LOCATOR: Selector = Selectors.data_id('ride-card-container') @property def ride_cards(self) -> Tuple[RideCard, ...]: return tuple( RideCard(self, element) for element in self.container.find_elements( *RideCard.ROOT_LOCATOR)) def filter_cards(self, ride: dict) -> RideCard: """Filter all ride cards for a match with a passenger name. :param ride: The ride yielded from a ride fixture. """ rider = ride['rider'] rider_name = f'{rider["first_name"]} {rider["last_name"]}' self.verify_card_created(rider_name) card_list: Tuple[RideCard, ...] = tuple(card for card in self.ride_cards if card.rider_name == rider_name) if not card_list: raise NoSuchElementException return card_list[0] def verify_card_created(self, rider_name: str) -> None: """Wait until a card is located which contains a specific name. An increased wait time is used within this function as cards can take upward of seven seconds to appear within the DOM. This amount is nearly doubled when running in parallel. :param rider_name: The name of the rider for a Ride object. """ self.container.wait_until_visible(*Selectors.text(rider_name), wait_time=10) def surface_ride_card(self, ride: dict) -> RideCard: """Validate a ride card, then raise it for later use. :param ride: The ride yielded from a ride fixture. """ return self.filter_cards(ride)
class AddressForm(Component): """Objects and methods for the Address Form component. The address form is used in both editing and creating addresses. It may be accessed by selecting 'EDIT' from an address kebab menu or by selecting the FAB button for creating a new address. """ ROOT_LOCATOR: Selector = Selectors.data_id('address-form-container') _button_container: Selector = Selectors.data_id( 'address-form-button-container') _cancel_button: Selector = Selectors.data_id('address-form-cancel-button') _label_input: Selector = Selectors.placeholder('Label') _location_input: Selector = Selectors.placeholder( 'Search for a location...') _places_autocomplete_suggestion: Selector = Selectors.data_id( 'places-autocomplete-suggestion') _save_button: Selector = Selectors.data_id('address-form-confirm-button') @property def button_container(self) -> WebElement: return self.container.find_element(*self._button_container) @property def cancel_button(self) -> WebElement: return self.button_container.find_element(*self._cancel_button) @property def label_input(self) -> WebElement: return self.container.find_element(*self._label_input) @property def location_input(self) -> WebElement: return self.container.find_element(*self._location_input) @property def save_button(self) -> WebElement: return self.button_container.find_element(*self._save_button) @property def suggestions(self) -> AutocompleteSuggestions: return AutocompleteSuggestions(self) def select_location(self, location: str) -> None: """Fill out an address location, then submit the location. :param location: The location for an address. """ self.location_input.fill(location) self.suggestions.select_suggestion(location)
class RideFilters(Component): """Objects and methods for the Ride Filters container.""" ROOT_LOCATOR: Selector = Selectors.data_id('ride-filters-container') TABS: List[str] = Admin.RIDE_FILTERS def select_filter(self, filter_type: str) -> None: """Filter all ride cards based on a selected type. :param filter_type: The specified ride filter. """ _groomed_tab: str = filter_type.lower().replace(' ', '-') if filter_type not in self.TABS: raise FilterException self.container.find_element( *Selectors.data_id(f'ride-filter-{_groomed_tab}')).click()
class RideRow(Component): """Objects and methods for the Ride Row component.""" ROOT_LOCATOR: Selector = Selectors.data_id('ride-list-row') _ride_drop_off: Selector = Selectors.data_id('ride-row-drooff-address') _ride_fare: Selector = Selectors.data_id('ride-row-fare') _ride_id: Selector = Selectors.data_id('ride-row-id') _ride_overview_button: Selector = Selectors.data_id('ride-overview-button') _ride_pick_up: Selector = Selectors.data_id('ride-row-pickup-address') _ride_status: Selector = Selectors.data_id('ride-row-status') @property def drop_off_address(self) -> str: return self.container.find_element(*self._ride_drop_off).text @property def fare(self) -> str: return self.container.find_element(*self._ride_fare).text @property def pick_up_address(self) -> str: return self.container.find_element(*self._ride_pick_up).text @property def ride_details_button(self) -> WebElement: return self.container.find_element(*self._ride_overview_button) @property def ride_id(self) -> str: """Return the unique ride ID for a ride row. The get_attribute method is used instead of .text as the ride_id is a hidden attribute. """ return self.container.find_element( *self._ride_id).get_attribute('innerText') @property def status(self) -> str: return self.container.find_element(*self._ride_status).text def open_ride_overview(self) -> object: """Navigate to the ride overview modal for a ride.""" self.ride_details_button.click() return RideOverviewModal(self).wait_for_component_to_be_present()
class ScheduledRidesList(Component): """Objects and methods for the Scheduled Ride list.""" ROOT_LOCATOR: Selector = Selectors.data_id('scheduled-rides-container') @property def scheduled_rides(self) -> Tuple[ScheduledRideRow, ...]: return tuple( ScheduledRideRow(self, ele) for ele in self.container.find_elements( *ScheduledRideRow.ROOT_LOCATOR)) def cancel_scheduled_ride(self) -> None: """Cancel the first ride in the scheduled rides container. This method will always cancel the first ride as it is the only ride that can be cancelled in the entire series. All other rides will feature a 'Delete' button as they will be of status 'Scheduled' instead of 'Booked'. """ self.scheduled_rides[0].cancel_button.click() def delete_scheduled_ride(self, date: str) -> None: """Delete a scheduled ride. :param date: A specified date in MM-DD-YYYY format. """ ride_row: ScheduledRideRow = self.filter_scheduled_rides(date) ride_row.delete_ride() def filter_scheduled_rides(self, date: str) -> ScheduledRideRow: """Filter all scheduled rides for a match with a specific date. :param date: A specified date in MM-DD-YYYY format. """ ride_list: Tuple[ScheduledRideRow, ...] = tuple(ride for ride in self.scheduled_rides if ride.date == date) if not ride_list: raise NoSuchElementException( f'A ride could not be found for the date: {date}.') return ride_list[0]
class GroupsList(Component): """Objects and methods for the Groups List component.""" ROOT_LOCATOR: Selector = Selectors.data_id('group-list-container') @property def groups_rows(self) -> Tuple: """Return a tuple of groups rows within the list container. """ rows: WebElements = self.container.find_elements( *GroupRow.ROOT_LOCATOR) return tuple(GroupRow(self, element) for element in rows) def filter_rows(self, group: dict, group_name: str = None) -> Union[GroupRow, None]: """Filter all group rows for a match with a group name. :param group_name: An optional name for a group. :param group: A group object yielded from a group fixture. """ name: str = group['name'] if group_name is None else group_name group_list: list = [ row for row in self.groups_rows if name == row.name ] if not group_list: return None return group_list[0] def surface_group_row(self, group: dict, group_name: str = None) -> Union[GroupRow, None]: """Raise a group row that matches a group name. :param group_name: An optional name for a group. :param group: A group object yielded from a group fixture. """ return self.filter_rows(group, group_name=group_name)
class AutocompleteSuggestions(object): """Objects and methods for autocomplete suggestions. :example usage: suggestions = AutocompleteSuggestions(self) suggestions.select_suggestion(location='4506 Emperor Boulevard Durham, NC, USA') """ def __init__(self, page): self.driver = page.driver _places_autocomplete_suggestion: Selector = Selectors.data_id( 'places-autocomplete-suggestion') @property def suggestion_list(self) -> WebElements: return self.driver.find_elements(*self._places_autocomplete_suggestion) def filter_suggestions(self, location: str) -> WebElement: """Filter location suggestions for a match with a location. :param location: The location for a ride. """ locations: Tuple[WebElement, ...] = tuple( loc for loc in self.suggestion_list if location in loc.text.replace('\n', ' ')) if not location: raise NoSuchElementException( f'A suggestion for: {location} could not be located.') return locations[0] def select_suggestion(self, location: str) -> None: """Select a location suggestion. :param location: The location for a ride. """ self.filter_suggestions(location).click() self.driver.wait_until_not_present( *self._places_autocomplete_suggestion, wait_time=1)
class Services(Base): """Services Page objects and methods for the OnDemand Admin application.""" URL_PATH: str = f'{Base.URL_PATH}/services' ROOT_LOCATOR: Selector = Selectors.data_id('services-page-container') @property def service_card_list(self) -> ServiceCardList: return ServiceCardList(self) def navigate_to_edit_by_service_id(self, service: dict) -> object: """Navigate to the Details Page for a specific service using a direct URL. This is a useful method for bypassing the application work flows following creation of a service using the API. :param service: The service yielded from a service fixture. """ service_id: int = service['service_id'] service_details_url: str = f'{self.URL_PATH}/{service_id}/details' self.driver.get(service_details_url) return EditService(self.driver, service_details_url).wait_for_page_to_load() def navigate_to_locations_by_service(self, service: dict) -> object: """Navigate to the Locations page for a specific service using a direct URL. This is a useful method for bypassing the application work flows following creation of a service using the API. :param service: The service intended for navigation. """ service_id: int = service['service_id'] locations_url = f'{self.URL_PATH}/{service_id}/locations' self.driver.get(locations_url) return Locations(self)
class PickupTimeModal(Component): """Objects and methods for the Pickup Time Modal component.""" ROOT_LOCATOR: Selector = Selectors.data_id('pickup-time-modal-container') _asap_ride_radio: Selector = Selectors.value('use-now') _future_ride_radio: Selector = Selectors.value('use-later') _back_button: Selector = Selectors.data_id('pickup-time-modal-back-button') _continue_button: Selector = Selectors.data_id( 'pickup-time-modal-continue-button') _date_field: Selector = Selectors.data_id('pickup-time-modal-date-field') _time_field: Selector = Selectors.data_id('pickup-time-modal-time-field') @property def asap_ride_button(self) -> WebElement: return self.container.find_element(*self._asap_ride_radio) @property def back_button(self) -> WebElement: return self.container.find_element(*self._back_button) @property def continue_button(self) -> WebElement: return self.container.find_element(*self._continue_button) @property def date_field(self) -> WebElement: return self.container.find_element(*self._date_field) @property def future_ride_button(self) -> WebElement: return self.container.find_element(*self._future_ride_radio) @property def time_field(self) -> WebElement: return self.container.find_element(*self._time_field) def submit_pickup_time(self) -> None: """Submit a selected pick up time.""" self.continue_button.click()
class RideSubscriptionTable(Component): """Objects and methods for the Ride Subscription Table component. The Ride Subscription Table component may be found by selecting 'Active', 'Upcoming' or 'Complete' under 'Recurring Rides' in the Rides page side bar navigation panel. It is only located within these spaces and should not be found elsewhere at this time. """ ROOT_LOCATOR: Selector = Selectors.data_id('subscription-table-container') @property def subscription_rows(self) -> Tuple[SubscriptionRow, ...]: return tuple( SubscriptionRow(self, element) for element in self.container.find_elements(*SubscriptionRow.ROOT_LOCATOR) ) def filter_subscription_rows(self, subscription_id: str) -> Union[SubscriptionRow, None]: """Filter all subscription rows for a match with a subscription ID. :param subscription_id: A unique subscription ID. """ row_list: Tuple[SubscriptionRow, ...] = tuple( row for row in self.subscription_rows if row.id == subscription_id ) if not row_list: return None return row_list[0] def surface_subscription_row(self, subscription_id: str) -> Union[SubscriptionRow, None]: """Surface a subscription row. :param subscription_id: A unique subscription ID. """ return self.filter_subscription_rows(str(subscription_id))
class AddressForm(Component): """Objects and methods for the Address Form component.""" ROOT_LOCATOR: Selector = Selectors.data_id('address-container') _address_field: Selector = Selectors.name('address') _cancel_button: Selector = Selectors.data_id('cancel-button') _delete_button: Selector = Selectors.data_id('delete-address-button') _label_field: Selector = Selectors.name('name') _save_button: Selector = Selectors.data_id('save-address-button') @property def address_field(self) -> WebElement: return self.container.find_element(*self._address_field) @property def cancel_button(self) -> WebElement: return self.container.find_element(*self._cancel_button) @property def delete_address_button(self) -> WebElement: return self.container.find_element(*self._delete_button) @property def label_field(self) -> WebElement: return self.container.find_element(*self._label_field) @property def save_button(self) -> WebElement: return self.container.find_element(*self._save_button) @property def suggestions(self) -> AutocompleteSuggestions: return AutocompleteSuggestions(self) def fill_form(self, address: str, label: str) -> None: """Fill out an address form, then submit the form. :param address: The street address. :param label: The label for the address. """ self.label_field.fill(label) self.address_field.fill(address) self.suggestions.select_suggestion(location=address)
class GroupRow(Component): """Objects and methods for the Restricted Groups row.""" ROOT_LOCATOR = Selectors.data_id('group-row') _groups_manage_row_name: Selector = Selectors.data_id('group-name') _groups_manage_row_allow_checkbox: Selector = Selectors.data_id( 'allow-group-checkbox') _groups_manage_row_deny_checkbox: Selector = Selectors.data_id( 'deny-group-checkbox') _groups_manage_status: Selector = Selectors.data_id('group-status') _group_id: Selector = Selectors.data_id('group-id') @property def group_id(self) -> str: """Return the unique service ID for a service row. The get_attribute method is used instead of .text as the service_id is a hidden attribute. """ return self.container.find_element( *self._group_id).get_attribute('innerText') @property def name(self) -> str: return self.container.find_element(*self._groups_manage_row_name).text @property def allow_checkbox(self) -> WebElement: return self.container.find_element( *self._groups_manage_row_allow_checkbox) @property def deny_checkbox(self) -> WebElement: return self.container.find_element( *self._groups_manage_row_deny_checkbox) @property def status(self) -> str: return self.container.find_element(*self._groups_manage_status).text
class LegacyRideBooking(Base): """Objects and methods for the Legacy Ride Booking page. The legacy ride booking page is a work flow which must be supported due to the volume of dispatchers who continue to utilize the page over the Admin Dispatch page. """ URL_PATH: str = f'{Base.URL_PATH}/rides/new' _asap_ride_radio: Selector = Selectors.data_id('use-now') _driver_note: Selector = Selectors.name('note') _drop_off_field: Selector = Selectors.name('dropoff') _first_name_field: Selector = Selectors.name('first_name') _future_ride_radio: Selector = Selectors.data_id('use-later') _last_name_field: Selector = Selectors.name('last_name') _pay_on_vehicle_radio: Selector = Selectors.radio('cash') _phone_field: Selector = Selectors.name('phone') _pick_up_field: Selector = Selectors.name('pickup') _places_autocomplete_suggestion: Selector = Selectors.data_id( 'places-autocomplete-suggestion') _service_dropdown: Selector = Selectors.name('serviceId') _submit_ride_button: Selector = Selectors.data_id('book_ride') _total_passenger_field: Selector = Selectors.name('capacity') _waive_fee_radio: Selector = Selectors.radio('waive') _wheelchair_switch: Selector = Selectors.name('wheelchair') @property def asap_ride_button(self) -> WebElement: return self.driver.find_element(*self._asap_ride_radio) @property def driver_note_field(self) -> WebElement: return self.driver.find_element(*self._driver_note) @property def drop_off_field(self) -> WebElement: return self.driver.find_element(*self._drop_off_field) @property def first_name_field(self) -> WebElement: return self.driver.find_element(*self._first_name_field) @property def future_ride_button(self) -> WebElement: return self.driver.find_element(*self._future_ride_radio) @property def last_name_field(self) -> WebElement: return self.driver.find_element(*self._last_name_field) @property def pay_on_vehicle_button(self) -> WebElement: return self.driver.find_element(*self._pay_on_vehicle_radio) @property def phone_field(self) -> WebElement: return self.driver.find_element(*self._phone_field) @property def pick_up_field(self) -> WebElement: return self.driver.find_element(*self._pick_up_field) @property def service_dropdown(self) -> Select: return self.driver.find_dropdown(*self._service_dropdown) @property def submit_ride_button(self) -> WebElement: return self.driver.find_element(*self._submit_ride_button) @property def suggestions(self) -> AutocompleteSuggestions: return AutocompleteSuggestions(self) @property def total_passenger_field(self) -> WebElement: return self.driver.find_element(*self._total_passenger_field) @property def waive_fee_button(self) -> WebElement: return self.driver.find_element(*self._waive_fee_radio) @property def wheelchair_switch(self) -> WebElement: return self.driver.find_element(*self._wheelchair_switch) def fill_ride_form(self, service: dict, ride: dict) -> None: """Fill out the legacy ride form. Use send_keys for phone_field as clearing the field within the fill method causes input to occur at the end of the field. This results in an input failure. :param service: The service yielded from a service API fixture. :param ride: The ride intended for booking. """ self.first_name_field.fill(ride['rider']['first_name']) self.last_name_field.fill(ride['rider']['last_name']) self.last_name_field.fill(Keys.TAB) self.phone_field.send_keys(ride['rider']['phone']) self.select_pick_up_location(ride['pickup']['address']) self.select_drop_off_location(ride['dropoff']['address']) # # Occurs further down the stack than its form location as it intermittently failed when it # was called first. # self.select_a_service(service['service_id']) if ride['note'] is not None: self.driver_note_field.send_keys(ride['note']) def pay_ride_fee(self, method: str) -> None: """Select a payment method based on a method string. :param method: A payment method for the ride. """ if method == 'cash': self.pay_on_vehicle_button.scroll_to().click() elif method == 'waive': self.waive_fee_button.scroll_to().click() else: raise PaymentMethodException( f'The payment method: "{method}" is not allowed for this ride.', ) def select_a_service(self, service_id: int) -> None: """Select a service within the service drop down. :param service_id: The service ID yielded from a service API fixture. """ try: self.driver.wait_until_visible(*self._service_dropdown, wait_time=4) self.service_dropdown.select_by_value(service_id) except NoSuchElementException: raise NoSuchElementException( f'The service ID: {service_id} cannot be found within the selected agency.\n' f'Please select a valid service within the selected agency.', ) def select_drop_off_location(self, drop_off: str) -> None: """Fill out a drop off location, then select an autocomplete suggestion. :param drop_off: The drop off location for a ride. """ self.drop_off_field.fill(drop_off) self.suggestions.select_suggestion(drop_off) def select_pick_up_location(self, pick_up: str) -> None: """Fill out a pick up location, then select an autocomplete suggestion. :param pick_up: The pick up location for a ride. """ self.pick_up_field.fill(pick_up) self.suggestions.select_suggestion(pick_up) def submit_ride_form(self) -> object: """Submit a legacy ride form.""" self.submit_ride_button.scroll_to().click() return Details(self.driver).wait_for_page_to_load()
class Registration(Page): """Objects and methods for the Register page.""" URL_PATH: str = build_login_url(path='/register') ROOT_LOCATOR: Selector = Selectors.class_name('register-container') _email_field: Selector = Selectors.name('email') _first_name_field: Selector = Selectors.name('first_name') _inline_errors: Selector = Selectors.class_name('errors') _last_name_field: Selector = Selectors.name('last_name') _password_field: Selector = Selectors.name('password') _phone_field: Selector = Selectors.name('phone') _privacy_policy_field: Selector = Selectors.name('privacy_policy_accepted') _repeat_password_field: Selector = Selectors.name('repeat_password') _submit_button: Selector = Selectors.data_id('submit-button') _username_field: Selector = Selectors.name('username') _user_exists_error: Selector = Selectors.data_id('error-message') @property def email_field(self) -> WebElement: return self.driver.find_element(*self._email_field) @property def first_name_field(self) -> WebElement: return self.driver.find_element(*self._first_name_field) @property def inline_errors(self) -> WebElements: return self.driver.find_elements(*self._inline_errors) @property def inline_error_messages(self) -> List[str]: return [error.text for error in self.inline_errors] @property def last_name_field(self) -> WebElement: return self.driver.find_element(*self._last_name_field) @property def password_field(self) -> WebElement: return self.driver.find_element(*self._password_field) @property def phone_field(self) -> WebElement: return self.driver.find_element(*self._phone_field) @property def privacy_policy_checkbox(self) -> WebElement: return self.driver.find_element(*self._privacy_policy_field) @property def repeat_password_field(self) -> WebElement: return self.driver.find_element(*self._repeat_password_field) @property def submit_button(self) -> WebElement: return self.driver.find_element(*self._submit_button) @property def username_field(self) -> WebElement: return self.driver.find_element(*self._username_field) @property def user_exists_error(self) -> WebElement: return self.driver.find_element(*self._user_exists_error) def fill_registration_form(self, user: User) -> None: """Fill out a user registration form. :param user: A user generated from a User Factory. """ self.username_field.fill(user.username) self.email_field.fill(user.email) self.first_name_field.fill(user.first_name) self.last_name_field.fill(user.last_name) self.phone_field.fill(user.phone) self.password_field.fill(user.password) self.repeat_password_field.fill(user.repeat_password) self.privacy_policy_checkbox.click()
def __init__(self, page: object, selector: str, tabs: list): self.tabs = tabs self.ROOT_LOCATOR: Selector = Selectors.data_id(selector) super().__init__(page)
class RideForm(Component): """Objects and methods for the Ride form.""" ROOT_LOCATOR: Selector = Selectors.data_id('ride-details-container') _driver_note_field: Selector = Selectors.name('note') _drop_off_field: Selector = Selectors.name('dropoff') _pick_up_field: Selector = Selectors.name('pickup') _service_drop_down: Selector = Selectors.name('serviceId') _service_drop_down_error: Selector = Selectors.data_id( 'service-dropdown-with-errors') _total_passenger_field: Selector = Selectors.name('capacity') _wheelchair_check_box: Selector = Selectors.name('wheelchair') @property def driver_note_field(self) -> WebElement: return self.container.find_element(*self._driver_note_field) @property def drop_off_field(self) -> WebElement: return self.container.find_element(*self._drop_off_field) @property def pick_up_field(self) -> WebElement: return self.container.find_element(*self._pick_up_field) @property def service_drop_down(self) -> Select: return self.container.find_dropdown(*self._service_drop_down) @property def suggestions(self) -> AutocompleteSuggestions: return AutocompleteSuggestions(self) @property def total_passenger_field(self) -> WebElement: return self.container.find_element(*self._total_passenger_field) @property def wheelchair_check_box(self) -> WebElement: return self.container.find_element(*self._wheelchair_check_box) def fill_ride_info_form(self, service: dict, ride: dict) -> None: """Fill out the ride information form. The ride param may be of type RecurringRide or Ride depending on the test which is being ran. The default type will be Ride as it is the most common data type for testing. :param service: The service yielded from a service API fixture. :param ride: A ride yielded from a ride fixture. """ try: ride_data = ride['ride'] except KeyError: ride_data = ride dropoff: dict = ride_data['dropoff'] note: str = ride_data['note'] pickup: dict = ride_data['pickup'] self.select_a_service(service['service_id']) self.select_pick_up_location(pickup['address']) self.select_drop_off_location(dropoff['address']) if note is not None: self.driver_note_field.send_keys(note) def select_a_service(self, service_id: str) -> None: """Select a service within the service drop down. :param service_id: The service ID yielded from a service API fixture. """ try: self.service_drop_down.select_by_value(service_id) except NoSuchElementException: raise NoSuchElementException( f'The service ID: {service_id} cannot be found within the selected agency.\n' f'Please select a valid service within the selected agency.', ) def select_drop_off_location(self, drop_off: str) -> None: """Fill out a drop off location, then select an autocomplete suggestion. :param drop_off: The drop off location for a ride. """ self.drop_off_field.fill(drop_off) self.suggestions.select_suggestion(drop_off) def select_pick_up_location(self, pick_up: str) -> None: """Fill out a pick up location, then select an autocomplete suggestion. :param pick_up: The pick up location for a ride. """ self.pick_up_field.fill(pick_up) self.suggestions.select_suggestion(pick_up) def service_error_check(self) -> bool: return self.container.find_element( *self._service_drop_down_error).is_displayed()
class Login(Page): """Login methods and objects for all TransLoc applications.""" URL_PATH: str = build_login_url(path='/login') ROOT_LOCATOR: Selector = Selectors.class_name('login-container') _error_message: Selector = Selectors.data_id('error-message') _forgot_password_link: Selector = Selectors.data_id('forgot-password-link') _password_field: Selector = Selectors.name('password') _sign_up_button: Selector = Selectors.data_id('sign-up-button') _submit_button: Selector = Selectors.data_id('submit-login-button') _success_message: Selector = Selectors.data_id('success-message') _university_login_button: Selector = Selectors.data_id( 'university-login-button') _username_field: Selector = Selectors.name('username') @property def error_message(self) -> WebElement: return self.driver.find_element(*self._error_message) @property def forgot_password_link(self) -> WebElement: return self.driver.find_element(*self._forgot_password_link) @property def password_field(self) -> WebElement: return self.driver.find_element(*self._password_field) @property def sign_up_button(self) -> WebElement: return self.driver.find_element(*self._sign_up_button) @property def submit_button(self) -> WebElement: return self.driver.find_element(*self._submit_button) @property def success_message(self) -> WebElement: return self.driver.find_element(*self._success_message) @property def university_login_button(self) -> WebElement: return self.driver.find_element(*self._university_login_button) @property def username_field(self) -> WebElement: return self.driver.find_element(*self._username_field) def add_auth_token(self): """Add a captured authorization token as an environment variable.""" auth_token = json.loads(os.getenv('AUTH_TOKEN')) self.driver.add_cookie(auth_token) def capture_token(self) -> None: """Capture an authorization token.""" auth_cookie = self.driver.get_cookies()[1] if 'expiry' in auth_cookie: del auth_cookie['expiry'] json_cookie = json.dumps(auth_cookie) os.environ['AUTH_TOKEN'] = json_cookie def check_login_fail(self) -> None: """Check whether an error message is shown.""" self.driver.wait_until_visible(*self._error_message) def login(self, username: Optional[str], password: Optional[str]) -> None: """Login in to a TransLoc application. :param password: The password for login. :param username: The username for login. """ self.username_field.fill(username) self.password_field.fill(password) self.submit_button.click()
class RideCard(Component): """Objects and methods for an individual Ride Card.""" ROOT_LOCATOR: Selector = Selectors.data_id('ride-card') card_locator: Selector = Selectors.data_id('ride-card') _drop_off_prioritized: Selector = Selectors.data_id('dropoff-prioritized') _entire_ride_prioritized: Selector = Selectors.data_id( 'entire-ride-prioritized') _kebab_menu: Selector = Selectors.data_id('ride-card-kebab-menu-button') _pick_up_prioritized: Selector = Selectors.data_id('pickup-prioritized') _ride_drop_off: Selector = Selectors.data_id('ride-card-drop-off-address') _ride_drop_off_time: Selector = Selectors.data_id( 'ride-card-drop-off-time') _ride_email: Selector = Selectors.data_id('ride-card-email') _ride_id: Selector = Selectors.data_id('ride-card-ride-id') _ride_note: Selector = Selectors.data_id('ride-card-driver-note') _ride_pick_up: Selector = Selectors.data_id('ride-card-pick-up-address') _ride_pick_up_time: Selector = Selectors.data_id('ride-card-pick-up-time') _ride_status: Selector = Selectors.data_id('ride-card-ride-status') _ride_wait: Selector = Selectors.data_id('ride-card-ride-wait') _rider_name: Selector = Selectors.data_id('ride-card-rider-name') _rider_phone: Selector = Selectors.data_id('ride-card-phone') @property def kebab_menu(self) -> KebabMenu: return KebabMenu(self) @property def prioritization_modal(self) -> RidePrioritizationModal: return RidePrioritizationModal(self) @property def rider_name(self) -> str: return self.container.find_element(*self._rider_name).text @property def drop_off_address(self) -> str: return self.container.find_element(*self._ride_drop_off).text @property def drop_off_time(self) -> str: return self.container.find_element(*self._ride_drop_off_time).text @property def email(self) -> str: """Return the rider email for a ride card. The get_attribute method is used instead of .text as the email is a hidden attribute. """ return self.container.find_element( *self._ride_email).get_attribute('innerText') @property def note(self) -> str: return self.container.find_element(*self._ride_note).text @property def phone(self) -> str: return self.container.find_element(*self._rider_phone).text @property def pick_up_address(self) -> str: return self.container.find_element(*self._ride_pick_up).text @property def pick_up_time(self) -> str: return self.container.find_element(*self._ride_pick_up_time).text @property def ride_status(self) -> str: return self.container.find_element(*self._ride_status).text @property def ride_wait(self) -> str: return self.container.find_element(*self._ride_wait).text @property def ride_card_kebab_button(self) -> WebElement: return self.container.find_element(*self._kebab_menu) @property def ride_id(self) -> str: """Return the unique ride ID for a ride card. The get_attribute method is used instead of .text as the ride_id is a hidden attribute. """ return self.container.find_element( *self._ride_id).get_attribute('innerText') def drop_off_prioritized(self) -> WebElement: return self.container.wait_until_present(*self._drop_off_prioritized) def entire_ride_prioritized(self) -> WebElement: return self.container.wait_until_present( *self._entire_ride_prioritized) def pick_up_prioritized(self) -> WebElement: return self.container.wait_until_present(*self._pick_up_prioritized) def open_kebab_menu(self) -> object: """Find and open a ride card kebab menu. :returns KebabMenu: An instance of the ride's Kebab Menu. """ self.ride_card_kebab_button.click() return KebabMenu(self).wait_for_component_to_be_present()
class Dispatch(Base): """Dispatch Page objects and methods for the OnDemand Admin application.""" URL_PATH: str = f'{Base.URL_PATH}/dispatch' ROOT_LOCATOR: Selector = Selectors.data_id('dispatch-page-container') @property def cancellation_modal(self) -> CancellationModal: return CancellationModal(self) @property def ride_booking_panel(self) -> RideBookingPanel: return RideBookingPanel(self) @property def ride_card_panel(self) -> RideCardPanel: return RideCardPanel(self) @property def ride_filters(self) -> RideFilters: return RideFilters(self) @property def rider_search(self) -> RiderSearch: return RiderSearch(self) @property def ride_subscription_modal(self) -> RideSubscriptionModal: return RideSubscriptionModal(self) def cancel_ride(self, cancel_reason: str, ride: dict) -> None: """Cancel a ride that matches a ride object. :param cancel_reason: The required cancellation reason. :param ride: The ride yielded from a ride fixture. """ card: RideCard = self.ride_card_panel.surface_ride_card(ride) card.open_kebab_menu() card.kebab_menu.cancel_ride_button.click() self.cancellation_modal.cancel_ride(cancel_reason) def fill_future_ride_form(self, ride: dict) -> None: """Fill out the Future Ride form. :param ride: The ride yielded from a ride fixture. """ self.ride_booking_panel.future_ride_form.fill_future_ride_form(ride) def fill_recurring_ride_form(self, ride: dict) -> None: """Fill out the Recurring Ride form. :param ride: The ride yielded from a recurring ride fixture. """ self.ride_booking_panel.recurring_ride_form.fill_recurring_ride_form( ride) def fill_ride_form(self, service: dict, ride: dict) -> None: """Fill out the Dispatch page ride form. :param service: The service yielded from a service API fixture. :param ride: The ride yielded from a ride fixture. """ self.ride_booking_panel.open_book_a_ride_form() self.ride_booking_panel.passenger_form.fill_passenger_info_form(ride) self.ride_booking_panel.ride_form.fill_ride_info_form(service, ride)
class GroupUserList(Component): """Objects and methods for the Groups List component.""" ROOT_LOCATOR: Selector = Selectors.data_id('rider-list-container') @property def user_rows(self) -> Tuple: """Return a tuple of rider rows within the list container.""" rows: WebElements = self.container.find_elements( *GroupUserRow.ROOT_LOCATOR) return tuple(GroupUserRow(self, element) for element in rows) def filter_rows( self, user: dict, user_email: str = None, wait_for_row: bool = False, ) -> Union[GroupUserRow, None]: """Filter all user rows for a match with a user name. :param user_email: An optional email for a user. :param user: A user object yielded from a user fixture. :param wait_for_row: Optionally wait for a row. """ email: str = user['email'] if user_email is None else user_email if wait_for_row: self.wait_for_user_row(user=user, user_email=user_email) group_list: list = [ row for row in self.user_rows if email == row.email ] if not group_list: return None return group_list[0] def wait_for_user_row(self, user: dict, user_email: str = None) -> None: """Wait until a user row is located which contains a specific email. An increased wait time is used within this function as rows can take upward of five seconds to appear within the DOM. This amount is nearly doubled when running in parallel. :param user_email: An optional email for a user. :param user: A user object yielded from a user fixture. """ email: str = user['email'] if user_email is None else user_email self.driver.wait_until_visible(*Selectors.text(email), wait_time=10) def surface_user_row( self, user: dict, user_email: str = None, wait_for_row: bool = False, ) -> Union[GroupUserRow, None]: """Raise a user row that matches a user name. :param user_email: An optional email for a user. :param user: A user object yielded from a user fixture. :param wait_for_row: Optionally wait for a row. """ return self.filter_rows(user=user, user_email=user_email, wait_for_row=wait_for_row)