Skip to content

Commit

Permalink
fix(testing): add migration for playwright to use serve-static or pre…
Browse files Browse the repository at this point in the history
…view for command
  • Loading branch information
Coly010 committed Aug 1, 2024
1 parent fe42f36 commit 28be2db
Show file tree
Hide file tree
Showing 3 changed files with 382 additions and 0 deletions.
6 changes: 6 additions & 0 deletions packages/playwright/migrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
"version": "18.1.0-beta.3",
"description": "Remove invalid baseUrl option from @nx/playwright:playwright targets in project.json.",
"implementation": "./src/migrations/update-18-1-0/remove-baseUrl-from-project-json"
},
"19-5-5-use-serve-static-preview-for-command": {
"cli": "nx",
"version": "19.5.5-beta.0",
"description": "Use serve-static or preview for webServerCommand.",
"implementation": "./src/migrations/update-19-5-5/use-serve-static-preview-for-command"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { ProjectGraph, type Tree } from '@nx/devkit';
import useServeStaticPreviewForCommand from './use-serve-static-preview-for-command';
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';

let projectGraph: ProjectGraph;
jest.mock('@nx/devkit', () => ({
...jest.requireActual<any>('@nx/devkit'),
createProjectGraphAsync: jest.fn().mockImplementation(async () => {
return projectGraph;
}),
}));

describe('useServeStaticPreviewForCommand', () => {
let tree: Tree;
let tempFs: TempFs;

beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
tempFs = new TempFs('add-e2e-ci');
tree.root = tempFs.tempDir;
projectGraph = {
nodes: {},
dependencies: {},
externalNodes: {},
};
});

afterEach(() => {
tempFs.reset();
});

it('should update when it does not use serve-static for non-vite', async () => {
// ARRANGE
addProject(tree, tempFs, { noVite: 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:4200';
export default defineConfig({
...nxE2EPreset(__filename, { testDir: './src' }),
use: {
baseURL,
trace: 'on-first-retry',
},
webServer: {
command: 'npx nx run app: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
addProject(tree, tempFs);

// 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: 'npx nx run app:preview',
url: 'http://localhost:4300',
reuseExistingServer: !process.env.CI,
cwd: workspaceRoot,
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});
"
`);
});
});

const basePlaywrightConfig = (
appName: string
) => `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 ${appName}:serve',
url: 'http://localhost:4200',
reuseExistingServer: !process.env.CI,
cwd: workspaceRoot,
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});`;

const viteConfig = `/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../../node_modules/.vite/app',
server: {
port: 4200,
host: 'localhost',
},
preview: {
port: 4300,
host: 'localhost',
},
plugins: [react(), nxViteTsPaths()],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
outDir: '../../dist/app',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
},
});`;

function addProject(
tree: Tree,
tempFs: TempFs,
overrides: {
noVite?: boolean;
} = {}
) {
const appProjectConfig = {
name: 'app',
root: 'app',
sourceRoot: `${'app'}/src`,
projectType: 'application',
};

const e2eProjectConfig = {
name: `app-e2e`,
root: `app-e2e`,
sourceRoot: `app-e2e/src`,
projectType: 'application',
};

if (!overrides.noVite) {
tree.write(`app/vite.config.ts`, viteConfig);
} else {
tree.write(`app/webpack.config.ts`, ``);
}

tree.write(`app/project.json`, JSON.stringify(appProjectConfig));
tree.write(`app-e2e/playwright.config.ts`, basePlaywrightConfig('app'));
tree.write(`app-e2e/project.json`, JSON.stringify(e2eProjectConfig));
if (!overrides.noVite) {
tempFs.createFile(`app/vite.config.ts`, viteConfig);
} else {
tempFs.createFile(`app/webpack.config.ts`, ``);
}
tempFs.createFilesSync({
[`app/project.json`]: JSON.stringify(appProjectConfig),
[`app-e2e/playwright.config.ts`]: basePlaywrightConfig('app'),
[`app-e2e/project.json`]: JSON.stringify(e2eProjectConfig),
});

projectGraph.nodes['app'] = {
name: 'app',
type: 'app',
data: {
projectType: 'application',
root: 'app',
targets: {},
},
};

projectGraph.nodes[`app-e2e`] = {
name: `app-e2e`,
type: 'app',
data: {
projectType: 'application',
root: `app-e2e`,
targets: {
e2e: {},
},
},
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import {
createProjectGraphAsync,
formatFiles,
getPackageManagerCommand,
joinPathFragments,
parseTargetString,
type Tree,
visitNotIgnoredFiles,
} from '@nx/devkit';
import { tsquery } from '@phenomnomnominal/tsquery';

export default async function (tree: Tree) {
const graph = await createProjectGraphAsync();
visitNotIgnoredFiles(tree, '', (path) => {
if (!path.endsWith('playwright.config.ts')) {
return;
}

let playwrightConfigFileContents = tree.read(path, 'utf-8');

const WEBSERVER_COMMAND_SELECTOR =
'PropertyAssignment:has(Identifier[name=webServer]) PropertyAssignment:has(Identifier[name=command]) > StringLiteral';
let ast = tsquery.ast(playwrightConfigFileContents);
const nodes = tsquery(ast, WEBSERVER_COMMAND_SELECTOR, {
visitAllChildren: true,
});
if (!nodes.length) {
return;
}

const commandValueNode = nodes[0];
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);
if (!matches) {
return;
}
const targetString = matches[0];
const parsedTargetString = parseTargetString(targetString, graph);

if (
parsedTargetString.target === 'serve-static' ||
parsedTargetString.target === 'preview'
) {
return;
}

project = parsedTargetString.project;
} else {
const NX_PROJECT_REGEX = "(?<=nx [^ ]+ )[^']+";
const matches = command.match(NX_PROJECT_REGEX);
if (!matches) {
return;
}
project = matches[0];
}

const pathToViteConfig = [
joinPathFragments(graph.nodes[project].data.root, 'vite.config.ts'),
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`;
tree.write(
path,
`${playwrightConfigFileContents.slice(
0,
commandValueNode.getStart()
)}"${newCommand}"${playwrightConfigFileContents.slice(
commandValueNode.getEnd()
)}`
);
} else {
const newCommand = `${
getPackageManagerCommand().exec
} nx run ${project}:preview`;
tree.write(
path,
`${playwrightConfigFileContents.slice(
0,
commandValueNode.getStart()
)}"${newCommand}"${playwrightConfigFileContents.slice(
commandValueNode.getEnd()
)}`
);
playwrightConfigFileContents = tree.read(path, 'utf-8');
ast = tsquery.ast(playwrightConfigFileContents);

const BASE_URL_SELECTOR =
'VariableDeclaration:has(Identifier[name=baseURL])';
const baseUrlNodes = tsquery(ast, BASE_URL_SELECTOR, {
visitAllChildren: true,
});
if (!baseUrlNodes.length) {
return;
}

const baseUrlNode = baseUrlNodes[0];
const newBaseUrlVariableDeclaration =
"baseURL = process.env['BASE_URL'] || 'http://localhost:4300';";
tree.write(
path,
`${playwrightConfigFileContents.slice(
0,
baseUrlNode.getStart()
)}${newBaseUrlVariableDeclaration}${playwrightConfigFileContents.slice(
baseUrlNode.getEnd()
)}`
);

playwrightConfigFileContents = tree.read(path, 'utf-8');
ast = tsquery.ast(playwrightConfigFileContents);
const WEB_SERVER_URL_SELECTOR =
'PropertyAssignment:has(Identifier[name=webServer]) PropertyAssignment:has(Identifier[name=url]) > StringLiteral';
const webServerUrlNodes = tsquery(ast, WEB_SERVER_URL_SELECTOR, {
visitAllChildren: true,
});
if (!webServerUrlNodes.length) {
return;
}

const webServerUrlNode = webServerUrlNodes[0];
const newWebServerUrl = "'http://localhost:4300'";
tree.write(
path,
`${playwrightConfigFileContents.slice(
0,
webServerUrlNode.getStart()
)}${newWebServerUrl}${playwrightConfigFileContents.slice(
webServerUrlNode.getEnd()
)}`
);
}
});

await formatFiles(tree);
}

0 comments on commit 28be2db

Please sign in to comment.