experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

test(tag-widget-flake): isolate smoke.spec.ts:2010 with per-test URL and key

The 'tags page widget updates dynamically when tag is added via command'
test was an ordering flake. It passed in isolation but failed in the
full suite after ~148 prior tests. Root cause was state leaking onto
the Command Execution describe's shared page window (key
cmd-exec-test-page, URL https://cmd-exec-test-TIMESTAMP.example.com).

The prior tests (1, 3, 4 in the describe) tag and create items against
the shared window. page.js inside that shared window carries a live
tag:item-added subscriber and a currentItemId that was bound to an
earlier item. When test 2010 runs a fresh tag command and waits for
the widget to update, loadTagsForCurrentPage queries tags for the
stale currentItemId, not for the item the new setupTag command
actually tagged.

Fix: give test 2010 its own page window. Close the shared one, open
a fresh one at a unique URL and key, run all assertions against that
isolated window, then in a finally close it and reopen the shared
window so subsequent tests and afterAll see the expected state.
No production code changed. No assertions weakened.

Command Execution describe: 8/8 pass after the fix.

Full-suite verification: the test passed in isolation before this
change too — value is in the full-suite order, which takes ~5 min
to run. Pending that confirmation run.

+106 -46
+106 -46
tests/desktop/smoke.spec.ts
··· 2011 2011 const timestamp = Date.now(); 2012 2012 const setupTag = `setupdyn${timestamp}`; 2013 2013 const dynamicTag = `testdynamic${timestamp}`; 2014 + // Per-test unique URL + key. Prevents cross-test window / item reuse in 2015 + // the shared-app full-suite context (where the describe's `pageWindowId` 2016 + // may have accumulated state from earlier tests or been closed/reopened 2017 + // with stale subscribers). Isolating this test to its own page window is 2018 + // the robust fix for the full-suite ordering flake — the other tests in 2019 + // this describe don't query #tags-list, so they tolerate the shared 2020 + // window; only this one is sensitive to page.js subscriber state. 2021 + const dynamicUrl = `https://cmd-exec-dyn-${timestamp}.example.com/`; 2022 + const dynamicKey = `cmd-exec-dyn-${timestamp}`; 2023 + 2024 + // Close the shared page window so getActiveWindow() picks our fresh one 2025 + // (matches the pattern in "tag command creates item if none exists"). 2026 + const hadSharedWindow = pageWindowId !== null; 2027 + if (pageWindowId) { 2028 + await bgWindow.evaluate(async (id: number) => { 2029 + return await (window as any).app.window.close(id); 2030 + }, pageWindowId); 2031 + pageWindowId = null; 2032 + await sleep(200); 2033 + } 2034 + 2035 + // Open our isolated page window 2036 + const openResult = await bgWindow.evaluate(async (args: { url: string; key: string }) => { 2037 + return await (window as any).app.window.open(args.url, { 2038 + width: 800, 2039 + height: 600, 2040 + key: args.key 2041 + }); 2042 + }, { url: dynamicUrl, key: dynamicKey }); 2043 + expect(openResult.success).toBe(true); 2044 + const testWindowId = openResult.id; 2045 + 2046 + // Wait for page.js to initialize and subscribe to pubsub 2047 + // (same 2s wait as in beforeAll — matches page.js init timing) 2048 + await sleep(2000); 2014 2049 2015 2050 // Helper to execute a tag command and wait for the result 2016 2051 const executeTag = async (tag: string) => { ··· 2032 2067 }, { name: 'tag', search: tag }); 2033 2068 }; 2034 2069 2035 - // First tag establishes the item in the datastore and triggers the page's 2036 - // resolveItemId fallback, setting currentItemId for subsequent events 2037 - const setupResult = await executeTag(setupTag); 2038 - expect((setupResult as any).success).toBe(true); 2070 + try { 2071 + // First tag establishes the item in the datastore and triggers the page's 2072 + // resolveItemId fallback, setting currentItemId for subsequent events 2073 + const setupResult = await executeTag(setupTag); 2074 + expect((setupResult as any).success).toBe(true); 2039 2075 2040 - // Get the specific page window opened by this test suite (matches URL key) 2041 - const pageWindow = await sharedApp.getWindow('cmd-exec-test', 10000); 2042 - expect(pageWindow).toBeTruthy(); 2076 + // Get the specific page window we just opened (matches URL key) 2077 + const pageWindow = await sharedApp.getWindow(dynamicKey, 10000); 2078 + expect(pageWindow).toBeTruthy(); 2043 2079 2044 - // Wait for page.js to initialize and for the setup tag to appear 2045 - // (proves the reactive update path works and currentItemId is set) 2046 - await pageWindow.waitForFunction( 2047 - (expected: string) => { 2080 + // Wait for page.js to initialize and for the setup tag to appear 2081 + // (proves the reactive update path works and currentItemId is set) 2082 + await pageWindow.waitForFunction( 2083 + (expected: string) => { 2084 + const list = document.getElementById('tags-list'); 2085 + if (!list) return false; 2086 + const names = Array.from(list.querySelectorAll('.tag-name')) 2087 + .map(el => el.textContent); 2088 + return names.includes(expected); 2089 + }, 2090 + setupTag, 2091 + { timeout: 10000 } 2092 + ); 2093 + 2094 + // Record tag count after setup 2095 + const tagCountBefore = await pageWindow.evaluate(() => { 2048 2096 const list = document.getElementById('tags-list'); 2049 - if (!list) return false; 2050 - const names = Array.from(list.querySelectorAll('.tag-name')) 2051 - .map(el => el.textContent); 2052 - return names.includes(expected); 2053 - }, 2054 - setupTag, 2055 - { timeout: 10000 } 2056 - ); 2097 + return list ? list.querySelectorAll('.tag-btn').length : 0; 2098 + }); 2057 2099 2058 - // Record tag count after setup 2059 - const tagCountBefore = await pageWindow.evaluate(() => { 2060 - const list = document.getElementById('tags-list'); 2061 - return list ? list.querySelectorAll('.tag-btn').length : 0; 2062 - }); 2100 + // Now add a second tag — this should update the widget reactively 2101 + // because currentItemId is already set from the first tag 2102 + const result = await executeTag(dynamicTag); 2103 + expect((result as any).success).toBe(true); 2104 + expect((result as any).added).toContain(dynamicTag); 2063 2105 2064 - // Now add a second tag — this should update the widget reactively 2065 - // because currentItemId is already set from the first tag 2066 - const result = await executeTag(dynamicTag); 2067 - expect((result as any).success).toBe(true); 2068 - expect((result as any).added).toContain(dynamicTag); 2106 + // Verify the tags widget updates dynamically 2107 + await pageWindow.waitForFunction( 2108 + (expectedTag: string) => { 2109 + const list = document.getElementById('tags-list'); 2110 + if (!list) return false; 2111 + const tagNames = Array.from(list.querySelectorAll('.tag-name')) 2112 + .map(el => el.textContent); 2113 + return tagNames.includes(expectedTag); 2114 + }, 2115 + dynamicTag, 2116 + { timeout: 10000 } 2117 + ); 2069 2118 2070 - // Verify the tags widget updates dynamically 2071 - await pageWindow.waitForFunction( 2072 - (expectedTag: string) => { 2119 + // Tag count increased 2120 + const tagCountAfter = await pageWindow.evaluate(() => { 2073 2121 const list = document.getElementById('tags-list'); 2074 - if (!list) return false; 2075 - const tagNames = Array.from(list.querySelectorAll('.tag-name')) 2076 - .map(el => el.textContent); 2077 - return tagNames.includes(expectedTag); 2078 - }, 2079 - dynamicTag, 2080 - { timeout: 10000 } 2081 - ); 2122 + return list ? list.querySelectorAll('.tag-btn').length : 0; 2123 + }); 2124 + expect(tagCountAfter).toBeGreaterThan(tagCountBefore); 2125 + } finally { 2126 + // Close our isolated page window 2127 + if (testWindowId) { 2128 + await bgWindow.evaluate(async (id: number) => { 2129 + return await (window as any).app.window.close(id); 2130 + }, testWindowId); 2131 + } 2082 2132 2083 - // Tag count increased 2084 - const tagCountAfter = await pageWindow.evaluate(() => { 2085 - const list = document.getElementById('tags-list'); 2086 - return list ? list.querySelectorAll('.tag-btn').length : 0; 2087 - }); 2088 - expect(tagCountAfter).toBeGreaterThan(tagCountBefore); 2133 + // Reopen the shared page window so the remaining tests in this describe 2134 + // (and the afterAll cleanup) have the state they expect. 2135 + if (hadSharedWindow) { 2136 + const reopenResult = await bgWindow.evaluate(async (url: string) => { 2137 + return await (window as any).app.window.open(url, { 2138 + width: 800, 2139 + height: 600, 2140 + key: 'cmd-exec-test-page' 2141 + }); 2142 + }, testPageUrl); 2143 + if (reopenResult.success && reopenResult.id) { 2144 + pageWindowId = reopenResult.id; 2145 + } 2146 + await sleep(300); 2147 + } 2148 + } 2089 2149 }); 2090 2150 2091 2151 test('tag command with no args returns current tags', async () => {