@rickcedwhat/playwright-smart-table 4.0.0 → 5.1.0
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 +319 -102
- package/dist/smartRow.js +7 -7
- package/dist/strategies/columns.d.ts +0 -34
- package/dist/strategies/columns.js +1 -34
- package/dist/strategies/headers.d.ts +0 -16
- package/dist/strategies/headers.js +1 -113
- package/dist/strategies/index.d.ts +0 -28
- package/dist/strategies/index.js +0 -3
- package/dist/strategies/pagination.d.ts +0 -21
- package/dist/strategies/pagination.js +1 -23
- package/dist/strategies/validation.d.ts +22 -0
- package/dist/strategies/validation.js +54 -0
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +102 -26
- package/dist/types.d.ts +97 -26
- package/dist/useTable.d.ts +2 -9
- package/dist/useTable.js +149 -49
- package/dist/utils/stringUtils.d.ts +22 -0
- package/dist/utils/stringUtils.js +73 -0
- package/dist/utils/traceUtils.d.ts +11 -0
- package/dist/utils/traceUtils.js +47 -0
- package/package.json +2 -2
package/dist/useTable.js
CHANGED
|
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.Strategies = exports.ResolutionStrategies = exports.
|
|
12
|
+
exports.Strategies = exports.ResolutionStrategies = exports.CellNavigationStrategies = exports.HeaderStrategies = exports.FillStrategies = exports.SortingStrategies = exports.PaginationStrategies = exports.useTable = void 0;
|
|
13
13
|
const typeContext_1 = require("./typeContext");
|
|
14
14
|
const sorting_1 = require("./strategies/sorting");
|
|
15
15
|
const pagination_1 = require("./strategies/pagination");
|
|
@@ -19,13 +19,14 @@ const headers_1 = require("./strategies/headers");
|
|
|
19
19
|
Object.defineProperty(exports, "HeaderStrategies", { enumerable: true, get: function () { return headers_1.HeaderStrategies; } });
|
|
20
20
|
const columns_1 = require("./strategies/columns");
|
|
21
21
|
Object.defineProperty(exports, "CellNavigationStrategies", { enumerable: true, get: function () { return columns_1.CellNavigationStrategies; } });
|
|
22
|
-
Object.defineProperty(exports, "ColumnStrategies", { enumerable: true, get: function () { return columns_1.ColumnStrategies; } });
|
|
23
22
|
const smartRow_1 = require("./smartRow");
|
|
24
23
|
const filterEngine_1 = require("./filterEngine");
|
|
25
24
|
const resolution_1 = require("./strategies/resolution");
|
|
26
25
|
Object.defineProperty(exports, "ResolutionStrategies", { enumerable: true, get: function () { return resolution_1.ResolutionStrategies; } });
|
|
27
26
|
const strategies_1 = require("./strategies");
|
|
28
27
|
Object.defineProperty(exports, "Strategies", { enumerable: true, get: function () { return strategies_1.Strategies; } });
|
|
28
|
+
const validation_1 = require("./strategies/validation");
|
|
29
|
+
const traceUtils_1 = require("./utils/traceUtils");
|
|
29
30
|
/**
|
|
30
31
|
* Main hook to interact with a table.
|
|
31
32
|
*/
|
|
@@ -40,7 +41,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
40
41
|
cellNavigation: columns_1.CellNavigationStrategies.default,
|
|
41
42
|
pagination: () => __awaiter(void 0, void 0, void 0, function* () { return false; }),
|
|
42
43
|
};
|
|
43
|
-
const config = Object.assign(Object.assign({ rowSelector: "tbody tr", headerSelector: "th", cellSelector: "td", maxPages: 1, headerTransformer: ({ text, index, locator }) => text, autoScroll: true, debug: false, onReset: () => __awaiter(void 0, void 0, void 0, function* () { }) }, configOptions), { strategies: Object.assign(Object.assign({}, defaultStrategies), configOptions.strategies) });
|
|
44
|
+
const config = Object.assign(Object.assign({ rowSelector: "tbody tr", headerSelector: "thead th", cellSelector: "td", maxPages: 1, headerTransformer: ({ text, index, locator }) => text, autoScroll: true, debug: false, onReset: () => __awaiter(void 0, void 0, void 0, function* () { }) }, configOptions), { strategies: Object.assign(Object.assign({}, defaultStrategies), configOptions.strategies) });
|
|
44
45
|
const resolve = (item, parent) => {
|
|
45
46
|
if (typeof item === 'string')
|
|
46
47
|
return parent.locator(item);
|
|
@@ -162,7 +163,8 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
162
163
|
page: rootLocator.page(),
|
|
163
164
|
resolve: resolve
|
|
164
165
|
};
|
|
165
|
-
const
|
|
166
|
+
const paginationResult = yield config.strategies.pagination(context);
|
|
167
|
+
const didLoadMore = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
|
|
166
168
|
if (didLoadMore) {
|
|
167
169
|
_hasPaginated = true;
|
|
168
170
|
currentPage++;
|
|
@@ -224,6 +226,9 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
224
226
|
return result;
|
|
225
227
|
yield _getMap(options === null || options === void 0 ? void 0 : options.timeout);
|
|
226
228
|
_isInitialized = true;
|
|
229
|
+
if (_headerMap) {
|
|
230
|
+
yield (0, traceUtils_1.addTraceEvent)(rootLocator.page(), 'init', { headers: Array.from(_headerMap.keys()), columnCount: _headerMap.size });
|
|
231
|
+
}
|
|
227
232
|
return result;
|
|
228
233
|
}),
|
|
229
234
|
scrollToColumn: (columnName) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -242,7 +247,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
242
247
|
}),
|
|
243
248
|
getHeaders: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
244
249
|
if (!_isInitialized || !_headerMap)
|
|
245
|
-
throw new Error('Table not initialized. Call await table.init() first.');
|
|
250
|
+
throw new Error('Table not initialized. Call await table.init() first, or use async methods like table.findRow() or table.getRows() which auto-initialize.');
|
|
246
251
|
return Array.from(_headerMap.keys());
|
|
247
252
|
}),
|
|
248
253
|
getHeaderCell: (columnName) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -299,22 +304,22 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
299
304
|
}
|
|
300
305
|
return results;
|
|
301
306
|
}),
|
|
302
|
-
|
|
307
|
+
getRow: (filters, options = { exact: false }) => {
|
|
303
308
|
if (!_isInitialized || !_headerMap)
|
|
304
|
-
throw new Error('Table not initialized. Call await table.init() first.');
|
|
309
|
+
throw new Error('Table not initialized. Call await table.init() first, or use async methods like table.findRow() or table.getRows() which auto-initialize.');
|
|
305
310
|
const allRows = resolve(config.rowSelector, rootLocator);
|
|
306
311
|
const matchedRows = filterEngine.applyFilters(allRows, filters, _headerMap, options.exact || false, rootLocator.page());
|
|
307
312
|
const rowLocator = matchedRows.first();
|
|
308
313
|
return _makeSmart(rowLocator, _headerMap, 0); // fallback index 0
|
|
309
314
|
},
|
|
310
|
-
|
|
315
|
+
getRowByIndex: (index, options = {}) => {
|
|
311
316
|
if (!_isInitialized || !_headerMap)
|
|
312
|
-
throw new Error('Table not initialized. Call await table.init() first.');
|
|
317
|
+
throw new Error('Table not initialized. Call await table.init() first, or use async methods like table.findRow() or table.getRows() which auto-initialize.');
|
|
313
318
|
const rowIndex = index - 1; // Convert 1-based to 0-based
|
|
314
319
|
const rowLocator = resolve(config.rowSelector, rootLocator).nth(rowIndex);
|
|
315
320
|
return _makeSmart(rowLocator, _headerMap, rowIndex);
|
|
316
321
|
},
|
|
317
|
-
|
|
322
|
+
findRow: (filters, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
318
323
|
yield _ensureInitialized();
|
|
319
324
|
let row = yield _findRowLocator(filters, options);
|
|
320
325
|
if (!row) {
|
|
@@ -322,7 +327,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
322
327
|
}
|
|
323
328
|
return _makeSmart(row, _headerMap, 0);
|
|
324
329
|
}),
|
|
325
|
-
|
|
330
|
+
getRows: (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
326
331
|
yield _ensureInitialized();
|
|
327
332
|
let rowLocators = resolve(config.rowSelector, rootLocator);
|
|
328
333
|
if (options === null || options === void 0 ? void 0 : options.filter) {
|
|
@@ -335,22 +340,44 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
335
340
|
}
|
|
336
341
|
return smartRows;
|
|
337
342
|
}),
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
343
|
+
findRows: (filters, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
344
|
+
var _a, _b, _c, _d;
|
|
345
|
+
yield _ensureInitialized();
|
|
346
|
+
const allRows = [];
|
|
347
|
+
const effectiveMaxPages = (_b = (_a = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _a !== void 0 ? _a : config.maxPages) !== null && _b !== void 0 ? _b : Infinity;
|
|
348
|
+
let pageCount = 0;
|
|
349
|
+
// Collect rows from current page
|
|
350
|
+
let rowLocators = resolve(config.rowSelector, rootLocator);
|
|
351
|
+
rowLocators = filterEngine.applyFilters(rowLocators, filters, _headerMap, (_c = options === null || options === void 0 ? void 0 : options.exact) !== null && _c !== void 0 ? _c : false, rootLocator.page());
|
|
352
|
+
let rows = yield rowLocators.all();
|
|
353
|
+
allRows.push(...rows.map((loc, i) => _makeSmart(loc, _headerMap, i)));
|
|
354
|
+
// Paginate and collect more rows
|
|
355
|
+
while (pageCount < effectiveMaxPages && config.strategies.pagination) {
|
|
356
|
+
const paginationResult = yield config.strategies.pagination({
|
|
357
|
+
root: rootLocator,
|
|
358
|
+
config,
|
|
359
|
+
resolve,
|
|
360
|
+
page: rootLocator.page()
|
|
361
|
+
});
|
|
362
|
+
const didPaginate = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
|
|
363
|
+
if (!didPaginate)
|
|
364
|
+
break;
|
|
365
|
+
pageCount++;
|
|
366
|
+
_hasPaginated = true;
|
|
367
|
+
// Collect rows from new page
|
|
368
|
+
rowLocators = resolve(config.rowSelector, rootLocator);
|
|
369
|
+
rowLocators = filterEngine.applyFilters(rowLocators, filters, _headerMap, (_d = options === null || options === void 0 ? void 0 : options.exact) !== null && _d !== void 0 ? _d : false, rootLocator.page());
|
|
370
|
+
rows = yield rowLocators.all();
|
|
371
|
+
allRows.push(...rows.map((loc, i) => _makeSmart(loc, _headerMap, i)));
|
|
372
|
+
}
|
|
373
|
+
if (options === null || options === void 0 ? void 0 : options.asJSON) {
|
|
374
|
+
return Promise.all(allRows.map(r => r.toJSON()));
|
|
375
|
+
}
|
|
376
|
+
return allRows;
|
|
353
377
|
}),
|
|
378
|
+
isInitialized: () => {
|
|
379
|
+
return _isInitialized;
|
|
380
|
+
},
|
|
354
381
|
sorting: {
|
|
355
382
|
apply: (columnName, direction) => __awaiter(void 0, void 0, void 0, function* () {
|
|
356
383
|
yield _ensureInitialized();
|
|
@@ -381,12 +408,13 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
381
408
|
init: result.init,
|
|
382
409
|
getHeaders: result.getHeaders,
|
|
383
410
|
getHeaderCell: result.getHeaderCell,
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
411
|
+
getRow: result.getRow,
|
|
412
|
+
getRowByIndex: result.getRowByIndex,
|
|
413
|
+
findRow: result.findRow,
|
|
414
|
+
getRows: result.getRows,
|
|
415
|
+
findRows: result.findRows,
|
|
387
416
|
getColumnValues: result.getColumnValues,
|
|
388
|
-
|
|
389
|
-
generateStrategyPrompt: result.generateStrategyPrompt,
|
|
417
|
+
isInitialized: result.isInitialized,
|
|
390
418
|
sorting: result.sorting,
|
|
391
419
|
scrollToColumn: result.scrollToColumn,
|
|
392
420
|
revalidate: result.revalidate,
|
|
@@ -395,10 +423,14 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
395
423
|
const getIsLast = (_c = options === null || options === void 0 ? void 0 : options.getIsLast) !== null && _c !== void 0 ? _c : (() => false);
|
|
396
424
|
const allData = [];
|
|
397
425
|
const effectiveMaxIterations = (_d = options === null || options === void 0 ? void 0 : options.maxIterations) !== null && _d !== void 0 ? _d : config.maxPages;
|
|
426
|
+
const batchSize = options === null || options === void 0 ? void 0 : options.batchSize;
|
|
427
|
+
const isBatching = batchSize !== undefined && batchSize > 1;
|
|
398
428
|
let index = 0;
|
|
399
429
|
let paginationResult = true;
|
|
400
430
|
let seenKeys = null;
|
|
401
|
-
|
|
431
|
+
let batchRows = [];
|
|
432
|
+
let batchStartIndex = 0;
|
|
433
|
+
logDebug(`Starting iterateThroughTable (maxIterations: ${effectiveMaxIterations}, batchSize: ${batchSize !== null && batchSize !== void 0 ? batchSize : 'none'})`);
|
|
402
434
|
while (index < effectiveMaxIterations) {
|
|
403
435
|
const rowLocators = yield resolve(config.rowSelector, rootLocator).all();
|
|
404
436
|
let rows = rowLocators.map((loc, i) => _makeSmart(loc, _headerMap, i));
|
|
@@ -416,21 +448,91 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
416
448
|
rows = deduplicated;
|
|
417
449
|
logDebug(`Deduplicated ${rowLocators.length} rows to ${rows.length} unique rows (total seen: ${seenKeys.size})`);
|
|
418
450
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
451
|
+
// Add rows to batch if batching is enabled
|
|
452
|
+
if (isBatching) {
|
|
453
|
+
batchRows.push(...rows);
|
|
454
|
+
}
|
|
455
|
+
const isLastIteration = index === effectiveMaxIterations - 1;
|
|
456
|
+
// Determine if we should invoke the callback
|
|
457
|
+
const batchComplete = isBatching && (index - batchStartIndex + 1) >= batchSize;
|
|
458
|
+
const shouldInvokeCallback = !isBatching || batchComplete || isLastIteration;
|
|
459
|
+
if (shouldInvokeCallback) {
|
|
460
|
+
const callbackRows = isBatching ? batchRows : rows;
|
|
461
|
+
const callbackIndex = isBatching ? batchStartIndex : index;
|
|
462
|
+
const isFirst = getIsFirst({ index: callbackIndex });
|
|
463
|
+
let isLast = getIsLast({ index: callbackIndex, paginationResult });
|
|
464
|
+
const isLastDueToMax = index === effectiveMaxIterations - 1;
|
|
465
|
+
if (isFirst && (options === null || options === void 0 ? void 0 : options.beforeFirst)) {
|
|
466
|
+
yield options.beforeFirst({ index: callbackIndex, rows: callbackRows, allData });
|
|
467
|
+
}
|
|
468
|
+
const batchInfo = isBatching ? {
|
|
469
|
+
startIndex: batchStartIndex,
|
|
470
|
+
endIndex: index,
|
|
471
|
+
size: index - batchStartIndex + 1
|
|
472
|
+
} : undefined;
|
|
473
|
+
const returnValue = yield callback({
|
|
474
|
+
index: callbackIndex,
|
|
475
|
+
isFirst,
|
|
476
|
+
isLast,
|
|
477
|
+
rows: callbackRows,
|
|
478
|
+
allData,
|
|
479
|
+
table: restrictedTable,
|
|
480
|
+
batchInfo
|
|
481
|
+
});
|
|
482
|
+
allData.push(returnValue);
|
|
483
|
+
// Determine if this is truly the last iteration
|
|
484
|
+
let finalIsLast = isLastDueToMax;
|
|
485
|
+
if (!isLastIteration) {
|
|
486
|
+
const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
|
|
487
|
+
paginationResult = yield paginationStrategy(context);
|
|
488
|
+
finalIsLast = getIsLast({ index: callbackIndex, paginationResult }) || !paginationResult;
|
|
489
|
+
}
|
|
490
|
+
if (finalIsLast && (options === null || options === void 0 ? void 0 : options.afterLast)) {
|
|
491
|
+
yield options.afterLast({ index: callbackIndex, rows: callbackRows, allData });
|
|
492
|
+
}
|
|
493
|
+
if (finalIsLast || !paginationResult) {
|
|
494
|
+
logDebug(`Reached last iteration (index: ${index}, paginationResult: ${paginationResult})`);
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
497
|
+
// Reset batch
|
|
498
|
+
if (isBatching) {
|
|
499
|
+
batchRows = [];
|
|
500
|
+
batchStartIndex = index + 1;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
// Continue paginating even when batching
|
|
505
|
+
const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
|
|
506
|
+
paginationResult = yield paginationStrategy(context);
|
|
507
|
+
if (!paginationResult) {
|
|
508
|
+
// Pagination failed, invoke callback with current batch
|
|
509
|
+
const callbackIndex = batchStartIndex;
|
|
510
|
+
const isFirst = getIsFirst({ index: callbackIndex });
|
|
511
|
+
const isLast = true;
|
|
512
|
+
if (isFirst && (options === null || options === void 0 ? void 0 : options.beforeFirst)) {
|
|
513
|
+
yield options.beforeFirst({ index: callbackIndex, rows: batchRows, allData });
|
|
514
|
+
}
|
|
515
|
+
const batchInfo = {
|
|
516
|
+
startIndex: batchStartIndex,
|
|
517
|
+
endIndex: index,
|
|
518
|
+
size: index - batchStartIndex + 1
|
|
519
|
+
};
|
|
520
|
+
const returnValue = yield callback({
|
|
521
|
+
index: callbackIndex,
|
|
522
|
+
isFirst,
|
|
523
|
+
isLast,
|
|
524
|
+
rows: batchRows,
|
|
525
|
+
allData,
|
|
526
|
+
table: restrictedTable,
|
|
527
|
+
batchInfo
|
|
528
|
+
});
|
|
529
|
+
allData.push(returnValue);
|
|
530
|
+
if (options === null || options === void 0 ? void 0 : options.afterLast) {
|
|
531
|
+
yield options.afterLast({ index: callbackIndex, rows: batchRows, allData });
|
|
532
|
+
}
|
|
533
|
+
logDebug(`Pagination failed mid-batch (index: ${index})`);
|
|
534
|
+
break;
|
|
535
|
+
}
|
|
434
536
|
}
|
|
435
537
|
index++;
|
|
436
538
|
logDebug(`Iteration ${index} completed, continuing...`);
|
|
@@ -444,6 +546,4 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
444
546
|
};
|
|
445
547
|
exports.useTable = useTable;
|
|
446
548
|
exports.PaginationStrategies = pagination_1.PaginationStrategies;
|
|
447
|
-
/** @deprecated Use Strategies.Pagination instead */
|
|
448
|
-
exports.DeprecatedTableStrategies = pagination_1.DeprecatedPaginationStrategies;
|
|
449
549
|
exports.SortingStrategies = sorting_1.SortingStrategies;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculate Levenshtein distance between two strings
|
|
3
|
+
* Used for "did you mean?" suggestions
|
|
4
|
+
*/
|
|
5
|
+
export declare function levenshteinDistance(a: string, b: string): number;
|
|
6
|
+
/**
|
|
7
|
+
* Calculate similarity score between two strings (0-1)
|
|
8
|
+
* 1 = identical, 0 = completely different
|
|
9
|
+
*/
|
|
10
|
+
export declare function stringSimilarity(a: string, b: string): number;
|
|
11
|
+
/**
|
|
12
|
+
* Find similar strings from a list
|
|
13
|
+
* Returns matches above threshold, sorted by similarity
|
|
14
|
+
*/
|
|
15
|
+
export declare function findSimilar(input: string, available: string[], threshold?: number): Array<{
|
|
16
|
+
value: string;
|
|
17
|
+
score: number;
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* Build a helpful error message for column not found
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildColumnNotFoundError(columnName: string, availableColumns: string[]): string;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.levenshteinDistance = levenshteinDistance;
|
|
4
|
+
exports.stringSimilarity = stringSimilarity;
|
|
5
|
+
exports.findSimilar = findSimilar;
|
|
6
|
+
exports.buildColumnNotFoundError = buildColumnNotFoundError;
|
|
7
|
+
/**
|
|
8
|
+
* Calculate Levenshtein distance between two strings
|
|
9
|
+
* Used for "did you mean?" suggestions
|
|
10
|
+
*/
|
|
11
|
+
function levenshteinDistance(a, b) {
|
|
12
|
+
const matrix = [];
|
|
13
|
+
for (let i = 0; i <= b.length; i++) {
|
|
14
|
+
matrix[i] = [i];
|
|
15
|
+
}
|
|
16
|
+
for (let j = 0; j <= a.length; j++) {
|
|
17
|
+
matrix[0][j] = j;
|
|
18
|
+
}
|
|
19
|
+
for (let i = 1; i <= b.length; i++) {
|
|
20
|
+
for (let j = 1; j <= a.length; j++) {
|
|
21
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
22
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution
|
|
26
|
+
matrix[i][j - 1] + 1, // insertion
|
|
27
|
+
matrix[i - 1][j] + 1 // deletion
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return matrix[b.length][a.length];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Calculate similarity score between two strings (0-1)
|
|
36
|
+
* 1 = identical, 0 = completely different
|
|
37
|
+
*/
|
|
38
|
+
function stringSimilarity(a, b) {
|
|
39
|
+
const distance = levenshteinDistance(a.toLowerCase(), b.toLowerCase());
|
|
40
|
+
const maxLen = Math.max(a.length, b.length);
|
|
41
|
+
return maxLen === 0 ? 1 : 1 - (distance / maxLen);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Find similar strings from a list
|
|
45
|
+
* Returns matches above threshold, sorted by similarity
|
|
46
|
+
*/
|
|
47
|
+
function findSimilar(input, available, threshold = 0.5) {
|
|
48
|
+
return available
|
|
49
|
+
.map(value => ({
|
|
50
|
+
value,
|
|
51
|
+
score: stringSimilarity(input, value)
|
|
52
|
+
}))
|
|
53
|
+
.filter(x => x.score >= threshold)
|
|
54
|
+
.sort((a, b) => b.score - a.score)
|
|
55
|
+
.slice(0, 3); // Top 3 suggestions
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Build a helpful error message for column not found
|
|
59
|
+
*/
|
|
60
|
+
function buildColumnNotFoundError(columnName, availableColumns) {
|
|
61
|
+
const suggestions = findSimilar(columnName, availableColumns);
|
|
62
|
+
let message = `Column '${columnName}' not found`;
|
|
63
|
+
if (suggestions.length > 0) {
|
|
64
|
+
message += `\n\nDid you mean:`;
|
|
65
|
+
suggestions.forEach(({ value, score }) => {
|
|
66
|
+
const percentage = Math.round(score * 100);
|
|
67
|
+
message += `\n • ${value} (${percentage}% match)`;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
message += `\n\nAvailable columns: ${availableColumns.join(', ')}`;
|
|
71
|
+
message += `\n\nTip: Column names are case-sensitive`;
|
|
72
|
+
return message;
|
|
73
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Page } from '@playwright/test';
|
|
2
|
+
/**
|
|
3
|
+
* Add a custom trace event to Playwright's trace viewer
|
|
4
|
+
* Uses page.evaluate to log events that appear in the trace
|
|
5
|
+
*/
|
|
6
|
+
export declare function addTraceEvent(page: Page, type: string, data?: Record<string, any>): Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* Check if tracing is currently enabled
|
|
9
|
+
* Used for conditional trace logic
|
|
10
|
+
*/
|
|
11
|
+
export declare function isTracingEnabled(page: Page): Promise<boolean>;
|
|
@@ -0,0 +1,47 @@
|
|
|
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.addTraceEvent = addTraceEvent;
|
|
13
|
+
exports.isTracingEnabled = isTracingEnabled;
|
|
14
|
+
/**
|
|
15
|
+
* Add a custom trace event to Playwright's trace viewer
|
|
16
|
+
* Uses page.evaluate to log events that appear in the trace
|
|
17
|
+
*/
|
|
18
|
+
function addTraceEvent(page_1, type_1) {
|
|
19
|
+
return __awaiter(this, arguments, void 0, function* (page, type, data = {}) {
|
|
20
|
+
try {
|
|
21
|
+
// Add a console log that will appear in the trace viewer
|
|
22
|
+
// Prefix with [SmartTable] for easy filtering
|
|
23
|
+
const message = `[SmartTable:${type}] ${JSON.stringify(data)}`;
|
|
24
|
+
yield page.evaluate((msg) => console.log(msg), message);
|
|
25
|
+
}
|
|
26
|
+
catch (_a) {
|
|
27
|
+
// Silently ignore if page is not available
|
|
28
|
+
// This ensures zero overhead when tracing is off
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check if tracing is currently enabled
|
|
34
|
+
* Used for conditional trace logic
|
|
35
|
+
*/
|
|
36
|
+
function isTracingEnabled(page) {
|
|
37
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
38
|
+
try {
|
|
39
|
+
// We can't directly check if tracing is enabled
|
|
40
|
+
// But we can safely call addTraceEvent - it will just be a no-op if not tracing
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
catch (_a) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rickcedwhat/playwright-smart-table",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "5.1.0",
|
|
4
|
+
"description": "Production-ready table testing for Playwright with smart column-aware locators. Core library with plugin support for custom table implementations.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/rickcedwhat/playwright-smart-table.git"
|