@vuu-ui/vuu-playwright-helpers 4.0.0-beta.6 → 4.0.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -2
- package/package.json +2 -2
- package/src/PwComponent.js +29 -0
- package/src/PwContextMenu.js +39 -0
- package/src/PwMenuItem.js +58 -0
- package/src/PwTable.js +30 -34
- package/src/PwTableHeaderCell.js +7 -7
- package/src/index.js +3 -0
- package/src/selector-utils.js +7 -0
- package/types/PwComponent.d.ts +43 -0
- package/types/PwContextMenu.d.ts +65 -0
- package/types/PwMenuItem.d.ts +48 -0
- package/types/PwTable.d.ts +19 -11
- package/types/PwTableHeaderCell.d.ts +8 -4
- package/types/index.d.ts +3 -0
- package/types/selector-utils.d.ts +3 -0
package/README.md
CHANGED
|
@@ -3,8 +3,16 @@
|
|
|
3
3
|
Playwright helpers for Vuu components.
|
|
4
4
|
|
|
5
5
|
All helpers extend `PlaywrightPageObject` and are instantiated from the Playwright `page`.
|
|
6
|
-
|
|
7
|
-
overridden at runtime
|
|
6
|
+
Component helpers extend `PwComponent`, which locates the component root element using
|
|
7
|
+
a default CSS selector. This can be overridden at runtime by passing a CSS selector or a
|
|
8
|
+
`Locator`. `PwComponent` provides common helpers:
|
|
9
|
+
|
|
10
|
+
- `locator` - the component root `Locator`, for web-first assertions not covered by a helper
|
|
11
|
+
- `isReady()` - waits until the component is visible, resolves with the component
|
|
12
|
+
- `isVisible()` - is the component currently visible (no waiting)
|
|
13
|
+
- `assertVisible()`, `assertNotAttached()`
|
|
14
|
+
|
|
15
|
+
## PwTable
|
|
8
16
|
|
|
9
17
|
```ts
|
|
10
18
|
// uses the default selector (.vuuTable)
|
|
@@ -23,3 +31,36 @@ await table.editCell("currency", 2, "EUR", "cancel");
|
|
|
23
31
|
Prefer `cell(colName, ariaRowIndex)` for new usage. `ariaRowIndex` is the 1-based
|
|
24
32
|
ARIA row index, including header rows (with one header, the first data row is 2).
|
|
25
33
|
The existing `locateCell` overloads remain available for gradual migration.
|
|
34
|
+
|
|
35
|
+
## PwContextMenu, PwMenuItem
|
|
36
|
+
|
|
37
|
+
`PwTable.showContextMenu(colName, ariaRowIndex)` right clicks a cell and resolves with a
|
|
38
|
+
`PwContextMenu` once the menu is visible.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
const menu = await table.showContextMenu("ric", 2);
|
|
42
|
+
|
|
43
|
+
expect(await menu.hasMenuItems()).toBe(true);
|
|
44
|
+
expect(await menu.hasMenuItems(["Copy text", "Filter"])).toBe(true); // exact, in order
|
|
45
|
+
expect(await menu.menuItemLabels()).toEqual(["Copy text", "Filter"]);
|
|
46
|
+
|
|
47
|
+
const copy = menu.menuItem("Copy text"); // or by 0 based index, menu.menuItem(0)
|
|
48
|
+
expect(await copy.hasIcon("edit")).toBe(true);
|
|
49
|
+
expect(await copy.isEnabled()).toBe(true);
|
|
50
|
+
expect(await menu.menuItem("Filter").hasChildren()).toBe(true);
|
|
51
|
+
|
|
52
|
+
// sub menus, opened with mouse (default) or keyboard
|
|
53
|
+
const filterMenu = await menu.menuItem("Filter").openSubMenu();
|
|
54
|
+
await filterMenu.menuItem("Include value").click();
|
|
55
|
+
await menu.assertNotAttached();
|
|
56
|
+
|
|
57
|
+
// keyboard
|
|
58
|
+
await menu.press("ArrowDown");
|
|
59
|
+
await menu.assertFocusedMenuItem("Copy text");
|
|
60
|
+
await menu.press("Enter");
|
|
61
|
+
await menu.close(); // Escape
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
When a key press depends on the focus change made by the previous key, call
|
|
65
|
+
`assertFocusedMenuItem` in between. Keys pressed before the menu has processed a
|
|
66
|
+
focus change may be delivered to the previously focused item.
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "4.0.0-beta.
|
|
2
|
+
"version": "4.0.0-beta.7",
|
|
3
3
|
"author": "heswell",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "./src/index.js",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"type-defs": "node ../../scripts/build-type-defs.mjs"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@vuu-ui/vuu-protocol-types": "4.0.0-beta.
|
|
12
|
+
"@vuu-ui/vuu-protocol-types": "4.0.0-beta.7"
|
|
13
13
|
},
|
|
14
14
|
"peerDependencies": {
|
|
15
15
|
"@playwright/test": "^1.62.1"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { expect } from "@playwright/test";
|
|
2
|
+
import { PlaywrightPageObject } from "./PlaywrightPageObject.js";
|
|
3
|
+
class PwComponent extends PlaywrightPageObject {
|
|
4
|
+
target;
|
|
5
|
+
constructor(page, target){
|
|
6
|
+
super(page), this.target = target;
|
|
7
|
+
}
|
|
8
|
+
get locator() {
|
|
9
|
+
if ("string" == typeof this.target || void 0 === this.target) return this.page.locator(this.target ?? this.defaultCssSelector);
|
|
10
|
+
return this.target;
|
|
11
|
+
}
|
|
12
|
+
async isReady(options) {
|
|
13
|
+
await this.locator.waitFor({
|
|
14
|
+
state: "visible",
|
|
15
|
+
...options
|
|
16
|
+
});
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
isVisible() {
|
|
20
|
+
return this.locator.isVisible();
|
|
21
|
+
}
|
|
22
|
+
async assertVisible() {
|
|
23
|
+
await expect(this.locator).toBeVisible();
|
|
24
|
+
}
|
|
25
|
+
async assertNotAttached() {
|
|
26
|
+
await expect(this.locator).not.toBeAttached();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export { PwComponent };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { expect } from "@playwright/test";
|
|
2
|
+
import { PwComponent } from "./PwComponent.js";
|
|
3
|
+
import { PwMenuItem } from "./PwMenuItem.js";
|
|
4
|
+
class PwContextMenu extends PwComponent {
|
|
5
|
+
defaultCssSelector = '.vuuContextMenuPanel[role="menu"]';
|
|
6
|
+
get menuItems() {
|
|
7
|
+
return this.locator.getByRole("menuitem");
|
|
8
|
+
}
|
|
9
|
+
async menuItemLabels() {
|
|
10
|
+
return (await this.menuItems.allInnerTexts()).map((text)=>text.trim());
|
|
11
|
+
}
|
|
12
|
+
async hasMenuItems(labels) {
|
|
13
|
+
if (void 0 === labels) return await this.menuItems.count() > 0;
|
|
14
|
+
const menuItemLabels = await this.menuItemLabels();
|
|
15
|
+
return menuItemLabels.length === labels.length && menuItemLabels.every((label, i)=>label === labels[i]);
|
|
16
|
+
}
|
|
17
|
+
menuItem(labelOrIndex) {
|
|
18
|
+
const locator = "number" == typeof labelOrIndex ? this.menuItems.nth(labelOrIndex) : this.menuItems.filter({
|
|
19
|
+
has: this.page.getByText(labelOrIndex, {
|
|
20
|
+
exact: true
|
|
21
|
+
})
|
|
22
|
+
});
|
|
23
|
+
return new PwMenuItem(this.page, locator);
|
|
24
|
+
}
|
|
25
|
+
get focusedMenuItem() {
|
|
26
|
+
return new PwMenuItem(this.page, this.menuItems.and(this.page.locator(':focus[tabindex="0"]')));
|
|
27
|
+
}
|
|
28
|
+
async assertFocusedMenuItem(label) {
|
|
29
|
+
await expect(this.focusedMenuItem.locator).toHaveText(label);
|
|
30
|
+
}
|
|
31
|
+
async press(...keys) {
|
|
32
|
+
for (const key of keys)await this.page.keyboard.press(key);
|
|
33
|
+
}
|
|
34
|
+
async close() {
|
|
35
|
+
await this.press("Escape");
|
|
36
|
+
await this.assertNotAttached();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export { PwContextMenu };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { expect } from "@playwright/test";
|
|
2
|
+
import { PwComponent } from "./PwComponent.js";
|
|
3
|
+
import { PwContextMenu } from "./PwContextMenu.js";
|
|
4
|
+
import { byId } from "./selector-utils.js";
|
|
5
|
+
class PwMenuItem extends PwComponent {
|
|
6
|
+
defaultCssSelector = '[role="menuitem"]';
|
|
7
|
+
async label() {
|
|
8
|
+
return (await this.locator.innerText()).trim();
|
|
9
|
+
}
|
|
10
|
+
async hasChildren() {
|
|
11
|
+
return await this.locator.getAttribute("aria-haspopup") === "menu";
|
|
12
|
+
}
|
|
13
|
+
async isDisabled() {
|
|
14
|
+
return await this.locator.getAttribute("aria-disabled") === "true";
|
|
15
|
+
}
|
|
16
|
+
async isEnabled() {
|
|
17
|
+
return !await this.isDisabled();
|
|
18
|
+
}
|
|
19
|
+
async isExpanded() {
|
|
20
|
+
return await this.locator.getAttribute("aria-expanded") === "true";
|
|
21
|
+
}
|
|
22
|
+
isFocused() {
|
|
23
|
+
return this.locator.evaluate((element)=>element === element.ownerDocument.activeElement);
|
|
24
|
+
}
|
|
25
|
+
async hasIcon(iconName) {
|
|
26
|
+
return await this.locator.getAttribute("data-icon") === iconName;
|
|
27
|
+
}
|
|
28
|
+
async click(options) {
|
|
29
|
+
await this.locator.click(options);
|
|
30
|
+
}
|
|
31
|
+
async hover() {
|
|
32
|
+
await this.locator.hover();
|
|
33
|
+
}
|
|
34
|
+
async focus() {
|
|
35
|
+
await this.locator.focus();
|
|
36
|
+
}
|
|
37
|
+
async press(key) {
|
|
38
|
+
await this.locator.press(key);
|
|
39
|
+
}
|
|
40
|
+
async subMenu() {
|
|
41
|
+
const subMenuId = await this.locator.getAttribute("aria-controls");
|
|
42
|
+
if (null === subMenuId) throw Error(`PwMenuItem, subMenu, menu item "${await this.label()}" has no open sub menu`);
|
|
43
|
+
return new PwContextMenu(this.page, byId(subMenuId)).isReady();
|
|
44
|
+
}
|
|
45
|
+
async openSubMenu(using = "mouse") {
|
|
46
|
+
if (!await this.hasChildren()) throw Error(`PwMenuItem, openSubMenu, menu item "${await this.label()}" has no sub menu`);
|
|
47
|
+
if ("mouse" === using) await this.hover();
|
|
48
|
+
else {
|
|
49
|
+
await this.focus();
|
|
50
|
+
await this.press("ArrowRight");
|
|
51
|
+
}
|
|
52
|
+
await this.locator.and(this.page.locator('[aria-expanded="true"]')).waitFor();
|
|
53
|
+
const subMenu = await this.subMenu();
|
|
54
|
+
if ("keyboard" === using) await expect(subMenu.focusedMenuItem.locator).toHaveCount(1);
|
|
55
|
+
return subMenu;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export { PwMenuItem };
|
package/src/PwTable.js
CHANGED
|
@@ -1,34 +1,24 @@
|
|
|
1
1
|
import { expect } from "@playwright/test";
|
|
2
|
-
import {
|
|
2
|
+
import { PwComponent } from "./PwComponent.js";
|
|
3
|
+
import { PwContextMenu } from "./PwContextMenu.js";
|
|
3
4
|
import { PwTableHeaderCell } from "./PwTableHeaderCell.js";
|
|
5
|
+
import { byDataIcon, byId, cssAttributeValue } from "./selector-utils.js";
|
|
4
6
|
const byAriaRowIndex = (ariaRowIndex)=>`[aria-rowindex="${ariaRowIndex}"]:scope`;
|
|
5
7
|
const xpathStringLiteral = (value)=>{
|
|
6
8
|
if (!value.includes("'")) return `'${value}'`;
|
|
7
9
|
if (!value.includes('"')) return `"${value}"`;
|
|
8
10
|
return `concat('${value.split("'").join("', \"'\", '")}')`;
|
|
9
11
|
};
|
|
10
|
-
|
|
11
|
-
const code = character.charCodeAt(0);
|
|
12
|
-
return '"' === character || "\\" === character || code < 32 || 127 === code ? `\\${code.toString(16)} ` : character;
|
|
13
|
-
}).join("");
|
|
14
|
-
const byDataIcon = (iconName)=>`[data-icon="${cssAttributeValue(iconName)}"]`;
|
|
15
|
-
class PwTable extends PlaywrightPageObject {
|
|
16
|
-
cssSelector;
|
|
12
|
+
class PwTable extends PwComponent {
|
|
17
13
|
defaultCssSelector = ".vuuTable";
|
|
18
|
-
constructor(page, cssSelector){
|
|
19
|
-
super(page), this.cssSelector = cssSelector;
|
|
20
|
-
}
|
|
21
|
-
get #locator() {
|
|
22
|
-
return this.page.locator(this.cssSelector ?? this.defaultCssSelector);
|
|
23
|
-
}
|
|
24
14
|
get contentContainer() {
|
|
25
|
-
return this
|
|
15
|
+
return this.locator.locator(".vuuTable-contentContainer");
|
|
26
16
|
}
|
|
27
17
|
get scrollbarContainer() {
|
|
28
|
-
return this
|
|
18
|
+
return this.locator.locator(".vuuTable-scrollbarContainer");
|
|
29
19
|
}
|
|
30
20
|
row(row) {
|
|
31
|
-
return this
|
|
21
|
+
return this.locator.locator(`[aria-rowindex="${row}"]`);
|
|
32
22
|
}
|
|
33
23
|
cell(colName, ariaRowIndex) {
|
|
34
24
|
return this.locateCell(ariaRowIndex, colName);
|
|
@@ -42,10 +32,10 @@ class PwTable extends PlaywrightPageObject {
|
|
|
42
32
|
await textbox.press("commit" === action ? "Enter" : "Escape");
|
|
43
33
|
}
|
|
44
34
|
async columnNames() {
|
|
45
|
-
return this
|
|
35
|
+
return this.locator.locator(".vuuTableHeaderCell .vuuTableHeaderCell-label").allTextContents();
|
|
46
36
|
}
|
|
47
37
|
async rowCount() {
|
|
48
|
-
const ariaRowCount = await this
|
|
38
|
+
const ariaRowCount = await this.locator.getByRole("table").getAttribute("aria-rowcount");
|
|
49
39
|
if (null === ariaRowCount) throw Error("PwTable, rowCount, table does not have an aria-rowcount");
|
|
50
40
|
return parseInt(ariaRowCount, 10);
|
|
51
41
|
}
|
|
@@ -54,23 +44,32 @@ class PwTable extends PlaywrightPageObject {
|
|
|
54
44
|
return cell.locator(byDataIcon(iconName));
|
|
55
45
|
}
|
|
56
46
|
cellsWithIcon(iconName) {
|
|
57
|
-
return this
|
|
47
|
+
return this.locator.getByRole("cell").filter({
|
|
58
48
|
has: this.page.locator(byDataIcon(iconName))
|
|
59
49
|
});
|
|
60
50
|
}
|
|
61
51
|
headerCell(columnName) {
|
|
62
|
-
return new PwTableHeaderCell(this.page,
|
|
52
|
+
return new PwTableHeaderCell(this.page, this.locator.locator(`.vuuTableHeaderCell[data-column-name="${cssAttributeValue(columnName)}"]`));
|
|
53
|
+
}
|
|
54
|
+
async showContextMenu(columnName, ariaRowIndex) {
|
|
55
|
+
const cell = await this.cell(columnName, ariaRowIndex);
|
|
56
|
+
await cell.click({
|
|
57
|
+
button: "right"
|
|
58
|
+
});
|
|
59
|
+
const menu = await new PwContextMenu(this.page).isReady();
|
|
60
|
+
const menuId = await menu.locator.getAttribute("id");
|
|
61
|
+
return menuId ? new PwContextMenu(this.page, byId(menuId)) : menu;
|
|
63
62
|
}
|
|
64
63
|
locateCell(rowOrText, column) {
|
|
65
|
-
if ("number" == typeof rowOrText && "number" == typeof column) return this
|
|
64
|
+
if ("number" == typeof rowOrText && "number" == typeof column) return this.locator.locator(`[aria-rowindex="${rowOrText}"] > [aria-colindex="${column}"]`);
|
|
66
65
|
if ("number" == typeof rowOrText && "string" == typeof column) {
|
|
67
|
-
const columnHeader = this
|
|
66
|
+
const columnHeader = this.locator.locator(`xpath=.//*[@data-column-name=${xpathStringLiteral(column)}]`).first();
|
|
68
67
|
return columnHeader.getAttribute("aria-colindex").then((ariaColIndex)=>{
|
|
69
68
|
if (null === ariaColIndex) throw Error(`PwTable, locateCell, column "${column}" does not have an aria-colindex`);
|
|
70
|
-
return this
|
|
69
|
+
return this.locator.locator(`[aria-rowindex="${rowOrText}"] > [aria-colindex="${ariaColIndex}"]`);
|
|
71
70
|
});
|
|
72
71
|
}
|
|
73
|
-
if ("string" == typeof rowOrText) return this
|
|
72
|
+
if ("string" == typeof rowOrText) return this.locator.getByRole("cell", {
|
|
74
73
|
name: rowOrText
|
|
75
74
|
});
|
|
76
75
|
}
|
|
@@ -88,9 +87,6 @@ class PwTable extends PlaywrightPageObject {
|
|
|
88
87
|
}
|
|
89
88
|
}
|
|
90
89
|
}
|
|
91
|
-
async assertVisible() {
|
|
92
|
-
return expect(this.#locator).toBeVisible();
|
|
93
|
-
}
|
|
94
90
|
getCellText(arg1, arg2, arg3, arg4) {
|
|
95
91
|
if ("number" == typeof arg1 && "number" == typeof arg2) return [
|
|
96
92
|
this.locateCell(arg1, arg2),
|
|
@@ -146,16 +142,16 @@ class PwTable extends PlaywrightPageObject {
|
|
|
146
142
|
const offsetFromEnd = totalRowCount - to;
|
|
147
143
|
const trailingBufferedRows = offsetFromEnd < renderBufferSize ? Math.min(0, offsetFromEnd) : renderBufferSize;
|
|
148
144
|
const renderedRowCount = to - from + leadingBufferedRows + trailingBufferedRows;
|
|
149
|
-
await expect(this
|
|
145
|
+
await expect(this.locator.getByRole("row")).toHaveCount(renderedRowCount + headerCount);
|
|
150
146
|
const firstRenderedRow = from - leadingBufferedRows + headerCount + 1;
|
|
151
147
|
const lastVisibleRow = to + headerCount;
|
|
152
148
|
const lastRenderedRow = to + headerCount + trailingBufferedRows;
|
|
153
|
-
await expect(this
|
|
154
|
-
await expect(this
|
|
149
|
+
await expect(this.locator.getByRole("row").locator(byAriaRowIndex(firstRenderedRow))).toBeVisible();
|
|
150
|
+
await expect(this.locator.getByRole("row").locator(byAriaRowIndex(lastVisibleRow))).toBeVisible();
|
|
155
151
|
if (trailingBufferedRows > 0) {
|
|
156
|
-
await expect(this
|
|
157
|
-
await expect(this
|
|
158
|
-
await expect(this
|
|
152
|
+
await expect(this.locator.getByRole("row").locator(byAriaRowIndex(lastVisibleRow + 1))).not.toBeInViewport();
|
|
153
|
+
await expect(this.locator.getByRole("row").locator(byAriaRowIndex(lastRenderedRow))).not.toBeInViewport();
|
|
154
|
+
await expect(this.locator.getByRole("row").locator(byAriaRowIndex(lastRenderedRow + 1))).not.toBeAttached();
|
|
159
155
|
}
|
|
160
156
|
}
|
|
161
157
|
async assertRenderedColumns({ rendered, visible }) {
|
package/src/PwTableHeaderCell.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { expect } from "@playwright/test";
|
|
2
|
-
import {
|
|
3
|
-
class PwTableHeaderCell extends
|
|
4
|
-
|
|
5
|
-
constructor(page,
|
|
6
|
-
super(page
|
|
2
|
+
import { PwComponent } from "./PwComponent.js";
|
|
3
|
+
class PwTableHeaderCell extends PwComponent {
|
|
4
|
+
defaultCssSelector = ".vuuTableHeaderCell";
|
|
5
|
+
constructor(page, target){
|
|
6
|
+
super(page, target);
|
|
7
7
|
}
|
|
8
8
|
get isSelected() {
|
|
9
|
-
return this.
|
|
9
|
+
return this.locator.evaluate((element)=>element.classList.contains("vuuTableHeaderCell-selected"));
|
|
10
10
|
}
|
|
11
11
|
async assertSelected() {
|
|
12
|
-
await expect(this.
|
|
12
|
+
await expect(this.locator).toContainClass("vuuTableHeaderCell-selected");
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
export { PwTableHeaderCell };
|
package/src/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
export { PlaywrightPageObject } from "./PlaywrightPageObject.js";
|
|
2
|
+
export { PwComponent } from "./PwComponent.js";
|
|
3
|
+
export { PwContextMenu } from "./PwContextMenu.js";
|
|
4
|
+
export { PwMenuItem } from "./PwMenuItem.js";
|
|
2
5
|
export { PwTable } from "./PwTable.js";
|
|
3
6
|
export { PwTableHeaderCell } from "./PwTableHeaderCell.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const cssAttributeValue = (value)=>Array.from(value, (character)=>{
|
|
2
|
+
const code = character.charCodeAt(0);
|
|
3
|
+
return '"' === character || "\\" === character || code < 32 || 127 === code ? `\\${code.toString(16)} ` : character;
|
|
4
|
+
}).join("");
|
|
5
|
+
const byDataIcon = (iconName)=>`[data-icon="${cssAttributeValue(iconName)}"]`;
|
|
6
|
+
const byId = (id)=>`[id="${cssAttributeValue(id)}"]`;
|
|
7
|
+
export { byDataIcon, byId, cssAttributeValue };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type Locator, type Page } from "@playwright/test";
|
|
2
|
+
import { PlaywrightPageObject } from "./PlaywrightPageObject";
|
|
3
|
+
/**
|
|
4
|
+
* Identifies the element a component helper operates on. Either a css selector,
|
|
5
|
+
* resolved against the page, or a Locator.
|
|
6
|
+
*/
|
|
7
|
+
export type PwComponentTarget = string | Locator;
|
|
8
|
+
/**
|
|
9
|
+
* Base class for helpers that wrap a single component instance. Subclasses
|
|
10
|
+
* provide a defaultCssSelector, which can be overridden at runtime by passing
|
|
11
|
+
* a css selector or Locator to the constructor.
|
|
12
|
+
*/
|
|
13
|
+
export declare abstract class PwComponent extends PlaywrightPageObject {
|
|
14
|
+
protected readonly target?: PwComponentTarget | undefined;
|
|
15
|
+
protected abstract readonly defaultCssSelector: string;
|
|
16
|
+
/**
|
|
17
|
+
* @param page the Playwright page
|
|
18
|
+
* @param target optional css selector or Locator for the component, overrides defaultCssSelector
|
|
19
|
+
*/
|
|
20
|
+
constructor(page: Page, target?: PwComponentTarget | undefined);
|
|
21
|
+
/**
|
|
22
|
+
* Locator for the component root element. Use this for web-first assertions
|
|
23
|
+
* that are not covered by a helper method.
|
|
24
|
+
*/
|
|
25
|
+
get locator(): Locator;
|
|
26
|
+
/**
|
|
27
|
+
* Wait until the component is attached and visible. Resolves with the
|
|
28
|
+
* component, so it can be chained.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* const menu = await new PwContextMenu(page).isReady();
|
|
32
|
+
*/
|
|
33
|
+
isReady(options?: {
|
|
34
|
+
timeout?: number;
|
|
35
|
+
}): Promise<this>;
|
|
36
|
+
/**
|
|
37
|
+
* Is the component currently visible. Does not wait or retry, prefer
|
|
38
|
+
* assertVisible within tests unless the component is known to be stable.
|
|
39
|
+
*/
|
|
40
|
+
isVisible(): Promise<boolean>;
|
|
41
|
+
assertVisible(): Promise<void>;
|
|
42
|
+
assertNotAttached(): Promise<void>;
|
|
43
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type Locator } from "@playwright/test";
|
|
2
|
+
import { PwComponent } from "./PwComponent";
|
|
3
|
+
import { PwMenuItem } from "./PwMenuItem";
|
|
4
|
+
/**
|
|
5
|
+
* A context menu (or one of its sub menus). Usually obtained from
|
|
6
|
+
* PwTable.showContextMenu.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* test("cell menu", async ({ page }) => {
|
|
10
|
+
* const table = new PwTable(page);
|
|
11
|
+
* const menu = await table.showContextMenu("ric", 2);
|
|
12
|
+
* expect(await menu.hasMenuItems(["Copy text", "Filter"])).toBe(true);
|
|
13
|
+
* const filterMenu = await menu.menuItem("Filter").openSubMenu();
|
|
14
|
+
* await filterMenu.menuItem("Include value").click();
|
|
15
|
+
* await menu.assertNotAttached();
|
|
16
|
+
* });
|
|
17
|
+
*/
|
|
18
|
+
export declare class PwContextMenu extends PwComponent {
|
|
19
|
+
protected readonly defaultCssSelector = ".vuuContextMenuPanel[role=\"menu\"]";
|
|
20
|
+
/**
|
|
21
|
+
* Locator for the menu items of this menu. Sub menus are rendered in their
|
|
22
|
+
* own (portalled) panels, so their items are not included.
|
|
23
|
+
*/
|
|
24
|
+
get menuItems(): Locator;
|
|
25
|
+
/**
|
|
26
|
+
* The labels of the menu items, in rendered order.
|
|
27
|
+
*/
|
|
28
|
+
menuItemLabels(): Promise<string[]>;
|
|
29
|
+
/**
|
|
30
|
+
* With no argument, does the menu have any menu items. With labels, does
|
|
31
|
+
* the menu have exactly these menu items, in this order.
|
|
32
|
+
*/
|
|
33
|
+
hasMenuItems(labels?: string[]): Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Menu item by label, or by 0 based index.
|
|
36
|
+
*/
|
|
37
|
+
menuItem(labelOrIndex: string | number): PwMenuItem;
|
|
38
|
+
/**
|
|
39
|
+
* The menu item that currently has keyboard focus. The item must also be the
|
|
40
|
+
* menu's active item (tabindex="0"), which confirms the menu has processed the
|
|
41
|
+
* focus change and is ready for the next key press.
|
|
42
|
+
*/
|
|
43
|
+
get focusedMenuItem(): PwMenuItem;
|
|
44
|
+
/**
|
|
45
|
+
* Assert the focused menu item. Wait on this between key presses, keys
|
|
46
|
+
* pressed before the menu has processed a focus change may be misdirected.
|
|
47
|
+
*/
|
|
48
|
+
assertFocusedMenuItem(label: string): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Press key(s) within the menu. Keys are sent to the focused element, which
|
|
51
|
+
* is assumed to be within the menu. Multiple keys are pressed without waiting
|
|
52
|
+
* for the menu to respond, use assertFocusedMenuItem between key presses where
|
|
53
|
+
* a key depends on the focus change of the previous key.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* await menu.press("ArrowDown");
|
|
57
|
+
* await menu.assertFocusedMenuItem("Filter");
|
|
58
|
+
* await menu.press("Enter");
|
|
59
|
+
*/
|
|
60
|
+
press(...keys: string[]): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Close the menu by pressing Escape.
|
|
63
|
+
*/
|
|
64
|
+
close(): Promise<void>;
|
|
65
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type Locator } from "@playwright/test";
|
|
2
|
+
import { PwComponent } from "./PwComponent";
|
|
3
|
+
import { PwContextMenu } from "./PwContextMenu";
|
|
4
|
+
/**
|
|
5
|
+
* A single item within a PwContextMenu. Usually obtained from
|
|
6
|
+
* PwContextMenu.menuItem rather than constructed directly.
|
|
7
|
+
*/
|
|
8
|
+
export declare class PwMenuItem extends PwComponent {
|
|
9
|
+
protected readonly defaultCssSelector = "[role=\"menuitem\"]";
|
|
10
|
+
/**
|
|
11
|
+
* The text label of the menu item.
|
|
12
|
+
*/
|
|
13
|
+
label(): Promise<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Does this menu item open a sub menu.
|
|
16
|
+
*/
|
|
17
|
+
hasChildren(): Promise<boolean>;
|
|
18
|
+
isDisabled(): Promise<boolean>;
|
|
19
|
+
isEnabled(): Promise<boolean>;
|
|
20
|
+
/**
|
|
21
|
+
* Is the sub menu of this menu item open. Always false for leaf items.
|
|
22
|
+
*/
|
|
23
|
+
isExpanded(): Promise<boolean>;
|
|
24
|
+
isFocused(): Promise<boolean>;
|
|
25
|
+
/**
|
|
26
|
+
* Is the menu item decorated with the given icon (data-icon attribute).
|
|
27
|
+
*/
|
|
28
|
+
hasIcon(iconName: string): Promise<boolean>;
|
|
29
|
+
/**
|
|
30
|
+
* Click the menu item. Playwright will not click a disabled item unless
|
|
31
|
+
* { force: true } is passed.
|
|
32
|
+
*/
|
|
33
|
+
click(options?: Parameters<Locator["click"]>[0]): Promise<void>;
|
|
34
|
+
hover(): Promise<void>;
|
|
35
|
+
focus(): Promise<void>;
|
|
36
|
+
press(key: string): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* The sub menu opened by this menu item. The sub menu must already be open,
|
|
39
|
+
* use openSubMenu to open it.
|
|
40
|
+
*/
|
|
41
|
+
subMenu(): Promise<PwContextMenu>;
|
|
42
|
+
/**
|
|
43
|
+
* Open the sub menu of this menu item, using the mouse (hover) or keyboard
|
|
44
|
+
* (ArrowRight). Resolves once the sub menu is visible and, when opened with
|
|
45
|
+
* the keyboard, once focus has moved into the sub menu.
|
|
46
|
+
*/
|
|
47
|
+
openSubMenu(using?: "mouse" | "keyboard"): Promise<PwContextMenu>;
|
|
48
|
+
}
|
package/types/PwTable.d.ts
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
import { type Locator
|
|
1
|
+
import { type Locator } from "@playwright/test";
|
|
2
2
|
import type { VuuRange } from "@vuu-ui/vuu-protocol-types";
|
|
3
|
-
import {
|
|
3
|
+
import { PwComponent } from "./PwComponent";
|
|
4
|
+
import { PwContextMenu } from "./PwContextMenu";
|
|
4
5
|
import { PwTableHeaderCell } from "./PwTableHeaderCell";
|
|
5
6
|
type AriaRole = "cell" | "textbox" | "combobox";
|
|
6
|
-
export declare class PwTable extends
|
|
7
|
-
#private;
|
|
8
|
-
private readonly cssSelector?;
|
|
7
|
+
export declare class PwTable extends PwComponent {
|
|
9
8
|
protected readonly defaultCssSelector = ".vuuTable";
|
|
10
|
-
/**
|
|
11
|
-
* @param page the Playwright page
|
|
12
|
-
* @param cssSelector optional selector for the Table, overrides defaultCssSelector
|
|
13
|
-
*/
|
|
14
|
-
constructor(page: Page, cssSelector?: string | undefined);
|
|
15
9
|
get contentContainer(): Locator;
|
|
16
10
|
get scrollbarContainer(): Locator;
|
|
17
11
|
/**
|
|
@@ -67,6 +61,21 @@ export declare class PwTable extends PlaywrightPageObject {
|
|
|
67
61
|
*/
|
|
68
62
|
cellsWithIcon(iconName: string): Locator;
|
|
69
63
|
headerCell(columnName: string): PwTableHeaderCell;
|
|
64
|
+
/**
|
|
65
|
+
* Right click a cell to invoke the context menu. Resolves once the menu is
|
|
66
|
+
* visible. Rejects if no context menu is shown.
|
|
67
|
+
*
|
|
68
|
+
* @param columnName name of the column
|
|
69
|
+
* @param ariaRowIndex 1-based aria row index, including headers (with one header, the first data row is 2)
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* test("shows cell menu", async ({ page }) => {
|
|
73
|
+
* const table = new PwTable(page);
|
|
74
|
+
* const menu = await table.showContextMenu("ric", 2);
|
|
75
|
+
* expect(await menu.menuItem("Copy text").isEnabled()).toBe(true);
|
|
76
|
+
* });
|
|
77
|
+
*/
|
|
78
|
+
showContextMenu(columnName: string, ariaRowIndex: number): Promise<PwContextMenu>;
|
|
70
79
|
/**
|
|
71
80
|
* Locate cell by cell contents
|
|
72
81
|
* @param text
|
|
@@ -83,7 +92,6 @@ export declare class PwTable extends PlaywrightPageObject {
|
|
|
83
92
|
locateCell(row: number, columnName: string): Promise<Locator>;
|
|
84
93
|
locateColumnHeader(column: number): Locator;
|
|
85
94
|
assertCellIsEditable(row: number, column: number, isEditable: boolean | undefined, value?: string): Promise<void>;
|
|
86
|
-
assertVisible(): Promise<void>;
|
|
87
95
|
private getCellText;
|
|
88
96
|
assertCellValue(cell: Locator, text: string, role?: AriaRole): Promise<void>;
|
|
89
97
|
assertCellValue(row: number, column: number, text: string, role?: AriaRole): Promise<void>;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { type Page } from "@playwright/test";
|
|
2
|
-
import {
|
|
3
|
-
export declare class PwTableHeaderCell extends
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { PwComponent, type PwComponentTarget } from "./PwComponent";
|
|
3
|
+
export declare class PwTableHeaderCell extends PwComponent {
|
|
4
|
+
protected readonly defaultCssSelector = ".vuuTableHeaderCell";
|
|
5
|
+
/**
|
|
6
|
+
* @param page the Playwright page
|
|
7
|
+
* @param target css selector or Locator for the header cell
|
|
8
|
+
*/
|
|
9
|
+
constructor(page: Page, target: PwComponentTarget);
|
|
6
10
|
get isSelected(): Promise<boolean>;
|
|
7
11
|
assertSelected(): Promise<void>;
|
|
8
12
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
export { PlaywrightPageObject } from "./PlaywrightPageObject";
|
|
2
|
+
export { PwComponent, type PwComponentTarget } from "./PwComponent";
|
|
3
|
+
export { PwContextMenu } from "./PwContextMenu";
|
|
4
|
+
export { PwMenuItem } from "./PwMenuItem";
|
|
2
5
|
export { PwTable } from "./PwTable";
|
|
3
6
|
export { PwTableHeaderCell } from "./PwTableHeaderCell";
|