@rickcedwhat/playwright-smart-table 1.0.1 → 1.0.4

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/package.json CHANGED
@@ -1,21 +1,25 @@
1
1
  {
2
2
  "name": "@rickcedwhat/playwright-smart-table",
3
- "version": "1.0.1",
3
+ "version": "1.0.4",
4
4
  "description": "A smart table utility for Playwright with built-in pagination strategies.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
7
10
  "scripts": {
8
11
  "build": "tsc",
12
+ "prepublishOnly": "npm run build",
9
13
  "test": "npx playwright test"
10
14
  },
11
15
  "keywords": ["playwright", "testing", "table", "automation"],
12
16
  "author": "",
13
17
  "license": "ISC",
14
18
  "peerDependencies": {
15
- "@playwright/test": "^1.30.0"
19
+ "@playwright/test": "*"
16
20
  },
17
21
  "devDependencies": {
18
- "@playwright/test": "^1.30.0",
22
+ "@playwright/test": "^1.50.0",
19
23
  "@types/node": "^20.0.0",
20
24
  "ts-node": "^10.9.0",
21
25
  "typescript": "^5.0.0"
@@ -1,34 +0,0 @@
1
- // playwright.config.ts
2
- import { defineConfig } from '@playwright/test';
3
-
4
- export default defineConfig({
5
- // Look for test files in the "tests" directory, relative to this configuration file.
6
- testDir: 'tests',
7
-
8
- // Run all tests in parallel.
9
- fullyParallel: true,
10
-
11
- // Fail the build on CI if you accidentally left test.only in the source code.
12
- forbidOnly: !!process.env.CI,
13
-
14
- // Retry on CI only.
15
- retries: process.env.CI ? 2 : 0,
16
-
17
- // Opt out of parallel tests on CI.
18
- workers: process.env.CI ? 1 : undefined,
19
-
20
- // Reporter to use
21
- reporter: 'html',
22
-
23
- use: {
24
- // Collect trace when retrying the failed test.
25
- // Setting to 'on' forces it for every run (useful for debugging now).
26
- trace: 'on',
27
-
28
- // Record video for failures
29
- video: 'retain-on-failure',
30
-
31
- // Take screenshot on failure
32
- screenshot: 'only-on-failure',
33
- },
34
- });
package/src/index.ts DELETED
@@ -1,3 +0,0 @@
1
- export * from './useTable';
2
- export * from './types';
3
- export * from './strategies';
@@ -1,106 +0,0 @@
1
- // src/strategies/index.ts
2
- import { expect } from '@playwright/test';
3
- import { PaginationStrategy, Selector, TableContext } from '../types';
4
-
5
- export const TableStrategies = {
6
- /**
7
- * Strategy: Clicks a "Next" button and waits for the first row of data to change.
8
- * Best for: Standard pagination (Page 1 > Page 2 > Page 3)
9
- * * @param nextButtonSelector - Selector for the next button (e.g. 'button.next' or getByRole('button', {name: 'Next'}))
10
- * @param timeout - How long to wait for the table to refresh (default: 5000ms)
11
- */
12
- clickNext: (nextButtonSelector: Selector, timeout = 5000): PaginationStrategy => {
13
- return async ({ root, config, resolve }: TableContext) => {
14
- // 1. Find the button using the table's helper
15
- const nextBtn = resolve(nextButtonSelector, root).first();
16
-
17
- // If button isn't there or disabled, we are at the end
18
- if (!await nextBtn.isVisible() || !await nextBtn.isEnabled()) {
19
- return false;
20
- }
21
-
22
- // 2. Snapshot the current state (text of the first row)
23
- // We use the table's OWN row selector to ensure we are looking at real data
24
- const firstRow = resolve(config.rowSelector, root).first();
25
- const oldText = await firstRow.innerText().catch(() => ""); // Handle empty tables gracefully
26
-
27
- // 3. Click the button
28
- await nextBtn.click();
29
-
30
- // 4. Smart Wait: Wait for the first row to have DIFFERENT text
31
- try {
32
- await expect(firstRow).not.toHaveText(oldText, { timeout });
33
- return true; // Success: Data changed
34
- } catch (e) {
35
- return false; // Failed: Timed out (probably end of data or broken button)
36
- }
37
- };
38
- },
39
-
40
- /**
41
- * Strategy: Clicks a "Load More" button and waits for the row count to increase.
42
- * Best for: Lists where "Load More" appends data to the bottom.
43
- * * @param buttonSelector - Selector for the load more button
44
- * @param timeout - Wait timeout (default: 5000ms)
45
- */
46
- clickLoadMore: (buttonSelector: Selector, timeout = 5000): PaginationStrategy => {
47
- return async ({ root, config, resolve }: TableContext) => {
48
- const loadMoreBtn = resolve(buttonSelector, root).first();
49
-
50
- if (!await loadMoreBtn.isVisible() || !await loadMoreBtn.isEnabled()) {
51
- return false;
52
- }
53
-
54
- // 1. Snapshot: Count current rows
55
- const rows = resolve(config.rowSelector, root);
56
- const oldCount = await rows.count();
57
-
58
- // 2. Click
59
- await loadMoreBtn.click();
60
-
61
- // 3. Smart Wait: Wait for row count to be greater than before
62
- try {
63
- await expect(async () => {
64
- const newCount = await rows.count();
65
- expect(newCount).toBeGreaterThan(oldCount);
66
- }).toPass({ timeout });
67
-
68
- return true;
69
- } catch (e) {
70
- return false;
71
- }
72
- };
73
- },
74
-
75
- /**
76
- * Strategy: Scrolls to the bottom of the table and waits for more rows to appear.
77
- * Best for: Infinite Scroll grids (Ag-Grid, Virtual Lists)
78
- * * @param timeout - Wait timeout (default: 5000ms)
79
- */
80
- infiniteScroll: (timeout = 5000): PaginationStrategy => {
81
- return async ({ root, config, resolve, page }: TableContext) => {
82
- const rows = resolve(config.rowSelector, root);
83
- const oldCount = await rows.count();
84
-
85
- if (oldCount === 0) return false;
86
-
87
- // 1. Scroll the very last row into view to trigger the fetch
88
- await rows.last().scrollIntoViewIfNeeded();
89
-
90
- // Optional: Press "End" to force scroll on stubborn grids (like AG Grid)
91
- await page.keyboard.press('End');
92
-
93
- // 2. Smart Wait: Wait for row count to increase
94
- try {
95
- await expect(async () => {
96
- const newCount = await rows.count();
97
- expect(newCount).toBeGreaterThan(oldCount);
98
- }).toPass({ timeout });
99
-
100
- return true;
101
- } catch (e) {
102
- return false;
103
- }
104
- };
105
- }
106
- };
package/src/types.ts DELETED
@@ -1,25 +0,0 @@
1
- // src/types.ts
2
- import { Locator, Page } from '@playwright/test';
3
-
4
- /**
5
- * A selector can be a CSS string or a function.
6
- * We allow 'parent' to be Locator OR Page to match your working logic.
7
- */
8
- export type Selector = string | ((parent: Locator | Page) => Locator);
9
-
10
- export interface TableConfig {
11
- rowSelector?: Selector;
12
- headerSelector?: Selector;
13
- cellSelector?: Selector;
14
- pagination?: PaginationStrategy;
15
- maxPages?: number;
16
- }
17
-
18
- export interface TableContext {
19
- root: Locator;
20
- config: Required<TableConfig>;
21
- page: Page;
22
- resolve: (item: Selector, parent: Locator | Page) => Locator;
23
- }
24
-
25
- export type PaginationStrategy = (context: TableContext) => Promise<boolean>;
package/src/useTable.ts DELETED
@@ -1,237 +0,0 @@
1
- // src/useTable.ts
2
- import { Locator, Page, expect } from '@playwright/test';
3
- import { TableConfig, TableContext, Selector } from './types';
4
-
5
- export const useTable = (rootLocator: Locator, configOptions: TableConfig = {}) => {
6
- const config: Required<TableConfig> = {
7
- rowSelector: "tbody tr",
8
- headerSelector: "th",
9
- cellSelector: "td",
10
- pagination: undefined as any,
11
- maxPages: 1,
12
- ...configOptions,
13
- };
14
-
15
- // ✅ UPDATE: Accept Locator OR Page (to match your work logic)
16
- const resolve = (item: Selector, parent: Locator | Page): Locator => {
17
- if (typeof item === 'string') return parent.locator(item);
18
- if (typeof item === 'function') return item(parent);
19
- return item;
20
- };
21
-
22
- let _headerMap: Map<string, number> | null = null;
23
-
24
- const _getMap = async () => {
25
- if (_headerMap) return _headerMap;
26
- // Headers are still resolved relative to the table root (safer)
27
- const headerLoc = resolve(config.headerSelector, rootLocator);
28
- try {
29
- await headerLoc.first().waitFor({ state: 'visible', timeout: 3000 });
30
- } catch (e) { /* Ignore hydration */ }
31
-
32
- const texts = await headerLoc.allInnerTexts();
33
- _headerMap = new Map(texts.map((t, i) => [t.trim() || `__col_${i}`, i]));
34
- return _headerMap;
35
- };
36
-
37
- const _findRowLocator = async (filters: Record<string, string | RegExp | number>, options: { exact?: boolean, maxPages?: number } = {}) => {
38
- const map = await _getMap();
39
- const page = rootLocator.page();
40
- const effectiveMaxPages = options.maxPages ?? config.maxPages;
41
- let currentPage = 1;
42
-
43
- while (true) {
44
- // 1. Row Locator uses ROOT (Matches your snippet)
45
- let rowLocator = resolve(config.rowSelector, rootLocator);
46
-
47
- for (const [colName, value] of Object.entries(filters)) {
48
- const colIndex = map.get(colName);
49
- if (colIndex === undefined) throw new Error(`Column '${colName}' not found.`);
50
-
51
- const exact = options.exact || false;
52
- const filterVal = typeof value === 'number' ? String(value) : value;
53
-
54
- // ✅ MATCHING YOUR WORK LOGIC EXACTLY
55
- // 2. Cell Template uses PAGE (Matches your snippet)
56
- const cellTemplate = resolve(config.cellSelector, page);
57
-
58
- // 3. Filter using .nth(colIndex)
59
- rowLocator = rowLocator.filter({
60
- has: cellTemplate.nth(colIndex).getByText(filterVal, { exact }),
61
- });
62
- }
63
-
64
- const count = await rowLocator.count();
65
- if (count > 1) {
66
- throw new Error(`Strict Mode Violation: Found ${count} rows matching ${JSON.stringify(filters)}.`);
67
- }
68
-
69
- if (count === 1) return rowLocator.first();
70
-
71
- // --- PAGINATION LOGIC ---
72
- if (config.pagination && currentPage < effectiveMaxPages) {
73
- const context: TableContext = {
74
- root: rootLocator,
75
- config: config,
76
- page: page,
77
- resolve: resolve
78
- };
79
-
80
- const didLoadMore = await config.pagination(context);
81
- if (didLoadMore) {
82
- currentPage++;
83
- continue;
84
- }
85
- }
86
- return null;
87
- }
88
- };
89
-
90
- return {
91
- getHeaders: async () => Array.from((await _getMap()).keys()),
92
-
93
- getByRow: async (filters: Record<string, string | RegExp | number>, options: { exact?: boolean, maxPages?: number } = {}) => {
94
- const row = await _findRowLocator(filters, options);
95
- if (!row) return resolve(config.rowSelector, rootLocator).filter({ hasText: "NON_EXISTENT_ROW_SENTINEL_" + Date.now() });
96
- return row;
97
- },
98
-
99
- getByCell: async (rowFilters: Record<string, string | RegExp | number>, targetColumn: string) => {
100
- const row = await _findRowLocator(rowFilters);
101
- if (!row) throw new Error(`Row not found: ${JSON.stringify(rowFilters)}`);
102
-
103
- const map = await _getMap();
104
- const colIndex = map.get(targetColumn);
105
- if (colIndex === undefined) throw new Error(`Column '${targetColumn}' not found.`);
106
-
107
- // Return the specific cell
108
- // We scope this to the found ROW to ensure we get the right cell
109
- if (typeof config.cellSelector === 'string') {
110
- return row.locator(config.cellSelector).nth(colIndex);
111
- } else {
112
- return resolve(config.cellSelector, row).nth(colIndex);
113
- }
114
- },
115
-
116
- getRows: async () => {
117
- const map = await _getMap();
118
- const rowLocator = resolve(config.rowSelector, rootLocator);
119
- const rowCount = await rowLocator.count();
120
- const results: Record<string, string>[] = [];
121
-
122
- for (let i = 0; i < rowCount; i++) {
123
- const row = rowLocator.nth(i);
124
- let cells: Locator;
125
- if (typeof config.cellSelector === 'string') {
126
- cells = row.locator(config.cellSelector);
127
- } else {
128
- cells = resolve(config.cellSelector, row);
129
- }
130
- const cellTexts = await cells.allInnerTexts();
131
- const rowData: Record<string, string> = {};
132
- for (const [colName, colIdx] of map.entries()) {
133
- rowData[colName] = (cellTexts[colIdx] || "").trim();
134
- }
135
- results.push(rowData);
136
- }
137
- return results;
138
- },
139
-
140
- getRowAsJSON: async (filters: Record<string, string | RegExp | number>) => {
141
- const row = await _findRowLocator(filters);
142
- if (!row) throw new Error(`Row not found: ${JSON.stringify(filters)}`);
143
-
144
- let cells: Locator;
145
- if (typeof config.cellSelector === 'string') {
146
- cells = row.locator(config.cellSelector);
147
- } else {
148
- cells = resolve(config.cellSelector, row);
149
- }
150
- const cellTexts = await cells.allInnerTexts();
151
- const map = await _getMap();
152
- const result: Record<string, string> = {};
153
- for (const [colName, colIndex] of map.entries()) {
154
- result[colName] = (cellTexts[colIndex] || "").trim();
155
- }
156
- return result;
157
- },
158
-
159
- /**
160
- * 🛠️ DEV TOOL: Prints a prompt to the console.
161
- * Copy the output and paste it into Gemini/ChatGPT to generate your config.
162
- */
163
- generateConfigPrompt: async () => {
164
- const html = await rootLocator.evaluate((el) => el.outerHTML);
165
- const separator = "=".repeat(50);
166
- const prompt = `
167
- ${separator}
168
- 🤖 COPY THE TEXT BELOW INTO GEMINI/ChatGPT 🤖
169
- ${separator}
170
-
171
- I am using a Playwright helper factory called 'useTable'.
172
- I need you to generate the configuration object based on the HTML structure below.
173
-
174
- Here is the table HTML:
175
- \`\`\`html
176
- ${html}
177
- \`\`\`
178
-
179
- Based on this HTML, generate the configuration object matching this signature:
180
- const table = useTable(page.locator('...'), {
181
- // Find the rows (exclude headers and empty spacer rows if possible)
182
- rowSelector: "...", // OR (root) => root.locator(...)
183
-
184
- // Find the column headers
185
- headerSelector: "...", // OR (root) => root.locator(...)
186
-
187
- // Find the cell (relative to a specific row)
188
- cellSelector: "...", // OR (row) => row.locator(...)
189
-
190
- // Find the "Next Page" button (if it exists in the HTML)
191
- paginationNextSelector: (root) => root.locator(...)
192
- });
193
-
194
- **Requirements:**
195
- 1. Prefer \`getByRole\` or \`getByTestId\` over CSS classes where possible.
196
- 2. If the table uses \`div\` structures (like React Table), ensure the \`rowSelector\` does not accidentally select the header row.
197
- 3. If there are "padding" or "loading" rows, use \`.filter()\` to exclude them.
198
-
199
- ${separator}
200
- `;
201
- console.log(prompt);
202
- }
203
-
204
- };
205
-
206
- /**
207
- * 🛠️ DEV TOOL: Prints a prompt to help write a custom Pagination Strategy.
208
- * It snapshots the HTML *surrounding* the table to find buttons/scroll containers.
209
- */
210
- generateStrategyPrompt: async () => {
211
- // 1. Get the parent container (often holds the pagination controls)
212
- const container = rootLocator.locator('xpath=..');
213
- const html = await container.evaluate((el) => el.outerHTML);
214
-
215
- const prompt = `
216
- ==================================================
217
- 🤖 COPY INTO GEMINI/ChatGPT TO WRITE A STRATEGY 🤖
218
- ==================================================
219
-
220
- I am using 'playwright-smart-table'. I need a custom Pagination Strategy.
221
- The table is inside this container HTML:
222
-
223
- \`\`\`html
224
- ${html.substring(0, 5000)} ... (truncated)
225
- \`\`\`
226
-
227
- Write a strategy that implements this interface:
228
- type PaginationStrategy = (context: TableContext) => Promise<boolean>;
229
-
230
- Requirements:
231
- 1. Identify the "Next" button OR the scroll container.
232
- 2. Return 'true' if data loaded, 'false' if end of data.
233
- 3. Use context.resolve() to find elements.
234
- `;
235
- console.log(prompt);
236
- }
237
- };
package/tests/mui.spec.ts DELETED
@@ -1,53 +0,0 @@
1
- import { test, expect } from '@playwright/test';
2
- import { useTable } from '../src/useTable';
3
- import { TableStrategies } from '../src/strategies';
4
-
5
- test('Material UI Data Grid Interaction', async ({ page }) => {
6
- // 1. Navigate to the MUI DataGrid demo
7
- await page.goto('https://mui.com/material-ui/react-table/');
8
-
9
- // The demo grid is usually slightly down the page
10
- const tableLocator = page.locator('.MuiDataGrid-root').first();
11
- await tableLocator.scrollIntoViewIfNeeded();
12
-
13
- // 2. Initialize Smart Table
14
- const table = useTable(tableLocator, {
15
- rowSelector: '.MuiDataGrid-row',
16
- headerSelector: '.MuiDataGrid-columnHeader',
17
- cellSelector: '.MuiDataGrid-cell', // MUI uses distinct divs for cells
18
- pagination: TableStrategies.clickNext(
19
- (root) => root.getByRole("button", { name: "Go to next page" })
20
- ),
21
- maxPages: 5
22
- });
23
-
24
- // Debug: See what columns were detected
25
- console.log('Headers Detected:', await table.getHeaders());
26
-
27
- // 3. Find a row (Melisandre is usually in the standard demo dataset)
28
- // Note: We use "Last name" because that matches the visible header text
29
- const row = await table.getByRow({ "Last name": "Melisandre" });
30
- await expect(row).toBeVisible();
31
- console.log('✅ Found Melisandre');
32
-
33
- // 4. Check specific cell data
34
- // "Age" column for Melisandre should be "150"
35
- const ageCell = await table.getByCell({ "Last name": "Melisandre" }, "Age");
36
- await expect(ageCell).toHaveText("150");
37
- console.log('✅ Verified Age is 150');
38
-
39
- // 5. Dump Data
40
- const userData = await table.getRowAsJSON({ "Last name": "Melisandre" });
41
- console.log('User Data JSON:', userData);
42
-
43
- // This works because we added getHeaders() back to the helper
44
- const headers = await table.getHeaders();
45
- console.log(headers);
46
-
47
- // 4. Change: Interact with the Checkbox
48
- // Logic: Find the cell in the first column (__col_0) for the row with Age: 150
49
- // Then click the input/label inside that cell.
50
- await (await table.getByCell({ Age: "150" }, "__col_0"))
51
- .getByLabel("Select row")
52
- .click();
53
- });
@@ -1,45 +0,0 @@
1
- import { test, expect } from '@playwright/test';
2
- import { useTable } from '../src/useTable';
3
-
4
- test.describe('README.md Examples Verification', () => {
5
-
6
- test('getRows() returns array of objects (Matches README)', async ({ page }) => {
7
- // 1. Arrange: Go to a standard table with a "Name" column
8
- await page.goto('https://datatables.net/examples/data_sources/dom');
9
- const table = useTable(page.locator('#example'), {
10
- headerSelector: 'thead th'
11
- });
12
-
13
- // 2. Act: Get all rows as JSON objects
14
- const rows = await table.getRows();
15
-
16
- // 3. Assert: Verify the structure matches the README example
17
- console.log('First Row:', rows[0]);
18
-
19
- // Datatables.net default sort order:
20
- // Row 0: Airi Satou
21
- // Row 1: Angelica Ramos
22
- expect(rows[0]['Name']).toBe('Airi Satou');
23
- expect(rows[1]['Name']).toBe('Angelica Ramos');
24
-
25
- // Verify other columns to ensure mapping is correct
26
- expect(rows[0]['Position']).toBe('Accountant');
27
- expect(rows[0]['Office']).toBe('Tokyo');
28
- });
29
-
30
- test('getRowAsJSON() returns single object', async ({ page }) => {
31
- await page.goto('https://datatables.net/examples/data_sources/dom');
32
- const table = useTable(page.locator('#example'), {
33
- headerSelector: 'thead th'
34
- });
35
-
36
- // Act
37
- const data = await table.getRowAsJSON({ Name: 'Ashton Cox' });
38
-
39
- // Assert
40
- expect(data['Name']).toBe('Ashton Cox');
41
- expect(data['Position']).toBe('Junior Technical Author');
42
- expect(data['Salary']).toContain('$86,000');
43
- });
44
-
45
- });
@@ -1,69 +0,0 @@
1
- // tests/strategies.spec.ts
2
- import { test, expect } from '@playwright/test';
3
- import { useTable } from '../src/useTable';
4
- import { TableStrategies } from '../src/strategies';
5
-
6
- test.describe('Real World Strategy Tests', () => {
7
-
8
- test('Strategy: Click Next (Datatables.net)', async ({ page }) => {
9
- await page.goto('https://datatables.net/examples/data_sources/dom');
10
-
11
- const tableLoc = page.locator('#example');
12
-
13
- const table = useTable(tableLoc, {
14
- rowSelector: 'tbody tr',
15
- headerSelector: 'thead th',
16
- cellSelector: 'td',
17
- // Strategy: Standard "Next" Button
18
- pagination: TableStrategies.clickNext('#example_next'),
19
- maxPages: 3
20
- });
21
-
22
- // Verify Page 1
23
- await expect(await table.getByRow({ Name: "Airi Satou" })).toBeVisible();
24
-
25
- // Verify Page 2 (Triggers Click Next)
26
- // "Bradley Greer" is usually on Page 2
27
- console.log("🔎 Searching for Bradley Greer (Page 2)...");
28
- await expect(await table.getByRow({ Name: "Bradley Greer" })).toBeVisible();
29
- console.log("✅ Found row on Page 2!");
30
- });
31
-
32
- test('Strategy: Infinite Scroll (HTMX Example)', async ({ page }) => {
33
- await page.goto('https://htmx.org/examples/infinite-scroll/');
34
-
35
- const tableLoc = page.locator('table');
36
-
37
- const table = useTable(tableLoc, {
38
- rowSelector: 'tbody tr',
39
- headerSelector: 'thead th',
40
- cellSelector: 'td',
41
- // Strategy: Infinite Scroll (Scroll to bottom -> Wait for row count to increase)
42
- pagination: TableStrategies.infiniteScroll(),
43
- maxPages: 5
44
- });
45
-
46
- // 1. Get initial count
47
- const initialRows = await table.getRows();
48
- console.log(`Initial Row Count: ${initialRows.length}`);
49
-
50
- // 2. Trigger Scroll by searching for a row that doesn't exist yet
51
- // HTMX demo generates random IDs. We'll simulate a deep search by asking for
52
- // the 40th row (since it loads ~20 at a time).
53
-
54
- // HACK: Since IDs are random, we simply check that the library *attempts* to scroll.
55
- // We expect it to eventually fail finding "NonExistent", but verify it tried 5 pages.
56
- console.log("🔎 Triggering Scroll...");
57
- const missing = await table.getByRow({ "ID": "NonExistentID" });
58
-
59
- // It should have tried 5 times (scrolling each time)
60
- await expect(missing).not.toBeVisible();
61
-
62
- const finalRows = await table.getRows();
63
- console.log(`Final Row Count: ${finalRows.length}`);
64
-
65
- expect(finalRows.length).toBeGreaterThan(initialRows.length);
66
- console.log("✅ Infinite Scroll successfully loaded more rows!");
67
- });
68
-
69
- });
@@ -1,37 +0,0 @@
1
- import { test, expect } from '@playwright/test';
2
- import { useTable } from '../src/useTable';
3
-
4
- test('The Internet Herokuapp - Standard Table', async ({ page }) => {
5
- //--------------------------------
6
- // Arrange:
7
- //--------------------------------
8
- await page.goto("https://the-internet.herokuapp.com/tables");
9
-
10
- // 1. Setup the helper
11
- // We select the second table (.nth(1)) just like your snippet
12
- const tableLocator = page.getByRole("table").nth(1);
13
-
14
- // No config needed! Defaults (tbody tr, th, td) work perfectly here.
15
- const table = useTable(tableLocator);
16
-
17
- //--------------------------------
18
- // Act & Assert:
19
- //--------------------------------
20
-
21
- // OPTION A: Check a specific value (Cleaner)
22
- // This verifies that the row with Last Name "Doe" has the Email "jdoe@hotmail.com"
23
- await expect(
24
- await table.getByCell({ "Last Name": "Doe" }, "Email")
25
- ).toHaveText("jdoe@hotmail.com");
26
-
27
- // OPTION B: Interact with the whole row
28
- // getByRow returns the standard Locator for that specific TR
29
- const row = await table.getByRow({ "Last Name": "Doe" });
30
-
31
- // Example: Verify visibility
32
- await expect(row).toBeVisible();
33
-
34
- // Example: Verify the 'edit' link is inside this specific row
35
- // (We use toBeVisible instead of click just to keep the test safe/repeatable)
36
- await expect(row.getByRole('link', { name: 'edit' })).toBeVisible();
37
- });
package/tsconfig.json DELETED
@@ -1,14 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "es2016",
4
- "module": "commonjs",
5
- "declaration": true, /* Generates .d.ts files so users get Intellisense */
6
- "outDir": "./dist", /* Where the compiled JS goes */
7
- "strict": true, /* Enable strict type checking */
8
- "esModuleInterop": true,
9
- "skipLibCheck": true,
10
- "forceConsistentCasingInFileNames": true
11
- },
12
- "include": ["src/**/*"],
13
- "exclude": ["node_modules", "**/*.spec.ts"]
14
- }