Skip to content

Commit 4c5cecb

Browse files
chore: replace cypress e2e with playwright in automatic vendor sharing
1 parent 53e568c commit 4c5cecb

File tree

6 files changed

+79
-250
lines changed

6 files changed

+79
-250
lines changed

advanced-api/automatic-vendor-sharing/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -449,10 +449,10 @@ jest.mock('app2/Button', () => {
449449

450450
```bash
451451
# Interactive mode
452-
npm run cypress:debug
452+
pnpm exec playwright test --ui
453453

454454
# Headless mode
455-
npm run e2e:ci
455+
pnpm run e2e:ci
456456
```
457457

458458
### Unit Tests
@@ -470,7 +470,7 @@ npm test
470470
- [Module Federation Documentation](https://module-federation.io/)
471471
- [Webpack Module Federation](https://webpack.js.org/concepts/module-federation/)
472472
- [Module Federation Enhanced](https://github.com/module-federation/enhanced)
473-
- [Best Practices Guide](../../cypress-e2e/README.md)
473+
- [Playwright Testing Guide](https://playwright.dev/docs/test-intro)
474474

475475
## Contributing
476476

advanced-api/automatic-vendor-sharing/cypress.env.json

Lines changed: 0 additions & 4 deletions
This file was deleted.

advanced-api/automatic-vendor-sharing/e2e/checkAutomaticVendorApps.cy.ts

Lines changed: 0 additions & 87 deletions
This file was deleted.
Lines changed: 66 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,176 +1,97 @@
1-
import { test, expect, Page } from '@playwright/test';
2-
3-
// Helper functions
4-
async function openLocalhost(page: Page, port: number) {
5-
await page.goto(`http://localhost:${port}`);
6-
await page.waitForLoadState('networkidle');
7-
}
8-
9-
async function checkElementWithTextPresence(page: Page, selector: string, text: string) {
10-
const element = page.locator(`${selector}:has-text("${text}")`);
11-
await expect(element).toBeVisible();
12-
}
13-
14-
async function clickElementWithText(page: Page, selector: string, text: string) {
15-
await page.click(`${selector}:has-text("${text}")`);
16-
}
17-
18-
19-
20-
const appsData = [
1+
import { test, expect } from '@playwright/test';
2+
import { BasePage } from './utils/base-test';
3+
import { Constants } from './utils/constants';
4+
import { selectors } from './utils/selectors';
5+
6+
type AppInfo = {
7+
host: number;
8+
appDisplayName: string;
9+
localButtonText: string;
10+
localButtonColor: string;
11+
remoteButtonText: string;
12+
remoteButtonColor: string;
13+
};
14+
15+
const headerText = Constants.commonConstantsData.headerText;
16+
17+
const apps: AppInfo[] = [
2118
{
22-
headerText: 'Module Federation with Automatic Vendor Sharing',
23-
appNameText: 'App 1 (Host & Remote)',
24-
buttonColor: 'rgb(255, 0, 0)',
2519
host: 3001,
20+
appDisplayName: Constants.commonConstantsData.appDisplayNames.app1,
21+
localButtonText: Constants.commonConstantsData.buttonLabels.app1,
22+
localButtonColor: Constants.color.app1Button,
23+
remoteButtonText: Constants.commonConstantsData.buttonLabels.app2,
24+
remoteButtonColor: Constants.color.app2Button,
2625
},
2726
{
28-
headerText: 'Module Federation with Automatic Vendor Sharing',
29-
appNameText: 'App 2 (Host & Remote)',
30-
buttonColor: 'rgb(0, 0, 139)',
3127
host: 3002,
28+
appDisplayName: Constants.commonConstantsData.appDisplayNames.app2,
29+
localButtonText: Constants.commonConstantsData.buttonLabels.app2,
30+
localButtonColor: Constants.color.app2Button,
31+
remoteButtonText: Constants.commonConstantsData.buttonLabels.app1,
32+
remoteButtonColor: Constants.color.app1Button,
3233
},
3334
];
3435

35-
test.describe('Automatic Vendor Sharing E2E Tests', () => {
36-
37-
appsData.forEach((appData) => {
38-
const { host, appNameText, headerText } = appData;
39-
40-
test.describe(`Check ${appNameText}`, () => {
41-
test(`should display ${appNameText} header and subheader correctly`, async ({ page }) => {
36+
test.describe('Automatic Vendor Sharing example', () => {
37+
for (const app of apps) {
38+
test.describe(app.appDisplayName, () => {
39+
test(`renders the shell for ${app.appDisplayName}`, async ({ page }) => {
40+
const basePage = new BasePage(page);
4241
const consoleErrors: string[] = [];
42+
4343
page.on('console', (msg) => {
4444
if (msg.type() === 'error') {
4545
consoleErrors.push(msg.text());
4646
}
4747
});
4848

49-
await openLocalhost(page, host);
50-
51-
// Check header and subheader exist
52-
await checkElementWithTextPresence(page, 'h1', headerText);
53-
await checkElementWithTextPresence(page, 'h2', appNameText);
49+
await basePage.openLocalhost(app.host);
5450

55-
// Verify no critical console errors
56-
const criticalErrors = consoleErrors.filter(error =>
57-
error.includes('Failed to fetch') ||
58-
error.includes('ChunkLoadError') ||
59-
error.includes('Module not found') ||
60-
error.includes('TypeError')
61-
);
62-
expect(criticalErrors).toHaveLength(0);
63-
});
51+
await expect(page.locator(selectors.tags.headers.h1)).toContainText(headerText);
52+
await expect(page.locator(selectors.tags.headers.h2)).toContainText(app.appDisplayName);
6453

65-
test(`should display ${appNameText} button correctly`, async ({ page }) => {
66-
await openLocalhost(page, host);
54+
const relevantErrors = consoleErrors.filter((error) => {
55+
if (error.includes('WebSocket connection to') && error.includes('WEB_SOCKET_CONNECT_MAGIC_ID')) {
56+
return false;
57+
}
6758

68-
const buttonText = `${appNameText.split(' ')[0]} ${appNameText.split(' ')[1]} Button`;
69-
70-
// Check button exists with correct text
71-
await checkElementWithTextPresence(page, 'button', buttonText);
72-
});
59+
if (error.includes('dynamic-remote-type-hints-plugin')) {
60+
return false;
61+
}
7362

74-
test(`should handle ${appNameText} button interactions`, async ({ page }) => {
75-
await openLocalhost(page, host);
63+
return true;
64+
});
7665

77-
const buttonText = `${appNameText.split(' ')[0]} ${appNameText.split(' ')[1]} Button`;
78-
79-
// Click the button and verify it responds
80-
await clickElementWithText(page, 'button', buttonText);
81-
82-
// Verify button is still visible and functional after click
83-
await checkElementWithTextPresence(page, 'button', buttonText);
84-
});
85-
});
86-
});
87-
88-
test.describe('Cross-App Integration Tests', () => {
89-
test('should demonstrate automatic vendor sharing between apps', async ({ page }) => {
90-
const networkRequests: string[] = [];
91-
92-
page.on('request', (request) => {
93-
networkRequests.push(request.url());
66+
expect(relevantErrors, 'Unexpected console errors detected in the browser console').toHaveLength(0);
9467
});
9568

96-
// Visit both apps to trigger vendor sharing
97-
await page.goto('http://localhost:3001');
98-
await page.waitForLoadState('networkidle');
99-
100-
await page.goto('http://localhost:3002');
101-
await page.waitForLoadState('networkidle');
102-
103-
// Verify shared dependencies are loaded efficiently
104-
const reactRequests = networkRequests.filter(url =>
105-
url.includes('react') && !url.includes('react-dom')
106-
);
107-
108-
// Should not load React multiple times due to vendor sharing
109-
expect(reactRequests.length).toBeLessThanOrEqual(10);
110-
});
69+
test(`exposes the styled local button for ${app.appDisplayName}`, async ({ page }) => {
70+
const basePage = new BasePage(page);
11171

112-
test('should handle CORS correctly for federated modules', async ({ page }) => {
113-
const corsErrors: string[] = [];
114-
page.on('response', (response) => {
115-
if (response.status() >= 400 && response.url().includes('localhost:300')) {
116-
corsErrors.push(`${response.status()} - ${response.url()}`);
117-
}
118-
});
72+
await basePage.openLocalhost(app.host);
11973

120-
// Test cross-origin requests work properly
121-
await page.goto('http://localhost:3001');
122-
await page.waitForLoadState('networkidle');
74+
const localButton = page.getByRole('button', { name: app.localButtonText });
75+
await expect(localButton).toBeVisible();
76+
await expect(localButton).toHaveCSS('background-color', app.localButtonColor);
12377

124-
// Should have no CORS errors
125-
expect(corsErrors).toHaveLength(0);
126-
});
78+
await localButton.click();
79+
await expect(localButton).toBeVisible();
80+
});
12781

128-
test('should load applications within reasonable time', async ({ page }) => {
129-
const startTime = Date.now();
130-
131-
await page.goto('http://localhost:3001');
132-
await page.waitForLoadState('networkidle');
133-
134-
const loadTime = Date.now() - startTime;
135-
expect(loadTime).toBeLessThan(10000); // Should load within 10 seconds
136-
});
137-
});
82+
test(`loads the remote button for ${app.appDisplayName}`, async ({ page }) => {
83+
const basePage = new BasePage(page);
13884

139-
test.describe('AutomaticVendorFederation Features', () => {
140-
test('should demonstrate shared vendor optimization', async ({ page }) => {
141-
await page.goto('http://localhost:3001');
142-
await page.waitForLoadState('networkidle');
85+
await basePage.openLocalhost(app.host);
86+
await basePage.waitForDynamicImport();
14387

144-
// Check that the main elements are present
145-
await checkElementWithTextPresence(page, 'h1', 'Module Federation with Automatic Vendor Sharing');
146-
await checkElementWithTextPresence(page, 'h2', 'App 1 (Host & Remote)');
147-
});
88+
const remoteButton = page.getByRole('button', { name: app.remoteButtonText });
89+
await expect(remoteButton).toBeVisible();
90+
await expect(remoteButton).toHaveCSS('background-color', app.remoteButtonColor);
14891

149-
test('should handle error boundaries correctly', async ({ page }) => {
150-
const consoleErrors: string[] = [];
151-
page.on('console', (msg) => {
152-
if (msg.type() === 'error') {
153-
consoleErrors.push(msg.text());
154-
}
92+
await remoteButton.click();
93+
await expect(remoteButton).toBeVisible();
15594
});
156-
157-
await page.goto('http://localhost:3001');
158-
await page.waitForLoadState('networkidle');
159-
160-
// Click button to test functionality
161-
const buttonExists = await page.locator('button').first().isVisible();
162-
if (buttonExists) {
163-
await page.locator('button').first().click();
164-
await page.waitForTimeout(1000);
165-
}
166-
167-
// Should handle any errors gracefully
168-
const criticalErrors = consoleErrors.filter(error =>
169-
error.includes('Uncaught') &&
170-
!error.includes('webpack-dev-server') &&
171-
!error.includes('DevTools')
172-
);
173-
expect(criticalErrors).toHaveLength(0);
17495
});
175-
});
176-
});
96+
}
97+
});
Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
export const Constants = {
22
commonConstantsData: {
3-
biDirectional: 'Module Federation with Automatic Vendor Sharing',
4-
button: 'Button',
5-
commonCountAppNames: {
3+
headerText: 'Module Federation with Automatic Vendor Sharing',
4+
appDisplayNames: {
65
app1: 'App 1 (Host & Remote)',
76
app2: 'App 2 (Host & Remote)',
87
},
8+
buttonLabels: {
9+
app1: 'App 1 Button',
10+
app2: 'App 2 Button',
11+
},
912
},
1013
color: {
11-
red: 'rgb(255, 0, 0)',
12-
deepBlue: 'rgb(0, 0, 139)',
14+
app1Button: 'rgb(136, 0, 0)',
15+
app2Button: 'rgb(0, 0, 204)',
1316
},
1417
};

advanced-api/automatic-vendor-sharing/e2e/utils/selectors.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
export const selectors = {
2-
dataTestIds: {
3-
app1Button: '[data-e2e="APP_1__BUTTON"]',
4-
app2Button: '[data-e2e="APP_2__BUTTON"]',
5-
},
62
tags: {
73
headers: {
8-
h1: 'h1',
9-
h2: 'h2',
4+
h1: 'header h1',
5+
h2: 'header h2',
106
},
117
coreElements: {
128
button: 'button',

0 commit comments

Comments
 (0)