From 87df34a0add5ed49308d7e00c4e55be5cb8415da Mon Sep 17 00:00:00 2001 From: Meis Date: Wed, 30 Oct 2024 14:45:44 -0600 Subject: [PATCH] [E2E] Get tests to consistently pass in `headless` mode (#1036) Closes #987 The failures I've experienced seem to be isolated to me + my machine, and most consistently in `headless` mode, but this workaround is implemented in a way that should not interfere with others' consistently passing tests. ## Changes - Implements retries when clicking a link that navigates to a new domain for the first time ## How to test this PR 1. Start local SBL Platform 2. `yarn playwright test --workers 4` 3. Ensure all tests pass ## Screenshots ![screencapture-localhost-9323-2024-10-30-13_31_18](https://github.com/user-attachments/assets/4ace1a05-fd75-4632-8502-4f2f9f1f6806) --- .../InstitutionProfile.spec.ts | 23 +++++++------ .../Navigation.spec.ts | 33 +++++++++++-------- ...eTestCfpbLogoGoesToConsumerFinance.spec.ts | 3 +- e2e/utils/clickExternalLinkWithRetry.ts | 31 +++++++++++++++++ 4 files changed, 63 insertions(+), 27 deletions(-) create mode 100644 e2e/utils/clickExternalLinkWithRetry.ts diff --git a/e2e/pages/shared-lending-platform/InstitutionProfile.spec.ts b/e2e/pages/shared-lending-platform/InstitutionProfile.spec.ts index e941bce11..def884cde 100644 --- a/e2e/pages/shared-lending-platform/InstitutionProfile.spec.ts +++ b/e2e/pages/shared-lending-platform/InstitutionProfile.spec.ts @@ -1,5 +1,6 @@ import { expect } from '@playwright/test'; import { test } from '../../fixtures/testFixture'; +import { clickLinkWithRetry } from '../../utils/clickExternalLinkWithRetry'; test('Institution Profile Page', async ({ page, @@ -267,22 +268,20 @@ test('Institution Profile Page', async ({ // GLEIF link await test.step('GLEIF links', async () => { - const [externalLink] = await Promise.all([ - context.waitForEvent('page'), - page - .getByRole('link', { - name: 'GLEIF', - exact: true, - }) - .click(), - ]); - await expect(externalLink, 'Resolves correctly').toHaveURL( + await clickLinkWithRetry({ + page, + target: page.getByRole('link', { + name: 'GLEIF', + exact: true, + }), + }); + await expect(page).toHaveURL( 'https://www.gleif.org/en/about-lei/get-an-lei-find-lei-issuing-organizations', ); - await expect(externalLink, 'Resolves correctly').toHaveTitle( + await expect(page).toHaveTitle( 'Get an LEI: Find LEI Issuing Organizations - LEI – GLEIF', ); - await externalLink.close(); + await page.goBack(); }); // Update Financial Institution links diff --git a/e2e/pages/shared-lending-platform/Navigation.spec.ts b/e2e/pages/shared-lending-platform/Navigation.spec.ts index fe5d4842a..07fea734e 100644 --- a/e2e/pages/shared-lending-platform/Navigation.spec.ts +++ b/e2e/pages/shared-lending-platform/Navigation.spec.ts @@ -1,5 +1,6 @@ import { expect } from '@playwright/test'; import { test } from '../../fixtures/testFixture'; +import { clickLinkWithRetry } from '../../utils/clickExternalLinkWithRetry'; test('Navigation', async ({ page, navigateToFilingHome }) => { navigateToFilingHome; @@ -57,10 +58,12 @@ test('Navigation', async ({ page, navigateToFilingHome }) => { }); await test.step('Footer Navigation', async () => { - await page - .locator('.o-footer') - .getByRole('link', { name: 'About Us', exact: true }) - .click(); + await clickLinkWithRetry({ + page, + target: page + .locator('.o-footer') + .getByRole('link', { name: 'About Us', exact: true }), + }); await expect(page.locator('h1')).toContainText('About us'); await page.goBack(); @@ -213,17 +216,19 @@ test('Navigation', async ({ page, navigateToFilingHome }) => { ); await page.goBack(); - await page - .locator('.o-footer') - .getByRole('link', { name: 'USA.gov' }) - .click(); + await clickLinkWithRetry({ + page, + target: page.locator('.o-footer').getByRole('link', { name: 'USA.gov' }), + }); await expect(page).toHaveURL(/.*usa.gov/); await page.goBack(); - await page - .locator('.o-footer') - .getByRole('link', { name: 'Office of Inspector General' }) - .click(); + await clickLinkWithRetry({ + page, + target: page + .locator('.o-footer') + .getByRole('link', { name: 'Office of Inspector General' }), + }); await expect(page).toHaveURL(/.*oig/); await page.goBack(); }); @@ -238,10 +243,10 @@ test('Navigation', async ({ page, navigateToFilingHome }) => { await expect(page.locator('h1')).toContainText( 'Get started filing your lending data', ); - await expect(page.locator('.navbar .nav-items')).toBeEmpty(); + await expect(page.locator('.navbar .nav-items')).toHaveCount(0); // Test CFPB Logo Link - await page.getByLabel('Home').click(); + await clickLinkWithRetry({ page, target: page.getByLabel('Home') }); await expect(page).toHaveURL('https://www.consumerfinance.gov/'); }); }); diff --git a/e2e/pages/shared-lending-platform/unauthenticated-homepage/sampleTestCfpbLogoGoesToConsumerFinance.spec.ts b/e2e/pages/shared-lending-platform/unauthenticated-homepage/sampleTestCfpbLogoGoesToConsumerFinance.spec.ts index daedc4251..b71f627bb 100644 --- a/e2e/pages/shared-lending-platform/unauthenticated-homepage/sampleTestCfpbLogoGoesToConsumerFinance.spec.ts +++ b/e2e/pages/shared-lending-platform/unauthenticated-homepage/sampleTestCfpbLogoGoesToConsumerFinance.spec.ts @@ -1,11 +1,12 @@ import { expect } from '@playwright/test'; import { test } from '../../../fixtures/testFixture'; +import { clickLinkWithRetry } from '../../../utils/clickExternalLinkWithRetry'; // this is just an example test (e2e tests should be way longer than this) test('Unauthenticated homepage: CFPB logo link goes to consumerfinance.gov', async ({ page, }) => { await page.goto('/'); - await page.getByLabel('Home').click(); + await clickLinkWithRetry({ page, target: page.getByLabel('Home') }); await expect(page).toHaveURL('https://www.consumerfinance.gov/'); }); diff --git a/e2e/utils/clickExternalLinkWithRetry.ts b/e2e/utils/clickExternalLinkWithRetry.ts new file mode 100644 index 000000000..b8b08f817 --- /dev/null +++ b/e2e/utils/clickExternalLinkWithRetry.ts @@ -0,0 +1,31 @@ +import type { Locator, Page } from '@playwright/test'; + +/** + * Helps address intermittent failures when loading external links + * by attempting to click the link multiple times, hoping one of + * the retries succeeds. + * @param page Page object to allow navigation operations + * @param target Target that should be clicked + */ +export const clickLinkWithRetry = async ({ + page, + target, + maxRetries = 2, +}: { + page: Page; + target: Locator; + maxRetries?: number; +}) => { + const URL_LOAD_FAILED = 'chrome-error://chromewebdata/'; + let counter = 0; + + await target.click(); + + while (page.url() === URL_LOAD_FAILED && counter < maxRetries) { + counter += 1; + // eslint-disable-next-line no-await-in-loop + await page.goBack(); + // eslint-disable-next-line no-await-in-loop + await target.click(); + } +};