@rickcedwhat/playwright-smart-table 6.11.0 → 6.13.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/dist/useTable.js CHANGED
@@ -31,6 +31,20 @@ const debugUtils_1 = require("./utils/debugUtils");
31
31
  const smartRowArray_1 = require("./utils/smartRowArray");
32
32
  const elementTracker_1 = require("./utils/elementTracker");
33
33
  const navigationBarrier_1 = require("./utils/navigationBarrier");
34
+ // Helper to safely serialize objects containing functions for logging
35
+ const safeStringify = (obj) => {
36
+ try {
37
+ return JSON.stringify(obj, (key, value) => {
38
+ if (typeof value === 'function') {
39
+ return '[Function]';
40
+ }
41
+ return value;
42
+ });
43
+ }
44
+ catch (e) {
45
+ return '[unserializable]';
46
+ }
47
+ };
34
48
  /**
35
49
  * Main hook to interact with a table.
36
50
  */
@@ -182,6 +196,7 @@ const useTable = (rootLocator, configOptions = {}) => {
182
196
  if (pagesJumped > 0) {
183
197
  tableState.currentPageIndex += pagesJumped;
184
198
  log(`_advancePage: ${primitive} advanced ${pagesJumped} page(s) — now at page ${tableState.currentPageIndex}`);
199
+ await (0, debugUtils_1.debugDelay)(config, 'pagination');
185
200
  }
186
201
  else {
187
202
  log(`_advancePage: ${primitive} returned false — end of data`);
@@ -195,6 +210,10 @@ const useTable = (rootLocator, configOptions = {}) => {
195
210
  var _a;
196
211
  if (tableMapper.isInitialized())
197
212
  return result;
213
+ if (config.strategies.sorting)
214
+ (0, validation_1.validateSortingStrategy)(config.strategies.sorting);
215
+ if (config.strategies.fill)
216
+ (0, validation_1.validateFillStrategy)(config.strategies.fill);
198
217
  (0, debugUtils_1.warnIfDebugInCI)(config);
199
218
  (0, debugUtils_1.logDebug)(config, 'info', 'Initializing table');
200
219
  const map = await tableMapper.getMap(options === null || options === void 0 ? void 0 : options.timeout);
@@ -220,6 +239,7 @@ const useTable = (rootLocator, configOptions = {}) => {
220
239
  return result;
221
240
  },
222
241
  scrollToColumn: async (columnName) => {
242
+ log(`scrollToColumn: column="${columnName}"`);
223
243
  const map = await tableMapper.getMap();
224
244
  const idx = map.get(columnName);
225
245
  if (idx === undefined)
@@ -229,10 +249,12 @@ const useTable = (rootLocator, configOptions = {}) => {
229
249
  await headerCell.scrollIntoViewIfNeeded();
230
250
  },
231
251
  getHeaders: async () => {
252
+ log("getHeaders: fetching available columns");
232
253
  const map = await tableMapper.getMap();
233
254
  return Array.from(map.keys());
234
255
  },
235
256
  getHeaderCell: async (columnName) => {
257
+ log(`getHeaderCell: column="${columnName}"`);
236
258
  const map = await tableMapper.getMap();
237
259
  const idx = map.get(columnName);
238
260
  if (idx === undefined)
@@ -241,10 +263,37 @@ const useTable = (rootLocator, configOptions = {}) => {
241
263
  },
242
264
  countRows: async () => {
243
265
  await _autoInit();
244
- const allRows = resolve(config.rowSelector, rootLocator);
245
- return allRows.count();
266
+ const pag = config.strategies.pagination;
267
+ const hasPagination = config.maxPages > 1 && !!((pag === null || pag === void 0 ? void 0 : pag.goNext) || (pag === null || pag === void 0 ? void 0 : pag.goNextBulk));
268
+ if (!hasPagination) {
269
+ log("countRows: counting rows in current viewport (no pagination)");
270
+ return resolve(config.rowSelector, rootLocator).count();
271
+ }
272
+ log(`countRows: paginating up to ${config.maxPages} page(s)`);
273
+ const tracker = new elementTracker_1.ElementTracker('countRows');
274
+ let total = 0;
275
+ let pagesScanned = 1;
276
+ try {
277
+ while (true) {
278
+ const newIndices = await tracker.getUnseenIndices(resolve(config.rowSelector, rootLocator));
279
+ total += newIndices.length;
280
+ log(`countRows: page ${pagesScanned} — ${newIndices.length} row(s) (running total: ${total})`);
281
+ if (pagesScanned >= config.maxPages)
282
+ break;
283
+ if (!await _advancePage(false))
284
+ break;
285
+ pagesScanned++;
286
+ }
287
+ }
288
+ finally {
289
+ await tracker.cleanup(rootLocator.page());
290
+ }
291
+ log(`countRows: ${total} total row(s) across ${pagesScanned} page(s) — resetting`);
292
+ await result.reset();
293
+ return total;
246
294
  },
247
295
  mapColumn: async (columnName, options = {}) => {
296
+ log(`mapColumn: column="${columnName}" options=${safeStringify(options)}`);
248
297
  await _autoInit();
249
298
  const map = await tableMapper.getMap();
250
299
  if (!map.has(columnName))
@@ -262,6 +311,7 @@ const useTable = (rootLocator, configOptions = {}) => {
262
311
  }, options);
263
312
  },
264
313
  getColumnValues: async (columnName, options = {}) => {
314
+ log(`getColumnValues: column="${columnName}" options=${safeStringify(options)}`);
265
315
  const values = await result.mapColumn(columnName, options);
266
316
  return values.map(v => String(v));
267
317
  },
@@ -288,15 +338,17 @@ const useTable = (rootLocator, configOptions = {}) => {
288
338
  log("Table revalidated.");
289
339
  },
290
340
  getRow: (filters, options = { exact: false }) => {
341
+ log(`getRow: filters=${safeStringify(filters)} exact=${options.exact}`);
291
342
  const map = tableMapper.getMapSync();
292
343
  if (!map)
293
344
  throw new Error('Initialization Error: You attempted to access a row before the table structure was mapped. Please call "await table.init()" once before using synchronous row access.');
294
345
  const allRows = resolve(config.rowSelector, rootLocator);
295
346
  const matchedRows = filterEngine.applyFilters(allRows, filters, map, options.exact || false, rootLocator.page(), rootLocator);
296
347
  const rowLocator = matchedRows.first();
297
- return _makeSmart(rowLocator, map, 0); // fallback index 0
348
+ return _makeSmart(rowLocator, map, undefined); // sync path cannot compute real index
298
349
  },
299
350
  getRowByIndex: (index) => {
351
+ log(`getRowByIndex: index=${index}`);
300
352
  const map = tableMapper.getMapSync();
301
353
  if (!map)
302
354
  throw new Error('Initialization Error: You attempted to access a row before the table structure was mapped. Please call "await table.init()" once before using synchronous row access.');
@@ -304,13 +356,17 @@ const useTable = (rootLocator, configOptions = {}) => {
304
356
  return _makeSmart(rowLocator, map, index);
305
357
  },
306
358
  findRow: async (filters, options) => {
359
+ log(`findRow: filters=${safeStringify(filters)} options=${safeStringify(options)}`);
307
360
  return rowFinder.findRow(filters, options);
308
361
  },
309
362
  findRows: async (filters, options) => {
363
+ log(`findRows: filters=${safeStringify(filters !== null && filters !== void 0 ? filters : {})} options=${safeStringify(options)}`);
310
364
  return rowFinder.findRows(filters !== null && filters !== void 0 ? filters : {}, options);
311
365
  },
312
366
  isInitialized: () => {
313
- return tableMapper.isInitialized();
367
+ const initialized = tableMapper.isInitialized();
368
+ log(`isInitialized: ${initialized}`);
369
+ return initialized;
314
370
  },
315
371
  sorting: {
316
372
  apply: async (columnName, direction) => {
@@ -329,7 +385,10 @@ const useTable = (rootLocator, configOptions = {}) => {
329
385
  }
330
386
  await config.strategies.sorting.doSort({ columnName, direction, context });
331
387
  if ((_a = config.strategies.loading) === null || _a === void 0 ? void 0 : _a.isTableLoading) {
332
- await config.strategies.loading.isTableLoading(context);
388
+ const deadline = Date.now() + 10000;
389
+ while (Date.now() < deadline && await config.strategies.loading.isTableLoading(context)) {
390
+ await rootLocator.page().waitForTimeout(100);
391
+ }
333
392
  }
334
393
  else {
335
394
  await rootLocator.page().waitForTimeout(200);
@@ -344,6 +403,7 @@ const useTable = (rootLocator, configOptions = {}) => {
344
403
  throw new Error(`Failed to sort column "${columnName}" to "${direction}" after ${maxRetries} attempts.`);
345
404
  },
346
405
  getState: async (columnName) => {
406
+ log(`sorting.getState: column="${columnName}"`);
347
407
  await _autoInit();
348
408
  if (!config.strategies.sorting)
349
409
  throw new Error('No sorting strategy has been configured.');
@@ -359,6 +419,7 @@ const useTable = (rootLocator, configOptions = {}) => {
359
419
  const effectiveMaxPages = config.maxPages;
360
420
  const tracker = new elementTracker_1.ElementTracker('iterator');
361
421
  const useBulk = false; // iterator has no options; default goNext
422
+ log(`iterator: starting (maxPages=${effectiveMaxPages})`);
362
423
  try {
363
424
  let rowIndex = 0;
364
425
  let pagesScanned = 1;
@@ -368,7 +429,7 @@ const useTable = (rootLocator, configOptions = {}) => {
368
429
  const pageRows = yield __await(rowLocators.all());
369
430
  const barrier = new navigationBarrier_1.NavigationBarrier(newIndices.length);
370
431
  for (const idx of newIndices) {
371
- yield yield __await({ row: _makeSmart(pageRows[idx], map, rowIndex, pagesScanned - 1, barrier), index: rowIndex, rowIndex });
432
+ yield yield __await({ row: _makeSmart(pageRows[idx], map, rowIndex, tableState.currentPageIndex, barrier), index: rowIndex, rowIndex, pageIndex: tableState.currentPageIndex });
372
433
  rowIndex++;
373
434
  }
374
435
  if (pagesScanned >= effectiveMaxPages)
@@ -385,6 +446,7 @@ const useTable = (rootLocator, configOptions = {}) => {
385
446
  },
386
447
  // ─── Row iteration (delegated to engine/tableIteration) ──────────────────
387
448
  forEach: async (callback, options = {}) => {
449
+ log(`forEach: options=${safeStringify(options)}`);
388
450
  await _autoInit();
389
451
  await (0, tableIteration_1.runForEach)({
390
452
  getRowLocators: () => resolve(config.rowSelector, rootLocator),
@@ -394,9 +456,11 @@ const useTable = (rootLocator, configOptions = {}) => {
394
456
  createSmartRowArray: smartRowArray_1.createSmartRowArray,
395
457
  config,
396
458
  getPage: () => rootLocator.page(),
459
+ getCurrentPageIndex: () => tableState.currentPageIndex,
397
460
  }, callback, options);
398
461
  },
399
462
  map: async (callback, options = {}) => {
463
+ log(`map: options=${safeStringify(options)}`);
400
464
  await _autoInit();
401
465
  return (0, tableIteration_1.runMap)({
402
466
  getRowLocators: () => resolve(config.rowSelector, rootLocator),
@@ -406,9 +470,11 @@ const useTable = (rootLocator, configOptions = {}) => {
406
470
  createSmartRowArray: smartRowArray_1.createSmartRowArray,
407
471
  config,
408
472
  getPage: () => rootLocator.page(),
473
+ getCurrentPageIndex: () => tableState.currentPageIndex,
409
474
  }, callback, options);
410
475
  },
411
476
  filter: async (predicate, options = {}) => {
477
+ log(`filter: options=${safeStringify(options)}`);
412
478
  await _autoInit();
413
479
  return (0, tableIteration_1.runFilter)({
414
480
  getRowLocators: () => resolve(config.rowSelector, rootLocator),
@@ -418,9 +484,11 @@ const useTable = (rootLocator, configOptions = {}) => {
418
484
  createSmartRowArray: smartRowArray_1.createSmartRowArray,
419
485
  config,
420
486
  getPage: () => rootLocator.page(),
487
+ getCurrentPageIndex: () => tableState.currentPageIndex,
421
488
  }, predicate, options);
422
489
  },
423
490
  generateConfig: async () => {
491
+ log("Generating table config prompt...");
424
492
  const html = await _getCleanHtml(rootLocator);
425
493
  const separator = "=".repeat(50);
426
494
  const content = `\n${separator} \n🤖 COPY INTO GEMINI / ChatGPT 🤖\n${separator} \nI am using 'playwright-smart-table'.\nTarget Table Locator: ${rootLocator.toString()} \nGenerate config for: \n\`\`\`html\n${html.substring(0, 10000)} ...\n\`\`\`\n${separator}\n`;
@@ -2,6 +2,16 @@ import { Locator } from '@playwright/test';
2
2
  export declare class ElementTracker {
3
3
  readonly id: string;
4
4
  constructor(prefix?: string);
5
+ /**
6
+ * Returns indices of newly seen or recycled elements without marking them as seen.
7
+ * Call {@link commitIndices} to persist the subset you intend to process.
8
+ */
9
+ peekUnseenIndices(locators: Locator): Promise<number[]>;
10
+ /**
11
+ * Marks the given indices as seen. Only call this for the subset you intend to process;
12
+ * uncommitted indices will be returned again by the next peek/getUnseenIndices call.
13
+ */
14
+ commitIndices(locators: Locator, indices: number[]): Promise<void>;
5
15
  /**
6
16
  * Finds the indices of newly seen elements in the browser, storing their text signature
7
17
  * in a WeakMap. This gracefully handles both append-only DOMs (by identity) and
@@ -6,11 +6,10 @@ class ElementTracker {
6
6
  this.id = `__smartTable_${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
7
7
  }
8
8
  /**
9
- * Finds the indices of newly seen elements in the browser, storing their text signature
10
- * in a WeakMap. This gracefully handles both append-only DOMs (by identity) and
11
- * virtualized DOMs (by text signature if nodes are recycled).
9
+ * Returns indices of newly seen or recycled elements without marking them as seen.
10
+ * Call {@link commitIndices} to persist the subset you intend to process.
12
11
  */
13
- async getUnseenIndices(locators) {
12
+ async peekUnseenIndices(locators) {
14
13
  return await locators.evaluateAll((elements, trackerId) => {
15
14
  const win = window;
16
15
  if (!win[trackerId]) {
@@ -19,17 +18,42 @@ class ElementTracker {
19
18
  const seenMap = win[trackerId];
20
19
  const newIndices = [];
21
20
  elements.forEach((el, index) => {
22
- // Determine a lightweight signature for the row (textContent strips HTML, fast)
23
21
  const signature = el.textContent || '';
24
- // If it's a new element, OR a recycled element with new data
25
22
  if (seenMap.get(el) !== signature) {
26
- seenMap.set(el, signature);
27
23
  newIndices.push(index);
28
24
  }
29
25
  });
30
26
  return newIndices;
31
27
  }, this.id);
32
28
  }
29
+ /**
30
+ * Marks the given indices as seen. Only call this for the subset you intend to process;
31
+ * uncommitted indices will be returned again by the next peek/getUnseenIndices call.
32
+ */
33
+ async commitIndices(locators, indices) {
34
+ await locators.evaluateAll((elements, [trackerId, indicesToCommit]) => {
35
+ const win = window;
36
+ if (!win[trackerId]) {
37
+ win[trackerId] = new WeakMap();
38
+ }
39
+ const seenMap = win[trackerId];
40
+ for (const index of indicesToCommit) {
41
+ const el = elements[index];
42
+ if (el)
43
+ seenMap.set(el, el.textContent || '');
44
+ }
45
+ }, [this.id, indices]);
46
+ }
47
+ /**
48
+ * Finds the indices of newly seen elements in the browser, storing their text signature
49
+ * in a WeakMap. This gracefully handles both append-only DOMs (by identity) and
50
+ * virtualized DOMs (by text signature if nodes are recycled).
51
+ */
52
+ async getUnseenIndices(locators) {
53
+ const indices = await this.peekUnseenIndices(locators);
54
+ await this.commitIndices(locators, indices);
55
+ return indices;
56
+ }
33
57
  /**
34
58
  * Cleans up the tracking map from the browser window object.
35
59
  */
@@ -7,6 +7,8 @@ export declare class NavigationBarrier {
7
7
  private finishedCount;
8
8
  private buckets;
9
9
  constructor(total: number);
10
+ /** True when more than one row shares this barrier (scrollToRow would evict peers). */
11
+ isParallel(): boolean;
10
12
  /**
11
13
  * Synchronize all active rows at a specific column index.
12
14
  * The last row to arrive will trigger the `moveAction`.
@@ -12,6 +12,10 @@ class NavigationBarrier {
12
12
  this.buckets = new Map();
13
13
  this.total = total;
14
14
  }
15
+ /** True when more than one row shares this barrier (scrollToRow would evict peers). */
16
+ isParallel() {
17
+ return this.total > 1;
18
+ }
15
19
  /**
16
20
  * Synchronize all active rows at a specific column index.
17
21
  * The last row to arrive will trigger the `moveAction`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rickcedwhat/playwright-smart-table",
3
- "version": "6.11.0",
3
+ "version": "6.13.0",
4
4
  "description": "Smart, column-aware table interactions for Playwright",
5
5
  "author": "Cedrick Catalan",
6
6
  "license": "MIT",
@@ -28,19 +28,19 @@
28
28
  "docs:build:all": "vitepress build docs",
29
29
  "docs:verify-links": "node scripts/verify-vitepress-links.mjs",
30
30
  "embed-version": "node scripts/embed-version.mjs",
31
- "build": "npm run embed-version && 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",
32
- "prepublishOnly": "npm run build",
31
+ "build": "pnpm run embed-version && pnpm run generate-types && pnpm run generate-config-types && pnpm run generate-docs && pnpm run generate-all-api-docs && pnpm run update-all-api-signatures && tsc",
32
+ "prepublishOnly": "pnpm run build",
33
33
  "clean-port": "lsof -ti:3000 | xargs kill -9 || true",
34
- "pretest": "npm run clean-port",
35
- "posttest": "npm run clean-port",
36
- "test": "npm run test:unit && npx playwright test",
34
+ "pretest": "pnpm run clean-port",
35
+ "posttest": "pnpm run clean-port",
36
+ "test": "pnpm run test:unit && pnpm exec playwright test",
37
37
  "test:unit": "vitest run --coverage --reporter=verbose --reporter=html",
38
38
  "test:unit:ui": "vitest --ui",
39
- "test:mutate": "npx stryker run",
40
- "pretest:e2e": "npm run clean-port",
41
- "posttest:e2e": "npm run clean-port",
42
- "test:e2e": "npx playwright test",
43
- "test:playground": "npx playwright test --config tests-external/playground/playwright.config.ts 2>&1 | tee \"tests-external/playground/logs/run-$(date +%Y%m%d-%H%M%S).log\"",
39
+ "test:mutate": "pnpm exec stryker run",
40
+ "pretest:e2e": "pnpm run clean-port",
41
+ "posttest:e2e": "pnpm run clean-port",
42
+ "test:e2e": "pnpm exec playwright test",
43
+ "test:playground": "pnpm exec playwright test --config tests-external/playground/playwright.config.ts 2>&1 | tee \"tests-external/playground/logs/run-$(date +%Y%m%d-%H%M%S).log\"",
44
44
  "dev:playground": "cd playground && npm run dev",
45
45
  "dev:mui": "cd tests/apps/mui-datagrid && npm run dev",
46
46
  "prepare": "husky"
@@ -87,17 +87,15 @@
87
87
  "@stryker-mutator/core": "^9.6.0",
88
88
  "@stryker-mutator/vitest-runner": "^9.6.0",
89
89
  "@types/node": "^25.5.2",
90
- "@vitest/coverage-v8": "^4.1.0",
91
- "@vitest/ui": "^4.1.0",
90
+ "@vitest/coverage-v8": "^3.2.4",
91
+ "@vitest/ui": "^3.2.4",
92
92
  "happy-dom": "^20.8.3",
93
- "@commitlint/cli": "^19.7.1",
94
- "@commitlint/config-conventional": "^19.7.1",
93
+ "@commitlint/cli": "^21.0.0",
94
+ "@commitlint/config-conventional": "^21.0.0",
95
95
  "husky": "^9.1.7",
96
96
  "typescript": "^6.0.2",
97
97
  "vitepress": "^1.6.4",
98
- "vitest": "^4.1.0"
98
+ "vitest": "^3.2.4"
99
99
  },
100
- "dependencies": {
101
- "@scarf/scarf": "^1.4.0"
102
- }
100
+ "packageManager": "pnpm@10.33.2"
103
101
  }
@@ -1,14 +0,0 @@
1
- import { StrategyContext } from '../../types';
2
- /**
3
- * Primitive navigation functions for Glide Data Grid.
4
- * These define HOW to move, not WHEN to move.
5
- * The orchestration logic lives in _navigateToCell.
6
- */
7
- export declare const glideGoUp: (context: StrategyContext) => Promise<void>;
8
- export declare const glideGoDown: (context: StrategyContext) => Promise<void>;
9
- export declare const glideGoLeft: (context: StrategyContext) => Promise<void>;
10
- export declare const glideGoRight: (context: StrategyContext) => Promise<void>;
11
- /** Coarse `scrollLeft` toward `columnIndex`; `goLeft`/`goRight` correct the remainder. */
12
- export declare const glideSeekColumnIndex: (context: StrategyContext, columnIndex: number) => Promise<void>;
13
- /** `scrollLeft = 0` so low `aria-colindex` cells exist again (see smartRow for optional `Home`). */
14
- export declare const glideSnapFirstColumnIntoView: (context: StrategyContext) => Promise<void>;
@@ -1,53 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.glideSnapFirstColumnIntoView = exports.glideSeekColumnIndex = exports.glideGoRight = exports.glideGoLeft = exports.glideGoDown = exports.glideGoUp = void 0;
4
- /**
5
- * Primitive navigation functions for Glide Data Grid.
6
- * These define HOW to move, not WHEN to move.
7
- * The orchestration logic lives in _navigateToCell.
8
- */
9
- const glideGoUp = async (context) => {
10
- await context.page.keyboard.press('ArrowUp');
11
- };
12
- exports.glideGoUp = glideGoUp;
13
- const glideGoDown = async (context) => {
14
- await context.page.keyboard.press('ArrowDown');
15
- };
16
- exports.glideGoDown = glideGoDown;
17
- // Horizontal: use page `.dvn-scroller` (canvas root is not its ancestor). Virtualized rows expose
18
- // a small window of `td[aria-colindex]` that shifts with scrollLeft.
19
- const nudgePageScroller = async (page, delta) => {
20
- const scroller = page.locator('.dvn-scroller').first();
21
- await scroller.evaluate((el, d) => {
22
- el.scrollLeft += d;
23
- }, delta);
24
- };
25
- const glideGoLeft = async (context) => {
26
- await nudgePageScroller(context.page, -400);
27
- };
28
- exports.glideGoLeft = glideGoLeft;
29
- const glideGoRight = async (context) => {
30
- await nudgePageScroller(context.page, 400);
31
- };
32
- exports.glideGoRight = glideGoRight;
33
- /** Coarse `scrollLeft` toward `columnIndex`; `goLeft`/`goRight` correct the remainder. */
34
- const glideSeekColumnIndex = async (context, columnIndex) => {
35
- const { page } = context;
36
- const scroller = page.locator('.dvn-scroller').first();
37
- await scroller.evaluate((el, colIdx) => {
38
- const max = Math.max(0, el.scrollWidth - el.clientWidth);
39
- const ratio = Math.min(1, Math.max(0, (colIdx + 0.5) / 64));
40
- el.scrollLeft = Math.floor(ratio * max);
41
- }, columnIndex);
42
- await page.waitForTimeout(100);
43
- };
44
- exports.glideSeekColumnIndex = glideSeekColumnIndex;
45
- /** `scrollLeft = 0` so low `aria-colindex` cells exist again (see smartRow for optional `Home`). */
46
- const glideSnapFirstColumnIntoView = async (context) => {
47
- const { page } = context;
48
- await page.locator('.dvn-scroller').first().evaluate((el) => {
49
- el.scrollLeft = 0;
50
- });
51
- await page.waitForTimeout(150);
52
- };
53
- exports.glideSnapFirstColumnIntoView = glideSnapFirstColumnIntoView;