{"_id":"@a11y-pulse/context-change-on-focus-audit","_rev":"4-12e67d63a57c7118ee72f4e245b176ed","name":"@a11y-pulse/context-change-on-focus-audit","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@a11y-pulse/context-change-on-focus-audit","version":"0.1.0","keywords":["accessibility","a11y","wcag","context-change-on-focus","on-focus","puppeteer","audit"],"author":{"name":"A11y Pulse Limited"},"license":"SEE LICENSE IN LICENSE.md","_id":"@a11y-pulse/context-change-on-focus-audit@0.1.0","maintainers":[{"name":"wildlyinaccurate","email":"joseph@wildlyinaccurate.com"}],"homepage":"https://github.com/A11y-Pulse/audits/tree/main/packages/context-change-on-focus-audit#readme","bugs":{"url":"https://github.com/A11y-Pulse/audits/issues"},"dist":{"shasum":"3cc491501ea5eca53ce49c7daf52abe41edbc25d","tarball":"https://registry.npmjs.org/@a11y-pulse/context-change-on-focus-audit/-/context-change-on-focus-audit-0.1.0.tgz","fileCount":9,"integrity":"sha512-a07dIzSa6Q5FN9DSbdXxNhI74+3v2/s6L5CMEo3SJ1xwh7ndQ6X2QOPW/d92yMrEGRr9RYVSbmnd2tCvurIMAA==","signatures":[{"sig":"MEYCIQDO1Fuz+b5r0ECMFy1OvqL06KAQwVL0ZzT7Dezye7+58wIhANfEHXDiP/2tmuvAKOXuKLJPUSPFrEytfDdzTVQtp+N3","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":35708},"type":"module","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"16989f9d06b39c893b2ee2607651b8cdafc20d5b","scripts":{"lint":"biome check","test":"npm run typecheck && npm run test:unit","build":"tsup && tsc -p tsconfig.build.json","lint:fix":"biome check --fix","test:unit":"vitest run","typecheck":"tsc --noEmit","prepublishOnly":"npm run build","test:integration":"vitest run --config vitest.integration.config.ts"},"_npmUser":{"name":"wildlyinaccurate","email":"joseph@wildlyinaccurate.com"},"repository":{"url":"git+https://github.com/A11y-Pulse/audits.git","type":"git","directory":"packages/context-change-on-focus-audit"},"_npmVersion":"11.11.0","description":"WCAG 3.2.1 On Focus audit that tabs through a page and detects focus-triggered context changes (new windows, auto-submits, navigation, and focus theft/removal).","directories":{},"_nodeVersion":"24.13.0","dependencies":{"@a11y-pulse/tab-orchestrator":"*"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"puppeteer":"^25.7.0"},"_npmOperationalInternal":{"tmp":"tmp/context-change-on-focus-audit_0.1.0_1787202060499_0.9884523380840999","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@a11y-pulse/context-change-on-focus-audit","version":"0.2.0","keywords":["accessibility","a11y","wcag","context-change-on-focus","on-focus","puppeteer","audit"],"author":{"name":"A11y Pulse Limited"},"license":"SEE LICENSE IN LICENSE.md","_id":"@a11y-pulse/context-change-on-focus-audit@0.2.0","maintainers":[{"name":"wildlyinaccurate","email":"joseph@wildlyinaccurate.com"}],"homepage":"https://github.com/A11y-Pulse/audits/tree/main/packages/context-change-on-focus-audit#readme","bugs":{"url":"https://github.com/A11y-Pulse/audits/issues"},"dist":{"shasum":"1c96608e6472e607bdd5351ec4d4b1210da2bbdc","tarball":"https://registry.npmjs.org/@a11y-pulse/context-change-on-focus-audit/-/context-change-on-focus-audit-0.2.0.tgz","fileCount":9,"integrity":"sha512-oZsW4Rrm/kyBcTWXcapSXtMSqpniAkv9kE3rFzpgXzsoEIoQR05uPBLawVf/Xio1cXHlHbTQG2C5gRWK2fUvQg==","signatures":[{"sig":"MEUCIDN9dwHpE9nILN5ymr/xBBZSMtyqBaOptRq8KXQNNMzaAiEA33192K2rsy6ObBQzefkb2KQV4vBNxdwPk0Ba7jckNww=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@a11y-pulse%2fcontext-change-on-focus-audit@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":35708},"type":"module","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"f05e0097f3dc9b26151bb00db20bf4ba98707bda","scripts":{"lint":"biome check","test":"npm run typecheck && npm run test:unit","build":"tsup && tsc -p tsconfig.build.json","lint:fix":"biome check --fix","test:unit":"vitest run","typecheck":"tsc --noEmit","prepublishOnly":"npm run build","test:integration":"vitest run --config vitest.integration.config.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3100d683-c84a-4643-b002-2448417a343a"}},"repository":{"url":"git+https://github.com/A11y-Pulse/audits.git","type":"git","directory":"packages/context-change-on-focus-audit"},"_npmVersion":"12.0.2","description":"WCAG 3.2.1 On Focus audit that tabs through a page and detects focus-triggered context changes (new windows, auto-submits, navigation, and focus theft/removal).","directories":{},"_nodeVersion":"22.23.2","dependencies":{"@a11y-pulse/tab-orchestrator":"*"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"puppeteer":"^25.7.0"},"_npmOperationalInternal":{"tmp":"tmp/context-change-on-focus-audit_0.2.0_1787212646516_0.9088486962947864","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@a11y-pulse/context-change-on-focus-audit","version":"0.2.1","keywords":["accessibility","a11y","wcag","context-change-on-focus","on-focus","puppeteer","audit"],"author":{"name":"A11y Pulse"},"license":"SEE LICENSE IN LICENSE.md","_id":"@a11y-pulse/context-change-on-focus-audit@0.2.1","maintainers":[{"name":"wildlyinaccurate","email":"joseph@wildlyinaccurate.com"}],"homepage":"https://github.com/A11y-Pulse/audits/tree/main/packages/context-change-on-focus-audit#readme","bugs":{"url":"https://github.com/A11y-Pulse/audits/issues"},"dist":{"shasum":"4900eb2606004b397b57da0a7a743400ef9fa97d","tarball":"https://registry.npmjs.org/@a11y-pulse/context-change-on-focus-audit/-/context-change-on-focus-audit-0.2.1.tgz","fileCount":9,"integrity":"sha512-iMDQXX84X9i4hKgzvtwzpTlNYiJvgUimlGvFUI21zbbN6oDobqdQ+SvEhFWBfo/BJFvYfOZWj15O2pl4YvicEg==","signatures":[{"sig":"MEYCIQCgPfDGaCh2UXEbO1hjGmoAKCkzfG2kyHpZZvfBAtIMegIhANO1yTM3M3yUkE2FocRxDeM6jHzNNmxCbnekXynM4ouP","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEQCIBNcnaTNThfFGcyCVf5VCShZOy6jQZVR0vh0XV5aKOxCAiAPR+fIueHmyvAeEcL0c4Qq03e64FANKjdHIG4ZkLp+6g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@a11y-pulse%2fcontext-change-on-focus-audit@0.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":35630},"type":"module","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","source":"./src/index.ts"}},"gitHead":"8431177b6098e09064306dc08bddfb59796b444d","scripts":{"lint":"biome check","test":"npm run typecheck && npm run test:unit","build":"tsup && tsc -p tsconfig.build.json","lint:fix":"biome check --fix","test:unit":"vitest run","typecheck":"tsc --noEmit","prepublishOnly":"npm run build","test:integration":"vitest run --config vitest.integration.config.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3100d683-c84a-4643-b002-2448417a343a"}},"repository":{"url":"git+https://github.com/A11y-Pulse/audits.git","type":"git","directory":"packages/context-change-on-focus-audit"},"_npmVersion":"12.0.2","description":"WCAG 3.2.1 On Focus audit that tabs through a page and detects focus-triggered context changes (new windows, auto-submits, navigation, and focus theft/removal).","directories":{},"_nodeVersion":"22.23.2","dependencies":{"@a11y-pulse/tab-orchestrator":"*"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"puppeteer":"^25.9.0"},"_npmOperationalInternal":{"tmp":"tmp/context-change-on-focus-audit_0.2.1_1788834912349_0.9346388573049753","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"_id":"@a11y-pulse/context-change-on-focus-audit@0.3.0","bugs":{"url":"https://github.com/A11y-Pulse/audits/issues"},"dist":{"shasum":"640da3dff0d6cbde32bdc64683a117121ef24c00","tarball":"https://registry.npmjs.org/@a11y-pulse/context-change-on-focus-audit/-/context-change-on-focus-audit-0.3.0.tgz","fileCount":9,"integrity":"sha512-3XZSwi1qOujdpiecAHvv23jc9vBa1m3PBaz71DbEq3eaB49SISrqRVA+YH7ObEo32Kng3HfXwGmcw7qLi8Dr7g==","signatures":[{"sig":"MEUCIQDBDasyAeU5m1Gvdojy0hzQFW764IBeccEIq/2NFgQr3AIgcL/86j7Wt2xBNPzyDFWhdC4urRrE4w3CQNakrQAzuFw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHLwsSpRwbeAUQqjiG0ZLVI/cPWujFB1Sxr1P7GNKvoLAiA6P61n0ULI1H1VwHAgIv/ko+c6fUhJj/z2th/D02d/5A=="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@a11y-pulse%2fcontext-change-on-focus-audit@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":36407},"name":"@a11y-pulse/context-change-on-focus-audit","type":"module","author":{"name":"A11y Pulse"},"engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","source":"./src/index.ts"}},"gitHead":"facc34c09fbb3982d29a1dc04944b5d035af7780","license":"SEE LICENSE IN LICENSE.md","scripts":{"lint":"biome check","test":"npm run typecheck && npm run test:unit","build":"tsup && tsc -p tsconfig.build.json","lint:fix":"biome check --fix","test:unit":"vitest run","typecheck":"tsc --noEmit","prepublishOnly":"npm run build","test:integration":"vitest run --config vitest.integration.config.ts"},"version":"0.3.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3100d683-c84a-4643-b002-2448417a343a"}},"homepage":"https://github.com/A11y-Pulse/audits/tree/main/packages/context-change-on-focus-audit#readme","keywords":["accessibility","a11y","wcag","context-change-on-focus","on-focus","puppeteer","audit"],"repository":{"url":"git+https://github.com/A11y-Pulse/audits.git","type":"git","directory":"packages/context-change-on-focus-audit"},"_npmVersion":"12.0.2","description":"WCAG 3.2.1 On Focus audit that tabs through a page and detects focus-triggered context changes (new windows, auto-submits, navigation, and focus theft/removal).","directories":{},"maintainers":[{"name":"wildlyinaccurate","email":"joseph@wildlyinaccurate.com"}],"_nodeVersion":"22.23.2","dependencies":{"@a11y-pulse/browser-adaptor":"*","@a11y-pulse/tab-orchestrator":"*"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"puppeteer":"^25.10.0","playwright-core":"^1.63.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/context-change-on-focus-audit_0.3.0_1789286327793_0.6197618202103885"}}},"time":{"created":"2026-08-20T05:01:00.346Z","modified":"2026-09-13T07:58:48.236Z","0.1.0":"2026-08-20T05:01:00.694Z","0.2.0":"2026-08-20T07:57:26.663Z","0.2.1":"2026-09-08T02:35:12.420Z","0.3.0":"2026-09-13T07:58:47.866Z"},"bugs":{"url":"https://github.com/A11y-Pulse/audits/issues"},"author":{"name":"A11y Pulse"},"license":"SEE LICENSE IN LICENSE.md","homepage":"https://github.com/A11y-Pulse/audits/tree/main/packages/context-change-on-focus-audit#readme","keywords":["accessibility","a11y","wcag","context-change-on-focus","on-focus","puppeteer","audit"],"repository":{"url":"git+https://github.com/A11y-Pulse/audits.git","type":"git","directory":"packages/context-change-on-focus-audit"},"description":"WCAG 3.2.1 On Focus audit that tabs through a page and detects focus-triggered context changes (new windows, auto-submits, navigation, and focus theft/removal).","maintainers":[{"name":"wildlyinaccurate","email":"joseph@wildlyinaccurate.com"}],"readme":"# @a11y-pulse/context-change-on-focus-audit\n\n[![npm version](https://img.shields.io/npm/v/@a11y-pulse/context-change-on-focus-audit)](https://www.npmjs.com/package/@a11y-pulse/context-change-on-focus-audit)\n[![CI](https://github.com/A11y-Pulse/audits/actions/workflows/ci.yml/badge.svg)](https://github.com/A11y-Pulse/audits/actions/workflows/ci.yml)\n[![License: PolyForm Shield 1.0.0](https://img.shields.io/badge/license-PolyForm%20Shield%201.0.0-blue)](./LICENSE.md)\n\nAn accessibility audit that aims to verify compliance with [**WCAG 2.2: 3.2.1 On Focus**](https://www.w3.org/WAI/WCAG22/Understanding/on-focus). It tabs through a page's focusable elements and detects whether merely receiving focus triggers a context change: a new window/tab, an auto-submitted form, a full navigation, focus being silently removed, or focus being redirected somewhere outside the element's own subtree. It is built to be framework-agnostic and can be used in any environment that allows you to programmatically focus elements and observe the page, such as Puppeteer, Playwright, or Selenium.\n\nThis audit was developed by [A11y Pulse](https://www.a11ypulse.com/) for its accessibility monitoring service. It is released as source-available under the [PolyForm Shield License 1.0.0](#license).\n\n## Install\n\n```bash\nnpm install @a11y-pulse/context-change-on-focus-audit @a11y-pulse/tab-orchestrator @a11y-pulse/browser-adaptor puppeteer\n```\n\n`@a11y-pulse/tab-orchestrator` drives the page (tabbing, markers, context-change observation), and [`@a11y-pulse/browser-adaptor`](../browser-adaptor) ships the bundled [Puppeteer and Playwright adaptors](#adaptors); `puppeteer` and `playwright-core` are optional peers of that package, each only required if you use the matching adaptor. Other frameworks can supply their own adaptor without installing either.\n\n## Quickstart\n\n```js\nimport { runContextChangeOnFocusAudit } from \"@a11y-pulse/context-change-on-focus-audit\";\nimport { PuppeteerAdaptor } from \"@a11y-pulse/browser-adaptor/puppeteer\";\nimport puppeteer from \"puppeteer\";\n\nconst browser = await puppeteer.launch();\nconst page = await browser.newPage();\nawait page.goto(\"https://who.likesdogs.nz/\");\n\nconst result = await runContextChangeOnFocusAudit(new PuppeteerAdaptor(page), {\n  elementLimit: 20,\n});\n\nconsole.log(result.summary);\n// { checked: 2, passed: 1, failed: 1, reachedLimit: false, reachedFailedElementLimit: false, timedOut: false, sessionEnd: 'completed' }\n\nconsole.log(result.elements);\n// [\n//   {\n//     selector: 'html>body>input#popup',\n//     html: '<input id=\"popup\">',\n//     tabIndex: 1,\n//     failed: true,\n//     findings: [{ kind: 'new-window', bucket: 'violation' }],\n//   },\n// ]\n\nawait browser.close();\n```\n\nSee [`@a11y-pulse/focus-appearance-audit`'s `examples/puppeteer`](../focus-appearance-audit/examples/puppeteer) for a runnable example of the same shared-adaptor pattern.\n\n## Shared tab session\n\n`runContextChangeOnFocusAudit` is a convenience wrapper: it builds a private `@a11y-pulse/tab-orchestrator` session, attaches one consumer, runs it, and hands back that consumer's result. If you're running more than one tab-driven audit against the same page (for example alongside [`@a11y-pulse/focus-appearance-audit`](../focus-appearance-audit) or [`@a11y-pulse/focus-not-obscured-audit`](../focus-not-obscured-audit)), drive a single shared orchestrator instead so the page is only tabbed through once. Use `createContextChangeOnFocusAudit` to get a `TabConsumer` you can `attach()` yourself:\n\n```ts\nimport { createTabOrchestrator } from \"@a11y-pulse/tab-orchestrator\";\nimport { createContextChangeOnFocusAudit } from \"@a11y-pulse/context-change-on-focus-audit\";\nimport { PuppeteerAdaptor } from \"@a11y-pulse/browser-adaptor/puppeteer\";\n\nconst orchestrator = createTabOrchestrator(new PuppeteerAdaptor(page));\n\nconst contextChange = createContextChangeOnFocusAudit({ elementLimit: 50 });\norchestrator.attach(contextChange);\n\nawait orchestrator.run();\n\nconsole.log(contextChange.result);\n```\n\n`contextChange.result` is only complete once `contextChange` has disconnected (by hitting one of its own limits) or the session has ended. Reading it before then is undefined. See [`@a11y-pulse/tab-orchestrator`](../tab-orchestrator) for the full session lifecycle and capability model. This audit declares only the `\"contextSignals\"` capability: it does not need `obscuring`, `unfocusedPair`, or `baselineStyles`, so it can run alongside the other audits on the same orchestrator without paying for their measurements.\n\nBecause a genuine navigation ends the whole shared session (`summary.sessionEnd === \"navigation\"`), any other consumers attached to the same orchestrator also stop receiving tab stops once one occurs. This is a property of the shared session, not something this audit can opt out of.\n\n## Browser support\n\n| Adaptor | Browser | Supported |\n| --- | --- | --- |\n| Puppeteer | Chrome | Yes |\n| Playwright | Chromium | Yes |\n| Playwright | WebKit | **No.** WebKit does not move focus to links when Tab is pressed, so the audit only reaches a subset of the page. |\n| Playwright | Firefox | Yes |\n\nVerified by this repo's integration suites, which run every audit against each of these engines. Unsupported and partial cases are skipped there with the reason printed alongside them.\n\n## Options\n\nThe following options can be passed to `runContextChangeOnFocusAudit` as `ContextChangeOnFocusOptions`:\n\n| Option                  | Type     | Default         | Description                                                                                                                                         |\n| ------------------------ | -------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `elementLimit`            | `number` | `1024`           | Max focusable elements to tab through.                                                                                                              |\n| `screenshotSettleDelay`   | `number` | `33`             | How long to wait (in ms) after each Tab for the page to settle before observing context signals.                                                    |\n| `failedElementLimit`      | `number` | `0` (never)      | Finish the audit early once this many elements have failed, leaving the rest unchecked. Useful as a fail-fast signal when you only need to know a page has focus problems, not their full extent. |\n| `timeout`                 | `number` | `0` (no timeout) | Limit how long (in ms) the audit runs before returning the results it has gathered so far.                                                          |\n\n## Result shape\n\n`runContextChangeOnFocusAudit` resolves to a `ContextChangeOnFocusResult`:\n\n```ts\ntype ContextChangeOnFocusResult = {\n  /** Every focusable element that was checked, in tab order. */\n  elements: Array<{\n    selector: string;\n    html: string;\n    tabIndex: number;\n    /** True if any of `findings` has `bucket === \"violation\"`. */\n    failed: boolean;\n    /** Every context-change finding observed at this tab stop, if any. */\n    findings: Array<{\n      kind:\n        | \"new-window\"\n        | \"auto-submit\"\n        | \"focus-removed\"\n        | \"focus-redirected-outside\"\n        | \"focus-redirected-same-subtree\"\n        | \"url-changed\"\n        | \"navigation\";\n      bucket: \"violation\" | \"incomplete\";\n    }>;\n  }>;\n  summary: {\n    checked: number;\n    passed: number;\n    /** Count of elements whose `failed` is `true`. */\n    failed: number;\n    /** True if `elementLimit` was hit before tabbing finished. */\n    reachedLimit: boolean;\n    /** True if the audit stopped early after hitting `failedElementLimit`. */\n    reachedFailedElementLimit: boolean;\n    /** True if the audit returned early because `timeout` elapsed. */\n    timedOut: boolean;\n    /**\n     * Why the tab session ended, or `null` when this consumer disconnected\n     * itself (element limit, failed-element limit, or timeout).\n     */\n    sessionEnd: \"completed\" | \"lostFocus\" | \"navigation\" | \"failed\" | null;\n  };\n};\n```\n\n### Findings\n\n[3.2.1 On Focus](https://www.w3.org/WAI/WCAG22/Understanding/on-focus) is a Level A criterion: receiving focus must not, by itself, trigger a change of context. Each tab stop can carry zero or more findings:\n\n- **`new-window`** (`violation`): focusing the element opened a new window/tab (`window.open`). The audit intercepts the call so no real popup opens.\n- **`auto-submit`** (`violation`): focusing the element submitted a form. The audit prevents the actual submission/navigation.\n- **`focus-removed`** (`violation`): focusing the element caused focus to be removed entirely (no longer on any element).\n- **`focus-redirected-outside`** (`violation`): focusing the element moved focus to a different element outside its own DOM subtree (focus theft).\n- **`focus-redirected-same-subtree`** (`incomplete`): focus moved to a descendant/ancestor of the intended element (e.g. a composite widget delegating focus to an inner control). This is legitimate delegation, not a failure, but is recorded since it changes which element ends up focused.\n- **`url-changed`** (`incomplete`): the URL changed (hash or `pushState`/`replaceState`) without a full navigation. Not necessarily a failure on its own, but worth reviewing.\n- **`navigation`** (`violation`): focusing the element triggered a full page navigation.\n\n`summary.failed` counts only elements with at least one `\"violation\"`-bucket finding; `\"incomplete\"` findings do not count as failures.\n\nA full navigation also ends the shared tab session early (`summary.sessionEnd === \"navigation\"`) once it is detected, since there is no longer a page to keep tabbing through.\n\n## Adaptors\n\nThe audit itself is framework-agnostic: it drives a page through an **adaptor**, a small interface of primitives (evaluate JS in the page, press Tab, hit-test the focused element, etc.) that the audit calls without knowing which browser automation library is behind it.\n\nThe `BrowserAdaptor` interface lives in [`@a11y-pulse/browser-adaptor`](../browser-adaptor), which also ships the bundled `PuppeteerAdaptor` and `PlaywrightAdaptor`. This package re-exports the type so `runContextChangeOnFocusAudit`'s argument type is available without a separate import. Other environments (Selenium, WebDriver) can be supported by implementing the same interface.\n\nUse [`@a11y-pulse/browser-adaptor`'s `src/adaptors/puppeteer.ts`](../browser-adaptor/src/adaptors/puppeteer.ts) as a reference implementation.\n\n## Limitations\n\n- **Tab order only.** The audit tabs through elements in native tab order. It does not yet exercise arrow-key composite widgets (menus, comboboxes, toolbars, etc.) where focus moves via `aria-activedescendant` or roving `tabindex` instead of native Tab.\n- **Navigation ends the session.** Once a full navigation is detected, the whole shared tab session ends (there is no more original page to tab through), so this audit (and any others sharing the same orchestrator) stops after that stop.\n- **Best-effort interception.** `window.open` and form submission are intercepted so the audit does not actually leave the page or open real popups, but unusual navigation mechanisms (e.g. a service worker or extension-driven redirect) may not be caught.\n\n## Releasing\n\nReleases are managed in the [A11y-Pulse/audits](https://github.com/A11y-Pulse/audits) monorepo with [Changesets](https://github.com/changesets/changesets). Publishing uses [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/) (OIDC). There is no long-lived `NPM_TOKEN`.\n\n### Ship a change\n\n1. Open a PR against `main` that includes a changeset (`npx changeset`) naming `@a11y-pulse/context-change-on-focus-audit`.\n2. After merge, the Release workflow opens a Version PR. Merging that PR publishes this package to npm and tags `@a11y-pulse/context-change-on-focus-audit@<version>`.\n\nTrusted Publisher on npm must stay configured for:\n\n| Field | Value |\n| --- | --- |\n| Organization or user | `A11y-Pulse` |\n| Repository | `audits` |\n| Workflow filename | `release.yml` |\n\n### Consumers (e.g. the A11y Pulse runner)\n\nBumping the published version in downstream apps is a separate change. Update the dependency range / lockfile there after the npm release lands.\n\n## License\n\nReleased under the [PolyForm Shield License 1.0.0](./LICENSE.md), in plain language:\n\n- **Source-available.** The source is public and you can read, fork, and modify it.\n- **Permitted for non-competing use.** You can use this package freely in your own products and services, as long as they don't compete with A11y Pulse.\n- **Competing products are forbidden.** You may not use this software (or a modified version of it) to build a product or service that competes with A11y Pulse's accessibility monitoring offering.\n\nSee [LICENSE.md](./LICENSE.md) for the full, binding terms.\n","readmeFilename":"README.md"}