diff --git a/advanced-api/automatic-vendor-sharing/README.md b/advanced-api/automatic-vendor-sharing/README.md index 49573321cb..a1c1a4d837 100644 --- a/advanced-api/automatic-vendor-sharing/README.md +++ b/advanced-api/automatic-vendor-sharing/README.md @@ -449,10 +449,10 @@ jest.mock('app2/Button', () => { ```bash # Interactive mode -npm run cypress:debug +pnpm exec playwright test --ui # Headless mode -npm run e2e:ci +pnpm run e2e:ci ``` ### Unit Tests @@ -470,7 +470,7 @@ npm test - [Module Federation Documentation](https://module-federation.io/) - [Webpack Module Federation](https://webpack.js.org/concepts/module-federation/) - [Module Federation Enhanced](https://github.com/module-federation/enhanced) -- [Best Practices Guide](../../cypress-e2e/README.md) +- [Playwright Testing Guide](https://playwright.dev/docs/test-intro) ## Contributing diff --git a/advanced-api/automatic-vendor-sharing/cypress.env.json b/advanced-api/automatic-vendor-sharing/cypress.env.json deleted file mode 100644 index e63233bb67..0000000000 --- a/advanced-api/automatic-vendor-sharing/cypress.env.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "allure": true, - "allureResultsPath": "../../cypress-e2e/results/allure-results" -} diff --git a/advanced-api/automatic-vendor-sharing/e2e/checkAutomaticVendorApps.cy.ts b/advanced-api/automatic-vendor-sharing/e2e/checkAutomaticVendorApps.cy.ts deleted file mode 100644 index 013f88e73b..0000000000 --- a/advanced-api/automatic-vendor-sharing/e2e/checkAutomaticVendorApps.cy.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { BaseMethods } from '../../../cypress-e2e/common/base'; -import { baseSelectors } from '../../../cypress-e2e/common/selectors'; -import { Constants } from '../../../cypress-e2e/fixtures/constants'; -import { CssAttr } from '../../../cypress-e2e/types/cssAttr'; - -const basePage: BaseMethods = new BaseMethods(); - -const appButtonPosition: number = 0; - -const appsData = [ - { - headerSelector: baseSelectors.tags.headers.h1, - subHeaderSelector: baseSelectors.tags.headers.h2, - buttonSelector: baseSelectors.tags.coreElements.button, - headerText: Constants.commonConstantsData.biDirectional, - appNameText: Constants.commonConstantsData.commonCountAppNames.app1, - buttonColor: Constants.color.red, - host: 3001, - }, - { - headerSelector: baseSelectors.tags.headers.h1, - subHeaderSelector: baseSelectors.tags.headers.h2, - buttonSelector: baseSelectors.tags.coreElements.button, - headerText: Constants.commonConstantsData.biDirectional, - appNameText: Constants.commonConstantsData.commonCountAppNames.app2, - buttonColor: Constants.color.deepBlue, - host: 3002, - }, -]; - -appsData.forEach( - (property: { - headerSelector: string; - subHeaderSelector: string; - buttonSelector: string; - headerText: string; - appNameText: string; - buttonColor: string; - host: number; - }) => { - const host = property.host === 3002 ? appsData[1].host : appsData[0].host; - const appName = property.host === 3002 ? appsData[1].appNameText : appsData[0].appNameText; - const color = property.host === 3002 ? appsData[1].buttonColor : appsData[0].buttonColor; - - describe(`Automatic Vendor Sharing`, () => { - context(`Check ${appName}`, () => { - beforeEach(() => { - basePage.openLocalhost({ - number: host, - }); - }); - - it(`Check ${appName} header and subheader exist on the page`, () => { - basePage.checkElementWithTextPresence({ - selector: property.headerSelector, - text: property.headerText, - }); - basePage.checkElementWithTextPresence({ - selector: property.subHeaderSelector, - text: `${appName}`, - }); - }); - - it(`Check buttons in ${appName} exist`, () => { - basePage.checkElementWithTextPresence({ - selector: property.buttonSelector, - text: `${appName} ${Constants.commonConstantsData.button}`, - }); - }); - - it(`Check button property in ${appName}`, () => { - basePage.checkElementContainText({ - selector: property.buttonSelector, - text: `${appName} ${Constants.commonConstantsData.button}`, - index: appButtonPosition, - }); - basePage.checkElementHaveProperty({ - selector: property.buttonSelector, - text: `${appName} ${Constants.commonConstantsData.button}`, - prop: CssAttr.background, - value: color, - }); - }); - }); - }); - }, -); diff --git a/advanced-api/automatic-vendor-sharing/e2e/checkAutomaticVendorApps.spec.ts b/advanced-api/automatic-vendor-sharing/e2e/checkAutomaticVendorApps.spec.ts index 6bd6ba97b7..f1b81d9746 100644 --- a/advanced-api/automatic-vendor-sharing/e2e/checkAutomaticVendorApps.spec.ts +++ b/advanced-api/automatic-vendor-sharing/e2e/checkAutomaticVendorApps.spec.ts @@ -1,176 +1,97 @@ -import { test, expect, Page } from '@playwright/test'; - -// Helper functions -async function openLocalhost(page: Page, port: number) { - await page.goto(`http://localhost:${port}`); - await page.waitForLoadState('networkidle'); -} - -async function checkElementWithTextPresence(page: Page, selector: string, text: string) { - const element = page.locator(`${selector}:has-text("${text}")`); - await expect(element).toBeVisible(); -} - -async function clickElementWithText(page: Page, selector: string, text: string) { - await page.click(`${selector}:has-text("${text}")`); -} - - - -const appsData = [ +import { test, expect } from '@playwright/test'; +import { BasePage } from './utils/base-test'; +import { Constants } from './utils/constants'; +import { selectors } from './utils/selectors'; + +type AppInfo = { + host: number; + appDisplayName: string; + localButtonText: string; + localButtonColor: string; + remoteButtonText: string; + remoteButtonColor: string; +}; + +const headerText = Constants.commonConstantsData.headerText; + +const apps: AppInfo[] = [ { - headerText: 'Module Federation with Automatic Vendor Sharing', - appNameText: 'App 1 (Host & Remote)', - buttonColor: 'rgb(255, 0, 0)', host: 3001, + appDisplayName: Constants.commonConstantsData.appDisplayNames.app1, + localButtonText: Constants.commonConstantsData.buttonLabels.app1, + localButtonColor: Constants.color.app1Button, + remoteButtonText: Constants.commonConstantsData.buttonLabels.app2, + remoteButtonColor: Constants.color.app2Button, }, { - headerText: 'Module Federation with Automatic Vendor Sharing', - appNameText: 'App 2 (Host & Remote)', - buttonColor: 'rgb(0, 0, 139)', host: 3002, + appDisplayName: Constants.commonConstantsData.appDisplayNames.app2, + localButtonText: Constants.commonConstantsData.buttonLabels.app2, + localButtonColor: Constants.color.app2Button, + remoteButtonText: Constants.commonConstantsData.buttonLabels.app1, + remoteButtonColor: Constants.color.app1Button, }, ]; -test.describe('Automatic Vendor Sharing E2E Tests', () => { - - appsData.forEach((appData) => { - const { host, appNameText, headerText } = appData; - - test.describe(`Check ${appNameText}`, () => { - test(`should display ${appNameText} header and subheader correctly`, async ({ page }) => { +test.describe('Automatic Vendor Sharing example', () => { + for (const app of apps) { + test.describe(app.appDisplayName, () => { + test(`renders the shell for ${app.appDisplayName}`, async ({ page }) => { + const basePage = new BasePage(page); const consoleErrors: string[] = []; + page.on('console', (msg) => { if (msg.type() === 'error') { consoleErrors.push(msg.text()); } }); - await openLocalhost(page, host); - - // Check header and subheader exist - await checkElementWithTextPresence(page, 'h1', headerText); - await checkElementWithTextPresence(page, 'h2', appNameText); + await basePage.openLocalhost(app.host); - // Verify no critical console errors - const criticalErrors = consoleErrors.filter(error => - error.includes('Failed to fetch') || - error.includes('ChunkLoadError') || - error.includes('Module not found') || - error.includes('TypeError') - ); - expect(criticalErrors).toHaveLength(0); - }); + await expect(page.locator(selectors.tags.headers.h1)).toContainText(headerText); + await expect(page.locator(selectors.tags.headers.h2)).toContainText(app.appDisplayName); - test(`should display ${appNameText} button correctly`, async ({ page }) => { - await openLocalhost(page, host); + const relevantErrors = consoleErrors.filter((error) => { + if (error.includes('WebSocket connection to') && error.includes('WEB_SOCKET_CONNECT_MAGIC_ID')) { + return false; + } - const buttonText = `${appNameText.split(' ')[0]} ${appNameText.split(' ')[1]} Button`; - - // Check button exists with correct text - await checkElementWithTextPresence(page, 'button', buttonText); - }); + if (error.includes('dynamic-remote-type-hints-plugin')) { + return false; + } - test(`should handle ${appNameText} button interactions`, async ({ page }) => { - await openLocalhost(page, host); + return true; + }); - const buttonText = `${appNameText.split(' ')[0]} ${appNameText.split(' ')[1]} Button`; - - // Click the button and verify it responds - await clickElementWithText(page, 'button', buttonText); - - // Verify button is still visible and functional after click - await checkElementWithTextPresence(page, 'button', buttonText); - }); - }); - }); - - test.describe('Cross-App Integration Tests', () => { - test('should demonstrate automatic vendor sharing between apps', async ({ page }) => { - const networkRequests: string[] = []; - - page.on('request', (request) => { - networkRequests.push(request.url()); + expect(relevantErrors, 'Unexpected console errors detected in the browser console').toHaveLength(0); }); - // Visit both apps to trigger vendor sharing - await page.goto('http://localhost:3001'); - await page.waitForLoadState('networkidle'); - - await page.goto('http://localhost:3002'); - await page.waitForLoadState('networkidle'); - - // Verify shared dependencies are loaded efficiently - const reactRequests = networkRequests.filter(url => - url.includes('react') && !url.includes('react-dom') - ); - - // Should not load React multiple times due to vendor sharing - expect(reactRequests.length).toBeLessThanOrEqual(10); - }); + test(`exposes the styled local button for ${app.appDisplayName}`, async ({ page }) => { + const basePage = new BasePage(page); - test('should handle CORS correctly for federated modules', async ({ page }) => { - const corsErrors: string[] = []; - page.on('response', (response) => { - if (response.status() >= 400 && response.url().includes('localhost:300')) { - corsErrors.push(`${response.status()} - ${response.url()}`); - } - }); + await basePage.openLocalhost(app.host); - // Test cross-origin requests work properly - await page.goto('http://localhost:3001'); - await page.waitForLoadState('networkidle'); + const localButton = page.getByRole('button', { name: app.localButtonText }); + await expect(localButton).toBeVisible(); + await expect(localButton).toHaveCSS('background-color', app.localButtonColor); - // Should have no CORS errors - expect(corsErrors).toHaveLength(0); - }); + await localButton.click(); + await expect(localButton).toBeVisible(); + }); - test('should load applications within reasonable time', async ({ page }) => { - const startTime = Date.now(); - - await page.goto('http://localhost:3001'); - await page.waitForLoadState('networkidle'); - - const loadTime = Date.now() - startTime; - expect(loadTime).toBeLessThan(10000); // Should load within 10 seconds - }); - }); + test(`loads the remote button for ${app.appDisplayName}`, async ({ page }) => { + const basePage = new BasePage(page); - test.describe('AutomaticVendorFederation Features', () => { - test('should demonstrate shared vendor optimization', async ({ page }) => { - await page.goto('http://localhost:3001'); - await page.waitForLoadState('networkidle'); + await basePage.openLocalhost(app.host); + await basePage.waitForDynamicImport(); - // Check that the main elements are present - await checkElementWithTextPresence(page, 'h1', 'Module Federation with Automatic Vendor Sharing'); - await checkElementWithTextPresence(page, 'h2', 'App 1 (Host & Remote)'); - }); + const remoteButton = page.getByRole('button', { name: app.remoteButtonText }); + await expect(remoteButton).toBeVisible(); + await expect(remoteButton).toHaveCSS('background-color', app.remoteButtonColor); - test('should handle error boundaries correctly', async ({ page }) => { - const consoleErrors: string[] = []; - page.on('console', (msg) => { - if (msg.type() === 'error') { - consoleErrors.push(msg.text()); - } + await remoteButton.click(); + await expect(remoteButton).toBeVisible(); }); - - await page.goto('http://localhost:3001'); - await page.waitForLoadState('networkidle'); - - // Click button to test functionality - const buttonExists = await page.locator('button').first().isVisible(); - if (buttonExists) { - await page.locator('button').first().click(); - await page.waitForTimeout(1000); - } - - // Should handle any errors gracefully - const criticalErrors = consoleErrors.filter(error => - error.includes('Uncaught') && - !error.includes('webpack-dev-server') && - !error.includes('DevTools') - ); - expect(criticalErrors).toHaveLength(0); }); - }); -}); \ No newline at end of file + } +}); diff --git a/advanced-api/automatic-vendor-sharing/e2e/utils/constants.ts b/advanced-api/automatic-vendor-sharing/e2e/utils/constants.ts index 96b1b49f2e..849d457cb0 100644 --- a/advanced-api/automatic-vendor-sharing/e2e/utils/constants.ts +++ b/advanced-api/automatic-vendor-sharing/e2e/utils/constants.ts @@ -1,14 +1,17 @@ export const Constants = { commonConstantsData: { - biDirectional: 'Module Federation with Automatic Vendor Sharing', - button: 'Button', - commonCountAppNames: { + headerText: 'Module Federation with Automatic Vendor Sharing', + appDisplayNames: { app1: 'App 1 (Host & Remote)', app2: 'App 2 (Host & Remote)', }, + buttonLabels: { + app1: 'App 1 Button', + app2: 'App 2 Button', + }, }, color: { - red: 'rgb(255, 0, 0)', - deepBlue: 'rgb(0, 0, 139)', + app1Button: 'rgb(136, 0, 0)', + app2Button: 'rgb(0, 0, 204)', }, }; \ No newline at end of file diff --git a/advanced-api/automatic-vendor-sharing/e2e/utils/selectors.ts b/advanced-api/automatic-vendor-sharing/e2e/utils/selectors.ts index 6761574198..54913f6202 100644 --- a/advanced-api/automatic-vendor-sharing/e2e/utils/selectors.ts +++ b/advanced-api/automatic-vendor-sharing/e2e/utils/selectors.ts @@ -1,12 +1,8 @@ export const selectors = { - dataTestIds: { - app1Button: '[data-e2e="APP_1__BUTTON"]', - app2Button: '[data-e2e="APP_2__BUTTON"]', - }, tags: { headers: { - h1: 'h1', - h2: 'h2', + h1: 'header h1', + h2: 'header h2', }, coreElements: { button: 'button',