Skip to content

Commit 111e15a

Browse files
Spencerspalgerkibanamachine
authored
[ftr] implement FtrService classes and migrate common services (#99546)
Co-authored-by: spalger <spalger@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
1 parent d8c2594 commit 111e15a

File tree

20 files changed

+292
-253
lines changed

20 files changed

+292
-253
lines changed

docs/developer/contributing/development-functional-tests.asciidoc

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,14 @@ export default function (/* { providerAPI } */) {
139139
}
140140
-----------
141141

142-
**Services**:::
143-
Services are named singleton values produced by a Service Provider. Tests and other services can retrieve service instances by asking for them by name. All functionality except the mocha API is exposed via services.
142+
**Service**:::
143+
A Service is a named singleton created using a subclass of `FtrService`. Tests and other services can retrieve service instances by asking for them by name. All functionality except the mocha API is exposed via services. When you write your own functional tests check for existing services that help with the interactions you're looking to execute, and add new services for interactions which aren't already encoded in a service.
144+
145+
**Service Providers**:::
146+
For legacy purposes, and for when creating a subclass of `FtrService` is inconvenient, you can also create services using a "Service Provider". These are functions which which create service instances and return them. These instances are cached and provided to tests. Currently these providers may also return a Promise for the service instance, allowing the service to do some setup work before tests run. We expect to fully deprecate and remove support for async service providers in the near future and instead require that services use the `lifecycle` service to run setup before tests. Providers which return instances of classes other than `FtrService` will likely remain supported for as long as possible.
144147

145148
**Page objects**:::
146-
Page objects are a special type of service that encapsulate behaviors common to a particular page or plugin. When you write your own plugin, you’ll likely want to add a page object (or several) that describes the common interactions your tests need to execute.
149+
Page objects are functionally equivalent to services, except they are loaded with a slightly different mechanism and generally defined separate from services. When you write your own functional tests you might want to write some of your services as Page objects, but it is not required.
147150

148151
**Test Files**:::
149152
The `FunctionalTestRunner`'s primary purpose is to execute test files. These files export a Test Provider that is called with a Provider API but is not expected to return a value. Instead Test Providers define a suite using https://mochajs.org/#bdd[mocha's BDD interface].

packages/kbn-test/src/functional_test_runner/lib/providers/provider_collection.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { loadTracer } from '../load_tracer';
1212
import { createAsyncInstance, isAsyncInstance } from './async_instance';
1313
import { Providers } from './read_provider_spec';
1414
import { createVerboseInstance } from './verbose_instance';
15+
import { GenericFtrService } from '../../public_types';
1516

