@rickcedwhat/playwright-smart-table 2.0.0 → 2.0.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 CHANGED
@@ -11,7 +11,7 @@ npm install @rickcedwhat/playwright-smart-table
11
11
 
12
12
  Requires @playwright/test as a peer dependency.
13
13
 
14
- šŸš€ Quick Start
14
+ ⚔ Quick Start
15
15
 
16
16
  1. The Standard HTML Table
17
17
 
@@ -23,17 +23,17 @@ import { useTable } from '@rickcedwhat/playwright-smart-table';
23
23
  test('Verify User Email', async ({ page }) => {
24
24
  const table = useTable(page.locator('#users-table'));
25
25
 
26
- // šŸŖ„ Magic: Finds the row with Name="Alice", then gets the Email cell
26
+ // šŸŖ„ Finds the row with Name="Alice", then gets the Email cell.
27
27
  // If Alice is on Page 2, it handles pagination automatically.
28
- await expect(
29
- await table.getByCell({ Name: 'Alice' }, 'Email')
30
- ).toHaveText('alice@example.com');
28
+ const row = await table.getByRow({ Name: 'Alice' });
29
+
30
+ await expect(row.getCell('Email')).toHaveText('alice@example.com');
31
31
  });
32
32
 
33
33
 
34
34
  2. Complex Grids (Material UI / AG-Grid / Divs)
35
35
 
36
- For modern React grids that use <div> structures, simply override the selectors.
36
+ For modern React grids, simply override the selectors and define a pagination strategy.
37
37
 
38
38
  import { useTable, TableStrategies } from '@rickcedwhat/playwright-smart-table';
39
39
 
@@ -43,110 +43,139 @@ const table = useTable(page.locator('.MuiDataGrid-root'), {
43
43
  cellSelector: '.MuiDataGrid-cell',
44
44
  // Strategy: Tell it how to find the next page
45
45
  pagination: TableStrategies.clickNext(
46
- (root) => root.getByRole('button', { name: 'Go to next page' })
46
+ // Use 'page' to find buttons outside the table container
47
+ (root) => root.page().getByRole('button', { name: 'Go to next page' })
47
48
  )
48
49
  });
49
50
 
50
51
 
51
- 🧩 Pagination Strategies
52
+ 🧠 SmartRow Pattern
52
53
 
53
- This library uses the Strategy Pattern to handle navigation. This ensures future stability: we can add new ways to paginate without breaking existing tests.
54
+ The core power of this library is the SmartRow.
54
55
 
55
- clickNext(selector)
56
+ Unlike a standard Playwright Locator, a SmartRow is aware of its context within the table's schema. It extends the standard Locator API, so you can chain standard Playwright methods (.click(), .isVisible()) directly off it.
56
57
 
57
- Best for standard tables (Datatables, lists).
58
+ getCell(columnName)
58
59
 
59
- Behavior: Clicks the button -> Waits for the first row of data to change.
60
+ Instead of writing brittle nth-child selectors, ask for the column by name.
60
61
 
61
- Selector: Can be a CSS string OR a Playwright locator function.
62
+ // āœ… Good: Resilient to column reordering
63
+ await row.getCell('Email').click();
62
64
 
63
- // CSS String
64
- pagination: TableStrategies.clickNext('button.next-page')
65
+ // āŒ Bad: Brittle
66
+ await row.locator('td').nth(2).click();
65
67
 
66
- // Locator Function (More Robust)
67
- pagination: TableStrategies.clickNext((root) => root.getByRole('button', { name: 'Next' }))
68
68
 
69
+ toJSON()
69
70
 
70
- infiniteScroll()
71
+ Extracts the entire row's data into a clean key-value object.
71
72
 
72
- Best for Virtualized Grids (AG-Grid) or lazy-loading lists (HTMX).
73
+ const data = await row.toJSON();
74
+ console.log(data);
75
+ // { Name: "Alice", Role: "Admin", Status: "Active" }
73
76
 
