@rickcedwhat/playwright-smart-table 6.1.0 → 6.2.0
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 -0
- package/dist/engine/rowFinder.d.ts +26 -0
- package/dist/engine/rowFinder.js +161 -0
- package/dist/engine/tableMapper.d.ts +16 -0
- package/dist/engine/tableMapper.js +136 -0
- package/dist/{examples/glide-strategies → strategies/glide}/columns.d.ts +1 -1
- package/dist/{examples/glide-strategies → strategies/glide}/headers.d.ts +1 -1
- package/dist/{src/strategies → strategies}/glide.js +2 -2
- package/dist/strategies/index.d.ts +33 -2
- package/dist/strategies/index.js +6 -0
- package/dist/{src/strategies → strategies}/loading.d.ts +14 -0
- package/dist/{src/strategies → strategies}/loading.js +31 -0
- package/dist/strategies/pagination.d.ts +26 -4
- package/dist/strategies/pagination.js +52 -23
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +32 -7
- package/dist/types.d.ts +37 -5
- package/dist/useTable.d.ts +32 -3
- package/dist/useTable.js +81 -224
- package/dist/utils/stringUtils.js +12 -3
- package/package.json +10 -5
- package/dist/src/filterEngine.d.ts +0 -11
- package/dist/src/filterEngine.js +0 -39
- package/dist/src/index.d.ts +0 -2
- package/dist/src/index.js +0 -18
- package/dist/src/smartRow.d.ts +0 -7
- package/dist/src/smartRow.js +0 -160
- package/dist/src/strategies/columns.d.ts +0 -18
- package/dist/src/strategies/columns.js +0 -21
- package/dist/src/strategies/fill.d.ts +0 -7
- package/dist/src/strategies/fill.js +0 -88
- package/dist/src/strategies/headers.d.ts +0 -13
- package/dist/src/strategies/headers.js +0 -30
- package/dist/src/strategies/index.d.ts +0 -54
- package/dist/src/strategies/index.js +0 -43
- package/dist/src/strategies/pagination.d.ts +0 -33
- package/dist/src/strategies/pagination.js +0 -79
- package/dist/src/strategies/resolution.d.ts +0 -22
- package/dist/src/strategies/resolution.js +0 -30
- package/dist/src/strategies/sorting.d.ts +0 -12
- package/dist/src/strategies/sorting.js +0 -68
- package/dist/src/strategies/validation.d.ts +0 -22
- package/dist/src/strategies/validation.js +0 -54
- package/dist/src/strategies/virtualizedPagination.d.ts +0 -32
- package/dist/src/strategies/virtualizedPagination.js +0 -80
- package/dist/src/typeContext.d.ts +0 -6
- package/dist/src/typeContext.js +0 -465
- package/dist/src/types.d.ts +0 -458
- package/dist/src/types.js +0 -2
- package/dist/src/useTable.d.ts +0 -44
- package/dist/src/useTable.js +0 -641
- package/dist/src/utils/debugUtils.d.ts +0 -17
- package/dist/src/utils/debugUtils.js +0 -62
- package/dist/src/utils/smartRowArray.d.ts +0 -14
- package/dist/src/utils/smartRowArray.js +0 -22
- package/dist/src/utils/stringUtils.d.ts +0 -22
- package/dist/src/utils/stringUtils.js +0 -73
- package/dist/src/utils.d.ts +0 -7
- package/dist/src/utils.js +0 -29
- package/dist/utils/traceUtils.d.ts +0 -11
- package/dist/utils/traceUtils.js +0 -47
- /package/dist/{src/plugins.d.ts → plugins.d.ts} +0 -0
- /package/dist/{src/plugins.js → plugins.js} +0 -0
- /package/dist/{src/strategies → strategies}/dedupe.d.ts +0 -0
- /package/dist/{src/strategies → strategies}/dedupe.js +0 -0
- /package/dist/{examples/glide-strategies → strategies/glide}/columns.js +0 -0
- /package/dist/{examples/glide-strategies → strategies/glide}/headers.js +0 -0
- /package/dist/{src/strategies → strategies}/glide.d.ts +0 -0
- /package/dist/{src/strategies → strategies}/rdg.d.ts +0 -0
- /package/dist/{src/strategies → strategies}/rdg.js +0 -0
- /package/dist/{src/strategies → strategies}/stabilization.d.ts +0 -0
- /package/dist/{src/strategies → strategies}/stabilization.js +0 -0
package/README.md
CHANGED
|
@@ -79,6 +79,8 @@ const allActive = await table.findRows({ Status: 'Active' });
|
|
|
79
79
|
## Key Features
|
|
80
80
|
|
|
81
81
|
- 🎯 **Smart Locators** - Find rows by content, not position
|
|
82
|
+
- 🧠 **Fuzzy Matching** - Smart suggestions for typos (e.g., incorrectly typed "Firstname" suggests "First Name" in error messages)
|
|
83
|
+
- ⚡ **Smart Initialization** - Handles loading states and dynamic headers automatically
|
|
82
84
|
- 📄 **Auto-Pagination** - Search across all pages automatically
|
|
83
85
|
- 🔍 **Column-Aware Access** - Access cells by column name
|
|
84
86
|
- 🛠️ **Debug Mode** - Visual debugging with slow motion and logging
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Locator, Page } from '@playwright/test';
|
|
2
|
+
import { FinalTableConfig, Selector, SmartRow } from '../types';
|
|
3
|
+
import { FilterEngine } from '../filterEngine';
|
|
4
|
+
import { TableMapper } from './tableMapper';
|
|
5
|
+
import { SmartRowArray } from '../utils/smartRowArray';
|
|
6
|
+
export declare class RowFinder<T = any> {
|
|
7
|
+
private rootLocator;
|
|
8
|
+
private config;
|
|
9
|
+
private filterEngine;
|
|
10
|
+
private tableMapper;
|
|
11
|
+
private makeSmartRow;
|
|
12
|
+
private resolve;
|
|
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
|
+
private log;
|
|
15
|
+
findRow(filters: Record<string, string | RegExp | number>, options?: {
|
|
16
|
+
exact?: boolean;
|
|
17
|
+
maxPages?: number;
|
|
18
|
+
}): Promise<SmartRow<T>>;
|
|
19
|
+
findRows<R extends {
|
|
20
|
+
asJSON?: boolean;
|
|
21
|
+
}>(filters: Partial<T> | Record<string, string | RegExp | number>, options?: {
|
|
22
|
+
exact?: boolean;
|
|
23
|
+
maxPages?: number;
|
|
24
|
+
} & R): Promise<R['asJSON'] extends true ? Record<string, string>[] : SmartRowArray<T>>;
|
|
25
|
+
private findRowLocator;
|
|
26
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.RowFinder = void 0;
|
|
13
|
+
const debugUtils_1 = require("../utils/debugUtils");
|
|
14
|
+
const smartRowArray_1 = require("../utils/smartRowArray");
|
|
15
|
+
const validation_1 = require("../strategies/validation");
|
|
16
|
+
class RowFinder {
|
|
17
|
+
constructor(rootLocator, config, resolve, filterEngine, tableMapper, makeSmartRow) {
|
|
18
|
+
this.rootLocator = rootLocator;
|
|
19
|
+
this.config = config;
|
|
20
|
+
this.filterEngine = filterEngine;
|
|
21
|
+
this.tableMapper = tableMapper;
|
|
22
|
+
this.makeSmartRow = makeSmartRow;
|
|
23
|
+
this.resolve = resolve;
|
|
24
|
+
}
|
|
25
|
+
log(msg) {
|
|
26
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', msg);
|
|
27
|
+
}
|
|
28
|
+
findRow(filters_1) {
|
|
29
|
+
return __awaiter(this, arguments, void 0, function* (filters, options = {}) {
|
|
30
|
+
(0, debugUtils_1.logDebug)(this.config, 'info', 'Searching for row', filters);
|
|
31
|
+
yield this.tableMapper.getMap();
|
|
32
|
+
const rowLocator = yield this.findRowLocator(filters, options);
|
|
33
|
+
if (rowLocator) {
|
|
34
|
+
(0, debugUtils_1.logDebug)(this.config, 'info', 'Row found');
|
|
35
|
+
yield (0, debugUtils_1.debugDelay)(this.config, 'findRow');
|
|
36
|
+
return this.makeSmartRow(rowLocator, yield this.tableMapper.getMap(), 0);
|
|
37
|
+
}
|
|
38
|
+
(0, debugUtils_1.logDebug)(this.config, 'error', 'Row not found', filters);
|
|
39
|
+
yield (0, debugUtils_1.debugDelay)(this.config, 'findRow');
|
|
40
|
+
const sentinel = this.resolve(this.config.rowSelector, this.rootLocator)
|
|
41
|
+
.filter({ hasText: "___SENTINEL_ROW_NOT_FOUND___" + Date.now() });
|
|
42
|
+
return this.makeSmartRow(sentinel, yield this.tableMapper.getMap(), 0);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
findRows(filters, options) {
|
|
46
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
47
|
+
var _a, _b;
|
|
48
|
+
const map = yield this.tableMapper.getMap();
|
|
49
|
+
const allRows = [];
|
|
50
|
+
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;
|
|
51
|
+
let pageCount = 0;
|
|
52
|
+
const collectMatches = () => __awaiter(this, void 0, void 0, function* () {
|
|
53
|
+
var _a, _b;
|
|
54
|
+
let rowLocators = this.resolve(this.config.rowSelector, this.rootLocator);
|
|
55
|
+
rowLocators = this.filterEngine.applyFilters(rowLocators, filters, map, (_a = options === null || options === void 0 ? void 0 : options.exact) !== null && _a !== void 0 ? _a : false, this.rootLocator.page());
|
|
56
|
+
const currentRows = yield rowLocators.all();
|
|
57
|
+
const isRowLoading = (_b = this.config.strategies.loading) === null || _b === void 0 ? void 0 : _b.isRowLoading;
|
|
58
|
+
for (let i = 0; i < currentRows.length; i++) {
|
|
59
|
+
const smartRow = this.makeSmartRow(currentRows[i], map, i);
|
|
60
|
+
if (isRowLoading && (yield isRowLoading(smartRow)))
|
|
61
|
+
continue;
|
|
62
|
+
allRows.push(smartRow);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
// Scan first page
|
|
66
|
+
yield collectMatches();
|
|
67
|
+
// Pagination Loop
|
|
68
|
+
while (pageCount < effectiveMaxPages && this.config.strategies.pagination) {
|
|
69
|
+
// Check if pagination needed? findRows assumes we want ALL matches across maxPages.
|
|
70
|
+
// If explicit maxPages is set, we paginate. If global maxPages is 1 (default), we stop.
|
|
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;
|
|
76
|
+
const context = {
|
|
77
|
+
root: this.rootLocator,
|
|
78
|
+
config: this.config,
|
|
79
|
+
resolve: this.resolve,
|
|
80
|
+
page: this.rootLocator.page()
|
|
81
|
+
};
|
|
82
|
+
const paginationResult = yield this.config.strategies.pagination(context);
|
|
83
|
+
const didPaginate = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
|
|
84
|
+
if (!didPaginate)
|
|
85
|
+
break;
|
|
86
|
+
pageCount++;
|
|
87
|
+
yield collectMatches();
|
|
88
|
+
}
|
|
89
|
+
if (options === null || options === void 0 ? void 0 : options.asJSON) {
|
|
90
|
+
return Promise.all(allRows.map(r => r.toJSON()));
|
|
91
|
+
}
|
|
92
|
+
return (0, smartRowArray_1.createSmartRowArray)(allRows);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
findRowLocator(filters_1) {
|
|
96
|
+
return __awaiter(this, arguments, void 0, function* (filters, options = {}) {
|
|
97
|
+
var _a, _b;
|
|
98
|
+
const map = yield this.tableMapper.getMap();
|
|
99
|
+
const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : this.config.maxPages;
|
|
100
|
+
let currentPage = 1;
|
|
101
|
+
this.log(`Looking for row: ${JSON.stringify(filters)} (MaxPages: ${effectiveMaxPages})`);
|
|
102
|
+
while (true) {
|
|
103
|
+
// Check Loading
|
|
104
|
+
if ((_b = this.config.strategies.loading) === null || _b === void 0 ? void 0 : _b.isTableLoading) {
|
|
105
|
+
const isLoading = yield this.config.strategies.loading.isTableLoading({
|
|
106
|
+
root: this.rootLocator,
|
|
107
|
+
config: this.config,
|
|
108
|
+
page: this.rootLocator.page(),
|
|
109
|
+
resolve: this.resolve
|
|
110
|
+
});
|
|
111
|
+
if (isLoading) {
|
|
112
|
+
this.log('Table is loading... waiting');
|
|
113
|
+
yield this.rootLocator.page().waitForTimeout(200);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const allRows = this.resolve(this.config.rowSelector, this.rootLocator);
|
|
118
|
+
const matchedRows = this.filterEngine.applyFilters(allRows, filters, map, options.exact || false, this.rootLocator.page());
|
|
119
|
+
const count = yield matchedRows.count();
|
|
120
|
+
this.log(`Page ${currentPage}: Found ${count} matches.`);
|
|
121
|
+
if (count > 1) {
|
|
122
|
+
const sampleData = [];
|
|
123
|
+
try {
|
|
124
|
+
const firstFewRows = yield matchedRows.all();
|
|
125
|
+
const sampleCount = Math.min(firstFewRows.length, 3);
|
|
126
|
+
for (let i = 0; i < sampleCount; i++) {
|
|
127
|
+
const rowData = yield this.makeSmartRow(firstFewRows[i], map, 0).toJSON();
|
|
128
|
+
sampleData.push(JSON.stringify(rowData));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (e) { }
|
|
132
|
+
const sampleMsg = sampleData.length > 0 ? `\nSample matching rows:\n${sampleData.map((d, i) => ` ${i + 1}. ${d}`).join('\n')}` : '';
|
|
133
|
+
throw new Error(`Ambiguous Row: Found ${count} rows matching ${JSON.stringify(filters)} on page ${currentPage}. ` +
|
|
134
|
+
`Expected exactly one match. Try adding more filters to make your query unique.${sampleMsg}`);
|
|
135
|
+
}
|
|
136
|
+
if (count === 1)
|
|
137
|
+
return matchedRows.first();
|
|
138
|
+
if (currentPage < effectiveMaxPages) {
|
|
139
|
+
this.log(`Page ${currentPage}: Not found. Attempting pagination...`);
|
|
140
|
+
const context = {
|
|
141
|
+
root: this.rootLocator,
|
|
142
|
+
config: this.config,
|
|
143
|
+
resolve: this.resolve,
|
|
144
|
+
page: this.rootLocator.page()
|
|
145
|
+
};
|
|
146
|
+
const paginationResult = yield this.config.strategies.pagination(context);
|
|
147
|
+
const didLoadMore = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
|
|
148
|
+
if (didLoadMore) {
|
|
149
|
+
currentPage++;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
this.log(`Page ${currentPage}: Pagination failed (end of data).`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
exports.RowFinder = RowFinder;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Locator, Page } from '@playwright/test';
|
|
2
|
+
import { FinalTableConfig, Selector } from '../types';
|
|
3
|
+
export declare class TableMapper {
|
|
4
|
+
private _headerMap;
|
|
5
|
+
private config;
|
|
6
|
+
private rootLocator;
|
|
7
|
+
private resolve;
|
|
8
|
+
constructor(rootLocator: Locator, config: FinalTableConfig, resolve: (item: Selector, parent: Locator | Page) => Locator);
|
|
9
|
+
private log;
|
|
10
|
+
getMap(timeout?: number): Promise<Map<string, number>>;
|
|
11
|
+
remapHeaders(): Promise<void>;
|
|
12
|
+
getMapSync(): Map<string, number> | null;
|
|
13
|
+
isInitialized(): boolean;
|
|
14
|
+
clear(): void;
|
|
15
|
+
private processHeaders;
|
|
16
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.TableMapper = void 0;
|
|
13
|
+
const headers_1 = require("../strategies/headers");
|
|
14
|
+
const debugUtils_1 = require("../utils/debugUtils");
|
|
15
|
+
class TableMapper {
|
|
16
|
+
constructor(rootLocator, config, resolve) {
|
|
17
|
+
this._headerMap = null;
|
|
18
|
+
this.rootLocator = rootLocator;
|
|
19
|
+
this.config = config;
|
|
20
|
+
this.resolve = resolve;
|
|
21
|
+
}
|
|
22
|
+
log(msg) {
|
|
23
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', msg);
|
|
24
|
+
}
|
|
25
|
+
getMap(timeout) {
|
|
26
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
27
|
+
var _a;
|
|
28
|
+
if (this._headerMap)
|
|
29
|
+
return this._headerMap;
|
|
30
|
+
this.log('Mapping headers...');
|
|
31
|
+
const headerTimeout = timeout !== null && timeout !== void 0 ? timeout : 3000;
|
|
32
|
+
const startTime = Date.now();
|
|
33
|
+
if (this.config.autoScroll) {
|
|
34
|
+
try {
|
|
35
|
+
yield this.rootLocator.scrollIntoViewIfNeeded({ timeout: 1000 });
|
|
36
|
+
}
|
|
37
|
+
catch (e) { }
|
|
38
|
+
}
|
|
39
|
+
const headerLoc = this.resolve(this.config.headerSelector, this.rootLocator);
|
|
40
|
+
const strategy = this.config.strategies.header || headers_1.HeaderStrategies.visible;
|
|
41
|
+
const context = {
|
|
42
|
+
root: this.rootLocator,
|
|
43
|
+
config: this.config,
|
|
44
|
+
page: this.rootLocator.page(),
|
|
45
|
+
resolve: this.resolve
|
|
46
|
+
};
|
|
47
|
+
let lastError = null;
|
|
48
|
+
while (Date.now() - startTime < headerTimeout) {
|
|
49
|
+
// 1. Wait for visibility
|
|
50
|
+
try {
|
|
51
|
+
yield headerLoc.first().waitFor({ state: 'visible', timeout: 200 });
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
// Continue to check existing/loading state even if not strictly "visible" yet
|
|
55
|
+
}
|
|
56
|
+
// 2. Check Smart Loading State
|
|
57
|
+
if ((_a = this.config.strategies.loading) === null || _a === void 0 ? void 0 : _a.isHeaderLoading) {
|
|
58
|
+
const isStable = !(yield this.config.strategies.loading.isHeaderLoading(context));
|
|
59
|
+
if (!isStable) {
|
|
60
|
+
this.log('Headers are loading/unstable... waiting');
|
|
61
|
+
yield new Promise(r => setTimeout(r, 100));
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// 3. Attempt Scan
|
|
66
|
+
try {
|
|
67
|
+
const rawHeaders = yield strategy(context);
|
|
68
|
+
const entries = yield this.processHeaders(rawHeaders);
|
|
69
|
+
// Success
|
|
70
|
+
this._headerMap = new Map(entries);
|
|
71
|
+
this.log(`Mapped ${entries.length} columns: ${JSON.stringify(entries.map(e => e[0]))}`);
|
|
72
|
+
return this._headerMap;
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
lastError = e;
|
|
76
|
+
this.log(`Header mapping failed (retrying): ${e.message}`);
|
|
77
|
+
yield new Promise(r => setTimeout(r, 100));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
throw lastError || new Error(`Timed out waiting for headers after ${headerTimeout}ms`);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
remapHeaders() {
|
|
84
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
85
|
+
this._headerMap = null;
|
|
86
|
+
yield this.getMap();
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
getMapSync() {
|
|
90
|
+
return this._headerMap;
|
|
91
|
+
}
|
|
92
|
+
isInitialized() {
|
|
93
|
+
return this._headerMap !== null;
|
|
94
|
+
}
|
|
95
|
+
clear() {
|
|
96
|
+
this._headerMap = null;
|
|
97
|
+
}
|
|
98
|
+
processHeaders(rawHeaders) {
|
|
99
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
100
|
+
const seenHeaders = new Set();
|
|
101
|
+
const entries = [];
|
|
102
|
+
for (let i = 0; i < rawHeaders.length; i++) {
|
|
103
|
+
let text = rawHeaders[i].trim() || `__col_${i}`;
|
|
104
|
+
if (this.config.headerTransformer) {
|
|
105
|
+
text = yield this.config.headerTransformer({
|
|
106
|
+
text,
|
|
107
|
+
index: i,
|
|
108
|
+
locator: this.rootLocator.locator(this.config.headerSelector).nth(i),
|
|
109
|
+
seenHeaders
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
entries.push([text, i]);
|
|
113
|
+
seenHeaders.add(text);
|
|
114
|
+
}
|
|
115
|
+
// Validation: Check for empty table
|
|
116
|
+
if (entries.length === 0) {
|
|
117
|
+
throw new Error(`Initialization Error: No columns found using selector "${this.config.headerSelector}". Check your selector or ensure the table is visible.`);
|
|
118
|
+
}
|
|
119
|
+
// Validation: Check for duplicates
|
|
120
|
+
const seen = new Set();
|
|
121
|
+
const duplicates = new Set();
|
|
122
|
+
for (const [name] of entries) {
|
|
123
|
+
if (seen.has(name)) {
|
|
124
|
+
duplicates.add(name);
|
|
125
|
+
}
|
|
126
|
+
seen.add(name);
|
|
127
|
+
}
|
|
128
|
+
if (duplicates.size > 0) {
|
|
129
|
+
const dupList = Array.from(duplicates).map(d => `"${d}"`).join(', ');
|
|
130
|
+
throw new Error(`Initialization Error: Duplicate column names found: ${dupList}. Use 'headerTransformer' to rename duplicate columns.`);
|
|
131
|
+
}
|
|
132
|
+
return entries;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
exports.TableMapper = TableMapper;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StrategyContext } from '../../
|
|
1
|
+
import { StrategyContext } from '../../types';
|
|
2
2
|
/**
|
|
3
3
|
* Strategy that clicks into the table to establish focus and then uses the Right Arrow key
|
|
4
4
|
* to navigate to the target CELL (navigates down to the row, then right to the column).
|
|
@@ -10,8 +10,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.GlideStrategies = exports.glideGetActiveCell = exports.glideGetCellLocator = exports.glidePaginationStrategy = exports.glideFillStrategy = void 0;
|
|
13
|
-
const columns_1 = require("
|
|
14
|
-
const headers_1 = require("
|
|
13
|
+
const columns_1 = require("./glide/columns");
|
|
14
|
+
const headers_1 = require("./glide/headers");
|
|
15
15
|
const pagination_1 = require("./pagination");
|
|
16
16
|
const stabilization_1 = require("./stabilization");
|
|
17
17
|
const glideFillStrategy = (_a) => __awaiter(void 0, [_a], void 0, function* ({ value, page }) {
|
|
@@ -4,10 +4,21 @@ export * from './columns';
|
|
|
4
4
|
export * from './headers';
|
|
5
5
|
export * from './fill';
|
|
6
6
|
export * from './resolution';
|
|
7
|
+
export * from './dedupe';
|
|
8
|
+
export * from './loading';
|
|
7
9
|
export declare const Strategies: {
|
|
8
10
|
Pagination: {
|
|
9
|
-
clickNext: (nextButtonSelector: import("..").Selector,
|
|
10
|
-
|
|
11
|
+
clickNext: (nextButtonSelector: import("..").Selector, options?: {
|
|
12
|
+
stabilization?: import("./stabilization").StabilizationStrategy;
|
|
13
|
+
timeout?: number;
|
|
14
|
+
}) => import("..").PaginationStrategy;
|
|
15
|
+
infiniteScroll: (options?: {
|
|
16
|
+
action?: "scroll" | "js-scroll";
|
|
17
|
+
scrollTarget?: import("..").Selector;
|
|
18
|
+
scrollAmount?: number;
|
|
19
|
+
stabilization?: import("./stabilization").StabilizationStrategy;
|
|
20
|
+
timeout?: number;
|
|
21
|
+
}) => import("..").PaginationStrategy;
|
|
11
22
|
};
|
|
12
23
|
Sorting: {
|
|
13
24
|
AriaSort: () => import("..").SortingStrategy;
|
|
@@ -24,4 +35,24 @@ export declare const Strategies: {
|
|
|
24
35
|
Resolution: {
|
|
25
36
|
default: import("./resolution").ColumnResolutionStrategy;
|
|
26
37
|
};
|
|
38
|
+
Dedupe: {
|
|
39
|
+
byTopPosition: (tolerance?: number) => import("..").DedupeStrategy;
|
|
40
|
+
};
|
|
41
|
+
Loading: {
|
|
42
|
+
Table: {
|
|
43
|
+
hasSpinner: (selector?: string) => ({ root }: import("..").TableContext) => Promise<boolean>;
|
|
44
|
+
custom: (fn: (context: import("..").TableContext) => Promise<boolean>) => (context: import("..").TableContext) => Promise<boolean>;
|
|
45
|
+
never: () => Promise<boolean>;
|
|
46
|
+
};
|
|
47
|
+
Row: {
|
|
48
|
+
hasClass: (className?: string) => (row: import("..").SmartRow) => Promise<boolean>;
|
|
49
|
+
hasText: (text?: string | RegExp) => (row: import("..").SmartRow) => Promise<boolean>;
|
|
50
|
+
hasEmptyCells: () => (row: import("..").SmartRow) => Promise<boolean>;
|
|
51
|
+
never: () => Promise<boolean>;
|
|
52
|
+
};
|
|
53
|
+
Headers: {
|
|
54
|
+
stable: (duration?: number) => (context: import("..").TableContext) => Promise<boolean>;
|
|
55
|
+
never: () => Promise<boolean>;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
27
58
|
};
|
package/dist/strategies/index.js
CHANGED
|
@@ -21,12 +21,16 @@ const columns_1 = require("./columns");
|
|
|
21
21
|
const headers_1 = require("./headers");
|
|
22
22
|
const fill_1 = require("./fill");
|
|
23
23
|
const resolution_1 = require("./resolution");
|
|
24
|
+
const dedupe_1 = require("./dedupe");
|
|
25
|
+
const loading_1 = require("./loading");
|
|
24
26
|
__exportStar(require("./pagination"), exports);
|
|
25
27
|
__exportStar(require("./sorting"), exports);
|
|
26
28
|
__exportStar(require("./columns"), exports);
|
|
27
29
|
__exportStar(require("./headers"), exports);
|
|
28
30
|
__exportStar(require("./fill"), exports);
|
|
29
31
|
__exportStar(require("./resolution"), exports);
|
|
32
|
+
__exportStar(require("./dedupe"), exports);
|
|
33
|
+
__exportStar(require("./loading"), exports);
|
|
30
34
|
exports.Strategies = {
|
|
31
35
|
Pagination: pagination_1.PaginationStrategies,
|
|
32
36
|
Sorting: sorting_1.SortingStrategies,
|
|
@@ -34,4 +38,6 @@ exports.Strategies = {
|
|
|
34
38
|
Header: headers_1.HeaderStrategies,
|
|
35
39
|
Fill: fill_1.FillStrategies,
|
|
36
40
|
Resolution: resolution_1.ResolutionStrategies,
|
|
41
|
+
Dedupe: dedupe_1.DedupeStrategies,
|
|
42
|
+
Loading: loading_1.LoadingStrategies,
|
|
37
43
|
};
|
|
@@ -45,4 +45,18 @@ export declare const LoadingStrategies: {
|
|
|
45
45
|
*/
|
|
46
46
|
never: () => Promise<boolean>;
|
|
47
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* Strategies for detecting if headers are loading/stable.
|
|
50
|
+
*/
|
|
51
|
+
Headers: {
|
|
52
|
+
/**
|
|
53
|
+
* Checks if the headers are stable (count and text) for a specified duration.
|
|
54
|
+
* @param duration Duration in ms for headers to remain unchanged to be considered stable (default: 200).
|
|
55
|
+
*/
|
|
56
|
+
stable: (duration?: number) => (context: TableContext) => Promise<boolean>;
|
|
57
|
+
/**
|
|
58
|
+
* Assume headers are never loading (immediate snapshot).
|
|
59
|
+
*/
|
|
60
|
+
never: () => Promise<boolean>;
|
|
61
|
+
};
|
|
48
62
|
};
|
|
@@ -78,5 +78,36 @@ exports.LoadingStrategies = {
|
|
|
78
78
|
* Assume row is never loading (default).
|
|
79
79
|
*/
|
|
80
80
|
never: () => __awaiter(void 0, void 0, void 0, function* () { return false; })
|
|
81
|
+
},
|
|
82
|
+
/**
|
|
83
|
+
* Strategies for detecting if headers are loading/stable.
|
|
84
|
+
*/
|
|
85
|
+
Headers: {
|
|
86
|
+
/**
|
|
87
|
+
* Checks if the headers are stable (count and text) for a specified duration.
|
|
88
|
+
* @param duration Duration in ms for headers to remain unchanged to be considered stable (default: 200).
|
|
89
|
+
*/
|
|
90
|
+
stable: (duration = 200) => (context) => __awaiter(void 0, void 0, void 0, function* () {
|
|
91
|
+
const { config, resolve, root } = context;
|
|
92
|
+
const getHeaderTexts = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
93
|
+
const headers = yield resolve(config.headerSelector, root).all();
|
|
94
|
+
return Promise.all(headers.map(h => h.innerText()));
|
|
95
|
+
});
|
|
96
|
+
const initial = yield getHeaderTexts();
|
|
97
|
+
// Wait for duration
|
|
98
|
+
yield context.page.waitForTimeout(duration);
|
|
99
|
+
const current = yield getHeaderTexts();
|
|
100
|
+
if (initial.length !== current.length)
|
|
101
|
+
return true; // Count changed, still loading
|
|
102
|
+
for (let i = 0; i < initial.length; i++) {
|
|
103
|
+
if (initial[i] !== current[i])
|
|
104
|
+
return true; // Content changed, still loading
|
|
105
|
+
}
|
|
106
|
+
return false; // Stable
|
|
107
|
+
}),
|
|
108
|
+
/**
|
|
109
|
+
* Assume headers are never loading (immediate snapshot).
|
|
110
|
+
*/
|
|
111
|
+
never: () => __awaiter(void 0, void 0, void 0, function* () { return false; })
|
|
81
112
|
}
|
|
82
113
|
};
|
|
@@ -1,11 +1,33 @@
|
|
|
1
1
|
import type { PaginationStrategy, Selector } from '../types';
|
|
2
|
+
import { StabilizationStrategy } from './stabilization';
|
|
2
3
|
export declare const PaginationStrategies: {
|
|
3
4
|
/**
|
|
4
|
-
* Strategy: Clicks a "Next" button and waits for
|
|
5
|
+
* Strategy: Clicks a "Next" button and waits for stabilization.
|
|
6
|
+
* @param nextButtonSelector Selector for the next page button.
|
|
7
|
+
* @param options.stabilization Strategy to determine when the page has updated.
|
|
8
|
+
* Defaults to `contentChanged({ scope: 'first' })`.
|
|
9
|
+
* @param options.timeout Timeout for the click action.
|
|
5
10
|
*/
|
|
6
|
-
clickNext: (nextButtonSelector: Selector,
|
|
11
|
+
clickNext: (nextButtonSelector: Selector, options?: {
|
|
12
|
+
stabilization?: StabilizationStrategy;
|
|
13
|
+
timeout?: number;
|
|
14
|
+
}) => PaginationStrategy;
|
|
7
15
|
/**
|
|
8
|
-
* Strategy:
|
|
16
|
+
* Strategy: Infinite Scroll (generic).
|
|
17
|
+
* Supports both simple "Scroll to Bottom" and "Virtualized Scroll".
|
|
18
|
+
*
|
|
19
|
+
* @param options.action 'scroll' (mouse wheel) or 'js-scroll' (direct scrollTop).
|
|
20
|
+
* @param options.scrollTarget Selector for the scroll container (defaults to table root).
|
|
21
|
+
* @param options.scrollAmount Amount to scroll in pixels (default 500).
|
|
22
|
+
* @param options.stabilization Strategy to determine if new content loaded.
|
|
23
|
+
* Defaults to `rowCountIncreased` (simple append).
|
|
24
|
+
* Use `contentChanged` for virtualization.
|
|
9
25
|
*/
|
|
10
|
-
infiniteScroll: (
|
|
26
|
+
infiniteScroll: (options?: {
|
|
27
|
+
action?: "scroll" | "js-scroll";
|
|
28
|
+
scrollTarget?: Selector;
|
|
29
|
+
scrollAmount?: number;
|
|
30
|
+
stabilization?: StabilizationStrategy;
|
|
31
|
+
timeout?: number;
|
|
32
|
+
}) => PaginationStrategy;
|
|
11
33
|
};
|
|
@@ -10,41 +10,70 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.PaginationStrategies = void 0;
|
|
13
|
-
const
|
|
13
|
+
const stabilization_1 = require("./stabilization");
|
|
14
14
|
exports.PaginationStrategies = {
|
|
15
15
|
/**
|
|
16
|
-
* Strategy: Clicks a "Next" button and waits for
|
|
16
|
+
* Strategy: Clicks a "Next" button and waits for stabilization.
|
|
17
|
+
* @param nextButtonSelector Selector for the next page button.
|
|
18
|
+
* @param options.stabilization Strategy to determine when the page has updated.
|
|
19
|
+
* Defaults to `contentChanged({ scope: 'first' })`.
|
|
20
|
+
* @param options.timeout Timeout for the click action.
|
|
17
21
|
*/
|
|
18
|
-
clickNext: (nextButtonSelector,
|
|
19
|
-
return (
|
|
22
|
+
clickNext: (nextButtonSelector, options = {}) => {
|
|
23
|
+
return (context) => __awaiter(void 0, void 0, void 0, function* () {
|
|
24
|
+
var _a;
|
|
25
|
+
const { root, resolve, page } = context;
|
|
20
26
|
const nextBtn = resolve(nextButtonSelector, root).first();
|
|
21
27
|
if (!(yield nextBtn.isVisible()) || !(yield nextBtn.isEnabled())) {
|
|
22
28
|
return false;
|
|
23
29
|
}
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
const success = yield (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}), timeout, page);
|
|
30
|
+
// Default stabilization: Wait for first row content to change
|
|
31
|
+
const stabilization = (_a = options.stabilization) !== null && _a !== void 0 ? _a : stabilization_1.StabilizationStrategies.contentChanged({ scope: 'first', timeout: options.timeout });
|
|
32
|
+
// Stabilization: Wrap action
|
|
33
|
+
const success = yield stabilization(context, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
34
|
+
yield nextBtn.click({ timeout: 2000 }).catch(() => { });
|
|
35
|
+
}));
|
|
31
36
|
return success;
|
|
32
37
|
});
|
|
33
38
|
},
|
|
34
39
|
/**
|
|
35
|
-
* Strategy:
|
|
40
|
+
* Strategy: Infinite Scroll (generic).
|
|
41
|
+
* Supports both simple "Scroll to Bottom" and "Virtualized Scroll".
|
|
42
|
+
*
|
|
43
|
+
* @param options.action 'scroll' (mouse wheel) or 'js-scroll' (direct scrollTop).
|
|
44
|
+
* @param options.scrollTarget Selector for the scroll container (defaults to table root).
|
|
45
|
+
* @param options.scrollAmount Amount to scroll in pixels (default 500).
|
|
46
|
+
* @param options.stabilization Strategy to determine if new content loaded.
|
|
47
|
+
* Defaults to `rowCountIncreased` (simple append).
|
|
48
|
+
* Use `contentChanged` for virtualization.
|
|
36
49
|
*/
|
|
37
|
-
infiniteScroll: (
|
|
38
|
-
return (
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
infiniteScroll: (options = {}) => {
|
|
51
|
+
return (context) => __awaiter(void 0, void 0, void 0, function* () {
|
|
52
|
+
var _a, _b;
|
|
53
|
+
const { root, resolve, page } = context;
|
|
54
|
+
const scrollTarget = options.scrollTarget
|
|
55
|
+
? resolve(options.scrollTarget, root)
|
|
56
|
+
: root;
|
|
57
|
+
// Default stabilization: Wait for row count to increase (Append mode)
|
|
58
|
+
const stabilization = (_a = options.stabilization) !== null && _a !== void 0 ? _a : stabilization_1.StabilizationStrategies.rowCountIncreased({ timeout: options.timeout });
|
|
59
|
+
const amount = (_b = options.scrollAmount) !== null && _b !== void 0 ? _b : 500;
|
|
60
|
+
const doScroll = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
61
|
+
const box = yield scrollTarget.boundingBox();
|
|
62
|
+
// Action: Scroll
|
|
63
|
+
if (options.action === 'js-scroll' || !box) {
|
|
64
|
+
yield scrollTarget.evaluate((el, y) => {
|
|
65
|
+
el.scrollTop += y;
|
|
66
|
+
}, amount);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// Mouse Wheel
|
|
70
|
+
yield page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
|
71
|
+
yield page.mouse.wheel(0, amount);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
// Stabilization: Wait
|
|
75
|
+
const success = yield stabilization(context, doScroll);
|
|
76
|
+
return success;
|
|
48
77
|
});
|
|
49
78
|
}
|
|
50
79
|
};
|