Skip to content

Microsoft Careers Scraper Automation Learnings

This lab is an offline, loopback-only fixture for the bespoke Microsoft careers apply flow. It does not call careers.microsoft.com, does not use a real account, and never submits. The local HttpListener binds to 127.0.0.1, renders deterministic seeded HTML, and exposes a submit route that is a no-op.

Selector strategy

Microsoft's careers flow is not a standard ATS vendor surface. There is no Workday-style data-automation-id contract and no iCIMS-style generated suffix pattern to lean on. The selector chain in MicrosoftSelectors therefore uses this order:

  1. Semantic id values when present, such as firstName, highestDegree, and submitApplication.
  2. Semantic name values, such as firstName, degree, and relocation.
  3. aria-label values for degraded DOMs where ids are dropped.
  4. Label-adjacent selectors such as label:has-text('First name') + input.

The engine always asks Playwright for the first visible match through Locator(selector).Locator("visible=true").First. This matters because multi-step fixtures keep prior steps in the DOM but hidden. Repeated ids across hidden steps are a common failure mode in scraper labs; visible-first resolution prevents a hidden previous-step control from being filled or clicked.

Fallback chain

The fixture randomizes each field with IdStyle.Primary, IdStyle.Degraded, IdStyle.AriaOnly, or IdStyle.Missing. Primary hits exercise semantic ids. Degraded hits exercise names. Aria-only hits exercise accessibility metadata. Optional missing fields exercise not-found telemetry without halting the whole loop.

The review submit button is intentionally part of the selector set only so the automation can locate it. The lab then logs would submit (suppressed) and never clicks. The fixture button also has onclick="return false" and /submit is a no-op route.

Timing quirks

The fixture injects seeded page activation delays on some iterations. The runner waits for #step-*.active, then field-level resolution applies short per-selector timeouts. This catches brittle timing assumptions while keeping the 20-iteration learning loop fast enough for local development.

Hit-rate numbers

The lab writes JSONL plus a console summary after every five iterations and at the end. A successful 20-iteration run reports:

  • Completed equals Iterations.
  • SuppressedSubmits equals Iterations.
  • SubmitLocated equals Iterations.
  • selector rows show primary hits versus fallback hits by field.
  • judgement-call:degree and judgement-call:custom-question record expected ambiguous-field pauses.

Verified run on July 4, 2026 with --iterations 20 --seed 1337:

Field Attempts Match rate Primary hits Fallback hits
Account continue 32 62.5% 8 12
Account email 42 47.6% 3 17
Account password 33 60.6% 11 9
Address 44 38.6% 9 8
Degree 32 62.5% 11 9
Employer 36 55.6% 10 10
First name 41 48.8% 6 14
Last name 35 57.1% 9 11
Next / Continue 80 100.0% 80 0
Phone 44 38.6% 8 9
Relocation preference 32 62.5% 12 8
Resume upload 26 76.9% 14 6
School 37 54.1% 8 12
Submit button 20 100.0% 20 0

Overall: 20 iterations, 20 completed, 0 faulted, 18 judgement pauses, 20 submit controls located, and 20 suppressed submits. Expected failure-mode telemetry was judgement-call:degree 16 times and judgement-call:custom-question 13 times.

Why Microsoft is a prime PageAgent-advisor case

Microsoft's bespoke DOM is exactly where a PageAgent-style advisor is useful. The engine can fill deterministic fields through ordered selectors, but degree and custom screening questions still need semantic interpretation of arbitrary page text and option lists. The lab raises RunStatus.AwaitingJudgement with an ApplicationRun.PendingCall instead of guessing.

The IPageAdvisor seam being built on branch feature-pageagent-swap can plug into this judgement pause unchanged: the engine already exposes the field label, question, context, ranked options, and current run state. The advisor can inspect the visible DOM and recommend a ranked answer without changing the submit guard or selector resolver.