Teamtailor¶
Observed on live postings, last on 2026-10-29. The fixture page is tests/pages/teamtailor/new.html, served as an employer's career site in tests/test_teamtailor.py.
Where the form is¶
- Career sites live at
<employer>.teamtailor.comor on the employer's domain (careers.<employer>.com,jobs.<employer>.fr), sometimes under a locale (/fr/jobs/…). A job is/jobs/<id>-<slug>; the double-send key ishttps://<host>/jobs/<id>. - On a custom domain, the page shows itself by the "powered by" link to
www.teamtailor.comin its footer. The footer text is translated ("Career site by Teamtailor", "Outil de recrutement de Teamtailor"), the link is not. Some sites drop the link; they still load Teamtailor'scareersite-*.jsfromteamtailor-cdn.com. - The form has its own page,
<job>/applications/new. "Apply for this job" on the job page opens the same form in an overlay; "Apply with LinkedIn" sits above it and is never used. applications/newis server-rendered HTML, served without a session, sorequest_readfetches it andbrowser_readtakes the loaded form's HTML, and one parser lists the fields from both. The request read covers*.teamtailor.comhosts only, since a custom domain is known only by its page. Scripts add two things the server's HTML lacks: a drop zone'sinput[type=file](the drop zone is read from itsforms--inputs--uploadcontroller, its label anddata-forms--inputs--upload-accepted-files-value), and a video question's accepted types, which the request read leavesnull.- A career site can send its applications elsewhere:
applications/newthen redirects to another host, and the posting isunsupportedas applying on another site.
Reading the form¶
form#job-application-form. Standard fields are namedcandidate[first_name],candidate[last_name],candidate[email],candidate[phone],candidate[job_applications_attributes][0][cover_letter], and the consent boxescandidate[consent_given]andcandidate[consent_given_future_jobs], which atsfill reads asYes/Nochoices. Which of them a form shows, and which are required, depends on the company.- A required field's label holds
<sup data-asterisk>; the translated "Required" next to it is screen-reader text. Inputs carry norequiredattribute. - The resume is a drop zone over
input[type=file]#candidate_resume_remote_url; "Additional files" is#candidate_file_remote_url. - Custom questions are
.questionblocks withdata-question-mandatory, a hidden[question_id](the field id isquestion:<question id>) and an answer input whose name ends in its kind. A qualifying question is a[data-careersite--form-target=qualifyingAnswers]block of the same make, always required. A follow-up question carriesdata-question-show-if-uuid(its earlier question'sdata-question-uuid) anddata-question-show-if-value(the option value that shows it); its condition isIsthat question and that option, itsdata-question-mandatorykept. A follow-up stays in the page hidden until it shows.
Each rendering, and on how many of 15 postings' forms (2026-10-19):
| Page | Field type | Postings |
|---|---|---|
candidate[first_name], [email]… text inputs, the cover letter textarea |
text |
15, the cover letter on 9 |
candidate[phone] (intl-tel-input) |
phone |
15 |
| consent checkboxes | choice, Yes/No |
15 |
resume drop zone #candidate_resume_remote_url |
file |
15 |
"Additional files" #candidate_file_remote_url, several files allowed |
file, one |
13 |
address autocomplete candidate[location][query] (role=combobox) |
typeahead |
7 |
office locations, fieldset of candidate[location_ids][] checkboxes |
multi_choice |
2 |
question [text], input or textarea |
text |
11 |
question [number] |
text, a number |
4 |
question [boolean] radios, labels translated |
choice |
8 |
question [choice] radios |
choice |
4 |
question [choice] dropdown of menuitemradio buttons over hidden radios |
choice |
3 |
question [choices][] checkboxes |
multi_choice |
2 |
question [choices][] dropdown of menuitemcheckbox buttons, with a search box |
multi_choice |
1 |
question [range], a slider with min, max, step |
choice of each step |
0, seen before |
question [date] (input[type=date]) |
date |
0, seen before |
| qualifying question | choice |
1 |
| follow-up question | as its kind, with its condition | 2 |
video question ([picked_video_attributes]), its "Upload video" input; recording is never used |
file, a clip |
3, required on 2 |
Filling¶
- The cookie banner is a non-modal
<dialog data-controller="common--cookies--alert">over the form. The blocker's lists hide it on every domain, custom ones included. A career site can make it a takeover (data-common--cookies--alert-takeover-modal-value="true"), opened withshowModal(): hidden, it still leaves the rest of the page inert, so keys typed into the address go nowhere and clicks find the page overlaid. Its "decline optional cookies" button (#disableAll) closes it, clicked before the fill. - Qualifying questions are answered first. While one is unanswered, or answered with what the employer does not accept, the rest of the form sits under
[data-careersite--form-target=formContentWrapper].cursor-not-allowedand takes no clicks; atsfill then stops withnot_qualified. - A follow-up question shows once its earlier question has the answer it waits for, and is filled once shown. Once filled, the adapter lists the questions the page shows (
checkVisibility()), for the core to compare with its conditions. - Click the consent box's label, or the box itself when the employer left the label empty (an inline
<p><br></p>, no box to click). - A picked file uploads at once:
POST /uploads/presigned_data, then to Teamtailor's S3. The form then holds the file's address incandidate[resume_remote_url], whose last path segment is the file name as Teamtailor stores it: ASCII, lowercase, each run of characters other than letters, digits,.and-one_(CV Élodie (2026).pdfiscv_elodie_2026_.pdf). The read-back gives the answer's name back when the stored one is its rewrite. - The phone field (intl-tel-input) takes an international number and keeps it as
+<digits>. - In a dropdown choice the hidden radios still have labels: pick from the dropdown, not the hidden label, the item whose
data-search-textis the answer (an apostrophe in it is common in French). A multiple choice dropdown stays open after a pick, over the fields below it, so it is closed once its options are picked. - The address is typed key by key, and the first suggestion (
[role=option], its text indata-description) matching it is picked; only a pick fillscandidate[location][place_id]and the city, country and coordinates beside it. - A video question offers to record a clip or to upload one; atsfill uploads the answer's file through its hidden
input[type=file], which takes the extensions in itsaccept. The page posts/uploads/presigned_data("type":"video"), puts the clip on S3, thenPOST /videoswith its storage address answers{id, sgid}. The form keeps the id in[picked_video_attributes][video_id], enabled only then;[video_attributes][file]stays disabled. Teamtailor transcodes the clip andGET /videos/<sgid>gives it back withfile_file_name, the stored name, and itstranscode_status. The page checks only the extension and avideo/*type, no size or length: a file it rejects (its "not supported" message) posts no video,answer_not_taken. One that does not decode is posted, its transcode endserrorand the form keeps no video id; one grey second of 64x64 H.264, without audio, is enough (the Test user's clip). - A range question is set on its slider,
input[type=range]. - A number question takes only a number; any other answer leaves it empty, a mismatch.
Read-back and submit¶
- Nothing reaches the employer before submit, so the read-back is the form's
FormData: each value, each checked option's label (or its dropdown item's text), for a file the name at the end of its stored address, for a video question thefile_file_nameTeamtailor gives back for the video id the form keeps, and the address only once a suggestion filled itsplace_id. Each additional file is stored under its owncandidate[uploads_attributes][<n>][file_remote_url], a text input the upload adds: an address, not a field. - Submit is
[type=submit]in the form, under a window at least 900px tall: in a short one the button sits below the fold and the click sends nothing. The evidence is the redirect to…/applications/<id>/thanks…, the id a UUID, titled "Applied to". An optional "Connect" step that follows is skipped. - Some employers hold the application until the applicant confirms their email: submit then redirects to
<job>/applications/email_verification_needed("Vérifiez votre adresse e-mail"), and the application completes only once the link emailed to the address in the form is clicked. The mailbox is outside atsfill (ADR 0015): the send endssubmittedwithheld: …, and the double-send guard stops another.
Known gaps¶
| Gap | What | Code | Postings |
|---|---|---|---|
file_question |
file question: a drop zone inside a .question |
unsupported_rendering |
0 |