diff --git a/.gitignore b/.gitignore index eb80260..9b25e7f 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ resources/.linkedin_cred # for automation reasons this file needs to be created, # please provide your own $LINKEDIN_USERNAME and $LINKEDIN_PASSWORD #---------------------------------- + +# driver log [ignored geckodriver.log] +*.log diff --git a/geckodriver.log b/geckodriver.log index d6af2c6..e34e7f8 100644 --- a/geckodriver.log +++ b/geckodriver.log @@ -97,3 +97,110 @@ JavaScript error: resource://gre/modules/Sqlite.jsm, line 927: Error: Connection ###!!! [Parent][RunMessage] Error: Channel closing: too late to send/recv, messages will be lost +1619477345968 geckodriver INFO Listening on 127.0.0.1:54077 +1619477346982 mozrunner::runner INFO Running command: "/usr/bin/firefox" "--marionette" "-headless" "--no-sandbox" "--disable-dev-shm-usage" "-foreground" "-no-remote" "-profile" "/tmp/rust_mozprofileMP8bev" +*** You are running in headless mode. + +(/usr/lib/firefox/firefox:17143): GLib-GObject-CRITICAL **: 00:49:08.072: g_object_set: assertion 'G_IS_OBJECT (object)' failed + +(/usr/lib/firefox/firefox:17165): GLib-GObject-CRITICAL **: 00:49:08.286: g_object_set: assertion 'G_IS_OBJECT (object)' failed +console.warn: SearchSettings: "get: No settings file exists, new profile?" (new Error("", "(unknown module)")) +1619477351103 Marionette INFO Listening on port 43151 + +(/usr/lib/firefox/firefox:17228): GLib-GObject-CRITICAL **: 00:49:11.172: g_object_set: assertion 'G_IS_OBJECT (object)' failed +1619477351251 Marionette WARN TLS certificate errors will be ignored for this session +1619477383665 Marionette INFO Stopped listening on port 43151 + +###!!! [Child][MessageChannel] Error: (msgtype=0x5D0009,name=PHttpChannel::Msg_DeletingChannel) Channel closing: too late to send/recv, messages will be lost + + +###!!! [Child][RunMessage] Error: Channel closing: too late to send/recv, messages will be lost + + +###!!! [Child][RunMessage] Error: Channel closing: too late to send/recv, messages will be lost + + +###!!! [Child][MessageChannel] Error: (msgtype=0x5D0004,name=PHttpChannel::Msg_Cancel) Channel closing: too late to send/recv, messages will be lost + + +###!!! [Child][MessageChannel] Error: (msgtype=0x5D0009,name=PHttpChannel::Msg_DeletingChannel) Channel closing: too late to send/recv, messages will be lost + +JavaScript error: resource://gre/modules/Sqlite.jsm, line 927: Error: Connection is not open. + +###!!! [Parent][RunMessage] Error: Channel closing: too late to send/recv, messages will be lost + + +###!!! [Parent][RunMessage] Error: Channel closing: too late to send/recv, messages will be lost + + +###!!! [Parent][RunMessage] Error: Channel closing: too late to send/recv, messages will be lost + +1619514021308 geckodriver INFO Listening on 127.0.0.1:48933 +1619514022322 mozrunner::runner INFO Running command: "/usr/bin/firefox" "--marionette" "-headless" "--no-sandbox" "--disable-dev-shm-usage" "-foreground" "-no-remote" "-profile" "/tmp/rust_mozprofileQ8vu07" +*** You are running in headless mode. + +(/usr/lib/firefox/firefox:21354): GLib-GObject-CRITICAL **: 11:00:23.426: g_object_set: assertion 'G_IS_OBJECT (object)' failed + +(/usr/lib/firefox/firefox:21376): GLib-GObject-CRITICAL **: 11:00:23.611: g_object_set: assertion 'G_IS_OBJECT (object)' failed +console.warn: SearchSettings: "get: No settings file exists, new profile?" (new Error("", "(unknown module)")) + +(/usr/lib/firefox/firefox:21423): GLib-GObject-CRITICAL **: 11:00:24.770: g_object_set: assertion 'G_IS_OBJECT (object)' failed +1619514026605 Marionette INFO Listening on port 40047 +1619514026666 Marionette WARN TLS certificate errors will be ignored for this session +1619514026804 Marionette INFO Stopped listening on port 40047 +console.error: Region.jsm: "Error fetching region" (new TypeError("NetworkError when attempting to fetch resource.", "")) +console.error: Region.jsm: "Failed to fetch region" (new Error("NO_RESULT", "resource://gre/modules/Region.jsm", 419)) +console.log: "RemoteSettingsWorker error: Error: Can't import when we've started shutting down." +console.log: "RemoteSettingsWorker error: Error: Can't import when we've started shutting down." +console.log: "RemoteSettingsWorker error: Error: Can't import when we've started shutting down." +console.log: "RemoteSettingsWorker error: Error: Can't import when we've started shutting down." +console.log: "RemoteSettingsWorker error: Error: Can't import when we've started shutting down." +console.error: "Could not load engine google@search.mozilla.org: [Exception... \"AddonManager is not initialized\" nsresult: \"0xc1f30001 (NS_ERROR_NOT_INITIALIZED)\" location: \"JS frame :: resource://gre/modules/AddonManager.jsm :: installBuiltinAddon :: line 2550\" data: no]" +console.error: "Could not load engine amazondotcom@search.mozilla.org: [Exception... \"AddonManager is not initialized\" nsresult: \"0xc1f30001 (NS_ERROR_NOT_INITIALIZED)\" location: \"JS frame :: resource://gre/modules/AddonManager.jsm :: installBuiltinAddon :: line 2550\" data: no]" +console.error: "Could not load engine wikipedia@search.mozilla.org: [Exception... \"AddonManager is not initialized\" nsresult: \"0xc1f30001 (NS_ERROR_NOT_INITIALIZED)\" location: \"JS frame :: resource://gre/modules/AddonManager.jsm :: installBuiltinAddon :: line 2550\" data: no]" +console.error: "Could not load engine bing@search.mozilla.org: [Exception... \"AddonManager is not initialized\" nsresult: \"0xc1f30001 (NS_ERROR_NOT_INITIALIZED)\" location: \"JS frame :: resource://gre/modules/AddonManager.jsm :: installBuiltinAddon :: line 2550\" data: no]" +console.error: "Could not load engine ddg@search.mozilla.org: [Exception... \"AddonManager is not initialized\" nsresult: \"0xc1f30001 (NS_ERROR_NOT_INITIALIZED)\" location: \"JS frame :: resource://gre/modules/AddonManager.jsm :: installBuiltinAddon :: line 2550\" data: no]" +console.warn: SearchService: "_init: abandoning init due to shutting down" +JavaScript error: resource://gre/modules/AsyncShutdown.jsm, line 575: uncaught exception: 2147500036 +JavaScript error: resource://gre/modules/AsyncShutdown.jsm, line 575: uncaught exception: 2147500036 +JavaScript error: resource://gre/modules/AsyncShutdown.jsm, line 575: uncaught exception: 2147500036 +JavaScript error: resource://gre/modules/AsyncShutdown.jsm, line 575: uncaught exception: 2147500036 +JavaScript error: resource://gre/modules/AsyncShutdown.jsm, line 575: uncaught exception: 2147500036 + +###!!! [Parent][RunMessage] Error: Channel closing: too late to send/recv, messages will be lost + +1619514095729 geckodriver INFO Listening on 127.0.0.1:47423 +1619514096740 mozrunner::runner INFO Running command: "/usr/bin/firefox" "--marionette" "-headless" "--no-sandbox" "--disable-dev-shm-usage" "-foreground" "-no-remote" "-profile" "/tmp/rust_mozprofileTzBmGQ" +*** You are running in headless mode. + +(/usr/lib/firefox/firefox:21572): GLib-GObject-CRITICAL **: 11:01:37.784: g_object_set: assertion 'G_IS_OBJECT (object)' failed + +(/usr/lib/firefox/firefox:21594): GLib-GObject-CRITICAL **: 11:01:38.004: g_object_set: assertion 'G_IS_OBJECT (object)' failed +console.warn: SearchSettings: "get: No settings file exists, new profile?" (new Error("", "(unknown module)")) + +(/usr/lib/firefox/firefox:21643): GLib-GObject-CRITICAL **: 11:01:39.164: g_object_set: assertion 'G_IS_OBJECT (object)' failed +1619514100892 Marionette INFO Listening on port 36233 +1619514100978 Marionette WARN TLS certificate errors will be ignored for this session +console.error: Region.jsm: "Error fetching region" (new Error("TIMEOUT", "resource://gre/modules/Region.jsm", 772)) +console.error: Region.jsm: "Failed to fetch region" (new Error("TIMEOUT", "resource://gre/modules/Region.jsm", 419)) +JavaScript error: https://static-exp1.licdn.com/sc/h/efncoxjdl37tofw662sg013tp, line 1: Error: Could not lazy load JS with src https://static-exp1.licdn.com/sc/h/b8nmakf6h0x06rajxf1vxrb8g +JavaScript error: , line 0: TypeError: NetworkError when attempting to fetch resource. +JavaScript error: https://static-exp1.licdn.com/sc/h/efncoxjdl37tofw662sg013tp, line 1: Error: Could not lazy load JS with src https://static-exp1.licdn.com/sc/h/b08gxllvwy6zylnb52u2u7ovr +1619514108613 Marionette INFO Stopped listening on port 36233 +JavaScript error: resource://gre/modules/Sqlite.jsm, line 927: Error: Connection is not open. +1619514145907 geckodriver INFO Listening on 127.0.0.1:34031 +1619514145914 mozrunner::runner INFO Running command: "/usr/bin/firefox" "--marionette" "--no-sandbox" "--disable-dev-shm-usage" "-foreground" "-no-remote" "-profile" "/tmp/rust_mozprofile5IfFtc" +console.warn: SearchSettings: "get: No settings file exists, new profile?" (new Error("", "(unknown module)")) +1619514150419 Marionette INFO Listening on port 42765 +1619514150461 Marionette WARN TLS certificate errors will be ignored for this session +console.error: Region.jsm: "Error fetching region" (new Error("TIMEOUT", "resource://gre/modules/Region.jsm", 772)) +console.error: Region.jsm: "Failed to fetch region" (new Error("TIMEOUT", "resource://gre/modules/Region.jsm", 419)) +JavaScript error: https://static-exp1.licdn.com/sc/h/gou6qda3x8bdh2k8t17uu6y4, line 1: Error: GoogleOneTapError display: unknown_reason +1619514159199 Marionette INFO Stopped listening on port 42765 +JavaScript error: resource://gre/modules/Sqlite.jsm, line 927: Error: Connection is not open. +1619514482813 geckodriver INFO Listening on 127.0.0.1:46739 +1619514483826 mozrunner::runner INFO Running command: "/usr/bin/firefox" "--marionette" "--no-sandbox" "--disable-dev-shm-usage" "-foreground" "-no-remote" "-profile" "/tmp/rust_mozprofilemlO84j" +console.warn: SearchSettings: "get: No settings file exists, new profile?" (new Error("", "(unknown module)")) +1619514488008 Marionette INFO Listening on port 44165 +1619514488090 Marionette WARN TLS certificate errors will be ignored for this session +1619514522985 Marionette INFO Stopped listening on port 44165 diff --git a/sources/linkedin_login.py b/sources/linkedin_login.py new file mode 100644 index 0000000..0fbc5c1 --- /dev/null +++ b/sources/linkedin_login.py @@ -0,0 +1,130 @@ +''' +Script : linkedin_login.py +Author : SI Mkhonza +Creation Date : 04-04-2021 +Finilised Date : 11-04-2021 +''' +#3rd-Party Modules +from selenium.webdriver.common.by import By +#Developer-defined Modules +from sources.browser_helper import get_browser as init_browser, wait_for_element, click_on_element +#developer Defined Exceptions +class BrowserSetupError(Exception): + pass +class InvalidLoginArguments(ValueError): + pass +#Implementation +class Linkedin(object): + ''' + Implements the automation of linkedin.com login process. + ''' + def __init__(self): + self.browser = None + self.username_element = None + self.password_element = None + self.login_button_element = None + + def set_browser(self, show_browser=False, enable_fullscreen=False)->None: + ''' + helper function which initiates a browser instance + ''' + self.browser = init_browser( + show_browser, + enable_fullscreen + ) + + def get_browser(self)->object: + ''' + helper function which get the browser instance + ''' + return self.browser + + def __validate_login_argument(self, argument:str, argument_name:str)->str: + ''' + helper function which validate login inputs + ''' + # gaurd condition + if argument is None: + raise InvalidLoginArguments( + f"{argument_name} is None" + ) + argument = argument.strip() + if not argument: + raise InvalidLoginArguments( + f"either, provide an empty, blank; {argument_name}" + ) + # end of guard condition + return argument + + def __goto_linkedin(self)->bool: + ''' + helper function which goes to the linkedin.com site + ''' + self.browser.get('https://www.linkedin.com/') + return True + + def __get_username_element(self)->object: + ''' + helper function which gets the username DOM element + ''' + return wait_for_element( + browser=self.get_browser(), + locate_by=By.XPATH, + element_reference='//*[@id="session_key"]', + wait_for_n_seconds=2 + ) + + def __get_password_element(self)->object: + ''' + helper function which gets the password DOM element + ''' + return wait_for_element( + browser=self.get_browser(), + locate_by=By.XPATH, + element_reference='//*[@id="session_password"]', + wait_for_n_seconds=2 + ) + + def __get_login_button_element(self)->object: + ''' + helper function which gets the login button DOM element + ''' + return wait_for_element( + browser=self.get_browser(), + locate_by=By.XPATH, + element_reference='/html/body/main/section[1]/div[2]/form/button', + wait_for_n_seconds=2 + ) + + def __locate_login_elements(self)->None: + ''' + helper function which binds all login DOM elements + ''' + self.username_element = self.__get_username_element() + self.password_element = self.__get_password_element() + self.login_button_element = self.__get_login_button_element() + + + def perform_login(self, username:str, password:str)->bool: + ''' + helper function which performs the actual login process + ''' + # let's validate + username = self.__validate_login_argument(username, 'username') + password = self.__validate_login_argument(password, 'password') + if self.get_browser() is None: + raise BrowserSetupError( + 'self.set_browser() never called!' + ) + # let's goto linkedin.com + self.__goto_linkedin() + # let's locate login elements + self.__locate_login_elements() + # let's perform actions on DOM elements + self.username_element.send_keys(username) + self.password_element.send_keys(password) + return click_on_element( + browser=self.get_browser(), + element=self.login_button_element, + wait_for_n_seconds=2 + ) diff --git a/tests/test_selenium_linkedin_login.py b/tests/test_selenium_linkedin_login.py index 142593d..0ff1ae6 100644 --- a/tests/test_selenium_linkedin_login.py +++ b/tests/test_selenium_linkedin_login.py @@ -1,105 +1,38 @@ -#------------------------------------------------------- -# Script : test_selenium_linkedin_login.py -# Author : SI Mkhonza -# Creation Date : 26-04-2021 -# finilised Date : 27-04-2021 -#------------------------------------------------------- - -# Built-in Modules -from time import sleep +''' +Script : test_selenium_linkedin_login.py +Author : SI Mkhonza +Creation Date : 26-04-2021 +Finilised Date : 27-04-2021 +''' +# built-in Modules import os - -# 3rd Party Modules -from selenium.webdriver.common.by import By - -# developer-defined Modules -from sources.browser_helper import get_browser, wait_for_element, click_on_element - -# developer Defined Exceptions -class InitiationError(Exception): - pass -class InvalidLoginArguments(ValueError): - pass -# ------------------------------------------------------------------------- -class Linkedin(object): - def __validate_login_argument(self, argument:str, argument_name:str)->str: - # gaurd condition - if argument is None: - raise InvalidLoginArguments(f"{argument_name} is None") - argument = argument.strip() - if not argument: - raise InvalidLoginArguments(f"either, provide an empty, blank; {argument_name}") - # end of guard condition - return argument - - def __goto_linkedin(self)->bool: - self.browser.get('https://www.linkedin.com/') - return True - - def __login(self, username:str, password:str)->bool: - # then let's validate username and password - username = self.__validate_login_argument(username, 'username') - password = self.__validate_login_argument(password, 'password') - self.__goto_linkedin() - # let's locate DOM field 'elements' - username_field = wait_for_element( - browser=self.browser, - locate_by=By.XPATH, - element_reference='//*[@id="session_key"]', - wait_for_n_seconds=2 - ) - password_field = wait_for_element( - browser=self.browser, - locate_by=By.XPATH, - element_reference='//*[@id="session_password"]', - wait_for_n_seconds=2 - ) - login_button = wait_for_element( - browser=self.browser, - locate_by=By.XPATH, - element_reference='/html/body/main/section[1]/div[2]/form/button', - wait_for_n_seconds=2 - ) - # let's perform actions on DOM elements - username_field.send_keys(username) - password_field.send_keys(password) - return click_on_element( - browser=self.browser, - element=login_button, - wait_for_n_seconds=30 - ) - - def perform_login(self, username=None, password=None, show_browser=True)->bool: - # let's get username and password from os.getenvirn(), if None is provided - USERNAME = os.getenv('LINKEDIN_USERNAME') if not username else username - PASSWORD = os.getenv('LINKEDIN_PASSWORD') if not password else password - self.browser = None - try: - # get browser driver - self.browser = get_browser(show_browser, enable_fullscreen=False) - except InitiationError: - print('Something want wrong; during Browser initialization!') - return False - - # let's try to login: - did_login = False - try: - assert self.__login( - USERNAME, - PASSWORD - ), 'Did not login to Linkedin!' - did_login = True - finally: - # clean up ... - if show_browser: - import time - time.sleep(5) - self.browser.close() - return did_login - - -def test_linkedin_login()->None: +from unittest import TestCase +# 3rd-party Modules +import pytest +# developer-defined Module +from sources.linkedin_login import Linkedin +''' +Test Implementation +''' +@pytest.fixture +def get_linkedin(): + # setup linkedin = Linkedin() - assert linkedin.perform_login(show_browser=False), 'Test failed, Couldnot Login to Linkedin!' - - + show_browser = False + linkedin.set_browser( + show_browser, + enable_fullscreen=False, + ) + yield linkedin + # teardown + if show_browser: + import time + time.sleep(30) + linkedin.get_browser().close() + + +def test_linkedin_login(get_linkedin)->None: + assert get_linkedin.perform_login( + username=os.getenv('LINKEDIN_USERNAME'), + password=os.getenv('LINKEDIN_PASSWORD') + ), 'Test failed, Couldnot Login to Linkedin!'