Files

80 lines
3.2 KiB
JavaScript

/**
* Waiting primitives shared by the on-call page objects.
*
* These exist to replace fixed `waitForTimeout` settles. A fixed sleep is a
* guess in both directions — too short and the read lands mid-repaint, too long
* and every call pays for the worst case — and it is silent either way.
*
* Two DIFFERENT needs, deliberately kept apart:
* - `settleRows` waits for something that must happen, and FAILS loudly if it
* never does.
* - `observeUntil` waits for something that may legitimately never happen and
* REPORTS which, because "it did not" is one of the answers its callers
* return rather than an error.
*/
const { expect } = require('@playwright/test');
/** OTable's own row hook. Index-based, so it restarts at 0 on every page. */
const TABLE_ROWS = '[data-test^="o2-table-row-"]';
/**
* A fingerprint of the rows currently drawn.
*
* The row index restarts at 0 on each page, so two different pages share the
* same `data-test` set — the text has to be part of the fingerprint or a page
* change is indistinguishable from no change at all. One round trip rather than
* one per row, because this is sampled repeatedly while waiting.
*/
async function rowSignature(page, rowSelector = TABLE_ROWS) {
return await page
.locator(rowSelector)
.evaluateAll((els) =>
els.map((el) => `${el.getAttribute('data-test')}:${(el.textContent || '').trim().slice(0, 80)}`).join('|'));
}
/**
* Wait until the table has stopped repainting.
*
* Requires several IDENTICAL consecutive samples rather than one pair: a single
* pair taken before the repaint has even begun looks perfectly stable, which is
* how this kind of wait passes for a fixed sleep with extra steps. Throws if the
* list never settles, so a genuinely stuck screen is reported and not absorbed.
*/
async function settleRows(page, { rowSelector = TABLE_ROWS, samples = 3, intervalMs = 150, timeout = 20000 } = {}) {
let previous = null;
let repeats = 0;
await expect
.poll(async () => {
const current = await rowSignature(page, rowSelector);
repeats = current === previous ? repeats + 1 : 0;
previous = current;
return repeats;
}, {
timeout,
intervals: [intervalMs],
message: 'the list never stopped repainting, so anything read off it would be a mid-refetch snapshot',
})
.toBeGreaterThanOrEqual(samples);
}
/**
* Poll `predicate` until it holds or the window closes, and report which.
*
* Deliberately NOT an assertion. Callers use this where "it never happened" is
* a result they return to the spec — a control that issues no request, a row
* that neither expands nor navigates — so throwing here would destroy the very
* observation the caller exists to make. Returns as soon as the predicate holds,
* so the full window is only ever paid when the answer really is "no".
*/
async function observeUntil(page, predicate, { timeout = 1200, intervalMs = 100 } = {}) {
const deadline = Date.now() + timeout;
for (;;) {
if (await predicate()) return true;
if (Date.now() >= deadline) return false;
await page.waitForTimeout(intervalMs);
}
}
module.exports = { TABLE_ROWS, rowSignature, settleRows, observeUntil };