74
- Behavior: Aggressively scrolls the container/window to the bottom -> Waits for row count to increase.
75
77
 
76
- pagination: TableStrategies.infiniteScroll()
78
+ šŸ“– API Reference
77
79
 
80
+ getByRow(filters, options?)
78
81
 
79
- clickLoadMore(selector)
82
+ Strict Retrieval. Finds a single specific row.
80
83
 
81
- Best for "Load More" buttons.
84
+ Throws Error if >1 rows match (ambiguous query).
82
85
 
83
- Behavior: Clicks button -> Waits for row count to increase.
86
+ Returns Sentinel if 0 rows match (allows not.toBeVisible() assertions).
84
87
 
85
- pagination: TableStrategies.clickLoadMore('button.load-more')
88
+ Auto-Paginates if the row isn't found on the current page.
86
89
 
90
+ // Find a row where Name is "Alice" AND Role is "Admin"
91
+ const row = await table.getByRow({ Name: "Alice", Role: "Admin" });
92
+ await expect(row).toBeVisible();
87
93
 
88
- šŸ“– API Reference
94
+ // Assert it does NOT exist
95
+ await expect(await table.getByRow({ Name: "Ghost" })).not.toBeVisible();
89
96
 
90
- getByRow(filters, options?)
91
97
 
92
- Returns the Locator for a specific row.
98
+ getAllRows(options?)
93
99
 
94
- Strict Mode: Throws an error if filters match more than 1 row.
100
+ Inclusive Retrieval. Gets a collection of rows.
95
101
 
96
- Auto-Pagination: Will search up to maxPages to find the row.
102
+ Returns: Array of SmartRow objects.
97
103
 
98
- // Find a row where Name is "Alice" AND Role is "Admin"
99
- const row = await table.getByRow({ Name: "Alice", Role: "Admin" });
100
- await expect(row).toBeVisible();
104
+ Best for: Checking existence ("at least one") or validating sort order.
105
+
106
+ // 1. Get ALL rows on the current page
107
+ const allRows = await table.getAllRows();
108
+
109
+ // 2. Get subset of rows (Filtering)
110
+ const activeUsers = await table.getAllRows({
111
+ filter: { Status: 'Active' }
112
+ });
113
+ expect(activeUsers.length).toBeGreaterThan(0); // "At least one active user"
101
114
 
115
+ // 3. Dump data to JSON
116
+ const data = await table.getAllRows({ asJSON: true });
117
+ console.log(data); // [{ Name: "Alice", Status: "Active" }, ...]
102
118
 
103
- getByCell(filters, targetColumn)
104
119
 
105
- Returns the Locator for a specific cell inside a matched row.
120
+ 🧩 Pagination Strategies
106
121
 
107
- Use this for interactions (clicking edit buttons, checking checkboxes).
122
+ This library uses the Strategy Pattern to handle navigation. You can use the built-in strategies or write your own.
108
123
 
109
- // Find Alice's row, then find the "Actions" column, then click the button inside it
110
- await table.getByCell({ Name: "Alice" }, "Actions").getByRole('button').click();
124
+ Built-in Strategies
111
125
 
126
+ clickNext(selector)
127
+ Best for standard tables (Datatables, lists). Clicks a button and waits for data to change.
112
128
 
113
- getRowAsJSON(filters)
129
+ pagination: TableStrategies.clickNext((root) =>
130
+ root.page().getByRole('button', { name: 'Next' })
131
+ )
114
132
 
115
- Returns a POJO (Plain Old JavaScript Object) of the row data. Useful for debugging or strict data assertions.
116
133
 
117
- const data = await table.getRowAsJSON({ ID: "101" });
118
- console.log(data);
119
- // Output: { ID: "101", Name: "Alice", Status: "Active" }
134
+ infiniteScroll()
135
+ Best for Virtualized Grids (AG-Grid, HTMX). Aggressively scrolls to trigger data loading.
136
+
137
+ pagination: TableStrategies.infiniteScroll()
120
138
 
121
139
 
