mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[Search] Stateful Index Details FTR refactors (#220716)
## Summary - Closes #206396 Refactors functional search tests to group all of the API key tests together. These can be flakey and while I have also refactored these to hopefully make them more reliable, if they do fail again in the future only API key tests will be skipped with them. Refactored the Index details tests to use archived indices to improve test reliability. Updated index details tests to handle other changes since they were skipped. I had hoped to port these same changes to the serverless tests, but that will have to be a follow-up done in another PR. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed
This commit is contained in:
parent
99776ed5ff
commit
088cd43f87
15 changed files with 487 additions and 215 deletions
|
@ -14,6 +14,7 @@ import {
|
|||
EuiFlexItem,
|
||||
EuiTourStep,
|
||||
EuiTextColor,
|
||||
EuiButtonEmpty,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { WorkflowId } from '@kbn/search-shared-ui';
|
||||
|
@ -36,6 +37,7 @@ export const GuideSelector: React.FC<GuideSelectorProps> = ({
|
|||
|
||||
return showTour ? (
|
||||
<EuiTourStep
|
||||
data-test-subj="searchIngestTour"
|
||||
content={
|
||||
<EuiText>
|
||||
<p>
|
||||
|
@ -54,6 +56,19 @@ export const GuideSelector: React.FC<GuideSelectorProps> = ({
|
|||
defaultMessage: 'New guides available!',
|
||||
})}
|
||||
anchorPosition="rightUp"
|
||||
footerAction={
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="searchIngestTourCloseButton"
|
||||
color="text"
|
||||
flush="right"
|
||||
onClick={() => setTourIsOpen(false)}
|
||||
size="xs"
|
||||
>
|
||||
{i18n.translate('xpack.searchIndices.closeTourAction', {
|
||||
defaultMessage: 'Close tour',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
}
|
||||
>
|
||||
<GuideSelectorTiles selectedWorkflowId={selectedWorkflowId} onChange={onChange} />
|
||||
</EuiTourStep>
|
||||
|
|
|
@ -18,7 +18,7 @@ export function SearchApiKeysProvider({ getService, getPageObjects }: FtrProvide
|
|||
const retry = getService('retry');
|
||||
const es = getService('es');
|
||||
|
||||
const getAPIKeyFromSessionStorage = async () => {
|
||||
const getAPIKeyFromSessionStorage = async (): Promise<{ encoded: string; id: string } | null> => {
|
||||
const sessionStorageKey = await browser.getSessionStorageItem('searchApiKey');
|
||||
return sessionStorageKey && JSON.parse(sessionStorageKey);
|
||||
};
|
||||
|
@ -34,32 +34,45 @@ export function SearchApiKeysProvider({ getService, getPageObjects }: FtrProvide
|
|||
|
||||
async expectAPIKeyAvailable() {
|
||||
await testSubjects.existOrFail('apiKeyFormAPIKey');
|
||||
await retry.try(async () => {
|
||||
expect(await testSubjects.getVisibleText('apiKeyFormAPIKey')).to.be(APIKEY_MASK);
|
||||
});
|
||||
await testSubjects.existOrFail('showAPIKeyButton');
|
||||
await retry.tryWithRetries(
|
||||
'api key is masked',
|
||||
async () => {
|
||||
expect(await testSubjects.getVisibleText('apiKeyFormAPIKey')).to.be(APIKEY_MASK);
|
||||
},
|
||||
{
|
||||
retryCount: 3,
|
||||
retryDelay: 1000,
|
||||
},
|
||||
async () => {
|
||||
await testSubjects.click('showAPIKeyButton');
|
||||
}
|
||||
);
|
||||
await testSubjects.click('showAPIKeyButton');
|
||||
let apiKey;
|
||||
await retry.try(async () => {
|
||||
apiKey = await testSubjects.getVisibleText('apiKeyFormAPIKey');
|
||||
expect(apiKey).to.be.a('string');
|
||||
expect(apiKey.length).to.be(60);
|
||||
expect(apiKey).to.not.be(APIKEY_MASK);
|
||||
});
|
||||
const sessionStorageKey = await getAPIKeyFromSessionStorage();
|
||||
expect(sessionStorageKey.encoded).to.eql(apiKey);
|
||||
},
|
||||
let apiKey: string;
|
||||
await retry.tryWithRetries(
|
||||
'Verify api key can be shown',
|
||||
async () => {
|
||||
apiKey = await testSubjects.getVisibleText('apiKeyFormAPIKey');
|
||||
expect(apiKey).to.be.a('string');
|
||||
expect(apiKey.length).to.be(60);
|
||||
expect(apiKey).to.not.be(APIKEY_MASK);
|
||||
},
|
||||
{
|
||||
retryCount: 3,
|
||||
retryDelay: 1000,
|
||||
},
|
||||
async () => {
|
||||
await testSubjects.click('showAPIKeyButton');
|
||||
}
|
||||
);
|
||||
|
||||
async expectShownAPIKeyAvailable() {
|
||||
await testSubjects.existOrFail('apiKeyFormAPIKey');
|
||||
let apiKey;
|
||||
await retry.try(async () => {
|
||||
apiKey = await testSubjects.getVisibleText('apiKeyFormAPIKey');
|
||||
expect(apiKey).to.be.a('string');
|
||||
expect(apiKey.length).to.be(60);
|
||||
expect(apiKey).to.not.be(APIKEY_MASK);
|
||||
});
|
||||
const sessionStorageKey = await getAPIKeyFromSessionStorage();
|
||||
expect(sessionStorageKey.encoded).to.eql(apiKey);
|
||||
// This is flakey - I'm seeing this fail even when the API key is shown
|
||||
// it appears reading the api key from session storage is not always reliable
|
||||
// in tests. :/
|
||||
// const sessionStorageKey = await getAPIKeyFromSessionStorage();
|
||||
// expect(sessionStorageKey).to.not.be(null);
|
||||
// expect(sessionStorageKey.encoded).to.eql(apiKey);
|
||||
},
|
||||
|
||||
async expectAPIKeyNoPrivileges() {
|
||||
|
|
|
@ -43,16 +43,28 @@ export function SearchIndexDetailPageProvider({ getService }: FtrProviderContext
|
|||
/^https?\:\/\/.*(\:\d+)?/
|
||||
);
|
||||
},
|
||||
async expectQuickStats() {
|
||||
async expectQuickStats(counts: { total: number; deleted: number } = { total: 0, deleted: 0 }) {
|
||||
await testSubjects.existOrFail('quickStats', { timeout: 2000 });
|
||||
const quickStatsElem = await testSubjects.find('quickStats');
|
||||
const quickStatsDocumentElem = await quickStatsElem.findByTestSubject(
|
||||
'QuickStatsDocumentCount'
|
||||
);
|
||||
expect(await quickStatsDocumentElem.getVisibleText()).to.contain('Document count\n0');
|
||||
expect(await quickStatsDocumentElem.getVisibleText()).not.to.contain('Total\n0');
|
||||
await quickStatsDocumentElem.click();
|
||||
expect(await quickStatsDocumentElem.getVisibleText()).to.contain('Total\n0\nDeleted\n0');
|
||||
const documentCountVisibileText = await quickStatsDocumentElem.getVisibleText();
|
||||
expect(documentCountVisibileText).to.contain(`Document count\n${counts.total}`);
|
||||
expect(documentCountVisibileText).not.to.contain(`Total\n${counts.total}`);
|
||||
await testSubjects.click('QuickStatsDocumentCount');
|
||||
await retry.tryWithRetries(
|
||||
'Wait for redirect to start page',
|
||||
async () => {
|
||||
expect(await testSubjects.getVisibleText('QuickStatsDocumentCount')).to.contain(
|
||||
`Total\n${counts.total}\nDeleted\n${counts.deleted}`
|
||||
);
|
||||
},
|
||||
{
|
||||
retryCount: 2,
|
||||
retryDelay: 1000,
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
async expectQuickStatsToHaveIndexStatus() {
|
||||
|
@ -88,6 +100,13 @@ export function SearchIndexDetailPageProvider({ getService }: FtrProviderContext
|
|||
await testSubjects.existOrFail('setupAISearchButton', { timeout: 2000 });
|
||||
},
|
||||
|
||||
async expectQuickStatsAIMappingsToHaveSemanticFields() {
|
||||
const quickStatsDocumentElem = await testSubjects.find('QuickStatsAIMappings');
|
||||
await quickStatsDocumentElem.click();
|
||||
expect(await quickStatsDocumentElem.getVisibleText()).to.contain('AI Search\n2 Fields');
|
||||
await testSubjects.missingOrFail('setupAISearchButton', { timeout: 2000 });
|
||||
},
|
||||
|
||||
async expectQuickStatsAIMappingsToHaveVectorFields() {
|
||||
const quickStatsDocumentElem = await testSubjects.find('QuickStatsAIMappings');
|
||||
await quickStatsDocumentElem.click();
|
||||
|
@ -166,10 +185,21 @@ export function SearchIndexDetailPageProvider({ getService }: FtrProviderContext
|
|||
).findByClassName('euiTitle');
|
||||
expect(await pageLoadErrorElement.getVisibleText()).to.contain('Not Found');
|
||||
},
|
||||
async hasPageReloadButton() {
|
||||
await testSubjects.existOrFail('reloadButton');
|
||||
},
|
||||
async pageReloadButtonIsVisible() {
|
||||
return testSubjects.isDisplayed('reloadButton');
|
||||
},
|
||||
async clickPageReload() {
|
||||
await retry.tryForTime(60 * 1000, async () => {
|
||||
await testSubjects.click('reloadButton', 2000);
|
||||
});
|
||||
await retry.tryForTime(
|
||||
60 * 1000,
|
||||
async () => {
|
||||
await testSubjects.click('reloadButton', 2000);
|
||||
},
|
||||
undefined,
|
||||
100
|
||||
);
|
||||
},
|
||||
async expectTabsExists() {
|
||||
await testSubjects.existOrFail('mappingsTab', { timeout: 2000 });
|
||||
|
@ -289,18 +319,18 @@ export function SearchIndexDetailPageProvider({ getService }: FtrProviderContext
|
|||
expect(await testSubjects.getVisibleText('breadcrumb last')).to.contain(indexName);
|
||||
},
|
||||
async expectBreadcrumbsToBeAvailable(breadcrumbsName: string) {
|
||||
const breadcrumbs = await testSubjects.findAll('breadcrumb');
|
||||
const breadcrumbs = await testSubjects.findAll('euiBreadcrumb');
|
||||
let isBreadcrumbShown: boolean = false;
|
||||
for (const breadcrumb of breadcrumbs) {
|
||||
if ((await breadcrumb.getVisibleText()) === breadcrumbsName) {
|
||||
isBreadcrumbShown = true;
|
||||
}
|
||||
}
|
||||
expect(isBreadcrumbShown).to.be(true);
|
||||
expect(isBreadcrumbShown).to.eql(true, `Breadcrumb ${breadcrumbsName} was not found`);
|
||||
},
|
||||
|
||||
async clickOnBreadcrumb(breadcrumbsName: string) {
|
||||
const breadcrumbs = await testSubjects.findAll('breadcrumb');
|
||||
const breadcrumbs = await testSubjects.findAll('euiBreadcrumb');
|
||||
for (const breadcrumb of breadcrumbs) {
|
||||
if ((await breadcrumb.getVisibleText()) === breadcrumbsName) {
|
||||
await breadcrumb.click();
|
||||
|
@ -322,5 +352,12 @@ export function SearchIndexDetailPageProvider({ getService }: FtrProviderContext
|
|||
const isMappingsFieldEnabled = await testSubjects.isEnabled('indexDetailsMappingsAddField');
|
||||
expect(isMappingsFieldEnabled).to.be(true);
|
||||
},
|
||||
|
||||
async dismissIngestTourIfShown() {
|
||||
if (await testSubjects.isDisplayed('searchIngestTourCloseButton')) {
|
||||
await testSubjects.click('searchIngestTourCloseButton');
|
||||
await testSubjects.missingOrFail('searchIngestTourCloseButton', { timeout: 2000 });
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ export function SearchStartProvider({ getService }: FtrProviderContext) {
|
|||
|
||||
return {
|
||||
async expectToBeOnStartPage() {
|
||||
expect(await browser.getCurrentUrl()).contain('/app/elasticsearch/start');
|
||||
await testSubjects.existOrFail('elasticsearchStartPage', { timeout: 2000 });
|
||||
},
|
||||
async expectToBeOnIndexDetailsPage() {
|
||||
|
|
BIN
x-pack/test/functional_search/fixtures/search-books/data.json.gz
Normal file
BIN
x-pack/test/functional_search/fixtures/search-books/data.json.gz
Normal file
Binary file not shown.
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"type": "index",
|
||||
"value": {
|
||||
"aliases": {
|
||||
},
|
||||
"index": "search-books",
|
||||
"mappings": {
|
||||
"properties": {
|
||||
"author": {
|
||||
"type": "text"
|
||||
},
|
||||
"name": {
|
||||
"type": "text"
|
||||
},
|
||||
"page_count": {
|
||||
"type": "float"
|
||||
},
|
||||
"release_date": {
|
||||
"type": "date"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"index": {
|
||||
"number_of_replicas": "1",
|
||||
"number_of_shards": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"type": "index",
|
||||
"value": {
|
||||
"aliases": {
|
||||
},
|
||||
"index": "search-empty-index",
|
||||
"mappings": {
|
||||
},
|
||||
"settings": {
|
||||
"index": {
|
||||
"number_of_replicas": "1",
|
||||
"number_of_shards": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"type": "index",
|
||||
"value": {
|
||||
"aliases": {
|
||||
},
|
||||
"index": "search-national-parks",
|
||||
"mappings": {
|
||||
"properties": {
|
||||
"acres": {
|
||||
"type": "float"
|
||||
},
|
||||
"date_established": {
|
||||
"type": "date"
|
||||
},
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"description_embeddings": {
|
||||
"dims": 1536,
|
||||
"index": true,
|
||||
"index_options": {
|
||||
"ef_construction": 100,
|
||||
"m": 16,
|
||||
"type": "int8_hnsw"
|
||||
},
|
||||
"similarity": "cosine",
|
||||
"type": "dense_vector"
|
||||
},
|
||||
"id": {
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"ignore_above": 256,
|
||||
"type": "keyword"
|
||||
}
|
||||
},
|
||||
"type": "text"
|
||||
},
|
||||
"location": {
|
||||
"type": "geo_point"
|
||||
},
|
||||
"nps_link": {
|
||||
"type": "text"
|
||||
},
|
||||
"square_km": {
|
||||
"type": "float"
|
||||
},
|
||||
"states": {
|
||||
"type": "text"
|
||||
},
|
||||
"title": {
|
||||
"type": "text"
|
||||
},
|
||||
"visitors": {
|
||||
"type": "float"
|
||||
},
|
||||
"world_heritage_site": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"index": {
|
||||
"number_of_replicas": "1",
|
||||
"number_of_shards": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => {
|
|||
loadTestFile(require.resolve('./tests/solution_navigation'));
|
||||
loadTestFile(require.resolve('./tests/search_overview'));
|
||||
loadTestFile(require.resolve('./tests/search_start'));
|
||||
loadTestFile(require.resolve('./tests/search_onboarding_api_keys.ts'));
|
||||
loadTestFile(require.resolve('./tests/search_index_details'));
|
||||
loadTestFile(require.resolve('./tests/index_management'));
|
||||
});
|
||||
|
|
|
@ -8,7 +8,6 @@ import type { FtrProviderContext } from '../ftr_provider_context';
|
|||
export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||
const pageObjects = getPageObjects([
|
||||
'searchIndexDetailsPage',
|
||||
'searchApiKeys',
|
||||
'header',
|
||||
'common',
|
||||
'indexManagement',
|
||||
|
@ -104,7 +103,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
name: 'classic-nav-index-management-listpage-ftr',
|
||||
solution: 'classic',
|
||||
}));
|
||||
await pageObjects.searchApiKeys.deleteAPIKeys();
|
||||
await es.indices.create({ index: indexName });
|
||||
});
|
||||
|
||||
|
|
|
@ -7,11 +7,14 @@
|
|||
import { FtrProviderContext } from '../ftr_provider_context';
|
||||
import { testHasEmbeddedConsole } from './embedded_console';
|
||||
|
||||
const archivedBooksIndex = 'x-pack/test/functional_search/fixtures/search-books';
|
||||
const archiveEmptyIndex = 'x-pack/test/functional_search/fixtures/search-empty-index';
|
||||
const archiveDenseVectorIndex = 'x-pack/test/functional_search/fixtures/search-national-parks';
|
||||
|
||||
export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||
const pageObjects = getPageObjects([
|
||||
'embeddedConsole',
|
||||
'searchIndexDetailsPage',
|
||||
'searchApiKeys',
|
||||
'header',
|
||||
'common',
|
||||
'indexManagement',
|
||||
|
@ -19,15 +22,29 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
'solutionNavigation',
|
||||
]);
|
||||
const es = getService('es');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const browser = getService('browser');
|
||||
const retry = getService('retry');
|
||||
const spaces = getService('spaces');
|
||||
const esDeleteAllIndices = getService('esDeleteAllIndices');
|
||||
const retry = getService('retry');
|
||||
|
||||
const indexName = 'test-my-index';
|
||||
const createIndices = async () => {
|
||||
await esArchiver.load(archivedBooksIndex);
|
||||
await esArchiver.load(archiveDenseVectorIndex);
|
||||
await esArchiver.load(archiveEmptyIndex);
|
||||
};
|
||||
const deleteIndices = async () => {
|
||||
await esArchiver.unload(archivedBooksIndex);
|
||||
await esArchiver.unload(archiveDenseVectorIndex);
|
||||
await esArchiver.unload(archiveEmptyIndex);
|
||||
await esDeleteAllIndices([indexDoesNotExistName]);
|
||||
};
|
||||
const indexWithDataName = 'search-books';
|
||||
const indexWithoutDataName = 'search-empty-index';
|
||||
const indexWithDenseVectorName = 'search-national-parks';
|
||||
const indexDoesNotExistName = 'search-not-found';
|
||||
|
||||
// Failing: See https://github.com/elastic/kibana/issues/206396
|
||||
describe.skip('Search index details page', function () {
|
||||
describe('Search index details page', function () {
|
||||
describe('Solution Nav - Search', function () {
|
||||
let cleanUpSpace: () => Promise<unknown>;
|
||||
let spaceCreated: { id: string } = { id: '' };
|
||||
|
@ -44,20 +61,21 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
solution: 'es',
|
||||
}));
|
||||
|
||||
await pageObjects.searchApiKeys.deleteAPIKeys();
|
||||
await es.indices.create({ index: indexName });
|
||||
await createIndices();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
// Clean up space created
|
||||
await cleanUpSpace();
|
||||
await esDeleteAllIndices(indexName);
|
||||
await deleteIndices();
|
||||
});
|
||||
describe('search index details page', () => {
|
||||
before(async () => {
|
||||
// Navigate to the spaces management page which will log us in Kibana
|
||||
await browser.navigateTo(spaces.getRootUrl(spaceCreated.id));
|
||||
await pageObjects.searchNavigation.navigateToIndexDetailPage(indexName);
|
||||
await pageObjects.searchNavigation.navigateToIndexDetailPage(indexWithoutDataName);
|
||||
await pageObjects.searchIndexDetailsPage.expectIndexDetailsPageIsLoaded();
|
||||
await pageObjects.searchIndexDetailsPage.dismissIngestTourIfShown();
|
||||
});
|
||||
it('can load index detail page', async () => {
|
||||
await pageObjects.searchIndexDetailsPage.expectIndexDetailPageHeader();
|
||||
|
@ -69,8 +87,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await testHasEmbeddedConsole(pageObjects);
|
||||
});
|
||||
it('should have breadcrumbs', async () => {
|
||||
await pageObjects.searchIndexDetailsPage.expectIndexNametoBeInBreadcrumbs(indexName);
|
||||
await pageObjects.searchIndexDetailsPage.expectBreadcrumbsToBeAvailable('Content');
|
||||
await pageObjects.searchIndexDetailsPage.expectIndexNametoBeInBreadcrumbs(
|
||||
indexWithoutDataName
|
||||
);
|
||||
await pageObjects.searchIndexDetailsPage.expectBreadcrumbsToBeAvailable('Data');
|
||||
await pageObjects.searchIndexDetailsPage.expectBreadcrumbsToBeAvailable(
|
||||
'Index Management'
|
||||
);
|
||||
|
@ -82,48 +102,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await pageObjects.searchIndexDetailsPage.clickOnBreadcrumb('Index Management');
|
||||
await pageObjects.indexManagement.expectToBeOnIndexManagement();
|
||||
|
||||
await pageObjects.searchNavigation.navigateToIndexDetailPage(indexName);
|
||||
await pageObjects.searchNavigation.navigateToIndexDetailPage(indexWithoutDataName);
|
||||
});
|
||||
|
||||
it('should have connection details', async () => {
|
||||
await pageObjects.searchIndexDetailsPage.expectConnectionDetails();
|
||||
});
|
||||
|
||||
describe('check code example texts', () => {
|
||||
const indexNameCodeExample = 'test-my-index2';
|
||||
before(async () => {
|
||||
await es.indices.create({ index: indexNameCodeExample });
|
||||
await pageObjects.searchNavigation.navigateToIndexDetailPage(indexNameCodeExample);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esDeleteAllIndices(indexNameCodeExample);
|
||||
});
|
||||
|
||||
it('should have basic example texts', async () => {
|
||||
await pageObjects.searchIndexDetailsPage.expectHasSampleDocuments();
|
||||
});
|
||||
});
|
||||
|
||||
describe('API key details', () => {
|
||||
it('should show api key', async () => {
|
||||
await pageObjects.searchApiKeys.deleteAPIKeys();
|
||||
await pageObjects.searchNavigation.navigateToIndexDetailPage(indexName);
|
||||
// sometimes the API key exists in the cluster and its lost in sessionStorage
|
||||
// if fails we retry to delete the API key and refresh the browser
|
||||
await retry.try(
|
||||
async () => {
|
||||
await pageObjects.searchApiKeys.expectAPIKeyExists();
|
||||
},
|
||||
async () => {
|
||||
await pageObjects.searchApiKeys.deleteAPIKeys();
|
||||
await browser.refresh();
|
||||
}
|
||||
);
|
||||
await pageObjects.searchApiKeys.expectAPIKeyAvailable();
|
||||
const apiKey = await pageObjects.searchApiKeys.getAPIKeyFromUI();
|
||||
await pageObjects.searchIndexDetailsPage.expectAPIKeyToBeVisibleInCodeBlock(apiKey);
|
||||
});
|
||||
it('should have basic example texts', async () => {
|
||||
await pageObjects.searchIndexDetailsPage.expectHasSampleDocuments();
|
||||
});
|
||||
|
||||
it('should have quick stats', async () => {
|
||||
|
@ -131,17 +118,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await pageObjects.searchIndexDetailsPage.expectQuickStatsToHaveIndexStatus();
|
||||
await pageObjects.searchIndexDetailsPage.expectQuickStatsToHaveIndexStorage('227b');
|
||||
await pageObjects.searchIndexDetailsPage.expectQuickStatsAIMappings();
|
||||
await es.indices.putMapping({
|
||||
index: indexName,
|
||||
properties: {
|
||||
my_field: {
|
||||
type: 'dense_vector',
|
||||
dims: 3,
|
||||
},
|
||||
},
|
||||
});
|
||||
await pageObjects.searchNavigation.navigateToIndexDetailPage(indexName);
|
||||
await pageObjects.searchIndexDetailsPage.expectQuickStatsAIMappingsToHaveVectorFields();
|
||||
});
|
||||
|
||||
it('should show code examples for adding documents', async () => {
|
||||
|
@ -164,14 +140,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
|
||||
describe('With data', () => {
|
||||
before(async () => {
|
||||
await es.index({
|
||||
index: indexName,
|
||||
refresh: true,
|
||||
body: {
|
||||
my_field: [1, 0, 1],
|
||||
},
|
||||
});
|
||||
await pageObjects.searchNavigation.navigateToIndexDetailPage(indexName);
|
||||
await pageObjects.searchNavigation.navigateToIndexDetailPage(indexWithDataName);
|
||||
});
|
||||
it('should have index documents', async () => {
|
||||
await pageObjects.searchIndexDetailsPage.expectHasIndexDocuments();
|
||||
|
@ -183,8 +152,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await pageObjects.searchIndexDetailsPage.clickMoreOptionsActionsButton();
|
||||
await pageObjects.searchIndexDetailsPage.expectAPIReferenceDocLinkExistsInMoreOptions();
|
||||
});
|
||||
it('should have one document in quick stats', async () => {
|
||||
await pageObjects.searchIndexDetailsPage.expectQuickStatsToHaveDocumentCount(1);
|
||||
it('should have documents in quick stats', async () => {
|
||||
await pageObjects.searchIndexDetailsPage.expectQuickStatsToHaveDocumentCount(46);
|
||||
});
|
||||
it('should have with data tabs', async () => {
|
||||
await pageObjects.searchIndexDetailsPage.expectTabsExists();
|
||||
|
@ -206,24 +175,35 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
|
||||
// re-open page to refresh queries for test (these will auto-refresh,
|
||||
// but waiting for that will make this test flakey)
|
||||
await pageObjects.searchNavigation.navigateToIndexDetailPage(indexName);
|
||||
await pageObjects.searchIndexDetailsPage.expectAddDocumentCodeExamples();
|
||||
await pageObjects.searchIndexDetailsPage.expectQuickStatsToHaveDocumentCount(0);
|
||||
await browser.refresh();
|
||||
await retry.tryWithRetries(
|
||||
'Wait for document count to update',
|
||||
async () => {
|
||||
await pageObjects.searchIndexDetailsPage.expectQuickStatsToHaveDocumentCount(45);
|
||||
},
|
||||
{
|
||||
retryCount: 5,
|
||||
retryDelay: 1000,
|
||||
},
|
||||
async () => {
|
||||
await browser.refresh();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('With dense vecotrs', () => {
|
||||
it('should have ai quick stats for index with semantic mappings', async () => {
|
||||
await pageObjects.searchNavigation.navigateToIndexDetailPage(indexWithDenseVectorName);
|
||||
await pageObjects.searchIndexDetailsPage.expectQuickStatsAIMappingsToHaveVectorFields();
|
||||
});
|
||||
});
|
||||
describe('has index actions enabled', () => {
|
||||
before(async () => {
|
||||
await es.index({
|
||||
index: indexName,
|
||||
body: {
|
||||
my_field: [1, 0, 1],
|
||||
},
|
||||
});
|
||||
await pageObjects.searchNavigation.navigateToIndexDetailPage(indexName);
|
||||
await pageObjects.searchNavigation.navigateToIndexDetailPage(indexWithDenseVectorName);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await pageObjects.searchNavigation.navigateToIndexDetailPage(indexName);
|
||||
await pageObjects.searchNavigation.navigateToIndexDetailPage(indexWithDenseVectorName);
|
||||
});
|
||||
|
||||
it('delete document button is enabled', async () => {
|
||||
|
@ -248,22 +228,35 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
|
||||
describe('page loading error', () => {
|
||||
before(async () => {
|
||||
await pageObjects.searchNavigation.navigateToIndexDetailPage(indexName);
|
||||
await esDeleteAllIndices(indexName);
|
||||
// manually navigate to index detail page for an index that doesn't exist
|
||||
await browser.navigateTo(
|
||||
`${spaces.getRootUrl(
|
||||
spaceCreated.id
|
||||
)}/app/elasticsearch/indices/index_details/${indexDoesNotExistName}/data`
|
||||
);
|
||||
});
|
||||
it('has page load error section', async () => {
|
||||
await pageObjects.searchIndexDetailsPage.expectPageLoadErrorExists();
|
||||
await pageObjects.searchIndexDetailsPage.expectIndexNotFoundErrorExists();
|
||||
});
|
||||
it('reload button shows details page again', async () => {
|
||||
await es.indices.create({ index: indexName });
|
||||
await pageObjects.searchIndexDetailsPage.clickPageReload();
|
||||
await pageObjects.searchIndexDetailsPage.expectIndexDetailPageHeader();
|
||||
await es.indices.create({ index: indexDoesNotExistName });
|
||||
await retry.tryForTime(
|
||||
30 * 1000,
|
||||
async () => {
|
||||
if (await pageObjects.searchIndexDetailsPage.pageReloadButtonIsVisible()) {
|
||||
await pageObjects.searchIndexDetailsPage.clickPageReload();
|
||||
}
|
||||
await pageObjects.searchIndexDetailsPage.expectIndexDetailPageHeader();
|
||||
},
|
||||
undefined,
|
||||
1000
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('Index more options menu', () => {
|
||||
before(async () => {
|
||||
await pageObjects.searchNavigation.navigateToIndexDetailPage(indexName);
|
||||
await pageObjects.searchNavigation.navigateToIndexDetailPage(indexWithDataName);
|
||||
});
|
||||
it('shows action menu in actions popover', async () => {
|
||||
await pageObjects.searchIndexDetailsPage.expectMoreOptionsActionButtonExists();
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||
const pageObjects = getPageObjects([
|
||||
'searchIndexDetailsPage',
|
||||
'searchApiKeys',
|
||||
'searchStart',
|
||||
'header',
|
||||
'common',
|
||||
'indexManagement',
|
||||
'searchNavigation',
|
||||
'solutionNavigation',
|
||||
]);
|
||||
const browser = getService('browser');
|
||||
const es = getService('es');
|
||||
const esDeleteAllIndices = getService('esDeleteAllIndices');
|
||||
const retry = getService('retry');
|
||||
const spaces = getService('spaces');
|
||||
|
||||
const indexName = 'test-my-index';
|
||||
|
||||
const deleteAllTestIndices = async () => {
|
||||
await esDeleteAllIndices(['search-*', 'test-*']);
|
||||
};
|
||||
|
||||
describe('Search onboarding API keys', () => {
|
||||
let cleanUpSpace: () => Promise<unknown>;
|
||||
let spaceCreated: { id: string } = { id: '' };
|
||||
before(async () => {
|
||||
// Navigate to the spaces management page which will log us in Kibana
|
||||
await pageObjects.common.navigateToUrl('management', 'kibana/spaces', {
|
||||
shouldUseHashForSubUrl: false,
|
||||
});
|
||||
|
||||
// Create a space with the search solution and navigate to its home page
|
||||
({ cleanUp: cleanUpSpace, space: spaceCreated } = await spaces.create({
|
||||
name: 'search-onboarding-apikeys-ftr',
|
||||
solution: 'es',
|
||||
}));
|
||||
|
||||
await pageObjects.searchApiKeys.deleteAPIKeys();
|
||||
await deleteAllTestIndices();
|
||||
|
||||
await browser.navigateTo(spaces.getRootUrl(spaceCreated.id));
|
||||
});
|
||||
after(async () => {
|
||||
// Clean up space created
|
||||
await cleanUpSpace();
|
||||
await pageObjects.searchApiKeys.deleteAPIKeys();
|
||||
await deleteAllTestIndices();
|
||||
});
|
||||
describe('Elasticsearch Start [Onboarding Empty State]', () => {
|
||||
let apiKeySession: { encoded: string; id: string } | null = null;
|
||||
const { searchApiKeys, searchStart, searchNavigation } = pageObjects;
|
||||
before(async () => {
|
||||
await retry.tryWithRetries(
|
||||
'Wait for redirect to start page',
|
||||
async () => {
|
||||
await pageObjects.searchStart.expectToBeOnStartPage();
|
||||
},
|
||||
{
|
||||
retryCount: 2,
|
||||
retryDelay: 1000,
|
||||
}
|
||||
);
|
||||
});
|
||||
it('should show the api key in code view', async () => {
|
||||
await searchStart.expectToBeOnStartPage();
|
||||
await searchStart.clickCodeViewButton();
|
||||
// sometimes the API key exists in the cluster and its lost in sessionStorage
|
||||
// if fails we retry to delete the API key and refresh the browser
|
||||
await retry.try(
|
||||
async () => {
|
||||
await searchApiKeys.expectAPIKeyExists();
|
||||
},
|
||||
async () => {
|
||||
await searchApiKeys.deleteAPIKeys();
|
||||
await browser.refresh();
|
||||
await searchStart.clickCodeViewButton();
|
||||
}
|
||||
);
|
||||
await searchApiKeys.expectAPIKeyAvailable();
|
||||
|
||||
const apiKeyUI = await searchApiKeys.getAPIKeyFromUI();
|
||||
apiKeySession = await searchApiKeys.getAPIKeyFromSessionStorage();
|
||||
|
||||
expect(apiKeySession).not.to.be(null);
|
||||
expect(apiKeyUI).to.eql(apiKeySession!.encoded);
|
||||
|
||||
// check that when browser is refreshed, the api key is still available
|
||||
await browser.refresh();
|
||||
await searchStart.clickCodeViewButton();
|
||||
await searchApiKeys.expectAPIKeyAvailable();
|
||||
await searchStart.expectAPIKeyPreGenerated();
|
||||
const refreshBrowserApiKeyUI = await searchApiKeys.getAPIKeyFromUI();
|
||||
expect(refreshBrowserApiKeyUI).to.eql(apiKeyUI);
|
||||
|
||||
// check that when api key is invalidated, a new one is generated
|
||||
await searchApiKeys.invalidateAPIKey(apiKeySession!.id);
|
||||
await browser.refresh();
|
||||
await searchStart.clickCodeViewButton();
|
||||
await searchApiKeys.expectAPIKeyAvailable();
|
||||
const newApiKeyUI = await searchApiKeys.getAPIKeyFromUI();
|
||||
expect(newApiKeyUI).to.not.eql(apiKeyUI);
|
||||
await searchStart.expectAPIKeyVisibleInCodeBlock(newApiKeyUI);
|
||||
});
|
||||
|
||||
it('should create a new api key when the existing one is invalidated', async () => {
|
||||
await searchStart.expectToBeOnStartPage();
|
||||
await searchStart.clickCodeViewButton();
|
||||
await searchApiKeys.expectAPIKeyAvailable();
|
||||
|
||||
// Get initial API key
|
||||
const initialApiKey = await searchApiKeys.getAPIKeyFromSessionStorage();
|
||||
expect(initialApiKey).to.not.be(null);
|
||||
expect(initialApiKey!.id).to.not.be(null);
|
||||
|
||||
// Navigate away to keep key in current session, invalidate key and return back
|
||||
await searchNavigation.navigateToIndexManagementPage();
|
||||
await searchApiKeys.invalidateAPIKey(initialApiKey!.id);
|
||||
await searchNavigation.navigateToElasticsearchStartPage(false, `/s/${spaceCreated.id}`);
|
||||
await searchStart.clickCodeViewButton();
|
||||
|
||||
// Check that new key was generated
|
||||
await searchApiKeys.expectAPIKeyAvailable();
|
||||
const newApiKey = await searchApiKeys.getAPIKeyFromSessionStorage();
|
||||
expect(newApiKey).to.not.be(null);
|
||||
expect(newApiKey!.id).to.not.eql(initialApiKey!.id);
|
||||
});
|
||||
|
||||
it('should explicitly ask to create api key when project already has an apikey', async () => {
|
||||
await searchApiKeys.clearAPIKeySessionStorage();
|
||||
await searchApiKeys.createAPIKey();
|
||||
await browser.refresh();
|
||||
await searchStart.expectToBeOnStartPage();
|
||||
await searchStart.clickCodeViewButton();
|
||||
await searchApiKeys.createApiKeyFromFlyout();
|
||||
await searchApiKeys.expectAPIKeyAvailable();
|
||||
});
|
||||
|
||||
it('Same API Key should be present on start page and index detail view', async () => {
|
||||
await searchStart.clickCodeViewButton();
|
||||
await searchApiKeys.expectAPIKeyAvailable();
|
||||
const apiKeyUI = await searchApiKeys.getAPIKeyFromUI();
|
||||
|
||||
await searchStart.clickUIViewButton();
|
||||
await searchStart.clickCreateIndexButton();
|
||||
await searchStart.expectToBeOnIndexDetailsPage();
|
||||
|
||||
await searchApiKeys.expectAPIKeyAvailable();
|
||||
const indexDetailsApiKey = await searchApiKeys.getAPIKeyFromUI();
|
||||
|
||||
expect(apiKeyUI).to.eql(indexDetailsApiKey);
|
||||
});
|
||||
});
|
||||
describe('index details page', () => {
|
||||
before(async () => {
|
||||
await es.indices.create({ index: indexName });
|
||||
await pageObjects.searchNavigation.navigateToIndexDetailPage(indexName);
|
||||
await pageObjects.searchIndexDetailsPage.expectIndexDetailsPageIsLoaded();
|
||||
await pageObjects.searchIndexDetailsPage.dismissIngestTourIfShown();
|
||||
});
|
||||
it('should show api key', async () => {
|
||||
// sometimes the API key exists in the cluster and its lost in sessionStorage
|
||||
// if fails we retry to delete the API key and refresh the browser
|
||||
await retry.try(
|
||||
async () => {
|
||||
await pageObjects.searchApiKeys.expectAPIKeyExists();
|
||||
},
|
||||
async () => {
|
||||
await pageObjects.searchApiKeys.deleteAPIKeys();
|
||||
await browser.refresh();
|
||||
}
|
||||
);
|
||||
await pageObjects.searchApiKeys.expectAPIKeyAvailable();
|
||||
const apiKey = await pageObjects.searchApiKeys.getAPIKeyFromUI();
|
||||
await pageObjects.searchIndexDetailsPage.expectAPIKeyToBeVisibleInCodeBlock(apiKey);
|
||||
});
|
||||
});
|
||||
describe.skip('create index page', () => {
|
||||
// TODO test API Keys displayed on create index page
|
||||
});
|
||||
});
|
||||
}
|
|
@ -5,22 +5,19 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../ftr_provider_context';
|
||||
import { testHasEmbeddedConsole } from './embedded_console';
|
||||
|
||||
export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||
const { common, searchApiKeys, searchStart, searchNavigation, embeddedConsole } = getPageObjects([
|
||||
const { common, searchStart, searchNavigation, embeddedConsole } = getPageObjects([
|
||||
'searchStart',
|
||||
'common',
|
||||
'searchApiKeys',
|
||||
'searchNavigation',
|
||||
'embeddedConsole',
|
||||
]);
|
||||
const esDeleteAllIndices = getService('esDeleteAllIndices');
|
||||
const es = getService('es');
|
||||
const browser = getService('browser');
|
||||
const retry = getService('retry');
|
||||
const spaces = getService('spaces');
|
||||
|
||||
const deleteAllTestIndices = async () => {
|
||||
|
@ -54,7 +51,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
describe('Developer rights', function () {
|
||||
beforeEach(async () => {
|
||||
await deleteAllTestIndices();
|
||||
await searchApiKeys.deleteAPIKeys();
|
||||
await searchNavigation.navigateToElasticsearchStartPage();
|
||||
});
|
||||
|
||||
|
@ -110,92 +106,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await searchStart.expectCreateIndexUIView();
|
||||
});
|
||||
|
||||
it('should show the api key in code view', async () => {
|
||||
await searchStart.expectToBeOnStartPage();
|
||||
await searchStart.clickCodeViewButton();
|
||||
// sometimes the API key exists in the cluster and its lost in sessionStorage
|
||||
// if fails we retry to delete the API key and refresh the browser
|
||||
await retry.try(
|
||||
async () => {
|
||||
await searchApiKeys.expectAPIKeyExists();
|
||||
},
|
||||
async () => {
|
||||
await searchApiKeys.deleteAPIKeys();
|
||||
await browser.refresh();
|
||||
await searchStart.clickCodeViewButton();
|
||||
}
|
||||
);
|
||||
await searchApiKeys.expectAPIKeyAvailable();
|
||||
|
||||
const apiKeyUI = await searchApiKeys.getAPIKeyFromUI();
|
||||
const apiKeySession = await searchApiKeys.getAPIKeyFromSessionStorage();
|
||||
|
||||
expect(apiKeyUI).to.eql(apiKeySession.encoded);
|
||||
|
||||
// check that when browser is refreshed, the api key is still available
|
||||
await browser.refresh();
|
||||
await searchStart.clickCodeViewButton();
|
||||
await searchApiKeys.expectAPIKeyAvailable();
|
||||
await searchStart.expectAPIKeyPreGenerated();
|
||||
const refreshBrowserApiKeyUI = await searchApiKeys.getAPIKeyFromUI();
|
||||
expect(refreshBrowserApiKeyUI).to.eql(apiKeyUI);
|
||||
|
||||
// check that when api key is invalidated, a new one is generated
|
||||
await searchApiKeys.invalidateAPIKey(apiKeySession.id);
|
||||
await browser.refresh();
|
||||
await searchStart.clickCodeViewButton();
|
||||
await searchApiKeys.expectAPIKeyAvailable();
|
||||
const newApiKeyUI = await searchApiKeys.getAPIKeyFromUI();
|
||||
expect(newApiKeyUI).to.not.eql(apiKeyUI);
|
||||
await searchStart.expectAPIKeyVisibleInCodeBlock(newApiKeyUI);
|
||||
});
|
||||
|
||||
it('should create a new api key when the existing one is invalidated', async () => {
|
||||
await searchStart.expectToBeOnStartPage();
|
||||
await searchStart.clickCodeViewButton();
|
||||
await searchApiKeys.expectAPIKeyAvailable();
|
||||
|
||||
// Get initial API key
|
||||
const initialApiKey = await searchApiKeys.getAPIKeyFromSessionStorage();
|
||||
expect(initialApiKey.id).to.not.be(null);
|
||||
|
||||
// Navigate away to keep key in current session, invalidate key and return back
|
||||
await searchNavigation.navigateToIndexManagementPage();
|
||||
await searchApiKeys.invalidateAPIKey(initialApiKey.id);
|
||||
await searchNavigation.navigateToElasticsearchStartPage(false, `/s/${spaceCreated.id}`);
|
||||
await searchStart.clickCodeViewButton();
|
||||
|
||||
// Check that new key was generated
|
||||
await searchApiKeys.expectAPIKeyAvailable();
|
||||
const newApiKey = await searchApiKeys.getAPIKeyFromSessionStorage();
|
||||
expect(newApiKey).to.not.be(null);
|
||||
expect(newApiKey.id).to.not.eql(initialApiKey.id);
|
||||
});
|
||||
|
||||
it('should explicitly ask to create api key when project already has an apikey', async () => {
|
||||
await searchApiKeys.clearAPIKeySessionStorage();
|
||||
await searchApiKeys.createAPIKey();
|
||||
await searchStart.expectToBeOnStartPage();
|
||||
await searchStart.clickCodeViewButton();
|
||||
await searchApiKeys.createApiKeyFromFlyout();
|
||||
await searchApiKeys.expectAPIKeyAvailable();
|
||||
});
|
||||
|
||||
it('Same API Key should be present on start page and index detail view', async () => {
|
||||
await searchStart.clickCodeViewButton();
|
||||
await searchApiKeys.expectAPIKeyAvailable();
|
||||
const apiKeyUI = await searchApiKeys.getAPIKeyFromUI();
|
||||
|
||||
await searchStart.clickUIViewButton();
|
||||
await searchStart.clickCreateIndexButton();
|
||||
await searchStart.expectToBeOnIndexDetailsPage();
|
||||
|
||||
await searchApiKeys.expectShownAPIKeyAvailable();
|
||||
const indexDetailsApiKey = await searchApiKeys.getAPIKeyFromUI();
|
||||
|
||||
expect(apiKeyUI).to.eql(indexDetailsApiKey);
|
||||
});
|
||||
|
||||
it('should have file upload link', async () => {
|
||||
await searchStart.expectToBeOnStartPage();
|
||||
await searchStart.clickFileUploadLink();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue