INTEGRATION-CONTRACT.md

8.1 KB · raw

rendered · source

AUTOFILL-TESTS — Integration Contract

Draft test coverage for the autofill layer (src/content/index.ts, inject.ts, detect.ts, fill.ts, save.ts, interaction.ts, policy.ts). Authored against v3.9.9, dev lane. All draft files live in src/drafts/AUTOFILL-TESTS/ and were verified green with a throwaway runner config — no source file was modified.

Status at a glance

Metric Count
Runnable unit/integration tests (vitest) 118
expect() assertions 212
it.todo (routed to e2e / proposed features) 23
describe.skipIf blocks (proposed-feature specs) 2 (6 tests)
Playwright e2e tests (draft) 10
Result 13 files, 114 passed / 6 skipped / 23 todo

Files

Shared (do not delete)

File Role Destination
_setup.ts chrome mock (side-effect on import) + DOM factories + jsdom getBoundingClientRect/ResizeObserver shims src/content/__support__/ or inline into each test
_smoke.test.ts proves index.ts/inject.ts import under the harness keep as a guard
vitest.drafts.config.ts (repo root) throwaway runner (includes drafts, drops the src/drafts/** exclude) delete after integration

vitest (unit/integration) → keep in src/content/ (or wherever the suite lives)

File Brief mapping Notes
content.detect.test.ts Test File 7, T-DT01–T-DT21 + Addendum 1 classifiers tested on hand-built arrays (see "querySelectorAllDeep" below)
content.fill.test.ts Test File 8, T-FL01–T-FL12 real DOM; React prototype-setter bypass
content.interaction.test.ts Test File 12, T-IT01–T-IT06 pure function
content.policy.test.ts Test File 11, T-PO01–T-PO06 real scraping; see deviations
content.dismissal.test.ts Test File 1, T-D01–T-D15 3-layer model via seams + fake timers
content.field-interaction.test.ts Test File 2, T-F01–T-F12 mocks ./inject + ./content-ml-detector
content.initial-scan.test.ts Test File 3, T-S01–T-S10 mocks ./inject (icon spy)
content.message-routing.test.ts Test File 4, T-M01–T-M10 drives captured onMessage listeners
content.field-icon.test.ts Test File 6, T-IC01–T-IC08 real light-DOM button
content.selector-ui.test.ts Test File 5, T-I01–T-I16 closed shadow — mount/dismiss only
content.totp-toast.addendum.test.ts Addendum 2, T-M11–T-M13 skipIf until showTotpCopyToast exists

Playwright (e2e) → move to tests/

File Brief mapping Destination
content.real-page.e2e.spec.ts Test File 10 / T-028, T-E01–T-E10 tests/content.real-page.e2e.spec.ts
login-form.fixture.html required fixture tests/fixtures/login-form.html (update the FIXTURE path + page.route/static server)

Environment / config changes needed

_testSeam usage

Tests that need module-private state use the existing _testSeam export (getLastDismissedField/setLastDismissedField/isDismissLocked/ DISMISS_LOCK_MS):

Behavioral findings the tests pin (read before "fixing" a failure)

  1. querySelectorAllDeep ancestor duplication. It re-queries every ancestor element, so a field under html>body>form appears 3× in detectLoginFields(). That inflates the password count, so isLoginForm(detectLoginFields()) is false for a normal single-password login — the page is recognised as fillable via the isSignupForm branch of isAnyCredentialForm instead. Detect tests therefore assert detection by membership and the page gate via isAnyCredentialForm, and test the login-vs-signup classifier on clean, hand-built arrays (matching the committed detect.test.ts). It also misses shadow-DOM inputs that are direct children of a shadow root (only those nested under an element are collected).
  2. Closed shadow roots. showAutofillSelector, showTOTPSelector, showCardSelector, and save.ts's banner all mount attachShadow({ mode: "closed" }). Their interiors (entry rows, search box, keyboard nav, TOTP chip, Save/Update buttons, homograph warning) are unreachable from any external script. Unit tests assert only mount/dismiss + outbound messages; the interiors are covered by the e2e through effects. Recommendation: add a test-only "open shadow in import.meta.env.MODE==='test'" seam if richer unit coverage of the selector interior is wanted.
  3. wasAlreadyFilled page-grace is NOT form-scoped (brief T-D10 assumed it was). For PAGE_FILL_GRACE_MS (3 s) it suppresses every field; after the window, only the filled form stays locked (filledForms is permanent). Tests pin the actual behavior.
  4. fillFields returns void (brief T-FL12 assumed it returns the filled set). Fill-success tracking is the caller's job (recordFillSuccess).
  5. detectPasswordPolicy ignores a minlength attribute (brief T-PO01); it reads maxLength, the pattern, and nearby text. It inspects only the first password field, so "most restrictive across fields" (T-PO06) is not implemented.
  6. No DISMISS_AUTOFILL / FILL_AND_SUBMIT messages in index.ts's listener (brief T-M06/T-M07). The search-palette path is SUMMON_AUTOFILL with no form (T-M08 ≡ T-M02 here).
  7. PCH-01-025 CSS.escape: jsdom's selector engine throws on CSS.escape's output, so the label-resolution benefit is only observable in the e2e. The unit tests pin the resilience invariant (a hostile id never aborts detection; the embedded-quote case fails if the try/catch is removed).
  8. Benign teardown log. Importing the real index.ts registers a url-watcher MutationObserver; its callback can log location is not defined once during jsdom teardown. Exit code is 0 and no test fails. Tests flush it per-test where practical.

Refactoring still needed beyond Phase 1 (to satisfy the brief fully)

Want Needs
Addendum 1 split-OTP isSplitOtpGroup (T-DT18–21) export isSplitOtpGroup(field) from detect.ts (3+ adjacent numeric single-char inputs; numeric guard). The skipIf spec activates automatically. Existing findOTPDigitBoxGroup (4–10 boxes) is already covered.
Addendum 2 showTotpCopyToast (T-M11–13) export showTotpCopyToast(secondsUntilClear) from inject.ts and call it after the auto-copy clipboard write. skipIf spec activates automatically.
Selector interior unit coverage (T-I02–16, T-SV06–09, T-D14/15) a test-only open-shadow seam, or rely on the e2e (current plan).
T-IC07 "suppress icon on autocomplete=current-password" no such policy exists in injectFieldIcon; add it to the detect/index gate if desired.

How to run (during integration)

# drafts only, via the throwaway config (delete the config after moving files):
npx vitest run --config vitest.drafts.config.ts

# after moving *.test.ts into src/content/ (or relaxing the exclude):
npx vitest run