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+ localSectionHeading : string ;
14+ localSectionDescription : string ;
15+ remoteSectionHeading : string ;
16+ remoteSectionDescription : string ;
17+ } ;
18+
19+ const headerText = Constants . commonConstantsData . headerText ;
20+ const localSectionHeading = Constants . sections . localHeading ;
21+ const infoSection = Constants . infoSection ;
22+
23+ const apps : AppInfo [ ] = [
2124 {
22- headerText : 'Module Federation with Automatic Vendor Sharing' ,
23- appNameText : 'App 1 (Host & Remote)' ,
24- buttonColor : 'rgb(255, 0, 0)' ,
2525 host : 3001 ,
26+ appDisplayName : Constants . commonConstantsData . appDisplayNames . app1 ,
27+ localButtonText : Constants . commonConstantsData . buttonLabels . app1 ,
28+ localButtonColor : Constants . color . app1Button ,
29+ remoteButtonText : Constants . commonConstantsData . buttonLabels . app2 ,
30+ remoteButtonColor : Constants . color . app2Button ,
31+ localSectionHeading,
32+ localSectionDescription : Constants . sections . descriptions . app1Local ,
33+ remoteSectionHeading : Constants . sections . remoteHeadings . app1 ,
34+ remoteSectionDescription : Constants . sections . descriptions . app1Remote ,
2635 } ,
2736 {
28- headerText : 'Module Federation with Automatic Vendor Sharing' ,
29- appNameText : 'App 2 (Host & Remote)' ,
30- buttonColor : 'rgb(0, 0, 139)' ,
3137 host : 3002 ,
38+ appDisplayName : Constants . commonConstantsData . appDisplayNames . app2 ,
39+ localButtonText : Constants . commonConstantsData . buttonLabels . app2 ,
40+ localButtonColor : Constants . color . app2Button ,
41+ remoteButtonText : Constants . commonConstantsData . buttonLabels . app1 ,
42+ remoteButtonColor : Constants . color . app1Button ,
43+ localSectionHeading,
44+ localSectionDescription : Constants . sections . descriptions . app2Local ,
45+ remoteSectionHeading : Constants . sections . remoteHeadings . app2 ,
46+ remoteSectionDescription : Constants . sections . descriptions . app2Remote ,
3247 } ,
3348] ;
3449
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 } ) => {
50+ test . describe ( 'Automatic Vendor Sharing example' , ( ) => {
51+ for ( const app of apps ) {
52+ test . describe ( app . appDisplayName , ( ) => {
53+ test ( `renders the shell for ${ app . appDisplayName } ` , async ( { page } ) => {
54+ const basePage = new BasePage ( page ) ;
4255 const consoleErrors : string [ ] = [ ] ;
56+
4357 page . on ( 'console' , ( msg ) => {
4458 if ( msg . type ( ) === 'error' ) {
4559 consoleErrors . push ( msg . text ( ) ) ;
4660 }
4761 } ) ;
4862
49- await openLocalhost ( page , host ) ;
63+ await basePage . openLocalhost ( app . host ) ;
5064
51- // Check header and subheader exist
52- await checkElementWithTextPresence ( page , 'h1' , headerText ) ;
53- await checkElementWithTextPresence ( page , 'h2' , appNameText ) ;
65+ await expect ( page . locator ( selectors . tags . headers . h1 ) ) . toContainText ( headerText ) ;
66+ await expect ( page . locator ( selectors . tags . headers . h2 ) ) . toContainText ( app . appDisplayName ) ;
5467
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- } ) ;
68+ await expect ( page . getByRole ( 'heading' , { level : 3 , name : app . localSectionHeading } ) ) . toBeVisible ( ) ;
69+ await expect ( page . getByText ( app . localSectionDescription ) ) . toBeVisible ( ) ;
6470
65- test ( `should display ${ appNameText } button correctly` , async ( { page } ) => {
66- await openLocalhost ( page , host ) ;
71+ await expect ( page . getByRole ( 'heading' , { level : 3 , name : app . remoteSectionHeading } ) ) . toBeVisible ( ) ;
72+ await expect ( page . getByText ( app . remoteSectionDescription , { exact : false } ) ) . toBeVisible ( ) ;
6773
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- } ) ;
74+ await expect ( page . getByRole ( 'heading' , { level : 3 , name : infoSection . heading } ) ) . toBeVisible ( ) ;
75+ await expect ( page . getByText ( infoSection . summary ) ) . toBeVisible ( ) ;
76+ await expect ( page . getByText ( infoSection . sharedDependencies ) ) . toBeVisible ( ) ;
77+ await expect ( page . getByText ( infoSection . loadStrategy ) ) . toBeVisible ( ) ;
78+ await expect ( page . getByText ( infoSection . benefits ) ) . toBeVisible ( ) ;
7379
74- test ( `should handle ${ appNameText } button interactions` , async ( { page } ) => {
75- await openLocalhost ( page , host ) ;
80+ const relevantErrors = consoleErrors . filter ( ( error ) => {
81+ if ( error . includes ( 'WebSocket connection to' ) && error . includes ( 'WEB_SOCKET_CONNECT_MAGIC_ID' ) ) {
82+ return false ;
83+ }
7684
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 ( ) ) ;
94- } ) ;
85+ if ( error . includes ( 'dynamic-remote-type-hints-plugin' ) ) {
86+ return false ;
87+ }
9588
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- } ) ;
89+ return true ;
90+ } ) ;
11191
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- }
92+ expect ( relevantErrors , 'Unexpected console errors detected in the browser console' ) . toHaveLength ( 0 ) ;
11893 } ) ;
11994
120- // Test cross-origin requests work properly
121- await page . goto ( 'http://localhost:3001' ) ;
122- await page . waitForLoadState ( 'networkidle' ) ;
95+ test ( `exposes the styled local button for ${ app . appDisplayName } ` , async ( { page } ) => {
96+ const basePage = new BasePage ( page ) ;
12397
124- // Should have no CORS errors
125- expect ( corsErrors ) . toHaveLength ( 0 ) ;
126- } ) ;
98+ await basePage . openLocalhost ( app . host ) ;
12799
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- } ) ;
100+ const localButton = page . getByRole ( 'button' , { name : app . localButtonText } ) ;
101+ await expect ( localButton ) . toBeVisible ( ) ;
102+ await expect ( localButton . locator ( 'span' ) ) . toHaveCount ( 1 ) ;
103+ await expect ( localButton ) . toHaveCSS ( 'background-color' , app . localButtonColor ) ;
138104
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' ) ;
105+ await localButton . click ( ) ;
106+ await expect ( localButton . locator ( 'span' ) ) . toHaveCount ( 2 ) ;
107+ await expect ( localButton . locator ( 'span' ) . nth ( 1 ) ) . toHaveText ( '1 ') ;
108+ } ) ;
143109
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- } ) ;
110+ test ( `loads the remote button for ${ app . appDisplayName } ` , async ( { page } ) => {
111+ const basePage = new BasePage ( page ) ;
148112
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- }
155- } ) ;
113+ await basePage . openLocalhost ( app . host ) ;
114+ await basePage . waitForDynamicImport ( ) ;
156115
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 ) ;
116+ const remoteButton = page . getByRole ( 'button' , { name : app . remoteButtonText } ) ;
117+ await expect ( remoteButton ) . toBeVisible ( ) ;
118+ await expect ( page . getByText ( app . remoteSectionDescription , { exact : false } ) ) . toBeVisible ( ) ;
119+ await expect ( remoteButton ) . toHaveCSS ( 'background-color' , app . remoteButtonColor ) ;
120+
121+ await remoteButton . click ( ) ;
122+ await expect ( remoteButton . locator ( 'span' ) ) . toHaveCount ( 2 ) ;
123+ await expect ( remoteButton . locator ( 'span' ) . nth ( 1 ) ) . toHaveText ( '1' ) ;
124+ } ) ;
174125 } ) ;
175- } ) ;
176- } ) ;
126+ }
127+ } ) ;
0 commit comments