@rickcedwhat/playwright-smart-table 3.2.0 → 5.0.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 +357 -100
- package/dist/filterEngine.d.ts +11 -0
- package/dist/filterEngine.js +38 -0
- package/dist/smartRow.d.ts +7 -0
- package/dist/smartRow.js +152 -0
- package/dist/strategies/columns.d.ts +5 -17
- package/dist/strategies/columns.js +2 -34
- package/dist/strategies/headers.d.ts +0 -16
- package/dist/strategies/headers.js +1 -113
- package/dist/strategies/index.d.ts +25 -0
- package/dist/strategies/index.js +19 -1
- package/dist/strategies/pagination.d.ts +0 -21
- package/dist/strategies/pagination.js +1 -23
- package/dist/strategies/resolution.d.ts +22 -0
- package/dist/strategies/resolution.js +30 -0
- package/dist/strategies/validation.d.ts +22 -0
- package/dist/strategies/validation.js +54 -0
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +188 -58
- package/dist/types.d.ts +177 -66
- package/dist/useTable.d.ts +5 -9
- package/dist/useTable.js +139 -268
- package/package.json +2 -2
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Locator, Page } from "@playwright/test";
|
|
2
|
+
import { FinalTableConfig } from "./types";
|
|
3
|
+
export declare class FilterEngine {
|
|
4
|
+
private config;
|
|
5
|
+
private resolve;
|
|
6
|
+
constructor(config: FinalTableConfig, resolve: (selector: any, parent: Locator | Page) => Locator);
|
|
7
|
+
/**
|
|
8
|
+
* Applies filters to a set of rows.
|
|
9
|
+
*/
|
|
10
|
+
applyFilters(baseRows: Locator, filters: Record<string, string | RegExp | number>, map: Map<string, number>, exact: boolean, page: Page): Locator;
|
|
11
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FilterEngine = void 0;
|
|
4
|
+
class FilterEngine {
|
|
5
|
+
constructor(config, resolve) {
|
|
6
|
+
this.config = config;
|
|
7
|
+
this.resolve = resolve;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Applies filters to a set of rows.
|
|
11
|
+
*/
|
|
12
|
+
applyFilters(baseRows, filters, map, exact, page) {
|
|
13
|
+
let filtered = baseRows;
|
|
14
|
+
// Iterate through each filter criteria
|
|
15
|
+
for (const [colName, value] of Object.entries(filters)) {
|
|
16
|
+
// Find column index
|
|
17
|
+
const colIndex = map.get(colName);
|
|
18
|
+
// TODO: Use ColumnStrategy for better resolution error handling
|
|
19
|
+
if (colIndex === undefined) {
|
|
20
|
+
throw new Error(`Filter Error: Column "${colName}" not found.`);
|
|
21
|
+
}
|
|
22
|
+
const filterVal = typeof value === 'number' ? String(value) : value;
|
|
23
|
+
// Use strategy if provided (For future: configured filter strategies)
|
|
24
|
+
// But for now, we implement the default logic or use custom if we add it to config later
|
|
25
|
+
// Default Filter Logic
|
|
26
|
+
const cellTemplate = this.resolve(this.config.cellSelector, page);
|
|
27
|
+
// This logic assumes 1:1 row-to-cell mapping based on index.
|
|
28
|
+
// filter({ has: ... }) checks if the row *contains* the matching cell.
|
|
29
|
+
// But we need to be specific about WHICH cell.
|
|
30
|
+
// Locator filtering by `has: locator.nth(index)` works if `locator` search is relative to the row.
|
|
31
|
+
filtered = filtered.filter({
|
|
32
|
+
has: cellTemplate.nth(colIndex).getByText(filterVal, { exact }),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return filtered;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.FilterEngine = FilterEngine;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Locator, Page } from '@playwright/test';
|
|
2
|
+
import { SmartRow as SmartRowType, FinalTableConfig, TableResult } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Factory to create a SmartRow by extending a Playwright Locator.
|
|
5
|
+
* We avoid Class/Proxy to ensure full compatibility with Playwright's expect(locator) matchers.
|
|
6
|
+
*/
|
|
7
|
+
export declare const createSmartRow: <T = any>(rowLocator: Locator, map: Map<string, number>, rowIndex: number | undefined, config: FinalTableConfig, rootLocator: Locator, resolve: (item: any, parent: Locator | Page) => Locator, table: TableResult<T> | null) => SmartRowType<T>;
|
package/dist/smartRow.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
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.createSmartRow = void 0;
|
|
13
|
+
const fill_1 = require("./strategies/fill");
|
|
14
|
+
/**
|
|
15
|
+
* Factory to create a SmartRow by extending a Playwright Locator.
|
|
16
|
+
* We avoid Class/Proxy to ensure full compatibility with Playwright's expect(locator) matchers.
|
|
17
|
+
*/
|
|
18
|
+
const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve, table) => {
|
|
19
|
+
const smart = rowLocator;
|
|
20
|
+
// Attach State
|
|
21
|
+
smart.rowIndex = rowIndex;
|
|
22
|
+
// Attach Methods
|
|
23
|
+
smart.getCell = (colName) => {
|
|
24
|
+
const idx = map.get(colName);
|
|
25
|
+
if (idx === undefined) {
|
|
26
|
+
const availableColumns = Array.from(map.keys());
|
|
27
|
+
throw new Error(`Column "${colName}" not found. Available: ${availableColumns.join(', ')}`);
|
|
28
|
+
}
|
|
29
|
+
if (config.strategies.getCellLocator) {
|
|
30
|
+
return config.strategies.getCellLocator({
|
|
31
|
+
row: rowLocator,
|
|
32
|
+
columnName: colName,
|
|
33
|
+
columnIndex: idx,
|
|
34
|
+
rowIndex: rowIndex,
|
|
35
|
+
page: rootLocator.page()
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return resolve(config.cellSelector, rowLocator).nth(idx);
|
|
39
|
+
};
|
|
40
|
+
smart.toJSON = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
41
|
+
const result = {};
|
|
42
|
+
const page = rootLocator.page();
|
|
43
|
+
for (const [col, idx] of map.entries()) {
|
|
44
|
+
if ((options === null || options === void 0 ? void 0 : options.columns) && !options.columns.includes(col)) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
// Get the cell locator
|
|
48
|
+
const cell = config.strategies.getCellLocator
|
|
49
|
+
? config.strategies.getCellLocator({
|
|
50
|
+
row: rowLocator,
|
|
51
|
+
columnName: col,
|
|
52
|
+
columnIndex: idx,
|
|
53
|
+
rowIndex: rowIndex,
|
|
54
|
+
page: page
|
|
55
|
+
})
|
|
56
|
+
: resolve(config.cellSelector, rowLocator).nth(idx);
|
|
57
|
+
let targetCell = cell;
|
|
58
|
+
// Check if cell exists
|
|
59
|
+
const count = yield cell.count();
|
|
60
|
+
if (count === 0) {
|
|
61
|
+
// Optimization: Check if we are ALREADY at the target cell
|
|
62
|
+
if (config.strategies.getActiveCell) {
|
|
63
|
+
const active = yield config.strategies.getActiveCell({
|
|
64
|
+
config,
|
|
65
|
+
root: rootLocator,
|
|
66
|
+
page,
|
|
67
|
+
resolve
|
|
68
|
+
});
|
|
69
|
+
if (active && active.rowIndex === rowIndex && active.columnIndex === idx) {
|
|
70
|
+
if (config.debug)
|
|
71
|
+
console.log(`[SmartRow] Already at target cell (r:${active.rowIndex}, c:${active.columnIndex}), skipping navigation.`);
|
|
72
|
+
targetCell = active.locator;
|
|
73
|
+
// Skip navigation and go to reading text
|
|
74
|
+
const text = yield targetCell.innerText();
|
|
75
|
+
result[col] = (text || '').trim();
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Cell doesn't exist - navigate to it
|
|
80
|
+
if (config.debug) {
|
|
81
|
+
console.log(`[SmartRow.toJSON] Cell not found for column "${col}" (index ${idx}), navigating...`);
|
|
82
|
+
}
|
|
83
|
+
yield config.strategies.cellNavigation({
|
|
84
|
+
config: config,
|
|
85
|
+
root: rootLocator,
|
|
86
|
+
page: page,
|
|
87
|
+
resolve: resolve,
|
|
88
|
+
column: col,
|
|
89
|
+
index: idx,
|
|
90
|
+
rowLocator: rowLocator,
|
|
91
|
+
rowIndex: rowIndex
|
|
92
|
+
});
|
|
93
|
+
// Optimization: check if we can get the active cell directly
|
|
94
|
+
if (config.strategies.getActiveCell) {
|
|
95
|
+
const activeCell = yield config.strategies.getActiveCell({
|
|
96
|
+
config,
|
|
97
|
+
root: rootLocator,
|
|
98
|
+
page,
|
|
99
|
+
resolve
|
|
100
|
+
});
|
|
101
|
+
if (activeCell) {
|
|
102
|
+
if (config.debug) {
|
|
103
|
+
console.log(`[SmartRow.toJSON] switching to active cell locator (r:${activeCell.rowIndex}, c:${activeCell.columnIndex})`);
|
|
104
|
+
}
|
|
105
|
+
targetCell = activeCell.locator;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const text = yield targetCell.innerText();
|
|
110
|
+
result[col] = (text || '').trim();
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
});
|
|
114
|
+
smart.smartFill = (data, fillOptions) => __awaiter(void 0, void 0, void 0, function* () {
|
|
115
|
+
for (const [colName, value] of Object.entries(data)) {
|
|
116
|
+
const colIdx = map.get(colName);
|
|
117
|
+
if (colIdx === undefined) {
|
|
118
|
+
throw new Error(`Column "${colName}" not found in fill data.`);
|
|
119
|
+
}
|
|
120
|
+
yield config.strategies.cellNavigation({
|
|
121
|
+
config: config,
|
|
122
|
+
root: rootLocator,
|
|
123
|
+
page: rootLocator.page(),
|
|
124
|
+
resolve: resolve,
|
|
125
|
+
column: colName,
|
|
126
|
+
index: colIdx,
|
|
127
|
+
rowLocator: rowLocator,
|
|
128
|
+
rowIndex: rowIndex
|
|
129
|
+
});
|
|
130
|
+
const strategy = config.strategies.fill || fill_1.FillStrategies.default;
|
|
131
|
+
yield strategy({
|
|
132
|
+
row: smart,
|
|
133
|
+
columnName: colName,
|
|
134
|
+
value,
|
|
135
|
+
index: rowIndex !== null && rowIndex !== void 0 ? rowIndex : -1,
|
|
136
|
+
page: rowLocator.page(),
|
|
137
|
+
rootLocator,
|
|
138
|
+
table: table,
|
|
139
|
+
fillOptions
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
smart.bringIntoView = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
144
|
+
if (rowIndex === undefined) {
|
|
145
|
+
throw new Error('Cannot bring row into view - row index is unknown. Use getRowByIndex() instead of getRow().');
|
|
146
|
+
}
|
|
147
|
+
// Scroll row into view using Playwright's built-in method
|
|
148
|
+
yield rowLocator.scrollIntoViewIfNeeded();
|
|
149
|
+
});
|
|
150
|
+
return smart;
|
|
151
|
+
};
|
|
152
|
+
exports.createSmartRow = createSmartRow;
|
|
@@ -1,30 +1,18 @@
|
|
|
1
1
|
import { StrategyContext } from '../types';
|
|
2
2
|
/**
|
|
3
|
-
* Defines the contract for a
|
|
4
|
-
* It is responsible for ensuring a specific
|
|
5
|
-
* typically by scrolling or
|
|
3
|
+
* 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
6
|
*/
|
|
7
|
-
export type
|
|
7
|
+
export type CellNavigationStrategy = (context: StrategyContext & {
|
|
8
8
|
column: string;
|
|
9
9
|
index: number;
|
|
10
10
|
rowIndex?: number;
|
|
11
11
|
}) => Promise<void>;
|
|
12
|
-
export declare const
|
|
12
|
+
export declare const CellNavigationStrategies: {
|
|
13
13
|
/**
|
|
14
14
|
* Default strategy: Assumes column is accessible or standard scrolling works.
|
|
15
15
|
* No specific action taken other than what Playwright's default locator handling does.
|
|
16
16
|
*/
|
|
17
17
|
default: () => Promise<void>;
|
|
18
|
-
/**
|
|
19
|
-
* Strategy that clicks into the table to establish focus and then uses the Right Arrow key
|
|
20
|
-
* to navigate to the target column index.
|
|
21
|
-
*
|
|
22
|
-
* Useful for canvas-based grids like Glide where DOM scrolling might not be enough for interaction
|
|
23
|
-
* or where keyboard navigation is the primary way to move focus.
|
|
24
|
-
*/
|
|
25
|
-
keyboard: (context: StrategyContext & {
|
|
26
|
-
column: string;
|
|
27
|
-
index: number;
|
|
28
|
-
rowIndex?: number;
|
|
29
|
-
}) => Promise<void>;
|
|
30
18
|
};
|
|
@@ -9,45 +9,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.
|
|
13
|
-
exports.
|
|
12
|
+
exports.CellNavigationStrategies = void 0;
|
|
13
|
+
exports.CellNavigationStrategies = {
|
|
14
14
|
/**
|
|
15
15
|
* Default strategy: Assumes column is accessible or standard scrolling works.
|
|
16
16
|
* No specific action taken other than what Playwright's default locator handling does.
|
|
17
17
|
*/
|
|
18
18
|
default: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
19
19
|
// No-op
|
|
20
|
-
}),
|
|
21
|
-
/**
|
|
22
|
-
* Strategy that clicks into the table to establish focus and then uses the Right Arrow key
|
|
23
|
-
* to navigate to the target column index.
|
|
24
|
-
*
|
|
25
|
-
* Useful for canvas-based grids like Glide where DOM scrolling might not be enough for interaction
|
|
26
|
-
* or where keyboard navigation is the primary way to move focus.
|
|
27
|
-
*/
|
|
28
|
-
keyboard: (context) => __awaiter(void 0, void 0, void 0, function* () {
|
|
29
|
-
const { root, page, index, rowLocator, rowIndex } = context;
|
|
30
|
-
if (typeof rowIndex !== 'number') {
|
|
31
|
-
throw new Error('Row index is required for keyboard navigation');
|
|
32
|
-
}
|
|
33
|
-
console.log(`[ColumnStrat:keyboard] Using Row Index Navigation: Row ${rowIndex}, Col ${index}`);
|
|
34
|
-
yield root.focus();
|
|
35
|
-
yield page.waitForTimeout(200);
|
|
36
|
-
// Robust Navigation:
|
|
37
|
-
// 1. Jump to Top-Left (Reset) - Sequence for Cross-OS (Mac/Windows)
|
|
38
|
-
yield page.keyboard.press('Control+Home');
|
|
39
|
-
yield page.keyboard.press('Meta+ArrowUp'); // Mac Go-To-Top
|
|
40
|
-
yield page.keyboard.press('Home'); // Ensure start of row
|
|
41
|
-
yield page.waitForTimeout(300);
|
|
42
|
-
// 2. Move Down to Target Row
|
|
43
|
-
for (let i = 0; i < rowIndex; i++) {
|
|
44
|
-
yield page.keyboard.press('ArrowDown');
|
|
45
|
-
}
|
|
46
|
-
// 3. Move Right to Target Column
|
|
47
|
-
for (let i = 0; i < index; i++) {
|
|
48
|
-
yield page.keyboard.press('ArrowRight');
|
|
49
|
-
}
|
|
50
|
-
yield page.waitForTimeout(100);
|
|
51
|
-
yield page.waitForTimeout(100);
|
|
52
20
|
})
|
|
53
21
|
};
|
|
@@ -10,20 +10,4 @@ export declare const HeaderStrategies: {
|
|
|
10
10
|
* This is fast but won't find virtualized columns off-screen.
|
|
11
11
|
*/
|
|
12
12
|
visible: ({ config, resolve, root }: StrategyContext) => Promise<string[]>;
|
|
13
|
-
/**
|
|
14
|
-
* Scans for headers by finding a scrollable container and setting scrollLeft.
|
|
15
|
-
*/
|
|
16
|
-
scrollRight: (context: StrategyContext, options?: {
|
|
17
|
-
limit?: number;
|
|
18
|
-
selector?: string;
|
|
19
|
-
scrollAmount?: number;
|
|
20
|
-
}) => Promise<string[]>;
|
|
21
|
-
/**
|
|
22
|
-
* Strategy that clicks into the table to establish focus and then uses the Right Arrow key
|
|
23
|
-
* to navigate cell-by-cell, collecting headers found along the way.
|
|
24
|
-
*/
|
|
25
|
-
keyboard: (context: StrategyContext, options?: {
|
|
26
|
-
limit?: number;
|
|
27
|
-
maxSilentClicks?: number;
|
|
28
|
-
}) => Promise<string[]>;
|
|
29
13
|
};
|
|
@@ -26,117 +26,5 @@ exports.HeaderStrategies = {
|
|
|
26
26
|
}
|
|
27
27
|
const texts = yield headerLoc.allInnerTexts();
|
|
28
28
|
return texts.map(t => t.trim());
|
|
29
|
-
})
|
|
30
|
-
/**
|
|
31
|
-
* Scans for headers by finding a scrollable container and setting scrollLeft.
|
|
32
|
-
*/
|
|
33
|
-
scrollRight: (context, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
34
|
-
var _a, _b;
|
|
35
|
-
const { resolve, config, root, page } = context;
|
|
36
|
-
const limit = (_a = options === null || options === void 0 ? void 0 : options.limit) !== null && _a !== void 0 ? _a : 20;
|
|
37
|
-
const scrollAmount = (_b = options === null || options === void 0 ? void 0 : options.scrollAmount) !== null && _b !== void 0 ? _b : 300;
|
|
38
|
-
const collectedHeaders = new Set();
|
|
39
|
-
const getVisible = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
40
|
-
const headerLoc = resolve(config.headerSelector, root);
|
|
41
|
-
const texts = yield headerLoc.allInnerTexts();
|
|
42
|
-
return texts.map(t => t.trim());
|
|
43
|
-
});
|
|
44
|
-
// Initial capture
|
|
45
|
-
let currentHeaders = yield getVisible();
|
|
46
|
-
currentHeaders.forEach(h => collectedHeaders.add(h));
|
|
47
|
-
// Find scroller using JS for better iframe/shadow support
|
|
48
|
-
const scrollerHandle = yield root.evaluateHandle((el, selector) => {
|
|
49
|
-
if (selector && el.matches(selector))
|
|
50
|
-
return el;
|
|
51
|
-
const effectiveSelector = selector || '.dvn-scroller';
|
|
52
|
-
const ancestor = el.closest(effectiveSelector);
|
|
53
|
-
if (ancestor)
|
|
54
|
-
return ancestor;
|
|
55
|
-
return document.querySelector(effectiveSelector);
|
|
56
|
-
}, options === null || options === void 0 ? void 0 : options.selector);
|
|
57
|
-
const isScrollerFound = yield scrollerHandle.evaluate(el => !!el);
|
|
58
|
-
if (isScrollerFound) {
|
|
59
|
-
yield scrollerHandle.evaluate(el => el.scrollLeft = 0);
|
|
60
|
-
yield page.waitForTimeout(200);
|
|
61
|
-
for (let i = 0; i < limit; i++) {
|
|
62
|
-
const sizeBefore = collectedHeaders.size;
|
|
63
|
-
yield scrollerHandle.evaluate((el, amount) => el.scrollLeft += amount, scrollAmount);
|
|
64
|
-
yield page.waitForTimeout(300);
|
|
65
|
-
const newHeaders = yield getVisible();
|
|
66
|
-
console.log(`[HeaderStrat:scrollRight] Scrolled ${scrollAmount}, found: ${newHeaders.length} visible.`);
|
|
67
|
-
newHeaders.forEach(h => collectedHeaders.add(h));
|
|
68
|
-
if (collectedHeaders.size === sizeBefore) {
|
|
69
|
-
yield scrollerHandle.evaluate((el, amount) => el.scrollLeft += amount, scrollAmount);
|
|
70
|
-
yield page.waitForTimeout(300);
|
|
71
|
-
const retryHeaders = yield getVisible();
|
|
72
|
-
retryHeaders.forEach(h => collectedHeaders.add(h));
|
|
73
|
-
if (collectedHeaders.size === sizeBefore)
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
console.warn("HeaderStrategies.scrollRight: Could not find scroller. Returning visible headers.");
|
|
80
|
-
}
|
|
81
|
-
// Scroll back to start
|
|
82
|
-
yield scrollerHandle.evaluate(el => el.scrollLeft = 0);
|
|
83
|
-
yield page.waitForTimeout(200);
|
|
84
|
-
return Array.from(collectedHeaders);
|
|
85
|
-
}),
|
|
86
|
-
/**
|
|
87
|
-
* Strategy that clicks into the table to establish focus and then uses the Right Arrow key
|
|
88
|
-
* to navigate cell-by-cell, collecting headers found along the way.
|
|
89
|
-
*/
|
|
90
|
-
keyboard: (context, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
91
|
-
var _a, _b;
|
|
92
|
-
const { resolve, config, root, page } = context;
|
|
93
|
-
const limit = (_a = options === null || options === void 0 ? void 0 : options.limit) !== null && _a !== void 0 ? _a : 100;
|
|
94
|
-
const maxSilentClicks = (_b = options === null || options === void 0 ? void 0 : options.maxSilentClicks) !== null && _b !== void 0 ? _b : 10;
|
|
95
|
-
const collectedHeaders = new Set();
|
|
96
|
-
const getVisible = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
97
|
-
const headerLoc = resolve(config.headerSelector, root);
|
|
98
|
-
const texts = yield headerLoc.allInnerTexts();
|
|
99
|
-
return texts.map(t => t.trim());
|
|
100
|
-
});
|
|
101
|
-
// 1. Initial capture
|
|
102
|
-
let currentHeaders = yield getVisible();
|
|
103
|
-
currentHeaders.forEach(h => collectedHeaders.add(h));
|
|
104
|
-
// 2. Click to focus
|
|
105
|
-
// Try to find the canvas sibling specifically for Glide, otherwise click root
|
|
106
|
-
const canvas = root.locator('xpath=ancestor::div').locator('canvas').first();
|
|
107
|
-
if ((yield canvas.count()) > 0) {
|
|
108
|
-
// Click lower in the canvas to hit a data cell instead of header
|
|
109
|
-
// Adjusted to y=60 to target Row 1
|
|
110
|
-
yield canvas.click({ position: { x: 50, y: 60 }, force: true }).catch(() => { });
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
yield root.click({ position: { x: 10, y: 10 }, force: true }).catch(() => { });
|
|
114
|
-
}
|
|
115
|
-
// Reset to home
|
|
116
|
-
yield page.keyboard.press('Control+Home');
|
|
117
|
-
yield page.keyboard.press('Home');
|
|
118
|
-
yield page.waitForTimeout(200);
|
|
119
|
-
currentHeaders = yield getVisible();
|
|
120
|
-
currentHeaders.forEach(h => collectedHeaders.add(h));
|
|
121
|
-
// 3. Navigate right loop
|
|
122
|
-
let silentCounter = 0;
|
|
123
|
-
for (let i = 0; i < limit; i++) {
|
|
124
|
-
const sizeBefore = collectedHeaders.size;
|
|
125
|
-
yield page.keyboard.press('ArrowRight');
|
|
126
|
-
yield page.waitForTimeout(100);
|
|
127
|
-
const newHeaders = yield getVisible();
|
|
128
|
-
console.log(`[HeaderStrat:keyboard] Step ${i}, found visible: ${newHeaders}`);
|
|
129
|
-
newHeaders.forEach(h => collectedHeaders.add(h));
|
|
130
|
-
if (collectedHeaders.size === sizeBefore) {
|
|
131
|
-
silentCounter++;
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
silentCounter = 0;
|
|
135
|
-
}
|
|
136
|
-
if (silentCounter >= maxSilentClicks) {
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return Array.from(collectedHeaders);
|
|
141
|
-
}),
|
|
29
|
+
})
|
|
142
30
|
};
|
|
@@ -1,2 +1,27 @@
|
|
|
1
1
|
export * from './pagination';
|
|
2
2
|
export * from './sorting';
|
|
3
|
+
export * from './columns';
|
|
4
|
+
export * from './headers';
|
|
5
|
+
export * from './fill';
|
|
6
|
+
export * from './resolution';
|
|
7
|
+
export declare const Strategies: {
|
|
8
|
+
Pagination: {
|
|
9
|
+
clickNext: (nextButtonSelector: import("..").Selector, timeout?: number) => import("..").PaginationStrategy;
|
|
10
|
+
infiniteScroll: (timeout?: number) => import("..").PaginationStrategy;
|
|
11
|
+
};
|
|
12
|
+
Sorting: {
|
|
13
|
+
AriaSort: () => import("..").SortingStrategy;
|
|
14
|
+
};
|
|
15
|
+
CellNavigation: {
|
|
16
|
+
default: () => Promise<void>;
|
|
17
|
+
};
|
|
18
|
+
Header: {
|
|
19
|
+
visible: ({ config, resolve, root }: import("..").StrategyContext) => Promise<string[]>;
|
|
20
|
+
};
|
|
21
|
+
Fill: {
|
|
22
|
+
default: ({ row, columnName, value, fillOptions }: Parameters<import("..").FillStrategy>[0]) => Promise<void>;
|
|
23
|
+
};
|
|
24
|
+
Resolution: {
|
|
25
|
+
default: import("./resolution").ColumnResolutionStrategy;
|
|
26
|
+
};
|
|
27
|
+
};
|
package/dist/strategies/index.js
CHANGED
|
@@ -14,6 +14,24 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
|
|
17
|
+
exports.Strategies = void 0;
|
|
18
|
+
const pagination_1 = require("./pagination");
|
|
19
|
+
const sorting_1 = require("./sorting");
|
|
20
|
+
const columns_1 = require("./columns");
|
|
21
|
+
const headers_1 = require("./headers");
|
|
22
|
+
const fill_1 = require("./fill");
|
|
23
|
+
const resolution_1 = require("./resolution");
|
|
18
24
|
__exportStar(require("./pagination"), exports);
|
|
19
25
|
__exportStar(require("./sorting"), exports);
|
|
26
|
+
__exportStar(require("./columns"), exports);
|
|
27
|
+
__exportStar(require("./headers"), exports);
|
|
28
|
+
__exportStar(require("./fill"), exports);
|
|
29
|
+
__exportStar(require("./resolution"), exports);
|
|
30
|
+
exports.Strategies = {
|
|
31
|
+
Pagination: pagination_1.PaginationStrategies,
|
|
32
|
+
Sorting: sorting_1.SortingStrategies,
|
|
33
|
+
CellNavigation: columns_1.CellNavigationStrategies,
|
|
34
|
+
Header: headers_1.HeaderStrategies,
|
|
35
|
+
Fill: fill_1.FillStrategies,
|
|
36
|
+
Resolution: resolution_1.ResolutionStrategies,
|
|
37
|
+
};
|
|
@@ -4,27 +4,6 @@ export declare const PaginationStrategies: {
|
|
|
4
4
|
* Strategy: Clicks a "Next" button and waits for the first row of data to change.
|
|
5
5
|
*/
|
|
6
6
|
clickNext: (nextButtonSelector: Selector, timeout?: number) => PaginationStrategy;
|
|
7
|
-
/**
|
|
8
|
-
* Strategy: Clicks a "Load More" button and waits for the row count to increase.
|
|
9
|
-
*/
|
|
10
|
-
clickLoadMore: (buttonSelector: Selector, timeout?: number) => PaginationStrategy;
|
|
11
|
-
/**
|
|
12
|
-
* Strategy: Scrolls to the bottom and waits for more rows to appear.
|
|
13
|
-
*/
|
|
14
|
-
infiniteScroll: (timeout?: number) => PaginationStrategy;
|
|
15
|
-
};
|
|
16
|
-
/**
|
|
17
|
-
* @deprecated Use `PaginationStrategies` instead. This alias will be removed in a future major version.
|
|
18
|
-
*/
|
|
19
|
-
export declare const TableStrategies: {
|
|
20
|
-
/**
|
|
21
|
-
* Strategy: Clicks a "Next" button and waits for the first row of data to change.
|
|
22
|
-
*/
|
|
23
|
-
clickNext: (nextButtonSelector: Selector, timeout?: number) => PaginationStrategy;
|
|
24
|
-
/**
|
|
25
|
-
* Strategy: Clicks a "Load More" button and waits for the row count to increase.
|
|
26
|
-
*/
|
|
27
|
-
clickLoadMore: (buttonSelector: Selector, timeout?: number) => PaginationStrategy;
|
|
28
7
|
/**
|
|
29
8
|
* Strategy: Scrolls to the bottom and waits for more rows to appear.
|
|
30
9
|
*/
|
|
@@ -9,7 +9,7 @@ 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.PaginationStrategies = void 0;
|
|
13
13
|
const utils_1 = require("../utils");
|
|
14
14
|
exports.PaginationStrategies = {
|
|
15
15
|
/**
|
|
@@ -31,24 +31,6 @@ exports.PaginationStrategies = {
|
|
|
31
31
|
return success;
|
|
32
32
|
});
|
|
33
33
|
},
|
|
34
|
-
/**
|
|
35
|
-
* Strategy: Clicks a "Load More" button and waits for the row count to increase.
|
|
36
|
-
*/
|
|
37
|
-
clickLoadMore: (buttonSelector, timeout = 5000) => {
|
|
38
|
-
return (_a) => __awaiter(void 0, [_a], void 0, function* ({ root, config, resolve, page }) {
|
|
39
|
-
const loadMoreBtn = resolve(buttonSelector, root).first();
|
|
40
|
-
if (!(yield loadMoreBtn.isVisible()) || !(yield loadMoreBtn.isEnabled())) {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
const rows = resolve(config.rowSelector, root);
|
|
44
|
-
const oldCount = yield rows.count();
|
|
45
|
-
yield loadMoreBtn.click();
|
|
46
|
-
return yield (0, utils_1.waitForCondition)(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
47
|
-
const newCount = yield rows.count();
|
|
48
|
-
return newCount > oldCount;
|
|
49
|
-
}), timeout, page);
|
|
50
|
-
});
|
|
51
|
-
},
|
|
52
34
|
/**
|
|
53
35
|
* Strategy: Scrolls to the bottom and waits for more rows to appear.
|
|
54
36
|
*/
|
|
@@ -66,7 +48,3 @@ exports.PaginationStrategies = {
|
|
|
66
48
|
});
|
|
67
49
|
}
|
|
68
50
|
};
|
|
69
|
-
/**
|
|
70
|
-
* @deprecated Use `PaginationStrategies` instead. This alias will be removed in a future major version.
|
|
71
|
-
*/
|
|
72
|
-
exports.TableStrategies = exports.PaginationStrategies;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { StrategyContext } from '../types';
|
|
2
|
+
export interface ColumnResolutionStrategy {
|
|
3
|
+
/**
|
|
4
|
+
* Resolves a column name (string or Regex) to a column index.
|
|
5
|
+
* Returns undefined if not found.
|
|
6
|
+
*/
|
|
7
|
+
resolveIndex(options: {
|
|
8
|
+
query: string | RegExp;
|
|
9
|
+
headerMap: Map<string, number>;
|
|
10
|
+
context: StrategyContext;
|
|
11
|
+
}): number | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Resolves a column name to a clean string name (for error messages or debugging).
|
|
14
|
+
*/
|
|
15
|
+
resolveName(options: {
|
|
16
|
+
query: string | RegExp;
|
|
17
|
+
headerMap: Map<string, number>;
|
|
18
|
+
}): string;
|
|
19
|
+
}
|
|
20
|
+
export declare const ResolutionStrategies: {
|
|
21
|
+
default: ColumnResolutionStrategy;
|
|
22
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ResolutionStrategies = void 0;
|
|
4
|
+
exports.ResolutionStrategies = {
|
|
5
|
+
default: {
|
|
6
|
+
resolveIndex: ({ query, headerMap }) => {
|
|
7
|
+
// 1. Exact / String Match
|
|
8
|
+
if (typeof query === 'string') {
|
|
9
|
+
if (headerMap.has(query))
|
|
10
|
+
return headerMap.get(query);
|
|
11
|
+
}
|
|
12
|
+
// 2. Regex Match
|
|
13
|
+
if (query instanceof RegExp) {
|
|
14
|
+
for (const [colName, idx] of headerMap.entries()) {
|
|
15
|
+
if (query.test(colName))
|
|
16
|
+
return idx;
|
|
17
|
+
}
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
// 3. (Optional) Fuzzy String Match fallback could go here
|
|
21
|
+
// But for strict default strategy, we might want to keep it simple first
|
|
22
|
+
// The original code didn't do fuzzy *resolution* logic inside the get(), it just did strict get().
|
|
23
|
+
// The fuzzy logic was only for *suggestions* on error.
|
|
24
|
+
return undefined;
|
|
25
|
+
},
|
|
26
|
+
resolveName: ({ query }) => {
|
|
27
|
+
return query.toString();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PaginationStrategy, SortingStrategy, FillStrategy } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Validates that a pagination strategy returns a boolean.
|
|
4
|
+
* @param result - The result from a pagination strategy
|
|
5
|
+
* @param strategyName - Name of the strategy for error messages
|
|
6
|
+
*/
|
|
7
|
+
export declare function validatePaginationResult(result: any, strategyName?: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Validates that a pagination strategy is properly configured.
|
|
10
|
+
* @param strategy - The pagination strategy to validate
|
|
11
|
+
*/
|
|
12
|
+
export declare function validatePaginationStrategy(strategy: any): strategy is PaginationStrategy;
|
|
13
|
+
/**
|
|
14
|
+
* Validates that a sorting strategy has the required methods.
|
|
15
|
+
* @param strategy - The sorting strategy to validate
|
|
16
|
+
*/
|
|
17
|
+
export declare function validateSortingStrategy(strategy: any): strategy is SortingStrategy;
|
|
18
|
+
/**
|
|
19
|
+
* Validates that a fill strategy is properly configured.
|
|
20
|
+
* @param strategy - The fill strategy to validate
|
|
21
|
+
*/
|
|
22
|
+
export declare function validateFillStrategy(strategy: any): strategy is FillStrategy;
|