Skip to content

Commit

Permalink
feat(testing): cypress using vite
Browse files Browse the repository at this point in the history
  • Loading branch information
mandarini committed Dec 6, 2022
1 parent 5d4ee02 commit 16c1c3a
Show file tree
Hide file tree
Showing 13 changed files with 218 additions and 13 deletions.
13 changes: 13 additions & 0 deletions docs/generated/packages/cypress.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,19 @@
"type": "boolean",
"default": false,
"description": "Do not add dependencies to `package.json`."
},
"rootProject": {
"description": "Create a application at the root of the workspace",
"type": "boolean",
"default": false,
"hidden": true
},
"bundler": {
"description": "The Cypress builder to use.",
"type": "string",
"enum": ["vite", "webpack", "none"],
"x-prompt": "Which Cypress builder do you want to use?",
"default": "webpack"
}
},
"required": ["name"],
Expand Down
6 changes: 5 additions & 1 deletion docs/generated/packages/storybook.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
},
"bundler": {
"description": "The bundler to use.",
"type": "string",
"enum": ["vite", "webpack"],
"x-prompt": "Which bundler do you want to use?",
"default": "webpack"
Expand Down Expand Up @@ -196,7 +197,10 @@
},
"bundler": {
"description": "The Storybook builder to use.",
"enum": ["vite", "webpack"]
"type": "string",
"enum": ["vite", "webpack"],
"x-prompt": "Which Storybook builder do you want to use?",
"default": "webpack"
}
},
"required": ["name"],
Expand Down
30 changes: 28 additions & 2 deletions packages/cypress/plugins/cypress-preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { workspaceRoot } from '@nrwl/devkit';
import { dirname, join, relative } from 'path';
import { lstatSync } from 'fs';

import path = require('path');
import vitePreprocessor from '../src/plugins/preprocessor-vite';

