feat(testing): use createNodesV2 for cypress and playwright (#26301)

This commit is contained in:
Jason Jean 2024-05-31 17:53:31 -04:00 committed by GitHub
parent 92718fd52b
commit 1e7cd7e9e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 591 additions and 469 deletions

View File

@ -1 +1 @@
export { createNodes, createDependencies } from './src/plugins/plugin';
export { createNodesV2, createNodes } from './src/plugins/plugin';

View File

@ -1,13 +1,11 @@
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 { createNodesV2 } 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';
@ -31,7 +29,7 @@ export async function convertToInferred(tree: Tree, options: Schema) {
ciTargetName: 'e2e-ci',
}),
postTargetTransformer,
createNodes,
createNodesV2,
options.project
);
@ -45,7 +43,7 @@ export async function convertToInferred(tree: Tree, options: Schema) {
ciTargetName: 'e2e-ci',
}),
postTargetTransformer,
createNodes,
createNodesV2,
options.project
);

View File

@ -10,14 +10,10 @@ import {
Tree,
updateNxJson,
} from '@nx/devkit';
import {
addPluginV1 as _addPlugin,
generateCombinations,
} from '@nx/devkit/src/utils/add-plugin';
import { createNodes } from '../../plugins/plugin';
import { addPlugin as _addPlugin } from '@nx/devkit/src/utils/add-plugin';
import { createNodesV2 } from '../../plugins/plugin';
import { cypressVersion, nxVersion } from '../../utils/versions';
import { Schema } from './schema';
import { CypressPluginOptions } from '../../plugins/plugin';
function setupE2ETargetDefaults(tree: Tree) {
const nxJson = readNxJson(tree);
@ -69,7 +65,7 @@ export function addPlugin(
tree,
graph,
'@nx/cypress/plugin',
createNodes,
createNodesV2,
{
targetName: ['e2e', 'cypress:e2e', 'cypress-e2e'],
openTargetName: ['open-cypress', 'cypress-open'],

View File

@ -1,14 +1,14 @@
import { CreateNodesContext } from '@nx/devkit';
import { defineConfig } from 'cypress';
import { createNodes } from './plugin';
import { createNodesV2 } from './plugin';
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
import { resetWorkspaceContext } from 'nx/src/utils/workspace-context';
import { join } from 'path';
import { nxE2EPreset } from '../../plugins/cypress-preset';
describe('@nx/cypress/plugin', () => {
let createNodesFunction = createNodes[1];
let createNodesFunction = createNodesV2[1];
let context: CreateNodesContext;
let tempFs: TempFs;
@ -65,7 +65,7 @@ describe('@nx/cypress/plugin', () => {
})
);
const nodes = await createNodesFunction(
'cypress.config.js',
['cypress.config.js'],
{
targetName: 'e2e',
},
@ -73,6 +73,9 @@ describe('@nx/cypress/plugin', () => {
);
expect(nodes).toMatchInlineSnapshot(`
[
[
"cypress.config.js",
{
"projects": {
".": {
@ -125,7 +128,9 @@ describe('@nx/cypress/plugin', () => {
},
},
},
}
},
],
]
`);
});
@ -143,7 +148,7 @@ describe('@nx/cypress/plugin', () => {
})
);
const nodes = await createNodesFunction(
'cypress.config.js',
['cypress.config.js'],
{
componentTestingTargetName: 'component-test',
},
@ -151,6 +156,9 @@ describe('@nx/cypress/plugin', () => {
);
expect(nodes).toMatchInlineSnapshot(`
[
[
"cypress.config.js",
{
"projects": {
".": {
@ -198,7 +206,9 @@ describe('@nx/cypress/plugin', () => {
},
},
},
}
},
],
]
`);
});
@ -220,7 +230,7 @@ describe('@nx/cypress/plugin', () => {
})
);
const nodes = await createNodesFunction(
'cypress.config.js',
['cypress.config.js'],
{
componentTestingTargetName: 'component-test',
},
@ -228,6 +238,9 @@ describe('@nx/cypress/plugin', () => {
);
expect(nodes).toMatchInlineSnapshot(`
[
[
"cypress.config.js",
{
"projects": {
".": {
@ -343,7 +356,9 @@ describe('@nx/cypress/plugin', () => {
},
},
},
}
},
],
]
`);
});

View File

@ -1,9 +1,11 @@
import {
CreateDependencies,
CreateNodes,
CreateNodesContext,
createNodesFromFiles,
CreateNodesV2,
detectPackageManager,
joinPathFragments,
logger,
normalizePath,
NxJsonConfiguration,
ProjectConfiguration,
@ -22,6 +24,7 @@ import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash
import { projectGraphCacheDirectory } from 'nx/src/utils/cache-directory';
import { NX_PLUGIN_OPTIONS } from '../utils/constants';
import { loadConfigFile } from '@nx/devkit/src/utils/config-utils';
import { hashObject } from 'nx/src/devkit-internals';
export interface CypressPluginOptions {
ciTargetName?: string;
@ -30,29 +33,59 @@ export interface CypressPluginOptions {
componentTestingTargetName?: string;
}
const cachePath = join(projectGraphCacheDirectory, 'cypress.hash');
const targetsCache = readTargetsCache();
function readTargetsCache(): Record<string, CypressTargets> {
function readTargetsCache(cachePath: string): Record<string, CypressTargets> {
return existsSync(cachePath) ? readJsonFile(cachePath) : {};
}
function writeTargetsToCache() {
const oldCache = readTargetsCache();
writeJsonFile(cachePath, {
...oldCache,
targetsCache,
});
function writeTargetsToCache(cachePath: string, results: CypressTargets) {
writeJsonFile(cachePath, results);
}
export const createDependencies: CreateDependencies = () => {
writeTargetsToCache();
return [];
};
const cypressConfigGlob = '**/cypress.config.{js,ts,mjs,cjs}';
export const createNodesV2: CreateNodesV2<CypressPluginOptions> = [
cypressConfigGlob,
async (configFiles, options, context) => {
const optionsHash = hashObject(options);
const cachePath = join(
projectGraphCacheDirectory,
`cypress-${optionsHash}.hash`
);
const targetsCache = readTargetsCache(cachePath);
try {
return await createNodesFromFiles(
(configFile, options, context) =>
createNodesInternal(configFile, options, context, targetsCache),
configFiles,
options,
context
);
} finally {
writeTargetsToCache(cachePath, targetsCache);
}
},
];
/**
* @deprecated This is replaced with {@link createNodesV2}. Update your plugin to export its own `createNodesV2` function that wraps this one instead.
* This function will change to the v2 function in Nx 20.
*/
export const createNodes: CreateNodes<CypressPluginOptions> = [
'**/cypress.config.{js,ts,mjs,cjs}',
async (configFilePath, options, context) => {
cypressConfigGlob,
(configFile, options, context) => {
logger.warn(
'`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.'
);
return createNodesInternal(configFile, options, context, {});
},
];
async function createNodesInternal(
configFilePath: string,
options: CypressPluginOptions,
context: CreateNodesContext,
targetsCache: CypressTargets
) {
options = normalizeOptions(options);
const projectRoot = dirname(configFilePath);
@ -88,8 +121,7 @@ export const createNodes: CreateNodes<CypressPluginOptions> = [
[projectRoot]: project,
},
};
},
];
}
function getOutputs(
projectRoot: string,

View File

@ -17,6 +17,7 @@ import {
TargetConfiguration,
Tree,
CreateNodes,
CreateNodesV2,
} from 'nx/src/devkit-exports';
import {
@ -52,8 +53,8 @@ class ExecutorToPluginMigrator<T> {
#targetDefaultsForExecutor: Partial<TargetConfiguration>;
#targetAndProjectsToMigrate: Map<string, Set<string>>;
#pluginToAddForTarget: Map<string, ExpandedPluginConfiguration<T>>;
#createNodes: CreateNodes<T>;
#configFiles: string[];
#createNodes?: CreateNodes<T>;
#createNodesV2?: CreateNodesV2<T>;
#createNodesResultsForTargets: Map<string, ConfigurationResult>;
constructor(
@ -63,7 +64,8 @@ class ExecutorToPluginMigrator<T> {
pluginPath: string,
pluginOptionsBuilder: PluginOptionsBuilder<T>,
postTargetTransformer: PostTargetTransformer,
createNodes: CreateNodes<T>,
createNodes?: CreateNodes<T>,
createNodesV2?: CreateNodesV2<T>,
specificProjectToMigrate?: string,
skipTargetFilter?: SkipTargetFilter
) {
@ -74,6 +76,7 @@ class ExecutorToPluginMigrator<T> {
this.#pluginOptionsBuilder = pluginOptionsBuilder;
this.#postTargetTransformer = postTargetTransformer;
this.#createNodes = createNodes;
this.#createNodesV2 = createNodesV2;
this.#specificProjectToMigrate = specificProjectToMigrate;
this.#skipTargetFilter = skipTargetFilter ?? ((...args) => [false, '']);
}
@ -224,6 +227,7 @@ class ExecutorToPluginMigrator<T> {
) {
const loadedPlugin = new LoadedNxPlugin(
{
createNodesV2: this.#createNodesV2,
createNodes: this.#createNodes,
name: this.#pluginPath,
},
@ -373,6 +377,7 @@ class ExecutorToPluginMigrator<T> {
for (const targetName of this.#targetAndProjectsToMigrate.keys()) {
const loadedPlugin = new LoadedNxPlugin(
{
createNodesV2: this.#createNodesV2,
createNodes: this.#createNodes,
name: this.#pluginPath,
},
@ -396,13 +401,38 @@ class ExecutorToPluginMigrator<T> {
}
}
this.#configFiles = Array.from(projectConfigs.matchingProjectFiles);
this.#createNodesResultsForTargets.set(targetName, projectConfigs);
}
}
}
export async function migrateExecutorToPlugin<T>(
tree: Tree,
projectGraph: ProjectGraph,
executor: string,
pluginPath: string,
pluginOptionsBuilder: PluginOptionsBuilder<T>,
postTargetTransformer: PostTargetTransformer,
createNodes: CreateNodesV2<T>,
specificProjectToMigrate?: string,
skipTargetFilter?: SkipTargetFilter
): Promise<Map<string, Set<string>>> {
const migrator = new ExecutorToPluginMigrator<T>(
tree,
projectGraph,
executor,
pluginPath,
pluginOptionsBuilder,
postTargetTransformer,
undefined,
createNodes,
specificProjectToMigrate,
skipTargetFilter
);
return await migrator.run();
}
export async function migrateExecutorToPluginV1<T>(
tree: Tree,
projectGraph: ProjectGraph,
executor: string,
@ -421,6 +451,7 @@ export async function migrateExecutorToPlugin<T>(
pluginOptionsBuilder,
postTargetTransformer,
createNodes,
undefined,
specificProjectToMigrate,
skipTargetFilter
);

View File

@ -6,7 +6,7 @@ import {
type Tree,
} from '@nx/devkit';
import { createNodes, EslintPluginOptions } from '../../plugins/plugin';
import { migrateExecutorToPlugin } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
import { migrateExecutorToPluginV1 } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
import { targetOptionsToCliMap } from './lib/target-options-map';
import { interpolate } from 'nx/src/tasks-runner/utils';
@ -19,7 +19,7 @@ export async function convertToInferred(tree: Tree, options: Schema) {
const projectGraph = await createProjectGraphAsync();
const migratedProjectsModern =
await migrateExecutorToPlugin<EslintPluginOptions>(
await migrateExecutorToPluginV1<EslintPluginOptions>(
tree,
projectGraph,
'@nx/eslint:lint',
@ -31,7 +31,7 @@ export async function convertToInferred(tree: Tree, options: Schema) {
);
const migratedProjectsLegacy =
await migrateExecutorToPlugin<EslintPluginOptions>(
await migrateExecutorToPluginV1<EslintPluginOptions>(
tree,
projectGraph,
'@nrwl/linter:eslint',

View File

@ -1,5 +1,5 @@
export {
createNodes,
createNodesV2,
PlaywrightPluginOptions,
createDependencies,
} from './src/plugins/plugin';

View File

@ -5,7 +5,7 @@ import {
type TargetConfiguration,
type Tree,
} from '@nx/devkit';
import { createNodes, PlaywrightPluginOptions } from '../../plugins/plugin';
import { createNodesV2, PlaywrightPluginOptions } from '../../plugins/plugin';
import { migrateExecutorToPlugin } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
interface Schema {
@ -24,7 +24,7 @@ export async function convertToInferred(tree: Tree, options: Schema) {
'@nx/playwright/plugin',
(targetName) => ({ targetName, ciTargetName: 'e2e-ci' }),
postTargetTransformer,
createNodes,
createNodesV2,
options.project
);

View File

@ -7,8 +7,8 @@ import {
runTasksInSerial,
Tree,
} from '@nx/devkit';
import { addPluginV1 } from '@nx/devkit/src/utils/add-plugin';
import { createNodes } from '../../plugins/plugin';
import { addPlugin } from '@nx/devkit/src/utils/add-plugin';
import { createNodesV2 } from '../../plugins/plugin';
import { nxVersion, playwrightVersion } from '../../utils/versions';
import { InitGeneratorSchema } from './schema';
@ -45,11 +45,11 @@ export async function initGeneratorInternal(
}
if (options.addPlugin) {
await addPluginV1(
await addPlugin(
tree,
await createProjectGraphAsync(),
'@nx/playwright/plugin',
createNodes,
createNodesV2,
{ targetName: ['e2e', 'playwright:e2e', 'playwright-e2e'] },
options.updatePackageScripts
);

View File

@ -1,11 +1,11 @@
import { CreateNodesContext } from '@nx/devkit';
import { TempFs } from '@nx/devkit/internal-testing-utils';
import { createNodes } from './plugin';
import { createNodesV2 } from './plugin';
import { PlaywrightTestConfig } from '@playwright/test';
describe('@nx/playwright/plugin', () => {
let createNodesFunction = createNodes[1];
let createNodesFunction = createNodesV2[1];
let context: CreateNodesContext;
let tempFs: TempFs;
@ -35,16 +35,20 @@ describe('@nx/playwright/plugin', () => {
it('should create nodes with default playwright configuration', async () => {
await mockPlaywrightConfig(tempFs, {});
const { projects } = await createNodesFunction(
'playwright.config.js',
const results = await createNodesFunction(
['playwright.config.js'],
{
targetName: 'e2e',
},
context
);
expect(projects).toMatchInlineSnapshot(`
expect(results).toMatchInlineSnapshot(`
[
[
"playwright.config.js",
{
"projects": {
".": {
"metadata": {
"targetGroups": {
@ -105,7 +109,10 @@ describe('@nx/playwright/plugin', () => {
},
},
},
}
},
},
],
]
`);
});
@ -117,16 +124,20 @@ describe('@nx/playwright/plugin', () => {
['html', { outputFolder: 'test-results/html' }],
],
});
const { projects } = await createNodesFunction(
'playwright.config.js',
const results = await createNodesFunction(
['playwright.config.js'],
{
targetName: 'e2e',
},
context
);
expect(projects).toMatchInlineSnapshot(`
expect(results).toMatchInlineSnapshot(`
[
[
"playwright.config.js",
{
"projects": {
".": {
"metadata": {
"targetGroups": {
@ -193,7 +204,10 @@ describe('@nx/playwright/plugin', () => {
},
},
},
}
},
},
],
]
`);
});
@ -213,16 +227,17 @@ describe('@nx/playwright/plugin', () => {
'not-tests/run-me.spec.ts': '',
});
const { projects } = await createNodesFunction(
'playwright.config.js',
const results = await createNodesFunction(
['playwright.config.js'],
{
targetName: 'e2e',
ciTargetName: 'e2e-ci',
},
context
);
const { targets } = projects['.'];
expect(projects['.'].metadata.targetGroups).toMatchInlineSnapshot(`
const project = results[0][1].projects['.'];
const { targets } = project;
expect(project.metadata.targetGroups).toMatchInlineSnapshot(`
{
"E2E (CI)": [
"e2e-ci--tests/run-me-2.spec.ts",

View File

@ -2,11 +2,13 @@ import { existsSync, readdirSync } from 'fs';
import { dirname, join, relative } from 'path';
import {
CreateDependencies,
CreateNodes,
CreateNodesContext,
createNodesFromFiles,
CreateNodesV2,
detectPackageManager,
joinPathFragments,
logger,
normalizePath,
ProjectConfiguration,
readJsonFile,
@ -22,6 +24,7 @@ import { minimatch } from 'minimatch';
import { projectGraphCacheDirectory } from 'nx/src/utils/cache-directory';
import { getLockFileName } from '@nx/js';
import { loadConfigFile } from '@nx/devkit/src/utils/config-utils';
import { hashObject } from 'nx/src/hasher/file-hasher';
export interface PlaywrightPluginOptions {
targetName?: string;
@ -33,32 +36,65 @@ interface NormalizedOptions {
ciTargetName?: string;
}
const cachePath = join(projectGraphCacheDirectory, 'playwright.hash');
const targetsCache = readTargetsCache();
type PlaywrightTargets = Pick<ProjectConfiguration, 'targets' | 'metadata'>;
function readTargetsCache(): Record<string, PlaywrightTargets> {
function readTargetsCache(
cachePath: string
): Record<string, PlaywrightTargets> {
return existsSync(cachePath) ? readJsonFile(cachePath) : {};
}
function writeTargetsToCache() {
const oldCache = readTargetsCache();
writeJsonFile(cachePath, {
...readTargetsCache,
targetsCache,
});
function writeTargetsToCache(
cachePath: string,
results: Record<string, PlaywrightTargets>
) {
writeJsonFile(cachePath, results);
}
export const createDependencies: CreateDependencies = () => {
writeTargetsToCache();
return [];
};
const playwrightConfigGlob = '**/playwright.config.{js,ts,cjs,cts,mjs,mts}';
export const createNodesV2: CreateNodesV2<PlaywrightPluginOptions> = [
playwrightConfigGlob,
async (configFilePaths, options, context) => {
const optionsHash = hashObject(options);
const cachePath = join(
projectGraphCacheDirectory,
`playwright-${optionsHash}.hash`
);
const targetsCache = readTargetsCache(cachePath);
try {
return await createNodesFromFiles(
(configFile, options, context) =>
createNodesInternal(configFile, options, context, targetsCache),
configFilePaths,
options,
context
);
} finally {
writeTargetsToCache(cachePath, targetsCache);
}
},
];
/**
* @deprecated This is replaced with {@link createNodesV2}. Update your plugin to export its own `createNodesV2` function that wraps this one instead.
* This function will change to the v2 function in Nx 20.
*/
export const createNodes: CreateNodes<PlaywrightPluginOptions> = [
'**/playwright.config.{js,ts,cjs,cts,mjs,mts}',
async (configFilePath, options, context) => {
playwrightConfigGlob,
async (configFile, options, context) => {
logger.warn(
'`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.'
);
return createNodesInternal(configFile, options, context, {});
},
];
async function createNodesInternal(
configFilePath: string,
options: PlaywrightPluginOptions,
context: CreateNodesContext,
targetsCache: Record<string, PlaywrightTargets>
) {
const projectRoot = dirname(configFilePath);
// Do not create a project if package.json and project.json isn't there.
@ -93,8 +129,7 @@ export const createNodes: CreateNodes<PlaywrightPluginOptions> = [
},
},
};
},
];
}
async function buildPlaywrightTargets(
configFilePath: string,