From a9426454a54791ace81019b559186e8634b7b24f Mon Sep 17 00:00:00 2001 From: Hatton Date: Thu, 3 Sep 2026 11:34:36 -0600 Subject: [PATCH 1/2] Set a collection's languages through an e2e hook, not by writing XML A test that needed different collection languages stopped Bloom, composed a `.bloomCollection` file by hand, and started Bloom again. No part of Bloom writes that file that way, so the test exercised a path a user never takes, and it got a different answer for it: the publish list showed the dropped language's own name for itself rather than the name the collection remembers. `e2e/setCollectionLanguages` takes one to three language tags and does the work of the Collection Settings dialog's OK button, `CollectionSettingsDialog.UpdateLanguageSettings` included. `helpers/collection.ts setCollectionLanguages` posts to it and restarts Bloom, which is still needed because Bloom reads the languages when it opens the collection. Test Case ID 169 therefore runs again. It was skipped on master because it failed on CI every time, on the language name: Bloom asks LibPalaso for that name "in" the collection's metadata language, and LibPalaso answers differently depending on whether a native ICU library is findable. Going through the dialog's own code keeps the collection's name for the language, so the list reads "Spanish" on every machine and that lookup is never reached. The lookup itself is a real defect, tracked on BL-16806, and AUTOMATION-DEBT.md now records both it and the coverage this test no longer gives. The Settings dialog is still invisible to CDP, so its own entry stays. --- src/BloomE2E/AUTOMATION-DEBT.md | 50 +++++------ src/BloomE2E/README.md | 13 ++- src/BloomE2E/helpers/collection.ts | 33 +++++++ src/BloomE2E/helpers/publish.ts | 48 +++++++---- .../tests/publish-text-languages.spec.ts | 69 +++++++-------- src/BloomExe/web/controllers/E2eTestingApi.cs | 86 +++++++++++++++++++ 6 files changed, 213 insertions(+), 86 deletions(-) diff --git a/src/BloomE2E/AUTOMATION-DEBT.md b/src/BloomE2E/AUTOMATION-DEBT.md index c1bd7c4a73b6..ac0ffdd79a37 100644 --- a/src/BloomE2E/AUTOMATION-DEBT.md +++ b/src/BloomE2E/AUTOMATION-DEBT.md @@ -14,18 +14,6 @@ House rules: - Ordinary dev/tooling friction goes to `PAPERCUTS.md` at the repo root instead; several entries below were promoted from there. -Work in progress, 2026-09-03 (BL-16799): every entry below that carries a **being fixed** -line is being paid down now, in a stack of small pull requests, one per improvement, each -branching off the one before it. The pull requests do not exist yet, so the branch name is -the identity here. Before you start on a marked entry, ask the owner of its branch. - -| Branch | What it pays down | -| --- | --- | -| `BL-16799-collection-languages` | The `e2e/setCollectionLanguages` hook, so no test composes `.bloomCollection` XML. | - -Three of these also add entries of their own, for the debt that is left after the fix. The -stack replaces PR #8276, which did all of this at once. - --- ## WinForms surfaces are invisible to CDP @@ -38,18 +26,13 @@ force-foreground trick. Fix direction: move these surfaces to the web UI (the te direction anyway), or expose each dialog's WebView2 on a discoverable CDP port. (Promoted from PAPERCUTS 2026-07-11.) -seen again 2026-09-01 (Test Case ID 169, `publish-text-languages.spec.ts`): the case -turns on which languages the collection has, and there is no API for that either. -`collectionSettings/changeLanguage` is not one: its only listener is the open WinForms -`CollectionSettingsDialog` (`CollectionSettingsDialog.cs:368`), so a POST to it while -the dialog is closed does nothing. The test therefore changes a collection language by -stopping Bloom, rewriting the `.bloomCollection`, and starting again, which is what the -new `bloomApp.restart(betweenStopAndStart)` fixture method is for. Each restart costs -about six seconds and loses whatever the editor had not yet saved. - -being fixed on `BL-16799-collection-languages`: the `e2e/setCollectionLanguages` hook -does the work of the Settings dialog's OK button, so no test composes `.bloomCollection` -XML. The dialog itself stays on this entry: nothing can drive or screenshot it. +seen again 2026-09-01, and the collection-languages half of it is now fixed: tests set +the collection's languages through the `e2e/setCollectionLanguages` hook, which does +the same work as clicking OK in the Collection Settings dialog, and +`helpers/collection.ts setCollectionLanguages` wraps it with the restart Bloom still +needs (about six seconds, and it loses whatever the editor had not yet saved). No test +composes `.bloomCollection` XML any more. What remains is the dialog itself: nothing +can drive or screenshot the Settings UI, so the journey test for it cannot be written. seen again 2026-09-01 (Test Case ID 349, `duplicate-page.spec.ts`): "Duplicate Page Many Times..." asks how many copies in `DuplicateManyDialog`, which is `WireUpForWinforms`, so the @@ -244,6 +227,25 @@ shows a `.bloom-page`. Two page adds in a row therefore lost the second one. helpers no longer act early; the production endpoints still reply success to a request they dropped. +## Bloom names a dropped language differently on CI and on a developer machine (BL-16806) + +`publish-text-languages.spec.ts` used to drop a language by rewriting the `.bloomCollection`, +and it then expected the publish list to show that language's own name for itself, "español". +On CI that assertion failed every time, with `Expected: español Received: espagnol` — French +for Spanish. Everything else about the row was right. + +The cause is known, and BL-16806 is the card that fixes it. Bloom asks LibPalaso for the name of +the dropped language "in" the collection's metadata language, French here. LibPalaso honors such +a request only where it can find a native ICU library, and Bloom ships icu.net but no +`icuuc.dll`, so the answer depends on the machine, not on the run: the CI runner answers +"espagnol", and a developer machine ignores the request and gives the autonym "español". It is +a real difference in what Bloom shows a user, not a test problem. + +No test on this branch covers it any more. The test now drops the language through +`e2e/setCollectionLanguages`, the code the Settings dialog's OK button runs, which keeps the +language's collection name, so the list reads "Spanish" on every machine and the lookup that +differs is never reached. Recorded here so the coverage loss is visible; the defect itself +belongs to BL-16806. (Found 2026-09-01, diagnosed on master 2026-09-02.) ## Filling a text box directly leaves part of the old text behind A `.bloom-editable` is a CKEditor surface, and Playwright's `fill()` on one leaves a diff --git a/src/BloomE2E/README.md b/src/BloomE2E/README.md index 7e0c94c7ac90..bc541285036d 100644 --- a/src/BloomE2E/README.md +++ b/src/BloomE2E/README.md @@ -58,12 +58,11 @@ and whatever tab is showing. Most tests need nothing else. | `restart` | Stop Bloom, run an optional callback, start it again on the same collection, and return the new page. | `restart(betweenStopAndStart)` is how a test changes something Bloom only reads at startup. The -collection's languages are the case that needed it: the collection Settings dialog is a WinForms -surface CDP cannot reach, and `collectionSettings/changeLanguage` only answers while that dialog -is open, so the way to change a language is to stop Bloom, rewrite the `.bloomCollection` with -`makeCollectionXml`, and start again. Bloom is killed rather than asked to quit, so leave the -page being edited before restarting or what was typed on it is lost. Use the page `restart` -returns; the old one is closed. +collection's languages are the case that needed it, and a test does not call `restart` itself for +that: `setCollectionLanguages(bloomApp, tags)` posts the new languages to the `e2e/` hook that +writes the `.bloomCollection`, then restarts Bloom and returns the new page. Bloom is killed +rather than asked to quit, so leave the page being edited before restarting or what was typed on +it is lost. Use the page `restart` returns; the old one is closed. Teardown kills the process tree, waits for the HTTP port to go dark, and deletes the temp copy. @@ -93,7 +92,7 @@ real bug in the code under test; read the message and fix it rather than working dialog it opens: `openFormatDialog`, `clickOutsideFormatDialog`, `dragFormatDialog`, `getFormatDialogPlacement`, and the scrolling and zooming that put the gear at the edge of the screen. -- `helpers/collection.ts` — `selectBook`, `waitForCollectionReady`. +- `helpers/collection.ts` — `selectBook`, `waitForCollectionReady`, `setCollectionLanguages`. - `helpers/bookMaking.ts` — make a book, add pages, type into it, read its pages. - `helpers/addPageDialog.ts` — open, read, scroll and close the real Add Page dialog, and add a page through it. Tests that only need a page in their book call `addPage` instead. diff --git a/src/BloomE2E/helpers/collection.ts b/src/BloomE2E/helpers/collection.ts index c5abfcbb1351..2380fdfa73f3 100644 --- a/src/BloomE2E/helpers/collection.ts +++ b/src/BloomE2E/helpers/collection.ts @@ -7,6 +7,7 @@ import { expect, type Page } from "@playwright/test"; import { apiGet, apiPost } from "./api"; +import type { IBloomApp } from "../fixtures/bloomTest"; /** * Wait until the editable collection is loaded and its books can be enumerated. Switching to the @@ -45,3 +46,35 @@ export async function selectBook( `&collection-id=${encodeURIComponent(collectionId)}`, ); } + +/** + * Set the collection's languages, and give Bloom back to the test with the change in effect. + * + * `tags` holds one to three language tags, for Language 1, Language 2 and Language 3. Fewer than + * three leaves the rest empty, which is how a collection ends up with no Language 3. + * + * Bloom reads a collection's languages when it opens the collection, so this restarts it, exactly + * as clicking OK in the Collection Settings dialog makes a user reopen the collection. The restart + * kills the process, so leave the page being edited first or what was typed on it is lost, and use + * the page this returns: the old one points at a dead target. + * + * The `e2e/setCollectionLanguages` hook writes the .bloomCollection with Bloom's own code, so + * everything else in the collection's settings survives. See AUTOMATION-DEBT.md for why there is + * no production API for this. + */ +export async function setCollectionLanguages( + bloomApp: IBloomApp, + tags: string[], +): Promise { + if (tags.length < 1 || tags.length > 3) + throw new Error( + `A collection has one to three languages; setCollectionLanguages was given ${tags.length}.`, + ); + await apiPost( + bloomApp.page, + "e2e/setCollectionLanguages", + JSON.stringify(tags), + "application/json", + ); + return bloomApp.restart(); +} diff --git a/src/BloomE2E/helpers/publish.ts b/src/BloomE2E/helpers/publish.ts index f66f7e73f20c..5e846947e8d6 100644 --- a/src/BloomE2E/helpers/publish.ts +++ b/src/BloomE2E/helpers/publish.ts @@ -175,24 +175,36 @@ export async function clickTextLanguage( export async function showBloomPubPreview(page: Page): Promise { await page.locator('[aria-label="refresh preview"]').click(); let player: Frame | undefined; - await expect - .poll( - async () => { - player = page - .frames() - .find((f) => f.url().includes("bloomplayer.htm")); - if (!player) return 0; - return player - .locator('[aria-label="Choose Language"]') - .count() - .catch(() => 0); - }, - { - timeout: 120000, - message: "The BloomPUB preview never showed the book.", - }, - ) - .toBeGreaterThan(0); + try { + await expect + .poll( + async () => { + player = page + .frames() + .find((f) => f.url().includes("bloomplayer.htm")); + if (!player) return 0; + return player + .locator('[aria-label="Choose Language"]') + .count() + .catch(() => 0); + }, + { timeout: 120000 }, + ) + .toBeGreaterThan(0); + } catch { + // Say which of the two steps did not happen: Bloom never gave the player a book to show, + // or the player has one and never finished loading it. Without this the failure looks the + // same either way, and the two have nothing to do with each other. + const frameUrls = page.frames().map((frame) => frame.url()); + throw new Error( + `The BloomPUB preview never showed the book. ` + + (player + ? `bloom-player is loaded at ${player.url()} but never offered the language ` + + `menu, so it never finished showing a book.` + : `No frame of this page is bloom-player, so Bloom never staged the ` + + `publication. Frames: ${frameUrls.join(", ")}.`), + ); + } return player!; } diff --git a/src/BloomE2E/tests/publish-text-languages.spec.ts b/src/BloomE2E/tests/publish-text-languages.spec.ts index 6f40964dfe84..8a4f458580f7 100644 --- a/src/BloomE2E/tests/publish-text-languages.spec.ts +++ b/src/BloomE2E/tests/publish-text-languages.spec.ts @@ -22,8 +22,7 @@ import { setContentLanguages, typeInGroup, } from "../helpers/bookMaking"; -import { selectBook } from "../helpers/collection"; -import { restartWithCollectionSettings } from "../helpers/collectionSettings"; +import { selectBook, setCollectionLanguages } from "../helpers/collection"; import { clickTextLanguage, expectTextLanguageRows, @@ -95,12 +94,9 @@ test.describe("the Text Languages publish list", () => { ); await goToPage(page, coverBeforeRestart!.id); - // Now swap German out for Spanish. Collection settings have no API and their dialog is a - // WinForms surface CDP cannot reach, so the way to change them is to quit Bloom, rewrite - // the .bloomCollection, and start again. See AUTOMATION-DEBT.md. - const newPage = await restartWithCollectionSettings(bloomApp, { - languages: FINAL_LANGUAGES, - }); + // Now swap German out for Spanish. Bloom reads the collection's languages when it opens + // the collection, so this restarts it; see setCollectionLanguages. + const newPage = await setCollectionLanguages(bloomApp, FINAL_LANGUAGES); bookFolder = await findBookFolder(newPage, BOOK_TITLE); await selectBook(newPage, bookFolder); await switchTab(newPage, "edit"); @@ -347,36 +343,34 @@ test.describe("the Text Languages publish list", () => { await setContentLanguages(page, ["en"]); }); - // Skipped until BL-16806 lands: https://issues.bloomlibrary.org/youtrack/issue/BL-16806 - // - // It fails on CI every time, on the language NAME. The difference it catches is real -- it is - // what Bloom shows a user -- but the fix has to settle WHICH name a dropped language gets, and - // until that is decided this test would keep the nightly red over a known cause. Re-enable it - // with the fix, and expect whatever name BL-16806 settles on. - // - // Expected: español Received: espagnol + // This test no longer meets the difference BL-16806 is about, and master's version of it + // does, so do not copy master's comment back here. Master drops the language by rewriting the + // .bloomCollection, and Bloom then asks LibPalaso for the name of the dropped language "in" + // the collection's metadata language. LibPalaso honors that only where a native ICU library + // is findable, and Bloom ships icu.net but no icuuc.dll, so the CI runner answers "espagnol", + // French for Spanish, every time, and a developer machine answers "español" every time. It is + // a real difference in what Bloom shows a user, tracked on BL-16806. // - // "espagnol" is French for Spanish, and the answer depends on the machine, not on the run. - // Bloom asks LibPalaso for the name of the dropped language "in" the collection's metadata - // language, which is French here; LibPalaso honors that request only where a native ICU - // library is findable, and Bloom ships icu.net but no icuuc.dll. So the CI runner gives - // "espagnol" (nightly runs 33665790357 and 33685669405) while a developer machine ignores the - // request and gives the autonym "español" (checked in the real Publish tab). Everything else - // about the row -- unchecked, not incomplete, enabled -- is right. - // - // A local failure of this test is usually something else: it has other steps that time out on - // a loaded machine, and dies before reaching this assertion. - test.skip("keeps a language that the collection no longer has, under its own name [Test Case ID 169]", async ({ + // This version drops the language through e2e/setCollectionLanguages, the code the Collection + // Settings dialog's OK button runs, which keeps the language's collection name. So the list + // reads "Spanish" on every machine, and the lookup that differs is never reached. The + // coverage that costs is recorded in src/BloomE2E/AUTOMATION-DEBT.md. + test("keeps a language that the collection no longer has, under the name the collection remembers [Test Case ID 169]", async ({ bloomApp, }) => { test.setTimeout(180000); // Drop Spanish from the collection. The book still has Spanish text, so the language stays - // in the list; but the collection no longer supplies a name for it, so Bloom falls back to - // the name the language calls itself. - const withoutSpanish = await restartWithCollectionSettings(bloomApp, { - languages: ["en", "fr"], - }); + // in the list, named "Spanish". Removing a language does not throw its settings away: + // CollectionSettingsDialog.UpdateLanguageSettings moves the displaced language to the end + // of the collection's list, name and font and all, so the name outlives the removal. (This + // test used to expect "español", the language's own name for itself, because it dropped + // the language by rewriting the .bloomCollection, which no part of Bloom does. Going + // through the same code the dialog's OK button runs is what changed the answer.) + const withoutSpanish = await setCollectionLanguages(bloomApp, [ + "en", + "fr", + ]); await selectBook(withoutSpanish, bookFolder); await openPublishDestination(withoutSpanish, "Web"); // In any order: where a language the collection no longer names sits in the list is not @@ -397,19 +391,20 @@ test.describe("the Text Languages publish list", () => { disabled: false, }, { - name: "español", + name: "Spanish", incomplete: false, checked: false, disabled: false, }, ], - "Spanish did not stay in the list, unchecked and under its own name.", + "Spanish did not stay in the list, unchecked and under the name the collection remembers.", ); // Put Spanish back, for the test that follows. - const withSpanish = await restartWithCollectionSettings(bloomApp, { - languages: FINAL_LANGUAGES, - }); + const withSpanish = await setCollectionLanguages( + bloomApp, + FINAL_LANGUAGES, + ); await selectBook(withSpanish, bookFolder); }); diff --git a/src/BloomExe/web/controllers/E2eTestingApi.cs b/src/BloomExe/web/controllers/E2eTestingApi.cs index aad75c0a2dcb..1f4bdb30ad75 100644 --- a/src/BloomExe/web/controllers/E2eTestingApi.cs +++ b/src/BloomExe/web/controllers/E2eTestingApi.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using Bloom.Api; using Bloom.Book; @@ -123,6 +124,21 @@ public void RegisterWithApiHandler(BloomApiHandler apiHandler) false // does not need the UI thread ); + // POST body is a JSON array of one to three language tags, e.g. ["en","fr","es"], for + // Language1, Language2 and Language3. Sets the collection's languages and writes the + // .bloomCollection file, which a test otherwise has to compose by hand: the Collection + // Settings dialog is a WinForms surface CDP cannot reach, and its own + // collectionSettings/changeLanguage endpoint only answers while that dialog is open. + // Changing a collection's languages still needs the collection to be reopened, exactly + // as it does for a user who clicks OK in that dialog, so the caller must restart Bloom + // (src/BloomE2E/helpers/collection.ts setCollectionLanguages does both). + // Must run on the UI thread because it changes the settings the UI is showing. + apiHandler.RegisterEndpointHandler( + kApiUrlPart + "setCollectionLanguages", + HandleSetCollectionLanguages, + true + ); + // GET returns the pages the Add Page dialog would offer for the selected book: the // path of its template book, and the id and label of each template page. A test needs // these to call the production "addPage" endpoint, and the dialog itself reads them @@ -312,6 +328,76 @@ private void HandleGetTemplatePages(ApiRequest request) request.ReplyWithJson(pages); } + /// + /// Set the collection's Language1, Language2 and Language3 to the tags in the POST body (a + /// JSON array of one to three tags), and save the .bloomCollection file. Fewer than three + /// tags leaves the languages that were not named empty, except that a collection naming + /// only one language repeats it as Language2, which is what Bloom's own new-collection code + /// writes. + /// + /// This does the same work as clicking OK in the Collection Settings dialog, including + /// keeping a language that is no longer one of the first three in the collection's list of + /// languages. Like that dialog, it needs the collection reopened before the change is + /// everywhere it should be; the caller restarts Bloom. + /// + private void HandleSetCollectionLanguages(ApiRequest request) + { + var tags = request.RequiredPostObject(); + if (tags == null || tags.Length < 1 || tags.Length > 3) + throw new ArgumentException( + "e2e/setCollectionLanguages takes a JSON array of one to three language tags." + ); + if (string.IsNullOrWhiteSpace(tags[0])) + throw new ArgumentException( + "e2e/setCollectionLanguages needs a tag for Language1." + ); + + // Start from the collection's own writing systems, so that everything about each + // language except its tag (the font, the line height, the writing direction) keeps the + // value it had, then put the requested tag on each one. + // + // Each language is named in English ("French", not "français"), which is what a person + // gets by keeping the English name Bloom's language chooser offers. Bloom calls a name + // that is not the language's own name for itself a custom name, and a custom name is + // the one thing it shows verbatim everywhere; leaving the name uncustomized would make + // each screen name the language in whatever language it liked. + var pending = new WritingSystem[3]; + for (var i = 0; i < 3; i++) + { + pending[i] = _collectionSettings.AllLanguages[i].Clone(); + var tag = i < tags.Length ? tags[i].Trim() : string.Empty; + pending[i].ChangeTag(tag); + if (!string.IsNullOrEmpty(tag)) + { + // ChangeTag has already replaced the name with the one the language uses for + // itself, but it leaves IsCustomName alone. So clear that flag before asking + // for the English name: a language that arrived here with a custom name would + // otherwise be told "you have a name a person chose", and hand back the name + // ChangeTag just computed instead of the English one. + pending[i].SetName(pending[i].Name, false); + pending[i].SetName(pending[i].GetNameInLanguage("en"), true); + } + } + if (string.IsNullOrEmpty(pending[1].Tag)) + { + pending[1].ChangeTag(pending[0].Tag); + // Same reason as the loop above: ChangeTag leaves IsCustomName alone, so a + // Language2 that arrived here with a custom name would keep the name of the + // language it just replaced. + pending[1].SetName(pending[1].Name, false); + pending[1].SetName(pending[1].GetNameInLanguage("en"), true); + } + + CollectionSettingsDialog.UpdateLanguageSettings( + _collectionSettings.AllLanguages, + pending, + pending.Select(language => language.FontName).ToArray() + ); + _collectionSettings.Save(); + + request.PostSucceeded(); + } + /// /// Stage the currently selected book as a BloomPUB and reply with the localhost URL of the /// staged .htm file, which a test can load in bloom-player. From a4de7be5969cc40fb1ca1717812e8597baf61af9 Mon Sep 17 00:00:00 2001 From: Hatton Date: Thu, 3 Sep 2026 17:02:28 -0600 Subject: [PATCH 2/2] Correct the claim that a Vite dev server must use port 5173 The e2e documentation told the reader to serve the dev server on 5173, and to stop a Bloom that already held that port. Both instructions came from a wrong diagnosis of a failure on 2026-09-02. --vite-port honours any port. ReactControl.ReplaceViteDevOrigin rewrites the literal http://localhost:5173 in the HTML of all three Edit frames, including the two *.vite-dev.pug files that the entry said ignored the flag, and its three callers have been in place since a170199f29 (2026-03-13). A run on port 5199 on 2026-09-03 loaded the shell, the toolbox and the page list from 5199; the page list drew its 53 thumbnails and the toolbox drew the Talking Book Tool. What really failed on 2026-09-02 is the other half of the same entry: BLOOM_E2E_VITE_PORT was unset, so Bloom probed 5173, found nothing, and served a day-old output/browser that had no duplicate-page-button test id. So the README now says to set the variable and use any free port, and the debt entry that blamed the port is gone. The remaining debt, that a run inherits its front end from whatever else is running, keeps the record. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01BaWw4EHWrbFF2fNJvMJVC8 --- src/BloomE2E/AUTOMATION-DEBT.md | 30 ++++++------------------------ src/BloomE2E/README.md | 26 ++++++++++++-------------- 2 files changed, 18 insertions(+), 38 deletions(-) diff --git a/src/BloomE2E/AUTOMATION-DEBT.md b/src/BloomE2E/AUTOMATION-DEBT.md index bf727cbd40d4..0f538fea9729 100644 --- a/src/BloomE2E/AUTOMATION-DEBT.md +++ b/src/BloomE2E/AUTOMATION-DEBT.md @@ -122,7 +122,12 @@ established 2026-09-01: - **Leaving the variable unset does not mean "no dev server".** A dev build probes port 5173 by itself (`ReactControl.TryGetActiveViteDevPort`), so a developer's own dev server silently decides what the suite tests, and Bloom offers no option that means "ignore any dev server" - (`--vite-port` rejects 0, and `ValidateStartupVitePort` requires the port to answer). + (`--vite-port` rejects 0, and `ValidateStartupVitePort` requires the port to answer). This is + what went wrong on 2026-09-02: the variable was unset, so a run served a day-old bundle and + `duplicate-page.spec.ts` reported a new test id as absent. `--vite-port` itself honours any + port. `ReactControl.ReplaceViteDevOrigin` rewrites the literal `http://localhost:5173` in all + three Edit frames, the two `*.vite-dev.pug` files included, and a run on 5199 on 2026-09-03 + loaded the shell, the toolbox and the page list from 5199. What remains: the fixture neither starts a dev server of its own nor checks that `output/browser` is newer than `src/BloomBrowserUI`, so a run with the variable unset can still test a stale @@ -420,29 +425,6 @@ meantime: run small and stay red, or stay large until the tests are fixed. developer chose to record the plan here rather than carry a red suite. The code is not in the history, so rebuilding it from this entry is part of the job.) -## A Vite dev server only reaches the whole UI on port 5173 - -`--vite-port` tells Bloom's shell which dev server to load the front end from, but two of the -Edit tab's frames ignore it. `bookEdit/pageThumbnailList/pageThumbnailList.vite-dev.pug` and -`bookEdit/toolbox/toolbox.vite-dev.pug` write `http://localhost:5173/...` into every import -they emit, so on any other port the page list and the toolbox load nothing and come up empty. - -That failure looks like the feature being missing, not like a port problem. A run on port 5199 -failed `duplicate-page.spec.ts` on 2026-09-02 with "waiting for -getByTestId('duplicate-page-button') to be visible", 30 seconds, because `#PageControls` had -never been filled. Nothing in the message points at the dev server. - -The same run showed the second half of it: `BLOOM_E2E_VITE_PORT` was unset, so Bloom fell back -to probing 5173 by itself, found nothing there, and served the built `output/browser` instead. -That bundle was a day old, so the suite silently tested yesterday's front end and reported the -new test id as absent. - -So both halves say the same thing: **serve the dev server on 5173 and set -`BLOOM_E2E_VITE_PORT=5173`.** Fix direction: emit the port into those two pug files the way the -shell gets it, so `--vite-port` means what it says; and give Bloom an option that means "ignore -any dev server", so a run can state which front end it is testing rather than inherit it from -the machine. (Found 2026-09-02.) - ## Typing in a text box raises no key events `typeInGroup` puts the whole string in with `keyboard.insertText`, which raises `input` diff --git a/src/BloomE2E/README.md b/src/BloomE2E/README.md index bc541285036d..c648ced38cdd 100644 --- a/src/BloomE2E/README.md +++ b/src/BloomE2E/README.md @@ -219,24 +219,22 @@ start a Vite dev server and name its port in `BLOOM_E2E_VITE_PORT`; the fixture `--vite-port` to Bloom, which loads every React control from the dev server. ```bash -# In one terminal, in src/BloomBrowserUI. Set PORT as well as --port: the port in -# vite.config.mts comes from process.env.PORT, and --port alone leaves the HMR and -# React-Refresh URLs pointing at 5173, which makes the page fail to load its entry module. -PORT=5173 pnpm exec vite --port 5173 --strictPort +# In one terminal, in src/BloomBrowserUI. Any free port works. Set PORT as well as --port: +# the port in vite.config.mts comes from process.env.PORT, and --port alone leaves the HMR +# and React-Refresh URLs pointing at 5173, which makes the page fail to load its entry +# module. +PORT=5199 pnpm exec vite --port 5199 --strictPort # In another, in src/BloomE2E -BLOOM_E2E_VITE_PORT=5173 pnpm exec playwright test +BLOOM_E2E_VITE_PORT=5199 pnpm exec playwright test ``` -**Use 5173, and set the variable.** The port is not free to choose: the page list and the toolbox -write `http://localhost:5173` into their own imports, so on any other port those two frames load -nothing and come up empty, which reads as the feature being missing rather than as a port -problem. And leaving `BLOOM_E2E_VITE_PORT` unset does not mean "no dev server": a dev build of -Bloom probes 5173 by itself, so an unset variable and a server somewhere else means the run -quietly tests the built bundle, however old it is. Both halves are in AUTOMATION-DEBT.md under -"A Vite dev server only reaches the whole UI on port 5173". - -So stop a Bloom that is already using 5173 before a run, rather than moving the dev server. +**Always set the variable.** An unset `BLOOM_E2E_VITE_PORT` does not mean "no dev server": a dev +build of Bloom probes 5173 by itself, so an unset variable and a dev server on another port means +the run quietly tests the built `output/browser`, however old it is. A stale bundle reads as the +feature being missing rather than as a stale bundle, which is what the 2026-09-02 failure looked +like. See AUTOMATION-DEBT.md under "Which front end the e2e suite tests depends on what else is +running". ## In CI