@rickcedwhat/playwright-smart-table 6.9.0 → 6.10.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 +9 -6
- package/dist/engine/rowFinder.js +153 -168
- package/dist/engine/tableIteration.js +105 -120
- package/dist/engine/tableMapper.js +88 -103
- package/dist/minimalConfigContext.d.ts +1 -1
- package/dist/minimalConfigContext.js +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/presets/glide/columns.js +23 -32
- package/dist/presets/glide/headers.js +18 -27
- package/dist/presets/glide/index.d.ts +9 -9
- package/dist/presets/glide/index.js +24 -33
- package/dist/presets/mui.js +197 -124
- package/dist/presets/rdg.d.ts +1 -1
- package/dist/presets/rdg.js +32 -41
- package/dist/smartRow.js +209 -101
- package/dist/strategies/columns.js +2 -11
- package/dist/strategies/dedupe.js +3 -12
- package/dist/strategies/fill.js +20 -29
- package/dist/strategies/headers.js +22 -31
- package/dist/strategies/index.d.ts +7 -0
- package/dist/strategies/index.js +3 -0
- package/dist/strategies/loading.js +24 -33
- package/dist/strategies/pagination.d.ts +10 -0
- package/dist/strategies/pagination.js +49 -30
- package/dist/strategies/sorting.js +18 -31
- package/dist/strategies/stabilization.js +28 -37
- package/dist/strategies/viewport.d.ts +52 -0
- package/dist/strategies/viewport.js +123 -0
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +160 -5
- package/dist/types.d.ts +153 -5
- package/dist/useTable.js +142 -76
- package/dist/utils/debugUtils.js +5 -16
- package/dist/utils/elementTracker.js +27 -40
- package/dist/utils/mutex.js +5 -16
- package/dist/utils/navigationBarrier.js +25 -36
- package/dist/utils/paginationPath.d.ts +4 -1
- package/dist/utils/paginationPath.js +164 -141
- package/dist/utils/smartRowArray.js +2 -11
- package/dist/utils.js +4 -13
- package/package.json +13 -8
package/README.md
CHANGED
|
@@ -43,8 +43,11 @@ const email = await row.locator('td').nth(emailIndex).textContent();
|
|
|
43
43
|
const row = table.getRow({ Name: 'John Doe' });
|
|
44
44
|
const email = await row.getCell('Email').textContent();
|
|
45
45
|
|
|
46
|
-
// ✅
|
|
47
|
-
const allEngineers = await table.findRows(
|
|
46
|
+
// ✅ Pagination-aware when you raise maxPages
|
|
47
|
+
const allEngineers = await table.findRows(
|
|
48
|
+
{ Department: 'Engineering' },
|
|
49
|
+
{ maxPages: 5 }
|
|
50
|
+
);
|
|
48
51
|
|
|
49
52
|
// ✅ Type-safe
|
|
50
53
|
type Employee = { Name: string; Email: string; Department: string };
|
|
@@ -72,8 +75,8 @@ const row = table.getRow({ Name: 'John Doe' });
|
|
|
72
75
|
// Access cells by column name
|
|
73
76
|
const email = await row.getCell('Email').innerText();
|
|
74
77
|
|
|
75
|
-
// Search
|
|
76
|
-
const allActive = await table.findRows({ Status: 'Active' });
|
|
78
|
+
// Search more than the current page by raising maxPages
|
|
79
|
+
const allActive = await table.findRows({ Status: 'Active' }, { maxPages: 5 });
|
|
77
80
|
```
|
|
78
81
|
|
|
79
82
|
### Iterating Across Pages
|
|
@@ -163,9 +166,9 @@ const table = useTable(page.locator('#table'), {
|
|
|
163
166
|
- 🎯 **Smart Locators** - Find rows by content, not position
|
|
164
167
|
- 🧠 **Fuzzy Matching** - Smart suggestions for typos in column names
|
|
165
168
|
- ⚡ **Smart Initialization** - Handles loading states and dynamic headers automatically
|
|
166
|
-
- 📄 **
|
|
169
|
+
- 📄 **Pagination-aware search** - Scan beyond the current page when `maxPages` is increased
|
|
167
170
|
- 🔍 **Column-Aware Access** - Access cells by column name
|
|
168
|
-
- 🔁 **Iteration Methods** - `forEach`, `map`, `filter`, and `for await...of` across
|
|
171
|
+
- 🔁 **Iteration Methods** - `forEach`, `map`, `filter`, and `for await...of` across the configured page range
|
|
169
172
|
- 🛠️ **Debug Mode** - Visual debugging with slow motion and logging
|
|
170
173
|
- 🔌 **[Extensible Strategies](docs/concepts/strategies.md)** - Support any table implementation
|
|
171
174
|
- 💪 **Type-Safe** - Full TypeScript support
|
package/dist/engine/rowFinder.js
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
3
|
exports.RowFinder = void 0;
|
|
13
4
|
const debugUtils_1 = require("../utils/debugUtils");
|
|
@@ -28,175 +19,169 @@ class RowFinder {
|
|
|
28
19
|
log(msg) {
|
|
29
20
|
(0, debugUtils_1.logDebug)(this.config, 'verbose', msg);
|
|
30
21
|
}
|
|
31
|
-
findRow(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return smartRow;
|
|
48
|
-
});
|
|
22
|
+
async findRow(filters, options = {}) {
|
|
23
|
+
(0, debugUtils_1.logDebug)(this.config, 'info', 'Searching for row', filters);
|
|
24
|
+
await this.tableMapper.getMap();
|
|
25
|
+
const rowLocator = await this.findRowLocator(filters, options);
|
|
26
|
+
if (rowLocator) {
|
|
27
|
+
(0, debugUtils_1.logDebug)(this.config, 'info', 'Row found');
|
|
28
|
+
await (0, debugUtils_1.debugDelay)(this.config, 'findRow');
|
|
29
|
+
return this.makeSmartRow(rowLocator, await this.tableMapper.getMap(), 0);
|
|
30
|
+
}
|
|
31
|
+
(0, debugUtils_1.logDebug)(this.config, 'error', 'Row not found', filters);
|
|
32
|
+
await (0, debugUtils_1.debugDelay)(this.config, 'findRow');
|
|
33
|
+
const sentinel = this.resolve(this.config.rowSelector, this.rootLocator)
|
|
34
|
+
.filter({ hasText: "___SENTINEL_ROW_NOT_FOUND___" + Date.now() });
|
|
35
|
+
const smartRow = this.makeSmartRow(sentinel, await this.tableMapper.getMap(), 0);
|
|
36
|
+
smartRow[sentinel_1.SENTINEL_ROW] = true;
|
|
37
|
+
return smartRow;
|
|
49
38
|
}
|
|
50
|
-
findRows() {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
allRows.push(smartRow);
|
|
80
|
-
added++;
|
|
81
|
-
}
|
|
82
|
-
this.log(`findRows: page ${this.tableState.currentPageIndex} — ${added} new match(es) (total: ${allRows.length})`);
|
|
83
|
-
});
|
|
84
|
-
// Scan first page
|
|
85
|
-
yield collectMatches();
|
|
86
|
-
// Pagination Loop
|
|
87
|
-
while (pagesScanned < effectiveMaxPages && this.config.strategies.pagination) {
|
|
88
|
-
const context = {
|
|
89
|
-
root: this.rootLocator,
|
|
90
|
-
config: this.config,
|
|
91
|
-
resolve: this.resolve,
|
|
92
|
-
page: this.rootLocator.page()
|
|
93
|
-
};
|
|
94
|
-
let paginationResult;
|
|
95
|
-
if ((_c = this.config.strategies.pagination) === null || _c === void 0 ? void 0 : _c.goNextBulk) {
|
|
96
|
-
paginationResult = yield this.config.strategies.pagination.goNextBulk(context);
|
|
97
|
-
}
|
|
98
|
-
else if ((_d = this.config.strategies.pagination) === null || _d === void 0 ? void 0 : _d.goNext) {
|
|
99
|
-
paginationResult = yield this.config.strategies.pagination.goNext(context);
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
this.log(`findRows: no pagination primitive — stopping`);
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
const didPaginate = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
|
|
106
|
-
if (!didPaginate) {
|
|
107
|
-
this.log(`findRows: pagination returned false — end of data`);
|
|
108
|
-
break;
|
|
39
|
+
async findRows(filters = {}, options) {
|
|
40
|
+
var _a, _b, _c, _d;
|
|
41
|
+
const filtersRecord = filters;
|
|
42
|
+
const map = await this.tableMapper.getMap();
|
|
43
|
+
const allRows = [];
|
|
44
|
+
const effectiveMaxPages = (_b = (_a = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _a !== void 0 ? _a : this.config.maxPages) !== null && _b !== void 0 ? _b : Infinity;
|
|
45
|
+
let pagesScanned = 1;
|
|
46
|
+
this.log(`findRows: starting (maxPages=${effectiveMaxPages}, filters=${JSON.stringify(filtersRecord)})`);
|
|
47
|
+
const tracker = new elementTracker_1.ElementTracker('findRows');
|
|
48
|
+
try {
|
|
49
|
+
const collectMatches = async () => {
|
|
50
|
+
var _a, _b;
|
|
51
|
+
let rowLocators = this.resolve(this.config.rowSelector, this.rootLocator);
|
|
52
|
+
// Only apply filters if we have them
|
|
53
|
+
if (Object.keys(filtersRecord).length > 0) {
|
|
54
|
+
rowLocators = this.filterEngine.applyFilters(rowLocators, filtersRecord, map, (_a = options === null || options === void 0 ? void 0 : options.exact) !== null && _a !== void 0 ? _a : false, this.rootLocator.page(), this.rootLocator);
|
|
55
|
+
}
|
|
56
|
+
// Get only newly seen matched rows
|
|
57
|
+
const newIndices = await tracker.getUnseenIndices(rowLocators);
|
|
58
|
+
const currentRows = await rowLocators.all();
|
|
59
|
+
const isRowLoading = (_b = this.config.strategies.loading) === null || _b === void 0 ? void 0 : _b.isRowLoading;
|
|
60
|
+
let added = 0;
|
|
61
|
+
for (const idx of newIndices) {
|
|
62
|
+
const smartRow = this.makeSmartRow(currentRows[idx], map, allRows.length, this.tableState.currentPageIndex);
|
|
63
|
+
if (isRowLoading && await isRowLoading(smartRow)) {
|
|
64
|
+
this.log(`findRows: page ${this.tableState.currentPageIndex} — row skipped (isRowLoading=true)`);
|
|
65
|
+
continue;
|
|
109
66
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
pagesScanned += pagesJumped;
|
|
113
|
-
this.log(`findRows: advanced ${pagesJumped} page(s), now at page ${this.tableState.currentPageIndex}`);
|
|
114
|
-
yield collectMatches();
|
|
67
|
+
allRows.push(smartRow);
|
|
68
|
+
added++;
|
|
115
69
|
}
|
|
70
|
+
this.log(`findRows: page ${this.tableState.currentPageIndex} — ${added} new match(es) (total: ${allRows.length})`);
|
|
71
|
+
};
|
|
72
|
+
// Scan first page
|
|
73
|
+
await collectMatches();
|
|
74
|
+
// Pagination Loop
|
|
75
|
+
while (pagesScanned < effectiveMaxPages && this.config.strategies.pagination) {
|
|
76
|
+
const context = {
|
|
77
|
+
root: this.rootLocator,
|
|
78
|
+
config: this.config,
|
|
79
|
+
resolve: this.resolve,
|
|
80
|
+
page: this.rootLocator.page()
|
|
81
|
+
};
|
|
82
|
+
let paginationResult;
|
|
83
|
+
if ((_c = this.config.strategies.pagination) === null || _c === void 0 ? void 0 : _c.goNextBulk) {
|
|
84
|
+
paginationResult = await this.config.strategies.pagination.goNextBulk(context);
|
|
85
|
+
}
|
|
86
|
+
else if ((_d = this.config.strategies.pagination) === null || _d === void 0 ? void 0 : _d.goNext) {
|
|
87
|
+
paginationResult = await this.config.strategies.pagination.goNext(context);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
this.log(`findRows: no pagination primitive — stopping`);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
const didPaginate = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
|
|
94
|
+
if (!didPaginate) {
|
|
95
|
+
this.log(`findRows: pagination returned false — end of data`);
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
const pagesJumped = typeof paginationResult === 'number' ? paginationResult : 1;
|
|
99
|
+
this.tableState.currentPageIndex += pagesJumped;
|
|
100
|
+
pagesScanned += pagesJumped;
|
|
101
|
+
this.log(`findRows: advanced ${pagesJumped} page(s), now at page ${this.tableState.currentPageIndex}`);
|
|
102
|
+
await collectMatches();
|
|
116
103
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
await tracker.cleanup(this.rootLocator.page());
|
|
107
|
+
}
|
|
108
|
+
this.log(`findRows: done — ${allRows.length} row(s) collected across ${pagesScanned} page(s)`);
|
|
109
|
+
return (0, smartRowArray_1.createSmartRowArray)(allRows);
|
|
123
110
|
}
|
|
124
|
-
findRowLocator(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
111
|
+
async findRowLocator(filters, options = {}) {
|
|
112
|
+
var _a, _b, _c, _d;
|
|
113
|
+
const map = await this.tableMapper.getMap();
|
|
114
|
+
const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : this.config.maxPages;
|
|
115
|
+
let pagesScanned = 1;
|
|
116
|
+
this.log(`Looking for row: ${JSON.stringify(filters)} (MaxPages: ${effectiveMaxPages})`);
|
|
117
|
+
while (true) {
|
|
118
|
+
// Check Loading
|
|
119
|
+
if ((_b = this.config.strategies.loading) === null || _b === void 0 ? void 0 : _b.isTableLoading) {
|
|
120
|
+
const isLoading = await this.config.strategies.loading.isTableLoading({
|
|
121
|
+
root: this.rootLocator,
|
|
122
|
+
config: this.config,
|
|
123
|
+
page: this.rootLocator.page(),
|
|
124
|
+
resolve: this.resolve
|
|
125
|
+
});
|
|
126
|
+
if (isLoading) {
|
|
127
|
+
this.log('Table is loading... waiting');
|
|
128
|
+
await this.rootLocator.page().waitForTimeout(200);
|
|
129
|
+
continue;
|
|
145
130
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
131
|
+
}
|
|
132
|
+
const allRows = this.resolve(this.config.rowSelector, this.rootLocator);
|
|
133
|
+
const matchedRows = this.filterEngine.applyFilters(allRows, filters, map, options.exact || false, this.rootLocator.page(), this.rootLocator);
|
|
134
|
+
const count = await matchedRows.count();
|
|
135
|
+
this.log(`Page ${this.tableState.currentPageIndex}: Found ${count} matches.`);
|
|
136
|
+
if (count > 1) {
|
|
137
|
+
const sampleData = [];
|
|
138
|
+
try {
|
|
139
|
+
const firstFewRows = await matchedRows.all();
|
|
140
|
+
const sampleCount = Math.min(firstFewRows.length, 3);
|
|
141
|
+
for (let i = 0; i < sampleCount; i++) {
|
|
142
|
+
const rowData = await this.makeSmartRow(firstFewRows[i], map, 0, this.tableState.currentPageIndex).toJSON();
|
|
143
|
+
sampleData.push(JSON.stringify(rowData));
|
|
159
144
|
}
|
|
160
|
-
catch (e) { }
|
|
161
|
-
const sampleMsg = sampleData.length > 0 ? `\nSample matching rows:\n${sampleData.map((d, i) => ` ${i + 1}. ${d}`).join('\n')}` : '';
|
|
162
|
-
throw new Error(`Ambiguous Row: Found ${count} rows matching ${JSON.stringify(filters)} on page ${this.tableState.currentPageIndex}. ` +
|
|
163
|
-
`Expected exactly one match. Try adding more filters to make your query unique.${sampleMsg}`);
|
|
164
145
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
146
|
+
catch (e) { }
|
|
147
|
+
const sampleMsg = sampleData.length > 0 ? `\nSample matching rows:\n${sampleData.map((d, i) => ` ${i + 1}. ${d}`).join('\n')}` : '';
|
|
148
|
+
throw new Error(`Ambiguous Row: Found ${count} rows matching ${JSON.stringify(filters)} on page ${this.tableState.currentPageIndex}. ` +
|
|
149
|
+
`Expected exactly one match. Try adding more filters to make your query unique.${sampleMsg}`);
|
|
150
|
+
}
|
|
151
|
+
if (count === 1)
|
|
152
|
+
return matchedRows.first();
|
|
153
|
+
if (pagesScanned < effectiveMaxPages) {
|
|
154
|
+
this.log(`Page ${this.tableState.currentPageIndex}: Not found. Attempting pagination...`);
|
|
155
|
+
const context = {
|
|
156
|
+
root: this.rootLocator,
|
|
157
|
+
config: this.config,
|
|
158
|
+
resolve: this.resolve,
|
|
159
|
+
page: this.rootLocator.page()
|
|
160
|
+
};
|
|
161
|
+
let paginationResult;
|
|
162
|
+
if ((_c = this.config.strategies.pagination) === null || _c === void 0 ? void 0 : _c.goNextBulk) {
|
|
163
|
+
paginationResult = await this.config.strategies.pagination.goNextBulk(context);
|
|
164
|
+
}
|
|
165
|
+
else if ((_d = this.config.strategies.pagination) === null || _d === void 0 ? void 0 : _d.goNext) {
|
|
166
|
+
paginationResult = await this.config.strategies.pagination.goNext(context);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
this.log(`Page ${this.tableState.currentPageIndex}: Pagination failed (no goNext or goNextBulk primitive).`);
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
const didLoadMore = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
|
|
173
|
+
if (didLoadMore) {
|
|
174
|
+
const pagesJumped = typeof paginationResult === 'number' ? paginationResult : 1;
|
|
175
|
+
this.tableState.currentPageIndex += pagesJumped;
|
|
176
|
+
pagesScanned += pagesJumped;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
this.log(`Page ${this.tableState.currentPageIndex}: Pagination failed (end of data).`);
|
|
196
181
|
}
|
|
197
|
-
return null;
|
|
198
182
|
}
|
|
199
|
-
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
200
185
|
}
|
|
201
186
|
}
|
|
202
187
|
exports.RowFinder = RowFinder;
|