fix(misc): update e2e config generators to align with new TS solution setup (#29638)
This PR updates the `@nx/detox:app` generator to match the new TS solution setup. The `@nx/cypress:configuration` and `@nx/cypress:configuration` generators are also updated so that they can be run on existing projects and generator the correct tsconfig files. The Playwright/Cypress example can be seen as follows: ```shell # Skip e2e nx g @nx/react:app apps/demo --bundler vite --e2eTestRunner none # now configure e2e nx g @nx/playwright --project demo ``` Now if you add this line to `apps/demo/e2e/example.spec.ts`: ``` const x: number = 'a'; ``` And run `nx typecheck demo`, it will pass. This happens because the `e2e/**/*.ts` pattern is missing. Thus, we need to ensure that a `tsconfig.e2e.json` project is added for the Playwright spec files. Same thing with Cypress. The Detox generator does not support adding configuration to existing project, so we don't quite get the same problem. The fix for Detox is just to make sure the tsconfig content is not following the old (integrated) version, but the updated TS solution version. ## Current Behavior Detox TS setup is incorrect. Running Cypress and Playwright configuration generator on existing projects generate invalid setup, such that spec files are not typechecked. ## Expected Behavior E2E generators should all generate correct TS setup. ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
This commit is contained in:
parent
8a6e44b788
commit
1e032fb9e5
@ -37,12 +37,23 @@ export function addBaseCypressSetup(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const opts = normalizeOptions(tree, projectConfig, options);
|
const opts = normalizeOptions(tree, projectConfig, options);
|
||||||
|
const isUsingTsSolutionConfig = isUsingTsSolutionSetup(tree);
|
||||||
const templateVars = {
|
const templateVars = {
|
||||||
...opts,
|
...opts,
|
||||||
jsx: !!opts.jsx,
|
jsx: !!opts.jsx,
|
||||||
offsetFromRoot: offsetFromRoot(projectConfig.root),
|
offsetFromRoot: offsetFromRoot(projectConfig.root),
|
||||||
offsetFromProjectRoot: opts.hasTsConfig ? opts.offsetFromProjectRoot : '',
|
offsetFromProjectRoot: opts.hasTsConfig ? opts.offsetFromProjectRoot : '',
|
||||||
tsConfigPath: opts.hasTsConfig
|
tsConfigPath:
|
||||||
|
// TS solution setup should always extend from tsconfig.base.json to use shared compilerOptions, the project's tsconfig.json will not have compilerOptions.
|
||||||
|
isUsingTsSolutionConfig
|
||||||
|
? getRelativePathToRootTsConfig(
|
||||||
|
tree,
|
||||||
|
opts.hasTsConfig
|
||||||
|
? joinPathFragments(projectConfig.root, options.directory)
|
||||||
|
: // If an existing tsconfig.json file does not exist, then cypress tsconfig will be moved to the project root.
|
||||||
|
projectConfig.root
|
||||||
|
)
|
||||||
|
: opts.hasTsConfig
|
||||||
? `${opts.offsetFromProjectRoot}tsconfig.json`
|
? `${opts.offsetFromProjectRoot}tsconfig.json`
|
||||||
: getRelativePathToRootTsConfig(tree, projectConfig.root),
|
: getRelativePathToRootTsConfig(tree, projectConfig.root),
|
||||||
linter: isEslintInstalled(tree) ? 'eslint' : 'none',
|
linter: isEslintInstalled(tree) ? 'eslint' : 'none',
|
||||||
@ -58,7 +69,7 @@ export function addBaseCypressSetup(
|
|||||||
|
|
||||||
generateFiles(
|
generateFiles(
|
||||||
tree,
|
tree,
|
||||||
isUsingTsSolutionSetup(tree)
|
isUsingTsSolutionConfig
|
||||||
? join(__dirname, 'files/tsconfig/ts-solution')
|
? join(__dirname, 'files/tsconfig/ts-solution')
|
||||||
: join(__dirname, 'files/tsconfig/non-ts-solution'),
|
: join(__dirname, 'files/tsconfig/non-ts-solution'),
|
||||||
projectConfig.root,
|
projectConfig.root,
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import {
|
|||||||
Tree,
|
Tree,
|
||||||
updateJson,
|
updateJson,
|
||||||
updateProjectConfiguration,
|
updateProjectConfiguration,
|
||||||
|
writeJson,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
import cypressE2EConfigurationGenerator from './configuration';
|
import cypressE2EConfigurationGenerator from './configuration';
|
||||||
@ -555,6 +556,77 @@ export default defineConfig({
|
|||||||
"
|
"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('TS Solution Setup', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
updateJson(tree, 'package.json', (json) => {
|
||||||
|
json.workspaces = ['packages/*', 'apps/*'];
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
writeJson(tree, 'tsconfig.base.json', {
|
||||||
|
compilerOptions: {
|
||||||
|
composite: true,
|
||||||
|
declaration: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
writeJson(tree, 'tsconfig.json', {
|
||||||
|
extends: './tsconfig.base.json',
|
||||||
|
files: [],
|
||||||
|
references: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle existing tsconfig.json files', async () => {
|
||||||
|
addProject(tree, { name: 'my-lib', type: 'libs' });
|
||||||
|
writeJson(tree, 'libs/my-lib/tsconfig.json', {
|
||||||
|
include: [],
|
||||||
|
files: [],
|
||||||
|
references: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await cypressE2EConfigurationGenerator(tree, {
|
||||||
|
project: 'my-lib',
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
js: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tree.read('libs/my-lib/tsconfig.json', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"{
|
||||||
|
"include": [],
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./src/tsconfig.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
expect(tree.read('libs/my-lib/src/tsconfig.json', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"{
|
||||||
|
"extends": "../../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "out-tsc/cypress",
|
||||||
|
"allowJs": true,
|
||||||
|
"types": ["cypress", "node"],
|
||||||
|
"sourceMap": false
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.js",
|
||||||
|
"../cypress.config.ts",
|
||||||
|
"../**/*.cy.ts",
|
||||||
|
"../**/*.cy.js",
|
||||||
|
"../**/*.d.ts"
|
||||||
|
],
|
||||||
|
"exclude": ["out-tsc", "test-output"]
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import {
|
|||||||
readJson,
|
readJson,
|
||||||
readProjectConfiguration,
|
readProjectConfiguration,
|
||||||
Tree,
|
Tree,
|
||||||
|
updateJson,
|
||||||
|
writeJson,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
import { Linter } from '@nx/eslint/src/generators/utils/linter';
|
import { Linter } from '@nx/eslint/src/generators/utils/linter';
|
||||||
@ -396,8 +398,38 @@ describe('detox application generator', () => {
|
|||||||
addPlugin: true,
|
addPlugin: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const tsConfig = readJson(tree, 'my-app-e2e/tsconfig.json');
|
expect(readJson(tree, 'my-app-e2e/tsconfig.json')).toMatchInlineSnapshot(`
|
||||||
expect(tsConfig.extends).toEqual('../tsconfig.base.json');
|
{
|
||||||
|
"extends": "../tsconfig.base.json",
|
||||||
|
"files": [],
|
||||||
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.e2e.json",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
expect(readJson(tree, 'my-app-e2e/tsconfig.e2e.json'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"outDir": "../dist/out-tsc",
|
||||||
|
"sourceMap": false,
|
||||||
|
"types": [
|
||||||
|
"node",
|
||||||
|
"jest",
|
||||||
|
"detox",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.js",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support a root tsconfig.json instead of tsconfig.base.json', async () => {
|
it('should support a root tsconfig.json instead of tsconfig.base.json', async () => {
|
||||||
@ -411,8 +443,120 @@ describe('detox application generator', () => {
|
|||||||
addPlugin: true,
|
addPlugin: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const tsConfig = readJson(tree, 'my-app-e2e/tsconfig.json');
|
expect(readJson(tree, 'my-app-e2e/tsconfig.json')).toMatchInlineSnapshot(`
|
||||||
expect(tsConfig.extends).toEqual('../tsconfig.json');
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"files": [],
|
||||||
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.e2e.json",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
expect(readJson(tree, 'my-app-e2e/tsconfig.e2e.json'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"outDir": "../dist/out-tsc",
|
||||||
|
"sourceMap": false,
|
||||||
|
"types": [
|
||||||
|
"node",
|
||||||
|
"jest",
|
||||||
|
"detox",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.js",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('TS Solution Setup', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
updateJson(tree, 'package.json', (json) => {
|
||||||
|
json.workspaces = ['packages/*', 'apps/*'];
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
writeJson(tree, 'tsconfig.base.json', {
|
||||||
|
compilerOptions: {
|
||||||
|
composite: true,
|
||||||
|
declaration: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
writeJson(tree, 'tsconfig.json', {
|
||||||
|
extends: './tsconfig.base.json',
|
||||||
|
files: [],
|
||||||
|
references: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create tsconfig.json and update project references', async () => {
|
||||||
|
writeJson(tree, 'apps/my-app/package.json', {
|
||||||
|
name: 'my-app',
|
||||||
|
});
|
||||||
|
|
||||||
|
await detoxApplicationGenerator(tree, {
|
||||||
|
e2eDirectory: 'apps/my-app-e2e',
|
||||||
|
appProject: 'my-app',
|
||||||
|
linter: Linter.None,
|
||||||
|
framework: 'react-native',
|
||||||
|
addPlugin: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tree.read('tsconfig.json', 'utf-8')).toMatchInlineSnapshot(`
|
||||||
|
"{
|
||||||
|
"extends": "./tsconfig.base.json",
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./apps/my-app-e2e"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
expect(tree.read('apps/my-app-e2e/package.json', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"{
|
||||||
|
"name": "my-app-e2e",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"nx": {
|
||||||
|
"sourceRoot": "apps/my-app-e2e/src",
|
||||||
|
"projectType": "application",
|
||||||
|
"implicitDependencies": [
|
||||||
|
"my-app"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
expect(tree.read('apps/my-app-e2e/tsconfig.json', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"sourceMap": false,
|
||||||
|
"outDir": "out-tsc/detox",
|
||||||
|
"allowJs": true,
|
||||||
|
"types": ["node", "jest", "detox"],
|
||||||
|
"rootDir": "src",
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"tsBuildInfoFile": "out-tsc/detox/tsconfig.tsbuildinfo"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.js"],
|
||||||
|
"exclude": ["out-tsc", "dist", "test-output"]
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -9,6 +9,11 @@ import { createFiles } from './lib/create-files';
|
|||||||
import { normalizeOptions } from './lib/normalize-options';
|
import { normalizeOptions } from './lib/normalize-options';
|
||||||
import { Schema } from './schema';
|
import { Schema } from './schema';
|
||||||
import { ensureDependencies } from './lib/ensure-dependencies';
|
import { ensureDependencies } from './lib/ensure-dependencies';
|
||||||
|
import {
|
||||||
|
addProjectToTsSolutionWorkspace,
|
||||||
|
updateTsconfigFiles,
|
||||||
|
} from '@nx/js/src/utils/typescript/ts-solution-setup';
|
||||||
|
import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields';
|
||||||
|
|
||||||
export async function detoxApplicationGenerator(host: Tree, schema: Schema) {
|
export async function detoxApplicationGenerator(host: Tree, schema: Schema) {
|
||||||
return await detoxApplicationGeneratorInternal(host, {
|
return await detoxApplicationGeneratorInternal(host, {
|
||||||
@ -38,6 +43,28 @@ export async function detoxApplicationGeneratorInternal(
|
|||||||
const lintingTask = await addLinting(host, options);
|
const lintingTask = await addLinting(host, options);
|
||||||
const depsTask = ensureDependencies(host, options);
|
const depsTask = ensureDependencies(host, options);
|
||||||
|
|
||||||
|
updateTsconfigFiles(
|
||||||
|
host,
|
||||||
|
options.e2eProjectRoot,
|
||||||
|
'tsconfig.json',
|
||||||
|
{
|
||||||
|
module: 'esnext',
|
||||||
|
moduleResolution: 'bundler',
|
||||||
|
outDir: 'out-tsc/detox',
|
||||||
|
allowJs: true,
|
||||||
|
types: ['node', 'jest', 'detox'],
|
||||||
|
},
|
||||||
|
options.linter === 'eslint'
|
||||||
|
? ['eslint.config.js', 'eslint.config.cjs', 'eslint.config.mjs']
|
||||||
|
: undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
if (options.isUsingTsSolutionConfig) {
|
||||||
|
addProjectToTsSolutionWorkspace(host, options.e2eProjectRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
sortPackageJsonFields(host, options.e2eProjectRoot);
|
||||||
|
|
||||||
if (!options.skipFormat) {
|
if (!options.skipFormat) {
|
||||||
await formatFiles(host);
|
await formatFiles(host);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ describe('Add Linting', () => {
|
|||||||
appExpoName: 'MyApp',
|
appExpoName: 'MyApp',
|
||||||
appRoot: 'apps/my-app',
|
appRoot: 'apps/my-app',
|
||||||
linter: Linter.EsLint,
|
linter: Linter.EsLint,
|
||||||
|
isUsingTsSolutionConfig: false,
|
||||||
framework: 'react-native',
|
framework: 'react-native',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -36,6 +37,7 @@ describe('Add Linting', () => {
|
|||||||
appExpoName: 'MyApp',
|
appExpoName: 'MyApp',
|
||||||
appRoot: 'apps/my-app',
|
appRoot: 'apps/my-app',
|
||||||
linter: Linter.EsLint,
|
linter: Linter.EsLint,
|
||||||
|
isUsingTsSolutionConfig: false,
|
||||||
framework: 'react-native',
|
framework: 'react-native',
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -54,6 +56,7 @@ describe('Add Linting', () => {
|
|||||||
appExpoName: 'MyApp',
|
appExpoName: 'MyApp',
|
||||||
appRoot: 'apps/my-app',
|
appRoot: 'apps/my-app',
|
||||||
linter: Linter.None,
|
linter: Linter.None,
|
||||||
|
isUsingTsSolutionConfig: false,
|
||||||
framework: 'react-native',
|
framework: 'react-native',
|
||||||
});
|
});
|
||||||
const project = readProjectConfiguration(tree, 'my-app-e2e');
|
const project = readProjectConfiguration(tree, 'my-app-e2e');
|
||||||
|
|||||||
@ -2,24 +2,56 @@ import {
|
|||||||
detectPackageManager,
|
detectPackageManager,
|
||||||
generateFiles,
|
generateFiles,
|
||||||
getPackageManagerCommand,
|
getPackageManagerCommand,
|
||||||
offsetFromRoot,
|
offsetFromRoot as _offsetFromRoot,
|
||||||
toJS,
|
toJS,
|
||||||
Tree,
|
Tree,
|
||||||
|
writeJson,
|
||||||
|
joinPathFragments,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { getRelativePathToRootTsConfig } from '@nx/js';
|
import { getRelativePathToRootTsConfig } from '@nx/js';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { NormalizedSchema } from './normalize-options';
|
import { NormalizedSchema } from './normalize-options';
|
||||||
|
|
||||||
export function createFiles(host: Tree, options: NormalizedSchema) {
|
export function createFiles(host: Tree, options: NormalizedSchema) {
|
||||||
|
const offsetFromRoot = _offsetFromRoot(options.e2eProjectRoot);
|
||||||
|
const rootTsConfigPath = getRelativePathToRootTsConfig(
|
||||||
|
host,
|
||||||
|
options.e2eProjectRoot
|
||||||
|
);
|
||||||
generateFiles(host, join(__dirname, '../files/app'), options.e2eProjectRoot, {
|
generateFiles(host, join(__dirname, '../files/app'), options.e2eProjectRoot, {
|
||||||
...options,
|
...options,
|
||||||
exec: getPackageManagerCommand(detectPackageManager(host.root)).exec,
|
exec: getPackageManagerCommand(detectPackageManager(host.root)).exec,
|
||||||
offsetFromRoot: offsetFromRoot(options.e2eProjectRoot),
|
offsetFromRoot,
|
||||||
rootTsConfigPath: getRelativePathToRootTsConfig(
|
rootTsConfigPath,
|
||||||
host,
|
|
||||||
options.e2eProjectRoot
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
if (options.isUsingTsSolutionConfig) {
|
||||||
|
writeJson(
|
||||||
|
host,
|
||||||
|
joinPathFragments(options.e2eProjectRoot, 'tsconfig.json'),
|
||||||
|
{
|
||||||
|
extends: `${offsetFromRoot}tsconfig.base.json`,
|
||||||
|
compilerOptions: {
|
||||||
|
sourceMap: false,
|
||||||
|
outDir: 'out-tsc/detox',
|
||||||
|
allowJs: true,
|
||||||
|
types: ['node', 'jest', 'detox'],
|
||||||
|
},
|
||||||
|
include: ['src/**/*.ts', 'src/**/*.js'],
|
||||||
|
exclude: ['out-tsc', 'test-output'],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
generateFiles(
|
||||||
|
host,
|
||||||
|
join(__dirname, '../files/non-ts-solution'),
|
||||||
|
options.e2eProjectRoot,
|
||||||
|
{
|
||||||
|
...options,
|
||||||
|
offsetFromRoot,
|
||||||
|
rootTsConfigPath,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
if (options.js) {
|
if (options.js) {
|
||||||
toJS(host);
|
toJS(host);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,6 +37,7 @@ describe('Normalize Options', () => {
|
|||||||
appDisplayName: 'MyApp',
|
appDisplayName: 'MyApp',
|
||||||
appExpoName: 'MyApp',
|
appExpoName: 'MyApp',
|
||||||
appRoot: 'apps/my-app',
|
appRoot: 'apps/my-app',
|
||||||
|
isUsingTsSolutionConfig: false,
|
||||||
linter: Linter.EsLint,
|
linter: Linter.EsLint,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -66,6 +67,7 @@ describe('Normalize Options', () => {
|
|||||||
e2eProjectName: 'my-app-e2e',
|
e2eProjectName: 'my-app-e2e',
|
||||||
e2eProjectRoot: 'apps/my-app-e2e',
|
e2eProjectRoot: 'apps/my-app-e2e',
|
||||||
framework: 'react-native',
|
framework: 'react-native',
|
||||||
|
isUsingTsSolutionConfig: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -94,6 +96,7 @@ describe('Normalize Options', () => {
|
|||||||
e2eDirectory: 'directory',
|
e2eDirectory: 'directory',
|
||||||
e2eProjectName: 'directory-my-app-e2e',
|
e2eProjectName: 'directory-my-app-e2e',
|
||||||
framework: 'react-native',
|
framework: 'react-native',
|
||||||
|
isUsingTsSolutionConfig: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import {
|
|||||||
ensureProjectName,
|
ensureProjectName,
|
||||||
} from '@nx/devkit/src/generators/project-name-and-root-utils';
|
} from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||||
import { Schema } from '../schema';
|
import { Schema } from '../schema';
|
||||||
|
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
|
||||||
|
|
||||||
export interface NormalizedSchema extends Schema {
|
export interface NormalizedSchema extends Schema {
|
||||||
appFileName: string; // the file name of app to be tested in kebab case
|
appFileName: string; // the file name of app to be tested in kebab case
|
||||||
@ -12,6 +13,7 @@ export interface NormalizedSchema extends Schema {
|
|||||||
appRoot: string; // the root path of e2e project. e.g. apps/app-directory/app
|
appRoot: string; // the root path of e2e project. e.g. apps/app-directory/app
|
||||||
e2eProjectName: string; // the name of e2e project
|
e2eProjectName: string; // the name of e2e project
|
||||||
e2eProjectRoot: string; // the root path of e2e project. e.g. apps/e2e-directory/e2e-app
|
e2eProjectRoot: string; // the root path of e2e project. e.g. apps/e2e-directory/e2e-app
|
||||||
|
isUsingTsSolutionConfig?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function normalizeOptions(
|
export async function normalizeOptions(
|
||||||
@ -45,5 +47,6 @@ export async function normalizeOptions(
|
|||||||
e2eName: e2eProjectName,
|
e2eName: e2eProjectName,
|
||||||
e2eProjectName,
|
e2eProjectName,
|
||||||
e2eProjectRoot,
|
e2eProjectRoot,
|
||||||
|
isUsingTsSolutionConfig: isUsingTsSolutionSetup(host),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,114 @@
|
|||||||
|
import 'nx/src/internal-testing-utils/mock-project-graph';
|
||||||
|
|
||||||
|
import { Tree, updateJson, writeJson } from '@nx/devkit';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import configGenerator from './configuration';
|
||||||
|
|
||||||
|
describe('Playwright e2e configuration', () => {
|
||||||
|
let tree: Tree;
|
||||||
|
beforeEach(() => {
|
||||||
|
tree = createTreeWithEmptyWorkspace();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('TS Solution Setup', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
updateJson(tree, 'package.json', (json) => {
|
||||||
|
json.workspaces = ['packages/*', 'apps/*'];
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
writeJson(tree, 'tsconfig.base.json', {
|
||||||
|
compilerOptions: {
|
||||||
|
composite: true,
|
||||||
|
declaration: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
writeJson(tree, 'tsconfig.json', {
|
||||||
|
extends: './tsconfig.base.json',
|
||||||
|
files: [],
|
||||||
|
references: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create tsconfig.json if it does not exist yet (default case for app generators)', async () => {
|
||||||
|
writeJson(tree, 'apps/myapp-e2e/package.json', {
|
||||||
|
name: '@proj/myapp-e2e',
|
||||||
|
});
|
||||||
|
|
||||||
|
await configGenerator(tree, {
|
||||||
|
project: '@proj/myapp-e2e',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tree.read('apps/myapp-e2e/tsconfig.json', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"outDir": "out-tsc/playwright",
|
||||||
|
"sourceMap": false
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.js",
|
||||||
|
"playwright.config.ts",
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.spec.js",
|
||||||
|
"src/**/*.test.ts",
|
||||||
|
"src/**/*.test.js",
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
],
|
||||||
|
"exclude": ["out-tsc", "test-output"]
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
expect(tree.exists('apps/myapp/tsconfig.e2e.json')).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use tsconfig.e2e.json if tsconfig.json already exists', async () => {
|
||||||
|
writeJson(tree, 'apps/myapp/package.json', {
|
||||||
|
name: '@proj/myapp',
|
||||||
|
});
|
||||||
|
writeJson(tree, 'apps/myapp/tsconfig.json', {
|
||||||
|
include: [],
|
||||||
|
files: [],
|
||||||
|
references: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await configGenerator(tree, {
|
||||||
|
project: '@proj/myapp',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tree.read('apps/myapp/tsconfig.json', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"{
|
||||||
|
"include": [],
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.e2e.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
expect(tree.read('apps/myapp/tsconfig.e2e.json', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"outDir": "out-tsc/playwright",
|
||||||
|
"sourceMap": false
|
||||||
|
},
|
||||||
|
"include": ["e2e/**/*.ts", "e2e/**/*.js", "playwright.config.ts"],
|
||||||
|
"exclude": ["out-tsc", "test-output"]
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -74,7 +74,46 @@ export async function configurationGeneratorInternal(
|
|||||||
|
|
||||||
const isTsSolutionSetup = isUsingTsSolutionSetup(tree);
|
const isTsSolutionSetup = isUsingTsSolutionSetup(tree);
|
||||||
const tsconfigPath = joinPathFragments(projectConfig.root, 'tsconfig.json');
|
const tsconfigPath = joinPathFragments(projectConfig.root, 'tsconfig.json');
|
||||||
if (!tree.exists(tsconfigPath)) {
|
if (tree.exists(tsconfigPath)) {
|
||||||
|
if (isTsSolutionSetup) {
|
||||||
|
const tsconfig: any = {
|
||||||
|
extends: getRelativePathToRootTsConfig(tree, projectConfig.root),
|
||||||
|
compilerOptions: {
|
||||||
|
allowJs: true,
|
||||||
|
outDir: 'out-tsc/playwright',
|
||||||
|
sourceMap: false,
|
||||||
|
},
|
||||||
|
include: [
|
||||||
|
joinPathFragments(options.directory, '**/*.ts'),
|
||||||
|
joinPathFragments(options.directory, '**/*.js'),
|
||||||
|
'playwright.config.ts',
|
||||||
|
],
|
||||||
|
exclude: ['out-tsc', 'test-output'],
|
||||||
|
};
|
||||||
|
|
||||||
|
// skip eslint from typechecking since it extends from root file that is outside rootDir
|
||||||
|
if (options.linter === 'eslint') {
|
||||||
|
tsconfig.exclude.push(
|
||||||
|
'eslint.config.js',
|
||||||
|
'eslint.config.mjs',
|
||||||
|
'eslint.config.cjs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJson(
|
||||||
|
tree,
|
||||||
|
joinPathFragments(projectConfig.root, 'tsconfig.e2e.json'),
|
||||||
|
tsconfig
|
||||||
|
);
|
||||||
|
|
||||||
|
updateJson(tree, tsconfigPath, (json) => {
|
||||||
|
// add the project tsconfig to the workspace root tsconfig.json references
|
||||||
|
json.references ??= [];
|
||||||
|
json.references.push({ path: './tsconfig.e2e.json' });
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
const tsconfig: any = {
|
const tsconfig: any = {
|
||||||
extends: getRelativePathToRootTsConfig(tree, projectConfig.root),
|
extends: getRelativePathToRootTsConfig(tree, projectConfig.root),
|
||||||
compilerOptions: {
|
compilerOptions: {
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { defineConfig, devices } from '@playwright/test';
|
import { defineConfig, devices } from '@playwright/test';
|
||||||
import { nxE2EPreset } from '@nx/playwright/preset';
|
import { nxE2EPreset } from '@nx/playwright/preset';
|
||||||
<% if(!webServerCommand || !webServerAddress) { %>// eslint-disable-next-line @typescript-eslint/no-unused-vars <% } %>
|
<% if (webServerCommand && webServerAddress) { %>import { workspaceRoot } from '@nx/devkit';<% } %>
|
||||||
import { workspaceRoot } from '@nx/devkit';
|
|
||||||
|
|
||||||
// For CI, you may want to set BASE_URL to the deployed application.
|
// For CI, you may want to set BASE_URL to the deployed application.
|
||||||
const baseURL = process.env['BASE_URL'] || '<% if(webServerAddress) {%><%= webServerAddress %><% } else {%>http://localhost:3000<% } %>';
|
const baseURL = process.env['BASE_URL'] || '<% if(webServerAddress) {%><%= webServerAddress %><% } else {%>http://localhost:3000<% } %>';
|
||||||
|
|||||||
@ -134,7 +134,6 @@ describe('app', () => {
|
|||||||
const snapshot = `
|
const snapshot = `
|
||||||
"import { defineConfig, devices } from '@playwright/test';
|
"import { defineConfig, devices } from '@playwright/test';
|
||||||
import { nxE2EPreset } from '@nx/playwright/preset';
|
import { nxE2EPreset } from '@nx/playwright/preset';
|
||||||
|
|
||||||
import { workspaceRoot } from '@nx/devkit';
|
import { workspaceRoot } from '@nx/devkit';
|
||||||
|
|
||||||
// For CI, you may want to set BASE_URL to the deployed application.
|
// For CI, you may want to set BASE_URL to the deployed application.
|
||||||
|
|||||||
@ -156,7 +156,6 @@ export default defineConfig({
|
|||||||
exports[`Remix Application Integrated Repo --e2eTestRunner should generate a playwright e2e application for the app 1`] = `
|
exports[`Remix Application Integrated Repo --e2eTestRunner should generate a playwright e2e application for the app 1`] = `
|
||||||
"import { defineConfig, devices } from '@playwright/test';
|
"import { defineConfig, devices } from '@playwright/test';
|
||||||
import { nxE2EPreset } from '@nx/playwright/preset';
|
import { nxE2EPreset } from '@nx/playwright/preset';
|
||||||
|
|
||||||
import { workspaceRoot } from '@nx/devkit';
|
import { workspaceRoot } from '@nx/devkit';
|
||||||
|
|
||||||
// For CI, you may want to set BASE_URL to the deployed application.
|
// For CI, you may want to set BASE_URL to the deployed application.
|
||||||
@ -678,7 +677,6 @@ exports[`Remix Application Standalone Project Repo should create the application
|
|||||||
exports[`Remix Application Standalone Project Repo should generate a playwright e2e application for the app 1`] = `
|
exports[`Remix Application Standalone Project Repo should generate a playwright e2e application for the app 1`] = `
|
||||||
"import { defineConfig, devices } from '@playwright/test';
|
"import { defineConfig, devices } from '@playwright/test';
|
||||||
import { nxE2EPreset } from '@nx/playwright/preset';
|
import { nxE2EPreset } from '@nx/playwright/preset';
|
||||||
|
|
||||||
import { workspaceRoot } from '@nx/devkit';
|
import { workspaceRoot } from '@nx/devkit';
|
||||||
|
|
||||||
// For CI, you may want to set BASE_URL to the deployed application.
|
// For CI, you may want to set BASE_URL to the deployed application.
|
||||||
|
|||||||
@ -327,7 +327,6 @@ describe('App', () => {
|
|||||||
exports[`application generator should set up project correctly with given options 5`] = `
|
exports[`application generator should set up project correctly with given options 5`] = `
|
||||||
"import { defineConfig, devices } from '@playwright/test';
|
"import { defineConfig, devices } from '@playwright/test';
|
||||||
import { nxE2EPreset } from '@nx/playwright/preset';
|
import { nxE2EPreset } from '@nx/playwright/preset';
|
||||||
|
|
||||||
import { workspaceRoot } from '@nx/devkit';
|
import { workspaceRoot } from '@nx/devkit';
|
||||||
|
|
||||||
// For CI, you may want to set BASE_URL to the deployed application.
|
// For CI, you may want to set BASE_URL to the deployed application.
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
exports[`app not nested should generate files if bundler is vite 1`] = `
|
exports[`app not nested should generate files if bundler is vite 1`] = `
|
||||||
"import { defineConfig, devices } from '@playwright/test';
|
"import { defineConfig, devices } from '@playwright/test';
|
||||||
import { nxE2EPreset } from '@nx/playwright/preset';
|
import { nxE2EPreset } from '@nx/playwright/preset';
|
||||||
|
|
||||||
import { workspaceRoot } from '@nx/devkit';
|
import { workspaceRoot } from '@nx/devkit';
|
||||||
|
|
||||||
// For CI, you may want to set BASE_URL to the deployed application.
|
// For CI, you may want to set BASE_URL to the deployed application.
|
||||||
@ -76,7 +75,6 @@ export default defineConfig({
|
|||||||
exports[`app not nested should setup playwright e2e project correctly for webpack 1`] = `
|
exports[`app not nested should setup playwright e2e project correctly for webpack 1`] = `
|
||||||
"import { defineConfig, devices } from '@playwright/test';
|
"import { defineConfig, devices } from '@playwright/test';
|
||||||
import { nxE2EPreset } from '@nx/playwright/preset';
|
import { nxE2EPreset } from '@nx/playwright/preset';
|
||||||
|
|
||||||
import { workspaceRoot } from '@nx/devkit';
|
import { workspaceRoot } from '@nx/devkit';
|
||||||
|
|
||||||
// For CI, you may want to set BASE_URL to the deployed application.
|
// For CI, you may want to set BASE_URL to the deployed application.
|
||||||
@ -149,7 +147,6 @@ export default defineConfig({
|
|||||||
exports[`app not nested should use serve target and port if bundler=vite, e2eTestRunner=playwright, addPlugin=false 1`] = `
|
exports[`app not nested should use serve target and port if bundler=vite, e2eTestRunner=playwright, addPlugin=false 1`] = `
|
||||||
"import { defineConfig, devices } from '@playwright/test';
|
"import { defineConfig, devices } from '@playwright/test';
|
||||||
import { nxE2EPreset } from '@nx/playwright/preset';
|
import { nxE2EPreset } from '@nx/playwright/preset';
|
||||||
|
|
||||||
import { workspaceRoot } from '@nx/devkit';
|
import { workspaceRoot } from '@nx/devkit';
|
||||||
|
|
||||||
// For CI, you may want to set BASE_URL to the deployed application.
|
// For CI, you may want to set BASE_URL to the deployed application.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user