@rickcedwhat/playwright-smart-table 5.3.0 → 6.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 +78 -957
- package/dist/examples/glide-strategies/columns.d.ts +13 -0
- package/dist/examples/glide-strategies/columns.js +43 -0
- package/dist/examples/glide-strategies/headers.d.ts +9 -0
- package/dist/examples/glide-strategies/headers.js +68 -0
- package/dist/src/filterEngine.d.ts +11 -0
- package/dist/src/filterEngine.js +39 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +18 -0
- package/dist/src/plugins.d.ts +32 -0
- package/dist/src/plugins.js +13 -0
- package/dist/src/smartRow.d.ts +7 -0
- package/dist/src/smartRow.js +160 -0
- package/dist/src/strategies/columns.d.ts +18 -0
- package/dist/src/strategies/columns.js +21 -0
- package/dist/src/strategies/dedupe.d.ts +9 -0
- package/dist/src/strategies/dedupe.js +27 -0
- package/dist/src/strategies/fill.d.ts +7 -0
- package/dist/src/strategies/fill.js +88 -0
- package/dist/src/strategies/glide.d.ts +29 -0
- package/dist/src/strategies/glide.js +98 -0
- package/dist/src/strategies/headers.d.ts +13 -0
- package/dist/src/strategies/headers.js +30 -0
- package/dist/src/strategies/index.d.ts +54 -0
- package/dist/src/strategies/index.js +43 -0
- package/dist/src/strategies/loading.d.ts +48 -0
- package/dist/src/strategies/loading.js +82 -0
- package/dist/src/strategies/pagination.d.ts +33 -0
- package/dist/src/strategies/pagination.js +79 -0
- package/dist/src/strategies/rdg.d.ts +25 -0
- package/dist/src/strategies/rdg.js +100 -0
- package/dist/src/strategies/resolution.d.ts +22 -0
- package/dist/src/strategies/resolution.js +30 -0
- package/dist/src/strategies/sorting.d.ts +12 -0
- package/dist/src/strategies/sorting.js +68 -0
- package/dist/src/strategies/stabilization.d.ts +29 -0
- package/dist/src/strategies/stabilization.js +91 -0
- package/dist/src/strategies/validation.d.ts +22 -0
- package/dist/src/strategies/validation.js +54 -0
- package/dist/src/strategies/virtualizedPagination.d.ts +32 -0
- package/dist/src/strategies/virtualizedPagination.js +80 -0
- package/dist/src/typeContext.d.ts +6 -0
- package/dist/src/typeContext.js +465 -0
- package/dist/src/types.d.ts +458 -0
- package/dist/src/types.js +2 -0
- package/dist/src/useTable.d.ts +44 -0
- package/dist/src/useTable.js +642 -0
- package/dist/src/utils/debugUtils.d.ts +17 -0
- package/dist/src/utils/debugUtils.js +62 -0
- package/dist/src/utils/smartRowArray.d.ts +14 -0
- package/dist/src/utils/smartRowArray.js +22 -0
- package/dist/src/utils/stringUtils.d.ts +22 -0
- package/dist/src/utils/stringUtils.js +73 -0
- package/dist/src/utils.d.ts +7 -0
- package/dist/src/utils.js +29 -0
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +27 -5
- package/dist/types.d.ts +27 -6
- package/dist/useTable.js +21 -16
- package/dist/utils/smartRowArray.d.ts +14 -0
- package/dist/utils/smartRowArray.js +22 -0
- package/package.json +16 -20
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { StrategyContext } from '../../src/types';
|
|
2
|
+
/**
|
|
3
|
+
* Strategy that clicks into the table to establish focus and then uses the Right Arrow key
|
|
4
|
+
* to navigate to the target CELL (navigates down to the row, then right to the column).
|
|
5
|
+
*
|
|
6
|
+
* Useful for canvas-based grids like Glide where DOM scrolling might not be enough for interaction
|
|
7
|
+
* or where keyboard navigation is the primary way to move focus.
|
|
8
|
+
*/
|
|
9
|
+
export declare const keyboardCellNavigation: (context: StrategyContext & {
|
|
10
|
+
column: string;
|
|
11
|
+
index: number;
|
|
12
|
+
rowIndex?: number;
|
|
13
|
+
}) => Promise<void>;
|
|
@@ -0,0 +1,43 @@
|
|
|
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.keyboardCellNavigation = void 0;
|
|
13
|
+
/**
|
|
14
|
+
* Strategy that clicks into the table to establish focus and then uses the Right Arrow key
|
|
15
|
+
* to navigate to the target CELL (navigates down to the row, then right to the column).
|
|
16
|
+
*
|
|
17
|
+
* Useful for canvas-based grids like Glide where DOM scrolling might not be enough for interaction
|
|
18
|
+
* or where keyboard navigation is the primary way to move focus.
|
|
19
|
+
*/
|
|
20
|
+
const keyboardCellNavigation = (context) => __awaiter(void 0, void 0, void 0, function* () {
|
|
21
|
+
const { root, page, index, rowIndex } = context;
|
|
22
|
+
if (typeof rowIndex !== 'number') {
|
|
23
|
+
throw new Error('Row index is required for keyboard navigation');
|
|
24
|
+
}
|
|
25
|
+
yield root.focus();
|
|
26
|
+
yield page.waitForTimeout(100);
|
|
27
|
+
// Robust Navigation:
|
|
28
|
+
// 1. Jump to Top-Left (Reset) - Sequence for Cross-OS (Mac/Windows)
|
|
29
|
+
yield page.keyboard.press('Control+Home');
|
|
30
|
+
yield page.keyboard.press('Meta+ArrowUp'); // Mac Go-To-Top
|
|
31
|
+
yield page.keyboard.press('Home'); // Ensure start of row
|
|
32
|
+
yield page.waitForTimeout(150);
|
|
33
|
+
// 2. Move Down to Target Row
|
|
34
|
+
for (let i = 0; i < rowIndex; i++) {
|
|
35
|
+
yield page.keyboard.press('ArrowDown');
|
|
36
|
+
}
|
|
37
|
+
// 3. Move Right to Target Column
|
|
38
|
+
for (let i = 0; i < index; i++) {
|
|
39
|
+
yield page.keyboard.press('ArrowRight');
|
|
40
|
+
}
|
|
41
|
+
yield page.waitForTimeout(50);
|
|
42
|
+
});
|
|
43
|
+
exports.keyboardCellNavigation = keyboardCellNavigation;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { StrategyContext } from '../../src/types';
|
|
2
|
+
/**
|
|
3
|
+
* Scans for headers by finding a scrollable container and setting scrollLeft.
|
|
4
|
+
*/
|
|
5
|
+
export declare const scrollRightHeader: (context: StrategyContext, options?: {
|
|
6
|
+
limit?: number;
|
|
7
|
+
selector?: string;
|
|
8
|
+
scrollAmount?: number;
|
|
9
|
+
}) => Promise<string[]>;
|
|
@@ -0,0 +1,68 @@
|
|
|
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.scrollRightHeader = void 0;
|
|
13
|
+
/**
|
|
14
|
+
* Scans for headers by finding a scrollable container and setting scrollLeft.
|
|
15
|
+
*/
|
|
16
|
+
const scrollRightHeader = (context, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
17
|
+
var _a, _b;
|
|
18
|
+
const { resolve, config, root, page } = context;
|
|
19
|
+
const limit = (_a = options === null || options === void 0 ? void 0 : options.limit) !== null && _a !== void 0 ? _a : 20;
|
|
20
|
+
const scrollAmount = (_b = options === null || options === void 0 ? void 0 : options.scrollAmount) !== null && _b !== void 0 ? _b : 300;
|
|
21
|
+
const collectedHeaders = new Set();
|
|
22
|
+
const getVisible = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
23
|
+
const headerLoc = resolve(config.headerSelector, root);
|
|
24
|
+
const texts = yield headerLoc.allInnerTexts();
|
|
25
|
+
return texts.map(t => t.trim());
|
|
26
|
+
});
|
|
27
|
+
// Initial capture
|
|
28
|
+
let currentHeaders = yield getVisible();
|
|
29
|
+
currentHeaders.forEach(h => collectedHeaders.add(h));
|
|
30
|
+
// Find scroller using JS for better iframe/shadow support
|
|
31
|
+
const scrollerHandle = yield root.evaluateHandle((el, selector) => {
|
|
32
|
+
if (selector && el.matches(selector))
|
|
33
|
+
return el;
|
|
34
|
+
const effectiveSelector = selector || '.dvn-scroller';
|
|
35
|
+
const ancestor = el.closest(effectiveSelector);
|
|
36
|
+
if (ancestor)
|
|
37
|
+
return ancestor;
|
|
38
|
+
return document.querySelector(effectiveSelector);
|
|
39
|
+
}, options === null || options === void 0 ? void 0 : options.selector);
|
|
40
|
+
const isScrollerFound = yield scrollerHandle.evaluate(el => !!el);
|
|
41
|
+
if (isScrollerFound) {
|
|
42
|
+
yield scrollerHandle.evaluate(el => el.scrollLeft = 0);
|
|
43
|
+
yield page.waitForTimeout(200);
|
|
44
|
+
for (let i = 0; i < limit; i++) {
|
|
45
|
+
const sizeBefore = collectedHeaders.size;
|
|
46
|
+
yield scrollerHandle.evaluate((el, amount) => el.scrollLeft += amount, scrollAmount);
|
|
47
|
+
yield page.waitForTimeout(300);
|
|
48
|
+
const newHeaders = yield getVisible();
|
|
49
|
+
newHeaders.forEach(h => collectedHeaders.add(h));
|
|
50
|
+
if (collectedHeaders.size === sizeBefore) {
|
|
51
|
+
yield scrollerHandle.evaluate((el, amount) => el.scrollLeft += amount, scrollAmount);
|
|
52
|
+
yield page.waitForTimeout(300);
|
|
53
|
+
const retryHeaders = yield getVisible();
|
|
54
|
+
retryHeaders.forEach(h => collectedHeaders.add(h));
|
|
55
|
+
if (collectedHeaders.size === sizeBefore)
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
console.warn("HeaderStrategies.scrollRight: Could not find scroller. Returning visible headers.");
|
|
62
|
+
}
|
|
63
|
+
// Scroll back to start
|
|
64
|
+
yield scrollerHandle.evaluate(el => el.scrollLeft = 0);
|
|
65
|
+
yield page.waitForTimeout(200);
|
|
66
|
+
return Array.from(collectedHeaders);
|
|
67
|
+
});
|
|
68
|
+
exports.scrollRightHeader = scrollRightHeader;
|
|
@@ -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,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FilterEngine = void 0;
|
|
4
|
+
const stringUtils_1 = require("./utils/stringUtils");
|
|
5
|
+
class FilterEngine {
|
|
6
|
+
constructor(config, resolve) {
|
|
7
|
+
this.config = config;
|
|
8
|
+
this.resolve = resolve;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Applies filters to a set of rows.
|
|
12
|
+
*/
|
|
13
|
+
applyFilters(baseRows, filters, map, exact, page) {
|
|
14
|
+
let filtered = baseRows;
|
|
15
|
+
// Iterate through each filter criteria
|
|
16
|
+
for (const [colName, value] of Object.entries(filters)) {
|
|
17
|
+
// Find column index
|
|
18
|
+
const colIndex = map.get(colName);
|
|
19
|
+
// TODO: Use ColumnStrategy for better resolution error handling
|
|
20
|
+
if (colIndex === undefined) {
|
|
21
|
+
throw new Error((0, stringUtils_1.buildColumnNotFoundError)(colName, Array.from(map.keys())));
|
|
22
|
+
}
|
|
23
|
+
const filterVal = typeof value === 'number' ? String(value) : value;
|
|
24
|
+
// Use strategy if provided (For future: configured filter strategies)
|
|
25
|
+
// But for now, we implement the default logic or use custom if we add it to config later
|
|
26
|
+
// Default Filter Logic
|
|
27
|
+
const cellTemplate = this.resolve(this.config.cellSelector, page);
|
|
28
|
+
// This logic assumes 1:1 row-to-cell mapping based on index.
|
|
29
|
+
// filter({ has: ... }) checks if the row *contains* the matching cell.
|
|
30
|
+
// But we need to be specific about WHICH cell.
|
|
31
|
+
// Locator filtering by `has: locator.nth(index)` works if `locator` search is relative to the row.
|
|
32
|
+
filtered = filtered.filter({
|
|
33
|
+
has: cellTemplate.nth(colIndex).getByText(filterVal, { exact }),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return filtered;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.FilterEngine = FilterEngine;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./useTable"), exports);
|
|
18
|
+
__exportStar(require("./types"), exports);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare const Plugins: {
|
|
2
|
+
RDG: {
|
|
3
|
+
Strategies: {
|
|
4
|
+
header: (context: import("./types").TableContext) => Promise<string[]>;
|
|
5
|
+
getCellLocator: ({ row, columnIndex }: any) => any;
|
|
6
|
+
cellNavigation: ({ root, page, index }: any) => Promise<void>;
|
|
7
|
+
pagination: import("./types").PaginationStrategy;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
Glide: {
|
|
11
|
+
Strategies: {
|
|
12
|
+
fill: import("./types").FillStrategy;
|
|
13
|
+
pagination: import("./types").PaginationStrategy;
|
|
14
|
+
header: (context: import("./types").StrategyContext, options?: {
|
|
15
|
+
limit?: number;
|
|
16
|
+
selector?: string;
|
|
17
|
+
scrollAmount?: number;
|
|
18
|
+
}) => Promise<string[]>;
|
|
19
|
+
cellNavigation: (context: import("./types").StrategyContext & {
|
|
20
|
+
column: string;
|
|
21
|
+
index: number;
|
|
22
|
+
rowIndex?: number;
|
|
23
|
+
}) => Promise<void>;
|
|
24
|
+
getCellLocator: ({ row, columnIndex }: any) => any;
|
|
25
|
+
getActiveCell: ({ page }: any) => Promise<{
|
|
26
|
+
rowIndex: number;
|
|
27
|
+
columnIndex: number;
|
|
28
|
+
locator: any;
|
|
29
|
+
} | null>;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Plugins = void 0;
|
|
4
|
+
const rdg_1 = require("./strategies/rdg");
|
|
5
|
+
const glide_1 = require("./strategies/glide");
|
|
6
|
+
exports.Plugins = {
|
|
7
|
+
RDG: {
|
|
8
|
+
Strategies: rdg_1.RDGStrategies
|
|
9
|
+
},
|
|
10
|
+
Glide: {
|
|
11
|
+
Strategies: glide_1.GlideStrategies
|
|
12
|
+
}
|
|
13
|
+
};
|
|
@@ -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>;
|
|
@@ -0,0 +1,160 @@
|
|
|
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
|
+
const stringUtils_1 = require("./utils/stringUtils");
|
|
15
|
+
const debugUtils_1 = require("./utils/debugUtils");
|
|
16
|
+
/**
|
|
17
|
+
* Factory to create a SmartRow by extending a Playwright Locator.
|
|
18
|
+
* We avoid Class/Proxy to ensure full compatibility with Playwright's expect(locator) matchers.
|
|
19
|
+
*/
|
|
20
|
+
const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve, table) => {
|
|
21
|
+
const smart = rowLocator;
|
|
22
|
+
// Attach State
|
|
23
|
+
smart.rowIndex = rowIndex;
|
|
24
|
+
// Attach Methods
|
|
25
|
+
smart.getCell = (colName) => {
|
|
26
|
+
const idx = map.get(colName);
|
|
27
|
+
if (idx === undefined) {
|
|
28
|
+
throw new Error((0, stringUtils_1.buildColumnNotFoundError)(colName, Array.from(map.keys())));
|
|
29
|
+
}
|
|
30
|
+
if (config.strategies.getCellLocator) {
|
|
31
|
+
return config.strategies.getCellLocator({
|
|
32
|
+
row: rowLocator,
|
|
33
|
+
columnName: colName,
|
|
34
|
+
columnIndex: idx,
|
|
35
|
+
rowIndex: rowIndex,
|
|
36
|
+
page: rootLocator.page()
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return resolve(config.cellSelector, rowLocator).nth(idx);
|
|
40
|
+
};
|
|
41
|
+
smart.toJSON = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
42
|
+
const result = {};
|
|
43
|
+
const page = rootLocator.page();
|
|
44
|
+
for (const [col, idx] of map.entries()) {
|
|
45
|
+
if ((options === null || options === void 0 ? void 0 : options.columns) && !options.columns.includes(col)) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
// Get the cell locator
|
|
49
|
+
const cell = config.strategies.getCellLocator
|
|
50
|
+
? config.strategies.getCellLocator({
|
|
51
|
+
row: rowLocator,
|
|
52
|
+
columnName: col,
|
|
53
|
+
columnIndex: idx,
|
|
54
|
+
rowIndex: rowIndex,
|
|
55
|
+
page: page
|
|
56
|
+
})
|
|
57
|
+
: resolve(config.cellSelector, rowLocator).nth(idx);
|
|
58
|
+
let targetCell = cell;
|
|
59
|
+
// Check if cell exists
|
|
60
|
+
const count = yield cell.count();
|
|
61
|
+
if (count === 0) {
|
|
62
|
+
// Optimization: Check if we are ALREADY at the target cell
|
|
63
|
+
if (config.strategies.getActiveCell) {
|
|
64
|
+
const active = yield config.strategies.getActiveCell({
|
|
65
|
+
config,
|
|
66
|
+
root: rootLocator,
|
|
67
|
+
page,
|
|
68
|
+
resolve
|
|
69
|
+
});
|
|
70
|
+
if (active && active.rowIndex === rowIndex && active.columnIndex === idx) {
|
|
71
|
+
if (config.debug)
|
|
72
|
+
console.log(`[SmartRow] Already at target cell (r:${active.rowIndex}, c:${active.columnIndex}), skipping navigation.`);
|
|
73
|
+
targetCell = active.locator;
|
|
74
|
+
// Skip navigation and go to reading text
|
|
75
|
+
const text = yield targetCell.innerText();
|
|
76
|
+
result[col] = (text || '').trim();
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Cell doesn't exist - navigate to it
|
|
81
|
+
if (config.debug) {
|
|
82
|
+
console.log(`[SmartRow.toJSON] Cell not found for column "${col}" (index ${idx}), navigating...`);
|
|
83
|
+
}
|
|
84
|
+
yield config.strategies.cellNavigation({
|
|
85
|
+
config: config,
|
|
86
|
+
root: rootLocator,
|
|
87
|
+
page: page,
|
|
88
|
+
resolve: resolve,
|
|
89
|
+
column: col,
|
|
90
|
+
index: idx,
|
|
91
|
+
rowLocator: rowLocator,
|
|
92
|
+
rowIndex: rowIndex
|
|
93
|
+
});
|
|
94
|
+
// Optimization: check if we can get the active cell directly
|
|
95
|
+
if (config.strategies.getActiveCell) {
|
|
96
|
+
const activeCell = yield config.strategies.getActiveCell({
|
|
97
|
+
config,
|
|
98
|
+
root: rootLocator,
|
|
99
|
+
page,
|
|
100
|
+
resolve
|
|
101
|
+
});
|
|
102
|
+
if (activeCell) {
|
|
103
|
+
if (config.debug) {
|
|
104
|
+
console.log(`[SmartRow.toJSON] switching to active cell locator (r:${activeCell.rowIndex}, c:${activeCell.columnIndex})`);
|
|
105
|
+
}
|
|
106
|
+
targetCell = activeCell.locator;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const text = yield targetCell.innerText();
|
|
111
|
+
result[col] = (text || '').trim();
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
});
|
|
115
|
+
smart.smartFill = (data, fillOptions) => __awaiter(void 0, void 0, void 0, function* () {
|
|
116
|
+
(0, debugUtils_1.logDebug)(config, 'info', 'Filling row', data);
|
|
117
|
+
for (const [colName, value] of Object.entries(data)) {
|
|
118
|
+
if (value === undefined)
|
|
119
|
+
continue;
|
|
120
|
+
const colIdx = map.get(colName);
|
|
121
|
+
if (colIdx === undefined) {
|
|
122
|
+
throw new Error((0, stringUtils_1.buildColumnNotFoundError)(colName, Array.from(map.keys())));
|
|
123
|
+
}
|
|
124
|
+
yield config.strategies.cellNavigation({
|
|
125
|
+
config: config,
|
|
126
|
+
root: rootLocator,
|
|
127
|
+
page: rootLocator.page(),
|
|
128
|
+
resolve: resolve,
|
|
129
|
+
column: colName,
|
|
130
|
+
index: colIdx,
|
|
131
|
+
rowLocator: rowLocator,
|
|
132
|
+
rowIndex: rowIndex
|
|
133
|
+
});
|
|
134
|
+
const strategy = config.strategies.fill || fill_1.FillStrategies.default;
|
|
135
|
+
(0, debugUtils_1.logDebug)(config, 'verbose', `Filling cell "${colName}" with value`, value);
|
|
136
|
+
yield strategy({
|
|
137
|
+
row: smart,
|
|
138
|
+
columnName: colName,
|
|
139
|
+
value,
|
|
140
|
+
index: rowIndex !== null && rowIndex !== void 0 ? rowIndex : -1,
|
|
141
|
+
page: rowLocator.page(),
|
|
142
|
+
rootLocator,
|
|
143
|
+
table: table,
|
|
144
|
+
fillOptions
|
|
145
|
+
});
|
|
146
|
+
// Delay after filling
|
|
147
|
+
yield (0, debugUtils_1.debugDelay)(config, 'getCell');
|
|
148
|
+
}
|
|
149
|
+
(0, debugUtils_1.logDebug)(config, 'info', 'Row fill complete');
|
|
150
|
+
});
|
|
151
|
+
smart.bringIntoView = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
152
|
+
if (rowIndex === undefined) {
|
|
153
|
+
throw new Error('Cannot bring row into view - row index is unknown. Use getRowByIndex() instead of getRow().');
|
|
154
|
+
}
|
|
155
|
+
// Scroll row into view using Playwright's built-in method
|
|
156
|
+
yield rowLocator.scrollIntoViewIfNeeded();
|
|
157
|
+
});
|
|
158
|
+
return smart;
|
|
159
|
+
};
|
|
160
|
+
exports.createSmartRow = createSmartRow;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { StrategyContext } from '../types';
|
|
2
|
+
/**
|
|
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
|
+
*/
|
|
7
|
+
export type CellNavigationStrategy = (context: StrategyContext & {
|
|
8
|
+
column: string;
|
|
9
|
+
index: number;
|
|
10
|
+
rowIndex?: number;
|
|
11
|
+
}) => Promise<void>;
|
|
12
|
+
export declare const CellNavigationStrategies: {
|
|
13
|
+
/**
|
|
14
|
+
* Default strategy: Assumes column is accessible or standard scrolling works.
|
|
15
|
+
* No specific action taken other than what Playwright's default locator handling does.
|
|
16
|
+
*/
|
|
17
|
+
default: () => Promise<void>;
|
|
18
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
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.CellNavigationStrategies = void 0;
|
|
13
|
+
exports.CellNavigationStrategies = {
|
|
14
|
+
/**
|
|
15
|
+
* Default strategy: Assumes column is accessible or standard scrolling works.
|
|
16
|
+
* No specific action taken other than what Playwright's default locator handling does.
|
|
17
|
+
*/
|
|
18
|
+
default: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
19
|
+
// No-op
|
|
20
|
+
})
|
|
21
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DedupeStrategy } from '../types';
|
|
2
|
+
export declare const DedupeStrategies: {
|
|
3
|
+
/**
|
|
4
|
+
* Deduplicates rows based on their vertical position (Y coordinate).
|
|
5
|
+
* Useful for virtualized tables where row DOM elements are reused but content changes.
|
|
6
|
+
* @param tolerance Pixel tolerance for position comparison (default: 2)
|
|
7
|
+
*/
|
|
8
|
+
byTopPosition: (tolerance?: number) => DedupeStrategy;
|
|
9
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
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.DedupeStrategies = void 0;
|
|
13
|
+
exports.DedupeStrategies = {
|
|
14
|
+
/**
|
|
15
|
+
* Deduplicates rows based on their vertical position (Y coordinate).
|
|
16
|
+
* Useful for virtualized tables where row DOM elements are reused but content changes.
|
|
17
|
+
* @param tolerance Pixel tolerance for position comparison (default: 2)
|
|
18
|
+
*/
|
|
19
|
+
byTopPosition: (tolerance = 2) => (row) => __awaiter(void 0, void 0, void 0, function* () {
|
|
20
|
+
const box = yield row.boundingBox();
|
|
21
|
+
if (!box)
|
|
22
|
+
return 'unknown';
|
|
23
|
+
// Round to nearest tolerance
|
|
24
|
+
const y = Math.round(box.y / tolerance) * tolerance;
|
|
25
|
+
return `pos_${y}`;
|
|
26
|
+
})
|
|
27
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FillStrategy } from '../types';
|
|
2
|
+
export declare const FillStrategies: {
|
|
3
|
+
/**
|
|
4
|
+
* Default strategy: Detects input type and fills accordingly (Text, Select, Checkbox, ContentEditable).
|
|
5
|
+
*/
|
|
6
|
+
default: ({ row, columnName, value, fillOptions }: Parameters<FillStrategy>[0]) => Promise<void>;
|
|
7
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
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.FillStrategies = void 0;
|
|
13
|
+
exports.FillStrategies = {
|
|
14
|
+
/**
|
|
15
|
+
* Default strategy: Detects input type and fills accordingly (Text, Select, Checkbox, ContentEditable).
|
|
16
|
+
*/
|
|
17
|
+
default: (_a) => __awaiter(void 0, [_a], void 0, function* ({ row, columnName, value, fillOptions }) {
|
|
18
|
+
var _b;
|
|
19
|
+
const cell = row.getCell(columnName);
|
|
20
|
+
// Use custom input mapper for this column if provided, otherwise auto-detect
|
|
21
|
+
let inputLocator;
|
|
22
|
+
if ((_b = fillOptions === null || fillOptions === void 0 ? void 0 : fillOptions.inputMappers) === null || _b === void 0 ? void 0 : _b[columnName]) {
|
|
23
|
+
inputLocator = fillOptions.inputMappers[columnName](cell);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
// Auto-detect input type
|
|
27
|
+
// Check for text input
|
|
28
|
+
const textInput = cell.locator('input[type="text"], input:not([type]), textarea').first();
|
|
29
|
+
const textInputCount = yield textInput.count().catch(() => 0);
|
|
30
|
+
// Check for select
|
|
31
|
+
const select = cell.locator('select').first();
|
|
32
|
+
const selectCount = yield select.count().catch(() => 0);
|
|
33
|
+
// Check for checkbox/radio
|
|
34
|
+
const checkbox = cell.locator('input[type="checkbox"], input[type="radio"], [role="checkbox"]').first();
|
|
35
|
+
const checkboxCount = yield checkbox.count().catch(() => 0);
|
|
36
|
+
// Check for contenteditable or div-based inputs
|
|
37
|
+
const contentEditable = cell.locator('[contenteditable="true"]').first();
|
|
38
|
+
const contentEditableCount = yield contentEditable.count().catch(() => 0);
|
|
39
|
+
// Determine which input to use (prioritize by commonality)
|
|
40
|
+
if (textInputCount > 0 && selectCount === 0 && checkboxCount === 0) {
|
|
41
|
+
inputLocator = textInput;
|
|
42
|
+
}
|
|
43
|
+
else if (selectCount > 0) {
|
|
44
|
+
inputLocator = select;
|
|
45
|
+
}
|
|
46
|
+
else if (checkboxCount > 0) {
|
|
47
|
+
inputLocator = checkbox;
|
|
48
|
+
}
|
|
49
|
+
else if (contentEditableCount > 0) {
|
|
50
|
+
inputLocator = contentEditable;
|
|
51
|
+
}
|
|
52
|
+
else if (textInputCount > 0) {
|
|
53
|
+
// Fallback to text input even if others exist
|
|
54
|
+
inputLocator = textInput;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// No input found - try to click the cell itself (might trigger an editor)
|
|
58
|
+
inputLocator = cell;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Fill based on value type and input type
|
|
62
|
+
const inputTag = yield inputLocator.evaluate((el) => el.tagName.toLowerCase()).catch(() => 'unknown');
|
|
63
|
+
const inputType = yield inputLocator.getAttribute('type').catch(() => null);
|
|
64
|
+
const isContentEditable = yield inputLocator.getAttribute('contenteditable').catch(() => null);
|
|
65
|
+
// console.log(`[SmartTable] Filling "${columnName}" with value "${value}" (input: ${inputTag}, type: ${inputType})`);
|
|
66
|
+
if (inputType === 'checkbox' || inputType === 'radio') {
|
|
67
|
+
// Boolean value for checkbox/radio
|
|
68
|
+
const shouldBeChecked = Boolean(value);
|
|
69
|
+
const isChecked = yield inputLocator.isChecked().catch(() => false);
|
|
70
|
+
if (isChecked !== shouldBeChecked) {
|
|
71
|
+
yield inputLocator.click();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else if (inputTag === 'select') {
|
|
75
|
+
// Select dropdown
|
|
76
|
+
yield inputLocator.selectOption(String(value));
|
|
77
|
+
}
|
|
78
|
+
else if (isContentEditable === 'true') {
|
|
79
|
+
// Contenteditable div
|
|
80
|
+
yield inputLocator.click();
|
|
81
|
+
yield inputLocator.fill(String(value));
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// Text input, textarea, or generic
|
|
85
|
+
yield inputLocator.fill(String(value));
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { FillStrategy } from '../types';
|
|
2
|
+
export declare const glideFillStrategy: FillStrategy;
|
|
3
|
+
export declare const glidePaginationStrategy: import("../types").PaginationStrategy;
|
|
4
|
+
export declare const glideGetCellLocator: ({ row, columnIndex }: any) => any;
|
|
5
|
+
export declare const glideGetActiveCell: ({ page }: any) => Promise<{
|
|
6
|
+
rowIndex: number;
|
|
7
|
+
columnIndex: number;
|
|
8
|
+
locator: any;
|
|
9
|
+
} | null>;
|
|
10
|
+
export declare const GlideStrategies: {
|
|
11
|
+
fill: FillStrategy;
|
|
12
|
+
pagination: import("../types").PaginationStrategy;
|
|
13
|
+
header: (context: import("../types").StrategyContext, options?: {
|
|
14
|
+
limit?: number;
|
|
15
|
+
selector?: string;
|
|
16
|
+
scrollAmount?: number;
|
|
17
|
+
}) => Promise<string[]>;
|
|
18
|
+
cellNavigation: (context: import("../types").StrategyContext & {
|
|
19
|
+
column: string;
|
|
20
|
+
index: number;
|
|
21
|
+
rowIndex?: number;
|
|
22
|
+
}) => Promise<void>;
|
|
23
|
+
getCellLocator: ({ row, columnIndex }: any) => any;
|
|
24
|
+
getActiveCell: ({ page }: any) => Promise<{
|
|
25
|
+
rowIndex: number;
|
|
26
|
+
columnIndex: number;
|
|
27
|
+
locator: any;
|
|
28
|
+
} | null>;
|
|
29
|
+
};
|