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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core-web/apps/dotcms-ui-e2e/src/pages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export { ListingContentTypesPage } from './listingContentTypes.page';
export { LoginPage } from './login.page';
export { NewEditContentFormPage } from './newEditContentForm.page';
export { PagesListPage } from './pagesList.page';
export { UveEditorPage } from './uveEditor.page';
63 changes: 63 additions & 0 deletions core-web/apps/dotcms-ui-e2e/src/pages/uveEditor.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Locator, Page } from '@playwright/test';

/** No persona — the default the editor itself applies when the address does not name one. */
const NO_PERSONA_ID = 'modes.persona.no.persona';

/**
* The Universal Visual Editor, as far as the Experiments entry points need it (#37478).
*
* Deliberately small: this covers the navigation bar, the toolbar's running-experiment badge and
* the Experiments panel, which is what the two experiments suites assert against. It is not an
* attempt to model the editor.
*/
export class UveEditorPage {
constructor(private page: Page) {}

/**
* Opens a page in the editor.
*
* The address is built rather than clicked through the pages list on purpose: `editEmaGuard`
* *substitutes* defaults for missing params instead of rejecting, so an incomplete URL opens a
* plausible-looking wrong page rather than failing. Naming all three keeps the destination
* unambiguous, which matters for a suite whose whole point is where the editor ends up.
*/
async open(pageUrl: string, languageId = 1) {
const params = new URLSearchParams({
url: pageUrl,
language_id: String(languageId),
'com.dotmarketing.persona.id': NO_PERSONA_ID
});

await this.page.goto(`/dotAdmin/#/edit-page/content?${params.toString()}`);
}

get navBar(): Locator {
return this.page.getByTestId('ema-nav-bar');
}

/** A navigation-bar item, by the accessible name the editor renders for it. */
navItem(name: string): Locator {
return this.navBar.getByRole('button', { name, exact: true });
}

/**
* The Experiments entry point.
*
* Named "A/B" in the navigation bar, not "Experiments" — `editema.editor.navbar.experiments`
* resolves to `A/B`. The accessible name is what the item actually renders, so that is what
* this locator has to ask for.
*/
get experimentsNavItem(): Locator {
return this.navItem('A/B');
}

/** The toolbar's "running until …" tag, present only while the page has a running experiment. */
get runningExperimentBadge(): Locator {
return this.page.getByTestId('runningExperimentTag');
}

/** The iframe holding the rendered page. Its `src` is how a reload is detected. */
get canvas(): Locator {
return this.page.locator('iframe[data-testid="iframe"]');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { faker } from '@faker-js/faker';
import { UveEditorPage } from '@pages';
import { expect, test } from '@playwright/test';
import { actionsPageWorkflow, createPage, Page } from '@requests/pages';

/**
* `FEATURE_FLAG_EXPERIMENTS_PORTLET` off — the shipped default (#37478, US3, FR-043, SC-009).
*
* **Nothing is flipped here, and that is the point.** The flag ships off and is read fail-closed,
* so a stock instance *is* the flag-off case. These are characterization tests: they pass on the
* build before this feature and must go on passing after it. They cannot go Red, which is why the
* plan's Red gate for this story covers only the flag-**on** assertions, which live in the unit
* suite (`dot-ema-shell.component.spec.ts`).
*
* The flag-on end of the switch is not exercised here: turning it on is a server-side
* configuration change that this harness has no way to make per-test.
*
* @see specs/37478-uve-experiments-panel/quickstart.md — V16
*/

let pageContentlet: Page;

test.beforeEach(async ({ request }) => {
const title = faker.lorem.words(3);
const url = title.split(' ').join('-');

pageContentlet = await createPage(request, {
title,
url,
friendlyName: title,
template: 'SYSTEM_TEMPLATE',
contentType: 'htmlpageasset',
cachettl: 0
});
});

test.afterEach(async ({ request }) => {
if (pageContentlet) {
await actionsPageWorkflow(request, pageContentlet.inode, [
'Unpublish',
'Archive',
'Destroy'
]);
}
});

test('Experiments leads to the legacy per-page screens @critical', async ({ page }) => {
const editor = new UveEditorPage(page);
await editor.open(`/${pageContentlet.url}`);

await expect(editor.experimentsNavItem).toBeVisible();
await editor.experimentsNavItem.click();

// The address the item carried before this feature existed, and the one it must still carry:
// `experiments/{pageId}`, relative to `edit-page`. The panel writes nothing to the address,
// so a URL assertion is what tells the two behaviours apart.
await expect(page).toHaveURL(new RegExp(`/edit-page/experiments/${pageContentlet.identifier}`));
});

test('no Experiments panel exists @critical', async ({ page }) => {
const editor = new UveEditorPage(page);
await editor.open(`/${pageContentlet.url}`);

await editor.experimentsNavItem.click();

// FR-044: none of the panel's behaviour is reachable or observable with the flag off.
await expect(page.getByTestId('experiments-panel')).toHaveCount(0);
});

/**
* FR-025d — the toolbar badge keeps its legacy deep link.
*
* Not implemented: the badge only renders while the page has a **running** experiment, and
* seeding one end to end means creating the experiment, adding a variant, setting a goal and
* starting it — and a started experiment needs Analytics configured on the instance, which the
* e2e environment does not guarantee. Covered instead by the unit suite at T074, which asserts
* the `routerLink` survives with the flag off.
*/
test.fixme('the toolbar badge keeps its legacy deep link', async () => {
// Needs a running-experiment seeding fixture; see the note above.
});

/**
* FR-045 — the full portlet stays reachable and unfiltered from the main navigation.
*
* Not implemented: the Experiments portlet is **opt-in**. It is declared in `portlet.xml` but no
* upgrade task adds it to any layout, so on a stock instance `/experiments` redirects to `/start`
* and the administration menu carries no Experiments entry at all. Asserting this needs a layout
* seeded for the test user, which this harness has no helper for.
*/
test.fixme('the full portlet is reachable and unfiltered from the main navigation', async () => {
// Needs a layout-seeding fixture; see the note above.
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { test } from '@playwright/test';

/**
* The Experiments panel beside the UVE canvas (#37478).
*
* The end-to-end half of the plan's Test Strategy — V1 in phase 1, V14 in phase 2 — and the only
* automated coverage of "the page is still there", which no unit test can assert.
*
* Both scenarios need `FEATURE_FLAG_EXPERIMENTS_PORTLET` **on**, which is a server-side
* configuration change this harness cannot make per-test. How the suite gets an instance with the
* flag on is the first thing T025 has to solve.
*
* @see specs/37478-uve-experiments-panel/quickstart.md — V1, V14
*/
test.describe('UVE Experiments panel', () => {
// T025: opening Experiments renders the panel with the page still on screen, the address
// unchanged and no iframe reload; closing leaves the page untouched.
test.fixme('opens beside the page without losing it', async () => {
// Implemented at T025.
});

// T067: the variant round trip returns to the same experiment's configuration.
test.fixme('returns from a variant to the configuration it left', async () => {
// Implemented at T067 (phase 2).
});
});
12 changes: 12 additions & 0 deletions core-web/libs/portlets/dot-experiments/data-access/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
export * from './lib/resolvers/dot-experiment-experiment.resolver';

export * from './lib/resolvers/dot-experiments-config-resolver';

/**
* The Experiments panel's view state, and the signal that tells the portlet's screens they are
* rendering inside the Universal Visual Editor (#37478).
*
* It lives in **this** lib rather than beside the screens it serves because the UVE shell
* provides it, and `@dotcms/portlets/dot-experiments/portlet` is lazy-loaded — `edit-ema`'s own
* routes reach it only through a dynamic `import()`, so a static import of it is forbidden. This
* lib is the boundary the two already share statically, which makes it the store's home. It also
* keeps the portlet lib fully lazy, which is what FR-037 is about.
*/
export * from './lib/dot-experiments-panel.store';
Loading
Loading