From dfede097218e7351c5fa311f8ff570875ae5da54 Mon Sep 17 00:00:00 2001 From: Muhammad Ibragimov <53621505+mibragimov@users.noreply.github.com> Date: Mon, 10 Oct 2022 18:40:02 +0500 Subject: [PATCH] [Console] Add tests for XJSON (#136711) * [Console] Add tests for XJSON * Convert rgb to hex * Address CR changes * Expand test coverage for XJSON * Retry until typing is finished Co-authored-by: Muhammad Ibragimov Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- test/functional/apps/console/_xjson.ts | 158 +++++++++++++++++-- test/functional/page_objects/console_page.ts | 101 ++++++++++++ 2 files changed, 248 insertions(+), 11 deletions(-) diff --git a/test/functional/apps/console/_xjson.ts b/test/functional/apps/console/_xjson.ts index 386cda8ef32e..1535337a2a84 100644 --- a/test/functional/apps/console/_xjson.ts +++ b/test/functional/apps/console/_xjson.ts @@ -7,6 +7,7 @@ */ import expect from '@kbn/expect'; +import { rgbToHex } from '@elastic/eui'; import { FtrProviderContext } from '../../ftr_provider_context'; export default ({ getService, getPageObjects }: FtrProviderContext) => { @@ -14,10 +15,9 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { const log = getService('log'); const PageObjects = getPageObjects(['common', 'console', 'header']); - describe("Console's XJSON features", function testXjson() { + describe('XJSON', function testXjson() { this.tags('includeFirefox'); before(async () => { - log.debug('navigateTo console'); await PageObjects.common.navigateToApp('console'); await retry.try(async () => { await PageObjects.console.collapseHelp(); @@ -28,22 +28,158 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { await PageObjects.console.clearTextArea(); }); - describe('with triple quoted strings', () => { + const executeRequest = async (request = '\n GET _search') => { + await PageObjects.console.enterRequest(request); + await PageObjects.console.clickPlay(); + await PageObjects.header.waitUntilLoadingHasFinished(); + }; + + describe('inline http request', () => { + it('should have method and path', async () => { + await PageObjects.console.enterRequest('\n PUT foo/bar'); + expect(await PageObjects.console.getRequestMethod()).to.be('PUT'); + expect(await PageObjects.console.getRequestPath()).to.be('foo/bar'); + }); + + it('should have optional query parameters', async () => { + await PageObjects.console.enterRequest('\n GET foo/bar?pretty'); + expect(await PageObjects.console.getRequestQueryParams()).to.be('pretty'); + }); + + it('should have optional request body', async () => { + await PageObjects.console.enterRequest('\n POST foo/bar\n {"foo": "bar"}'); + log.debug('request body: ' + (await PageObjects.console.getRequestBody())); + expect(await PageObjects.console.getRequestBody()).to.be('{"foo": "bar"}'); + }); + + it('should not have validation errors', async () => { + await PageObjects.console.enterRequest('\n GET foo/bar'); + expect(await PageObjects.console.hasErrorMarker()).to.be(false); + }); + + it('should have validation error for invalid method', async () => { + await PageObjects.console.enterRequest('\n FOO foo/bar'); + // Retry because the error marker is not always immediately visible. + await retry.try(async () => { + expect(await PageObjects.console.hasErrorMarker()).to.be(true); + }); + }); + + it('should have validation error for invalid path', async () => { + await PageObjects.console.enterRequest('\n GET'); + // Retry because the error marker is not always immediately visible. + await retry.try(async () => { + expect(await PageObjects.console.hasErrorMarker()).to.be(true); + }); + }); + + it('should have validation error for invalid body', async () => { + await PageObjects.console.enterRequest('\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.hasErrorMarker()).to.be(true); + }); + }); + + it('should have correct syntax highlighting', async () => { + await PageObjects.console.enterRequest('\n GET foo/bar'); + expect(await PageObjects.console.getRequestLineHighlighting()).to.contain( + 'ace_method ace_whitespace ace_url ace_part ace_url ace_slash ace_url ace_part' + ); + }); + + it('should have correct syntax highlighting for method', async () => { + await PageObjects.console.enterRequest('\n PUT foo/bar'); + const color = await PageObjects.console.getRequestMethodColor(); + expect(rgbToHex(color)).to.be('#c80a68'); + }); + + it('should have correct syntax highlighting for path', async () => { + await PageObjects.console.enterRequest('\n PUT foo/bar'); + const color = await PageObjects.console.getRequestPathColor(); + expect(rgbToHex(color)).to.be('#00756c'); + }); + + it('should have correct syntax highlighting for query', async () => { + await PageObjects.console.enterRequest('\n PUT foo/bar?pretty'); + const color = await PageObjects.console.getRequestQueryColor(); + expect(rgbToHex(color)).to.be('#00756c'); + }); + + it('should have correct syntax highlighting for body', async () => { + await PageObjects.console.enterRequest('\n PUT foo/bar\n {"foo": "bar"}'); + const color = await PageObjects.console.getRequestBodyColor(); + expect(rgbToHex(color)).to.be('#343741'); + }); + + it('should have multiple bodies for _msearch requests', async () => { + await PageObjects.console.enterRequest( + '\nGET foo/_msearch \n{}\n{"query": {"match_all": {}}}\n{"index": "bar"}\n{"query": {"match_all": {}}}' + ); + // Retry because body elements are not always immediately visible. + await retry.try(async () => { + expect(await PageObjects.console.getRequestBodyCount()).to.be(4); + }); + }); + + it('should not trigger error for multiple bodies for _msearch requests', async () => { + await PageObjects.console.enterRequest( + '\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.hasErrorMarker()).to.be(false); + }); + }); + + it('should not trigger validation errors for multiple JSON blocks', async () => { + await PageObjects.console.enterRequest('\nPOST test/doc/1 \n{\n "foo": "bar"'); + await PageObjects.console.enterRequest('\nPOST test/doc/2 \n{\n "foo": "baz"'); + await PageObjects.console.enterRequest('\nPOST test/doc/3 \n{\n "foo": "qux"'); + // Retry until typing is finished. + await retry.try(async () => { + expect(await PageObjects.console.hasErrorMarker()).to.be(false); + }); + }); + it('should allow escaping quotation mark by wrapping it in triple quotes', async () => { await PageObjects.console.enterRequest( '\nPOST test/_doc/1 \n{\n "foo": """look "escaped" quotes"""' ); - await PageObjects.console.clickPlay(); - await PageObjects.header.waitUntilLoadingHasFinished(); + // Retry until typing is finished and validation errors are gone. + await retry.try(async () => { + expect(await PageObjects.console.hasErrorMarker()).to.be(false); + }); + }); + + it('should have correct syntax highlighting for inline comments', async () => { + await PageObjects.console.enterRequest( + '\nPOST test/_doc/1 \n{\n "foo": "bar" # inline comment' + ); + const color = await PageObjects.console.getCommentColor(); + expect(rgbToHex(color)).to.be('#41755c'); + }); + + it('should allow inline comments in request url row', async () => { + await executeRequest('\n GET _search // inline comment'); expect(await PageObjects.console.hasErrorMarker()).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'); + expect(await PageObjects.console.hasErrorMarker()).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.responseHasDeprecationWarning()).to.be(true); }); - }); - describe('with invalid syntax', () => { - it('should trigger validation errors', async () => { - await PageObjects.console.enterRequest('\nGET test/doc/1 \n{\n "foo": \'\''); - expect(await PageObjects.console.hasInvalidSyntax()).to.be(true); - expect(await PageObjects.console.hasErrorMarker()).to.be(true); + it('should not print warning for non-deprecated request', async () => { + await executeRequest('\n GET _search'); + expect(await PageObjects.console.responseHasDeprecationWarning()).to.be(false); }); }); }); diff --git a/test/functional/page_objects/console_page.ts b/test/functional/page_objects/console_page.ts index 9f662a09146e..0069682f828d 100644 --- a/test/functional/page_objects/console_page.ts +++ b/test/functional/page_objects/console_page.ts @@ -227,6 +227,19 @@ export class ConsolePageObject extends FtrService { return await this.find.existsByCssSelector('.ace_error'); } + public async getTokenColor(token: string) { + const element = await this.find.byClassName(token); + return await element.getComputedStyle('color'); + } + + public async responseHasDeprecationWarning() { + // Retry for a while to allow the deprecation warning to appear + return await this.retry.try(async () => { + const response = await this.getResponse(); + return response.trim().startsWith('#!'); + }); + } + public async clickFoldWidget() { const widget = await this.find.byCssSelector('.ace_fold-widget'); await widget.click(); @@ -404,4 +417,92 @@ export class ConsolePageObject extends FtrService { const button = await this.testSubjects.find('consoleMenuAutoIndent'); await button.click(); } + + public async getRequestMethod() { + const requestEditor = await this.getRequestEditor(); + const requestMethod = await requestEditor.findByClassName('ace_method'); + const method = await requestMethod.getVisibleText(); + return method.trim(); + } + + public async getRequestPath() { + const requestEditor = await this.getRequestEditor(); + const requestPath = await requestEditor.findAllByCssSelector('.ace_url'); + const path = []; + for (const pathPart of requestPath) { + const className = await pathPart.getAttribute('class'); + if (className.includes('ace_param')) { + // This is a parameter, we don't want to include it in the path + break; + } + path.push(await pathPart.getVisibleText()); + } + return path.join('').trim(); + } + + public async getRequestQueryParams() { + const requestEditor = await this.getRequestEditor(); + const requestQueryParams = await requestEditor.findAllByCssSelector('.ace_url.ace_param'); + + if (requestQueryParams.length === 0) { + // No query params + return; + } + + const params = []; + for (const param of requestQueryParams) { + params.push(await param.getVisibleText()); + } + return params.join('').trim(); + } + + public async getRequestBody() { + let request = await this.getRequest(); + // Remove new lines at the beginning of the request + request = request.replace(/^\n/, ''); + const method = await this.getRequestMethod(); + const path = await this.getRequestPath(); + const query = await this.getRequestQueryParams(); + + if (query) { + return request.replace(`${method} ${path}?${query}`, '').trim(); + } + + return request.replace(`${method} ${path}`, '').trim(); + } + + public async getRequestLineHighlighting() { + const requestEditor = await this.getRequestEditor(); + const requestLine = await requestEditor.findAllByCssSelector('.ace_line > *'); + const line = []; + for (const linePart of requestLine) { + line.push(await linePart.getAttribute('class')); + } + return line.join(' '); + } + + public async getRequestMethodColor() { + return await this.getTokenColor('ace_method'); + } + + public async getRequestPathColor() { + return await this.getTokenColor('ace_url'); + } + + public async getRequestQueryColor() { + return await this.getTokenColor('ace_param'); + } + + public async getRequestBodyColor() { + return await this.getTokenColor('ace_paren'); + } + + public async getCommentColor() { + return await this.getTokenColor('ace_comment'); + } + + public async getRequestBodyCount() { + const body = await this.getRequestBody(); + return body.split('\n').length; + } }