def handle_mfa_code_flow(driver): if driver.find_elements_by_id('submitDest'): # MFA flow LOGGER.info( 'One time multi-factor auth required. This will not happen after the first debbit run.' ) try: multi_mfa_options = False try: WebDriverWait(driver, 0).until( expected_conditions.element_to_be_clickable( (By.ID, "submitDest"))) except TimeoutException: # The Send code button is not clickable. This means there are multiple MFA options. Ask user which one to use. multi_mfa_options = True if multi_mfa_options: mfa_options = {} for i in range(1, 10): if driver.find_elements_by_id('m' + str(i) + 'label'): mfa_options[i] = driver.find_element_by_id( 'm' + str(i) + 'label').text LOGGER.info('') LOGGER.info('Choose a multi-factor authentication option.') for k, v in mfa_options.items(): LOGGER.info(' ' + str(k) + ' - ' + v) LOGGER.info('Type a number 1-9 and then hit enter: ') user_mfa_choice_input = input() # TODO put timeout around this user_mfa_choice_index = ''.join([ c for c in user_mfa_choice_input if c.isdigit() ]) # sanitize input to remove all non digit characters driver.find_element_by_id('m' + user_mfa_choice_index + 'label').click() time.sleep(1 + random.random() * 2) time.sleep(1 + random.random() * 2) driver.find_element_by_id("submitDest").click() WebDriverWait(driver, 20).until( expected_conditions.element_to_be_clickable( (By.ID, "codeValue"))) LOGGER.info('Enter OTP here: ') otp = input() # TODO put timeout around this elem = driver.find_element_by_id("codeValue") elem.send_keys(otp) time.sleep(1 + random.random() * 2) driver.find_element_by_xpath("//*[contains(@id,'ubmit')]").click( ) # submit or Submit button WebDriverWait(driver, 120).until( utils.AnyExpectedCondition( expected_conditions.element_to_be_clickable( (By.XPATH, "//img[contains(@src,'btnNoThanks')]")), expected_conditions.element_to_be_clickable( (By.XPATH, "//button[contains(text(),'Make a payment')]")))) except (KeyboardInterrupt, SystemExit): raise except Exception: pass # User may have intervened by clicking around in the UI, allow failures to be ignored
def web_automation(driver, merchant, amount): driver.get('https://www.amazon.com/gp/product/B086KKT3RX') WebDriverWait(driver, 90).until( expected_conditions.element_to_be_clickable( (By.ID, "gcui-asv-reload-buynow-button"))) for i in range(300): if driver.find_element_by_id( "gcui-asv-reload-buynow-button" ).text == 'Buy Now': # wait for 'Loading...' text to turn into 'Buy Now' break time.sleep(0.1) time.sleep(1 + random.random() * 2) # slow down automation randomly to help avoid bot detection driver.find_element_by_id('gcui-asv-reload-form-custom-amount').send_keys( utils.cents_to_str(amount)) time.sleep(1 + random.random() * 2) # slow down automation randomly to help avoid bot detection driver.find_element_by_id("gcui-asv-reload-buynow-button").click() WebDriverWait(driver, 90).until( utils.AnyExpectedCondition( expected_conditions.element_to_be_clickable( (By.ID, 'ap_email')), # first time login expected_conditions.element_to_be_clickable( (By.XPATH, "//*[contains(text(),'" + merchant.usr + "')]")), # username found on login page # Already logged in expected_conditions.element_to_be_clickable( (By.XPATH, "//*[contains(text(),'Order Summary')]")), # Checkout page expected_conditions.element_to_be_clickable( (By.XPATH, "//*[contains(text(),'a payment method')]" )) # Another version of the checkout page )) if not driver.find_elements_by_xpath( "//*[contains(text(),'Order Summary')]" ) and not driver.find_elements_by_xpath( "//*[contains(text(),'a payment method')]" ): # Not in checkout, so we did not auto login. Finish login flow. if driver.find_elements_by_xpath("//*[contains(text(),'" + merchant.usr + "')]"): driver.find_element_by_xpath( "//*[contains(text(),'" + merchant.usr + "')]").click( ) # click username in case we're on the Switch Accounts page WebDriverWait(driver, 30).until( expected_conditions.element_to_be_clickable( (By.ID, 'signInSubmit'))) time.sleep(1 + random.random() * 2) if driver.find_elements_by_id( 'ap_email' ): # if first run, fill in email. If subsequent run, nothing to fill in try: driver.find_element_by_id('ap_email').send_keys(merchant.usr) time.sleep(1 + random.random() * 2) except ElementNotInteractableException: # Sometimes this field is prefilled with Firstname Lastname and does not accept input pass if driver.find_elements_by_id('continue'): # a/b tested new UI flow driver.find_element_by_id('continue').click() WebDriverWait(driver, 5).until( expected_conditions.element_to_be_clickable( (By.ID, 'ap_password'))) time.sleep(1 + random.random() * 2) if driver.find_elements_by_name('rememberMe'): time.sleep(1 + random.random() * 2) driver.find_element_by_name('rememberMe').click() driver.find_element_by_id('ap_password').send_keys(merchant.psw) time.sleep(1 + random.random() * 2) driver.find_element_by_id('signInSubmit').click() time.sleep(1 + random.random() * 2) handle_anti_automation_challenge(driver, merchant) try: # Push Notification / Email MFA WebDriverWait(driver, 5).until( expected_conditions.element_to_be_clickable( (By.XPATH, "//*[contains(text(),'approve the notification')]"))) if driver.find_elements_by_xpath( "//*[contains(text(),'approve the notification')]"): LOGGER.info('\n') LOGGER.info( 'Please approve the Amazon login notification sent to your email or phone. Debbit will wait up to 3 minutes.' ) for i in range( 180 ): # Wait for up to 3 minutes for user to approve login notification if not driver.find_elements_by_xpath( "//*[contains(text(),'approve the notification')]" ): break time.sleep(1) except TimeoutException: pass try: # OTP text message WebDriverWait(driver, 5).until( expected_conditions.element_to_be_clickable( (By.XPATH, "//*[contains(text(),'phone number ending in')]"))) if driver.find_elements_by_id('auth-mfa-remember-device'): driver.find_element_by_id('auth-mfa-remember-device').click() sent_to_text = driver.find_element_by_xpath( "//*[contains(text(),'phone number ending in')]").text LOGGER.info(sent_to_text) LOGGER.info('Enter OTP here:') otp = input() driver.find_element_by_id('auth-mfa-otpcode').send_keys(otp) time.sleep(1 + random.random() * 2) driver.find_element_by_id('auth-signin-button').click() time.sleep(1 + random.random() * 2) except TimeoutException: pass try: # OTP email validation WebDriverWait(driver, 5).until( expected_conditions.element_to_be_clickable( (By.XPATH, "//*[contains(text(),'One Time Pass')]"))) otp_email = True except TimeoutException: otp_email = False try: driver.find_element_by_xpath( "//*[contains(text(),'one-time pass')]").click() time.sleep(1 + random.random() * 2) otp_email = True except common.exceptions.NoSuchElementException: pass if otp_email: if driver.find_elements_by_id('continue'): driver.find_element_by_id('continue').click() time.sleep(1 + random.random() * 2) handle_anti_automation_challenge(driver, merchant) try: # User may have manually advanced to gift card screen or stopped at OTP input. Handle OTP input if on OTP screen. WebDriverWait(driver, 5).until( expected_conditions.element_to_be_clickable( (By.XPATH, "//*[contains(text(),'Enter OTP')]"))) sent_to_text = driver.find_element_by_xpath( "//*[contains(text(),'@')]").text LOGGER.info(sent_to_text) LOGGER.info('Enter OTP here:') otp = input() elem = driver.find_element_by_xpath("//input") elem.send_keys(otp) time.sleep(1 + random.random() * 2) elem.send_keys(Keys.TAB) time.sleep(1 + random.random() * 2) elem.send_keys(Keys.ENTER) time.sleep(1 + random.random() * 2) except TimeoutException: pass try: WebDriverWait(driver, 5).until( expected_conditions.element_to_be_clickable( (By.XPATH, "//*[contains(text(),'Not now')]"))) driver.find_element_by_xpath( "//*[contains(text(),'Not now')]").click() time.sleep(1 + random.random() * 2) except TimeoutException: # add mobile number page pass # Now expecting to be on checkout page with debit card selection present WebDriverWait(driver, 30).until( utils.AnyExpectedCondition( expected_conditions.element_to_be_clickable( (By.XPATH, "//*[contains(text(),'Order Summary')]")), # Checkout page expected_conditions.element_to_be_clickable( (By.XPATH, "//*[contains(text(),'a payment method')]" )) # Another version of the checkout page )) if driver.find_elements_by_id('payChangeButtonId'): # expand list of cards time.sleep(1 + random.random() * 2) driver.find_element_by_id('payChangeButtonId').click() WebDriverWait(driver, 10).until( expected_conditions.element_to_be_clickable( (By.XPATH, "//span[contains(text(),'ending in " + merchant.card[-4:] + "')]"))) if driver.find_elements_by_id( 'payment-change-link' ): # expand list of cards if prior element did not exist time.sleep(1 + random.random() * 2) driver.find_element_by_id('payment-change-link').click() WebDriverWait(driver, 10).until( expected_conditions.element_to_be_clickable( (By.XPATH, "//span[contains(text(),'ending in " + merchant.card[-4:] + "')]"))) card_selected = False for element in driver.find_elements_by_xpath( "//span[contains(text(),'ending in " + merchant.card[-4:] + "')]"): try: # Amazon has redundant non-clickable elements. This will try each one until one works. time.sleep(1 + random.random() * 2) element.click() card_selected = True break except WebDriverException: pass if not card_selected: raise Exception( 'Unable to find or unable to click on card that has last 4 digits matching config file card.' ) if driver.find_elements_by_id('orderSummaryPrimaryActionBtn'): time.sleep(1 + random.random() * 2) driver.find_element_by_id('orderSummaryPrimaryActionBtn').click( ) # Click "Use this payment method" button else: # Find Continue button. There are also non clickable spans with 'Continue' in them so try all of them until one works. for element in driver.find_elements_by_xpath( "//span[contains(text(),'Continue')]"): try: time.sleep(1 + random.random() * 2) element.find_element_by_xpath('../..').click( ) # the grandparent element of the text is the clickable Continue button break except Exception: pass WebDriverWait(driver, 10).until( utils.AnyExpectedCondition( expected_conditions.element_to_be_clickable( (By.ID, 'submitOrderButtonId') ), # "Place your order" button showing, card ready to be used expected_conditions.element_to_be_clickable( (By.ID, 'placeYourOrder') ), # Other checkout page "Place your order" button showing, card ready to be used expected_conditions.element_to_be_clickable( (By.XPATH, "//input[@placeholder='ending in " + merchant.card[-4:] + "']")) # Verify card flow )) if driver.find_elements_by_xpath("//input[@placeholder='ending in " + merchant.card[-4:] + "']"): # Verify card flow elem = driver.find_element_by_xpath( "//input[@placeholder='ending in " + merchant.card[-4:] + "']") time.sleep(1 + random.random() * 2) elem.send_keys(merchant.card) time.sleep(1 + random.random() * 2) elem.send_keys(Keys.TAB) time.sleep(1 + random.random() * 2) elem.send_keys(Keys.ENTER) time.sleep(10 + random.random() * 2) if driver.find_elements_by_id('orderSummaryPrimaryActionBtn'): driver.find_element_by_id('orderSummaryPrimaryActionBtn').click( ) # Click "Use this payment method" button else: # Find Continue text, the grandparent element of the text is the clickable Continue button driver.find_element_by_xpath( "//span[contains(text(),'Continue')]").find_element_by_xpath( '../..').click() WebDriverWait(driver, 10).until( utils.AnyExpectedCondition( expected_conditions.element_to_be_clickable( (By.ID, 'submitOrderButtonId') ), # "Place your order" button showing, card ready to be used expected_conditions.element_to_be_clickable( (By.ID, 'placeYourOrder') ), # Other checkout page "Place your order" button showing, card ready to be used )) time.sleep(1 + random.random() * 2) if not is_order_total_correct(driver, amount): return Result.unverified if driver.find_elements_by_id('submitOrderButtonId'): time.sleep(1 + random.random() * 2) driver.find_element_by_id( 'submitOrderButtonId').click() # Click "Place your order" button else: time.sleep(1 + random.random() * 2) driver.find_element_by_id('placeYourOrder').click( ) # Other checkout page click "Place your order" button try: WebDriverWait(driver, 30).until( expected_conditions.element_to_be_clickable(( By.XPATH, "//*[contains(text(), 'your order has been placed') or contains(text(),'Order placed')]" ))) except TimeoutException: LOGGER.error( 'Clicked "Place your order" button, but unable to confirm if order was successful.' ) return Result.unverified if driver.find_elements_by_xpath( "//*[contains(text(), 'your order has been placed') or contains(text(),'Order placed')]" ): return Result.success else: LOGGER.error( 'Clicked "Place your order" button, but unable to confirm if order was successful.' ) return Result.unverified
def web_automation(driver, merchant, amount): driver.get('https://www.amazon.com/asv/reload/order') logged_in = utils.is_logged_in( driver, timeout=30, logged_out_element=( By.XPATH, "//button[contains(text(),'Sign In to Continue')]"), logged_in_element=(By.XPATH, "//button[starts-with(text(),'Reload')]")) time.sleep(1 + random.random() * 2) # slow down automation randomly to help avoid bot detection if not logged_in: try: driver.find_element_by_xpath( "//button[contains(text(),'Sign In to Continue')]").click() time.sleep(1 + random.random() * 2) except ElementClickInterceptedException: # spinner blocking button time.sleep(3) driver.find_element_by_xpath( "//button[contains(text(),'Sign In to Continue')]").click() time.sleep(1 + random.random() * 2) WebDriverWait(driver, 30).until( utils.AnyExpectedCondition( expected_conditions.element_to_be_clickable( (By.ID, 'ap_email')), # first time login expected_conditions.element_to_be_clickable( (By.XPATH, "//*[contains(text(),'" + merchant.usr + "')]")) # username found on page )) if driver.find_elements_by_xpath("//*[contains(text(),'" + merchant.usr + "')]"): driver.find_element_by_xpath( "//*[contains(text(),'" + merchant.usr + "')]").click( ) # click username in case we're on the Switch Accounts page WebDriverWait(driver, 30).until( expected_conditions.element_to_be_clickable( (By.ID, 'signInSubmit'))) time.sleep(1 + random.random() * 2) if driver.find_elements_by_id( 'ap_email' ): # if first run, fill in email. If subsequent run, nothing to fill in driver.find_element_by_id('ap_email').send_keys(merchant.usr) time.sleep(1 + random.random() * 2) if driver.find_elements_by_id('continue'): # a/b tested new UI flow driver.find_element_by_id('continue').click() WebDriverWait(driver, 5).until( expected_conditions.element_to_be_clickable( (By.ID, 'ap_password'))) time.sleep(1 + random.random() * 2) driver.find_element_by_id('ap_password').send_keys(merchant.psw) time.sleep(1 + random.random() * 2) driver.find_element_by_id('signInSubmit').click() time.sleep(1 + random.random() * 2) handle_anti_automation_challenge(driver, merchant) try: # OTP text message WebDriverWait(driver, 5).until( expected_conditions.element_to_be_clickable( (By.XPATH, "//*[contains(text(),'phone number ending in')]"))) if driver.find_elements_by_id('auth-mfa-remember-device'): driver.find_element_by_id('auth-mfa-remember-device').click() sent_to_text = driver.find_element_by_xpath( "//*[contains(text(),'phone number ending in')]").text LOGGER.info(sent_to_text) LOGGER.info('Enter OTP here:') otp = input() driver.find_element_by_id('auth-mfa-otpcode').send_keys(otp) time.sleep(1 + random.random() * 2) driver.find_element_by_id('auth-signin-button').click() time.sleep(1 + random.random() * 2) except TimeoutException: pass try: # OTP email validation WebDriverWait(driver, 5).until( expected_conditions.element_to_be_clickable( (By.XPATH, "//*[contains(text(),'One Time Pass')]"))) otp_email = True except TimeoutException: otp_email = False try: driver.find_element_by_xpath( "//*[contains(text(),'one-time pass')]").click() time.sleep(1 + random.random() * 2) otp_email = True except common.exceptions.NoSuchElementException: pass if otp_email: if driver.find_elements_by_id('continue'): driver.find_element_by_id('continue').click() time.sleep(1 + random.random() * 2) handle_anti_automation_challenge(driver, merchant) try: # User may have manually advanced to gift card screen or stopped at OTP input. Handle OTP input if on OTP screen. WebDriverWait(driver, 5).until( expected_conditions.element_to_be_clickable( (By.XPATH, "//*[contains(text(),'Enter OTP')]"))) sent_to_text = driver.find_element_by_xpath( "//*[contains(text(),'@')]").text LOGGER.info(sent_to_text) LOGGER.info('Enter OTP here:') otp = input() elem = driver.find_element_by_xpath("//input") elem.send_keys(otp) time.sleep(1 + random.random() * 2) elem.send_keys(Keys.TAB) time.sleep(1 + random.random() * 2) elem.send_keys(Keys.ENTER) time.sleep(1 + random.random() * 2) except TimeoutException: pass try: WebDriverWait(driver, 5).until( expected_conditions.element_to_be_clickable( (By.XPATH, "//*[contains(text(),'Not now')]"))) driver.find_element_by_xpath( "//*[contains(text(),'Not now')]").click() time.sleep(1 + random.random() * 2) except TimeoutException: # add mobile number page pass WebDriverWait(driver, 30).until( expected_conditions.element_to_be_clickable( (By.ID, 'asv-manual-reload-amount'))) driver.find_element_by_id('asv-manual-reload-amount').send_keys( utils.cents_to_str(amount)) time.sleep(1 + random.random() * 2) for element in driver.find_elements_by_xpath( "//span[contains(text(),'ending in " + merchant.card[-4:] + "')]"): try: # Amazon has redundant non-clickable elements. This will try each one until one works. element.click() time.sleep(1 + random.random() * 2) break except WebDriverException: pass driver.find_element_by_xpath( "//button[starts-with(text(),'Reload') and contains(text(),'" + utils.cents_to_str(amount) + "')]").click() time.sleep(1 + random.random() * 2) time.sleep(10) # give page a chance to load if 'thank-you' not in driver.current_url: WebDriverWait(driver, 30).until( expected_conditions.element_to_be_clickable( (By.XPATH, "//input[@placeholder='ending in " + merchant.card[-4:] + "']"))) elem = driver.find_element_by_xpath( "//input[@placeholder='ending in " + merchant.card[-4:] + "']") time.sleep(1 + random.random() * 2) elem.send_keys(merchant.card) time.sleep(1 + random.random() * 2) elem.send_keys(Keys.TAB) time.sleep(1 + random.random() * 2) elem.send_keys(Keys.ENTER) WebDriverWait(driver, 30).until( expected_conditions.element_to_be_clickable( (By.XPATH, "//button[contains(text(),'Reload $" + utils.cents_to_str(amount) + "')]"))) time.sleep(1 + random.random() * 2) driver.find_element_by_xpath( "//button[starts-with(text(),'Reload') and contains(text(),'" + utils.cents_to_str(amount) + "')]").click() time.sleep(10) # give page a chance to load if 'thank-you' not in driver.current_url: return Result.unverified return Result.success
def web_automation(driver, merchant, amount): driver.get('https://www.att.com/my/#/passthrough/overview') # Wait until login screen, promotion pop-up, or account dashboard shows. WebDriverWait(driver, 120).until( utils.AnyExpectedCondition( expected_conditions.element_to_be_clickable( (By.NAME, "password")), # logged out expected_conditions.element_to_be_clickable( (By.XPATH, "//*[contains(@id,'ancel')]" )), # mfa flow identified by cancel button expected_conditions.element_to_be_clickable( (By.XPATH, "//img[contains(@src,'btnNoThanks')]")), # logged in expected_conditions.element_to_be_clickable( (By.XPATH, "//button[contains(text(),'Make a payment')]")) # logged in )) handle_mfa_code_flow(driver) time.sleep( 1 + random.random() * 2 ) # AT&T is using bot detection software, slow down the automation a bit to help avoid detection if driver.find_elements_by_name( 'password'): # password field found, need to log in try: driver.find_element_by_id('userID').send_keys(merchant.usr) time.sleep(1 + random.random() * 2) except common.exceptions.NoSuchElementException: pass driver.find_element_by_name('password').send_keys(merchant.psw) time.sleep(1 + random.random() * 2) driver.find_element_by_xpath( "//button[contains(text(),'Sign in')]").click() try: # Wait for potential promotions screen, regular account overview, or OTP flow WebDriverWait(driver, 120).until( utils.AnyExpectedCondition( expected_conditions.element_to_be_clickable( (By.XPATH, "//img[contains(@src,'btnNoThanks')]")), expected_conditions.element_to_be_clickable( (By.XPATH, "//button[contains(text(),'Make a payment')]")), expected_conditions.element_to_be_clickable( (By.XPATH, "//*[contains(@id,'ancel')]" )) # mfa flow identified by cancel button )) except TimeoutException: pass # Try continuing to the makePayment page just in case log in worked, but timeout failed time.sleep(1 + random.random() * 2) handle_mfa_code_flow(driver) driver.get("https://www.att.com/my/#/makePayment") WebDriverWait(driver, 20).until( expected_conditions.element_to_be_clickable( (By.ID, "MAP_Amount_TextField"))) time.sleep(1 + random.random() * 2) cur_balance_html_text = driver.find_element_by_xpath( "//span[contains(text(), 'Balance due')]").text cur_balance = cur_balance_html_text.split('$')[1] if utils.str_to_cents(cur_balance) == 0: LOGGER.warning('AT&T balance is zero, will try again later.') return Result.skipped elif utils.str_to_cents(cur_balance) < amount: amount = utils.str_to_cents(cur_balance) elem = driver.find_element_by_id('MAP_Amount_TextField') elem.clear() time.sleep(1 + random.random() * 2) elem.send_keys(utils.cents_to_str(amount)) time.sleep(1 + random.random() * 2) if driver.find_elements_by_xpath( "//*[contains(text(),'Debit or credit card')]"): driver.find_element_by_xpath( "//*[contains(text(),'Debit or credit card')]").click() time.sleep(1 + random.random() * 2) driver.find_element_by_xpath("//input[@value='" + merchant.card + "']").click() time.sleep(1 + random.random() * 2) driver.find_element_by_xpath("//*[contains(text(),'Pay $" + utils.cents_to_str(amount) + "')]").click() time.sleep(1 + random.random() * 2) try: WebDriverWait(driver, 120).until( utils.AnyExpectedCondition( expected_conditions.presence_of_element_located( (By.XPATH, "//*[contains(text(),'We got your $" + utils.cents_to_str(amount) + " payment')]")), expected_conditions.presence_of_element_located(( By.XPATH, "//*[contains(text(),'multiple payments for the same amount')]" )))) if driver.find_elements_by_xpath( "//*[contains(text(),'multiple payments for the same amount')]" ): LOGGER.info( "Duplicate payment amount not accepted within 24 hours. Trying again later. Please use a larger range between amount_min and amount_max in config.txt for att_bill_pay to avoid duplicate payment amount scenarios." ) return Result.skipped elif driver.find_elements_by_xpath("//*[text()='We got your $ + " + utils.cents_to_str(amount) + " payment']"): return Result.success else: return Result.unverified except (KeyboardInterrupt, SystemExit): raise except Exception: return Result.unverified # Purchase command was executed, yet we are unable to verify that it was successfully executed. # since debbit may have spent money but isn't sure, we log the error and stop any further payments for this merchant until the user intervenes return Result.success
def web_automation(driver, merchant, amount): driver.get('https://www.att.com/my/#/passthrough/overview') # Wait until login screen, promotion pop-up, or account dashboard shows. WebDriverWait(driver, 120).until(utils.AnyExpectedCondition( expected_conditions.element_to_be_clickable((By.ID, "password")), expected_conditions.element_to_be_clickable((By.XPATH, "//img[contains(@src,'btnNoThanks')]")), expected_conditions.element_to_be_clickable((By.XPATH, "//button[contains(text(),'Make a payment')]")), )) if not driver.find_elements_by_id('password'): # password field found, need to log in try: driver.find_element_by_id('userName').send_keys(merchant.usr) except common.exceptions.ElementNotInteractableException: pass try: driver.find_element_by_xpath("//a[@value='" + merchant.usr + "']").click() except common.exceptions.NoSuchElementException: pass driver.find_element_by_id('password').send_keys(merchant.psw) driver.find_element_by_xpath("//button[contains(@id,'loginButton')]").click() # Wait for potential promotions screen, regular account overview, or OTP flow WebDriverWait(driver, 30).until(utils.AnyExpectedCondition( expected_conditions.element_to_be_clickable((By.XPATH, "//img[contains(@src,'btnNoThanks')]")), expected_conditions.element_to_be_clickable((By.XPATH, "//button[contains(text(),'Make a payment')]")), expected_conditions.element_to_be_clickable((By.NAME, "Send code")) )) try: # OTP text validation driver.find_element_by_name("Send code").click() WebDriverWait(driver, 20).until(expected_conditions.element_to_be_clickable((By.ID, "verificationCodeInput"))) sent_to_text = driver.find_element_by_xpath("//*[contains(text(),'We sent it to')]").text.strip() LOGGER.info(sent_to_text) LOGGER.info('Enter OTP here: ') otp = input() elem = driver.find_element_by_id("verificationCodeInput") elem.send_keys(otp) elem.send_keys(Keys.ENTER) except common.exceptions.NoSuchElementException: pass # Wait for potential promotions screen or regular account overview WebDriverWait(driver, 30).until(utils.AnyExpectedCondition( expected_conditions.element_to_be_clickable((By.XPATH, "//img[contains(@src,'btnNoThanks')]")), expected_conditions.element_to_be_clickable((By.XPATH, "//button[contains(text(),'Make a payment')]")) )) try: # Dismiss promotions screen if it appeared driver.find_element_by_xpath("//*[contains(@src,'btnNoThanks')]").click() WebDriverWait(driver, 20).until(expected_conditions.element_to_be_clickable((By.XPATH, "//*[contains(text(),'Make a payment')]"))) except common.exceptions.NoSuchElementException: pass driver.get("https://www.att.com/my/#/makePayment") # Enter amount and select payment card WebDriverWait(driver, 20).until(expected_conditions.element_to_be_clickable((By.ID, "pmtAmount0"))) elem = driver.find_element_by_id('pmtAmount0') elem.clear() elem.send_keys(utils.cents_to_str(amount)) elem = driver.find_element_by_id("paymentMethod0") before_first_payment_card = "Select Payment Method" after_last_payment_card = "New checking / savings account" while elem.get_attribute("value").lower() != before_first_payment_card.lower(): elem.send_keys(Keys.UP) while elem.get_attribute("value").lower() != merchant.card.lower() and elem.get_attribute("value").lower() != after_last_payment_card.lower(): elem.send_keys(Keys.DOWN) if elem.get_attribute("value").lower() == after_last_payment_card.lower(): raise Exception("Payment method " + merchant.card + " not found in list of saved payment methods") # Continue elem.send_keys(Keys.ENTER) try: WebDriverWait(driver, 20).until(expected_conditions.presence_of_element_located((By.XPATH, "//html/body/div[contains(@class,'modalwrapper active')]//p[contains(text(),'paying more than the amount due')]"))) driver.find_element_by_xpath("//html/body/div[contains(@class,'modalwrapper active')]//button[text()='OK']").click() except TimeoutException: pass # Submit WebDriverWait(driver, 20).until(expected_conditions.element_to_be_clickable((By.XPATH, "//button[text()='Submit']"))) WebDriverWait(driver, 20).until(expected_conditions.invisibility_of_element_located((By.ID, "loaderOverlay"))) time.sleep(2) driver.find_element_by_xpath("//button[text()='Submit']").click() try: WebDriverWait(driver, 20).until(expected_conditions.presence_of_element_located((By.XPATH, "//*[contains(text(),'Thank you for your payment')]"))) WebDriverWait(driver, 20).until(expected_conditions.presence_of_element_located((By.XPATH, "//*[text()='$" + utils.cents_to_str(amount) + "']"))) except TimeoutException: return Result.unverified # Purchase command was executed, yet we are unable to verify that it was successfully executed. # since debbit may have spent money but isn't sure, we log the error and stop any further payments for this merchant until the user intervenes return Result.success
def web_automation(driver, merchant, amount): driver.get('https://www.att.com/my/#/passthrough/overview') # Wait until login screen, promotion pop-up, or account dashboard shows. WebDriverWait(driver, 120).until( utils.AnyExpectedCondition( expected_conditions.element_to_be_clickable( (By.NAME, "password")), # logged out expected_conditions.element_to_be_clickable( (By.XPATH, "//*[contains(@id,'ancel')]" )), # mfa flow identified by cancel button expected_conditions.element_to_be_clickable( (By.XPATH, "//img[contains(@src,'btnNoThanks')]")), # logged in expected_conditions.element_to_be_clickable( (By.XPATH, "//button[contains(text(),'Make a payment')]")) # logged in )) handle_mfa_code_flow(driver) time.sleep( 1 + random.random() * 2 ) # AT&T is using bot detection software, slow down the automation a bit to help avoid detection if driver.find_elements_by_name( 'password'): # password field found, need to log in try: driver.find_element_by_id('userID').send_keys(merchant.usr) time.sleep(1 + random.random() * 2) except common.exceptions.NoSuchElementException: pass driver.find_element_by_name('password').send_keys(merchant.psw) time.sleep(1 + random.random() * 2) driver.find_element_by_xpath( "//button[contains(text(),'Sign in')]").click() try: # Wait for potential promotions screen, regular account overview, or OTP flow WebDriverWait(driver, 120).until( utils.AnyExpectedCondition( expected_conditions.element_to_be_clickable( (By.XPATH, "//img[contains(@src,'btnNoThanks')]")), expected_conditions.element_to_be_clickable( (By.XPATH, "//button[contains(text(),'Make a payment')]")), expected_conditions.element_to_be_clickable( (By.XPATH, "//*[contains(@id,'ancel')]" )) # mfa flow identified by cancel button )) except TimeoutException: pass # Try continuing to the makePayment page just in case log in worked, but timeout failed time.sleep(1 + random.random() * 2) handle_mfa_code_flow(driver) driver.get("https://www.att.com/my/#/makePayment") # Enter amount and select payment card WebDriverWait(driver, 20).until( expected_conditions.element_to_be_clickable((By.ID, "pmtAmount0"))) time.sleep(1 + random.random() * 2) elem = driver.find_element_by_id('pmtAmount0') elem.clear() time.sleep(1 + random.random() * 2) elem.send_keys(utils.cents_to_str(amount)) time.sleep(1 + random.random() * 2) elem = driver.find_element_by_id("paymentMethod0") before_first_payment_card = "Select Payment Method" after_last_payment_card = "New checking / savings account" while elem.get_attribute( "value").lower() != before_first_payment_card.lower(): elem.send_keys(Keys.UP) time.sleep(1 + random.random()) while elem.get_attribute( "value").lower() != merchant.card.lower() and elem.get_attribute( "value").lower() != after_last_payment_card.lower(): elem.send_keys(Keys.DOWN) time.sleep(1 + random.random()) if elem.get_attribute("value").lower() == after_last_payment_card.lower(): raise Exception("Payment method " + merchant.card + " not found in list of saved payment methods") # Continue elem.send_keys(Keys.ENTER) time.sleep(1 + random.random() * 2) try: WebDriverWait(driver, 20).until( expected_conditions.presence_of_element_located(( By.XPATH, "//html/body/div[contains(@class,'modalwrapper active')]//p[contains(text(),'paying more than the amount due')]" ))) driver.find_element_by_xpath( "//html/body/div[contains(@class,'modalwrapper active')]//button[text()='OK']" ).click() time.sleep(1 + random.random() * 2) except TimeoutException: pass # Submit WebDriverWait(driver, 20).until( expected_conditions.element_to_be_clickable( (By.XPATH, "//button[text()='Submit']"))) WebDriverWait(driver, 20).until( expected_conditions.invisibility_of_element_located( (By.ID, "loaderOverlay"))) time.sleep(2 + random.random()) driver.find_element_by_xpath("//button[text()='Submit']").click() try: WebDriverWait(driver, 120).until( utils.AnyExpectedCondition( expected_conditions.presence_of_element_located( (By.XPATH, "//*[contains(text(),'Thank you for your payment')]")), expected_conditions.presence_of_element_located( (By.XPATH, "//*[contains(text(),'payment was unsuccessful')]")))) if driver.find_elements_by_xpath( "//*[contains(text(),'multiple payments')]"): return Result.skipped # att does not allow payments of the same dollar amount within 24 hours, skip this purchase and try again 24 hours later elif driver.find_elements_by_xpath("//*[text()='$" + utils.cents_to_str(amount) + "']"): return Result.success else: return Result.unverified except (KeyboardInterrupt, SystemExit): raise except Exception: return Result.unverified # Purchase command was executed, yet we are unable to verify that it was successfully executed. # since debbit may have spent money but isn't sure, we log the error and stop any further payments for this merchant until the user intervenes return Result.success