@elastic/synthetics 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +11 -9
- package/dist/cli.js.map +1 -1
- package/dist/common_types.d.ts +12 -3
- package/dist/common_types.d.ts.map +1 -1
- package/dist/core/runner.d.ts.map +1 -1
- package/dist/core/runner.js +12 -6
- package/dist/core/runner.js.map +1 -1
- package/dist/dsl/monitor.d.ts +4 -0
- package/dist/dsl/monitor.d.ts.map +1 -1
- package/dist/dsl/monitor.js +6 -0
- package/dist/dsl/monitor.js.map +1 -1
- package/dist/generator/index.d.ts.map +1 -1
- package/dist/generator/index.js +22 -16
- package/dist/generator/index.js.map +1 -1
- package/dist/options.d.ts +11 -2
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +66 -59
- package/dist/options.js.map +1 -1
- package/dist/push/index.d.ts.map +1 -1
- package/dist/push/index.js +14 -9
- package/dist/push/index.js.map +1 -1
- package/dist/push/monitor.d.ts.map +1 -1
- package/dist/push/monitor.js +9 -2
- package/dist/push/monitor.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +24 -13
- package/src/common_types.ts +13 -3
- package/src/core/runner.ts +17 -6
- package/src/dsl/monitor.ts +13 -1
- package/src/generator/index.ts +24 -20
- package/src/options.ts +81 -66
- package/src/push/index.ts +22 -9
- package/src/push/monitor.ts +12 -3
package/src/generator/index.ts
CHANGED
|
@@ -22,13 +22,14 @@
|
|
|
22
22
|
* THE SOFTWARE.
|
|
23
23
|
*
|
|
24
24
|
*/
|
|
25
|
+
import { URL } from 'url';
|
|
25
26
|
import { execSync } from 'child_process';
|
|
26
27
|
import { existsSync } from 'fs';
|
|
27
28
|
import { mkdir, readFile, writeFile } from 'fs/promises';
|
|
28
29
|
import { bold, cyan, yellow } from 'kleur/colors';
|
|
29
30
|
import { join, relative, dirname, basename } from 'path';
|
|
30
31
|
// @ts-ignore-next-line: has no exported member 'Input'
|
|
31
|
-
import { prompt, Input } from 'enquirer';
|
|
32
|
+
import { prompt, Input, Password } from 'enquirer';
|
|
32
33
|
import { getProjectApiKeyURL, progress, write as stdWrite } from '../helpers';
|
|
33
34
|
import {
|
|
34
35
|
getPackageManager,
|
|
@@ -60,6 +61,8 @@ export const REGULAR_FILES_PATH = [
|
|
|
60
61
|
];
|
|
61
62
|
export const CONFIG_PATH = 'synthetics.config.ts';
|
|
62
63
|
|
|
64
|
+
const IS_URL = new RegExp('^(https?:\\/\\/)');
|
|
65
|
+
|
|
63
66
|
export class Generator {
|
|
64
67
|
pkgManager = 'npm';
|
|
65
68
|
constructor(public projectDir: string) {}
|
|
@@ -80,29 +83,30 @@ export class Generator {
|
|
|
80
83
|
return JSON.parse(process.env.TEST_QUESTIONS);
|
|
81
84
|
}
|
|
82
85
|
|
|
83
|
-
const { onCloud } = await prompt<{ onCloud: string }>({
|
|
84
|
-
type: 'confirm',
|
|
85
|
-
name: 'onCloud',
|
|
86
|
-
initial: 'y',
|
|
87
|
-
message: 'Do you use Elastic Cloud',
|
|
88
|
-
});
|
|
89
86
|
const url = await new Input({
|
|
90
|
-
header: onCloud
|
|
91
|
-
? yellow(
|
|
92
|
-
'Get cloud.id from your deployment https://www.elastic.co/guide/en/cloud/current/ec-cloud-id.html'
|
|
93
|
-
)
|
|
94
|
-
: '',
|
|
95
|
-
message: onCloud
|
|
96
|
-
? 'What is your cloud.id'
|
|
97
|
-
: 'What is the url of your Kibana instance',
|
|
98
87
|
name: 'url',
|
|
88
|
+
message: 'Enter Elastic Kibana URL or Cloud ID',
|
|
99
89
|
required: true,
|
|
100
|
-
|
|
101
|
-
|
|
90
|
+
validate(value) {
|
|
91
|
+
try {
|
|
92
|
+
if (!IS_URL.test(value)) {
|
|
93
|
+
value = cloudIDToKibanaURL(value);
|
|
94
|
+
}
|
|
95
|
+
new URL(value);
|
|
96
|
+
return true;
|
|
97
|
+
} catch (e) {
|
|
98
|
+
return 'Invalid URL or Cloud ID';
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
result(value: string) {
|
|
102
|
+
if (!IS_URL.test(value)) {
|
|
103
|
+
value = cloudIDToKibanaURL(value);
|
|
104
|
+
}
|
|
105
|
+
return value;
|
|
102
106
|
},
|
|
103
107
|
}).run();
|
|
104
108
|
|
|
105
|
-
const auth = await new
|
|
109
|
+
const auth = await new Password({
|
|
106
110
|
name: 'auth',
|
|
107
111
|
header: yellow(
|
|
108
112
|
`Generate API key from Kibana ${getProjectApiKeyURL(url)}`
|
|
@@ -249,7 +253,7 @@ export class Generator {
|
|
|
249
253
|
}
|
|
250
254
|
// Add push command
|
|
251
255
|
if (!pkgJSON.scripts.push) {
|
|
252
|
-
pkgJSON.scripts.push =
|
|
256
|
+
pkgJSON.scripts.push = `npx @elastic/synthetics push`;
|
|
253
257
|
}
|
|
254
258
|
|
|
255
259
|
await this.createFile(
|
|
@@ -287,7 +291,7 @@ All set, you can run below commands inside: ${this.projectDir}:
|
|
|
287
291
|
)}
|
|
288
292
|
|
|
289
293
|
${yellow(
|
|
290
|
-
'
|
|
294
|
+
'Configure API Key via `SYNTHETICS_API_KEY` env variable or --auth CLI flag.'
|
|
291
295
|
)}
|
|
292
296
|
|
|
293
297
|
Visit https://www.elastic.co/guide/en/observability/current/synthetic-run-tests.html to learn more.
|
package/src/options.ts
CHANGED
|
@@ -26,74 +26,49 @@
|
|
|
26
26
|
import merge from 'deepmerge';
|
|
27
27
|
import { createOption } from 'commander';
|
|
28
28
|
import { readConfig } from './config';
|
|
29
|
-
import type { CliArgs,
|
|
30
|
-
import { THROTTLING_WARNING_MSG,
|
|
29
|
+
import type { CliArgs, RunOptions } from './common_types';
|
|
30
|
+
import { THROTTLING_WARNING_MSG, warn } from './helpers';
|
|
31
31
|
|
|
32
32
|
type Mode = 'run' | 'push';
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Normalize the options passed via CLI and Synthetics config file
|
|
36
|
+
*
|
|
37
|
+
* Order of preference for options:
|
|
38
|
+
* 1. Local options configured via Runner API
|
|
39
|
+
* 2. CLI flags
|
|
40
|
+
* 3. Configuration file
|
|
41
|
+
*/
|
|
34
42
|
export async function normalizeOptions(
|
|
35
43
|
cliArgs: CliArgs,
|
|
36
44
|
mode: Mode = 'run'
|
|
37
45
|
): Promise<RunOptions> {
|
|
46
|
+
/**
|
|
47
|
+
* Move filtering flags from the top level to filter object
|
|
48
|
+
* and delete the old keys
|
|
49
|
+
*/
|
|
50
|
+
const grepOpts = {
|
|
51
|
+
pattern: cliArgs.pattern,
|
|
52
|
+
tags: cliArgs.tags,
|
|
53
|
+
match: cliArgs.match,
|
|
54
|
+
};
|
|
55
|
+
delete cliArgs.pattern;
|
|
56
|
+
delete cliArgs.tags;
|
|
57
|
+
delete cliArgs.match;
|
|
58
|
+
|
|
38
59
|
const options: RunOptions = {
|
|
39
60
|
...cliArgs,
|
|
61
|
+
grepOpts,
|
|
40
62
|
environment: process.env['NODE_ENV'] || 'development',
|
|
41
63
|
};
|
|
42
64
|
/**
|
|
43
|
-
*
|
|
44
|
-
* eventually by the Synthetics UI.
|
|
45
|
-
*/
|
|
46
|
-
if (cliArgs.richEvents) {
|
|
47
|
-
options.reporter = cliArgs.reporter ?? 'json';
|
|
48
|
-
options.ssblocks = true;
|
|
49
|
-
options.network = true;
|
|
50
|
-
options.trace = true;
|
|
51
|
-
options.quietExitCode = true;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (cliArgs.capability) {
|
|
55
|
-
const supportedCapabilities = [
|
|
56
|
-
'trace',
|
|
57
|
-
'network',
|
|
58
|
-
'filmstrips',
|
|
59
|
-
'metrics',
|
|
60
|
-
'ssblocks',
|
|
61
|
-
];
|
|
62
|
-
/**
|
|
63
|
-
* trace - record chrome trace events(LCP, FCP, CLS, etc.) for all journeys
|
|
64
|
-
* network - capture network information for all journeys
|
|
65
|
-
* filmstrips - record detailed filmstrips for all journeys
|
|
66
|
-
* metrics - capture performance metrics (DOM Nodes, Heap size, etc.) for each step
|
|
67
|
-
* ssblocks - Dedupes the screenshots in to blocks to save storage space
|
|
68
|
-
*/
|
|
69
|
-
for (const flag of cliArgs.capability) {
|
|
70
|
-
if (supportedCapabilities.includes(flag)) {
|
|
71
|
-
options[flag] = true;
|
|
72
|
-
} else {
|
|
73
|
-
console.warn(
|
|
74
|
-
`Missing capability "${flag}", current supported capabilities are ${supportedCapabilities.join(
|
|
75
|
-
', '
|
|
76
|
-
)}`
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Validate and read synthetics config file
|
|
84
|
-
* based on the environment
|
|
65
|
+
* Validate and read synthetics config file based on the environment
|
|
85
66
|
*/
|
|
86
67
|
const config =
|
|
87
68
|
cliArgs.config || !cliArgs.inline
|
|
88
69
|
? await readConfig(options.environment, cliArgs.config)
|
|
89
70
|
: {};
|
|
90
71
|
|
|
91
|
-
/**
|
|
92
|
-
* Order of preference for options that are used while running are
|
|
93
|
-
* 1. Local options configured via Runner API
|
|
94
|
-
* 2. CLI flags
|
|
95
|
-
* 3. Configuration file
|
|
96
|
-
*/
|
|
97
72
|
options.params = Object.freeze(merge(config.params, cliArgs.params || {}));
|
|
98
73
|
|
|
99
74
|
/**
|
|
@@ -118,10 +93,49 @@ export async function normalizeOptions(
|
|
|
118
93
|
*/
|
|
119
94
|
switch (mode) {
|
|
120
95
|
case 'run':
|
|
96
|
+
if (cliArgs.capability) {
|
|
97
|
+
const supportedCapabilities = [
|
|
98
|
+
'trace',
|
|
99
|
+
'network',
|
|
100
|
+
'filmstrips',
|
|
101
|
+
'metrics',
|
|
102
|
+
'ssblocks',
|
|
103
|
+
];
|
|
104
|
+
/**
|
|
105
|
+
* trace - record chrome trace events(LCP, FCP, CLS, etc.) for all journeys
|
|
106
|
+
* network - capture network information for all journeys
|
|
107
|
+
* filmstrips - record detailed filmstrips for all journeys
|
|
108
|
+
* metrics - capture performance metrics (DOM Nodes, Heap size, etc.) for each step
|
|
109
|
+
* ssblocks - Dedupes the screenshots in to blocks to save storage space
|
|
110
|
+
*/
|
|
111
|
+
for (const flag of cliArgs.capability) {
|
|
112
|
+
if (supportedCapabilities.includes(flag)) {
|
|
113
|
+
options[flag] = true;
|
|
114
|
+
} else {
|
|
115
|
+
console.warn(
|
|
116
|
+
`Missing capability "${flag}", current supported capabilities are ${supportedCapabilities.join(
|
|
117
|
+
', '
|
|
118
|
+
)}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Group all events that can be consumed by heartbeat and
|
|
126
|
+
* eventually by the Synthetics UI.
|
|
127
|
+
*/
|
|
128
|
+
if (cliArgs.richEvents) {
|
|
129
|
+
options.reporter = cliArgs.reporter ?? 'json';
|
|
130
|
+
options.ssblocks = true;
|
|
131
|
+
options.network = true;
|
|
132
|
+
options.trace = true;
|
|
133
|
+
options.quietExitCode = true;
|
|
134
|
+
}
|
|
135
|
+
|
|
121
136
|
options.screenshots = cliArgs.screenshots ?? 'on';
|
|
122
137
|
break;
|
|
123
138
|
case 'push':
|
|
124
|
-
validatePushOptions(options as PushOptions);
|
|
125
139
|
/**
|
|
126
140
|
* Merge the default monitor config from synthetics.config.ts file
|
|
127
141
|
* with the CLI options passed via push command
|
|
@@ -152,14 +166,6 @@ export function getHeadlessFlag(
|
|
|
152
166
|
return configHeadless ?? true;
|
|
153
167
|
}
|
|
154
168
|
|
|
155
|
-
export function validatePushOptions(opts: PushOptions) {
|
|
156
|
-
if (opts.tags || opts.match) {
|
|
157
|
-
throw error(`Aborted. Invalid CLI flags.
|
|
158
|
-
|
|
159
|
-
Tags and Match are not supported in push command.`);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
169
|
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
|
164
170
|
function toObject(value: boolean | Record<string, any>): Record<string, any> {
|
|
165
171
|
const defaulVal = {};
|
|
@@ -183,19 +189,17 @@ export function parseThrottling() {
|
|
|
183
189
|
export function getCommonCommandOpts() {
|
|
184
190
|
const params = createOption(
|
|
185
191
|
'-p, --params <jsonstring>',
|
|
186
|
-
'JSON object that
|
|
187
|
-
);
|
|
188
|
-
params.argParser(JSON.parse);
|
|
192
|
+
'JSON object that defines any variables your tests require.'
|
|
193
|
+
).argParser(JSON.parse);
|
|
189
194
|
|
|
190
195
|
const playwrightOpts = createOption(
|
|
191
196
|
'--playwright-options <jsonstring>',
|
|
192
197
|
'JSON object to pass in custom Playwright options for the agent. Options passed will be merged with Playwright options defined in your synthetics.config.js file.'
|
|
193
|
-
);
|
|
194
|
-
playwrightOpts.argParser(JSON.parse);
|
|
198
|
+
).argParser(JSON.parse);
|
|
195
199
|
|
|
196
200
|
const pattern = createOption(
|
|
197
201
|
'--pattern <pattern>',
|
|
198
|
-
'RegExp pattern to match journey
|
|
202
|
+
'RegExp pattern to match journey files in the current working directory (default: /*.journey.(ts|js)$/)'
|
|
199
203
|
);
|
|
200
204
|
|
|
201
205
|
const apiDocsLink =
|
|
@@ -210,7 +214,16 @@ export function getCommonCommandOpts() {
|
|
|
210
214
|
|
|
211
215
|
const configOpt = createOption(
|
|
212
216
|
'-c, --config <path>',
|
|
213
|
-
'configuration
|
|
217
|
+
'path to the configuration file (default: synthetics.config.(js|ts))'
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const tags = createOption(
|
|
221
|
+
'--tags <name...>',
|
|
222
|
+
'run/push tests with the tag(s) matching a pattern'
|
|
223
|
+
);
|
|
224
|
+
const match = createOption(
|
|
225
|
+
'--match <name>',
|
|
226
|
+
'run/push tests with a name or tags that matches a pattern'
|
|
214
227
|
);
|
|
215
228
|
|
|
216
229
|
return {
|
|
@@ -220,5 +233,7 @@ export function getCommonCommandOpts() {
|
|
|
220
233
|
playwrightOpts,
|
|
221
234
|
pattern,
|
|
222
235
|
configOpt,
|
|
236
|
+
tags,
|
|
237
|
+
match,
|
|
223
238
|
};
|
|
224
239
|
}
|
package/src/push/index.ts
CHANGED
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
isLightweightMonitorSupported,
|
|
59
59
|
logDiff,
|
|
60
60
|
} from './utils';
|
|
61
|
+
import { log } from '../core/logger';
|
|
61
62
|
|
|
62
63
|
export async function push(monitors: Monitor[], options: PushOptions) {
|
|
63
64
|
const duplicates = trackDuplicates(monitors);
|
|
@@ -99,8 +100,17 @@ export async function push(monitors: Monitor[], options: PushOptions) {
|
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
if (removedIDs.size > 0) {
|
|
103
|
+
log(`deleting monitor ids: ${Array.from(removedIDs.keys()).join(', ')}`);
|
|
102
104
|
if (updatedMonitors.size === 0 && unchangedIDs.size === 0) {
|
|
103
|
-
await
|
|
105
|
+
await confirmDelete(
|
|
106
|
+
`Pushing without any monitors will delete all monitors associated with the project.\n Do you want to continue?`,
|
|
107
|
+
options.yes
|
|
108
|
+
);
|
|
109
|
+
} else {
|
|
110
|
+
await confirmDelete(
|
|
111
|
+
`Deleting ${removedIDs.size} monitors. Do you want to continue?`,
|
|
112
|
+
options.yes
|
|
113
|
+
);
|
|
104
114
|
}
|
|
105
115
|
const chunks = getChunks(Array.from(removedIDs), CHUNK_SIZE);
|
|
106
116
|
for (const chunk of chunks) {
|
|
@@ -114,22 +124,22 @@ export async function push(monitors: Monitor[], options: PushOptions) {
|
|
|
114
124
|
done(`Pushed: ${grey(getMonitorManagementURL(options.url))}`);
|
|
115
125
|
}
|
|
116
126
|
|
|
117
|
-
async function
|
|
118
|
-
|
|
119
|
-
const { deleteAll } = await prompt<{ deleteAll: boolean }>({
|
|
127
|
+
async function confirmDelete(message: string, skip: boolean) {
|
|
128
|
+
const { deleteMonitors } = await prompt<{ deleteMonitors: boolean }>({
|
|
120
129
|
type: 'confirm',
|
|
121
130
|
skip() {
|
|
122
|
-
|
|
131
|
+
write('');
|
|
132
|
+
if (skip || !process.stdout.isTTY) {
|
|
123
133
|
this.initial = process.env.TEST_OVERRIDE ?? true;
|
|
124
134
|
return true;
|
|
125
135
|
}
|
|
126
136
|
return false;
|
|
127
137
|
},
|
|
128
|
-
name: '
|
|
129
|
-
message
|
|
138
|
+
name: 'deleteMonitors',
|
|
139
|
+
message,
|
|
130
140
|
initial: false,
|
|
131
141
|
});
|
|
132
|
-
if (!
|
|
142
|
+
if (!deleteMonitors) {
|
|
133
143
|
throw warn('Push command Aborted');
|
|
134
144
|
}
|
|
135
145
|
}
|
|
@@ -288,7 +298,10 @@ export async function pushLegacy(monitors: Monitor[], options: PushOptions) {
|
|
|
288
298
|
);
|
|
289
299
|
}
|
|
290
300
|
} else {
|
|
291
|
-
await
|
|
301
|
+
await confirmDelete(
|
|
302
|
+
`Pushing without any monitors will delete all monitors associated with the project.\n Do you want to continue?`,
|
|
303
|
+
options.yes
|
|
304
|
+
);
|
|
292
305
|
}
|
|
293
306
|
await liveProgress(
|
|
294
307
|
createMonitorsLegacy({ schemas, keepStale: false, options }),
|
package/src/push/monitor.ts
CHANGED
|
@@ -167,8 +167,8 @@ export async function createLightweightMonitors(
|
|
|
167
167
|
) {
|
|
168
168
|
const lwFiles = new Set<string>();
|
|
169
169
|
// Filter monitor files based on the provided pattern
|
|
170
|
-
const pattern = options.pattern
|
|
171
|
-
? new RegExp(options.pattern, 'i')
|
|
170
|
+
const pattern = options.grepOpts?.pattern
|
|
171
|
+
? new RegExp(options.grepOpts?.pattern, 'i')
|
|
172
172
|
: /.(yml|yaml)$/;
|
|
173
173
|
const ignore = /(node_modules|.github)/;
|
|
174
174
|
await totalist(workDir, (rel, abs) => {
|
|
@@ -211,7 +211,9 @@ export async function createLightweightMonitors(
|
|
|
211
211
|
offsets.push(monNode.srcToken.offset);
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
const mergedConfig = parsedDoc.toJS()[
|
|
214
|
+
const mergedConfig = parsedDoc.toJS()[
|
|
215
|
+
'heartbeat.monitors'
|
|
216
|
+
] as Array<MonitorConfig>;
|
|
215
217
|
for (let i = 0; i < mergedConfig.length; i++) {
|
|
216
218
|
const monitor = mergedConfig[i];
|
|
217
219
|
// Skip browser monitors from the YML files
|
|
@@ -220,7 +222,14 @@ export async function createLightweightMonitors(
|
|
|
220
222
|
}
|
|
221
223
|
const { line, col } = lineCounter.linePos(offsets[i]);
|
|
222
224
|
try {
|
|
225
|
+
/**
|
|
226
|
+
* Build the monitor object from the yaml config along with global configuration
|
|
227
|
+
* and perform the match based on the provided filters
|
|
228
|
+
*/
|
|
223
229
|
const mon = buildMonitorFromYaml(monitor, options);
|
|
230
|
+
if (!mon.isMatch(options.grepOpts?.match, options.grepOpts?.tags)) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
224
233
|
mon.setSource({ file, line, column: col });
|
|
225
234
|
monitors.push(mon);
|
|
226
235
|
} catch (e) {
|