122
- getRows()
140
+ clickLoadMore(selector)
141
+ Best for "Load More" buttons. Clicks and waits for row count to increase.
123
142
 
124
- Returns an array of all rows on the current page.
143
+ Writing Custom Strategies
125
144
 
126
- const allRows = await table.getRows();
127
- expect(allRows[0].Name).toBe("Alice"); // Verify sort order
145
+ A Strategy is just a function that receives the table context and returns a Promise<boolean> (true if navigation happened, false if we reached the end).
128
146
 
147
+ import { PaginationStrategy } from '@rickcedwhat/playwright-smart-table';
129
148
 
130
- šŸ› ļø Developer Tools
149
+ const myCustomStrategy: PaginationStrategy = async ({ root, page, config }) => {
150
+ // 1. Check if we can navigate
151
+ const nextBtn = page.getByTestId('custom-next-arrow');
152
+ if (!await nextBtn.isVisible()) return false;
131
153
 
132
- The library includes helper tools to generate configurations for you.
154
+ // 2. Perform Navigation
155
+ await nextBtn.click();
133
156
 
134
- // Print the HTML structure prompt to console
135
- // Copy-paste the output into ChatGPT/Gemini to get your config object
136
- await table.generateConfigPrompt();
157
+ // 3. Smart Wait (Crucial!)
158
+ // Wait for a loading spinner to disappear, or data to change
159
+ await expect(page.locator('.spinner')).not.toBeVisible();
160
+
161
+ return true; // We successfully moved to the next page
162
+ };
163
+
164
+
165
+ šŸ› ļø Developer Tools
137
166
 
138
- // Print a prompt to help write a custom Pagination Strategy
139
- await table.generateStrategyPrompt();
167
+ Don't waste time writing selectors manually. Use the generator tools to create your config.
140
168
 
169
+ generateConfigPrompt(options?)
141
170
 
142
- šŸ›”ļø Stability & Versioning
171
+ Prints a prompt you can paste into ChatGPT/Gemini to generate the TableConfig for your specific HTML.
143
172
 
144
- This package follows Semantic Versioning.
173
+ // Options: 'console' (default), 'report' (Playwright HTML Report), 'file'
174
+ await table.generateConfigPrompt({ output: 'report' });
145
175
 
146
- 1.x.x: No breaking changes to the useTable signature.
147
176
 
148
- New strategies may be added, but existing ones will remain stable.
177
+ generateStrategyPrompt(options?)
149
178
 
150
- To ensure stability in your projects, install with:
179
+ Prints a prompt to help you write a custom Pagination Strategy.
151
180
 
