@rickcedwhat/playwright-smart-table 2.1.0 → 2.1.1

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
@@ -15,39 +15,46 @@ Requires @playwright/test as a peer dependency.
15
15
 
16
16
  1. The Standard HTML Table
17
17
 
18
- For standard tables (<table>, <tr>, <td>), no configuration is needed.
19
-
20
- import { test, expect } from '@playwright/test';
21
- import { useTable } from '@rickcedwhat/playwright-smart-table';
22
-
23
- test('Verify User Email', async ({ page }) => {
24
- const table = useTable(page.locator('#users-table'));
25
-
26
- // 🪄 Finds the row with Name="Alice", then gets the Email cell.
27
- // If Alice is on Page 2, it handles pagination automatically.
28
- const row = await table.getByRow({ Name: 'Alice' });
29
-
30
- await expect(row.getCell('Email')).toHaveText('alice@example.com');
18
+ For standard tables (<table>, <tr>, <td>), no configuration is needed (defaults work for most standard HTML tables).
19
+
20
+ <!-- embed: quick-start -->
21
+ ```typescript
22
+ const table = useTable(page.locator('#example'), {
23
+ headerSelector: 'thead th' // Override for this specific site
31
24
  });
32
25
 
26
+ // 🪄 Finds the row with Name="Airi Satou", then gets the Position cell.
27
+ // If Airi is on Page 2, it handles pagination automatically.
28
+ const row = await table.getByRow({ Name: 'Airi Satou' });
29
+
30
+ await expect(row.getCell('Position')).toHaveText('Accountant');
31
+ ```
32
+ <!-- /embed: quick-start -->
33
33
 
34
34
  2. Complex Grids (Material UI / AG-Grid / Divs)
35
35
 
36
36
  For modern React grids, simply override the selectors and define a pagination strategy.
37
37
 
38
- import { useTable, TableStrategies } from '@rickcedwhat/playwright-smart-table';
39
-
40
- const table = useTable(page.locator('.MuiDataGrid-root'), {
41
- rowSelector: '.MuiDataGrid-row',
42
- headerSelector: '.MuiDataGrid-columnHeader',
43
- cellSelector: '.MuiDataGrid-cell',
38
+ <!-- embed: pagination -->
39
+ ```typescript
40
+ const table = useTable(page.locator('#example'), {
41
+ rowSelector: 'tbody tr',
42
+ headerSelector: 'thead th',
43
+ cellSelector: 'td',
44
44
  // Strategy: Tell it how to find the next page
45
- pagination: TableStrategies.clickNext(
46
- // Use 'page' to find buttons outside the table container
47
- (root) => root.page().getByRole('button', { name: 'Go to next page' })
48
- )
45
+ pagination: TableStrategies.clickNext(() =>
46
+ page.getByRole('link', { name: 'Next' })
47
+ ),
48
+ maxPages: 5 // Allow scanning up to 5 pages
49
49
  });
50
50
 
51
+ // ✅ Verify Colleen is NOT visible initially
52
+ await expect(page.getByText("Colleen Hurst")).not.toBeVisible();
53
+
54
+ await expect(await table.getByRow({ Name: "Colleen Hurst" })).toBeVisible();
55
+ // NOTE: We're now on the page where Colleen Hurst exists (typically Page 2)
56
+ ```
57
+ <!-- /embed: pagination -->
51
58
 
52
59
  🧠 SmartRow Pattern
53
60
 
@@ -55,25 +62,21 @@ The core power of this library is the SmartRow.
55
62
 
56
63
  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.
57
64
 
58
- getCell(columnName)
59
-
60
- Instead of writing brittle nth-child selectors, ask for the column by name.
65
+ <!-- embed: smart-row -->
66
+ ```typescript
67
+ // 1. Get SmartRow via getByRow
68
+ const row = await table.getByRow({ Name: 'Airi Satou' });
61
69
 
70
+ // 2. Interact with cell (No more getByCell needed!)
62
71
  // ✅ Good: Resilient to column reordering
63
- await row.getCell('Email').click();
64
-
65
- // ❌ Bad: Brittle
66
- await row.locator('td').nth(2).click();
67
-
68
-
69
- toJSON()
70
-
71
- Extracts the entire row's data into a clean key-value object.
72
+ await row.getCell('Position').click();
72
73
 
74
+ // 3. Dump data from row
73
75
  const data = await row.toJSON();
74
- console.log(data);
75
- // { Name: "Alice", Role: "Admin", Status: "Active" }
76
-
76
+ console.log(data);
77
+ // { Name: "Airi Satou", Position: "Accountant", ... }
78
+ ```
79
+ <!-- /embed: smart-row -->
77
80
 
78
81
  📖 API Reference
79
82
 
@@ -87,13 +90,16 @@ Returns Sentinel if 0 rows match (allows not.toBeVisible() assertions).
87
90
 
88
91
  Auto-Paginates if the row isn't found on the current page.
89
92
 
90
- // Find a row where Name is "Alice" AND Role is "Admin"
91
- const row = await table.getByRow({ Name: "Alice", Role: "Admin" });
93
+ <!-- embed: get-by-row -->
94
+ ```typescript
95
+ // Find a row where Name is "Airi Satou" AND Office is "Tokyo"
96
+ const row = await table.getByRow({ Name: "Airi Satou", Office: "Tokyo" });
92
97
  await expect(row).toBeVisible();
93
98
 
94
99
  // Assert it does NOT exist
95
- await expect(await table.getByRow({ Name: "Ghost" })).not.toBeVisible();
96
-
100
+ await expect(await table.getByRow({ Name: "Ghost User" })).not.toBeVisible();
101
+ ```
102
+ <!-- /embed: get-by-row -->
97
103
 
98
104
  getAllRows(options?)
99
105
 
@@ -103,19 +109,22 @@ Returns: Array of SmartRow objects.
103
109
 
104
110
  Best for: Checking existence ("at least one") or validating sort order.
105
111
 
112
+ <!-- embed: get-all-rows -->
113
+ ```typescript
106
114
  // 1. Get ALL rows on the current page
107
115
  const allRows = await table.getAllRows();
108
116
 
109
117
  // 2. Get subset of rows (Filtering)
110
- const activeUsers = await table.getAllRows({
111
- filter: { Status: 'Active' }
118
+ const tokyoUsers = await table.getAllRows({
119
+ filter: { Office: 'Tokyo' }
112
120
  });
113
- expect(activeUsers.length).toBeGreaterThan(0); // "At least one active user"
121
+ expect(tokyoUsers.length).toBeGreaterThan(0);
114
122
 
115
123
  // 3. Dump data to JSON
116
124
  const data = await table.getAllRows({ asJSON: true });
117
- console.log(data); // [{ Name: "Alice", Status: "Active" }, ...]
118
-
125
+ console.log(data); // [{ Name: "Airi Satou", ... }, ...]
126
+ ```
127
+ <!-- /embed: get-all-rows -->
119
128
 
120
129
  🧩 Pagination Strategies
121
130
 
@@ -123,44 +132,19 @@ This library uses the Strategy Pattern to handle navigation. You can use the bui
123
132
 
124
133
  Built-in Strategies
125
134
 
126
- clickNext(selector)
127
- Best for standard tables (Datatables, lists). Clicks a button and waits for data to change.
135
+ clickNext(selector) Best for standard tables (Datatables, lists). Clicks a button and waits for data to change.
128
136
 
129
137
  pagination: TableStrategies.clickNext((root) =>
130
138
  root.page().getByRole('button', { name: 'Next' })
131
139
  )
132
140
 
133
141
 
134
- infiniteScroll()
135
- Best for Virtualized Grids (AG-Grid, HTMX). Aggressively scrolls to trigger data loading.
142
+ infiniteScroll() Best for Virtualized Grids (AG-Grid, HTMX). Aggressively scrolls to trigger data loading.
136
143
 
137
144
  pagination: TableStrategies.infiniteScroll()
138
145
 
139
146
 
140
- clickLoadMore(selector)
141
- Best for "Load More" buttons. Clicks and waits for row count to increase.
142
-
143
- Writing Custom Strategies
144
-
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).
146
-
147
- import { PaginationStrategy } from '@rickcedwhat/playwright-smart-table';
148
-
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;
153
-
154
- // 2. Perform Navigation
155
- await nextBtn.click();
156
-
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
-
147
+ clickLoadMore(selector) Best for "Load More" buttons. Clicks and waits for row count to increase.
164
148
 
165
149
  🛠️ Developer Tools
166
150
 
@@ -170,8 +154,8 @@ generateConfigPrompt(options?)
170
154
 
171
155
  Prints a prompt you can paste into ChatGPT/Gemini to generate the TableConfig for your specific HTML.
172
156
 
173
- // Options: 'console' (default), 'report' (Playwright HTML Report), 'file'
174
- await table.generateConfigPrompt({ output: 'report' });
157
+ // Options: 'console' (default), 'error' (Throw error to see prompt in trace/cloud)
158
+ await table.generateConfigPrompt({ output: 'console' });
175
159
 
176
160
 
177
161
  generateStrategyPrompt(options?)
@@ -34,20 +34,40 @@ exports.TableStrategies = {
34
34
  clickNext: (nextButtonSelector, timeout = 5000) => {
35
35
  return (_a) => __awaiter(void 0, [_a], void 0, function* ({ root, config, resolve, page }) {
36
36
  const nextBtn = resolve(nextButtonSelector, root).first();
37
- // Check if button exists/enabled before clicking
38
- if (!(yield nextBtn.isVisible()) || !(yield nextBtn.isEnabled())) {
37
+ // Debug log (can be verbose, maybe useful for debugging only)
38
+ // console.log(`[Strategy: clickNext] Checking button...`);
39
+ // Check if button exists/enabled before clicking.
40
+ // We do NOT wait here because if the button isn't visible/enabled,
41
+ // we assume we reached the last page.
42
+ if (!(yield nextBtn.isVisible())) {
43
+ console.log(`[Strategy: clickNext] Button not visible. Stopping pagination.`);
44
+ return false;
45
+ }
46
+ if (!(yield nextBtn.isEnabled())) {
47
+ console.log(`[Strategy: clickNext] Button disabled. Stopping pagination.`);
39
48
  return false;
40
49
  }
41
50
  // 1. Snapshot current state
42
51
  const firstRow = resolve(config.rowSelector, root).first();
43
52
  const oldText = yield firstRow.innerText().catch(() => "");
44
53
  // 2. Click
45
- yield nextBtn.click();
46
- // 3. Smart Wait (Polling) - No 'expect' needed
47
- return yield waitForCondition(() => __awaiter(void 0, void 0, void 0, function* () {
54
+ console.log(`[Strategy: clickNext] Clicking next button...`);
55
+ try {
56
+ yield nextBtn.click({ timeout: 2000 });
57
+ }
58
+ catch (e) {
59
+ console.warn(`[Strategy: clickNext] Click failed (blocked or detached): ${e}`);
60
+ return false;
61
+ }
62
+ // 3. Smart Wait (Polling)
63
+ const success = yield waitForCondition(() => __awaiter(void 0, void 0, void 0, function* () {
48
64
  const newText = yield firstRow.innerText().catch(() => "");
49
65
  return newText !== oldText;
50
66
  }), timeout, page);
67
+ if (!success) {
68
+ console.warn(`[Strategy: clickNext] Warning: Table content did not change after clicking Next.`);
69
+ }
70
+ return success;
51
71
  });
52
72
  },
53
73
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rickcedwhat/playwright-smart-table",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "A smart table utility for Playwright with built-in pagination strategies that are fully extensible.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -13,9 +13,11 @@
13
13
  ],
14
14
  "scripts": {
15
15
  "generate-types": "node scripts/embed-types.mjs",
16
- "build": "npm run generate-types && tsc",
16
+ "generate-docs": "node scripts/generate-readme.mjs",
17
+ "build": "npm run generate-types && npm run generate-docs && tsc",
17
18
  "prepublishOnly": "npm run build",
18
- "test": "npx playwright test"
19
+ "test": "npx playwright test",
20
+ "prepare": "husky install"
19
21
  },
20
22
  "keywords": [
21
23
  "playwright",
@@ -37,6 +39,7 @@
37
39
  "@playwright/test": "^1.50.0",
38
40
  "@types/node": "^20.0.0",
39
41
  "ts-node": "^10.9.0",
40
- "typescript": "^5.0.0"
42
+ "typescript": "^5.0.0",
43
+ "husky": "^8.0.0"
41
44
  }
42
45
  }