INTEGRATION-CONTRACT.md

8.1 KB · raw

rendered · source

1# AUTOFILL-TESTS — Integration Contract23Draft test coverage for the autofill layer (`src/content/index.ts`,4`inject.ts`, `detect.ts`, `fill.ts`, `save.ts`, `interaction.ts`, `policy.ts`).5Authored against **v3.9.9**, dev lane. All draft files live in6`src/drafts/AUTOFILL-TESTS/` and were verified green with a throwaway runner7config — **no source file was modified**.89## Status at a glance1011| Metric | Count |12| --- | --- |13| Runnable unit/integration tests (vitest) | 118 |14| `expect()` assertions | 212 |15| `it.todo` (routed to e2e / proposed features) | 23 |16| `describe.skipIf` blocks (proposed-feature specs) | 2 (6 tests) |17| Playwright e2e tests (draft) | 10 |18| Result | **13 files, 114 passed / 6 skipped / 23 todo** |1920## Files2122### Shared (do not delete)23| File | Role | Destination |24| --- | --- | --- |25| `_setup.ts` | chrome mock (side-effect on import) + DOM factories + jsdom `getBoundingClientRect`/`ResizeObserver` shims | `src/content/__support__/` or inline into each test |26| `_smoke.test.ts` | proves `index.ts`/`inject.ts` import under the harness | keep as a guard |27| `vitest.drafts.config.ts` *(repo root)* | throwaway runner (includes drafts, drops the `src/drafts/**` exclude) | **delete after integration** |2829### vitest (unit/integration) → keep in `src/content/` (or wherever the suite lives)30| File | Brief mapping | Notes |31| --- | --- | --- |32| `content.detect.test.ts` | Test File 7, T-DT01–T-DT21 + Addendum 1 | classifiers tested on hand-built arrays (see "querySelectorAllDeep" below) |33| `content.fill.test.ts` | Test File 8, T-FL01–T-FL12 | real DOM; React prototype-setter bypass |34| `content.interaction.test.ts` | Test File 12, T-IT01–T-IT06 | pure function |35| `content.policy.test.ts` | Test File 11, T-PO01–T-PO06 | real scraping; see deviations |36| `content.dismissal.test.ts` | Test File 1, T-D01–T-D15 | 3-layer model via seams + fake timers |37| `content.field-interaction.test.ts` | Test File 2, T-F01–T-F12 | mocks `./inject` + `./content-ml-detector` |38| `content.initial-scan.test.ts` | Test File 3, T-S01–T-S10 | mocks `./inject` (icon spy) |39| `content.message-routing.test.ts` | Test File 4, T-M01–T-M10 | drives captured `onMessage` listeners |40| `content.field-icon.test.ts` | Test File 6, T-IC01–T-IC08 | real light-DOM button |41| `content.selector-ui.test.ts` | Test File 5, T-I01–T-I16 | **closed shadow** — mount/dismiss only |42| `content.totp-toast.addendum.test.ts` | Addendum 2, T-M11–T-M13 | `skipIf` until `showTotpCopyToast` exists |4344### Playwright (e2e) → move to `tests/`45| File | Brief mapping | Destination |46| --- | --- | --- |47| `content.real-page.e2e.spec.ts` | Test File 10 / T-028, T-E01–T-E10 | `tests/content.real-page.e2e.spec.ts` |48| `login-form.fixture.html` | required fixture | `tests/fixtures/login-form.html` (update the `FIXTURE` path + `page.route`/static server) |4950## Environment / config changes needed5152- **No `environmentMatchGlobs` change required.** Every file uses the per-file53  `// @vitest-environment jsdom` pragma (the convention already used by54  `detect.test.ts`, `fill.test.ts`, etc.). The stale glob comment in the55  committed `content.orchestration.test.ts` does not apply.56- To run these in the real suite, either (a) move the `*.test.ts` files into57  `src/content/`, or (b) drop/relax the `exclude: ["src/drafts/**"]` line in58  `vitest.config.ts`. Until then they only run via `vitest.drafts.config.ts`.59- **chrome mock:** there is **no** `__mocks__/chrome.ts` in the repo (the brief60  assumed one). `_setup.ts` provides the mock the content scripts need:61  `runtime.sendMessage` (promise + callback forms), `runtime.onMessage`62  (capturable), `runtime.id`, `runtime.getURL`, `storage.session/local`, plus63  `navigator.clipboard` and a `ResizeObserver` polyfill. Fold this into the64  project's shared test support during integration.6566## `_testSeam` usage6768Tests that need module-private state use the existing `_testSeam` export69(`getLastDismissedField`/`setLastDismissedField`/`isDismissLocked`/70`DISMISS_LOCK_MS`):71- `content.dismissal.test.ts` — Layers 1 & 2.72The other index.ts files drive behavior through the public seams73(`handleFieldInteraction`, `initialScan`, `wasAlreadyFilled`) and the captured74`onDismiss`/`onFillFields` callbacks the selector is opened with — **no new75exports required.**7677## Behavioral findings the tests pin (read before "fixing" a failure)78791. **`querySelectorAllDeep` ancestor duplication.** It re-queries every ancestor80   element, so a field under `html>body>form` appears 3× in81   `detectLoginFields()`. That inflates the password count, so82   `isLoginForm(detectLoginFields())` is `false` for a normal single-password83   login — the page is recognised as fillable via the `isSignupForm` branch of84   `isAnyCredentialForm` instead. Detect tests therefore assert detection by85   *membership* and the page gate via `isAnyCredentialForm`, and test the86   login-vs-signup classifier on clean, hand-built arrays (matching the committed87   `detect.test.ts`). It also misses shadow-DOM inputs that are *direct* children88   of a shadow root (only those nested under an element are collected).892. **Closed shadow roots.** `showAutofillSelector`, `showTOTPSelector`,90   `showCardSelector`, and `save.ts`'s banner all mount `attachShadow({ mode:91   "closed" })`. Their interiors (entry rows, search box, keyboard nav, TOTP92   chip, Save/Update buttons, homograph warning) are unreachable from any93   external script. Unit tests assert only mount/dismiss + outbound messages;94   the interiors are covered by the e2e through effects. **Recommendation:** add95   a test-only "open shadow in `import.meta.env.MODE==='test'`" seam if richer96   unit coverage of the selector interior is wanted.973. **`wasAlreadyFilled` page-grace is NOT form-scoped** (brief T-D10 assumed it98   was). For `PAGE_FILL_GRACE_MS` (3 s) it suppresses *every* field; after the99   window, only the filled form stays locked (`filledForms` is permanent). Tests100   pin the actual behavior.1014. **`fillFields` returns `void`** (brief T-FL12 assumed it returns the filled102   set). Fill-success tracking is the caller's job (`recordFillSuccess`).1035. **`detectPasswordPolicy` ignores a `minlength` attribute** (brief T-PO01); it104   reads `maxLength`, the `pattern`, and nearby text. It inspects only the first105   password field, so "most restrictive across fields" (T-PO06) is not106   implemented.1076. **No `DISMISS_AUTOFILL` / `FILL_AND_SUBMIT` messages** in index.ts's listener108   (brief T-M06/T-M07). The search-palette path is `SUMMON_AUTOFILL` with no109   form (T-M08 ≡ T-M02 here).1107. **PCH-01-025 CSS.escape:** jsdom's selector engine *throws* on `CSS.escape`'s111   output, so the label-resolution benefit is only observable in the e2e. The112   unit tests pin the resilience invariant (a hostile id never aborts detection;113   the embedded-quote case fails if the try/catch is removed).1148. **Benign teardown log.** Importing the real `index.ts` registers a url-watcher115   `MutationObserver`; its callback can log `location is not defined` once during116   jsdom teardown. Exit code is 0 and no test fails. Tests flush it per-test117   where practical.118119## Refactoring still needed beyond Phase 1 (to satisfy the brief fully)120121| Want | Needs |122| --- | --- |123| 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. |124| 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. |125| 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). |126| T-IC07 "suppress icon on `autocomplete=current-password`" | no such policy exists in `injectFieldIcon`; add it to the detect/index gate if desired. |127128## How to run (during integration)129130```sh131# drafts only, via the throwaway config (delete the config after moving files):132npx vitest run --config vitest.drafts.config.ts133134# after moving *.test.ts into src/content/ (or relaxing the exclude):135npx vitest run136```