@rickcedwhat/playwright-smart-table 6.2.0 → 6.3.1
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 +2 -2
- package/dist/engine/rowFinder.d.ts +7 -6
- package/dist/engine/rowFinder.js +49 -17
- package/dist/filterEngine.d.ts +2 -2
- package/dist/filterEngine.js +15 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/plugins.d.ts +7 -5
- package/dist/smartRow.d.ts +1 -1
- package/dist/smartRow.js +131 -53
- package/dist/strategies/columns.d.ts +18 -2
- package/dist/strategies/glide/columns.d.ts +8 -10
- package/dist/strategies/glide/columns.js +24 -23
- package/dist/strategies/glide.d.ts +7 -5
- package/dist/strategies/glide.js +7 -1
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +39 -14
- package/dist/types.d.ts +36 -15
- package/dist/useTable.d.ts +1 -1
- package/dist/useTable.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Production-ready table testing for Playwright with smart column-aware locators.**
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@rickcedwhat/playwright-smart-table)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
8
|
---
|
|
@@ -84,7 +84,7 @@ const allActive = await table.findRows({ Status: 'Active' });
|
|
|
84
84
|
- 📄 **Auto-Pagination** - Search across all pages automatically
|
|
85
85
|
- 🔍 **Column-Aware Access** - Access cells by column name
|
|
86
86
|
- 🛠️ **Debug Mode** - Visual debugging with slow motion and logging
|
|
87
|
-
- 🔌 **Extensible Strategies** - Support any table implementation
|
|
87
|
+
- 🔌 **[Extensible Strategies](docs/concepts/strategies.md)** - Support any table implementation
|
|
88
88
|
- 💪 **Type-Safe** - Full TypeScript support
|
|
89
89
|
- 🚀 **Production-Ready** - Battle-tested in real-world applications
|
|
90
90
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Locator, Page } from '@playwright/test';
|
|
2
|
-
import { FinalTableConfig, Selector, SmartRow } from '../types';
|
|
2
|
+
import { FinalTableConfig, Selector, SmartRow, FilterValue } from '../types';
|
|
3
3
|
import { FilterEngine } from '../filterEngine';
|
|
4
4
|
import { TableMapper } from './tableMapper';
|
|
5
5
|
import { SmartRowArray } from '../utils/smartRowArray';
|
|
@@ -12,15 +12,16 @@ export declare class RowFinder<T = any> {
|
|
|
12
12
|
private resolve;
|
|
13
13
|
constructor(rootLocator: Locator, config: FinalTableConfig, resolve: (item: Selector, parent: Locator | Page) => Locator, filterEngine: FilterEngine, tableMapper: TableMapper, makeSmartRow: (loc: Locator, map: Map<string, number>, index: number) => SmartRow<T>);
|
|
14
14
|
private log;
|
|
15
|
-
findRow(filters: Record<string,
|
|
15
|
+
findRow(filters: Record<string, FilterValue>, options?: {
|
|
16
16
|
exact?: boolean;
|
|
17
17
|
maxPages?: number;
|
|
18
18
|
}): Promise<SmartRow<T>>;
|
|
19
|
-
findRows<
|
|
20
|
-
asJSON?: boolean;
|
|
21
|
-
}>(filters: Partial<T> | Record<string, string | RegExp | number>, options?: {
|
|
19
|
+
findRows(filtersOrOptions?: (Partial<T> | Record<string, FilterValue>) & ({
|
|
22
20
|
exact?: boolean;
|
|
23
21
|
maxPages?: number;
|
|
24
|
-
}
|
|
22
|
+
}), legacyOptions?: {
|
|
23
|
+
exact?: boolean;
|
|
24
|
+
maxPages?: number;
|
|
25
|
+
}): Promise<SmartRowArray<T>>;
|
|
25
26
|
private findRowLocator;
|
|
26
27
|
}
|
package/dist/engine/rowFinder.js
CHANGED
|
@@ -8,6 +8,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
12
|
+
var t = {};
|
|
13
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
14
|
+
t[p] = s[p];
|
|
15
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
16
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
17
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
18
|
+
t[p[i]] = s[p[i]];
|
|
19
|
+
}
|
|
20
|
+
return t;
|
|
21
|
+
};
|
|
11
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
23
|
exports.RowFinder = void 0;
|
|
13
24
|
const debugUtils_1 = require("../utils/debugUtils");
|
|
@@ -42,21 +53,48 @@ class RowFinder {
|
|
|
42
53
|
return this.makeSmartRow(sentinel, yield this.tableMapper.getMap(), 0);
|
|
43
54
|
});
|
|
44
55
|
}
|
|
45
|
-
findRows(
|
|
56
|
+
findRows(filtersOrOptions,
|
|
57
|
+
// Deprecated: verify legacy usage pattern support
|
|
58
|
+
legacyOptions) {
|
|
46
59
|
return __awaiter(this, void 0, void 0, function* () {
|
|
60
|
+
// Detect argument pattern:
|
|
61
|
+
// Pattern A: findRows({ Name: 'Alice' }, { maxPages: 5 })
|
|
62
|
+
// Pattern B: findRows({ maxPages: 5 }) <-- No filters, just options
|
|
63
|
+
// Pattern C: findRows({ Name: 'Alice' }) <-- Only filters
|
|
47
64
|
var _a, _b;
|
|
65
|
+
let filters = {};
|
|
66
|
+
let options = {};
|
|
67
|
+
if (legacyOptions) {
|
|
68
|
+
// Pattern A
|
|
69
|
+
filters = filtersOrOptions;
|
|
70
|
+
options = legacyOptions;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// Pattern B or C
|
|
74
|
+
// We need to separate unknown keys (filters) from known options (exact, maxPages)
|
|
75
|
+
// However, filtersOrOptions can be null/undefined
|
|
76
|
+
if (filtersOrOptions) {
|
|
77
|
+
const _c = filtersOrOptions, { exact, maxPages } = _c, rest = __rest(_c, ["exact", "maxPages"]);
|
|
78
|
+
options = { exact, maxPages };
|
|
79
|
+
filters = rest;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
48
82
|
const map = yield this.tableMapper.getMap();
|
|
49
83
|
const allRows = [];
|
|
50
|
-
const effectiveMaxPages = (_b = (_a = options
|
|
84
|
+
const effectiveMaxPages = (_b = (_a = options.maxPages) !== null && _a !== void 0 ? _a : this.config.maxPages) !== null && _b !== void 0 ? _b : Infinity;
|
|
51
85
|
let pageCount = 0;
|
|
52
86
|
const collectMatches = () => __awaiter(this, void 0, void 0, function* () {
|
|
53
87
|
var _a, _b;
|
|
88
|
+
// ... logic ...
|
|
54
89
|
let rowLocators = this.resolve(this.config.rowSelector, this.rootLocator);
|
|
55
|
-
|
|
90
|
+
// Only apply filters if we have them
|
|
91
|
+
if (Object.keys(filters).length > 0) {
|
|
92
|
+
rowLocators = this.filterEngine.applyFilters(rowLocators, filters, map, (_a = options.exact) !== null && _a !== void 0 ? _a : false, this.rootLocator.page());
|
|
93
|
+
}
|
|
56
94
|
const currentRows = yield rowLocators.all();
|
|
57
95
|
const isRowLoading = (_b = this.config.strategies.loading) === null || _b === void 0 ? void 0 : _b.isRowLoading;
|
|
58
96
|
for (let i = 0; i < currentRows.length; i++) {
|
|
59
|
-
const smartRow = this.makeSmartRow(currentRows[i], map, i);
|
|
97
|
+
const smartRow = this.makeSmartRow(currentRows[i], map, allRows.length + i);
|
|
60
98
|
if (isRowLoading && (yield isRowLoading(smartRow)))
|
|
61
99
|
continue;
|
|
62
100
|
allRows.push(smartRow);
|
|
@@ -64,31 +102,25 @@ class RowFinder {
|
|
|
64
102
|
});
|
|
65
103
|
// Scan first page
|
|
66
104
|
yield collectMatches();
|
|
67
|
-
// Pagination Loop
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// Wait, loop condition `pageCount < effectiveMaxPages`. If maxPages=1, 0 < 1 is true.
|
|
72
|
-
// We paginate AFTER first scan.
|
|
73
|
-
// If maxPages=1, we should NOT paginate.
|
|
74
|
-
if (effectiveMaxPages <= 1)
|
|
75
|
-
break;
|
|
105
|
+
// Pagination Loop - Corrected logic
|
|
106
|
+
// We always scan at least 1 page.
|
|
107
|
+
// If maxPages > 1, and we have a pagination strategy, we try to go next.
|
|
108
|
+
while (pageCount < effectiveMaxPages - 1 && this.config.strategies.pagination) {
|
|
76
109
|
const context = {
|
|
77
110
|
root: this.rootLocator,
|
|
78
111
|
config: this.config,
|
|
79
112
|
resolve: this.resolve,
|
|
80
113
|
page: this.rootLocator.page()
|
|
81
114
|
};
|
|
115
|
+
// Check if we should stop? (e.g. if we found enough rows? No, findRows finds ALL)
|
|
82
116
|
const paginationResult = yield this.config.strategies.pagination(context);
|
|
83
|
-
const didPaginate = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
|
|
117
|
+
const didPaginate = yield (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
|
|
84
118
|
if (!didPaginate)
|
|
85
119
|
break;
|
|
86
120
|
pageCount++;
|
|
121
|
+
// Wait for reload logic if needed? Usually pagination handles it.
|
|
87
122
|
yield collectMatches();
|
|
88
123
|
}
|
|
89
|
-
if (options === null || options === void 0 ? void 0 : options.asJSON) {
|
|
90
|
-
return Promise.all(allRows.map(r => r.toJSON()));
|
|
91
|
-
}
|
|
92
124
|
return (0, smartRowArray_1.createSmartRowArray)(allRows);
|
|
93
125
|
});
|
|
94
126
|
}
|
package/dist/filterEngine.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Locator, Page } from "@playwright/test";
|
|
2
|
-
import { FinalTableConfig } from "./types";
|
|
2
|
+
import { FinalTableConfig, FilterValue } from "./types";
|
|
3
3
|
export declare class FilterEngine {
|
|
4
4
|
private config;
|
|
5
5
|
private resolve;
|
|
@@ -7,5 +7,5 @@ export declare class FilterEngine {
|
|
|
7
7
|
/**
|
|
8
8
|
* Applies filters to a set of rows.
|
|
9
9
|
*/
|
|
10
|
-
applyFilters(baseRows: Locator, filters: Record<string,
|
|
10
|
+
applyFilters(baseRows: Locator, filters: Record<string, FilterValue>, map: Map<string, number>, exact: boolean, page: Page): Locator;
|
|
11
11
|
}
|
package/dist/filterEngine.js
CHANGED
|
@@ -20,7 +20,7 @@ class FilterEngine {
|
|
|
20
20
|
if (colIndex === undefined) {
|
|
21
21
|
throw new Error((0, stringUtils_1.buildColumnNotFoundError)(colName, Array.from(map.keys())));
|
|
22
22
|
}
|
|
23
|
-
const filterVal =
|
|
23
|
+
const filterVal = value;
|
|
24
24
|
// Use strategy if provided (For future: configured filter strategies)
|
|
25
25
|
// But for now, we implement the default logic or use custom if we add it to config later
|
|
26
26
|
// Default Filter Logic
|
|
@@ -29,9 +29,20 @@ class FilterEngine {
|
|
|
29
29
|
// filter({ has: ... }) checks if the row *contains* the matching cell.
|
|
30
30
|
// But we need to be specific about WHICH cell.
|
|
31
31
|
// Locator filtering by `has: locator.nth(index)` works if `locator` search is relative to the row.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
const targetCell = cellTemplate.nth(colIndex);
|
|
33
|
+
if (typeof filterVal === 'function') {
|
|
34
|
+
// Locator-based filter: (cell) => cell.locator(...)
|
|
35
|
+
filtered = filtered.filter({
|
|
36
|
+
has: filterVal(targetCell)
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
// Text-based filter
|
|
41
|
+
const textVal = typeof filterVal === 'number' ? String(filterVal) : filterVal;
|
|
42
|
+
filtered = filtered.filter({
|
|
43
|
+
has: targetCell.getByText(textVal, { exact }),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
35
46
|
}
|
|
36
47
|
return filtered;
|
|
37
48
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -16,3 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./useTable"), exports);
|
|
18
18
|
__exportStar(require("./types"), exports);
|
|
19
|
+
__exportStar(require("./plugins"), exports);
|
package/dist/plugins.d.ts
CHANGED
|
@@ -16,11 +16,13 @@ export declare const Plugins: {
|
|
|
16
16
|
selector?: string;
|
|
17
17
|
scrollAmount?: number;
|
|
18
18
|
}) => Promise<string[]>;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
navigation: {
|
|
20
|
+
goUp: (context: import("./types").StrategyContext) => Promise<void>;
|
|
21
|
+
goDown: (context: import("./types").StrategyContext) => Promise<void>;
|
|
22
|
+
goLeft: (context: import("./types").StrategyContext) => Promise<void>;
|
|
23
|
+
goRight: (context: import("./types").StrategyContext) => Promise<void>;
|
|
24
|
+
goHome: (context: import("./types").StrategyContext) => Promise<void>;
|
|
25
|
+
};
|
|
24
26
|
getCellLocator: ({ row, columnIndex }: any) => any;
|
|
25
27
|
getActiveCell: ({ page }: any) => Promise<{
|
|
26
28
|
rowIndex: number;
|
package/dist/smartRow.d.ts
CHANGED
|
@@ -4,4 +4,4 @@ import { SmartRow as SmartRowType, FinalTableConfig, TableResult } from './types
|
|
|
4
4
|
* Factory to create a SmartRow by extending a Playwright Locator.
|
|
5
5
|
* We avoid Class/Proxy to ensure full compatibility with Playwright's expect(locator) matchers.
|
|
6
6
|
*/
|
|
7
|
-
export declare const createSmartRow: <T = any>(rowLocator: Locator, map: Map<string, number>, rowIndex: number | undefined, config: FinalTableConfig
|
|
7
|
+
export declare const createSmartRow: <T = any>(rowLocator: Locator, map: Map<string, number>, rowIndex: number | undefined, config: FinalTableConfig<T>, rootLocator: Locator, resolve: (item: any, parent: Locator | Page) => Locator, table: TableResult<T> | null) => SmartRowType<T>;
|
package/dist/smartRow.js
CHANGED
|
@@ -13,6 +13,99 @@ exports.createSmartRow = void 0;
|
|
|
13
13
|
const fill_1 = require("./strategies/fill");
|
|
14
14
|
const stringUtils_1 = require("./utils/stringUtils");
|
|
15
15
|
const debugUtils_1 = require("./utils/debugUtils");
|
|
16
|
+
/**
|
|
17
|
+
* Internal helper to navigate to a cell with active cell optimization.
|
|
18
|
+
* Uses navigation primitives (goUp, goDown, goLeft, goRight, goHome) for orchestration.
|
|
19
|
+
* Falls back to cellNavigation for backward compatibility.
|
|
20
|
+
* Returns the target cell locator after navigation.
|
|
21
|
+
*/
|
|
22
|
+
const _navigateToCell = (params) => __awaiter(void 0, void 0, void 0, function* () {
|
|
23
|
+
const { config, rootLocator, page, resolve, column, index, rowLocator, rowIndex } = params;
|
|
24
|
+
// Get active cell if strategy is available
|
|
25
|
+
let activeCell = null;
|
|
26
|
+
if (config.strategies.getActiveCell) {
|
|
27
|
+
activeCell = yield config.strategies.getActiveCell({
|
|
28
|
+
config,
|
|
29
|
+
root: rootLocator,
|
|
30
|
+
page,
|
|
31
|
+
resolve
|
|
32
|
+
});
|
|
33
|
+
// Optimization: Check if we are ALREADY at the target cell
|
|
34
|
+
if (activeCell && activeCell.rowIndex === rowIndex && activeCell.columnIndex === index) {
|
|
35
|
+
// Skip navigation - we're already there!
|
|
36
|
+
return activeCell.locator;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const context = { config, root: rootLocator, page, resolve };
|
|
40
|
+
// Use navigation primitives if available
|
|
41
|
+
if (config.strategies.navigation) {
|
|
42
|
+
const nav = config.strategies.navigation;
|
|
43
|
+
if (typeof rowIndex !== 'number') {
|
|
44
|
+
throw new Error('Row index is required for navigation');
|
|
45
|
+
}
|
|
46
|
+
// Determine starting position
|
|
47
|
+
let startRow = 0;
|
|
48
|
+
let startCol = 0;
|
|
49
|
+
if (activeCell && activeCell.rowIndex >= 0 && activeCell.columnIndex >= 0) {
|
|
50
|
+
// Use current position
|
|
51
|
+
startRow = activeCell.rowIndex;
|
|
52
|
+
startCol = activeCell.columnIndex;
|
|
53
|
+
}
|
|
54
|
+
else if (nav.goHome) {
|
|
55
|
+
// Reset to top-left
|
|
56
|
+
yield nav.goHome(context);
|
|
57
|
+
}
|
|
58
|
+
// Calculate movement needed
|
|
59
|
+
const rowDiff = rowIndex - startRow;
|
|
60
|
+
const colDiff = index - startCol;
|
|
61
|
+
// Navigate vertically
|
|
62
|
+
for (let i = 0; i < Math.abs(rowDiff); i++) {
|
|
63
|
+
if (rowDiff > 0 && nav.goDown) {
|
|
64
|
+
yield nav.goDown(context);
|
|
65
|
+
}
|
|
66
|
+
else if (rowDiff < 0 && nav.goUp) {
|
|
67
|
+
yield nav.goUp(context);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Navigate horizontally
|
|
71
|
+
for (let i = 0; i < Math.abs(colDiff); i++) {
|
|
72
|
+
if (colDiff > 0 && nav.goRight) {
|
|
73
|
+
yield nav.goRight(context);
|
|
74
|
+
}
|
|
75
|
+
else if (colDiff < 0 && nav.goLeft) {
|
|
76
|
+
yield nav.goLeft(context);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
yield page.waitForTimeout(50);
|
|
80
|
+
}
|
|
81
|
+
else if (config.strategies.cellNavigation) {
|
|
82
|
+
// Fallback to legacy cellNavigation strategy
|
|
83
|
+
yield config.strategies.cellNavigation({
|
|
84
|
+
config,
|
|
85
|
+
root: rootLocator,
|
|
86
|
+
page,
|
|
87
|
+
resolve,
|
|
88
|
+
column,
|
|
89
|
+
index,
|
|
90
|
+
rowLocator,
|
|
91
|
+
rowIndex,
|
|
92
|
+
activeCell
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// Get the active cell locator after navigation (for virtualized tables)
|
|
96
|
+
if (config.strategies.getActiveCell) {
|
|
97
|
+
const updatedActiveCell = yield config.strategies.getActiveCell({
|
|
98
|
+
config,
|
|
99
|
+
root: rootLocator,
|
|
100
|
+
page,
|
|
101
|
+
resolve
|
|
102
|
+
});
|
|
103
|
+
if (updatedActiveCell) {
|
|
104
|
+
return updatedActiveCell.locator;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
});
|
|
16
109
|
/**
|
|
17
110
|
* Factory to create a SmartRow by extending a Playwright Locator.
|
|
18
111
|
* We avoid Class/Proxy to ensure full compatibility with Playwright's expect(locator) matchers.
|
|
@@ -39,13 +132,23 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
39
132
|
return resolve(config.cellSelector, rowLocator).nth(idx);
|
|
40
133
|
};
|
|
41
134
|
smart.toJSON = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
135
|
+
var _a;
|
|
42
136
|
const result = {};
|
|
43
137
|
const page = rootLocator.page();
|
|
44
138
|
for (const [col, idx] of map.entries()) {
|
|
45
139
|
if ((options === null || options === void 0 ? void 0 : options.columns) && !options.columns.includes(col)) {
|
|
46
140
|
continue;
|
|
47
141
|
}
|
|
48
|
-
//
|
|
142
|
+
// Check if we have a data mapper for this column
|
|
143
|
+
const mapper = (_a = config.dataMapper) === null || _a === void 0 ? void 0 : _a[col];
|
|
144
|
+
if (mapper) {
|
|
145
|
+
// Use custom mapper
|
|
146
|
+
// Ensure we have the cell first (same navigation logic)
|
|
147
|
+
// ... wait, the navigation logic below assumes we need to navigate.
|
|
148
|
+
// If we have a mapper, we still need the cell locator.
|
|
149
|
+
// Let's reuse the navigation logic to get targetCell
|
|
150
|
+
}
|
|
151
|
+
// --- Navigation Logic Start ---
|
|
49
152
|
const cell = config.strategies.getCellLocator
|
|
50
153
|
? config.strategies.getCellLocator({
|
|
51
154
|
row: rowLocator,
|
|
@@ -56,59 +159,34 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
56
159
|
})
|
|
57
160
|
: resolve(config.cellSelector, rowLocator).nth(idx);
|
|
58
161
|
let targetCell = cell;
|
|
59
|
-
// Check if cell exists
|
|
60
162
|
const count = yield cell.count();
|
|
61
163
|
if (count === 0) {
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
resolve
|
|
69
|
-
});
|
|
70
|
-
if (active && active.rowIndex === rowIndex && active.columnIndex === idx) {
|
|
71
|
-
if (config.debug)
|
|
72
|
-
console.log(`[SmartRow] Already at target cell (r:${active.rowIndex}, c:${active.columnIndex}), skipping navigation.`);
|
|
73
|
-
targetCell = active.locator;
|
|
74
|
-
// Skip navigation and go to reading text
|
|
75
|
-
const text = yield targetCell.innerText();
|
|
76
|
-
result[col] = (text || '').trim();
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
// Cell doesn't exist - navigate to it
|
|
81
|
-
if (config.debug) {
|
|
82
|
-
console.log(`[SmartRow.toJSON] Cell not found for column "${col}" (index ${idx}), navigating...`);
|
|
83
|
-
}
|
|
84
|
-
yield config.strategies.cellNavigation({
|
|
85
|
-
config: config,
|
|
86
|
-
root: rootLocator,
|
|
87
|
-
page: page,
|
|
88
|
-
resolve: resolve,
|
|
164
|
+
// Cell not in DOM (virtualized) - navigate to it
|
|
165
|
+
const navigatedCell = yield _navigateToCell({
|
|
166
|
+
config,
|
|
167
|
+
rootLocator,
|
|
168
|
+
page,
|
|
169
|
+
resolve,
|
|
89
170
|
column: col,
|
|
90
171
|
index: idx,
|
|
91
|
-
rowLocator
|
|
92
|
-
rowIndex
|
|
172
|
+
rowLocator,
|
|
173
|
+
rowIndex
|
|
93
174
|
});
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const activeCell = yield config.strategies.getActiveCell({
|
|
97
|
-
config,
|
|
98
|
-
root: rootLocator,
|
|
99
|
-
page,
|
|
100
|
-
resolve
|
|
101
|
-
});
|
|
102
|
-
if (activeCell) {
|
|
103
|
-
if (config.debug) {
|
|
104
|
-
console.log(`[SmartRow.toJSON] switching to active cell locator (r:${activeCell.rowIndex}, c:${activeCell.columnIndex})`);
|
|
105
|
-
}
|
|
106
|
-
targetCell = activeCell.locator;
|
|
107
|
-
}
|
|
175
|
+
if (navigatedCell) {
|
|
176
|
+
targetCell = navigatedCell;
|
|
108
177
|
}
|
|
109
178
|
}
|
|
110
|
-
|
|
111
|
-
|
|
179
|
+
// --- Navigation Logic End ---
|
|
180
|
+
if (mapper) {
|
|
181
|
+
// Apply mapper
|
|
182
|
+
const mappedValue = yield mapper(targetCell);
|
|
183
|
+
result[col] = mappedValue;
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
// Default string extraction
|
|
187
|
+
const text = yield targetCell.innerText();
|
|
188
|
+
result[col] = (text || '').trim();
|
|
189
|
+
}
|
|
112
190
|
}
|
|
113
191
|
return result;
|
|
114
192
|
});
|
|
@@ -121,15 +199,15 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
121
199
|
if (colIdx === undefined) {
|
|
122
200
|
throw new Error((0, stringUtils_1.buildColumnNotFoundError)(colName, Array.from(map.keys())));
|
|
123
201
|
}
|
|
124
|
-
yield
|
|
125
|
-
config
|
|
126
|
-
|
|
202
|
+
yield _navigateToCell({
|
|
203
|
+
config,
|
|
204
|
+
rootLocator,
|
|
127
205
|
page: rootLocator.page(),
|
|
128
|
-
resolve
|
|
206
|
+
resolve,
|
|
129
207
|
column: colName,
|
|
130
208
|
index: colIdx,
|
|
131
|
-
rowLocator
|
|
132
|
-
rowIndex
|
|
209
|
+
rowLocator,
|
|
210
|
+
rowIndex
|
|
133
211
|
});
|
|
134
212
|
const strategy = config.strategies.fill || fill_1.FillStrategies.default;
|
|
135
213
|
(0, debugUtils_1.logDebug)(config, 'verbose', `Filling cell "${colName}" with value`, value);
|
|
@@ -1,13 +1,29 @@
|
|
|
1
|
+
import type { Locator } from '@playwright/test';
|
|
1
2
|
import { StrategyContext } from '../types';
|
|
2
3
|
/**
|
|
4
|
+
* Primitive navigation functions that define HOW to move within a table.
|
|
5
|
+
* The orchestration logic (WHEN to move) lives in _navigateToCell.
|
|
6
|
+
*/
|
|
7
|
+
export interface NavigationPrimitives {
|
|
8
|
+
goUp?: (context: StrategyContext) => Promise<void>;
|
|
9
|
+
goDown?: (context: StrategyContext) => Promise<void>;
|
|
10
|
+
goLeft?: (context: StrategyContext) => Promise<void>;
|
|
11
|
+
goRight?: (context: StrategyContext) => Promise<void>;
|
|
12
|
+
goHome?: (context: StrategyContext) => Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* @deprecated Use NavigationPrimitives instead. This will be removed in a future version.
|
|
3
16
|
* Defines the contract for a cell navigation strategy.
|
|
4
|
-
* It is responsible for ensuring a specific CELL is visible/focused (navigates to row + column),
|
|
5
|
-
* typically by scrolling or using keyboard navigation.
|
|
6
17
|
*/
|
|
7
18
|
export type CellNavigationStrategy = (context: StrategyContext & {
|
|
8
19
|
column: string;
|
|
9
20
|
index: number;
|
|
10
21
|
rowIndex?: number;
|
|
22
|
+
activeCell?: {
|
|
23
|
+
rowIndex: number;
|
|
24
|
+
columnIndex: number;
|
|
25
|
+
locator: Locator;
|
|
26
|
+
} | null;
|
|
11
27
|
}) => Promise<void>;
|
|
12
28
|
export declare const CellNavigationStrategies: {
|
|
13
29
|
/**
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { StrategyContext } from '../../types';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Useful for canvas-based grids like Glide where DOM scrolling might not be enough for interaction
|
|
7
|
-
* or where keyboard navigation is the primary way to move focus.
|
|
3
|
+
* Primitive navigation functions for Glide Data Grid.
|
|
4
|
+
* These define HOW to move, not WHEN to move.
|
|
5
|
+
* The orchestration logic lives in _navigateToCell.
|
|
8
6
|
*/
|
|
9
|
-
export declare const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
export declare const glideGoUp: (context: StrategyContext) => Promise<void>;
|
|
8
|
+
export declare const glideGoDown: (context: StrategyContext) => Promise<void>;
|
|
9
|
+
export declare const glideGoLeft: (context: StrategyContext) => Promise<void>;
|
|
10
|
+
export declare const glideGoRight: (context: StrategyContext) => Promise<void>;
|
|
11
|
+
export declare const glideGoHome: (context: StrategyContext) => Promise<void>;
|
|
@@ -9,35 +9,36 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.
|
|
12
|
+
exports.glideGoHome = exports.glideGoRight = exports.glideGoLeft = exports.glideGoDown = exports.glideGoUp = void 0;
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* Useful for canvas-based grids like Glide where DOM scrolling might not be enough for interaction
|
|
18
|
-
* or where keyboard navigation is the primary way to move focus.
|
|
14
|
+
* Primitive navigation functions for Glide Data Grid.
|
|
15
|
+
* These define HOW to move, not WHEN to move.
|
|
16
|
+
* The orchestration logic lives in _navigateToCell.
|
|
19
17
|
*/
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
const glideGoUp = (context) => __awaiter(void 0, void 0, void 0, function* () {
|
|
19
|
+
yield context.page.keyboard.press('ArrowUp');
|
|
20
|
+
});
|
|
21
|
+
exports.glideGoUp = glideGoUp;
|
|
22
|
+
const glideGoDown = (context) => __awaiter(void 0, void 0, void 0, function* () {
|
|
23
|
+
yield context.page.keyboard.press('ArrowDown');
|
|
24
|
+
});
|
|
25
|
+
exports.glideGoDown = glideGoDown;
|
|
26
|
+
const glideGoLeft = (context) => __awaiter(void 0, void 0, void 0, function* () {
|
|
27
|
+
yield context.page.keyboard.press('ArrowLeft');
|
|
28
|
+
});
|
|
29
|
+
exports.glideGoLeft = glideGoLeft;
|
|
30
|
+
const glideGoRight = (context) => __awaiter(void 0, void 0, void 0, function* () {
|
|
31
|
+
yield context.page.keyboard.press('ArrowRight');
|
|
32
|
+
});
|
|
33
|
+
exports.glideGoRight = glideGoRight;
|
|
34
|
+
const glideGoHome = (context) => __awaiter(void 0, void 0, void 0, function* () {
|
|
35
|
+
const { root, page } = context;
|
|
25
36
|
yield root.focus();
|
|
26
37
|
yield page.waitForTimeout(100);
|
|
27
|
-
//
|
|
28
|
-
// 1. Jump to Top-Left (Reset) - Sequence for Cross-OS (Mac/Windows)
|
|
38
|
+
// Reset to top-left - Cross-OS sequence (Mac/Windows)
|
|
29
39
|
yield page.keyboard.press('Control+Home');
|
|
30
40
|
yield page.keyboard.press('Meta+ArrowUp'); // Mac Go-To-Top
|
|
31
41
|
yield page.keyboard.press('Home'); // Ensure start of row
|
|
32
42
|
yield page.waitForTimeout(150);
|
|
33
|
-
// 2. Move Down to Target Row
|
|
34
|
-
for (let i = 0; i < rowIndex; i++) {
|
|
35
|
-
yield page.keyboard.press('ArrowDown');
|
|
36
|
-
}
|
|
37
|
-
// 3. Move Right to Target Column
|
|
38
|
-
for (let i = 0; i < index; i++) {
|
|
39
|
-
yield page.keyboard.press('ArrowRight');
|
|
40
|
-
}
|
|
41
|
-
yield page.waitForTimeout(50);
|
|
42
43
|
});
|
|
43
|
-
exports.
|
|
44
|
+
exports.glideGoHome = glideGoHome;
|
|
@@ -15,11 +15,13 @@ export declare const GlideStrategies: {
|
|
|
15
15
|
selector?: string;
|
|
16
16
|
scrollAmount?: number;
|
|
17
17
|
}) => Promise<string[]>;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
navigation: {
|
|
19
|
+
goUp: (context: import("../types").StrategyContext) => Promise<void>;
|
|
20
|
+
goDown: (context: import("../types").StrategyContext) => Promise<void>;
|
|
21
|
+
goLeft: (context: import("../types").StrategyContext) => Promise<void>;
|
|
22
|
+
goRight: (context: import("../types").StrategyContext) => Promise<void>;
|
|
23
|
+
goHome: (context: import("../types").StrategyContext) => Promise<void>;
|
|
24
|
+
};
|
|
23
25
|
getCellLocator: ({ row, columnIndex }: any) => any;
|
|
24
26
|
getActiveCell: ({ page }: any) => Promise<{
|
|
25
27
|
rowIndex: number;
|
package/dist/strategies/glide.js
CHANGED
|
@@ -92,7 +92,13 @@ exports.GlideStrategies = {
|
|
|
92
92
|
fill: exports.glideFillStrategy,
|
|
93
93
|
pagination: exports.glidePaginationStrategy,
|
|
94
94
|
header: headers_1.scrollRightHeader,
|
|
95
|
-
|
|
95
|
+
navigation: {
|
|
96
|
+
goUp: columns_1.glideGoUp,
|
|
97
|
+
goDown: columns_1.glideGoDown,
|
|
98
|
+
goLeft: columns_1.glideGoLeft,
|
|
99
|
+
goRight: columns_1.glideGoRight,
|
|
100
|
+
goHome: columns_1.glideGoHome
|
|
101
|
+
},
|
|
96
102
|
getCellLocator: exports.glideGetCellLocator,
|
|
97
103
|
getActiveCell: exports.glideGetActiveCell
|
|
98
104
|
};
|
package/dist/typeContext.d.ts
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* This file is generated by scripts/embed-types.js
|
|
4
4
|
* It contains the raw text of types.ts to provide context for LLM prompts.
|
|
5
5
|
*/
|
|
6
|
-
export declare const TYPE_CONTEXT = "\n/**\n * Flexible selector type - can be a CSS string, function returning a Locator, or Locator itself.\n * @example\n * // String selector\n * rowSelector: 'tbody tr'\n * \n * // Function selector\n * rowSelector: (root) => root.locator('[role=\"row\"]')\n */\nexport type Selector = string | ((root: Locator | Page) => Locator);\n\n/**\n * Function to get a cell locator given row, column info.\n * Replaces the old cellResolver.\n */\nexport type GetCellLocatorFn = (args: {\n row: Locator;\n columnName: string;\n columnIndex: number;\n rowIndex?: number;\n page: Page;\n}) => Locator;\n\n/**\n * Function to get the currently active/focused cell.\n * Returns null if no cell is active.\n */\nexport type GetActiveCellFn = (args: TableContext) => Promise<{\n rowIndex: number;\n columnIndex: number;\n columnName?: string;\n locator: Locator;\n} | null>;\n\n\n/**\n * SmartRow - A Playwright Locator with table-aware methods.\n * \n * Extends all standard Locator methods (click, isVisible, etc.) with table-specific functionality.\n * \n * @example\n * const row = table.getRow({ Name: 'John Doe' });\n * await row.click(); // Standard Locator method\n * const email = row.getCell('Email'); // Table-aware method\n * const data = await row.toJSON(); // Extract all row data\n * await row.smartFill({ Name: 'Jane', Status: 'Active' }); // Fill form fields\n */\nexport type SmartRow<T = any> = Locator & {\n /** Optional row index (0-based) if known */\n rowIndex?: number;\n\n /**\n * Get a cell locator by column name.\n * @param column - Column name (case-sensitive)\n * @returns Locator for the cell\n * @example\n * const emailCell = row.getCell('Email');\n * await expect(emailCell).toHaveText('john@example.com');\n */\n getCell(column: string): Locator;\n\n /**\n * Extract all cell data as a key-value object.\n * @param options - Optional configuration\n * @param options.columns - Specific columns to extract (extracts all if not specified)\n * @returns Promise resolving to row data\n * @example\n * const data = await row.toJSON();\n * // { Name: 'John', Email: 'john@example.com', ... }\n * \n * const partial = await row.toJSON({ columns: ['Name', 'Email'] });\n * // { Name: 'John', Email: 'john@example.com' }\n */\n toJSON(options?: { columns?: string[] }): Promise<T>;\n\n /**\n * Scrolls/paginates to bring this row into view.\n * Only works if rowIndex is known (e.g., from getRowByIndex).\n * @throws Error if rowIndex is unknown\n */\n bringIntoView(): Promise<void>;\n\n /**\n * Intelligently fills form fields in the row.\n * Automatically detects input types (text, select, checkbox, contenteditable).\n * \n * @param data - Column-value pairs to fill\n * @param options - Optional configuration\n * @param options.inputMappers - Custom input selectors per column\n * @example\n * // Auto-detection\n * await row.smartFill({ Name: 'John', Status: 'Active', Subscribe: true });\n * \n * // Custom input mappers\n * await row.smartFill(\n * { Name: 'John' },\n * { inputMappers: { Name: (cell) => cell.locator('.custom-input') } }\n * );\n */\n smartFill: (data: Partial<T> | Record<string, any>, options?: FillOptions) => Promise<void>;\n};\n\nexport type StrategyContext = TableContext & { rowLocator?: Locator; rowIndex?: number };\n\n/**\n * Defines the contract for a sorting strategy.\n */\nexport interface SortingStrategy {\n /**\n * Performs the sort action on a column.\n */\n doSort(options: {\n columnName: string;\n direction: 'asc' | 'desc';\n context: StrategyContext;\n }): Promise<void>;\n\n /**\n * Retrieves the current sort state of a column.\n */\n getSortState(options: {\n columnName: string;\n context: StrategyContext;\n }): Promise<'asc' | 'desc' | 'none'>;\n}\n\n/**\n * Debug configuration for development and troubleshooting\n */\nexport type DebugConfig = {\n /**\n * Slow down operations for debugging\n * - number: Apply same delay to all operations (ms)\n * - object: Granular delays per operation type\n */\n slow?: number | {\n pagination?: number;\n getCell?: number;\n findRow?: number;\n default?: number;\n };\n /**\n * Log level for debug output\n * - 'verbose': All logs (verbose, info, error)\n * - 'info': Info and error logs only\n * - 'error': Error logs only\n * - 'none': No logs\n */\n logLevel?: 'verbose' | 'info' | 'error' | 'none';\n};\n\nexport interface TableContext {\n root: Locator;\n config: FinalTableConfig;\n page: Page;\n resolve: (selector: Selector, parent: Locator | Page) => Locator;\n}\n\nexport type PaginationStrategy = (context: TableContext) => Promise<boolean>;\n\nexport type DedupeStrategy = (row: SmartRow) => string | number | Promise<string | number>;\n\nexport interface PromptOptions {\n /**\n * Output Strategy:\n * - 'error': Throws an error with the prompt (useful for platforms that capture error output cleanly).\n * - 'console': Standard console logs (Default).\n */\n output?: 'console' | 'error';\n includeTypes?: boolean;\n}\n\nexport type FillStrategy = (options: {\n row: SmartRow;\n columnName: string;\n value: any;\n index: number;\n page: Page;\n rootLocator: Locator;\n table: TableResult; // The parent table instance\n fillOptions?: FillOptions;\n}) => Promise<void>;\n\nexport type { HeaderStrategy } from './strategies/headers';\nexport type { CellNavigationStrategy } from './strategies/columns';\n\n/**\n * Strategy to resolve column names (string or regex) to their index.\n */\nexport type { ColumnResolutionStrategy } from './strategies/resolution';\n\n/**\n * Strategy to filter rows based on criteria.\n */\nexport interface FilterStrategy {\n apply(options: {\n rows: Locator;\n filter: { column: string, value: string | RegExp | number };\n colIndex: number;\n tableContext: TableContext;\n }): Locator;\n}\n\n/**\n * Strategy to check if the table or rows are loading.\n */\nexport interface LoadingStrategy {\n isTableLoading?: (context: TableContext) => Promise<boolean>;\n isRowLoading?: (row: SmartRow) => Promise<boolean>;\n isHeaderLoading?: (context: TableContext) => Promise<boolean>;\n}\n\n/**\n * Organized container for all table interaction strategies.\n */\nexport interface TableStrategies {\n /** Strategy for discovering/scanning headers */\n header?: HeaderStrategy;\n /** Strategy for navigating to specific cells (row + column) */\n cellNavigation?: CellNavigationStrategy;\n /** Strategy for filling form inputs */\n fill?: FillStrategy;\n /** Strategy for paginating through data */\n pagination?: PaginationStrategy;\n /** Strategy for sorting columns */\n sorting?: SortingStrategy;\n /** Strategy for deduplicating rows during iteration/scrolling */\n dedupe?: DedupeStrategy;\n /** Function to get a cell locator */\n getCellLocator?: GetCellLocatorFn;\n /** Function to get the currently active/focused cell */\n getActiveCell?: GetActiveCellFn;\n /** Custom helper to check if a table is fully loaded/ready */\n isTableLoaded?: (args: TableContext) => Promise<boolean>;\n /** Custom helper to check if a row is fully loaded/ready */\n isRowLoaded?: (args: { row: Locator, index: number }) => Promise<boolean>;\n /** Custom helper to check if a cell is fully loaded/ready (e.g. for editing) */\n isCellLoaded?: (args: { cell: Locator, column: string, row: Locator }) => Promise<boolean>;\n /** Strategy for detecting loading states */\n loading?: LoadingStrategy;\n}\n\n/**\n * Configuration options for useTable.\n */\nexport interface TableConfig {\n /** Selector for the table headers */\n headerSelector?: string;\n /** Selector for the table rows */\n rowSelector?: string;\n /** Selector for the cells within a row */\n cellSelector?: string;\n /** Number of pages to scan for verification */\n maxPages?: number;\n /** Hook to rename columns dynamically */\n headerTransformer?: (args: { text: string, index: number, locator: Locator, seenHeaders: Set<string> }) => string | Promise<string>;\n /** Automatically scroll to table on init */\n autoScroll?: boolean;\n /** Debug options for development and troubleshooting */\n debug?: DebugConfig;\n /** Reset hook */\n onReset?: (context: TableContext) => Promise<void>;\n /** All interaction strategies */\n strategies?: TableStrategies;\n}\n\nexport interface FinalTableConfig extends TableConfig {\n headerSelector: string;\n rowSelector: string;\n cellSelector: string;\n maxPages: number;\n autoScroll: boolean;\n debug?: TableConfig['debug'];\n headerTransformer: (args: { text: string, index: number, locator: Locator, seenHeaders: Set<string> }) => string | Promise<string>;\n onReset: (context: TableContext) => Promise<void>;\n strategies: TableStrategies;\n}\n\n\nexport interface FillOptions {\n /**\n * Custom input mappers for specific columns.\n * Maps column names to functions that return the input locator for that cell.\n */\n inputMappers?: Record<string, (cell: Locator) => Locator>;\n}\n\n/**\n * Options for generateConfigPrompt\n */\nexport interface PromptOptions {\n /**\n * Output Strategy:\n * - 'error': Throws an error with the prompt (useful for platforms that capture error output cleanly).\n * - 'console': Standard console logs (Default).\n */\n output?: 'console' | 'error';\n /**\n * Include TypeScript type definitions in the prompt\n * @default true\n */\n includeTypes?: boolean;\n}\n\nexport interface TableResult<T = any> {\n /**\n * Initializes the table by resolving headers. Must be called before using sync methods.\n * @param options Optional timeout for header resolution (default: 3000ms)\n */\n init(options?: { timeout?: number }): Promise<TableResult>;\n\n /**\n * SYNC: Checks if the table has been initialized.\n * @returns true if init() has been called and completed, false otherwise\n */\n isInitialized(): boolean;\n\n getHeaders: () => Promise<string[]>;\n getHeaderCell: (columnName: string) => Promise<Locator>;\n\n /**\n * Finds a row by filters on the current page only. Returns immediately (sync).\n * Throws error if table is not initialized.\n */\n getRow: (\n filters: Record<string, string | RegExp | number>,\n options?: { exact?: boolean }\n ) => SmartRow;\n\n /**\n * Gets a row by 1-based index on the current page.\n * Throws error if table is not initialized.\n * @param index 1-based row index\n * @param options Optional settings including bringIntoView\n */\n getRowByIndex: (\n index: number,\n options?: { bringIntoView?: boolean }\n ) => SmartRow;\n\n /**\n * ASYNC: Searches for a single row across pages using pagination.\n * Auto-initializes the table if not already initialized.\n * @param filters - The filter criteria to match\n * @param options - Search options including exact match and max pages\n */\n findRow: (\n filters: Record<string, string | RegExp | number>,\n options?: { exact?: boolean, maxPages?: number }\n ) => Promise<SmartRow>;\n\n /**\n * ASYNC: Searches for all matching rows across pages using pagination.\n * Auto-initializes the table if not already initialized.\n * @param filters - The filter criteria to match\n * @param options - Search options including exact match, max pages, and asJSON\n */\n findRows: <R extends { asJSON?: boolean }>(\n filters: Record<string, string | RegExp | number>,\n options?: { exact?: boolean, maxPages?: number } & R\n ) => Promise<R['asJSON'] extends true ? Record<string, string>[] : SmartRowArray>;\n\n /**\n * Navigates to a specific column using the configured CellNavigationStrategy.\n */\n scrollToColumn: (columnName: string) => Promise<void>;\n\n /**\n * Gets all rows on the current page only (does not paginate).\n * Auto-initializes the table if not already initialized.\n * Returns a SmartRowArray which extends Array with a toJSON() helper method.\n * @param options - Filter options\n */\n getRows: (options?: { filter?: Record<string, any>, exact?: boolean }) => Promise<SmartRowArray>;\n\n /**\n * Resets the table state (clears cache, flags) and invokes the onReset strategy.\n */\n reset: () => Promise<void>;\n\n /**\n * Revalidates the table's structure (headers, columns) without resetting pagination or state.\n * Useful when columns change visibility or order dynamically.\n */\n revalidate: () => Promise<void>;\n\n /**\n * Scans a specific column across all pages and returns the values.\n */\n getColumnValues: <V = string>(column: string, options?: { mapper?: (cell: Locator) => Promise<V> | V, maxPages?: number }) => Promise<V[]>;\n\n /**\n * Provides access to sorting actions and assertions.\n */\n sorting: {\n /**\n * Applies the configured sorting strategy to the specified column.\n * @param columnName The name of the column to sort.\n * @param direction The direction to sort ('asc' or 'desc').\n */\n apply(columnName: string, direction: 'asc' | 'desc'): Promise<void>;\n /**\n * Gets the current sort state of a column using the configured sorting strategy.\n * @param columnName The name of the column to check.\n * @returns A promise that resolves to 'asc', 'desc', or 'none'.\n */\n getState(columnName: string): Promise<'asc' | 'desc' | 'none'>;\n };\n\n /**\n * Iterates through paginated table data, calling the callback for each iteration.\n * Callback return values are automatically appended to allData, which is returned.\n */\n iterateThroughTable: <T = any>(\n callback: (context: {\n index: number;\n isFirst: boolean;\n isLast: boolean;\n rows: SmartRowArray;\n allData: T[];\n table: RestrictedTableResult;\n batchInfo?: {\n startIndex: number;\n endIndex: number;\n size: number;\n };\n\n }) => T | T[] | Promise<T | T[]>,\n options?: {\n pagination?: PaginationStrategy;\n dedupeStrategy?: DedupeStrategy;\n maxIterations?: number;\n batchSize?: number;\n getIsFirst?: (context: { index: number }) => boolean;\n getIsLast?: (context: { index: number, paginationResult: boolean }) => boolean;\n beforeFirst?: (context: { index: number, rows: SmartRowArray, allData: any[] }) => void | Promise<void>;\n afterLast?: (context: { index: number, rows: SmartRowArray, allData: any[] }) => void | Promise<void>;\n /**\n * If true, flattens array results from callback into the main data array.\n * If false (default), pushes the return value as-is (preserves batching/arrays).\n */\n autoFlatten?: boolean;\n }\n ) => Promise<T[]>;\n\n /**\n * Generate an AI-friendly configuration prompt for debugging.\n * Outputs table HTML and TypeScript definitions to help AI assistants generate config.\n */\n generateConfigPrompt: (options?: PromptOptions) => Promise<void>;\n}\n\n/**\n * Restricted table result that excludes methods that shouldn't be called during iteration.\n */\nexport type RestrictedTableResult<T = any> = Omit<TableResult<T>, 'searchForRow' | 'iterateThroughTable' | 'reset'>;\n";
|
|
6
|
+
export declare const TYPE_CONTEXT = "\n/**\n * Flexible selector type - can be a CSS string, function returning a Locator, or Locator itself.\n * @example\n * // String selector\n * rowSelector: 'tbody tr'\n * \n * // Function selector\n * rowSelector: (root) => root.locator('[role=\"row\"]')\n */\nexport type Selector = string | ((root: Locator | Page) => Locator);\n\n/**\n * Value used to filter rows.\n * - string/number/RegExp: filter by text content of the cell.\n * - function: filter by custom locator logic within the cell.\n * @example\n * // Text filter\n * { Name: 'John' }\n * \n * // Custom locator filter (e.g. checkbox is checked)\n * { Status: (cell) => cell.locator('input:checked') }\n */\nexport type FilterValue = string | RegExp | number | ((cell: Locator) => Locator);\n\n/**\n * Function to get a cell locator given row, column info.\n * Replaces the old cellResolver.\n */\nexport type GetCellLocatorFn = (args: {\n row: Locator;\n columnName: string;\n columnIndex: number;\n rowIndex?: number;\n page: Page;\n}) => Locator;\n\n/**\n * Function to get the currently active/focused cell.\n * Returns null if no cell is active.\n */\nexport type GetActiveCellFn = (args: TableContext) => Promise<{\n rowIndex: number;\n columnIndex: number;\n columnName?: string;\n locator: Locator;\n} | null>;\n\n\n/**\n * SmartRow - A Playwright Locator with table-aware methods.\n * \n * Extends all standard Locator methods (click, isVisible, etc.) with table-specific functionality.\n * \n * @example\n * const row = table.getRow({ Name: 'John Doe' });\n * await row.click(); // Standard Locator method\n * const email = row.getCell('Email'); // Table-aware method\n * const data = await row.toJSON(); // Extract all row data\n * await row.smartFill({ Name: 'Jane', Status: 'Active' }); // Fill form fields\n */\nexport type SmartRow<T = any> = Locator & {\n /** Optional row index (0-based) if known */\n rowIndex?: number;\n\n /**\n * Get a cell locator by column name.\n * @param column - Column name (case-sensitive)\n * @returns Locator for the cell\n * @example\n * const emailCell = row.getCell('Email');\n * await expect(emailCell).toHaveText('john@example.com');\n */\n getCell(column: string): Locator;\n\n /**\n * Extract all cell data as a key-value object.\n * @param options - Optional configuration\n * @param options.columns - Specific columns to extract (extracts all if not specified)\n * @returns Promise resolving to row data\n * @example\n * const data = await row.toJSON();\n * // { Name: 'John', Email: 'john@example.com', ... }\n * \n * const partial = await row.toJSON({ columns: ['Name', 'Email'] });\n * // { Name: 'John', Email: 'john@example.com' }\n */\n toJSON(options?: { columns?: string[] }): Promise<T>;\n\n /**\n * Scrolls/paginates to bring this row into view.\n * Only works if rowIndex is known (e.g., from getRowByIndex).\n * @throws Error if rowIndex is unknown\n */\n bringIntoView(): Promise<void>;\n\n /**\n * Intelligently fills form fields in the row.\n * Automatically detects input types (text, select, checkbox, contenteditable).\n * \n * @param data - Column-value pairs to fill\n * @param options - Optional configuration\n * @param options.inputMappers - Custom input selectors per column\n * @example\n * // Auto-detection\n * await row.smartFill({ Name: 'John', Status: 'Active', Subscribe: true });\n * \n * // Custom input mappers\n * await row.smartFill(\n * { Name: 'John' },\n * { inputMappers: { Name: (cell) => cell.locator('.custom-input') } }\n * );\n */\n smartFill: (data: Partial<T> | Record<string, any>, options?: FillOptions) => Promise<void>;\n};\n\nexport type StrategyContext = TableContext & { rowLocator?: Locator; rowIndex?: number };\n\n/**\n * Defines the contract for a sorting strategy.\n */\nexport interface SortingStrategy {\n /**\n * Performs the sort action on a column.\n */\n doSort(options: {\n columnName: string;\n direction: 'asc' | 'desc';\n context: StrategyContext;\n }): Promise<void>;\n\n /**\n * Retrieves the current sort state of a column.\n */\n getSortState(options: {\n columnName: string;\n context: StrategyContext;\n }): Promise<'asc' | 'desc' | 'none'>;\n}\n\n/**\n * Debug configuration for development and troubleshooting\n */\nexport type DebugConfig = {\n /**\n * Slow down operations for debugging\n * - number: Apply same delay to all operations (ms)\n * - object: Granular delays per operation type\n */\n slow?: number | {\n pagination?: number;\n getCell?: number;\n findRow?: number;\n default?: number;\n };\n /**\n * Log level for debug output\n * - 'verbose': All logs (verbose, info, error)\n * - 'info': Info and error logs only\n * - 'error': Error logs only\n * - 'none': No logs\n */\n logLevel?: 'verbose' | 'info' | 'error' | 'none';\n};\n\nexport interface TableContext<T = any> {\n root: Locator;\n config: FinalTableConfig<T>;\n page: Page;\n resolve: (selector: Selector, parent: Locator | Page) => Locator;\n}\n\nexport type PaginationStrategy = (context: TableContext) => Promise<boolean>;\n\nexport type DedupeStrategy = (row: SmartRow) => string | number | Promise<string | number>;\n\nexport interface PromptOptions {\n /**\n * Output Strategy:\n * - 'error': Throws an error with the prompt (useful for platforms that capture error output cleanly).\n * - 'console': Standard console logs (Default).\n */\n output?: 'console' | 'error';\n includeTypes?: boolean;\n}\n\nexport type FillStrategy = (options: {\n row: SmartRow;\n columnName: string;\n value: any;\n index: number;\n page: Page;\n rootLocator: Locator;\n table: TableResult; // The parent table instance\n fillOptions?: FillOptions;\n}) => Promise<void>;\n\nexport type { HeaderStrategy } from './strategies/headers';\nexport type { CellNavigationStrategy, NavigationPrimitives } from './strategies/columns';\n\n/**\n * Strategy to resolve column names (string or regex) to their index.\n */\nexport type { ColumnResolutionStrategy } from './strategies/resolution';\n\n/**\n * Strategy to filter rows based on criteria.\n */\nexport interface FilterStrategy {\n apply(options: {\n rows: Locator;\n filter: { column: string, value: FilterValue };\n colIndex: number;\n tableContext: TableContext;\n }): Locator;\n}\n\n/**\n * Strategy to check if the table or rows are loading.\n */\nexport interface LoadingStrategy {\n isTableLoading?: (context: TableContext) => Promise<boolean>;\n isRowLoading?: (row: SmartRow) => Promise<boolean>;\n isHeaderLoading?: (context: TableContext) => Promise<boolean>;\n}\n\n/**\n * Organized container for all table interaction strategies.\n */\nexport interface TableStrategies {\n /** Strategy for discovering/scanning headers */\n header?: HeaderStrategy;\n /** Primitive navigation functions (goUp, goDown, goLeft, goRight, goHome) */\n navigation?: NavigationPrimitives;\n /** @deprecated Use navigation primitives instead. Strategy for navigating to specific cells (row + column) */\n cellNavigation?: CellNavigationStrategy;\n /** Strategy for filling form inputs */\n fill?: FillStrategy;\n /** Strategy for paginating through data */\n pagination?: PaginationStrategy;\n /** Strategy for sorting columns */\n sorting?: SortingStrategy;\n /** Strategy for deduplicating rows during iteration/scrolling */\n dedupe?: DedupeStrategy;\n /** Function to get a cell locator */\n getCellLocator?: GetCellLocatorFn;\n /** Function to get the currently active/focused cell */\n getActiveCell?: GetActiveCellFn;\n /** Custom helper to check if a table is fully loaded/ready */\n isTableLoaded?: (args: TableContext) => Promise<boolean>;\n /** Custom helper to check if a row is fully loaded/ready */\n isRowLoaded?: (args: { row: Locator, index: number }) => Promise<boolean>;\n /** Custom helper to check if a cell is fully loaded/ready (e.g. for editing) */\n isCellLoaded?: (args: { cell: Locator, column: string, row: Locator }) => Promise<boolean>;\n /** Strategy for detecting loading states */\n loading?: LoadingStrategy;\n}\n\n/**\n * Configuration options for useTable.\n */\n/**\n * Configuration options for useTable.\n */\nexport interface TableConfig<T = any> {\n /** Selector for the table headers */\n headerSelector?: string;\n /** Selector for the table rows */\n rowSelector?: string;\n /** Selector for the cells within a row */\n cellSelector?: string;\n /** Number of pages to scan for verification */\n maxPages?: number;\n /** Hook to rename columns dynamically */\n headerTransformer?: (args: { text: string, index: number, locator: Locator, seenHeaders: Set<string> }) => string | Promise<string>;\n /** Automatically scroll to table on init */\n autoScroll?: boolean;\n /** Debug options for development and troubleshooting */\n debug?: DebugConfig;\n /** Reset hook */\n onReset?: (context: TableContext) => Promise<void>;\n /** All interaction strategies */\n strategies?: TableStrategies;\n /**\n * Custom data mappers for specific columns.\n * Allows extracting complex data types (boolean, number) instead of just string.\n */\n dataMapper?: Partial<Record<keyof T, (cell: Locator) => Promise<T[keyof T]> | T[keyof T]>>;\n}\n\nexport interface FinalTableConfigLike<T = any> extends TableConfig<T> {\n headerSelector: string;\n rowSelector: string;\n cellSelector: string;\n maxPages: number;\n autoScroll: boolean;\n debug?: TableConfig['debug'];\n headerTransformer: (args: { text: string, index: number, locator: Locator, seenHeaders: Set<string> }) => string | Promise<string>;\n onReset: (context: TableContext) => Promise<void>;\n strategies: TableStrategies;\n}\n// Alias for backward compatibility if needed, or update usages\nexport type FinalTableConfig<T = any> = FinalTableConfigLike<T>;\n\n\nexport interface FillOptions {\n /**\n * Custom input mappers for specific columns.\n * Maps column names to functions that return the input locator for that cell.\n */\n inputMappers?: Record<string, (cell: Locator) => Locator>;\n}\n\n/**\n * Options for generateConfigPrompt\n */\nexport interface PromptOptions {\n /**\n * Output Strategy:\n * - 'error': Throws an error with the prompt (useful for platforms that capture error output cleanly).\n * - 'console': Standard console logs (Default).\n */\n output?: 'console' | 'error';\n /**\n * Include TypeScript type definitions in the prompt\n * @default true\n */\n includeTypes?: boolean;\n}\n\nexport interface TableResult<T = any> {\n /**\n * Initializes the table by resolving headers. Must be called before using sync methods.\n * @param options Optional timeout for header resolution (default: 3000ms)\n */\n init(options?: { timeout?: number }): Promise<TableResult>;\n\n /**\n * SYNC: Checks if the table has been initialized.\n * @returns true if init() has been called and completed, false otherwise\n */\n isInitialized(): boolean;\n\n getHeaders: () => Promise<string[]>;\n getHeaderCell: (columnName: string) => Promise<Locator>;\n\n /**\n * Finds a row by filters on the current page only. Returns immediately (sync).\n * Throws error if table is not initialized.\n */\n getRow: (\n filters: Record<string, FilterValue>,\n options?: { exact?: boolean }\n ) => SmartRow;\n\n /**\n * Gets a row by 1-based index on the current page.\n * Throws error if table is not initialized.\n * @param index 1-based row index\n * @param options Optional settings including bringIntoView\n */\n getRowByIndex: (\n index: number,\n options?: { bringIntoView?: boolean }\n ) => SmartRow;\n\n /**\n * ASYNC: Searches for a single row across pages using pagination.\n * Auto-initializes the table if not already initialized.\n * @param filters - The filter criteria to match\n * @param options - Search options including exact match and max pages\n */\n findRow: (\n filters: Record<string, FilterValue>,\n options?: { exact?: boolean, maxPages?: number }\n ) => Promise<SmartRow>;\n\n /**\n * ASYNC: Searches for all matching rows across pages using pagination.\n * Auto-initializes the table if not already initialized.\n * @param filters - The filter criteria to match\n * @param options - Search options including exact match and max pages\n */\n findRows: (\n filters: Record<string, FilterValue>,\n options?: { exact?: boolean, maxPages?: number }\n ) => Promise<SmartRowArray<T>>;\n\n /**\n * Navigates to a specific column using the configured CellNavigationStrategy.\n */\n scrollToColumn: (columnName: string) => Promise<void>;\n\n /**\n * Gets all rows on the current page only (does not paginate).\n * Auto-initializes the table if not already initialized.\n * Returns a SmartRowArray which extends Array with a toJSON() helper method.\n * @param options - Filter options\n */\n getRows: (options?: { filter?: Record<string, any>, exact?: boolean }) => Promise<SmartRowArray>;\n\n /**\n * Resets the table state (clears cache, flags) and invokes the onReset strategy.\n */\n reset: () => Promise<void>;\n\n /**\n * Revalidates the table's structure (headers, columns) without resetting pagination or state.\n * Useful when columns change visibility or order dynamically.\n */\n revalidate: () => Promise<void>;\n\n /**\n * Scans a specific column across all pages and returns the values.\n */\n getColumnValues: <V = string>(column: string, options?: { mapper?: (cell: Locator) => Promise<V> | V, maxPages?: number }) => Promise<V[]>;\n\n /**\n * Provides access to sorting actions and assertions.\n */\n sorting: {\n /**\n * Applies the configured sorting strategy to the specified column.\n * @param columnName The name of the column to sort.\n * @param direction The direction to sort ('asc' or 'desc').\n */\n apply(columnName: string, direction: 'asc' | 'desc'): Promise<void>;\n /**\n * Gets the current sort state of a column using the configured sorting strategy.\n * @param columnName The name of the column to check.\n * @returns A promise that resolves to 'asc', 'desc', or 'none'.\n */\n getState(columnName: string): Promise<'asc' | 'desc' | 'none'>;\n };\n\n /**\n * Iterates through paginated table data, calling the callback for each iteration.\n * Callback return values are automatically appended to allData, which is returned.\n */\n iterateThroughTable: <T = any>(\n callback: (context: {\n index: number;\n isFirst: boolean;\n isLast: boolean;\n rows: SmartRowArray;\n allData: T[];\n table: RestrictedTableResult;\n batchInfo?: {\n startIndex: number;\n endIndex: number;\n size: number;\n };\n\n }) => T | T[] | Promise<T | T[]>,\n options?: {\n pagination?: PaginationStrategy;\n dedupeStrategy?: DedupeStrategy;\n maxIterations?: number;\n batchSize?: number;\n getIsFirst?: (context: { index: number }) => boolean;\n getIsLast?: (context: { index: number, paginationResult: boolean }) => boolean;\n beforeFirst?: (context: { index: number, rows: SmartRowArray, allData: any[] }) => void | Promise<void>;\n afterLast?: (context: { index: number, rows: SmartRowArray, allData: any[] }) => void | Promise<void>;\n /**\n * If true, flattens array results from callback into the main data array.\n * If false (default), pushes the return value as-is (preserves batching/arrays).\n */\n autoFlatten?: boolean;\n }\n ) => Promise<T[]>;\n\n /**\n * Generate an AI-friendly configuration prompt for debugging.\n * Outputs table HTML and TypeScript definitions to help AI assistants generate config.\n */\n generateConfigPrompt: (options?: PromptOptions) => Promise<void>;\n}\n\n/**\n * Restricted table result that excludes methods that shouldn't be called during iteration.\n */\nexport type RestrictedTableResult<T = any> = Omit<TableResult<T>, 'searchForRow' | 'iterateThroughTable' | 'reset'>;\n";
|
package/dist/typeContext.js
CHANGED
|
@@ -18,6 +18,19 @@ exports.TYPE_CONTEXT = `
|
|
|
18
18
|
*/
|
|
19
19
|
export type Selector = string | ((root: Locator | Page) => Locator);
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Value used to filter rows.
|
|
23
|
+
* - string/number/RegExp: filter by text content of the cell.
|
|
24
|
+
* - function: filter by custom locator logic within the cell.
|
|
25
|
+
* @example
|
|
26
|
+
* // Text filter
|
|
27
|
+
* { Name: 'John' }
|
|
28
|
+
*
|
|
29
|
+
* // Custom locator filter (e.g. checkbox is checked)
|
|
30
|
+
* { Status: (cell) => cell.locator('input:checked') }
|
|
31
|
+
*/
|
|
32
|
+
export type FilterValue = string | RegExp | number | ((cell: Locator) => Locator);
|
|
33
|
+
|
|
21
34
|
/**
|
|
22
35
|
* Function to get a cell locator given row, column info.
|
|
23
36
|
* Replaces the old cellResolver.
|
|
@@ -158,9 +171,9 @@ export type DebugConfig = {
|
|
|
158
171
|
logLevel?: 'verbose' | 'info' | 'error' | 'none';
|
|
159
172
|
};
|
|
160
173
|
|
|
161
|
-
export interface TableContext {
|
|
174
|
+
export interface TableContext<T = any> {
|
|
162
175
|
root: Locator;
|
|
163
|
-
config: FinalTableConfig
|
|
176
|
+
config: FinalTableConfig<T>;
|
|
164
177
|
page: Page;
|
|
165
178
|
resolve: (selector: Selector, parent: Locator | Page) => Locator;
|
|
166
179
|
}
|
|
@@ -191,7 +204,7 @@ export type FillStrategy = (options: {
|
|
|
191
204
|
}) => Promise<void>;
|
|
192
205
|
|
|
193
206
|
export type { HeaderStrategy } from './strategies/headers';
|
|
194
|
-
export type { CellNavigationStrategy } from './strategies/columns';
|
|
207
|
+
export type { CellNavigationStrategy, NavigationPrimitives } from './strategies/columns';
|
|
195
208
|
|
|
196
209
|
/**
|
|
197
210
|
* Strategy to resolve column names (string or regex) to their index.
|
|
@@ -204,7 +217,7 @@ export type { ColumnResolutionStrategy } from './strategies/resolution';
|
|
|
204
217
|
export interface FilterStrategy {
|
|
205
218
|
apply(options: {
|
|
206
219
|
rows: Locator;
|
|
207
|
-
filter: { column: string, value:
|
|
220
|
+
filter: { column: string, value: FilterValue };
|
|
208
221
|
colIndex: number;
|
|
209
222
|
tableContext: TableContext;
|
|
210
223
|
}): Locator;
|
|
@@ -225,7 +238,9 @@ export interface LoadingStrategy {
|
|
|
225
238
|
export interface TableStrategies {
|
|
226
239
|
/** Strategy for discovering/scanning headers */
|
|
227
240
|
header?: HeaderStrategy;
|
|
228
|
-
/**
|
|
241
|
+
/** Primitive navigation functions (goUp, goDown, goLeft, goRight, goHome) */
|
|
242
|
+
navigation?: NavigationPrimitives;
|
|
243
|
+
/** @deprecated Use navigation primitives instead. Strategy for navigating to specific cells (row + column) */
|
|
229
244
|
cellNavigation?: CellNavigationStrategy;
|
|
230
245
|
/** Strategy for filling form inputs */
|
|
231
246
|
fill?: FillStrategy;
|
|
@@ -252,7 +267,10 @@ export interface TableStrategies {
|
|
|
252
267
|
/**
|
|
253
268
|
* Configuration options for useTable.
|
|
254
269
|
*/
|
|
255
|
-
|
|
270
|
+
/**
|
|
271
|
+
* Configuration options for useTable.
|
|
272
|
+
*/
|
|
273
|
+
export interface TableConfig<T = any> {
|
|
256
274
|
/** Selector for the table headers */
|
|
257
275
|
headerSelector?: string;
|
|
258
276
|
/** Selector for the table rows */
|
|
@@ -271,9 +289,14 @@ export interface TableConfig {
|
|
|
271
289
|
onReset?: (context: TableContext) => Promise<void>;
|
|
272
290
|
/** All interaction strategies */
|
|
273
291
|
strategies?: TableStrategies;
|
|
292
|
+
/**
|
|
293
|
+
* Custom data mappers for specific columns.
|
|
294
|
+
* Allows extracting complex data types (boolean, number) instead of just string.
|
|
295
|
+
*/
|
|
296
|
+
dataMapper?: Partial<Record<keyof T, (cell: Locator) => Promise<T[keyof T]> | T[keyof T]>>;
|
|
274
297
|
}
|
|
275
298
|
|
|
276
|
-
export interface
|
|
299
|
+
export interface FinalTableConfigLike<T = any> extends TableConfig<T> {
|
|
277
300
|
headerSelector: string;
|
|
278
301
|
rowSelector: string;
|
|
279
302
|
cellSelector: string;
|
|
@@ -284,6 +307,8 @@ export interface FinalTableConfig extends TableConfig {
|
|
|
284
307
|
onReset: (context: TableContext) => Promise<void>;
|
|
285
308
|
strategies: TableStrategies;
|
|
286
309
|
}
|
|
310
|
+
// Alias for backward compatibility if needed, or update usages
|
|
311
|
+
export type FinalTableConfig<T = any> = FinalTableConfigLike<T>;
|
|
287
312
|
|
|
288
313
|
|
|
289
314
|
export interface FillOptions {
|
|
@@ -332,7 +357,7 @@ export interface TableResult<T = any> {
|
|
|
332
357
|
* Throws error if table is not initialized.
|
|
333
358
|
*/
|
|
334
359
|
getRow: (
|
|
335
|
-
filters: Record<string,
|
|
360
|
+
filters: Record<string, FilterValue>,
|
|
336
361
|
options?: { exact?: boolean }
|
|
337
362
|
) => SmartRow;
|
|
338
363
|
|
|
@@ -354,7 +379,7 @@ export interface TableResult<T = any> {
|
|
|
354
379
|
* @param options - Search options including exact match and max pages
|
|
355
380
|
*/
|
|
356
381
|
findRow: (
|
|
357
|
-
filters: Record<string,
|
|
382
|
+
filters: Record<string, FilterValue>,
|
|
358
383
|
options?: { exact?: boolean, maxPages?: number }
|
|
359
384
|
) => Promise<SmartRow>;
|
|
360
385
|
|
|
@@ -362,12 +387,12 @@ export interface TableResult<T = any> {
|
|
|
362
387
|
* ASYNC: Searches for all matching rows across pages using pagination.
|
|
363
388
|
* Auto-initializes the table if not already initialized.
|
|
364
389
|
* @param filters - The filter criteria to match
|
|
365
|
-
* @param options - Search options including exact match
|
|
390
|
+
* @param options - Search options including exact match and max pages
|
|
366
391
|
*/
|
|
367
|
-
findRows:
|
|
368
|
-
filters: Record<string,
|
|
369
|
-
options?: { exact?: boolean, maxPages?: number }
|
|
370
|
-
) => Promise<
|
|
392
|
+
findRows: (
|
|
393
|
+
filters: Record<string, FilterValue>,
|
|
394
|
+
options?: { exact?: boolean, maxPages?: number }
|
|
395
|
+
) => Promise<SmartRowArray<T>>;
|
|
371
396
|
|
|
372
397
|
/**
|
|
373
398
|
* Navigates to a specific column using the configured CellNavigationStrategy.
|
package/dist/types.d.ts
CHANGED
|
@@ -10,6 +10,18 @@ import type { SmartRowArray } from './utils/smartRowArray';
|
|
|
10
10
|
* rowSelector: (root) => root.locator('[role="row"]')
|
|
11
11
|
*/
|
|
12
12
|
export type Selector = string | ((root: Locator | Page) => Locator);
|
|
13
|
+
/**
|
|
14
|
+
* Value used to filter rows.
|
|
15
|
+
* - string/number/RegExp: filter by text content of the cell.
|
|
16
|
+
* - function: filter by custom locator logic within the cell.
|
|
17
|
+
* @example
|
|
18
|
+
* // Text filter
|
|
19
|
+
* { Name: 'John' }
|
|
20
|
+
*
|
|
21
|
+
* // Custom locator filter (e.g. checkbox is checked)
|
|
22
|
+
* { Status: (cell) => cell.locator('input:checked') }
|
|
23
|
+
*/
|
|
24
|
+
export type FilterValue = string | RegExp | number | ((cell: Locator) => Locator);
|
|
13
25
|
/**
|
|
14
26
|
* Function to get a cell locator given row, column info.
|
|
15
27
|
* Replaces the old cellResolver.
|
|
@@ -143,9 +155,9 @@ export type DebugConfig = {
|
|
|
143
155
|
*/
|
|
144
156
|
logLevel?: 'verbose' | 'info' | 'error' | 'none';
|
|
145
157
|
};
|
|
146
|
-
export interface TableContext {
|
|
158
|
+
export interface TableContext<T = any> {
|
|
147
159
|
root: Locator;
|
|
148
|
-
config: FinalTableConfig
|
|
160
|
+
config: FinalTableConfig<T>;
|
|
149
161
|
page: Page;
|
|
150
162
|
resolve: (selector: Selector, parent: Locator | Page) => Locator;
|
|
151
163
|
}
|
|
@@ -171,9 +183,9 @@ export type FillStrategy = (options: {
|
|
|
171
183
|
fillOptions?: FillOptions;
|
|
172
184
|
}) => Promise<void>;
|
|
173
185
|
export type { HeaderStrategy } from './strategies/headers';
|
|
174
|
-
export type { CellNavigationStrategy } from './strategies/columns';
|
|
186
|
+
export type { CellNavigationStrategy, NavigationPrimitives } from './strategies/columns';
|
|
175
187
|
import { HeaderStrategy } from './strategies/headers';
|
|
176
|
-
import { CellNavigationStrategy } from './strategies/columns';
|
|
188
|
+
import { CellNavigationStrategy, NavigationPrimitives } from './strategies/columns';
|
|
177
189
|
/**
|
|
178
190
|
* Strategy to resolve column names (string or regex) to their index.
|
|
179
191
|
*/
|
|
@@ -186,7 +198,7 @@ export interface FilterStrategy {
|
|
|
186
198
|
rows: Locator;
|
|
187
199
|
filter: {
|
|
188
200
|
column: string;
|
|
189
|
-
value:
|
|
201
|
+
value: FilterValue;
|
|
190
202
|
};
|
|
191
203
|
colIndex: number;
|
|
192
204
|
tableContext: TableContext;
|
|
@@ -206,7 +218,9 @@ export interface LoadingStrategy {
|
|
|
206
218
|
export interface TableStrategies {
|
|
207
219
|
/** Strategy for discovering/scanning headers */
|
|
208
220
|
header?: HeaderStrategy;
|
|
209
|
-
/**
|
|
221
|
+
/** Primitive navigation functions (goUp, goDown, goLeft, goRight, goHome) */
|
|
222
|
+
navigation?: NavigationPrimitives;
|
|
223
|
+
/** @deprecated Use navigation primitives instead. Strategy for navigating to specific cells (row + column) */
|
|
210
224
|
cellNavigation?: CellNavigationStrategy;
|
|
211
225
|
/** Strategy for filling form inputs */
|
|
212
226
|
fill?: FillStrategy;
|
|
@@ -239,7 +253,10 @@ export interface TableStrategies {
|
|
|
239
253
|
/**
|
|
240
254
|
* Configuration options for useTable.
|
|
241
255
|
*/
|
|
242
|
-
|
|
256
|
+
/**
|
|
257
|
+
* Configuration options for useTable.
|
|
258
|
+
*/
|
|
259
|
+
export interface TableConfig<T = any> {
|
|
243
260
|
/** Selector for the table headers */
|
|
244
261
|
headerSelector?: string;
|
|
245
262
|
/** Selector for the table rows */
|
|
@@ -263,8 +280,13 @@ export interface TableConfig {
|
|
|
263
280
|
onReset?: (context: TableContext) => Promise<void>;
|
|
264
281
|
/** All interaction strategies */
|
|
265
282
|
strategies?: TableStrategies;
|
|
283
|
+
/**
|
|
284
|
+
* Custom data mappers for specific columns.
|
|
285
|
+
* Allows extracting complex data types (boolean, number) instead of just string.
|
|
286
|
+
*/
|
|
287
|
+
dataMapper?: Partial<Record<keyof T, (cell: Locator) => Promise<T[keyof T]> | T[keyof T]>>;
|
|
266
288
|
}
|
|
267
|
-
export interface
|
|
289
|
+
export interface FinalTableConfigLike<T = any> extends TableConfig<T> {
|
|
268
290
|
headerSelector: string;
|
|
269
291
|
rowSelector: string;
|
|
270
292
|
cellSelector: string;
|
|
@@ -280,6 +302,7 @@ export interface FinalTableConfig extends TableConfig {
|
|
|
280
302
|
onReset: (context: TableContext) => Promise<void>;
|
|
281
303
|
strategies: TableStrategies;
|
|
282
304
|
}
|
|
305
|
+
export type FinalTableConfig<T = any> = FinalTableConfigLike<T>;
|
|
283
306
|
export interface FillOptions {
|
|
284
307
|
/**
|
|
285
308
|
* Custom input mappers for specific columns.
|
|
@@ -322,7 +345,7 @@ export interface TableResult<T = any> {
|
|
|
322
345
|
* Finds a row by filters on the current page only. Returns immediately (sync).
|
|
323
346
|
* Throws error if table is not initialized.
|
|
324
347
|
*/
|
|
325
|
-
getRow: (filters: Record<string,
|
|
348
|
+
getRow: (filters: Record<string, FilterValue>, options?: {
|
|
326
349
|
exact?: boolean;
|
|
327
350
|
}) => SmartRow;
|
|
328
351
|
/**
|
|
@@ -340,7 +363,7 @@ export interface TableResult<T = any> {
|
|
|
340
363
|
* @param filters - The filter criteria to match
|
|
341
364
|
* @param options - Search options including exact match and max pages
|
|
342
365
|
*/
|
|
343
|
-
findRow: (filters: Record<string,
|
|
366
|
+
findRow: (filters: Record<string, FilterValue>, options?: {
|
|
344
367
|
exact?: boolean;
|
|
345
368
|
maxPages?: number;
|
|
346
369
|
}) => Promise<SmartRow>;
|
|
@@ -348,14 +371,12 @@ export interface TableResult<T = any> {
|
|
|
348
371
|
* ASYNC: Searches for all matching rows across pages using pagination.
|
|
349
372
|
* Auto-initializes the table if not already initialized.
|
|
350
373
|
* @param filters - The filter criteria to match
|
|
351
|
-
* @param options - Search options including exact match
|
|
374
|
+
* @param options - Search options including exact match and max pages
|
|
352
375
|
*/
|
|
353
|
-
findRows: <
|
|
354
|
-
asJSON?: boolean;
|
|
355
|
-
}>(filters: Record<string, string | RegExp | number>, options?: {
|
|
376
|
+
findRows: (filters: Record<string, FilterValue>, options?: {
|
|
356
377
|
exact?: boolean;
|
|
357
378
|
maxPages?: number;
|
|
358
|
-
}
|
|
379
|
+
}) => Promise<SmartRowArray<T>>;
|
|
359
380
|
/**
|
|
360
381
|
* Navigates to a specific column using the configured CellNavigationStrategy.
|
|
361
382
|
*/
|
package/dist/useTable.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { Strategies } from './strategies';
|
|
|
8
8
|
/**
|
|
9
9
|
* Main hook to interact with a table.
|
|
10
10
|
*/
|
|
11
|
-
export declare const useTable: <T = any>(rootLocator: Locator, configOptions?: TableConfig) => TableResult<T>;
|
|
11
|
+
export declare const useTable: <T = any>(rootLocator: Locator, configOptions?: TableConfig<T>) => TableResult<T>;
|
|
12
12
|
export declare const PaginationStrategies: {
|
|
13
13
|
clickNext: (nextButtonSelector: Selector, options?: {
|
|
14
14
|
stabilization?: import("./strategies/stabilization").StabilizationStrategy;
|
package/dist/useTable.js
CHANGED
|
@@ -225,10 +225,12 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
225
225
|
return _makeSmart(rowLocator, map, index);
|
|
226
226
|
},
|
|
227
227
|
findRow: (filters, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
228
|
+
// @ts-ignore
|
|
228
229
|
return rowFinder.findRow(filters, options);
|
|
229
230
|
}),
|
|
230
231
|
getRows: (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
231
232
|
console.warn('DEPRECATED: table.getRows() is deprecated and will be removed in a future version. Use table.findRows() instead.');
|
|
233
|
+
// @ts-ignore
|
|
232
234
|
return rowFinder.findRows((options === null || options === void 0 ? void 0 : options.filter) || {}, Object.assign(Object.assign({}, options), { maxPages: 1 }));
|
|
233
235
|
}),
|
|
234
236
|
findRows: (filters, options) => __awaiter(void 0, void 0, void 0, function* () {
|