Best Python code snippet using Airtest
client.py
Source:client.py
...16 self.jsf_state = 017 self.debug = debug18 self._location_cache = {} # Maps location strings to location codes19 self.last_resp = None # Keep the last response for debugging20 self.prev_currency_code = self._translate_code('ALCurrencyFormat', 'USD') # Default currency for the21 # _annotate_fx function22 self._log_file = open('log_' + time.strftime('%Y-%m-%d %H%M%S') + '.txt', 'wb')23 @staticmethod24 def _get_numeric_code(code_type, code):25 try:26 code_num = int(code)27 except ValueError:28 code_num = codes[code_type][code]29 return code_num30 def _translate_code(self, code_type, code):31 """32 Check if code is a text code, and if it is, translate it; if it's already33 a number, just return it directly34 """35 return 'type:{0};id:{1};'.format(code_type, self._get_numeric_code(code_type, code))36 @staticmethod37 def _format_date(date):38 """39 Convert dates to the correct, needed string format40 """41 if isinstance(date, datetime.datetime):42 return date.strftime('%m/%d/%y')43 # Assume it's in an okay format otherwise44 return date45 def log(self, *args):46 for arg in args:47 print >> self._log_file, arg48 def _update_state(self, resp):49 # Get the jsf_tree_64 and jsf_state_64 variables to be used next50 match = re.search('jsf_tree_64" value="(\d+)', resp.text)51 orig_resp = resp52 if match is None:53 # Some pages return a window.location redirect54 match = re.search('window.location = "([^"]*)', resp.text)55 if match is not None:56 sso_url = match.group(1)57 resp = self.session.get(sso_url, verify=False)58 self.log('Opening SSO URL: ' + sso_url)59 # Some pages return a frame, and we need to get the contents of that frame to get the ER60 elif resp.text.find(urls.TAB_FRAME_PATH) != -1:61 resp = self.session.get(urls.TAB_FRAME, verify=False)62 self.log('Opening tab frame: ' + urls.TAB_FRAME)63 match = re.search('jsf_tree_64" value="(\d+)', resp.text)64 jsf_state = match.group(1)65 match2 = re.search('XM_FORM_TOKEN" value="([^"]+)', resp.text)66 xm_form_token = match2.group(1)67 match2 = re.search('XM_FORM_NAME" value="([^"]+)', resp.text)68 xm_form_name = match2.group(1)69 resp = self.session.post(urls.TAB_FRAME, {70 'jsf_tree_64': jsf_state,71 'jsf_state_64': jsf_state,72 'XM_FORM_TOKEN': xm_form_token,73 'XM_FORM_NAME': xm_form_name,74 'jsf_viewid': "/suite/common/initial_tab_frame.jsp",75 'startForm_SUBMIT': '1'76 }, verify=False)77 match = re.search('jsf_tree_64" value="(\d+)', resp.text)78 if match is None:79 raise Exception('Could not find jsf_tree_64 for response to {0} ({1}): {2}'.format(80 resp.url, orig_resp.url, resp.text))81 self.jsf_state = match.group(1)82 # Get the AJAX token83 match = re.search('XM_FORM_TOKEN" value="([^"]+)', resp.text)84 if match is None:85 raise Exception('Could not find XM_FORM_TOKEN for response to {0} ({1}): {2}'.format(86 resp.url, orig_resp.url, resp.text))87 self.xm_form_token = match.group(1)88 # Get the AJAX token89 match = re.search('XM_FORM_NAME" value="([^"]+)', resp.text)90 if match is None:91 raise Exception('Could not find XM_FORM_NAME for response to {0} ({1}): {2}'.format(92 resp.url, orig_resp.url, resp.text))93 self.xm_form_name = match.group(1)94 def _get(self, url, update_state = True, **kwargs):95 self.log('GET: ' + url)96 resp = self.session.get(url, verify=False, **kwargs)97 self.log('GET response: ' + resp.text)98 self.last_resp = resp99 if update_state:100 self._update_state(resp)101 return resp102 def _post(self, url, post_fields = None, update_state = True, **kwargs):103 if post_fields is None:104 post_fields = {}105 post_fields['jsf_tree_64'] = self.jsf_state106 post_fields['jsf_state_64'] = self.jsf_state107 if self.xm_form_token and self.xm_form_name:108 post_fields['XM_FORM_TOKEN'] = self.xm_form_token109 post_fields['XM_FORM_NAME'] = self.xm_form_name110 self.log('POST: ' + url)111 self.log('POST fields: ', post_fields)112 resp = self.session.post(url, post_fields, verify=False, **kwargs)113 self.last_resp = resp114 self.log('Response: ' + resp.text)115 if update_state:116 self._update_state(resp)117 return resp118 def login_expenses(self, username, tracking_num = None):119 s = self.session120 # Log in to expenses121 params = {122 'externalURL': '/ExtnWebApplication?documentType=100',123 'userLogin': username124 }125 if tracking_num is not None:126 params['trackingNum'] = tracking_num127 r = s.get(urls.ER_AUTHENTICATION, verify=False, params=params)128 # Do Javascript redirect manually129 match = re.search('window.location = "([^"]*)', r.text)130 if match is None:131 raise Exception('Could not log in to expense reporting')132 sso_url = match.group(1)133 resp = self._get(sso_url)134 return resp135 @staticmethod136 def _get_form_data(resp_text):137 """138 Given a page's HTML, get the form values and submit URL139 :param resp_text: page's HTML140 :return: tuple of (url, dict with form values)141 """142 h = HTMLParser.HTMLParser()143 # Get the submission URL144 match = re.search('action="([^"]+)', resp_text)145 url = h.unescape(match.group(1))146 # Get the form fields147 matches = re.findall('<input[^>]* name="([^"]+)"[^>]* value="([^"]+)', resp_text)148 form_fields = {match[0]: h.unescape(match[1]) for match in matches}149 return (url, form_fields)150 def login(self, username, password):151 # Submit analytics152 self.submit_analytics(username)153 s = self.session154 # SSO login155 r = s.get(urls.LOGIN, verify=False)156 if r.text.find('Access rights validated') == -1:157 raise Exception('Could not log in; please make sure you are logged in on McKinsey VPN')158 (url, form_fields) = self._get_form_data(r.text)159 # Re-request as POST160 r = s.post(url, form_fields, verify=False)161 # VPN login required since we don't have proxy pre-installed162 r = s.post(urls.VPN_LOGIN, {163 'username': username,164 'password': password,165 'vhost': 'standard'166 })167 match = re.search('input type="hidden" name="dummy" value="([^"]+)', r.text)168 if match is None:169 print >> sys.stderr, r.text170 raise Exception('Could not log in to proxy; make sure you\'re connected on McKinsey VPN and your password is correct')171 dummy = match.group(1)172 # Finish login by doing SSO again173 r = s.post(url, {174 'dummy': dummy175 }, verify=False)176 (url, form_fields) = self._get_form_data(r.text)177 r = s.post(url, form_fields, verify=False)178 # Final login to expenses tool with SSO password179 (url, form_fields) = self._get_form_data(r.text)180 r = s.post(urls.AUTHENTICATION, form_fields, verify=False)181 self.username = username182 # Log in to expenses183 self.login_expenses(username)184 def submit_analytics(self, username):185 s = self.session186 r = s.get(urls.ANALYTICS, params={'user': username})187 def open_expense_report(self, tracking_num):188 self.login_expenses(self.username, tracking_num)189 resp = self._post(urls.START_APP, {190 'jsf_viewid': '/app/document/startApp.jsp',191 'startForm:RealUserLoginInput': '',192 'startForm:copyDocInput': 'false',193 'startForm:DocNumInput': tracking_num,194 'startForm:HtmlAuditInput': 'false',195 'startForm:StartWithSpreadsheetImportInput': 'false',196 'startForm:QuickItemInput': 'false',197 'startForm:VoiceItemInput': 'false',198 'startForm:startBtn': 'StartApp',199 'startForm_SUBMIT': '1',200 'startForm:_link_hidden_': ''201 })202 if resp.text.find(tracking_num) == -1:203 raise Exception('Could not open ER step 1 ' + str(tracking_num) + ': ' + resp.text)204 first_jsf_state = self.jsf_state205 resp = self._post(urls.TAB_FRAME, {206 'jsf_viewid': '/suite/common/initial_tab_frame.jsp',207 'startForm_SUBMIT': '1',208 'autoScroll': '',209 'startForm:_link_hidden_': ''210 })211 if self.jsf_state <= first_jsf_state:212 # This should have incremented the jsf_state by 1213 raise Exception('Could not open ER step 2 '+ resp.text)214 return resp215 def close_expense_report(self, tracking_num, skip_open = False):216 if not skip_open:217 self.open_expense_report(tracking_num)218 # First load the prerequisite page219 resp = self._post(urls.CLOSE_EXPENSE_REPORT, {})220 # Actually close the expense report221 resp = self._post(urls.CLOSE_EXPENSE_REPORT, {222 'mainForm_SUBMIT': '1',223 'mainForm:_link_hidden_': 'mainForm:closeBtn2'224 }, False)225 # Check for correct response226 if resp.text.find('Closing window!') == -1:227 raise Exception('Error while closing expense report: ' + resp.text)228 return resp229 def create_expense_report(self, title):230 # Do the start form231 resp = self._post(urls.START_APP)232 if resp.status_code >= 400:233 raise Exception('Error while starting app: ' + resp.text)234 # Create expense report235 resp = self._post(urls.CREATE_ER, {236 'headerForm:title-PFAField': title,237 'headerForm:ADC_1210321500': '', # Charge code ID238 'headerForm:ADC_1210321500_input': '', # Charge code239 'headerForm_SUBMIT': '1',240 'headerForm:continueBtn2': 'Continue'241 })242 if resp.status_code >= 400:243 raise Exception('Error while starting app: ' + resp.text)244 # Return the ER code245 match = re.search('ER\d+', resp.text)246 if match is None:247 raise Exception('Error while getting Expense Report ID: ' + resp.text)248 return match.group(0)249 def delete_expense_report(self, er, skip_close=False):250 if not skip_close:251 self.close_expense_report(er)252 resp = self._get(urls.REAL_INDEX)253 # Get the form submission URL254 pattern = 'action="([^"]*)"'255 match = re.search(pattern, resp.text)256 if match is None:257 raise Exception('Unexpected HTML while getting list of reports; could not find form submission URL')258 form_submit_url = match.group(1)259 # Split it based on the tr260 split_text = re.split('<tr class="itemTable_Row\d">', resp.text)261 # Find the entry corresponding to our ER262 for text in split_text:263 if text.find(er) != -1:264 pattern = "(myDocumentsForm:workItemsListId_\d+:_id\d+)';" +\265 "document.forms\['myDocumentsForm'\].elements\['trackingNo'\].value='([\d-]+)'"266 match = re.search(pattern, text)267 if match is None:268 raise Exception('Unexpected HTML while getting list of reports: could not find trackingNo: ' +269 pattern + ' in ' + text)270 listId = match.group(1)271 trackingNo = match.group(2)272 resp = self._post(form_submit_url, {273 'recallMessage': 'Are you sure you want to recall this document?',274 'jsf_viewid': '/portal/inbox/mydocuments.jsp',275 'myDocumentsForm_SUBMIT': '1',276 'trackingNo': trackingNo,277 'skipRequiredValidations': '',278 'workitemId': '',279 'navPath': '',280 'myDocumentsForm:_link_hidden_': str(listId)281 })282 if resp.text.find('You cannot delete this document') != -1:283 # Get the actual error message if possible284 pattern = 'You cannot delete this document at this time.[^<]*'285 match = re.search(pattern, resp.text)286 if match is not None:287 raise Exception('Could not delete document: ' + match.group(0))288 else:289 raise Exception('Could not delete document: ' + resp.text)290 return291 raise Exception('Could not find the report with code ' + str(er))292 def create_entry(self, expenseType):293 """294 Create an expense entry295 :param expenseType: string of the expense type (e.g. "Hotel", "Meals 1 - Travel Dining Alone")296 :rtype response297 """298 # Initial load of the page to set state299 self.log('Initial add expense page load')300 resp = self._post(urls.ADD_EXPENSE)301 self.log('Creating expense: ' + expenseType)302 resp = self._post(urls.ADD_EXPENSE, {303 'expenseTypeId': self._get_numeric_code('ExpenseType', expenseType),304 'mainForm_SUBMIT': '1',305 'mainForm:_link_hidden_': "mainForm:headerExpenseTypesList_3:headerExpenseTypeSelectLink"306 })307 if resp.status_code >= 400 or resp.text.find('Expense Item') == -1:308 raise Exception('Error while creating expense: ' + resp.text)309 return resp310 def confirm_line_warnings(self, resp):311 """312 Check if we have line warnings from submitting a request, and if it does,313 auto-accept it314 """315 if resp.text.find('lineViolationForm') != -1:316 new_resp = self._post(urls.SKIP_VIOLATION, {317 'lineViolationForm:continueBtn2': 'Continue',318 'lineViolationForm_SUBMIT': '1',319 'lineViolationForm:_link_hidden_': ''320 })321 return new_resp322 return resp323 suggestions_cache = {}324 def _get_suggestions(self, field, value):325 # Use the cache when possible326 if field in self.suggestions_cache and value in self.suggestions_cache[field]:327 return self.suggestions_cache[field][value]328 resp = self._post(urls.LOOKUP_CODE + '?affectedAjaxComponent=' + field,329 {field: value}, False)330 matches = re.findall('id="[^"]*:([^:"]*)">([^<]*)</li>', resp.text)331 if field not in self.suggestions_cache:332 self.suggestions_cache[field] = {}333 if value not in self.suggestions_cache[field]:334 self.suggestions_cache[field][value] = matches335 return matches336 def _get_ajax_suggestions(self, field, value):337 # Use the cache when possible338 if field in self.suggestions_cache and value in self.suggestions_cache[field]:339 return self.suggestions_cache[field][value]340 resp = self._post(urls.LOOKUP_CODE, {341 'affectedAjaxComponent': field,342 'affectedAjaxComponentValue': value343 }, update_state=False)344 # Build the cache structures345 if field not in self.suggestions_cache:346 self.suggestions_cache[field] = {}347 results = resp.json()348 matches = []349 for result in results:350 code = result['value']351 label = urllib.unquote(result['label']).decode('utf8')352 if label != '':353 matches.append((code, label))354 self.suggestions_cache[field][value] = matches355 return matches356 def _get_location_suggestions(self, location):357 """358 Given a string for a location, returns the list of all matches and codes359 :param location: city name (e.g. "San Francisco")360 :return: [(code, locationName), (code2, locationName2), ...]361 :rtype: list362 """363 return self._get_ajax_suggestions('editItemForm:ADC_3048906', location)364 @staticmethod365 def _clarify(suggestions, strings, allow_none = False):366 """367 Prompt the user for clarification from a list of options368 :param suggestions: list of suggestions369 :return: the item in the list the user eventually selects370 """371 for i in xrange(len(suggestions)):372 print '[{0}] {1}'.format(i + 1, strings[i])373 if allow_none:374 print '[0] NONE of the above'375 confirmed = False376 selection = -1377 while not confirmed:378 while not ((selection >= 1 and selection <= len(suggestions)) or (allow_none and selection == 0)):379 try:380 print 'Enter the number: '381 selection = int(raw_input())382 except:383 print 'That was not a number: please try again'384 selected_string = strings[selection - 1] if selection != 0 else 'None of the above'385 print 'You selected "{0}"; is that correct?'.format(selected_string)386 print '(Enter y for yes, n for no) '387 confirm = raw_input()388 if confirm == 'y':389 confirmed = True390 if selection >= 1:391 return suggestions[selection - 1]392 return None # allow_none is true393 def _get_location(self, location, prompt = True):394 """395 Get the code for a location, prompting the user to clarify if multiple396 options397 :param location: city name string398 :return: a tuple of the code and the name (e.g. (3001280, 'San Salvador/El Salvador'))399 """400 if location in self._location_cache:401 return self._location_cache[location]402 suggestions = self._get_location_suggestions(location)403 while len(suggestions) == 0:404 print 'We could not find a location starting with "' + location + '"; did you misspell it? Try another:'405 new_location = raw_input()406 suggestions = self._get_location_suggestions(location)407 if len(suggestions) == 1:408 # Single suggestion: just use it409 self._location_cache[location] = suggestions[0]410 else:411 # Multiple suggestions: clarify with user412 strings = [suggestion[1] for suggestion in suggestions]413 print 'Which of the above did you mean by "{0}"'.format(location) + '?'414 self._location_cache[location] = self._clarify(suggestions, strings)415 return self._location_cache[location]416 def _get_charge_code_suggestions(self, charge_code):417 return self._get_ajax_suggestions('editItemForm:allocation_panel:multipleAllocationTable_0:projectChoice',418 charge_code)419 def _get_charge_code(self, charge_code):420 charge_codes = self._get_charge_code_suggestions(charge_code)421 if len(charge_codes) == 0:422 raise Exception('"{0}" is not a valid charge code'.format(charge_code))423 return charge_codes[0]424 def _get_exchange_rate(self, date, amount, currency=None, currency_code=None):425 """426 Get the exchange rate and amount427 :param currency: currency of the payment (optional; either this or currency_code)428 :param currency_code: currency code of the payment (optional; this or currency)429 :param date: date of the payment430 :param amount: amount, in local currency, to convert431 :return: (amount_paid, exchange_rate)432 """433 if currency:434 currency_code = self._translate_code('ALCurrencyFormat', currency)435 resp = self._post(urls.AJAX_REQUEST, {436 'execute': 'editItemForm:currencyVal-PFAChoice editItemForm:date-PFAField editItemForm:amountVal-PFAField',437 'method': 'form.setFXRate',438 'render': 'editItemForm:paidAmountPanelGroup editItemForm:fxRatePanelGroup',439 'action': 'updateTarget',440 'componentId': 'editItemForm:currencyVal-PFAChoice',441 'editItemForm:currencyVal-PFAChoice': currency_code,442 'editItemForm:date-PFAField': date,443 'editItemForm:amountVal-PFAField': amount,444 'jsf_viewid': '/app/er_line.jsp'445 }, update_state=False)446 usd_code = self._translate_code('ALCurrencyFormat', 'USD')447 if currency_code == usd_code:448 # US$ is the default currency; it won't be present449 return amount, '1'450 match1 = re.search('editItemForm:paidAmountVal" type="text" value="([\d.]+)',451 resp.text)452 if match1 is None:453 raise Exception('Could not get amount / exchange rate for currency {0} on {1}:\n{2}'.format(454 currency_code, date, resp.text))455 amount_paid = match1.group(1)456 match2 = re.search('editItemForm:fxRateVal-PFAField" type="text" value="([\d.]+)',457 resp.text)458 if match2 is None:459 raise Exception('Could not get exchange rate for currency {0} on {1}:\n{2}'.format(460 currency_code, date, resp.text))461 exchange_rate = match2.group(1)462 return amount_paid, exchange_rate463 def _annotate_fx(self, post_request):464 """465 Annotate a POST request with the needed currency conversion codes466 :param post_request: post_request to annotate: we use the467 - editItemForm:date-PFAField468 - editItemForm:amountVal-PFAField469 - editItemForm:currencyVal-PFAChoice470 to build the actual request471 :return: None472 """473 currency_code = post_request['editItemForm:currencyVal-PFAChoice']474 amount = post_request['editItemForm:amountVal-PFAField']475 date = post_request['editItemForm:date-PFAField']476 usd_code = self._translate_code('ALCurrencyFormat', 'USD')477 if currency_code == usd_code and self.prev_currency_code == usd_code:478 # Even if we're using USD, if we're switching back from another currency, we HAVE to make the479 # _get_exchange_rate request480 post_request['editItemForm:fxRateVal-PFAField'] = '1'481 post_request['editItemForm:paidAmountVal'] = amount482 return post_request483 self.prev_currency_code = currency_code484 amount_paid, exchange_rate = self._get_exchange_rate(date, amount, currency_code=currency_code)485 post_request['editItemForm:fxRateVal-PFAField'] = exchange_rate486 post_request['editItemForm:paidAmountVal'] = amount_paid487 return post_request488 def create_hotel_entry(self, charge_code, location, check_in, check_out, total,489 room_rate, tax1=None, tax2=None, tax3=None, tax4=None, tax5=None,490 breakfast=None, parking=None, internet=None,491 currency='USD', notes='', meals=None):492 if meals is None:493 meals = [] # Array of (date, meal, amount)494 self.create_entry('Hotel')495 check_in_str = self._format_date(check_in)496 check_out_str = self._format_date(check_out)497 currency_code = self._translate_code('ALCurrencyFormat', currency)498 (location_code, location_name) = self._get_location(location)499 (charge_code_code, charge_code) = self._get_charge_code(charge_code)500 # Go to the itemize page501 submit_post_request = {502 'editItemForm:date-PFAField': check_out_str,503 'editItemForm:amountVal-PFAField': total,504 'editItemForm:currencyVal-PFAChoice': currency_code,505 'editItemForm:ADC_3048906_input': location_name,506 'editItemForm:ADC_3048906': location_code,507 'editItemForm:receipt-PFAField': 'true',508 'editItemForm:allocation_panel:multipleAllocationTable_0:projectChoice_input': charge_code,509 'editItemForm:allocation_panel:multipleAllocationTable_0:projectChoice': charge_code_code,510 'editItemForm:ADC_-1999999888': 'type:SubExpenseType;id:-1999992755;', # Hotel for Firm Member511 'editItemForm:ADC_3048902': '',512 'editItemForm:note_panel:noteFld': notes,513 'editItemForm_SUBMIT': '1',514 'editItemForm:_link_hidden_': ''515 }516 submit_post_request = self._annotate_fx(submit_post_request)517 itemize_post = submit_post_request.copy()518 itemize_post['editItemForm:_link_hidden_'] = 'editItemForm:addItemizeBtn'519 resp = self._post(urls.SUBMIT_EXPENSE, itemize_post)520 resp = self.confirm_line_warnings(resp)521 if resp.text.find('Quick Itemize') == -1:522 raise Exception('Could not open the itemize screen for hotels: instead got response ' + resp.text)523 # Open the Quick Itemize page524 # resp = self._post(urls.LINE_ITEMIZE, {525 # 'editItemizationsForm:newExpenseTypeVal': '',526 # 'editItemizationsForm:quickItemizationBtn': 'Quick Itemize',527 # 'editItemizationsForm_SUBMIT': '1',528 # 'editItemizationsForm:_link_hidden_': 'editItemForm:addItemizeBtn'529 # })530 #531 # if resp.text.find('Daily Lodging Charges') == -1:532 # raise Exception('Could not open the quick itemize screen: instead got response ' + resp.text)533 start = datetime.datetime.strptime(check_in_str, '%m/%d/%y')534 end = datetime.datetime.strptime(check_out_str, '%m/%d/%y')535 delta = end - start536 number_days = delta.days537 # Submit the itemizations538 resp = self._post(urls.QUICK_ITEMIZE_SUBMIT, {539 'editItemizationsForm:hotelQISubview:endDateVal': check_out_str,540 'editItemizationsForm:hotelQISubview:numberDaysVal': number_days,541 'editItemizationsForm:hotelQISubview:lodgingChargesGroup_46021': room_rate, # Taxes542 'editItemizationsForm:hotelQISubview:lodgingChargesGroup_46029': tax1 if tax1 else '',543 'editItemizationsForm:hotelQISubview:lodgingChargesGroup_46029_1': tax2 if tax2 else '',544 'editItemizationsForm:hotelQISubview:lodgingChargesGroup_46029_2': tax3 if tax3 else '',545 'editItemizationsForm:hotelQISubview:lodgingChargesGroup_46029_3': tax4 if tax4 else '',546 'editItemizationsForm:hotelQISubview:lodgingChargesGroup_46029_4': tax5 if tax5 else '',547 'editItemizationsForm:hotelQISubview:otherChargesGroup_-1999586252': breakfast if breakfast else '', # Breakfast548 'editItemizationsForm:hotelQISubview:otherChargesGroup_46025': parking if parking else '', # Parking / Toll / Other549 'editItemizationsForm:hotelQISubview:otherChargesGroup_-1999898515': internet if internet else '', # Internet - Wifi550 'editItemizationsForm:hotelQISubview:continue2Btn': 'Continue',551 'editItemizationsForm_SUBMIT': '1',552 'editItemizationsForm:_link_hidden_': ''553 })554 resp = self.confirm_line_warnings(resp)555 if resp.text.find('Finish Itemization') == -1 and resp.text.find('Save Itemization') == -1:556 raise Exception('Could not do quick itemize: instead got response ' + resp.text)557 # Itemize meals individually558 for meal in meals:559 resp = self._post(urls.LINE_ITEMIZE, {560 'editItemizationsForm:_link_hidden_': 'editItemizationsForm:expenseTypesList_2:expenseTypeSelectLink',561 'editItemizationsForm_SUBMIT': '1',562 'expenseTypeId': self._get_numeric_code('ExpenseType', 'Meals 1 - Travel Dining Alone')563 })564 if resp.text.find('Meal Type') == -1:565 raise Exception('Could not itemize meal as a part of a hotel bill: instead got response ' + resp.text)566 itemize_post_fields = {567 'editItemForm:date-PFAField': self._format_date(meal['date']),568 'editItemForm:amountVal-PFAField': meal['amount'],569 'editItemForm:ADC_-1999999878': self._translate_code('MealType', meal['type']),570 'editItemForm:allocation_panel:multipleAllocationTable_0:projectChoice_input': charge_code,571 'editItemForm:allocation_panel:multipleAllocationTable_0:projectChoice': charge_code_code,572 'editItemForm:note_panel:noteFld': '',573 'editItemForm:saveBtn2': 'Save',574 'editItemForm_SUBMIT': '1',575 'editItemForm:_link_hidden_': 'editItemForm:saveBtn'576 }577 resp = self._post(urls.LINE_ITEMIZE_SUBMIT, itemize_post_fields)578 resp = self.confirm_line_warnings(resp)579 if resp.text.find('Save Itemization') == -1 and resp.text.find('Finish Itemization') == -1:580 raise Exception('Could not itemize meal: instead got response ' + resp.text)581 # Close the itemization screen582 resp = self._post(urls.LINE_ITEMIZE, {583 'editItemizationsForm:saveBtn2': 'Save+Itemization',584 'editItemizationsForm_SUBMIT': '1',585 'editItemizationsForm:_link_hidden_': ''586 })587 if resp.text.find('Currency') == -1:588 raise Exception('Error while finishing itemization: ' + resp.text)589 # Do the final submissions590 submit_post = submit_post_request.copy()591 submit_post['editItemForm:_link_hidden_'] = 'editItemForm:saveBtn'592 resp = self._post(urls.SUBMIT_EXPENSE, submit_post)593 resp = self.confirm_line_warnings(resp)594 if resp.text.find('My Receipts') == -1:595 raise Exception('Error while submitting added hotel expense: ' + resp.text)596 def create_meal1_entry(self, charge_code, location, date, amount, meal, currency = 'USD', notes=''):597 self.create_entry('Meals 1 - Travel Dining Alone')598 date_str = self._format_date(date)599 currency_code = self._translate_code('ALCurrencyFormat', currency)600 (location_code, location_name) = self._get_location(location)601 (charge_code_code, charge_code) = self._get_charge_code(charge_code)602 meal_type = self._translate_code('MealType', meal)603 resp = self._post(urls.SUBMIT_EXPENSE, self._annotate_fx({604 'editItemForm:date-PFAField': date_str,605 'editItemForm:amountVal-PFAField': amount,606 'editItemForm:currencyVal-PFAChoice': currency_code,607 'editItemForm:ADC_-1999999878': meal_type,608 'editItemForm:ADC_3048906_input': location_name,609 'editItemForm:ADC_3048906': location_code,610 'editItemForm:receipt-PFAField': 'true',611 'editItemForm:allocation_panel:multipleAllocationTable_0:projectChoice_input': charge_code,612 'editItemForm:allocation_panel:multipleAllocationTable_0:projectChoice': charge_code_code,613 'editItemForm:ADC_3048902': '',614 'editItemForm:note_panel:noteFld': notes,615 'editItemForm_SUBMIT': '1',616 'editItemForm:_link_hidden_': 'editItemForm:saveBtn'617 }))618 resp = self.confirm_line_warnings(resp)619 if resp.text.find('My Receipts') == -1:620 raise Exception('Error while submitting added Meals 1 expense: ' + resp.text)621 def create_meal2_entry(self, charge_code, location, date, amount, nature, attendee_count,622 currency='USD', notes=''):623 self.create_entry('Meals 2 - In Office')624 date_str = self._format_date(date)625 currency_code = self._translate_code('ALCurrencyFormat', currency)626 (location_code, location_name) = self._get_location(location)627 (charge_code_code, charge_code) = self._get_charge_code(charge_code)628 nature_and_relevance = self._translate_code('SubExpenseType2', nature)629 resp = self._post(urls.SUBMIT_EXPENSE, self._annotate_fx({630 'editItemForm:date-PFAField': date_str,631 'editItemForm:amountVal-PFAField': amount,632 'editItemForm:currencyVal-PFAChoice': currency_code,633 'editItemForm:ADC_1000080050': nature_and_relevance,634 'editItemForm:ADC_3048909': str(int(attendee_count)),635 'editItemForm:ADC_3048906_input': location_name,636 'editItemForm:ADC_3048906': location_code,637 'editItemForm:ADC_-1999999888': 'type:SubExpenseType;id:-1999992755;', # Meal for firm member638 'editItemForm:receipt-PFAField': 'true',639 'editItemForm:allocation_panel:multipleAllocationTable_0:projectChoice_input': charge_code,640 'editItemForm:allocation_panel:multipleAllocationTable_0:projectChoice': charge_code_code,641 'editItemForm:ADC_3048902': '', # Vendor642 'editItemForm:note_panel:noteFld': notes,643 'editItemForm_SUBMIT': '1',644 'editItemForm:_link_hidden_': 'editItemForm:saveBtn'645 }))646 resp = self.confirm_line_warnings(resp)647 if resp.text.find('My Receipts') == -1:648 raise Exception('Error while submitting added Meals 2 expense: ' + resp.text)649 @staticmethod650 def _get_existing_vendors(resp_text):651 """652 Get a dict of {'vendor': 'code'} based on the HTML on a certain page653 @param resp_text: HTML response for a page654 @return:655 """656 matches = re.findall('(type:Vendor;id:[^"]*)"(?: selected="selected")?>([^<]*)', resp_text)657 matches_dict = {vendor: id for id, vendor in matches}658 return matches_dict659 people_cache = {}660 def create_meal3_entry(self, charge_code, location, date, amount, nature, meal,661 vendor, attendees, currency = 'USD', notes=''):662 """663 By far the most complex meal type: add a Team Dinner style meal664 """665 resp = self.create_entry('Meals 3 - Group Outside Office')666 existing_vendors = self._get_existing_vendors(resp.text)667 date_str = self._format_date(date)668 currency_code = self._translate_code('ALCurrencyFormat', currency)669 (location_code, location_name) = self._get_location(location)670 (charge_code_code, charge_code) = self._get_charge_code(charge_code)671 nature_and_relevance = self._translate_code('SubExpenseType3', nature)672 meal_type = self._translate_code('MealType', meal)673 # Final submission data674 post_data = {675 'editItemForm:date-PFAField': date_str,676 'editItemForm:amountVal-PFAField': amount,677 'editItemForm:currencyVal-PFAChoice': currency_code,678 'editItemForm:ADC_1000080051': nature_and_relevance,679 'editItemForm:ADC_-1999999878': meal_type,680 'editItemForm:ADC_3048902': '', # Vendor681 'editItemForm:ADC_3048906_input': location_name,682 'editItemForm:ADC_3048906': location_code,683 'editItemForm:receipt-PFAField': 'true',684 'editItemForm:allocation_panel:multipleAllocationTable_0:projectChoice_input': charge_code,685 'editItemForm:allocation_panel:multipleAllocationTable_0:projectChoice': charge_code_code,686 'editItemForm:note_panel:noteFld': notes,687 'editItemForm_SUBMIT': '1',688 'editItemForm:_link_hidden_': 'editItemForm:saveBtn'689 }690 post_data = self._annotate_fx(post_data)691 # Add the vendors, if needed692 if vendor not in existing_vendors:693 add_vendor_post_data = post_data.copy()694 add_vendor_post_data['editItemForm:_link_hidden_'] = 'editItemForm:ADC_3048902Btn'695 add_vendor_post_data['targetName'] = "form.currentERLineItem.extensions['vendorName']'"696 resp = self._post(urls.SUBMIT_EXPENSE, add_vendor_post_data)697 if resp.text.find('Add Vendor / Establishment') == -1:698 raise Exception('Could not open Add Vendor page; got error: ' + resp.text)699 resp = self._post(urls.ADD_VENDOR, {700 'addCorpDataScreenForm:addCorpDataVal': vendor,701 'addCorpDataScreenForm:saveBtn': 'Save',702 'addCorpDataScreenForm_SUBMIT': '1',703 'addCorpDataScreenForm:_link_hidden_': ''704 })705 existing_vendors = self._get_existing_vendors(resp.text)706 if vendor not in existing_vendors:707 raise Exception('Failed to add Vendor {0}; instead got: '.format(vendor) + resp.text)708 vendor_id = existing_vendors[vendor]709 post_data['editItemForm:ADC_3048902'] = vendor_id710 # Add the attendees711 # 1. Open up the screen712 add_attendee_post_data = post_data.copy()713 add_attendee_post_data['editItemForm:_link_hidden_'] = \714 'editItemForm:guest_wrapper_panel:guestQuickAddBtnChooserBtn'715 add_attendee_post_data['editItemForm:guest_wrapper_panel:addBtn'] = 'Add'716 resp = self._post(urls.SUBMIT_EXPENSE, add_attendee_post_data)717 if resp.text.find('Find Guests') == -1:718 raise Exception('Could not open Find Guests screen: ' + resp.text)719 # 2. Search for attendees720 guest_chooser_base = {721 'guestForm:guestChooserTabbedPane_indexSubmit': '',722 'guestForm:guest_chooser_search_tab:namesVal': '',723 'guestForm:guest_chooser_search_tab:guestType-PFAChoice': '',724 'guestForm:guest_chooser_search_tab:title-PFAField': '',725 'guestForm:guest_chooser_search_tab:company-PFAField': '',726 'guestForm:guest_chooser_search_tab:addressChoice': '',727 'guestForm:guest_chooser_search_tab:isPersonal-PFAChoice': '',728 'guestForm:guest_chooser_search_tab:guestListChoice': '',729 'guestForm:guest_chooser_recent_tab:recentRadio': 'true',730 'pagingEnabled': 'false',731 'guestForm_SUBMIT': '1'732 }733 search_guests = guest_chooser_base.copy()734 attendee_string = ';'.join(attendees) # Search uses colon-separated attendees735 search_guests['guestForm:guest_chooser_search_tab:namesVal'] = attendee_string736 search_guests['guestForm:guest_chooser_search_tab:searchBtn'] = 'Search'737 resp = self._post(urls.GUEST_CHOOSER, search_guests)738 if resp.text.find('Guest Details') == -1:739 raise Exception('Couldn\'t successfully search for guests: ' + resp.text)740 # Extract the attendee data741 def extract_people(resp_text):742 """743 :rtype : list744 """745 html_parts = re.split('name="guestForm:guest_chooser_search_tab:_id[^:]*:searchSelectCheckbox', resp_text)746 del html_parts[0]747 def extract_info(html_part):748 """749 Extracts the ID, name, and position within McKinsey from the attendee search data750 :param html_part: HTML for the particular person's entry751 :return: a dict with all matching people's information752 """753 match = re.search('value="([^"]+)', html_part)754 id = match.group(1)755 match = re.search('col3143086Val">([^<]*)', html_part)756 first_name = match.group(1)757 match = re.search('col3143087Val">([^<]*)', html_part)758 last_name = match.group(1)759 match = re.search('col3143088Val">([^<]*)', html_part)760 company = match.group(1)761 pattern = 'guestForm:guest_chooser_search_tab:_id\d+_\d+:searchSelectCheckbox'762 match = re.search(pattern, html_part)763 checkbox_id = match.group(0)764 # Whether checkbox is checked; boxes for just-added people765 # are automatically checked766 checked = html_part.find('checked="checked"') != -1767 return {768 'id': id,769 'first_name': first_name,770 'last_name': last_name,771 'name': '{0} {1}'.format(first_name, last_name),772 'company': company,773 'checked': checked,774 'checkbox_id': checkbox_id775 }776 return [extract_info(html) for html in html_parts]777 people = extract_people(resp.text)778 def key_by_name(people):779 """780 Convert the people from extract_text to be keyed by name, with781 multiple people with the same name under an array782 :param people: people from extract_people783 :return: dict { 'First Last': [person1, person2], ...}784 :rtype: dict785 """786 people_by_name = {}787 for person in people:788 name = person['name']789 if name not in people_by_name:790 people_by_name[name] = []791 people_by_name[name].append(person)792 return people_by_name793 existing_attendees = re.findall('guestParentGuestsLabel\d+">([^<]+)', resp.text)794 # Remove all attendees who are auto-added (normally, only oneself)795 missing_attendees = [attendee for attendee in attendees if attendee not in existing_attendees]796 relevant_people = key_by_name(people)797 matched_people = [relevant_people[name][0] for name in missing_attendees if\798 name in relevant_people and len(relevant_people[name]) == 1]799 missing_people = [name for name in missing_attendees if name not in relevant_people]800 ambiguous_people = [name for name in missing_attendees if\801 name in relevant_people and len(relevant_people[name]) > 1]802 # Clarify the ambiguous people803 for name in ambiguous_people:804 print 'Which of the following do you mean by "{0}"?'.format(name)805 relevant = relevant_people[name]806 strings = ['{0} ({1} at {2})'.format(person['name'], person['position'], person['company']) for\807 person in relevant]808 selected = self._clarify(relevant, strings, True)809 if selected is None:810 missing_people.append(name)811 else:812 matched_people.append(selected)813 # Add all matched people first814 def add_guests(people):815 """816 Add all the matched people by making server requests817 :param people: all the guests to add to the invitees list818 """819 add_guest = guest_chooser_base.copy()820 add_guest['guestForm:guest_chooser_search_tab:addBtn1'] = 'Add'821 for person in people:822 add_guest[person['checkbox_id']] = person['id']823 resp = self._post(urls.GUEST_CHOOSER, add_guest)824 missing_names = []825 for person in people:826 name = person['name']827 if resp.text.find(name) == -1:828 missing_names.append(name)829 if missing_names:830 raise Exception('Failed to add guests {0}: '.format(', '.join(missing_names)) + '\n'831 + resp.text)832 # Add everyone matched so far833 add_guests(matched_people)834 # Add the missing people835 for name in missing_people:836 print 'We need to add a new entry for "{0}"'.format(name)837 print 'Is {0} a McKinsey employee? ("y" for yes, anything else for no) '.format(name)838 is_internal_input = raw_input()839 is_internal = is_internal_input.upper() == 'Y'840 if is_internal:841 company = 'McKinsey'842 else:843 print 'What company does {0} work for? '.format(name)844 company = raw_input()845 new_guest = guest_chooser_base.copy()846 new_guest['guestForm:guest_chooser_search_tab:createBtn1'] = 'New'847 resp = self._post(urls.GUEST_CHOOSER, new_guest)848 if resp.text.find('New Guest') == -1:849 raise Exception('Could not open the "New Guest" screen: ' + resp.text)850 space_index = name.rfind(' ')851 first_name = name[0:space_index]852 last_name = name[space_index + 1:]853 resp = self._post(urls.GUEST_ENTRY, {854 'guestEntryForm:firstName-PFAField': first_name,855 'guestEntryForm:lastName-PFAField': last_name,856 'guestEntryForm:guestType-PFAChoice': '0' if is_internal else '1',857 'guestEntryForm:title-PFAField': '',858 'guestEntryForm:company-PFAField': company,859 'guestEntryForm:guestAddressesToggleState': 'true',860 'guestEntryForm:saveBtn': 'Save',861 'guestEntryForm_SUBMIT': '1',862 'guestEntryForm:_link_hidden_': ''863 })864 search_results = extract_people(resp.text)865 existing_attendees = re.findall('guestParentGuestsLabel\d+">([^<]+)', resp.text)866 if name not in existing_attendees:867 raise Exception('Could not add person "{0}": '.format(name) + resp.text)868 # Close the save dialog869 close_dialog = guest_chooser_base.copy()870 close_dialog['guestForm:saveBtn2'] = 'Save'871 resp = self._post(urls.GUEST_CHOOSER, close_dialog)872 if resp.text.find('date-PFAField') == -1:873 raise Exception('Failed to close add guests dialog: ' + resp.text)874 # Finally submit:875 resp = self._post(urls.SUBMIT_EXPENSE, self._annotate_fx(post_data))876 resp = self.confirm_line_warnings(resp)877 if resp.text.find('My Receipts') == -1:878 raise Exception('Error while submitting added Meals 3 expense: ' + resp.text)879 def create_taxi_entry(self, charge_code, location, date, amount, source, destination,880 explanation=None, currency='USD', notes=''):881 self.create_entry('Taxi / Car Services')882 date_str = self._format_date(date)883 currency_code = self._translate_code('ALCurrencyFormat', currency)884 (location_code, location_name) = self._get_location(location)885 (charge_code_code, charge_code) = self._get_charge_code(charge_code)886 from_code = self._translate_code('FromTo', string.capwords(source))887 to_code = self._translate_code('FromTo', string.capwords(destination))888 resp = self._post(urls.SUBMIT_EXPENSE, self._annotate_fx({889 'editItemForm:date-PFAField': date_str,890 'editItemForm:amountVal-PFAField': amount,891 'editItemForm:currencyVal-PFAChoice': currency_code,892 'editItemForm:ADC_-1999888637': from_code,893 'editItemForm:ADC_-1999888627': to_code,894 'editItemForm:ADC_3048906_input': location_name,895 'editItemForm:ADC_3048906': location_code,896 'editItemForm:receipt-PFAField': 'true',897 'editItemForm:allocation_panel:multipleAllocationTable_0:projectChoice_input': charge_code,898 'editItemForm:allocation_panel:multipleAllocationTable_0:projectChoice': charge_code_code,899 'editItemForm:note_panel:noteFld': notes,900 'editItemForm_SUBMIT': '1',901 'editItemForm:_link_hidden_': 'editItemForm:saveBtn'...
table_parser.py
Source:table_parser.py
...29 "HIGHDEG": str,30 "MAIN": float,31 "HCM2": float,32 }33def _translate_code(json_file, df_column, if_None: str="Unknown"):34 j_file = json.loads(pathlib.Path(json_file).read_text())35 return lambda x:j_file.get(str(x), if_None)36converter_list = [37 ("RELAFFIL", _translate_code("translations/relaffil.json", "RELAFFIL", if_None="None")),38 ("CONTROL", _translate_code("translations/control.json", "CONTROL")),39 ("ST_FIPS", _translate_code("translations/st_fips.json", "ST_FIPS")),40 ("HIGHDEG", _translate_code("translations/high_deg.json", "HIGHDEG")),41 ]42converters = {x:y for x,y in converter_list} # To return "ST_FIPS": lambda x:st_fips_json.get(str(x), "Unknown"), etc 43base_df = pd.read_csv(44 "base_data/Most-Recent-Cohorts-All-Data-Elements.csv",45 usecols=list(column_values.keys()),46 converters=converters,47 dtype=column_values,48)49base_df.fillna(0, inplace=True)50base_df['location'] = base_df.apply(lambda x:f"{x.LATITUDE}, {x.LONGITUDE}", axis=1)51# Create DATAFRAME FOR ACTIVE HBCUs and PBIs52hbcus = base_df.loc[(base_df.HBCU == 1) & (base_df.CURROPER == 1)]53pbis = base_df.loc[(base_df.PBI == 1) & (base_df.CURROPER == 1)]54def _gen_slug_link(school):...
utils.py
Source:utils.py
...23 response = requests.get(self._url)24 except requests.exceptions.RequestException as exc:25 self.valid = False26 self.errors.append(self._translate_exception(exc))27 self.errors.append(self._translate_code(None)[-1])28 else:29 self.status_code = response.status_code30 success, message = self._translate_code(self.status_code)31 self.valid = success32 if message:33 self.errors.append(message)34 @staticmethod35 def _translate_exception(exc):36 """Translate a 'requests' exception to a user-facing message."""37 if isinstance(exc, requests.exceptions.ConnectionError):38 return "Unable to connect to domain."39 return str(exc)40 @staticmethod41 def _translate_code(code):42 """Determine if an HTTP status code should be consider successful."""43 pattern = "URL responded with a non-successful status code: {}"44 if code:45 success = 200 <= code < 40046 if success:47 message = None48 else:49 message = pattern.format(code)50 else:51 success = False52 message = pattern.format("(no response)")53 return success, message54def filter_invalid_urls(urls):55 """Yield a URL response for each invalid URL."""...
Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!