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.
Files changed (69) hide show
  1. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/PKG-INFO +1 -1
  2. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/pyproject.toml +1 -1
  3. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/pyproject.toml.orig +1 -1
  4. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/join.py +50 -11
  5. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/LICENSE +0 -0
  6. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/README.md +0 -0
  7. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/__init__.py +0 -0
  8. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/__init__.py +0 -0
  9. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/adp.py +0 -0
  10. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/adpmyjobs.py +0 -0
  11. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/ashby.py +0 -0
  12. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/bamboohr.py +0 -0
  13. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/breezy.py +0 -0
  14. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/dayforce.py +0 -0
  15. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/eightfold.py +0 -0
  16. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/greenhouse.py +0 -0
  17. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/gupy.py +0 -0
  18. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/hireology.py +0 -0
  19. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/hiringthing.py +0 -0
  20. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/hrmdirect.py +0 -0
  21. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/isolvedhire.py +0 -0
  22. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/jazzhr.py +0 -0
  23. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/jobaffinity.py +0 -0
  24. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/jobstreet.py +0 -0
  25. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/lever.py +0 -0
  26. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/manatal.py +0 -0
  27. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/oracle.py +0 -0
  28. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/paycor.py +0 -0
  29. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/paylocity.py +0 -0
  30. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/personio.py +0 -0
  31. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/phenom.py +0 -0
  32. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/pinpoint.py +0 -0
  33. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/recruitee.py +0 -0
  34. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/recruiterflow.py +0 -0
  35. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/rippling.py +0 -0
  36. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/smartrecruiters.py +0 -0
  37. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/successfactors.js +0 -0
  38. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/successfactors.py +0 -0
  39. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/taleo.py +0 -0
  40. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/teamtailor.py +0 -0
  41. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/ukg.py +0 -0
  42. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/welcomekit.py +0 -0
  43. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/wellfound.py +0 -0
  44. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/werecruit.py +0 -0
  45. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/workable.py +0 -0
  46. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/workatastartup.py +0 -0
  47. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/workday.py +0 -0
  48. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/wttj.py +0 -0
  49. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/adapters/zohorecruit.py +0 -0
  50. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/cli.py +0 -0
  51. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/__init__.py +0 -0
  52. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/accounts.py +0 -0
  53. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/adapter.py +0 -0
  54. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/attempts.py +0 -0
  55. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/browser.py +0 -0
  56. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/commands.py +0 -0
  57. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/conditions.py +0 -0
  58. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/countries.py +0 -0
  59. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/html.py +0 -0
  60. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/matching.py +0 -0
  61. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/otp.py +0 -0
  62. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/passwords.py +0 -0
  63. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/paths.py +0 -0
  64. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/registry.py +0 -0
  65. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/results.py +0 -0
  66. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/snapshot.py +0 -0
  67. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/core/telemetry.py +0 -0
  68. {atsfill-0.0.6.dev204 → atsfill-0.0.6.dev205}/src/atsfill/py.typed +0 -0
  69. {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.dev204
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "atsfill"
3
- version = "0.0.6.dev204"
3
+ version = "0.0.6.dev205"
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"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "atsfill"
3
- version = "0.0.6.dev204"
3
+ version = "0.0.6.dev205"
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"
@@ -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
- card = cards.nth(options.index(answer))
391
- if card.get_attribute("aria-checked") != "true":
392
- card.click()
393
- checked = [c.get_attribute("aria-checked") == "true" for c in cards.all()]
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
- if not target.count():
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
- try:
431
- button.click(timeout=STEP_TIMEOUT_MS)
432
- page.wait_for_url(lambda url: url != on, timeout=STEP_TIMEOUT_MS)
433
- except Timeout:
434
- raise RuntimeError(f"Continue on the {step} step went nowhere") from None
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