mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Add functional test for Kibana embedded in iframe (#68544)
* convert kbn test config into TS * add test for Kibana embedded in iframe * run embedded tests in functional suite * ignore tls errors in functional tests by default * switch test to https * remove env vars mutation * allow to pass ssl config to Kibana * pass ssl config to axios * adopt KbnClient interfaces * adopt KibanaServer * use KbnRequester in security service * set sameSiteCookies:None in test * acceptInsecureCerts in chrome * remove leftovers * fix type error * remove unnecessary field * address comments * refactor plugin * refactor test * make acceptInsecureCerts configurable * run firefox tests on ci * up TS version * fix firefox.sh script * fix path Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
d2006ea8a0
commit
c8c20e4ca8
30 changed files with 393 additions and 113 deletions
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { ToolingLog } from '../tooling_log';
|
||||
import { KbnClientRequester, ReqOptions } from './kbn_client_requester';
|
||||
import { KibanaConfig, KbnClientRequester, ReqOptions } from './kbn_client_requester';
|
||||
import { KbnClientStatus } from './kbn_client_status';
|
||||
import { KbnClientPlugins } from './kbn_client_plugins';
|
||||
import { KbnClientVersion } from './kbn_client_version';
|
||||
|
@ -26,7 +26,7 @@ import { KbnClientSavedObjects } from './kbn_client_saved_objects';
|
|||
import { KbnClientUiSettings, UiSettingValues } from './kbn_client_ui_settings';
|
||||
|
||||
export class KbnClient {
|
||||
private readonly requester = new KbnClientRequester(this.log, this.kibanaUrls);
|
||||
private readonly requester = new KbnClientRequester(this.log, this.kibanaConfig);
|
||||
readonly status = new KbnClientStatus(this.requester);
|
||||
readonly plugins = new KbnClientPlugins(this.status);
|
||||
readonly version = new KbnClientVersion(this.status);
|
||||
|
@ -43,10 +43,10 @@ export class KbnClient {
|
|||
*/
|
||||
constructor(
|
||||
private readonly log: ToolingLog,
|
||||
private readonly kibanaUrls: string[],
|
||||
private readonly kibanaConfig: KibanaConfig,
|
||||
private readonly uiSettingDefaults?: UiSettingValues
|
||||
) {
|
||||
if (!kibanaUrls.length) {
|
||||
if (!kibanaConfig.url) {
|
||||
throw new Error('missing Kibana urls');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,10 +16,9 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import Url from 'url';
|
||||
|
||||
import Axios from 'axios';
|
||||
import Https from 'https';
|
||||
import Axios, { AxiosResponse } from 'axios';
|
||||
|
||||
import { isAxiosRequestError, isAxiosResponseError } from '../axios';
|
||||
import { ToolingLog } from '../tooling_log';
|
||||
|
@ -70,20 +69,38 @@ const delay = (ms: number) =>
|
|||
setTimeout(resolve, ms);
|
||||
});
|
||||
|
||||
export interface KibanaConfig {
|
||||
url: string;
|
||||
ssl?: {
|
||||
enabled: boolean;
|
||||
key: string;
|
||||
certificate: string;
|
||||
certificateAuthorities: string;
|
||||
};
|
||||
}
|
||||
|
||||
export class KbnClientRequester {
|
||||
constructor(private readonly log: ToolingLog, private readonly kibanaUrls: string[]) {}
|
||||
private readonly httpsAgent: Https.Agent | null;
|
||||
constructor(private readonly log: ToolingLog, private readonly kibanaConfig: KibanaConfig) {
|
||||
this.httpsAgent =
|
||||
kibanaConfig.ssl && kibanaConfig.ssl.enabled
|
||||
? new Https.Agent({
|
||||
cert: kibanaConfig.ssl.certificate,
|
||||
key: kibanaConfig.ssl.key,
|
||||
ca: kibanaConfig.ssl.certificateAuthorities,
|
||||
})
|
||||
: null;
|
||||
}
|
||||
|
||||
private pickUrl() {
|
||||
const url = this.kibanaUrls.shift()!;
|
||||
this.kibanaUrls.push(url);
|
||||
return url;
|
||||
return this.kibanaConfig.url;
|
||||
}
|
||||
|
||||
public resolveUrl(relativeUrl: string = '/') {
|
||||
return Url.resolve(this.pickUrl(), relativeUrl);
|
||||
}
|
||||
|
||||
async request<T>(options: ReqOptions): Promise<T> {
|
||||
async request<T>(options: ReqOptions): Promise<AxiosResponse<T>> {
|
||||
const url = Url.resolve(this.pickUrl(), options.path);
|
||||
const description = options.description || `${options.method} ${url}`;
|
||||
let attempt = 0;
|
||||
|
@ -93,7 +110,7 @@ export class KbnClientRequester {
|
|||
attempt += 1;
|
||||
|
||||
try {
|
||||
const response = await Axios.request<T>({
|
||||
const response = await Axios.request({
|
||||
method: options.method,
|
||||
url,
|
||||
data: options.body,
|
||||
|
@ -101,9 +118,10 @@ export class KbnClientRequester {
|
|||
headers: {
|
||||
'kbn-xsrf': 'kbn-client',
|
||||
},
|
||||
httpsAgent: this.httpsAgent,
|
||||
});
|
||||
|
||||
return response.data;
|
||||
return response;
|
||||
} catch (error) {
|
||||
const conflictOnGet = isConcliftOnGetError(error);
|
||||
const requestedRetries = options.retries !== undefined;
|
||||
|
|
|
@ -71,12 +71,13 @@ export class KbnClientSavedObjects {
|
|||
public async migrate() {
|
||||
this.log.debug('Migrating saved objects');
|
||||
|
||||
return await this.requester.request<MigrateResponse>({
|
||||
const { data } = await this.requester.request<MigrateResponse>({
|
||||
description: 'migrate saved objects',
|
||||
path: uriencode`/internal/saved_objects/_migrate`,
|
||||
method: 'POST',
|
||||
body: {},
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -85,11 +86,12 @@ export class KbnClientSavedObjects {
|
|||
public async get<Attributes extends Record<string, any>>(options: GetOptions) {
|
||||
this.log.debug('Gettings saved object: %j', options);
|
||||
|
||||
return await this.requester.request<SavedObjectResponse<Attributes>>({
|
||||
const { data } = await this.requester.request<SavedObjectResponse<Attributes>>({
|
||||
description: 'get saved object',
|
||||
path: uriencode`/api/saved_objects/${options.type}/${options.id}`,
|
||||
method: 'GET',
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -98,7 +100,7 @@ export class KbnClientSavedObjects {
|
|||
public async create<Attributes extends Record<string, any>>(options: IndexOptions<Attributes>) {
|
||||
this.log.debug('Creating saved object: %j', options);
|
||||
|
||||
return await this.requester.request<SavedObjectResponse<Attributes>>({
|
||||
const { data } = await this.requester.request<SavedObjectResponse<Attributes>>({
|
||||
description: 'update saved object',
|
||||
path: options.id
|
||||
? uriencode`/api/saved_objects/${options.type}/${options.id}`
|
||||
|
@ -113,6 +115,7 @@ export class KbnClientSavedObjects {
|
|||
references: options.references,
|
||||
},
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -121,7 +124,7 @@ export class KbnClientSavedObjects {
|
|||
public async update<Attributes extends Record<string, any>>(options: UpdateOptions<Attributes>) {
|
||||
this.log.debug('Updating saved object: %j', options);
|
||||
|
||||
return await this.requester.request<SavedObjectResponse<Attributes>>({
|
||||
const { data } = await this.requester.request<SavedObjectResponse<Attributes>>({
|
||||
description: 'update saved object',
|
||||
path: uriencode`/api/saved_objects/${options.type}/${options.id}`,
|
||||
query: {
|
||||
|
@ -134,6 +137,7 @@ export class KbnClientSavedObjects {
|
|||
references: options.references,
|
||||
},
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -142,10 +146,12 @@ export class KbnClientSavedObjects {
|
|||
public async delete(options: GetOptions) {
|
||||
this.log.debug('Deleting saved object %s/%s', options);
|
||||
|
||||
return await this.requester.request({
|
||||
const { data } = await this.requester.request({
|
||||
description: 'delete saved object',
|
||||
path: uriencode`/api/saved_objects/${options.type}/${options.id}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,10 +52,11 @@ export class KbnClientStatus {
|
|||
* Get the full server status
|
||||
*/
|
||||
async get() {
|
||||
return await this.requester.request<ApiResponseStatus>({
|
||||
const { data } = await this.requester.request<ApiResponseStatus>({
|
||||
method: 'GET',
|
||||
path: 'api/status',
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -57,10 +57,11 @@ export class KbnClientUiSettings {
|
|||
* Unset a uiSetting
|
||||
*/
|
||||
async unset(setting: string) {
|
||||
return await this.requester.request<any>({
|
||||
const { data } = await this.requester.request<any>({
|
||||
path: uriencode`/api/kibana/settings/${setting}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -105,11 +106,11 @@ export class KbnClientUiSettings {
|
|||
}
|
||||
|
||||
private async getAll() {
|
||||
const resp = await this.requester.request<UiSettingsApiResponse>({
|
||||
const { data } = await this.requester.request<UiSettingsApiResponse>({
|
||||
path: '/api/kibana/settings',
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
return resp.settings;
|
||||
return data.settings;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,14 @@ const urlPartsSchema = () =>
|
|||
password: Joi.string(),
|
||||
pathname: Joi.string().regex(/^\//, 'start with a /'),
|
||||
hash: Joi.string().regex(/^\//, 'start with a /'),
|
||||
ssl: Joi.object()
|
||||
.keys({
|
||||
enabled: Joi.boolean().default(false),
|
||||
certificate: Joi.string().optional(),
|
||||
certificateAuthorities: Joi.string().optional(),
|
||||
key: Joi.string().optional(),
|
||||
})
|
||||
.default(),
|
||||
})
|
||||
.default();
|
||||
|
||||
|
@ -122,6 +130,7 @@ export const schema = Joi.object()
|
|||
type: Joi.string().valid('chrome', 'firefox', 'ie', 'msedge').default('chrome'),
|
||||
|
||||
logPollingMs: Joi.number().default(100),
|
||||
acceptInsecureCerts: Joi.boolean().default(false),
|
||||
})
|
||||
.default(),
|
||||
|
||||
|
|
|
@ -16,26 +16,34 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { kibanaTestUser } from './users';
|
||||
import url from 'url';
|
||||
import { kibanaTestUser } from './users';
|
||||
|
||||
interface UrlParts {
|
||||
protocol?: string;
|
||||
hostname?: string;
|
||||
port?: number;
|
||||
auth?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export const kbnTestConfig = new (class KbnTestConfig {
|
||||
getPort() {
|
||||
return this.getUrlParts().port;
|
||||
}
|
||||
|
||||
getUrlParts() {
|
||||
getUrlParts(): UrlParts {
|
||||
// allow setting one complete TEST_KIBANA_URL for ES like https://elastic:changeme@example.com:9200
|
||||
if (process.env.TEST_KIBANA_URL) {
|
||||
const testKibanaUrl = url.parse(process.env.TEST_KIBANA_URL);
|
||||
return {
|
||||
protocol: testKibanaUrl.protocol.slice(0, -1),
|
||||
protocol: testKibanaUrl.protocol?.slice(0, -1),
|
||||
hostname: testKibanaUrl.hostname,
|
||||
port: parseInt(testKibanaUrl.port, 10),
|
||||
port: testKibanaUrl.port ? parseInt(testKibanaUrl.port, 10) : undefined,
|
||||
auth: testKibanaUrl.auth,
|
||||
username: testKibanaUrl.auth.split(':')[0],
|
||||
password: testKibanaUrl.auth.split(':')[1],
|
||||
username: testKibanaUrl.auth?.split(':')[0],
|
||||
password: testKibanaUrl.auth?.split(':')[1],
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -44,7 +52,7 @@ export const kbnTestConfig = new (class KbnTestConfig {
|
|||
return {
|
||||
protocol: process.env.TEST_KIBANA_PROTOCOL || 'http',
|
||||
hostname: process.env.TEST_KIBANA_HOSTNAME || 'localhost',
|
||||
port: parseInt(process.env.TEST_KIBANA_PORT, 10) || 5620,
|
||||
port: process.env.TEST_KIBANA_PORT ? parseInt(process.env.TEST_KIBANA_PORT, 10) : 5620,
|
||||
auth: `${username}:${password}`,
|
||||
username,
|
||||
password,
|
|
@ -49,7 +49,7 @@ export class EsArchiver {
|
|||
this.client = client;
|
||||
this.dataDir = dataDir;
|
||||
this.log = log;
|
||||
this.kbnClient = new KbnClient(log, [kibanaUrl]);
|
||||
this.kbnClient = new KbnClient(log, { url: kibanaUrl });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -27,9 +27,9 @@ export function KibanaServerProvider({ getService }: FtrProviderContext) {
|
|||
const config = getService('config');
|
||||
const lifecycle = getService('lifecycle');
|
||||
const url = Url.format(config.get('servers.kibana'));
|
||||
const ssl = config.get('servers.kibana').ssl;
|
||||
const defaults = config.get('uiSettings.defaults');
|
||||
|
||||
const kbn = new KbnClient(log, [url], defaults);
|
||||
const kbn = new KbnClient(log, { url, ssl }, defaults);
|
||||
|
||||
if (defaults) {
|
||||
lifecycle.beforeTests.add(async () => {
|
||||
|
|
|
@ -17,27 +17,20 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
import util from 'util';
|
||||
import { ToolingLog } from '@kbn/dev-utils';
|
||||
import { KbnClient, ToolingLog } from '@kbn/dev-utils';
|
||||
|
||||
export class Role {
|
||||
private log: ToolingLog;
|
||||
private axios: AxiosInstance;
|
||||
|
||||
constructor(url: string, log: ToolingLog) {
|
||||
this.log = log;
|
||||
this.axios = axios.create({
|
||||
headers: { 'kbn-xsrf': 'x-pack/ftr/services/security/role' },
|
||||
baseURL: url,
|
||||
maxRedirects: 0,
|
||||
validateStatus: () => true, // we do our own validation below and throw better error messages
|
||||
});
|
||||
}
|
||||
constructor(private log: ToolingLog, private kibanaServer: KbnClient) {}
|
||||
|
||||
public async create(name: string, role: any) {
|
||||
this.log.debug(`creating role ${name}`);
|
||||
const { data, status, statusText } = await this.axios.put(`/api/security/role/${name}`, role);
|
||||
const { data, status, statusText } = await this.kibanaServer.request({
|
||||
path: `/api/security/role/${name}`,
|
||||
method: 'PUT',
|
||||
body: role,
|
||||
retries: 0,
|
||||
});
|
||||
if (status !== 204) {
|
||||
throw new Error(
|
||||
`Expected status code of 204, received ${status} ${statusText}: ${util.inspect(data)}`
|
||||
|
@ -47,7 +40,10 @@ export class Role {
|
|||
|
||||
public async delete(name: string) {
|
||||
this.log.debug(`deleting role ${name}`);
|
||||
const { data, status, statusText } = await this.axios.delete(`/api/security/role/${name}`);
|
||||
const { data, status, statusText } = await this.kibanaServer.request({
|
||||
path: `/api/security/role/${name}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (status !== 204 && status !== 404) {
|
||||
throw new Error(
|
||||
`Expected status code of 204 or 404, received ${status} ${statusText}: ${util.inspect(
|
||||
|
|
|
@ -17,30 +17,19 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
import util from 'util';
|
||||
import { ToolingLog } from '@kbn/dev-utils';
|
||||
import { KbnClient, ToolingLog } from '@kbn/dev-utils';
|
||||
|
||||
export class RoleMappings {
|
||||
private log: ToolingLog;
|
||||
private axios: AxiosInstance;
|
||||
|
||||
constructor(url: string, log: ToolingLog) {
|
||||
this.log = log;
|
||||
this.axios = axios.create({
|
||||
headers: { 'kbn-xsrf': 'x-pack/ftr/services/security/role_mappings' },
|
||||
baseURL: url,
|
||||
maxRedirects: 0,
|
||||
validateStatus: () => true, // we do our own validation below and throw better error messages
|
||||
});
|
||||
}
|
||||
constructor(private log: ToolingLog, private kbnClient: KbnClient) {}
|
||||
|
||||
public async create(name: string, roleMapping: Record<string, any>) {
|
||||
this.log.debug(`creating role mapping ${name}`);
|
||||
const { data, status, statusText } = await this.axios.post(
|
||||
`/internal/security/role_mapping/${name}`,
|
||||
roleMapping
|
||||
);
|
||||
const { data, status, statusText } = await this.kbnClient.request({
|
||||
path: `/internal/security/role_mapping/${name}`,
|
||||
method: 'POST',
|
||||
body: roleMapping,
|
||||
});
|
||||
if (status !== 200) {
|
||||
throw new Error(
|
||||
`Expected status code of 200, received ${status} ${statusText}: ${util.inspect(data)}`
|
||||
|
@ -51,9 +40,10 @@ export class RoleMappings {
|
|||
|
||||
public async delete(name: string) {
|
||||
this.log.debug(`deleting role mapping ${name}`);
|
||||
const { data, status, statusText } = await this.axios.delete(
|
||||
`/internal/security/role_mapping/${name}`
|
||||
);
|
||||
const { data, status, statusText } = await this.kbnClient.request({
|
||||
path: `/internal/security/role_mapping/${name}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (status !== 200 && status !== 404) {
|
||||
throw new Error(
|
||||
`Expected status code of 200 or 404, received ${status} ${statusText}: ${util.inspect(
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { format as formatUrl } from 'url';
|
||||
|
||||
import { Role } from './role';
|
||||
import { User } from './user';
|
||||
import { RoleMappings } from './role_mappings';
|
||||
|
@ -28,14 +26,14 @@ import { createTestUserService } from './test_user';
|
|||
export async function SecurityServiceProvider(context: FtrProviderContext) {
|
||||
const { getService } = context;
|
||||
const log = getService('log');
|
||||
const config = getService('config');
|
||||
const url = formatUrl(config.get('servers.kibana'));
|
||||
const role = new Role(url, log);
|
||||
const user = new User(url, log);
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
|
||||
const role = new Role(log, kibanaServer);
|
||||
const user = new User(log, kibanaServer);
|
||||
const testUser = await createTestUserService(role, user, context);
|
||||
|
||||
return new (class SecurityService {
|
||||
roleMappings = new RoleMappings(url, log);
|
||||
roleMappings = new RoleMappings(log, kibanaServer);
|
||||
testUser = testUser;
|
||||
role = role;
|
||||
user = user;
|
||||
|
|
|
@ -17,33 +17,22 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
import util from 'util';
|
||||
import { ToolingLog } from '@kbn/dev-utils';
|
||||
import { KbnClient, ToolingLog } from '@kbn/dev-utils';
|
||||
|
||||
export class User {
|
||||
private log: ToolingLog;
|
||||
private axios: AxiosInstance;
|
||||
|
||||
constructor(url: string, log: ToolingLog) {
|
||||
this.log = log;
|
||||
this.axios = axios.create({
|
||||
headers: { 'kbn-xsrf': 'x-pack/ftr/services/security/user' },
|
||||
baseURL: url,
|
||||
maxRedirects: 0,
|
||||
validateStatus: () => true, // we do our own validation below and throw better error messages
|
||||
});
|
||||
}
|
||||
constructor(private log: ToolingLog, private kbnClient: KbnClient) {}
|
||||
|
||||
public async create(username: string, user: any) {
|
||||
this.log.debug(`creating user ${username}`);
|
||||
const { data, status, statusText } = await this.axios.post(
|
||||
`/internal/security/users/${username}`,
|
||||
{
|
||||
const { data, status, statusText } = await this.kbnClient.request({
|
||||
path: `/internal/security/users/${username}`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
username,
|
||||
...user,
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
if (status !== 200) {
|
||||
throw new Error(
|
||||
`Expected status code of 200, received ${status} ${statusText}: ${util.inspect(data)}`
|
||||
|
@ -54,9 +43,10 @@ export class User {
|
|||
|
||||
public async delete(username: string) {
|
||||
this.log.debug(`deleting user ${username}`);
|
||||
const { data, status, statusText } = await this.axios.delete(
|
||||
`/internal/security/users/${username}`
|
||||
);
|
||||
const { data, status, statusText } = await await this.kbnClient.request({
|
||||
path: `/internal/security/users/${username}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (status !== 204) {
|
||||
throw new Error(
|
||||
`Expected status code of 204, received ${status} ${statusText}: ${util.inspect(data)}`
|
||||
|
|
|
@ -529,5 +529,10 @@ export async function BrowserProvider({ getService }: FtrProviderContext) {
|
|||
await driver.executeScript('document.body.scrollLeft = ' + scrollSize);
|
||||
return this.getScrollLeft();
|
||||
}
|
||||
|
||||
public async switchToFrame(idOrElement: number | WebElementWrapper) {
|
||||
const _id = idOrElement instanceof WebElementWrapper ? idOrElement._webElement : idOrElement;
|
||||
await driver.switchTo().frame(_id);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import { resolve } from 'path';
|
|||
import { mergeMap } from 'rxjs/operators';
|
||||
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { initWebDriver } from './webdriver';
|
||||
import { initWebDriver, BrowserConfig } from './webdriver';
|
||||
import { Browsers } from './browsers';
|
||||
|
||||
export async function RemoteProvider({ getService }: FtrProviderContext) {
|
||||
|
@ -58,12 +58,12 @@ export async function RemoteProvider({ getService }: FtrProviderContext) {
|
|||
Fs.writeFileSync(path, JSON.stringify(JSON.parse(coverageJson), null, 2));
|
||||
};
|
||||
|
||||
const { driver, consoleLog$ } = await initWebDriver(
|
||||
log,
|
||||
browserType,
|
||||
lifecycle,
|
||||
config.get('browser.logPollingMs')
|
||||
);
|
||||
const browserConfig: BrowserConfig = {
|
||||
logPollingMs: config.get('browser.logPollingMs'),
|
||||
acceptInsecureCerts: config.get('browser.acceptInsecureCerts'),
|
||||
};
|
||||
|
||||
const { driver, consoleLog$ } = await initWebDriver(log, browserType, lifecycle, browserConfig);
|
||||
const isW3CEnabled = (driver as any).executor_.w3c;
|
||||
|
||||
const caps = await driver.getCapabilities();
|
||||
|
|
|
@ -73,13 +73,18 @@ Executor.prototype.execute = preventParallelCalls(
|
|||
(command: { getName: () => string }) => NO_QUEUE_COMMANDS.includes(command.getName())
|
||||
);
|
||||
|
||||
export interface BrowserConfig {
|
||||
logPollingMs: number;
|
||||
acceptInsecureCerts: boolean;
|
||||
}
|
||||
|
||||
let attemptCounter = 0;
|
||||
let edgePaths: { driverPath: string | undefined; browserPath: string | undefined };
|
||||
async function attemptToCreateCommand(
|
||||
log: ToolingLog,
|
||||
browserType: Browsers,
|
||||
lifecycle: Lifecycle,
|
||||
logPollingMs: number
|
||||
config: BrowserConfig
|
||||
) {
|
||||
const attemptId = ++attemptCounter;
|
||||
log.debug('[webdriver] Creating session');
|
||||
|
@ -114,6 +119,7 @@ async function attemptToCreateCommand(
|
|||
if (certValidation === '0') {
|
||||
chromeOptions.push('ignore-certificate-errors');
|
||||
}
|
||||
|
||||
if (remoteDebug === '1') {
|
||||
// Visit chrome://inspect in chrome to remotely view/debug
|
||||
chromeOptions.push('headless', 'disable-gpu', 'remote-debugging-port=9222');
|
||||
|
@ -125,6 +131,7 @@ async function attemptToCreateCommand(
|
|||
});
|
||||
chromeCapabilities.set('unexpectedAlertBehaviour', 'accept');
|
||||
chromeCapabilities.set('goog:loggingPrefs', { browser: 'ALL' });
|
||||
chromeCapabilities.setAcceptInsecureCerts(config.acceptInsecureCerts);
|
||||
|
||||
const session = await new Builder()
|
||||
.forBrowser(browserType)
|
||||
|
@ -137,7 +144,7 @@ async function attemptToCreateCommand(
|
|||
consoleLog$: pollForLogEntry$(
|
||||
session,
|
||||
logging.Type.BROWSER,
|
||||
logPollingMs,
|
||||
config.logPollingMs,
|
||||
lifecycle.cleanup.after$
|
||||
).pipe(
|
||||
takeUntil(lifecycle.cleanup.after$),
|
||||
|
@ -174,7 +181,7 @@ async function attemptToCreateCommand(
|
|||
consoleLog$: pollForLogEntry$(
|
||||
session,
|
||||
logging.Type.BROWSER,
|
||||
logPollingMs,
|
||||
config.logPollingMs,
|
||||
lifecycle.cleanup.after$
|
||||
).pipe(
|
||||
takeUntil(lifecycle.cleanup.after$),
|
||||
|
@ -206,6 +213,7 @@ async function attemptToCreateCommand(
|
|||
'browser.helperApps.neverAsk.saveToDisk',
|
||||
'application/comma-separated-values, text/csv, text/plain'
|
||||
);
|
||||
firefoxOptions.setAcceptInsecureCerts(config.acceptInsecureCerts);
|
||||
|
||||
if (headlessBrowser === '1') {
|
||||
// See: https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode
|
||||
|
@ -317,7 +325,7 @@ export async function initWebDriver(
|
|||
log: ToolingLog,
|
||||
browserType: Browsers,
|
||||
lifecycle: Lifecycle,
|
||||
logPollingMs: number
|
||||
config: BrowserConfig
|
||||
) {
|
||||
const logger = getLogger('webdriver.http.Executor');
|
||||
logger.setLevel(logging.Level.FINEST);
|
||||
|
@ -348,7 +356,7 @@ export async function initWebDriver(
|
|||
while (true) {
|
||||
const command = await Promise.race([
|
||||
delay(30 * SECOND),
|
||||
attemptToCreateCommand(log, browserType, lifecycle, logPollingMs),
|
||||
attemptToCreateCommand(log, browserType, lifecycle, config),
|
||||
]);
|
||||
|
||||
if (!command) {
|
||||
|
|
|
@ -7,4 +7,5 @@ checks-reporter-with-killswitch "X-Pack firefox smoke test" \
|
|||
--debug --bail \
|
||||
--kibana-install-dir "$KIBANA_INSTALL_DIR" \
|
||||
--include-tag "includeFirefox" \
|
||||
--config test/functional/config.firefox.js;
|
||||
--config test/functional/config.firefox.js \
|
||||
--config test/functional_embedded/config.firefox.ts;
|
||||
|
|
|
@ -51,6 +51,7 @@ const onlyNotInCoverageTests = [
|
|||
require.resolve('../test/licensing_plugin/config.legacy.ts'),
|
||||
require.resolve('../test/endpoint_api_integration_no_ingest/config.ts'),
|
||||
require.resolve('../test/reporting_api_integration/config.js'),
|
||||
require.resolve('../test/functional_embedded/config.ts'),
|
||||
];
|
||||
|
||||
require('@kbn/plugin-helpers').babelRegister();
|
||||
|
|
27
x-pack/test/functional_embedded/config.firefox.ts
Normal file
27
x-pack/test/functional_embedded/config.firefox.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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
|
||||
|
||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const chromeConfig = await readConfigFile(require.resolve('./config'));
|
||||
|
||||
return {
|
||||
...chromeConfig.getAll(),
|
||||
|
||||
browser: {
|
||||
type: 'firefox',
|
||||
acceptInsecureCerts: true,
|
||||
},
|
||||
|
||||
suiteTags: {
|
||||
exclude: ['skipFirefox'],
|
||||
},
|
||||
|
||||
junit: {
|
||||
reportName: 'Firefox Kibana Embedded in iframe with X-Pack Security',
|
||||
},
|
||||
};
|
||||
}
|
67
x-pack/test/functional_embedded/config.ts
Normal file
67
x-pack/test/functional_embedded/config.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import Fs from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils';
|
||||
import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
|
||||
import { pageObjects } from '../functional/page_objects';
|
||||
|
||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const kibanaFunctionalConfig = await readConfigFile(require.resolve('../functional/config.js'));
|
||||
|
||||
const iframeEmbeddedPlugin = resolve(__dirname, './plugins/iframe_embedded');
|
||||
|
||||
const servers = {
|
||||
...kibanaFunctionalConfig.get('servers'),
|
||||
elasticsearch: {
|
||||
...kibanaFunctionalConfig.get('servers.elasticsearch'),
|
||||
},
|
||||
kibana: {
|
||||
...kibanaFunctionalConfig.get('servers.kibana'),
|
||||
protocol: 'https',
|
||||
ssl: {
|
||||
enabled: true,
|
||||
key: Fs.readFileSync(KBN_KEY_PATH).toString('utf8'),
|
||||
certificate: Fs.readFileSync(KBN_CERT_PATH).toString('utf8'),
|
||||
certificateAuthorities: Fs.readFileSync(CA_CERT_PATH).toString('utf8'),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
testFiles: [require.resolve('./tests')],
|
||||
servers,
|
||||
services: kibanaFunctionalConfig.get('services'),
|
||||
pageObjects,
|
||||
browser: {
|
||||
acceptInsecureCerts: true,
|
||||
},
|
||||
junit: {
|
||||
reportName: 'Kibana Embedded in iframe with X-Pack Security',
|
||||
},
|
||||
|
||||
esTestCluster: kibanaFunctionalConfig.get('esTestCluster'),
|
||||
apps: {
|
||||
...kibanaFunctionalConfig.get('apps'),
|
||||
},
|
||||
|
||||
kbnTestServer: {
|
||||
...kibanaFunctionalConfig.get('kbnTestServer'),
|
||||
serverArgs: [
|
||||
...kibanaFunctionalConfig.get('kbnTestServer.serverArgs'),
|
||||
`--plugin-path=${iframeEmbeddedPlugin}`,
|
||||
'--server.ssl.enabled=true',
|
||||
`--server.ssl.key=${KBN_KEY_PATH}`,
|
||||
`--server.ssl.certificate=${KBN_CERT_PATH}`,
|
||||
`--server.ssl.certificateAuthorities=${CA_CERT_PATH}`,
|
||||
|
||||
'--xpack.security.sameSiteCookies=None',
|
||||
'--xpack.security.secureCookies=true',
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
12
x-pack/test/functional_embedded/ftr_provider_context.d.ts
vendored
Normal file
12
x-pack/test/functional_embedded/ftr_provider_context.d.ts
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { GenericFtrProviderContext } from '@kbn/test/types/ftr';
|
||||
import { pageObjects } from '../functional/page_objects';
|
||||
import { services } from './services';
|
||||
|
||||
export type FtrProviderContext = GenericFtrProviderContext<typeof services, typeof pageObjects>;
|
||||
export { pageObjects };
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"id": "iframe_embedded",
|
||||
"version": "1.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"server": true,
|
||||
"ui": false
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "iframe_embedded",
|
||||
"version": "0.0.0",
|
||||
"kibana": {
|
||||
"version": "kibana"
|
||||
},
|
||||
"scripts": {
|
||||
"kbn": "node ../../../../../scripts/kbn.js",
|
||||
"build": "rm -rf './target' && tsc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "3.9.5"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { PluginInitializerContext } from 'kibana/server';
|
||||
import { IframeEmbeddedPlugin } from './plugin';
|
||||
|
||||
export const plugin = (initContext: PluginInitializerContext) =>
|
||||
new IframeEmbeddedPlugin(initContext);
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import Url from 'url';
|
||||
import { Plugin, CoreSetup, PluginInitializerContext } from 'src/core/server';
|
||||
|
||||
function renderBody(iframeUrl: string) {
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Kibana embedded in iframe</title>
|
||||
</head>
|
||||
<body>
|
||||
<iframe data-test-subj="iframe_embedded" width="1000" height="1200" src="${iframeUrl}" frameborder="0"/>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
export class IframeEmbeddedPlugin implements Plugin {
|
||||
constructor(initializerContext: PluginInitializerContext) {}
|
||||
|
||||
public setup(core: CoreSetup) {
|
||||
core.http.resources.register(
|
||||
{
|
||||
path: '/iframe_embedded',
|
||||
validate: false,
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const { protocol, port, host } = core.http.getServerInfo();
|
||||
|
||||
const kibanaUrl = Url.format({ protocol, hostname: host, port });
|
||||
|
||||
return response.renderHtml({
|
||||
body: renderBody(kibanaUrl),
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
public start() {}
|
||||
public stop() {}
|
||||
}
|
9
x-pack/test/functional_embedded/services.ts
Normal file
9
x-pack/test/functional_embedded/services.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { services as functionalServices } from '../functional/services';
|
||||
|
||||
export const services = functionalServices;
|
42
x-pack/test/functional_embedded/tests/iframe_embedded.ts
Normal file
42
x-pack/test/functional_embedded/tests/iframe_embedded.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import Url from 'url';
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
import { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const PageObjects = getPageObjects(['security', 'common']);
|
||||
const browser = getService('browser');
|
||||
const config = getService('config');
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
describe('in iframe', () => {
|
||||
it('should open Kibana for logged-in user', async () => {
|
||||
const isChromeHiddenBefore = await PageObjects.common.isChromeHidden();
|
||||
expect(isChromeHiddenBefore).to.be(true);
|
||||
|
||||
await PageObjects.security.login();
|
||||
|
||||
const { protocol, hostname, port } = config.get('servers.kibana');
|
||||
|
||||
const url = Url.format({
|
||||
protocol,
|
||||
hostname,
|
||||
port,
|
||||
pathname: 'iframe_embedded',
|
||||
});
|
||||
|
||||
await browser.navigateTo(url);
|
||||
|
||||
const iframe = await testSubjects.find('iframe_embedded');
|
||||
await browser.switchToFrame(iframe);
|
||||
|
||||
const isChromeHidden = await PageObjects.common.isChromeHidden();
|
||||
expect(isChromeHidden).to.be(false);
|
||||
});
|
||||
});
|
||||
}
|
14
x-pack/test/functional_embedded/tests/index.ts
Normal file
14
x-pack/test/functional_embedded/tests/index.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('Kibana embedded', function () {
|
||||
this.tags('ciGroup2');
|
||||
loadTestFile(require.resolve('./iframe_embedded'));
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue