@rickcedwhat/playwright-smart-table 2.0.7 → 2.0.9

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.
@@ -2,29 +2,14 @@ import { PaginationStrategy, Selector } from '../types';
2
2
  export declare const TableStrategies: {
3
3
  /**
4
4
  * Strategy: Clicks a "Next" button and waits for the first row of data to change.
5
- * Best for: Standard pagination (Page 1 > Page 2 > Page 3)
6
- * * @param nextButtonSelector - Selector for the next button (e.g. 'button.next' or getByRole('button', {name: 'Next'}))
7
- * @param timeout - How long to wait for the table to refresh (default: 5000ms)
8
5
  */
9
6
  clickNext: (nextButtonSelector: Selector, timeout?: number) => PaginationStrategy;
10
7
  /**
11
8
  * Strategy: Clicks a "Load More" button and waits for the row count to increase.
12
- * Best for: Lists where "Load More" appends data to the bottom.
13
- * * @param buttonSelector - Selector for the load more button
14
- * @param timeout - Wait timeout (default: 5000ms)
15
9
  */
16
10
  clickLoadMore: (buttonSelector: Selector, timeout?: number) => PaginationStrategy;
17
11
  /**
18
- * Strategy: Scrolls a specific container (or the window) to the bottom.
19
- * Best for: Infinite Scroll grids (Ag-Grid, Virtual Lists)
20
- * * @param options.timeout - Wait timeout (default: 5000ms)
21
- * @param options.scrollerSelector - (Optional) Selector for the scrollable container.
22
- * If omitted, tries to scroll the table root.
23
- * @param options.interval - (Optional) Polling interval in ms (default: 1000ms)
12
+ * Strategy: Scrolls to the bottom and waits for more rows to appear.
24
13
  */
25
- infiniteScroll: (options?: {
26
- timeout?: number;
27
- scrollerSelector?: Selector;
28
- interval?: number;
29
- }) => PaginationStrategy;
14
+ infiniteScroll: (timeout?: number) => PaginationStrategy;
30
15
  };
@@ -10,118 +10,88 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.TableStrategies = void 0;
13
- // src/strategies/index.ts
14
- const test_1 = require("@playwright/test");
13
+ /**
14
+ * Internal helper to wait for a condition to be met.
15
+ * Replaces the dependency on 'expect(...).toPass()' to ensure compatibility
16
+ * with environments like QA Wolf where 'expect' is not globally available.
17
+ */
18
+ const waitForCondition = (predicate, timeout, page // Context page for pauses
19
+ ) => __awaiter(void 0, void 0, void 0, function* () {
20
+ const startTime = Date.now();
21
+ while (Date.now() - startTime < timeout) {
22
+ if (yield predicate()) {
23
+ return true;
24
+ }
25
+ // Wait 100ms before next check (Standard Polling)
26
+ yield page.waitForTimeout(100).catch(() => new Promise(r => setTimeout(r, 100)));
27
+ }
28
+ return false;
29
+ });
15
30
  exports.TableStrategies = {
16
31
  /**
17
32
  * Strategy: Clicks a "Next" button and waits for the first row of data to change.
18
- * Best for: Standard pagination (Page 1 > Page 2 > Page 3)
19
- * * @param nextButtonSelector - Selector for the next button (e.g. 'button.next' or getByRole('button', {name: 'Next'}))
20
- * @param timeout - How long to wait for the table to refresh (default: 5000ms)
21
33
  */
22
34
  clickNext: (nextButtonSelector, timeout = 5000) => {
23
- return (_a) => __awaiter(void 0, [_a], void 0, function* ({ root, config, resolve }) {
24
- // 1. Find the button using the table's helper
35
+ return (_a) => __awaiter(void 0, [_a], void 0, function* ({ root, config, resolve, page }) {
25
36
  const nextBtn = resolve(nextButtonSelector, root).first();
26
- // If button isn't there or disabled, we are at the end
37
+ // Check if button exists/enabled before clicking
27
38
  if (!(yield nextBtn.isVisible()) || !(yield nextBtn.isEnabled())) {
28
39
  return false;
29
40
  }
30
- // 2. Snapshot the current state (text of the first row)
31
- // We use the table's OWN row selector to ensure we are looking at real data
41
+ // 1. Snapshot current state
32
42
  const firstRow = resolve(config.rowSelector, root).first();
33
- const oldText = yield firstRow.innerText().catch(() => ""); // Handle empty tables gracefully
34
- // 3. Click the button
43
+ const oldText = yield firstRow.innerText().catch(() => "");
44
+ // 2. Click
35
45
  yield nextBtn.click();
36
- // 4. Smart Wait: Wait for the first row to have DIFFERENT text
37
- try {
38
- yield (0, test_1.expect)(firstRow).not.toHaveText(oldText, { timeout });
39
- return true; // Success: Data changed
40
- }
41
- catch (e) {
42
- return false; // Failed: Timed out (probably end of data or broken button)
43
- }
46
+ // 3. Smart Wait (Polling) - No 'expect' needed
47
+ return yield waitForCondition(() => __awaiter(void 0, void 0, void 0, function* () {
48
+ const newText = yield firstRow.innerText().catch(() => "");
49
+ return newText !== oldText;
50
+ }), timeout, page);
44
51
  });
45
52
  },
46
53
  /**
47
54
  * Strategy: Clicks a "Load More" button and waits for the row count to increase.
48
- * Best for: Lists where "Load More" appends data to the bottom.
49
- * * @param buttonSelector - Selector for the load more button
50
- * @param timeout - Wait timeout (default: 5000ms)
51
55
  */
52
56
  clickLoadMore: (buttonSelector, timeout = 5000) => {
53
- return (_a) => __awaiter(void 0, [_a], void 0, function* ({ root, config, resolve }) {
57
+ return (_a) => __awaiter(void 0, [_a], void 0, function* ({ root, config, resolve, page }) {
54
58
  const loadMoreBtn = resolve(buttonSelector, root).first();
55
59
  if (!(yield loadMoreBtn.isVisible()) || !(yield loadMoreBtn.isEnabled())) {
56
60
  return false;
57
61
  }
58
- // 1. Snapshot: Count current rows
62
+ // 1. Snapshot count
59
63
  const rows = resolve(config.rowSelector, root);
60
64
  const oldCount = yield rows.count();
61
65
  // 2. Click
62
66
  yield loadMoreBtn.click();
63
- // 3. Smart Wait: Wait for row count to be greater than before
64
- try {
65
- yield (0, test_1.expect)(() => __awaiter(void 0, void 0, void 0, function* () {
66
- const newCount = yield rows.count();
67
- (0, test_1.expect)(newCount).toBeGreaterThan(oldCount);
68
- })).toPass({ timeout });
69
- return true;
70
- }
71
- catch (e) {
72
- return false;
73
- }
67
+ // 3. Smart Wait (Polling)
68
+ return yield waitForCondition(() => __awaiter(void 0, void 0, void 0, function* () {
69
+ const newCount = yield rows.count();
70
+ return newCount > oldCount;
71
+ }), timeout, page);
74
72
  });
75
73
  },
76
74
  /**
77
- * Strategy: Scrolls a specific container (or the window) to the bottom.
78
- * Best for: Infinite Scroll grids (Ag-Grid, Virtual Lists)
79
- * * @param options.timeout - Wait timeout (default: 5000ms)
80
- * @param options.scrollerSelector - (Optional) Selector for the scrollable container.
81
- * If omitted, tries to scroll the table root.
82
- * @param options.interval - (Optional) Polling interval in ms (default: 1000ms)
75
+ * Strategy: Scrolls to the bottom and waits for more rows to appear.
83
76
  */
84
- infiniteScroll: (options) => {
85
- const { timeout = 5000, scrollerSelector, interval = 1000 } = options || {};
77
+ infiniteScroll: (timeout = 5000) => {
86
78
  return (_a) => __awaiter(void 0, [_a], void 0, function* ({ root, config, resolve, page }) {
87
79
  const rows = resolve(config.rowSelector, root);
88
80
  const oldCount = yield rows.count();
89
81
  if (oldCount === 0)
90
82
  return false;
91
- // Aggressive Scroll Logic:
92
- // We use expect.poll to RETRY the scroll action if the count hasn't increased.
93
- // This fixes flakiness where the first scroll might be missed by the intersection observer.
83
+ // 1. Trigger Scroll
84
+ yield rows.last().scrollIntoViewIfNeeded();
85
+ // Optional: Keyboard press for robust grid handling
94
86
  try {
95
- yield test_1.expect.poll(() => __awaiter(void 0, void 0, void 0, function* () {
96
- // 1. Determine target container
97
- // If user provided a specific scroller (e.g. the div WRAPPING the table), use it.
98
- // Otherwise, default to the root locator.
99
- const scroller = scrollerSelector
100
- ? resolve(scrollerSelector, root)
101
- : root;
102
- // 2. Perform the Scroll
103
- // Method A: DOM Manipulation (Fastest/Most Reliable for containers)
104
- // We set scrollTop to a huge number to force it to the bottom
105
- yield scroller.evaluate((el) => {
106
- el.scrollTop = el.scrollHeight;
107
- }).catch(() => { }); // Ignore if element doesn't support scrollTop (e.g. it's a window wrapper)
108
- // Method B: Playwright Native (Fallback)
109
- // Scroll the last row into view (good for Window scroll)
110
- yield rows.last().scrollIntoViewIfNeeded().catch(() => { });
111
- // Method C: Keyboard (Desperation)
112
- yield page.keyboard.press('End');
113
- // 3. Return count for assertion
114
- return rows.count();
115
- }), {
116
- timeout,
117
- // āœ… FIX: Use user-provided interval (default 1000ms)
118
- intervals: [interval]
119
- }).toBeGreaterThan(oldCount);
120
- return true;
121
- }
122
- catch (e) {
123
- return false;
87
+ yield page.keyboard.press('End');
124
88
  }
89
+ catch (e) { }
90
+ // 2. Smart Wait (Polling)
91
+ return yield waitForCondition(() => __awaiter(void 0, void 0, void 0, function* () {
92
+ const newCount = yield rows.count();
93
+ return newCount > oldCount;
94
+ }), timeout, page);
125
95
  });
126
96
  }
127
97
  };
@@ -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 * - 'report': Attaches to Playwright Report (Requires testInfo).\n */\n output?: 'console' | 'report' | 'error';\n includeTypes?: boolean;\n testInfo?: TestInfo;\n}\n\nexport interface TableConfig {\n rowSelector?: Selector;\n headerSelector?: Selector;\n cellSelector?: Selector;\n pagination?: PaginationStrategy;\n maxPages?: number;\n headerTransformer?: (text: string, index: number) => 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 headerTransformer?: (text: string, index: number) => 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";
@@ -28,11 +28,9 @@ export interface PromptOptions {
28
28
  * Output Strategy:
29
29
  * - 'error': Throws an error with the prompt (Best for Cloud/QA Wolf to get clean text).
30
30
  * - 'console': Standard console logs (Default).
31
- * - 'report': Attaches to Playwright Report (Requires testInfo).
32
31
  */
33
- output?: 'console' | 'report' | 'error';
32
+ output?: 'console' | 'error';
34
33
  includeTypes?: boolean;
35
- testInfo?: TestInfo;
36
34
  }
37
35
 
38
36
  export interface TableConfig {
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Locator, Page, TestInfo } from '@playwright/test';
1
+ import type { Locator, Page } from '@playwright/test';
2
2
  export type Selector = string | ((root: Locator | Page) => Locator);
3
3
  export type SmartRow = Locator & {
4
4
  getCell(column: string): Locator;
@@ -16,11 +16,9 @@ export interface PromptOptions {
16
16
  * Output Strategy:
17
17
  * - 'error': Throws an error with the prompt (Best for Cloud/QA Wolf to get clean text).
18
18
  * - 'console': Standard console logs (Default).
19
- * - 'report': Attaches to Playwright Report (Requires testInfo).
20
19
  */
21
- output?: 'console' | 'report' | 'error';
20
+ output?: 'console' | 'error';
22
21
  includeTypes?: boolean;
23
- testInfo?: TestInfo;
24
22
  }
25
23
  export interface TableConfig {
26
24
  rowSelector?: Selector;
@@ -1,3 +1,3 @@
1
- import { Locator } from '@playwright/test';
1
+ import type { Locator } from '@playwright/test';
2
2
  import { TableConfig, TableResult } from './types';
3
3
  export declare const useTable: (rootLocator: Locator, configOptions?: TableConfig) => TableResult;
package/dist/useTable.js CHANGED
@@ -10,7 +10,6 @@ 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
13
  const typeContext_1 = require("./typeContext");
15
14
  const useTable = (rootLocator, configOptions = {}) => {
16
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) => text, autoScroll: true }, configOptions);
@@ -116,7 +115,7 @@ const useTable = (rootLocator, configOptions = {}) => {
116
115
  }
117
116
  });
118
117
  const _handlePrompt = (promptName_1, content_1, ...args_1) => __awaiter(void 0, [promptName_1, content_1, ...args_1], void 0, function* (promptName, content, options = {}) {
119
- const { output = 'console', includeTypes = true, testInfo } = options;
118
+ const { output = 'console', includeTypes = true } = options;
120
119
  let finalPrompt = content;
121
120
  if (includeTypes) {
122
121
  finalPrompt += `\n\nšŸ‘‡ Useful TypeScript Definitions šŸ‘‡\n\`\`\`typescript\n${typeContext_1.TYPE_CONTEXT}\n\`\`\`\n`;
@@ -125,23 +124,6 @@ const useTable = (rootLocator, configOptions = {}) => {
125
124
  console.log(`āš ļø Throwing error to display [${promptName}] cleanly...`);
126
125
  throw new Error(finalPrompt);
127
126
  }
128
- if (output === 'report') {
129
- let activeInfo = testInfo;
130
- if (!activeInfo) {
131
- try {
132
- activeInfo = test_1.test.info();
133
- }
134
- catch (e) { }
135
- }
136
- if (activeInfo) {
137
- yield activeInfo.attach(promptName, { body: finalPrompt, contentType: 'text/markdown' });
138
- console.log(`āœ… [${promptName}] Attached to Playwright Report.`);
139
- return;
140
- }
141
- else {
142
- console.warn(`āš ļø [${promptName}] Cannot attach to report (Context unavailable). Logging to console instead.`);
143
- }
144
- }
145
127
  console.log(finalPrompt);
146
128
  });
147
129
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rickcedwhat/playwright-smart-table",
3
- "version": "2.0.7",
3
+ "version": "2.0.9",
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",
package/dist/presets.d.ts DELETED
@@ -1,18 +0,0 @@
1
- import { Locator } from '@playwright/test';
2
- import { TableConfig } from './types';
3
- /**
4
- * Preset for Key-Value Forms.
5
- * * Default Structure:
6
- * - Row: div.form-group
7
- * - Cell: Direct children (> *)
8
- * - Columns: ['Label', 'Input']
9
- */
10
- export declare const useForm: (rootLocator: Locator, options?: TableConfig) => import("./types").TableResult;
11
- /**
12
- * Preset for Navigation Menus.
13
- * * Default Structure:
14
- * - Row: li
15
- * - Cell: null (The row IS the cell)
16
- * - Columns: ['Item']
17
- */
18
- export declare const useMenu: (menuLocator: Locator, options?: TableConfig) => import("./types").TableResult;
package/dist/presets.js DELETED
@@ -1,30 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useMenu = exports.useForm = void 0;
4
- const useTable_1 = require("./useTable");
5
- /**
6
- * Preset for Key-Value Forms.
7
- * * Default Structure:
8
- * - Row: div.form-group
9
- * - Cell: Direct children (> *)
10
- * - Columns: ['Label', 'Input']
11
- */
12
- const useForm = (rootLocator, options = {}) => {
13
- return (0, useTable_1.useTable)(rootLocator, Object.assign({
14
- // Defaults:
15
- rowSelector: 'div.form-group', cellSelector: (row) => row.locator('> *'), headerSelector: null, columnNames: ['Label', 'Input'] }, options));
16
- };
17
- exports.useForm = useForm;
18
- /**
19
- * Preset for Navigation Menus.
20
- * * Default Structure:
21
- * - Row: li
22
- * - Cell: null (The row IS the cell)
23
- * - Columns: ['Item']
24
- */
25
- const useMenu = (menuLocator, options = {}) => {
26
- return (0, useTable_1.useTable)(menuLocator, Object.assign({
27
- // Defaults:
28
- rowSelector: 'li', cellSelector: null, headerSelector: null, columnNames: ['Item'] }, options));
29
- };
30
- exports.useMenu = useMenu;