diff --git a/docker-compose.yml b/docker-compose.yml index 494f64db0de..36bbe54ef43 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -35,6 +35,7 @@ x-e2e-env: # Cloud Manager-specific test configuration. CY_TEST_SUITE: ${CY_TEST_SUITE} CY_TEST_REGION: ${CY_TEST_REGION} + CY_TEST_FEATURE_FLAGS: ${CY_TEST_FEATURE_FLAGS} CY_TEST_TAGS: ${CY_TEST_TAGS} CY_TEST_DISABLE_RETRIES: ${CY_TEST_DISABLE_RETRIES} @@ -80,14 +81,9 @@ x-e2e-runners: context: . dockerfile: ./packages/manager/Dockerfile target: e2e - depends_on: - web: - condition: service_healthy env_file: ./packages/manager/.env volumes: *default-volumes - # TODO Stop using entrypoint, use CMD instead. - # (Or just make `yarn` the entrypoint, but either way stop forcing `cy:e2e`). - entrypoint: ['yarn', 'cy:e2e'] + entrypoint: 'yarn' services: # Serves a local instance of Cloud Manager for Cypress to use for its tests. @@ -110,6 +106,48 @@ services: timeout: 10s retries: 10 + # Cypress test runner service to run tests against a remotely-served Cloud instance. + # + # This is useful when testing against a standard Cloud Manager environment, + # like Production at cloud.linode.com, but can also be used to run tests against + # pre-Prod environments, PR preview links, and more. + cypress_remote: + <<: *default-runner + environment: + <<: *default-env + MANAGER_OAUTH: ${MANAGER_OAUTH} + + # Cypress test runner service to run tests against a locally-served Cloud instance. + # + # This is useful when testing against a customized or in-development build of + # Cloud Manager. + cypress_local: + <<: *default-runner + environment: + <<: *default-env + MANAGER_OAUTH: ${MANAGER_OAUTH} + depends_on: + web: + condition: service_healthy + + # Cypress component test runner service. + # + # Unlike other Cloud Manager Cypress tests, these tests can be run without + # requiring a Cloud Manager environment. + cypress_component: + <<: *default-runner + environment: + CY_TEST_DISABLE_RETRIES: ${CY_TEST_DISABLE_RETRIES} + CY_TEST_JUNIT_REPORT: ${CY_TEST_JUNIT_REPORT} + + + # --> ! DEPRECATION NOTICE ! <-- + # The services below this line are deprecated, and will be deleted soon. + # Don't build any pipelines or write any scripts that depend on these. + # Instead, opt to use `cypress_local` in places where you would've used `e2e`, + # use `cypress_remote` in places where you would've used `e2e_heimdall`, and + # use `cypress_component` in places where you would've used `component`. + # Generic end-to-end test runner for Cloud's primary testing pipeline. # Configured to run against a local Cloud instance. e2e: @@ -117,6 +155,7 @@ services: environment: <<: *default-env MANAGER_OAUTH: ${MANAGER_OAUTH} + entrypoint: ['yarn', 'cy:e2e'] # Component test runner. # Does not require any Cloud Manager environment to run. @@ -136,14 +175,4 @@ services: environment: <<: *default-env MANAGER_OAUTH: ${MANAGER_OAUTH} - - region-1: - build: - context: . - dockerfile: ./packages/manager/Dockerfile - target: e2e - env_file: ./packages/manager/.env - volumes: *default-volumes - environment: - <<: *default-env - MANAGER_OAUTH: ${MANAGER_OAUTH_1} + entrypoint: ['yarn', 'cy:e2e'] diff --git a/docs/development-guide/08-testing.md b/docs/development-guide/08-testing.md index 5635774c867..9ce93c1169b 100644 --- a/docs/development-guide/08-testing.md +++ b/docs/development-guide/08-testing.md @@ -191,12 +191,13 @@ Environment variables related to the general operation of the Cloud Manager Cypr | `CY_TEST_SUITE` | Name of the Cloud Manager UI test suite to run. Possible values are `core`, `region`, or `synthetic`. | `region` | Unset; defaults to `core` suite | | `CY_TEST_TAGS` | Query identifying tests that should run by specifying allowed and disallowed tags. | `method:e2e` | Unset; all tests run by default | -###### Regions -These environment variables are used by Cloud Manager's UI tests to override region selection behavior. This can be useful for testing Cloud Manager functionality against a specific region. +###### Overriding Behavior +These environment variables can be used to override some behaviors of Cloud Manager's UI tests. This can be useful when testing Cloud Manager for nonstandard or work-in-progress functionality. -| Environment Variable | Description | Example | Default | -|----------------------|-------------------------------------------------|-----------|---------------------------------------| -| `CY_TEST_REGION` | ID of region to test (as used by Linode APIv4). | `us-east` | Unset; regions are selected at random | +| Environment Variable | Description | Example | Default | +|-------------------------|-------------------------------------------------|-----------|--------------------------------------------| +| `CY_TEST_REGION` | ID of region to test (as used by Linode APIv4). | `us-east` | Unset; regions are selected at random | +| `CY_TEST_FEATURE_FLAGS` | JSON string containing feature flag data | `{}` | Unset; feature flag data is not overridden | ###### Run Splitting These environment variables facilitate splitting the Cypress run between multiple runners without the use of any third party services. This can be useful for improving Cypress test performance in some circumstances. For additional performance gains, an optional test weights file can be specified using `CY_TEST_SPLIT_RUN_WEIGHTS` (see `CY_TEST_GENWEIGHTS` to generate test weights). diff --git a/packages/manager/.changeset/pr-11088-tech-stories-1729535071709.md b/packages/manager/.changeset/pr-11088-tech-stories-1729535071709.md new file mode 100644 index 00000000000..037e8d3821f --- /dev/null +++ b/packages/manager/.changeset/pr-11088-tech-stories-1729535071709.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tech Stories +--- + +Replace 'e2e', 'e2e_heimdall', and 'component' Docker Compose services with 'cypress_local', 'cypress_remote', and 'cypress_component' ([#11088](https://github.com/linode/manager/pull/11088)) diff --git a/packages/manager/.changeset/pr-11088-tests-1729535093463.md b/packages/manager/.changeset/pr-11088-tests-1729535093463.md new file mode 100644 index 00000000000..2dc798af453 --- /dev/null +++ b/packages/manager/.changeset/pr-11088-tests-1729535093463.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tests +--- + +Allow overriding feature flags via CY_TEST_FEATURE_FLAGS environment variable ([#11088](https://github.com/linode/manager/pull/11088)) diff --git a/packages/manager/.changeset/pr-11088-tests-1729535165205.md b/packages/manager/.changeset/pr-11088-tests-1729535165205.md new file mode 100644 index 00000000000..a895a08ccd5 --- /dev/null +++ b/packages/manager/.changeset/pr-11088-tests-1729535165205.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tests +--- + +Allow pipeline Slack notifications to be customized ([#11088](https://github.com/linode/manager/pull/11088)) diff --git a/packages/manager/.changeset/pr-11088-tests-1729535197632.md b/packages/manager/.changeset/pr-11088-tests-1729535197632.md new file mode 100644 index 00000000000..24c079ce5dc --- /dev/null +++ b/packages/manager/.changeset/pr-11088-tests-1729535197632.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tests +--- + +Show PR title in Slack CI notifications ([#11088](https://github.com/linode/manager/pull/11088)) diff --git a/packages/manager/Dockerfile b/packages/manager/Dockerfile index 2cfa22aca4e..466edaf64ea 100644 --- a/packages/manager/Dockerfile +++ b/packages/manager/Dockerfile @@ -47,4 +47,3 @@ ENV CI=1 ENV NO_COLOR=1 ENV HOME=/home/node/ ENV CYPRESS_CACHE_FOLDER=/home/node/.cache/Cypress -ENTRYPOINT yarn cy:ci diff --git a/packages/manager/cypress.config.ts b/packages/manager/cypress.config.ts index 09322be48ba..b51562d9f38 100644 --- a/packages/manager/cypress.config.ts +++ b/packages/manager/cypress.config.ts @@ -17,6 +17,7 @@ import { enableJunitReport } from './cypress/support/plugins/junit-report'; import { generateTestWeights } from './cypress/support/plugins/generate-weights'; import { logTestTagInfo } from './cypress/support/plugins/test-tagging-info'; import cypressViteConfig from './cypress/vite.config'; +import { featureFlagOverrides } from './cypress/support/plugins/feature-flag-override'; /** * Exports a Cypress configuration object. @@ -91,6 +92,7 @@ export default defineConfig({ fetchAccount, fetchLinodeRegions, regionOverrideCheck, + featureFlagOverrides, logTestTagInfo, splitCypressRun, enableJunitReport(), diff --git a/packages/manager/cypress/support/constants/feature-flags.ts b/packages/manager/cypress/support/constants/feature-flags.ts new file mode 100644 index 00000000000..08e996ca15d --- /dev/null +++ b/packages/manager/cypress/support/constants/feature-flags.ts @@ -0,0 +1,11 @@ +/** + * @file Constants related to Cypress's handling of LaunchDarkly feature flags. + */ + +// LaunchDarkly URL pattern for feature flag retrieval. +export const launchDarklyUrlPattern = + 'https://app.launchdarkly.com/sdk/evalx/*/contexts/*'; + +// LaunchDarkly URL pattern for feature flag / event streaming. +export const launchDarklyClientstreamPattern = + 'https://clientstream.launchdarkly.com/eval/*/*'; diff --git a/packages/manager/cypress/support/e2e.ts b/packages/manager/cypress/support/e2e.ts index 2e21f7ca60b..5996d3d71aa 100644 --- a/packages/manager/cypress/support/e2e.ts +++ b/packages/manager/cypress/support/e2e.ts @@ -60,11 +60,13 @@ chai.use(function (chai, utils) { // Test setup. import { deleteInternalHeader } from './setup/delete-internal-header'; +import { mockFeatureFlagRequests } from './setup/mock-feature-flags-request'; import { mockFeatureFlagClientstream } from './setup/feature-flag-clientstream'; import { mockAccountRequest } from './setup/mock-account-request'; import { trackApiRequests } from './setup/request-tracking'; trackApiRequests(); mockAccountRequest(); +mockFeatureFlagRequests(); mockFeatureFlagClientstream(); deleteInternalHeader(); diff --git a/packages/manager/cypress/support/intercepts/feature-flags.ts b/packages/manager/cypress/support/intercepts/feature-flags.ts index 9bf393efb58..d77ab55f531 100644 --- a/packages/manager/cypress/support/intercepts/feature-flags.ts +++ b/packages/manager/cypress/support/intercepts/feature-flags.ts @@ -3,17 +3,13 @@ */ import { getResponseDataFromMockData } from 'support/util/feature-flags'; +import { + launchDarklyUrlPattern, + launchDarklyClientstreamPattern, +} from 'support/constants/feature-flags'; import type { FeatureFlagMockData } from 'support/util/feature-flags'; -// LaunchDarkly URL pattern for feature flag retrieval. -const launchDarklyUrlPattern = - 'https://app.launchdarkly.com/sdk/evalx/*/contexts/*'; - -// LaunchDarkly URL pattern for feature flag / event streaming. -const launchDarklyClientstreamPattern = - 'https://clientstream.launchdarkly.com/eval/*/*'; - /** * Intercepts GET request to feature flag clientstream URL and mocks the response. * diff --git a/packages/manager/cypress/support/plugins/feature-flag-override.ts b/packages/manager/cypress/support/plugins/feature-flag-override.ts new file mode 100644 index 00000000000..b8694cc9ff9 --- /dev/null +++ b/packages/manager/cypress/support/plugins/feature-flag-override.ts @@ -0,0 +1,39 @@ +import type { CypressPlugin } from './plugin'; + +/** + * Handles setup related to Launch Darkly feature flag overrides. + * + * Checks if the user has passed overrides via the `CY_TEST_FEATURE_FLAGS` env, + * and validates its value if so by attempting to parse it as JSON. If that + * succeeds, the parsed override object is exposed to Cypress via the + * `featureFlagOverrides` config. + */ +export const featureFlagOverrides: CypressPlugin = (_on, config) => { + const featureFlagOverridesJson = config.env?.['CY_TEST_FEATURE_FLAGS']; + + let featureFlagOverrides = undefined; + if (featureFlagOverridesJson) { + const notice = + 'Feature flag overrides are enabled with the following JSON payload:'; + const jsonWarning = + 'Be aware that malformed or invalid feature flag data can trigger crashes and other unexpected behavior.'; + + console.info(`${notice}\n\n${featureFlagOverridesJson}\n\n${jsonWarning}`); + + try { + featureFlagOverrides = JSON.parse(featureFlagOverridesJson); + } catch (e) { + throw new Error( + `Unable to parse feature flag JSON:\n\n${featureFlagOverridesJson}\n\nPlease double check your 'CY_TEST_FEATURE_FLAGS' value and try again.` + ); + } + } + + return { + ...config, + env: { + ...config.env, + featureFlagOverrides, + }, + }; +}; diff --git a/packages/manager/cypress/support/setup/mock-feature-flags-request.ts b/packages/manager/cypress/support/setup/mock-feature-flags-request.ts new file mode 100644 index 00000000000..2f45f33f0cf --- /dev/null +++ b/packages/manager/cypress/support/setup/mock-feature-flags-request.ts @@ -0,0 +1,37 @@ +/** + * @file Intercepts and mocks Launch Darkly feature flag requests with override data if specified. + */ + +import { launchDarklyUrlPattern } from 'support/constants/feature-flags'; + +/** + * If feature flag overrides have been specified, intercept every LaunchDarkly + * feature flag request and modify the response to contain the override data. + * + * This override happens before other intercepts and mocks (e.g. via `mockGetFeatureFlags` + * and `mockAppendFeatureFlags`), so mocks set up by those functions will take + * priority in the event that both modify the same feature flag value. + */ +export const mockFeatureFlagRequests = () => { + const featureFlagOverrides = Cypress.env('featureFlagOverrides'); + + if (featureFlagOverrides) { + beforeEach(() => { + cy.intercept( + { + middleware: true, + url: launchDarklyUrlPattern, + }, + (req) => { + req.on('before:response', (res) => { + const overriddenFeatureFlagData = { + ...res.body, + ...featureFlagOverrides, + }; + res.body = overriddenFeatureFlagData; + }); + } + ); + }); + } +}; diff --git a/scripts/junit-summary/formatters/github-formatter.ts b/scripts/junit-summary/formatters/github-formatter.ts index c1161a61cf0..d039db83bed 100644 --- a/scripts/junit-summary/formatters/github-formatter.ts +++ b/scripts/junit-summary/formatters/github-formatter.ts @@ -39,6 +39,8 @@ export const githubFormatter: Formatter = ( const breakdown = `:x: ${runInfo.failing} Failing | :green_heart: ${runInfo.passing} Passing | :arrow_right_hook: ${runInfo.skipped} Skipped | :clock1: ${secondsToTimeString(runInfo.time)}\n\n`; + const extra = metadata.extra ? `${metadata.extra}\n\n` : null; + const failedTestSummary = (() => { const heading = `### Details`; const failedTestHeader = `
Failing Tests | ||
---|---|---|
Spec | Test |