Skip to content

Commit a263d58

Browse files
Merge pull request feder-cr#905 from chakaponi/bug/incorrect-element-selectors
fix: 🐛 Hotfix. Change selectors due to new html structure Co-authored-by: chakaponi <[email protected]>
2 parents 812f1e9 + 5ef40a1 commit a263d58

File tree

3 files changed

+52
-73
lines changed

3 files changed

+52
-73
lines changed

src/ai_hawk/job_manager.py

Lines changed: 25 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,6 @@ def start_applying(self):
244244
def get_jobs_from_page(self, scroll=False):
245245

246246
try:
247-
248247
no_jobs_element = self.driver.find_element(By.CLASS_NAME, 'jobs-search-two-pane__no-results-banner--expand')
249248
if 'No matching jobs found' in no_jobs_element.text or 'unfortunately, things aren' in self.driver.page_source.lower():
250249
logger.debug("No matching jobs found on this page, skipping.")
@@ -255,22 +254,23 @@ def get_jobs_from_page(self, scroll=False):
255254

256255
try:
257256
# XPath query to find the ul tag with class scaffold-layout__list-container
258-
job_results_xpath_query = "//ul[contains(@class, 'scaffold-layout__list-container')]"
259-
job_results = self.driver.find_element(By.XPATH, job_results_xpath_query)
257+
jobs_xpath_query = "//ul[contains(@class, 'scaffold-layout__list-container')]"
258+
jobs_container = self.driver.find_element(By.XPATH, jobs_xpath_query)
260259

261260
if scroll:
262-
job_results_scrolableElament = job_results.find_element(By.XPATH,"..")
263-
logger.warning(f'is scrollable: {browser_utils.is_scrollable(job_results_scrolableElament)}')
261+
jobs_container_scrolableElement = jobs_container.find_element(By.XPATH,"..")
262+
logger.warning(f'is scrollable: {browser_utils.is_scrollable(jobs_container_scrolableElement)}')
263+
264+
browser_utils.scroll_slow(self.driver, jobs_container_scrolableElement)
265+
browser_utils.scroll_slow(self.driver, jobs_container_scrolableElement, step=300, reverse=True)
264266

265-
browser_utils.scroll_slow(self.driver, job_results_scrolableElament)
266-
browser_utils.scroll_slow(self.driver, job_results_scrolableElament, step=300, reverse=True)
267+
job_element_list = jobs_container.find_elements(By.XPATH, ".//li[contains(@class, 'jobs-search-results__list-item') and contains(@class, 'ember-view')]")
267268

268-
job_list_elements = job_results.find_elements(By.XPATH, ".//li[contains(@class, 'jobs-search-results__list-item') and contains(@class, 'ember-view')]")
269-
if not job_list_elements:
269+
if not job_element_list:
270270
logger.debug("No job class elements found on page, skipping.")
271271
return []
272272

273-
return job_list_elements
273+
return job_element_list
274274

275275
except NoSuchElementException as e:
276276
logger.warning(f'No job results found on the page. \n expection: {traceback.format_exc()}')
@@ -281,20 +281,9 @@ def get_jobs_from_page(self, scroll=False):
281281
return []
282282

283283
def read_jobs(self):
284-
try:
285-
no_jobs_element = self.driver.find_element(By.CLASS_NAME, 'jobs-search-two-pane__no-results-banner--expand')
286-
if 'No matching jobs found' in no_jobs_element.text or 'unfortunately, things aren' in self.driver.page_source.lower():
287-
raise Exception("No more jobs on this page")
288-
except NoSuchElementException:
289-
pass
290-
291-
job_results = self.driver.find_element(By.CLASS_NAME, "jobs-search-results-list")
292-
browser_utils.scroll_slow(self.driver, job_results)
293-
browser_utils.scroll_slow(self.driver, job_results, step=300, reverse=True)
294-
job_list_elements = self.driver.find_elements(By.CLASS_NAME, 'scaffold-layout__list-container')[0].find_elements(By.CLASS_NAME, 'jobs-search-results__list-item')
295-
if not job_list_elements:
296-
raise Exception("No job class elements found on page")
297-
job_list = [self.job_tile_to_job(job_element) for job_element in job_list_elements]
284+
285+
job_element_list = self.get_jobs_from_page()
286+
job_list = [self.job_tile_to_job(job_element) for job_element in job_element_list]
298287
for job in job_list:
299288
if self.is_blacklisted(job.title, job.company, job.link, job.location):
300289
logger.info(f"Blacklisted {job.title} at {job.company} in {job.location}, skipping...")
@@ -307,21 +296,9 @@ def read_jobs(self):
307296
continue
308297

