@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.
Files changed (42) hide show
  1. package/README.md +9 -6
  2. package/dist/engine/rowFinder.js +153 -168
  3. package/dist/engine/tableIteration.js +105 -120
  4. package/dist/engine/tableMapper.js +88 -103
  5. package/dist/minimalConfigContext.d.ts +1 -1
  6. package/dist/minimalConfigContext.js +1 -1
  7. package/dist/packageVersion.d.ts +1 -1
  8. package/dist/packageVersion.js +1 -1
  9. package/dist/presets/glide/columns.js +23 -32
  10. package/dist/presets/glide/headers.js +18 -27
  11. package/dist/presets/glide/index.d.ts +9 -9
  12. package/dist/presets/glide/index.js +24 -33
  13. package/dist/presets/mui.js +197 -124
  14. package/dist/presets/rdg.d.ts +1 -1
  15. package/dist/presets/rdg.js +32 -41
  16. package/dist/smartRow.js +209 -101
  17. package/dist/strategies/columns.js +2 -11
  18. package/dist/strategies/dedupe.js +3 -12
  19. package/dist/strategies/fill.js +20 -29
  20. package/dist/strategies/headers.js +22 -31
  21. package/dist/strategies/index.d.ts +7 -0
  22. package/dist/strategies/index.js +3 -0
  23. package/dist/strategies/loading.js +24 -33
  24. package/dist/strategies/pagination.d.ts +10 -0
  25. package/dist/strategies/pagination.js +49 -30
  26. package/dist/strategies/sorting.js +18 -31
  27. package/dist/strategies/stabilization.js +28 -37
  28. package/dist/strategies/viewport.d.ts +52 -0
  29. package/dist/strategies/viewport.js +123 -0
  30. package/dist/typeContext.d.ts +1 -1
  31. package/dist/typeContext.js +160 -5
  32. package/dist/types.d.ts +153 -5
  33. package/dist/useTable.js +142 -76
  34. package/dist/utils/debugUtils.js +5 -16
  35. package/dist/utils/elementTracker.js +27 -40
  36. package/dist/utils/mutex.js +5 -16
  37. package/dist/utils/navigationBarrier.js +25 -36
  38. package/dist/utils/paginationPath.d.ts +4 -1
  39. package/dist/utils/paginationPath.js +164 -141
  40. package/dist/utils/smartRowArray.js +2 -11
  41. package/dist/utils.js +4 -13
  42. 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
