@elastic/synthetics 1.24.4 → 1.26.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/README.md +67 -0
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/common_types.d.ts +25 -0
- package/dist/common_types.d.ts.map +1 -1
- package/dist/core/certs.d.ts +55 -0
- package/dist/core/certs.d.ts.map +1 -0
- package/dist/core/certs.js +111 -0
- package/dist/core/certs.js.map +1 -0
- package/dist/core/gatherer.d.ts +4 -4
- package/dist/core/gatherer.d.ts.map +1 -1
- package/dist/core/gatherer.js +44 -7
- package/dist/core/gatherer.js.map +1 -1
- package/dist/core/globals.js +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +16 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/runner.d.ts +1 -1
- package/dist/core/runner.d.ts.map +1 -1
- package/dist/core/runner.js +87 -58
- package/dist/core/runner.js.map +1 -1
- package/dist/dsl/api-journey.d.ts +62 -0
- package/dist/dsl/api-journey.d.ts.map +1 -0
- package/dist/dsl/api-journey.js +55 -0
- package/dist/dsl/api-journey.js.map +1 -0
- package/dist/dsl/index.d.ts +1 -0
- package/dist/dsl/index.d.ts.map +1 -1
- package/dist/dsl/index.js +1 -0
- package/dist/dsl/index.js.map +1 -1
- package/dist/dsl/journey.d.ts +10 -2
- package/dist/dsl/journey.d.ts.map +1 -1
- package/dist/dsl/journey.js +11 -2
- package/dist/dsl/journey.js.map +1 -1
- package/dist/dsl/monitor.d.ts +2 -1
- package/dist/dsl/monitor.d.ts.map +1 -1
- package/dist/dsl/monitor.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +66 -1
- package/dist/loader.js.map +1 -1
- package/dist/network-timings.d.ts +55 -0
- package/dist/network-timings.d.ts.map +1 -0
- package/dist/network-timings.js +107 -0
- package/dist/network-timings.js.map +1 -0
- package/dist/options.d.ts +1 -0
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +16 -0
- package/dist/options.js.map +1 -1
- package/dist/plugins/api-network.d.ts +39 -0
- package/dist/plugins/api-network.d.ts.map +1 -0
- package/dist/plugins/api-network.js +235 -0
- package/dist/plugins/api-network.js.map +1 -0
- package/dist/plugins/network.d.ts +0 -6
- package/dist/plugins/network.d.ts.map +1 -1
- package/dist/plugins/network.js +5 -70
- package/dist/plugins/network.js.map +1 -1
- package/dist/plugins/plugin-manager.d.ts +11 -4
- package/dist/plugins/plugin-manager.d.ts.map +1 -1
- package/dist/plugins/plugin-manager.js +36 -16
- package/dist/plugins/plugin-manager.js.map +1 -1
- package/dist/push/index.d.ts.map +1 -1
- package/dist/push/index.js +4 -0
- package/dist/push/index.js.map +1 -1
- package/dist/push/monitor.d.ts.map +1 -1
- package/dist/push/monitor.js +1 -1
- package/dist/push/monitor.js.map +1 -1
- package/dist/push/run-local.d.ts.map +1 -1
- package/dist/push/run-local.js +15 -3
- package/dist/push/run-local.js.map +1 -1
- package/dist/push/utils.d.ts +7 -0
- package/dist/push/utils.d.ts.map +1 -1
- package/dist/push/utils.js +15 -1
- package/dist/push/utils.js.map +1 -1
- package/dist/reporters/base.d.ts +2 -2
- package/dist/reporters/base.d.ts.map +1 -1
- package/dist/reporters/base.js.map +1 -1
- package/dist/reporters/index.d.ts +2 -2
- package/dist/reporters/index.d.ts.map +1 -1
- package/dist/reporters/index.js.map +1 -1
- package/dist/reporters/json.d.ts +6 -2
- package/dist/reporters/json.d.ts.map +1 -1
- package/dist/reporters/json.js +68 -33
- package/dist/reporters/json.js.map +1 -1
- package/package.json +11 -21
- package/src/cli.ts +3 -0
- package/src/common_types.ts +34 -0
- package/src/core/certs.ts +122 -0
- package/src/core/gatherer.ts +63 -10
- package/src/core/globals.ts +1 -1
- package/src/core/index.ts +28 -0
- package/src/core/runner.ts +94 -67
- package/src/dsl/api-journey.ts +93 -0
- package/src/dsl/index.ts +1 -0
- package/src/dsl/journey.ts +21 -9
- package/src/dsl/monitor.ts +2 -0
- package/src/index.ts +6 -0
- package/src/loader.ts +78 -2
- package/src/network-timings.ts +130 -0
- package/src/options.ts +25 -0
- package/src/plugins/api-network.ts +252 -0
- package/src/plugins/network.ts +5 -87
- package/src/plugins/plugin-manager.ts +60 -19
- package/src/push/index.ts +11 -0
- package/src/push/monitor.ts +6 -2
- package/src/push/run-local.ts +25 -15
- package/src/push/utils.ts +15 -0
- package/src/reporters/base.ts +2 -1
- package/src/reporters/index.ts +2 -1
- package/src/reporters/json.ts +74 -41
- package/templates/README.md +3 -1
- package/templates/journeys/advanced-api-example-helpers.ts +79 -0
- package/templates/journeys/advanced-api-example.journey.ts +52 -0
- package/templates/journeys/api-example.journey.ts +35 -0
- package/templates/synthetics.config.ts +2 -0
- package/.husky/install.mjs +0 -7
package/src/reporters/json.ts
CHANGED
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
JourneyStartResult,
|
|
52
52
|
StepEndResult,
|
|
53
53
|
JourneyEndResult,
|
|
54
|
+
APIJourneyEndResult,
|
|
54
55
|
PageMetrics,
|
|
55
56
|
} from '../common_types';
|
|
56
57
|
import { inspect } from 'util';
|
|
@@ -137,6 +138,25 @@ function getMetadata() {
|
|
|
137
138
|
};
|
|
138
139
|
}
|
|
139
140
|
|
|
141
|
+
// ECS `server.ip`/`server.port`. Emitted for API journeys only; browser
|
|
142
|
+
// output keeps the address under `http.response` to preserve its shape.
|
|
143
|
+
function formatServer(response: NetworkInfo['response']) {
|
|
144
|
+
if (!response?.remoteIPAddress && !response?.remotePort) return undefined;
|
|
145
|
+
return {
|
|
146
|
+
ip: response.remoteIPAddress,
|
|
147
|
+
port: response.remotePort,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Epoch seconds -> ISO-8601, tolerating missing/non-finite input. A
|
|
152
|
+
// malformed cert date would otherwise throw `RangeError` and sink the
|
|
153
|
+
// whole `journey/end` document (see json.test.ts regression).
|
|
154
|
+
function epochToIso(epochSeconds: number | undefined): string | undefined {
|
|
155
|
+
if (epochSeconds == null || !Number.isFinite(epochSeconds)) return undefined;
|
|
156
|
+
const date = new Date(epochSeconds * 1000);
|
|
157
|
+
return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
|
|
158
|
+
}
|
|
159
|
+
|
|
140
160
|
function formatTLS(tls: SecurityDetails) {
|
|
141
161
|
if (!tls || !tls.protocol) {
|
|
142
162
|
return;
|
|
@@ -151,8 +171,8 @@ function formatTLS(tls: SecurityDetails) {
|
|
|
151
171
|
subject: {
|
|
152
172
|
common_name: tls.subjectName,
|
|
153
173
|
},
|
|
154
|
-
not_after:
|
|
155
|
-
not_before:
|
|
174
|
+
not_after: epochToIso(tls.validTo),
|
|
175
|
+
not_before: epochToIso(tls.validFrom),
|
|
156
176
|
},
|
|
157
177
|
},
|
|
158
178
|
version_protocol: name,
|
|
@@ -208,6 +228,7 @@ export function formatNetworkFields(network: NetworkInfo) {
|
|
|
208
228
|
response,
|
|
209
229
|
},
|
|
210
230
|
tls: formatTLS(response?.securityDetails),
|
|
231
|
+
...(browser?.name === 'api' && { server: formatServer(response) }),
|
|
211
232
|
};
|
|
212
233
|
|
|
213
234
|
const pickItems: Array<keyof NetworkInfo> = [
|
|
@@ -257,10 +278,15 @@ function journeyInfo(journey: Partial<Journey>, type: OutputFields['type']) {
|
|
|
257
278
|
if (!journey) {
|
|
258
279
|
return;
|
|
259
280
|
}
|
|
281
|
+
// Surface non-default journey types (e.g. `api`) for downstream routing;
|
|
282
|
+
// browser output omits the field to stay byte-compatible.
|
|
283
|
+
const journeyType =
|
|
284
|
+
journey.type && journey.type !== 'browser' ? journey.type : undefined;
|
|
260
285
|
const info: JourneyInfo = {
|
|
261
286
|
name: journey.name,
|
|
262
287
|
id: journey.id,
|
|
263
288
|
tags: journey.tags,
|
|
289
|
+
...(journeyType ? { type: journeyType } : {}),
|
|
264
290
|
};
|
|
265
291
|
const isEnd = type === 'journey/end';
|
|
266
292
|
if (isEnd) {
|
|
@@ -439,40 +465,42 @@ export default class JSONReporter extends BaseReporter {
|
|
|
439
465
|
|
|
440
466
|
override async onJourneyEnd(
|
|
441
467
|
journey: Journey,
|
|
442
|
-
|
|
443
|
-
timestamp,
|
|
444
|
-
browserDelay,
|
|
445
|
-
networkinfo,
|
|
446
|
-
browserconsole,
|
|
447
|
-
options,
|
|
448
|
-
}: JourneyEndResult
|
|
468
|
+
result: JourneyEndResult | APIJourneyEndResult
|
|
449
469
|
) {
|
|
450
|
-
const {
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
470
|
+
const { timestamp, networkinfo, options } = result;
|
|
471
|
+
const isAPIJourney = journey.type === 'api';
|
|
472
|
+
// Browser-only fields; absent for API journeys.
|
|
473
|
+
const browserDelay = (result as JourneyEndResult).browserDelay;
|
|
474
|
+
const browserconsole = (result as JourneyEndResult).browserconsole;
|
|
475
|
+
|
|
476
|
+
if (!isAPIJourney) {
|
|
477
|
+
const { ssblocks, screenshots } = options;
|
|
478
|
+
const writeScreenshots =
|
|
479
|
+
screenshots === 'on' ||
|
|
480
|
+
(screenshots === 'only-on-failure' && journey.status === 'failed');
|
|
481
|
+
if (writeScreenshots) {
|
|
482
|
+
await gatherScreenshots(
|
|
483
|
+
join(CACHE_PATH, 'screenshots'),
|
|
484
|
+
async screenshot => {
|
|
485
|
+
const { data, timestamp, step } = screenshot;
|
|
486
|
+
if (!data) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
if (ssblocks) {
|
|
490
|
+
await this.writeScreenshotBlocks(journey, screenshot);
|
|
491
|
+
} else {
|
|
492
|
+
this.writeJSON({
|
|
493
|
+
type: 'step/screenshot',
|
|
494
|
+
timestamp,
|
|
495
|
+
journey,
|
|
496
|
+
step,
|
|
497
|
+
blob: data,
|
|
498
|
+
blob_mime: 'image/jpeg',
|
|
499
|
+
});
|
|
500
|
+
}
|
|
473
501
|
}
|
|
474
|
-
|
|
475
|
-
|
|
502
|
+
);
|
|
503
|
+
}
|
|
476
504
|
}
|
|
477
505
|
|
|
478
506
|
if (networkinfo) {
|
|
@@ -504,18 +532,23 @@ export default class JSONReporter extends BaseReporter {
|
|
|
504
532
|
});
|
|
505
533
|
}
|
|
506
534
|
|
|
535
|
+
const endPayload: Payload = {
|
|
536
|
+
status: journey.status,
|
|
537
|
+
// Omit `browser_delay_us` for API journeys where it has no meaning;
|
|
538
|
+
// key order preserves the existing browser wire shape.
|
|
539
|
+
...(isAPIJourney
|
|
540
|
+
? {}
|
|
541
|
+
: { browser_delay_us: getDurationInUs(browserDelay) }),
|
|
542
|
+
// timestamp in microseconds at which the current node process began, measured in Unix time.
|
|
543
|
+
process_startup_epoch_us: Math.trunc(processStart * 1000),
|
|
544
|
+
};
|
|
545
|
+
|
|
507
546
|
this.writeJSON({
|
|
508
547
|
type: 'journey/end',
|
|
509
548
|
journey,
|
|
510
549
|
timestamp,
|
|
511
550
|
error: journey.error,
|
|
512
|
-
payload:
|
|
513
|
-
status: journey.status,
|
|
514
|
-
// convert from monotonic seconds time to microseconds
|
|
515
|
-
browser_delay_us: getDurationInUs(browserDelay),
|
|
516
|
-
// timestamp in microseconds at which the current node process began, measured in Unix time.
|
|
517
|
-
process_startup_epoch_us: Math.trunc(processStart * 1000),
|
|
518
|
-
},
|
|
551
|
+
payload: endPayload,
|
|
519
552
|
});
|
|
520
553
|
}
|
|
521
554
|
|
package/templates/README.md
CHANGED
|
@@ -9,7 +9,9 @@ You can always use this command to scafold new Synthetics projects.
|
|
|
9
9
|
Key places to look:
|
|
10
10
|
|
|
11
11
|
- The `synthetics.config.ts` file contains configuration for your project.
|
|
12
|
-
- The `journeys` directory contains both basic and more advanced examples of using synthetics
|
|
12
|
+
- The `journeys` directory contains both basic and more advanced examples of using synthetics:
|
|
13
|
+
- `example.journey.ts` and `advanced-example.journey.ts` are **browser journeys** that drive Chromium against a publicly hosted [Todos List](https://elastic.github.io/synthetics-demo/).
|
|
14
|
+
- `api-example.journey.ts` and `advanced-api-example.journey.ts` are **API journeys** that run without a browser, hitting [jsonplaceholder.typicode.com](https://jsonplaceholder.typicode.com) for a quick, runnable demonstration of multi-step API monitoring. Use these for OAuth-protected endpoints, multi-step API flows, and high-frequency lightweight checks.
|
|
13
15
|
- The `.github` directory contains an example github action, demonstrating the use of a CI service for automatically running tests on merges and PR creation.
|
|
14
16
|
|
|
15
17
|
To run the tests locally, use the following command:
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
step,
|
|
3
|
+
APIRequestContext,
|
|
4
|
+
APIResponse,
|
|
5
|
+
expect,
|
|
6
|
+
} from '@elastic/synthetics';
|
|
7
|
+
|
|
8
|
+
// Helpers for `advanced-api-example.journey.ts` — same idea as
|
|
9
|
+
// `advanced-example-helpers.ts` for browser journeys, but operating on
|
|
10
|
+
// `APIRequestContext` instead of `Page`. Wrapping each call in a `step`
|
|
11
|
+
// gives you per-request timing, status, and error attribution in the
|
|
12
|
+
// reporter output.
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generic "assert this call returns the expected status" wrapper. Useful
|
|
16
|
+
* when a step is just a single request with a known good status code
|
|
17
|
+
* and you don't need to inspect the body.
|
|
18
|
+
*/
|
|
19
|
+
export const expectStatusStep = (
|
|
20
|
+
name: string,
|
|
21
|
+
call: () => Promise<APIResponse>,
|
|
22
|
+
expectedStatus = 200
|
|
23
|
+
) => {
|
|
24
|
+
step(name, async () => {
|
|
25
|
+
const res = await call();
|
|
26
|
+
expect(res.status()).toBe(expectedStatus);
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const listTodosStep = (
|
|
31
|
+
request: APIRequestContext,
|
|
32
|
+
apiUrl: string,
|
|
33
|
+
state: { ids: number[] }
|
|
34
|
+
) => {
|
|
35
|
+
step('list todos', async () => {
|
|
36
|
+
const res = await request.get(`${apiUrl}/todos?_limit=5`);
|
|
37
|
+
expect(res.status()).toBe(200);
|
|
38
|
+
const todos: Array<{ id: number }> = await res.json();
|
|
39
|
+
expect(todos.length).toBeGreaterThan(0);
|
|
40
|
+
state.ids = todos.map(t => t.id);
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const createTodoStep = (
|
|
45
|
+
request: APIRequestContext,
|
|
46
|
+
apiUrl: string,
|
|
47
|
+
title: string,
|
|
48
|
+
state: { createdId?: number }
|
|
49
|
+
) => {
|
|
50
|
+
step(`create todo "${title}"`, async () => {
|
|
51
|
+
const res = await request.post(`${apiUrl}/todos`, {
|
|
52
|
+
data: { title, completed: false, userId: 1 },
|
|
53
|
+
});
|
|
54
|
+
expect(res.status()).toBe(201);
|
|
55
|
+
const created = await res.json();
|
|
56
|
+
expect(created).toMatchObject({ title, completed: false });
|
|
57
|
+
state.createdId = created.id;
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Delete a todo whose id is produced by an earlier step. The id is
|
|
63
|
+
* supplied as a thunk so the step builder can be registered up-front
|
|
64
|
+
* but resolve the actual id lazily, at execution time, after the
|
|
65
|
+
* earlier step has populated state.
|
|
66
|
+
*/
|
|
67
|
+
export const deleteTodoStep = (
|
|
68
|
+
request: APIRequestContext,
|
|
69
|
+
apiUrl: string,
|
|
70
|
+
getId: () => number | undefined
|
|
71
|
+
) => {
|
|
72
|
+
step('delete created todo', async () => {
|
|
73
|
+
const id = getId();
|
|
74
|
+
if (id == null) return; // earlier step didn't produce an id; skip silently
|
|
75
|
+
const res = await request.delete(`${apiUrl}/todos/${id}`);
|
|
76
|
+
// jsonplaceholder returns 200 for DELETE; real APIs typically 204.
|
|
77
|
+
expect([200, 204]).toContain(res.status());
|
|
78
|
+
});
|
|
79
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { apiJourney, monitor, step, expect } from '@elastic/synthetics';
|
|
2
|
+
import {
|
|
3
|
+
listTodosStep,
|
|
4
|
+
createTodoStep,
|
|
5
|
+
deleteTodoStep,
|
|
6
|
+
} from './advanced-api-example-helpers';
|
|
7
|
+
|
|
8
|
+
// This file demonstrates the recommended shape for non-trivial API
|
|
9
|
+
// monitors: small, named, reusable step builders, with shared state
|
|
10
|
+
// passed by reference between steps so later steps can act on data
|
|
11
|
+
// produced by earlier ones (e.g. a token from `/auth`, an id from
|
|
12
|
+
// `/create`, etc.).
|
|
13
|
+
//
|
|
14
|
+
// Each step gets its own entry in the reporter output, so you get
|
|
15
|
+
// per-request status, latency, and (for HTTPS) TLS / remote-address
|
|
16
|
+
// telemetry — same as a browser journey's network waterfall.
|
|
17
|
+
//
|
|
18
|
+
// Note on execution order: the journey callback below runs once at
|
|
19
|
+
// *registration* time and every `step(...)` call enqueues a step body
|
|
20
|
+
// that executes later, in order. We register all steps unconditionally
|
|
21
|
+
// at the top level and read `state` only inside step bodies — by the
|
|
22
|
+
// time a step body runs, the steps before it have already populated
|
|
23
|
+
// the shared object. If any prerequisite step fails, the runner skips
|
|
24
|
+
// the rest of the journey, so guards inside later steps are kept
|
|
25
|
+
// minimal.
|
|
26
|
+
|
|
27
|
+
apiJourney('todos API: list and round-trip a todo', ({ request, params }) => {
|
|
28
|
+
monitor.use({
|
|
29
|
+
id: 'todos-api-roundtrip',
|
|
30
|
+
schedule: 10,
|
|
31
|
+
tags: ['api', 'todos'],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Shared state populated by earlier steps and consumed by later ones.
|
|
35
|
+
const state: { ids: number[]; createdId?: number } = { ids: [] };
|
|
36
|
+
|
|
37
|
+
listTodosStep(request, params.apiUrl, state);
|
|
38
|
+
createTodoStep(request, params.apiUrl, 'synthetic check', state);
|
|
39
|
+
|
|
40
|
+
// Inline step that reads `state` populated by `listTodosStep`. The
|
|
41
|
+
// body runs after the previous steps have completed, so by this
|
|
42
|
+
// point `state.ids` is populated.
|
|
43
|
+
step('fetch first todo from list', async () => {
|
|
44
|
+
expect(state.ids.length).toBeGreaterThan(0);
|
|
45
|
+
const id = state.ids[0];
|
|
46
|
+
const res = await request.get(`${params.apiUrl}/todos/${id}`);
|
|
47
|
+
expect(res.status()).toBe(200);
|
|
48
|
+
expect((await res.json()).id).toBe(id);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
deleteTodoStep(request, params.apiUrl, () => state.createdId);
|
|
52
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { apiJourney, monitor, step, expect } from '@elastic/synthetics';
|
|
2
|
+
|
|
3
|
+
// API journeys run without launching a browser — they only use
|
|
4
|
+
// Playwright's APIRequestContext to hit HTTP endpoints. They are pushed
|
|
5
|
+
// to Kibana as HTTP-type monitors and are a good fit for OAuth-protected
|
|
6
|
+
// endpoints, multi-step API flows, and lightweight high-frequency checks.
|
|
7
|
+
//
|
|
8
|
+
// `params.apiUrl` is sourced from `synthetics.config.ts` and points at
|
|
9
|
+
// the jsonplaceholder.typicode.com demo API by default; override it for
|
|
10
|
+
// your own service via `--params '{"apiUrl":"..."}'` or per-environment
|
|
11
|
+
// in the config file.
|
|
12
|
+
|
|
13
|
+
apiJourney('My Example API Journey', ({ request, params }) => {
|
|
14
|
+
// Only relevant for the push command to create
|
|
15
|
+
// monitors in Kibana
|
|
16
|
+
monitor.use({
|
|
17
|
+
id: 'example-api-monitor',
|
|
18
|
+
schedule: 10,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
step('list todos', async () => {
|
|
22
|
+
const res = await request.get(`${params.apiUrl}/todos`);
|
|
23
|
+
expect(res.status()).toBe(200);
|
|
24
|
+
const todos = await res.json();
|
|
25
|
+
expect(Array.isArray(todos)).toBe(true);
|
|
26
|
+
expect(todos.length).toBeGreaterThan(0);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
step('fetch a single todo', async () => {
|
|
30
|
+
const res = await request.get(`${params.apiUrl}/todos/1`);
|
|
31
|
+
expect(res.status()).toBe(200);
|
|
32
|
+
const todo = await res.json();
|
|
33
|
+
expect(todo).toMatchObject({ id: 1, userId: expect.any(Number) });
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -4,6 +4,8 @@ export default env => {
|
|
|
4
4
|
const config: SyntheticsConfig = {
|
|
5
5
|
params: {
|
|
6
6
|
url: 'https://elastic.github.io/synthetics-demo/',
|
|
7
|
+
// Base URL for the API journey examples; replace with your service.
|
|
8
|
+
apiUrl: 'https://jsonplaceholder.typicode.com',
|
|
7
9
|
},
|
|
8
10
|
playwrightOptions: {
|
|
9
11
|
ignoreHTTPSErrors: false,
|