Skip to content
Merged
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
50 changes: 43 additions & 7 deletions playwright/fixtures/ui5-fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ export interface UI5WCFixtures {
ui5wc: UI5WCHelpers;
}

/**
* Escapes a value for use inside a double-quoted CSS attribute selector
* (`[attr="<value>"]`).
*/
function escapeAttributeValue(value: string): string {
// `CSS.escape` is a browser-only global; these selector strings are built in
// the Node test process, where `CSS` is undefined. Only `\` and `"` can break
// out of a double-quoted attribute value, so escaping them is sufficient.
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
}

export class UI5WCHelpers {
constructor(protected page: Page) {}

Expand Down Expand Up @@ -78,13 +89,38 @@ export class UI5WCHelpers {
await expect(dropdown.locator('[ui5-responsive-popover]:not([tokenizer-popover])')).toHaveAttribute('open');

const isSelect = await dropdown.evaluate((el) => el.hasAttribute('ui5-select'));
if (isSelect) {
const item = dropdown.getByText(text, { exact: true });
await item.click();
} else {
const item = dropdown.locator(`[text="${text}"]`);
await item.click();
}
// Select renders its options as plain text nodes; ComboBox/MultiComboBox
// items carry the value in a `text` attribute. escapeAttributeValue guards
// the attribute value so quotes/backslashes in the text cannot break the selector.
const item = isSelect
? dropdown.getByText(text, { exact: true }).first()
: dropdown.locator(`[text="${escapeAttributeValue(text)}"]`).first();
await this.clickAtBoundingBoxCenter(item);
}

/**
* Clicks a locator using raw viewport coordinates at its bounding-box center.
*
* Waits until the located element has a bounding box with a height > 0, then
* clicks its center. Unlike a normal `locator.click()`, this does not run
* Playwright's "receives events" actionability check — which UI5 Web Components
* can fail when their real target sits in the shadow DOM behind an event-
* transparent host.
*
* @param locator Locator to click.
*/
protected async clickAtBoundingBoxCenter(locator: Locator): Promise<void> {
let boundingBox: NonNullable<Awaited<ReturnType<Locator['boundingBox']>>> | null = null;
await expect
.poll(async () => {
const current = await locator.boundingBox();
boundingBox = current && current.height > 0 ? current : null;
return boundingBox?.height ?? 0;
})
.toBeGreaterThan(0);
const centerX = boundingBox!.x + boundingBox!.width / 2;
const centerY = boundingBox!.y + boundingBox!.height / 2;
await this.page.mouse.click(centerX, centerY);
}

/**
Expand Down
Loading