@rickcedwhat/playwright-smart-table 6.7.0 → 6.7.3

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/dist/useTable.js CHANGED
@@ -33,6 +33,7 @@ const tableMapper_1 = require("./engine/tableMapper");
33
33
  const rowFinder_1 = require("./engine/rowFinder");
34
34
  const debugUtils_1 = require("./utils/debugUtils");
35
35
  const smartRowArray_1 = require("./utils/smartRowArray");
36
+ const elementTracker_1 = require("./utils/elementTracker");
36
37
  /**
37
38
  * Main hook to interact with a table.
38
39
  */
@@ -121,9 +122,24 @@ const useTable = (rootLocator, configOptions = {}) => {
121
122
  console.log(`⚠️ Throwing error to display [${promptName}] cleanly...`);
122
123
  throw new Error(finalPrompt);
123
124
  });
124
- const _ensureInitialized = () => __awaiter(void 0, void 0, void 0, function* () {
125
+ const _autoInit = () => __awaiter(void 0, void 0, void 0, function* () {
125
126
  yield tableMapper.getMap();
126
127
  });
128
+ const _advancePage = () => __awaiter(void 0, void 0, void 0, function* () {
129
+ var _a;
130
+ const context = { root: rootLocator, config, page: rootLocator.page(), resolve, getHeaderCell: result.getHeaderCell, getHeaders: result.getHeaders, scrollToColumn: result.scrollToColumn };
131
+ let advanced;
132
+ if (typeof config.strategies.pagination === 'function') {
133
+ advanced = !!(yield config.strategies.pagination(context));
134
+ }
135
+ else {
136
+ advanced = !!(((_a = config.strategies.pagination) === null || _a === void 0 ? void 0 : _a.goNext) && (yield config.strategies.pagination.goNext(context)));
137
+ }
138
+ if (advanced) {
139
+ tableState.currentPageIndex++;
140
+ }
141
+ return advanced;
142
+ });
127
143
  const result = {
128
144
  get currentPageIndex() { return tableState.currentPageIndex; },
129
145
  set currentPageIndex(v) { tableState.currentPageIndex = v; },
@@ -160,7 +176,7 @@ const useTable = (rootLocator, configOptions = {}) => {
160
176
  reset: () => __awaiter(void 0, void 0, void 0, function* () {
161
177
  var _a;
162
178
  log("Resetting table...");
163
- const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
179
+ const context = { root: rootLocator, config, page: rootLocator.page(), resolve, getHeaderCell: result.getHeaderCell };
164
180
  yield config.onReset(context);
165
181
  if (typeof config.strategies.pagination !== 'function' && ((_a = config.strategies.pagination) === null || _a === void 0 ? void 0 : _a.goToFirst)) {
166
182
  log("Auto-navigating to first page...");
@@ -172,7 +188,8 @@ const useTable = (rootLocator, configOptions = {}) => {
172
188
  _hasPaginated = false;
173
189
  tableState.currentPageIndex = 0;
174
190
  tableMapper.clear();
175
- log("Table reset complete.");
191
+ log("Table reset complete. Calling autoInit to restore state.");
192
+ yield _autoInit();
176
193
  }),
177
194
  revalidate: () => __awaiter(void 0, void 0, void 0, function* () {
178
195
  log("Revalidating table structure...");
@@ -182,16 +199,16 @@ const useTable = (rootLocator, configOptions = {}) => {
182
199
  getRow: (filters, options = { exact: false }) => {
183
200
  const map = tableMapper.getMapSync();
184
201
  if (!map)
185
- throw new Error('Table not initialized. Call await table.init() first, or use async methods like table.findRow() or table.getRows() which auto-initialize.');
202
+ throw new Error('Table not initialized. Call await table.init() first, or use async methods like table.findRow() or table.findRows() which auto-initialize.');
186
203
  const allRows = resolve(config.rowSelector, rootLocator);
187
204
  const matchedRows = filterEngine.applyFilters(allRows, filters, map, options.exact || false, rootLocator.page());
188
205
  const rowLocator = matchedRows.first();
189
206
  return _makeSmart(rowLocator, map, 0); // fallback index 0
190
207
  },
191
- getRowByIndex: (index, options = {}) => {
208
+ getRowByIndex: (index) => {
192
209
  const map = tableMapper.getMapSync();
193
210
  if (!map)
194
- throw new Error('Table not initialized. Call await table.init() first, or use async methods like table.findRow() or table.getRows() which auto-initialize.');
211
+ throw new Error('Table not initialized. Call await table.init() first, or use async methods like table.findRow() or table.findRows() which auto-initialize.');
195
212
  const rowLocator = resolve(config.rowSelector, rootLocator).nth(index);
196
213
  return _makeSmart(rowLocator, map, index);
197
214
  },
@@ -208,7 +225,7 @@ const useTable = (rootLocator, configOptions = {}) => {
208
225
  sorting: {
209
226
  apply: (columnName, direction) => __awaiter(void 0, void 0, void 0, function* () {
210
227
  var _a;
211
- yield _ensureInitialized();
228
+ yield _autoInit();
212
229
  if (!config.strategies.sorting)
213
230
  throw new Error('No sorting strategy has been configured.');
214
231
  log(`Applying sort for column "${columnName}" (${direction})`);
@@ -237,7 +254,7 @@ const useTable = (rootLocator, configOptions = {}) => {
237
254
  throw new Error(`Failed to sort column "${columnName}" to "${direction}" after ${maxRetries} attempts.`);
238
255
  }),
239
256
  getState: (columnName) => __awaiter(void 0, void 0, void 0, function* () {
240
- yield _ensureInitialized();
257
+ yield _autoInit();
241
258
  if (!config.strategies.sorting)
242
259
  throw new Error('No sorting strategy has been configured.');
243
260
  const context = { root: rootLocator, config, page: rootLocator.page(), resolve, getHeaderCell: result.getHeaderCell };
@@ -247,216 +264,211 @@ const useTable = (rootLocator, configOptions = {}) => {
247
264
  // ─── Shared async row iterator ───────────────────────────────────────────
248
265
  [Symbol.asyncIterator]() {
249
266
  return __asyncGenerator(this, arguments, function* _a() {
250
- var _b;
251
- yield __await(_ensureInitialized());
267
+ yield __await(_autoInit());
252
268
  const map = tableMapper.getMapSync();
253
269
  const effectiveMaxPages = config.maxPages;
254
- let rowIndex = 0;
255
- let pagesScanned = 1;
256
- while (true) {
257
- const pageRows = yield __await(resolve(config.rowSelector, rootLocator).all());
258
- for (const rowLocator of pageRows) {
259
- yield yield __await({ row: _makeSmart(rowLocator, map, rowIndex), rowIndex });
260
- rowIndex++;
261
- }
262
- if (pagesScanned >= effectiveMaxPages)
263
- break;
264
- const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
265
- let advanced;
266
- if (typeof config.strategies.pagination === 'function') {
267
- advanced = !!(yield __await(config.strategies.pagination(context)));
268
- }
269
- else {
270
- advanced = !!(((_b = config.strategies.pagination) === null || _b === void 0 ? void 0 : _b.goNext) && (yield __await(config.strategies.pagination.goNext(context))));
270
+ const tracker = new elementTracker_1.ElementTracker('iterator');
271
+ try {
272
+ let rowIndex = 0;
273
+ let pagesScanned = 1;
274
+ while (true) {
275
+ const rowLocators = resolve(config.rowSelector, rootLocator);
276
+ const newIndices = yield __await(tracker.getUnseenIndices(rowLocators));
277
+ const pageRows = yield __await(rowLocators.all());
278
+ for (const idx of newIndices) {
279
+ yield yield __await({ row: _makeSmart(pageRows[idx], map, rowIndex), rowIndex });
280
+ rowIndex++;
281
+ }
282
+ if (pagesScanned >= effectiveMaxPages)
283
+ break;
284
+ if (!(yield __await(_advancePage())))
285
+ break;
286
+ pagesScanned++;
271
287
  }
272
- if (!advanced)
273
- break;
274
- tableState.currentPageIndex++;
275
- pagesScanned++;
288
+ }
289
+ finally {
290
+ yield __await(tracker.cleanup(rootLocator.page()));
276
291
  }
277
292
  });
278
293
  },
279
294
  // ─── Private row-iteration engine ────────────────────────────────────────
280
295
  forEach: (callback_1, ...args_1) => __awaiter(void 0, [callback_1, ...args_1], void 0, function* (callback, options = {}) {
281
- var _a, _b, _c, _d;
282
- yield _ensureInitialized();
296
+ var _a, _b, _c;
297
+ yield _autoInit();
283
298
  const map = tableMapper.getMapSync();
284
299
  const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : config.maxPages;
285
300
  const dedupeStrategy = (_b = options.dedupe) !== null && _b !== void 0 ? _b : config.strategies.dedupe;
286
301
  const dedupeKeys = dedupeStrategy ? new Set() : null;
287
302
  const parallel = (_c = options.parallel) !== null && _c !== void 0 ? _c : false;
288
- let rowIndex = 0;
289
- let stopped = false;
290
- let pagesScanned = 1;
291
- const stop = () => { stopped = true; };
292
- while (!stopped) {
293
- const pageRows = yield resolve(config.rowSelector, rootLocator).all();
294
- const smartRows = pageRows.map((r, i) => _makeSmart(r, map, rowIndex + i));
295
- if (parallel) {
296
- yield Promise.all(smartRows.map((row) => __awaiter(void 0, void 0, void 0, function* () {
297
- if (stopped)
298
- return;
299
- if (dedupeKeys) {
300
- const key = yield dedupeStrategy(row);
301
- if (dedupeKeys.has(key))
303
+ const tracker = new elementTracker_1.ElementTracker('forEach');
304
+ try {
305
+ let rowIndex = 0;
306
+ let stopped = false;
307
+ let pagesScanned = 1;
308
+ const stop = () => { stopped = true; };
309
+ while (!stopped) {
310
+ const rowLocators = resolve(config.rowSelector, rootLocator);
311
+ const newIndices = yield tracker.getUnseenIndices(rowLocators);
312
+ const pageRows = yield rowLocators.all();
313
+ const smartRows = newIndices.map((idx, i) => _makeSmart(pageRows[idx], map, rowIndex + i));
314
+ if (parallel) {
315
+ yield Promise.all(smartRows.map((row) => __awaiter(void 0, void 0, void 0, function* () {
316
+ if (stopped)
302
317
  return;
303
- dedupeKeys.add(key);
304
- }
305
- yield callback({ row, rowIndex: row.rowIndex, stop });
306
- })));
307
- }
308
- else {
309
- for (const row of smartRows) {
310
- if (stopped)
311
- break;
312
- if (dedupeKeys) {
313
- const key = yield dedupeStrategy(row);
314
- if (dedupeKeys.has(key))
315
- continue;
316
- dedupeKeys.add(key);
318
+ if (dedupeKeys) {
319
+ const key = yield dedupeStrategy(row);
320
+ if (dedupeKeys.has(key))
321
+ return;
322
+ dedupeKeys.add(key);
323
+ }
324
+ yield callback({ row, rowIndex: row.rowIndex, stop });
325
+ })));
326
+ }
327
+ else {
328
+ for (const row of smartRows) {
329
+ if (stopped)
330
+ break;
331
+ if (dedupeKeys) {
332
+ const key = yield dedupeStrategy(row);
333
+ if (dedupeKeys.has(key))
334
+ continue;
335
+ dedupeKeys.add(key);
336
+ }
337
+ yield callback({ row, rowIndex: row.rowIndex, stop });
317
338
  }
318
- yield callback({ row, rowIndex: row.rowIndex, stop });
319
339
  }
340
+ rowIndex += smartRows.length;
341
+ if (stopped || pagesScanned >= effectiveMaxPages)
342
+ break;
343
+ if (!(yield _advancePage()))
344
+ break;
345
+ pagesScanned++;
320
346
  }
321
- rowIndex += smartRows.length;
322
- if (stopped || pagesScanned >= effectiveMaxPages)
323
- break;
324
- const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
325
- let advanced;
326
- if (typeof config.strategies.pagination === 'function') {
327
- advanced = !!(yield config.strategies.pagination(context));
328
- }
329
- else {
330
- advanced = !!(((_d = config.strategies.pagination) === null || _d === void 0 ? void 0 : _d.goNext) && (yield config.strategies.pagination.goNext(context)));
331
- }
332
- if (!advanced)
333
- break;
334
- tableState.currentPageIndex++;
335
- pagesScanned++;
347
+ }
348
+ finally {
349
+ yield tracker.cleanup(rootLocator.page());
336
350
  }
337
351
  }),
338
352
  map: (callback_1, ...args_1) => __awaiter(void 0, [callback_1, ...args_1], void 0, function* (callback, options = {}) {
339
- var _a, _b, _c, _d;
340
- yield _ensureInitialized();
353
+ var _a, _b, _c;
354
+ yield _autoInit();
341
355
  const map = tableMapper.getMapSync();
342
356
  const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : config.maxPages;
343
357
  const dedupeStrategy = (_b = options.dedupe) !== null && _b !== void 0 ? _b : config.strategies.dedupe;
344
358
  const dedupeKeys = dedupeStrategy ? new Set() : null;
345
359
  const parallel = (_c = options.parallel) !== null && _c !== void 0 ? _c : true;
360
+ const tracker = new elementTracker_1.ElementTracker('map');
346
361
  const results = [];
347
- let rowIndex = 0;
348
- let stopped = false;
349
- let pagesScanned = 1;
350
- const stop = () => { stopped = true; };
351
- while (!stopped) {
352
- const pageRows = yield resolve(config.rowSelector, rootLocator).all();
353
- const smartRows = pageRows.map((r, i) => _makeSmart(r, map, rowIndex + i));
354
- if (parallel) {
355
- const SKIP = Symbol('skip');
356
- const pageResults = yield Promise.all(smartRows.map((row) => __awaiter(void 0, void 0, void 0, function* () {
357
- if (dedupeKeys) {
358
- const key = yield dedupeStrategy(row);
359
- if (dedupeKeys.has(key))
360
- return SKIP;
361
- dedupeKeys.add(key);
362
+ try {
363
+ let rowIndex = 0;
364
+ let stopped = false;
365
+ let pagesScanned = 1;
366
+ const stop = () => { stopped = true; };
367
+ while (!stopped) {
368
+ const rowLocators = resolve(config.rowSelector, rootLocator);
369
+ const newIndices = yield tracker.getUnseenIndices(rowLocators);
370
+ const pageRows = yield rowLocators.all();
371
+ const smartRows = newIndices.map((idx, i) => _makeSmart(pageRows[idx], map, rowIndex + i));
372
+ if (parallel) {
373
+ const SKIP = Symbol('skip');
374
+ const pageResults = yield Promise.all(smartRows.map((row) => __awaiter(void 0, void 0, void 0, function* () {
375
+ if (dedupeKeys) {
376
+ const key = yield dedupeStrategy(row);
377
+ if (dedupeKeys.has(key))
378
+ return SKIP;
379
+ dedupeKeys.add(key);
380
+ }
381
+ return callback({ row, rowIndex: row.rowIndex, stop });
382
+ })));
383
+ for (const r of pageResults) {
384
+ if (r !== SKIP)
385
+ results.push(r);
362
386
  }
363
- return callback({ row, rowIndex: row.rowIndex, stop });
364
- })));
365
- for (const r of pageResults) {
366
- if (r !== SKIP)
367
- results.push(r);
368
387
  }
369
- }
370
- else {
371
- for (const row of smartRows) {
372
- if (stopped)
373
- break;
374
- if (dedupeKeys) {
375
- const key = yield dedupeStrategy(row);
376
- if (dedupeKeys.has(key))
377
- continue;
378
- dedupeKeys.add(key);
388
+ else {
389
+ for (const row of smartRows) {
390
+ if (stopped)
391
+ break;
392
+ if (dedupeKeys) {
393
+ const key = yield dedupeStrategy(row);
394
+ if (dedupeKeys.has(key))
395
+ continue;
396
+ dedupeKeys.add(key);
397
+ }
398
+ results.push(yield callback({ row, rowIndex: row.rowIndex, stop }));
379
399
  }
380
- results.push(yield callback({ row, rowIndex: row.rowIndex, stop }));
381
400
  }
401
+ rowIndex += smartRows.length;
402
+ if (stopped || pagesScanned >= effectiveMaxPages)
403
+ break;
404
+ if (!(yield _advancePage()))
405
+ break;
406
+ pagesScanned++;
382
407
  }
383
- rowIndex += smartRows.length;
384
- if (stopped || pagesScanned >= effectiveMaxPages)
385
- break;
386
- const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
387
- let advanced;
388
- if (typeof config.strategies.pagination === 'function') {
389
- advanced = !!(yield config.strategies.pagination(context));
390
- }
391
- else {
392
- advanced = !!(((_d = config.strategies.pagination) === null || _d === void 0 ? void 0 : _d.goNext) && (yield config.strategies.pagination.goNext(context)));
393
- }
394
- if (!advanced)
395
- break;
396
- tableState.currentPageIndex++;
397
- pagesScanned++;
408
+ }
409
+ finally {
410
+ yield tracker.cleanup(rootLocator.page());
398
411
  }
399
412
  return results;
400
413
  }),
401
414
  filter: (predicate_1, ...args_1) => __awaiter(void 0, [predicate_1, ...args_1], void 0, function* (predicate, options = {}) {
402
- var _a, _b, _c, _d;
403
- yield _ensureInitialized();
415
+ var _a, _b, _c;
416
+ yield _autoInit();
404
417
  const map = tableMapper.getMapSync();
405
418
  const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : config.maxPages;
406
419
  const dedupeStrategy = (_b = options.dedupe) !== null && _b !== void 0 ? _b : config.strategies.dedupe;
407
420
  const dedupeKeys = dedupeStrategy ? new Set() : null;
408
421
  const parallel = (_c = options.parallel) !== null && _c !== void 0 ? _c : false;
422
+ const tracker = new elementTracker_1.ElementTracker('filter');
409
423
  const matched = [];
410
- let rowIndex = 0;
411
- let stopped = false;
412
- let pagesScanned = 1;
413
- const stop = () => { stopped = true; };
414
- while (!stopped) {
415
- const pageRows = yield resolve(config.rowSelector, rootLocator).all();
416
- const smartRows = pageRows.map((r, i) => _makeSmart(r, map, rowIndex + i, pagesScanned - 1));
417
- if (parallel) {
418
- const flags = yield Promise.all(smartRows.map((row) => __awaiter(void 0, void 0, void 0, function* () {
419
- if (dedupeKeys) {
420
- const key = yield dedupeStrategy(row);
421
- if (dedupeKeys.has(key))
422
- return false;
423
- dedupeKeys.add(key);
424
- }
425
- return predicate({ row, rowIndex: row.rowIndex, stop });
426
- })));
427
- smartRows.forEach((row, i) => { if (flags[i])
428
- matched.push(row); });
429
- }
430
- else {
431
- for (const row of smartRows) {
432
- if (stopped)
433
- break;
434
- if (dedupeKeys) {
435
- const key = yield options.dedupe(row);
436
- if (dedupeKeys.has(key))
437
- continue;
438
- dedupeKeys.add(key);
439
- }
440
- if (yield predicate({ row, rowIndex: row.rowIndex, stop })) {
441
- matched.push(row);
424
+ try {
425
+ let rowIndex = 0;
426
+ let stopped = false;
427
+ let pagesScanned = 1;
428
+ const stop = () => { stopped = true; };
429
+ while (!stopped) {
430
+ const rowLocators = resolve(config.rowSelector, rootLocator);
431
+ const newIndices = yield tracker.getUnseenIndices(rowLocators);
432
+ const pageRows = yield rowLocators.all();
433
+ const smartRows = newIndices.map((idx, i) => _makeSmart(pageRows[idx], map, rowIndex + i, pagesScanned - 1));
434
+ if (parallel) {
435
+ const flags = yield Promise.all(smartRows.map((row) => __awaiter(void 0, void 0, void 0, function* () {
436
+ if (dedupeKeys) {
437
+ const key = yield dedupeStrategy(row);
438
+ if (dedupeKeys.has(key))
439
+ return false;
440
+ dedupeKeys.add(key);
441
+ }
442
+ return predicate({ row, rowIndex: row.rowIndex, stop });
443
+ })));
444
+ smartRows.forEach((row, i) => { if (flags[i])
445
+ matched.push(row); });
446
+ }
447
+ else {
448
+ for (const row of smartRows) {
449
+ if (stopped)
450
+ break;
451
+ if (dedupeKeys) {
452
+ const key = yield dedupeStrategy(row);
453
+ if (dedupeKeys.has(key))
454
+ continue;
455
+ dedupeKeys.add(key);
456
+ }
457
+ if (yield predicate({ row, rowIndex: row.rowIndex, stop })) {
458
+ matched.push(row);
459
+ }
442
460
  }
443
461
  }
462
+ rowIndex += smartRows.length;
463
+ if (stopped || pagesScanned >= effectiveMaxPages)
464
+ break;
465
+ if (!(yield _advancePage()))
466
+ break;
467
+ pagesScanned++;
444
468
  }
445
- rowIndex += smartRows.length;
446
- if (stopped || pagesScanned >= effectiveMaxPages)
447
- break;
448
- const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
449
- let advanced;
450
- if (typeof config.strategies.pagination === 'function') {
451
- advanced = !!(yield config.strategies.pagination(context));
452
- }
453
- else {
454
- advanced = !!(((_d = config.strategies.pagination) === null || _d === void 0 ? void 0 : _d.goNext) && (yield config.strategies.pagination.goNext(context)));
455
- }
456
- if (!advanced)
457
- break;
458
- tableState.currentPageIndex++;
459
- pagesScanned++;
469
+ }
470
+ finally {
471
+ yield tracker.cleanup(rootLocator.page());
460
472
  }
461
473
  return (0, smartRowArray_1.createSmartRowArray)(matched);
462
474
  }),
@@ -0,0 +1,15 @@
1
+ import { Locator } from '@playwright/test';
2
+ export declare class ElementTracker {
3
+ readonly id: string;
4
+ constructor(prefix?: string);
5
+ /**
6
+ * Finds the indices of newly seen elements in the browser, storing their text signature
7
+ * in a WeakMap. This gracefully handles both append-only DOMs (by identity) and
8
+ * virtualized DOMs (by text signature if nodes are recycled).
9
+ */
10
+ getUnseenIndices(locators: Locator): Promise<number[]>;
11
+ /**
12
+ * Cleans up the tracking map from the browser window object.
13
+ */
14
+ cleanup(page: any): Promise<void>;
15
+ }
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.ElementTracker = void 0;
13
+ class ElementTracker {
14
+ constructor(prefix = 'tracker') {
15
+ this.id = `__smartTable_${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
16
+ }
17
+ /**
18
+ * Finds the indices of newly seen elements in the browser, storing their text signature
19
+ * in a WeakMap. This gracefully handles both append-only DOMs (by identity) and
20
+ * virtualized DOMs (by text signature if nodes are recycled).
21
+ */
22
+ getUnseenIndices(locators) {
23
+ return __awaiter(this, void 0, void 0, function* () {
24
+ return yield locators.evaluateAll((elements, trackerId) => {
25
+ const win = window;
26
+ if (!win[trackerId]) {
27
+ win[trackerId] = new WeakMap();
28
+ }
29
+ const seenMap = win[trackerId];
30
+ const newIndices = [];
31
+ elements.forEach((el, index) => {
32
+ // Determine a lightweight signature for the row (textContent strips HTML, fast)
33
+ const signature = el.textContent || '';
34
+ // If it's a new element, OR a recycled element with new data
35
+ if (seenMap.get(el) !== signature) {
36
+ seenMap.set(el, signature);
37
+ newIndices.push(index);
38
+ }
39
+ });
40
+ return newIndices;
41
+ }, this.id);
42
+ });
43
+ }
44
+ /**
45
+ * Cleans up the tracking map from the browser window object.
46
+ */
47
+ cleanup(page) {
48
+ return __awaiter(this, void 0, void 0, function* () {
49
+ try {
50
+ yield page.evaluate((trackerId) => {
51
+ delete window[trackerId];
52
+ }, this.id);
53
+ }
54
+ catch (e) {
55
+ // Ignore context destroyed errors during cleanup
56
+ }
57
+ });
58
+ }
59
+ }
60
+ exports.ElementTracker = ElementTracker;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rickcedwhat/playwright-smart-table",
3
- "version": "6.7.0",
3
+ "version": "6.7.3",
4
4
  "description": "Smart, column-aware table interactions for Playwright",
5
5
  "author": "Cedrick Catalan",
6
6
  "license": "MIT",
@@ -23,11 +23,16 @@
23
23
  "docs:build": "vitepress build docs",
24
24
  "build": "npm run generate-types && npm run generate-config-types && npm run generate-docs && npm run generate-all-api-docs && npm run update-all-api-signatures && tsc",
25
25
  "prepublishOnly": "npm run build",
26
+ "clean-port": "lsof -ti:3000 | xargs kill -9 || true",
27
+ "pretest": "npm run clean-port",
28
+ "posttest": "npm run clean-port",
26
29
  "test": "npm run test:unit && npx playwright test",
27
30
  "test:unit": "vitest run --reporter=verbose --reporter=html",
28
31
  "test:unit:ui": "vitest --ui",
32
+ "pretest:e2e": "npm run clean-port",
33
+ "posttest:e2e": "npm run clean-port",
29
34
  "test:e2e": "npx playwright test",
30
- "test:compatibility": "npx playwright test compatibility",
35
+ "test:compatibility": "npm run clean-port && npx playwright test compatibility",
31
36
  "prepare": "husky install"
32
37
  },
33
38
  "keywords": [