fix(testing): playwright migration should find correct targetName (#27386)
<!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> Migration is not correctly finding the intended target name to use for the config file ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> Migration should find the correct target name for the config file ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
This commit is contained in:
parent
4108bfc8a6
commit
64f1aaf282
@ -37,6 +37,8 @@
|
||||
"@phenomnomnominal/tsquery": "~5.0.1",
|
||||
"@nx/devkit": "file:../devkit",
|
||||
"@nx/eslint": "file:../eslint",
|
||||
"@nx/webpack": "file:../webpack",
|
||||
"@nx/vite": "file:../vite",
|
||||
"@nx/js": "file:../js",
|
||||
"tslib": "^2.3.0",
|
||||
"minimatch": "9.0.3"
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { ProjectGraph, type Tree } from '@nx/devkit';
|
||||
import { ProjectGraph, readNxJson, type Tree, updateNxJson } from '@nx/devkit';
|
||||
import useServeStaticPreviewForCommand from './use-serve-static-preview-for-command';
|
||||
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
|
||||
import { join } from 'path';
|
||||
|
||||
let projectGraph: ProjectGraph;
|
||||
jest.mock('@nx/devkit', () => ({
|
||||
@ -28,6 +29,7 @@ describe('useServeStaticPreviewForCommand', () => {
|
||||
|
||||
afterEach(() => {
|
||||
tempFs.reset();
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
it('should update when it does not use serve-static for non-vite', async () => {
|
||||
@ -69,8 +71,72 @@ describe('useServeStaticPreviewForCommand', () => {
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should use the serveStaticTargetName in the nx.json', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/webpack/plugin',
|
||||
options: {
|
||||
serveStaticTargetName: 'webpack:serve-static',
|
||||
},
|
||||
});
|
||||
updateNxJson(tree, nxJson);
|
||||
addProject(tree, tempFs, { noVite: true });
|
||||
mockWebpackConfig({
|
||||
output: {
|
||||
path: 'dist/foo',
|
||||
},
|
||||
});
|
||||
|
||||
// ACT
|
||||
await useServeStaticPreviewForCommand(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read('app-e2e/playwright.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { defineConfig, devices } from '@playwright/test';
|
||||
import { nxE2EPreset } from '@nx/playwright/preset';
|
||||
|
||||
import { workspaceRoot } from '@nx/devkit';
|
||||
|
||||
const baseURL = process.env['BASE_URL'] || 'http://localhost:4200';
|
||||
|
||||
export default defineConfig({
|
||||
...nxE2EPreset(__filename, { testDir: './src' }),
|
||||
use: {
|
||||
baseURL,
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
webServer: {
|
||||
command: 'npx nx run app:webpack:serve-static',
|
||||
url: 'http://localhost:4200',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
cwd: workspaceRoot,
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
],
|
||||
});
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should update when it does not use preview for vite', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/vite/plugin',
|
||||
options: {
|
||||
previewTargetName: 'vite:preview',
|
||||
},
|
||||
});
|
||||
updateNxJson(tree, nxJson);
|
||||
addProject(tree, tempFs);
|
||||
|
||||
// ACT
|
||||
@ -93,7 +159,7 @@ describe('useServeStaticPreviewForCommand', () => {
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
webServer: {
|
||||
command: 'npx nx run app:preview',
|
||||
command: 'npx nx run app:vite:preview',
|
||||
url: 'http://localhost:4300',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
cwd: workspaceRoot,
|
||||
@ -108,10 +174,66 @@ describe('useServeStaticPreviewForCommand', () => {
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not replace the full command', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/vite/plugin',
|
||||
options: {
|
||||
previewTargetName: 'vite:preview',
|
||||
},
|
||||
});
|
||||
updateNxJson(tree, nxJson);
|
||||
addProject(tree, tempFs, { hasAdditionalCommand: true });
|
||||
|
||||
// ACT
|
||||
await useServeStaticPreviewForCommand(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read('app-e2e/playwright.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { defineConfig, devices } from '@playwright/test';
|
||||
import { nxE2EPreset } from '@nx/playwright/preset';
|
||||
|
||||
import { workspaceRoot } from '@nx/devkit';
|
||||
|
||||
const baseURL = process.env['BASE_URL'] || 'http://localhost:4300';
|
||||
|
||||
export default defineConfig({
|
||||
...nxE2EPreset(__filename, { testDir: './src' }),
|
||||
use: {
|
||||
baseURL,
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
webServer: {
|
||||
command: 'echo "start" && npx nx run app:vite:preview',
|
||||
url: 'http://localhost:4300',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
cwd: workspaceRoot,
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
],
|
||||
});
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
function mockWebpackConfig(config: any) {
|
||||
jest.mock(join(tempFs.tempDir, 'app/webpack.config.ts'), () => config, {
|
||||
virtual: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const basePlaywrightConfig = (
|
||||
appName: string
|
||||
appName: string,
|
||||
hasAdditionalCommand?: boolean
|
||||
) => `import { defineConfig, devices } from '@playwright/test';
|
||||
import { nxE2EPreset } from '@nx/playwright/preset';
|
||||
|
||||
@ -126,7 +248,9 @@ export default defineConfig({
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
webServer: {
|
||||
command: 'npx nx run ${appName}:serve',
|
||||
command: '${
|
||||
hasAdditionalCommand ? 'echo "start" && ' : ''
|
||||
}npx nx run ${appName}:serve',
|
||||
url: 'http://localhost:4200',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
cwd: workspaceRoot,
|
||||
@ -174,6 +298,7 @@ function addProject(
|
||||
tempFs: TempFs,
|
||||
overrides: {
|
||||
noVite?: boolean;
|
||||
hasAdditionalCommand?: boolean;
|
||||
} = {}
|
||||
) {
|
||||
const appProjectConfig = {
|
||||
@ -193,20 +318,36 @@ function addProject(
|
||||
if (!overrides.noVite) {
|
||||
tree.write(`app/vite.config.ts`, viteConfig);
|
||||
} else {
|
||||
tree.write(`app/webpack.config.ts`, ``);
|
||||
tree.write(
|
||||
`app/webpack.config.ts`,
|
||||
`module.exports = {output: {
|
||||
path: 'dist/foo',
|
||||
}}`
|
||||
);
|
||||
}
|
||||
|
||||
tree.write(`app/project.json`, JSON.stringify(appProjectConfig));
|
||||
tree.write(`app-e2e/playwright.config.ts`, basePlaywrightConfig('app'));
|
||||
tree.write(
|
||||
`app-e2e/playwright.config.ts`,
|
||||
basePlaywrightConfig('app', overrides.hasAdditionalCommand)
|
||||
);
|
||||
tree.write(`app-e2e/project.json`, JSON.stringify(e2eProjectConfig));
|
||||
if (!overrides.noVite) {
|
||||
tempFs.createFile(`app/vite.config.ts`, viteConfig);
|
||||
tempFs.createFileSync(`app/vite.config.ts`, viteConfig);
|
||||
} else {
|
||||
tempFs.createFile(`app/webpack.config.ts`, ``);
|
||||
tempFs.createFileSync(
|
||||
`app/webpack.config.ts`,
|
||||
`module.exports = {output: {
|
||||
path: 'dist/foo',
|
||||
}}`
|
||||
);
|
||||
}
|
||||
tempFs.createFilesSync({
|
||||
[`app/project.json`]: JSON.stringify(appProjectConfig),
|
||||
[`app-e2e/playwright.config.ts`]: basePlaywrightConfig('app'),
|
||||
[`app-e2e/playwright.config.ts`]: basePlaywrightConfig(
|
||||
'app',
|
||||
overrides.hasAdditionalCommand
|
||||
),
|
||||
[`app-e2e/project.json`]: JSON.stringify(e2eProjectConfig),
|
||||
});
|
||||
|
||||
|
||||
@ -1,18 +1,41 @@
|
||||
import {
|
||||
CreateNodesV2,
|
||||
createProjectGraphAsync,
|
||||
formatFiles,
|
||||
getPackageManagerCommand,
|
||||
joinPathFragments,
|
||||
parseTargetString,
|
||||
readNxJson,
|
||||
type Tree,
|
||||
visitNotIgnoredFiles,
|
||||
} from '@nx/devkit';
|
||||
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||
import addE2eCiTargetDefaults from './add-e2e-ci-target-defaults';
|
||||
import type { ConfigurationResult } from 'nx/src/project-graph/utils/project-configuration-utils';
|
||||
import { LoadedNxPlugin } from 'nx/src/project-graph/plugins/internal-api';
|
||||
import { retrieveProjectConfigurations } from 'nx/src/project-graph/utils/retrieve-workspace-files';
|
||||
import { ProjectConfigurationsError } from 'nx/src/project-graph/error-types';
|
||||
import {
|
||||
WebpackPluginOptions,
|
||||
createNodesV2 as webpackCreateNodesV2,
|
||||
} from '@nx/webpack/src/plugins/plugin';
|
||||
import {
|
||||
VitePluginOptions,
|
||||
createNodesV2 as viteCreateNodesV2,
|
||||
} from '@nx/vite/plugin';
|
||||
import type { Node } from 'typescript';
|
||||
|
||||
export default async function (tree: Tree) {
|
||||
const graph = await createProjectGraphAsync();
|
||||
visitNotIgnoredFiles(tree, '', (path) => {
|
||||
|
||||
const collectedProjects: {
|
||||
projectName: string;
|
||||
configFile: string;
|
||||
configFileType: 'webpack' | 'vite';
|
||||
playwrightConfigFile: string;
|
||||
commandValueNode: Node;
|
||||
}[] = [];
|
||||
visitNotIgnoredFiles(tree, '', async (path) => {
|
||||
if (!path.endsWith('playwright.config.ts')) {
|
||||
return;
|
||||
}
|
||||
@ -33,8 +56,8 @@ export default async function (tree: Tree) {
|
||||
const command = commandValueNode.getText();
|
||||
let project: string;
|
||||
if (command.includes('nx run')) {
|
||||
const NX_TARGET_REGEX = "(?<=nx run )[^']+";
|
||||
const matches = command.match(NX_TARGET_REGEX);
|
||||
const NX_RUN_TARGET_REGEX = "(?<=nx run )[^']+";
|
||||
const matches = command.match(NX_RUN_TARGET_REGEX);
|
||||
if (!matches) {
|
||||
return;
|
||||
}
|
||||
@ -63,34 +86,71 @@ export default async function (tree: Tree) {
|
||||
joinPathFragments(graph.nodes[project].data.root, 'vite.config.js'),
|
||||
].find((p) => tree.exists(p));
|
||||
|
||||
if (!pathToViteConfig) {
|
||||
const newCommand = `${
|
||||
getPackageManagerCommand().exec
|
||||
} nx run ${project}:serve-static`;
|
||||
const pathToWebpackConfig = [
|
||||
joinPathFragments(graph.nodes[project].data.root, 'webpack.config.ts'),
|
||||
joinPathFragments(graph.nodes[project].data.root, 'webpack.config.js'),
|
||||
].find((p) => tree.exists(p));
|
||||
|
||||
collectedProjects.push({
|
||||
projectName: project,
|
||||
configFile: pathToWebpackConfig ?? pathToViteConfig,
|
||||
configFileType: pathToWebpackConfig ? 'webpack' : 'vite',
|
||||
playwrightConfigFile: path,
|
||||
commandValueNode,
|
||||
});
|
||||
});
|
||||
|
||||
for (const projectToMigrate of collectedProjects) {
|
||||
let playwrightConfigFileContents = tree.read(
|
||||
projectToMigrate.playwrightConfigFile,
|
||||
'utf-8'
|
||||
);
|
||||
const targetName = await getServeStaticTargetNameForConfigFile(
|
||||
tree,
|
||||
projectToMigrate.configFileType === 'webpack'
|
||||
? '@nx/webpack/plugin'
|
||||
: '@nx/vite/plugin',
|
||||
projectToMigrate.configFile,
|
||||
projectToMigrate.configFileType === 'webpack'
|
||||
? 'serve-static'
|
||||
: 'preview',
|
||||
projectToMigrate.configFileType === 'webpack'
|
||||
? 'serveStaticTargetName'
|
||||
: 'previewTargetName',
|
||||
projectToMigrate.configFileType === 'webpack'
|
||||
? webpackCreateNodesV2
|
||||
: viteCreateNodesV2
|
||||
);
|
||||
const oldCommand = projectToMigrate.commandValueNode.getText();
|
||||
const newCommand = oldCommand.replace(
|
||||
/nx.*[^"']/,
|
||||
`nx run ${projectToMigrate.projectName}:${targetName}`
|
||||
);
|
||||
if (projectToMigrate.configFileType === 'webpack') {
|
||||
tree.write(
|
||||
path,
|
||||
projectToMigrate.playwrightConfigFile,
|
||||
`${playwrightConfigFileContents.slice(
|
||||
0,
|
||||
commandValueNode.getStart()
|
||||
)}"${newCommand}"${playwrightConfigFileContents.slice(
|
||||
commandValueNode.getEnd()
|
||||
projectToMigrate.commandValueNode.getStart()
|
||||
)}${newCommand}${playwrightConfigFileContents.slice(
|
||||
projectToMigrate.commandValueNode.getEnd()
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
const newCommand = `${
|
||||
getPackageManagerCommand().exec
|
||||
} nx run ${project}:preview`;
|
||||
tree.write(
|
||||
path,
|
||||
projectToMigrate.playwrightConfigFile,
|
||||
`${playwrightConfigFileContents.slice(
|
||||
0,
|
||||
commandValueNode.getStart()
|
||||
)}"${newCommand}"${playwrightConfigFileContents.slice(
|
||||
commandValueNode.getEnd()
|
||||
projectToMigrate.commandValueNode.getStart()
|
||||
)}${newCommand}${playwrightConfigFileContents.slice(
|
||||
projectToMigrate.commandValueNode.getEnd()
|
||||
)}`
|
||||
);
|
||||
playwrightConfigFileContents = tree.read(path, 'utf-8');
|
||||
ast = tsquery.ast(playwrightConfigFileContents);
|
||||
playwrightConfigFileContents = tree.read(
|
||||
projectToMigrate.playwrightConfigFile,
|
||||
'utf-8'
|
||||
);
|
||||
let ast = tsquery.ast(playwrightConfigFileContents);
|
||||
|
||||
const BASE_URL_SELECTOR =
|
||||
'VariableDeclaration:has(Identifier[name=baseURL])';
|
||||
@ -105,7 +165,7 @@ export default async function (tree: Tree) {
|
||||
const newBaseUrlVariableDeclaration =
|
||||
"baseURL = process.env['BASE_URL'] || 'http://localhost:4300';";
|
||||
tree.write(
|
||||
path,
|
||||
projectToMigrate.playwrightConfigFile,
|
||||
`${playwrightConfigFileContents.slice(
|
||||
0,
|
||||
baseUrlNode.getStart()
|
||||
@ -114,7 +174,10 @@ export default async function (tree: Tree) {
|
||||
)}`
|
||||
);
|
||||
|
||||
playwrightConfigFileContents = tree.read(path, 'utf-8');
|
||||
playwrightConfigFileContents = tree.read(
|
||||
projectToMigrate.playwrightConfigFile,
|
||||
'utf-8'
|
||||
);
|
||||
ast = tsquery.ast(playwrightConfigFileContents);
|
||||
const WEB_SERVER_URL_SELECTOR =
|
||||
'PropertyAssignment:has(Identifier[name=webServer]) PropertyAssignment:has(Identifier[name=url]) > StringLiteral';
|
||||
@ -128,7 +191,7 @@ export default async function (tree: Tree) {
|
||||
const webServerUrlNode = webServerUrlNodes[0];
|
||||
const newWebServerUrl = "'http://localhost:4300'";
|
||||
tree.write(
|
||||
path,
|
||||
projectToMigrate.playwrightConfigFile,
|
||||
`${playwrightConfigFileContents.slice(
|
||||
0,
|
||||
webServerUrlNode.getStart()
|
||||
@ -137,8 +200,57 @@ export default async function (tree: Tree) {
|
||||
)}`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await addE2eCiTargetDefaults(tree);
|
||||
await formatFiles(tree);
|
||||
}
|
||||
|
||||
async function getServeStaticTargetNameForConfigFile<T>(
|
||||
tree: Tree,
|
||||
pluginName: string,
|
||||
configFile: string,
|
||||
defaultTargetName: string,
|
||||
targetNamePluginOption: keyof T,
|
||||
createNodesV2: CreateNodesV2<T>
|
||||
) {
|
||||
const nxJson = readNxJson(tree);
|
||||
const matchingPluginRegistrations = nxJson.plugins?.filter((p) =>
|
||||
typeof p === 'string' ? p === pluginName : p.plugin === pluginName
|
||||
);
|
||||
|
||||
if (!matchingPluginRegistrations) {
|
||||
return defaultTargetName;
|
||||
}
|
||||
|
||||
let targetName = defaultTargetName;
|
||||
for (const plugin of matchingPluginRegistrations) {
|
||||
let projectConfigs: ConfigurationResult;
|
||||
try {
|
||||
const loadedPlugin = new LoadedNxPlugin(
|
||||
{ createNodesV2, name: pluginName },
|
||||
plugin
|
||||
);
|
||||
projectConfigs = await retrieveProjectConfigurations(
|
||||
[loadedPlugin],
|
||||
tree.root,
|
||||
nxJson
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof ProjectConfigurationsError) {
|
||||
projectConfigs = e.partialProjectConfigurationsResult;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (projectConfigs.matchingProjectFiles.includes(configFile)) {
|
||||
targetName =
|
||||
typeof plugin === 'string'
|
||||
? defaultTargetName
|
||||
: (plugin.options?.[targetNamePluginOption] as string) ??
|
||||
defaultTargetName;
|
||||
}
|
||||
}
|
||||
return targetName;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user