@elastic/eui-test-helpers 1.3.0 → 1.4.0-snapshot.1787665877015

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.
Files changed (34) hide show
  1. package/README.md +2 -0
  2. package/lib/cjs/components/basic_table/selectors.d.ts +17 -0
  3. package/lib/cjs/components/basic_table/selectors.js +31 -0
  4. package/lib/cjs/components/selectable/selectors.d.ts +11 -0
  5. package/lib/cjs/components/selectable/selectors.js +25 -0
  6. package/lib/cjs/index.d.ts +2 -0
  7. package/lib/cjs/index.js +15 -1
  8. package/lib/cjs/playwright/components/basic_table/object.d.ts +50 -0
  9. package/lib/cjs/playwright/components/basic_table/object.js +93 -0
  10. package/lib/cjs/playwright/components/selectable/object.d.ts +53 -0
  11. package/lib/cjs/playwright/components/selectable/object.js +92 -0
  12. package/lib/cjs/selectors.d.ts +1 -0
  13. package/lib/cjs/selectors.js +8 -1
  14. package/lib/esm/components/basic_table/selectors.d.ts +17 -0
  15. package/lib/esm/components/basic_table/selectors.js +25 -0
  16. package/lib/esm/components/basic_table/selectors.js.map +1 -0
  17. package/lib/esm/components/selectable/selectors.d.ts +11 -0
  18. package/lib/esm/components/selectable/selectors.js +19 -0
  19. package/lib/esm/components/selectable/selectors.js.map +1 -0
  20. package/lib/esm/index.d.ts +2 -0
  21. package/lib/esm/index.js +2 -0
  22. package/lib/esm/index.js.map +1 -1
  23. package/lib/esm/playwright/components/basic_table/object.d.ts +50 -0
  24. package/lib/esm/playwright/components/basic_table/object.js +76 -0
  25. package/lib/esm/playwright/components/basic_table/object.js.map +1 -0
  26. package/lib/esm/playwright/components/selectable/object.d.ts +53 -0
  27. package/lib/esm/playwright/components/selectable/object.js +83 -0
  28. package/lib/esm/playwright/components/selectable/object.js.map +1 -0
  29. package/lib/esm/selectors.d.ts +1 -0
  30. package/lib/esm/selectors.js +1 -0
  31. package/lib/esm/selectors.js.map +1 -1
  32. package/package.json +3 -2
  33. package/src/components/basic_table/README.md +30 -0
  34. package/src/components/selectable/README.md +27 -0
package/README.md CHANGED
@@ -50,6 +50,8 @@ their own version at runtime.
50
50
  | `EuiDataGridObject` | [src/components/datagrid/README.md](src/components/datagrid/README.md) |
51
51
  | `EuiSuperSelectObject` | [src/components/form/super_select/README.md](src/components/form/super_select/README.md) |
52
52
  | `EuiGlobalToastListObject` | [src/components/toast/README.md](src/components/toast/README.md) |
53
+ | `EuiSelectableObject` | [src/components/selectable/README.md](src/components/selectable/README.md) |
54
+ | `EuiBasicTableObject` | [src/components/basic_table/README.md](src/components/basic_table/README.md) |
53
55
 
54
56
  ## Contributing
