@@ -239,6 +239,170 @@ test.describe('Publish realm', () => {
239239 ) . toBeVisible ( ) ;
240240 } ) ;
241241
242+ test ( 're-publishing reflects updated source content on the published URL (CS-11043)' , async ( {
243+ page,
244+ } ) => {
245+ // CS-11043 regression net. The bug was: a republish reported success
246+ // server-side but the published URL kept serving the previous publish's
247+ // rendered HTML, sometimes for tens of hours. Every existing
248+ // publish-realm test does exactly one publish — this is the gap the
249+ // bug slipped through. Here we publish, change content, publish
250+ // again, and assert the published URL shows the new content (and not
251+ // the old).
252+
253+ await clearLocalStorage ( page , serverIndexUrl ) ;
254+ user = await createSubscribedUserAndLogin (
255+ page ,
256+ 'publish-realm' ,
257+ serverIndexUrl ,
258+ ) ;
259+
260+ let serverURL = new URL ( serverIndexUrl ) ;
261+ let defaultRealmURL = `${ serverURL . protocol } //${ serverURL . host } /${ user . username } /new-workspace/` ;
262+
263+ await createRealm ( page , 'new-workspace' , '1New Workspace' ) ;
264+
265+ // Define a card type whose isolated template renders a single
266+ // sentinel string we can grep for in the published HTML.
267+ await postCardSource (
268+ page ,
269+ defaultRealmURL ,
270+ 'sentinel-card.gts' ,
271+ `
272+ import { CardDef, Component, field, contains } from "https://cardstack.com/base/card-api";
273+ import StringField from "https://cardstack.com/base/string";
274+
275+ export class SentinelCard extends CardDef {
276+ @field value = contains(StringField);
277+
278+ static isolated = class extends Component<typeof this> {
279+ <template>
280+ <div data-test-sentinel-output>{{@model.value}}</div>
281+ </template>
282+ };
283+ }
284+ ` ,
285+ ) ;
286+
287+ // Initial index.json: an instance of SentinelCard carrying the
288+ // sentinel that we expect the first publish to render.
289+ let initialSentinel = `sentinel-initial-${ Date . now ( ) } ` ;
290+ await postCardSource (
291+ page ,
292+ defaultRealmURL ,
293+ 'index.json' ,
294+ JSON . stringify (
295+ {
296+ data : {
297+ type : 'card' ,
298+ attributes : { value : initialSentinel } ,
299+ meta : {
300+ adoptsFrom : { module : './sentinel-card' , name : 'SentinelCard' } ,
301+ } ,
302+ } ,
303+ } ,
304+ null ,
305+ 2 ,
306+ ) ,
307+ ) ;
308+
309+ // Open the publish modal and do the first publish.
310+ await page . locator ( '[data-test-workspace="1New Workspace"]' ) . click ( ) ;
311+ await page . locator ( '[data-test-submode-switcher] button' ) . click ( ) ;
312+ await page . locator ( '[data-test-boxel-menu-item-text="Host"]' ) . click ( ) ;
313+ await page . locator ( '[data-test-publish-realm-button]' ) . click ( ) ;
314+ await page . locator ( '[data-test-default-domain-checkbox]' ) . click ( ) ;
315+ await page . locator ( '[data-test-publish-button]' ) . click ( ) ;
316+ await page . waitForSelector ( '[data-test-unpublish-button]' ) ;
317+
318+ // Open the published URL and verify the initial sentinel renders.
319+ let firstTabPromise = page . waitForEvent ( 'popup' ) ;
320+ await page
321+ . locator (
322+ '[data-test-publish-realm-modal] [data-test-open-boxel-space-button]' ,
323+ )
324+ . click ( ) ;
325+ let firstTab = await firstTabPromise ;
326+ await firstTab . waitForLoadState ( ) ;
327+ await expect ( firstTab . locator ( '[data-test-sentinel-output]' ) ) . toHaveText (
328+ initialSentinel ,
329+ { timeout : 30_000 } ,
330+ ) ;
331+ await firstTab . close ( ) ;
332+ await page . bringToFront ( ) ;
333+
334+ // Close the modal so we can re-open it cleanly for the second publish.
335+ await page . locator ( '[data-test-close-modal]' ) . click ( ) ;
336+
337+ // Change the index card's sentinel value. This is the "user edits
338+ // their realm between publishes" step.
339+ let updatedSentinel = `sentinel-updated-${ Date . now ( ) } ` ;
340+ await postCardSource (
341+ page ,
342+ defaultRealmURL ,
343+ 'index.json' ,
344+ JSON . stringify (
345+ {
346+ data : {
347+ type : 'card' ,
348+ attributes : { value : updatedSentinel } ,
349+ meta : {
350+ adoptsFrom : { module : './sentinel-card' , name : 'SentinelCard' } ,
351+ } ,
352+ } ,
353+ } ,
354+ null ,
355+ 2 ,
356+ ) ,
357+ ) ;
358+
359+ // Re-open the publish modal. The default-domain checkbox is still
360+ // there (the realm appears as already-published in the modal); the
361+ // publish button is what we re-click to push the new content.
362+ await page . locator ( '[data-test-publish-realm-button]' ) . click ( ) ;
363+ // The publish handler awaits sourceRealm.indexing() before doing the
364+ // copy, so we don't need a manual settle here — the click below
365+ // serializes behind any pending incremental indexing on the source.
366+ await page . locator ( '[data-test-default-domain-checkbox]' ) . click ( ) ;
367+ let publishButton = page . locator ( '[data-test-publish-button]' ) ;
368+ // Set up the network wait BEFORE clicking — the handler awaits the
369+ // full reindex before returning 202, so the response is the
370+ // authoritative "publish is done" signal. 60s budget covers the
371+ // from-scratch reindex even on slow CI.
372+ let publishResponsePromise = page . waitForResponse (
373+ ( r ) =>
374+ r . url ( ) . endsWith ( '/_publish-realm' ) && r . request ( ) . method ( ) === 'POST' ,
375+ { timeout : 60_000 } ,
376+ ) ;
377+ await publishButton . click ( ) ;
378+ let publishResponse = await publishResponsePromise ;
379+ expect (
380+ publishResponse . status ( ) ,
381+ 'second publish should succeed' ,
382+ ) . toBeLessThan ( 300 ) ;
383+
384+ // Open the published URL again and verify the UPDATED sentinel
385+ // renders — and the initial sentinel does NOT. This is the
386+ // assertion CS-11043 would have failed: the old test only checked
387+ // for "card visible", which stays true even when serving stale
388+ // content.
389+ let secondTabPromise = page . waitForEvent ( 'popup' ) ;
390+ await page
391+ . locator (
392+ '[data-test-publish-realm-modal] [data-test-open-boxel-space-button]' ,
393+ )
394+ . click ( ) ;
395+ let secondTab = await secondTabPromise ;
396+ await secondTab . waitForLoadState ( ) ;
397+ await expect ( secondTab . locator ( '[data-test-sentinel-output]' ) ) . toHaveText (
398+ updatedSentinel ,
399+ { timeout : 30_000 } ,
400+ ) ;
401+ await expect ( secondTab . locator ( 'body' ) ) . not . toContainText ( initialSentinel ) ;
402+ await secondTab . close ( ) ;
403+ await page . bringToFront ( ) ;
404+ } ) ;
405+
242406 test ( 'open site popover opens with shift-click' , async ( { page } ) => {
243407 await publishDefaultRealm ( page ) ;
244408
0 commit comments