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