309298
def apply_jobs(self):
310-
try:
311-
no_jobs_element = self.driver.find_element(By.CLASS_NAME, 'jobs-search-two-pane__no-results-banner--expand')
312-
if 'No matching jobs found' in no_jobs_element.text or 'unfortunately, things aren' in self.driver.page_source.lower():
313-
logger.debug("No matching jobs found on this page, skipping")
314-
return
315-
except NoSuchElementException:
316-
pass
317-
318-
job_list_elements = self.get_jobs_from_page()
299+
job_element_list = self.get_jobs_from_page()
319300

320-
if not job_list_elements:
321-
logger.debug("No job class elements found on page, skipping")
322-
return
323-
324-
job_list = [self.job_tile_to_job(job_element) for job_element in job_list_elements]
301+
job_list = [self.job_tile_to_job(job_element) for job_element in job_element_list]
325302

326303
for job in job_list:
327304

@@ -494,7 +471,7 @@ def job_tile_to_job(self, job_tile) -> Job:
494471
logger.debug(f"Job link extracted: {job.link}")
495472
except NoSuchElementException:
496473
logger.warning("Job link is missing.")
497-
474+
498475
try:
499476
job.company = job_tile.find_element(By.XPATH, ".//div[contains(@class, 'artdeco-entity-lockup__subtitle')]//span").text
500477
logger.debug(f"Job company extracted: {job.company}")
@@ -517,11 +494,17 @@ def job_tile_to_job(self, job_tile) -> Job:
517494
except NoSuchElementException:
518495
logger.warning("Job location is missing.")
519496

497+
520498
try:
521-
job.apply_method = job_tile.find_element(By.CLASS_NAME, 'job-card-container__apply-method').text
499+
job_state = job_tile.find_element(By.XPATH, ".//ul[contains(@class, 'job-card-list__footer-wrapper')]//li[contains(@class, 'job-card-container__apply-method')]").text
522500
except NoSuchElementException as e:
523-
job.apply_method = "Applied"
524-
logger.warning(f'Apply method not found, assuming \'Applied\'. {e} {traceback.format_exc()}')
501+
try:
502+
# Fetching state when apply method is not found
503+
job_state = job_tile.find_element(By.XPATH, ".//ul[contains(@class, 'job-card-list__footer-wrapper')]//li[contains(@class, 'job-card-container__footer-job-state')]").text
504+
job.apply_method = "Applied"
505+
logger.warning(f'Apply method not found, state {job_state}. {e} {traceback.format_exc()}')
506+
except NoSuchElementException as e:
507+
logger.warning(f'Apply method and state not found. {e} {traceback.format_exc()}')
525508

526509
return job
527510

src/ai_hawk/linkedIn_easy_applier.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,8 +376,8 @@ def fill_up(self, job_context : JobContext) -> None:
376376
EC.presence_of_element_located((By.CLASS_NAME, 'jobs-easy-apply-content'))
377377
)
378378

379-
pb4_elements = easy_apply_content.find_elements(By.CLASS_NAME, 'pb4')
380-
for element in pb4_elements:
379+
input_elements = easy_apply_content.find_elements(By.CLASS_NAME, 'jobs-easy-apply-form-section__grouping')
380+
for element in input_elements:
381381
self._process_form_element(element, job_context)
382382
except Exception as e:
383383
logger.error(f"Failed to find form elements: {e}")

