class FutureRideForm(Component): """Objects and methods for the future ride form.""" ROOT_LOCATOR: Selector = Selectors.data_id('future-ride-container') _date_field: Selector = Selectors.name('pickupDate') _time_field: Selector = Selectors.name('time') @property def date_field(self) -> WebElement: return self.container.find_element(*self._date_field) @property def time_field(self) -> WebElement: return self.container.find_element(*self._time_field) def fill_future_ride_form(self, ride: dict) -> None: """Fill out the future ride form. :param ride: The ride yielded from a ride fixture. """ ride_time: datetime = datetime.strptime( ride['pickup']['timestamp'], '%Y-%m-%dT%H:%M:%S.%fZ', ) self.time_field.fill_picker_input(ride_time.strftime('%I:%M %p')) self.date_field.fill_picker_input(ride_time.strftime('%m-%d-%Y'))
class EditForm(Component): """Edit Form component objects and methods for the Rides Info component. The Edit Form component contains tools for editing the pick up and drop off locations for a ride featured within the Details page. The Edit Form may be accessed from the Ride Info component. """ ROOT_LOCATOR: Selector = Selectors.data_id('details-edit-ride-container') _pick_up_address: Selector = Selectors.name('pickup') _drop_off_address: Selector = Selectors.name('dropoff') _swap_button: Selector = Selectors.data_id('edit-swap-button') _update_button: Selector = Selectors.data_id('edit-update-button') _cancel_button: Selector = Selectors.data_id('edit-cancel-button') _total_passenger_field: Selector = Selectors.name('capacity') _wheelchair_check_box: Selector = Selectors.name('wheelchair') @property def cancel_button(self) -> WebElement: return self.container.find_element(*self._cancel_button) @property def drop_off_address(self) -> WebElement: return self.container.find_element(*self._drop_off_address) @property def pick_up_address(self) -> WebElement: return self.container.find_element(*self._pick_up_address) @property def schedule_form(self) -> ScheduleForm: return ScheduleForm(self) @property def swap_button(self) -> WebElement: return self.container.find_element(*self._swap_button) @property def total_passenger_field(self) -> WebElement: return self.container.find_element(*self._total_passenger_field) @property def update_button(self) -> WebElement: return self.container.find_element(*self._update_button) @property def wheelchair_check_box(self) -> WebElement: return self.container.find_element(*self._wheelchair_check_box) def swap_addresses(self) -> None: """Select the swap button, then save the edit.""" self.swap_button.click() self.update_button.click()
class CancellationModal(Component): """Cancellation Modal component objects and methods for OnDemand applications. The cancellation modal may be accessed by selected 'Cancel' for a ride The modal may be found in the Ride Card or Ride Row kebab menus. """ ROOT_LOCATOR: Selector = Selectors.data_id('cancellation-modal-container') _cancel_button: Selector = Selectors.data_id('cancellation-modal-cancel-button') _confirm_button: Selector = Selectors.data_id('cancellation-modal-confirm-button') _cancel_reason: Selector = Selectors.name('terminal-reason') @property def ride_cancel_input(self) -> WebElement: return self.container.find_element(*self._cancel_reason) @property def ride_cancel_confirm(self) -> WebElement: return self.container.find_element(*self._confirm_button) def cancel_ride(self, reason: str) -> None: """Cancel a ride, then wait for the cancellation modal to no longer be visible. :param reason: The cancellation reason for the ride. """ self.ride_cancel_input.fill(reason) self.ride_cancel_confirm.click() self.wait_for_component_to_not_be_visible()
class GroupForm(Component): """Objects and methods for the Group Form component.""" ROOT_LOCATOR: Selector = Selectors.data_id('group-container') _cancel_button: Selector = Selectors.data_id('cancel-button') _delete_button: Selector = Selectors.data_id('delete-group-button') _name_field: Selector = Selectors.name('name') _save_button: Selector = Selectors.data_id('save-group-button') @property def cancel_button(self) -> WebElement: return self.container.find_element(*self._cancel_button) @property def delete_group_button(self) -> WebElement: return self.container.find_element(*self._delete_button) @property def name_field(self) -> WebElement: return self.container.find_element(*self._name_field) @property def save_button(self) -> WebElement: return self.container.find_element(*self._save_button) def fill_form(self, name: str) -> None: """Fill out a group form, then submit the form. :param name: The name of the group. """ self.name_field.fill(name)
class ScheduleForm(Component): """Objects and methods for the Schedule form. The Schedule Form component contains toggles and pickers for editing a ride's pickup time from within the Details page. """ ROOT_LOCATOR: Selector = Selectors.data_id('ride-edit-future-container') _asap_ride_button: Selector = Selectors.data_id('ride-edit-asap-button') _future_ride_button: Selector = Selectors.data_id( 'ride-edit-future-button') _date_field: Selector = Selectors.name('pickupDate') _time_field: Selector = Selectors.name('time') @property def asap_ride_button(self) -> WebElement: return self.container.find_element(*self._asap_ride_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_button) @property def time_field(self) -> WebElement: return self.container.find_element(*self._time_field) 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 != 'asap': raise RideTypeException( f'The ride type: {ride_type} is not supported.\n' f'Expected "asap" or "future"." or "recurring".', )
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 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 GroupUserForm(Component): """Objects and methods for the Group Form component.""" ROOT_LOCATOR: Selector = Selectors.data_id('rider-editor-form-container') _clear_button: Selector = Selectors.data_id('filter-clear') _email_field: Selector = Selectors.name('rider-email') @property def clear_button(self) -> WebElement: return self.container.find_element(*self._clear_button) @property def email_field(self) -> WebElement: return self.container.find_element(*self._email_field) def fill_form(self, email: str) -> None: """Fill out email field, then submit the form. :param name: The email of the user. """ self.email_field.fill(email)
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()
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 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 RecurringRideForm(Component): """Objects and methods for the recurring ride form.""" ROOT_LOCATOR: Selector = Selectors.data_id('recurring-ride-container') _end_date_field: Selector = Selectors.name('endDate') _start_date_field: Selector = Selectors.name('startDate') _time_field: Selector = Selectors.name('time') _days: List[str] = [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', ] @property def end_date_field(self) -> WebElement: return self.container.find_element(*self._end_date_field) @property def start_date_field(self) -> WebElement: return self.container.find_element(*self._start_date_field) @property def time_field(self) -> WebElement: return self.container.find_element(*self._time_field) def fill_recurring_ride_form(self, recurring_ride: dict) -> None: """Fill out the recurring ride form. Each timestamp must be converted into a datetime object, then converted to specific strings for input. For day names, the datetime objects are processed through a set comprehension and for loop which selects each day within the total listing. Set is used so that only one entry is ever selected. The ride.pickup datetime object is input to the start date field while the last datetime object within the ride_schedule list is input to the end date field. Finally, the time is parsed from the first datetime object and passed to the time field since the time of each ride will always be the same. :param recurring_ride: The recurring ride yielded from a recurring ride fixture. """ rides: List[dict] = recurring_ride['rides'] ride_start_date: datetime = datetime.strptime( recurring_ride['ride']['pickup']['timestamp'], '%Y-%m-%dT%H:%M:%S.%fZ', ) ride_schedule: List[datetime] = [ datetime.strptime(date_string['timestamp'], '%Y-%m-%dT%H:%M:%S.%fZ') for date_string in rides ] ride_days: Set[str] = {day.strftime('%A') for day in ride_schedule} ride_time: datetime = datetime.strptime(rides[0]['timestamp'], '%Y-%m-%dT%H:%M:%S.%fZ') for day in ride_days: self.select_day(day) self.start_date_field.fill_picker_input( ride_start_date.strftime('%m-%d-%Y')) self.end_date_field.fill_picker_input( ride_schedule[-1].strftime('%m-%d-%Y')) self.time_field.fill_picker_input(ride_time.strftime('%I:%M %p')) def select_day(self, day: str) -> None: """Select a specific day for a recurring ride schedule. :param day: The specified day. """ _groomed_day: str = day.lower() if day not in self._days: raise SelectionException( f'The day: {day} is invalid. Please enter a valid day.') self.container.find_element( *Selectors.data_test_id(f'day-of-week-input-{_groomed_day}'), ).click()