@@ -20,6 +20,7 @@ import { expect } from 'chai';
2020import { TestSession } from '@salesforce/cli-plugins-testkit' ;
2121import axios from 'axios' ;
2222import * as dotenv from 'dotenv' ;
23+ import { chromium , Browser , Page } from 'playwright' ;
2324import { toKebabCase } from './helpers/utils.js' ;
2425import { createSfdxProject , createLwcComponent } from './helpers/projectSetup.js' ;
2526import { startLightningDevServer } from './helpers/devServerUtils.js' ;
@@ -31,6 +32,7 @@ const INSTANCE_URL = process.env.TESTKIT_HUB_INSTANCE;
3132const TEST_TIMEOUT_MS = 60_000 ;
3233const STARTUP_DELAY_MS = 5000 ;
3334const DEV_SERVER_PORT = 3000 ;
35+ const HMR_WAIT_MS = 3000 ; // Time to wait for HMR to apply changes
3436
3537// Skip this test in CI environment - run only locally
3638const shouldSkipTest = process . env . CI === 'true' || process . env . CI === '1' ;
@@ -104,6 +106,88 @@ const shouldSkipTest = process.env.CI === 'true' || process.env.CI === '1';
104106 componentHttpSuccess = false ;
105107 }
106108
109+ // Launch browser and test HMR
110+ let browser : Browser | null = null ;
111+ let page : Page | null = null ;
112+ let hmrTestPassed = false ;
113+
114+ try {
115+ browser = await chromium . launch ( { headless : true } ) ;
116+ page = await browser . newPage ( ) ;
117+
118+ // Navigate to component URL
119+ await page . goto ( componentUrl , { waitUntil : 'networkidle' } ) ;
120+
121+ // Get initial content - check for the greeting text
122+ const initialGreeting = await page . locator ( 'h1' ) . textContent ( ) ;
123+ expect ( initialGreeting ) . to . include ( 'Hello, World!' ) ;
124+
125+ // Get the component file path
126+ const componentJsPath = path . join (
127+ projectDir ,
128+ 'force-app' ,
129+ 'main' ,
130+ 'default' ,
131+ 'lwc' ,
132+ componentName ,
133+ `${ componentName } .js`
134+ ) ;
135+
136+ // Read current component file
137+ const originalJsContent = await fs . promises . readFile ( componentJsPath , 'utf8' ) ;
138+
139+ // Modify the component - change greeting text
140+ const modifiedJsContent = originalJsContent . replace (
141+ "greeting = 'Hello, World!';" ,
142+ "greeting = 'Hello, HMR Test!';"
143+ ) ;
144+ await fs . promises . writeFile ( componentJsPath , modifiedJsContent ) ;
145+
146+ // Wait for HMR to detect and apply changes
147+ await new Promise ( ( r ) => setTimeout ( r , HMR_WAIT_MS ) ) ;
148+
149+ // Wait for the page content to update (HMR should update without full reload)
150+ try {
151+ // Wait for the h1 element to contain the new text (HMR should update without full reload)
152+ // eslint-disable-next-line unicorn/numeric-separators-style
153+ await page . locator ( 'h1' ) . waitFor ( { state : 'visible' , timeout : 10000 } ) ;
154+
155+ // Poll for the updated content with retries
156+ let retries = 20 ;
157+ let foundUpdatedContent = false ;
158+ while ( retries > 0 && ! foundUpdatedContent ) {
159+ // eslint-disable-next-line no-await-in-loop
160+ const currentGreeting = await page . locator ( 'h1' ) . textContent ( ) ;
161+ if ( currentGreeting ?. includes ( 'Hello, HMR Test!' ) ) {
162+ foundUpdatedContent = true ;
163+ } else {
164+ // eslint-disable-next-line no-await-in-loop
165+ await new Promise ( ( r ) => setTimeout ( r , 500 ) ) ;
166+ retries -- ;
167+ }
168+ }
169+
170+ // Verify the change is reflected
171+ const updatedGreeting = await page . locator ( 'h1' ) . textContent ( ) ;
172+ expect ( updatedGreeting ) . to . include ( 'Hello, HMR Test!' ) ;
173+ expect ( foundUpdatedContent , 'HMR did not update the component within the timeout period' ) . to . be . true ;
174+ hmrTestPassed = true ;
175+ } catch ( hmrError ) {
176+ stderrOutput += `HMR test failed: ${ String ( hmrError ) } \n` ;
177+ hmrTestPassed = false ;
178+ }
179+
180+ // Restore original content
181+ await fs . promises . writeFile ( componentJsPath , originalJsContent ) ;
182+ } catch ( browserError ) {
183+ const err = browserError as { message ?: string } ;
184+ stderrOutput += `Browser automation error: ${ err . message ?? 'Unknown error' } \n` ;
185+ hmrTestPassed = false ;
186+ } finally {
187+ if ( page ) await page . close ( ) ;
188+ if ( browser ) await browser . close ( ) ;
189+ }
190+
107191 // Clean up
108192 try {
109193 if ( serverProcess . pid && process . kill ( serverProcess . pid , 0 ) ) {
@@ -133,5 +217,7 @@ const shouldSkipTest = process.env.CI === 'true' || process.env.CI === '1';
133217 componentHttpSuccess ,
134218 `Dev server did not respond with HTTP 200 for component URL. Tried URL: ${ componentUrl } `
135219 ) . to . be . true ;
220+ expect ( hmrTestPassed , `HMR test failed. Component changes were not hot-swapped. Full stderr: ${ stderrOutput } ` ) . to . be
221+ . true ;
136222 } ) ;
137223} ) ;
0 commit comments