152
- "@rickcedwhat/playwright-smart-table": "^1.0.0"
181
+ await table.generateStrategyPrompt({ output: 'console' });
@@ -84,16 +84,18 @@ exports.TableStrategies = {
84
84
  const oldCount = yield rows.count();
85
85
  if (oldCount === 0)
86
86
  return false;
87
- // 1. Scroll the very last row into view to trigger the fetch
88
- yield rows.last().scrollIntoViewIfNeeded();
89
- // Optional: Press "End" to force scroll on stubborn grids (like AG Grid)
90
- yield page.keyboard.press('End');
91
- // 2. Smart Wait: Wait for row count to increase
87
+ // Aggressive Scroll Logic:
88
+ // We use expect.poll to RETRY the scroll action if the count hasn't increased.
89
+ // This fixes flakiness where the first scroll might be missed by the intersection observer.
92
90
  try {
93
- yield (0, test_1.expect)(() => __awaiter(void 0, void 0, void 0, function* () {
94
- const newCount = yield rows.count();
95
- (0, test_1.expect)(newCount).toBeGreaterThan(oldCount);
96
- })).toPass({ timeout });
91
+ yield test_1.expect.poll(() => __awaiter(void 0, void 0, void 0, function* () {
92
+ // 1. Trigger: Scroll the last row into view
93
+ yield rows.last().scrollIntoViewIfNeeded();
94
+ // 2. Force: Press "End" to help with stubborn window-scrollers
95
+ yield page.keyboard.press('End');
96
+ // 3. Return count for assertion
97
+ return rows.count();
98
+ }), { timeout }).toBeGreaterThan(oldCount);
97
99
  return true;
98
100
  }
99
101
  catch (e) {
@@ -0,0 +1,6 @@
1
+ /**
2
+ * šŸ¤– AUTO-GENERATED FILE. DO NOT EDIT.
3
+ * This file is generated by scripts/embed-types.js
4
+ * It contains the raw text of types.ts to provide context for LLM prompts.
5
+ */
6
+ export declare const TYPE_CONTEXT = "\n// src/types.ts\n\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\n/**\n * A function that handles pagination logic.\n * Returns true if more data was loaded (navigation occurred), false if end of data.\n */\nexport type PaginationStrategy = (context: TableContext) => Promise<boolean>;\n\nexport interface PromptOptions {\n /** * Where to output the prompt.\n * - 'console': Logs to stdout (default).\n * - 'report': Attaches to the Playwright HTML report (best for CI/VMs).\n * - 'file': Writes to a local file (e.g. 'smart-table-prompt.md').\n */\n output?: 'console' | 'report' | 'file';\n \n /**\n * Include TypeScript interfaces in the prompt?\n * Helps LLMs generate correct code structure.\n * Default: true\n */\n includeTypes?: boolean;\n}\n\nexport interface TableConfig {\n rowSelector?: Selector;\n headerSelector?: Selector;\n cellSelector?: Selector;\n /**\n * Strategy for handling pagination.\n * Use presets from TableStrategies or write your own.\n */\n pagination?: PaginationStrategy;\n maxPages?: number;\n /**\n * Optional hook to rename columns dynamically.\n * Useful for naming empty columns (like '__col_0') to something semantic like 'Actions'.\n */\n headerTransformer?: (text: string, index: number) => string;\n /**\n * Automatically scroll the table into view on first interaction.\n * Helps ensure the table is visible in traces/videos.\n * Default: true\n */\n autoScroll?: boolean;\n}\n\nexport interface TableResult {\n getHeaders: () => Promise<string[]>;\n getHeaderCell: (columnName: string) => Promise<Locator>;\n\n /** * Find a specific row by its content.\n * Default: Returns SmartRow (Locator).\n * Option { asJSON: true }: Returns Record<string, string> (Data).\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 /** * Get all rows on the current page.\n * Can be filtered by column values.\n * * @param options.asJSON - Returns data objects instead of SmartRows\n * @param options.filter - Key-Value pairs to filter rows (e.g. { Status: 'Active' })\n * @param options.exact - Exact match for filters (default: false)\n */\n getAllRows: <T extends { asJSON?: boolean }>(\n options?: { \n filter?: Record<string, string | RegExp | number>; \n exact?: boolean \n } & 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";
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TYPE_CONTEXT = void 0;
4
+ /**
5
+ * šŸ¤– AUTO-GENERATED FILE. DO NOT EDIT.
6
+ * This file is generated by scripts/embed-types.js
7
+ * It contains the raw text of types.ts to provide context for LLM prompts.
8
+ */
9
+ exports.TYPE_CONTEXT = `
10
+ // src/types.ts
11
+
12
+ export type Selector = string | ((root: Locator | Page) => Locator);
13
+
14
+ export type SmartRow = Locator & {
15
+ getCell(column: string): Locator;
16
+ toJSON(): Promise<Record<string, string>>;
17
+ };
18
+
19
+ export interface TableContext {
20
+ root: Locator;
21
+ config: Required<TableConfig>;
22
+ page: Page;
23
+ resolve: (selector: Selector, parent: Locator | Page) => Locator;
24
+ }
25
+
26
+ /**
27
+ * A function that handles pagination logic.
28
+ * Returns true if more data was loaded (navigation occurred), false if end of data.
29
+ */
30
+ export type PaginationStrategy = (context: TableContext) => Promise<boolean>;
31
+
32
+ export interface PromptOptions {
33
+ /** * Where to output the prompt.
34
+ * - 'console': Logs to stdout (default).
35
+ * - 'report': Attaches to the Playwright HTML report (best for CI/VMs).
36
+ * - 'file': Writes to a local file (e.g. 'smart-table-prompt.md').
37
+ */
38
+ output?: 'console' | 'report' | 'file';
39
+
40
+ /**
41
+ * Include TypeScript interfaces in the prompt?
42
+ * Helps LLMs generate correct code structure.
43
+ * Default: true
44
+ */
45
+ includeTypes?: boolean;
46
+ }
47
+
48
+ export interface TableConfig {
49
+ rowSelector?: Selector;
50
+ headerSelector?: Selector;
51
+ cellSelector?: Selector;
52
+ /**
53
+ * Strategy for handling pagination.
54
+ * Use presets from TableStrategies or write your own.
55
+ */
56
+ pagination?: PaginationStrategy;
57
+ maxPages?: number;
58
+ /**
59
+ * Optional hook to rename columns dynamically.
60
+ * Useful for naming empty columns (like '__col_0') to something semantic like 'Actions'.
61
+ */
62
+ headerTransformer?: (text: string, index: number) => string;
63
+ /**
64
+ * Automatically scroll the table into view on first interaction.
65
+ * Helps ensure the table is visible in traces/videos.
66
+ * Default: true
67
+ */
68
+ autoScroll?: boolean;
69
+ }
70
+
71
+ export interface TableResult {
72
+ getHeaders: () => Promise<string[]>;
73
+ getHeaderCell: (columnName: string) => Promise<Locator>;
74
+
75
+ /** * Find a specific row by its content.
76
+ * Default: Returns SmartRow (Locator).
77
+ * Option { asJSON: true }: Returns Record<string, string> (Data).
78
+ */
79
+ getByRow: <T extends { asJSON?: boolean }>(
80
+ filters: Record<string, string | RegExp | number>,
81
+ options?: { exact?: boolean, maxPages?: number } & T
82
+ ) => Promise<T['asJSON'] extends true ? Record<string, string> : SmartRow>;
83
+
84
+ /** * Get all rows on the current page.
85
+ * Can be filtered by column values.
86
+ * * @param options.asJSON - Returns data objects instead of SmartRows
87
+ * @param options.filter - Key-Value pairs to filter rows (e.g. { Status: 'Active' })
88
+ * @param options.exact - Exact match for filters (default: false)
89
+ */
90
+ getAllRows: <T extends { asJSON?: boolean }>(
91
+ options?: {
92
+ filter?: Record<string, string | RegExp | number>;
93
+ exact?: boolean
94
+ } & T
95
+ ) => Promise<T['asJSON'] extends true ? Record<string, string>[] : SmartRow[]>;
96
+
97
+ generateConfigPrompt: (options?: PromptOptions) => Promise<void>;
98
+ generateStrategyPrompt: (options?: PromptOptions) => Promise<void>;
99
+ }
100
+ `;
package/dist/types.d.ts CHANGED
@@ -15,6 +15,20 @@ export interface TableContext {
15
15
  * Returns true if more data was loaded (navigation occurred), false if end of data.
16
16
  */
17
17
  export type PaginationStrategy = (context: TableContext) => Promise<boolean>;
18
+ export interface PromptOptions {
19
+ /** * Where to output the prompt.
20
+ * - 'console': Logs to stdout (default).
21
+ * - 'report': Attaches to the Playwright HTML report (best for CI/VMs).
22
+ * - 'file': Writes to a local file (e.g. 'smart-table-prompt.md').
23
+ */
24
+ output?: 'console' | 'report' | 'file';
25
+ /**
26
+ * Include TypeScript interfaces in the prompt?
27
+ * Helps LLMs generate correct code structure.
28
+ * Default: true
29
+ */
30
+ includeTypes?: boolean;
31
+ }
18
32
  export interface TableConfig {
19
33
  rowSelector?: Selector;
20
34
  headerSelector?: Selector;
@@ -30,6 +44,12 @@ export interface TableConfig {
30
44
  * Useful for naming empty columns (like '__col_0') to something semantic like 'Actions'.
31
45
  */
32
46
  headerTransformer?: (text: string, index: number) => string;
47
+ /**
48
+ * Automatically scroll the table into view on first interaction.
49
+ * Helps ensure the table is visible in traces/videos.
50
+ * Default: true
51
+ */
52
+ autoScroll?: boolean;
33
53
  }
34
54
  export interface TableResult {
35
55
  getHeaders: () => Promise<string[]>;
@@ -45,12 +65,17 @@ export interface TableResult {
45
65
  maxPages?: number;
46
66
  } & T) => Promise<T['asJSON'] extends true ? Record<string, string> : SmartRow>;
47
67
  /** * Get all rows on the current page.
48
- * Default: Returns SmartRow[] (Locators).
49
- * Option { asJSON: true }: Returns Record<string, string>[] (Data).
68
+ * Can be filtered by column values.
69
+ * * @param options.asJSON - Returns data objects instead of SmartRows
70
+ * @param options.filter - Key-Value pairs to filter rows (e.g. { Status: 'Active' })
71
+ * @param options.exact - Exact match for filters (default: false)
50
72
  */
51
73
  getAllRows: <T extends {
52
74
  asJSON?: boolean;
53
- }>(options?: T) => Promise<T['asJSON'] extends true ? Record<string, string>[] : SmartRow[]>;
54
- generateConfigPrompt: () => Promise<void>;
55
- generateStrategyPrompt: () => Promise<void>;
75
+ }>(options?: {
76
+ filter?: Record<string, string | RegExp | number>;
77
+ exact?: boolean;
78
+ } & T) => Promise<T['asJSON'] extends true ? Record<string, string>[] : SmartRow[]>;
79
+ generateConfigPrompt: (options?: PromptOptions) => Promise<void>;
80
+ generateStrategyPrompt: (options?: PromptOptions) => Promise<void>;
56
81
  }
package/dist/useTable.js CHANGED
@@ -10,8 +10,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.useTable = void 0;
13
+ const test_1 = require("@playwright/test");
14
+ const typeContext_1 = require("./typeContext");
13
15
  const useTable = (rootLocator, configOptions = {}) => {
14
- const config = Object.assign({ rowSelector: "tbody tr", headerSelector: "th", cellSelector: "td", pagination: undefined, maxPages: 1, headerTransformer: undefined }, configOptions);
16
+ const config = Object.assign({ rowSelector: "tbody tr", headerSelector: "th", cellSelector: "td", pagination: undefined, maxPages: 1, headerTransformer: undefined, autoScroll: true }, configOptions);
15
17
  const resolve = (item, parent) => {
16
18
  if (typeof item === 'string')
17
19
  return parent.locator(item);
@@ -23,6 +25,9 @@ const useTable = (rootLocator, configOptions = {}) => {
23
25
  const _getMap = () => __awaiter(void 0, void 0, void 0, function* () {
24
26
  if (_headerMap)
25
27
  return _headerMap;
28
+ if (config.autoScroll) {
29
+ yield rootLocator.scrollIntoViewIfNeeded();
30
+ }
26
31
  const headerLoc = resolve(config.headerSelector, rootLocator);
27
32
  try {
28
33
  yield headerLoc.first().waitFor({ state: 'visible', timeout: 3000 });
@@ -63,35 +68,39 @@ const useTable = (rootLocator, configOptions = {}) => {
63
68
  });
64
69
  return smart;
65
70
  };
71
+ const _applyFilters = (baseRows, filters, map, exact) => {
72
+ let filtered = baseRows;
73
+ const page = rootLocator.page();
74
+ for (const [colName, value] of Object.entries(filters)) {
75
+ const colIndex = map.get(colName);
76
+ if (colIndex === undefined)
77
+ throw new Error(`Column '${colName}' not found.`);
78
+ const filterVal = typeof value === 'number' ? String(value) : value;
79
+ const cellTemplate = resolve(config.cellSelector, page);
80
+ filtered = filtered.filter({
81
+ has: cellTemplate.nth(colIndex).getByText(filterVal, { exact }),
82
+ });
83
+ }
84
+ return filtered;
85
+ };
66
86
  const _findRowLocator = (filters_1, ...args_1) => __awaiter(void 0, [filters_1, ...args_1], void 0, function* (filters, options = {}) {
67
87
  var _a;
68
88
  const map = yield _getMap();
69
- const page = rootLocator.page();
70
89
  const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : config.maxPages;
71
90
  let currentPage = 1;
72
91
  while (true) {
73
- let rowLocator = resolve(config.rowSelector, rootLocator);
74
- for (const [colName, value] of Object.entries(filters)) {
75
- const colIndex = map.get(colName);
76
- if (colIndex === undefined)
77
- throw new Error(`Column '${colName}' not found. Available: ${[...map.keys()].join(', ')}`);
78
- const exact = options.exact || false;
79
- const filterVal = typeof value === 'number' ? String(value) : value;
80
- const cellTemplate = resolve(config.cellSelector, page);
81
- rowLocator = rowLocator.filter({
82
- has: cellTemplate.nth(colIndex).getByText(filterVal, { exact }),
83
- });
84
- }
85
- const count = yield rowLocator.count();
92
+ const allRows = resolve(config.rowSelector, rootLocator);
93
+ const matchedRows = _applyFilters(allRows, filters, map, options.exact || false);
94
+ const count = yield matchedRows.count();
86
95
  if (count > 1)
87
96
  throw new Error(`Strict Mode Violation: Found ${count} rows matching ${JSON.stringify(filters)}.`);
88
97
  if (count === 1)
89
- return rowLocator.first();
98
+ return matchedRows.first();
90
99
  if (config.pagination && currentPage < effectiveMaxPages) {
91
100
  const context = {
92
101
  root: rootLocator,
93
102
  config: config,
94
- page: page,
103
+ page: rootLocator.page(),
95
104
  resolve: resolve
96
105
  };
97
106
  const didLoadMore = yield config.pagination(context);
@@ -103,6 +112,29 @@ const useTable = (rootLocator, configOptions = {}) => {
103
112
  return null;
104
113
  }
105
114
  });
115
+ const _handlePrompt = (promptName_1, content_1, ...args_1) => __awaiter(void 0, [promptName_1, content_1, ...args_1], void 0, function* (promptName, content, options = {}) {
116
+ const { output = 'console', includeTypes = true } = options;
117
+ let finalPrompt = content;
118
+ if (includeTypes) {
119
+ finalPrompt += `\n\nšŸ‘‡ Useful TypeScript Definitions šŸ‘‡\n\`\`\`typescript\n${typeContext_1.TYPE_CONTEXT}\n\`\`\`\n`;
120
+ }
121
+ if (output === 'console') {
122
+ console.log(finalPrompt);
123
+ }
124
+ else if (output === 'report') {
125
+ if (test_1.test.info()) {
126
+ yield test_1.test.info().attach(promptName, {
127
+ body: finalPrompt,
128
+ contentType: 'text/markdown'
129
+ });
130
+ console.log(`āœ… Attached '${promptName}' to Playwright Report.`);
131
+ }
132
+ else {
133
+ console.warn('āš ļø Cannot attach to report: No active test info found. Logging to console instead.');
134
+ console.log(finalPrompt);
135
+ }
136
+ }
137
+ });
106
138
  return {
107
139
  getHeaders: () => __awaiter(void 0, void 0, void 0, function* () { return Array.from((yield _getMap()).keys()); }),
108
140
  getHeaderCell: (columnName) => __awaiter(void 0, void 0, void 0, function* () {
@@ -114,38 +146,39 @@ const useTable = (rootLocator, configOptions = {}) => {
114
146
  }),
115
147
  getByRow: (filters, options) => __awaiter(void 0, void 0, void 0, function* () {
116
148
  let row = yield _findRowLocator(filters, options);
117
- // āœ… FIX: Sentinel Logic for negative assertions (expect(row).not.toBeVisible())
118
149
  if (!row) {
119
150
  row = resolve(config.rowSelector, rootLocator).filter({ hasText: "___SENTINEL_ROW_NOT_FOUND___" + Date.now() });
120
151
  }
121
152
  const smartRow = _makeSmart(row, yield _getMap());
122
153
  if (options === null || options === void 0 ? void 0 : options.asJSON) {
123
- // If row doesn't exist, toJSON() returns empty object or throws?
124
- // For safety, let's let it run naturally (it will likely return empty strings)
125
154
  return smartRow.toJSON();
126
155
  }
127
156
  return smartRow;
128
157
  }),
129
158
  getAllRows: (options) => __awaiter(void 0, void 0, void 0, function* () {
130
159
  const map = yield _getMap();
131
- const rowLocators = yield resolve(config.rowSelector, rootLocator).all();
132
- const smartRows = rowLocators.map(loc => _makeSmart(loc, map));
160
+ let rowLocators = resolve(config.rowSelector, rootLocator);
161
+ if (options === null || options === void 0 ? void 0 : options.filter) {
162
+ rowLocators = _applyFilters(rowLocators, options.filter, map, options.exact || false);
163
+ }
164
+ const rows = yield rowLocators.all();
165
+ const smartRows = rows.map(loc => _makeSmart(loc, map));
133
166
  if (options === null || options === void 0 ? void 0 : options.asJSON) {
134
167
  return Promise.all(smartRows.map(r => r.toJSON()));
135
168
  }
136
169
  return smartRows;
137
170
  }),
138
- generateConfigPrompt: () => __awaiter(void 0, void 0, void 0, function* () {
171
+ generateConfigPrompt: (options) => __awaiter(void 0, void 0, void 0, function* () {
139
172
  const html = yield rootLocator.evaluate((el) => el.outerHTML);
140
173
  const separator = "=".repeat(50);
141
- const prompt = `\n${separator}\nšŸ¤– COPY INTO GEMINI/ChatGPT šŸ¤–\n${separator}\nI am using 'playwright-smart-table'. Generate config for:\n\`\`\`html\n${html.substring(0, 5000)} ...\n\`\`\`\n${separator}\n`;
142
- console.log(prompt);
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, 5000)} ...\n\`\`\`\n${separator}\n`;
175
+ yield _handlePrompt('Smart Table Config', content, options);
143
176
  }),
144
- generateStrategyPrompt: () => __awaiter(void 0, void 0, void 0, function* () {
177
+ generateStrategyPrompt: (options) => __awaiter(void 0, void 0, void 0, function* () {
145
178
  const container = rootLocator.locator('xpath=..');
146
179
  const html = yield container.evaluate((el) => el.outerHTML);
147
- const prompt = `\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, 5000)} ...\n\`\`\`\n`;
148
- console.log(prompt);
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, 5000)} ...\n\`\`\`\n`;
181
+ yield _handlePrompt('Smart Table Strategy', content, options);
149
182
  })
150
183
  };
151
184
  };
package/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "@rickcedwhat/playwright-smart-table",
3
- "version": "2.0.0",
4
- "description": "A smart table utility for Playwright with built-in pagination strategies.",
3
+ "version": "2.0.2",
4
+ "description": "A smart table utility for Playwright with built-in pagination strategies that are fully extensible.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "files": [
8
8
  "dist"
9
9
  ],
10
10
  "scripts": {
11
- "build": "tsc",
11
+ "generate-types": "node scripts/embed-types.mjs",
12
+ "build": "npm run generate-types && tsc",
12
13
  "prepublishOnly": "npm run build",
13
- "publish-public": "npm publish --access public",
14
+ "release": "npm run build && npm publish --access public",
14
15
  "test": "npx playwright test"
15
16
  },
16
17
  "keywords": [