atsfill 0.0.4.dev37__tar.gz → 0.0.4.dev39__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.4.dev37 → atsfill-0.0.4.dev39}/PKG-INFO +1 -1
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/pyproject.toml +2 -2
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/pyproject.toml.orig +2 -2
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/adapters/bamboohr.py +5 -2
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/adapters/dayforce.py +5 -3
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/adapters/paylocity.py +2 -1
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/adapters/recruitee.py +1 -1
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/LICENSE +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/README.md +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/__init__.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/adapters/__init__.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/adapters/ashby.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/adapters/breezy.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/adapters/greenhouse.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/adapters/hireology.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/adapters/jazzhr.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/adapters/lever.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/adapters/manatal.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/adapters/oracle.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/adapters/paycor.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/adapters/smartrecruiters.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/adapters/teamtailor.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/adapters/workable.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/adapters/wttj.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/cli.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/core/__init__.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/core/accounts.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/core/adapter.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/core/attempts.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/core/browser.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/core/commands.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/core/matching.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/core/paths.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/core/registry.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/core/results.py +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/py.typed +0 -0
- {atsfill-0.0.4.dev37 → atsfill-0.0.4.dev39}/src/atsfill/registry.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: atsfill
|
|
3
|
-
Version: 0.0.4.
|
|
3
|
+
Version: 0.0.4.dev39
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "atsfill"
|
|
3
|
-
version = "0.0.4.
|
|
3
|
+
version = "0.0.4.dev39"
|
|
4
4
|
description = "Read, dry-run and submit job application forms on hiring systems: Ashby, Greenhouse, Teamtailor, Welcome to the Jungle."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -68,7 +68,7 @@ select = [
|
|
|
68
68
|
[tool.pytest.ini_options]
|
|
69
69
|
testpaths = ["tests"]
|
|
70
70
|
markers = ["live: drives a real hiring system page with data from .env and .tmp/; opt-in with -m live"]
|
|
71
|
-
addopts = "-m 'not live' -n
|
|
71
|
+
addopts = "-m 'not live' -n auto --dist loadscope"
|
|
72
72
|
|
|
73
73
|
[[tool.uv.index]]
|
|
74
74
|
name = "testpypi"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "atsfill"
|
|
3
|
-
version = "0.0.4.
|
|
3
|
+
version = "0.0.4.dev39"
|
|
4
4
|
description = "Read, dry-run and submit job application forms on hiring systems: Ashby, Greenhouse, Teamtailor, Welcome to the Jungle."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -55,7 +55,7 @@ testpaths = ["tests"]
|
|
|
55
55
|
markers = [
|
|
56
56
|
"live: drives a real hiring system page with data from .env and .tmp/; opt-in with -m live",
|
|
57
57
|
]
|
|
58
|
-
addopts = "-m 'not live' -n
|
|
58
|
+
addopts = "-m 'not live' -n auto --dist loadscope"
|
|
59
59
|
|
|
60
60
|
[[tool.uv.index]]
|
|
61
61
|
name = "testpypi"
|
|
@@ -30,6 +30,7 @@ CHALLENGE = "iframe[src*='recaptcha/api2/bframe']"
|
|
|
30
30
|
FORM_TIMEOUT_MS = 30_000
|
|
31
31
|
UPLOAD_TIMEOUT_MS = 30_000
|
|
32
32
|
SHOWN_TIMEOUT_MS = 2_000
|
|
33
|
+
STATES_TIMEOUT_MS = 15_000 # for the state's list a country change refetches
|
|
33
34
|
SETTLE_MS = 300 # for React to render what a response brought
|
|
34
35
|
CAPTCHA_TIMEOUT_MS = 20_000
|
|
35
36
|
SUBMIT_TIMEOUT_MS = 90_000
|
|
@@ -215,12 +216,14 @@ class BambooHR:
|
|
|
215
216
|
case "file":
|
|
216
217
|
_attach(form, str(name), answer)
|
|
217
218
|
case "select" if field_id == "countryId":
|
|
219
|
+
if _shown(control) == value:
|
|
220
|
+
continue # the state's list stays
|
|
218
221
|
with (
|
|
219
222
|
suppress(Timeout),
|
|
220
223
|
page.expect_response(
|
|
221
|
-
lambda r: "/ajax/get_states" in r.url, timeout=
|
|
224
|
+
lambda r: "/ajax/get_states" in r.url, timeout=STATES_TIMEOUT_MS
|
|
222
225
|
),
|
|
223
|
-
): # the state's list follows the country
|
|
226
|
+
): # the state's list follows the country
|
|
224
227
|
_pick(page, control, value)
|
|
225
228
|
page.wait_for_timeout(SETTLE_MS)
|
|
226
229
|
case "select":
|
|
@@ -35,6 +35,8 @@ STEP_TIMEOUT_MS = 20_000
|
|
|
35
35
|
SUBMIT_TIMEOUT_MS = 90_000
|
|
36
36
|
UPDATE_TRIES = 3
|
|
37
37
|
FOLLOWUP_TIMEOUT_MS = 3_000
|
|
38
|
+
DIAL_TIMEOUT_MS = 2_000 # per dial code tried, longest first
|
|
39
|
+
OPTIONS_TIMEOUT_MS = 5_000 # for a dropdown's options to render
|
|
38
40
|
NEXT_TRIES = 3
|
|
39
41
|
STEP_POLLS = 16 # of POLL_MS, before Next is clicked again
|
|
40
42
|
POLL_MS = 500
|
|
@@ -700,7 +702,7 @@ def _pick(page: Page, target: Locator, texts: list[str]) -> bool:
|
|
|
700
702
|
target.fill("")
|
|
701
703
|
target.press_sequentially(text[:40], delay=10)
|
|
702
704
|
with suppress(Timeout):
|
|
703
|
-
options.first.wait_for(timeout=
|
|
705
|
+
options.first.wait_for(timeout=OPTIONS_TIMEOUT_MS)
|
|
704
706
|
page.evaluate(_REVEAL, [target.get_attribute("id"), [text]])
|
|
705
707
|
if _click_option(page, target, _plain(text).casefold().__eq__):
|
|
706
708
|
return True
|
|
@@ -750,7 +752,7 @@ def _pick_suggestion(page: Page, target: Locator, value: str) -> None:
|
|
|
750
752
|
_open(target)
|
|
751
753
|
target.press_sequentially(value[:40], delay=10)
|
|
752
754
|
with suppress(Timeout):
|
|
753
|
-
page.locator(_option(target)).first.wait_for(timeout=
|
|
755
|
+
page.locator(_option(target)).first.wait_for(timeout=OPTIONS_TIMEOUT_MS)
|
|
754
756
|
if not _click_option(page, target, lambda shown: shown.startswith(_plain(value).casefold())):
|
|
755
757
|
target.press("Escape")
|
|
756
758
|
|
|
@@ -769,7 +771,7 @@ def _fill_phone(page: Page, selector: str, value: str) -> None:
|
|
|
769
771
|
dial.press_sequentially(f"+{code}", delay=10)
|
|
770
772
|
options = page.locator(_option(dial)).filter(has_text=re.compile(rf"\+{code}(\s|$)"))
|
|
771
773
|
with suppress(Timeout):
|
|
772
|
-
options.first.wait_for(timeout=
|
|
774
|
+
options.first.wait_for(timeout=DIAL_TIMEOUT_MS)
|
|
773
775
|
if options.count():
|
|
774
776
|
main = options.filter(has_text=MAIN_COUNTRY.get(code, "\0"))
|
|
775
777
|
(main if main.count() else options).first.click()
|
|
@@ -31,6 +31,7 @@ SUBMIT = "[data-automation-id=btnSubmit]"
|
|
|
31
31
|
STEP = "#appDetailDiv .type-bold.float-right" # "Step 2 of 5"
|
|
32
32
|
FORCED_RESUME = ".modal.in #btn-forceResume"
|
|
33
33
|
FORM_TIMEOUT_MS = 30_000
|
|
34
|
+
COOKIE_TIMEOUT_MS = 5_000
|
|
34
35
|
STEP_TIMEOUT_MS = 20_000
|
|
35
36
|
UPLOAD_TIMEOUT_MS = 30_000
|
|
36
37
|
SUBMIT_TIMEOUT_MS = 90_000
|
|
@@ -473,7 +474,7 @@ def _present(page: Page, control: Control) -> bool:
|
|
|
473
474
|
def _dismiss_cookies(page: Page) -> None:
|
|
474
475
|
close = page.locator("#onetrust-banner-sdk .onetrust-close-btn-handler")
|
|
475
476
|
with suppress(Timeout):
|
|
476
|
-
close.first.wait_for(state="visible", timeout=
|
|
477
|
+
close.first.wait_for(state="visible", timeout=COOKIE_TIMEOUT_MS)
|
|
477
478
|
close.first.click()
|
|
478
479
|
|
|
479
480
|
|
|
@@ -63,7 +63,7 @@ _READ = """form => {
|
|
|
63
63
|
field = {id: name, required: false, options: [], accept: null,
|
|
64
64
|
maxLength: el.maxLength > 0 ? el.maxLength : null};
|
|
65
65
|
if (el.type === "radio") field.kind = "radio";
|
|
66
|
-
else if (el.type === "checkbox" &&
|
|
66
|
+
else if (el.type === "checkbox" && /\\.(flag|consent)$/.test(name)) {
|
|
67
67
|
field.kind = "legal";
|
|
68
68
|
field.label = text(form.querySelector(`[id="${el.getAttribute("aria-labelledby")}"]`));
|
|
69
69
|
} else if (el.type === "checkbox") field.kind = "checkboxes";
|
|
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
|