feat(testing): add convert-to-inferred migration generator for cypress (#22884)
This commit is contained in:
parent
4ef832f4d5
commit
44820f2c4b
@ -7048,6 +7048,14 @@
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "convert-to-inferred",
|
||||
"path": "/nx-api/cypress/generators/convert-to-inferred",
|
||||
"name": "convert-to-inferred",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
|
||||
@ -545,6 +545,15 @@
|
||||
"originalFilePath": "/packages/cypress/src/generators/migrate-to-cypress-11/schema.json",
|
||||
"path": "/nx-api/cypress/generators/migrate-to-cypress-11",
|
||||
"type": "generator"
|
||||
},
|
||||
"/nx-api/cypress/generators/convert-to-inferred": {
|
||||
"description": "Convert existing Cypress project(s) using `@nx/cypress:cypress` executor to use `@nx/cypress/plugin`.",
|
||||
"file": "generated/packages/cypress/generators/convert-to-inferred.json",
|
||||
"hidden": false,
|
||||
"name": "convert-to-inferred",
|
||||
"originalFilePath": "/packages/cypress/src/generators/convert-to-inferred/schema.json",
|
||||
"path": "/nx-api/cypress/generators/convert-to-inferred",
|
||||
"type": "generator"
|
||||
}
|
||||
},
|
||||
"path": "/nx-api/cypress"
|
||||
|
||||
@ -537,6 +537,15 @@
|
||||
"originalFilePath": "/packages/cypress/src/generators/migrate-to-cypress-11/schema.json",
|
||||
"path": "cypress/generators/migrate-to-cypress-11",
|
||||
"type": "generator"
|
||||
},
|
||||
{
|
||||
"description": "Convert existing Cypress project(s) using `@nx/cypress:cypress` executor to use `@nx/cypress/plugin`.",
|
||||
"file": "generated/packages/cypress/generators/convert-to-inferred.json",
|
||||
"hidden": false,
|
||||
"name": "convert-to-inferred",
|
||||
"originalFilePath": "/packages/cypress/src/generators/convert-to-inferred/schema.json",
|
||||
"path": "cypress/generators/convert-to-inferred",
|
||||
"type": "generator"
|
||||
}
|
||||
],
|
||||
"githubRoot": "https://github.com/nrwl/nx/blob/master",
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "convert-to-inferred",
|
||||
"factory": "./src/generators/convert-to-inferred/convert-to-inferred",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/schema",
|
||||
"$id": "NxCypressConvertToInferred",
|
||||
"description": "Convert existing Cypress project(s) using `@nx/cypress:cypress` executor to use `@nx/cypress/plugin`.",
|
||||
"title": "Convert Cypress project from executor to plugin",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The project to convert from using the `@nx/cypress:cypress` executor to use `@nx/cypress/plugin`.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"skipFormat": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to format files at the end of the migration.",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"presets": []
|
||||
},
|
||||
"description": "Convert existing Cypress project(s) using `@nx/cypress:cypress` executor to use `@nx/cypress/plugin`.",
|
||||
"implementation": "/packages/cypress/src/generators/convert-to-inferred/convert-to-inferred.ts",
|
||||
"aliases": [],
|
||||
"hidden": false,
|
||||
"path": "/packages/cypress/src/generators/convert-to-inferred/schema.json",
|
||||
"type": "generator"
|
||||
}
|
||||
@ -372,6 +372,7 @@
|
||||
- [configuration](/nx-api/cypress/generators/configuration)
|
||||
- [component-configuration](/nx-api/cypress/generators/component-configuration)
|
||||
- [migrate-to-cypress-11](/nx-api/cypress/generators/migrate-to-cypress-11)
|
||||
- [convert-to-inferred](/nx-api/cypress/generators/convert-to-inferred)
|
||||
- [detox](/nx-api/detox)
|
||||
- [documents](/nx-api/detox/documents)
|
||||
- [Overview](/nx-api/detox/documents/overview)
|
||||
|
||||
@ -32,6 +32,11 @@
|
||||
"factory": "./src/generators/migrate-to-cypress-11/migrate-to-cypress-11#migrateCypressProject",
|
||||
"schema": "./src/generators/migrate-to-cypress-11/schema.json",
|
||||
"description": "Migrate existing Cypress e2e projects to Cypress v11"
|
||||
},
|
||||
"convert-to-inferred": {
|
||||
"factory": "./src/generators/convert-to-inferred/convert-to-inferred",
|
||||
"schema": "./src/generators/convert-to-inferred/schema.json",
|
||||
"description": "Convert existing Cypress project(s) using `@nx/cypress:cypress` executor to use `@nx/cypress/plugin`."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,465 @@
|
||||
import {
|
||||
getRelativeProjectJsonSchemaPath,
|
||||
updateProjectConfiguration,
|
||||
} from 'nx/src/generators/utils/project-configuration';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { convertToInferred } from './convert-to-inferred';
|
||||
import {
|
||||
addProjectConfiguration as _addProjectConfiguration,
|
||||
type ExpandedPluginConfiguration,
|
||||
joinPathFragments,
|
||||
type ProjectConfiguration,
|
||||
type ProjectGraph,
|
||||
readNxJson,
|
||||
readProjectConfiguration,
|
||||
type Tree,
|
||||
updateNxJson,
|
||||
writeJson,
|
||||
} from '@nx/devkit';
|
||||
import { TempFs } from '@nx/devkit/internal-testing-utils';
|
||||
import { join } from 'node:path';
|
||||
|
||||
let fs: TempFs;
|
||||
|
||||
let projectGraph: ProjectGraph;
|
||||
jest.mock('@nx/devkit', () => ({
|
||||
...jest.requireActual<any>('@nx/devkit'),
|
||||
createProjectGraphAsync: jest.fn().mockImplementation(async () => {
|
||||
return projectGraph;
|
||||
}),
|
||||
updateProjectConfiguration: jest
|
||||
.fn()
|
||||
.mockImplementation((tree, projectName, projectConfiguration) => {
|
||||
function handleEmptyTargets(
|
||||
projectName: string,
|
||||
projectConfiguration: ProjectConfiguration
|
||||
): void {
|
||||
if (
|
||||
projectConfiguration.targets &&
|
||||
!Object.keys(projectConfiguration.targets).length
|
||||
) {
|
||||
// Re-order `targets` to appear after the `// target` comment.
|
||||
delete projectConfiguration.targets;
|
||||
projectConfiguration[
|
||||
'// targets'
|
||||
] = `to see all targets run: nx show project ${projectName} --web`;
|
||||
projectConfiguration.targets = {};
|
||||
} else {
|
||||
delete projectConfiguration['// targets'];
|
||||
}
|
||||
}
|
||||
|
||||
const projectConfigFile = joinPathFragments(
|
||||
projectConfiguration.root,
|
||||
'project.json'
|
||||
);
|
||||
|
||||
if (!tree.exists(projectConfigFile)) {
|
||||
throw new Error(
|
||||
`Cannot update Project ${projectName} at ${projectConfiguration.root}. It either doesn't exist yet, or may not use project.json for configuration. Use \`addProjectConfiguration()\` instead if you want to create a new project.`
|
||||
);
|
||||
}
|
||||
handleEmptyTargets(projectName, projectConfiguration);
|
||||
writeJson(tree, projectConfigFile, {
|
||||
name: projectConfiguration.name ?? projectName,
|
||||
$schema: getRelativeProjectJsonSchemaPath(tree, projectConfiguration),
|
||||
...projectConfiguration,
|
||||
root: undefined,
|
||||
});
|
||||
projectGraph.nodes[projectName].data = projectConfiguration;
|
||||
}),
|
||||
}));
|
||||
|
||||
function addProjectConfiguration(
|
||||
tree: Tree,
|
||||
name: string,
|
||||
project: ProjectConfiguration
|
||||
) {
|
||||
_addProjectConfiguration(tree, name, project);
|
||||
projectGraph.nodes[name] = {
|
||||
name: name,
|
||||
type: project.projectType === 'application' ? 'app' : 'lib',
|
||||
data: {
|
||||
projectType: project.projectType,
|
||||
root: project.root,
|
||||
targets: project.targets,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
interface CreateCypressTestProjectOptions {
|
||||
appName: string;
|
||||
appRoot: string;
|
||||
e2eTargetName: string;
|
||||
}
|
||||
|
||||
const defaultCreateCypressTestProjectOptions: CreateCypressTestProjectOptions =
|
||||
{
|
||||
appName: 'myapp-e2e',
|
||||
appRoot: 'myapp-e2e',
|
||||
e2eTargetName: 'e2e',
|
||||
};
|
||||
|
||||
function createTestProject(
|
||||
tree: Tree,
|
||||
opts: Partial<CreateCypressTestProjectOptions> = defaultCreateCypressTestProjectOptions
|
||||
) {
|
||||
let projectOpts = { ...defaultCreateCypressTestProjectOptions, ...opts };
|
||||
const project: ProjectConfiguration = {
|
||||
name: projectOpts.appName,
|
||||
root: projectOpts.appRoot,
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
[projectOpts.e2eTargetName]: {
|
||||
executor: '@nx/cypress:cypress',
|
||||
options: {
|
||||
cypressConfig: `${projectOpts.appRoot}/cypress.config.ts`,
|
||||
testingType: `e2e`,
|
||||
devServerTarget: 'myapp:serve',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const cypressConfigContents = `import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src' }),
|
||||
baseUrl: 'http://localhost:4200',
|
||||
},
|
||||
});`;
|
||||
|
||||
tree.write(`${projectOpts.appRoot}/cypress.config.ts`, cypressConfigContents);
|
||||
fs.createFileSync(
|
||||
`${projectOpts.appRoot}/cypress.config.ts`,
|
||||
cypressConfigContents
|
||||
);
|
||||
jest.doMock(
|
||||
join(fs.tempDir, `${projectOpts.appRoot}/cypress.config.ts`),
|
||||
() => ({
|
||||
default: {
|
||||
e2e: {
|
||||
baseUrl: 'http://localhost:4200',
|
||||
},
|
||||
},
|
||||
}),
|
||||
{
|
||||
virtual: true,
|
||||
}
|
||||
);
|
||||
|
||||
addProjectConfiguration(tree, project.name, project);
|
||||
fs.createFileSync(
|
||||
`${projectOpts.appRoot}/project.json`,
|
||||
JSON.stringify(project)
|
||||
);
|
||||
return project;
|
||||
}
|
||||
|
||||
describe('Cypress - Convert Executors To Plugin', () => {
|
||||
let tree: Tree;
|
||||
|
||||
beforeEach(() => {
|
||||
fs = new TempFs('cypress');
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
tree.root = fs.tempDir;
|
||||
|
||||
projectGraph = {
|
||||
nodes: {},
|
||||
dependencies: {},
|
||||
externalNodes: {},
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.reset();
|
||||
});
|
||||
|
||||
describe('--project', () => {
|
||||
it('should setup a new Cypress plugin and only migrate one specific project', async () => {
|
||||
// ARRANGE
|
||||
const existingProject = createTestProject(tree, {
|
||||
appRoot: 'existing',
|
||||
appName: 'existing',
|
||||
e2eTargetName: 'e2e',
|
||||
});
|
||||
const project = createTestProject(tree, {
|
||||
e2eTargetName: 'test',
|
||||
});
|
||||
const secondProject = createTestProject(tree, {
|
||||
appRoot: 'second',
|
||||
appName: 'second',
|
||||
e2eTargetName: 'test',
|
||||
});
|
||||
const thirdProject = createTestProject(tree, {
|
||||
appRoot: 'third',
|
||||
appName: 'third',
|
||||
e2eTargetName: 'integration',
|
||||
});
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/cypress/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'e2e-ci',
|
||||
},
|
||||
});
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
// ACT
|
||||
await convertToInferred(tree, { project: 'myapp-e2e', skipFormat: true });
|
||||
|
||||
// ASSERT
|
||||
// project.json modifications
|
||||
const updatedProject = readProjectConfiguration(tree, project.name);
|
||||
const targetKeys = Object.keys(updatedProject.targets);
|
||||
['test'].forEach((key) => expect(targetKeys).not.toContain(key));
|
||||
|
||||
// nx.json modifications
|
||||
const nxJsonPlugins = readNxJson(tree).plugins;
|
||||
const addedTestCypressPlugin = nxJsonPlugins.find((plugin) => {
|
||||
if (
|
||||
typeof plugin !== 'string' &&
|
||||
plugin.plugin === '@nx/cypress/plugin' &&
|
||||
plugin.include?.length === 1
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
expect(addedTestCypressPlugin).toBeTruthy();
|
||||
expect(
|
||||
(addedTestCypressPlugin as ExpandedPluginConfiguration).include
|
||||
).toEqual(['myapp-e2e/**/*']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('--all', () => {
|
||||
it('should successfully migrate a project using Cypress executors to plugin', async () => {
|
||||
const project = createTestProject(tree);
|
||||
|
||||
// ACT
|
||||
await convertToInferred(tree, { skipFormat: true });
|
||||
|
||||
// ASSERT
|
||||
// project.json modifications
|
||||
const updatedProject = readProjectConfiguration(tree, project.name);
|
||||
const targetKeys = Object.keys(updatedProject.targets);
|
||||
expect(targetKeys).not.toContain('e2e');
|
||||
|
||||
// nx.json modifications
|
||||
const nxJsonPlugins = readNxJson(tree).plugins;
|
||||
const hasCypressPlugin = nxJsonPlugins.find((plugin) =>
|
||||
typeof plugin === 'string'
|
||||
? plugin === '@nx/cypress/plugin'
|
||||
: plugin.plugin === '@nx/cypress/plugin'
|
||||
);
|
||||
expect(hasCypressPlugin).toBeTruthy();
|
||||
if (typeof hasCypressPlugin !== 'string') {
|
||||
[
|
||||
['targetName', 'e2e'],
|
||||
['ciTargetName', 'e2e-ci'],
|
||||
].forEach(([targetOptionName, targetName]) => {
|
||||
expect(hasCypressPlugin.options[targetOptionName]).toEqual(
|
||||
targetName
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should setup Cypress plugin to match projects', async () => {
|
||||
// ARRANGE
|
||||
const project = createTestProject(tree, {
|
||||
e2eTargetName: 'test',
|
||||
});
|
||||
|
||||
// ACT
|
||||
await convertToInferred(tree, { skipFormat: true });
|
||||
|
||||
// ASSERT
|
||||
// project.json modifications
|
||||
const updatedProject = readProjectConfiguration(tree, project.name);
|
||||
const targetKeys = Object.keys(updatedProject.targets);
|
||||
['test'].forEach((key) => expect(targetKeys).not.toContain(key));
|
||||
|
||||
// nx.json modifications
|
||||
const nxJsonPlugins = readNxJson(tree).plugins;
|
||||
const hasCypressPlugin = nxJsonPlugins.find((plugin) =>
|
||||
typeof plugin === 'string'
|
||||
? plugin === '@nx/cypress/plugin'
|
||||
: plugin.plugin === '@nx/cypress/plugin'
|
||||
);
|
||||
expect(hasCypressPlugin).toBeTruthy();
|
||||
if (typeof hasCypressPlugin !== 'string') {
|
||||
[
|
||||
['targetName', 'test'],
|
||||
['ciTargetName', 'e2e-ci'],
|
||||
].forEach(([targetOptionName, targetName]) => {
|
||||
expect(hasCypressPlugin.options[targetOptionName]).toEqual(
|
||||
targetName
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should setup a new Cypress plugin to match only projects migrated', async () => {
|
||||
// ARRANGE
|
||||
const existingProject = createTestProject(tree, {
|
||||
appRoot: 'existing',
|
||||
appName: 'existing',
|
||||
e2eTargetName: 'e2e',
|
||||
});
|
||||
const project = createTestProject(tree, {
|
||||
e2eTargetName: 'test',
|
||||
});
|
||||
const secondProject = createTestProject(tree, {
|
||||
appRoot: 'second',
|
||||
appName: 'second',
|
||||
e2eTargetName: 'test',
|
||||
});
|
||||
const thirdProject = createTestProject(tree, {
|
||||
appRoot: 'third',
|
||||
appName: 'third',
|
||||
e2eTargetName: 'integration',
|
||||
});
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/cypress/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'e2e-ci',
|
||||
},
|
||||
});
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
// ACT
|
||||
await convertToInferred(tree, { skipFormat: true });
|
||||
|
||||
// ASSERT
|
||||
// project.json modifications
|
||||
const updatedProject = readProjectConfiguration(tree, project.name);
|
||||
const targetKeys = Object.keys(updatedProject.targets);
|
||||
['test'].forEach((key) => expect(targetKeys).not.toContain(key));
|
||||
|
||||
// nx.json modifications
|
||||
const nxJsonPlugins = readNxJson(tree).plugins;
|
||||
const addedTestCypressPlugin = nxJsonPlugins.find((plugin) => {
|
||||
if (
|
||||
typeof plugin !== 'string' &&
|
||||
plugin.plugin === '@nx/cypress/plugin' &&
|
||||
plugin.include?.length === 2
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
expect(addedTestCypressPlugin).toBeTruthy();
|
||||
expect(
|
||||
(addedTestCypressPlugin as ExpandedPluginConfiguration).include
|
||||
).toEqual(['myapp-e2e/**/*', 'second/**/*']);
|
||||
|
||||
const addedIntegrationCypressPlugin = nxJsonPlugins.find((plugin) => {
|
||||
if (
|
||||
typeof plugin !== 'string' &&
|
||||
plugin.plugin === '@nx/cypress/plugin' &&
|
||||
plugin.include?.length === 1
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
expect(addedIntegrationCypressPlugin).toBeTruthy();
|
||||
expect(
|
||||
(addedIntegrationCypressPlugin as ExpandedPluginConfiguration).include
|
||||
).toEqual(['third/**/*']);
|
||||
});
|
||||
|
||||
it('should keep Cypress options in project.json', async () => {
|
||||
// ARRANGE
|
||||
const project = createTestProject(tree);
|
||||
project.targets.e2e.options.runnerUi = true;
|
||||
updateProjectConfiguration(tree, project.name, project);
|
||||
|
||||
// ACT
|
||||
await convertToInferred(tree, { skipFormat: true });
|
||||
|
||||
// ASSERT
|
||||
// project.json modifications
|
||||
const updatedProject = readProjectConfiguration(tree, project.name);
|
||||
expect(updatedProject.targets.e2e).toMatchInlineSnapshot(`
|
||||
{
|
||||
"options": {
|
||||
"runner-ui": true,
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
// nx.json modifications
|
||||
const nxJsonPlugins = readNxJson(tree).plugins;
|
||||
const hasCypressPlugin = nxJsonPlugins.find((plugin) =>
|
||||
typeof plugin === 'string'
|
||||
? plugin === '@nx/cypress/plugin'
|
||||
: plugin.plugin === '@nx/cypress/plugin'
|
||||
);
|
||||
expect(hasCypressPlugin).toBeTruthy();
|
||||
if (typeof hasCypressPlugin !== 'string') {
|
||||
[
|
||||
['targetName', 'e2e'],
|
||||
['ciTargetName', 'e2e-ci'],
|
||||
].forEach(([targetOptionName, targetName]) => {
|
||||
expect(hasCypressPlugin.options[targetOptionName]).toEqual(
|
||||
targetName
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should add Cypress options found in targetDefaults for the executor to the project.json', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.targetDefaults ??= {};
|
||||
nxJson.targetDefaults['@nx/cypress:cypress'] = {
|
||||
options: {
|
||||
exit: false,
|
||||
},
|
||||
};
|
||||
updateNxJson(tree, nxJson);
|
||||
const project = createTestProject(tree);
|
||||
|
||||
// ACT
|
||||
await convertToInferred(tree, { skipFormat: true });
|
||||
|
||||
// ASSERT
|
||||
// project.json modifications
|
||||
const updatedProject = readProjectConfiguration(tree, project.name);
|
||||
expect(updatedProject.targets.e2e).toMatchInlineSnapshot(`
|
||||
{
|
||||
"options": {
|
||||
"no-exit": true,
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
// nx.json modifications
|
||||
const nxJsonPlugins = readNxJson(tree).plugins;
|
||||
const hasCypressPlugin = nxJsonPlugins.find((plugin) =>
|
||||
typeof plugin === 'string'
|
||||
? plugin === '@nx/cypress/plugin'
|
||||
: plugin.plugin === '@nx/cypress/plugin'
|
||||
);
|
||||
expect(hasCypressPlugin).toBeTruthy();
|
||||
if (typeof hasCypressPlugin !== 'string') {
|
||||
[
|
||||
['targetName', 'e2e'],
|
||||
['ciTargetName', 'e2e-ci'],
|
||||
].forEach(([targetOptionName, targetName]) => {
|
||||
expect(hasCypressPlugin.options[targetOptionName]).toEqual(
|
||||
targetName
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,136 @@
|
||||
import {
|
||||
CreateNodesContext,
|
||||
createProjectGraphAsync,
|
||||
formatFiles,
|
||||
joinPathFragments,
|
||||
type TargetConfiguration,
|
||||
type Tree,
|
||||
} from '@nx/devkit';
|
||||
import { migrateExecutorToPlugin } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
|
||||
import { createNodes } from '../../plugins/plugin';
|
||||
import { targetOptionsToCliMap } from './lib/target-options-map';
|
||||
import { upsertBaseUrl } from './lib/upsert-baseUrl';
|
||||
import { addDevServerTargetToConfig } from './lib/add-dev-server-target-to-config';
|
||||
import { addExcludeSpecPattern } from './lib/add-exclude-spec-pattern';
|
||||
|
||||
interface Schema {
|
||||
project?: string;
|
||||
all?: boolean;
|
||||
skipFormat?: boolean;
|
||||
}
|
||||
|
||||
export async function convertToInferred(tree: Tree, options: Schema) {
|
||||
const projectGraph = await createProjectGraphAsync();
|
||||
await migrateExecutorToPlugin(
|
||||
tree,
|
||||
projectGraph,
|
||||
'@nx/cypress:cypress',
|
||||
'@nx/cypress/plugin',
|
||||
(targetName) => ({
|
||||
targetName,
|
||||
ciTargetName: 'e2e-ci',
|
||||
}),
|
||||
postTargetTransformer,
|
||||
createNodes,
|
||||
options.project
|
||||
);
|
||||
|
||||
if (!options.skipFormat) {
|
||||
await formatFiles(tree);
|
||||
}
|
||||
}
|
||||
|
||||
function postTargetTransformer(
|
||||
target: TargetConfiguration,
|
||||
tree: Tree
|
||||
): TargetConfiguration {
|
||||
if (target.options) {
|
||||
const configFilePath = target.options.cypressConfig;
|
||||
|
||||
delete target.options.cypressConfig;
|
||||
delete target.options.copyFiles;
|
||||
delete target.options.skipServe;
|
||||
|
||||
for (const key in targetOptionsToCliMap) {
|
||||
if (target.options[key]) {
|
||||
target.options[targetOptionsToCliMap[key]] = target.options[key];
|
||||
delete target.options[key];
|
||||
}
|
||||
}
|
||||
|
||||
if ('exit' in target.options && !target.options.exit) {
|
||||
delete target.options.exit;
|
||||
target.options['no-exit'] = true;
|
||||
}
|
||||
|
||||
if (target.options.testingType) {
|
||||
delete target.options.testingType;
|
||||
}
|
||||
|
||||
if (target.options.watch) {
|
||||
target.options.headed = true;
|
||||
target.options['no-exit'] = true;
|
||||
delete target.options.watch;
|
||||
}
|
||||
|
||||
if (target.options.baseUrl) {
|
||||
upsertBaseUrl(tree, configFilePath, target.options.baseUrl);
|
||||
delete target.options.baseUrl;
|
||||
}
|
||||
|
||||
if (target.options.devServerTarget) {
|
||||
const webServerCommands: Record<string, string> = {
|
||||
default: `npx nx run ${target.options.devServerTarget}`,
|
||||
};
|
||||
delete target.options.devServerTarget;
|
||||
|
||||
if (target.configurations) {
|
||||
for (const configuration in target.configurations) {
|
||||
if (target.configurations[configuration]?.devServerTarget) {
|
||||
webServerCommands[
|
||||
configuration
|
||||
] = `npx nx run ${target.configurations[configuration].devServerTarget}`;
|
||||
delete target.configurations[configuration].devServerTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addDevServerTargetToConfig(
|
||||
tree,
|
||||
configFilePath,
|
||||
webServerCommands,
|
||||
target.configurations?.ci?.devServerTarget
|
||||
);
|
||||
}
|
||||
|
||||
if (target.options.ignoreTestFiles) {
|
||||
addExcludeSpecPattern(
|
||||
tree,
|
||||
configFilePath,
|
||||
target.options.ignoreTestFiles
|
||||
);
|
||||
delete target.options.ignoreTestFiles;
|
||||
}
|
||||
|
||||
if (Object.keys(target.options).length === 0) {
|
||||
delete target.options;
|
||||
}
|
||||
if (
|
||||
target.configurations &&
|
||||
Object.keys(target.configurations).length !== 0
|
||||
) {
|
||||
for (const configuration in target.configurations) {
|
||||
if (Object.keys(target.configurations[configuration]).length === 0) {
|
||||
delete target.configurations[configuration];
|
||||
}
|
||||
}
|
||||
if (Object.keys(target.configurations).length === 0) {
|
||||
delete target.configurations;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
export default convertToInferred;
|
||||
@ -0,0 +1,211 @@
|
||||
import type { Tree } from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from 'nx/src/devkit-testing-exports';
|
||||
import { addDevServerTargetToConfig } from './add-dev-server-target-to-config';
|
||||
|
||||
describe('addDevServerTargetToConfig', () => {
|
||||
let tree: Tree;
|
||||
const configFilePath = 'cypress.config.ts';
|
||||
const configFileContents = `import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src' }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
},
|
||||
});`;
|
||||
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
tree.write(configFilePath, configFileContents);
|
||||
});
|
||||
|
||||
describe('devServerTarget only', () => {
|
||||
it('should add webServerCommands when it does not exist', () => {
|
||||
// ACT
|
||||
addDevServerTargetToConfig(tree, configFilePath, {
|
||||
default: 'npx nx run myorg:serve',
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(configFilePath, 'utf-8')).toMatchInlineSnapshot(`
|
||||
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, {webServerCommands: {"default":"npx nx run myorg:serve"}, cypressDir: 'src' }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
},
|
||||
});"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should do nothing if the webServerCommands exists and matches the devServerTarget', () => {
|
||||
// ARRANGE
|
||||
tree.write(
|
||||
configFilePath,
|
||||
`import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src', webServerCommands: {default: "npx nx run myorg:serve"} }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
},
|
||||
});`
|
||||
);
|
||||
// ACT
|
||||
addDevServerTargetToConfig(tree, configFilePath, {
|
||||
default: 'npx nx run myorg:serve',
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(configFilePath, 'utf-8')).toMatchInlineSnapshot(`
|
||||
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src', webServerCommands: {"default":"npx nx run myorg:serve"} }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
},
|
||||
});"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should update the webServerCommands if it does not match', () => {
|
||||
// ARRANGE
|
||||
tree.write(
|
||||
configFilePath,
|
||||
`import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src', webServerCommands: {default: "npx nx run test:serve"} }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
},
|
||||
});`
|
||||
);
|
||||
// ACT
|
||||
addDevServerTargetToConfig(tree, configFilePath, {
|
||||
default: 'npx nx run myorg:serve',
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(configFilePath, 'utf-8')).toMatchInlineSnapshot(`
|
||||
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src', webServerCommands: {"default":"npx nx run myorg:serve"} }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
},
|
||||
});"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('devServerTarget and ci.devServerTarget', () => {
|
||||
it('should add webServerCommands and ciWebServerCommand when it does not exist', () => {
|
||||
// ACT
|
||||
addDevServerTargetToConfig(
|
||||
tree,
|
||||
configFilePath,
|
||||
{ default: 'npx nx run myorg:serve' },
|
||||
'myorg:static-serve'
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(configFilePath, 'utf-8')).toMatchInlineSnapshot(`
|
||||
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, {ciWebServerCommand: "npx nx run myorg:static-serve",webServerCommands: {"default":"npx nx run myorg:serve"}, cypressDir: 'src' }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
},
|
||||
});"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should do nothing if the webServerCommands and ciWebServerCommand exists and matches the devServerTarget', () => {
|
||||
// ARRANGE
|
||||
tree.write(
|
||||
configFilePath,
|
||||
`import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src', webServerCommands: {default: "npx nx run myorg:serve"}, ciWebServerCommand: "npx nx run myorg:static-serve" }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
},
|
||||
});`
|
||||
);
|
||||
// ACT
|
||||
addDevServerTargetToConfig(
|
||||
tree,
|
||||
configFilePath,
|
||||
{ default: 'npx nx run myorg:serve' },
|
||||
'myorg:static-serve'
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(configFilePath, 'utf-8')).toMatchInlineSnapshot(`
|
||||
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src', webServerCommands: {"default":"npx nx run myorg:serve"}, ciWebServerCommand: "npx nx run myorg:static-serve" }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
},
|
||||
});"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should update the webServerCommands and ciWebServerCommand if it does not match', () => {
|
||||
// ARRANGE
|
||||
tree.write(
|
||||
configFilePath,
|
||||
`import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src', webServerCommands: {default: "npx nx run test:serve"}, ciWebServerCommand: "npx nx run test:static-serve" }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
},
|
||||
});`
|
||||
);
|
||||
// ACT
|
||||
addDevServerTargetToConfig(
|
||||
tree,
|
||||
configFilePath,
|
||||
{
|
||||
default: 'npx nx run myorg:serve',
|
||||
production: 'npx nx run myorg:serve:production',
|
||||
ci: 'npx nx run myorg-static-serve',
|
||||
},
|
||||
'myorg:static-serve'
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(configFilePath, 'utf-8')).toMatchInlineSnapshot(`
|
||||
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src', webServerCommands: {"default":"npx nx run myorg:serve","production":"npx nx run myorg:serve:production","ci":"npx nx run myorg-static-serve"}, ciWebServerCommand: "npx nx run myorg:static-serve" }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
},
|
||||
});"
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,106 @@
|
||||
import type { Tree } from '@nx/devkit';
|
||||
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||
|
||||
/**
|
||||
* Add or update the webServerCommands and ciWebServerCommand options in the Cypress Config
|
||||
* Scenarios Covered:
|
||||
* 1. Only devServerTarget Exists
|
||||
* 2. devServerTarget and configuration.ci.devServerTarget Exists
|
||||
*
|
||||
* For each, the following scenarios are covered:
|
||||
* a. The command is not listed in the config, so it is added
|
||||
* b. Replace the existing webServerCommands with the value passed in
|
||||
*/
|
||||
export function addDevServerTargetToConfig(
|
||||
tree: Tree,
|
||||
configFilePath: string,
|
||||
webServerCommands: Record<string, string>,
|
||||
ciDevServerTarget?: string
|
||||
) {
|
||||
let configFileContents = tree.read(configFilePath, 'utf-8');
|
||||
|
||||
let ast = tsquery.ast(configFileContents);
|
||||
|
||||
const NX_E2E_PRESET_OPTIONS_SELECTOR =
|
||||
'PropertyAssignment:has(Identifier[name=e2e]) CallExpression:has(Identifier[name=nxE2EPreset]) > ObjectLiteralExpression';
|
||||
const nxE2ePresetOptionsNodes = tsquery(ast, NX_E2E_PRESET_OPTIONS_SELECTOR, {
|
||||
visitAllChildren: true,
|
||||
});
|
||||
if (nxE2ePresetOptionsNodes.length !== 0) {
|
||||
let nxE2ePresetOptionsNode = nxE2ePresetOptionsNodes[0];
|
||||
const WEB_SERVER_COMMANDS_SELECTOR =
|
||||
'PropertyAssignment:has(Identifier[name=webServerCommands])';
|
||||
const webServerCommandsNodes = tsquery(
|
||||
nxE2ePresetOptionsNode,
|
||||
WEB_SERVER_COMMANDS_SELECTOR,
|
||||
{ visitAllChildren: true }
|
||||
);
|
||||
if (webServerCommandsNodes.length !== 0) {
|
||||
// Already exists, replace it
|
||||
tree.write(
|
||||
configFilePath,
|
||||
`${configFileContents.slice(
|
||||
0,
|
||||
webServerCommandsNodes[0].getStart()
|
||||
)}webServerCommands: ${JSON.stringify(
|
||||
webServerCommands
|
||||
)}${configFileContents.slice(webServerCommandsNodes[0].getEnd())}`
|
||||
);
|
||||
} else {
|
||||
tree.write(
|
||||
configFilePath,
|
||||
`${configFileContents.slice(
|
||||
0,
|
||||
nxE2ePresetOptionsNode.getStart() + 1
|
||||
)}webServerCommands: ${JSON.stringify(
|
||||
webServerCommands
|
||||
)},${configFileContents.slice(nxE2ePresetOptionsNode.getStart() + 1)}`
|
||||
);
|
||||
}
|
||||
|
||||
if (ciDevServerTarget) {
|
||||
configFileContents = tree.read(configFilePath, 'utf-8');
|
||||
ast = tsquery.ast(configFileContents);
|
||||
nxE2ePresetOptionsNode = tsquery(ast, NX_E2E_PRESET_OPTIONS_SELECTOR, {
|
||||
visitAllChildren: true,
|
||||
})[0];
|
||||
|
||||
const CI_WEB_SERVER_COMMANDS_SELECTOR =
|
||||
'PropertyAssignment:has(Identifier[name=ciWebServerCommand])';
|
||||
const ciWebServerCommandsNodes = tsquery(
|
||||
nxE2ePresetOptionsNode,
|
||||
CI_WEB_SERVER_COMMANDS_SELECTOR,
|
||||
{ visitAllChildren: true }
|
||||
);
|
||||
|
||||
if (ciWebServerCommandsNodes.length !== 0) {
|
||||
const ciWebServerCommandNode =
|
||||
ciWebServerCommandsNodes[0].getChildAt(2);
|
||||
const ciWebServerCommand = ciWebServerCommandNode
|
||||
.getText()
|
||||
.replace(/["']/g, '');
|
||||
if (!ciWebServerCommand.includes(ciDevServerTarget)) {
|
||||
tree.write(
|
||||
configFilePath,
|
||||
`${configFileContents.slice(
|
||||
0,
|
||||
ciWebServerCommandNode.getStart()
|
||||
)}"npx nx run ${ciDevServerTarget}"${configFileContents.slice(
|
||||
ciWebServerCommandNode.getEnd()
|
||||
)}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
tree.write(
|
||||
configFilePath,
|
||||
`${configFileContents.slice(
|
||||
0,
|
||||
nxE2ePresetOptionsNode.getStart() + 1
|
||||
)}ciWebServerCommand: "npx nx run ${ciDevServerTarget}",${configFileContents.slice(
|
||||
nxE2ePresetOptionsNode.getStart() + 1
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,200 @@
|
||||
import type { Tree } from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from 'nx/src/devkit-testing-exports';
|
||||
import { addExcludeSpecPattern } from './add-exclude-spec-pattern';
|
||||
|
||||
describe('addExcludeSpecPattern', () => {
|
||||
let tree: Tree;
|
||||
const configFilePath = 'cypress.config.ts';
|
||||
const configFileContents = `import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src' }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
},
|
||||
});`;
|
||||
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
tree.write(configFilePath, configFileContents);
|
||||
});
|
||||
|
||||
it('should add excludeSpecPattern string if it does not exist', () => {
|
||||
// ACT
|
||||
addExcludeSpecPattern(tree, configFilePath, 'mytests/**/*.spec.ts');
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(configFilePath, 'utf-8')).toMatchInlineSnapshot(`
|
||||
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {excludeSpecPattern: "mytests/**/*.spec.ts",
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src' }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
},
|
||||
});"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should add excludeSpecPattern array if it does not exist', () => {
|
||||
// ACT
|
||||
addExcludeSpecPattern(tree, configFilePath, [
|
||||
'mytests/**/*.spec.ts',
|
||||
'mysecondtests/**/*.spec.ts',
|
||||
]);
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(configFilePath, 'utf-8')).toMatchInlineSnapshot(`
|
||||
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {excludeSpecPattern: ["mytests/**/*.spec.ts","mysecondtests/**/*.spec.ts"],
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src' }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
},
|
||||
});"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should update the existing excludeSpecPattern if one exists when using string', () => {
|
||||
// ARRANGE
|
||||
tree.write(
|
||||
configFilePath,
|
||||
`import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src' }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
excludeSpecPattern: "somefile.spec.ts"
|
||||
},
|
||||
});`
|
||||
);
|
||||
|
||||
// ACT
|
||||
addExcludeSpecPattern(tree, configFilePath, 'mytests/**/*.spec.ts');
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(configFilePath, 'utf-8')).toMatchInlineSnapshot(`
|
||||
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src' }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
excludeSpecPattern: ["mytests/**/*.spec.ts"]
|
||||
},
|
||||
});"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should update the existing excludeSpecPattern if one exists when using array', () => {
|
||||
// ARRANGE
|
||||
tree.write(
|
||||
configFilePath,
|
||||
`import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src' }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
excludeSpecPattern: ["somefile.spec.ts"]
|
||||
},
|
||||
});`
|
||||
);
|
||||
|
||||
// ACT
|
||||
addExcludeSpecPattern(tree, configFilePath, ['mytests/**/*.spec.ts']);
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(configFilePath, 'utf-8')).toMatchInlineSnapshot(`
|
||||
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src' }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
excludeSpecPattern: ["mytests/**/*.spec.ts"]
|
||||
},
|
||||
});"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should update the existing excludeSpecPattern if one exists when using string with an array of new options', () => {
|
||||
// ARRANGE
|
||||
tree.write(
|
||||
configFilePath,
|
||||
`import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src' }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
excludeSpecPattern: "somefile.spec.ts"
|
||||
},
|
||||
});`
|
||||
);
|
||||
|
||||
// ACT
|
||||
addExcludeSpecPattern(tree, configFilePath, [
|
||||
'mytests/**/*.spec.ts',
|
||||
'mysecondtests/**/*.spec.ts',
|
||||
]);
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(configFilePath, 'utf-8')).toMatchInlineSnapshot(`
|
||||
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src' }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
excludeSpecPattern: ["mytests/**/*.spec.ts","mysecondtests/**/*.spec.ts"]
|
||||
},
|
||||
});"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should update the existing excludeSpecPattern if one exists when using array with a new pattern string', () => {
|
||||
// ARRANGE
|
||||
tree.write(
|
||||
configFilePath,
|
||||
`import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src' }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
excludeSpecPattern: ["somefile.spec.ts"]
|
||||
},
|
||||
});`
|
||||
);
|
||||
|
||||
// ACT
|
||||
addExcludeSpecPattern(tree, configFilePath, 'mytests/**/*.spec.ts');
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(configFilePath, 'utf-8')).toMatchInlineSnapshot(`
|
||||
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src' }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
excludeSpecPattern: ["mytests/**/*.spec.ts"]
|
||||
},
|
||||
});"
|
||||
`);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,55 @@
|
||||
import type { Tree } from '@nx/devkit';
|
||||
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||
export function addExcludeSpecPattern(
|
||||
tree: Tree,
|
||||
configFilePath: string,
|
||||
excludeSpecPattern: string | string[]
|
||||
) {
|
||||
let configFileContents = tree.read(configFilePath, 'utf-8');
|
||||
|
||||
let ast = tsquery.ast(configFileContents);
|
||||
|
||||
const E2E_CONFIG_SELECTOR =
|
||||
'PropertyAssignment:has(Identifier[name=e2e]) > ObjectLiteralExpression';
|
||||
const e2eConfigNodes = tsquery(ast, E2E_CONFIG_SELECTOR, {
|
||||
visitAllChildren: true,
|
||||
});
|
||||
if (e2eConfigNodes.length !== 0) {
|
||||
const e2eConfigNode = e2eConfigNodes[0];
|
||||
const EXCLUDE_SPEC_PATTERN_SELECTOR =
|
||||
'PropertyAssignment:has(Identifier[name="excludeSpecPattern"])';
|
||||
const excludeSpecPatternNodes = tsquery(
|
||||
e2eConfigNode,
|
||||
EXCLUDE_SPEC_PATTERN_SELECTOR,
|
||||
{ visitAllChildren: true }
|
||||
);
|
||||
|
||||
if (excludeSpecPatternNodes.length !== 0) {
|
||||
const excludeSpecPatternNode = excludeSpecPatternNodes[0];
|
||||
|
||||
let updatedExcludePattern = Array.isArray(excludeSpecPattern)
|
||||
? excludeSpecPattern
|
||||
: [excludeSpecPattern];
|
||||
|
||||
tree.write(
|
||||
configFilePath,
|
||||
`${configFileContents.slice(
|
||||
0,
|
||||
excludeSpecPatternNode.getStart()
|
||||
)}excludeSpecPattern: ${JSON.stringify(
|
||||
updatedExcludePattern
|
||||
)}${configFileContents.slice(excludeSpecPatternNode.getEnd())}`
|
||||
);
|
||||
} else {
|
||||
tree.write(
|
||||
configFilePath,
|
||||
`${configFileContents.slice(
|
||||
0,
|
||||
e2eConfigNode.getStart() + 1
|
||||
)}excludeSpecPattern: ${JSON.stringify(
|
||||
excludeSpecPattern
|
||||
)},${configFileContents.slice(e2eConfigNode.getStart() + 1)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
export const targetOptionsToCliMap = {
|
||||
headed: 'headed',
|
||||
headless: 'headless',
|
||||
key: 'key',
|
||||
record: 'record',
|
||||
parallel: 'parallel',
|
||||
browser: 'browser',
|
||||
env: 'env',
|
||||
spec: 'spec',
|
||||
ciBuildId: 'ci-build-id',
|
||||
group: 'group',
|
||||
reporter: 'reporter',
|
||||
reporterOptions: 'reporter-options',
|
||||
tag: 'tag',
|
||||
port: 'port',
|
||||
quiet: 'quiet',
|
||||
runnerUi: 'runner-ui',
|
||||
};
|
||||
@ -0,0 +1,78 @@
|
||||
import type { Tree } from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from 'nx/src/devkit-testing-exports';
|
||||
import { upsertBaseUrl } from './upsert-baseUrl';
|
||||
|
||||
describe('upsertBaseUrl', () => {
|
||||
let tree: Tree;
|
||||
const configFilePath = 'cypress.config.ts';
|
||||
const configFileContents = `import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src' }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
},
|
||||
});`;
|
||||
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
tree.write(configFilePath, configFileContents);
|
||||
});
|
||||
|
||||
it('should do nothing if the baseUrl value exists and matches', () => {
|
||||
// ACT
|
||||
upsertBaseUrl(tree, configFilePath, 'http://localhost:4200');
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(configFilePath, 'utf-8')).toEqual(configFileContents);
|
||||
});
|
||||
|
||||
it('should update the config if the baseUrl value exists and does not match', () => {
|
||||
// ACT
|
||||
upsertBaseUrl(tree, configFilePath, 'http://localhost:4201');
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(configFilePath, 'utf-8')).toMatchInlineSnapshot(`
|
||||
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src' }),
|
||||
baseUrl: "http://localhost:4201",
|
||||
},
|
||||
});"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should add the baseUrl property if it does not exist', () => {
|
||||
// ARRANGE
|
||||
tree.write(
|
||||
configFilePath,
|
||||
`import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src' }),
|
||||
},
|
||||
});`
|
||||
);
|
||||
// ACT
|
||||
upsertBaseUrl(tree, configFilePath, 'http://localhost:4200');
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(configFilePath, 'utf-8')).toMatchInlineSnapshot(`
|
||||
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, { cypressDir: 'src' }),
|
||||
baseUrl: "http://localhost:4200",
|
||||
},
|
||||
});"
|
||||
`);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,56 @@
|
||||
import type { Tree } from '@nx/devkit';
|
||||
|
||||
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||
|
||||
export function upsertBaseUrl(
|
||||
tree: Tree,
|
||||
configFilePath: string,
|
||||
baseUrlValueInProject: string
|
||||
) {
|
||||
const configFileContents = tree.read(configFilePath, 'utf-8');
|
||||
|
||||
const ast = tsquery.ast(configFileContents);
|
||||
const BASE_URL_SELECTOR =
|
||||
'PropertyAssignment:has(Identifier[name=e2e]) PropertyAssignment:has(Identifier[name="baseUrl"])';
|
||||
|
||||
const baseUrlNodes = tsquery(ast, BASE_URL_SELECTOR, {
|
||||
visitAllChildren: true,
|
||||
});
|
||||
if (baseUrlNodes.length !== 0) {
|
||||
// The property exists in the config
|
||||
const baseUrlValueNode = baseUrlNodes[0].getChildAt(2);
|
||||
const baseUrlValue = baseUrlValueNode.getText().replace(/(["'])/, '');
|
||||
|
||||
if (baseUrlValue === baseUrlValueInProject) {
|
||||
return;
|
||||
}
|
||||
|
||||
tree.write(
|
||||
configFilePath,
|
||||
`${configFileContents.slice(
|
||||
0,
|
||||
baseUrlValueNode.getStart()
|
||||
)}"${baseUrlValueInProject}"${configFileContents.slice(
|
||||
baseUrlValueNode.getEnd()
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
const E2E_OBJECT_SELECTOR =
|
||||
'PropertyAssignment:has(Identifier[name=e2e]) ObjectLiteralExpression';
|
||||
|
||||
const e2eConfigNodes = tsquery(ast, E2E_OBJECT_SELECTOR, {
|
||||
visitAllChildren: true,
|
||||
});
|
||||
if (e2eConfigNodes.length !== 0) {
|
||||
const e2eConfigNode = e2eConfigNodes[0];
|
||||
tree.write(
|
||||
configFilePath,
|
||||
`${configFileContents.slice(
|
||||
0,
|
||||
e2eConfigNode.getEnd() - 1
|
||||
)}baseUrl: "${baseUrlValueInProject}",
|
||||
${configFileContents.slice(e2eConfigNode.getEnd() - 1)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/schema",
|
||||
"$id": "NxCypressConvertToInferred",
|
||||
"description": "Convert existing Cypress project(s) using `@nx/cypress:cypress` executor to use `@nx/cypress/plugin`.",
|
||||
"title": "Convert Cypress project from executor to plugin",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The project to convert from using the `@nx/cypress:cypress` executor to use `@nx/cypress/plugin`.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"skipFormat": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to format files at the end of the migration.",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -32,7 +32,8 @@ const {
|
||||
|
||||
type PluginOptionsBuilder<T> = (targetName: string) => T;
|
||||
type PostTargetTransformer = (
|
||||
targetConfiguration: TargetConfiguration
|
||||
targetConfiguration: TargetConfiguration,
|
||||
tree?: Tree
|
||||
) => TargetConfiguration;
|
||||
type SkipTargetFilter = (
|
||||
targetConfiguration: TargetConfiguration
|
||||
@ -129,7 +130,7 @@ class ExecutorToPluginMigrator<T> {
|
||||
delete projectTarget.executor;
|
||||
|
||||
deleteMatchingProperties(projectTarget, createdTarget);
|
||||
projectTarget = this.#postTargetTransformer(projectTarget);
|
||||
projectTarget = this.#postTargetTransformer(projectTarget, this.tree);
|
||||
|
||||
if (
|
||||
projectTarget.options &&
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user