Upload once. Apply everywhere.
Stop retyping your life story into every job form.
Website · Chrome Web Store · Firefox Add-ons · GitHub · How it works · Dashboard
Sign up free at autocvapply.com - upload your CV, connect the extension, fill forms in minutes.
Job board Auto Apply: LinkedIn, Indeed, Totaljobs, Glassdoor, and Reed - full end-to-end apply from the extension sidebar. On other ATS forms, you review every field and click Submit yourself.
Getting started
Using AutoCVApply
- How it works
- Job board Auto Apply
- Features
- Cover letters
- Supported platforms
- Postbox design
- Pricing
- Security & privacy
- Testing overview
- Job search tips
- Links
For developers
- Architecture
- Tech stack
- Project structure
- Getting started (local dev)
- Extension bridge (MCP)
- Form corpus quality engineering
- Job board Auto Apply testing
- Key commands
- API
- Deployment
- Contributing
- License
Demo video coming soon. Until then, see How it works on autocvapply.com or sign up and try Draft All on a real Greenhouse or Ashby form.
Job applications are a copy-paste endurance test. Workday wants your address. Greenhouse wants it again. Ashby wants a cover letter you've already written three times this week. Every ATS renders the same questions differently - custom widgets, shadow DOM, multi-step wizards, iframe embeds.
AutoCVApply reads your CV once, builds a structured profile, and stamps it onto application forms through a battle-tested Chrome extension - so you spend time on roles that matter, not on retyping your phone number for the forty-seventh time.
| Without AutoCVApply | With AutoCVApply |
|---|---|
| Retype contact details, education, and skills on every ATS | One structured profile - filled from your uploaded CV |
| Blank stare at "Why do you want this role?" textareas | Draft All streams AI answers for free-text questions |
| Skip cover letters because they're tedious | One-click cover letter tailored to the job description |
| Same generic CV for every posting | Tailored resume draft matched to the role |
| No idea how your CV reads against the JD | ATS score with keyword and formatting feedback |
| Pray comboboxes and wizards don't break mid-form | 6,229-scenario test corpus - Greenhouse, Ashby, Workday, UK job boards, and more |
| Obvious bot-like paste fills that trip ATS checks | Anti-bot detection - character-by-character typing, natural pauses, human-like navigation on job board Auto Apply |
| Click through Easy Apply one job at a time | Job board Auto Apply - LinkedIn, Indeed, Totaljobs, Glassdoor, and Reed from the extension sidebar |
| Submit applications blindly | You stay in control on ATS forms - we fill fields; you review and submit |
Job board Auto Apply (LinkedIn, Indeed, Totaljobs, Glassdoor, Reed) runs end-to-end: search filtered jobs, open each posting, fill every step, and submit. More boards are on the way.
- Job seekers applying across multiple ATS platforms in a week
- Career changers who need tailored answers without rewriting everything
- Privacy-conscious applicants who want a clear account, revocable API tokens, and a published privacy policy
- Developers and contributors who care about verified form-fill quality, not just demo GIFs
Why choose AutoCVApply?
- Save time - autofill plus AI drafting for the questions that actually slow you down
- Stay honest - answers draw from your profile and preferences, not invented credentials
- Sound human, not generic - Draft All answers are scored on hundreds of scenarios to catch AI tropes, em dashes, and filler while staying grounded in your CV
- Trust the engineering - four-layer fill verification, Playwright smoke tests, and 437 PHPUnit methods
- Avoid bot flags - human-like typing, pauses between fields, and natural navigation on job board Auto Apply runs
- British Postbox UI - Royal Mail red, navy, warm paper tones. Feels like sending a letter, not filling a spreadsheet
Recommended: create a free account at autocvapply.com, upload your CV, then install from the Chrome Web Store or Firefox Add-ons (or download a zip from your dashboard for Edge, Brave, or temporary Firefox sideload).
- Sign up and upload your CV
- Install the extension from the Chrome Web Store or Firefox Add-ons - or open Dashboard → Extension for zip installs
- For zip installs: download for your browser and sideload using the on-screen instructions
- Copy your connection JSON (
token+api_base) into the extension sidebar
| Browser | Install method | Store listing |
|---|---|---|
| Chrome | Chrome Web Store → Add to Chrome | Live |
| Edge / Brave | Store listing or dashboard zip → Load unpacked | Chrome Web Store |
| Firefox | Firefox Add-ons → Add to Firefox | Live |
Clone this repo, run composer run setup, build the extension with npm run build:extension, then load extension/dist/ unpacked in Chrome. See Getting started (local dev) for full setup.
flowchart LR
A["📄 Upload CV<br/>PDF or Word"] --> B["🤖 AI extraction<br/>NanoGPT"]
B --> C["✏️ Review profile<br/>Dashboard"]
C --> D["🔌 Connect extension<br/>API token"]
D --> E["⚡ Autofill + Draft All<br/>Any ATS · any site"]
D --> L["🤖 Auto Apply tab<br/>LinkedIn · Indeed · Totaljobs · Glassdoor · Reed"]
E --> F["📝 Cover letters<br/>Tailored resumes · ATS score"]
L --> F
| Step | What happens |
|---|---|
| 1. Post your CV | Drop a PDF or DOCX. Tesseract OCR + NanoGPT extract name, contact, skills, experience, and education into a structured profile. |
| 2. Check the details | Tweak anything we missed - summary, visa status, salary expectations, application preferences. |
| 3. Connect the extension | Install the Chrome or Firefox extension, paste your connection JSON (token + api_base) from the dashboard. |
| 4. Fill and draft | Autofill fields on any job form. Draft All streams AI-written answers for free-text questions, cover letters, and tailored resumes. You submit when ready. |
| 5. Auto Apply (job boards) | Open the extension sidebar Auto Apply tab, pick LinkedIn, Indeed, Totaljobs, Glassdoor, or Reed, and let the extension search, open each job, fill every step, and submit. |
Full end-to-end applications on LinkedIn Easy Apply, Indeed Apply, Totaljobs Quick Apply, Glassdoor Easy Apply, and Reed Easy Apply - not just field fill:
| Step | What happens |
|---|---|
| Search | Extension runs job search with Easy Apply filters from the sidebar Auto Apply tab |
| Open | Each matching job opens in a tab; listings without one-click apply are skipped |
| Fill | Contact info, screening questions, resume steps, and multi-step wizards are filled from your profile |
| Submit | Continue, review, and submit buttons advance and complete the application |
On Greenhouse, Ashby, Workday, and other ATS platforms, autofill and Draft All still work as before - you review and click Submit yourself. More full Auto Apply job boards are on the way.
| Module | Feature | What it does |
|---|---|---|
| CV parsing | PDF & Word upload | Structured profile extraction with Tesseract OCR + local pdftoppm preprocessing |
| CV parsing | Editable profile | Skills, experience, education, summary, application preferences - you control the source of truth |
| Autofill | One-click fill | Profile data stamped onto native inputs, comboboxes, radios, checkboxes, multi-step wizards |
| Autofill | Anti-bot detection | Character-by-character typing on short fields, pauses between applies, hover-and-click navigation on job board Auto Apply |
| Autofill | Shadow DOM & iframes | Content scripts traverse embedded ATS widgets other extensions miss |
| Auto Apply | LinkedIn Easy Apply E2E | Sidebar Auto Apply tab: search, open jobs, fill steps, submit applications |
| Auto Apply | Indeed Apply E2E | Indeed Apply search, contact/resume/questions/review steps, submit via Auto Apply tab |
| Auto Apply | Totaljobs Quick Apply E2E | Genesis Quick Apply search, fill, and submit from the sidebar |
| Auto Apply | Glassdoor Easy Apply E2E | Host-page search plus Indeed Apply iframe steps where required |
| Auto Apply | Reed Easy Apply E2E | Reed search, job detail, modal review, and submit |
| Application Assistant | Field inventory | AI maps the page's questions to fillable refs before drafting |
| Application Assistant | Job context | Extracts title, company, and description from the posting |
| Application Assistant | Draft All | Streams batch answers for unanswered fields (NDJSON) |
| Application Assistant | Draft field | Single-field AI answer on demand |
| Application Assistant | Answer quality | Rubric scoring on AI drafts - human tone, profile grounding, banned AI phrases, no em dashes |
| Documents | Cover letter | Styled, job-specific PDF from your profile + posting (extension Auto Apply + Cover tab) |
| Documents | Tailored resume | Role-matched resume draft |
| Documents | ATS score | Keyword and compatibility feedback against the job description |
| Dashboard | Cover letter settings | Design + font preferences (including Random) with live preview |
| Dashboard | Usage & billing | Monthly autofill allowance, extension connection, GoCardless subscriptions (UK) |
| Analytics | Public aggregate stats | Daily totals across all users - no personal application history exposed |
AutoCVApply generates styled cover letters on the fly in the extension during Auto Apply and from the Cover tab. On the dashboard, open Cover letter settings to pick a design and font (or Random for either), preview the result live, and use those preferences whenever a letter is attached.
Draft All and Quick Answer use AI, but we score generated answers extensively before shipping changes - not just on a handful of demo forms.
| What we check | Why it matters |
|---|---|
| Grounding | Real employers, roles, and skills from your profile - not invented credentials |
| Human tone | Banned AI phrases and overused words ("leverage", "proven track record", "I am thrilled to apply") |
| Formatting | No em dashes or markdown - plain text ready to paste into employer forms |
| Honesty | Gaps acknowledged instead of fabricated experience |
Scoring runs across 190 profile-mapping scenarios, 124 answer-quality scenarios, 150 real ATS form E2E scoring fixtures, and Assist sidebar test cases. Developer audit commands: answer-quality:audit, assist-answer-quality:audit, form-e2e:score (see scripts/extension-benchmark/README.md).
Job sites increasingly flag instant paste fills and robotic click patterns. AutoCVApply fills like a person where it matters:
| Behaviour | What it does |
|---|---|
| Character-by-character typing | Short text fields (names, emails, years, locations) type one keystroke at a time with realistic delays |
| Paced batch apply | Draft All fills one field at a time with scroll, focus, and pause between each |
| Human-like job board navigation | Smooth scroll, hover-before-click, and variable delays when opening apply flows and stepping through multi-page forms on LinkedIn, Indeed, Totaljobs, Glassdoor, and Reed |
| Smart fallbacks | Long free-text answers still paste instantly so multi-paragraph drafts do not take minutes |
Autofill works on most major ATS and employer career sites. Auto Apply runs end-to-end from the extension sidebar on supported job boards (search, fill every step, submit). More boards across the UK, Ireland, US, Canada, Australia, and New Zealand are on the way.
Plus many more employer career sites and ATS variants.
Full end-to-end apply: search filtered jobs, open each posting, fill every step, and submit from the extension sidebar Auto Apply tab.
+ more boards across the Anglosphere.
Note: On ATS forms (Greenhouse, Ashby, Workday, etc.) autofill and Draft All fill fields for you - you review and click Submit yourself. Job board Auto Apply completes submissions end-to-end on supported boards above.
Regenerate this block after changing platform lists in resources/js/lib/site.ts:
npm run readme:platformsThis rewrites the block between <!-- readme-platforms:start --> and <!-- readme-platforms:end --> in this file.
| Platform | Coverage | Notes |
|---|---|---|
| Ashby | Curated + smoke + widget checks | Yes/no and checkbox widget scenarios |
| Greenhouse | Curated + smoke | Scraped real boards in corpus |
| Lever | Curated + smoke | Multi-step apply flows |
| Workday | Curated + smoke | Wizard-style applications |
| SmartRecruiters | Curated + smoke | Long multi-section forms |
| Teamtailor | Curated + smoke | Nordic/EU hiring stacks |
| BambooHR | Curated tier | HR suite apply pages |
| Trakstar | Curated tier | Performance/hiring forms |
| WordPress / WPForms | Curated + smoke | Generic employer sites |
| Any site | Extension runs on <all_urls> |
Deepest test coverage on ATS platforms above - not where we stop |
British utilitarian UI - Royal Mail red, navy, warm paper tones. Built to feel like sending a letter, not filling in a spreadsheet.
Plans are based on extension autofill allowance. CV upload and profile editing are free on every plan.
| Plan | Price | Autofills / month |
|---|---|---|
| Free | £0 | 250 |
| Starter | £7/mo | 2,500 |
| Pro | £17/mo | 15,000 |
Each successfully filled form input uses one autofill. Allowances reset on the 1st of each month.
- Your profile, your account - CV and structured profile data live on autocvapply.com under WorkOS authentication. You can delete your account from settings.
- Extension fills locally - the browser extension fetches your profile via a revocable Sanctum token and writes values into the page DOM. It does not send completed submissions back to us.
- No data selling - we do not sell personal data. See the full privacy policy.
- AI processing - CV parsing and drafting send text to our NanoGPT provider as needed to extract fields or generate answers. Job context from the page may be included in draft requests.
- Source available - PolyForm Noncommercial-licensed core. Inspect the extension, backend, and 6,229-scenario test corpus on GitHub. Free for personal and non-commercial use; commercial use requires permission.
- You submit on ATS forms - autofill and Draft All never auto-click Submit on Greenhouse, Ashby, Workday, and similar sites. Job board Auto Apply (LinkedIn, Indeed, Totaljobs, Glassdoor, Reed) completes submissions end-to-end from the sidebar; more boards are on the way.
AutoCVApply is verified by two complementary test pyramids - not a handful of smoke tests:
| Pyramid | Scope | Fast tier (CI on every push) |
|---|---|---|
| ATS form corpus | 6,229 extraction scenarios (3,942 vetted), 124 curated fill-verify cases, 13 platform smoke fixtures | Curated JSDOM (70) + Playwright smoke |
| Job board Auto Apply | 212 LinkedIn captures, 44 Indeed step fixtures, 1,700 synthetic job-board scenarios (Totaljobs, Glassdoor, Reed, SimplyHired, CV-Library), unit + offline corpus tests | Unit tests + offline corpus |
Both pyramids enforce 100% pass rates on critical tiers before merge. Full tier breakdowns, commands, and fixture layout are in Form corpus quality engineering and Job board Auto Apply testing below.
Practical advice for high-volume applications (no magic numbers - your mileage varies):
- Apply while the posting is fresh. Early applicants often face less competition; automation helps you move faster without cutting corners on quality.
- Keep your master CV ATS-simple. Single column, standard headings, no graphics in the parse path. Use AutoCVApply's ATS score against the job description before you submit.
- Tailor the narrative, not just keywords. Draft All and cover letters work best when your profile summary and application preferences reflect what you actually want.
- Review every field. We fill aggressively; you confirm accuracy - especially salary, visa, and eligibility questions.
- Track applications yourself for now. Use your own spreadsheet or notes; a personal application tracker in the dashboard is on the roadmap.
| Website | autocvapply.com |
| Dashboard | autocvapply.com/dashboard |
| How it works | autocvapply.com/how-to |
| Analytics | autocvapply.com/analytics (JSON) |
| Blog | autocvapply.com/blog |
| Privacy | autocvapply.com/privacy |
| Contact | autocvapply.com/contact |
| GitHub | github.com/tmwclaxton/autoapplycv |
| Discord | discord.gg/DqqqTv3Spt - extension help & community |
| Chrome Web Store | chromewebstore.google.com/.../autocvapply |
| Firefox Add-ons | addons.mozilla.org/.../autocvapply |
| Form corpus docs | scripts/form-corpus/README.md |
| Extension bridge (MCP) | scripts/extension-bridge/README.md |
| Job board automation playbook | docs/platform-automation-playbook.md |
flowchart TB
subgraph Browser["Chrome Extension (MV3)"]
CS["Content scripts<br/>form-heuristics · field-inventory"]
SP["Side panel<br/>assist · documents"]
BG["Service worker<br/>connection · file transfer"]
end
subgraph Laravel["Laravel 13 Backend"]
WEB["Inertia + Vue dashboard"]
API["Sanctum API<br/>profile · assist · autofill"]
AI["NanoGPT<br/>CV parse · draft · score"]
DB[(PostgreSQL)]
end
CS <-->|"field inventory<br/>apply answers"| SP
SP -->|"Bearer token"| API
BG -->|"Bearer token"| API
WEB -->|"WorkOS SSO"| WEB
API --> AI
API --> DB
| Component | Role |
|---|---|
extension/src/content/ |
DOM heuristics, field inventory, iframe traversal, portal bar |
extension/src/sidepanel/ |
Connection setup, Draft All UI, document uploads |
app/Services/ApplicationAssistantService.php |
Inventory, job context, streaming draft-all, cover letters |
app/Services/CvParserService.php |
PDF/Word ingestion, OCR, NanoGPT structured extraction |
scripts/form-corpus/ |
Synthetic corpus generation, fill verification pyramid, E2E harness |
| Layer | Technology |
|---|---|
| Backend | Laravel 13, PHP 8.5 |
| Frontend | Inertia v3, Vue 3, Tailwind CSS v4 |
| Auth | WorkOS (web), Laravel Sanctum (extension API) |
| AI | NanoGPT (gpt-4.1-mini) - CV extraction, drafting, ATS scoring |
| OCR | Tesseract + poppler (pdftoppm) locally; NanoGPT vision as fallback |
| Payments | GoCardless (UK Direct Debit subscriptions) |
| Extension | Chrome MV3 - content scripts, side panel, service worker |
| Fill verification | JSDOM, Playwright, pixelmatch, Tesseract.js |
| Routing | Laravel Wayfinder (typed TS route helpers) |
autocvapply/
├── app/
│ ├── Http/Controllers/ # Web + API + billing + webhooks
│ ├── Models/ # User, CvProfile, CvUpload
│ └── Services/ # CV parser, Application Assistant, NanoGPT
├── extension/
│ ├── src/content/ # form-heuristics.js, field-inventory.js
│ ├── src/sidepanel/ # Connection, Draft All, documents
│ └── dist/ # Built extension (load unpacked)
├── scripts/form-corpus/ # Corpus generation + fill verification pyramid
├── scripts/extension-bridge/ # Localhost bridge + MCP server for agent-driven Chrome
├── resources/js/
│ ├── pages/ # Inertia pages (Welcome, Dashboard, Billing…)
│ └── components/postbox/ # Shared Postbox UI components
├── tests/
│ ├── Unit/Extension/ # 11 extension test suites (fill, E2E, extraction)
│ └── fixtures/
│ ├── form-extraction/ # 6,229-scenario corpus (html, expected, manifest)
│ └── extension-e2e/ # E2E mocks, scenarios, reports
└── config/subscriptions.php # Plan tiers and token limits
- PHP 8.5+, Composer
- Node.js 20+, npm
- PostgreSQL (or SQLite for quick local dev)
- Docker Sail optional
git clone https://github.com/tmwclaxton/autoapplycv.git
cd autoapplycv
cp .env.example .env
composer install
npm install
php artisan key:generate
php artisan migrate
npm run buildOr use the one-shot setup:
composer run setupCopy .env.example to .env and configure:
APP_URL=http://localhost
# WorkOS - required for login
WORKOS_CLIENT_ID=
WORKOS_API_KEY=
WORKOS_REDIRECT_URL="${APP_URL}/authenticate"
# NanoGPT - required for CV parsing
NANOGPT_API_KEY=
# GoCardless - optional, for paid subscriptions
GOCARDLESS_ACCESS_TOKEN=
GOCARDLESS_WEBHOOK_SECRET=composer run devStarts the Laravel server, queue worker, log tail, and Vite dev server together.
With Docker Sail:
./vendor/bin/sail up -d
./vendor/bin/sail npm run devVisit http://localhost.
npm run build:extensionThe build uses APP_URL from .env only to exclude your local dashboard from content-script injection. The extension API endpoint comes from the dashboard connection JSON (token + api_base).
Then in Chrome:
- Open
chrome://extensions - Enable Developer mode
- Click Load unpacked
- Select the
extension/dist/folder
Generate a connection from the dashboard (Copy includes token + api_base) and paste it into the extension sidebar.
For extension work, the slow path is edit → rebuild → manually click through Chrome → guess what broke. The extension bridge connects Cursor (or any MCP client) to your real unpacked Chrome profile over localhost so agents can inspect live DOM state, fill fields, advance apply steps, and run Auto Apply without you acting as the robot.
Typical wins:
- See what the extension sees -
get_field_inventory,read_field_values,get_page_htmlon the tab you already have open (cookies, SSO, Reed/Indeed session intact). - Step through apply flows - platform tools like
reed_tab_message,indeed_tab_message,linkedin_tab_messagecall the same content-script handlers the orchestrator uses (REED_APPLY_STATE,REED_FILL_AND_ADVANCE, etc.). - Run Auto Apply from the agent -
start_auto_apply+auto_apply_status/auto_apply_stopfor LinkedIn, Indeed, Totaljobs, Glassdoor, or Reed. - Tight rebuild loop -
npm run extension:build-reloadrebuildsextension/dist/and asks the connected extension to reload without a manual trip tochrome://extensions.
flowchart LR
Cursor["Cursor agent<br/>MCP tools"]
MCP["mcp-server.mjs<br/>stdio"]
HTTP["Bridge HTTP<br/>127.0.0.1:7433"]
WS["Bridge WebSocket<br/>127.0.0.1:7432"]
BG["Extension<br/>service worker"]
CS["Content scripts<br/>on job tabs"]
Cursor --> MCP --> HTTP --> WS --> BG --> CS
| Piece | Role |
|---|---|
extension/src/shared/bridge-client.js |
Outbound WebSocket from the MV3 background worker |
scripts/extension-bridge/server.mjs |
Local WebSocket + HTTP command API (npm run extension-bridge) |
scripts/extension-bridge/mcp-server.mjs |
MCP stdio wrapper over the HTTP API |
The bridge is dev-only: unpacked builds from extension/dist/ auto-connect on localhost. It binds to 127.0.0.1 only.
Terminal 1 - bridge (leave running):
npm run extension-bridgeTerminal 2 - Laravel + Vite as usual (composer run dev).
Chrome - load unpacked from extension/dist/ (see Build the browser extension). Connect the extension to your dashboard token.
Cursor - register the MCP server in .cursor/mcp.json (use your repo path for cwd):
{
"mcpServers": {
"autocvapply-extension": {
"command": "node",
"args": ["scripts/extension-bridge/mcp-server.mjs"],
"cwd": "/path/to/autocvapply"
}
}
}Restart Cursor after editing MCP config. The bridge server must be running before MCP tools work.
Verify:
curl -s http://127.0.0.1:7433/statusExpect "extensionConnected": true when Chrome has the unpacked extension loaded.
# After editing extension/src/** or form-heuristics:
npm run extension:build-reload
# Optional: targeted platform unit test (no browser)
node --test scripts/extension-test/reed-platform.test.mjs
# Optional: drive Auto Apply from Node (uses the same bridge HTTP API)
node scripts/extension-test/auto-apply-marathon.mjs --platform=reed --target=5 --role="software engineer" --location=London --fit=falseIn Cursor, a productive apply-debug session usually looks like:
extension_statusorlist_tabs- find the apply tab.set_active_tab- pin it for follow-up commands.reed_tab_message/indeed_tab_message/ … withREED_APPLY_STATE(or equivalent) - read step fingerprint, validation errors, review/submit flags.get_field_inventory+apply_answerorstart_draft_all- fill the current step.- Platform
_FILL_AND_ADVANCEmessage - click Continue/Submit in the real page. save_fixture- capture redacted HTML into the form corpus when you find a new edge case.
For multi-step Indeed-style flows: fill → click_control (Continue) → wait_for_tab → repeat.
| Tool | Use when |
|---|---|
extension_status |
Check bridge + extension connection and active tab |
get_field_inventory / read_field_values |
See fillable refs and live DOM values |
apply_answer / start_draft_all |
Fill one field or run Draft All on the pinned tab |
click_control / click_ref |
Advance wizards (Continue, Next, etc.) |
navigate_tab / wait_for_tab |
Open jobs or wait for SPA navigation |
linkedin_tab_message, indeed_tab_message, totaljobs_tab_message, glassdoor_tab_message, reed_tab_message |
Call platform content-script handlers directly |
start_auto_apply, auto_apply_status, auto_apply_stop |
Run or monitor job-board Auto Apply |
save_fixture |
Add redacted HTML to tests/fixtures/form-extraction/ |
Full tool list, HTTP examples, env vars, and security notes: scripts/extension-bridge/README.md.
For the full job-board shipping workflow (MCP autofill → synthetic corpus → live marathon → commit), see docs/platform-automation-playbook.md.
This is not a side-project extension with a handful of smoke tests. AutoCVApply ships with one of the most exhaustive form-autofill verification pipelines in the job-application tooling space - built because a single missed combobox on a Greenhouse form is a failed application.
| Metric | Count | Source |
|---|---|---|
| Form extraction scenarios | 6,229 | tests/fixtures/form-extraction/manifest.json |
| Vetted scenarios | 3,942 | same manifest (status: vetted; remainder pending review) |
| HTML fixtures | 6,232 | tests/fixtures/form-extraction/html/ |
| Expected field-inventory snapshots | 6,006 | tests/fixtures/form-extraction/expected/ |
Scraped real apply pages (web-*) |
1,761 | same manifest |
Job board synthetic (syn-tj-500-*, syn-gd-300-*, syn-reed-300-*, syn-sh-300-*, syn-cvl-300-*) |
1,700 | Totaljobs, Glassdoor, Reed, SimplyHired, CV-Library Auto Apply flows |
| Curated fill-verify scenarios | 124 (70 JSDOM · 54 Playwright) | tests/fixtures/form-extraction/fill-verify-curated.json |
| Platform smoke scenarios | 13 (+ 2 Ashby widget checks) | fill-verify-smoke.json · run-ashby-*-playwright.mjs |
| Extension E2E scenarios | 103 (10 in CI) | tests/fixtures/extension-e2e/e2e-scenarios.json |
| Profile-mapping benchmark scenarios | 190 | scripts/extension-benchmark/profile-mapping-corpus.json |
| Answer-quality benchmark scenarios | 124 | scripts/extension-benchmark/answer-quality-corpus.json |
| PHPUnit test methods | 437 | tests/**/*Test.php |
| Platform buckets in curated tier | 20 | scripts/form-corpus/lib/curated-manifest.mjs |
The corpus blends 1,761 scraped real ATS and job-board pages (web-*) with 4,156 synthetic scenarios - including 500 syn-complex-500 ATS-style fixtures, 500 syn-corpus2 bulk forms, 500 syn-tj-500 Totaljobs flows, 300 syn-gd-300 Glassdoor flows, 300 syn-reed-300 Reed flows, 300 syn-sh-300 SimplyHired flows, 300 syn-cvl-300 CV-Library flows, 60 syn-weird edge cases, plus framework mega-forms for React, Vue, Angular, Svelte, Shadow DOM, Workday wizards, conditional fields, and combobox edge cases.
Every change to form-heuristics.js or field-inventory.js must survive the full pyramid before merge:
flowchart TB
subgraph L1["Layer 1 - Fast feedback"]
U["PHPUnit unit tests<br/>437 methods"]
P["Propagation + mock answer tests"]
D["Debug log golden replay"]
end
subgraph L2["Layer 2 - Curated JSDOM"]
J["70 synthetic scenarios<br/>4-layer verification · 100% pass"]
end
subgraph L3["Layer 3 - Real browser"]
S["Platform smoke<br/>13 ATS fixtures + Ashby widgets"]
PW["Curated Playwright<br/>54 scraped ATS pages"]
VR["Visual regression<br/>screenshot baselines"]
end
subgraph L4["Layer 4 - Full extension"]
E2E["Extension E2E<br/>103 scenarios · real MV3 + mocked API"]
end
L1 --> L2 --> L3 --> L4
| Tier | Engine | Scope | CI job |
|---|---|---|---|
| Unit | JSDOM / Node | Propagation, mock answers, debug-log replay | tests.yml → php-tests |
| Corpus sanity | PHPUnit | Corpus size checks (6,229 scenarios, 3,942 vetted) | tests.yml → php-tests |
| Form extraction eval | JSDOM | All 3,942 vetted scenarios vs expected field inventory | tests-heavy.yml (manual) |
| Curated JSDOM | JSDOM | 70 synthetic scenarios, 4-layer checks at 100% | tests.yml → extension-fill |
| Platform smoke | Playwright | 1 scenario per ATS/platform + Ashby yes/no + checkbox | tests.yml → extension-fill |
| Curated Playwright | Playwright | 54 priority scraped ATS fixtures | tests-heavy.yml (manual) |
| Visual regression | Playwright + pixelmatch | Baseline compare on smoke subset | tests-heavy.yml (manual) |
| Extension E2E | Playwright + unpacked MV3 | Full Draft All with mocked assist API (103 total · 10 in CI) | extension-fill (CI subset) · tests-heavy.yml (full batch) |
Each curated JSDOM scenario passes four independent checks - not just "did we set a value?":
| Layer | What it proves |
|---|---|
| DOM readback | Re-reads filled values from the DOM after applyAnswerByRefAllFrames |
| HTML5 validity | element.checkValidity() / form.checkValidity() on native controls |
| Accessibility state | aria-checked, aria-selected, aria-pressed, combobox collapsed state |
| Error banners | Ashby/Greenhouse-style validation messages, [role="alert"], [aria-invalid="true"] |
Additional tiers add OCR readback (Playwright + Tesseract on Ashby fixtures), pixel diff (before/after screenshot % change), and debug log golden replay (extension phase summaries).
| Tier | Critical | Overall |
|---|---|---|
| JSDOM curated | 100% | 100% |
| Playwright priority | 100% | 100% |
| Platform smoke | 100% | 100% |
| Extension E2E | 100% | 100% |
Fast feedback runs on every push to main and develop; the heavy corpus eval tier is manual-only:
| Workflow | Trigger | What runs |
|---|---|---|
tests.yml → php-tests |
Push / PR | Laravel suite on PostgreSQL 17 - excludes @group playwright and @group extension-e2e; corpus sanity checks only (not full extraction eval) |
tests.yml → extension-fill |
Push / PR | npm run build:extension → curated JSDOM verify (70 scenarios) → Playwright platform smoke |
tests-heavy.yml |
Manual dispatch | Full 3,942-scenario extraction eval · comprehensive fill verify · curated Playwright · visual regression · extension E2E batch (~103 scenarios) |
lint.yml |
Push / PR | Laravel Pint, ESLint, Prettier |
prod_deploy.yml |
Push to main |
Docker build → GHCR push → deploy to production |
After changing form heuristics locally, run the smoke tier before opening a PR:
npm run form-corpus:fill-verify:smoke
# or
FORM_CORPUS_PLAYWRIGHT=1 php artisan test --compact --filter=test_platform_smoke_playwright_passesSee scripts/form-corpus/README.md for the full maintenance workflow, report paths, and the manual heavy tier.
LinkedIn, Indeed, Totaljobs, Glassdoor, and Reed Auto Apply have their own verification pyramid, separate from the 6,229-scenario ATS form corpus. It runs on every push via PHPUnit wrappers and can be exercised locally without a live session for most tiers.
flowchart TB
subgraph L1["Layer 1 - Unit (JSDOM / Node)"]
U1["linkedin-auto-apply.mjs<br/>search URLs, job cards, modal state"]
U2["auto-apply-pause-resume.mjs<br/>blockers + session pause/resume"]
U3["auto-apply-activity-visibility.mjs<br/>sidebar activity UI rules"]
U4["linkedin-full-flow-report.mjs<br/>E2E report parsing"]
end
subgraph L2["Layer 2 - Offline corpus (JSDOM)"]
C["linkedin-easy-apply-corpus.mjs<br/>212 live captures + 52 synthetic edge cases"]
I["indeed-auto-apply-offline.mjs<br/>44 apply-step fixtures"]
TJ["syn-tj-500-* · syn-gd-300-* · syn-reed-300-* · syn-sh-300-* · syn-cvl-300-*<br/>1,700 job board synthetic scenarios"]
end
subgraph L3["Layer 3 - Browser offline"]
O["linkedin-auto-apply-offline-step.mjs<br/>Playwright + fixture HTML"]
end
subgraph L4["Layer 4 - Live E2E (manual)"]
E["linkedin-auto-apply-full-flow.mjs<br/>real LinkedIn session + MV3 extension"]
end
L1 --> L2 --> L3 --> L4
| Tier | Script / test | What it validates | CI |
|---|---|---|---|
| Unit | scripts/extension-test/linkedin-auto-apply.mjs |
Search URL building, job card parsing, Easy Apply button state, modal detection, step fingerprints, cookie/save-dialog dismiss | LinkedInAutoApplyTest |
| Unit | auto-apply-pause-resume.mjs, auto-apply-activity-visibility.mjs |
Validation blockers, pause/resume session state, sidebar activity panel visibility | Default PHPUnit |
| Offline corpus | linkedin-easy-apply-corpus.mjs |
Each fixture: modal open/closed, primary actions (Next/Review/Submit), validation errors, submitted confirmation, multi-step flow progression | LinkedInEasyApplyCorpusTest |
| Offline corpus | indeed-auto-apply-offline.mjs, indeed-apply-*.mjs |
Indeed Apply search URLs, step detection, contact/resume/questions/review fixtures | npm run test:indeed-auto-apply-offline |
| Offline corpus | syn-tj-500-*, syn-gd-300-*, syn-reed-300-*, syn-sh-300-*, syn-cvl-300-* |
1,700 synthetic Totaljobs, Glassdoor, Reed, SimplyHired, and CV-Library search/apply scenarios in the form corpus | npm run form-corpus:validate-*-corpus |
| Unit | reed-platform.test.mjs |
Reed search URLs, job ID parsing, apply URL builders | node --test scripts/extension-test/reed-platform.test.mjs |
| Browser offline | linkedin-auto-apply-offline-step.mjs |
Real Chrome + unpacked extension clicking through a fixture modal step | LinkedInFullFlowReportTest (requires EXTENSION_E2E=1) |
| Live E2E | linkedin-auto-apply-full-flow.mjs |
Full orchestrator: search, open jobs, Draft All per step, advance, submit; writes tests/output/linkedin-auto-apply-full-flow/report.json |
Manual (LINKEDIN_LIVE_E2E=1) |
| Location | Contents |
|---|---|
tests/fixtures/auto-apply/linkedin/captured/ |
212 live-captured LinkedIn HTML files (authoritative regression source) |
tests/fixtures/form-extraction/html/web-indeed-* |
44 Indeed Apply step fixtures (contact, resume, questions, review) |
tests/fixtures/form-extraction/html/syn-tj-500-* |
500 Totaljobs Quick Apply synthetic scenarios |
tests/fixtures/form-extraction/html/syn-gd-300-* |
300 Glassdoor Easy Apply host + iframe scenarios |
tests/fixtures/form-extraction/html/syn-reed-300-* |
300 Reed Easy Apply search, job detail, and modal review scenarios |
tests/fixtures/auto-apply/linkedin/captured-manifest.json |
Metadata per capture: step, capture_reason, validation errors, stuck diagnostics |
tests/fixtures/auto-apply/linkedin/ |
50 synthetic progression flows plus error-* and edge-* fixtures (fallback when captures are unavailable) |
tests/fixtures/auto-apply/linkedin-search-results*.html |
Search results and job detail page fixtures for card parsing |
See tests/fixtures/auto-apply/linkedin/README.md for capture flags, stuck-step suffixes, and fixture layout.
Run the fast tiers before changing linkedin-auto-apply.js, linkedin-easy-apply-fields.js, or the auto-apply orchestrator:
# Unit tests (search, cards, modal state)
node scripts/extension-test/linkedin-auto-apply.mjs
# Pause/resume blockers and sidebar activity UI
npm run test:auto-apply-pause-resume
npm run test:auto-apply-activity-visibility
# Offline corpus (CI default - prefers live captures)
npm run test:linkedin-easy-apply-corpus:run -- --captured-only
# Full corpus including synthetic fallback
npm run test:linkedin-easy-apply-corpus
# PHPUnit wrappers (runs Node scripts above)
php artisan test --compact tests/Unit/Extension/LinkedInAutoApplyTest.php
php artisan test --compact tests/Unit/Extension/LinkedInEasyApplyCorpusTest.phpBrowser offline step (build extension first):
npm run build:extension
npm run test:linkedin-full-flow:offline-step
# or via PHPUnit:
EXTENSION_E2E=1 php artisan test --compact --filter=test_linkedin_auto_apply_offline_stepLive full-flow E2E (requires LinkedIn test account in .env):
npm run build:extension
LINKEDIN_LIVE_E2E=1 npm run test:linkedin-full-flow -- --max-jobs=3 --roles="software engineer"
# or via PHPUnit:
LINKEDIN_LIVE_E2E=1 php artisan test --compact tests/Feature/Extension/LinkedInAutoApplyFullFlowTest.phpRun before changing indeed-auto-apply.js, indeed-platform.js, or the auto-apply orchestrator:
# Offline orchestrator + step fixtures (contact, resume, questions, review)
npm run test:indeed-auto-apply-offline
npm run test:indeed-apply-contact
npm run test:indeed-apply-questions
npm run test:indeed-apply-job-006
npm run test:indeed-apply-job-007
npm run test:indeed-apply-job-008Indeed step fixtures live under tests/fixtures/form-extraction/html/web-indeed-* and in the form-extraction manifest.
Use a headed Playwright session to grow the offline corpus from real LinkedIn Easy Apply modals:
# Credentials in .env only (never commit):
# LINKEDIN_TEST_EMAIL=...
# LINKEDIN_TEST_PASSWORD=...
npm run extension-e2e:capture-linkedin-corpus -- --target-fixtures=50 --max-jobs=20 --roles="software engineer,frontend developer"The capture script logs in, rotates search roles, opens Easy Apply jobs, saves step states (open, filled, validation errors, multi-step review, stuck flows), redacts PII before write, and updates captured-manifest.json.
After capture or sanitizer rule changes, re-process fixtures before committing:
node scripts/extension-e2e/resanitize-linkedin-captured.mjs
node scripts/extension-e2e/rebuild-captured-manifest.mjs
npm run test:linkedin-easy-apply-corpus:run -- --captured-onlyCaptured HTML is sanitized on save and can be re-sanitized in bulk:
- Strips
<script>tags - Replaces emails with
candidate@example.com, phone with+44 7700 900123, names withAlex Candidate - Scrubs
.envcredentials (LINKEDIN_TEST_EMAIL,LINKEDIN_TEST_PASSWORD) and known API key patterns viaredactSecrets
Before committing new fixtures:
npm run secrets:check-fixturesDo not paste unredacted live LinkedIn HTML into tests/fixtures/auto-apply/linkedin/captured/.
| Command | Purpose |
|---|---|
composer run dev |
Laravel + queue + Pail + Vite |
composer test |
Pint check + full PHPUnit suite |
npm run build:extension |
Build MV3 extension to extension/dist/ |
npm run extension:build-reload |
Build + hot-reload unpacked extension via bridge |
npm run extension-bridge |
Start localhost bridge (WebSocket + HTTP for MCP) |
npm run form-corpus:fill-verify:curated |
Curated JSDOM tier (CI default) |
npm run form-corpus:fill-verify:smoke |
Per-platform Playwright smoke |
npm run platform-logos:download |
Refresh platform logos in public/images/platforms/logos/ |
npm run readme:platforms |
Regenerate Supported platforms block in README from site.ts |
npm run form-corpus:visual-regression |
Screenshot baseline compare |
npm run form-corpus:build-curated |
Regenerate curated + smoke manifests |
npm run test:linkedin-easy-apply-corpus:run -- --captured-only |
LinkedIn offline corpus (212 live captures) |
npm run test:linkedin-full-flow |
LinkedIn live E2E (requires LINKEDIN_LIVE_E2E=1 + credentials) |
npm run extension-e2e:capture-linkedin-corpus |
Capture live LinkedIn Easy Apply HTML for offline tests |
npm run lint:check |
ESLint |
composer lint:check |
Laravel Pint |
# Default CI (excludes playwright + extension-e2e groups)
php artisan test --compact --exclude-group=extension-e2e,playwright
# Playwright smoke (fast CI tier)
FORM_CORPUS_PLAYWRIGHT=1 php artisan test --compact --group=playwright --exclude-group=extension-e2e
# Extension E2E CI subset
EXTENSION_E2E=1 php artisan test --compact --group=extension-e2e
# Full ~103 scenario extension E2E (manual via tests-heavy.yml, 30-60+ min)
EXTENSION_E2E=1 EXTENSION_E2E_FULL=1 php artisan test --compact --group=extension-e2eThe extension authenticates with Laravel Sanctum bearer tokens.
| Method | Endpoint | Description |
|---|---|---|
GET |
/analytics.json |
Public aggregate usage totals (no auth; for README badges) |
GET |
/api/profile |
Fetch user profile + subscription usage |
POST |
/extension/connection |
Generate extension connection JSON (dashboard session) |
POST |
/api/applications/assist/inventory |
Field inventory for current page |
POST |
/api/applications/assist/job-context |
Extract job title, company, description |
POST |
/api/applications/assist/draft-all |
Stream batch field answers (NDJSON) |
POST |
/api/applications/assist/draft-field |
Single field draft |
POST |
/api/applications/assist/cover-letter |
Generate cover letter |
POST |
/api/applications/assist/tailored-resume |
Tailored resume draft |
DELETE |
/api/tokens/{token} |
Revoke a token |
Production runs in Docker (DockerfileProd) with Nginx, PHP-FPM, a queue worker, and schedule:work (via supervisord). Pushes to main build a GHCR image and deploy via GitHub Actions. The Laravel scheduler must keep running for weekly blog:generate (Mondays 09:00 app timezone / UTC) and the worker heartbeat job.
CV extraction and Draft All use deepseek/deepseek-v4-flash:throughput by default (config/cv.php). To override the model in production, set NANOGPT_CV_MODEL in the server .env at /opt/autocvapply/.env; remove that key to use the config default after deploy.
Live site: autocvapply.com
Issues and pull requests welcome on GitHub.
- Fork the repo
- Create a feature branch
- Write tests for your changes
- If you touched
form-heuristics.jsorfield-inventory.js, runnpm run form-corpus:fill-verify:smoke - Run
composer testandnpm run lint:check - Open a PR
PolyForm Noncommercial License 1.0.0. Free for personal and non-commercial use - fork it, study it, run it locally. Commercial use (selling, SaaS, paid services built on this code, etc.) requires permission; contact us via autocvapply.com/contact.
Get started free at autocvapply.com
Built for people who'd rather apply to jobs than retype their CV.
Verified against 6,229 form scenarios. Battle-tested on real ATS platforms and UK job boards.