atsfill 0.0.6.dev204__tar.gz → 0.0.6.dev205__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/PKG-INFO +1 -1
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/pyproject.toml +1 -1
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/pyproject.toml.orig +1 -1
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/join.py +50 -11
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/LICENSE +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/README.md +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/__init__.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/__init__.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/adp.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/adpmyjobs.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/ashby.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/bamboohr.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/breezy.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/dayforce.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/eightfold.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/greenhouse.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/gupy.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/hireology.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/hiringthing.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/hrmdirect.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/isolvedhire.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/jazzhr.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/jobaffinity.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/jobstreet.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/lever.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/manatal.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/oracle.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/paycor.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/paylocity.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/personio.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/phenom.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/pinpoint.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/recruitee.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/recruiterflow.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/rippling.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/smartrecruiters.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/successfactors.js +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/successfactors.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/taleo.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/teamtailor.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/ukg.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/welcomekit.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/wellfound.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/werecruit.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/workable.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/workatastartup.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/workday.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/wttj.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/zohorecruit.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/cli.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/__init__.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/accounts.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/adapter.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/attempts.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/browser.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/commands.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/conditions.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/countries.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/html.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/matching.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/otp.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/passwords.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/paths.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/registry.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/results.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/snapshot.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/telemetry.py +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/py.typed +0 -0
- {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/registry.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: atsfill
|
|
3
|
-
Version: 0.0.6.
|
|
3
|
+
Version: 0.0.6.dev205
|
|
4
4
|
Summary: Read, dry-run and submit job application forms on hiring systems: Ashby, Greenhouse, Teamtailor, Welcome to the Jungle.
|
|
5
5
|
Keywords: job-applications,ats,ashby,greenhouse,teamtailor,ai-agents,cli
|
|
6
6
|
Author: shbernal
|
|
@@ -48,6 +48,10 @@ SUBMIT_TIMEOUT_MS = 60_000
|
|
|
48
48
|
BUTTON_TIMEOUT_MS = 2_000
|
|
49
49
|
OPENING_TIMEOUT_S = 20
|
|
50
50
|
CONTINUE_TIMEOUT_S = 15
|
|
51
|
+
ADVANCE_TIMEOUT_MS = 3_000 # how long a clicked card takes to move the wizard on by itself
|
|
52
|
+
INPUT_TIMEOUT_MS = 3_000 # how long a step takes to show its own input
|
|
53
|
+
CLICKS = 3 # a click on a step still rendering is lost, on a card or on Continue
|
|
54
|
+
LEAVE_TIMEOUT_MS = 5_000 # how long Continue takes to leave a step, per click
|
|
51
55
|
POLL_MS = 250
|
|
52
56
|
MAX_STEPS = 60
|
|
53
57
|
MAX_MONTHS = 36 # how far the calendar is paged for a date
|
|
@@ -107,6 +111,17 @@ _SHOWN = """root => {
|
|
|
107
111
|
return p ? p.textContent.trim() : "";
|
|
108
112
|
}"""
|
|
109
113
|
|
|
114
|
+
# Continue, clicked in one go with the check that the wizard is still on the step: the footer's
|
|
115
|
+
# last button on `review` is Confirm & apply, the send, and a step can leave on its own.
|
|
116
|
+
_CONTINUE = """(button, step) => {
|
|
117
|
+
const on = location.pathname.replace(/\\/$/, "").split("/").pop() === step;
|
|
118
|
+
if (!on || button.type === "submit") return false;
|
|
119
|
+
button.click();
|
|
120
|
+
return true;
|
|
121
|
+
}"""
|
|
122
|
+
|
|
123
|
+
_CHECKED = """cards => cards.map(card => card.getAttribute("aria-checked") === "true")"""
|
|
124
|
+
|
|
110
125
|
# The ids of the inputs that are empty, of those given.
|
|
111
126
|
_EMPTY = (
|
|
112
127
|
"""ids => ids.filter(id => { const e = document.getElementById(id); return e && !e.value; })"""
|
|
@@ -387,10 +402,10 @@ class Join:
|
|
|
387
402
|
if isinstance(answer, str):
|
|
388
403
|
if answer not in options or options.index(answer) >= len(texts):
|
|
389
404
|
raise NotTaken(f"no option {answer!r} in {field_id}: {texts}")
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
checked =
|
|
405
|
+
if _take(page, cards, options.index(answer)):
|
|
406
|
+
self._seen[field_id] = answer # the wizard took it and moved on
|
|
407
|
+
return
|
|
408
|
+
checked = cards.evaluate_all(_CHECKED)
|
|
394
409
|
if True in checked and checked.index(True) < len(options):
|
|
395
410
|
self._seen[field_id] = options[checked.index(True)]
|
|
396
411
|
case "DATE":
|
|
@@ -402,7 +417,9 @@ class Join:
|
|
|
402
417
|
case _ if isinstance(answer, str):
|
|
403
418
|
name = f"screeningQuestions.answer-{question['id']}"
|
|
404
419
|
target = page.locator(f'[name="{name}"], [id="{name}"], #salaryExpectationAmount')
|
|
405
|
-
|
|
420
|
+
try: # the step before's input stays a moment after the URL has changed
|
|
421
|
+
target.first.wait_for(timeout=INPUT_TIMEOUT_MS)
|
|
422
|
+
except Timeout:
|
|
406
423
|
target = page.locator(
|
|
407
424
|
"textarea:visible, input:visible:not([type=file]):not([role=combobox])"
|
|
408
425
|
)
|
|
@@ -419,19 +436,41 @@ def _continue(page: Page) -> Locator:
|
|
|
419
436
|
return page.locator("button:visible:has(> [data-button-label])").last
|
|
420
437
|
|
|
421
438
|
|
|
439
|
+
def _take(page: Page, cards: Locator, index: int) -> bool:
|
|
440
|
+
"""Clicks a card until it is checked, and says whether the wizard then moved on by itself."""
|
|
441
|
+
on = page.url
|
|
442
|
+
for _ in range(CLICKS):
|
|
443
|
+
if cards.evaluate_all(_CHECKED)[index]:
|
|
444
|
+
break
|
|
445
|
+
cards.nth(index).click()
|
|
446
|
+
try:
|
|
447
|
+
page.wait_for_url(lambda url: url != on, timeout=ADVANCE_TIMEOUT_MS)
|
|
448
|
+
except Timeout:
|
|
449
|
+
continue
|
|
450
|
+
return True
|
|
451
|
+
return False
|
|
452
|
+
|
|
453
|
+
|
|
422
454
|
def _next(page: Page, step: str) -> None:
|
|
455
|
+
"""Leaves the step with Continue, unless the wizard has already left it."""
|
|
423
456
|
button = _continue(page)
|
|
424
457
|
deadline = time.monotonic() + CONTINUE_TIMEOUT_S # an upload enables it once stored
|
|
425
|
-
while button.is_disabled() and time.monotonic() < deadline:
|
|
458
|
+
while _step(page) == step and button.is_disabled() and time.monotonic() < deadline:
|
|
426
459
|
page.wait_for_timeout(POLL_MS)
|
|
460
|
+
if _step(page) != step:
|
|
461
|
+
return
|
|
427
462
|
if button.is_disabled():
|
|
428
463
|
raise NotTaken(f"the {step} step keeps Continue disabled: a required answer is missing")
|
|
429
464
|
on = page.url
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
465
|
+
for _ in range(CLICKS):
|
|
466
|
+
try:
|
|
467
|
+
if not button.evaluate(_CONTINUE, step, timeout=STEP_TIMEOUT_MS):
|
|
468
|
+
return # the wizard left the step meanwhile
|
|
469
|
+
page.wait_for_url(lambda url: url != on, timeout=LEAVE_TIMEOUT_MS)
|
|
470
|
+
except Timeout:
|
|
471
|
+
continue
|
|
472
|
+
return
|
|
473
|
+
raise RuntimeError(f"Continue on the {step} step went nowhere")
|
|
435
474
|
|
|
436
475
|
|
|
437
476
|
def _question(q: dict[str, Any]) -> Field:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|