diff --git a/.github/workflows/.reusable-docker-e2e-tests.yml b/.github/workflows/.reusable-docker-e2e-tests.yml index 247361ecb3f4..b6fd246285fa 100644 --- a/.github/workflows/.reusable-docker-e2e-tests.yml +++ b/.github/workflows/.reusable-docker-e2e-tests.yml @@ -22,6 +22,11 @@ on: description: The concurrent number of browsers to be used on testing required: false default: 16 + pg-max-connections: + type: number + description: Postgres max_connections for the E2E database. Raise above the default of 100 for high-concurrency runs (e.g. private-cloud) to avoid connection exhaustion. + required: false + default: 100 runs-on: type: string description: The runner label to use. Defaults to `depot-ubuntu-latest` @@ -115,6 +120,7 @@ jobs: E2E_IMAGE: ${{ inputs.e2e-image }} E2E_CONCURRENCY: ${{ inputs.concurrency }} E2E_RETRIES: 2 + PG_MAX_CONNECTIONS: ${{ inputs.pg-max-connections }} VISUAL_REGRESSION: ${{ inputs.visual-regression && '1' || '' }} VISUAL_REGRESSION_ARGS: ${{ inputs.visual-regression-update && '--update-snapshots' || '' }} SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} diff --git a/.github/workflows/platform-pull-request.yml b/.github/workflows/platform-pull-request.yml index b51f819f6051..3ecb29788e8c 100644 --- a/.github/workflows/platform-pull-request.yml +++ b/.github/workflows/platform-pull-request.yml @@ -171,6 +171,8 @@ jobs: api-image: ${{ needs.docker-build-private-cloud.outputs.image }} args: --grep "@oss|@enterprise" visual-regression: ${{ matrix.runs-on == 'depot-ubuntu-latest-16' }} + # TODO: identify whether the E2E leaks connections, leading to the connection bloat + pg-max-connections: 200 secrets: GCR_TOKEN: ${{ needs.permissions-check.outputs.can-write == 'true' && secrets.GITHUB_TOKEN || '' }} SLACK_TOKEN: ${{ needs.permissions-check.outputs.can-write == 'true' && secrets.SLACK_TOKEN || '' }} diff --git a/frontend/docker-compose-e2e-tests.yml b/frontend/docker-compose-e2e-tests.yml index 1f881ece6303..6dd4fc2878f5 100644 --- a/frontend/docker-compose-e2e-tests.yml +++ b/frontend/docker-compose-e2e-tests.yml @@ -7,6 +7,7 @@ version: '3' services: db: image: docker.io/library/postgres:15-alpine + command: postgres -c max_connections=${PG_MAX_CONNECTIONS:-100} environment: POSTGRES_PASSWORD: password POSTGRES_DB: flagsmith diff --git a/frontend/e2e/helpers/e2e-helpers.playwright.ts b/frontend/e2e/helpers/e2e-helpers.playwright.ts index ad74b4f56157..6b438133d262 100644 --- a/frontend/e2e/helpers/e2e-helpers.playwright.ts +++ b/frontend/e2e/helpers/e2e-helpers.playwright.ts @@ -1,8 +1,8 @@ import { Page, expect } from '@playwright/test'; -import { LONG_TIMEOUT, byId, log, logUsingLastSection, getFlagsmith } from './utils.playwright'; +import { LONG_TIMEOUT, SHORT_TIMEOUT, byId, log, logUsingLastSection, getFlagsmith } from './utils.playwright'; // Re-export for backwards compatibility -export { LONG_TIMEOUT, byId, log, logUsingLastSection, getFlagsmith }; +export { LONG_TIMEOUT, SHORT_TIMEOUT, byId, log, logUsingLastSection, getFlagsmith }; export type MultiVariate = { value: string; weight: number }; @@ -40,6 +40,26 @@ export class E2EHelpers { }); } + // Asserts a navbar link is reachable. A link can either be shown inline, or + // collapsed into the OverflowNav "more" menu when the navbar runs out of + // horizontal space (e.g. when a feature flag adds an extra item). Try inline + // first; if it isn't visible, open the overflow menu and retry. Only fails if + // the link is reachable via neither. + async waitForNavElementVisible(selector: string) { + logUsingLastSection(`Waiting nav element visible (inline or overflow) ${selector}`); + const element = this.page.locator(selector).first(); + try { + await element.waitFor({ state: 'visible', timeout: SHORT_TIMEOUT }); + return; + } catch { + const overflowButton = this.page.locator(byId('overflow-nav-button')).first(); + if (await overflowButton.isVisible()) { + await overflowButton.click(); + } + await element.waitFor({ state: 'visible', timeout: LONG_TIMEOUT }); + } + } + async waitForElementNotClickable(selector: string) { logUsingLastSection(`Waiting element not clickable ${selector}`); const element = this.page.locator(selector).first(); @@ -898,11 +918,20 @@ export class E2EHelpers { if (entityName) { await this.click(byId(`permissions-${entityName.toLowerCase()}`)); } + // Wait for the permission save (POST/PUT) to commit before closing, so a later read can't race the grant. + const savePromise = this.page.waitForResponse( + (res) => + res.url().includes('/user-permissions/') && + ['POST', 'PUT'].includes(res.request().method()) && + res.ok(), + { timeout: LONG_TIMEOUT }, + ); if (permission === 'ADMIN') { await this.click(byId(`admin-switch-${level}`)); } else { await this.click(byId(`permission-switch-${permission}`)); } + await savePromise; await this.closeModal(); } diff --git a/frontend/e2e/helpers/utils.playwright.ts b/frontend/e2e/helpers/utils.playwright.ts index 2322496d4da1..f8818aba7b2a 100644 --- a/frontend/e2e/helpers/utils.playwright.ts +++ b/frontend/e2e/helpers/utils.playwright.ts @@ -4,6 +4,7 @@ import { IFlagsmith } from '@flagsmith/flagsmith/types'; import Project from '../../common/project'; export const LONG_TIMEOUT = 20000; +export const SHORT_TIMEOUT = 5000; export const byId = (id: string) => `[data-test="${id}"]`; diff --git a/frontend/e2e/tests/project-permission-test.pw.ts b/frontend/e2e/tests/project-permission-test.pw.ts index 658158672caf..c59d1836fd8e 100644 --- a/frontend/e2e/tests/project-permission-test.pw.ts +++ b/frontend/e2e/tests/project-permission-test.pw.ts @@ -24,6 +24,7 @@ test.describe('Project Permission Tests', () => { waitForElementNotExist, waitForElementVisible, waitForFeatureSwitch, + waitForNavElementVisible, waitForPageFullyLoaded, } = createHelpers(page); @@ -84,7 +85,7 @@ test.describe('Project Permission Tests', () => { log('User with ADMIN permissions can set project settings') await login(E2E_NON_ADMIN_USER_WITH_PROJECT_PERMISSIONS, PASSWORD) await gotoProject(PROJECT_NAME) - await waitForElementVisible('#project-settings-link') + await waitForNavElementVisible('#project-settings-link') await logout() log('Remove user as project ADMIN') await login(E2E_USER, PASSWORD) diff --git a/frontend/web/components/navigation/OverflowNav.tsx b/frontend/web/components/navigation/OverflowNav.tsx index 18861d8bd8f9..944fb7a6831a 100644 --- a/frontend/web/components/navigation/OverflowNav.tsx +++ b/frontend/web/components/navigation/OverflowNav.tsx @@ -82,6 +82,7 @@ const OverflowNav: FC = ({ style={{ height: buttonWidth, width: buttonWidth }} onClick={() => setOpen(!open)} theme='secondary' + data-test='overflow-nav-button' className='d-flex align-items-center justify-content-center m-0 p-0' >