- // ✅ Auto-pagination across all pages
47
- const allEngineers = await table.findRows({ Department: 'Engineering' });
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 across paginated tables
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
- - 📄 **Auto-Pagination** - Search across all pages automatically
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 all pages
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
@@ -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(filters_1) {
32
- return __awaiter(this, arguments, void 0, function* (filters, options = {}) {
33
- (0, debugUtils_1.logDebug)(this.config, 'info', 'Searching for row', filters);
34
- yield this.tableMapper.getMap();
35
- const rowLocator = yield this.findRowLocator(filters, options);
36
- if (rowLocator) {
37
- (0, debugUtils_1.logDebug)(this.config, 'info', 'Row found');
38
- yield (0, debugUtils_1.debugDelay)(this.config, 'findRow');
39
- return this.makeSmartRow(rowLocator, yield this.tableMapper.getMap(), 0);
40
- }
41
- (0, debugUtils_1.logDebug)(this.config, 'error', 'Row not found', filters);
42
- yield (0, debugUtils_1.debugDelay)(this.config, 'findRow');
43
- const sentinel = this.resolve(this.config.rowSelector, this.rootLocator)
44
- .filter({ hasText: "___SENTINEL_ROW_NOT_FOUND___" + Date.now() });
45
- const smartRow = this.makeSmartRow(sentinel, yield this.tableMapper.getMap(), 0);
46
- smartRow[sentinel_1.SENTINEL_ROW] = true;
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
- return __awaiter(this, arguments, void 0, function* (filters = {}, options) {
52
- var _a, _b, _c, _d;
53
- const filtersRecord = filters;
54
- const map = yield this.tableMapper.getMap();
55
- const allRows = [];
56
- 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;
57
- let pagesScanned = 1;
58
- this.log(`findRows: starting (maxPages=${effectiveMaxPages}, filters=${JSON.stringify(filtersRecord)})`);
59
- const tracker = new elementTracker_1.ElementTracker('findRows');
60
- try {
61
- const collectMatches = () => __awaiter(this, void 0, void 0, function* () {
62
- var _a, _b;
63
- let rowLocators = this.resolve(this.config.rowSelector, this.rootLocator);
64
- // Only apply filters if we have them
65
- if (Object.keys(filtersRecord).length > 0) {
66
- 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);
67
- }
68
- // Get only newly seen matched rows
69
- const newIndices = yield tracker.getUnseenIndices(rowLocators);
70
- const currentRows = yield rowLocators.all();
71
- const isRowLoading = (_b = this.config.strategies.loading) === null || _b === void 0 ? void 0 : _b.isRowLoading;
72
- let added = 0;
73
- for (const idx of newIndices) {
74
- const smartRow = this.makeSmartRow(currentRows[idx], map, allRows.length, this.tableState.currentPageIndex);
75
- if (isRowLoading && (yield isRowLoading(smartRow))) {
76
- this.log(`findRows: page ${this.tableState.currentPageIndex} — row skipped (isRowLoading=true)`);
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
- const pagesJumped = typeof paginationResult === 'number' ? paginationResult : 1;
111
- this.tableState.currentPageIndex += pagesJumped;
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
- finally {
118
- yield tracker.cleanup(this.rootLocator.page());
119
- }
120
- this.log(`findRows: done — ${allRows.length} row(s) collected across ${pagesScanned} page(s)`);
121
- return (0, smartRowArray_1.createSmartRowArray)(allRows);
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(filters_1) {
125
- return __awaiter(this, arguments, void 0, function* (filters, options = {}) {
126
- var _a, _b, _c, _d;
127
- const map = yield this.tableMapper.getMap();
128
- const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : this.config.maxPages;
129
- let pagesScanned = 1;
130
- this.log(`Looking for row: ${JSON.stringify(filters)} (MaxPages: ${effectiveMaxPages})`);
131
- while (true) {
132
- // Check Loading
133
- if ((_b = this.config.strategies.loading) === null || _b === void 0 ? void 0 : _b.isTableLoading) {
134
- const isLoading = yield this.config.strategies.loading.isTableLoading({
135
- root: this.rootLocator,
136
- config: this.config,
137
- page: this.rootLocator.page(),
138
- resolve: this.resolve
139
- });
140
- if (isLoading) {
141
- this.log('Table is loading... waiting');
142
- yield this.rootLocator.page().waitForTimeout(200);
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
- const allRows = this.resolve(this.config.rowSelector, this.rootLocator);
147
- const matchedRows = this.filterEngine.applyFilters(allRows, filters, map, options.exact || false, this.rootLocator.page(), this.rootLocator);
148
- const count = yield matchedRows.count();
149
- this.log(`Page ${this.tableState.currentPageIndex}: Found ${count} matches.`);
150
- if (count > 1) {
151
- const sampleData = [];
152
- try {
153
- const firstFewRows = yield matchedRows.all();
154
- const sampleCount = Math.min(firstFewRows.length, 3);
155
- for (let i = 0; i < sampleCount; i++) {
156
- const rowData = yield this.makeSmartRow(firstFewRows[i], map, 0, this.tableState.currentPageIndex).toJSON();
157
- sampleData.push(JSON.stringify(rowData));
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
- if (count === 1)
166
- return matchedRows.first();
167
- if (pagesScanned < effectiveMaxPages) {
168
- this.log(`Page ${this.tableState.currentPageIndex}: Not found. Attempting pagination...`);
169
- const context = {
170
- root: this.rootLocator,
171
- config: this.config,
172
- resolve: this.resolve,
173
- page: this.rootLocator.page()
174
- };
175
- let paginationResult;
176
- if ((_c = this.config.strategies.pagination) === null || _c === void 0 ? void 0 : _c.goNextBulk) {
177
- paginationResult = yield this.config.strategies.pagination.goNextBulk(context);
178
- }
179
- else if ((_d = this.config.strategies.pagination) === null || _d === void 0 ? void 0 : _d.goNext) {
180
- paginationResult = yield this.config.strategies.pagination.goNext(context);
181
- }
182
- else {
183
- this.log(`Page ${this.tableState.currentPageIndex}: Pagination failed (no goNext or goNextBulk primitive).`);
184
- return null;
185
- }
186
- const didLoadMore = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
187
- if (didLoadMore) {
188
- const pagesJumped = typeof paginationResult === 'number' ? paginationResult : 1;
189
- this.tableState.currentPageIndex += pagesJumped;
190
- pagesScanned += pagesJumped;
191
- continue;
192
- }
193
- else {
194
- this.log(`Page ${this.tableState.currentPageIndex}: Pagination failed (end of data).`);
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;