Skip to content

Conversation

@kaigritun
Copy link

Problem

When external ESM loaders like tsx/esm are used alongside Playwright (via import 'tsx/esm' in config), they intercept Playwright's .esm.preflight import requests and execute the actual test file content instead of returning empty content. This causes:

  • test() calls to run twice (once during preflight, once during actual import)
  • "duplicate test title" errors

Reproduction: https://github.com/Abhinegi2/playwright-issue

Solution

Check if the registration location file path ends with .esm.preflight in _currentSuite() and return undefined to skip registration. This ensures tests registered during external loader's preflight phase are silently ignored.

Testing

Verified the fix works by:

  1. Setting up reproduction case with tsx/esm
  2. Confirming error reproduces without fix
  3. Applying fix and confirming tests run correctly (1 test listed, passes)

Related

Fixes #39172

When external ESM loaders like tsx/esm intercept Playwright's
.esm.preflight imports, they may execute the actual test file
content instead of returning empty content. This causes duplicate
test registrations and 'duplicate test title' errors.

Fix by checking if the registration location has '.esm.preflight'
extension and returning early without registering the test.

Fixes microsoft#39172
private _currentSuite(location: Location, title: string): Suite | undefined {
// Skip registrations from ESM preflight imports. This can happen when external
// ESM loaders (like tsx/esm) intercept Playwright's .esm.preflight requests
// and execute the actual test file content instead of returning empty content.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do they resolve .esm.preflight to a test file? I would think they don't know what to do with it?

@kaigritun
Copy link
Author

Good question! The issue happens with loaders like tsx/esm that use an import hook pattern. When they see an import for foo.test.ts.esm.preflight, they:

  1. Strip the extension to find the base file (foo.test.ts)
  2. Resolve and execute that file instead
  3. Return the result (ignoring that .esm.preflight was supposed to be empty)

It's essentially an over-eager resolution - they treat the preflight request as 'just wanting the test file with a weird extension' rather than understanding it's a Playwright-specific signal.

I can add more detail to the comment if that helps clarify the edge case.

@kaigritun
Copy link
Author

Great question! The .esm.preflight suffix doesn't need to resolve to a test file - that's actually the point of the fix.

During ESM preflight loading, Node.js imports the test file to analyze its exports, but without setting up the test environment. The file extension check (sourceFile.endsWith('.esm.preflight')) detects this preflight context and skips test registration entirely.

The actual test execution happens later with the real file path (without the suffix), at which point registration proceeds normally. This prevents duplicate registrations that were causing the 'file not found' errors in the original issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Playwright 1.58.0 collects *.esm.preflight files as tests, causing duplicate test title errors

2 participants