Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
8edf244
docs(content-drive): spec for #37426 browse scopes for All, site root…
zJaaal Sep 9, 2026
907c1d8
docs(content-drive): settle what the drive opens on, for #37426
zJaaal Sep 9, 2026
63ed916
Merge branch 'main' into 37426-content-drive-browse-scopes
zJaaal Sep 10, 2026
6b40cd1
docs(content-drive): answer the spec review, for #37426
zJaaal Sep 11, 2026
aec8240
Merge branch 'main' into 37426-content-drive-browse-scopes
zJaaal Sep 11, 2026
5147264
docs(content-drive): say browse scope, never bare scope, for #37426
zJaaal Sep 11, 2026
cbd3e08
Merge remote-tracking branch 'origin/37426-content-drive-browse-scope…
zJaaal Sep 11, 2026
d400cf4
Merge branch 'main' into 37426-content-drive-browse-scopes
zJaaal Sep 11, 2026
afe61f1
docs(content-drive): the contract delta and request model, for #37426
zJaaal Sep 11, 2026
e6dd946
Merge remote-tracking branch 'origin/37426-content-drive-browse-scope…
zJaaal Sep 11, 2026
7c52553
docs(content-drive): name the scope, pin the row, keep the toggle in …
zJaaal Sep 14, 2026
0ddb7a4
docs(content-drive): accept the missing location column, on the record
zJaaal Sep 14, 2026
8d622ea
test(browser): pin the host clause a listing emits when nobody asks, …
zJaaal Sep 14, 2026
a4fb9e2
test(drive): red — a browse scope with a folder path is not yet refus…
zJaaal Sep 14, 2026
a2b6b8b
feat(drive): green — refuse a browse scope named with a folder path (…
zJaaal Sep 14, 2026
014fa08
refactor(browser): three states for System Host, where a flag said tw…
zJaaal Sep 14, 2026
b01006c
feat(drive): the site root stops meaning the whole site (#37426)
zJaaal Sep 14, 2026
88805e9
feat(dotcms-ui): one URL value becomes the two the endpoint expects (…
zJaaal Sep 14, 2026
5081ab8
Merge branch 'main' into 37426-content-drive-browse-scopes
zJaaal Sep 14, 2026
a0a9299
feat(dotcms-ui): the sidebar's first entry, and a column that scrolls…
zJaaal Sep 14, 2026
c6eed99
Merge remote-tracking branch 'origin/37426-content-drive-browse-scope…
zJaaal Sep 14, 2026
1eac9b3
feat(dotcms-ui): all site content names no place to put things (#37426)
zJaaal Sep 14, 2026
070be7c
feat(dotcms-ui): take the System Host chip away where it decides noth…
zJaaal Sep 14, 2026
afd8367
feat(dotcms-ui): the System Host row, and stop the root meaning every…
zJaaal Sep 14, 2026
301d4f2
fix(browser): make both query builders name the same hosts (#37426)
zJaaal Sep 14, 2026
33e2eef
Merge branch 'main' into 37426-content-drive-browse-scopes
zJaaal Sep 14, 2026
65bb3cf
feat(dotcms-ui): let content actually land on System Host (#37426)
zJaaal Sep 14, 2026
14c5d0a
Merge remote-tracking branch 'origin/37426-content-drive-browse-scope…
zJaaal Sep 14, 2026
b9fa27e
Merge branch 'main' into 37426-content-drive-browse-scopes
zJaaal Sep 15, 2026
a182590
fix(browser): point the integration tests at the three-state (#37426)
zJaaal Sep 15, 2026
3c66875
Merge branch 'main' into 37426-content-drive-browse-scopes
zJaaal Sep 15, 2026
9c00530
fix(content-drive): name System Host as the upload destination (#37426)
zJaaal Sep 15, 2026
9d2e1fe
feat(content-drive): let All Site Content accept new content (#37426)
zJaaal Sep 15, 2026
ce5cea2
fix(content-drive): sidebar metrics, and say where an upload actually…
zJaaal Sep 15, 2026
7a60b78
feat(content-drive): drag and drop onto the sidebar entries, and cove…
zJaaal Sep 15, 2026
fdd9dbe
Merge branch 'main' into 37426-content-drive-browse-scopes
zJaaal Sep 15, 2026
07e2a3d
fix(content-drive): keep the tree selection in step with the location…
zJaaal Sep 15, 2026
743f388
Merge remote-tracking branch 'origin/37426-content-drive-browse-scope…
zJaaal Sep 15, 2026
44538a0
docs(content-drive): Mod + B hides the sidebar, not just the tree (#3…
zJaaal Sep 15, 2026
3455f64
fix(content-drive): the upload indicator names the site, not its root…
zJaaal Sep 15, 2026
afb26fa
feat(content-drive): report a run in flight as a status toast (#37426)
zJaaal Sep 16, 2026
dad2523
fix(content-drive): stop System Host and the site root fighting over …
zJaaal Sep 16, 2026
474f403
fix(content-drive): reload the listing after an upload to System Host…
zJaaal Sep 16, 2026
9af0608
style(content-drive): format browsedFolderRef (#37426)
zJaaal Sep 16, 2026
e034463
Merge branch 'main' into 37426-content-drive-browse-scopes
zJaaal Sep 16, 2026
b8df227
fix(content-drive): ignore unknown fields on the drive request form (…
zJaaal Sep 16, 2026
9081cbf
Merge remote-tracking branch 'origin/37426-content-drive-browse-scope…
zJaaal Sep 16, 2026
3218004
fix(content-drive): no new folder on System Host, and a path that res…
zJaaal Sep 16, 2026
daac5e4
fix(content-drive): point the e2e at the toast, and drop the unreacha…
zJaaal Sep 16, 2026
39c059f
Merge branch 'main' into 37426-content-drive-browse-scopes
zJaaal Sep 16, 2026
c9bf503
feat(content-drive): ease the folder dialog between tabs instead of s…
zJaaal Sep 16, 2026
d46e024
Revert "feat(content-drive): ease the folder dialog between tabs inst…
zJaaal Sep 16, 2026
4a8f41d
style(ui): line the sidebar chevron up, and drop the toast's left str…
zJaaal Sep 16, 2026
0d8ae66
style(content-drive): shorten the upload status, and stop it being cl…
zJaaal Sep 16, 2026
8832db4
refactor(content-drive): a scope bar, and the run reporting where it …
zJaaal Sep 16, 2026
35ede6c
fix(content-drive): stop the status toast swallowing clicks on the pa…
zJaaal Sep 17, 2026
7e3f685
Merge branch 'main' into 37426-content-drive-browse-scopes
zJaaal Sep 17, 2026
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
151 changes: 144 additions & 7 deletions core-web/apps/dotcms-ui-e2e/src/pages/contentDrive.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,14 @@ export class ContentDrivePage {
readonly currentSiteHostname: Locator;
readonly listTitles: Locator;
readonly treeNodeLabels: Locator;
readonly allSiteContentRow: Locator;
readonly systemHostRow: Locator;
readonly searchField: Locator;
readonly uploadIndicator: Locator;
readonly uploadProgress: Locator;
readonly statusToast: Locator;
readonly statusToastSummary: Locator;
readonly scopeBar: Locator;
readonly scopeBarSummary: Locator;
readonly scopeBarToggle: Locator;
readonly toasts: Locator;

constructor(private page: Page) {
Expand All @@ -45,9 +50,21 @@ export class ContentDrivePage {
this.currentSiteHostname = this.sidebar.getByTestId('tree-node-label').first();
this.listTitles = page.getByTestId('item-title-text');
this.treeNodeLabels = this.sidebar.getByTestId('tree-node-label');
// The toolbar's in-flight indicator, and the position it shows when a run reports one.
this.uploadIndicator = page.getByTestId('action-execution-indicator');
this.uploadProgress = page.getByTestId('action-execution-progress');
// The two entries that are not part of the hierarchy. They carry their own testids and no
// `tree-node-label`, which is why `currentSiteHostname` above still finds the site row.
this.allSiteContentRow = this.sidebar.getByTestId('all-site-content');
this.systemHostRow = this.sidebar.getByTestId('system-host');
// A run in flight is reported by the status toast, not by the toolbar. The toolbar used to
// draw an indicator at the end of the filter row (`action-execution-indicator`); that markup
// is gone, so anything still looking for it is asserting on a testid that cannot appear.
this.statusToast = page.getByTestId('dot-status-toast');
this.statusToastSummary = page.getByTestId('status-toast-summary');
// The bar above the listing: what is being shown, and the one control that changes it.
// Its slot is always in the DOM and opens by height, so visibility is the question to ask
// rather than presence.
this.scopeBar = page.getByTestId('scope-bar');
this.scopeBarSummary = page.getByTestId('scope-bar-summary');
this.scopeBarToggle = page.getByTestId('scope-bar-toggle');
this.toasts = page.locator('.p-toast-message');
}

Expand Down Expand Up @@ -291,10 +308,86 @@ export class ContentDrivePage {
* learns the rules changed; without the indicator the batch looks finished when it is not.
*/
async expectHandedToBackground() {
await expect(this.toasts.filter({ hasText: 'in the background' }).first()).toBeVisible({
// One surface says both halves now. The status toast carries the in-flight wording, which
// for a backgrounded batch is the sentence that tells the author the batch is theirs to
// leave — so its presence is the indicator, and there is no second element to check.
// It used to be two: a wide advisory toast plus the toolbar's indicator, which meant a
// backgrounded upload announced itself twice.
await expect(this.statusToastSummary.filter({ hasText: 'in the background' })).toBeVisible({
timeout: OUTCOME_TIMEOUT
});
await expect(this.uploadIndicator).toBeVisible();
}

/** What the status toast is saying right now, if anything. */
async expectStatusToastContaining(text: string) {
await expect(this.statusToastSummary.filter({ hasText: text }).first()).toBeVisible({
timeout: OUTCOME_TIMEOUT
});
}

/**
* The status toast has stopped reporting.
*
* A run that never clears its toast leaves the portlet claiming work is in flight forever, and
* the toast is sticky precisely so it cannot time itself out -- which makes it the store's job
* to end it, and therefore worth asserting.
*/
async expectStatusToastGone() {
await expect(this.statusToastSummary).toHaveCount(0, { timeout: OUTCOME_TIMEOUT });
}

/**
* Clicks the listing's rows-per-page control.
*
* Used to prove the status toast is not sitting on top of it. This control is at the bottom
* centre of the viewport, which is exactly where the toast renders, and it is always present
* and enabled -- unlike the next-page button, which depends on how much the folder holds.
*
* Playwright fails a click that an overlay intercepts, naming the element in the way, so the
* assertion is the click itself.
*/
async openRowsPerPage() {
await this.page.getByRole('combobox', { name: 'Rows per page' }).click({ timeout: 5000 });
}

/** Whether the scope bar is open, which is a question about height rather than presence. */
async scopeBarIsOpen(): Promise<boolean> {
const slot = this.page.getByTestId('scope-bar-slot');
const box = await slot.boundingBox();

return (box?.height ?? 0) > 0;
}

/** Flips the System Host toggle and waits for the listing it re-requests. */
async toggleSystemHostInScopeBar() {
const listing = this.page.waitForResponse(
(response) => response.url().includes('/v1/drive/search') && response.ok()
);
await this.scopeBarToggle.click();
await listing;
}

/** Opens the New menu and returns the labels it offers. */
async openNewMenu(): Promise<string[]> {
await this.toolbar.getByTestId('add-new-button').click();
const items = this.page.getByRole('menuitem');
await expect(items.first()).toBeVisible({ timeout: 10000 });

return items.allInnerTexts();
}

/**
* The path the folder dialog says a new folder will land on.
*
* Read rather than asserted here because the wrong value is not a missing element: the builder
* used to paste a location that is not a folder path straight after the hostname, so the field
* was populated and confidently wrong.
*/
async folderDialogPath(): Promise<string> {
const path = this.page.getByTestId('folder-path-preview');
await expect(path).toBeVisible({ timeout: 10000 });

return (await path.innerText()).trim();
}

/** A message the author can read, whatever severity it arrived with. */
Expand Down Expand Up @@ -374,6 +467,50 @@ export class ContentDrivePage {
async expectNoSingleFileWarning() {
await expect(this.page.locator('.p-toast-message-warn')).toHaveCount(0);
}

/** Selects the All Site Content entry and waits for the listing it triggers. */
async selectAllSiteContent() {
await this.selectSidebarEntry(this.allSiteContentRow);
}

/** Selects the System Host entry and waits for the listing it triggers. */
async selectSystemHost() {
await this.selectSidebarEntry(this.systemHostRow);
}

/**
* Clicks a sidebar entry and waits for the listing request the click sets off.
*
* Armed before the click, not after: the response can land first, and then a wait registered
* afterwards never resolves.
*/
private async selectSidebarEntry(row: Locator) {
const listing = this.page.waitForResponse(
(response) => response.url().includes('/v1/drive/search') && response.ok()
);
await row.click();
await listing;
}

/**
* Asserts which sidebar entry reads as the current one.
*
* `aria-current` rather than a class: the rows announce selection to assistive tech through
* it, so asserting on it checks the thing that actually has to be right.
*/
async expectSelectedEntry(entry: 'all' | 'system-host' | 'neither') {
const row = entry === 'system-host' ? this.systemHostRow : this.allSiteContentRow;
await expect(row).toHaveAttribute('aria-current', entry === 'neither' ? /^$/ : 'true', {
timeout: entry === 'neither' ? 2000 : undefined
});
}

/** Whether an entry currently announces itself as the selected one. */
async isEntrySelected(entry: 'all' | 'system-host') {
const row = entry === 'system-host' ? this.systemHostRow : this.allSiteContentRow;

return (await row.getAttribute('aria-current')) === 'true';
}
}

/** A tiny in-memory PNG, so the tests carry no fixture files. */
Expand Down
Loading
Loading