@rickcedwhat/playwright-smart-table 2.1.1 ā 2.1.2
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 +49 -9
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +19 -0
- package/dist/types.d.ts +20 -0
- package/dist/useTable.js +133 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,12 +8,11 @@ This library abstracts away the complexity of testing dynamic web tables. It han
|
|
|
8
8
|
|
|
9
9
|
npm install @rickcedwhat/playwright-smart-table
|
|
10
10
|
|
|
11
|
-
|
|
12
11
|
Requires @playwright/test as a peer dependency.
|
|
13
12
|
|
|
14
13
|
ā” Quick Start
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
The Standard HTML Table
|
|
17
16
|
|
|
18
17
|
For standard tables (<table>, <tr>, <td>), no configuration is needed (defaults work for most standard HTML tables).
|
|
19
18
|
|
|
@@ -31,7 +30,7 @@ await expect(row.getCell('Position')).toHaveText('Accountant');
|
|
|
31
30
|
```
|
|
32
31
|
<!-- /embed: quick-start -->
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
Complex Grids (Material UI / AG-Grid / Divs)
|
|
35
34
|
|
|
36
35
|
For modern React grids, simply override the selectors and define a pagination strategy.
|
|
37
36
|
|
|
@@ -78,6 +77,50 @@ console.log(data);
|
|
|
78
77
|
```
|
|
79
78
|
<!-- /embed: smart-row -->
|
|
80
79
|
|
|
80
|
+
š Advanced Usage
|
|
81
|
+
|
|
82
|
+
š Debug Mode
|
|
83
|
+
|
|
84
|
+
Having trouble finding rows? Enable debug mode to see exactly what the library sees (headers mapped, rows scanned, pagination triggers).
|
|
85
|
+
|
|
86
|
+
<!-- embed: advanced-debug -->
|
|
87
|
+
```typescript
|
|
88
|
+
const table = useTable(page.locator('#example'), {
|
|
89
|
+
headerSelector: 'thead th',
|
|
90
|
+
debug: true
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
<!-- /embed: advanced-debug -->
|
|
94
|
+
|
|
95
|
+
š Resetting State
|
|
96
|
+
|
|
97
|
+
If your tests navigate deep into a table (e.g., Page 5), subsequent searches might fail. Use .reset() to return to the start.
|
|
98
|
+
|
|
99
|
+
<!-- embed: advanced-reset -->
|
|
100
|
+
```typescript
|
|
101
|
+
// Navigate deep into the table (simulated by finding a row on page 2)
|
|
102
|
+
// For the test to pass, we need a valid row. 'Angelica Ramos' is usually on page 1 or 2 depending on sorting.
|
|
103
|
+
try {
|
|
104
|
+
await table.getByRow({ Name: 'Angelica Ramos' });
|
|
105
|
+
} catch (e) {}
|
|
106
|
+
|
|
107
|
+
// Reset internal state (and potentially UI) to Page 1
|
|
108
|
+
await table.reset();
|
|
109
|
+
```
|
|
110
|
+
<!-- /embed: advanced-reset -->
|
|
111
|
+
|
|
112
|
+
š Column Scanning
|
|
113
|
+
|
|
114
|
+
Need to verify a specific column is sorted or contains specific data? Use getColumnValues for a high-performance scan.
|
|
115
|
+
|
|
116
|
+
<!-- embed: advanced-column-scan -->
|
|
117
|
+
```typescript
|
|
118
|
+
// Quickly grab all text values from the "Office" column
|
|
119
|
+
const offices = await table.getColumnValues('Office');
|
|
120
|
+
expect(offices).toContain('Tokyo');
|
|
121
|
+
```
|
|
122
|
+
<!-- /embed: advanced-column-scan -->
|
|
123
|
+
|
|
81
124
|
š API Reference
|
|
82
125
|
|
|
83
126
|
getByRow(filters, options?)
|
|
@@ -134,16 +177,14 @@ Built-in Strategies
|
|
|
134
177
|
|
|
135
178
|
clickNext(selector) Best for standard tables (Datatables, lists). Clicks a button and waits for data to change.
|
|
136
179
|
|
|
137
|
-
pagination: TableStrategies.clickNext((root) =>
|
|
138
|
-
|
|
180
|
+
pagination: TableStrategies.clickNext((root) =>
|
|
181
|
+
root.page().getByRole('button', { name: 'Next' })
|
|
139
182
|
)
|
|
140
183
|
|
|
141
|
-
|
|
142
184
|
infiniteScroll() Best for Virtualized Grids (AG-Grid, HTMX). Aggressively scrolls to trigger data loading.
|
|
143
185
|
|
|
144
186
|
pagination: TableStrategies.infiniteScroll()
|
|
145
187
|
|
|
146
|
-
|
|
147
188
|
clickLoadMore(selector) Best for "Load More" buttons. Clicks and waits for row count to increase.
|
|
148
189
|
|
|
149
190
|
š ļø Developer Tools
|
|
@@ -157,9 +198,8 @@ Prints a prompt you can paste into ChatGPT/Gemini to generate the TableConfig fo
|
|
|
157
198
|
// Options: 'console' (default), 'error' (Throw error to see prompt in trace/cloud)
|
|
158
199
|
await table.generateConfigPrompt({ output: 'console' });
|
|
159
200
|
|
|
160
|
-
|
|
161
201
|
generateStrategyPrompt(options?)
|
|
162
202
|
|
|
163
203
|
Prints a prompt to help you write a custom Pagination Strategy.
|
|
164
204
|
|
|
165
|
-
await table.generateStrategyPrompt({ output: 'console' });
|
|
205
|
+
await table.generateStrategyPrompt({ output: 'console' });
|
package/dist/typeContext.d.ts
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* This file is generated by scripts/embed-types.js
|
|
4
4
|
* It contains the raw text of types.ts to provide context for LLM prompts.
|
|
5
5
|
*/
|
|
6
|
-
export declare const TYPE_CONTEXT = "\nexport type Selector = string | ((root: Locator | Page) => Locator);\n\nexport type SmartRow = Locator & {\n getCell(column: string): Locator;\n toJSON(): Promise<Record<string, string>>;\n};\n\nexport interface TableContext {\n root: Locator;\n config: Required<TableConfig>;\n page: Page;\n resolve: (selector: Selector, parent: Locator | Page) => Locator;\n}\n\nexport type PaginationStrategy = (context: TableContext) => Promise<boolean>;\n\nexport interface PromptOptions {\n /**\n * Output Strategy:\n * - 'error': Throws an error with the prompt (Best for Cloud/QA Wolf to get clean text).\n * - 'console': Standard console logs (Default).\n */\n output?: 'console' | 'error';\n includeTypes?: boolean;\n}\n\nexport interface TableConfig {\n rowSelector?: Selector;\n headerSelector?: Selector;\n cellSelector?: Selector;\n pagination?: PaginationStrategy;\n maxPages?: number;\n /**\n * Hook to rename columns dynamically.\n * * @param args.text - The default innerText of the header.\n * @param args.index - The column index.\n * @param args.locator - The specific header cell locator.\n */\n headerTransformer?: (args: { text: string, index: number, locator: Locator }) => string | Promise<string>;\n autoScroll?: boolean;\n}\n\nexport interface TableResult {\n getHeaders: () => Promise<string[]>;\n getHeaderCell: (columnName: string) => Promise<Locator>;\n\n getByRow: <T extends { asJSON?: boolean }>(\n filters: Record<string, string | RegExp | number>, \n options?: { exact?: boolean, maxPages?: number } & T\n ) => Promise<T['asJSON'] extends true ? Record<string, string> : SmartRow>;\n\n getAllRows: <T extends { asJSON?: boolean }>(\n options?: { filter?: Record<string, any>, exact?: boolean } & T\n ) => Promise<T['asJSON'] extends true ? Record<string, string>[] : SmartRow[]>;\n\n generateConfigPrompt: (options?: PromptOptions) => Promise<void>;\n generateStrategyPrompt: (options?: PromptOptions) => Promise<void>;\n}\n";
|
|
6
|
+
export declare const TYPE_CONTEXT = "\nexport type Selector = string | ((root: Locator | Page) => Locator);\n\nexport type SmartRow = Locator & {\n getCell(column: string): Locator;\n toJSON(): Promise<Record<string, string>>;\n};\n\nexport interface TableContext {\n root: Locator;\n config: Required<TableConfig>;\n page: Page;\n resolve: (selector: Selector, parent: Locator | Page) => Locator;\n}\n\nexport type PaginationStrategy = (context: TableContext) => Promise<boolean>;\n\nexport interface PromptOptions {\n /**\n * Output Strategy:\n * - 'error': Throws an error with the prompt (Best for Cloud/QA Wolf to get clean text).\n * - 'console': Standard console logs (Default).\n */\n output?: 'console' | 'error';\n includeTypes?: boolean;\n}\n\nexport interface TableConfig {\n rowSelector?: Selector;\n headerSelector?: Selector;\n cellSelector?: Selector;\n pagination?: PaginationStrategy;\n maxPages?: number;\n /**\n * Hook to rename columns dynamically.\n * * @param args.text - The default innerText of the header.\n * @param args.index - The column index.\n * @param args.locator - The specific header cell locator.\n */\n headerTransformer?: (args: { text: string, index: number, locator: Locator }) => string | Promise<string>;\n autoScroll?: boolean;\n /**\n * Enable debug mode to log internal state to console.\n */\n debug?: boolean;\n /**\n * Strategy to reset the table to the first page.\n * Called when table.reset() is invoked.\n */\n onReset?: (context: TableContext) => Promise<void>;\n}\n\nexport interface TableResult {\n getHeaders: () => Promise<string[]>;\n getHeaderCell: (columnName: string) => Promise<Locator>;\n\n getByRow: <T extends { asJSON?: boolean }>(\n filters: Record<string, string | RegExp | number>, \n options?: { exact?: boolean, maxPages?: number } & T\n ) => Promise<T['asJSON'] extends true ? Record<string, string> : SmartRow>;\n\n getAllRows: <T extends { asJSON?: boolean }>(\n options?: { filter?: Record<string, any>, exact?: boolean } & T\n ) => Promise<T['asJSON'] extends true ? Record<string, string>[] : SmartRow[]>;\n\n generateConfigPrompt: (options?: PromptOptions) => Promise<void>;\n generateStrategyPrompt: (options?: PromptOptions) => Promise<void>;\n\n /**\n * Resets the table state (clears cache, flags) and invokes the onReset strategy.\n */\n reset: () => Promise<void>;\n\n /**\n * Scans a specific column across all pages and returns the values.\n */\n getColumnValues: <V = string>(column: string, options?: { mapper?: (cell: Locator) => Promise<V> | V, maxPages?: number }) => Promise<V[]>;\n}\n";
|
package/dist/typeContext.js
CHANGED
|
@@ -47,6 +47,15 @@ export interface TableConfig {
|
|
|
47
47
|
*/
|
|
48
48
|
headerTransformer?: (args: { text: string, index: number, locator: Locator }) => string | Promise<string>;
|
|
49
49
|
autoScroll?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Enable debug mode to log internal state to console.
|
|
52
|
+
*/
|
|
53
|
+
debug?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Strategy to reset the table to the first page.
|
|
56
|
+
* Called when table.reset() is invoked.
|
|
57
|
+
*/
|
|
58
|
+
onReset?: (context: TableContext) => Promise<void>;
|
|
50
59
|
}
|
|
51
60
|
|
|
52
61
|
export interface TableResult {
|
|
@@ -64,5 +73,15 @@ export interface TableResult {
|
|
|
64
73
|
|
|
65
74
|
generateConfigPrompt: (options?: PromptOptions) => Promise<void>;
|
|
66
75
|
generateStrategyPrompt: (options?: PromptOptions) => Promise<void>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Resets the table state (clears cache, flags) and invokes the onReset strategy.
|
|
79
|
+
*/
|
|
80
|
+
reset: () => Promise<void>;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Scans a specific column across all pages and returns the values.
|
|
84
|
+
*/
|
|
85
|
+
getColumnValues: <V = string>(column: string, options?: { mapper?: (cell: Locator) => Promise<V> | V, maxPages?: number }) => Promise<V[]>;
|
|
67
86
|
}
|
|
68
87
|
`;
|
package/dist/types.d.ts
CHANGED
|
@@ -38,6 +38,15 @@ export interface TableConfig {
|
|
|
38
38
|
locator: Locator;
|
|
39
39
|
}) => string | Promise<string>;
|
|
40
40
|
autoScroll?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Enable debug mode to log internal state to console.
|
|
43
|
+
*/
|
|
44
|
+
debug?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Strategy to reset the table to the first page.
|
|
47
|
+
* Called when table.reset() is invoked.
|
|
48
|
+
*/
|
|
49
|
+
onReset?: (context: TableContext) => Promise<void>;
|
|
41
50
|
}
|
|
42
51
|
export interface TableResult {
|
|
43
52
|
getHeaders: () => Promise<string[]>;
|
|
@@ -56,4 +65,15 @@ export interface TableResult {
|
|
|
56
65
|
} & T) => Promise<T['asJSON'] extends true ? Record<string, string>[] : SmartRow[]>;
|
|
57
66
|
generateConfigPrompt: (options?: PromptOptions) => Promise<void>;
|
|
58
67
|
generateStrategyPrompt: (options?: PromptOptions) => Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Resets the table state (clears cache, flags) and invokes the onReset strategy.
|
|
70
|
+
*/
|
|
71
|
+
reset: () => Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Scans a specific column across all pages and returns the values.
|
|
74
|
+
*/
|
|
75
|
+
getColumnValues: <V = string>(column: string, options?: {
|
|
76
|
+
mapper?: (cell: Locator) => Promise<V> | V;
|
|
77
|
+
maxPages?: number;
|
|
78
|
+
}) => Promise<V[]>;
|
|
59
79
|
}
|
package/dist/useTable.js
CHANGED
|
@@ -12,7 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.useTable = void 0;
|
|
13
13
|
const typeContext_1 = require("./typeContext");
|
|
14
14
|
const useTable = (rootLocator, configOptions = {}) => {
|
|
15
|
-
const config = Object.assign({ rowSelector: "tbody tr", headerSelector: "th", cellSelector: "td", pagination: () => __awaiter(void 0, void 0, void 0, function* () { return false; }), maxPages: 1, headerTransformer: ({ text, index, locator }) => text, autoScroll: true }, configOptions);
|
|
15
|
+
const config = Object.assign({ rowSelector: "tbody tr", headerSelector: "th", cellSelector: "td", pagination: () => __awaiter(void 0, void 0, void 0, function* () { return false; }), maxPages: 1, headerTransformer: ({ text, index, locator }) => text, autoScroll: true, debug: false, onReset: () => __awaiter(void 0, void 0, void 0, function* () { console.warn("ā ļø .reset() called but no 'onReset' strategy defined in config."); }) }, configOptions);
|
|
16
16
|
const resolve = (item, parent) => {
|
|
17
17
|
if (typeof item === 'string')
|
|
18
18
|
return parent.locator(item);
|
|
@@ -20,10 +20,17 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
20
20
|
return item(parent);
|
|
21
21
|
return item;
|
|
22
22
|
};
|
|
23
|
+
// Internal State
|
|
23
24
|
let _headerMap = null;
|
|
25
|
+
let _hasPaginated = false;
|
|
26
|
+
const logDebug = (msg) => {
|
|
27
|
+
if (config.debug)
|
|
28
|
+
console.log(`š [SmartTable Debug] ${msg}`);
|
|
29
|
+
};
|
|
24
30
|
const _getMap = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
25
31
|
if (_headerMap)
|
|
26
32
|
return _headerMap;
|
|
33
|
+
logDebug('Mapping headers...');
|
|
27
34
|
if (config.autoScroll) {
|
|
28
35
|
try {
|
|
29
36
|
yield rootLocator.scrollIntoViewIfNeeded({ timeout: 1000 });
|
|
@@ -37,7 +44,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
37
44
|
catch (e) { /* Ignore hydration */ }
|
|
38
45
|
// 1. Fetch data efficiently
|
|
39
46
|
const texts = yield headerLoc.allInnerTexts();
|
|
40
|
-
const locators = yield headerLoc.all();
|
|
47
|
+
const locators = yield headerLoc.all();
|
|
41
48
|
// 2. Map Headers (Async)
|
|
42
49
|
const entries = yield Promise.all(texts.map((t, i) => __awaiter(void 0, void 0, void 0, function* () {
|
|
43
50
|
let text = t.trim() || `__col_${i}`;
|
|
@@ -51,6 +58,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
51
58
|
return [text, i];
|
|
52
59
|
})));
|
|
53
60
|
_headerMap = new Map(entries);
|
|
61
|
+
logDebug(`Mapped ${entries.length} columns: ${JSON.stringify(entries.map(e => e[0]))}`);
|
|
54
62
|
return _headerMap;
|
|
55
63
|
});
|
|
56
64
|
const _makeSmart = (rowLocator, map) => {
|
|
@@ -99,15 +107,18 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
99
107
|
const map = yield _getMap();
|
|
100
108
|
const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : config.maxPages;
|
|
101
109
|
let currentPage = 1;
|
|
110
|
+
logDebug(`Looking for row: ${JSON.stringify(filters)} (MaxPages: ${effectiveMaxPages})`);
|
|
102
111
|
while (true) {
|
|
103
112
|
const allRows = resolve(config.rowSelector, rootLocator);
|
|
104
113
|
const matchedRows = _applyFilters(allRows, filters, map, options.exact || false);
|
|
105
114
|
const count = yield matchedRows.count();
|
|
115
|
+
logDebug(`Page ${currentPage}: Found ${count} matches.`);
|
|
106
116
|
if (count > 1)
|
|
107
117
|
throw new Error(`Strict Mode Violation: Found ${count} rows matching ${JSON.stringify(filters)}.`);
|
|
108
118
|
if (count === 1)
|
|
109
119
|
return matchedRows.first();
|
|
110
120
|
if (currentPage < effectiveMaxPages) {
|
|
121
|
+
logDebug(`Page ${currentPage}: Not found. Attempting pagination...`);
|
|
111
122
|
const context = {
|
|
112
123
|
root: rootLocator,
|
|
113
124
|
config: config,
|
|
@@ -116,9 +127,16 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
116
127
|
};
|
|
117
128
|
const didLoadMore = yield config.pagination(context);
|
|
118
129
|
if (didLoadMore) {
|
|
130
|
+
_hasPaginated = true;
|
|
119
131
|
currentPage++;
|
|
120
132
|
continue;
|
|
121
133
|
}
|
|
134
|
+
else {
|
|
135
|
+
logDebug(`Page ${currentPage}: Pagination failed (end of data).`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (_hasPaginated) {
|
|
139
|
+
console.warn(`ā ļø [SmartTable] Row not found. The table has been paginated (Current Page: ${currentPage}). You may need to call 'await table.reset()' if the target row is on a previous page.`);
|
|
122
140
|
}
|
|
123
141
|
return null;
|
|
124
142
|
}
|
|
@@ -135,6 +153,34 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
135
153
|
}
|
|
136
154
|
console.log(finalPrompt);
|
|
137
155
|
});
|
|
156
|
+
// Helper to extract clean HTML for prompts
|
|
157
|
+
const _getCleanHtml = (loc) => __awaiter(void 0, void 0, void 0, function* () {
|
|
158
|
+
return loc.evaluate((el) => {
|
|
159
|
+
const clone = el.cloneNode(true);
|
|
160
|
+
// 1. Remove Heavy/Useless Elements
|
|
161
|
+
const removeSelectors = 'script, style, svg, path, circle, rect, noscript, [hidden]';
|
|
162
|
+
clone.querySelectorAll(removeSelectors).forEach(n => n.remove());
|
|
163
|
+
// 2. Clean Attributes
|
|
164
|
+
const walker = document.createTreeWalker(clone, NodeFilter.SHOW_ELEMENT);
|
|
165
|
+
let currentNode = walker.currentNode;
|
|
166
|
+
while (currentNode) {
|
|
167
|
+
currentNode.removeAttribute('style'); // Inline styles are noise
|
|
168
|
+
currentNode.removeAttribute('data-reactid');
|
|
169
|
+
// 3. Condense Tailwind Classes (Heuristic)
|
|
170
|
+
// If class string is very long (>50 chars), keep the first few tokens and truncate.
|
|
171
|
+
// This preserves "MuiRow" but cuts "text-sm p-4 hover:bg-gray-50 ..."
|
|
172
|
+
const cls = currentNode.getAttribute('class');
|
|
173
|
+
if (cls && cls.length > 80) {
|
|
174
|
+
const tokens = cls.split(' ');
|
|
175
|
+
if (tokens.length > 5) {
|
|
176
|
+
currentNode.setAttribute('class', tokens.slice(0, 4).join(' ') + ' ...');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
currentNode = walker.nextNode();
|
|
180
|
+
}
|
|
181
|
+
return clone.outerHTML;
|
|
182
|
+
});
|
|
183
|
+
});
|
|
138
184
|
return {
|
|
139
185
|
getHeaders: () => __awaiter(void 0, void 0, void 0, function* () { return Array.from((yield _getMap()).keys()); }),
|
|
140
186
|
getHeaderCell: (columnName) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -144,6 +190,52 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
144
190
|
throw new Error(`Column '${columnName}' not found.`);
|
|
145
191
|
return resolve(config.headerSelector, rootLocator).nth(idx);
|
|
146
192
|
}),
|
|
193
|
+
reset: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
194
|
+
logDebug("Resetting table...");
|
|
195
|
+
const context = {
|
|
196
|
+
root: rootLocator,
|
|
197
|
+
config: config,
|
|
198
|
+
page: rootLocator.page(),
|
|
199
|
+
resolve: resolve
|
|
200
|
+
};
|
|
201
|
+
yield config.onReset(context);
|
|
202
|
+
_hasPaginated = false;
|
|
203
|
+
_headerMap = null;
|
|
204
|
+
logDebug("Table reset complete.");
|
|
205
|
+
}),
|
|
206
|
+
getColumnValues: (column, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
207
|
+
var _a, _b;
|
|
208
|
+
const map = yield _getMap();
|
|
209
|
+
const colIdx = map.get(column);
|
|
210
|
+
if (colIdx === undefined)
|
|
211
|
+
throw new Error(`Column '${column}' not found.`);
|
|
212
|
+
const mapper = (_a = options === null || options === void 0 ? void 0 : options.mapper) !== null && _a !== void 0 ? _a : ((c) => c.innerText());
|
|
213
|
+
const effectiveMaxPages = (_b = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _b !== void 0 ? _b : config.maxPages;
|
|
214
|
+
let currentPage = 1;
|
|
215
|
+
const results = [];
|
|
216
|
+
logDebug(`Getting column values for '${column}' (Pages: ${effectiveMaxPages})`);
|
|
217
|
+
while (true) {
|
|
218
|
+
const rows = yield resolve(config.rowSelector, rootLocator).all();
|
|
219
|
+
for (const row of rows) {
|
|
220
|
+
const cell = typeof config.cellSelector === 'string'
|
|
221
|
+
? row.locator(config.cellSelector).nth(colIdx)
|
|
222
|
+
: resolve(config.cellSelector, row).nth(colIdx);
|
|
223
|
+
results.push(yield mapper(cell));
|
|
224
|
+
}
|
|
225
|
+
if (currentPage < effectiveMaxPages) {
|
|
226
|
+
const context = {
|
|
227
|
+
root: rootLocator, config, page: rootLocator.page(), resolve
|
|
228
|
+
};
|
|
229
|
+
if (yield config.pagination(context)) {
|
|
230
|
+
_hasPaginated = true;
|
|
231
|
+
currentPage++;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
return results;
|
|
238
|
+
}),
|
|
147
239
|
getByRow: (filters, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
148
240
|
let row = yield _findRowLocator(filters, options);
|
|
149
241
|
if (!row) {
|
|
@@ -169,17 +261,51 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
169
261
|
return smartRows;
|
|
170
262
|
}),
|
|
171
263
|
generateConfigPrompt: (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
172
|
-
const html = yield rootLocator
|
|
264
|
+
const html = yield _getCleanHtml(rootLocator);
|
|
173
265
|
const separator = "=".repeat(50);
|
|
174
|
-
const content = `\n${separator}\nš¤ COPY INTO GEMINI/ChatGPT š¤\n${separator}\nI am using 'playwright-smart-table'. Generate config for:\n\`\`\`html\n${html.substring(0,
|
|
266
|
+
const content = `\n${separator}\nš¤ COPY INTO GEMINI/ChatGPT š¤\n${separator}\nI am using 'playwright-smart-table'. Generate config for:\n\`\`\`html\n${html.substring(0, 10000)} ...\n\`\`\`\n${separator}\n`;
|
|
175
267
|
yield _handlePrompt('Smart Table Config', content, options);
|
|
176
268
|
}),
|
|
177
269
|
generateStrategyPrompt: (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
178
270
|
const container = rootLocator.locator('xpath=..');
|
|
179
|
-
const html = yield container
|
|
180
|
-
const content = `\n==================================================\nš¤ COPY INTO GEMINI/ChatGPT TO WRITE A STRATEGY š¤\n==================================================\nI need a custom Pagination Strategy for 'playwright-smart-table'.\nContainer HTML:\n\`\`\`html\n${html.substring(0,
|
|
271
|
+
const html = yield _getCleanHtml(container);
|
|
272
|
+
const content = `\n==================================================\nš¤ COPY INTO GEMINI/ChatGPT TO WRITE A STRATEGY š¤\n==================================================\nI need a custom Pagination Strategy for 'playwright-smart-table'.\nContainer HTML:\n\`\`\`html\n${html.substring(0, 10000)} ...\n\`\`\`\n`;
|
|
181
273
|
yield _handlePrompt('Smart Table Strategy', content, options);
|
|
182
|
-
})
|
|
274
|
+
}),
|
|
275
|
+
/* * š§ ROADMAP (v2.2) š§
|
|
276
|
+
* The following features are planned. Implementations are tentative.
|
|
277
|
+
* DO NOT DELETE THIS SECTION UNTIL IMPLEMENTED OR REMOVED.
|
|
278
|
+
* THIS IS BEING USED TO TRACK FUTURE DEVELOPMENT.
|
|
279
|
+
*/
|
|
280
|
+
// __roadmap__fill: async (data: Record<string, any>) => {
|
|
281
|
+
// /* // * Goal: Fill a row with data intelligently.
|
|
282
|
+
// * Priority: Medium
|
|
283
|
+
// * Challenge: Handling different input types (select, checkbox, custom divs) blindly.
|
|
284
|
+
// */
|
|
285
|
+
// // const row = ... get row context ...
|
|
286
|
+
// // for (const [col, val] of Object.entries(data)) {
|
|
287
|
+
// // const cell = row.getCell(col);
|
|
288
|
+
// // const input = cell.locator('input, select, [role="checkbox"]');
|
|
289
|
+
// // if (await input.count() > 1) console.warn("Ambiguous input");
|
|
290
|
+
// // // Heuristics go here...
|
|
291
|
+
// // }
|
|
292
|
+
// // Note: Maybe we could pass the locator in the options for more control.
|
|
293
|
+
// },
|
|
294
|
+
// __roadmap__auditPages: async (options: { maxPages: number, audit: (rows: SmartRow[], page: number) => Promise<void> }) => {
|
|
295
|
+
// /*
|
|
296
|
+
// * Goal: Walk through pages and run a verification function on every page.
|
|
297
|
+
// * Priority: Low (Specific use case)
|
|
298
|
+
// * Logic:
|
|
299
|
+
// * let page = 1;
|
|
300
|
+
// * while (page <= options.maxPages) {
|
|
301
|
+
// * const rows = await getAllRows();
|
|
302
|
+
// * await options.audit(rows, page);
|
|
303
|
+
// * if (!await pagination(ctx)) break;
|
|
304
|
+
// * page++;
|
|
305
|
+
// * }
|
|
306
|
+
// */
|
|
307
|
+
// // Note: Maybe make is possible to skip several pages at once if the pagination strategy supports it.
|
|
308
|
+
// }
|
|
183
309
|
};
|
|
184
310
|
};
|
|
185
311
|
exports.useTable = useTable;
|
package/package.json
CHANGED