tests/test_aihawk_job_manager.py

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -71,21 +71,33 @@ def test_get_jobs_from_page_no_jobs(mocker, job_manager):
7171

7272
def test_get_jobs_from_page_with_jobs(mocker, job_manager):
7373
"""Test get_jobs_from_page when job elements are found."""
74-
# Mock the no_jobs_element to behave correctly
75-
mock_no_jobs_element = mocker.Mock()
76-
mock_no_jobs_element.text = "No matching jobs found"
74+
# Mock no_jobs_element to simulate the absence of "No matching jobs found" banner
75+
no_jobs_element_mock = mocker.Mock()
76+
no_jobs_element_mock.text = "" # Empty text means "No matching jobs found" is not present
7777

78-
# Mocking the find_element to return the mock no_jobs_element
79-
mocker.patch.object(job_manager.driver, 'find_element',
80-
return_value=mock_no_jobs_element)
78+
# Mock the driver to simulate the page source
79+
mocker.patch.object(job_manager.driver, 'page_source', return_value="")
8180

82-
# Mock the page_source
83-
mocker.patch.object(job_manager.driver, 'page_source',
84-
return_value="some page content")
81+
# Mock the outer find_element
82+
container_mock = mocker.Mock()
8583

86-
# Ensure jobs are returned as empty list due to "No matching jobs found"
87-
jobs = job_manager.get_jobs_from_page()
88-
assert jobs == [] # No jobs expected due to "No matching jobs found"
84+
# Mock the inner find_elements to return job list items
85+
job_element_mock = mocker.Mock()
86+
# Simulating two job items
87+
job_elements_list = [job_element_mock, job_element_mock]
88+
89+
# Return the container mock, which itself returns the job elements list
90+
container_mock.find_elements.return_value = job_elements_list
91+
mocker.patch.object(job_manager.driver, 'find_element', side_effect=[
92+
no_jobs_element_mock,
93+
container_mock
94+
])
95+
96+
job_manager.get_jobs_from_page()
97+
98+
assert job_manager.driver.find_element.call_count == 2
99+
assert container_mock.find_elements.call_count == 1
100+
89101

90102

91103
def test_apply_jobs_with_no_jobs(mocker, job_manager):
@@ -94,9 +106,6 @@ def test_apply_jobs_with_no_jobs(mocker, job_manager):
94106
mock_element = mocker.Mock()
95107
mock_element.text = "No matching jobs found"
96108

97-
# Mock the driver to simulate the page source
98-
mocker.patch.object(job_manager.driver, 'page_source', return_value="")
99-
100109
# Mock the driver to return the mock element when find_element is called
101110
mocker.patch.object(job_manager.driver, 'find_element',
102111
return_value=mock_element)
@@ -111,26 +120,13 @@ def test_apply_jobs_with_no_jobs(mocker, job_manager):
111120
def test_apply_jobs_with_jobs(mocker, job_manager):
112121
"""Test apply_jobs when jobs are present."""
113122

114-
# Mock no_jobs_element to simulate the absence of "No matching jobs found" banner
115-
no_jobs_element = mocker.Mock()
116-
no_jobs_element.text = "" # Empty text means "No matching jobs found" is not present
117-
mocker.patch.object(job_manager.driver, 'find_element',
118-
return_value=no_jobs_element)
119-
120123
# Mock the page_source to simulate what the page looks like when jobs are present
121124
mocker.patch.object(job_manager.driver, 'page_source',
122125
return_value="some job content")
123126

124-
# Mock the outer find_elements (scaffold-layout__list-container)
125-
container_mock = mocker.Mock()
126-
127-
# Mock the inner find_elements to return job list items
127+
# Simulating two job elements
128128
job_element_mock = mocker.Mock()
129-
# Simulating two job items
130129
job_elements_list = [job_element_mock, job_element_mock]
131-
132-
mocker.patch.object(job_manager.driver, 'find_elements',
133-
return_value=[container_mock])
134130

135131
mocker.patch.object(job_manager, 'get_jobs_from_page', return_value=job_elements_list)
136132

0 commit comments

Comments
 (0)