Skip to content

Commit 8f2eff7

Browse files
committed
Wait for renderer window by URL in smoke tests
1 parent aad2b25 commit 8f2eff7

1 file changed

Lines changed: 75 additions & 60 deletions

File tree

e2e-tests/fixtures/app.fixture.ts

Lines changed: 75 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -48,82 +48,97 @@ export const test = base.extend<TestAppFixtures, WorkerAppFixtures>({
4848
],
4949

5050
mainPage: async ({ electronApp }, use, testInfo: TestInfo) => {
51-
const page = await electronApp.firstWindow({ timeout: PROCESS_READY_TIMEOUT });
5251
const rendererUrl = 'http://localhost:1212/index.html?logLevel=debug';
52+
const readyDeadline = Date.now() + PROCESS_READY_TIMEOUT;
5353

54-
console.log(`Window URL: ${page.url()}`);
5554
const onPageError = (err: Error) => console.error(`Page error: ${err.message}`);
5655
const onConsoleMsg = (msg: ConsoleMessage) => {
5756
if (msg.type() === 'error') console.error(`Console error: ${msg.text()}`);
5857
};
59-
page.on('pageerror', onPageError);
60-
page.on('console', onConsoleMsg);
6158

62-
const readyDeadline = Date.now() + PROCESS_READY_TIMEOUT;
63-
let rootAttached = false;
59+
const attachListeners = (page: Page) => {
60+
page.on('pageerror', onPageError);
61+
page.on('console', onConsoleMsg);
62+
};
63+
64+
const detachListeners = (page: Page) => {
65+
page.off('pageerror', onPageError);
66+
page.off('console', onConsoleMsg);
67+
};
6468

69+
let page: Page | undefined;
6570
while (Date.now() < readyDeadline) {
66-
const currentUrl = page.url();
67-
68-
// CI can intermittently land on chrome-error://chromewebdata/ if Electron opens before the
69-
// renderer navigation has fully settled. Drive the page back to the actual renderer URL and
70-
// retry until React mounts or timeout.
71-
if (
72-
currentUrl.startsWith('chrome-error://') ||
73-
currentUrl === 'about:blank' ||
74-
!currentUrl.startsWith('http://localhost:1212/')
75-
) {
71+
const pages = electronApp.windows();
72+
page =
73+
pages.find((candidate) => {
74+
const candidateUrl = candidate.url();
75+
return (
76+
candidateUrl.startsWith('http://localhost:1212/') &&
77+
!candidateUrl.includes('devtools://')
78+
);
79+
}) ?? pages.find((candidate) => !candidate.url().includes('devtools://'));
80+
81+
if (page) {
82+
attachListeners(page);
83+
7684
try {
77-
await page.goto(rendererUrl, {
78-
waitUntil: 'domcontentloaded',
79-
timeout: 15_000,
85+
const currentUrl = page.url();
86+
console.log(`Window URL: ${currentUrl}`);
87+
88+
// CI can intermittently land on chrome-error://chromewebdata/ if Electron opens before the
89+
// renderer navigation has fully settled. Drive the page back to the actual renderer URL and
90+
// retry until React mounts or timeout.
91+
if (
92+
currentUrl.startsWith('chrome-error://') ||
93+
currentUrl === 'about:blank' ||
94+
!currentUrl.startsWith('http://localhost:1212/')
95+
) {
96+
await page.goto(rendererUrl, {
97+
waitUntil: 'domcontentloaded',
98+
timeout: 15_000,
99+
});
100+
}
101+
102+
const remaining = Math.max(0, readyDeadline - Date.now());
103+
if (remaining <= 0) break;
104+
105+
await page.waitForLoadState('domcontentloaded');
106+
await page.waitForSelector('#root', {
107+
state: 'attached',
108+
timeout: Math.min(5_000, remaining),
80109
});
110+
111+
console.log(`Window URL: ${page.url()}`);
112+
await use(page);
113+
detachListeners(page);
114+
115+
if (testInfo.status !== testInfo.expectedStatus) {
116+
const screenshotPath = testInfo.outputPath('failure.png');
117+
try {
118+
await page.screenshot({ path: screenshotPath, fullPage: true });
119+
await testInfo.attach('failure-screenshot', {
120+
path: screenshotPath,
121+
contentType: 'image/png',
122+
});
123+
console.log(`Failure screenshot saved to ${screenshotPath}`);
124+
} catch {
125+
console.warn('Could not capture failure screenshot (window may already be closed)');
126+
}
127+
}
128+
return;
81129
} catch {
82-
// Retry loop handles transient dev-server unavailability.
130+
detachListeners(page);
131+
page = undefined;
83132
}
84133
}
85134

86-
const remaining = Math.max(0, readyDeadline - Date.now());
87-
if (remaining <= 0) break;
88-
89-
try {
90-
await page.waitForLoadState('domcontentloaded');
91-
await page.waitForSelector('#root', {
92-
state: 'attached',
93-
timeout: Math.min(5_000, remaining),
94-
});
95-
rootAttached = true;
96-
break;
97-
} catch {
98-
// Keep retrying until deadline.
99-
}
135+
await new Promise<void>((resolve) => {
136+
setTimeout(resolve, 500);
137+
});
100138
}
101139

102-
if (!rootAttached) {
103-
throw new Error(
104-
`Main renderer did not mount #root within ${PROCESS_READY_TIMEOUT}ms (current URL: ${page.url()})`,
105-
);
106-
}
107-
108-
console.log(`Window URL: ${page.url()}`);
109-
110-
await use(page);
111-
112-
page.off('pageerror', onPageError);
113-
page.off('console', onConsoleMsg);
114-
115-
if (testInfo.status !== testInfo.expectedStatus) {
116-
const screenshotPath = testInfo.outputPath('failure.png');
117-
try {
118-
await page.screenshot({ path: screenshotPath, fullPage: true });
119-
await testInfo.attach('failure-screenshot', {
120-
path: screenshotPath,
121-
contentType: 'image/png',
122-
});
123-
console.log(`Failure screenshot saved to ${screenshotPath}`);
124-
} catch {
125-
console.warn('Could not capture failure screenshot (window may already be closed)');
126-
}
127-
}
140+
throw new Error(
141+
`Main renderer did not mount #root within ${PROCESS_READY_TIMEOUT}ms (last URL: ${page?.url() ?? 'no window'})`,
142+
);
128143
},
129144
});

0 commit comments

Comments
 (0)