Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
584d53c
MWPW-181321 draft
npeltier Dec 9, 2025
98fee59
draft
npeltier Dec 12, 2025
717df32
Merge branch 'main' of github.com:adobecom/mas into MWPW-181321
npeltier Dec 12, 2025
86375c3
add unit test
npeltier Dec 15, 2025
1934efa
Merge branch 'main' of github.com:adobecom/mas into MWPW-181321
npeltier Dec 15, 2025
3f82809
final changes
npeltier Dec 16, 2025
29aae53
some changes for disabled styling
npeltier Dec 16, 2025
3082c41
Merge branch 'main' of github.com:adobecom/mas into MWPW-181321
npeltier Dec 16, 2025
1372ce7
fix ph style
npeltier Dec 16, 2025
bc6fcd7
prettier fixes
npeltier Dec 16, 2025
a6e32d3
review fixes
npeltier Dec 16, 2025
4fc41ae
remove some non express default locales
npeltier Dec 17, 2025
2527c40
UX feedbacks
npeltier Dec 17, 2025
cb55e88
fix ut
npeltier Dec 17, 2025
671a560
Merge branch 'main' of github.com:adobecom/mas into MWPW-181321
npeltier Dec 17, 2025
72a8d0f
couple more fixes
npeltier Dec 18, 2025
d87f014
fixed typo
npeltier Dec 18, 2025
f8b6edf
some more fixe
npeltier Dec 18, 2025
feb0873
another fix
npeltier Dec 18, 2025
e482caf
Nala: adapt to the new locale picker
afmicka Dec 18, 2025
001213f
Merge branch 'main' into MWPW-181321
npeltier Dec 18, 2025
524c0fd
better sorting of locales
npeltier Dec 18, 2025
8229897
fixing routing region
npeltier Dec 19, 2025
2e38446
QA review feedbacks
npeltier Dec 19, 2025
d2d259b
fixing rte wcs contextes
npeltier Dec 19, 2025
47d6ac0
Merge branch 'main' into MWPW-181321
npeltier Dec 19, 2025
b62b300
some review fixes
npeltier Dec 23, 2025
6ec0d33
Merge branch 'main' of github.com:adobecom/mas into MWPW-181321
npeltier Dec 23, 2025
6409d1a
Merge branch 'MWPW-181321' of github.com:adobecom/mas into MWPW-181321
npeltier Dec 23, 2025
70064bd
Add Nala for create fragment
afmicka Dec 24, 2025
7ad035d
factorize preview config, use better timeout for dictionary
npeltier Jan 5, 2026
ce7b8c5
fix unit tests
npeltier Jan 5, 2026
bd131e0
fix file name
npeltier Jan 5, 2026
af80a16
fixing (again) file reference
npeltier Jan 5, 2026
b35348f
fixing bad fragment cache
npeltier Jan 6, 2026
5d2c80c
Merge branch 'main' of github.com:adobecom/mas into MWPW-181321
npeltier Jan 6, 2026
8c89170
use main renderCommerce
npeltier Jan 6, 2026
6cbe2ed
Nala: fix instability
afmicka Jan 6, 2026
015a3b9
Nala: callout try to fix
afmicka Jan 6, 2026
3c6e39b
fixing locale cache and fr_CA
npeltier Jan 6, 2026
abb32bb
Nala: change clearing the callout field
afmicka Jan 6, 2026
601f5eb
more systematic region reset
npeltier Jan 6, 2026
c4895a9
make CA region for all surfaces
npeltier Jan 6, 2026
7fa7029
Merge branch 'MWPW-181321' of github.com:adobecom/mas into MWPW-181321
npeltier Jan 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -483,14 +483,14 @@ test.describe('M@S Studio ACOM Plans Individuals card test suite', () => {

await test.step('step-3: Remove callout field', async () => {
await expect(await editor.calloutRTE).toBeVisible();
await expect(await editor.calloutRTE).toContainText(data.calloutText.original);
await expect(editor.calloutRTE).toBeVisible();
await editor.calloutRTE.scrollIntoViewIfNeeded();
await page.waitForTimeout(500);
await expect(await editor.calloutRTE).toContainText(data.calloutText.original);
await editor.calloutRTE.click();
await page.waitForTimeout(500);
await editor.calloutRTE.fill('');
await editor.calloutRTE.clear();
await page.waitForTimeout(1000);
await expect(editor.calloutRTE).toHaveText('');
await expect(await editor.calloutRTE).toHaveText('');
});

await test.step('step-4: Validate callout field is removed', async () => {
Expand Down
4 changes: 2 additions & 2 deletions nala/studio/placeholders/placeholders.page.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ export default class PlaceholdersPage {
this.page = page;

// Header elements
// this.localePicker = page.locator('mas-nav sp-button:has-text("en_US")');
this.localePicker = page.locator('mas-nav-locale-picker sp-action-menu');
this.localePicker = page.locator('mas-top-nav mas-locale-picker sp-action-menu');
this.regionPicker = page.locator('mas-locale-picker[mode="region"] sp-action-menu');

this.createButton = page.locator('sp-button.create-button');
this.searchInput = page.getByRole('searchbox', { name: 'Search' });
Expand Down
1 change: 1 addition & 0 deletions nala/studio/placeholders/specs/placeholders.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default {
name: '@studio-placeholders-locale-picker',
path: '/studio.html',
data: {
localePicker: 'French (FR)',
locale: 'fr_FR',
},
browserParams: '#page=placeholders&path=nala&locale=en_US',
Expand Down
2 changes: 1 addition & 1 deletion nala/studio/placeholders/tests/placeholders.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ test.describe('M@S Studio Placeholders Test Suite', () => {
});

await test.step('step-3: Change the placeholder locale', async () => {
await placeholders.selectLocale(data.locale);
await placeholders.selectLocale(data.localePicker);
});

await test.step('step-4: Validate different locale placeholders are loaded', async () => {
Expand Down
137 changes: 136 additions & 1 deletion nala/studio/studio.page.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
this.renderView = page.locator('#render');
this.tableView = page.locator('sp-table');
this.tableViewHeaders = page.locator('sp-table-head');
this.tableViewRows = this.tableView.locator('sp-table-row');
this.tableViewFragmentTable = (fragmentId) => this.tableView.locator(`mas-fragment-table[data-id="${fragmentId}"]`);
this.tableViewRowByFragmentId = (fragmentId) => this.tableView.locator(`sp-table-row[value="${fragmentId}"]`);
this.tableViewPathCell = (row) => row.locator('sp-table-cell.name');
this.tableViewTitleCell = (row) => row.locator('sp-table-cell.title');
this.quickActions = page.locator('.quick-actions');
this.editorPanel = page.locator('mas-fragment-editor > #fragment-editor #editor-content');
this.confirmationDialog = page.locator('sp-dialog[variant="confirmation"]');
Expand All @@ -46,7 +51,7 @@
// Topnav panel
this.topnav = page.locator('mas-top-nav');
this.surfacePicker = page.locator('mas-nav-folder-picker sp-action-menu');
this.localePicker = page.locator('mas-nav-locale-picker sp-action-menu');
this.localePicker = page.locator('mas-top-nav mas-locale-picker sp-action-menu');
this.fragmentsTable = page.locator('.breadcrumbs-container sp-breadcrumb-item:has-text("Fragments")');
// Sidenav toolbar
this.sideNav = page.locator('mas-side-nav');
Expand All @@ -60,6 +65,13 @@
this.collectionsButton = this.sideNav.locator('mas-side-nav-item[label="Collections"]');
this.placeholdersButton = this.sideNav.locator('mas-side-nav-item[label="Placeholders"]');
this.supportButton = this.sideNav.locator('mas-side-nav-item[label="Support"]');
// Create dialog elements
this.createButton = page.locator('sp-button:has-text("Create")').first();
this.createDialog = page.locator('mas-create-dialog');
this.createDialogTitleInput = this.createDialog.locator('sp-textfield#fragment-title input');
this.createDialogOSIButton = this.createDialog.locator('osi-field#osi #offerSelectorToolButtonOSI');
this.createDialogCreateButton = this.createDialog.locator('sp-button:has-text("Create")');
this.createDialogMerchCardOption = page.getByRole('menuitem', { name: 'Merch Card', exact: true }).first();
}

async getCard(id, cloned, secondID) {
Expand Down Expand Up @@ -313,7 +325,7 @@
});
throw new Error(`All attempts failed:\n\n${attemptErrors.join('\n\n')}`);
}
throw new Error(e.message);

Check failure on line 328 in nala/studio/studio.page.js

View workflow job for this annotation

GitHub Actions / Running Nala E2E UI Tests (20.x)

[mas-live-chromium] › nala/studio/ccd/suggested/tests/suggested_save.test.js:310:5 › M@S Studio CCD Suggested card test suite › @studio-suggested-save-edited-cta-link

1) [mas-live-chromium] › nala/studio/ccd/suggested/tests/suggested_save.test.js:310:5 › M@S Studio CCD Suggested card test suite › @studio-suggested-save-edited-cta-link,@Mas-Studio @ccd @ccd-save @ccd-suggested @ccd-suggested-save › step-8: Save link changes and save card Error: All attempts failed: [Attempt 1/2] [NO_RESPONSE] Save operation failed - no success toast shown [Attempt 2/2] [CLICK_FAILED] Save button click did not trigger progress circle at studio/studio.page.js:328 326 | throw new Error(`All attempts failed:\n\n${attemptErrors.join('\n\n')}`); 327 | } > 328 | throw new Error(e.message); | ^ 329 | } finally { 330 | this.page.removeListener('console', consoleListener); 331 | } at StudioPage.saveCard (/home/runner/work/mas/mas/nala/studio/studio.page.js:328:19) at /home/runner/work/mas/mas/nala/studio/ccd/suggested/tests/suggested_save.test.js:366:13 at /home/runner/work/mas/mas/nala/studio/ccd/suggested/tests/suggested_save.test.js:363:9
} finally {
this.page.removeListener('console', consoleListener);
}
Expand Down Expand Up @@ -447,4 +459,127 @@
await this.discardDialog.click();
await expect(await editor.panel).not.toBeVisible();
}

/**
* Switch to table view
*/
async switchToTableView() {
await expect(this.previewMenu).toBeVisible({ timeout: 10000 });
await this.previewMenu.scrollIntoViewIfNeeded();
await this.previewMenu.click();
await this.page.waitForTimeout(500);
await expect(this.tableViewOption).toBeVisible({ timeout: 10000 });
await this.tableViewOption.click();
await this.page.waitForTimeout(2000);
await expect(this.tableView).toBeVisible({ timeout: 15000 });
}

/**
* Create a new fragment
* Fragment title and card title are automatically generated with run ID (similar to cloneCard) to be cleaned up after execution of the test
* @param {Object} options - Configuration options
* @param {string} options.osi - OSI to search and select
* @param {string} options.variant - Variant type to select in the editor (e.g., 'ccd-suggested', 'ccd-slice', 'plans', 'ah-try-buy-widget')
* @param {EditorPage} editor - Editor page object instance
* @returns {Promise<string>} The fragment ID of the created card
*/
async createFragment({ osi, variant }, editor) {
if (!osi) {
throw new Error('osi is required parameter');
}
if (!variant) {
throw new Error('variant is required parameter');
}

await expect(this.createButton).toBeVisible({ timeout: 10000 });
await this.createButton.click();

await expect(this.createDialogMerchCardOption).toBeVisible({ timeout: 10000 });
await this.createDialogMerchCardOption.click();

await expect(this.createDialog).toBeVisible({ timeout: 15000 });
await this.page.waitForTimeout(500);

await expect(this.createDialogTitleInput).toBeVisible({ timeout: 10000 });
const runId = getCurrentRunId();
const titleWithRunId = `MAS Nala Automation Fragment [${runId}]`;
await this.createDialogTitleInput.fill(titleWithRunId);

await expect(this.createDialogOSIButton).toBeVisible({ timeout: 10000 });
await this.createDialogOSIButton.click();

await expect(this.ost.searchField).toBeVisible({ timeout: 15000 });
await this.ost.searchField.fill(osi);
await this.ost.nextButton.click();
await expect(this.ost.priceUse).toBeVisible({ timeout: 10000 });
await this.ost.priceUse.click();
await this.page.waitForTimeout(1000);

await expect(this.createDialogCreateButton).toBeVisible({ timeout: 10000 });
await this.createDialogCreateButton.click();

// Wait for positive toast to appear and then disappear after fragment creation
await this.toastPositive.waitFor({ state: 'visible', timeout: 15000 }).catch(() => {
// If toast doesn't appear, continue
});
await this.toastPositive.waitFor({ state: 'hidden', timeout: 15000 }).catch(() => {
// If toast disappears quickly or doesn't appear, continue
});

await this.editorPanel.waitFor({
state: 'visible',
timeout: 30000,
});
await this.page.waitForTimeout(1000);

await expect(editor.variant).toBeVisible({ timeout: 10000 });
await editor.variant.locator('sp-picker').first().click();
await this.page.locator(`sp-menu-item[value="${variant}"]`).first().click();
await this.page.waitForTimeout(1000);

// Wait for sidenav elements to be enabled before interacting with card fields
// This ensures the variant has been fully processed
// Check that the disabled attribute is not present (mas-side-nav-item uses disabled attribute)
await expect(this.deleteCardButton).not.toHaveAttribute('disabled', { timeout: 30000 });
await expect(this.saveCardButton).not.toHaveAttribute('disabled', { timeout: 30000 });

// Enter card title (auto-generated with run ID, same as fragment title)
await expect(editor.title).toBeVisible({ timeout: 10000 });
await editor.title.fill(titleWithRunId);

await expect(editor.prices).toBeVisible({ timeout: 10000 });
const pricesOSTButton = editor.prices.locator(editor.OSTButton);
await expect(pricesOSTButton).toBeVisible({ timeout: 10000 });
await pricesOSTButton.click();

await expect(this.ost.priceUse).toBeVisible({ timeout: 15000 });
await this.ost.priceUse.click();
await this.page.waitForTimeout(1000);

await this.saveCard();

// Wait for positive toast to disappear before navigating away
await this.toastPositive.waitFor({ state: 'hidden', timeout: 15000 }).catch(() => {
// If toast doesn't appear or disappears quickly, continue
});

const currentUrl = this.page.url();
const fragmentIdMatch = currentUrl.match(/fragment=([^&]+)/);
let fragmentId = fragmentIdMatch ? fragmentIdMatch[1] : null;

// If not in URL, get from card preview in editor
if (!fragmentId) {
fragmentId = await this.page
.locator('aem-fragment[fragment]')
.first()
.getAttribute('fragment')
.catch(() => null);
}

if (!fragmentId) {
throw new Error('Failed to retrieve fragment ID from URL or card preview');
}

return fragmentId;
}
}
15 changes: 15 additions & 0 deletions nala/studio/studio.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ export default {
{
tcid: '12',
name: '@studio-locale-change',
data: {
localePicker: 'French (FR)',
locale: 'fr_FR',
},
path: '/studio.html',
tags: '@mas-studio',
},
Expand All @@ -122,5 +126,16 @@ export default {
browserParams: '#page=content&path=nala',
tags: '@mas-studio',
},
{
tcid: '14',
name: '@studio-create-fragment',
path: '/studio.html',
browserParams: '#page=content&path=nala',
data: {
osi: 'yIcVsmjmQCHKQ-TvUJxH3-kop4ifvwoMBBzVg3qfaTg',
variant: 'plans',
},
tags: '@mas-studio',
},
],
};
84 changes: 81 additions & 3 deletions nala/studio/studio.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { test, expect, studio, editor, miloLibs, setTestPage } from '../libs/mas-test.js';
import { getCurrentRunId } from '../utils/fragment-tracker.js';
import StudioSpec from './studio.spec.js';

