diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-22 15:48:48 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-22 15:48:48 -0500 |
| commit | 8c291b81cd7fb10479a55fb47e9a9cebcfc1b9b8 (patch) | |
| tree | 9f4d2fc7d3dff7f5404576759fee9d4a44f74b73 /playwright-js/lib/helpers.js | |
| parent | e6f8db82fb3e97ebf11866de2166ff4505871c21 (diff) | |
| download | rulesets-8c291b81cd7fb10479a55fb47e9a9cebcfc1b9b8.tar.gz rulesets-8c291b81cd7fb10479a55fb47e9a9cebcfc1b9b8.zip | |
refactor(skills): locator-first playwright guidance, drop emoji markers
Two cleanups to the playwright skills, landed together since they overlap the same files.
The skills taught networkidle as the readiness check and leaned on raw page.click/fill/waitForSelector. Playwright discourages networkidle for readiness, so the guidance in both SKILL.md files now waits for a visible app landmark via a web assertion or locator, the login and form examples use getByLabel/getByRole plus expect, the API reference leads with that pattern, and lib/helpers.js defaults waitForPageReady to load (preferring a caller-supplied landmark) and races the success indicator in authenticate instead of waiting on networkidle.
The second cleanup strips emoji console markers across run.js, helpers.js, both SKILL.md files, and the py examples, replacing each with a plain ASCII tag like [ok], [error], or [scan]. node --check and py_compile pass, and an emoji grep comes back clean.
Diffstat (limited to 'playwright-js/lib/helpers.js')
| -rw-r--r-- | playwright-js/lib/helpers.js | 54 |
1 files changed, 35 insertions, 19 deletions
diff --git a/playwright-js/lib/helpers.js b/playwright-js/lib/helpers.js index 0920d68..42aeedd 100644 --- a/playwright-js/lib/helpers.js +++ b/playwright-js/lib/helpers.js @@ -82,30 +82,45 @@ async function createPage(context, options = {}) { } /** - * Smart wait for page to be ready + * Smart wait for page to be ready. + * + * Readiness defaults to the 'load' event. Pass `options.waitForSelector` with an + * app-specific landmark (a heading, a role=main region, known text) to wait on + * something the app actually rendered — that's the most reliable readiness signal + * and the one to prefer. `networkidle` is intentionally NOT the default: Playwright + * discourages it for readiness, since a page can be interactive long before the + * network goes quiet (and may never go quiet with polling or analytics). Callers + * can still pass `waitUntil: 'networkidle'` explicitly if they really need it. * @param {Object} page - Playwright page - * @param {Object} options - Wait options + * @param {Object} options - Wait options (waitUntil, timeout, waitForSelector) */ async function waitForPageReady(page, options = {}) { const waitOptions = { - waitUntil: options.waitUntil || 'networkidle', + waitUntil: options.waitUntil || 'load', timeout: options.timeout || 30000 }; - + + // Prefer waiting on a caller-supplied landmark selector when given; it's a + // far stronger readiness signal than any load-state event. + if (options.waitForSelector) { + try { + await page.waitForSelector(options.waitForSelector, { + state: 'visible', + timeout: waitOptions.timeout + }); + return; + } catch (e) { + console.warn(`Landmark "${options.waitForSelector}" not visible in time, falling back to load state...`); + } + } + try { - await page.waitForLoadState(waitOptions.waitUntil, { - timeout: waitOptions.timeout + await page.waitForLoadState(waitOptions.waitUntil, { + timeout: waitOptions.timeout }); } catch (e) { console.warn('Page load timeout, continuing...'); } - - // Additional wait for dynamic content if selector provided - if (options.waitForSelector) { - await page.waitForSelector(options.waitForSelector, { - timeout: options.timeout - }); - } } /** @@ -215,10 +230,11 @@ async function authenticate(page, credentials, selectors = {}) { await safeType(page, finalSelectors.password, credentials.password); await safeClick(page, finalSelectors.submit); - // Wait for navigation or success indicator + // Wait for a post-login landmark to become visible (preferred), or for a + // navigation to settle. networkidle is avoided here as a readiness signal. await Promise.race([ - page.waitForNavigation({ waitUntil: 'networkidle' }), - page.waitForSelector(selectors.successIndicator || '.dashboard, .user-menu, .logout', { timeout: 10000 }) + page.waitForSelector(selectors.successIndicator || '.dashboard, .user-menu, .logout', { state: 'visible', timeout: 10000 }), + page.waitForNavigation({ waitUntil: 'load' }) ]).catch(() => { console.log('Login might have completed without navigation'); }); @@ -383,7 +399,7 @@ async function detectDevServers(customPorts = []) { const detectedServers = []; - console.log('🔍 Checking for running dev servers...'); + console.log('[scan] Checking for running dev servers...'); for (const port of allPorts) { try { @@ -397,7 +413,7 @@ async function detectDevServers(customPorts = []) { }, (res) => { if (res.statusCode < 500) { detectedServers.push(`http://localhost:${port}`); - console.log(` ✅ Found server on port ${port}`); + console.log(` [ok] Found server on port ${port}`); } resolve(); }); @@ -416,7 +432,7 @@ async function detectDevServers(customPorts = []) { } if (detectedServers.length === 0) { - console.log(' ❌ No dev servers detected'); + console.log(' [none] No dev servers detected'); } return detectedServers; |
