Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ test.describe("CollectionTopBarControls on Bloom.exe", () => {
const connection = await connectToBloomExe();

try {
await clickWorkspaceTab(connection.page, "Collections");
await clickWorkspaceTab(connection.page, "collection");
await waitForActiveWorkspaceTab("collection");

await expect(
Expand Down
4 changes: 4 additions & 0 deletions src/BloomBrowserUI/react_components/TopBar/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ const Tab: React.FunctionComponent<{
<li role="presentation">
<a
role="tab"
// Automation clicks tabs by this id. The visible label is localized, so matching
// on it would confine every test to an English UI.
data-testid={`workspace-tab-${props.tab.id}`}
aria-selected={props.selected ? "true" : "false"}
aria-disabled={props.disabled ? "true" : "false"}
css={css`
Expand Down Expand Up @@ -227,6 +230,7 @@ export const BloomTabs: React.FunctionComponent<{
return (
<ul
role="tablist"
data-testid="workspace-tabs"
css={
// style as tabs
css`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ test.describe("Bloom exe CDP top bar", () => {
const connection = await connectToBloomExe();

try {
await clickWorkspaceTab(connection.page, "Collections");
await clickWorkspaceTab(connection.page, "collection");
await waitForActiveWorkspaceTab("collection");
await expect(connection.page.locator("body")).toHaveClass(
/collection-mode/,
);

await clickWorkspaceTab(connection.page, "Publish");
await clickWorkspaceTab(connection.page, "publish");
await waitForActiveWorkspaceTab("publish");
await expect(connection.page.locator("body")).toHaveClass(
/publish-mode/,
);

await clickWorkspaceTab(connection.page, "Edit");
await clickWorkspaceTab(connection.page, "edit");
await waitForActiveWorkspaceTab("edit");
await expect(connection.page.locator("body")).toHaveClass(
/edit-mode/,
Expand Down Expand Up @@ -56,7 +56,7 @@ test.describe("Bloom exe CDP top bar", () => {
)
.toBe(true);

await clickWorkspaceTab(connection.page, "Publish");
await clickWorkspaceTab(connection.page, "publish");
await waitForActiveWorkspaceTab("publish");

await expect
Expand All @@ -67,7 +67,7 @@ test.describe("Bloom exe CDP top bar", () => {
)
.toBe(true);

await clickWorkspaceTab(connection.page, "Edit");
await clickWorkspaceTab(connection.page, "edit");
await waitForActiveWorkspaceTab("edit");
} finally {
await connection.browser.close();
Expand Down
25 changes: 11 additions & 14 deletions src/BloomBrowserUI/react_components/component-tester/bloomExeCdp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,23 +70,20 @@ export const connectToBloomExe = async (): Promise<{
return { browser, page };
};

/**
* Click a workspace tab in the real top bar. The tab is found by the test id that
* react_components/TopBar/TopBar.tsx puts on it, so this works in any UI language.
*
* Bloom hides the Edit and Publish tabs until a book is selected, so a caller that wants either
* of them must select a book first.
*/
export const clickWorkspaceTab = async (
page: Page,
name: WorkspaceTabId extends infer _T
? "Collections" | "Edit" | "Publish"
: never,
tab: WorkspaceTabId,
): Promise<void> => {
await page.waitForSelector("#main-tabs button", {
timeout: 10000,
});

await page.locator("#main-tabs button").filter({ hasText: name }).first();

await page
.locator("#main-tabs button")
.filter({ hasText: name })
.first()
.click();
const target = page.getByTestId(`workspace-tab-${tab}`);
await target.waitFor({ state: "visible", timeout: 10000 });
await target.click();
};

export const getWorkspaceTabs = async (): Promise<{
Expand Down
25 changes: 4 additions & 21 deletions src/BloomE2E/AUTOMATION-DEBT.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ and ask its author.

| Pull request | Branch | What it pays down |
| --- | --- | --- |
| #8298 | `BL-16799-tab-test-ids` | `data-testid` on the workspace tabs, so no test matches a localized label. |
| #8299 | `BL-16799-page-change` | `editView/jumpToPage` refuses a jump it cannot do, and every page-changing helper waits for the Edit tab to settle. |
| #8300 | `BL-16799-collection-languages` | The `e2e/setCollectionLanguages` hook, so no test composes `.bloomCollection` XML. |
| not yet open | `e2e-private-user-settings` | Every Bloom a test launches keeps its user settings in a folder of its own, named on the command line, so a run starts from defaults and its settings die with its temp folder. |
Expand Down Expand Up @@ -140,29 +139,13 @@ than the source and naming the file that is newer. Bloom needs an explicit "no d
option before the second half of that can be trusted.
(Found 2026-09-01 while fixing the top-bar test ids.)

## The top bar has no stable test ids, so tests match on localized text

`TopBar.tsx` renders the workspace tabs as `<a role="tab">` with a localized `<Span>`
label and no id, class, or `data-testid`. Two costs, both already paid: the
component-tester's `bloomExeCdp.ts` drives `#main-tabs button`, a selector that exists
nowhere in the source, so `bloom-exe-tabs.uitest.ts` cannot have worked for some time
(it needs a developer's Bloom already running, and nothing runs it in CI — see the entry
below); and `src/BloomE2E/helpers/workspace.ts` has to map tab ids to the English labels
"Collections"/"Edit"/"Publish", so the suite silently only works in an English UI —
which rules out automating the UI-language cases. Fix direction:
`data-testid="workspace-tab-collection"` (etc.) on each tab, and drop the label matching.
The shell root has its own test id as of 2026-09-01, so the fixture no longer identifies
Bloom's shell document by `[role="tablist"]`.
(Found 2026-09-01 while scaffolding src/BloomE2E.)

being fixed on `BL-16799-tab-test-ids`, which puts a test id on each tab and drops the
label matching.
## The Edit tab's page thumbnail menu has no stable test ids, so tests match on localized text

seen again 2026-09-01, in the Edit tab's page thumbnail menu: the items
`pageThumbnailList.tsx` renders carry no id, class or `data-testid` (all their styling is
inline), so `src/BloomE2E/helpers/pageThumbnails.ts` has to find "Copy Page" and "Paste Page"
The items `pageThumbnailList.tsx` renders carry no id, class or `data-testid` (all their
styling is inline), so `src/BloomE2E/helpers/pageThumbnails.ts` has to find "Copy Page" and "Paste Page"
by their English labels, exactly as the top bar does. Same fix: a `data-testid` per command,
taken from the `commandId` the menu already has.
(Found 2026-09-01 while scaffolding src/BloomE2E.)

## One toolbox harness test asserts on classes that do not exist

Expand Down
17 changes: 8 additions & 9 deletions src/BloomE2E/helpers/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,11 @@ export interface IWorkspaceTabs {
navigationLocked: boolean;
}

// The visible label on each tab in the top bar. These differ from the API's tab ids, which is why
// a test names the id and this map does the translating. The labels are localized, so a run in
// another UI language would need this to come from the l10n data instead.
const TAB_LABEL: Record<WorkspaceTabId, string> = {
collection: "Collections",
edit: "Edit",
publish: "Publish",
};
// The test id on each tab in the top bar, set in react_components/TopBar/TopBar.tsx. The tab ids
// here are Bloom's own API names, and the test ids are built from them, so this needs no map.
function tabTestId(tab: WorkspaceTabId): string {
return `workspace-tab-${tab}`;
}

/** Ask Bloom which workspace tab is active and what state the others are in. */
export async function getTabs(page: Page): Promise<IWorkspaceTabs> {
Expand Down Expand Up @@ -60,13 +57,15 @@ export async function waitForActiveTab(
*
* Bloom hides the Edit and Publish tabs entirely until a book is selected, so a test that wants
* either of them must select a book first (see helpers/collection.ts).
*
* The tab is found by its test id, not by its label, so this works in any UI language.
*/
export async function switchTab(
page: Page,
tab: WorkspaceTabId,
timeoutMs = 30000,
): Promise<void> {
const target = page.getByRole("tab", { name: TAB_LABEL[tab] });
const target = page.getByTestId(tabTestId(tab));
await target.waitFor({ state: "visible", timeout: timeoutMs });
await target.click();
await waitForActiveTab(page, tab, timeoutMs);
Expand Down