Package: admitted
Admitted/ədˈmɪtɪd/ verb : allowed entry (as to a place, fellowship, or privilege)This project is very new. The API is very likely to change.This library aims to make automating tasks that require
authentication on web sites simpler. In general it would
be better to make HTTP requests using an appropriate
library, but at times it is not obvious how to replicate
the login process and you don't want to have to reverse
engineer the site just to get your task done. That is where
this library comes in.We use Selenium to automate a Chrome instance, and set the
user data directory to the Chrome default so that "remember
me" settings will persist to avoid 2FA on every instance.
We install ChromeDriver in a user binary location and manage
ensuring the ChromeDriver version is correct for the current
installed Chrome version.We expose afetchmethod to make HTTP requests to the site
with credentials through Chrome, eliminating the need to copy
cookies and headers; and adirect_requestmethod that usesurllib3(which is also a dependency of Selenium) to make
anonymous HTTP requests.We also introduce a couple of methods that support exploring
a site's Javascript environment from within Python:page.window.new_keys()lists non-default global variables,
andpage.window[key]will access global variables.page.browser.debug_show_pagewill dump a text version of
the current page to the console (ifhtml2textis
installed, otherwise the raw page source).Installationpippip install admitted, orpip install admitted[debug]to includehtml2textfor
exploratory work, orpip install admitted[dev]for the development environment.Requirement format for this GitHub repo as a dependencyadmitted @ git+https://git@github.com/Accounting-Data-Solutions/admitted@mainUsageGenerally, theadmittedAPI is intended to follow theencouraged practice of page object modelsby establishing a pattern of definingPageclasses each
with one initialization method that defines selectors for
all relevant elements on the page and one or more action
methods defining the desired interaction with the page.Define your SiteThe Site is a special version of a Page object that defines
your login page and the method to complete the login action.
All other Page objects will have a reference to this for
testing if you are authenticated and repeating the login
if necessary.The default behavior ofSite.__init__is to call theloginmethod; this can be avoided by passingpostpone=TruetoSite.fromadmittedimportSite,PageclassMySite(Site):def__init__(self):# get login credentials from secure locationcredentials={"username":"user","password":"do_not_actually_put_your_password_in_your_code",}super().__init__(login_url="https://www.example.com/login",credentials=credentials,)def_init_login(self):self.username_selector="input#username"self.password_selector="input#password"self.submit_selector="button#login"def_do_login(self)->"MySite":self.css(self.username_selector).clear().send_keys(self.credentials["username"])self.css(self.password_selector).clear().send_keys(self.credentials["password"])self.css(self.submit_selector).click()returnselfdefis_authenticated(self)->bool:returnself.window["localStorage.accessToken"]isnotNoneDefine a PageThe default behavior ofPage.navigateis to callself.site.loginon the first retry if navigation fails.classMyPage(Page):def__init__(self):super().__init__(MySite())self.navigate("https://www.example.com/interest")def_init_page(self)->None:self.element_of_interest="//div[@id='interest']"self.action_button="#action-btn"defget_interesting_text(self)->str:element=self.xpath(self.element_of_interest)returnelement.textdefdo_page_action(self)->None:self.css(self.action_button).click()Use your Page objectpage=MyPage()print(f"Received '{page.get_interesting_text()}'. Interesting!")page.do_page_action()print(f"Non-default global variables are{page.window.new_keys()}")print(f"The document title is '{page.window['document.title']}'.")response=page.window.fetch(method="post",url="/api/option",payload={"showInterest":True},headers={"x-snowflake":"example-option-header"})print(f"Fetch returned '{response.json}'.")response=page.direct_request(method="get",url="https://www.google.com")print(f"The length of Google's page source is{len(response.text)}characters.")HTTP Response APIThePage.window.fetchandPage.direct_requestmethods
both return aResponseobject with the following API.contentproperty: Response body asbytes.textproperty: Response body asstr, orNone.jsonproperty: JSON parsed response body, orNone.htmlproperty:lxmlparsed HTML element tree, orNone.write_streammethod: Stream response data to the provided file pointer ifdirect_requestmethod was called withstream=True, otherwise writesResponse.content.destinationargument: file pointer for a file opened with a write binary mode.chunck_sizeargument: (optional) number of bytes to write at a time.Returnsdestination.ReferencesSelenium Python bindings documentationSelenium project documentationlxml html parser documentationDevelopmentConfigure pre-commit hooks to format, lint, and test code before commit..git/hooks/pre-commit#!/bin/bashif[-z"${VIRTUAL_ENV}"];thenecho"Please activate your virtual environment before commit!"exit1firoot=$(gitrev-parse--show-toplevel)black${root}|whilereadline;doif[[${line}=="reformatted*"]];thenlen=$(($(wc-c<<<${line})-12))file=${line:12:len}gitadd${file}fidonepylint--rcfile${root}/pyproject.toml${root}/src/admitted
pytest-x-rN--no-cov--no-headerReleaseA release is published to PyPI by a GitHub Action when there
is a push tomainwith a tag. See.github/workflows/publish.yml.