bahmutov / bahmutov/cypress-split
Empty spec location outside project tree breaks tsconfig path aliases in supportFile
- Dominant language
- JavaScript
- Stars
- 285
- Forks
- 28
- PR merge metrics
- No merged PRs in 30d
Description
### Environment
- cypress-split: 1.24.31
- Cypress: 15.17.0 (batteries-included webpack preprocessor)
- Node: v22.22.3
- TypeScript: 5.9.3
- Monorepo (Nx) with TS path aliases declared in a root tsconfig.base.json, extended by the app's tsconfig.json
### What happens
When a split chunk gets no specs, cypress-split falls back to copying its empty placeholder spec into the OS temp dir:
cypress-split: chunk 2 of 8
cypress-split: no specs to run, running an empty spec file /tmp/empty-2-of-8.cy.js
Cypress then still loads the project's supportFile (src/support/e2e.ts) before that empty spec. The support file uses TS path aliases:
```typescript
import '@app/e2e-support/setup';
import './commands'; // -> import '@app/e2e-support/commands/global';
````
Bundling fails:
```sh
Oops...we found an error preparing this test file:
> src/support/e2e.ts
Error: Webpack Compilation Error
Module not found: Error: Can't resolve '@app/e2e-support/commands/global' ...
Module not found: Error: Can't resolve '@app/e2e-support/setup' ...
```
The exact same run works fine on any chunk that does receive specs.
Root cause
The empty spec is written to a emp diretorcy outside the cypress project folder
// src/index.js
```javascript
const tempFilename = path.join(
os.tmpdir(),
`empty-${splitIndex + 1}-of-${splitN}.cy.js`,
)
const emptyFilename = path.resolve(__dirname, 'empty-spec.cy.js')
fs.copyFileSync(emptyFilename, tempFilename)
config.specPattern = tempFilename
```
Cypress's batteries-included webpack preprocessor resolves the tsconfig.json relative to the spec file's path (via get-tsconfig, walking up from file.filePath). It only registers tsconfig-paths-webpack-plugin when a tsconfig is found:
```javascript
const configFile = getTsconfig.getTsconfig(file.filePath);
// ...
if (configFile?.path) {
webpackOptions.resolve.plugins = [new TsconfigPathsPlugin({ configFile: configFile.path, silent: true })];
}
```
Because the spec lives in /tmp, no tsconfig.json is found in its parent hierarchy, so configFile is null and the paths plugin is never registered. The support file (which is inside the project and relies on those aliases) is bundled in that
same compilation and its @app/* imports fail to resolve
This happens since the update from cypress 15.14.2 to any cypress version >=15.15.0
### Workaround
A workaround is to manually specify where to find the tsconfig file:
// cypress.config.ts
```typescript
import webpackPreprocessor from '@cypress/webpack-preprocessor';
import { defineConfig } from 'cypress';
import cypressSplit from 'cypress-split';
import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin';
const tsConfigFile = path.resolve(__dirname, 'tsconfig.json');
export default defineConfig({
allowCypressEnv: false,
e2e: {
async setupNodeEvents(on, config) {
on(
'file:preprocessor',
webpackPreprocessor({
webpackOptions: {
resolve: {
extensions: ['.ts', '.tsx', '.js'],
plugins: [new TsconfigPathsPlugin({ configFile: tsConfigFile })],
},
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: [
{
loader: 'ts-loader',
options: {
configFile: tsConfigFile,
transpileOnly: true,
},
},
],
},
],
},
},
}),
);
cypressSplit(on, config);
return config;
},
},
});
```
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in src/index.js at the empty-spec fallback that copies empty-spec.cy.js into the OS temp directory. Reproduce with Cypress 15.15 or newer in a TypeScript project using path aliases, then inspect how an empty chunk is preprocessed alongside src/support/e2e.ts. Done means empty chunks can bundle the support file and resolve its aliases without the manual webpack workaround.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cypress, javascript, typescript, webpack
- Domain
- testing-qa
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 62/100