Skip to content

Commit

Permalink
chore(react): separate module federation tests into separate test fil…
Browse files Browse the repository at this point in the history
…es for better distribution (nrwl#28765)

<!-- 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 -->
Module Federation tests take ~21-25mins

![image](https://github.com/user-attachments/assets/771f6314-2fba-4340-9617-d7fb0dd40c93)


## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Module Federation tests take ~11-13mins

![image](https://github.com/user-attachments/assets/9a720178-5f0c-4b60-a3af-53b3f0db1111)


## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
  • Loading branch information
Coly010 authored Nov 4, 2024
1 parent eea38d7 commit 48db8dd
Show file tree
Hide file tree
Showing 12 changed files with 2,410 additions and 2,359 deletions.
528 changes: 528 additions & 0 deletions e2e/react/src/module-federation/core.rspack.test.ts

Large diffs are not rendered by default.

348 changes: 348 additions & 0 deletions e2e/react/src/module-federation/core.webpack.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,348 @@
import { stripIndents } from '@nx/devkit';
import {
checkFilesExist,
cleanupProject,
killPorts,
killProcessAndPorts,
newProject,
runCLI,
runCLIAsync,
runCommandUntil,
runE2ETests,
uniq,
updateFile,
} from '@nx/e2e/utils';
import { readPort } from './utils';

describe('React Module Federation', () => {
describe('Default Configuration', () => {
beforeAll(() => {
newProject({ packages: ['@nx/react', '@nx/webpack'] });
});

afterAll(() => cleanupProject());

it.each`
js
${false}
${true}
`(
'should generate host and remote apps with "--js=$js"',
async ({ js }) => {
const shell = uniq('shell');
const remote1 = uniq('remote1');
const remote2 = uniq('remote2');
const remote3 = uniq('remote3');

runCLI(
`generate @nx/react:host ${shell} --remotes=${remote1},${remote2},${remote3} --bundler=webpack --e2eTestRunner=cypress --style=css --no-interactive --skipFormat --js=${js}`
);

checkFilesExist(
`${shell}/module-federation.config.${js ? 'js' : 'ts'}`
);
checkFilesExist(
`${remote1}/module-federation.config.${js ? 'js' : 'ts'}`
);
checkFilesExist(
`${remote2}/module-federation.config.${js ? 'js' : 'ts'}`
);
checkFilesExist(
`${remote3}/module-federation.config.${js ? 'js' : 'ts'}`
);

await expect(runCLIAsync(`test ${shell}`)).resolves.toMatchObject({
combinedOutput: expect.stringContaining(
'Test Suites: 1 passed, 1 total'
),
});

updateFile(
`${shell}-e2e/src/integration/app.spec.${js ? 'js' : 'ts'}`,
stripIndents`
import { getGreeting } from '../support/app.po';
describe('shell app', () => {
it('should display welcome message', () => {
cy.visit('/')
getGreeting().contains('Welcome ${shell}');
});
it('should load remote 1', () => {
cy.visit('/${remote1}')
getGreeting().contains('Welcome ${remote1}');
});
it('should load remote 2', () => {
cy.visit('/${remote2}')
getGreeting().contains('Welcome ${remote2}');
});
it('should load remote 3', () => {
cy.visit('/${remote3}')
getGreeting().contains('Welcome ${remote3}');
});
});
`
);

[shell, remote1, remote2, remote3].forEach((app) => {
['development', 'production'].forEach(async (configuration) => {
const cliOutput = runCLI(`run ${app}:build:${configuration}`);
expect(cliOutput).toContain('Successfully ran target');
});
});

const serveResult = await runCommandUntil(`serve ${shell}`, (output) =>
output.includes(`http://localhost:${readPort(shell)}`)
);

await killProcessAndPorts(serveResult.pid, readPort(shell));

if (runE2ETests()) {
const e2eResultsSwc = await runCommandUntil(
`e2e ${shell}-e2e --no-watch --verbose`,
(output) => output.includes('All specs passed!')
);

await killProcessAndPorts(e2eResultsSwc.pid, readPort(shell));

const e2eResultsTsNode = await runCommandUntil(
`e2e ${shell}-e2e --no-watch --verbose`,
(output) =>
output.includes('Successfully ran target e2e for project'),
{
env: { NX_PREFER_TS_NODE: 'true' },
}
);
await killProcessAndPorts(e2eResultsTsNode.pid, readPort(shell));
}
},
500_000
);

it('should generate host and remote apps and use playwright for e2es', async () => {
const shell = uniq('shell');
const remote1 = uniq('remote1');
const remote2 = uniq('remote2');
const remote3 = uniq('remote3');

runCLI(
`generate @nx/react:host apps/${shell} --name=${shell} --remotes=${remote1},${remote2},${remote3} --bundler=webpack --e2eTestRunner=playwright --style=css --no-interactive --skipFormat`
);

checkFilesExist(`apps/${shell}/module-federation.config.ts`);
checkFilesExist(`apps/${remote1}/module-federation.config.ts`);
checkFilesExist(`apps/${remote2}/module-federation.config.ts`);
checkFilesExist(`apps/${remote3}/module-federation.config.ts`);

await expect(runCLIAsync(`test ${shell}`)).resolves.toMatchObject({
combinedOutput: expect.stringContaining(
'Test Suites: 1 passed, 1 total'
),
});

updateFile(
`apps/${shell}-e2e/src/example.spec.ts`,
stripIndents`
import { test, expect } from '@playwright/test';
test('should display welcome message', async ({page}) => {
await page.goto("/");
expect(await page.locator('h1').innerText()).toContain('Welcome');
});
test('should load remote 1', async ({page}) => {
await page.goto("/${remote1}");
expect(await page.locator('h1').innerText()).toContain('${remote1}');
});
test('should load remote 2', async ({page}) => {
await page.goto("/${remote2}");
expect(await page.locator('h1').innerText()).toContain('${remote2}');
});
test('should load remote 3', async ({page}) => {
await page.goto("/${remote3}");
expect(await page.locator('h1').innerText()).toContain('${remote3}');
});
`
);

if (runE2ETests()) {
const e2eResultsSwc = await runCommandUntil(
`e2e ${shell}-e2e`,
(output) => output.includes('Successfully ran target e2e for project')
);

await killProcessAndPorts(e2eResultsSwc.pid, readPort(shell));

const e2eResultsTsNode = await runCommandUntil(
`e2e ${shell}-e2e`,
(output) =>
output.includes('Successfully ran target e2e for project'),
{
env: { NX_PREFER_TS_NODE: 'true' },
}
);
await killProcessAndPorts(e2eResultsTsNode.pid, readPort(shell));
}
}, 500_000);

describe('ssr', () => {
it('should generate host and remote apps with ssr', async () => {
const shell = uniq('shell');
const remote1 = uniq('remote1');
const remote2 = uniq('remote2');
const remote3 = uniq('remote3');

await runCLIAsync(
`generate @nx/react:host ${shell} --bundler=webpack --ssr --remotes=${remote1},${remote2},${remote3} --style=css --no-interactive --skipFormat`
);

expect(readPort(shell)).toEqual(4200);
expect(readPort(remote1)).toEqual(4201);
expect(readPort(remote2)).toEqual(4202);
expect(readPort(remote3)).toEqual(4203);

[shell, remote1, remote2, remote3].forEach((app) => {
checkFilesExist(
`${app}/module-federation.config.ts`,
`${app}/module-federation.server.config.ts`
);
['build', 'server'].forEach((target) => {
['development', 'production'].forEach(async (configuration) => {
const cliOutput = runCLI(`run ${app}:${target}:${configuration}`);
expect(cliOutput).toContain('Successfully ran target');

await killPorts(readPort(app));
});
});
});
}, 500_000);

it('should serve remotes as static when running the host by default', async () => {
const shell = uniq('shell');
const remote1 = uniq('remote1');
const remote2 = uniq('remote2');
const remote3 = uniq('remote3');

await runCLIAsync(
`generate @nx/react:host ${shell} --ssr --bundler=webpack --remotes=${remote1},${remote2},${remote3} --style=css --e2eTestRunner=cypress --no-interactive --skipFormat`
);

const serveResult = await runCommandUntil(`serve ${shell}`, (output) =>
output.includes(`Nx SSR Static remotes proxies started successfully`)
);

await killProcessAndPorts(serveResult.pid);
}, 500_000);

it('should serve remotes as static and they should be able to be accessed from the host', async () => {
const shell = uniq('shell');
const remote1 = uniq('remote1');
const remote2 = uniq('remote2');
const remote3 = uniq('remote3');

await runCLIAsync(
`generate @nx/react:host ${shell} --bundler=webpack --ssr --remotes=${remote1},${remote2},${remote3} --style=css --e2eTestRunner=cypress --no-interactive --skipFormat`
);

const capitalize = (s: string) =>
s.charAt(0).toUpperCase() + s.slice(1);

updateFile(`${shell}-e2e/src/e2e/app.cy.ts`, (content) => {
return `
describe('${shell}-e2e', () => {
beforeEach(() => cy.visit('/'));
it('should display welcome message', () => {
expect(cy.get('ul li').should('have.length', 4));
expect(cy.get('ul li').eq(0).should('have.text', 'Home'));
expect(cy.get('ul li').eq(1).should('have.text', '${capitalize(
remote1
)}'));
expect(cy.get('ul li').eq(2).should('have.text', '${capitalize(
remote2
)}'));
expect(cy.get('ul li').eq(3).should('have.text', '${capitalize(
remote3
)}'));
});
});
`;
});

if (runE2ETests()) {
const hostE2eResults = await runCommandUntil(
`e2e ${shell}-e2e --no-watch --verbose`,
(output) => output.includes('All specs passed!')
);
await killProcessAndPorts(hostE2eResults.pid);
}
}, 600_000);
});

it('should should support generating host and remote apps with the new name and root format', async () => {
const shell = uniq('shell');
const remote = uniq('remote');

runCLI(
`generate @nx/react:host ${shell} --bundler=webpack --no-interactive --skipFormat`
);
runCLI(
`generate @nx/react:remote ${remote} --bundler=webpack --host=${shell} --no-interactive --skipFormat`
);

const shellPort = readPort(shell);
const remotePort = readPort(remote);

// check files are generated without the layout directory ("apps/") and
// using the project name as the directory when no directory is provided
checkFilesExist(`${shell}/module-federation.config.ts`);
checkFilesExist(`${remote}/module-federation.config.ts`);

// check default generated host is built successfully
const buildOutputSwc = runCLI(`run ${shell}:build:development`);
expect(buildOutputSwc).toContain('Successfully ran target build');

const buildOutputTsNode = runCLI(`run ${shell}:build:development`, {
env: { NX_PREFER_TS_NODE: 'true' },
});
expect(buildOutputTsNode).toContain('Successfully ran target build');

// check serves devRemotes ok
const shellProcessSwc = await runCommandUntil(
`serve ${shell} --devRemotes=${remote} --verbose`,
(output) => {
return output.includes(
`All remotes started, server ready at http://localhost:${shellPort}`
);
}
);
await killProcessAndPorts(
shellProcessSwc.pid,
shellPort,
remotePort + 1,
remotePort
);

const shellProcessTsNode = await runCommandUntil(
`serve ${shell} --devRemotes=${remote} --verbose`,
(output) => {
return output.includes(
`All remotes started, server ready at http://localhost:${shellPort}`
);
},
{
env: { NX_PREFER_TS_NODE: 'true' },
}
);
await killProcessAndPorts(
shellProcessTsNode.pid,
shellPort,
remotePort + 1,
remotePort
);
}, 500_000);
});
});
Loading

0 comments on commit 48db8dd

Please sign in to comment.