1617
export class ProviderCollection {
1718
private readonly instances = new Map();
@@ -58,12 +59,19 @@ export class ProviderCollection {
5859
}
5960

6061
public invokeProviderFn(provider: (args: any) => any) {
61-
return provider({
62+
const ctx = {
6263
getService: this.getService,
6364
hasService: this.hasService,
6465
getPageObject: this.getPageObject,
6566
getPageObjects: this.getPageObjects,
66-
});
67+
};
68+
69+
if (provider.prototype instanceof GenericFtrService) {
70+
const Constructor = (provider as any) as new (ctx: any) => any;
71+
return new Constructor(ctx);
72+
}
73+
74+
return provider(ctx);
6775
}
6876

6977
private findProvider(type: string, name: string) {

packages/kbn-test/src/functional_test_runner/public_types.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { Test, Suite } from './fake_mocha_types';
1313

1414
export { Lifecycle, Config, FailureMetadata };
1515

16-
interface AsyncInstance<T> {
16+
export interface AsyncInstance<T> {
1717
/**
1818
* Services that are initialized async are not ready before the tests execute, so you might need
1919
* to call `init()` and await the promise it returns before interacting with the service
@@ -39,7 +39,11 @@ export type ProvidedType<T extends (...args: any[]) => any> = MaybeAsyncInstance
3939
* promise types into the async instances that other providers will receive.
4040
*/
4141
type ProvidedTypeMap<T extends {}> = {
42-
[K in keyof T]: T[K] extends (...args: any[]) => any ? ProvidedType<T[K]> : unknown;
42+
[K in keyof T]: T[K] extends new (...args: any[]) => infer X
43+
? X
44+
: T[K] extends (...args: any[]) => any
45+
? ProvidedType<T[K]>
46+
: unknown;
4347
};
4448

4549
export interface GenericFtrProviderContext<
@@ -84,6 +88,10 @@ export interface GenericFtrProviderContext<
8488
loadTestFile(path: string): void;
8589
}
8690

91+
export class GenericFtrService<ProviderContext extends GenericFtrProviderContext<any, any>> {
92+
constructor(protected readonly ctx: ProviderContext) {}
93+
}
94+
8795
export interface FtrConfigProviderContext {
8896
log: ToolingLog;
8997
readConfigFile(path: string): Promise<Config>;

test/common/ftr_provider_context.d.ts renamed to test/common/ftr_provider_context.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
* Side Public License, v 1.
77
*/
88

9-
import { GenericFtrProviderContext } from '@kbn/test';
9+
import { GenericFtrProviderContext, GenericFtrService } from '@kbn/test';
1010

1111
import { services } from './services';
1212

1313
export type FtrProviderContext = GenericFtrProviderContext<typeof services, {}>;
14+
export class FtrService extends GenericFtrService<FtrProviderContext> {}

test/common/services/deployment.ts

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,39 +10,37 @@ import { get } from 'lodash';
1010
import fetch from 'node-fetch';
1111
import { getUrl } from '@kbn/test';
1212

13-
import { FtrProviderContext } from '../ftr_provider_context';
13+
import { FtrService } from '../ftr_provider_context';
1414

15-
export function DeploymentProvider({ getService }: FtrProviderContext) {
16-
const config = getService('config');
15+
export class DeploymentService extends FtrService {
16+
private readonly config = this.ctx.getService('config');
1717

18-
return {
19-
/**
20-
* Returns Kibana host URL
21-
*/
22-
getHostPort() {
23-
return getUrl.baseUrl(config.get('servers.kibana'));
24-
},
18+
/**
19+
* Returns Kibana host URL
20+
*/
21+
getHostPort() {
22+
return getUrl.baseUrl(this.config.get('servers.kibana'));
23+
}
2524

26-
/**
27-
* Returns ES host URL
28-
*/
29-
getEsHostPort() {
30-
return getUrl.baseUrl(config.get('servers.elasticsearch'));
31-
},
25+
/**
26+
* Returns ES host URL
27+
*/
28+
getEsHostPort() {
29+
return getUrl.baseUrl(this.config.get('servers.elasticsearch'));
30+
}
3231

33-
async isCloud(): Promise<boolean> {
34-
const baseUrl = this.getHostPort();
35-
const username = config.get('servers.kibana.username');
36-
const password = config.get('servers.kibana.password');
37-
const response = await fetch(baseUrl + '/api/stats?extended', {
38-
method: 'get',
39-
headers: {
40-
'Content-Type': 'application/json',
41-
Authorization: 'Basic ' + Buffer.from(username + ':' + password).toString('base64'),
42-
},
43-
});
44-
const data = await response.json();
45-
return get(data, 'usage.cloud.is_cloud_enabled', false);
46-
},
47-
};
32+
async isCloud(): Promise<boolean> {
33+
const baseUrl = this.getHostPort();
34+
const username = this.config.get('servers.kibana.username');
35+
const password = this.config.get('servers.kibana.password');
36+
const response = await fetch(baseUrl + '/api/stats?extended', {
37+
method: 'get',
38+
headers: {
39+
'Content-Type': 'application/json',
40+
Authorization: 'Basic ' + Buffer.from(username + ':' + password).toString('base64'),
41+
},
42+
});
43+
const data = await response.json();
44+
return get(data, 'usage.cloud.is_cloud_enabled', false);
45+
}
4846
}

test/common/services/index.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,26 @@
66
* Side Public License, v 1.
77
*/
88

9-
import { DeploymentProvider } from './deployment';
9+
import { DeploymentService } from './deployment';
1010
import { LegacyEsProvider } from './legacy_es';
1111
import { ElasticsearchProvider } from './elasticsearch';
1212
import { EsArchiverProvider } from './es_archiver';
1313
import { KibanaServerProvider } from './kibana_server';
14-
import { RetryProvider } from './retry';
15-
import { RandomnessProvider } from './randomness';
14+
import { RetryService } from './retry';
15+
import { RandomnessService } from './randomness';
1616
import { SecurityServiceProvider } from './security';
1717
import { EsDeleteAllIndicesProvider } from './es_delete_all_indices';
18-
import { SavedObjectInfoProvider } from './saved_object_info';
18+
import { SavedObjectInfoService } from './saved_object_info';
1919

2020
export const services = {
21-
deployment: DeploymentProvider,
21+
deployment: DeploymentService,
2222
legacyEs: LegacyEsProvider,
2323
es: ElasticsearchProvider,
2424
esArchiver: EsArchiverProvider,
2525
kibanaServer: KibanaServerProvider,
26-
retry: RetryProvider,
27-
randomness: RandomnessProvider,
26+
retry: RetryService,
27+
randomness: RandomnessService,
2828
security: SecurityServiceProvider,
2929
esDeleteAllIndices: EsDeleteAllIndicesProvider,
30-
savedObjectInfo: SavedObjectInfoProvider,
30+
savedObjectInfo: SavedObjectInfoService,
3131
};

test/common/services/kibana_server/kibana_server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { KbnClient } from '@kbn/test';
1111

1212
import { FtrProviderContext } from '../../ftr_provider_context';
1313

14-
export function KibanaServerProvider({ getService }: FtrProviderContext) {
14+
export function KibanaServerProvider({ getService }: FtrProviderContext): KbnClient {
1515
const log = getService('log');
1616
const config = getService('config');
1717
const lifecycle = getService('lifecycle');

test/common/services/randomness.ts

Lines changed: 49 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,20 @@
77
*/
88

99
import Chance from 'chance';
10+
import { ToolingLog } from '@kbn/dev-utils';
1011

11-
import { FtrProviderContext } from '../ftr_provider_context';
12+
import { FtrService } from '../ftr_provider_context';
13+
14+
let __CACHED_SEED__: number | undefined;
15+
function getSeed(log: ToolingLog) {
16+
if (__CACHED_SEED__ !== undefined) {
17+
return __CACHED_SEED__;
18+
}
19+
20+
__CACHED_SEED__ = Date.now();
21+
log.debug('randomness seed: %j', __CACHED_SEED__);
22+
return __CACHED_SEED__;
23+
}
1224

1325
interface CharOptions {
1426
pool?: string;
@@ -27,52 +39,45 @@ interface NumberOptions {
2739
max?: number;
2840
}
2941

30-
export function RandomnessProvider({ getService }: FtrProviderContext) {
31-
const log = getService('log');
32-
33-
const seed = Date.now();
34-
log.debug('randomness seed: %j', seed);
35-
36-
const chance = new Chance(seed);
42+
export class RandomnessService extends FtrService {
43+
private readonly chance = new Chance(getSeed(this.ctx.getService('log')));
3744

38-
return new (class RandomnessService {
39-
/**
40-
* Generate a random natural number
41-
*
42-
* range: 0 to 9007199254740991
43-
*
44-
*/
45-
naturalNumber(options?: NumberOptions) {
46-
return chance.natural(options);
47-
}
45+
/**
46+
* Generate a random natural number
47+
*
48+
* range: 0 to 9007199254740991
49+
*
50+
*/
51+
naturalNumber(options?: NumberOptions) {
52+
return this.chance.natural(options);
53+
}
4854

49-
/**
50-
* Generate a random integer
51-
*/
52-
integer(options?: NumberOptions) {
53-
return chance.integer(options);
54-
}
55+
/**
56+
* Generate a random integer
57+
*/
58+
integer(options?: NumberOptions) {
59+
return this.chance.integer(options);
60+
}
5561

56-
/**
57-
* Generate a random number, defaults to at least 4 and no more than 8 syllables
58-
*/
59-
word(options: { syllables?: number } = {}) {
60-
const { syllables = this.naturalNumber({ min: 4, max: 8 }) } = options;
62+
/**
63+
* Generate a random number, defaults to at least 4 and no more than 8 syllables
64+
*/
65+
word(options: { syllables?: number } = {}) {
66+
const { syllables = this.naturalNumber({ min: 4, max: 8 }) } = options;
6167

62-
return chance.word({
63-
syllables,
64-
});
65-
}
68+
return this.chance.word({
69+
syllables,
70+
});
71+
}
6672

67-
/**
68-
* Generate a random string, defaults to at least 8 and no more than 15 alpha-numeric characters
69-
*/
70-
string(options: StringOptions = {}) {
71-
return chance.string({
72-
length: this.naturalNumber({ min: 8, max: 15 }),
73-
...(options.pool === 'undefined' ? { alpha: true, numeric: true, symbols: false } : {}),
74-
...options,
75-
});
76-
}
77-
})();
73+
/**
74+
* Generate a random string, defaults to at least 8 and no more than 15 alpha-numeric characters
75+
*/
76+
string(options: StringOptions = {}) {
77+
return this.chance.string({
78+
length: this.naturalNumber({ min: 8, max: 15 }),
79+
...(options.pool === 'undefined' ? { alpha: true, numeric: true, symbols: false } : {}),
80+
...options,
81+
});
82+
}
7883
}

test/common/services/retry/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
* Side Public License, v 1.
77
*/
88

9-
export { RetryProvider } from './retry';
9+
export { RetryService } from './retry';

0 commit comments

Comments
 (0)