diff --git a/.buildkite/pipelines/pull_request/fleet_cypress.yml b/.buildkite/pipelines/pull_request/fleet_cypress.yml index fab4f90c5bfea..fcccdfe7a6799 100644 --- a/.buildkite/pipelines/pull_request/fleet_cypress.yml +++ b/.buildkite/pipelines/pull_request/fleet_cypress.yml @@ -4,7 +4,8 @@ steps: agents: queue: n2-4-spot depends_on: build - timeout_in_minutes: 120 + timeout_in_minutes: 50 + parallelism: 6 retry: automatic: - exit_status: '-1' diff --git a/.buildkite/pipelines/pull_request/osquery_cypress.yml b/.buildkite/pipelines/pull_request/osquery_cypress.yml index 8e8ace5ff7975..2d47091e32431 100644 --- a/.buildkite/pipelines/pull_request/osquery_cypress.yml +++ b/.buildkite/pipelines/pull_request/osquery_cypress.yml @@ -23,16 +23,3 @@ steps: artifact_paths: - "target/kibana-osquery/**/*" - - command: .buildkite/scripts/steps/functional/security_serverless_osquery.sh - label: 'Serverless Osquery Cypress Tests' - agents: - queue: n2-4-spot - depends_on: build - timeout_in_minutes: 50 - parallelism: 6 - retry: - automatic: - - exit_status: '*' - limit: 1 - artifact_paths: - - "target/kibana-osquery/**/*" diff --git a/.buildkite/scripts/steps/functional/fleet_cypress.sh b/.buildkite/scripts/steps/functional/fleet_cypress.sh index a77d912a59fff..2a9f6747d15c4 100755 --- a/.buildkite/scripts/steps/functional/fleet_cypress.sh +++ b/.buildkite/scripts/steps/functional/fleet_cypress.sh @@ -3,12 +3,14 @@ set -euo pipefail source .buildkite/scripts/steps/functional/common.sh +source .buildkite/scripts/steps/functional/common_cypress.sh export JOB=kibana-fleet-cypress +export KIBANA_INSTALL_DIR=${KIBANA_BUILD_LOCATION} echo "--- Fleet Cypress tests" -node scripts/functional_tests \ - --debug --bail \ - --kibana-install-dir "$KIBANA_BUILD_LOCATION" \ - --config x-pack/test/fleet_cypress/cli_config.ts +cd x-pack/plugins/fleet + +set +e +yarn cypress:run:reporter; status=$?; yarn junit:merge || :; exit $status diff --git a/.buildkite/scripts/steps/functional/profiling_cypress.sh b/.buildkite/scripts/steps/functional/profiling_cypress.sh index 38799acc90778..1f6fb316b77ad 100644 --- a/.buildkite/scripts/steps/functional/profiling_cypress.sh +++ b/.buildkite/scripts/steps/functional/profiling_cypress.sh @@ -13,5 +13,5 @@ echo "--- Profiling Cypress Tests" cd "$XPACK_DIR" -node plugins/profiling/scripts/test/e2e.js \ +NODE_OPTIONS=--openssl-legacy-provider node plugins/profiling/scripts/test/e2e.js \ --kibana-install-dir "$KIBANA_BUILD_LOCATION" \ \ No newline at end of file diff --git a/package.json b/package.json index 807da90338d78..7f35e37f44361 100644 --- a/package.json +++ b/package.json @@ -849,7 +849,7 @@ "del": "^6.1.0", "elastic-apm-node": "^3.49.1", "email-addresses": "^5.0.0", - "execa": "^4.0.2", + "execa": "^5.1.1", "expiry-js": "0.1.7", "extract-zip": "^2.0.1", "fast-deep-equal": "^3.1.1", @@ -1414,7 +1414,7 @@ "cssnano": "^5.1.12", "cssnano-preset-default": "^5.2.12", "csstype": "^3.0.2", - "cypress": "^12.13.0", + "cypress": "^12.17.4", "cypress-axe": "^1.4.0", "cypress-file-upload": "^5.0.8", "cypress-multi-reporters": "^1.6.3", diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/document_migrator.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/document_migrator.test.ts index 2e3a99b4b463e..43cf127a9ab19 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/document_migrator.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/document_migrator.test.ts @@ -713,6 +713,36 @@ describe('DocumentMigrator', () => { ]); }); + it('does not lose namespaces in documents with undefined namespace and defined namespaces property', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry( + { name: 'dog', namespaceType: 'multiple', convertToMultiNamespaceTypeVersion: '1.0.0' } + // no migration transforms are defined, the typeMigrationVersion will be derived from 'convertToMultiNamespaceTypeVersion' + ), + }); + migrator.prepareMigrations(); + const obj = { + id: 'mischievous', + type: 'dog', + attributes: { name: 'Ann' }, + coreMigrationVersion: kibanaVersion, + typeMigrationVersion: '0.1.0', + namespaces: ['something'], + } as SavedObjectUnsanitizedDoc; + const actual = migrator.migrateAndConvert(obj); + expect(actual).toEqual([ + { + id: 'mischievous', + type: 'dog', + attributes: { name: 'Ann' }, + coreMigrationVersion: kibanaVersion, + typeMigrationVersion: '1.0.0', + namespaces: ['something'], + }, + ]); + }); + it('does not fail when encountering documents with coreMigrationVersion higher than the latest known', () => { const migrator = new DocumentMigrator({ ...testOpts(), diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/internal_transforms.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/internal_transforms.ts index 762fa20342c49..11090badd9173 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/internal_transforms.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/document_migrator/internal_transforms.ts @@ -105,6 +105,13 @@ function convertNamespaceType(doc: SavedObjectUnsanitizedDoc) { const { namespace, ...otherAttrs } = doc; const additionalDocs: SavedObjectUnsanitizedDoc[] = []; + if (namespace == null && otherAttrs.namespaces) { + return { + additionalDocs, + transformedDoc: otherAttrs, + }; + } + // If this object exists in the default namespace, return it with the appropriate `namespaces` field without changing its ID. if (namespace === undefined) { return { diff --git a/test/scripts/jenkins_fleet_cypress.sh b/test/scripts/jenkins_fleet_cypress.sh index a6d9557812374..e43259c1c1c3f 100755 --- a/test/scripts/jenkins_fleet_cypress.sh +++ b/test/scripts/jenkins_fleet_cypress.sh @@ -5,10 +5,8 @@ source test/scripts/jenkins_test_setup_xpack.sh echo " -> Running fleet cypress tests" cd "$XPACK_DIR" -node scripts/functional_tests \ - --debug --bail \ - --kibana-install-dir "$KIBANA_INSTALL_DIR" \ - --config test/fleet_cypress/cli_config.ts +cd x-pack/plugins/fleet +yarn --cwd x-pack/plugins/fleet cypress:run echo "" echo "" diff --git a/x-pack/plugins/apm/scripts/test/e2e.js b/x-pack/plugins/apm/scripts/test/e2e.js index 5be124dbab08c..ae0c8c8e10276 100644 --- a/x-pack/plugins/apm/scripts/test/e2e.js +++ b/x-pack/plugins/apm/scripts/test/e2e.js @@ -69,7 +69,11 @@ function runTests() { return childProcess.spawnSync('node', spawnArgs, { cwd: e2eDir, - env: { ...process.env, CYPRESS_CLI_ARGS: JSON.stringify(cypressCliArgs) }, + env: { + ...process.env, + CYPRESS_CLI_ARGS: JSON.stringify(cypressCliArgs), + NODE_OPTIONS: '--openssl-legacy-provider', + }, encoding: 'utf8', stdio: 'inherit', }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/documents.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/documents.tsx index b82305b4c4d04..e197d0551253e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/documents.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/documents.tsx @@ -10,6 +10,7 @@ import React, { useEffect, useState, ChangeEvent } from 'react'; import { useActions, useValues } from 'kea'; import { + EuiCallOut, EuiFieldSearch, EuiFlexGroup, EuiFlexItem, @@ -73,7 +74,7 @@ export const SearchIndexDocuments: React.FC = () => { const { makeRequest: getDocuments } = useActions(documentLogic); const { makeRequest: getMappings } = useActions(mappingLogic); - const { data, status } = useValues(documentLogic); + const { data, status, error } = useValues(documentLogic); const { data: mappingData, status: mappingStatus } = useValues(mappingLogic); const docs = data?.results?.hits.hits ?? []; @@ -83,6 +84,8 @@ export const SearchIndexDocuments: React.FC = () => { const shouldShowAccessControlSwitcher = hasDocumentLevelSecurityFeature && productFeatures.hasDocumentLevelSecurityEnabled; + const isAccessControlIndexNotFound = + shouldShowAccessControlSwitcher && error?.body?.statusCode === 404; useEffect(() => { getDocuments({ @@ -140,11 +143,29 @@ export const SearchIndexDocuments: React.FC = () => { - {docs.length === 0 && + {isAccessControlIndexNotFound && ( + +

+ {i18n.translate('xpack.enterpriseSearch.content.searchIndex.documents.noIndex', { + defaultMessage: + "An Access Control Index won't be created until you enable document-level security and run your first access control sync.", + })} +

+
+ )} + {!isAccessControlIndexNotFound && + docs.length === 0 && i18n.translate('xpack.enterpriseSearch.content.searchIndex.documents.noMappings', { defaultMessage: 'No documents found for index', })} - {docs.length > 0 && ( + {!isAccessControlIndexNotFound && docs.length > 0 && ( { ? indexName : indexName.replace('search-', CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX); const { makeRequest: makeMappingRequest } = useActions(mappingsWithPropsApiLogic(indexToShow)); - const { data: mappingData } = useValues(mappingsWithPropsApiLogic(indexToShow)); + const { data: mappingData, error } = useValues(mappingsWithPropsApiLogic(indexToShow)); const shouldShowAccessControlSwitch = hasDocumentLevelSecurityFeature && productFeatures.hasDocumentLevelSecurityEnabled; + const isAccessControlIndexNotFound = + shouldShowAccessControlSwitch && error?.body?.statusCode === 404; + useEffect(() => { makeMappingRequest({ indexName: indexToShow }); }, [indexToShow, indexName]); @@ -76,9 +80,27 @@ export const SearchIndexIndexMappings: React.FC = () => {
)} - - {JSON.stringify(mappingData, null, 2)} - + {isAccessControlIndexNotFound ? ( + +

+ {i18n.translate('xpack.enterpriseSearch.content.searchIndex.mappings.noIndex', { + defaultMessage: + "An Access Control Index won't be created until you enable document-level security and run your first access control sync.", + })} +

+
+ ) : ( + + {JSON.stringify(mappingData, null, 2)} + + )}
diff --git a/x-pack/plugins/fleet/common/constants/epm.ts b/x-pack/plugins/fleet/common/constants/epm.ts index 52cb24271afa5..3548fee93fbf2 100644 --- a/x-pack/plugins/fleet/common/constants/epm.ts +++ b/x-pack/plugins/fleet/common/constants/epm.ts @@ -9,7 +9,7 @@ import { ElasticsearchAssetType, KibanaAssetType } from '../types/models'; export const PACKAGES_SAVED_OBJECT_TYPE = 'epm-packages'; export const ASSETS_SAVED_OBJECT_TYPE = 'epm-packages-assets'; -export const MAX_TIME_COMPLETE_INSTALL = 60000; +export const MAX_TIME_COMPLETE_INSTALL = 30 * 60 * 1000; // 30 minutes export const FLEET_SYSTEM_PACKAGE = 'system'; export const FLEET_ELASTIC_AGENT_PACKAGE = 'elastic_agent'; diff --git a/x-pack/plugins/fleet/cypress.config.ts b/x-pack/plugins/fleet/cypress.config.ts index f4002d16c7414..7c39707298c93 100644 --- a/x-pack/plugins/fleet/cypress.config.ts +++ b/x-pack/plugins/fleet/cypress.config.ts @@ -26,14 +26,16 @@ export default defineCypressConfig({ viewportWidth: 1440, screenshotOnRunFailure: true, - env: { - protocol: 'http', - hostname: 'localhost', - configport: '5601', - }, - e2e: { baseUrl: 'http://localhost:5601', + + experimentalRunAllSpecs: true, + experimentalMemoryManagement: true, + numTestsKeptInMemory: 3, + + specPattern: './cypress/e2e/**/*.cy.ts', + supportFile: './cypress/support/e2e.ts', + setupNodeEvents(on, config) { // eslint-disable-next-line @typescript-eslint/no-var-requires, @kbn/imports/no_boundary_crossing return require('./cypress/plugins')(on, config); diff --git a/x-pack/plugins/fleet/cypress/README.md b/x-pack/plugins/fleet/cypress/README.md index f18bce6eaa83d..8300ec26ede37 100644 --- a/x-pack/plugins/fleet/cypress/README.md +++ b/x-pack/plugins/fleet/cypress/README.md @@ -52,8 +52,9 @@ node scripts/build_kibana_platform_plugins # launch the cypress test runner cd x-pack/plugins/fleet -yarn cypress:run-as-ci +yarn cypress:run ``` + #### FTR + Interactive This is the preferred mode for developing new tests. @@ -67,17 +68,7 @@ node scripts/build_kibana_platform_plugins # launch the cypress test runner cd x-pack/plugins/fleet -yarn cypress:open-as-ci -``` - -Alternatively, kibana test server can be started separately, to pick up changes in UI (e.g. change in data-test-subj selector) - -``` -# launch kibana test server -node scripts/functional_tests_server --config x-pack/test/fleet_cypress/config.ts - -# launch cypress runner -node scripts/functional_test_runner --config x-pack/test/fleet_cypress/visual_config.ts +yarn cypress:open ``` Note that you can select the browser you want to use on the top right side of the interactive runner. @@ -108,7 +99,7 @@ Each file inside the screens folder represents a screen in our application. _Tasks_ are functions that may be reused across tests. -Each file inside the tasks folder represents a screen of our application. +Each file inside the tasks folder represents a screen of our application. ## Test data @@ -141,7 +132,7 @@ Note that the command will create the folder if it does not exist. ## Development Best Practices -### Clean up the state +### Clean up the state Remember to clean up the state of the test after its execution, typically with the `cleanKibana` function. Be mindful of failure scenarios, as well: if your test fails, will it leave the environment in a recoverable state? @@ -164,7 +155,7 @@ Remember that minimizing the number of times the web page is loaded, we minimize The `checkA11y({ skipFailures: false });` call uses [axe-core](https://github.com/dequelabs/axe-core) to perform a full page check for accessibility violations. -See [axe-core](https://github.com/dequelabs/axe-core)'s documentation for details on what is checked for. +See [axe-core](https://github.com/dequelabs/axe-core)'s documentation for details on what is checked for. ## Linting diff --git a/x-pack/plugins/fleet/cypress/e2e/a11y/home_page.cy.ts b/x-pack/plugins/fleet/cypress/e2e/a11y/home_page.cy.ts index 39debfc94662a..9c3779bc613fc 100644 --- a/x-pack/plugins/fleet/cypress/e2e/a11y/home_page.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/a11y/home_page.cy.ts @@ -33,11 +33,18 @@ import { AGENT_POLICY_NAME_LINK } from '../../screens/integrations'; import { cleanupAgentPolicies, unenrollAgent } from '../../tasks/cleanup'; import { setFleetServerHost } from '../../tasks/fleet_server'; +import { login } from '../../tasks/login'; +import { request } from '../../tasks/common'; + describe('Home page', () => { before(() => { setFleetServerHost('https://fleetserver:8220'); }); + beforeEach(() => { + login(); + }); + describe('Agents', () => { beforeEach(() => { navigateTo(FLEET); @@ -148,7 +155,7 @@ describe('Home page', () => { describe('Uninstall Tokens', () => { before(() => { - cy.request({ + request({ method: 'POST', url: '/api/fleet/agent_policies', body: { name: 'Agent policy for A11y test', namespace: 'default', id: 'agent-policy-a11y' }, @@ -160,7 +167,7 @@ describe('Home page', () => { cy.getBySel(UNINSTALL_TOKENS_TAB).click(); }); after(() => { - cy.request({ + request({ method: 'POST', url: '/api/fleet/agent_policies/delete', body: { agentPolicyId: 'agent-policy-a11y' }, @@ -180,6 +187,7 @@ describe('Home page', () => { describe('Data Streams', () => { before(() => { + login(); navigateTo(FLEET); cy.getBySel(DATA_STREAMS_TAB, { timeout: 15000 }).should('be.visible'); cy.getBySel(DATA_STREAMS_TAB).click(); diff --git a/x-pack/plugins/fleet/cypress/e2e/agent_binary_download_source.cy.ts b/x-pack/plugins/fleet/cypress/e2e/agent_binary_download_source.cy.ts index 095a87a28e130..e5d9f1d58529a 100644 --- a/x-pack/plugins/fleet/cypress/e2e/agent_binary_download_source.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/agent_binary_download_source.cy.ts @@ -11,13 +11,19 @@ import { AGENT_BINARY_SOURCES_FLYOUT, AGENT_POLICY_FORM, } from '../screens/fleet'; -import { cleanupDownloadSources } from '../tasks/cleanup'; +import { cleanupAgentPolicies, cleanupDownloadSources } from '../tasks/cleanup'; import { FLEET, navigateTo } from '../tasks/navigation'; import { CONFIRM_MODAL } from '../screens/navigation'; +import { request } from '../tasks/common'; +import { login } from '../tasks/login'; + describe('Agent binary download source section', () => { beforeEach(() => { cleanupDownloadSources(); + cleanupAgentPolicies(); + + login(); navigateTo(FLEET); }); @@ -72,7 +78,7 @@ describe('Agent binary download source section', () => { }); it('the download source is displayed in agent policy settings', () => { - cy.request({ + request({ method: 'POST', url: `api/fleet/agent_download_sources`, body: { @@ -82,7 +88,7 @@ describe('Agent binary download source section', () => { }, headers: { 'kbn-xsrf': 'kibana' }, }); - cy.request({ + request({ method: 'POST', url: '/api/fleet/agent_policies', body: { diff --git a/x-pack/plugins/fleet/cypress/e2e/agent_policy.cy.ts b/x-pack/plugins/fleet/cypress/e2e/agent_policy.cy.ts index 916590c05c0b6..8d9604318e129 100644 --- a/x-pack/plugins/fleet/cypress/e2e/agent_policy.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/agent_policy.cy.ts @@ -4,11 +4,14 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { TOAST_CLOSE_BTN } from '../screens/navigation'; import { setupFleetServer } from '../tasks/fleet_server'; import { AGENT_FLYOUT, AGENT_POLICY_DETAILS_PAGE } from '../screens/fleet'; +import { login } from '../tasks/login'; + describe('Edit agent policy', () => { beforeEach(() => { + login(); + cy.intercept('/api/fleet/agent_policies/policy-1', { item: { id: 'policy-1', @@ -35,7 +38,6 @@ describe('Edit agent policy', () => { it('should edit agent policy', () => { cy.visit('/app/fleet/policies/policy-1/settings'); - cy.getBySel(TOAST_CLOSE_BTN).click(); cy.get('[placeholder="Optional description"').clear().type('desc'); cy.intercept('/api/fleet/agent_policies/policy-1', { diff --git a/x-pack/plugins/fleet/cypress/e2e/agents/agent_list.cy.ts b/x-pack/plugins/fleet/cypress/e2e/agents/agent_list.cy.ts index fd85e05e51aec..9c5a564148997 100644 --- a/x-pack/plugins/fleet/cypress/e2e/agents/agent_list.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/agents/agent_list.cy.ts @@ -13,6 +13,9 @@ import { deleteFleetServerDocs, deleteAgentDocs, cleanupAgentPolicies } from '.. import type { CreateAgentPolicyRequest } from '../../../common/types'; import { setUISettings } from '../../tasks/ui_settings'; +import { request } from '../../tasks/common'; +import { login } from '../../tasks/login'; + const createAgentDocs = (kibanaVersion: string) => [ createAgentDoc('agent-1', 'policy-1'), // this agent will have upgrade available createAgentDoc('agent-2', 'policy-2', 'error', kibanaVersion), @@ -63,7 +66,7 @@ const POLICIES: Array = [ ]; function createAgentPolicy(body: CreateAgentPolicyRequest['body']) { - cy.request({ + request({ method: 'POST', url: '/api/fleet/agent_policies', headers: { 'kbn-xsrf': 'xx' }, @@ -99,11 +102,13 @@ describe('View agents list', () => { } }); after(() => { - deleteFleetServerDocs(); - deleteAgentDocs(); + deleteFleetServerDocs(true); + deleteAgentDocs(true); cleanupAgentPolicies(); }); beforeEach(() => { + login(); + cy.intercept('/api/fleet/agents/setup', { isReady: true, missing_optional_features: [], @@ -367,7 +372,6 @@ describe('View agents list', () => { cy.get('.euiModalFooter button:enabled').contains('Assign policy').click(); cy.wait('@getAgents'); assertTableIsEmpty(); - cy.pause(); // Select new policy is filters cy.getBySel(FLEET_AGENT_LIST_PAGE.POLICY_FILTER).click(); cy.get('button').contains('Agent policy 4').click(); diff --git a/x-pack/plugins/fleet/cypress/e2e/enrollment_token.cy.ts b/x-pack/plugins/fleet/cypress/e2e/enrollment_token.cy.ts index 84b910c856dfb..8a6da30ade3ed 100644 --- a/x-pack/plugins/fleet/cypress/e2e/enrollment_token.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/enrollment_token.cy.ts @@ -8,9 +8,12 @@ import { cleanupAgentPolicies } from '../tasks/cleanup'; import { ENROLLMENT_TOKENS } from '../screens/fleet'; +import { request } from '../tasks/common'; +import { login } from '../tasks/login'; + describe('Enrollment token page', () => { before(() => { - cy.request({ + request({ method: 'POST', url: '/api/fleet/agent_policies', body: { @@ -28,6 +31,10 @@ describe('Enrollment token page', () => { cleanupAgentPolicies(); }); + beforeEach(() => { + login(); + }); + it('Create new Token', () => { cy.visit('app/fleet/enrollment-tokens'); cy.getBySel(ENROLLMENT_TOKENS.CREATE_TOKEN_BUTTON).click(); diff --git a/x-pack/plugins/fleet/cypress/e2e/fleet_agent_flyout.cy.ts b/x-pack/plugins/fleet/cypress/e2e/fleet_agent_flyout.cy.ts index b907fb8ef4c79..5d610d108cf1d 100644 --- a/x-pack/plugins/fleet/cypress/e2e/fleet_agent_flyout.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/fleet_agent_flyout.cy.ts @@ -10,6 +10,9 @@ import { cleanupAgentPolicies, deleteFleetServerDocs, deleteAgentDocs } from '.. import { createAgentDoc } from '../tasks/agents'; import { setFleetServerHost } from '../tasks/fleet_server'; import { FLEET, navigateTo } from '../tasks/navigation'; +import { request } from '../tasks/common'; + +import { login } from '../tasks/login'; const FLEET_SERVER_POLICY_ID = 'fleet-server-policy'; @@ -25,7 +28,7 @@ describe('Fleet add agent flyout', () => { cleanUp(); let policyId: string; // Create a Fleet server policy - cy.request({ + request({ method: 'POST', url: '/api/fleet/agent_policies', headers: { 'kbn-xsrf': 'xx' }, @@ -59,6 +62,8 @@ describe('Fleet add agent flyout', () => { }); setFleetServerHost(); }); + + login(); }); afterEach(() => { diff --git a/x-pack/plugins/fleet/cypress/e2e/fleet_settings.cy.ts b/x-pack/plugins/fleet/cypress/e2e/fleet_settings.cy.ts index f37a4180877ea..34bf23739495b 100644 --- a/x-pack/plugins/fleet/cypress/e2e/fleet_settings.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/fleet_settings.cy.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { TOAST_CLOSE_BTN, CONFIRM_MODAL } from '../screens/navigation'; +import { CONFIRM_MODAL } from '../screens/navigation'; import { SETTINGS_SAVE_BTN, SETTINGS_OUTPUTS, @@ -14,9 +14,12 @@ import { FLEET_SERVER_SETUP, GENERATE_FLEET_SERVER_POLICY_BUTTON, } from '../screens/fleet'; +import { login } from '../tasks/login'; describe('Edit settings', () => { beforeEach(() => { + login(); + cy.intercept('/api/fleet/fleet_server_hosts', { items: [ { @@ -43,7 +46,6 @@ describe('Edit settings', () => { }); cy.visit('/app/fleet/settings'); - cy.getBySel(TOAST_CLOSE_BTN).click(); }); it('should allow to update Fleet server hosts', () => { diff --git a/x-pack/plugins/fleet/cypress/e2e/fleet_startup.cy.ts b/x-pack/plugins/fleet/cypress/e2e/fleet_startup.cy.ts index 2c90e930fcb1e..21eefd56eedea 100644 --- a/x-pack/plugins/fleet/cypress/e2e/fleet_startup.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/fleet_startup.cy.ts @@ -17,31 +17,20 @@ import { LANDING_PAGE_ADD_FLEET_SERVER_BUTTON, } from '../screens/fleet'; import { cleanupAgentPolicies, unenrollAgent } from '../tasks/cleanup'; +import { request } from '../tasks/common'; import { verifyPolicy, verifyAgentPackage, navigateToTab } from '../tasks/fleet'; -import { deleteFleetServer } from '../tasks/fleet_server'; +import { deleteFleetServer, setFleetServerHost } from '../tasks/fleet_server'; +import { login } from '../tasks/login'; import { FLEET, navigateTo } from '../tasks/navigation'; describe('Fleet startup', () => { - // skipping Fleet Server enroll, to enable, comment out runner.ts line 23 - describe.skip('Fleet Server', () => { - it('should display Add agent button and Healthy agent once Fleet Agent page loaded', () => { - navigateTo(FLEET); - cy.get('.euiBadge').contains('Healthy'); - - verifyPolicy('Fleet Server policy', ['Fleet Server']); - }); - - after(() => { - unenrollAgent(); - cleanupAgentPolicies(); - }); - }); - describe('Create policies', () => { before(() => { unenrollAgent(); cleanupAgentPolicies(); deleteFleetServer(); + + setFleetServerHost(); }); after(() => { @@ -49,11 +38,12 @@ describe('Fleet startup', () => { }); beforeEach(() => { + login(); navigateTo(FLEET); }); it('should have no agent policy by default', () => { - cy.request('/api/fleet/agent_policies?full=true').then((response: any) => { + request({ url: '/api/fleet/agent_policies?full=true' }).then((response: any) => { expect(response.body.items.length).to.equal(0); }); }); diff --git a/x-pack/plugins/fleet/cypress/e2e/install_assets.cy.ts b/x-pack/plugins/fleet/cypress/e2e/install_assets.cy.ts index 886f439a1a2ef..e22d601d06c33 100644 --- a/x-pack/plugins/fleet/cypress/e2e/install_assets.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/install_assets.cy.ts @@ -9,9 +9,12 @@ import type { Interception } from 'cypress/types/net-stubbing'; import { CONFIRM_MODAL } from '../screens/navigation'; import { SETTINGS } from '../screens/integrations'; +import { login } from '../tasks/login'; describe('Install unverified package assets', () => { beforeEach(() => { + login(); + cy.intercept('POST', '/api/fleet/epm/packages/fleet_server/*', (req) => { if (!req.body.force) { return req.reply({ diff --git a/x-pack/plugins/fleet/cypress/e2e/integrations_mock.cy.ts b/x-pack/plugins/fleet/cypress/e2e/integrations_mock.cy.ts index d40cae5b58971..0e519dc483897 100644 --- a/x-pack/plugins/fleet/cypress/e2e/integrations_mock.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/integrations_mock.cy.ts @@ -7,11 +7,16 @@ import { navigateTo } from '../tasks/navigation'; import { UPDATE_PACKAGE_BTN } from '../screens/integrations'; -import { LOADING_SPINNER, TOAST_CLOSE_BTN } from '../screens/navigation'; +import { LOADING_SPINNER } from '../screens/navigation'; import { AGENT_POLICY_SAVE_INTEGRATION } from '../screens/fleet'; import { INSTALLED_VERSION, INTEGRATION_POLICIES_UPGRADE_CHECKBOX } from '../screens/integrations'; +import { login } from '../tasks/login'; describe('Add Integration - Mock API', () => { + beforeEach(() => { + login(); + }); + describe('upgrade package and upgrade package policy', () => { const oldVersion = '0.3.3'; const newVersion = '1.3.4'; @@ -143,7 +148,6 @@ describe('Add Integration - Mock API', () => { '/app/fleet/policies/package-1/upgrade-package-policy/apache-2?from=integrations-policy-list' ); - cy.getBySel(TOAST_CLOSE_BTN).click(); cy.getBySel(AGENT_POLICY_SAVE_INTEGRATION).click(); cy.wait('@updateApachePolicy').then((interception) => { diff --git a/x-pack/plugins/fleet/cypress/e2e/integrations_real.cy.ts b/x-pack/plugins/fleet/cypress/e2e/integrations_real.cy.ts index 3e570d0e76f78..3649f5b281c66 100644 --- a/x-pack/plugins/fleet/cypress/e2e/integrations_real.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/integrations_real.cy.ts @@ -31,6 +31,8 @@ import { import { LOADING_SPINNER, CONFIRM_MODAL } from '../screens/navigation'; import { ADD_PACKAGE_POLICY_BTN } from '../screens/fleet'; import { cleanupAgentPolicies } from '../tasks/cleanup'; +import { request } from '../tasks/common'; +import { login } from '../tasks/login'; function setupIntegrations() { cy.intercept( @@ -50,30 +52,14 @@ function setupIntegrations() { cy.wait('@packages'); } -it('should install integration without policy', () => { - cy.visit('/app/integrations/detail/tomcat/settings'); - - cy.getBySel(SETTINGS.INSTALL_ASSETS_BTN).click(); - cy.get('.euiCallOut').contains('This action will install 1 assets'); - cy.getBySel(CONFIRM_MODAL.CONFIRM_BUTTON).click(); - - cy.getBySel(LOADING_SPINNER).should('not.exist'); - - cy.getBySel(SETTINGS.UNINSTALL_ASSETS_BTN).click(); - cy.getBySel(CONFIRM_MODAL.CONFIRM_BUTTON).click(); - cy.getBySel(LOADING_SPINNER).should('not.exist'); - cy.getBySel(SETTINGS.INSTALL_ASSETS_BTN).should('exist'); -}); - describe('Add Integration - Real API', () => { const integration = 'apache'; - after(() => { - deleteIntegrations(); - }); + beforeEach(() => { + login(); - afterEach(() => { cleanupAgentPolicies(); + deleteIntegrations(); }); it('should install integration without policy', () => { @@ -103,7 +89,7 @@ describe('Add Integration - Real API', () => { it('should add integration to policy', () => { const agentPolicyId = 'policy_1'; - cy.request({ + request({ method: 'POST', url: `/api/fleet/agent_policies`, body: { @@ -116,7 +102,7 @@ describe('Add Integration - Real API', () => { headers: { 'kbn-xsrf': 'cypress' }, }); - cy.request('/api/fleet/agent_policies').then((response: any) => { + request({ url: '/api/fleet/agent_policies' }).then((response: any) => { cy.visit(`/app/fleet/policies/${agentPolicyId}`); cy.intercept( diff --git a/x-pack/plugins/fleet/cypress/e2e/package_policy.cy.ts b/x-pack/plugins/fleet/cypress/e2e/package_policy.cy.ts index 8b1569574c4e7..0802f5e985dd6 100644 --- a/x-pack/plugins/fleet/cypress/e2e/package_policy.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/package_policy.cy.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { TOAST_CLOSE_BTN } from '../screens/navigation'; +import { login } from '../tasks/login'; describe('Edit package policy', () => { const policyConfig = { @@ -32,6 +32,8 @@ describe('Edit package policy', () => { ], }; beforeEach(() => { + login(); + cy.intercept('/api/fleet/package_policies/policy-1', { item: policyConfig, }); @@ -110,7 +112,6 @@ describe('Edit package policy', () => { it('should edit package policy', () => { cy.visit('/app/fleet/policies/fleet-server-policy/edit-integration/policy-1'); - cy.getBySel(TOAST_CLOSE_BTN).click(); cy.getBySel('packagePolicyDescriptionInput').clear().type('desc'); cy.intercept('PUT', '/api/fleet/package_policies/policy-1', { diff --git a/x-pack/plugins/fleet/cypress/e2e/package_policy_pipelines_and_mappings_real.cy.ts b/x-pack/plugins/fleet/cypress/e2e/package_policy_pipelines_and_mappings_real.cy.ts index 3fed3fef1d94b..b8c43224ffb4a 100644 --- a/x-pack/plugins/fleet/cypress/e2e/package_policy_pipelines_and_mappings_real.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/package_policy_pipelines_and_mappings_real.cy.ts @@ -17,7 +17,15 @@ const INPUT_TEST_PACKAGE = 'input_package-1.0.0'; const INTEGRATION_TEST_PACKAGE = 'logs_integration-1.0.0'; const INTEGRATION_TEST_PACKAGE_NO_DATASET = 'logs_int_no_dataset-1.0.0'; +import { request } from '../tasks/common'; +import { login } from '../tasks/login'; +import { cleanupAgentPolicies } from '../tasks/cleanup'; + describe('Input package create and edit package policy', () => { + beforeEach(() => { + login(); + }); + const agentPolicyId = 'test-input-package-policy'; const agentPolicyName = 'Test input package policy'; const packagePolicyName = 'input-package-policy'; @@ -35,7 +43,7 @@ describe('Input package create and edit package policy', () => { before(() => { cy.task('installTestPackage', INPUT_TEST_PACKAGE); - cy.request({ + request({ method: 'POST', url: `/api/fleet/agent_policies`, body: { @@ -48,18 +56,12 @@ describe('Input package create and edit package policy', () => { headers: { 'kbn-xsrf': 'cypress' }, }); }); + after(() => { - // delete agent policy - cy.request({ - method: 'POST', - url: `/api/fleet/agent_policies/delete`, - headers: { 'kbn-xsrf': 'cypress' }, - body: JSON.stringify({ - agentPolicyId, - }), - }); + cleanupAgentPolicies(); cy.task('uninstallTestPackage', INPUT_TEST_PACKAGE); }); + it('should successfully create a package policy', () => { cy.visit(`/app/integrations/detail/${INPUT_TEST_PACKAGE}/overview`); cy.getBySel(ADD_INTEGRATION_POLICY_BTN).click(); @@ -102,6 +104,10 @@ describe('Input package create and edit package policy', () => { }); describe('Integration package with custom dataset create and edit package policy', () => { + beforeEach(() => { + login(); + }); + const agentPolicyId = 'test-logs-integration-package-policy'; const agentPolicyName = 'Test integration with custom dataset package policy'; const packagePolicyName = 'logs-integration-package-policy'; @@ -110,7 +116,7 @@ describe('Integration package with custom dataset create and edit package policy before(() => { cy.task('installTestPackage', INTEGRATION_TEST_PACKAGE); - cy.request({ + request({ method: 'POST', url: `/api/fleet/agent_policies`, body: { @@ -123,18 +129,12 @@ describe('Integration package with custom dataset create and edit package policy headers: { 'kbn-xsrf': 'cypress' }, }); }); + after(() => { - // delete agent policy - cy.request({ - method: 'POST', - url: `/api/fleet/agent_policies/delete`, - headers: { 'kbn-xsrf': 'cypress' }, - body: JSON.stringify({ - agentPolicyId, - }), - }); + cleanupAgentPolicies(); cy.task('uninstallTestPackage', INTEGRATION_TEST_PACKAGE); }); + it('should successfully create a package policy', () => { cy.visit(`/app/integrations/detail/${INTEGRATION_TEST_PACKAGE}/overview`); cy.getBySel(ADD_INTEGRATION_POLICY_BTN).click(); @@ -167,6 +167,10 @@ describe('Integration package with custom dataset create and edit package policy }); describe('Integration package with fixed dataset create and edit package policy', () => { + beforeEach(() => { + login(); + }); + const agentPolicyId = 'test-integration-package-policy'; const agentPolicyName = 'Test integration package policy'; const packagePolicyName = 'integration-package-policy'; @@ -174,7 +178,7 @@ describe('Integration package with fixed dataset create and edit package policy' before(() => { cy.task('installTestPackage', INTEGRATION_TEST_PACKAGE_NO_DATASET); - cy.request({ + request({ method: 'POST', url: `/api/fleet/agent_policies`, body: { @@ -187,18 +191,12 @@ describe('Integration package with fixed dataset create and edit package policy' headers: { 'kbn-xsrf': 'cypress' }, }); }); + after(() => { - // delete agent policy - cy.request({ - method: 'POST', - url: `/api/fleet/agent_policies/delete`, - headers: { 'kbn-xsrf': 'cypress' }, - body: JSON.stringify({ - agentPolicyId, - }), - }); + cleanupAgentPolicies(); cy.task('uninstallTestPackage', INTEGRATION_TEST_PACKAGE_NO_DATASET); }); + it('should successfully create a package policy', () => { cy.visit(`/app/integrations/detail/${INTEGRATION_TEST_PACKAGE_NO_DATASET}/overview`); cy.getBySel(ADD_INTEGRATION_POLICY_BTN).click(); diff --git a/x-pack/plugins/fleet/cypress/e2e/privileges_editor_role.cy.ts b/x-pack/plugins/fleet/cypress/e2e/privileges_editor_role.cy.ts index 1fab19c3f018a..1d49b2744d32c 100644 --- a/x-pack/plugins/fleet/cypress/e2e/privileges_editor_role.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/privileges_editor_role.cy.ts @@ -7,7 +7,7 @@ import { FLEET, INTEGRATIONS, navigateTo } from '../tasks/navigation'; import { createUsers, BuiltInEditorUser, deleteUsers } from '../tasks/privileges'; -import { loginWithUserAndWaitForPage, logout } from '../tasks/login'; +import { login, loginWithUserAndWaitForPage, logout } from '../tasks/login'; import { getIntegrationCard } from '../screens/integrations'; @@ -26,6 +26,10 @@ describe('When the user has Editor built-in role', () => { createUsers(usersToCreate); }); + beforeEach(() => { + login(); + }); + afterEach(() => { logout(); }); diff --git a/x-pack/plugins/fleet/cypress/e2e/privileges_fleet_all_integrations_none.cy.ts b/x-pack/plugins/fleet/cypress/e2e/privileges_fleet_all_integrations_none.cy.ts index 53e6122d75a00..eaae2c3a7e01c 100644 --- a/x-pack/plugins/fleet/cypress/e2e/privileges_fleet_all_integrations_none.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/privileges_fleet_all_integrations_none.cy.ts @@ -12,7 +12,7 @@ import { FleetAllIntegrNoneUser, deleteUsersAndRoles, } from '../tasks/privileges'; -import { loginWithUserAndWaitForPage, logout } from '../tasks/login'; +import { login, loginWithUserAndWaitForPage, logout } from '../tasks/login'; import { MISSING_PRIVILEGES } from '../screens/fleet'; const rolesToCreate = [FleetAllIntegrNoneRole]; @@ -23,6 +23,10 @@ describe('When the user has All privilege for Fleet but None for integrations', createUsersAndRoles(usersToCreate, rolesToCreate); }); + beforeEach(() => { + login(); + }); + afterEach(() => { logout(); }); diff --git a/x-pack/plugins/fleet/cypress/e2e/privileges_fleet_all_integrations_read.cy.ts b/x-pack/plugins/fleet/cypress/e2e/privileges_fleet_all_integrations_read.cy.ts index 6f1e905e29b63..f7483bc6ed894 100644 --- a/x-pack/plugins/fleet/cypress/e2e/privileges_fleet_all_integrations_read.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/privileges_fleet_all_integrations_read.cy.ts @@ -12,7 +12,7 @@ import { FleetAllIntegrReadUser, deleteUsersAndRoles, } from '../tasks/privileges'; -import { loginWithUserAndWaitForPage, logout } from '../tasks/login'; +import { login, loginWithUserAndWaitForPage, logout } from '../tasks/login'; import { navigateToTab, createAgentPolicy } from '../tasks/fleet'; import { cleanupAgentPolicies, unenrollAgent } from '../tasks/cleanup'; import { getIntegrationCard } from '../screens/integrations'; @@ -31,6 +31,7 @@ const usersToCreate = [FleetAllIntegrReadUser]; describe('When the user has All privilege for Fleet but Read for integrations', () => { before(() => { + login(); createUsersAndRoles(usersToCreate, rolesToCreate); }); diff --git a/x-pack/plugins/fleet/cypress/e2e/privileges_viewer_role.cy.ts b/x-pack/plugins/fleet/cypress/e2e/privileges_viewer_role.cy.ts index 25a5ea23caf16..b477b2e068b77 100644 --- a/x-pack/plugins/fleet/cypress/e2e/privileges_viewer_role.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/privileges_viewer_role.cy.ts @@ -7,7 +7,7 @@ import { FLEET, INTEGRATIONS } from '../tasks/navigation'; import { createUsers, BuiltInViewerUser, deleteUsers } from '../tasks/privileges'; -import { loginWithUserAndWaitForPage, logout } from '../tasks/login'; +import { login, loginWithUserAndWaitForPage, logout } from '../tasks/login'; import { getIntegrationCard } from '../screens/integrations'; @@ -22,6 +22,10 @@ describe('When the user has Viewer built-in role', () => { createUsers(usersToCreate); }); + beforeEach(() => { + login(); + }); + afterEach(() => { logout(); }); diff --git a/x-pack/plugins/fleet/cypress/e2e/uninstall_token.cy.ts b/x-pack/plugins/fleet/cypress/e2e/uninstall_token.cy.ts index 212e7aa0b3a9e..a05acc82aa988 100644 --- a/x-pack/plugins/fleet/cypress/e2e/uninstall_token.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/uninstall_token.cy.ts @@ -11,6 +11,9 @@ import { cleanupAgentPolicies } from '../tasks/cleanup'; import { UNINSTALL_TOKENS } from '../screens/fleet'; import type { GetUninstallTokenResponse } from '../../common/types/rest_spec/uninstall_token'; +import { request } from '../tasks/common'; +import { login } from '../tasks/login'; + describe('Uninstall token page', () => { before(() => { cleanupAgentPolicies(); @@ -18,6 +21,8 @@ describe('Uninstall token page', () => { }); beforeEach(() => { + login(); + cy.visit('app/fleet/uninstall-tokens'); cy.intercept('GET', 'api/fleet/uninstall_tokens/*').as('getTokenRequest'); @@ -74,7 +79,7 @@ describe('Uninstall token page', () => { const generatePolicies = () => { for (let i = 1; i <= 3; i++) { - cy.request({ + request({ method: 'POST', url: '/api/fleet/agent_policies', body: { name: `Agent policy ${i}00`, namespace: 'default', id: `agent-policy-${i}00` }, diff --git a/x-pack/plugins/fleet/cypress/plugins/index.ts b/x-pack/plugins/fleet/cypress/plugins/index.ts index ee01dd20c470c..4b5a843f4a957 100644 --- a/x-pack/plugins/fleet/cypress/plugins/index.ts +++ b/x-pack/plugins/fleet/cypress/plugins/index.ts @@ -63,6 +63,7 @@ const plugin: Cypress.PluginConfig = (on, config) => { index, query, ignore_unavailable: ignoreUnavailable, + allow_no_indices: true, refresh: true, conflicts: 'proceed', }); diff --git a/x-pack/plugins/fleet/cypress/screens/fleet_outputs.ts b/x-pack/plugins/fleet/cypress/screens/fleet_outputs.ts index 0e018cd301d1b..8e7121877b8bf 100644 --- a/x-pack/plugins/fleet/cypress/screens/fleet_outputs.ts +++ b/x-pack/plugins/fleet/cypress/screens/fleet_outputs.ts @@ -5,9 +5,7 @@ * 2.0. */ -import { request } from '@kbn/osquery-plugin/cypress/tasks/common'; - -import { visit } from '../tasks/common'; +import { request, visit } from '../tasks/common'; import { getSpecificSelectorId, @@ -39,10 +37,9 @@ export const interceptOutputId = (cb: (caseId: string) => void) => { }; export const cleanupOutput = (outputId: string) => { - cy.request({ + request({ method: 'DELETE', url: `/api/fleet/outputs/${outputId}`, - headers: { 'kbn-xsrf': 'xx' }, }); }; @@ -51,7 +48,6 @@ const loadOutput = (body: Record) => method: 'POST', body, url: `/api/fleet/outputs`, - headers: { 'kbn-xsrf': 'xx' }, }).then((response) => response.body); export const kafkaOutputBody = { diff --git a/x-pack/plugins/fleet/cypress/support/e2e.ts b/x-pack/plugins/fleet/cypress/support/e2e.ts index 9e570cbe0c932..f7a0d664bddfc 100644 --- a/x-pack/plugins/fleet/cypress/support/e2e.ts +++ b/x-pack/plugins/fleet/cypress/support/e2e.ts @@ -23,6 +23,7 @@ // *********************************************************** // Import commands.js using ES2015 syntax: +import { request } from '../tasks/common'; import './commands'; declare global { @@ -40,7 +41,7 @@ function getBySel(selector: string, ...args: any[]) { } function getKibanaVersion() { - return cy.request('/api/status').then(({ body }) => { + return request<{ version: { number: string } }>({ url: '/api/status' }).then(({ body }) => { return body.version.number; }); } diff --git a/x-pack/plugins/fleet/cypress/tasks/cleanup.ts b/x-pack/plugins/fleet/cypress/tasks/cleanup.ts index 2a1e57271f08a..20150366184cf 100644 --- a/x-pack/plugins/fleet/cypress/tasks/cleanup.ts +++ b/x-pack/plugins/fleet/cypress/tasks/cleanup.ts @@ -5,48 +5,63 @@ * 2.0. */ +import { DEFAULT_DOWNLOAD_SOURCE_ID, DEFAULT_DOWNLOAD_SOURCE_URI } from '../../common/constants'; + +import { request } from './common'; + export function cleanupAgentPolicies() { - cy.request('/api/fleet/agent_policies').then((response: any) => { + request({ url: '/api/fleet/agent_policies' }).then((response: any) => { response.body.items .filter((policy: any) => policy.agents === 0) .forEach((policy: any) => { - cy.request({ + request({ method: 'POST', url: '/api/fleet/agent_policies/delete', body: { agentPolicyId: policy.id }, - headers: { 'kbn-xsrf': 'kibana' }, }); }); }); } export function unenrollAgent() { - cy.request('/api/fleet/agents?page=1&perPage=20&showInactive=false&showUpgradeable=false').then( - (response: any) => { - response.body.items.forEach((agent: any) => { - cy.request({ - method: 'POST', - url: `api/fleet/agents/${agent.id}/unenroll`, - body: { revoke: true }, - headers: { 'kbn-xsrf': 'kibana' }, - }); + request({ + url: '/api/fleet/agents?page=1&perPage=20&showInactive=false&showUpgradeable=false', + }).then((response: any) => { + response.body.items.forEach((agent: any) => { + request({ + method: 'POST', + url: `api/fleet/agents/${agent.id}/unenroll`, + body: { revoke: true }, }); - } - ); + }); + }); } export function cleanupDownloadSources() { - cy.request('/api/fleet/agent_download_sources').then((response: any) => { - response.body.items - .filter((ds: any) => !ds.is_default) - .forEach((ds: any) => { - cy.request({ - method: 'DELETE', - url: `/api/fleet/agent_download_sources/${ds.id}`, - headers: { 'kbn-xsrf': 'kibana' }, + request({ url: '/api/fleet/agent_download_sources' }) + .then((response: any) => { + response.body.items + .filter((ds: any) => !ds.is_default) + .forEach((ds: any) => { + request({ + method: 'DELETE', + url: `/api/fleet/agent_download_sources/${ds.id}`, + }); }); - }); - }); + }) + .then(() => + // Restore the default download source to its original state + request({ + url: `/api/fleet/agent_download_sources/${DEFAULT_DOWNLOAD_SOURCE_ID}`, + method: 'PUT', + // Ignore 404's, etc, when no download source exists + failOnStatusCode: false, + body: { + name: 'Elastic Artifacts', + host: DEFAULT_DOWNLOAD_SOURCE_URI, + }, + }) + ); } export function deleteFleetServerDocs(ignoreUnavailable: boolean = false) { diff --git a/x-pack/plugins/fleet/cypress/tasks/common.ts b/x-pack/plugins/fleet/cypress/tasks/common.ts index dffeb93fd914d..55de5f51d388f 100644 --- a/x-pack/plugins/fleet/cypress/tasks/common.ts +++ b/x-pack/plugins/fleet/cypress/tasks/common.ts @@ -14,6 +14,29 @@ import { getUrlWithRoute } from './login'; const LOADING_INDICATOR = '[data-test-subj="globalLoadingIndicator"]'; const LOADING_INDICATOR_HIDDEN = '[data-test-subj="globalLoadingIndicator-hidden"]'; +// Grab username + password from environment variables +export const API_AUTH = Object.freeze({ + user: Cypress.env('KIBANA_USERNAME') ?? Cypress.env('ELASTICSEARCH_USERNAME'), + pass: Cypress.env('KIBANA_PASSWORD') ?? Cypress.env('ELASTICSEARCH_PASSWORD'), +}); + +export const COMMON_API_HEADERS = Object.freeze({ + 'kbn-xsrf': 'cypress', + 'x-elastic-internal-origin': 'fleet', +}); + +// Replaces request - adds baseline authentication + global headers +export const request = ({ + headers, + ...options +}: Partial): Cypress.Chainable> => { + return cy.request({ + auth: API_AUTH, + headers: { ...COMMON_API_HEADERS, ...headers }, + ...options, + }); +}; + /** * For all the new features tours we show in the app, this method disables them * by setting their configs in the local storage. It prevents the tours from appearing diff --git a/x-pack/plugins/fleet/cypress/tasks/fleet_server.ts b/x-pack/plugins/fleet/cypress/tasks/fleet_server.ts index 2b696f7d7edfb..3cb3f85212403 100644 --- a/x-pack/plugins/fleet/cypress/tasks/fleet_server.ts +++ b/x-pack/plugins/fleet/cypress/tasks/fleet_server.ts @@ -5,6 +5,7 @@ * 2.0. */ import { createAgentDoc } from './agents'; +import { request } from './common'; const FLEET_SERVER_POLICY_ID = 'fleet-server-policy'; @@ -13,7 +14,7 @@ export async function setupFleetServer() { const policyId: string = FLEET_SERVER_POLICY_ID; let kibanaVersion: string; - cy.request({ + request({ method: 'POST', url: '/api/fleet/agent_policies', headers: { 'kbn-xsrf': 'xx' }, @@ -28,7 +29,7 @@ export async function setupFleetServer() { // 409 is expected if the policy already exists // this allows the test to be run repeatedly in dev if (response.status > 299 && response.status !== 409) { - throw new Error(`Failed to create Fleet Server policy: ${response.body.message}`); + throw new Error(`Failed to create Fleet Server policy: ${JSON.stringify(response.body)}`); } }); @@ -63,10 +64,9 @@ export function deleteFleetServer() { } export function setFleetServerHost(host = 'https://fleetserver:8220') { - cy.request({ + request({ method: 'POST', url: '/api/fleet/fleet_server_hosts', - headers: { 'kbn-xsrf': 'xx' }, body: { name: 'Default host', host_urls: [host], diff --git a/x-pack/plugins/fleet/cypress/tasks/integrations.ts b/x-pack/plugins/fleet/cypress/tasks/integrations.ts index 71a8c3cd2f9a7..5af5d95a38525 100644 --- a/x-pack/plugins/fleet/cypress/tasks/integrations.ts +++ b/x-pack/plugins/fleet/cypress/tasks/integrations.ts @@ -12,7 +12,9 @@ import { } from '../screens/integrations'; import { AGENT_POLICY_SYSTEM_MONITORING_CHECKBOX, EXISTING_HOSTS_TAB } from '../screens/fleet'; -import { TOAST_CLOSE_BTN, CONFIRM_MODAL } from '../screens/navigation'; +import { CONFIRM_MODAL } from '../screens/navigation'; + +import { request } from './common'; export const addIntegration = ({ useExistingPolicy } = { useExistingPolicy: false }) => { cy.getBySel(ADD_INTEGRATION_POLICY_BTN).click(); @@ -30,7 +32,6 @@ export const addIntegration = ({ useExistingPolicy } = { useExistingPolicy: fals force: true, }); } - cy.getBySel(TOAST_CLOSE_BTN).click(); cy.getBySel(CREATE_PACKAGE_POLICY_SAVE_BTN).click(); // sometimes agent is assigned to default policy, sometimes not cy.getBySel(CONFIRM_MODAL.CONFIRM_BUTTON).click(); @@ -47,23 +48,37 @@ export function clickIfVisible(selector: string) { }); } -export const deleteIntegrations = async () => { - const ids: string[] = []; - cy.request('/api/fleet/package_policies').then((response: any) => { - response.body.items.forEach((policy: any) => ids.push(policy.id)); - cy.request({ - url: `/api/fleet/package_policies/delete`, - headers: { 'kbn-xsrf': 'cypress' }, - body: `{ "packagePolicyIds": ${JSON.stringify(ids)}, "force": true }`, - method: 'POST', +export const deleteIntegrations = () => { + request({ url: '/api/fleet/package_policies' }) + .then((packagePoliciesResponse: any) => { + const packagePolicyIds = packagePoliciesResponse.body.items.map((policy: any) => policy.id); + + request({ + url: `/api/fleet/package_policies/delete`, + body: `{ "packagePolicyIds": ${JSON.stringify(packagePolicyIds)}, "force": true }`, + method: 'POST', + }); + }) + .then(() => { + request({ url: '/api/fleet/epm/packages' }).then((packagesResponse: any) => { + for (const pkg of packagesResponse.body.items.filter( + (item: any) => item.status === 'installed' + )) { + request({ + url: `/api/fleet/epm/packages/${pkg.name}/${pkg.version}`, + method: 'DELETE', + body: { + force: true, + }, + }); + } + }); }); - }); }; export const installPackageWithVersion = (integration: string, version: string) => { - cy.request({ + request({ url: `/api/fleet/epm/packages/${integration}/${version}`, - headers: { 'kbn-xsrf': 'cypress' }, body: '{ "force": true }', method: 'POST', }); diff --git a/x-pack/plugins/fleet/cypress/tasks/login.ts b/x-pack/plugins/fleet/cypress/tasks/login.ts index 2df7b88f1607b..1e50fff48bb90 100644 --- a/x-pack/plugins/fleet/cypress/tasks/login.ts +++ b/x-pack/plugins/fleet/cypress/tasks/login.ts @@ -12,6 +12,7 @@ import * as yaml from 'js-yaml'; import type { ROLES } from './privileges'; import { hostDetailsUrl, LOGOUT_URL } from './navigation'; +import { request } from './common'; /** * Credentials in the `kibana.dev.yml` config file will be used to authenticate @@ -135,7 +136,7 @@ export const deleteRoleAndUser = (role: ROLES) => { }; export const loginWithUser = (user: User) => { - cy.request({ + request({ body: { providerType: 'basic', providerName: 'basic', @@ -145,7 +146,6 @@ export const loginWithUser = (user: User) => { password: user.password, }, }, - headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, method: 'POST', url: constructUrlWithUser(user, LOGIN_API_ENDPOINT), }); @@ -162,7 +162,7 @@ export const loginWithRole = async (role: ROLES) => { port: Cypress.env('configport'), } as UrlObject); cy.log(`origin: ${theUrl}`); - cy.request({ + request({ body: { providerType: 'basic', providerName: 'basic', @@ -200,8 +200,9 @@ export const login = (role?: ROLES) => { * Returns `true` if the credentials used to login to Kibana are provided * via environment variables */ -const credentialsProvidedByEnvironment = (): boolean => - Cypress.env(ELASTICSEARCH_USERNAME) != null && Cypress.env(ELASTICSEARCH_PASSWORD) != null; +const credentialsProvidedByEnvironment = (): boolean => { + return Cypress.env(ELASTICSEARCH_USERNAME) != null && Cypress.env(ELASTICSEARCH_PASSWORD) != null; +}; /** * Authenticates with Kibana by reading credentials from the @@ -215,7 +216,7 @@ const loginViaEnvironmentCredentials = () => { ); // programmatically authenticate without interacting with the Kibana login page - cy.request({ + request({ body: { providerType: 'basic', providerName: 'basic', @@ -246,7 +247,7 @@ const loginViaConfig = () => { const config = yaml.safeLoad(kibanaDevYml); // programmatically authenticate without interacting with the Kibana login page - cy.request({ + request({ body: { providerType: 'basic', providerName: 'basic', diff --git a/x-pack/plugins/fleet/cypress/tasks/privileges.ts b/x-pack/plugins/fleet/cypress/tasks/privileges.ts index 06dcf4018d1a7..da73f27e459b7 100644 --- a/x-pack/plugins/fleet/cypress/tasks/privileges.ts +++ b/x-pack/plugins/fleet/cypress/tasks/privileges.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { request } from './common'; import { constructUrlWithUser, getEnvAuth } from './login'; interface User { @@ -186,7 +187,7 @@ export const createRoles = (roles: Role[]) => { const envUser = getEnvAuth(); for (const role of roles) { cy.log(`Creating role: ${JSON.stringify(role)}`); - cy.request({ + request({ body: role.privileges, headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, method: 'PUT', @@ -202,7 +203,7 @@ export const deleteRoles = (roles: Role[]) => { for (const role of roles) { cy.log(`Deleting role: ${JSON.stringify(role)}`); - cy.request({ + request({ headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, method: 'DELETE', url: constructUrlWithUser(envUser, `/api/security/role/${role.name}`), @@ -221,7 +222,7 @@ export const createUsers = (users: User[]) => { for (const user of users) { const userInfo = getUserInfo(user); cy.log(`Creating user: ${JSON.stringify(user)}`); - cy.request({ + request({ body: { username: user.username, password: user.password, @@ -242,7 +243,7 @@ export const deleteUsers = (users: User[]) => { const envUser = getEnvAuth(); for (const user of users) { cy.log(`Deleting user: ${JSON.stringify(user)}`); - cy.request({ + request({ headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, method: 'DELETE', url: constructUrlWithUser(envUser, `/internal/security/users/${user.username}`), diff --git a/x-pack/plugins/fleet/cypress/tasks/ui_settings.ts b/x-pack/plugins/fleet/cypress/tasks/ui_settings.ts index fceffad66c41e..e0d28a0256500 100644 --- a/x-pack/plugins/fleet/cypress/tasks/ui_settings.ts +++ b/x-pack/plugins/fleet/cypress/tasks/ui_settings.ts @@ -5,9 +5,11 @@ * 2.0. */ +import { request } from './common'; + // Create a Fleet server policy export function setUISettings(settingsKey: string, settingsValue: any) { - cy.request({ + request({ method: 'POST', url: '/internal/kibana/settings', headers: { 'kbn-xsrf': 'xx' }, diff --git a/x-pack/plugins/fleet/cypress/tsconfig.json b/x-pack/plugins/fleet/cypress/tsconfig.json index c1aa789f67c58..28fe4b5114243 100644 --- a/x-pack/plugins/fleet/cypress/tsconfig.json +++ b/x-pack/plugins/fleet/cypress/tsconfig.json @@ -28,6 +28,5 @@ "force": true }, "@kbn/rison", - "@kbn/osquery-plugin/cypress" ] } diff --git a/x-pack/plugins/fleet/package.json b/x-pack/plugins/fleet/package.json index d7687fbac90dc..3e20162ab1d91 100644 --- a/x-pack/plugins/fleet/package.json +++ b/x-pack/plugins/fleet/package.json @@ -5,12 +5,10 @@ "private": true, "license": "Elastic License 2.0", "scripts": { - "cypress": "../../../node_modules/.bin/cypress", - "cypress:open": "yarn cypress open --config-file ./cypress.config.ts", - "cypress:open-as-ci": "node ../../../scripts/functional_tests --config ../../test/fleet_cypress/visual_config.ts", - "cypress:run": "yarn cypress:run:reporter --browser chrome --spec './cypress/e2e/**/*.cy.ts'; status=$?; yarn junit:merge && exit $status", - "cypress:run-as-ci": "node ../../../scripts/functional_tests --config ../../test/fleet_cypress/cli_config.ts", - "cypress:run:reporter": "yarn cypress run --config-file ./cypress.config.ts --reporter ../../../node_modules/cypress-multi-reporters --reporter-options configFile=./cypress/reporter_config.json", + "cypress": "NODE_OPTIONS=--openssl-legacy-provider node ../security_solution/scripts/start_cypress_parallel --config-file ../fleet/cypress.config.ts --ftr-config-file ../../../x-pack/test/fleet_cypress/cli_config", + "cypress:open": "yarn cypress open", + "cypress:run": "yarn cypress run", + "cypress:run:reporter": "yarn cypress run --reporter ../../../node_modules/cypress-multi-reporters --reporter-options configFile=../fleet/cypress/reporter_config.json", "junit:merge": "../../../node_modules/.bin/mochawesome-merge ../../../target/kibana-fleet/cypress/results/mochawesome*.json > ../../../target/kibana-fleet/cypress/results/output.json && ../../../node_modules/.bin/marge ../../../target/kibana-fleet/cypress/results/output.json --reportDir ../../../target/kibana-fleet/cypress/results && mkdir -p ../../../target/junit && cp ../../../target/kibana-fleet/cypress/results/*.xml ../../../target/junit/", "openapi:build": "npx @redocly/openapi-cli bundle --ext yaml --output ./common/openapi/bundled.yaml ./common/openapi/entrypoint.yaml && npx @redocly/openapi-cli bundle --ext json --output ./common/openapi/bundled.json ./common/openapi/entrypoint.yaml", "openapi:lint": "npx @redocly/cli lint ./common/openapi/bundled.yaml", diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agents_selection_status.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agents_selection_status.test.tsx new file mode 100644 index 0000000000000..07f42b65fdb0c --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agents_selection_status.test.tsx @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { createFleetTestRendererMock } from '../../../../../../mock'; + +import { AgentsSelectionStatus } from './agents_selection_status'; + +function render(props: any) { + const renderer = createFleetTestRendererMock(); + + return renderer.render(); +} + +const defaultProps = { + totalAgents: 30, + selectableAgents: 20, + managedAgentsOnCurrentPage: 0, + selectionMode: 'manual', + setSelectionMode: jest.fn(), + selectedAgents: [], + setSelectedAgents: jest.fn(), +}; + +function generateAgents(n: number) { + return [...Array(n).keys()].map((i) => ({ + id: `agent${i}`, + active: true, + })); +} + +describe('AgentsSelectionStatus', () => { + describe('when selection mode is manual', () => { + describe('when there are no selected agents', () => { + it('should not show any selection options', () => { + const res = render(defaultProps); + expect(res.queryByTestId('selectedAgentCountLabel')).toBeNull(); + expect(res.queryByTestId('clearAgentSelectionButton')).toBeNull(); + expect(res.queryByTestId('selectedEverythingOnAllPagesButton')).toBeNull(); + }); + }); + + describe('when there are selected agents', () => { + it('should show the number of selected agents and the Clear selection button', () => { + const res = render({ ...defaultProps, selectedAgents: generateAgents(2) }); + expect(res.queryByTestId('selectedAgentCountLabel')).not.toBeNull(); + expect(res.queryByTestId('clearAgentSelectionButton')).not.toBeNull(); + }); + + it('should not show the Select everything on all pages button if not all agents are selected', () => { + const res = render({ ...defaultProps, selectedAgents: generateAgents(2) }); + expect(res.queryByTestId('selectedEverythingOnAllPagesButton')).toBeNull(); + }); + + it('should not show the Select everything on all pages button if all agents are selected but there are no more selectable agents', () => { + const res = render({ + ...defaultProps, + totalAgents: 20, + selectableAgents: 19, + managedAgentsOnCurrentPage: 1, + selectedAgents: generateAgents(19), + }); + expect(res.queryByTestId('selectedEverythingOnAllPagesButton')).toBeNull(); + }); + + it('should show the Select everything on all pages button if all agents are selected and there are more selectable agents', () => { + const res = render({ ...defaultProps, selectedAgents: generateAgents(20) }); + expect(res.queryByTestId('selectedEverythingOnAllPagesButton')).not.toBeNull(); + }); + }); + }); + + describe('when selection mode is query', () => { + describe('when there are agents', () => { + it('should show the number of selected agents and the Clear selection button', () => { + const res = render({ + ...defaultProps, + selectionMode: 'query', + selectedAgents: generateAgents(2), + }); + expect(res.queryByTestId('selectedAgentCountLabel')).not.toBeNull(); + expect(res.queryByTestId('clearAgentSelectionButton')).not.toBeNull(); + }); + + it('should not show the Select everything on all pages button', () => { + const res = render({ + ...defaultProps, + selectionMode: 'query', + selectedAgents: generateAgents(20), + }); + expect(res.queryByTestId('selectedEverythingOnAllPagesButton')).toBeNull(); + }); + }); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agents_selection_status.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agents_selection_status.tsx index 57728f275ccb5..0e1b6e43db46e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agents_selection_status.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agents_selection_status.tsx @@ -34,6 +34,7 @@ const Button = styled(EuiButtonEmpty)` export const AgentsSelectionStatus: React.FunctionComponent<{ totalAgents: number; selectableAgents: number; + managedAgentsOnCurrentPage: number; selectionMode: SelectionMode; setSelectionMode: (mode: SelectionMode) => void; selectedAgents: Agent[]; @@ -41,15 +42,19 @@ export const AgentsSelectionStatus: React.FunctionComponent<{ }> = ({ totalAgents, selectableAgents, + managedAgentsOnCurrentPage, selectionMode, setSelectionMode, selectedAgents, setSelectedAgents, }) => { + const showSelectionInfoAndOptions = + (selectionMode === 'manual' && selectedAgents.length > 0) || + (selectionMode === 'query' && totalAgents > 0); const showSelectEverything = selectionMode === 'manual' && selectedAgents.length === selectableAgents && - selectableAgents < totalAgents; + selectableAgents < totalAgents - managedAgentsOnCurrentPage; return ( <> @@ -74,14 +79,13 @@ export const AgentsSelectionStatus: React.FunctionComponent<{ )} - {(selectionMode === 'manual' && selectedAgents.length) || - (selectionMode === 'query' && totalAgents > 0) ? ( + {showSelectionInfoAndOptions ? ( <> - + -