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
97 changes: 97 additions & 0 deletions nala/blocks/ppn-dropdown/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Primary Product Name Dropdown Tests

This folder contains the Playwright coverage for the Primary Product Name dropdown in the DA metadata builder.

## Key Model

This suite behaves more like a DA authoring tool flow than a published-page Nala test:

- It opens `da.live/edit`
- It depends on an authenticated DA session
- It validates metadata builder behavior inside the author UI

The edit URL is built from:

- `https://da.live/edit?ref=md-form-block#/adobecom/da-bacom`

with draft paths from [ppn-dropdown.spec.js](/Users/xiasun/Documents/Workspace/da-bacom/nala/blocks/ppn-dropdown/ppn-dropdown.spec.js).

## First-Time Setup

Log into DA first so Playwright can reuse the saved session:

```bash
npm run nala:login
```

This saves your session to:

- [auth.json](/Users/xiasun/Documents/Workspace/da-bacom/nala/utils/auth.json)

If the saved session expires, run `npm run nala:login` again.

## What the Suite Covers

- Functional dropdown behavior
- Regression checks for previously reported issues
- DA UI integration
- Edge cases around multiple property-value rows and removal

The suite is tagged with:

- `@ppn-dropdown`
- `@functional`
- `@bug-regression`
- `@non-functional`
- `@edge-case`

## Common Commands

Run the full PPN dropdown suite through Nala:

```bash
npm run nala local @ppn-dropdown
```

Run only the smoke subset:

```bash
npm run nala local "@ppn-dropdown @smoke"
```

Run one specific test case by tag:

```bash
npm run nala local @tc1
```

Run the suite directly with Playwright on Chromium:

```bash
npx playwright test nala/blocks/ppn-dropdown/ppn-dropdown.test.js --project=da-bacom-live-chromium
```

Run in headed mode for debugging:

```bash
npm run nala local @ppn-dropdown mode=headed
```

Run one test in Playwright UI mode:

```bash
npx playwright test nala/blocks/ppn-dropdown/ppn-dropdown.test.js \
--project=da-bacom-live-chromium --grep @tc1 --ui
```

## Recommended Workflow

1. Run `npm run nala:login`
2. Confirm `nala/utils/auth.json` was created or refreshed
3. Run the desired PPN command
4. If DA redirects you back to login during a run, refresh auth and rerun

## Notes

- These tests are intended for intentional manual execution, not casual PR coverage
- Because they depend on DA authentication, they are best run in headed mode when debugging author-side behavior
196 changes: 196 additions & 0 deletions nala/blocks/ppn-dropdown/ppn-dropdown.page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import { expect } from '@playwright/test';

const DA_EDIT_BASE = 'https://da.live/edit?ref=md-form-block#/adobecom/da-bacom';