const { features } = StudioSpec;
Expand Down Expand Up @@ -365,6 +366,7 @@ test.describe('M@S Studio feature test suite', () => {

// @studio-locale-change - Validate locale change in mas studio
test(`${features[12].name},${features[12].tags}`, async ({ page, baseURL }) => {
const { data } = features[12];
const testPage = `${baseURL}${features[12].path}${miloLibs}`;
setTestPage(testPage);

Expand All @@ -377,13 +379,14 @@ test.describe('M@S Studio feature test suite', () => {
await expect(await studio.localePicker).toBeVisible();
await expect(await studio.localePicker).toHaveAttribute('value', 'en_US');
await studio.localePicker.click();
await page.getByRole('menuitem', { name: 'fr_FR' }).click();
await page.waitForTimeout(500);
await page.getByRole('menuitem', { name: `${data.localePicker}` }).click();
await page.waitForTimeout(2000);
});

await test.step('step-3: Validate locale change', async () => {
await expect(await studio.localePicker).toHaveAttribute('value', 'fr_FR');
await expect(page).toHaveURL(`${testPage}#locale=fr_FR&page=welcome&path=acom`);
await expect(await studio.localePicker).toHaveAttribute('value', data.locale);
await expect(page).toHaveURL(`${testPage}#locale=${data.locale}&page=welcome&path=acom`);
await expect(await studio.sideNav).toBeVisible();
await expect(await studio.homeButton).toBeVisible();
await expect(await studio.fragmentsButton).toBeVisible();
Expand Down Expand Up @@ -426,4 +429,79 @@ test.describe('M@S Studio feature test suite', () => {
await expect(await editor.panel).toBeVisible();
});
});

// @studio-create-fragment - Validate creating a new fragment
test(`${features[14].name},${features[14].tags}`, async ({ page, baseURL }) => {
const { data } = features[14];
const testPage = `${baseURL}${features[14].path}${miloLibs}${features[14].browserParams}`;
setTestPage(testPage);
let fragmentId;
const runId = getCurrentRunId();
const expectedTitle = `MAS Nala Automation Fragment [${runId}]`;

await test.step('step-1: Go to MAS Studio test page', async () => {
await page.goto(testPage);
await page.waitForLoadState('domcontentloaded');
await expect(await studio.renderView).toBeVisible();
});

await test.step('step-2: Create fragment', async () => {
fragmentId = await studio.createFragment(
{
osi: data.osi,
variant: data.variant,
},
editor,
);
expect(fragmentId).toBeTruthy();
await page.waitForTimeout(3000);
});

await test.step('step-3: Verify fragment is visible in content page', async () => {
await expect(studio.fragmentsTable).toBeVisible();
await studio.fragmentsTable.scrollIntoViewIfNeeded();
await studio.fragmentsTable.click();
await page.waitForTimeout(2000);
await expect(studio.renderView).toBeVisible();
});

await test.step('step-4: Verify fragment has correct variant', async () => {
const createdCard = await studio.getCard(fragmentId);
await expect(createdCard).toBeVisible();
await expect(createdCard).toHaveAttribute('variant', data.variant);
});

await test.step('step-5: Switch to table view and verify fragment details', async () => {
await studio.switchToTableView();
await page.waitForTimeout(2000);

// Find the fragment row by data-id attribute on mas-fragment-table
const fragmentRow = studio.tableViewRowByFragmentId(fragmentId);
await expect(fragmentRow).toBeVisible();

// Get the path cell (class "name")
const pathCell = studio.tableViewPathCell(fragmentRow);
const fragmentPath = await pathCell.textContent();
expect(fragmentPath).toBeTruthy();
expect(fragmentPath).not.toContain('undefined');
expect(fragmentPath.trim().length).toBeGreaterThan(0);

// Get the title cell (class "title")
const titleCell = studio.tableViewTitleCell(fragmentRow);
const fragmentTitle = await titleCell.textContent();
expect(fragmentTitle).toBeTruthy();
expect(fragmentTitle.trim().length).toBeGreaterThan(0);
expect(fragmentTitle.trim()).toBe(expectedTitle);
});

await test.step('step-6: Open editor from table view and verify fragment details', async () => {
const fragmentRow = studio.tableViewRowByFragmentId(fragmentId);
await fragmentRow.dblclick();
await expect(await editor.panel).toBeVisible({ timeout: 30000 });
await expect(await editor.variant).toBeVisible();
await expect(await editor.variant).toHaveAttribute('default-value', data.variant);
await expect(await editor.OSI).toBeVisible();
await expect(await editor.OSI).toContainText(data.osi);
});
});
});
Loading
Loading