55
57
 
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Stable selectors for
3
+ * {@link https://eui.elastic.co/docs/components/tabular-content/tables/|EuiBasicTable}
4
+ * (also covers `EuiInMemoryTable`, which renders a `EuiBasicTable` underneath).
5
+ * `*_SELECTOR` values are CSS.
6
+ */
7
+ export declare const EuiBasicTableSelectors: {
8
+ /** Root element (the `.euiBasicTable` container carrying the consumer's `data-test-subj`). */
9
+ ROOT_SELECTOR: string;
10
+ /** A data row. Also matches EUI's own empty/error message row — callers must exclude those. */
11
+ ROW_SELECTOR: string;
12
+ /**
13
+ * A field-data column's header cell. `%s` is the column's `field`. EUI appends a
14
+ * positional index (`tableHeaderCell_<field>_<index>`), so this is a prefix match.
15
+ */
16
+ HEADER_CELL_SELECTOR_PREFIX: string;
17
+ };
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.EuiBasicTableSelectors = void 0;
7
+ /*
8
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
9
+ * or more contributor license agreements. Licensed under the Elastic License
10
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
11
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
12
+ * Side Public License, v 1.
13
+ */
14
+
15
+ /**
16
+ * Stable selectors for
17
+ * {@link https://eui.elastic.co/docs/components/tabular-content/tables/|EuiBasicTable}
18
+ * (also covers `EuiInMemoryTable`, which renders a `EuiBasicTable` underneath).
19
+ * `*_SELECTOR` values are CSS.
20
+ */
21
+ const EuiBasicTableSelectors = exports.EuiBasicTableSelectors = {
22
+ /** Root element (the `.euiBasicTable` container carrying the consumer's `data-test-subj`). */
23
+ ROOT_SELECTOR: '.euiBasicTable',
24
+ /** A data row. Also matches EUI's own empty/error message row — callers must exclude those. */
25
+ ROW_SELECTOR: '.euiTableRow',
26
+ /**
27
+ * A field-data column's header cell. `%s` is the column's `field`. EUI appends a
28
+ * positional index (`tableHeaderCell_<field>_<index>`), so this is a prefix match.
29
+ */
30
+ HEADER_CELL_SELECTOR_PREFIX: 'tableHeaderCell_'
31
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Stable selectors for
3
+ * {@link https://eui.elastic.co/docs/components/forms/selection/selectable/|EuiSelectable}.
4
+ * `*_SELECTOR` values are CSS.
5
+ */
6
+ export declare const EuiSelectableSelectors: {
7
+ /** Root element (the `.euiSelectable` container carrying the consumer's `data-test-subj`). */
8
+ ROOT_SELECTOR: string;
9
+ /** The search box, present only when the selectable is `searchable`. */
10
+ SEARCH_SELECTOR: string;
11
+ };
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.EuiSelectableSelectors = void 0;
7
+ /*
8
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
9
+ * or more contributor license agreements. Licensed under the Elastic License
10
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
11
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
12
+ * Side Public License, v 1.
13
+ */
14
+
15
+ /**
16
+ * Stable selectors for
17
+ * {@link https://eui.elastic.co/docs/components/forms/selection/selectable/|EuiSelectable}.
18
+ * `*_SELECTOR` values are CSS.
19
+ */
20
+ const EuiSelectableSelectors = exports.EuiSelectableSelectors = {
21
+ /** Root element (the `.euiSelectable` container carrying the consumer's `data-test-subj`). */
22
+ ROOT_SELECTOR: '.euiSelectable',
23
+ /** The search box, present only when the selectable is `searchable`. */
24
+ SEARCH_SELECTOR: '.euiSelectableSearch'
25
+ };
@@ -3,3 +3,5 @@ export { EuiComboBoxObject } from './playwright/components/combo_box/object';
3
3
  export { EuiDataGridObject } from './playwright/components/datagrid/object';
4
4
  export { EuiSuperSelectObject } from './playwright/components/form/super_select/object';
5
5
  export { EuiGlobalToastListObject } from './playwright/components/toast/object';
6
+ export { EuiSelectableObject } from './playwright/components/selectable/object';
7
+ export { EuiBasicTableObject } from './playwright/components/basic_table/object';
package/lib/cjs/index.js CHANGED
@@ -9,6 +9,12 @@ Object.defineProperty(exports, "BaseObject", {
9
9
  return _base_object.BaseObject;
10
10
  }
11
11
  });
12
+ Object.defineProperty(exports, "EuiBasicTableObject", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _object6.EuiBasicTableObject;
16
+ }
17
+ });
12
18
  Object.defineProperty(exports, "EuiComboBoxObject", {
13
19
  enumerable: true,
14
20
  get: function () {
@@ -27,6 +33,12 @@ Object.defineProperty(exports, "EuiGlobalToastListObject", {
27
33
  return _object4.EuiGlobalToastListObject;
28
34
  }
29
35
  });
36
+ Object.defineProperty(exports, "EuiSelectableObject", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _object5.EuiSelectableObject;
40
+ }
41
+ });
30
42
  Object.defineProperty(exports, "EuiSuperSelectObject", {
31
43
  enumerable: true,
32
44
  get: function () {
@@ -37,4 +49,6 @@ var _base_object = require("./playwright/base_object");
37
49
  var _object = require("./playwright/components/combo_box/object");
38
50
  var _object2 = require("./playwright/components/datagrid/object");
39
51
  var _object3 = require("./playwright/components/form/super_select/object");
40
- var _object4 = require("./playwright/components/toast/object");
52
+ var _object4 = require("./playwright/components/toast/object");
53
+ var _object5 = require("./playwright/components/selectable/object");
54
+ var _object6 = require("./playwright/components/basic_table/object");
@@ -0,0 +1,50 @@
1
+ import type { Locator } from '@playwright/test';
2
+ import { BaseObject, type ObjectScope } from '../../base_object';
3
+ /**
4
+ * Playwright Component Object for {@link
5
+ * https://eui.elastic.co/docs/components/tabular-content/tables/ EuiBasicTable}
6
+ * (also covers `EuiInMemoryTable`, which renders a `EuiBasicTable` underneath and
7
+ * passes the `data-test-subj` straight through — there is no separate object for it).
8
+ *
9
+ * `testSubj` must match the `data-test-subj` set by the consumer on the
10
+ * `<EuiBasicTable>`/`<EuiInMemoryTable>` (EUI spreads it onto the `.euiBasicTable`
11
+ * root). See the package README for what's deliberately excluded from v1.
12
+ */
13
+ export declare class EuiBasicTableObject extends BaseObject {
14
+ constructor(scope: ObjectScope, testSubj: string);
15
+ /**
16
+ * The table's data rows, as a `Locator` so callers keep Playwright auto-retry
17
+ * for count and content assertions (e.g. `expect(table.rows).toHaveCount(3)`).
18
+ *
19
+ * Excludes EUI's own "no items found" (and error-message) row: when the table
20
+ * is empty, EUI still renders a single `.euiTableRow` whose one cell spans
21
+ * every column (`colSpan` on `EuiTableRowCell`, the `td[colspan]` HTML
22
+ * attribute). Real data rows don't set `colspan` on their cells, so filtering
23
+ * it out is what keeps `toHaveCount(0)` correct on an empty table instead of
24
+ * reading `1`.
25
+ */
26
+ get rows(): Locator;
27
+ /**
28
+ * The cells of the given field-data column, one per row, as a `Locator` for
29
+ * retrying content/count assertions (e.g.
30
+ * `expect(await table.cells('status')).toHaveText(['Running'])`).
31
+ *
32
+ * Resolves the column's position from EUI's own header cell
33
+ * (`tableHeaderCell_<field>_<index>`, matched exactly so a column named e.g.
34
+ * `status` doesn't also match a `status_detail` header) via its native
35
+ * `cellIndex`, then reads the cell at that position in every row — the same
36
+ * positional alignment EUI itself relies on, so it holds regardless of a
37
+ * leading selection-checkbox column. Matches both `<td>` and `<th>`: EUI
38
+ * renders the `rowHeader` column's body cell as a `<th scope="row">` for
39
+ * accessibility, not a `<td>`. Throws if no column with that `field` is
40
+ * rendered.
41
+ *
42
+ * EUI generates a computed column's header test-subj from its `name` using
43
+ * this exact same `tableHeaderCell_<name>_<index>` shape (there's no `field`
44
+ * to key on), so a computed column whose `name` happens to equal another
45
+ * column's `field` can resolve ambiguously — this method can't distinguish
46
+ * the two from the DOM alone. Not expected in practice; call it out if you
47
+ * hit it.
48
+ */
49
+ cells(field: string): Promise<Locator>;
50
+ }
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.EuiBasicTableObject = void 0;
7
+ var _base_object = require("../../base_object");
8
+ var _selectors = require("../../../components/basic_table/selectors");
9
+ /*
10
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
11
+ * or more contributor license agreements. Licensed under the Elastic License
12
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
13
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
14
+ * Side Public License, v 1.
15
+ */
16
+
17
+ /**
18
+ * Playwright Component Object for {@link
19
+ * https://eui.elastic.co/docs/components/tabular-content/tables/ EuiBasicTable}
20
+ * (also covers `EuiInMemoryTable`, which renders a `EuiBasicTable` underneath and
21
+ * passes the `data-test-subj` straight through — there is no separate object for it).
22
+ *
23
+ * `testSubj` must match the `data-test-subj` set by the consumer on the
24
+ * `<EuiBasicTable>`/`<EuiInMemoryTable>` (EUI spreads it onto the `.euiBasicTable`
25
+ * root). See the package README for what's deliberately excluded from v1.
26
+ */
27
+ class EuiBasicTableObject extends _base_object.BaseObject {
28
+ constructor(scope, testSubj) {
29
+ super(scope, testSubj, _selectors.EuiBasicTableSelectors.ROOT_SELECTOR);
30
+ }
31
+
32
+ /**
33
+ * The table's data rows, as a `Locator` so callers keep Playwright auto-retry
34
+ * for count and content assertions (e.g. `expect(table.rows).toHaveCount(3)`).
35
+ *
36
+ * Excludes EUI's own "no items found" (and error-message) row: when the table
37
+ * is empty, EUI still renders a single `.euiTableRow` whose one cell spans
38
+ * every column (`colSpan` on `EuiTableRowCell`, the `td[colspan]` HTML
39
+ * attribute). Real data rows don't set `colspan` on their cells, so filtering
40
+ * it out is what keeps `toHaveCount(0)` correct on an empty table instead of
41
+ * reading `1`.
42
+ */
43
+ get rows() {
44
+ return this.root.locator(_selectors.EuiBasicTableSelectors.ROW_SELECTOR).filter({
45
+ hasNot: this.root.page().locator('td[colspan]')
46
+ });
47
+ }
48
+
49
+ /**
50
+ * The cells of the given field-data column, one per row, as a `Locator` for
51
+ * retrying content/count assertions (e.g.
52
+ * `expect(await table.cells('status')).toHaveText(['Running'])`).
53
+ *
54
+ * Resolves the column's position from EUI's own header cell
55
+ * (`tableHeaderCell_<field>_<index>`, matched exactly so a column named e.g.
56
+ * `status` doesn't also match a `status_detail` header) via its native
57
+ * `cellIndex`, then reads the cell at that position in every row — the same
58
+ * positional alignment EUI itself relies on, so it holds regardless of a
59
+ * leading selection-checkbox column. Matches both `<td>` and `<th>`: EUI
60
+ * renders the `rowHeader` column's body cell as a `<th scope="row">` for
61
+ * accessibility, not a `<td>`. Throws if no column with that `field` is
62
+ * rendered.
63
+ *
64
+ * EUI generates a computed column's header test-subj from its `name` using
65
+ * this exact same `tableHeaderCell_<name>_<index>` shape (there's no `field`
66
+ * to key on), so a computed column whose `name` happens to equal another
67
+ * column's `field` can resolve ambiguously — this method can't distinguish
68
+ * the two from the DOM alone. Not expected in practice; call it out if you
69
+ * hit it.
70
+ */
71
+ async cells(field) {
72
+ const escaped = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
73
+ const cellIndex = await this.root.evaluate((tableEl, {
74
+ prefix,
75
+ pattern,
76
+ field: originalField
77
+ }) => {
78
+ const regex = new RegExp(`^${prefix}${pattern}_\\d+$`);
79
+ const headers = Array.from(tableEl.querySelectorAll('thead th, thead td'));
80
+ const header = headers.find(el => regex.test(el.getAttribute('data-test-subj') ?? ''));
81
+ if (!header) {
82
+ throw new Error(`EuiBasicTableObject.cells: no column with field "${originalField}" found in this table's header.`);
83
+ }
84
+ return header.cellIndex;
85
+ }, {
86
+ prefix: _selectors.EuiBasicTableSelectors.HEADER_CELL_SELECTOR_PREFIX,
87
+ pattern: escaped,
88
+ field
89
+ });
90
+ return this.rows.locator(`:is(td, th):nth-child(${cellIndex + 1})`);
91
+ }
92
+ }
93
+ exports.EuiBasicTableObject = EuiBasicTableObject;
@@ -0,0 +1,53 @@
1
+ import type { Locator } from '@playwright/test';
2
+ import { BaseObject, type ObjectScope } from '../../base_object';
3
+ /**
4
+ * Playwright Component Object for {@link
5
+ * https://eui.elastic.co/docs/components/forms/selection/selectable/ EuiSelectable}.
6
+ *
7
+ * `testSubj` must match the `data-test-subj` set by the consumer on the
8
+ * `<EuiSelectable>` (EUI spreads it onto the `.euiSelectable` root). When the
9
+ * selectable renders inside a popover the consumer opens, pass that popover as
10
+ * `scope` and drive the open/close from the test — the popover is not this
11
+ * component's concern.
12
+ */
13
+ export declare class EuiSelectableObject extends BaseObject {
14
+ constructor(scope: ObjectScope, testSubj: string);
15
+ /**
16
+ * The options currently mounted in the list, as a `Locator` so callers keep
17
+ * Playwright auto-retry for count and content assertions
18
+ * (e.g. `expect(options).toHaveCount(3)`). The list is virtualized, so this is
19
+ * the rendered window — `search()` first to filter the target into view
20
+ * rather than scanning a long list.
21
+ */
22
+ get options(): Locator;
23
+ /**
24
+ * Selects the option with the given label. A no-op if it is already selected.
25
+ * EUI reports that as `aria-checked` for a multi-selection list, or
26
+ * `aria-selected` for a single-selection one, so both are checked.
27
+ *
28
+ * Matches on the option's label element (`.euiSelectableListItem__text`), not
29
+ * its accessible name. `append`/`prepend` badges render in a sibling element,
30
+ * so they are excluded. EUI appends screen-reader state text inside the label
31
+ * element (a checked option reads `"<label> . Checked option."`), which always
32
+ * starts with a `.`, so the match allows an optional `.`-prefixed suffix. That
33
+ * boundary is what keeps `selectOption('New York')` from also matching
34
+ * `New York City`: a real longer label continues with a word, not a `.`.
35
+ *
36
+ * This is for lists in their default state. Once you call `search()`, EUI
37
+ * wraps the matched text in `<mark>` and injects highlight markers, so a label
38
+ * match no longer resolves. On a searched list, select via the option's own
39
+ * `data-test-subj`, or drive it through {@link options} directly.
40
+ *
41
+ * Only matches the plain label. A consumer using a custom `renderOption` to
42
+ * add its own content (e.g. a description) inside the option renders that
43
+ * content in the same label element, which breaks the anchored match. Select
44
+ * those options via {@link options} instead (e.g.
45
+ * `options.filter({ hasText: label })`).
46
+ */
47
+ selectOption(label: string): Promise<void>;
48
+ /**
49
+ * Type into the selectable's search box to filter the options. Throws if the
50
+ * selectable is not searchable (no search box rendered).
51
+ */
52
+ search(term: string): Promise<void>;
53
+ }
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.EuiSelectableObject = void 0;
7
+ var _base_object = require("../../base_object");
8
+ var _selectors = require("../../../components/selectable/selectors");
9
+ /*
10
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
11
+ * or more contributor license agreements. Licensed under the Elastic License
12
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
13
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
14
+ * Side Public License, v 1.
15
+ */
16
+
17
+ /**
18
+ * Playwright Component Object for {@link
19
+ * https://eui.elastic.co/docs/components/forms/selection/selectable/ EuiSelectable}.
20
+ *
21
+ * `testSubj` must match the `data-test-subj` set by the consumer on the
22
+ * `<EuiSelectable>` (EUI spreads it onto the `.euiSelectable` root). When the
23
+ * selectable renders inside a popover the consumer opens, pass that popover as
24
+ * `scope` and drive the open/close from the test — the popover is not this
25
+ * component's concern.
26
+ */
27
+ class EuiSelectableObject extends _base_object.BaseObject {
28
+ constructor(scope, testSubj) {
29
+ super(scope, testSubj, _selectors.EuiSelectableSelectors.ROOT_SELECTOR);
30
+ }
31
+
32
+ /**
33
+ * The options currently mounted in the list, as a `Locator` so callers keep
34
+ * Playwright auto-retry for count and content assertions
35
+ * (e.g. `expect(options).toHaveCount(3)`). The list is virtualized, so this is
36
+ * the rendered window — `search()` first to filter the target into view
37
+ * rather than scanning a long list.
38
+ */
39
+ get options() {
40
+ return this.root.getByRole('option');
41
+ }
42
+
43
+ /**
44
+ * Selects the option with the given label. A no-op if it is already selected.
45
+ * EUI reports that as `aria-checked` for a multi-selection list, or
46
+ * `aria-selected` for a single-selection one, so both are checked.
47
+ *
48
+ * Matches on the option's label element (`.euiSelectableListItem__text`), not
49
+ * its accessible name. `append`/`prepend` badges render in a sibling element,
50
+ * so they are excluded. EUI appends screen-reader state text inside the label
51
+ * element (a checked option reads `"<label> . Checked option."`), which always
52
+ * starts with a `.`, so the match allows an optional `.`-prefixed suffix. That
53
+ * boundary is what keeps `selectOption('New York')` from also matching
54
+ * `New York City`: a real longer label continues with a word, not a `.`.
55
+ *
56
+ * This is for lists in their default state. Once you call `search()`, EUI
57
+ * wraps the matched text in `<mark>` and injects highlight markers, so a label
58
+ * match no longer resolves. On a searched list, select via the option's own
59
+ * `data-test-subj`, or drive it through {@link options} directly.
60
+ *
61
+ * Only matches the plain label. A consumer using a custom `renderOption` to
62
+ * add its own content (e.g. a description) inside the option renders that
63
+ * content in the same label element, which breaks the anchored match. Select
64
+ * those options via {@link options} instead (e.g.
65
+ * `options.filter({ hasText: label })`).
66
+ */
67
+ async selectOption(label) {
68
+ const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
69
+ const labelText = new RegExp(`^${escaped}(\\s*\\..*)?$`);
70
+ const option = this.root.getByRole('option').filter({
71
+ has: this.root.page().locator('.euiSelectableListItem__text', {
72
+ hasText: labelText
73
+ })
74
+ });
75
+ const [ariaChecked, ariaSelected] = await Promise.all([option.getAttribute('aria-checked'), option.getAttribute('aria-selected')]);
76
+ if (ariaChecked === 'true' || ariaSelected === 'true') {
77
+ return;
78
+ }
79
+ await option.click();
80
+ }
81
+
82
+ /**
83
+ * Type into the selectable's search box to filter the options. Throws if the
84
+ * selectable is not searchable (no search box rendered).
85
+ */
86
+ async search(term) {
87
+ // `fill()` auto-waits for the search box; if the selectable is not
88
+ // searchable it surfaces a clear locator-timeout on its own.
89
+ await this.root.locator(_selectors.EuiSelectableSelectors.SEARCH_SELECTOR).fill(term);
90
+ }
91
+ }
92
+ exports.EuiSelectableObject = EuiSelectableObject;
@@ -2,3 +2,4 @@ export { EuiComboBoxSelectors } from './components/combo_box/selectors';
2
2
  export { EuiDataGridSelectors } from './components/datagrid/selectors';
3
3
  export { EuiSuperSelectSelectors } from './components/form/super_select/selectors';
4
4
  export { EuiGlobalToastListSelectors } from './components/toast/selectors';
5
+ export { EuiSelectableSelectors } from './components/selectable/selectors';
@@ -21,6 +21,12 @@ Object.defineProperty(exports, "EuiGlobalToastListSelectors", {
21
21
  return _selectors4.EuiGlobalToastListSelectors;
22
22
  }
23
23
  });
24
+ Object.defineProperty(exports, "EuiSelectableSelectors", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _selectors5.EuiSelectableSelectors;
28
+ }
29
+ });
24
30
  Object.defineProperty(exports, "EuiSuperSelectSelectors", {
25
31
  enumerable: true,
26
32
  get: function () {
@@ -30,4 +36,5 @@ Object.defineProperty(exports, "EuiSuperSelectSelectors", {
30
36
  var _selectors = require("./components/combo_box/selectors");
31
37
  var _selectors2 = require("./components/datagrid/selectors");
32
38
  var _selectors3 = require("./components/form/super_select/selectors");
33
- var _selectors4 = require("./components/toast/selectors");
39
+ var _selectors4 = require("./components/toast/selectors");
40
+ var _selectors5 = require("./components/selectable/selectors");
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Stable selectors for
3
+ * {@link https://eui.elastic.co/docs/components/tabular-content/tables/|EuiBasicTable}
4
+ * (also covers `EuiInMemoryTable`, which renders a `EuiBasicTable` underneath).
5
+ * `*_SELECTOR` values are CSS.
6
+ */
7
+ export declare const EuiBasicTableSelectors: {
8
+ /** Root element (the `.euiBasicTable` container carrying the consumer's `data-test-subj`). */
9
+ ROOT_SELECTOR: string;
10
+ /** A data row. Also matches EUI's own empty/error message row — callers must exclude those. */
11
+ ROW_SELECTOR: string;
12
+ /**
13
+ * A field-data column's header cell. `%s` is the column's `field`. EUI appends a
14
+ * positional index (`tableHeaderCell_<field>_<index>`), so this is a prefix match.
15
+ */
16
+ HEADER_CELL_SELECTOR_PREFIX: string;
17
+ };
@@ -0,0 +1,25 @@
1
+ /*
2
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3
+ * or more contributor license agreements. Licensed under the Elastic License
4
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
5
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
6
+ * Side Public License, v 1.
7
+ */
8
+ /**
9
+ * Stable selectors for
10
+ * {@link https://eui.elastic.co/docs/components/tabular-content/tables/|EuiBasicTable}
11
+ * (also covers `EuiInMemoryTable`, which renders a `EuiBasicTable` underneath).
12
+ * `*_SELECTOR` values are CSS.
13
+ */
14
+ export const EuiBasicTableSelectors = {
15
+ /** Root element (the `.euiBasicTable` container carrying the consumer's `data-test-subj`). */
16
+ ROOT_SELECTOR: '.euiBasicTable',
17
+ /** A data row. Also matches EUI's own empty/error message row — callers must exclude those. */
18
+ ROW_SELECTOR: '.euiTableRow',
19
+ /**
20
+ * A field-data column's header cell. `%s` is the column's `field`. EUI appends a
21
+ * positional index (`tableHeaderCell_<field>_<index>`), so this is a prefix match.
22
+ */
23
+ HEADER_CELL_SELECTOR_PREFIX: 'tableHeaderCell_',
24
+ };
25
+ //# sourceMappingURL=selectors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selectors.js","sourceRoot":"","sources":["../../../../src/components/basic_table/selectors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,8FAA8F;IAC9F,aAAa,EAAE,gBAAgB;IAE/B,+FAA+F;IAC/F,YAAY,EAAE,cAAc;IAE5B;;;OAGG;IACH,2BAA2B,EAAE,kBAAkB;CAChD,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Stable selectors for
3
+ * {@link https://eui.elastic.co/docs/components/forms/selection/selectable/|EuiSelectable}.
4
+ * `*_SELECTOR` values are CSS.
5
+ */
6
+ export declare const EuiSelectableSelectors: {
7
+ /** Root element (the `.euiSelectable` container carrying the consumer's `data-test-subj`). */
8
+ ROOT_SELECTOR: string;
9
+ /** The search box, present only when the selectable is `searchable`. */
10
+ SEARCH_SELECTOR: string;
11
+ };
@@ -0,0 +1,19 @@
1
+ /*
2
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3
+ * or more contributor license agreements. Licensed under the Elastic License
4
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
5
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
6
+ * Side Public License, v 1.
7
+ */
8
+ /**
9
+ * Stable selectors for
10
+ * {@link https://eui.elastic.co/docs/components/forms/selection/selectable/|EuiSelectable}.
11
+ * `*_SELECTOR` values are CSS.
12
+ */
13
+ export const EuiSelectableSelectors = {
14
+ /** Root element (the `.euiSelectable` container carrying the consumer's `data-test-subj`). */
15
+ ROOT_SELECTOR: '.euiSelectable',
16
+ /** The search box, present only when the selectable is `searchable`. */
17
+ SEARCH_SELECTOR: '.euiSelectableSearch',
18
+ };
19
+ //# sourceMappingURL=selectors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selectors.js","sourceRoot":"","sources":["../../../../src/components/selectable/selectors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,8FAA8F;IAC9F,aAAa,EAAE,gBAAgB;IAE/B,wEAAwE;IACxE,eAAe,EAAE,sBAAsB;CACxC,CAAC"}
@@ -3,3 +3,5 @@ export { EuiComboBoxObject } from './playwright/components/combo_box/object';
3
3
  export { EuiDataGridObject } from './playwright/components/datagrid/object';
4
4
  export { EuiSuperSelectObject } from './playwright/components/form/super_select/object';
5
5
  export { EuiGlobalToastListObject } from './playwright/components/toast/object';
6
+ export { EuiSelectableObject } from './playwright/components/selectable/object';
7
+ export { EuiBasicTableObject } from './playwright/components/basic_table/object';
package/lib/esm/index.js CHANGED
@@ -10,4 +10,6 @@ export { EuiComboBoxObject } from './playwright/components/combo_box/object';
10
10
  export { EuiDataGridObject } from './playwright/components/datagrid/object';
11
11
  export { EuiSuperSelectObject } from './playwright/components/form/super_select/object';
12
12
  export { EuiGlobalToastListObject } from './playwright/components/toast/object';
13
+ export { EuiSelectableObject } from './playwright/components/selectable/object';
14
+ export { EuiBasicTableObject } from './playwright/components/basic_table/object';
13
15
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAoB,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,yCAAyC,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,kDAAkD,CAAC;AACxF,OAAO,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAoB,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,yCAAyC,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,kDAAkD,CAAC;AACxF,OAAO,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAC;AAChF,OAAO,EAAE,mBAAmB,EAAE,MAAM,2CAA2C,CAAC;AAChF,OAAO,EAAE,mBAAmB,EAAE,MAAM,4CAA4C,CAAC"}
@@ -0,0 +1,50 @@
1
+ import type { Locator } from '@playwright/test';
2
+ import { BaseObject, type ObjectScope } from '../../base_object';
3
+ /**
4
+ * Playwright Component Object for {@link
5
+ * https://eui.elastic.co/docs/components/tabular-content/tables/ EuiBasicTable}
6
+ * (also covers `EuiInMemoryTable`, which renders a `EuiBasicTable` underneath and
7
+ * passes the `data-test-subj` straight through — there is no separate object for it).
8
+ *
9
+ * `testSubj` must match the `data-test-subj` set by the consumer on the
10
+ * `<EuiBasicTable>`/`<EuiInMemoryTable>` (EUI spreads it onto the `.euiBasicTable`
11
+ * root). See the package README for what's deliberately excluded from v1.
12
+ */
13
+ export declare class EuiBasicTableObject extends BaseObject {
14
+ constructor(scope: ObjectScope, testSubj: string);
15
+ /**
16
+ * The table's data rows, as a `Locator` so callers keep Playwright auto-retry
17
+ * for count and content assertions (e.g. `expect(table.rows).toHaveCount(3)`).
18
+ *
19
+ * Excludes EUI's own "no items found" (and error-message) row: when the table
20
+ * is empty, EUI still renders a single `.euiTableRow` whose one cell spans
21
+ * every column (`colSpan` on `EuiTableRowCell`, the `td[colspan]` HTML
22
+ * attribute). Real data rows don't set `colspan` on their cells, so filtering
23
+ * it out is what keeps `toHaveCount(0)` correct on an empty table instead of
24
+ * reading `1`.
25
+ */
26
+ get rows(): Locator;
27
+ /**
28
+ * The cells of the given field-data column, one per row, as a `Locator` for
29
+ * retrying content/count assertions (e.g.
30
+ * `expect(await table.cells('status')).toHaveText(['Running'])`).
31
+ *
32
+ * Resolves the column's position from EUI's own header cell
33
+ * (`tableHeaderCell_<field>_<index>`, matched exactly so a column named e.g.
34
+ * `status` doesn't also match a `status_detail` header) via its native
35
+ * `cellIndex`, then reads the cell at that position in every row — the same
36
+ * positional alignment EUI itself relies on, so it holds regardless of a
37
+ * leading selection-checkbox column. Matches both `<td>` and `<th>`: EUI
38
+ * renders the `rowHeader` column's body cell as a `<th scope="row">` for
39
+ * accessibility, not a `<td>`. Throws if no column with that `field` is
40
+ * rendered.
41
+ *
42
+ * EUI generates a computed column's header test-subj from its `name` using
43
+ * this exact same `tableHeaderCell_<name>_<index>` shape (there's no `field`
44
+ * to key on), so a computed column whose `name` happens to equal another
45
+ * column's `field` can resolve ambiguously — this method can't distinguish
46
+ * the two from the DOM alone. Not expected in practice; call it out if you
47
+ * hit it.
48
+ */
49
+ cells(field: string): Promise<Locator>;
50
+ }
@@ -0,0 +1,76 @@
1
+ /*
2
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3
+ * or more contributor license agreements. Licensed under the Elastic License
4
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
5
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
6
+ * Side Public License, v 1.
7
+ */
8
+ import { BaseObject } from '../../base_object';
9
+ import { EuiBasicTableSelectors } from '../../../components/basic_table/selectors';
10
+ /**
11
+ * Playwright Component Object for {@link
12
+ * https://eui.elastic.co/docs/components/tabular-content/tables/ EuiBasicTable}
13
+ * (also covers `EuiInMemoryTable`, which renders a `EuiBasicTable` underneath and
14
+ * passes the `data-test-subj` straight through — there is no separate object for it).
15
+ *
16
+ * `testSubj` must match the `data-test-subj` set by the consumer on the
17
+ * `<EuiBasicTable>`/`<EuiInMemoryTable>` (EUI spreads it onto the `.euiBasicTable`
18
+ * root). See the package README for what's deliberately excluded from v1.
19
+ */
20
+ export class EuiBasicTableObject extends BaseObject {
21
+ constructor(scope, testSubj) {
22
+ super(scope, testSubj, EuiBasicTableSelectors.ROOT_SELECTOR);
23
+ }
24
+ /**
25
+ * The table's data rows, as a `Locator` so callers keep Playwright auto-retry
26
+ * for count and content assertions (e.g. `expect(table.rows).toHaveCount(3)`).
27
+ *
28
+ * Excludes EUI's own "no items found" (and error-message) row: when the table
29
+ * is empty, EUI still renders a single `.euiTableRow` whose one cell spans
30
+ * every column (`colSpan` on `EuiTableRowCell`, the `td[colspan]` HTML
31
+ * attribute). Real data rows don't set `colspan` on their cells, so filtering
32
+ * it out is what keeps `toHaveCount(0)` correct on an empty table instead of
33
+ * reading `1`.
34
+ */
35
+ get rows() {
36
+ return this.root
37
+ .locator(EuiBasicTableSelectors.ROW_SELECTOR)
38
+ .filter({ hasNot: this.root.page().locator('td[colspan]') });
39
+ }
40
+ /**
41
+ * The cells of the given field-data column, one per row, as a `Locator` for
42
+ * retrying content/count assertions (e.g.
43
+ * `expect(await table.cells('status')).toHaveText(['Running'])`).
44
+ *
45
+ * Resolves the column's position from EUI's own header cell
46
+ * (`tableHeaderCell_<field>_<index>`, matched exactly so a column named e.g.
47
+ * `status` doesn't also match a `status_detail` header) via its native
48
+ * `cellIndex`, then reads the cell at that position in every row — the same
49
+ * positional alignment EUI itself relies on, so it holds regardless of a
50
+ * leading selection-checkbox column. Matches both `<td>` and `<th>`: EUI
51
+ * renders the `rowHeader` column's body cell as a `<th scope="row">` for
52
+ * accessibility, not a `<td>`. Throws if no column with that `field` is
53
+ * rendered.
54
+ *
55
+ * EUI generates a computed column's header test-subj from its `name` using
56
+ * this exact same `tableHeaderCell_<name>_<index>` shape (there's no `field`
57
+ * to key on), so a computed column whose `name` happens to equal another
58
+ * column's `field` can resolve ambiguously — this method can't distinguish
59
+ * the two from the DOM alone. Not expected in practice; call it out if you
60
+ * hit it.
61
+ */
62
+ async cells(field) {
63
+ const escaped = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
64
+ const cellIndex = await this.root.evaluate((tableEl, { prefix, pattern, field: originalField }) => {
65
+ const regex = new RegExp(`^${prefix}${pattern}_\\d+$`);
66
+ const headers = Array.from(tableEl.querySelectorAll('thead th, thead td'));
67
+ const header = headers.find((el) => regex.test(el.getAttribute('data-test-subj') ?? ''));
68
+ if (!header) {
69
+ throw new Error(`EuiBasicTableObject.cells: no column with field "${originalField}" found in this table's header.`);
70
+ }
71
+ return header.cellIndex;
72
+ }, { prefix: EuiBasicTableSelectors.HEADER_CELL_SELECTOR_PREFIX, pattern: escaped, field });
73
+ return this.rows.locator(`:is(td, th):nth-child(${cellIndex + 1})`);
74
+ }
75
+ }
76
+ //# sourceMappingURL=object.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"object.js","sourceRoot":"","sources":["../../../../../src/playwright/components/basic_table/object.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAE,UAAU,EAAoB,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AAEnF;;;;;;;;;GASG;AACH,MAAM,OAAO,mBAAoB,SAAQ,UAAU;IACjD,YAAY,KAAkB,EAAE,QAAgB;QAC9C,KAAK,CAAC,KAAK,EAAE,QAAQ,EAAE,sBAAsB,CAAC,aAAa,CAAC,CAAC;IAC/D,CAAC;IAED;;;;;;;;;;OAUG;IACH,IAAW,IAAI;QACb,OAAO,IAAI,CAAC,IAAI;aACb,OAAO,CAAC,sBAAsB,CAAC,YAAY,CAAC;aAC5C,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;IACjE,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,KAAK,CAAC,KAAK,CAAC,KAAa;QACvB,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;QAC7D,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,CACxC,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,EAAE;YACrD,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,IAAI,MAAM,GAAG,OAAO,QAAQ,CAAC,CAAC;YACvD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAC,CAAC;YAC3E,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CACjC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAChB,CAAC;YACtC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CACb,oDAAoD,aAAa,iCAAiC,CACnG,CAAC;YACJ,CAAC;YACD,OAAO,MAAM,CAAC,SAAS,CAAC;QAC1B,CAAC,EACD,EAAE,MAAM,EAAE,sBAAsB,CAAC,2BAA2B,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CACxF,CAAC;QACF,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,yBAAyB,SAAS,GAAG,CAAC,GAAG,CAAC,CAAC;IACtE,CAAC;CACF"}
@@ -0,0 +1,53 @@
1
+ import type { Locator } from '@playwright/test';
2
+ import { BaseObject, type ObjectScope } from '../../base_object';
3
+ /**
4
+ * Playwright Component Object for {@link
5
+ * https://eui.elastic.co/docs/components/forms/selection/selectable/ EuiSelectable}.
6
+ *
7
+ * `testSubj` must match the `data-test-subj` set by the consumer on the
8
+ * `<EuiSelectable>` (EUI spreads it onto the `.euiSelectable` root). When the
9
+ * selectable renders inside a popover the consumer opens, pass that popover as
10
+ * `scope` and drive the open/close from the test — the popover is not this
11
+ * component's concern.
12
+ */
13
+ export declare class EuiSelectableObject extends BaseObject {
14
+ constructor(scope: ObjectScope, testSubj: string);
15
+ /**
16
+ * The options currently mounted in the list, as a `Locator` so callers keep
17
+ * Playwright auto-retry for count and content assertions
18
+ * (e.g. `expect(options).toHaveCount(3)`). The list is virtualized, so this is
19
+ * the rendered window — `search()` first to filter the target into view
20
+ * rather than scanning a long list.
21
+ */
22
+ get options(): Locator;
23
+ /**
24
+ * Selects the option with the given label. A no-op if it is already selected.
25
+ * EUI reports that as `aria-checked` for a multi-selection list, or
26
+ * `aria-selected` for a single-selection one, so both are checked.
27
+ *
28
+ * Matches on the option's label element (`.euiSelectableListItem__text`), not
29
+ * its accessible name. `append`/`prepend` badges render in a sibling element,
30
+ * so they are excluded. EUI appends screen-reader state text inside the label
31
+ * element (a checked option reads `"<label> . Checked option."`), which always
32
+ * starts with a `.`, so the match allows an optional `.`-prefixed suffix. That
33
+ * boundary is what keeps `selectOption('New York')` from also matching
34
+ * `New York City`: a real longer label continues with a word, not a `.`.
35
+ *
36
+ * This is for lists in their default state. Once you call `search()`, EUI
37
+ * wraps the matched text in `<mark>` and injects highlight markers, so a label
38
+ * match no longer resolves. On a searched list, select via the option's own
39
+ * `data-test-subj`, or drive it through {@link options} directly.
40
+ *
41
+ * Only matches the plain label. A consumer using a custom `renderOption` to
42
+ * add its own content (e.g. a description) inside the option renders that
43
+ * content in the same label element, which breaks the anchored match. Select
44
+ * those options via {@link options} instead (e.g.
45
+ * `options.filter({ hasText: label })`).
46
+ */
47
+ selectOption(label: string): Promise<void>;
48
+ /**
49
+ * Type into the selectable's search box to filter the options. Throws if the
50
+ * selectable is not searchable (no search box rendered).
51
+ */
52
+ search(term: string): Promise<void>;
53
+ }
@@ -0,0 +1,83 @@
1
+ /*
2
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3
+ * or more contributor license agreements. Licensed under the Elastic License
4
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
5
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
6
+ * Side Public License, v 1.
7
+ */
8
+ import { BaseObject } from '../../base_object';
9
+ import { EuiSelectableSelectors } from '../../../components/selectable/selectors';
10
+ /**
11
+ * Playwright Component Object for {@link
12
+ * https://eui.elastic.co/docs/components/forms/selection/selectable/ EuiSelectable}.
13
+ *
14
+ * `testSubj` must match the `data-test-subj` set by the consumer on the
15
+ * `<EuiSelectable>` (EUI spreads it onto the `.euiSelectable` root). When the
16
+ * selectable renders inside a popover the consumer opens, pass that popover as
17
+ * `scope` and drive the open/close from the test — the popover is not this
18
+ * component's concern.
19
+ */
20
+ export class EuiSelectableObject extends BaseObject {
21
+ constructor(scope, testSubj) {
22
+ super(scope, testSubj, EuiSelectableSelectors.ROOT_SELECTOR);
23
+ }
24
+ /**
25
+ * The options currently mounted in the list, as a `Locator` so callers keep
26
+ * Playwright auto-retry for count and content assertions
27
+ * (e.g. `expect(options).toHaveCount(3)`). The list is virtualized, so this is
28
+ * the rendered window — `search()` first to filter the target into view
29
+ * rather than scanning a long list.
30
+ */
31
+ get options() {
32
+ return this.root.getByRole('option');
33
+ }
34
+ /**
35
+ * Selects the option with the given label. A no-op if it is already selected.
36
+ * EUI reports that as `aria-checked` for a multi-selection list, or
37
+ * `aria-selected` for a single-selection one, so both are checked.
38
+ *
39
+ * Matches on the option's label element (`.euiSelectableListItem__text`), not
40
+ * its accessible name. `append`/`prepend` badges render in a sibling element,
41
+ * so they are excluded. EUI appends screen-reader state text inside the label
42
+ * element (a checked option reads `"<label> . Checked option."`), which always
43
+ * starts with a `.`, so the match allows an optional `.`-prefixed suffix. That
44
+ * boundary is what keeps `selectOption('New York')` from also matching
45
+ * `New York City`: a real longer label continues with a word, not a `.`.
46
+ *
47
+ * This is for lists in their default state. Once you call `search()`, EUI
48
+ * wraps the matched text in `<mark>` and injects highlight markers, so a label
49
+ * match no longer resolves. On a searched list, select via the option's own
50
+ * `data-test-subj`, or drive it through {@link options} directly.
51
+ *
52
+ * Only matches the plain label. A consumer using a custom `renderOption` to
53
+ * add its own content (e.g. a description) inside the option renders that
54
+ * content in the same label element, which breaks the anchored match. Select
55
+ * those options via {@link options} instead (e.g.
56
+ * `options.filter({ hasText: label })`).
57
+ */
58
+ async selectOption(label) {
59
+ const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
60
+ const labelText = new RegExp(`^${escaped}(\\s*\\..*)?$`);
61
+ const option = this.root
62
+ .getByRole('option')
63
+ .filter({ has: this.root.page().locator('.euiSelectableListItem__text', { hasText: labelText }) });
64
+ const [ariaChecked, ariaSelected] = await Promise.all([
65
+ option.getAttribute('aria-checked'),
66
+ option.getAttribute('aria-selected'),
67
+ ]);
68
+ if (ariaChecked === 'true' || ariaSelected === 'true') {
69
+ return;
70
+ }
71
+ await option.click();
72
+ }
73
+ /**
74
+ * Type into the selectable's search box to filter the options. Throws if the
75
+ * selectable is not searchable (no search box rendered).
76
+ */
77
+ async search(term) {
78
+ // `fill()` auto-waits for the search box; if the selectable is not
79
+ // searchable it surfaces a clear locator-timeout on its own.
80
+ await this.root.locator(EuiSelectableSelectors.SEARCH_SELECTOR).fill(term);
81
+ }
82
+ }
83
+ //# sourceMappingURL=object.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"object.js","sourceRoot":"","sources":["../../../../../src/playwright/components/selectable/object.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAE,UAAU,EAAoB,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAC;AAElF;;;;;;;;;GASG;AACH,MAAM,OAAO,mBAAoB,SAAQ,UAAU;IACjD,YAAY,KAAkB,EAAE,QAAgB;QAC9C,KAAK,CAAC,KAAK,EAAE,QAAQ,EAAE,sBAAsB,CAAC,aAAa,CAAC,CAAC;IAC/D,CAAC;IAED;;;;;;OAMG;IACH,IAAW,OAAO;QAChB,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACvC,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,KAAK,CAAC,YAAY,CAAC,KAAa;QAC9B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;QAC7D,MAAM,SAAS,GAAG,IAAI,MAAM,CAAC,IAAI,OAAO,eAAe,CAAC,CAAC;QACzD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI;aACrB,SAAS,CAAC,QAAQ,CAAC;aACnB,MAAM,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,8BAA8B,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC;QACrG,MAAM,CAAC,WAAW,EAAE,YAAY,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACpD,MAAM,CAAC,YAAY,CAAC,cAAc,CAAC;YACnC,MAAM,CAAC,YAAY,CAAC,eAAe,CAAC;SACrC,CAAC,CAAC;QACH,IAAI,WAAW,KAAK,MAAM,IAAI,YAAY,KAAK,MAAM,EAAE,CAAC;YACtD,OAAO;QACT,CAAC;QACD,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,MAAM,CAAC,IAAY;QACvB,mEAAmE;QACnE,6DAA6D;QAC7D,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,sBAAsB,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7E,CAAC;CACF"}
@@ -2,3 +2,4 @@ export { EuiComboBoxSelectors } from './components/combo_box/selectors';
2
2
  export { EuiDataGridSelectors } from './components/datagrid/selectors';
3
3
  export { EuiSuperSelectSelectors } from './components/form/super_select/selectors';
4
4
  export { EuiGlobalToastListSelectors } from './components/toast/selectors';
5
+ export { EuiSelectableSelectors } from './components/selectable/selectors';
@@ -9,4 +9,5 @@ export { EuiComboBoxSelectors } from './components/combo_box/selectors';
9
9
  export { EuiDataGridSelectors } from './components/datagrid/selectors';
10
10
  export { EuiSuperSelectSelectors } from './components/form/super_select/selectors';
11
11
  export { EuiGlobalToastListSelectors } from './components/toast/selectors';
12
+ export { EuiSelectableSelectors } from './components/selectable/selectors';
12
13
  //# sourceMappingURL=selectors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"selectors.js","sourceRoot":"","sources":["../../src/selectors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAC;AACnF,OAAO,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC"}
1
+ {"version":3,"file":"selectors.js","sourceRoot":"","sources":["../../src/selectors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAC;AACnF,OAAO,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elastic/eui-test-helpers",
3
- "version": "1.3.0",
3
+ "version": "1.4.0-snapshot.1787665877015",
4
4
  "description": "A library of EUI test helpers for RTL, Cypress and Scout",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "repository": {
@@ -57,5 +57,6 @@
57
57
  "!**/*.spec.js.map",
58
58
  "README.md",
59
59
  "licenses"
60
- ]
60
+ ],
61
+ "stableVersion": "1.4.0"
61
62
  }
@@ -0,0 +1,30 @@
1
+ # EuiBasicTableObject
2
+
3
+ Playwright Component Object for [EuiBasicTable](https://eui.elastic.co/docs/components/tabular-content/tables/). Also covers `EuiInMemoryTable`, which renders a `EuiBasicTable` underneath and passes the `data-test-subj` straight through — there is no separate object for it.
4
+
5
+ ## Usage
6
+
7
+ ```ts
8
+ import { EuiBasicTableObject } from '@elastic/eui-test-helpers';
9
+
10
+ const table = new EuiBasicTableObject(page, 'myTable');
11
+ expect(await table.rows).toHaveCount(3);
12
+ expect(await table.cells('status')).toHaveText(['Running', 'Finished', 'Archived']);
13
+ ```
14
+
15
+ Set `data-test-subj` on the `<EuiBasicTable>`/`<EuiInMemoryTable>` (EUI spreads it onto the `.euiBasicTable` root, which the component-type guard verifies).
16
+
17
+ ## API
18
+
19
+ | Member | Description |
20
+ |---|---|
21
+ | `rows` | `Locator` for the table's data rows, excluding EUI's own "no items found"/error-message row. |
22
+ | `cells(field)` | `Locator` for a field-data column's cells across all rows, resolved from EUI's own header `data-test-subj`. |
23
+
24
+ `rows` excludes EUI's empty/error message row by filtering out any row whose cell carries a `colspan` — real data rows don't set one, EUI's own placeholder row always does (it spans every column).
25
+
26
+ `cells(field)` resolves the column's position from the header cell EUI renders as `tableHeaderCell_<field>_<index>` (matched exactly, so a column named `status` won't also match a `status_detail` header), then reads the cell at that position in every row. This holds regardless of a leading selection-checkbox column, since the checkbox column is a real cell in both the header and body rows, and it matches both `<td>` and `<th>` since EUI renders a `rowHeader` column's body cell as `<th scope="row">`. Throws if no column with that `field` is rendered.
27
+
28
+ ## Deliberately excluded
29
+
30
+ Row actions (consumer-supplied popovers/buttons), sorting, and pagination are not covered. They are either app-specific wiring rather than EUI-internal DOM knowledge, or had no evidenced consumer need at the time this was written. Open an issue with a concrete use case if you need one of them.
@@ -0,0 +1,27 @@
1
+ # EuiSelectableObject
2
+
3
+ Playwright Component Object for [EuiSelectable](https://eui.elastic.co/docs/components/forms/selection/selectable/).
4
+
5
+ ## Usage
6
+
7
+ ```ts
8
+ import { EuiSelectableObject } from '@elastic/eui-test-helpers';
9
+
10
+ const selectable = new EuiSelectableObject(page, 'mySelectable');
11
+ await selectable.search('logs');
12
+ await selectable.selectOption('logs-*');
13
+ ```
14
+
15
+ Set `data-test-subj` on the `<EuiSelectable>` (EUI spreads it onto the `.euiSelectable` root, which the component-type guard verifies). When the selectable renders inside a popover, pass the popover as the `scope` and open or close it from your test. The popover is not this component's concern.
16
+
17
+ ## API
18
+
19
+ | Member | Description |
20
+ |---|---|
21
+ | `options` | `Locator` for the rendered options, keeping Playwright auto-retry for count and content assertions. The list is virtualized, so `search()` first rather than scanning a long list. |
22
+ | `selectOption(label)` | Selects the option with the given label. A no-op if it is already checked. |
23
+ | `search(term)` | Types into the search box. Throws if the selectable is not searchable. |
24
+
25
+ `selectOption(label)` matches the label element and suits lists in their default state. It tolerates the screen-reader state text EUI appends (a checked option reads `<label> . Checked option.`) and `append` badges, and it will not match a different option whose label merely starts with the same text. Once you call `search()`, EUI injects highlight markers into the option's label, so a label match no longer resolves. On a searched list, select via the option's own `data-test-subj` or drive it through `options` directly.
26
+
27
+ It also only matches the plain label. A consumer using a custom `renderOption` to add its own content (e.g. a description) inside the option renders that content in the same label element, which breaks the anchored match. Select those options via `options` instead (e.g. `options.filter({ hasText: label })`).