export default class PPNDropdown {
constructor(page) {
this.page = page;

this.openLibraryButton = page.locator('div.open-library, [title="Open library"]');
this.libraryPanel = page.locator('[class*="library-panel"], [class*="library"]:not(.open-library)').first();
this.libraryMetadataItem = page.locator(
'text=Metadata >> visible=true, '
+ '[class*="metadata-builder"], '
+ 'button:has-text("Metadata"), '
+ 'div:has-text("Metadata"):not(:has(div))',
).first();

this.metadataBuilderDialog = page.locator(
'[role="dialog"]:has-text("Metadata Builder"), [class*="da-library-metadata"]',
).first();
this.metadataDialogCloseButton = page.locator('button:has-text("Close")');
this.metadataDialogTitle = page.locator(
'h1:has-text("Metadata Builder"), h2:has-text("Metadata Builder")',
).first();
this.metadataTable = page.locator('table:has-text("metadata")');
this.selectPropertyText = page.locator('button:has-text("Close")');
this.metadataIframe = page.locator(
'.da-library-type-plugin iframe, iframe[src*="md-formatter"]',
).first();

this.propertyDropdownSelector = 'select.select-property';
this.valueDropdownSelector = 'select.select-value';

this.allDropdowns = page.locator('select');
this.allNativeSelects = page.locator('select');

this.addButton = page.locator(
'button:has-text("+"), '
+ 'button[title*="add" i], '
+ '[aria-label*="add" i], '
+ 'button:has(svg[class*="plus"])',
).first();

this.removeButton = page.locator(
'button:has-text("-"), '
+ 'button[title*="remove" i], '
+ 'button[title*="delete" i], '
+ '[aria-label*="remove" i]',
).first();

this.saveButton = page.locator(
'button:has-text("Save"), '
+ 'button[title*="save" i], '
+ '[aria-label*="save" i]',
).first();

this.propertyRows = page.locator('tr:has(td), [class*="row"]:has(select)');
this.metadataFields = page.locator('td, [class*="field"]');

this.errorMessage = page.locator(
'[class*="error"], [class*="validation"], [role="alert"]',
);
this.requiredFieldError = page.locator('[class*="required"]');
this.metadataBlock = page.locator('table:has-text("metadata")');

this.placeholderProperty = 'Select property';
this.placeholderValue = 'Select value';
}

static getEditUrl(pagePath) {
return `${DA_EDIT_BASE}${pagePath}`;
}

async openMetadataBuilder() {
await this.openLibraryButton.waitFor({ state: 'visible', timeout: 30000 });
await this.openLibraryButton.click();
await this.page.waitForTimeout(1000);

const libraryPanel = this.page.locator('[class*="da-library"]').first();
for (let i = 0; i < 10; i += 1) {
await libraryPanel.press('End');
await this.page.waitForTimeout(300);
}
await this.page.waitForTimeout(500);

const metadataBuilderItem = this.page.getByText('Metadata Builder', { exact: true });
const dialogCloseButton = this.page.locator('button:has-text("Close")');
const dialogTitle = this.page.locator(
'h1:has-text("Metadata Builder"), h2:has-text("Metadata Builder")',
);

for (let attempt = 1; attempt <= 5; attempt += 1) {
await metadataBuilderItem.click({ force: true });
await this.page.waitForTimeout(1500);

const dialogOpened = await dialogCloseButton.isVisible().catch(() => false)
|| await dialogTitle.isVisible().catch(() => false);
if (dialogOpened) break;
}
}

getMetadataFrameLocator() {
return this.page.frameLocator(
'.da-library-type-plugin iframe, iframe[src*="md-formatter"]',
);
}

getPropertyDropdown() {
return this.getMetadataFrameLocator().locator(this.propertyDropdownSelector);
}

getValueDropdown() {
return this.getMetadataFrameLocator().locator(this.valueDropdownSelector);
}

async selectProperty(propertyName) {
await this.getPropertyDropdown().selectOption({ label: propertyName });
}

async selectValue(valueName) {
await this.getValueDropdown().selectOption({ label: valueName });
}

async selectValueByIndex(index) {
await this.valueDropdown.selectOption({ index });
}

async clickAddButton() {
await this.addButton.click();
}

async clickRemoveButton(rowIndex = 0) {
const btns = this.page.locator('.property-row button.remove, .property-row button:has-text("-")');
await btns.nth(rowIndex).click();
}

async getPropertyOptions() {
return this.getPropertyDropdown().locator('option').allTextContents();
}

async getValueOptions() {
return this.getValueDropdown().locator('option').allTextContents();
}

async getSelectedProperty() {
return this.propertyDropdown.inputValue();
}

async getSelectedValue() {
return this.valueDropdown.inputValue();
}

async hasEmptyValueOptions() {
const options = await this.getValueOptions();
const empty = options.filter((opt, i) => {
if (i === 0 && opt === this.placeholderValue) return false;
return opt.trim() === '';
});
return empty.length > 0;
}

async countValidValueOptions() {
const options = await this.getValueOptions();
return options.filter((o) => o.trim() !== '' && o !== this.placeholderValue).length;
}

async attemptFreeTextInput(text) {
await this.valueDropdown.focus();
await this.page.keyboard.type(text);
}

async isValueDropdownSelect() {
const tag = await this.valueDropdown.evaluate((el) => el.tagName.toLowerCase());
return tag === 'select';
}

async addPropertyValuePair(property, value) {
await this.selectProperty(property);
await this.selectValue(value);
await this.clickAddButton();
}

async verifyMetadataFieldValue(property, expectedValue) {
const field = this.page.locator(`[data-property="${property}"]`);
await expect(field).toContainText(expectedValue);
}

async getPropertyRowCount() {
return this.propertyRows.count();
}

async saveMetadata() {
await this.saveButton.click();
await this.page.waitForTimeout(1000);
}
}
Loading
Loading