Initial Commit
This commit is contained in:
88
openspec/changes/swap-testing-e2e-cloakbrowser/proposal.md
Normal file
88
openspec/changes/swap-testing-e2e-cloakbrowser/proposal.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# swap-testing-e2e-cloakbrowser
|
||||
|
||||
## Why
|
||||
|
||||
`@playwright/test` ships a stock Chromium that is trivially fingerprinted as
|
||||
automation (`navigator.webdriver === true`, `HeadlessChrome` UA, missing
|
||||
`window.chrome`, GPU/canvas/audio leaks, CDP detection). E2E suites that
|
||||
exercise production-like flows against bot-aware infrastructure
|
||||
(Cloudflare Turnstile, reCAPTCHA v3, ShieldSquare, FingerprintJS,
|
||||
BrowserScan) either fail outright or skew metrics on the first request.
|
||||
|
||||
[CloakBrowser](https://github.com/CloakHQ/cloakbrowser) (`cloakbrowser`,
|
||||
v0.3.28) is a real Chromium binary with 49 source-level C++ patches
|
||||
covering canvas, WebGL, audio, fonts, GPU, screen, WebRTC, network
|
||||
timing, automation signals, and CDP input behaviour. It is a drop-in
|
||||
replacement at the browser-launch layer: the returned object is a
|
||||
`playwright-core` `Browser` and works with every existing Playwright
|
||||
test, fixture, page-object, and helper without API changes.
|
||||
|
||||
This proposal swaps every browser-launch call inside
|
||||
`@suaveplan/testing-e2e` to `cloakbrowser`, retains `@playwright/test`
|
||||
as the test runner (CloakBrowser does not provide one), and exposes the
|
||||
stealth-specific knobs (`humanize`, `proxy`, `geoip`,
|
||||
`launchPersistentContext`, extension support) as first-class API surface.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **ADDED:** `CLOAK_LAUNCH_FAILED`, `CLOAK_BINARY_MISSING`,
|
||||
`CLOAK_PROXY_INVALID` error codes + `CloakLaunchError` class +
|
||||
`classifyCloakError` helper that turns raw cloakbrowser errors into
|
||||
classified `TestingE2eError` instances with remediation hints.
|
||||
- **ADDED:** `@suaveplan/testing-e2e/cloakserve` subpath —
|
||||
`connectCloakServe(options, chromium?)` + `buildCloakServeUrl(options)`
|
||||
pair that wraps `chromium.connectOverCDP(...)` for the
|
||||
[cloakserve](https://github.com/CloakHQ/CloakBrowser#cdp-server-mode)
|
||||
CDP multiplexer. Strongly-typed query-parameter surface
|
||||
(`fingerprint`, `timezone`, `locale`, `platform`, GPU vendor /
|
||||
renderer, hardware concurrency, device memory, screen dimensions,
|
||||
`proxy`, `geoip`). Errors wrapped in `CloakLaunchError`.
|
||||
- **ADDED:** `@suaveplan/testing-e2e/presets` subpath — stealth
|
||||
presets matching the upstream README's "Recommended config for
|
||||
anti-bot sites" block. `STEALTH_DEFAULT_CLOAK`,
|
||||
`STEALTH_HEADED_CLOAK`, `stealthMax(proxy)`,
|
||||
`stealthMaxHeadless(proxy)`.
|
||||
- **ADDED:** `tests/no-disallowed.test.ts` rule against
|
||||
`waitForTimeout` / `wait_for_timeout`. Both forms emit CDP traffic
|
||||
that reCAPTCHA v3 / Enterprise behavioural scorers detect; the
|
||||
upstream README documents this explicitly. Test files excluded.
|
||||
- **ADDED:** `docker/` bundle — `Dockerfile.cloakserve` (extends
|
||||
`cloakhq/cloakbrowser`, adds the Kasada / Akamai font set, runs
|
||||
`cloakserve` on 9222), `Dockerfile.manager` (extends
|
||||
`cloakhq/cloakbrowser-manager`, exposes 8080 with a `cloakprofiles`
|
||||
named volume), `docker-compose.yml` binding both to `127.0.0.1`
|
||||
with the manager gated behind the `manager` compose profile,
|
||||
`.env.example` for pinned tags + `AUTH_TOKEN`, operator `README.md`.
|
||||
- **MODIFIED:** `extendedTest`'s `browser` fixture now launches via
|
||||
`cloakbrowser.launch()` instead of the stock Playwright chromium
|
||||
fixture. `freshPage` and any downstream fixture composition keep
|
||||
the same signature. The launcher is unit-test-injectable via the
|
||||
`launchBrowserWithCloak(opts, injectedLaunch?)` helper.
|
||||
- **MODIFIED:** `launchWithExtension` accepts an injectable
|
||||
`launchPersistentContext` function (default: cloakbrowser's). The
|
||||
legacy `chromium: BrowserType` parameter is removed.
|
||||
- **MODIFIED:** `packages/testing/testing-e2e/package.json` adds
|
||||
`cloakbrowser: ^0.3.28` as a direct dependency. (The dep is pinned
|
||||
in-package rather than added to `workspaces.catalogs.testing` to
|
||||
keep this change isolated from concurrent in-flight work that also
|
||||
touches the root catalog; promote to the testing catalog as a
|
||||
follow-up if other packages start consuming `cloakbrowser`.)
|
||||
- **MODIFIED:** Package description, README updated. **No public
|
||||
re-exports of cloakbrowser symbols** — per CLAUDE.md rule 4,
|
||||
consumers `import { launch } from "cloakbrowser"` directly when
|
||||
bypassing the fixture layer.
|
||||
|
||||
## Impact
|
||||
|
||||
- **Affected specs:** `testing-e2e`.
|
||||
- **Affected code:** all six consumer packages currently importing from
|
||||
`@suaveplan/testing-e2e` continue to work unchanged — the browser
|
||||
launcher swap is transparent to existing tests because the returned
|
||||
`Browser` object is the same `playwright-core` `Browser` type.
|
||||
- **Binary footprint:** CI runners gain a ~200MB stealth Chromium
|
||||
download on first run (cached under `~/.cache/cloakbrowser/`).
|
||||
Acceptable: the alternative is `playwright install chromium` (~170MB)
|
||||
on every `@playwright/test` consumer.
|
||||
- **Out of scope:** Python flavour (`cloakbrowser` PyPI), `cloakserve`
|
||||
CDP multiplexer, `cloakbrowser-manager` Docker profile UI — none are
|
||||
consumed by the genesis monorepo today.
|
||||
@@ -0,0 +1,212 @@
|
||||
# testing-e2e — delta (swap-testing-e2e-cloakbrowser)
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Cloakserve CDP connect helper
|
||||
|
||||
The package MUST expose `@suaveplan/testing-e2e/cloakserve` (also
|
||||
re-exported from the package root) providing `connectCloakServe(options, chromium?)`
|
||||
and `buildCloakServeUrl(options)`. The URL builder MUST map camelCase
|
||||
option keys to cloakserve's kebab-case query-string keys
|
||||
(`hardwareConcurrency` → `hardware-concurrency`, etc.), stringify
|
||||
booleans, skip `undefined`/`null` values, and default the base URL to
|
||||
`http://localhost:9222`. `connectCloakServe` MUST inject `@playwright/test`'s
|
||||
`chromium` by default with a lazy dynamic-import cache, and wrap any
|
||||
`connectOverCDP` rejection in a `CloakLaunchError` whose `context`
|
||||
carries `{ cloakServeUrl }`.
|
||||
|
||||
#### Scenario: buildCloakServeUrl maps keys and base URL
|
||||
|
||||
- **WHEN** `buildCloakServeUrl({ fingerprint: 42, hardwareConcurrency: 8, geoip: true })` is called
|
||||
- **THEN** the result equals `http://localhost:9222/?fingerprint=42&hardware-concurrency=8&geoip=true` (param order is preserved)
|
||||
|
||||
**Verified by:** `src/cloakserve/build-cloakserve-url.test.ts::"appends fingerprint seed"` AND `src/cloakserve/build-cloakserve-url.test.ts::"maps camelCase to kebab-case"` AND `src/cloakserve/build-cloakserve-url.test.ts::"stringifies booleans"`
|
||||
|
||||
#### Scenario: connectCloakServe forwards options and returns the Browser
|
||||
|
||||
- **WHEN** `connectCloakServe({ fingerprint: 11111, timezone: "America/New_York" }, injectedDriver)` is called
|
||||
- **THEN** `injectedDriver.connectOverCDP` is called with a URL containing `fingerprint=11111` and `timezone=America%2FNew_York`, and the returned `Browser` is the injected fake
|
||||
|
||||
**Verified by:** `src/cloakserve/cloakserve-connect.test.ts::"uses injected chromium and builds the cloakserve URL"`
|
||||
|
||||
#### Scenario: connectCloakServe wraps errors
|
||||
|
||||
- **WHEN** the underlying `connectOverCDP` rejects with `ECONNREFUSED`
|
||||
- **THEN** `connectCloakServe` rethrows a `CloakLaunchError` with
|
||||
`code === TESTING_E2E.CLOAK.PROXY_INVALID` and
|
||||
`context.cloakServeUrl` set to the URL that was attempted
|
||||
|
||||
**Verified by:** `src/cloakserve/cloakserve-connect.test.ts::"wraps connection errors in CloakLaunchError"` AND `src/cloakserve/cloakserve-connect.test.ts::"classifies generic connect errors as LAUNCH_FAILED"`
|
||||
|
||||
### Requirement: Stealth presets
|
||||
|
||||
The package MUST expose `@suaveplan/testing-e2e/presets` (also
|
||||
re-exported from the package root) providing frozen `STEALTH_DEFAULT_CLOAK`
|
||||
(`{ headless: true }`) and `STEALTH_HEADED_CLOAK`
|
||||
(`{ headless: false, humanize: true }`) plus `stealthMax(proxy)` and
|
||||
`stealthMaxHeadless(proxy)` factories that return fresh objects on
|
||||
every call. `stealthMax(proxy)` MUST return
|
||||
`{ headless: false, humanize: true, geoip: true, proxy }` — exactly
|
||||
matching the CloakBrowser README's "Recommended config for anti-bot
|
||||
sites" block.
|
||||
|
||||
#### Scenario: stealthMax matches the upstream recommendation
|
||||
|
||||
- **WHEN** `stealthMax("http://proxy:8080")` is called
|
||||
- **THEN** the result is `{ headless: false, humanize: true, geoip: true, proxy: "http://proxy:8080" }`
|
||||
|
||||
**Verified by:** `src/presets/stealth-presets.test.ts::"matches the upstream recommended config"`
|
||||
|
||||
#### Scenario: static presets are frozen
|
||||
|
||||
- **WHEN** `Object.isFrozen(STEALTH_DEFAULT_CLOAK)` is checked
|
||||
- **THEN** it is `true` (and same for `STEALTH_HEADED_CLOAK`)
|
||||
|
||||
**Verified by:** `src/presets/stealth-presets.test.ts::"is headless and frozen"` AND `src/presets/stealth-presets.test.ts::"is headed + humanized and frozen"`
|
||||
|
||||
### Requirement: `waitForTimeout` lint guard
|
||||
|
||||
The package's source-guard test MUST flag `waitForTimeout` and
|
||||
`wait_for_timeout` calls anywhere under `src/` (excluding `.test.ts`
|
||||
and `.bench.ts`). Both forms emit CDP protocol traffic that
|
||||
reCAPTCHA v3 / Enterprise behavioural scorers detect — the upstream
|
||||
CloakBrowser README documents this as a primary cause of low scores.
|
||||
|
||||
#### Scenario: guard fires on a synthetic violation
|
||||
|
||||
- **WHEN** the `no-disallowed source guard` test runs against the
|
||||
current `src/` tree
|
||||
- **THEN** the test passes (no violations) AND the `PATTERNS` array
|
||||
contains entries named `waitForTimeout` and `wait_for_timeout`
|
||||
|
||||
**Verified by:** `tests/no-disallowed.test.ts::"src/ contains no disallowed constructs"`
|
||||
|
||||
### Requirement: Docker bundle
|
||||
|
||||
The package MUST ship a `docker/` directory containing
|
||||
`Dockerfile.cloakserve` (extending `cloakhq/cloakbrowser`, installing
|
||||
the Kasada / Akamai font set, running `cloakserve` on `9222`),
|
||||
`Dockerfile.manager` (extending `cloakhq/cloakbrowser-manager`,
|
||||
exposing 8080 with a `cloakprofiles` named volume),
|
||||
`docker-compose.yml` (both services bound to `127.0.0.1`, manager
|
||||
behind the `manager` compose profile, named volume defined),
|
||||
`.env.example` (`CLOAKBROWSER_TAG`, `MANAGER_TAG`,
|
||||
`MANAGER_AUTH_TOKEN`), and a `docker/README.md` operator playbook.
|
||||
Both Dockerfiles MUST declare a healthcheck.
|
||||
|
||||
#### Scenario: cloakserve Dockerfile builds on the upstream image and exposes 9222
|
||||
|
||||
- **WHEN** `docker build -f docker/Dockerfile.cloakserve` is run
|
||||
- **THEN** the resulting image is layered on `cloakhq/cloakbrowser`,
|
||||
exposes port `9222`, defaults its CMD to `cloakserve`, and runs the
|
||||
documented font-install step
|
||||
|
||||
**Verified by:** manual operator verification (`docker compose up cloakserve` + smoke connect via `connectCloakServe`); source-level invariants are enforced by reading the Dockerfile in CI is out of scope for this change
|
||||
|
||||
### Requirement: CLOAK_* error codes
|
||||
|
||||
The package MUST emit `CloakLaunchError extends TestingE2eError` with
|
||||
codes `TESTING_E2E.CLOAK.LAUNCH_FAILED`,
|
||||
`TESTING_E2E.CLOAK.BINARY_MISSING`, and
|
||||
`TESTING_E2E.CLOAK.PROXY_INVALID`. All three codes MUST appear in the
|
||||
exported `TESTING_E2E_ERROR_CODES` registry and conform to
|
||||
`TESTING_E2E_ERROR_CODE_PATTERN`.
|
||||
|
||||
#### Scenario: codes registered
|
||||
|
||||
- **WHEN** `TESTING_E2E_ERROR_CODES` is iterated
|
||||
- **THEN** the three CLOAK codes are present and each matches the
|
||||
registry pattern
|
||||
|
||||
**Verified by:** `src/errors/testing-e2e-error.test.ts::"registers CLOAK_* codes"` AND `src/errors/testing-e2e-error.test.ts::"CloakLaunchError exposes the LAUNCH_FAILED code by default"`
|
||||
|
||||
#### Scenario: BINARY_MISSING raised when cloakbrowser binary not present
|
||||
|
||||
- **WHEN** the underlying cloakbrowser launcher throws an error whose
|
||||
message includes `binary not found`, `ENOENT`, or `Failed to download`
|
||||
- **THEN** the calling fixture rethrows a `CloakLaunchError` with code
|
||||
`TESTING_E2E.CLOAK.BINARY_MISSING`, the original error as `cause`,
|
||||
and a remediation hint pointing to `npx cloakbrowser install`
|
||||
|
||||
**Verified by:** `src/errors/testing-e2e-error.test.ts::"classifyCloakError maps ENOENT to BINARY_MISSING"`
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Layer 0 dep-graph isolation
|
||||
|
||||
The `@suaveplan/testing-e2e` `dependencies` and `peerDependencies` MUST
|
||||
NOT contain any `@suaveplan/*` workspace package. `dependencies` MUST
|
||||
include `cloakbrowser` (any valid range — direct or `catalog:testing`)
|
||||
alongside the existing `@playwright/test`, `@opentelemetry/api`,
|
||||
`tslib`, and `zod` entries. `devDependencies` MAY include
|
||||
`@suaveplan/testing` for the package's own tests.
|
||||
|
||||
#### Scenario: runtime closure clean and contains cloakbrowser
|
||||
|
||||
- **WHEN** `bunx turbo ls --filter=@suaveplan/testing-e2e...` is run
|
||||
- **THEN** no `@suaveplan/*` package appears in the runtime closure
|
||||
AND `cloakbrowser` is present as a direct dependency
|
||||
|
||||
**Verified by:** `tests/dep-graph.test.ts::"no @suaveplan/* in dependencies or peerDependencies"` AND `tests/dep-graph.test.ts::"cloakbrowser is a direct dependency"`
|
||||
|
||||
### Requirement: `extendedTest` default browser is stealth
|
||||
|
||||
`extendedTest.browser` MUST be overridden so the default browser used
|
||||
by every fixture downstream (`freshPage`, consumer-extended fixtures)
|
||||
launches via `cloakbrowser.launch()` (drop-in for stock chromium).
|
||||
The override MUST honour `testInfo.project.use.cloak` if defined,
|
||||
otherwise launch with cloakbrowser defaults (`headless: true`, stealth
|
||||
args on). The launcher MUST be injectable for unit testing — the
|
||||
fixture takes an internal default but exposes the launcher symbol on
|
||||
the `cloak` worker fixture so tests can supply a spy.
|
||||
|
||||
#### Scenario: default browser is cloakbrowser
|
||||
|
||||
- **WHEN** a test runs under `extendedTest` with no per-project
|
||||
override
|
||||
- **THEN** the `browser` fixture is produced by the cloakbrowser
|
||||
`launch` function (identity check via injected spy)
|
||||
|
||||
**Verified by:** `src/fixtures/extended-test.test.ts::"browser fixture is launched via cloakbrowser launch"` AND `src/fixtures/extended-test.test.ts::"cloak options from testInfo.project.use.cloak flow into launch"`
|
||||
|
||||
#### Scenario: errors map to CloakLaunchError
|
||||
|
||||
- **WHEN** the underlying cloakbrowser launcher throws
|
||||
- **THEN** the fixture rethrows a `CloakLaunchError` with a classified
|
||||
code (`LAUNCH_FAILED`, `BINARY_MISSING`, or `PROXY_INVALID`)
|
||||
|
||||
**Verified by:** `src/fixtures/extended-test.test.ts::"wraps launch errors in CloakLaunchError"`
|
||||
|
||||
### Requirement: Extension fixture uses cloakbrowser launchPersistentContext
|
||||
|
||||
`launchWithExtension` MUST accept a `launchPersistentContext` function
|
||||
parameter (default: cloakbrowser's). The legacy
|
||||
`chromium: BrowserType` parameter is removed. Extension args
|
||||
(`--disable-extensions-except`, `--load-extension`) and userDataDir
|
||||
behaviour are unchanged.
|
||||
|
||||
#### Scenario: default launcher is cloakbrowser
|
||||
|
||||
- **WHEN** `launchWithExtension({ extensionPath, userDataDir })` is
|
||||
called with no explicit launcher
|
||||
- **THEN** `cloakbrowser.launchPersistentContext` is invoked with the
|
||||
expected `userDataDir` and Chromium args
|
||||
|
||||
**Verified by:** `src/extension-fixture/launch-with-extension.test.ts::"defaults to cloakbrowser launchPersistentContext"`
|
||||
|
||||
#### Scenario: launcher is injectable
|
||||
|
||||
- **WHEN** `launchWithExtension({ launchPersistentContext: spy, extensionPath, userDataDir })` is called
|
||||
- **THEN** the provided `spy` is invoked exactly once with the
|
||||
expected `userDataDir` and Chromium args
|
||||
|
||||
**Verified by:** `src/extension-fixture/launch-with-extension.test.ts::"uses injected launcher when provided"`
|
||||
|
||||
#### Scenario: launchPersistentContext errors map to CloakLaunchError
|
||||
|
||||
- **WHEN** the underlying launcher throws an error matching the
|
||||
`ENOENT`/binary classifier
|
||||
- **THEN** `launchWithExtension` rethrows a `CloakLaunchError` with
|
||||
`code === TESTING_E2E.CLOAK.BINARY_MISSING`
|
||||
|
||||
**Verified by:** `src/extension-fixture/launch-with-extension.test.ts::"wraps launcher errors with CloakLaunchError"`
|
||||
51
openspec/changes/swap-testing-e2e-cloakbrowser/tasks.md
Normal file
51
openspec/changes/swap-testing-e2e-cloakbrowser/tasks.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# swap-testing-e2e-cloakbrowser — tasks
|
||||
|
||||
- [x] 1. Add `cloakbrowser: ^0.3.28` as a direct dep in `packages/testing/testing-e2e/package.json` (skip the root testing catalog for now to avoid entanglement with concurrent root-`package.json` work).
|
||||
- [x] 2. Add `./cloakserve` and `./presets` to the package exports map; update description.
|
||||
- [x] 3. Add `CLOAK_*` error codes:
|
||||
- [x] Append `LAUNCH_FAILED`, `BINARY_MISSING`, `PROXY_INVALID` to `src/errors/codes.ts` registry under domain `CLOAK`.
|
||||
- [x] Add `CloakLaunchError` class to `src/errors/testing-e2e-error.ts`.
|
||||
- [x] Add `classifyCloakError(err)` + `wrapCloakError(err, context?)` helpers.
|
||||
- [x] Co-located `.md` + tests covering all three codes + the classifier + the wrapper remediation hints.
|
||||
- [x] 4. Refactor `src/extension-fixture/launch-with-extension.ts`:
|
||||
- [x] Drop `chromium: BrowserType` parameter.
|
||||
- [x] Accept optional `launchPersistentContext` injector (default: `cloakbrowser`'s, resolved via dynamic `import` with cache + `_resetDefaultLauncherCache` test hook).
|
||||
- [x] Forward `launchOptions` (proxy / humanize / geoip / …) untouched.
|
||||
- [x] Wrap launcher errors with `wrapCloakError`.
|
||||
- [x] Update tests + `.md`; update `launch-with-extension.types.ts`.
|
||||
- [x] 5. Override `browser` worker fixture inside `src/fixtures/extended-test.ts`:
|
||||
- [x] Add `cloak: CloakOptions` worker option (Playwright `option: true`) with `{ headless: true }` default.
|
||||
- [x] Extract `launchBrowserWithCloak(opts, injectedLaunch?)` as a unit-testable helper backed by `cloakbrowser.launch`.
|
||||
- [x] Override `browser` fixture to call the helper; `apiContext` depends on Playwright's built-in `playwright` fixture (not `browser`) so API-only tests never start Chromium.
|
||||
- [x] Wrap errors with `wrapCloakError`.
|
||||
- [x] Update tests (source-level pins + runtime classifier tests) + `.md`.
|
||||
- [x] 6. Add `src/cloakserve/`:
|
||||
- [x] `cloakserve-options.types.ts` with `CloakServeFingerprint` + `CloakServeConnectOptions`.
|
||||
- [x] `build-cloakserve-url.ts` — pure URL builder with camelCase → kebab-case mapping.
|
||||
- [x] `cloakserve-connect.ts` — `connectCloakServe(options, chromium?)` wrapping `chromium.connectOverCDP`; lazy `@playwright/test` import + `_resetCloakServeChromiumCache` hook; errors wrapped in `CloakLaunchError`.
|
||||
- [x] `index.ts` barrel with explicit named exports (rule 10).
|
||||
- [x] `.md` per impl file + tests covering URL building, default base URL, kebab-case mapping, boolean stringification, error classification, lazy import caching.
|
||||
- [x] 7. Add `src/presets/`:
|
||||
- [x] `STEALTH_DEFAULT_CLOAK`, `STEALTH_HEADED_CLOAK` frozen presets.
|
||||
- [x] `stealthMax(proxy)` + `stealthMaxHeadless(proxy)` factory presets matching the upstream "Recommended config for anti-bot sites" block.
|
||||
- [x] `.md` + tests.
|
||||
- [x] 8. Extend `tests/no-disallowed.test.ts` with patterns flagging `waitForTimeout` and `wait_for_timeout` calls in `src/`.
|
||||
- [x] 9. Add `docker/` bundle:
|
||||
- [x] `Dockerfile.cloakserve` extending `cloakhq/cloakbrowser`, installing the Kasada / Akamai font set, defaulting to `cloakserve`, healthcheck on `/json/version`.
|
||||
- [x] `Dockerfile.manager` extending `cloakhq/cloakbrowser-manager`, `/data` volume, healthcheck on `/api/status`.
|
||||
- [x] `docker-compose.yml` — both services bound to `127.0.0.1`; manager behind the `manager` compose profile; `cloakprofiles` named volume.
|
||||
- [x] `.env.example` for `CLOAKBROWSER_TAG`, `MANAGER_TAG`, `MANAGER_AUTH_TOKEN`.
|
||||
- [x] `docker/README.md` operator playbook.
|
||||
- [x] 10. Update `src/index.ts` barrel: re-export new cloakserve symbols, presets, and CLOAK error helpers.
|
||||
- [x] 11. Update vite externals + `vite.config.ts` entries for the new subpath modules.
|
||||
- [x] 12. Rewrite `README.md` — stealth swap summary, binary download note, humanize/proxy/geoip features, worked examples (extendedTest, cloakserve, presets, Docker, extension fixture).
|
||||
- [x] 13. Add `tests/dep-graph.test.ts` assertion that `cloakbrowser` is a direct dep.
|
||||
- [x] 14. Pre-publish gate: `bunx turbo lint typecheck test --filter=@suaveplan/testing-e2e` green; `bunx turbo test:coverage --filter=@suaveplan/testing-e2e` at 100% on all metrics; `bun run docs:check` 100%.
|
||||
- [ ] 15. On ship (post-merge): fold delta into `openspec/specs/testing-e2e/spec.md`; archive change folder under `openspec/archive/<date>-swap-testing-e2e-cloakbrowser/`.
|
||||
|
||||
## 0. Pre-flight (non-negotiable)
|
||||
|
||||
- [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each)
|
||||
- [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
|
||||
- [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest.
|
||||
|
||||
Reference in New Issue
Block a user