mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
test(e2e): wait for route readiness
## Summary - Replaces the fixed post-hash `browser.pause(2_000)` with a route readiness wait. - `navigateViaHash()` now waits for the target hash, `document.readyState === "complete"`, and a mounted React root before returning. - `walkOnboarding()` now waits for `#/home` and a Home-page marker after the onboarding next button unmounts. - Documents the navigation-readiness pattern in the E2E guide. ## Problem - #1864 reports a first-navigation race after onboarding: the hash changes, but the target panel can be empty because React has not settled yet. - The old helper returned after a fixed pause, so specs could start looking for panel text before the routed view mounted. ## Solution - Add `waitForHashRouteReady()` to make hash navigation wait on concrete browser/app signals. - Add `waitForPostOnboardingHome()` so the onboarding walker does not hand control back until Home is actually ready. - Throw navigation readiness failures immediately instead of hiding them behind a later text timeout. ## Submission Checklist - [x] Tests added or updated (happy path + at least one failure / edge case) per [Testing Strategy](../gitbooks/developing/testing-strategy.md#failure-path-requirement) - helper behavior tightened; E2E suite is the exercising path. - [x] **Diff coverage >= 80%** - N/A locally: E2E helper/doc change; CI E2E jobs are authoritative. - [x] Coverage matrix updated - N/A: E2E helper behavior change, no feature row added/removed/renamed. - [x] All affected feature IDs from the matrix are listed in the PR description under `## Related` - N/A: no matrix feature ID applies. - [x] No new external network dependencies introduced (mock backend used per [Testing Strategy](../gitbooks/developing/testing-strategy.md#mock-policy)) - [x] Manual smoke checklist updated if this touches release-cut surfaces ([`docs/RELEASE-MANUAL-SMOKE.md`](../docs/RELEASE-MANUAL-SMOKE.md)) - N/A: no release-cut surface. - [x] Linked issue closed via `Closes #NNN` in the `## Related` section ## Impact - Runtime/user impact: none; test helper only. - E2E impact: hash navigation and post-onboarding transitions now wait on real readiness signals instead of fixed sleeps. - Failure mode improves: route readiness failures surface at navigation time with the target hash in the error. ## Related - Closes #1864 - Follow-up PR(s)/TODOs: N/A --- ## AI Authored PR Metadata (required for Codex/Linear PRs) ### Linear Issue - Key: N/A - URL: N/A ### Commit & Branch - Branch: `codex/1864-e2e-navigation-readiness` - Commit SHA: `15f56af3c6c865fbd322010d25b047a998ebd964` ### Validation Run - [x] `pnpm --filter openhuman-app exec prettier --check test/e2e/helpers/shared-flows.ts` - passed - [x] `pnpm typecheck` - passed - [x] Focused tests: N/A, E2E helper change not run locally - [x] Rust fmt/check (if changed): `cargo fmt --all --check` - passed; `git diff --check` - passed - [x] Tauri fmt/check (if changed): N/A ### Validation Blocked - `command:` full E2E rerun - `error:` not run locally; requires built desktop app/Appium harness - `impact:` remote E2E CI remains authoritative for the harness change ### Behavior Changes - Intended behavior change: E2E helpers wait for route/onboarding readiness before specs continue. - User-visible effect: none. ### Parity Contract - Legacy behavior preserved: same routes and onboarding flow; only readiness timing changed. - Guard/fallback/dispatch parity checks: existing text assertions remain in specs after helper navigation. ### Duplicate / Superseded PR Handling - Duplicate PR(s): N/A - Canonical PR: this PR - Resolution (closed/superseded/updated): N/A <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Enhanced E2E helpers for more reliable hash-based navigation and for stronger verification that onboarding completes and the Home page is fully settled. * **Documentation** * Updated E2E testing guide with cross-platform navigation guidance recommending the hash navigation helper and noting post-onboarding Home-page readiness checks. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/tinyhumansai/openhuman/pull/2304?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: aqilaziz <gonzes7@gmail.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
This commit is contained in:
co-authored by
Steven Enamakel
parent
0f5e91421b
commit
c9f293d8ec
@@ -113,19 +113,92 @@ const HASH_TO_SIDEBAR_LABEL = {
|
||||
'/settings/intelligence': 'Intelligence',
|
||||
};
|
||||
|
||||
function normalizeHash(value) {
|
||||
const raw = String(value || '');
|
||||
const withPrefix = raw.startsWith('#') ? raw : `#${raw}`;
|
||||
return withPrefix.replace(/\/$/, '');
|
||||
}
|
||||
|
||||
function routeReadySelector(hash) {
|
||||
const path = normalizeHash(hash).replace(/^#/, '');
|
||||
const selectors = {
|
||||
'/notifications': '[data-testid="integration-notifications-section"]',
|
||||
'/settings/cron-jobs': '[data-testid="cron-jobs-panel"]',
|
||||
'/settings/privacy': '[data-testid="settings-privacy-panel"]',
|
||||
'/settings/migration': '[data-testid="migration-form"]',
|
||||
'/settings/voice': '[data-testid="voice-providers-section"]',
|
||||
'/settings/memory-data': '[data-testid="memory-workspace"]',
|
||||
'/settings/intelligence': '[data-testid="memory-workspace"]',
|
||||
};
|
||||
return selectors[path] || null;
|
||||
}
|
||||
|
||||
async function routeSignature() {
|
||||
return browser.execute(() => {
|
||||
const root = document.getElementById('root');
|
||||
return (root?.innerText || root?.textContent || '').trim().slice(0, 500);
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForHashRouteReady(hash, options = {}) {
|
||||
const { timeout = 10_000, previousSignature = '', allowSameSignature = false } = options;
|
||||
const expected = normalizeHash(hash);
|
||||
const readySelector = routeReadySelector(hash);
|
||||
await browser.waitUntil(
|
||||
async () =>
|
||||
Boolean(
|
||||
await browser.execute(
|
||||
({ target, selector, before, allowSame }) => {
|
||||
if (document.readyState !== 'complete') return false;
|
||||
const current = window.location.hash.replace(/\/$/, '');
|
||||
if (current !== target) return false;
|
||||
const root = document.getElementById('root');
|
||||
if (!root) return false;
|
||||
if (selector && root.querySelector(selector)) return true;
|
||||
|
||||
const signature = (root.innerText || root.textContent || '').trim().slice(0, 500);
|
||||
if (!signature) return false;
|
||||
return allowSame || signature !== before;
|
||||
},
|
||||
{
|
||||
target: expected,
|
||||
selector: readySelector,
|
||||
before: previousSignature,
|
||||
allowSame: allowSameSignature,
|
||||
}
|
||||
)
|
||||
),
|
||||
{
|
||||
timeout,
|
||||
interval: 250,
|
||||
timeoutMsg: `hash route ${expected} did not become ready within ${timeout}ms`,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function navigateViaHash(hash) {
|
||||
const normalized = String(hash).replace(/\/$/, '') || hash;
|
||||
|
||||
if (supportsExecuteScript()) {
|
||||
const beforeHash = normalizeHash(await browser.execute(() => window.location.hash));
|
||||
const beforeSignature = await routeSignature();
|
||||
const targetHash = normalizeHash(hash);
|
||||
try {
|
||||
await browser.execute(h => {
|
||||
window.location.hash = h;
|
||||
}, hash);
|
||||
await browser.pause(2_000);
|
||||
await waitForHashRouteReady(hash, {
|
||||
previousSignature: beforeSignature,
|
||||
allowSameSignature: beforeHash === targetHash,
|
||||
});
|
||||
const currentHash = await browser.execute(() => window.location.hash);
|
||||
console.log(`[E2E] Navigated to ${hash} (current: ${currentHash})`);
|
||||
} catch (err) {
|
||||
console.log(`[E2E] Hash navigation to ${hash} failed:`, err);
|
||||
const detail = err instanceof Error ? err.message : String(err);
|
||||
const wrapped = new Error(`[E2E] Hash navigation to ${hash} failed: ${detail}`);
|
||||
wrapped.cause = err;
|
||||
throw wrapped;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -378,6 +451,29 @@ export async function dismissBootCheckGateIfVisible(timeoutMs = 12_000): Promise
|
||||
return everSeen;
|
||||
}
|
||||
|
||||
async function waitForPostOnboardingHome(logPrefix, timeout = 20_000) {
|
||||
if (supportsExecuteScript()) {
|
||||
await browser.waitUntil(
|
||||
async () =>
|
||||
Boolean(await browser.execute(() => window.location.hash.replace(/\/$/, '') === '#/home')),
|
||||
{
|
||||
timeout: Math.min(timeout, 10_000),
|
||||
interval: 300,
|
||||
timeoutMsg: 'onboarding completed but hash did not settle on #/home',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const homeText = await waitForHomePage(timeout);
|
||||
if (!homeText) {
|
||||
const tree = await dumpAccessibilityTree();
|
||||
console.log(`${logPrefix} Home page not ready after onboarding. Tree:\n`, tree.slice(0, 4000));
|
||||
throw new Error('Onboarding dismissed but Home page did not become ready');
|
||||
}
|
||||
|
||||
console.log(`${logPrefix} Post-onboarding Home page confirmed: found "${homeText}"`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk through onboarding by advancing the `data-testid="onboarding-next-button"`
|
||||
* until it unmounts. The button is rendered on every step (see
|
||||
@@ -448,6 +544,7 @@ export async function walkOnboarding(logPrefix = '[E2E]', maxSteps = 12): Promis
|
||||
|
||||
if (status === 'gone') {
|
||||
console.log(`${logPrefix} Onboarding dismissed after ${step} step(s)`);
|
||||
await waitForPostOnboardingHome(logPrefix);
|
||||
return;
|
||||
}
|
||||
if (status === 'gone-but-onboarding-hash') {
|
||||
|
||||
@@ -126,6 +126,10 @@ Use `waitForTestId(testId)` and `clickTestId(testId)` from `element-helpers.ts`
|
||||
3. **Use `hasAppChrome()`** instead of checking for `XCUIElementTypeMenuBar`
|
||||
4. **Use `waitForWebView()`** instead of checking for `XCUIElementTypeWebView`
|
||||
5. For macOS-only tests, use `process.platform` guards or separate spec files
|
||||
6. Use `navigateViaHash(route)` for hash routes; it waits for the hash,
|
||||
`document.readyState`, and a mounted React root before returning. After
|
||||
onboarding, `walkOnboarding()` also waits for `#/home` plus a Home-page
|
||||
marker before specs navigate elsewhere.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user