mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Console] Enable monaco by default (#184862)
## Summary Closes https://github.com/elastic/kibana/issues/184025 This PR enables the migration from Ace to Monaco in Dev Tools Console by default in the main branch. All serverless projects will still have the migration disabled by default. After 8.15 is branched, the migration will be disabled there as well. The intended release version for this migration is 8.16. ### Functional tests This PR creates a copy of functional tests for Monaco Console and keeps the tests for Ace in a separate folder. When the migration is released, we can remove the code for Ace together with tests. The Monaco tests are not the exact copy of the Ace tests, since some functionality and autocomplete behaviour is slightly different in the migrated Console. For example, the auto-closing of brackets works in Monaco when typing something, but is not kicking in in the tests. Flaky test runner ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [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 - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
37ca5c6bd9
commit
5e346b2561
43 changed files with 1626 additions and 50 deletions
|
@ -98,7 +98,8 @@ enabled:
|
|||
- test/api_integration/config.js
|
||||
- test/examples/config.js
|
||||
- test/functional/apps/bundles/config.ts
|
||||
- test/functional/apps/console/config.ts
|
||||
- test/functional/apps/console/monaco/config.ts
|
||||
- test/functional/apps/console/ace/config.ts
|
||||
- test/functional/apps/context/config.ts
|
||||
- test/functional/apps/dashboard_elements/controls/common/config.ts
|
||||
- test/functional/apps/dashboard_elements/controls/options_list/config.ts
|
||||
|
|
|
@ -108,6 +108,8 @@ xpack.index_management.enableDataStreamsStorageColumn: false
|
|||
xpack.index_management.enableMappingsSourceFieldSection: false
|
||||
# Disable toggle for enabling data retention in DSL form from Index Management UI
|
||||
xpack.index_management.enableTogglingDataRetention: false
|
||||
# Disable the Monaco migration in Console
|
||||
console.dev.enableMonaco: false
|
||||
|
||||
# Keep deeplinks visible so that they are shown in the sidenav
|
||||
dev_tools.deeplinks.navLinkStatus: visible
|
||||
|
|
|
@ -15,7 +15,7 @@ export interface ParsedRequest {
|
|||
startOffset: number;
|
||||
endOffset?: number;
|
||||
method: string;
|
||||
url: string;
|
||||
url?: string;
|
||||
data?: Array<Record<string, unknown>>;
|
||||
}
|
||||
export interface ConsoleParserResult {
|
||||
|
|
|
@ -169,6 +169,7 @@ exports[`<CodeEditor /> is rendered 1`] = `
|
|||
</p>
|
||||
</React.Fragment>
|
||||
}
|
||||
data-test-subj="codeEditorAccessibilityOverlay"
|
||||
delay="regular"
|
||||
display="block"
|
||||
position="top"
|
||||
|
|
|
@ -155,6 +155,8 @@ export interface CodeEditorProps {
|
|||
* Enables the editor to get disabled when pressing ESC to resolve focus trapping for accessibility.
|
||||
*/
|
||||
accessibilityOverlayEnabled?: boolean;
|
||||
|
||||
dataTestSubj?: string;
|
||||
}
|
||||
|
||||
export const CodeEditor: React.FC<CodeEditorProps> = ({
|
||||
|
@ -186,6 +188,7 @@ export const CodeEditor: React.FC<CodeEditorProps> = ({
|
|||
}),
|
||||
fitToContent,
|
||||
accessibilityOverlayEnabled = true,
|
||||
dataTestSubj,
|
||||
}) => {
|
||||
const { colorMode, euiTheme } = useEuiTheme();
|
||||
const useDarkTheme = useDarkThemeProp ?? colorMode === 'DARK';
|
||||
|
@ -280,6 +283,7 @@ export const CodeEditor: React.FC<CodeEditorProps> = ({
|
|||
|
||||
return (
|
||||
<EuiToolTip
|
||||
data-test-subj="codeEditorAccessibilityOverlay"
|
||||
display="block"
|
||||
content={
|
||||
<>
|
||||
|
@ -485,7 +489,7 @@ export const CodeEditor: React.FC<CodeEditorProps> = ({
|
|||
<div
|
||||
css={styles.container}
|
||||
onKeyDown={onKeyDown}
|
||||
data-test-subj="kibanaCodeEditor"
|
||||
data-test-subj={dataTestSubj ?? 'kibanaCodeEditor'}
|
||||
className="kibanaCodeEditor"
|
||||
>
|
||||
{accessibilityOverlayEnabled && renderPrompt()}
|
||||
|
|
|
@ -115,6 +115,7 @@ export const MonacoEditor = ({ initialTextValue }: EditorProps) => {
|
|||
width: 100%;
|
||||
`}
|
||||
ref={divRef}
|
||||
data-test-subj="consoleMonacoEditorContainer"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
className="conApp__editorActions"
|
||||
|
@ -151,6 +152,7 @@ export const MonacoEditor = ({ initialTextValue }: EditorProps) => {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<CodeEditor
|
||||
dataTestSubj={'consoleMonacoEditor'}
|
||||
languageId={CONSOLE_LANG_ID}
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
|
|
|
@ -93,6 +93,7 @@ export const MonacoEditorOutput: FunctionComponent = () => {
|
|||
</label>
|
||||
</EuiScreenReaderOnly>
|
||||
<CodeEditor
|
||||
dataTestSubj={'consoleMonacoOutput'}
|
||||
languageId={mode}
|
||||
value={value}
|
||||
fullWidth={true}
|
||||
|
|
|
@ -22,8 +22,8 @@ import { AdjustedParsedRequest } from '../types';
|
|||
* - the request body is stringified from an object using JSON.stringify
|
||||
*/
|
||||
export const stringifyRequest = (parsedRequest: ParsedRequest): EditorRequest => {
|
||||
const url = removeTrailingWhitespaces(parsedRequest.url);
|
||||
const method = parsedRequest.method.toUpperCase();
|
||||
const url = parsedRequest.url ? removeTrailingWhitespaces(parsedRequest.url) : '';
|
||||
const method = parsedRequest.method?.toUpperCase() ?? '';
|
||||
const data = parsedRequest.data?.map((parsedData) => JSON.stringify(parsedData, null, 2));
|
||||
return { url, method, data: data ?? [] };
|
||||
};
|
||||
|
|
|
@ -25,7 +25,7 @@ export const parseLine = (line: string): ParsedLineTokens => {
|
|||
// try to parse into method and url (split on whitespace)
|
||||
const parts = line.split(whitespacesRegex);
|
||||
// 1st part is the method
|
||||
const method = parts[0];
|
||||
const method = parts[0].toUpperCase();
|
||||
// 2nd part is the url
|
||||
const url = parts[1];
|
||||
// try to parse into url path and url params (split on question mark)
|
||||
|
|
|
@ -31,7 +31,7 @@ const schemaLatest = schema.object(
|
|||
defaultValue: 'stack',
|
||||
}),
|
||||
}),
|
||||
dev: schema.object({ enableMonaco: schema.boolean({ defaultValue: false }) }),
|
||||
dev: schema.object({ enableMonaco: schema.boolean({ defaultValue: true }) }),
|
||||
},
|
||||
{ defaultValue: undefined }
|
||||
);
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import _ from 'lodash';
|
||||
import expect from '@kbn/expect';
|
||||
import { asyncForEach } from '@kbn/std';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const log = getService('log');
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
import { asyncForEach } from '@kbn/std';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const log = getService('log');
|
|
@ -8,33 +8,8 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
import { asyncForEach } from '@kbn/std';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
const DEFAULT_REQUEST = `
|
||||
# Welcome to the Dev Tools Console!
|
||||
#
|
||||
# You can use Console to explore the Elasticsearch API. See the \n Elasticsearch API reference to learn more:
|
||||
# https://www.elastic.co/guide/en/elasticsearch/reference/current\n /rest-apis.html
|
||||
#
|
||||
# Here are a few examples to get you started.
|
||||
|
||||
|
||||
# Create an index
|
||||
PUT /my-index
|
||||
|
||||
|
||||
# Add a document to my-index
|
||||
POST /my-index/_doc
|
||||
{
|
||||
"id": "park_rocky-mountain",
|
||||
"title": "Rocky Mountain",
|
||||
"description": "Bisected north to south by the Continental \n Divide, this portion of the Rockies has ecosystems varying \n from over 150 riparian lakes to montane and subalpine forests \n to treeless alpine tundra."
|
||||
}
|
||||
|
||||
|
||||
# Perform a search in my-index
|
||||
GET /my-index/_search?q="rocky mountain"
|
||||
`.trim();
|
||||
import { DEFAULT_INPUT_VALUE } from '@kbn/console-plugin/common/constants';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const retry = getService('retry');
|
||||
|
@ -58,7 +33,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await retry.try(async () => {
|
||||
const actualRequest = await PageObjects.console.getRequest();
|
||||
log.debug(actualRequest);
|
||||
expect(actualRequest.trim()).to.eql(DEFAULT_REQUEST);
|
||||
expect(actualRequest.replace(/\s/g, '')).to.eql(DEFAULT_INPUT_VALUE.replace(/\s/g, ''));
|
||||
});
|
||||
});
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const retry = getService('retry');
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const log = getService('log');
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const retry = getService('retry');
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const log = getService('log');
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const retry = getService('retry');
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default ({ getService, getPageObjects }: FtrProviderContext) => {
|
||||
const retry = getService('retry');
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const PageObjects = getPageObjects(['common', 'console', 'header', 'home']);
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
import { rgbToHex } from '@elastic/eui';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default ({ getService, getPageObjects }: FtrProviderContext) => {
|
||||
const retry = getService('retry');
|
27
test/functional/apps/console/ace/config.ts
Normal file
27
test/functional/apps/console/ace/config.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { FtrConfigProviderContext } from '@kbn/test';
|
||||
import { configureHTTP2 } from '../../../../common/configure_http2';
|
||||
|
||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const functionalConfig = await readConfigFile(require.resolve('../../../config.base.js'));
|
||||
|
||||
return configureHTTP2({
|
||||
...functionalConfig.getAll(),
|
||||
testFiles: [require.resolve('.')],
|
||||
kbnTestServer: {
|
||||
...functionalConfig.get('kbnTestServer'),
|
||||
serverArgs: [
|
||||
...functionalConfig.get('kbnTestServer.serverArgs'),
|
||||
// disabling the monaco editor to run tests for ace
|
||||
`--console.dev.enableMonaco=false`,
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
|
@ -6,7 +6,9 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export default function ({ getService, loadTestFile }) {
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
||||
const browser = getService('browser');
|
||||
const config = getService('config');
|
||||
|
374
test/functional/apps/console/monaco/_autocomplete.ts
Normal file
374
test/functional/apps/console/monaco/_autocomplete.ts
Normal file
|
@ -0,0 +1,374 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import expect from '@kbn/expect';
|
||||
import { asyncForEach } from '@kbn/std';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const log = getService('log');
|
||||
const retry = getService('retry');
|
||||
const PageObjects = getPageObjects(['common', 'console', 'header']);
|
||||
const find = getService('find');
|
||||
|
||||
describe('console autocomplete feature', function describeIndexTests() {
|
||||
this.tags('includeFirefox');
|
||||
before(async () => {
|
||||
log.debug('navigateTo console');
|
||||
await PageObjects.common.navigateToApp('console');
|
||||
// Ensure that the text area can be interacted with
|
||||
await PageObjects.console.closeHelpIfExists();
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
log.debug('setAutocompleteTrace true');
|
||||
await PageObjects.console.setAutocompleteTrace(true);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
log.debug('setAutocompleteTrace false');
|
||||
await PageObjects.console.setAutocompleteTrace(false);
|
||||
});
|
||||
|
||||
it('should provide basic auto-complete functionality', async () => {
|
||||
await PageObjects.console.monaco.enterText(`GET _search\n`);
|
||||
await PageObjects.console.monaco.pressEnter();
|
||||
await PageObjects.console.monaco.enterText(`{\n\t"query": {`);
|
||||
await PageObjects.console.monaco.pressEnter();
|
||||
await PageObjects.console.sleepForDebouncePeriod();
|
||||
await PageObjects.console.monaco.promptAutocomplete();
|
||||
expect(PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(true);
|
||||
});
|
||||
|
||||
describe('Autocomplete behavior', () => {
|
||||
beforeEach(async () => {
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
});
|
||||
|
||||
it('HTTP methods', async () => {
|
||||
const suggestions = {
|
||||
G: ['GET'],
|
||||
P: ['PATCH', 'POST', 'PUT'],
|
||||
D: ['DELETE'],
|
||||
H: ['HEAD'],
|
||||
};
|
||||
for (const [char, methods] of Object.entries(suggestions)) {
|
||||
await PageObjects.console.sleepForDebouncePeriod();
|
||||
log.debug('Key type "%s"', char);
|
||||
await PageObjects.console.monaco.enterText(char);
|
||||
|
||||
await retry.waitFor('autocomplete to be visible', () =>
|
||||
PageObjects.console.monaco.isAutocompleteVisible()
|
||||
);
|
||||
expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(true);
|
||||
|
||||
for (const [i, method] of methods.entries()) {
|
||||
expect(await PageObjects.console.monaco.getAutocompleteSuggestion(i)).to.be.eql(method);
|
||||
}
|
||||
|
||||
await PageObjects.console.monaco.pressEscape();
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
}
|
||||
});
|
||||
|
||||
it('ES API endpoints', async () => {
|
||||
const suggestions = {
|
||||
'GET _': ['_alias', '_all'],
|
||||
'PUT _': ['_all'],
|
||||
'POST _': ['_aliases', '_all'],
|
||||
'DELETE _': ['_all'],
|
||||
'HEAD _': ['_alias', '_all'],
|
||||
};
|
||||
for (const [text, endpoints] of Object.entries(suggestions)) {
|
||||
for (const char of text) {
|
||||
await PageObjects.console.sleepForDebouncePeriod();
|
||||
log.debug('Key type "%s"', char);
|
||||
await PageObjects.console.monaco.enterText(char);
|
||||
}
|
||||
|
||||
await retry.waitFor('autocomplete to be visible', () =>
|
||||
PageObjects.console.monaco.isAutocompleteVisible()
|
||||
);
|
||||
expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(true);
|
||||
|
||||
for (const [i, endpoint] of endpoints.entries()) {
|
||||
expect(await PageObjects.console.monaco.getAutocompleteSuggestion(i)).to.be.eql(
|
||||
endpoint
|
||||
);
|
||||
}
|
||||
|
||||
await PageObjects.console.monaco.pressEscape();
|
||||
await PageObjects.console.monaco.pressEnter();
|
||||
}
|
||||
});
|
||||
|
||||
it('JSON autocompletion with placeholder fields', async () => {
|
||||
await PageObjects.console.monaco.enterText('GET _search\n');
|
||||
await PageObjects.console.monaco.enterText('{');
|
||||
await PageObjects.console.monaco.pressEnter();
|
||||
|
||||
for (const char of '"ag') {
|
||||
await PageObjects.console.sleepForDebouncePeriod();
|
||||
log.debug('Key type "%s"', char);
|
||||
await PageObjects.console.monaco.enterText(char);
|
||||
}
|
||||
await retry.waitFor('autocomplete to be visible', () =>
|
||||
PageObjects.console.monaco.isAutocompleteVisible()
|
||||
);
|
||||
expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(true);
|
||||
await PageObjects.console.monaco.pressEnter();
|
||||
await PageObjects.console.sleepForDebouncePeriod();
|
||||
|
||||
expect((await PageObjects.console.monaco.getEditorText()).replace(/\s/g, '')).to.be.eql(
|
||||
`
|
||||
GET _search
|
||||
{
|
||||
"aggs": {
|
||||
"NAME": {
|
||||
"AGG_TYPE": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
`.replace(/\s/g, '')
|
||||
);
|
||||
});
|
||||
|
||||
it('Dynamic autocomplete', async () => {
|
||||
await PageObjects.console.monaco.enterText('POST test/_doc\n{}');
|
||||
await PageObjects.console.clickPlay();
|
||||
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
expect(await PageObjects.console.getResponseStatus()).to.be('201');
|
||||
|
||||
await PageObjects.console.monaco.pressEnter();
|
||||
for (const char of 'POST t') {
|
||||
await PageObjects.console.sleepForDebouncePeriod();
|
||||
log.debug('Key type "%s"', char);
|
||||
await PageObjects.console.monaco.enterText(char);
|
||||
}
|
||||
await retry.waitFor('autocomplete to be visible', () =>
|
||||
PageObjects.console.monaco.isAutocompleteVisible()
|
||||
);
|
||||
expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(true);
|
||||
|
||||
expect(await PageObjects.console.monaco.getAutocompleteSuggestion(0)).to.be.eql('test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('anti-regression watchdogs', () => {
|
||||
beforeEach(async () => {
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
});
|
||||
|
||||
// flaky
|
||||
it.skip('should suppress auto-complete on arrow keys', async () => {
|
||||
await PageObjects.console.monaco.enterText(`\nGET _search\nGET _search`);
|
||||
await PageObjects.console.monaco.pressEnter();
|
||||
const keyPresses = [
|
||||
'pressUp',
|
||||
'pressUp',
|
||||
'pressDown',
|
||||
'pressDown',
|
||||
'pressRight',
|
||||
'pressRight',
|
||||
'pressLeft',
|
||||
'pressLeft',
|
||||
] as const;
|
||||
for (const keyPress of keyPresses) {
|
||||
await PageObjects.console.sleepForDebouncePeriod();
|
||||
log.debug('Key', keyPress);
|
||||
await PageObjects.console.monaco[keyPress]();
|
||||
expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('should activate auto-complete for methods case-insensitively', async () => {
|
||||
const methods = _.sampleSize(
|
||||
_.compact(
|
||||
`
|
||||
GET GEt GeT Get gET gEt geT get
|
||||
PUT PUt PuT Put pUT pUt puT put
|
||||
POST POSt POsT POst PoST PoSt PosT Post pOST pOSt pOsT pOst poST poSt posT post
|
||||
DELETE DELETe DELEtE DELEte DELeTE DELeTe DELetE DELete DElETE DElETe DElEtE DElEte DEleTE DEleTe DEletE DElete
|
||||
DeLETE DeLETe DeLEtE DeLEte DeLeTE DeLeTe DeLetE DeLete DelETE DelETe DelEtE DelEte DeleTE DeleTe DeletE Delete
|
||||
dELETE dELETe dELEtE dELEte dELeTE dELeTe dELetE dELete dElETE dElETe dElEtE dElEte dEleTE dEleTe dEletE dElete
|
||||
deLETE deLETe deLEtE deLEte deLeTE deLeTe deLetE deLete delETE delETe delEtE delEte deleTE deleTe deletE delete
|
||||
HEAD HEAd HEaD HEad HeAD HeAd HeaD Head hEAD hEAd hEaD hEad heAD heAd heaD head
|
||||
`.split(/\s+/m)
|
||||
),
|
||||
20 // 20 of 112 (approx. one-fifth) should be enough for testing
|
||||
);
|
||||
|
||||
for (const method of methods) {
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
|
||||
for (const char of method.slice(0, -1)) {
|
||||
await PageObjects.console.sleepForDebouncePeriod();
|
||||
log.debug('Key type "%s"', char);
|
||||
await PageObjects.console.monaco.enterText(char); // e.g. 'P' -> 'Po' -> 'Pos'
|
||||
await retry.waitFor('autocomplete to be visible', () =>
|
||||
PageObjects.console.monaco.isAutocompleteVisible()
|
||||
);
|
||||
expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(true);
|
||||
}
|
||||
|
||||
for (const char of [method.at(-1), ' ', '_']) {
|
||||
await PageObjects.console.sleepForDebouncePeriod();
|
||||
log.debug('Key type "%s"', char);
|
||||
await PageObjects.console.monaco.enterText(char!); // e.g. 'Post ' -> 'Post _'
|
||||
}
|
||||
|
||||
await retry.waitFor('autocomplete to be visible', () =>
|
||||
PageObjects.console.monaco.isAutocompleteVisible()
|
||||
);
|
||||
expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('should activate auto-complete for a single character immediately following a slash in URL', async () => {
|
||||
await PageObjects.console.monaco.enterText('GET .kibana');
|
||||
|
||||
for (const char of ['/', '_']) {
|
||||
await PageObjects.console.sleepForDebouncePeriod();
|
||||
log.debug('Key type "%s"', char);
|
||||
await PageObjects.console.monaco.enterText(char); // i.e. 'GET .kibana/' -> 'GET .kibana/_'
|
||||
}
|
||||
|
||||
await retry.waitFor('autocomplete to be visible', () =>
|
||||
PageObjects.console.monaco.isAutocompleteVisible()
|
||||
);
|
||||
expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(true);
|
||||
});
|
||||
|
||||
it('should activate auto-complete for multiple indices after comma in URL', async () => {
|
||||
await PageObjects.console.monaco.enterText('GET _cat/indices/.kibana');
|
||||
|
||||
await PageObjects.console.sleepForDebouncePeriod();
|
||||
log.debug('Key type ","');
|
||||
await PageObjects.console.monaco.enterText(','); // i.e. 'GET /_cat/indices/.kibana,'
|
||||
|
||||
await PageObjects.console.sleepForDebouncePeriod();
|
||||
log.debug('Key type Ctrl+SPACE');
|
||||
await PageObjects.console.monaco.pressCtrlSpace();
|
||||
|
||||
await retry.waitFor('autocomplete to be visible', () =>
|
||||
PageObjects.console.monaco.isAutocompleteVisible()
|
||||
);
|
||||
expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(true);
|
||||
});
|
||||
|
||||
// not fixed for monaco yet https://github.com/elastic/kibana/issues/184442
|
||||
it.skip('should not activate auto-complete after comma following endpoint in URL', async () => {
|
||||
await PageObjects.console.monaco.enterText('GET _search');
|
||||
|
||||
await PageObjects.console.sleepForDebouncePeriod();
|
||||
log.debug('Key type ","');
|
||||
await PageObjects.console.monaco.enterText(','); // i.e. 'GET _search,'
|
||||
|
||||
await PageObjects.console.sleepForDebouncePeriod();
|
||||
log.debug('Key type Ctrl+SPACE');
|
||||
await PageObjects.console.monaco.pressCtrlSpace();
|
||||
|
||||
expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
// not implemented for monaco yet https://github.com/elastic/kibana/issues/184856
|
||||
describe.skip('with a missing comma in query', () => {
|
||||
const LINE_NUMBER = 4;
|
||||
beforeEach(async () => {
|
||||
await PageObjects.console.clearTextArea();
|
||||
await PageObjects.console.enterRequest();
|
||||
await PageObjects.console.pressEnter();
|
||||
});
|
||||
|
||||
it('should add a comma after previous non empty line', async () => {
|
||||
await PageObjects.console.enterText(`{\n\t"query": {\n\t\t"match": {}`);
|
||||
await PageObjects.console.pressEnter();
|
||||
await PageObjects.console.pressEnter();
|
||||
await PageObjects.console.pressEnter();
|
||||
await PageObjects.console.sleepForDebouncePeriod();
|
||||
await PageObjects.console.promptAutocomplete();
|
||||
await PageObjects.console.pressEnter();
|
||||
await retry.try(async () => {
|
||||
let conApp = await find.byCssSelector('.conApp');
|
||||
const firstInnerHtml = await conApp.getAttribute('innerHTML');
|
||||
await PageObjects.common.sleep(500);
|
||||
conApp = await find.byCssSelector('.conApp');
|
||||
const secondInnerHtml = await conApp.getAttribute('innerHTML');
|
||||
return firstInnerHtml === secondInnerHtml;
|
||||
});
|
||||
const textAreaString = await PageObjects.console.getAllVisibleText();
|
||||
log.debug('Text Area String Value==================\n');
|
||||
log.debug(textAreaString);
|
||||
expect(textAreaString).to.contain(',');
|
||||
const text = await PageObjects.console.getVisibleTextAt(LINE_NUMBER);
|
||||
const lastChar = text.charAt(text.length - 1);
|
||||
expect(lastChar).to.be.eql(',');
|
||||
});
|
||||
|
||||
it('should add a comma after the triple quoted strings', async () => {
|
||||
await PageObjects.console.enterText(`{\n\t"query": {\n\t\t"term": """some data"""`);
|
||||
await PageObjects.console.pressEnter();
|
||||
await PageObjects.console.sleepForDebouncePeriod();
|
||||
await PageObjects.console.promptAutocomplete();
|
||||
await PageObjects.console.pressEnter();
|
||||
|
||||
await retry.waitForWithTimeout('text area to contain comma', 25000, async () => {
|
||||
const textAreaString = await PageObjects.console.getAllVisibleText();
|
||||
return textAreaString.includes(',');
|
||||
});
|
||||
|
||||
const text = await PageObjects.console.getVisibleTextAt(LINE_NUMBER);
|
||||
const lastChar = text.charAt(text.length - 1);
|
||||
expect(lastChar).to.be.eql(',');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with conditional templates', async () => {
|
||||
const CONDITIONAL_TEMPLATES = [
|
||||
{
|
||||
type: 'fs',
|
||||
template: `"location": "path"`,
|
||||
},
|
||||
{
|
||||
type: 'url',
|
||||
template: `"url": ""`,
|
||||
},
|
||||
{ type: 's3', template: `"bucket": ""` },
|
||||
{
|
||||
type: 'azure',
|
||||
template: `"path": ""`,
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(async () => {
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
await PageObjects.console.monaco.enterText('POST _snapshot/test_repo\n');
|
||||
});
|
||||
|
||||
await asyncForEach(CONDITIONAL_TEMPLATES, async ({ type, template }) => {
|
||||
it('should insert different templates depending on the value of type', async () => {
|
||||
await PageObjects.console.monaco.enterText(`{\n\t"type": "${type}",\n`);
|
||||
await PageObjects.console.sleepForDebouncePeriod();
|
||||
// Prompt autocomplete for 'settings'
|
||||
await PageObjects.console.monaco.promptAutocomplete('s');
|
||||
|
||||
await retry.waitFor('autocomplete to be visible', () =>
|
||||
PageObjects.console.monaco.isAutocompleteVisible()
|
||||
);
|
||||
await PageObjects.console.monaco.pressEnter();
|
||||
await retry.try(async () => {
|
||||
const request = await PageObjects.console.monaco.getEditorText();
|
||||
log.debug(request);
|
||||
expect(request).to.contain(`${template}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
149
test/functional/apps/console/monaco/_comments.ts
Normal file
149
test/functional/apps/console/monaco/_comments.ts
Normal file
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { asyncForEach } from '@kbn/std';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const log = getService('log');
|
||||
const PageObjects = getPageObjects(['common', 'console', 'header']);
|
||||
|
||||
// flaky
|
||||
describe.skip('console app', function testComments() {
|
||||
this.tags('includeFirefox');
|
||||
before(async () => {
|
||||
log.debug('navigateTo console');
|
||||
await PageObjects.common.navigateToApp('console');
|
||||
await PageObjects.console.closeHelpIfExists();
|
||||
});
|
||||
|
||||
describe('with comments', async () => {
|
||||
const enterRequest = async (url: string, body: string) => {
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
await PageObjects.console.monaco.enterText(`${url}\n${body}`);
|
||||
};
|
||||
|
||||
async function runTests(
|
||||
tests: Array<{ description: string; url?: string; body: string }>,
|
||||
fn: () => Promise<void>
|
||||
) {
|
||||
await asyncForEach(tests, async ({ description, url, body }) => {
|
||||
it(description, async () => {
|
||||
await enterRequest(url ?? '\nGET search', body);
|
||||
await fn();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('with single line comments', async () => {
|
||||
await runTests(
|
||||
[
|
||||
{
|
||||
url: '\n// GET _search',
|
||||
body: '',
|
||||
description: 'should allow in request url, using //',
|
||||
},
|
||||
{
|
||||
body: '{\n\t\t"query": {\n\t\t\t// "match_all": {}\n}\n}',
|
||||
description: 'should allow in request body, using //',
|
||||
},
|
||||
{
|
||||
url: '\n # GET _search',
|
||||
body: '',
|
||||
description: 'should allow in request url, using #',
|
||||
},
|
||||
{
|
||||
body: '{\n\t\t"query": {\n\t\t\t# "match_all": {}\n}\n}',
|
||||
description: 'should allow in request body, using #',
|
||||
},
|
||||
{
|
||||
description: 'should accept as field names, using //',
|
||||
body: '{\n "//": {} }',
|
||||
},
|
||||
{
|
||||
description: 'should accept as field values, using //',
|
||||
body: '{\n "f": "//" }',
|
||||
},
|
||||
{
|
||||
description: 'should accept as field names, using #',
|
||||
body: '{\n "#": {} }',
|
||||
},
|
||||
{
|
||||
description: 'should accept as field values, using #',
|
||||
body: '{\n "f": "#" }',
|
||||
},
|
||||
],
|
||||
async () => {
|
||||
expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(false);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('with multiline comments', async () => {
|
||||
await runTests(
|
||||
[
|
||||
{
|
||||
url: '\n /* \nGET _search \n*/',
|
||||
body: '',
|
||||
description: 'should allow in request url, using /* */',
|
||||
},
|
||||
{
|
||||
body: '{\n\t\t"query": {\n\t\t\t/* "match_all": {} */ \n}\n}',
|
||||
description: 'should allow in request body, using /* */',
|
||||
},
|
||||
{
|
||||
description: 'should accept as field names, using /*',
|
||||
body: '{\n "/*": {} \n\t\t /* "f": 1 */ \n}',
|
||||
},
|
||||
{
|
||||
description: 'should accept as field values, using */',
|
||||
body: '{\n /* "f": 1 */ \n"f": "*/" \n}',
|
||||
},
|
||||
],
|
||||
async () => {
|
||||
expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(false);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('with invalid syntax in request body', async () => {
|
||||
await runTests(
|
||||
[
|
||||
{
|
||||
description: 'should highlight invalid syntax',
|
||||
body: '{\n "query": \'\'', // E.g. using single quotes
|
||||
},
|
||||
],
|
||||
|
||||
async () => {
|
||||
expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(true);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('with invalid request', async () => {
|
||||
await runTests(
|
||||
[
|
||||
{
|
||||
description: 'with invalid character should display error marker',
|
||||
body: '{\n $ "query": {}',
|
||||
},
|
||||
{
|
||||
description: 'with missing field name',
|
||||
body: '{\n "query": {},\n {}',
|
||||
},
|
||||
],
|
||||
async () => {
|
||||
expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(true);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
150
test/functional/apps/console/monaco/_console.ts
Normal file
150
test/functional/apps/console/monaco/_console.ts
Normal file
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { asyncForEach } from '@kbn/std';
|
||||
import { DEFAULT_INPUT_VALUE } from '@kbn/console-plugin/common/constants';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const retry = getService('retry');
|
||||
const log = getService('log');
|
||||
const browser = getService('browser');
|
||||
const PageObjects = getPageObjects(['common', 'console', 'header']);
|
||||
const security = getService('security');
|
||||
|
||||
describe('console app', function describeIndexTests() {
|
||||
this.tags('includeFirefox');
|
||||
before(async () => {
|
||||
log.debug('navigateTo console');
|
||||
await PageObjects.common.navigateToApp('console');
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await PageObjects.console.closeHelpIfExists();
|
||||
});
|
||||
|
||||
it('should show the default request', async () => {
|
||||
await retry.try(async () => {
|
||||
const actualRequest = await PageObjects.console.monaco.getEditorText();
|
||||
log.debug(actualRequest);
|
||||
expect(actualRequest.replace(/\s/g, '')).to.eql(DEFAULT_INPUT_VALUE.replace(/\s/g, ''));
|
||||
});
|
||||
});
|
||||
|
||||
// issue with the url params with whitespaces https://github.com/elastic/kibana/issues/184927
|
||||
it.skip('default request response should include `"timed_out" : false`', async () => {
|
||||
const expectedResponseContains = `"timed_out": false`;
|
||||
await PageObjects.console.monaco.selectAllRequests();
|
||||
await PageObjects.console.clickPlay();
|
||||
await retry.try(async () => {
|
||||
const actualResponse = await PageObjects.console.monaco.getOutputText();
|
||||
log.debug(actualResponse);
|
||||
expect(actualResponse).to.contain(expectedResponseContains);
|
||||
});
|
||||
});
|
||||
|
||||
// the resizer doesn't work the same as in ace https://github.com/elastic/kibana/issues/184352
|
||||
it.skip('should resize the editor', async () => {
|
||||
const editor = await PageObjects.console.monaco.getEditor();
|
||||
await browser.setWindowSize(1300, 1100);
|
||||
const initialSize = await editor.getSize();
|
||||
await browser.setWindowSize(1000, 1100);
|
||||
const afterSize = await editor.getSize();
|
||||
expect(initialSize.width).to.be.greaterThan(afterSize.width);
|
||||
});
|
||||
|
||||
it('should return statusCode 400 to unsupported HTTP verbs', async () => {
|
||||
const expectedResponseContains = '"statusCode": 400';
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
await PageObjects.console.monaco.enterText('OPTIONS /');
|
||||
await PageObjects.console.clickPlay();
|
||||
await retry.try(async () => {
|
||||
const actualResponse = await PageObjects.console.monaco.getOutputText();
|
||||
log.debug(actualResponse);
|
||||
expect(actualResponse).to.contain(expectedResponseContains);
|
||||
|
||||
expect(await PageObjects.console.hasSuccessBadge()).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with kbn: prefix in request', () => {
|
||||
before(async () => {
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
});
|
||||
it('it should send successful request to Kibana API', async () => {
|
||||
const expectedResponseContains = 'default space';
|
||||
await PageObjects.console.monaco.enterText('GET kbn:/api/spaces/space');
|
||||
await PageObjects.console.clickPlay();
|
||||
await retry.try(async () => {
|
||||
const actualResponse = await PageObjects.console.monaco.getOutputText();
|
||||
log.debug(actualResponse);
|
||||
expect(actualResponse).to.contain(expectedResponseContains);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with query params', () => {
|
||||
it('should issue a successful request', async () => {
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
await PageObjects.console.monaco.enterText(
|
||||
'GET _cat/aliases?format=json&v=true&pretty=true'
|
||||
);
|
||||
await PageObjects.console.clickPlay();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
// set the width of the browser, so that the response status is visible
|
||||
await browser.setWindowSize(1300, 1100);
|
||||
await retry.try(async () => {
|
||||
const status = await PageObjects.console.getResponseStatus();
|
||||
expect(status).to.eql(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiple requests output', function () {
|
||||
const sendMultipleRequests = async (requests: string[]) => {
|
||||
await asyncForEach(requests, async (request) => {
|
||||
await PageObjects.console.monaco.enterText(request);
|
||||
});
|
||||
await PageObjects.console.monaco.selectAllRequests();
|
||||
await PageObjects.console.clickPlay();
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
await security.testUser.setRoles(['kibana_admin', 'test_index']);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await security.testUser.restoreDefaults();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await PageObjects.console.closeHelpIfExists();
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
});
|
||||
|
||||
it('should contain comments starting with # symbol', async () => {
|
||||
await sendMultipleRequests(['\n PUT test-index', '\n DELETE test-index']);
|
||||
await retry.try(async () => {
|
||||
const response = await PageObjects.console.monaco.getOutputText();
|
||||
log.debug(response);
|
||||
expect(response).to.contain('# PUT test-index 200');
|
||||
expect(response).to.contain('# DELETE test-index 200');
|
||||
});
|
||||
});
|
||||
|
||||
// not implemented for monaco yet https://github.com/elastic/kibana/issues/184010
|
||||
it.skip('should display status badges', async () => {
|
||||
await sendMultipleRequests(['\n GET _search/test', '\n GET _search']);
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
expect(await PageObjects.console.hasWarningBadge()).to.be(true);
|
||||
expect(await PageObjects.console.hasSuccessBadge()).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
54
test/functional/apps/console/monaco/_console_ccs.ts
Normal file
54
test/functional/apps/console/monaco/_console_ccs.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const retry = getService('retry');
|
||||
const log = getService('log');
|
||||
const browser = getService('browser');
|
||||
const PageObjects = getPageObjects(['common', 'console']);
|
||||
const remoteEsArchiver = getService('remoteEsArchiver' as 'esArchiver');
|
||||
|
||||
describe('Console App CCS', function describeIndexTests() {
|
||||
this.tags('includeFirefox');
|
||||
before(async () => {
|
||||
await remoteEsArchiver.loadIfNeeded(
|
||||
'test/functional/fixtures/es_archiver/logstash_functional'
|
||||
);
|
||||
// resize the editor to allow the whole of the response to be displayed
|
||||
await browser.setWindowSize(1200, 1800);
|
||||
log.debug('navigateTo console');
|
||||
await PageObjects.common.navigateToApp('console');
|
||||
await retry.try(async () => {
|
||||
await PageObjects.console.collapseHelp();
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await remoteEsArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional');
|
||||
});
|
||||
|
||||
describe('Perform CCS Search in Console', () => {
|
||||
before(async () => {
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
});
|
||||
it('it should be able to access remote data', async () => {
|
||||
await PageObjects.console.monaco.enterText(
|
||||
'\nGET ftr-remote:logstash-*/_search\n {\n "query": {\n "bool": {\n "must": [\n {"match": {"extension" : "jpg"} \n}\n]\n}\n}\n}'
|
||||
);
|
||||
await PageObjects.console.clickPlay();
|
||||
await retry.try(async () => {
|
||||
const actualResponse = await PageObjects.console.monaco.getOutputText();
|
||||
expect(actualResponse).to.contain('"_index": "ftr-remote:logstash-2015.09.20"');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
104
test/functional/apps/console/monaco/_context_menu.ts
Normal file
104
test/functional/apps/console/monaco/_context_menu.ts
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const log = getService('log');
|
||||
const retry = getService('retry');
|
||||
const PageObjects = getPageObjects(['common', 'console']);
|
||||
const browser = getService('browser');
|
||||
const toasts = getService('toasts');
|
||||
|
||||
describe('console context menu', function testContextMenu() {
|
||||
before(async () => {
|
||||
await PageObjects.common.navigateToApp('console');
|
||||
// Ensure that the text area can be interacted with
|
||||
await PageObjects.console.closeHelpIfExists();
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
await PageObjects.console.monaco.enterText('GET _search');
|
||||
});
|
||||
|
||||
it('should open context menu', async () => {
|
||||
expect(await PageObjects.console.isContextMenuOpen()).to.be(false);
|
||||
await PageObjects.console.clickContextMenu();
|
||||
expect(PageObjects.console.isContextMenuOpen()).to.be.eql(true);
|
||||
});
|
||||
|
||||
it('should have options to copy as curl, open documentation, and auto indent', async () => {
|
||||
await PageObjects.console.clickContextMenu();
|
||||
expect(PageObjects.console.isContextMenuOpen()).to.be.eql(true);
|
||||
expect(PageObjects.console.isCopyAsCurlButtonVisible()).to.be.eql(true);
|
||||
expect(PageObjects.console.isOpenDocumentationButtonVisible()).to.be.eql(true);
|
||||
expect(PageObjects.console.isAutoIndentButtonVisible()).to.be.eql(true);
|
||||
});
|
||||
|
||||
it('should copy as curl and show toast when copy as curl button is clicked', async () => {
|
||||
await PageObjects.console.clickContextMenu();
|
||||
await PageObjects.console.clickCopyAsCurlButton();
|
||||
|
||||
const resultToast = await toasts.getElementByIndex(1);
|
||||
const toastText = await resultToast.getVisibleText();
|
||||
|
||||
if (toastText.includes('Write permission denied')) {
|
||||
log.debug('Write permission denied, skipping test');
|
||||
return;
|
||||
}
|
||||
|
||||
expect(toastText).to.be('Request copied as cURL');
|
||||
|
||||
const canReadClipboard = await browser.checkBrowserPermission('clipboard-read');
|
||||
if (canReadClipboard) {
|
||||
const clipboardText = await browser.getClipboardValue();
|
||||
expect(clipboardText).to.contain('curl -XGET');
|
||||
}
|
||||
});
|
||||
|
||||
it('should open documentation when open documentation button is clicked', async () => {
|
||||
await PageObjects.console.clickContextMenu();
|
||||
await PageObjects.console.clickOpenDocumentationButton();
|
||||
|
||||
await retry.tryForTime(10000, async () => {
|
||||
await browser.switchTab(1);
|
||||
});
|
||||
|
||||
// Retry until the documentation is loaded
|
||||
await retry.try(async () => {
|
||||
const url = await browser.getCurrentUrl();
|
||||
expect(url).to.contain('search-search.html');
|
||||
});
|
||||
|
||||
// Close the documentation tab
|
||||
await browser.closeCurrentWindow();
|
||||
await browser.switchTab(0);
|
||||
});
|
||||
|
||||
// not implemented yet for monaco https://github.com/elastic/kibana/issues/185891
|
||||
it.skip('should toggle auto indent when auto indent button is clicked', async () => {
|
||||
await PageObjects.console.clearTextArea();
|
||||
await PageObjects.console.enterRequest('GET _search\n{"query": {"match_all": {}}}');
|
||||
await PageObjects.console.clickContextMenu();
|
||||
await PageObjects.console.clickAutoIndentButton();
|
||||
// Retry until the request is auto indented
|
||||
await retry.try(async () => {
|
||||
const request = await PageObjects.console.getRequest();
|
||||
expect(request).to.be.eql('GET _search\n{\n "query": {\n "match_all": {}\n }\n}');
|
||||
});
|
||||
|
||||
await PageObjects.console.clickContextMenu();
|
||||
// Click the auto-indent button again to condense request
|
||||
await PageObjects.console.clickAutoIndentButton();
|
||||
// Retry until the request is condensed
|
||||
await retry.try(async () => {
|
||||
const request = await PageObjects.console.getRequest();
|
||||
expect(request).to.be.eql('GET _search\n{"query":{"match_all":{}}}');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
129
test/functional/apps/console/monaco/_misc_console_behavior.ts
Normal file
129
test/functional/apps/console/monaco/_misc_console_behavior.ts
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const retry = getService('retry');
|
||||
const browser = getService('browser');
|
||||
const PageObjects = getPageObjects(['common', 'console', 'header']);
|
||||
|
||||
describe('misc console behavior', function testMiscConsoleBehavior() {
|
||||
this.tags('includeFirefox');
|
||||
before(async () => {
|
||||
await browser.setWindowSize(1200, 800);
|
||||
await PageObjects.common.navigateToApp('console');
|
||||
// Ensure that the text area can be interacted with
|
||||
await PageObjects.console.closeHelpIfExists();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
});
|
||||
|
||||
describe('keyboard shortcuts', () => {
|
||||
let tabCount = 1;
|
||||
|
||||
after(async () => {
|
||||
if (tabCount > 1) {
|
||||
await browser.closeCurrentWindow();
|
||||
await browser.switchTab(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('should execute the request when Ctrl+Enter is pressed', async () => {
|
||||
await PageObjects.console.monaco.enterText('GET _search');
|
||||
await PageObjects.console.monaco.pressCtrlEnter();
|
||||
await retry.try(async () => {
|
||||
const response = await PageObjects.console.monaco.getOutputText();
|
||||
expect(response).to.contain('"timed_out": false');
|
||||
});
|
||||
});
|
||||
|
||||
it('should auto indent current request when Ctrl+I is pressed', async () => {
|
||||
await PageObjects.console.monaco.enterText('GET _search\n{"query": {"match_all": {}}}');
|
||||
await PageObjects.console.monaco.selectCurrentRequest();
|
||||
await PageObjects.console.monaco.pressCtrlI();
|
||||
await retry.waitFor('request to be auto indented', async () => {
|
||||
const request = await PageObjects.console.monaco.getEditorText();
|
||||
return request === 'GET _search\n{\n "query": {\n "match_all": {}\n }\n}';
|
||||
});
|
||||
});
|
||||
|
||||
it('should jump to the previous request when Ctrl+Up is pressed', async () => {
|
||||
await PageObjects.console.monaco.enterText('\nGET _search/foo');
|
||||
await PageObjects.console.monaco.enterText('\nGET _search/bar');
|
||||
await PageObjects.console.monaco.pressCtrlUp();
|
||||
await retry.waitFor('request to be selected', async () => {
|
||||
const request = await PageObjects.console.monaco.getEditorTextAtLine(1);
|
||||
return request === 'GET _search/foo';
|
||||
});
|
||||
});
|
||||
|
||||
it('should jump to the next request when Ctrl+Down is pressed', async () => {
|
||||
await PageObjects.console.monaco.enterText('\nGET _search/foo');
|
||||
await PageObjects.console.monaco.enterText('\nGET _search/bar');
|
||||
await PageObjects.console.monaco.pressCtrlUp();
|
||||
await PageObjects.console.monaco.pressCtrlDown();
|
||||
await retry.waitFor('request to be selected', async () => {
|
||||
const request = await PageObjects.console.monaco.getEditorTextAtLine(2);
|
||||
return request === 'GET _search/bar';
|
||||
});
|
||||
});
|
||||
|
||||
// flaky
|
||||
it.skip('should go to line number when Ctrl+L is pressed', async () => {
|
||||
await PageObjects.console.monaco.enterText(
|
||||
'\nGET _search/foo\n{\n "query": {\n "match_all": {} \n} \n}'
|
||||
);
|
||||
await PageObjects.console.monaco.pressCtrlL();
|
||||
// Sleep to allow the line number input to be focused
|
||||
await PageObjects.common.sleep(1000);
|
||||
const alert = await browser.getAlert();
|
||||
await alert?.sendKeys('4');
|
||||
await alert?.accept();
|
||||
await PageObjects.common.sleep(1000);
|
||||
expect(await PageObjects.console.monaco.getCurrentLineNumber()).to.be(4);
|
||||
});
|
||||
|
||||
// flaky
|
||||
it.skip('should open documentation when Ctrl+/ is pressed', async () => {
|
||||
await PageObjects.console.monaco.enterText('GET _search');
|
||||
await PageObjects.console.monaco.pressEscape();
|
||||
await PageObjects.console.monaco.pressCtrlSlash();
|
||||
await retry.tryForTime(10000, async () => {
|
||||
await browser.switchTab(1);
|
||||
tabCount++;
|
||||
});
|
||||
|
||||
// Retry until the documentation is loaded
|
||||
await retry.try(async () => {
|
||||
const url = await browser.getCurrentUrl();
|
||||
expect(url).to.contain('search-search.html');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('customizable font size', () => {
|
||||
// flaky
|
||||
it.skip('should allow the font size to be customized', async () => {
|
||||
await PageObjects.console.setFontSizeSetting(20);
|
||||
await retry.try(async () => {
|
||||
// the settings are not applied synchronously, so we retry for a time
|
||||
expect(await PageObjects.console.monaco.getFontSize()).to.be('20px');
|
||||
});
|
||||
|
||||
await PageObjects.console.setFontSizeSetting(24);
|
||||
await retry.try(async () => {
|
||||
// the settings are not applied synchronously, so we retry for a time
|
||||
expect(await PageObjects.console.monaco.getFontSize()).to.be('24px');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
41
test/functional/apps/console/monaco/_settings.ts
Normal file
41
test/functional/apps/console/monaco/_settings.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const log = getService('log');
|
||||
const PageObjects = getPageObjects(['common', 'console']);
|
||||
|
||||
describe('console settings', function testSettings() {
|
||||
this.tags('includeFirefox');
|
||||
before(async () => {
|
||||
log.debug('navigateTo console');
|
||||
await PageObjects.common.navigateToApp('console');
|
||||
// Ensure that the text area can be interacted with
|
||||
await PageObjects.console.closeHelpIfExists();
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
});
|
||||
|
||||
it('displays the a11y overlay', async () => {
|
||||
await PageObjects.console.monaco.pressEscape();
|
||||
const isOverlayVisible = await PageObjects.console.monaco.isA11yOverlayVisible();
|
||||
expect(isOverlayVisible).to.be(true);
|
||||
});
|
||||
|
||||
it('disables the a11y overlay via settings', async () => {
|
||||
await PageObjects.console.openSettings();
|
||||
await PageObjects.console.toggleA11yOverlaySetting();
|
||||
|
||||
await PageObjects.console.monaco.pressEscape();
|
||||
const isOverlayVisible = await PageObjects.console.monaco.isA11yOverlayVisible();
|
||||
expect(isOverlayVisible).to.be(false);
|
||||
});
|
||||
});
|
||||
}
|
101
test/functional/apps/console/monaco/_text_input.ts
Normal file
101
test/functional/apps/console/monaco/_text_input.ts
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const retry = getService('retry');
|
||||
const toasts = getService('toasts');
|
||||
const PageObjects = getPageObjects(['common', 'console', 'header']);
|
||||
|
||||
describe('text input', function testTextInput() {
|
||||
before(async () => {
|
||||
await PageObjects.common.navigateToApp('console');
|
||||
await PageObjects.console.closeHelpIfExists();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
});
|
||||
|
||||
describe('with a data URI in the load_from query', () => {
|
||||
it('loads the data from the URI', async () => {
|
||||
await PageObjects.common.navigateToApp('console', {
|
||||
hash: '#/console?load_from=data:text/plain,BYUwNmD2Q',
|
||||
});
|
||||
|
||||
await retry.try(async () => {
|
||||
const actualRequest = await PageObjects.console.monaco.getEditorText();
|
||||
expect(actualRequest.trim()).to.eql('hello');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with invalid data', () => {
|
||||
it('shows a toast error', async () => {
|
||||
await PageObjects.common.navigateToApp('console', {
|
||||
hash: '#/console?load_from=data:text/plain,BYUwNmD2',
|
||||
});
|
||||
|
||||
await retry.try(async () => {
|
||||
expect(await toasts.getCount()).to.equal(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// not yet implemented for monaco https://github.com/elastic/kibana/issues/186001
|
||||
describe.skip('copy/pasting cURL commands into the console', () => {
|
||||
it('should convert cURL commands into the console request format', async () => {
|
||||
await PageObjects.console.monaco.enterText(
|
||||
`\n curl -XGET "http://localhost:9200/_search?pretty" -d'\n{"query": {"match_all": {}}}'`
|
||||
);
|
||||
await PageObjects.console.monaco.copyRequestsToClipboard();
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
await PageObjects.console.monaco.pasteClipboardValue();
|
||||
await retry.try(async () => {
|
||||
const actualRequest = await PageObjects.console.monaco.getEditorText();
|
||||
expect(actualRequest.trim()).to.eql('GET /_search?pretty\n {"query": {"match_all": {}}}');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('console history', () => {
|
||||
const sendRequest = async (request: string) => {
|
||||
await PageObjects.console.monaco.enterText(request);
|
||||
await PageObjects.console.clickPlay();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
};
|
||||
|
||||
it('should show the history', async () => {
|
||||
await sendRequest('GET /_search?pretty');
|
||||
await PageObjects.console.clickHistory();
|
||||
await retry.try(async () => {
|
||||
const history = await PageObjects.console.getHistoryEntries();
|
||||
expect(history).to.eql(['/_search?pretty (a few seconds ago)']);
|
||||
});
|
||||
|
||||
// Clear the history
|
||||
await PageObjects.console.clickClearHistory();
|
||||
await PageObjects.console.closeHistory();
|
||||
});
|
||||
|
||||
it('should load a request from history', async () => {
|
||||
await sendRequest('GET _search\n{"query": {"match_all": {}}}');
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
await PageObjects.console.clickHistory();
|
||||
await PageObjects.console.loadRequestFromHistory(0);
|
||||
await retry.try(async () => {
|
||||
const actualRequest = await PageObjects.console.monaco.getEditorText();
|
||||
expect(actualRequest.trim()).to.eql(
|
||||
'GET _search\n{\n "query": {\n "match_all": {}\n }\n}'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
71
test/functional/apps/console/monaco/_variables.ts
Normal file
71
test/functional/apps/console/monaco/_variables.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default ({ getService, getPageObjects }: FtrProviderContext) => {
|
||||
const retry = getService('retry');
|
||||
const log = getService('log');
|
||||
const PageObjects = getPageObjects(['common', 'console', 'header']);
|
||||
|
||||
describe('Console variables', function testConsoleVariables() {
|
||||
// FLAKY on firefox: https://github.com/elastic/kibana/issues/157776
|
||||
// this.tags('includeFirefox');
|
||||
before(async () => {
|
||||
log.debug('navigateTo console');
|
||||
await PageObjects.common.navigateToApp('console');
|
||||
await PageObjects.console.closeHelpIfExists();
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
});
|
||||
|
||||
it('should allow creating a new variable', async () => {
|
||||
await PageObjects.console.addNewVariable({ name: 'index1', value: 'test' });
|
||||
const variables = await PageObjects.console.getVariables();
|
||||
log.debug(variables);
|
||||
expect(variables).to.contain('index1');
|
||||
});
|
||||
|
||||
it('should allow removing a variable', async () => {
|
||||
await PageObjects.console.addNewVariable({ name: 'index2', value: 'test' });
|
||||
await PageObjects.console.removeVariables();
|
||||
const variables = await PageObjects.console.getVariables();
|
||||
expect(variables).to.eql([]);
|
||||
});
|
||||
|
||||
describe('with variables in url', () => {
|
||||
it('should send a successful request', async () => {
|
||||
await PageObjects.console.addNewVariable({ name: 'index3', value: '_search' });
|
||||
await PageObjects.console.monaco.enterText('\n GET ${index3}');
|
||||
await PageObjects.console.clickPlay();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
await retry.try(async () => {
|
||||
const status = await PageObjects.console.getResponseStatus();
|
||||
expect(status).to.eql(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with variables in request body', () => {
|
||||
// bug in monaco https://github.com/elastic/kibana/issues/185999
|
||||
it.skip('should send a successful request', async () => {
|
||||
await PageObjects.console.addNewVariable({ name: 'query1', value: '{"match_all": {}}' });
|
||||
await PageObjects.console.monaco.enterText('\n GET _search\n');
|
||||
await PageObjects.console.monaco.enterText(`{\n\t"query": "\${query1}"`);
|
||||
await PageObjects.console.clickPlay();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
await retry.try(async () => {
|
||||
const status = await PageObjects.console.getResponseStatus();
|
||||
expect(status).to.eql(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
51
test/functional/apps/console/monaco/_vector_tile.ts
Normal file
51
test/functional/apps/console/monaco/_vector_tile.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const PageObjects = getPageObjects(['common', 'console', 'header', 'home']);
|
||||
const retry = getService('retry');
|
||||
const security = getService('security');
|
||||
|
||||
describe('console vector tiles response validation', function describeIndexTests() {
|
||||
before(async () => {
|
||||
await security.testUser.setRoles(['kibana_admin', 'kibana_sample_admin']);
|
||||
await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', {
|
||||
useActualUrl: true,
|
||||
});
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.home.addSampleDataSet('logs');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.common.navigateToApp('console');
|
||||
await PageObjects.console.closeHelpIfExists();
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
});
|
||||
|
||||
it('should validate response', async () => {
|
||||
await PageObjects.console.monaco.enterText(
|
||||
`GET kibana_sample_data_logs/_mvt/geo.coordinates/0/0/0`
|
||||
);
|
||||
await PageObjects.console.clickPlay();
|
||||
await retry.try(async () => {
|
||||
const actualResponse = await PageObjects.console.monaco.getOutputText();
|
||||
expect(actualResponse).to.contain('"meta": [');
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', {
|
||||
useActualUrl: true,
|
||||
});
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.home.removeSampleDataSet('logs');
|
||||
await security.testUser.restoreDefaults();
|
||||
});
|
||||
});
|
||||
}
|
118
test/functional/apps/console/monaco/_xjson.ts
Normal file
118
test/functional/apps/console/monaco/_xjson.ts
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default ({ getService, getPageObjects }: FtrProviderContext) => {
|
||||
const retry = getService('retry');
|
||||
const PageObjects = getPageObjects(['common', 'console', 'header']);
|
||||
|
||||
describe('XJSON', function testXjson() {
|
||||
this.tags('includeFirefox');
|
||||
before(async () => {
|
||||
await PageObjects.common.navigateToApp('console');
|
||||
await PageObjects.console.closeHelpIfExists();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
});
|
||||
|
||||
const executeRequest = async (request = '\n GET _search') => {
|
||||
await PageObjects.console.monaco.enterText(request);
|
||||
await PageObjects.console.clickPlay();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
};
|
||||
|
||||
describe('inline http request', () => {
|
||||
it('should not have validation errors', async () => {
|
||||
await PageObjects.console.monaco.enterText('\n GET foo/bar');
|
||||
expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(false);
|
||||
});
|
||||
|
||||
it('should have validation error for invalid method', async () => {
|
||||
await PageObjects.console.monaco.enterText('\n FOO foo/bar');
|
||||
// Retry because the error marker is not always immediately visible.
|
||||
await retry.try(async () => {
|
||||
expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have validation error for invalid path', async () => {
|
||||
await PageObjects.console.monaco.enterText('\n GET');
|
||||
// Retry because the error marker is not always immediately visible.
|
||||
await retry.try(async () => {
|
||||
expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have validation error for invalid body', async () => {
|
||||
await PageObjects.console.monaco.enterText('\n POST foo/bar\n {"foo": "bar"');
|
||||
// Retry because the error marker is not always immediately visible.
|
||||
await retry.try(async () => {
|
||||
expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not trigger error for multiple bodies for _msearch requests', async () => {
|
||||
await PageObjects.console.monaco.enterText(
|
||||
'\nGET foo/_msearch \n{}\n{"query": {"match_all": {}}}\n{"index": "bar"}\n{"query": {"match_all": {}}}'
|
||||
);
|
||||
// Retry until typing is finished.
|
||||
await retry.try(async () => {
|
||||
expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not trigger validation errors for multiple JSON blocks', async () => {
|
||||
await PageObjects.console.monaco.enterText('\nPOST test/doc/1 \n{\n "foo": "bar"\n}');
|
||||
await PageObjects.console.monaco.enterText('\nPOST test/doc/2 \n{\n "foo": "baz"\n}');
|
||||
await PageObjects.console.monaco.enterText('\nPOST test/doc/3 \n{\n "foo": "qux"\n}');
|
||||
// Retry until typing is finished.
|
||||
await retry.try(async () => {
|
||||
expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow escaping quotation mark by wrapping it in triple quotes', async () => {
|
||||
await PageObjects.console.monaco.enterText(
|
||||
'\nPOST test/_doc/1 \n{\n "foo": """look "escaped" quotes"""\n}'
|
||||
);
|
||||
// Retry until typing is finished and validation errors are gone.
|
||||
await retry.try(async () => {
|
||||
expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow inline comments in request url row', async () => {
|
||||
await executeRequest('\n GET _search // inline comment');
|
||||
expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(false);
|
||||
expect(await PageObjects.console.getResponseStatus()).to.eql(200);
|
||||
});
|
||||
|
||||
it('should allow inline comments in request body', async () => {
|
||||
await executeRequest(
|
||||
'\n GET _search \n{\n "query": {\n "match_all": {} // inline comment\n}\n}'
|
||||
);
|
||||
expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(false);
|
||||
expect(await PageObjects.console.getResponseStatus()).to.eql(200);
|
||||
});
|
||||
|
||||
it('should print warning for deprecated request', async () => {
|
||||
await executeRequest('\nGET .kibana');
|
||||
expect(await PageObjects.console.monaco.responseHasDeprecationWarning()).to.be(true);
|
||||
});
|
||||
|
||||
it('should not print warning for non-deprecated request', async () => {
|
||||
await executeRequest('\n GET _search');
|
||||
expect(await PageObjects.console.monaco.responseHasDeprecationWarning()).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
|
@ -7,10 +7,10 @@
|
|||
*/
|
||||
|
||||
import { FtrConfigProviderContext } from '@kbn/test';
|
||||
import { configureHTTP2 } from '../../../common/configure_http2';
|
||||
import { configureHTTP2 } from '../../../../common/configure_http2';
|
||||
|
||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const functionalConfig = await readConfigFile(require.resolve('../../config.base.js'));
|
||||
const functionalConfig = await readConfigFile(require.resolve('../../../config.base.js'));
|
||||
|
||||
return configureHTTP2({
|
||||
...functionalConfig.getAll(),
|
34
test/functional/apps/console/monaco/index.ts
Normal file
34
test/functional/apps/console/monaco/index.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
||||
const browser = getService('browser');
|
||||
const config = getService('config');
|
||||
|
||||
describe('console app', function () {
|
||||
before(async function () {
|
||||
await browser.setWindowSize(1300, 1100);
|
||||
});
|
||||
if (config.get('esTestCluster.ccs')) {
|
||||
loadTestFile(require.resolve('./_console_ccs'));
|
||||
} else {
|
||||
loadTestFile(require.resolve('./_console'));
|
||||
loadTestFile(require.resolve('./_autocomplete'));
|
||||
loadTestFile(require.resolve('./_vector_tile'));
|
||||
loadTestFile(require.resolve('./_comments'));
|
||||
loadTestFile(require.resolve('./_variables'));
|
||||
loadTestFile(require.resolve('./_xjson'));
|
||||
loadTestFile(require.resolve('./_misc_console_behavior'));
|
||||
loadTestFile(require.resolve('./_context_menu'));
|
||||
loadTestFile(require.resolve('./_text_input'));
|
||||
loadTestFile(require.resolve('./_settings'));
|
||||
}
|
||||
});
|
||||
}
|
|
@ -17,5 +17,13 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
junit: {
|
||||
reportName: 'Dashboard Elements - Controls tests',
|
||||
},
|
||||
kbnTestServer: {
|
||||
...functionalConfig.get('kbnTestServer'),
|
||||
serverArgs: [
|
||||
...functionalConfig.get('kbnTestServer.serverArgs'),
|
||||
// disabling the monaco editor to run tests for ace
|
||||
`--console.dev.enableMonaco=false`,
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,5 +17,13 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
junit: {
|
||||
reportName: 'Dashboard Elements - Controls Options List tests',
|
||||
},
|
||||
kbnTestServer: {
|
||||
...functionalConfig.get('kbnTestServer'),
|
||||
serverArgs: [
|
||||
...functionalConfig.get('kbnTestServer.serverArgs'),
|
||||
// disabling the monaco editor to run tests for ace
|
||||
`--console.dev.enableMonaco=false`,
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
testFiles: [
|
||||
require.resolve('./apps/dashboard/group3'),
|
||||
require.resolve('./apps/discover/ccs_compatibility'),
|
||||
require.resolve('./apps/console/_console_ccs'),
|
||||
require.resolve('./apps/console/monaco/_console_ccs'),
|
||||
require.resolve('./apps/management/ccs_compatibility'),
|
||||
require.resolve('./apps/getting_started'),
|
||||
],
|
||||
|
|
|
@ -16,7 +16,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
return {
|
||||
...baseConfig.getAll(),
|
||||
|
||||
testFiles: [require.resolve('../apps/console')],
|
||||
testFiles: [require.resolve('../apps/console/monaco')],
|
||||
|
||||
junit: {
|
||||
reportName: 'Firefox UI Functional Tests - Console',
|
||||
|
|
|
@ -18,6 +18,166 @@ export class ConsolePageObject extends FtrService {
|
|||
private readonly common = this.ctx.getPageObject('common');
|
||||
private readonly browser = this.ctx.getService('browser');
|
||||
|
||||
public monaco = {
|
||||
getTextArea: async () => {
|
||||
const codeEditor = await this.testSubjects.find('consoleMonacoEditor');
|
||||
return await codeEditor.findByTagName('textarea');
|
||||
},
|
||||
getEditorText: async () => {
|
||||
const codeEditor = await this.testSubjects.find('consoleMonacoEditor');
|
||||
const editorViewDiv = await codeEditor.findByClassName('view-lines');
|
||||
return await editorViewDiv.getVisibleText();
|
||||
},
|
||||
getEditorTextAtLine: async (line: number) => {
|
||||
const codeEditor = await this.testSubjects.find('consoleMonacoEditor');
|
||||
const editorViewDiv = await codeEditor.findAllByClassName('view-line');
|
||||
return await editorViewDiv[line].getVisibleText();
|
||||
},
|
||||
getCurrentLineNumber: async () => {
|
||||
const textArea = await this.monaco.getTextArea();
|
||||
const styleAttribute = (await textArea.getAttribute('style')) ?? '';
|
||||
const height = parseFloat(styleAttribute.replace(/.*height: ([+-]?\d+(\.\d+)?).*/, '$1'));
|
||||
const top = parseFloat(styleAttribute.replace(/.*top: ([+-]?\d+(\.\d+)?).*/, '$1'));
|
||||
// calculate the line number by dividing the top position by the line height
|
||||
// and adding 1 because line numbers start at 1
|
||||
return Math.ceil(top / height) + 1;
|
||||
},
|
||||
clearEditorText: async () => {
|
||||
const textArea = await this.monaco.getTextArea();
|
||||
await textArea.clickMouseButton();
|
||||
await textArea.clearValueWithKeyboard();
|
||||
},
|
||||
getOutputText: async () => {
|
||||
const outputPanel = await this.testSubjects.find('consoleMonacoOutput');
|
||||
const outputViewDiv = await outputPanel.findByClassName('monaco-scrollable-element');
|
||||
return await outputViewDiv.getVisibleText();
|
||||
},
|
||||
pressEnter: async () => {
|
||||
const textArea = await this.monaco.getTextArea();
|
||||
await textArea.pressKeys(Key.ENTER);
|
||||
},
|
||||
enterText: async (text: string) => {
|
||||
const textArea = await this.monaco.getTextArea();
|
||||
await textArea.type(text);
|
||||
},
|
||||
promptAutocomplete: async (letter = 'b') => {
|
||||
const textArea = await this.monaco.getTextArea();
|
||||
await textArea.type(letter);
|
||||
await this.retry.waitFor('autocomplete to be visible', () =>
|
||||
this.monaco.isAutocompleteVisible()
|
||||
);
|
||||
},
|
||||
isAutocompleteVisible: async () => {
|
||||
const element = await this.find.byClassName('suggest-widget').catch(() => null);
|
||||
if (!element) return false;
|
||||
|
||||
const attribute = await element.getAttribute('style');
|
||||
return !attribute?.includes('display: none;');
|
||||
},
|
||||
getAutocompleteSuggestion: async (index: number) => {
|
||||
const suggestionsWidget = await this.find.byClassName('suggest-widget');
|
||||
const suggestions = await suggestionsWidget.findAllByClassName('monaco-list-row');
|
||||
const label = await suggestions[index].findByClassName('label-name');
|
||||
return label.getVisibleText();
|
||||
},
|
||||
pressUp: async (shift: boolean = false) => {
|
||||
const textArea = await this.monaco.getTextArea();
|
||||
await textArea.pressKeys(shift ? [Key.SHIFT, Key.UP] : Key.UP);
|
||||
},
|
||||
pressDown: async (shift: boolean = false) => {
|
||||
const textArea = await this.monaco.getTextArea();
|
||||
await textArea.pressKeys(shift ? [Key.SHIFT, Key.DOWN] : Key.DOWN);
|
||||
},
|
||||
pressRight: async (shift: boolean = false) => {
|
||||
const textArea = await this.monaco.getTextArea();
|
||||
await textArea.pressKeys(shift ? [Key.SHIFT, Key.RIGHT] : Key.RIGHT);
|
||||
},
|
||||
pressLeft: async (shift: boolean = false) => {
|
||||
const textArea = await this.monaco.getTextArea();
|
||||
await textArea.pressKeys(shift ? [Key.SHIFT, Key.LEFT] : Key.LEFT);
|
||||
},
|
||||
pressCtrlSpace: async () => {
|
||||
const textArea = await this.monaco.getTextArea();
|
||||
await textArea.pressKeys([
|
||||
Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'],
|
||||
Key.SPACE,
|
||||
]);
|
||||
},
|
||||
pressCtrlEnter: async () => {
|
||||
const textArea = await this.monaco.getTextArea();
|
||||
await textArea.pressKeys([
|
||||
Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'],
|
||||
Key.ENTER,
|
||||
]);
|
||||
},
|
||||
pressCtrlI: async () => {
|
||||
const textArea = await this.monaco.getTextArea();
|
||||
await textArea.pressKeys([Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'], 'i']);
|
||||
},
|
||||
pressCtrlUp: async () => {
|
||||
const textArea = await this.monaco.getTextArea();
|
||||
await textArea.pressKeys([
|
||||
Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'],
|
||||
Key.UP,
|
||||
]);
|
||||
},
|
||||
pressCtrlDown: async () => {
|
||||
const textArea = await this.monaco.getTextArea();
|
||||
await textArea.pressKeys([
|
||||
Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'],
|
||||
Key.DOWN,
|
||||
]);
|
||||
},
|
||||
pressCtrlL: async () => {
|
||||
const textArea = await this.monaco.getTextArea();
|
||||
await textArea.pressKeys([Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'], 'l']);
|
||||
},
|
||||
pressCtrlSlash: async () => {
|
||||
const textArea = await this.monaco.getTextArea();
|
||||
await textArea.pressKeys([Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'], '/']);
|
||||
},
|
||||
pressEscape: async () => {
|
||||
const textArea = await this.monaco.getTextArea();
|
||||
await textArea.pressKeys(Key.ESCAPE);
|
||||
},
|
||||
selectAllRequests: async () => {
|
||||
const textArea = await this.monaco.getTextArea();
|
||||
const selectionKey = Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'];
|
||||
await textArea.pressKeys([selectionKey, 'a']);
|
||||
},
|
||||
getEditor: async () => {
|
||||
return await this.testSubjects.find('consoleMonacoEditor');
|
||||
},
|
||||
hasInvalidSyntax: async () => {
|
||||
return await this.find.existsByCssSelector('.squiggly-error');
|
||||
},
|
||||
responseHasDeprecationWarning: async () => {
|
||||
const response = await this.monaco.getOutputText();
|
||||
return response.trim().startsWith('#!');
|
||||
},
|
||||
selectCurrentRequest: async () => {
|
||||
const textArea = await this.monaco.getTextArea();
|
||||
await textArea.clickMouseButton();
|
||||
},
|
||||
getFontSize: async () => {
|
||||
const codeEditor = await this.testSubjects.find('consoleMonacoEditor');
|
||||
const editorViewDiv = await codeEditor.findByClassName('view-line');
|
||||
return await editorViewDiv.getComputedStyle('font-size');
|
||||
},
|
||||
pasteClipboardValue: async () => {
|
||||
const textArea = await this.monaco.getTextArea();
|
||||
await textArea.pressKeys([Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'], 'v']);
|
||||
},
|
||||
copyRequestsToClipboard: async () => {
|
||||
const textArea = await this.monaco.getTextArea();
|
||||
await textArea.pressKeys([Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'], 'a']);
|
||||
await textArea.pressKeys([Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'], 'c']);
|
||||
},
|
||||
isA11yOverlayVisible: async () => {
|
||||
return await this.testSubjects.exists('codeEditorAccessibilityOverlay');
|
||||
},
|
||||
};
|
||||
|
||||
public async getVisibleTextFromAceEditor(editor: WebElementWrapper) {
|
||||
const lines = await editor.findAllByClassName('ace_line_group');
|
||||
const linesText = await Promise.all(lines.map(async (line) => await line.getVisibleText()));
|
||||
|
|
|
@ -72,5 +72,6 @@
|
|||
"@kbn/ftr-common-functional-ui-services",
|
||||
"@kbn/monaco",
|
||||
"@kbn/search-types",
|
||||
"@kbn/console-plugin",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -13,5 +13,13 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
return {
|
||||
...functionalConfig.getAll(),
|
||||
testFiles: [require.resolve('.')],
|
||||
kbnTestServer: {
|
||||
...functionalConfig.get('kbnTestServer'),
|
||||
serverArgs: [
|
||||
...functionalConfig.get('kbnTestServer.serverArgs'),
|
||||
// disabling the monaco editor to run tests for ace
|
||||
`--console.dev.enableMonaco=false`,
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue