From 446afab484e0e270409aff3358b115d007257b60 Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Tue, 14 Jul 2026 14:35:12 +0200 Subject: [PATCH 01/27] Add real preview/publish e2e tests and shared createDocument helper --- test/e2e/tests/preview_publish.spec.js | 50 +++++++++++++++++++++++--- test/e2e/utils/page.js | 26 ++++++++++++++ 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/test/e2e/tests/preview_publish.spec.js b/test/e2e/tests/preview_publish.spec.js index c20cd3323..346e81b47 100644 --- a/test/e2e/tests/preview_publish.spec.js +++ b/test/e2e/tests/preview_publish.spec.js @@ -1,6 +1,8 @@ import { test, expect } from '@playwright/test'; import ENV from '../utils/env.js'; -import { getQuery, TEST_ORG, TEST_SITE } from '../utils/page.js'; +import { + getQuery, getTestPageURL, createDocument, TEST_ORG, TEST_SITE, +} from '../utils/page.js'; // Requires write access to TEST_SITE. pingtest must exist in the /tests directory. const TESTS_DIR = `${ENV}/${getQuery()}#/${TEST_ORG}/${TEST_SITE}/tests`; @@ -14,7 +16,6 @@ async function selectItem(page, name) { } test('Preview and Publish buttons appear when a file is selected', async ({ page }) => { - test.skip(TEST_SITE !== 'da-status', 'Requires write access to da-status'); await page.goto(TESTS_DIR); await expect(page.getByText('pingtest'), 'Precondition: pingtest must exist').toBeVisible(); @@ -25,7 +26,6 @@ test('Preview and Publish buttons appear when a file is selected', async ({ page }); test('Clicking Preview opens a confirmation dialog', async ({ page }) => { - test.skip(TEST_SITE !== 'da-status', 'Requires write access to da-status'); await page.goto(TESTS_DIR); await expect(page.getByText('pingtest'), 'Precondition: pingtest must exist').toBeVisible(); @@ -36,8 +36,50 @@ test('Clicking Preview opens a confirmation dialog', async ({ page }) => { await expect(page.locator('da-dialog')).toContainText('Preview the'); }); +test.only('Preview actually previews the selected page', async ({ page }, workerInfo) => { + test.setTimeout(60000); + + const url = getTestPageURL('preview', workerInfo); + const pageName = url.split('/').pop(); + await createDocument(page, url, 'preview test'); + + await page.goto(TESTS_DIR); + await expect(page.getByText(pageName), 'Precondition: new page must exist').toBeVisible(); + + await selectItem(page, pageName); + await page.locator('button.preview-button').filter({ visible: true }).click(); + + await expect(page.locator('da-dialog')).toContainText('Preview the'); + await page.locator('sl-button.accent').filter({ visible: true }).click(); + + await expect(page.locator('button.da-aem-results-btn')).toBeVisible({ timeout: 30000 }); + await expect(page.locator('button.da-aem-results-btn')).toContainText('Previewed 1 item'); + await expect(page.locator('da-dialog').filter({ hasText: 'Errors' })).toHaveCount(0); +}); + +test('Publish actually publishes the selected page', async ({ page }, workerInfo) => { + test.setTimeout(60000); + + const url = getTestPageURL('publish', workerInfo); + const pageName = url.split('/').pop(); + await createDocument(page, url, 'publish test'); + + await page.goto(TESTS_DIR); + await expect(page.getByText(pageName), 'Precondition: new page must exist').toBeVisible(); + + await selectItem(page, pageName); + await page.locator('button.publish-button').filter({ visible: true }).click(); + + await expect(page.locator('da-dialog')).toContainText('Publish the'); + await page.locator('sl-button.accent').filter({ visible: true }).click(); + + await expect(page.locator('button.da-aem-results-btn')).toBeVisible({ timeout: 30000 }); + await expect(page.locator('button.da-aem-results-btn')).toContainText('Published 1 item'); + await expect(page.locator('da-dialog').filter({ hasText: 'Errors' })).toHaveCount(0); +}); + test('Preview and Publish buttons are hidden when only a folder is selected', async ({ page }) => { - test.skip(TEST_SITE !== 'da-status', 'Requires write access to da-status'); + //test.skip(TEST_SITE !== 'da-status', 'Requires write access to da-status'); await page.goto(`${ENV}/${getQuery()}#/${TEST_ORG}/${TEST_SITE}`); await expect(page.getByText('tests'), 'Precondition: tests folder must exist').toBeVisible(); diff --git a/test/e2e/utils/page.js b/test/e2e/utils/page.js index f34d41122..9122f0aa4 100644 --- a/test/e2e/utils/page.js +++ b/test/e2e/utils/page.js @@ -9,6 +9,7 @@ * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ +import { expect } from '@playwright/test'; import ENV, { TEST_ORG, TEST_SITE } from './env.js'; export { TEST_ORG, TEST_SITE }; @@ -81,6 +82,31 @@ export function getTestResourceAge(fileName) { const SELECT_ALL = process.platform === 'darwin' ? 'Meta+a' : 'Control+a'; +/** + * Navigates to a document URL and creates a new document from the blank-page prompt, + * waiting for the ProseMirror editor to become editable. Used by tests that need a + * fresh document to work with (editing, deleting, previewing, publishing, etc). + * + * @param {import('@playwright/test').Page} page + * @param {string} url - The URL of the (not yet existing) document to create. + * @param {string} [content] - Optional text content to type into the document. If + * provided, the function waits for the resulting save to complete before returning. + */ +export async function createDocument(page, url, content) { + await page.goto(url); + await page.getByText('Create document', { exact: true }).click(); + await expect(page.locator('div.ProseMirror')).toBeVisible(); + await expect(page.locator('div.ProseMirror')).toHaveAttribute('contenteditable', 'true'); + // Allow Y.js WebSocket to stabilize before typing + await page.waitForTimeout(2000); + + if (content) { + const savePromise = waitForSave(page); + await fill(page, content); + await savePromise; + } +} + export async function fill(page, text) { const proseMirror = page.locator('div.ProseMirror'); await proseMirror.click(); From d61af03a643a9d94c1d39427f232138e51c02a2e Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Tue, 14 Jul 2026 15:03:46 +0200 Subject: [PATCH 02/27] Match copy_rename.spec.js pattern for document creation/save wait --- test/e2e/tests/preview_publish.spec.js | 20 ++++++++++++++++---- test/e2e/utils/page.js | 10 +--------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/test/e2e/tests/preview_publish.spec.js b/test/e2e/tests/preview_publish.spec.js index 346e81b47..96f45ff0f 100644 --- a/test/e2e/tests/preview_publish.spec.js +++ b/test/e2e/tests/preview_publish.spec.js @@ -1,7 +1,7 @@ import { test, expect } from '@playwright/test'; import ENV from '../utils/env.js'; import { - getQuery, getTestPageURL, createDocument, TEST_ORG, TEST_SITE, + getQuery, getTestPageURL, createDocument, fill, TEST_ORG, TEST_SITE, } from '../utils/page.js'; // Requires write access to TEST_SITE. pingtest must exist in the /tests directory. @@ -36,12 +36,18 @@ test('Clicking Preview opens a confirmation dialog', async ({ page }) => { await expect(page.locator('da-dialog')).toContainText('Preview the'); }); -test.only('Preview actually previews the selected page', async ({ page }, workerInfo) => { +test('Preview actually previews the selected page', async ({ page }, workerInfo) => { test.setTimeout(60000); const url = getTestPageURL('preview', workerInfo); const pageName = url.split('/').pop(); - await createDocument(page, url, 'preview test'); + await createDocument(page, url); + + // Enter some text onto the page + await fill(page, 'preview test'); + + // Wait to ensure its saved in da-admin + await page.waitForTimeout(5000); await page.goto(TESTS_DIR); await expect(page.getByText(pageName), 'Precondition: new page must exist').toBeVisible(); @@ -62,7 +68,13 @@ test('Publish actually publishes the selected page', async ({ page }, workerInfo const url = getTestPageURL('publish', workerInfo); const pageName = url.split('/').pop(); - await createDocument(page, url, 'publish test'); + await createDocument(page, url); + + // Enter some text onto the page + await fill(page, 'publish test'); + + // Wait to ensure its saved in da-admin + await page.waitForTimeout(5000); await page.goto(TESTS_DIR); await expect(page.getByText(pageName), 'Precondition: new page must exist').toBeVisible(); diff --git a/test/e2e/utils/page.js b/test/e2e/utils/page.js index 9122f0aa4..02093263e 100644 --- a/test/e2e/utils/page.js +++ b/test/e2e/utils/page.js @@ -89,22 +89,14 @@ const SELECT_ALL = process.platform === 'darwin' ? 'Meta+a' : 'Control+a'; * * @param {import('@playwright/test').Page} page * @param {string} url - The URL of the (not yet existing) document to create. - * @param {string} [content] - Optional text content to type into the document. If - * provided, the function waits for the resulting save to complete before returning. */ -export async function createDocument(page, url, content) { +export async function createDocument(page, url) { await page.goto(url); await page.getByText('Create document', { exact: true }).click(); await expect(page.locator('div.ProseMirror')).toBeVisible(); await expect(page.locator('div.ProseMirror')).toHaveAttribute('contenteditable', 'true'); // Allow Y.js WebSocket to stabilize before typing await page.waitForTimeout(2000); - - if (content) { - const savePromise = waitForSave(page); - await fill(page, content); - await savePromise; - } } export async function fill(page, text) { From 33259fc888208f913f360bd5a33f0b810a5b295e Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Tue, 14 Jul 2026 18:07:39 +0200 Subject: [PATCH 03/27] Open previewed/published page in a new tab and verify content --- test/e2e/tests/preview_publish.spec.js | 34 +++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/test/e2e/tests/preview_publish.spec.js b/test/e2e/tests/preview_publish.spec.js index 96f45ff0f..ca0c38f28 100644 --- a/test/e2e/tests/preview_publish.spec.js +++ b/test/e2e/tests/preview_publish.spec.js @@ -36,8 +36,8 @@ test('Clicking Preview opens a confirmation dialog', async ({ page }) => { await expect(page.locator('da-dialog')).toContainText('Preview the'); }); -test('Preview actually previews the selected page', async ({ page }, workerInfo) => { - test.setTimeout(60000); +test('Preview actually previews the selected page', async ({ page, context }, workerInfo) => { + test.setTimeout(90000); const url = getTestPageURL('preview', workerInfo); const pageName = url.split('/').pop(); @@ -61,10 +61,23 @@ test('Preview actually previews the selected page', async ({ page }, workerInfo) await expect(page.locator('button.da-aem-results-btn')).toBeVisible({ timeout: 30000 }); await expect(page.locator('button.da-aem-results-btn')).toContainText('Previewed 1 item'); await expect(page.locator('da-dialog').filter({ hasText: 'Errors' })).toHaveCount(0); + + // Open the previewed page in its own browser tab and confirm it actually rendered + await page.locator('button.da-aem-results-btn').click(); + const previewLink = page.locator('da-dialog a').first(); + await expect(previewLink).toBeVisible(); + const previewUrl = await previewLink.getAttribute('href'); + + const previewTab = await context.newPage(); + await previewTab.goto(previewUrl); + await expect(previewTab.locator('body')).toContainText('preview test'); + + // Give the preview tab a moment before wrapping up + await page.waitForTimeout(5000); }); -test('Publish actually publishes the selected page', async ({ page }, workerInfo) => { - test.setTimeout(60000); +test('Publish actually publishes the selected page', async ({ page, context }, workerInfo) => { + test.setTimeout(90000); const url = getTestPageURL('publish', workerInfo); const pageName = url.split('/').pop(); @@ -88,6 +101,19 @@ test('Publish actually publishes the selected page', async ({ page }, workerInfo await expect(page.locator('button.da-aem-results-btn')).toBeVisible({ timeout: 30000 }); await expect(page.locator('button.da-aem-results-btn')).toContainText('Published 1 item'); await expect(page.locator('da-dialog').filter({ hasText: 'Errors' })).toHaveCount(0); + + // Open the published page in its own browser tab and confirm it actually rendered + await page.locator('button.da-aem-results-btn').click(); + const publishLink = page.locator('da-dialog a').first(); + await expect(publishLink).toBeVisible(); + const publishUrl = await publishLink.getAttribute('href'); + + const publishTab = await context.newPage(); + await publishTab.goto(publishUrl); + await expect(publishTab.locator('body')).toContainText('publish test'); + + // Give the publish tab a moment before wrapping up + await page.waitForTimeout(5000); }); test('Preview and Publish buttons are hidden when only a folder is selected', async ({ page }) => { From 6ce7df4f6d4bf638d22fff61952942fefbb6c127 Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Tue, 14 Jul 2026 18:17:36 +0200 Subject: [PATCH 04/27] Add bulk preview/publish e2e tests for 12 pages in a folder --- test/e2e/tests/preview_publish.spec.js | 90 ++++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 5 deletions(-) diff --git a/test/e2e/tests/preview_publish.spec.js b/test/e2e/tests/preview_publish.spec.js index ca0c38f28..58f40cd14 100644 --- a/test/e2e/tests/preview_publish.spec.js +++ b/test/e2e/tests/preview_publish.spec.js @@ -1,12 +1,14 @@ import { test, expect } from '@playwright/test'; import ENV from '../utils/env.js'; import { - getQuery, getTestPageURL, createDocument, fill, TEST_ORG, TEST_SITE, + getQuery, getTestPageURL, getTestFolderURL, createDocument, fill, TEST_ORG, TEST_SITE, } from '../utils/page.js'; // Requires write access to TEST_SITE. pingtest must exist in the /tests directory. const TESTS_DIR = `${ENV}/${getQuery()}#/${TEST_ORG}/${TEST_SITE}/tests`; +const BULK_PAGE_COUNT = 12; + async function selectItem(page, name) { const checkbox = page .locator('div.da-item-list-item-inner').filter({ hasText: name, exact: true }) @@ -15,6 +17,44 @@ async function selectItem(page, name) { await page.keyboard.press(' '); } +// Creates a folder under the tests directory and returns both its URL and the +// hash-path portion (e.g. /org/site/tests/pw-foo-123-chromium) so pages can be +// created directly inside it via getTestPageURL(..., folderPath). +async function createFolder(page, workerInfo, testIdentifier) { + const folderURL = getTestFolderURL(testIdentifier, workerInfo); + const folderName = folderURL.split('/').pop(); + + await page.goto(TESTS_DIR); + await expect(page.getByRole('button', { name: 'New' })).toBeEnabled(); + await page.getByRole('button', { name: 'New' }).click({ force: true }); + await page.getByRole('menuitem', { name: 'Folder' }).click(); + await page.getByPlaceholder('folder name').fill(folderName); + await page.getByRole('button', { name: 'Create' }).click(); + + const folderPath = new URL(folderURL).hash.slice(1); + return { folderURL, folderPath }; +} + +// Creates `count` pages inside the given folder, each with some distinguishing +// text content, and returns the list of page names created. +async function createPagesInFolder(page, workerInfo, folderPath, prefix, count) { + const pageNames = []; + for (let i = 0; i < count; i += 1) { + const url = getTestPageURL(`${prefix}${i}`, workerInfo, folderPath); + const pageName = url.split('/').pop(); + pageNames.push(pageName); + + // eslint-disable-next-line no-await-in-loop + await createDocument(page, url); + // eslint-disable-next-line no-await-in-loop + await fill(page, `${prefix} test ${i}`); + // Wait to ensure its saved in da-admin + // eslint-disable-next-line no-await-in-loop + await page.waitForTimeout(5000); + } + return pageNames; +} + test('Preview and Publish buttons appear when a file is selected', async ({ page }) => { await page.goto(TESTS_DIR); await expect(page.getByText('pingtest'), 'Precondition: pingtest must exist').toBeVisible(); @@ -36,8 +76,8 @@ test('Clicking Preview opens a confirmation dialog', async ({ page }) => { await expect(page.locator('da-dialog')).toContainText('Preview the'); }); -test('Preview actually previews the selected page', async ({ page, context }, workerInfo) => { - test.setTimeout(90000); +test('Preview the selected page', async ({ page, context }, workerInfo) => { + test.setTimeout(30000); const url = getTestPageURL('preview', workerInfo); const pageName = url.split('/').pop(); @@ -76,8 +116,8 @@ test('Preview actually previews the selected page', async ({ page, context }, wo await page.waitForTimeout(5000); }); -test('Publish actually publishes the selected page', async ({ page, context }, workerInfo) => { - test.setTimeout(90000); +test('Publish the selected page', async ({ page, context }, workerInfo) => { + test.setTimeout(30000); const url = getTestPageURL('publish', workerInfo); const pageName = url.split('/').pop(); @@ -116,6 +156,46 @@ test('Publish actually publishes the selected page', async ({ page, context }, w await page.waitForTimeout(5000); }); +test('Preview 12 pages in a folder', async ({ page }, workerInfo) => { + test.setTimeout(300000); + + const { folderURL, folderPath } = await createFolder(page, workerInfo, 'bulkprev'); + await createPagesInFolder(page, workerInfo, folderPath, 'bulkprev', BULK_PAGE_COUNT); + + await page.goto(folderURL); + await expect(page.locator('div.da-item-list-item-inner')).toHaveCount(BULK_PAGE_COUNT); + + await page.locator('input#select-all').click(); + await page.locator('button.preview-button').filter({ visible: true }).click(); + + await expect(page.locator('da-dialog')).toContainText('Preview the'); + await page.locator('sl-button.accent').filter({ visible: true }).click(); + + await expect(page.locator('button.da-aem-results-btn')).toBeVisible({ timeout: 60000 }); + await expect(page.locator('button.da-aem-results-btn')).toContainText(`Previewed ${BULK_PAGE_COUNT} items`); + await expect(page.locator('da-dialog').filter({ hasText: 'Errors' })).toHaveCount(0); +}); + +test('Publish 12 pages in a folder', async ({ page }, workerInfo) => { + test.setTimeout(300000); + + const { folderURL, folderPath } = await createFolder(page, workerInfo, 'bulkpub'); + await createPagesInFolder(page, workerInfo, folderPath, 'bulkpub', BULK_PAGE_COUNT); + + await page.goto(folderURL); + await expect(page.locator('div.da-item-list-item-inner')).toHaveCount(BULK_PAGE_COUNT); + + await page.locator('input#select-all').click(); + await page.locator('button.publish-button').filter({ visible: true }).click(); + + await expect(page.locator('da-dialog')).toContainText('Publish the'); + await page.locator('sl-button.accent').filter({ visible: true }).click(); + + await expect(page.locator('button.da-aem-results-btn')).toBeVisible({ timeout: 60000 }); + await expect(page.locator('button.da-aem-results-btn')).toContainText(`Published ${BULK_PAGE_COUNT} items`); + await expect(page.locator('da-dialog').filter({ hasText: 'Errors' })).toHaveCount(0); +}); + test('Preview and Publish buttons are hidden when only a folder is selected', async ({ page }) => { //test.skip(TEST_SITE !== 'da-status', 'Requires write access to da-status'); await page.goto(`${ENV}/${getQuery()}#/${TEST_ORG}/${TEST_SITE}`); From 57fb2a4ec520fd0915acac477833c5e3d7e815f7 Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Tue, 14 Jul 2026 18:23:51 +0200 Subject: [PATCH 05/27] Scope select-all locator to browse list to avoid strict-mode dup id violation --- test/e2e/tests/preview_publish.spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/e2e/tests/preview_publish.spec.js b/test/e2e/tests/preview_publish.spec.js index 58f40cd14..cd831ce7f 100644 --- a/test/e2e/tests/preview_publish.spec.js +++ b/test/e2e/tests/preview_publish.spec.js @@ -156,7 +156,7 @@ test('Publish the selected page', async ({ page, context }, workerInfo) => { await page.waitForTimeout(5000); }); -test('Preview 12 pages in a folder', async ({ page }, workerInfo) => { +test.only('Preview 12 pages in a folder', async ({ page }, workerInfo) => { test.setTimeout(300000); const { folderURL, folderPath } = await createFolder(page, workerInfo, 'bulkprev'); @@ -165,7 +165,7 @@ test('Preview 12 pages in a folder', async ({ page }, workerInfo) => { await page.goto(folderURL); await expect(page.locator('div.da-item-list-item-inner')).toHaveCount(BULK_PAGE_COUNT); - await page.locator('input#select-all').click(); + await page.locator('da-list.da-list-type-browse input#select-all').click(); await page.locator('button.preview-button').filter({ visible: true }).click(); await expect(page.locator('da-dialog')).toContainText('Preview the'); @@ -185,7 +185,7 @@ test('Publish 12 pages in a folder', async ({ page }, workerInfo) => { await page.goto(folderURL); await expect(page.locator('div.da-item-list-item-inner')).toHaveCount(BULK_PAGE_COUNT); - await page.locator('input#select-all').click(); + await page.locator('da-list.da-list-type-browse input#select-all').click(); await page.locator('button.publish-button').filter({ visible: true }).click(); await expect(page.locator('da-dialog')).toContainText('Publish the'); From 5f1ec35a769d28a947ce8ed58934e6c7fb62e12c Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Tue, 14 Jul 2026 18:28:40 +0200 Subject: [PATCH 06/27] fix: remove test comment --- test/e2e/tests/preview_publish.spec.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/e2e/tests/preview_publish.spec.js b/test/e2e/tests/preview_publish.spec.js index cd831ce7f..ef002e0c3 100644 --- a/test/e2e/tests/preview_publish.spec.js +++ b/test/e2e/tests/preview_publish.spec.js @@ -156,7 +156,7 @@ test('Publish the selected page', async ({ page, context }, workerInfo) => { await page.waitForTimeout(5000); }); -test.only('Preview 12 pages in a folder', async ({ page }, workerInfo) => { +test('Preview 12 pages in a folder', async ({ page }, workerInfo) => { test.setTimeout(300000); const { folderURL, folderPath } = await createFolder(page, workerInfo, 'bulkprev'); @@ -197,7 +197,6 @@ test('Publish 12 pages in a folder', async ({ page }, workerInfo) => { }); test('Preview and Publish buttons are hidden when only a folder is selected', async ({ page }) => { - //test.skip(TEST_SITE !== 'da-status', 'Requires write access to da-status'); await page.goto(`${ENV}/${getQuery()}#/${TEST_ORG}/${TEST_SITE}`); await expect(page.getByText('tests'), 'Precondition: tests folder must exist').toBeVisible(); From 0b4f0050530b61bb4c349d5fb18468da0d9e4a4c Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Wed, 15 Jul 2026 09:06:06 +0200 Subject: [PATCH 07/27] Add delete + unpublish cleanup to all 4 preview/publish e2e tests --- test/e2e/tests/preview_publish.spec.js | 47 +++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/test/e2e/tests/preview_publish.spec.js b/test/e2e/tests/preview_publish.spec.js index ef002e0c3..4432b43ee 100644 --- a/test/e2e/tests/preview_publish.spec.js +++ b/test/e2e/tests/preview_publish.spec.js @@ -55,6 +55,25 @@ async function createPagesInFolder(page, workerInfo, folderPath, prefix, count) return pageNames; } +// Deletes the currently selected item(s) and unpublishes them (removes preview and +// live copies), so tests that actually preview/publish real content don't leave +// anything behind in AEM. Assumes the delete confirmation dialog isn't open yet. +async function deleteAndUnpublish(page) { + await page.locator('button.delete-button').filter({ visible: true }).click(); + + // Check "Unpublish" so the preview/live copies are removed, not just the DA document + await page.locator('input[name="confirm-unpublish"]').click(); + + // Unpublishing (and bulk deletes of 10+ items) requires typing YES to confirm + await page.locator('sl-input[placeholder="YES"]').locator('input').fill('YES'); + + await page.locator('sl-button.negative').filter({ visible: true }).click(); + + // Wait for the delete button to disappear, which is when we're done + await expect(page.locator('button.delete-button').filter({ visible: true })) + .not.toBeVisible({ timeout: 60000 }); +} + test('Preview and Publish buttons appear when a file is selected', async ({ page }) => { await page.goto(TESTS_DIR); await expect(page.getByText('pingtest'), 'Precondition: pingtest must exist').toBeVisible(); @@ -77,7 +96,7 @@ test('Clicking Preview opens a confirmation dialog', async ({ page }) => { }); test('Preview the selected page', async ({ page, context }, workerInfo) => { - test.setTimeout(30000); + test.setTimeout(60000); const url = getTestPageURL('preview', workerInfo); const pageName = url.split('/').pop(); @@ -114,10 +133,16 @@ test('Preview the selected page', async ({ page, context }, workerInfo) => { // Give the preview tab a moment before wrapping up await page.waitForTimeout(5000); + await previewTab.close(); + + // Clean up: unpublish and delete the test page + await page.locator('button.da-dialog-close-btn').click(); + await selectItem(page, pageName); + await deleteAndUnpublish(page); }); test('Publish the selected page', async ({ page, context }, workerInfo) => { - test.setTimeout(30000); + test.setTimeout(60000); const url = getTestPageURL('publish', workerInfo); const pageName = url.split('/').pop(); @@ -154,10 +179,16 @@ test('Publish the selected page', async ({ page, context }, workerInfo) => { // Give the publish tab a moment before wrapping up await page.waitForTimeout(5000); + await publishTab.close(); + + // Clean up: unpublish and delete the test page + await page.locator('button.da-dialog-close-btn').click(); + await selectItem(page, pageName); + await deleteAndUnpublish(page); }); test('Preview 12 pages in a folder', async ({ page }, workerInfo) => { - test.setTimeout(300000); + test.setTimeout(360000); const { folderURL, folderPath } = await createFolder(page, workerInfo, 'bulkprev'); await createPagesInFolder(page, workerInfo, folderPath, 'bulkprev', BULK_PAGE_COUNT); @@ -174,10 +205,14 @@ test('Preview 12 pages in a folder', async ({ page }, workerInfo) => { await expect(page.locator('button.da-aem-results-btn')).toBeVisible({ timeout: 60000 }); await expect(page.locator('button.da-aem-results-btn')).toContainText(`Previewed ${BULK_PAGE_COUNT} items`); await expect(page.locator('da-dialog').filter({ hasText: 'Errors' })).toHaveCount(0); + + // Clean up: unpublish and delete all 12 test pages + await page.locator('da-list.da-list-type-browse input#select-all').click(); + await deleteAndUnpublish(page); }); test('Publish 12 pages in a folder', async ({ page }, workerInfo) => { - test.setTimeout(300000); + test.setTimeout(360000); const { folderURL, folderPath } = await createFolder(page, workerInfo, 'bulkpub'); await createPagesInFolder(page, workerInfo, folderPath, 'bulkpub', BULK_PAGE_COUNT); @@ -194,6 +229,10 @@ test('Publish 12 pages in a folder', async ({ page }, workerInfo) => { await expect(page.locator('button.da-aem-results-btn')).toBeVisible({ timeout: 60000 }); await expect(page.locator('button.da-aem-results-btn')).toContainText(`Published ${BULK_PAGE_COUNT} items`); await expect(page.locator('da-dialog').filter({ hasText: 'Errors' })).toHaveCount(0); + + // Clean up: unpublish and delete all 12 test pages + await page.locator('da-list.da-list-type-browse input#select-all').click(); + await deleteAndUnpublish(page); }); test('Preview and Publish buttons are hidden when only a folder is selected', async ({ page }) => { From 5817a9b3f4d05c5205d34f757f4f98f4eba0918f Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Wed, 15 Jul 2026 09:10:01 +0200 Subject: [PATCH 08/27] Delete the empty test folder after bulk preview/publish cleanup --- test/e2e/tests/preview_publish.spec.js | 27 ++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/test/e2e/tests/preview_publish.spec.js b/test/e2e/tests/preview_publish.spec.js index 4432b43ee..7899cba6f 100644 --- a/test/e2e/tests/preview_publish.spec.js +++ b/test/e2e/tests/preview_publish.spec.js @@ -74,6 +74,25 @@ async function deleteAndUnpublish(page) { .not.toBeVisible({ timeout: 60000 }); } +// Deletes an (empty) folder from the tests directory. Folders don't have anything +// to unpublish, and an empty folder's delete count is below the typed-YES threshold, +// so this is a plain delete confirmation. +async function deleteFolder(page, folderName) { + await page.goto(TESTS_DIR); + await expect(page.getByText(folderName), 'Precondition: folder must exist').toBeVisible(); + + await selectItem(page, folderName); + await page.locator('button.delete-button').filter({ visible: true }).click(); + + const confirmButton = page.locator('sl-button.negative').filter({ visible: true }); + await expect(confirmButton).toBeEnabled({ timeout: 10000 }); + await confirmButton.click(); + + // Wait for the delete button to disappear, which is when we're done + await expect(page.locator('button.delete-button').filter({ visible: true })) + .not.toBeVisible({ timeout: 30000 }); +} + test('Preview and Publish buttons appear when a file is selected', async ({ page }) => { await page.goto(TESTS_DIR); await expect(page.getByText('pingtest'), 'Precondition: pingtest must exist').toBeVisible(); @@ -191,6 +210,7 @@ test('Preview 12 pages in a folder', async ({ page }, workerInfo) => { test.setTimeout(360000); const { folderURL, folderPath } = await createFolder(page, workerInfo, 'bulkprev'); + const folderName = folderPath.split('/').pop(); await createPagesInFolder(page, workerInfo, folderPath, 'bulkprev', BULK_PAGE_COUNT); await page.goto(folderURL); @@ -206,15 +226,17 @@ test('Preview 12 pages in a folder', async ({ page }, workerInfo) => { await expect(page.locator('button.da-aem-results-btn')).toContainText(`Previewed ${BULK_PAGE_COUNT} items`); await expect(page.locator('da-dialog').filter({ hasText: 'Errors' })).toHaveCount(0); - // Clean up: unpublish and delete all 12 test pages + // Clean up: unpublish and delete all 12 test pages, then the now-empty folder await page.locator('da-list.da-list-type-browse input#select-all').click(); await deleteAndUnpublish(page); + await deleteFolder(page, folderName); }); test('Publish 12 pages in a folder', async ({ page }, workerInfo) => { test.setTimeout(360000); const { folderURL, folderPath } = await createFolder(page, workerInfo, 'bulkpub'); + const folderName = folderPath.split('/').pop(); await createPagesInFolder(page, workerInfo, folderPath, 'bulkpub', BULK_PAGE_COUNT); await page.goto(folderURL); @@ -230,9 +252,10 @@ test('Publish 12 pages in a folder', async ({ page }, workerInfo) => { await expect(page.locator('button.da-aem-results-btn')).toContainText(`Published ${BULK_PAGE_COUNT} items`); await expect(page.locator('da-dialog').filter({ hasText: 'Errors' })).toHaveCount(0); - // Clean up: unpublish and delete all 12 test pages + // Clean up: unpublish and delete all 12 test pages, then the now-empty folder await page.locator('da-list.da-list-type-browse input#select-all').click(); await deleteAndUnpublish(page); + await deleteFolder(page, folderName); }); test('Preview and Publish buttons are hidden when only a folder is selected', async ({ page }) => { From 089cb4829dd6754c16f743b740f0d1c28525018b Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Wed, 15 Jul 2026 13:54:48 +0200 Subject: [PATCH 09/27] Skip unpublish checkbox in cleanup for now (plain delete only) --- test/e2e/tests/preview_publish.spec.js | 34 +++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/test/e2e/tests/preview_publish.spec.js b/test/e2e/tests/preview_publish.spec.js index 7899cba6f..da83d1a82 100644 --- a/test/e2e/tests/preview_publish.spec.js +++ b/test/e2e/tests/preview_publish.spec.js @@ -55,17 +55,17 @@ async function createPagesInFolder(page, workerInfo, folderPath, prefix, count) return pageNames; } -// Deletes the currently selected item(s) and unpublishes them (removes preview and -// live copies), so tests that actually preview/publish real content don't leave -// anything behind in AEM. Assumes the delete confirmation dialog isn't open yet. -async function deleteAndUnpublish(page) { +// Deletes the currently selected item(s). Note: this does NOT unpublish them +// (leaves any preview/live copies in place) — the unpublish checkbox is disabled +// for now due to an outstanding issue. Assumes the delete confirmation dialog +// isn't open yet. +async function deleteItems(page, itemCount) { await page.locator('button.delete-button').filter({ visible: true }).click(); - // Check "Unpublish" so the preview/live copies are removed, not just the DA document - await page.locator('input[name="confirm-unpublish"]').click(); - - // Unpublishing (and bulk deletes of 10+ items) requires typing YES to confirm - await page.locator('sl-input[placeholder="YES"]').locator('input').fill('YES'); + // Bulk deletes of 10+ items require typing YES to confirm + if (itemCount >= 10) { + await page.locator('sl-input[placeholder="YES"]').locator('input').fill('YES'); + } await page.locator('sl-button.negative').filter({ visible: true }).click(); @@ -154,10 +154,10 @@ test('Preview the selected page', async ({ page, context }, workerInfo) => { await page.waitForTimeout(5000); await previewTab.close(); - // Clean up: unpublish and delete the test page + // Clean up: delete the test page await page.locator('button.da-dialog-close-btn').click(); await selectItem(page, pageName); - await deleteAndUnpublish(page); + await deleteItems(page, 1); }); test('Publish the selected page', async ({ page, context }, workerInfo) => { @@ -200,10 +200,10 @@ test('Publish the selected page', async ({ page, context }, workerInfo) => { await page.waitForTimeout(5000); await publishTab.close(); - // Clean up: unpublish and delete the test page + // Clean up: delete the test page await page.locator('button.da-dialog-close-btn').click(); await selectItem(page, pageName); - await deleteAndUnpublish(page); + await deleteItems(page, 1); }); test('Preview 12 pages in a folder', async ({ page }, workerInfo) => { @@ -226,9 +226,9 @@ test('Preview 12 pages in a folder', async ({ page }, workerInfo) => { await expect(page.locator('button.da-aem-results-btn')).toContainText(`Previewed ${BULK_PAGE_COUNT} items`); await expect(page.locator('da-dialog').filter({ hasText: 'Errors' })).toHaveCount(0); - // Clean up: unpublish and delete all 12 test pages, then the now-empty folder + // Clean up: delete all 12 test pages, then the now-empty folder await page.locator('da-list.da-list-type-browse input#select-all').click(); - await deleteAndUnpublish(page); + await deleteItems(page, BULK_PAGE_COUNT); await deleteFolder(page, folderName); }); @@ -252,9 +252,9 @@ test('Publish 12 pages in a folder', async ({ page }, workerInfo) => { await expect(page.locator('button.da-aem-results-btn')).toContainText(`Published ${BULK_PAGE_COUNT} items`); await expect(page.locator('da-dialog').filter({ hasText: 'Errors' })).toHaveCount(0); - // Clean up: unpublish and delete all 12 test pages, then the now-empty folder + // Clean up: delete all 12 test pages, then the now-empty folder await page.locator('da-list.da-list-type-browse input#select-all').click(); - await deleteAndUnpublish(page); + await deleteItems(page, BULK_PAGE_COUNT); await deleteFolder(page, folderName); }); From 48774fc6d35dfbd465b5d0521710e63ee9e1566c Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Wed, 15 Jul 2026 15:47:26 +0200 Subject: [PATCH 10/27] Simplify cleanup delete: drop typed-YES confirmation for bulk deletes too --- test/e2e/tests/preview_publish.spec.js | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/test/e2e/tests/preview_publish.spec.js b/test/e2e/tests/preview_publish.spec.js index da83d1a82..65b40524a 100644 --- a/test/e2e/tests/preview_publish.spec.js +++ b/test/e2e/tests/preview_publish.spec.js @@ -55,18 +55,13 @@ async function createPagesInFolder(page, workerInfo, folderPath, prefix, count) return pageNames; } -// Deletes the currently selected item(s). Note: this does NOT unpublish them -// (leaves any preview/live copies in place) — the unpublish checkbox is disabled -// for now due to an outstanding issue. Assumes the delete confirmation dialog -// isn't open yet. -async function deleteItems(page, itemCount) { +// Deletes the currently selected item(s) with a plain delete confirmation. Note: +// this does NOT unpublish them (leaves any preview/live copies in place) — the +// unpublish checkbox is skipped for now due to an outstanding issue, and no typed +// YES confirmation is used either. Assumes the delete confirmation dialog isn't +// open yet. +async function deleteItems(page) { await page.locator('button.delete-button').filter({ visible: true }).click(); - - // Bulk deletes of 10+ items require typing YES to confirm - if (itemCount >= 10) { - await page.locator('sl-input[placeholder="YES"]').locator('input').fill('YES'); - } - await page.locator('sl-button.negative').filter({ visible: true }).click(); // Wait for the delete button to disappear, which is when we're done @@ -157,7 +152,7 @@ test('Preview the selected page', async ({ page, context }, workerInfo) => { // Clean up: delete the test page await page.locator('button.da-dialog-close-btn').click(); await selectItem(page, pageName); - await deleteItems(page, 1); + await deleteItems(page); }); test('Publish the selected page', async ({ page, context }, workerInfo) => { @@ -203,7 +198,7 @@ test('Publish the selected page', async ({ page, context }, workerInfo) => { // Clean up: delete the test page await page.locator('button.da-dialog-close-btn').click(); await selectItem(page, pageName); - await deleteItems(page, 1); + await deleteItems(page); }); test('Preview 12 pages in a folder', async ({ page }, workerInfo) => { @@ -228,7 +223,7 @@ test('Preview 12 pages in a folder', async ({ page }, workerInfo) => { // Clean up: delete all 12 test pages, then the now-empty folder await page.locator('da-list.da-list-type-browse input#select-all').click(); - await deleteItems(page, BULK_PAGE_COUNT); + await deleteItems(page); await deleteFolder(page, folderName); }); @@ -254,7 +249,7 @@ test('Publish 12 pages in a folder', async ({ page }, workerInfo) => { // Clean up: delete all 12 test pages, then the now-empty folder await page.locator('da-list.da-list-type-browse input#select-all').click(); - await deleteItems(page, BULK_PAGE_COUNT); + await deleteItems(page); await deleteFolder(page, folderName); }); From 0643d3cf17184a668b6dc1e796347a30f2236a42 Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Wed, 15 Jul 2026 16:39:04 +0200 Subject: [PATCH 11/27] Remove explicit cleanup; rely on scheduled delete.spec.js sweep instead --- test/e2e/tests/preview_publish.spec.js | 59 ++++---------------------- 1 file changed, 8 insertions(+), 51 deletions(-) diff --git a/test/e2e/tests/preview_publish.spec.js b/test/e2e/tests/preview_publish.spec.js index 65b40524a..5d68bdcf2 100644 --- a/test/e2e/tests/preview_publish.spec.js +++ b/test/e2e/tests/preview_publish.spec.js @@ -55,39 +55,6 @@ async function createPagesInFolder(page, workerInfo, folderPath, prefix, count) return pageNames; } -// Deletes the currently selected item(s) with a plain delete confirmation. Note: -// this does NOT unpublish them (leaves any preview/live copies in place) — the -// unpublish checkbox is skipped for now due to an outstanding issue, and no typed -// YES confirmation is used either. Assumes the delete confirmation dialog isn't -// open yet. -async function deleteItems(page) { - await page.locator('button.delete-button').filter({ visible: true }).click(); - await page.locator('sl-button.negative').filter({ visible: true }).click(); - - // Wait for the delete button to disappear, which is when we're done - await expect(page.locator('button.delete-button').filter({ visible: true })) - .not.toBeVisible({ timeout: 60000 }); -} - -// Deletes an (empty) folder from the tests directory. Folders don't have anything -// to unpublish, and an empty folder's delete count is below the typed-YES threshold, -// so this is a plain delete confirmation. -async function deleteFolder(page, folderName) { - await page.goto(TESTS_DIR); - await expect(page.getByText(folderName), 'Precondition: folder must exist').toBeVisible(); - - await selectItem(page, folderName); - await page.locator('button.delete-button').filter({ visible: true }).click(); - - const confirmButton = page.locator('sl-button.negative').filter({ visible: true }); - await expect(confirmButton).toBeEnabled({ timeout: 10000 }); - await confirmButton.click(); - - // Wait for the delete button to disappear, which is when we're done - await expect(page.locator('button.delete-button').filter({ visible: true })) - .not.toBeVisible({ timeout: 30000 }); -} - test('Preview and Publish buttons appear when a file is selected', async ({ page }) => { await page.goto(TESTS_DIR); await expect(page.getByText('pingtest'), 'Precondition: pingtest must exist').toBeVisible(); @@ -149,10 +116,8 @@ test('Preview the selected page', async ({ page, context }, workerInfo) => { await page.waitForTimeout(5000); await previewTab.close(); - // Clean up: delete the test page - await page.locator('button.da-dialog-close-btn').click(); - await selectItem(page, pageName); - await deleteItems(page); + // Note: the test page is intentionally not deleted here — it's cleaned up + // later by the scheduled cleanup in delete.spec.js. }); test('Publish the selected page', async ({ page, context }, workerInfo) => { @@ -195,17 +160,14 @@ test('Publish the selected page', async ({ page, context }, workerInfo) => { await page.waitForTimeout(5000); await publishTab.close(); - // Clean up: delete the test page - await page.locator('button.da-dialog-close-btn').click(); - await selectItem(page, pageName); - await deleteItems(page); + // Note: the test page is intentionally not deleted here — it's cleaned up + // later by the scheduled cleanup in delete.spec.js. }); test('Preview 12 pages in a folder', async ({ page }, workerInfo) => { test.setTimeout(360000); const { folderURL, folderPath } = await createFolder(page, workerInfo, 'bulkprev'); - const folderName = folderPath.split('/').pop(); await createPagesInFolder(page, workerInfo, folderPath, 'bulkprev', BULK_PAGE_COUNT); await page.goto(folderURL); @@ -221,17 +183,14 @@ test('Preview 12 pages in a folder', async ({ page }, workerInfo) => { await expect(page.locator('button.da-aem-results-btn')).toContainText(`Previewed ${BULK_PAGE_COUNT} items`); await expect(page.locator('da-dialog').filter({ hasText: 'Errors' })).toHaveCount(0); - // Clean up: delete all 12 test pages, then the now-empty folder - await page.locator('da-list.da-list-type-browse input#select-all').click(); - await deleteItems(page); - await deleteFolder(page, folderName); + // Note: the test pages and folder are intentionally not deleted here — they're + // cleaned up later by the scheduled cleanup in delete.spec.js. }); test('Publish 12 pages in a folder', async ({ page }, workerInfo) => { test.setTimeout(360000); const { folderURL, folderPath } = await createFolder(page, workerInfo, 'bulkpub'); - const folderName = folderPath.split('/').pop(); await createPagesInFolder(page, workerInfo, folderPath, 'bulkpub', BULK_PAGE_COUNT); await page.goto(folderURL); @@ -247,10 +206,8 @@ test('Publish 12 pages in a folder', async ({ page }, workerInfo) => { await expect(page.locator('button.da-aem-results-btn')).toContainText(`Published ${BULK_PAGE_COUNT} items`); await expect(page.locator('da-dialog').filter({ hasText: 'Errors' })).toHaveCount(0); - // Clean up: delete all 12 test pages, then the now-empty folder - await page.locator('da-list.da-list-type-browse input#select-all').click(); - await deleteItems(page); - await deleteFolder(page, folderName); + // Note: the test pages and folder are intentionally not deleted here — they're + // cleaned up later by the scheduled cleanup in delete.spec.js. }); test('Preview and Publish buttons are hidden when only a folder is selected', async ({ page }) => { From c8288ca0f204db018aff36c1793ab3a40b0da972 Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Wed, 15 Jul 2026 17:12:49 +0200 Subject: [PATCH 12/27] Dismiss optional alert banner on first browse-view load in each test --- test/e2e/tests/preview_publish.spec.js | 27 ++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/test/e2e/tests/preview_publish.spec.js b/test/e2e/tests/preview_publish.spec.js index 5d68bdcf2..98a0dc822 100644 --- a/test/e2e/tests/preview_publish.spec.js +++ b/test/e2e/tests/preview_publish.spec.js @@ -9,6 +9,15 @@ const TESTS_DIR = `${ENV}/${getQuery()}#/${TEST_ORG}/${TEST_SITE}/tests`; const BULK_PAGE_COUNT = 12; +// Some environments show a dismissible alert banner the first time the browse +// view loads. If present, dismiss it so it doesn't block later interactions. +async function dismissAlertBanner(page) { + const alert = page.getByRole('alert'); + if (await alert.isVisible().catch(() => false)) { + await alert.getByRole('button').first().click(); + } +} + async function selectItem(page, name) { const checkbox = page .locator('div.da-item-list-item-inner').filter({ hasText: name, exact: true }) @@ -25,6 +34,7 @@ async function createFolder(page, workerInfo, testIdentifier) { const folderName = folderURL.split('/').pop(); await page.goto(TESTS_DIR); + await dismissAlertBanner(page); await expect(page.getByRole('button', { name: 'New' })).toBeEnabled(); await page.getByRole('button', { name: 'New' }).click({ force: true }); await page.getByRole('menuitem', { name: 'Folder' }).click(); @@ -57,6 +67,7 @@ async function createPagesInFolder(page, workerInfo, folderPath, prefix, count) test('Preview and Publish buttons appear when a file is selected', async ({ page }) => { await page.goto(TESTS_DIR); + await dismissAlertBanner(page); await expect(page.getByText('pingtest'), 'Precondition: pingtest must exist').toBeVisible(); await selectItem(page, 'pingtest'); @@ -67,6 +78,7 @@ test('Preview and Publish buttons appear when a file is selected', async ({ page test('Clicking Preview opens a confirmation dialog', async ({ page }) => { await page.goto(TESTS_DIR); + await dismissAlertBanner(page); await expect(page.getByText('pingtest'), 'Precondition: pingtest must exist').toBeVisible(); await selectItem(page, 'pingtest'); @@ -90,6 +102,7 @@ test('Preview the selected page', async ({ page, context }, workerInfo) => { await page.waitForTimeout(5000); await page.goto(TESTS_DIR); + await dismissAlertBanner(page); await expect(page.getByText(pageName), 'Precondition: new page must exist').toBeVisible(); await selectItem(page, pageName); @@ -115,9 +128,6 @@ test('Preview the selected page', async ({ page, context }, workerInfo) => { // Give the preview tab a moment before wrapping up await page.waitForTimeout(5000); await previewTab.close(); - - // Note: the test page is intentionally not deleted here — it's cleaned up - // later by the scheduled cleanup in delete.spec.js. }); test('Publish the selected page', async ({ page, context }, workerInfo) => { @@ -134,6 +144,7 @@ test('Publish the selected page', async ({ page, context }, workerInfo) => { await page.waitForTimeout(5000); await page.goto(TESTS_DIR); + await dismissAlertBanner(page); await expect(page.getByText(pageName), 'Precondition: new page must exist').toBeVisible(); await selectItem(page, pageName); @@ -159,9 +170,6 @@ test('Publish the selected page', async ({ page, context }, workerInfo) => { // Give the publish tab a moment before wrapping up await page.waitForTimeout(5000); await publishTab.close(); - - // Note: the test page is intentionally not deleted here — it's cleaned up - // later by the scheduled cleanup in delete.spec.js. }); test('Preview 12 pages in a folder', async ({ page }, workerInfo) => { @@ -182,9 +190,6 @@ test('Preview 12 pages in a folder', async ({ page }, workerInfo) => { await expect(page.locator('button.da-aem-results-btn')).toBeVisible({ timeout: 60000 }); await expect(page.locator('button.da-aem-results-btn')).toContainText(`Previewed ${BULK_PAGE_COUNT} items`); await expect(page.locator('da-dialog').filter({ hasText: 'Errors' })).toHaveCount(0); - - // Note: the test pages and folder are intentionally not deleted here — they're - // cleaned up later by the scheduled cleanup in delete.spec.js. }); test('Publish 12 pages in a folder', async ({ page }, workerInfo) => { @@ -205,13 +210,11 @@ test('Publish 12 pages in a folder', async ({ page }, workerInfo) => { await expect(page.locator('button.da-aem-results-btn')).toBeVisible({ timeout: 60000 }); await expect(page.locator('button.da-aem-results-btn')).toContainText(`Published ${BULK_PAGE_COUNT} items`); await expect(page.locator('da-dialog').filter({ hasText: 'Errors' })).toHaveCount(0); - - // Note: the test pages and folder are intentionally not deleted here — they're - // cleaned up later by the scheduled cleanup in delete.spec.js. }); test('Preview and Publish buttons are hidden when only a folder is selected', async ({ page }) => { await page.goto(`${ENV}/${getQuery()}#/${TEST_ORG}/${TEST_SITE}`); + await dismissAlertBanner(page); await expect(page.getByText('tests'), 'Precondition: tests folder must exist').toBeVisible(); await selectItem(page, 'tests'); From 4bda98655ca813a0649d0659f3848caeb2fb18c2 Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Wed, 15 Jul 2026 17:13:23 +0200 Subject: [PATCH 13/27] Target the alert banner's Dismiss button by accessible name --- test/e2e/tests/preview_publish.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/tests/preview_publish.spec.js b/test/e2e/tests/preview_publish.spec.js index 98a0dc822..eff8959a1 100644 --- a/test/e2e/tests/preview_publish.spec.js +++ b/test/e2e/tests/preview_publish.spec.js @@ -14,7 +14,7 @@ const BULK_PAGE_COUNT = 12; async function dismissAlertBanner(page) { const alert = page.getByRole('alert'); if (await alert.isVisible().catch(() => false)) { - await alert.getByRole('button').first().click(); + await alert.getByRole('button', { name: 'Dismiss' }).click(); } } From 8dc8b6e146b19b371b51380e0b3b7cbc8037932c Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Wed, 15 Jul 2026 17:19:48 +0200 Subject: [PATCH 14/27] fix: improve test stability --- test/e2e/tests/preview_publish.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/e2e/tests/preview_publish.spec.js b/test/e2e/tests/preview_publish.spec.js index eff8959a1..ebfd3daf2 100644 --- a/test/e2e/tests/preview_publish.spec.js +++ b/test/e2e/tests/preview_publish.spec.js @@ -12,6 +12,7 @@ const BULK_PAGE_COUNT = 12; // Some environments show a dismissible alert banner the first time the browse // view loads. If present, dismiss it so it doesn't block later interactions. async function dismissAlertBanner(page) { + await page.waitForTimeout(5000); const alert = page.getByRole('alert'); if (await alert.isVisible().catch(() => false)) { await alert.getByRole('button', { name: 'Dismiss' }).click(); From e1ccbd7385ceffa209b998509b1b6acaf896242d Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Wed, 15 Jul 2026 17:45:06 +0200 Subject: [PATCH 15/27] Make saveToAem HLX6-aware via nx aem.preview/aem.publish API --- blocks/shared/utils.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/blocks/shared/utils.js b/blocks/shared/utils.js index 3e30b52ba..1fc4d36fa 100644 --- a/blocks/shared/utils.js +++ b/blocks/shared/utils.js @@ -168,11 +168,14 @@ export function parseAemError(xError) { return xError.replace('[admin] ', ''); } +// `action` is the admin API namespace: 'preview' or 'live' (the latter is the +// admin API's name for publish). Routes through nx's `aem` API, which detects +// HLX6 vs legacy (via isHlx6) and calls the correct endpoint for the org/site. export async function saveToAem(path, action) { - const [owner, repo, ...parts] = path.slice(1).toLowerCase().split('/'); - const aemPath = parts.join('/'); - const url = `${AEM_ORIGIN}/${action}/${owner}/${repo}/main/${aemPath}`; - const resp = await daFetch(url, { method: 'POST' }); + const { aem } = await getNx2Api(); + const aemPath = path.toLowerCase(); + const call = action === 'live' ? aem.publish : aem.preview; + const resp = await call(aemPath); if (!resp.ok) { const { status, headers } = resp; const authErr = [401, 403].some((s) => s === status); From 9a3b30e9f613c6818a81131cc2987090c07a5cdf Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Wed, 15 Jul 2026 21:01:51 +0200 Subject: [PATCH 16/27] fix: stabilize test --- test/e2e/tests/preview_publish.spec.js | 29 ++++++++++++++++++-------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/test/e2e/tests/preview_publish.spec.js b/test/e2e/tests/preview_publish.spec.js index ebfd3daf2..70a5844e8 100644 --- a/test/e2e/tests/preview_publish.spec.js +++ b/test/e2e/tests/preview_publish.spec.js @@ -12,7 +12,6 @@ const BULK_PAGE_COUNT = 12; // Some environments show a dismissible alert banner the first time the browse // view loads. If present, dismiss it so it doesn't block later interactions. async function dismissAlertBanner(page) { - await page.waitForTimeout(5000); const alert = page.getByRole('alert'); if (await alert.isVisible().catch(() => false)) { await alert.getByRole('button', { name: 'Dismiss' }).click(); @@ -35,6 +34,7 @@ async function createFolder(page, workerInfo, testIdentifier) { const folderName = folderURL.split('/').pop(); await page.goto(TESTS_DIR); + await page.waitForTimeout(5000); await dismissAlertBanner(page); await expect(page.getByRole('button', { name: 'New' })).toBeEnabled(); await page.getByRole('button', { name: 'New' }).click({ force: true }); @@ -57,17 +57,23 @@ async function createPagesInFolder(page, workerInfo, folderPath, prefix, count) // eslint-disable-next-line no-await-in-loop await createDocument(page, url); + + // Allow Y.js WebSocket to stabilize before typing + // eslint-disable-next-line no-await-in-loop + await page.waitForTimeout(2000); + // eslint-disable-next-line no-await-in-loop await fill(page, `${prefix} test ${i}`); - // Wait to ensure its saved in da-admin + // eslint-disable-next-line no-await-in-loop - await page.waitForTimeout(5000); + await page.waitForTimeout(3000); } return pageNames; } test('Preview and Publish buttons appear when a file is selected', async ({ page }) => { await page.goto(TESTS_DIR); + await page.waitForTimeout(5000); await dismissAlertBanner(page); await expect(page.getByText('pingtest'), 'Precondition: pingtest must exist').toBeVisible(); @@ -79,6 +85,7 @@ test('Preview and Publish buttons appear when a file is selected', async ({ page test('Clicking Preview opens a confirmation dialog', async ({ page }) => { await page.goto(TESTS_DIR); + await page.waitForTimeout(5000); await dismissAlertBanner(page); await expect(page.getByText('pingtest'), 'Precondition: pingtest must exist').toBeVisible(); @@ -96,16 +103,17 @@ test('Preview the selected page', async ({ page, context }, workerInfo) => { const pageName = url.split('/').pop(); await createDocument(page, url); - // Enter some text onto the page + // Allow Y.js WebSocket to stabilize before typing + await page.waitForTimeout(2000); await fill(page, 'preview test'); // Wait to ensure its saved in da-admin - await page.waitForTimeout(5000); + await page.waitForTimeout(3000); await page.goto(TESTS_DIR); - await dismissAlertBanner(page); await expect(page.getByText(pageName), 'Precondition: new page must exist').toBeVisible(); + await dismissAlertBanner(page); await selectItem(page, pageName); await page.locator('button.preview-button').filter({ visible: true }).click(); @@ -138,15 +146,16 @@ test('Publish the selected page', async ({ page, context }, workerInfo) => { const pageName = url.split('/').pop(); await createDocument(page, url); - // Enter some text onto the page + // Allow Y.js WebSocket to stabilize before typing + await page.waitForTimeout(2000); await fill(page, 'publish test'); // Wait to ensure its saved in da-admin - await page.waitForTimeout(5000); + await page.waitForTimeout(3000); await page.goto(TESTS_DIR); - await dismissAlertBanner(page); await expect(page.getByText(pageName), 'Precondition: new page must exist').toBeVisible(); + await dismissAlertBanner(page); await selectItem(page, pageName); await page.locator('button.publish-button').filter({ visible: true }).click(); @@ -181,6 +190,7 @@ test('Preview 12 pages in a folder', async ({ page }, workerInfo) => { await page.goto(folderURL); await expect(page.locator('div.da-item-list-item-inner')).toHaveCount(BULK_PAGE_COUNT); + await dismissAlertBanner(page); await page.locator('da-list.da-list-type-browse input#select-all').click(); await page.locator('button.preview-button').filter({ visible: true }).click(); @@ -201,6 +211,7 @@ test('Publish 12 pages in a folder', async ({ page }, workerInfo) => { await page.goto(folderURL); await expect(page.locator('div.da-item-list-item-inner')).toHaveCount(BULK_PAGE_COUNT); + await dismissAlertBanner(page); await page.locator('da-list.da-list-type-browse input#select-all').click(); await page.locator('button.publish-button').filter({ visible: true }).click(); From 18268d2142411dbbbf235634c6a172b5793a61d4 Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Wed, 15 Jul 2026 21:33:39 +0200 Subject: [PATCH 17/27] Share one set of 12 bulk test pages between preview and publish tests --- test/e2e/tests/delete.spec.js | 2 + test/e2e/tests/preview_publish.spec.js | 74 ++++++++++++++------------ 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/test/e2e/tests/delete.spec.js b/test/e2e/tests/delete.spec.js index 8634ce60c..81ffe341e 100644 --- a/test/e2e/tests/delete.spec.js +++ b/test/e2e/tests/delete.spec.js @@ -87,6 +87,8 @@ test('Delete multiple old pages', async ({ page }, workerInfo) => { // Wait for the delete button to disappear which is when we're done await expect(page.locator('button.delete-button').filter({ visible: true })).not.toBeVisible({ timeout: 600000 }); + + console.log('Deleted', itemsToDelete, 'test files and folders'); }); test('Empty out open editors on deleted documents', async ({ browser, page }, workerInfo) => { diff --git a/test/e2e/tests/preview_publish.spec.js b/test/e2e/tests/preview_publish.spec.js index 70a5844e8..339deb12d 100644 --- a/test/e2e/tests/preview_publish.spec.js +++ b/test/e2e/tests/preview_publish.spec.js @@ -133,9 +133,6 @@ test('Preview the selected page', async ({ page, context }, workerInfo) => { const previewTab = await context.newPage(); await previewTab.goto(previewUrl); await expect(previewTab.locator('body')).toContainText('preview test'); - - // Give the preview tab a moment before wrapping up - await page.waitForTimeout(5000); await previewTab.close(); }); @@ -176,56 +173,63 @@ test('Publish the selected page', async ({ page, context }, workerInfo) => { const publishTab = await context.newPage(); await publishTab.goto(publishUrl); await expect(publishTab.locator('body')).toContainText('publish test'); - - // Give the publish tab a moment before wrapping up - await page.waitForTimeout(5000); await publishTab.close(); }); -test('Preview 12 pages in a folder', async ({ page }, workerInfo) => { - test.setTimeout(360000); +test.describe.serial('Bulk preview/publish 12 pages in a folder', () => { + // Created once in beforeAll and reused by both tests below, since creating + // 12 real pages is expensive and the same set can be previewed and then + // published in sequence. + let folderURL; + let folderPath; - const { folderURL, folderPath } = await createFolder(page, workerInfo, 'bulkprev'); - await createPagesInFolder(page, workerInfo, folderPath, 'bulkprev', BULK_PAGE_COUNT); + test.beforeAll(async ({ browser }, workerInfo) => { + const page = await browser.newPage(); + ({ folderURL, folderPath } = await createFolder(page, workerInfo, 'bulk')); + await createPagesInFolder(page, workerInfo, folderPath, 'bulk', BULK_PAGE_COUNT); + await page.close(); + }); - await page.goto(folderURL); - await expect(page.locator('div.da-item-list-item-inner')).toHaveCount(BULK_PAGE_COUNT); - await dismissAlertBanner(page); + test('Preview 12 pages in a folder', async ({ page }) => { + test.setTimeout(120000); - await page.locator('da-list.da-list-type-browse input#select-all').click(); - await page.locator('button.preview-button').filter({ visible: true }).click(); + await page.goto(folderURL); + await expect(page.locator('div.da-item-list-item-inner')).toHaveCount(BULK_PAGE_COUNT); + await dismissAlertBanner(page); - await expect(page.locator('da-dialog')).toContainText('Preview the'); - await page.locator('sl-button.accent').filter({ visible: true }).click(); + await page.locator('da-list.da-list-type-browse input#select-all').click(); + await page.locator('button.preview-button').filter({ visible: true }).click(); - await expect(page.locator('button.da-aem-results-btn')).toBeVisible({ timeout: 60000 }); - await expect(page.locator('button.da-aem-results-btn')).toContainText(`Previewed ${BULK_PAGE_COUNT} items`); - await expect(page.locator('da-dialog').filter({ hasText: 'Errors' })).toHaveCount(0); -}); + await expect(page.locator('da-dialog')).toContainText('Preview the'); + await page.locator('sl-button.accent').filter({ visible: true }).click(); -test('Publish 12 pages in a folder', async ({ page }, workerInfo) => { - test.setTimeout(360000); + await expect(page.locator('button.da-aem-results-btn')).toBeVisible({ timeout: 60000 }); + await expect(page.locator('button.da-aem-results-btn')).toContainText(`Previewed ${BULK_PAGE_COUNT} items`); + await expect(page.locator('da-dialog').filter({ hasText: 'Errors' })).toHaveCount(0); + }); - const { folderURL, folderPath } = await createFolder(page, workerInfo, 'bulkpub'); - await createPagesInFolder(page, workerInfo, folderPath, 'bulkpub', BULK_PAGE_COUNT); + test('Publish 12 pages in a folder', async ({ page }) => { + test.setTimeout(120000); - await page.goto(folderURL); - await expect(page.locator('div.da-item-list-item-inner')).toHaveCount(BULK_PAGE_COUNT); - await dismissAlertBanner(page); + await page.goto(folderURL); + await expect(page.locator('div.da-item-list-item-inner')).toHaveCount(BULK_PAGE_COUNT); + await dismissAlertBanner(page); - await page.locator('da-list.da-list-type-browse input#select-all').click(); - await page.locator('button.publish-button').filter({ visible: true }).click(); + await page.locator('da-list.da-list-type-browse input#select-all').click(); + await page.locator('button.publish-button').filter({ visible: true }).click(); - await expect(page.locator('da-dialog')).toContainText('Publish the'); - await page.locator('sl-button.accent').filter({ visible: true }).click(); + await expect(page.locator('da-dialog')).toContainText('Publish the'); + await page.locator('sl-button.accent').filter({ visible: true }).click(); - await expect(page.locator('button.da-aem-results-btn')).toBeVisible({ timeout: 60000 }); - await expect(page.locator('button.da-aem-results-btn')).toContainText(`Published ${BULK_PAGE_COUNT} items`); - await expect(page.locator('da-dialog').filter({ hasText: 'Errors' })).toHaveCount(0); + await expect(page.locator('button.da-aem-results-btn')).toBeVisible({ timeout: 60000 }); + await expect(page.locator('button.da-aem-results-btn')).toContainText(`Published ${BULK_PAGE_COUNT} items`); + await expect(page.locator('da-dialog').filter({ hasText: 'Errors' })).toHaveCount(0); + }); }); test('Preview and Publish buttons are hidden when only a folder is selected', async ({ page }) => { await page.goto(`${ENV}/${getQuery()}#/${TEST_ORG}/${TEST_SITE}`); + await page.waitForTimeout(5000); await dismissAlertBanner(page); await expect(page.getByText('tests'), 'Precondition: tests folder must exist').toBeVisible(); From 4e8b2c03312fc0fa8a1b9db6b61be21b2f9a1260 Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Wed, 15 Jul 2026 21:36:49 +0200 Subject: [PATCH 18/27] Raise beforeAll hook timeout for bulk page creation --- test/e2e/tests/preview_publish.spec.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/e2e/tests/preview_publish.spec.js b/test/e2e/tests/preview_publish.spec.js index 339deb12d..ba193d496 100644 --- a/test/e2e/tests/preview_publish.spec.js +++ b/test/e2e/tests/preview_publish.spec.js @@ -184,6 +184,9 @@ test.describe.serial('Bulk preview/publish 12 pages in a folder', () => { let folderPath; test.beforeAll(async ({ browser }, workerInfo) => { + // Creating 12 real pages takes well over the default 30s hook timeout. + test.setTimeout(180000); + const page = await browser.newPage(); ({ folderURL, folderPath } = await createFolder(page, workerInfo, 'bulk')); await createPagesInFolder(page, workerInfo, folderPath, 'bulk', BULK_PAGE_COUNT); From 668814c93e1fdf8f10ccbcbd7f85b6bc7c2c029b Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Thu, 16 Jul 2026 09:28:18 +0200 Subject: [PATCH 19/27] fix: test --- test/e2e/tests/preview_publish.spec.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/e2e/tests/preview_publish.spec.js b/test/e2e/tests/preview_publish.spec.js index ba193d496..3a49e0045 100644 --- a/test/e2e/tests/preview_publish.spec.js +++ b/test/e2e/tests/preview_publish.spec.js @@ -234,7 +234,8 @@ test('Preview and Publish buttons are hidden when only a folder is selected', as await page.goto(`${ENV}/${getQuery()}#/${TEST_ORG}/${TEST_SITE}`); await page.waitForTimeout(5000); await dismissAlertBanner(page); - await expect(page.getByText('tests'), 'Precondition: tests folder must exist').toBeVisible(); + + await expect(page.getByRole('link', { name: 'tests' }), 'Precondition: tests folder must exist').toBeVisible(); await selectItem(page, 'tests'); From 03f2b31f4a149bd2a039511c76c032874fd5a63b Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Thu, 16 Jul 2026 11:25:22 +0200 Subject: [PATCH 20/27] fix: stabilizing --- test/e2e/tests/edit.spec.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/test/e2e/tests/edit.spec.js b/test/e2e/tests/edit.spec.js index 4f30e2850..a4b224f3d 100644 --- a/test/e2e/tests/edit.spec.js +++ b/test/e2e/tests/edit.spec.js @@ -20,6 +20,7 @@ test('Update Document', async ({ browser, page }, workerInfo) => { const url = getTestPageURL('edit1', workerInfo); await page.goto(url); + await page.waitForTimeout(2000); await page.getByText('Create document', { exact: true }).click(); await expect(page.locator('div.ProseMirror')).toBeVisible(); await expect(page.locator('div.ProseMirror')).toHaveAttribute('contenteditable', 'true'); @@ -116,8 +117,10 @@ test('Change document by switching anchors', async ({ page }, workerInfo) => { await expect(page.locator('div.ProseMirror')).toBeVisible(); await expect(page.locator('div.ProseMirror')).toHaveAttribute('contenteditable', 'true'); // Allow Y.js WebSocket to stabilize before typing - await page.waitForTimeout(3000); + await page.waitForTimeout(2000); await fill(page, 'page B'); + await page.waitForTimeout(3000); + // Verify the fill took effect locally before waiting for persistence await expect(page.locator('div.ProseMirror')).toContainText('page B'); // Wait for Y.js to persist the content to the server @@ -154,12 +157,12 @@ test('Add code mark', async ({ page }, workerInfo) => { // Forward for (let i = 0; i < 10; i += 1) { await page.keyboard.press('ArrowLeft'); - await page.waitForTimeout(100); + await page.waitForTimeout(200); } await page.keyboard.press('`'); for (let i = 0; i < 4; i += 1) { await page.keyboard.press('ArrowRight'); - await page.waitForTimeout(100); + await page.waitForTimeout(200); } await page.keyboard.press('`'); // leave time for the code mark to be processed @@ -174,13 +177,13 @@ test('Add code mark', async ({ page }, workerInfo) => { await page.keyboard.press('End'); for (let i = 0; i < 6; i += 1) { await page.keyboard.press('ArrowLeft'); - await page.waitForTimeout(100); + await page.waitForTimeout(200); } await page.keyboard.press('`'); await page.locator('div.ProseMirror').locator('code'); for (let i = 0; i < 5; i += 1) { await page.keyboard.press('ArrowLeft'); - await page.waitForTimeout(100); + await page.waitForTimeout(200); } await page.keyboard.press('`'); codeElement = proseMirror.locator('code'); From 61e33f7f37e696f88e1da08b5508ec3bc72ab8db Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Thu, 16 Jul 2026 13:47:07 +0200 Subject: [PATCH 21/27] Move dismissAlertBanner to shared test/e2e/utils/utils.js --- test/e2e/playwright.config.js | 3 +-- test/e2e/tests/preview_publish.spec.js | 10 +--------- test/e2e/utils/utils.js | 24 ++++++++++++++++++++++++ 3 files changed, 26 insertions(+), 11 deletions(-) create mode 100644 test/e2e/utils/utils.js diff --git a/test/e2e/playwright.config.js b/test/e2e/playwright.config.js index 5addc1f82..0cd26f1fa 100644 --- a/test/e2e/playwright.config.js +++ b/test/e2e/playwright.config.js @@ -19,7 +19,7 @@ module.exports = defineConfig({ /* Retry on CI only */ retries: process.env.CI ? 3 : 0, /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, + workers: process.env.CI ? 1 : 1, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: 'html', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ @@ -95,4 +95,3 @@ module.exports = defineConfig({ // reuseExistingServer: !process.env.CI, // }, }); - diff --git a/test/e2e/tests/preview_publish.spec.js b/test/e2e/tests/preview_publish.spec.js index 3a49e0045..03e66406c 100644 --- a/test/e2e/tests/preview_publish.spec.js +++ b/test/e2e/tests/preview_publish.spec.js @@ -3,21 +3,13 @@ import ENV from '../utils/env.js'; import { getQuery, getTestPageURL, getTestFolderURL, createDocument, fill, TEST_ORG, TEST_SITE, } from '../utils/page.js'; +import { dismissAlertBanner } from '../utils/utils.js'; // Requires write access to TEST_SITE. pingtest must exist in the /tests directory. const TESTS_DIR = `${ENV}/${getQuery()}#/${TEST_ORG}/${TEST_SITE}/tests`; const BULK_PAGE_COUNT = 12; -// Some environments show a dismissible alert banner the first time the browse -// view loads. If present, dismiss it so it doesn't block later interactions. -async function dismissAlertBanner(page) { - const alert = page.getByRole('alert'); - if (await alert.isVisible().catch(() => false)) { - await alert.getByRole('button', { name: 'Dismiss' }).click(); - } -} - async function selectItem(page, name) { const checkbox = page .locator('div.da-item-list-item-inner').filter({ hasText: name, exact: true }) diff --git a/test/e2e/utils/utils.js b/test/e2e/utils/utils.js new file mode 100644 index 000000000..8ecf6786e --- /dev/null +++ b/test/e2e/utils/utils.js @@ -0,0 +1,24 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/** + * Some environments show a dismissible alert banner the first time the browse + * view loads. If present, dismiss it so it doesn't block later interactions. + * + * @param {import('@playwright/test').Page} page + */ +export async function dismissAlertBanner(page) { + const alert = page.getByRole('alert'); + if (await alert.isVisible().catch(() => false)) { + await alert.getByRole('button', { name: 'Dismiss' }).click(); + } +} From e743ff8d5e6027a951fa4a9fb4b2145dbbd12d61 Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Thu, 16 Jul 2026 13:48:31 +0200 Subject: [PATCH 22/27] Dismiss alert banner in delete.spec.js's old-pages cleanup test --- test/e2e/tests/delete.spec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/e2e/tests/delete.spec.js b/test/e2e/tests/delete.spec.js index 81ffe341e..713bcd790 100644 --- a/test/e2e/tests/delete.spec.js +++ b/test/e2e/tests/delete.spec.js @@ -14,6 +14,7 @@ import ENV from '../utils/env.js'; import { getQuery, getTestPageURL, getTestResourceAge, tabBackward, fill, TEST_ORG, TEST_SITE, } from '../utils/page.js'; +import { dismissAlertBanner } from '../utils/utils.js'; // Files are deleted after 2 hours by default const MIN_HOURS = process.env.PW_DELETE_HOURS ? Number(process.env.PW_DELETE_HOURS) : 2; @@ -32,6 +33,7 @@ test('Delete multiple old pages', async ({ page }, workerInfo) => { // Open the directory listing await page.goto(`${ENV}/${getQuery()}#/${TEST_ORG}/${TEST_SITE}/tests`); + await dismissAlertBanner(page); // This page will always be there as its used by a test await expect(page.getByText('pingtest'), 'Precondition').toBeVisible(); From 4b72e326638611c355bb7111bef494b92195ea60 Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Thu, 16 Jul 2026 13:50:18 +0200 Subject: [PATCH 23/27] Dismiss alert banner in the empty-open-editors delete test too --- test/e2e/tests/delete.spec.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/e2e/tests/delete.spec.js b/test/e2e/tests/delete.spec.js index 713bcd790..1f886511c 100644 --- a/test/e2e/tests/delete.spec.js +++ b/test/e2e/tests/delete.spec.js @@ -33,11 +33,12 @@ test('Delete multiple old pages', async ({ page }, workerInfo) => { // Open the directory listing await page.goto(`${ENV}/${getQuery()}#/${TEST_ORG}/${TEST_SITE}/tests`); - await dismissAlertBanner(page); // This page will always be there as its used by a test await expect(page.getByText('pingtest'), 'Precondition').toBeVisible(); + await dismissAlertBanner(page); + // List the resources and check fot the ones that are to be deleted. These are always pages // created by the getTestPageURL() function in page.js const items = page.locator('.da-item-list-item-name'); @@ -120,6 +121,7 @@ test('Empty out open editors on deleted documents', async ({ browser, page }, wo const list = await browser.newPage(); await list.goto(`${ENV}/${getQuery()}#/${TEST_ORG}/${TEST_SITE}/tests`); + await dismissAlertBanner(list); await list.waitForTimeout(3000); await list.reload(); From acfd4ae67fb2c58c7aed329504b926c0f69a035b Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Thu, 16 Jul 2026 14:18:06 +0200 Subject: [PATCH 24/27] Dismiss alert banner in edit.spec.js's Create Delete Document test --- test/e2e/tests/delete.spec.js | 2 +- test/e2e/tests/edit.spec.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/test/e2e/tests/delete.spec.js b/test/e2e/tests/delete.spec.js index 1f886511c..e80be835b 100644 --- a/test/e2e/tests/delete.spec.js +++ b/test/e2e/tests/delete.spec.js @@ -121,7 +121,6 @@ test('Empty out open editors on deleted documents', async ({ browser, page }, wo const list = await browser.newPage(); await list.goto(`${ENV}/${getQuery()}#/${TEST_ORG}/${TEST_SITE}/tests`); - await dismissAlertBanner(list); await list.waitForTimeout(3000); await list.reload(); @@ -132,6 +131,7 @@ test('Empty out open editors on deleted documents', async ({ browser, page }, wo await tabBackward(list); await list.keyboard.press(' '); await list.waitForTimeout(500); + await dismissAlertBanner(list); await list.locator('button.delete-button').filter({ visible: true }).click(); // Give the modal a chance to open diff --git a/test/e2e/tests/edit.spec.js b/test/e2e/tests/edit.spec.js index a4b224f3d..c9cae4500 100644 --- a/test/e2e/tests/edit.spec.js +++ b/test/e2e/tests/edit.spec.js @@ -14,6 +14,7 @@ import ENV from '../utils/env.js'; import { getQuery, getTestPageURL, tabBackward, fill, TEST_ORG, TEST_SITE, } from '../utils/page.js'; +import { dismissAlertBanner } from '../utils/utils.js'; test('Update Document', async ({ browser, page }, workerInfo) => { test.setTimeout(30000); @@ -46,6 +47,7 @@ test('Create Delete Document', async ({ browser, page }, workerInfo) => { const pageName = url.split('/').pop(); await page.goto(`${ENV}/${getQuery()}#/${TEST_ORG}/${TEST_SITE}/tests`); + await dismissAlertBanner(page); await expect(page.locator('button.da-actions-new-button')).toBeEnabled(); await page.locator('button.da-actions-new-button').click({ force: true }); await page.getByRole('menuitem', { name: 'Document' }).click(); @@ -60,6 +62,7 @@ test('Create Delete Document', async ({ browser, page }, workerInfo) => { const newPage = await browser.newPage(); await newPage.goto(`${ENV}/${getQuery()}#/${TEST_ORG}/${TEST_SITE}/tests`); + await dismissAlertBanner(newPage); await newPage.waitForTimeout(3000); await newPage.reload(); From 3104620f3d9040359fb8653898ade029cfa8fb6f Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Thu, 16 Jul 2026 14:40:20 +0200 Subject: [PATCH 25/27] Dismiss alert banner in copy_rename.spec.js --- test/e2e/tests/copy_rename.spec.js | 2 ++ test/e2e/tests/edit.spec.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/test/e2e/tests/copy_rename.spec.js b/test/e2e/tests/copy_rename.spec.js index d16fb55f5..0c3ad69ac 100644 --- a/test/e2e/tests/copy_rename.spec.js +++ b/test/e2e/tests/copy_rename.spec.js @@ -14,6 +14,7 @@ import ENV from '../utils/env.js'; import { getQuery, getTestFolderURL, getTestPageURL, fill, TEST_ORG, TEST_SITE, } from '../utils/page.js'; +import { dismissAlertBanner } from '../utils/utils.js'; test('Copy and Rename with Versioned document', async ({ page }, workerInfo) => { test.skip( @@ -61,6 +62,7 @@ const link = await page.getByRole('link', { name: orgPageName }); // Go back to the directory view await page.goto(`${ENV}/${getQuery()}#/${TEST_ORG}/${TEST_SITE}/tests`); + await dismissAlertBanner(page); const copyFolderURL = getTestFolderURL('copy', workerInfo); const copyFolderName = copyFolderURL.split('/').pop(); diff --git a/test/e2e/tests/edit.spec.js b/test/e2e/tests/edit.spec.js index c9cae4500..cfefbc769 100644 --- a/test/e2e/tests/edit.spec.js +++ b/test/e2e/tests/edit.spec.js @@ -47,7 +47,6 @@ test('Create Delete Document', async ({ browser, page }, workerInfo) => { const pageName = url.split('/').pop(); await page.goto(`${ENV}/${getQuery()}#/${TEST_ORG}/${TEST_SITE}/tests`); - await dismissAlertBanner(page); await expect(page.locator('button.da-actions-new-button')).toBeEnabled(); await page.locator('button.da-actions-new-button').click({ force: true }); await page.getByRole('menuitem', { name: 'Document' }).click(); @@ -74,6 +73,7 @@ test('Create Delete Document', async ({ browser, page }, workerInfo) => { await newPage.waitForTimeout(500); await page.close(); // Close the original page to avoid it writing the content + await dismissAlertBanner(newPage); // There are 2 delete buttons, one on the Browse panel and another on the Search one // select the visible one. await newPage.locator('button.delete-button').filter({ visible: true }).click(); From bff7d4e93cfbc52a33083f5e5a1b82f47c994051 Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Thu, 16 Jul 2026 14:55:47 +0200 Subject: [PATCH 26/27] Only wait for alert banner when it isn't already present --- test/e2e/tests/copy_rename.spec.js | 4 +++- test/e2e/tests/preview_publish.spec.js | 4 ---- test/e2e/utils/utils.js | 11 ++++++++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/test/e2e/tests/copy_rename.spec.js b/test/e2e/tests/copy_rename.spec.js index 0c3ad69ac..bd1d0a6b1 100644 --- a/test/e2e/tests/copy_rename.spec.js +++ b/test/e2e/tests/copy_rename.spec.js @@ -20,7 +20,7 @@ test('Copy and Rename with Versioned document', async ({ page }, workerInfo) => test.skip( TEST_SITE !== 'da-status', ` -On Helix 6 the copy and paste from one folder to another doesn't work yet, it fails on this line: +On Helix 6 the copy and paste from one folder to another doesn't work yet, it fails on this line: const link = await page.getByRole('link', { name: orgPageName }); `, ); @@ -93,6 +93,8 @@ const link = await page.getByRole('link', { name: orgPageName }); await page.goto(`${ENV}/${getQuery()}#/${TEST_ORG}/${TEST_SITE}/tests`); await page.reload(); // Clears any leftover selection, if any + await dismissAlertBanner(page); + const checkbox = page.locator('div.da-item-list-item-inner').filter({ hasText: orgPageName }) .locator('input[type="checkbox"][name="item-selected"]'); await checkbox.focus(); diff --git a/test/e2e/tests/preview_publish.spec.js b/test/e2e/tests/preview_publish.spec.js index 03e66406c..d52cfdba4 100644 --- a/test/e2e/tests/preview_publish.spec.js +++ b/test/e2e/tests/preview_publish.spec.js @@ -26,7 +26,6 @@ async function createFolder(page, workerInfo, testIdentifier) { const folderName = folderURL.split('/').pop(); await page.goto(TESTS_DIR); - await page.waitForTimeout(5000); await dismissAlertBanner(page); await expect(page.getByRole('button', { name: 'New' })).toBeEnabled(); await page.getByRole('button', { name: 'New' }).click({ force: true }); @@ -65,7 +64,6 @@ async function createPagesInFolder(page, workerInfo, folderPath, prefix, count) test('Preview and Publish buttons appear when a file is selected', async ({ page }) => { await page.goto(TESTS_DIR); - await page.waitForTimeout(5000); await dismissAlertBanner(page); await expect(page.getByText('pingtest'), 'Precondition: pingtest must exist').toBeVisible(); @@ -77,7 +75,6 @@ test('Preview and Publish buttons appear when a file is selected', async ({ page test('Clicking Preview opens a confirmation dialog', async ({ page }) => { await page.goto(TESTS_DIR); - await page.waitForTimeout(5000); await dismissAlertBanner(page); await expect(page.getByText('pingtest'), 'Precondition: pingtest must exist').toBeVisible(); @@ -224,7 +221,6 @@ test.describe.serial('Bulk preview/publish 12 pages in a folder', () => { test('Preview and Publish buttons are hidden when only a folder is selected', async ({ page }) => { await page.goto(`${ENV}/${getQuery()}#/${TEST_ORG}/${TEST_SITE}`); - await page.waitForTimeout(5000); await dismissAlertBanner(page); await expect(page.getByRole('link', { name: 'tests' }), 'Precondition: tests folder must exist').toBeVisible(); diff --git a/test/e2e/utils/utils.js b/test/e2e/utils/utils.js index 8ecf6786e..42a563deb 100644 --- a/test/e2e/utils/utils.js +++ b/test/e2e/utils/utils.js @@ -18,7 +18,16 @@ */ export async function dismissAlertBanner(page) { const alert = page.getByRole('alert'); - if (await alert.isVisible().catch(() => false)) { + + let visible = await alert.isVisible().catch(() => false); + if (!visible) { + // The banner can take a moment to appear; wait once and check again + // before giving up, but skip the wait entirely if it's already there. + await page.waitForTimeout(3000); + visible = await alert.isVisible().catch(() => false); + } + + if (visible) { await alert.getByRole('button', { name: 'Dismiss' }).click(); } } From 9203f43803fecd3d085f96c74702bf5479910540 Mon Sep 17 00:00:00 2001 From: Markus Haack Date: Thu, 16 Jul 2026 14:59:31 +0200 Subject: [PATCH 27/27] fix: restore test config --- test/e2e/playwright.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/playwright.config.js b/test/e2e/playwright.config.js index 0cd26f1fa..61ceae8c4 100644 --- a/test/e2e/playwright.config.js +++ b/test/e2e/playwright.config.js @@ -19,7 +19,7 @@ module.exports = defineConfig({ /* Retry on CI only */ retries: process.env.CI ? 3 : 0, /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : 1, + workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: 'html', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */