Active investigation. The root cause is understood but the final workaround is not selected.
Playwright throws Error: Requiring @playwright/test second time when a shared config or helper package is linked via file: and both the consumer and the linked package resolve their own physical copies of @playwright/test. The Playwright runner enforces a singleton load and errors on the second physical path.
In dotdot, this is complicated by symlinked peer repos: Node resolves modules from the real path of the linked repo, not the consumer, so runtime dependencies must exist in the linked repo’s node_modules.
- Dotdot links peer repos via
file:into the consumer’snode_modules, but Node resolution follows the real path of the symlink target. - If the peer repo does not have
node_modules, runtime deps likeeffectfail to resolve when the Playwright config imports shared code. - Adding
node_modulesto the peer repo fixes runtime deps but also installs@playwright/test, creating a second physical Playwright copy and triggering the duplicate-load error. - A working workaround is to route configs through
@overeng/utils/node/playwright/config/modwhich returns a plain config object without importing@playwright/testat runtime.
@playwright/testmust stay a dev dependency in shared packages (for local testing).NODE_OPTIONS=--preserve-symlinksdoes not work here because Node refuses to strip TS types undernode_modules.- Playwright’s guard is hard-coded; there is no supported env flag to disable it.
- The workaround relies on a config helper that returns a plain object, so consumers must import it via
@overeng/utils/node/playwright/config/mod.
Repro repo: /Users/schickling/Code/overengineeringstudio/workspace3/peer-playwright-duplicate
shared-utils/ # imports @playwright/test
consumer/ # imports shared-utils via file:
cd shared-utils
bun install
cd ../consumer
bun install
bunx playwright test
Expected error:
Error: Requiring @playwright/test second time
- microsoft/playwright#15819 (closed) — Shared package with its own
@playwright/testcauses double load; maintainers point to avoiding duplicate installs. - microsoft/playwright#31478 (closed) — Two internal packages both importing
@playwright/testtrigger the guard in a consumer project. - microsoft/playwright#24300 (closed) — Yarn workspace hoisting/deduping resolves duplicate load.
- microsoft/playwright#24564 (closed) — General report of the same guard error in multi-package setups.
- microsoft/playwright#15349 (closed) — Discussion on sharing helpers/config with Playwright Test across packages.
- microsoft/playwright#33159 (closed) — VS Code Test Explorer reports the guard error in a shared fixture setup.
- microsoft/playwright#32959 (closed) — VS Code Test Explorer triggers duplicate load with shared package imports.
- microsoft/playwright#32958 (closed) — Duplicate issue for VS Code Test Explorer error.
- microsoft/playwright#11817 (closed) — Early discussion of using shared fixtures/modules; leads to guidance on avoiding double load.
- oven-sh/bun#3835 (closed) — Bun
file:dependency behavior can create nested installs.
This idea would allow dotdot to avoid duplicate Playwright installs while keeping @playwright/test as a dev dependency in shared packages.
- Add a new optional field to
dotdot.json(root + repo-level):sharedDeps: map of package name to version range
dotdot syncwrites adotdot.shared-deps.jsonmanifest in the workspace root.dotdot installinstalls shared deps into a workspace-level store:- e.g.
.dotdot/shared-node-modules/node_modules/<package>
- e.g.
dotdot execinjectsNODE_PATHpointing to the shared store so Node/Bun resolve the shared deps first.dotdot execoptionally enforces that shared deps are NOT installed in peer repos (detect and warn).
- Must work with ESM resolution for Node and Bun.
- Must ensure a single physical install path for singleton-sensitive packages (Playwright).
- Must keep peer repo installs isolated for all other deps.
NODE_OPTIONS=--preserve-symlinksfails for TS sources undernode_modules.- Playwright guard cannot be disabled; single physical path is required.
- Bun recommends isolated installs in dotdot setups (no auto-hoisting).
- Does Playwright’s loader honor
NODE_PATHfor ESM imports when run viabunx playwright? - Can dotdot inject
NODE_PATHconsistently for all test/dev commands (including IDE integrations)? - Should shared deps be installed per workspace or per repo group?
- How should dotdot handle version conflicts across repos for shared deps?
- How do we ensure shared deps are never duplicated in peer repos (warn vs auto-prune)?
- Install shared packages as tarballs/registry packages (avoid
file:symlink resolution). - Inline Playwright config in consumers (avoid shared helper import).
- Playwright guard implementation: https://github.com/microsoft/playwright/blob/37d58bd440ea06966c98508714854563db46df0a/packages/playwright/src/index.ts#L25-L43