1+ import { test , expect } from './utils/base-test' ;
2+ import { selectors } from './utils/selectors' ;
3+ import { Constants } from './utils/constants' ;
4+
5+ test . describe ( 'Dynamic Remotes E2E Tests' , ( ) => {
6+
7+ test . describe ( 'Host Application (App 1)' , ( ) => {
8+ test ( 'should display host application elements correctly' , async ( { basePage } ) => {
9+ const consoleErrors : string [ ] = [ ] ;
10+ basePage . page . on ( 'console' , ( msg ) => {
11+ if ( msg . type ( ) === 'error' ) {
12+ consoleErrors . push ( msg . text ( ) ) ;
13+ }
14+ } ) ;
15+
16+ await basePage . openLocalhost ( 3001 ) ;
17+
18+ // Check main elements exist
19+ await basePage . checkElementWithTextPresence ( 'h1' , 'Dynamic System Host' ) ;
20+ await basePage . checkElementWithTextPresence ( 'h2' , 'App 1' ) ;
21+ await basePage . checkElementWithTextPresence ( 'p' , 'The Dynamic System will take advantage of Module Federation' ) ;
22+
23+ // Check both buttons exist
24+ await basePage . checkElementWithTextPresence ( 'button' , 'Load App 2 Widget' ) ;
25+ await basePage . checkElementWithTextPresence ( 'button' , 'Load App 3 Widget' ) ;
26+
27+ // Verify no critical console errors
28+ const criticalErrors = consoleErrors . filter ( error =>
29+ error . includes ( 'Failed to fetch' ) ||
30+ error . includes ( 'ChunkLoadError' ) ||
31+ error . includes ( 'Module not found' )
32+ ) ;
33+ expect ( criticalErrors ) . toHaveLength ( 0 ) ;
34+ } ) ;
35+
36+ test ( 'should dynamically load App 2 widget successfully' , async ( { basePage } ) => {
37+ const consoleErrors : string [ ] = [ ] ;
38+ basePage . page . on ( 'console' , ( msg ) => {
39+ if ( msg . type ( ) === 'error' ) {
40+ consoleErrors . push ( msg . text ( ) ) ;
41+ }
42+ } ) ;
43+
44+ await basePage . openLocalhost ( 3001 ) ;
45+
46+ // Click to load App 2 widget
47+ await basePage . clickElementWithText ( 'button' , 'Load App 2 Widget' ) ;
48+ await basePage . waitForDynamicImport ( ) ;
49+
50+ // Verify App 2 widget loaded
51+ await basePage . checkElementVisibility ( selectors . dataTestIds . app2Widget ) ;
52+ await basePage . checkElementWithTextPresence ( 'h2' , 'App 2 Widget' ) ;
53+ await basePage . checkElementBackgroundColor ( selectors . dataTestIds . app2Widget , 'rgb(255, 0, 0)' ) ;
54+
55+ // Check for moment.js date formatting
56+ await basePage . checkDateFormat ( ) ;
57+
58+ // Verify no module federation errors
59+ const moduleErrors = consoleErrors . filter ( error =>
60+ error . includes ( 'Loading remote module' ) ||
61+ error . includes ( 'Module Federation' )
62+ ) ;
63+ expect ( moduleErrors ) . toHaveLength ( 0 ) ;
64+ } ) ;
65+
66+ test ( 'should dynamically load App 3 widget successfully' , async ( { basePage } ) => {
67+ const consoleErrors : string [ ] = [ ] ;
68+ basePage . page . on ( 'console' , ( msg ) => {
69+ if ( msg . type ( ) === 'error' ) {
70+ consoleErrors . push ( msg . text ( ) ) ;
71+ }
72+ } ) ;
73+
74+ await basePage . openLocalhost ( 3001 ) ;
75+
76+ // Click to load App 3 widget
77+ await basePage . clickElementWithText ( 'button' , 'Load App 3 Widget' ) ;
78+ await basePage . waitForDynamicImport ( ) ;
79+
80+ // Verify App 3 widget loaded
81+ await basePage . checkElementVisibility ( selectors . dataTestIds . app3Widget ) ;
82+ await basePage . checkElementWithTextPresence ( 'h2' , 'App 3 Widget' ) ;
83+ await basePage . checkElementBackgroundColor ( selectors . dataTestIds . app3Widget , 'rgb(128, 0, 128)' ) ;
84+
85+ // Check for moment.js date formatting
86+ await basePage . checkDateFormat ( ) ;
87+
88+ // Verify no module federation errors
89+ const moduleErrors = consoleErrors . filter ( error =>
90+ error . includes ( 'Loading remote module' ) ||
91+ error . includes ( 'Module Federation' )
92+ ) ;
93+ expect ( moduleErrors ) . toHaveLength ( 0 ) ;
94+ } ) ;
95+
96+ test ( 'should handle sequential loading of both widgets' , async ( { basePage } ) => {
97+ await basePage . openLocalhost ( 3001 ) ;
98+
99+ // Load App 2 widget first
100+ await basePage . clickElementWithText ( 'button' , 'Load App 2 Widget' ) ;
101+ await basePage . waitForDynamicImport ( ) ;
102+ await basePage . checkElementVisibility ( selectors . dataTestIds . app2Widget ) ;
103+
104+ // Then load App 3 widget
105+ await basePage . clickElementWithText ( 'button' , 'Load App 3 Widget' ) ;
106+ await basePage . waitForDynamicImport ( ) ;
107+ await basePage . checkElementVisibility ( selectors . dataTestIds . app3Widget ) ;
108+
109+ // Both widgets should be visible
110+ await basePage . checkElementVisibility ( selectors . dataTestIds . app2Widget ) ;
111+ await basePage . checkElementVisibility ( selectors . dataTestIds . app3Widget ) ;
112+ } ) ;
113+
114+ test ( 'should show loading states and handle errors gracefully' , async ( { basePage } ) => {
115+ await basePage . openLocalhost ( 3001 ) ;
116+
117+ // Check that buttons are initially enabled
118+ const app2Button = basePage . page . locator ( 'button' ) . filter ( { hasText : 'Load App 2 Widget' } ) ;
119+ await expect ( app2Button ) . toBeEnabled ( ) ;
120+
121+ // Monitor for any error boundaries or error states
122+ const errorMessages = basePage . page . locator ( 'text="⚠️"' ) ;
123+ await expect ( errorMessages ) . toHaveCount ( 0 ) ;
124+ } ) ;
125+ } ) ;
126+
127+ test . describe ( 'Remote Application - App 2' , ( ) => {
128+ test ( 'should display App 2 standalone correctly' , async ( { basePage } ) => {
129+ const consoleErrors : string [ ] = [ ] ;
130+ basePage . page . on ( 'console' , ( msg ) => {
131+ if ( msg . type ( ) === 'error' ) {
132+ consoleErrors . push ( msg . text ( ) ) ;
133+ }
134+ } ) ;
135+
136+ await basePage . openLocalhost ( 3002 ) ;
137+
138+ // Check App 2 widget displays correctly when accessed directly
139+ await basePage . checkElementVisibility ( selectors . dataTestIds . app2Widget ) ;
140+ await basePage . checkElementWithTextPresence ( 'h2' , 'App 2 Widget' ) ;
141+ await basePage . checkElementBackgroundColor ( selectors . dataTestIds . app2Widget , 'rgb(255, 0, 0)' ) ;
142+
143+ // Check moment.js functionality
144+ await basePage . checkElementWithTextPresence ( 'p' , "Moment shouldn't download twice" ) ;
145+ await basePage . checkDateFormat ( ) ;
146+
147+ // Verify no console errors
148+ expect ( consoleErrors . filter ( e => ! e . includes ( 'webpack-dev-server' ) ) ) . toHaveLength ( 0 ) ;
149+ } ) ;
150+ } ) ;
151+
152+ test . describe ( 'Remote Application - App 3' , ( ) => {
153+ test ( 'should display App 3 standalone correctly' , async ( { basePage } ) => {
154+ const consoleErrors : string [ ] = [ ] ;
155+ basePage . page . on ( 'console' , ( msg ) => {
156+ if ( msg . type ( ) === 'error' ) {
157+ consoleErrors . push ( msg . text ( ) ) ;
158+ }
159+ } ) ;
160+
161+ await basePage . openLocalhost ( 3003 ) ;
162+
163+ // Check App 3 widget displays correctly when accessed directly
164+ await basePage . checkElementVisibility ( selectors . dataTestIds . app3Widget ) ;
165+ await basePage . checkElementWithTextPresence ( 'h2' , 'App 3 Widget' ) ;
166+ await basePage . checkElementBackgroundColor ( selectors . dataTestIds . app3Widget , 'rgb(128, 0, 128)' ) ;
167+
168+ // Check for moment.js date formatting
169+ await basePage . checkDateFormat ( ) ;
170+
171+ // Verify no console errors
172+ expect ( consoleErrors . filter ( e => ! e . includes ( 'webpack-dev-server' ) ) ) . toHaveLength ( 0 ) ;
173+ } ) ;
174+ } ) ;
175+
176+ test . describe ( 'Module Federation Features' , ( ) => {
177+ test ( 'should efficiently share dependencies between applications' , async ( { page } ) => {
178+ const networkRequests : string [ ] = [ ] ;
179+
180+ page . on ( 'request' , ( request ) => {
181+ networkRequests . push ( request . url ( ) ) ;
182+ } ) ;
183+
184+ // Navigate to host
185+ await page . goto ( 'http://localhost:3001' ) ;
186+ await page . waitForLoadState ( 'networkidle' ) ;
187+
188+ // Load both remotes
189+ await page . click ( 'button:has-text("Load App 2 Widget")' ) ;
190+ await page . waitForTimeout ( 3000 ) ;
191+
192+ await page . click ( 'button:has-text("Load App 3 Widget")' ) ;
193+ await page . waitForTimeout ( 3000 ) ;
194+
195+ // Verify React is shared efficiently (should not be loaded multiple times)
196+ const reactRequests = networkRequests . filter ( url =>
197+ url . includes ( 'react' ) && ! url . includes ( 'react-dom' ) && ! url . includes ( 'react-redux' )
198+ ) ;
199+ expect ( reactRequests . length ) . toBeLessThan ( 5 ) ;
200+
201+ // Verify moment.js is shared between remotes
202+ const momentRequests = networkRequests . filter ( url => url . includes ( 'moment' ) ) ;
203+ expect ( momentRequests . length ) . toBeLessThan ( 4 ) ;
204+ } ) ;
205+
206+ test ( 'should handle cross-origin requests correctly' , async ( { page } ) => {
207+ // Monitor for CORS errors
208+ const corsErrors : string [ ] = [ ] ;
209+ page . on ( 'response' , ( response ) => {
210+ if ( response . status ( ) >= 400 && response . url ( ) . includes ( 'localhost:300' ) ) {
211+ corsErrors . push ( `${ response . status ( ) } - ${ response . url ( ) } ` ) ;
212+ }
213+ } ) ;
214+
215+ await page . goto ( 'http://localhost:3001' ) ;
216+ await page . waitForLoadState ( 'networkidle' ) ;
217+
218+ // Load remotes
219+ await page . click ( 'button:has-text("Load App 2 Widget")' ) ;
220+ await page . waitForTimeout ( 2000 ) ;
221+
222+ // Should have no CORS errors
223+ expect ( corsErrors ) . toHaveLength ( 0 ) ;
224+ } ) ;
225+
226+ test ( 'should maintain proper error boundaries during failures' , async ( { page } ) => {
227+ const consoleErrors : string [ ] = [ ] ;
228+ page . on ( 'console' , ( msg ) => {
229+ if ( msg . type ( ) === 'error' ) {
230+ consoleErrors . push ( msg . text ( ) ) ;
231+ }
232+ } ) ;
233+
234+ await page . goto ( 'http://localhost:3001' ) ;
235+ await page . waitForLoadState ( 'networkidle' ) ;
236+
237+ // Try to load widgets normally
238+ await page . click ( 'button:has-text("Load App 2 Widget")' ) ;
239+ await page . waitForTimeout ( 2000 ) ;
240+
241+ // Check for React error boundaries working
242+ const errorBoundaryMessages = await page . locator ( 'text="⚠️ Component Failed to Load"' ) . count ( ) ;
243+
244+ // Should handle any errors gracefully (either no errors or proper error boundaries)
245+ const criticalErrors = consoleErrors . filter ( error =>
246+ error . includes ( 'Uncaught' ) &&
247+ ! error . includes ( 'webpack-dev-server' ) &&
248+ ! error . includes ( 'DevTools' )
249+ ) ;
250+ expect ( criticalErrors ) . toHaveLength ( 0 ) ;
251+ } ) ;
252+ } ) ;
253+
254+ test . describe ( 'Environment Configuration' , ( ) => {
255+ test ( 'should use environment-based remote URLs' , async ( { page } ) => {
256+ const networkRequests : string [ ] = [ ] ;
257+
258+ page . on ( 'request' , ( request ) => {
259+ networkRequests . push ( request . url ( ) ) ;
260+ } ) ;
261+
262+ await page . goto ( 'http://localhost:3001' ) ;
263+ await page . waitForLoadState ( 'networkidle' ) ;
264+
265+ // Verify requests are going to the correct localhost ports
266+ const remoteRequests = networkRequests . filter ( url =>
267+ url . includes ( 'localhost:3002' ) || url . includes ( 'localhost:3003' )
268+ ) ;
269+
270+ expect ( remoteRequests . length ) . toBeGreaterThan ( 0 ) ;
271+ } ) ;
272+ } ) ;
273+
274+ test . describe ( 'Performance and Loading' , ( ) => {
275+ test ( 'should load all applications within reasonable time' , async ( { page } ) => {
276+ const startTime = Date . now ( ) ;
277+
278+ await page . goto ( 'http://localhost:3001' ) ;
279+ await page . waitForLoadState ( 'networkidle' ) ;
280+
281+ const loadTime = Date . now ( ) - startTime ;
282+ expect ( loadTime ) . toBeLessThan ( 10000 ) ; // Should load within 10 seconds
283+ } ) ;
284+
285+ test ( 'should handle dynamic imports efficiently' , async ( { page } ) => {
286+ await page . goto ( 'http://localhost:3001' ) ;
287+ await page . waitForLoadState ( 'networkidle' ) ;
288+
289+ const startTime = Date . now ( ) ;
290+
291+ await page . click ( 'button:has-text("Load App 2 Widget")' ) ;
292+ await page . waitForSelector ( '[data-e2e="APP_2__WIDGET"]' , { timeout : 10000 } ) ;
293+
294+ const dynamicLoadTime = Date . now ( ) - startTime ;
295+ expect ( dynamicLoadTime ) . toBeLessThan ( 8000 ) ; // Dynamic loading should be fast
296+ } ) ;
297+ } ) ;
298+ } ) ;
0 commit comments