Welcome to the Jungle¶
Observed on live postings, last on 2026-10-19; the account on 2026-10-24. The fixture pages are tests/pages/wttj/job.html, served as a signed-in job page in tests/test_wttj.py, and tests/pages/wttj/account.html, the sign-in and sign-up forms.
Where the form is¶
- Jobs:
www.welcometothejungle.com/<lang>/companies/<employer>/jobs/<slug>. The double-send key drops the language:https://www.welcometothejungle.com/companies/<employer>/jobs/<slug>. easyapply.jobs/r/<id>links (from job boards) redirect to the job, with or without a session, and open the apply form by themselves. The key comes from the job they land on.- Some jobs apply on the employer's own site: their apply button is a link, and atsfill reports them
unsupported. - CloudFront lets Camoufox through headless with its own user agent. (Headless Chromium is answered 403.)
Account¶
Applying needs a signed-in account, one per user for the whole system. The signed-in check is GET https://api.welcometothejungle.com/api/v2/users/me: 200 for an account, 204 for a visitor. A session saved from Chromium works in Camoufox.
- Sign-in:
/en/signin(it becomes/en/authenticate/signin), email and password, posted toPOST api.welcometothejungle.com/api/v1/sessions: 201, then the page leaves for the profile's onboarding (/en/complete-profile/…). A wrong password is 401, "Email or password incorrect":refused: wrong_password. - Sign-up (
login wttj --create --email <address> --details <file>, itsnamethe first name):/en/authenticate/signup, a first name (required), email, password, and a newsletter box left unticked; posted toPOST /api/v3/registrations: 201, signed in, on the onboarding, which need not be finished to apply. A taken email is 422 witherrors.email:refused: email_in_use. The page checks the password's strength as it is typed (POST /api/v3/passwords/validate) and refuses one under 12 characters without posting:refused: password_rule. The page loads an invisible reCAPTCHA v3; headless Camoufox passed it. - The page gets its CSRF token (cookie
csrf-token) about a second after it renders. A form posted before then is answered 403 and the page shows nothing, so both forms wait for it. - The account is created unconfirmed and a "Confirm your new account" email follows:
login --createendsblocked: email_link. Clicking its link signs every session out (users/meanswers{"info": "wttj-forced-signed-out"}), sologin wttjruns once more after it. Whether an unconfirmed account can apply is unobserved. - Sign-ins in a burst from one address make
/en/signinanswer 429, "Too Many Requests", for a while:blocked: rate_limited. - With a password source, a form command that finds the session signed out signs in again on its own.
The job page is rendered for a visitor first: the apply button is a link to /en/authenticate/signin until the page knows the account, a moment later.
Reading and filling¶
The apply button ([data-testid="job_header-button-apply"]) opens a modal with the form holding [data-testid=apply-form-submit], prefilled from the account profile. Field ids are the input names. Every form is the account profile's fields, the ones the employer picked, and the employer's questions; the inventory, on 15 employers, then 10 at the adapter gate:
| Name | Field type | Rendering | Postings |
|---|---|---|---|
firstname, lastname, subtitle |
text |
input, required | 15 |
media.<site>: website, linkedin, twitter, github, stackoverflow |
text |
input, required by some employers | 25 |
cover_letter |
text |
textarea, required by some employers | 24 |
questions.<hash> |
text |
textarea, required, one to three | 5 |
phone |
phone |
text input | 15 |
location |
typeahead |
downshift combobox | 15 |
resume |
file |
PDF only, behind the account profile's CV | 25 |
portfolio |
file |
PDF only | 1 |
consent |
choice, Yes/No |
checkbox | 15 |
No question rendered as a checkbox, a radio or a select: those, and any other checkbox, are listed as unhandled_rendering. A page that is not a job, or an apply button that opens no form, is an adapter_error.
- The email comes from the account and is disabled; the avatar is not read.
- A field holding a value when the form opens is
prefilledwith it (a ticked boxYes, a résumé itsdata-id); one not answered keeps what the account profile put there, required or not. The account profile's location arrives up to a second after the rest, so the form is read once it has, or after 3 seconds. - The account profile's CV shows first, with a Remove button: remove it, then upload the tailored PDF. "Utiliser comme CV par défaut", which would replace the account profile's CV, is never ticked.
- Location suggestions load only while typing key by key, into a box cleared by select-all and Backspace: Playwright's
fill("")(select-all and Delete) leaves the account profile's value in it, and the answer lands after it. The first suggestion, "Position actuelle", geolocates and is skipped. Suggestions readParis, Île-de-France, France. The first answer typed on a page sometimes loads none, "Position actuelle" alone, for good: it is typed again. - Nothing is sent before submit, the CV included, so the read-back is the form's inputs and the CV's shown name (
[data-id]).
Submitting¶
[data-testid="apply-form-submit"] sends POST /api/v1/applications, which answers 201 with an application.reference: that is the evidence. A login wall instead of the form means the session expired: blocked: login_required.