Skip to content

Commit af043bd

Browse files
committed
e2e: Fix flaky login and redirect tests
Resolve flakiness in E2E tests by using deterministic waiting mechanisms for backend API responses. - Login Test (`login.spec.ts`): - Make the syncing state screenshot test deterministic by intercepting the `/v1/users/me/ping` API request and blocking it using a Promise until both light and dark theme screenshots are captured. This prevents the UI from prematurely transitioning to the idle state, which was causing race conditions and snapshot mismatches (especially in WebKit). - Redirect Tests (`feature-page.spec.ts`, `utils.ts`): - Synchronize E2E tests by waiting for backend API GET responses before asserting redirects, rather than relying on page load events or timing out. - In `feature-page.spec.ts`: - Update `redirects for a moved feature` test to wait for both the redirect response (`old-feature`) and the redirect target response (`new-feature`), filtering both by `GET` method to avoid matching CORS `OPTIONS` preflights. This ensures the test doesn't assert the URL before the redirect target has finished loading, resolving failures on cold start cache misses. - Update `shows gone page for a split feature` to wait for the `before-split-feature` GET response before asserting the 410 URL. - In `utils.ts`: - Update `goTo404Page` helper to wait for the feature's GET response before asserting the 404 URL. This shifts network wait times to the safe 30-second default timeout of `waitForResponse`, preventing them from eating into the strict 5-second assertion timeout of `toHaveURL` in resource-constrained CI environments.
1 parent 675d99b commit af043bd

3 files changed

Lines changed: 37 additions & 6 deletions

File tree

e2e/tests/feature-page.spec.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,12 +214,19 @@ test('date range changes are preserved in the URL', async ({page}) => {
214214

215215
test('redirects for a moved feature', async ({page, browserName}) => {
216216
test.skip(browserName === 'webkit', 'Skipping webkit due to flakiness');
217-
// Wait for the app to fetch the old feature data which triggers the redirect.
218-
const responsePromise = page.waitForResponse(response =>
219-
response.url().includes('/v1/features/old-feature'),
217+
const oldResponsePromise = page.waitForResponse(
218+
response =>
219+
response.url().includes('/v1/features/old-feature') &&
220+
response.request().method() === 'GET',
221+
);
222+
const newResponsePromise = page.waitForResponse(
223+
response =>
224+
response.url().includes('/v1/features/new-feature') &&
225+
response.request().method() === 'GET',
220226
);
221227
await page.goto('http://localhost:5555/features/old-feature');
222-
await responsePromise;
228+
await oldResponsePromise;
229+
await newResponsePromise;
223230

224231
// Expect the URL to be updated to the new feature's URL.
225232
await expect(page).toHaveURL(
@@ -248,7 +255,14 @@ test('redirects for a moved feature', async ({page, browserName}) => {
248255
});
249256

250257
test('shows gone page for a split feature', async ({page}) => {
258+
// Wait for the API request to complete before checking for redirect.
259+
const responsePromise = page.waitForResponse(
260+
response =>
261+
response.url().includes('/v1/features/before-split-feature') &&
262+
response.request().method() === 'GET',
263+
);
251264
await page.goto('http://localhost:5555/features/before-split-feature');
265+
await responsePromise;
252266

253267
// Expect to be redirected to the 'feature-gone-split' page.
254268
await expect(page).toHaveURL(

e2e/tests/login.spec.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,14 @@ test.describe('Login Component States', () => {
2828
test('displays spinner and is disabled during profile sync', async ({
2929
page,
3030
}) => {
31-
// Intercept the pingUser request to introduce a delay.
31+
let resolvePing: (() => void) | undefined;
32+
const pingPromise = new Promise<void>(resolve => {
33+
resolvePing = resolve;
34+
});
35+
36+
// Intercept the pingUser request and hold it.
3237
await page.route('**/v1/users/me/ping', async route => {
33-
await new Promise(resolve => setTimeout(resolve, 1000)); // Delay to ensure 'syncing' state is capturable
38+
await pingPromise;
3439
await route.continue();
3540
});
3641

@@ -58,6 +63,11 @@ test.describe('Login Component States', () => {
5863
},
5964
);
6065

66+
// Resolve the ping to let the sync complete.
67+
if (resolvePing) {
68+
resolvePing();
69+
}
70+
6171
// Now, wait for the sync to complete and verify the final state.
6272
await expect(loginButton.locator('sl-spinner')).toBeHidden();
6373
await expect(loginButton).not.toBeDisabled();

e2e/tests/utils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,14 @@ export async function freezeAnimations(page: Page) {
261261
}
262262

263263
export async function goTo404Page(page: Page, query: string): Promise<void> {
264+
const responsePromise = page.waitForResponse(
265+
response =>
266+
response.url().includes(`/v1/features/${query}`) &&
267+
response.request().method() === 'GET',
268+
);
264269
await page.goto(`${BASE_URL}/features/${query}`);
270+
await responsePromise;
271+
265272
await expect(page).toHaveURL(
266273
`${BASE_URL}/errors-404/feature-not-found?q=${query}`,
267274
);

0 commit comments

Comments
 (0)