interface BaseCypressPreset {
videosFolder: string;
screenshotsFolder: string;
Expand Down Expand Up @@ -58,12 +61,35 @@ export function nxBaseCypressPreset(pathToConfig: string): BaseCypressPreset {
*
* @param pathToConfig will be used to construct the output paths for videos and screenshots
*/
export function nxE2EPreset(pathToConfig: string) {
return {
export function nxE2EPreset(
pathToConfig: string,
options?: { vite: { viteConfigPath: string } }
) {
const baseConfig = {
...nxBaseCypressPreset(pathToConfig),
fileServerFolder: '.',
supportFile: 'src/support/e2e.ts',
specPattern: 'src/**/*.cy.{js,jsx,ts,tsx}',
fixturesFolder: 'src/fixtures',
};

/**
* - Not necessary to have the path to vite config
* - vite.config if it exists it can be cypress-project specific
* - generate without path to vite-config, add in docs how user can provide it
* - add in the docs how to set up custom/more setupNodeEvents
*/

if (options.vite?.viteConfigPath) {
return {
...baseConfig,
setupNodeEvents(on) {
on(
'file:preprocessor',
vitePreprocessor(`${options.vite?.viteConfigPath}`)
);
},
};
}
return baseConfig;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
toJS,
Tree,
updateJson,
getProjects,
} from '@nrwl/devkit';
import { Linter, lintProjectGenerator } from '@nrwl/linter';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
Expand Down Expand Up @@ -49,6 +48,16 @@ function createFiles(tree: Tree, options: CypressProjectSchema) {
const cypressFiles =
cypressVersion && cypressVersion < 10 ? 'v9-and-under' : 'v10-and-after';

// I understand that this may seem like an overkill,
// but since we're giving the user the option to set their own vite config
// file, we need to make sure that we're referencing the correct file.
// We cannot assume the file name
const projectViteConfigFile = getViteConfigFileFullPath(
tree,
options.projectName.replace(`-e2e`, ``),
options.projectRoot
);

generateFiles(
tree,
join(__dirname, './files', cypressFiles),
Expand All @@ -63,6 +72,8 @@ function createFiles(tree: Tree, options: CypressProjectSchema) {
tree,
options.projectRoot
),
bundler: options.bundler,
projectViteConfigFile: projectViteConfigFile,
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,11 @@ import { defineConfig } from 'cypress';
import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset';

export default defineConfig({
e2e: nxE2EPreset(__dirname)
});
e2e: nxE2EPreset(__dirname<% if (bundler === 'vite'){ %>,
{
vite: {
viteConfigPath: '<%= cypressProjectSpecificViteConfigPath %>'
}
}
<% } %>)
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export interface Schema {
standaloneConfig?: boolean;
skipPackageJson?: boolean;
rootProject?: boolean;
bundler?: 'webpack' | 'vite' | 'none';
}
13 changes: 13 additions & 0 deletions packages/cypress/src/generators/cypress-project/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@
"type": "boolean",
"default": false,
"description": "Do not add dependencies to `package.json`."
},
"rootProject": {
"description": "Create a application at the root of the workspace",
"type": "boolean",
"default": false,
"hidden": true
},
"bundler": {
"description": "The Cypress builder to use.",
"type": "string",
"enum": ["vite", "webpack", "none"],
"x-prompt": "Which Cypress builder do you want to use?",
"default": "webpack"
}
},
"required": ["name"],
Expand Down
89 changes: 89 additions & 0 deletions packages/cypress/src/plugins/preprocessor-vite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// copied from https://github.com/mammadataei/cypress-vite

import * as path from 'path';
import { build, InlineConfig } from 'vite';
import type { RollupOutput, RollupWatcher, WatcherOptions } from 'rollup';

type CypressPreprocessor = (
file: Record<string, any>
) => string | Promise<string>;

/**
* Cypress preprocessor for running e2e tests using vite.
*
* @param {string} userConfigPath
* @example
* setupNodeEvents(on) {
* on(
* 'file:preprocessor',
* vitePreprocessor(path.resolve(__dirname, './vite.config.ts')),
* )
* },
*/
function vitePreprocessor(userConfigPath?: string): CypressPreprocessor {
return async (file) => {
const { outputPath, filePath, shouldWatch } = file;

const fileName = path.basename(outputPath);
const filenameWithoutExtension = path.basename(
outputPath,
path.extname(outputPath)
);

const defaultConfig: InlineConfig = {
logLevel: 'silent',
define: {
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
},
build: {
emptyOutDir: false,
minify: false,
outDir: path.dirname(outputPath),
sourcemap: true,
write: true,
watch: getWatcherConfig(shouldWatch),
lib: {
entry: filePath,
fileName: () => fileName,
formats: ['umd'],
name: filenameWithoutExtension,
},
},
};

const watcher = await build({
configFile: userConfigPath,
...defaultConfig,
});

if (shouldWatch && isWatcher(watcher)) {
watcher.on('event', (event) => {
if (event.code === 'END') {
file.emit('rerun');
}

if (event.code === 'ERROR') {
console.error(event);
}
});

file.on('close', () => {
watcher.close();
});
}

return outputPath;
};
}

function getWatcherConfig(shouldWatch: boolean): WatcherOptions | null {
return shouldWatch ? {} : null;
}

type BuildResult = RollupWatcher | RollupOutput | RollupOutput[];

function isWatcher(watcher: BuildResult): watcher is RollupWatcher {
return (watcher as RollupWatcher).on !== undefined;
}

export default vitePreprocessor;
5 changes: 4 additions & 1 deletion packages/storybook/src/generators/configuration/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@
},
"bundler": {
"description": "The Storybook builder to use.",
"enum": ["vite", "webpack"]
"type": "string",
"enum": ["vite", "webpack"],
"x-prompt": "Which Storybook builder do you want to use?",
"default": "webpack"
}
},
"required": ["name"],
Expand Down
1 change: 1 addition & 0 deletions packages/storybook/src/generators/init/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
},
"bundler": {
"description": "The bundler to use.",
"type": "string",
"enum": ["vite", "webpack"],
"x-prompt": "Which bundler do you want to use?",
"default": "webpack"
Expand Down
2 changes: 2 additions & 0 deletions packages/vite/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './src/utils/versions';
export * from './src/utils/generator-utils';
export * from './src/utils/options-utils';
export { viteConfigurationGenerator } from './src/generators/configuration/configuration';
export { vitestGenerator } from './src/generators/vitest/vitest-generator';
24 changes: 24 additions & 0 deletions packages/vite/src/utils/generator-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import {
TargetConfiguration,
Tree,
updateProjectConfiguration,
workspaceRoot,
writeJson,
} from '@nrwl/devkit';
import { ViteBuildExecutorOptions } from '../executors/build/schema';
import { ViteDevServerExecutorOptions } from '../executors/dev-server/schema';
import { VitestExecutorOptions } from '../executors/test/schema';
import { Schema } from '../generators/configuration/schema';
import { normalizeConfigFilePath } from './options-utils';

/**
* This function is used to find the build and serve targets for
Expand Down Expand Up @@ -475,3 +477,25 @@ ${options.includeVitest ? '/// <reference types="vitest" />' : ''}

tree.write(viteConfigPath, viteConfigContent);
}

export function getViteConfigFileFullPath(
tree: Tree,
projectName?: string,
projectRoot?: string
): string | undefined {
if (projectName) {
const projectConfig = readProjectConfiguration(tree, projectName);
const serveTarget = findExistingTargets(projectConfig.targets).serveTarget;
const projectViteConfigFileName =
projectConfig.targets[serveTarget].options.configFile;
return normalizeConfigFilePath(
projectViteConfigFileName,
workspaceRoot,
projectConfig.root,
tree
);
} else if (projectRoot) {
return normalizeConfigFilePath(undefined, workspaceRoot, projectRoot, tree);
}
return undefined;
}
24 changes: 18 additions & 6 deletions packages/vite/src/utils/options-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
logger,
parseTargetString,
readTargetOptions,
Tree,
} from '@nrwl/devkit';
import { existsSync } from 'fs';
import { join, relative } from 'path';
Expand Down Expand Up @@ -44,17 +45,28 @@ export async function getBuildAndSharedConfig(
} as InlineConfig);
}

// I understand that this may seem like an overkill,
// but since we're giving the user the option to set their own vite config
// file, we need to make sure that we're referencing the correct file.
// We cannot assume the file name
export function normalizeConfigFilePath(
configFile: string,
workspaceRoot: string,
projectRoot: string
projectRoot: string,
tree?: Tree
): string {
const customViteFilePath = joinPathFragments(
`${workspaceRoot}/${configFile}`
);
const jsViteFilePath = joinPathFragments(`${projectRoot}/vite.config.js`);
const tsViteFilePath = joinPathFragments(`${projectRoot}/vite.config.ts`);

return configFile
? joinPathFragments(`${workspaceRoot}/${configFile}`)
: existsSync(joinPathFragments(`${projectRoot}/vite.config.ts`))
? joinPathFragments(`${projectRoot}/vite.config.ts`)
: existsSync(joinPathFragments(`${projectRoot}/vite.config.js`))
? joinPathFragments(`${projectRoot}/vite.config.js`)
? customViteFilePath
: tree?.exists(tsViteFilePath) || existsSync(tsViteFilePath)
? tsViteFilePath
: tree?.exists(jsViteFilePath) || existsSync(jsViteFilePath)
? jsViteFilePath
: undefined;
}

Expand Down

0 comments on commit 16c1c3a

Please sign in to comment.