Skip to content

Commit 37f58be

Browse files
committed
add Write Synthetics test
1 parent e7f9293 commit 37f58be

File tree

1 file changed

+329
-13
lines changed

1 file changed

+329
-13
lines changed

solutions/observability/apps/write-synthetic-test.md

Lines changed: 329 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,343 @@ mapped_urls:
44
- https://www.elastic.co/guide/en/serverless/current/observability-synthetics-create-test.html
55
---
66

7-
# Write a synthetic test
7+
# Write a synthetic test [synthetics-create-test]
88

9-
% What needs to be done: Align serverless/stateful
9+
After [setting up a Synthetics project](../../../solutions/observability/apps/create-monitors-with-project-monitors.md), you can start writing synthetic tests that check critical actions and requests that an end-user might make on your site.
1010

11-
% Use migrated content from existing pages that map to this page:
1211

13-
% - [ ] ./raw-migrated-files/observability-docs/observability/synthetics-create-test.md
14-
% - [ ] ./raw-migrated-files/docs-content/serverless/observability-synthetics-create-test.md
12+
## Syntax overview [synthetics-syntax]
1513

16-
% Internal links rely on the following IDs being on this page (e.g. as a heading ID, paragraph ID, etc):
14+
To write synthetic tests for your application, you’ll need to know basic JavaScript and [Playwright](https://playwright.dev/) syntax.
1715

18-
$$$before-after$$$
16+
::::{tip}
17+
[Playwright](https://playwright.dev/) is a browser testing library developed by Microsoft. It’s fast, reliable, and features a modern API that automatically waits for page elements to be ready.
18+
::::
1919

20-
$$$synthetics-create-journey$$$
2120

22-
$$$synthetics-create-step$$$
21+
The synthetics agent exposes an API for creating and running tests, including:
2322

24-
$$$synthetics-make-assertions$$$
23+
`journey`
24+
: Tests one discrete unit of functionality. Takes two parameters: a `name` (string) and a `callback` (function). Learn more in [Create a journey](../../../solutions/observability/apps/write-synthetic-test.md#synthetics-create-journey).
2525

26-
$$$synthetics-playwright$$$
26+
`step`
27+
: Actions within a journey that should be completed in a specific order. Takes two parameters: a `name` (string) and a `callback` (function). Learn more in [Add steps](../../../solutions/observability/apps/write-synthetic-test.md#synthetics-create-step).
2728

28-
$$$synthetics-request-param$$$
29+
`expect`
30+
: Check that a value meets a specific condition. There are several supported checks. Learn more in [Make assertions](../../../solutions/observability/apps/write-synthetic-test.md#synthetics-make-assertions).
2931

30-
$$$synthetics-test-locally$$$
32+
`beforeAll`
33+
: Runs a provided function once, before any `journey` runs. If the provided function is a promise, the runner will wait for the promise to resolve before invoking the `journey`. Takes one parameter: a `callback` (function). Learn more in [Set up and remove a global state](../../../solutions/observability/apps/write-synthetic-test.md#before-after).
34+
35+
`before`
36+
: Runs a provided function before a single `journey` runs. Takes one parameter: a `callback` (function). Learn more in [Set up and remove a global state](../../../solutions/observability/apps/write-synthetic-test.md#before-after).
37+
38+
`afterAll`
39+
: Runs a provided function once, after all the `journey` runs have completed. Takes one parameter: a `callback` (function). Learn more in [Set up and remove a global state](../../../solutions/observability/apps/write-synthetic-test.md#before-after).
40+
41+
`after`
42+
: Runs a provided function after a single `journey` has completed. Takes one parameter: a `callback` (function). Learn more in [Set up and remove a global state](../../../solutions/observability/apps/write-synthetic-test.md#before-after).
43+
44+
`monitor`
45+
: The `monitor.use` method allows you to determine a monitor’s configuration on a journey-by-journey basis. If you want two journeys to create monitors with different intervals, for example, you should call `monitor.use` in each of them and set the `schedule` property to different values in each. Note that this is only relevant when using the `push` command to create monitors in {{kib}} an Observability serverless project. Learn more in [Configure individual monitors](../../../solutions/observability/apps/configure-individual-browser-monitors.md).
46+
47+
48+
## Create a journey [synthetics-create-journey]
49+
50+
Create a new file using the `.journey.ts` or `.journey.js` file extension or edit one of the example journey files.
51+
52+
A *journey* tests one discrete unit of functionality. For example, logging into a website, adding something to a cart, or joining a mailing list.
53+
54+
The journey function takes two parameters: a `name` and a `callback`. The `name` helps you identify an individual journey. The `callback` argument is a function that encapsulates what the journey does. The callback provides access to fresh Playwright `page`, `params`, `browser`, and `context` instances.
55+
56+
```js
57+
journey('Journey name', ({ page, browser, context, params, request }) => {
58+
// Add steps here
59+
});
60+
```
61+
62+
63+
### Arguments [synthetics-journey-ref]
64+
65+
**`name`** (*string*)
66+
: A user-defined string to describe the journey.
67+
68+
**`callback`** (*function*)
69+
: A function where you will add steps.
70+
71+
**Instances**:
72+
73+
`page`
74+
: A [page](https://playwright.dev/docs/api/class-page) object from Playwright that lets you control the browser’s current page.
75+
76+
`browser`
77+
: A [browser](https://playwright.dev/docs/api/class-playwright) object created by Playwright.
78+
79+
`context`
80+
: A [browser context](https://playwright.dev/docs/api/class-browsercontext) that doesn’t share cookies or cache with other browser contexts.
81+
82+
`params`
83+
: User-defined variables that allow you to invoke the Synthetics suite with custom parameters. For example, if you want to use a different homepage depending on the `env` (`localhost` for `dev` and a URL for `prod`). See [Work with params and secrets](../../../solutions/observability/apps/work-with-params-secrets.md) for more information.
84+
85+
`request`
86+
: A request object that can be used to make API requests independently of the browser interactions. For example, to get authentication credentials or tokens in service of a browser-based test. See [Make API requests](../../../solutions/observability/apps/write-synthetic-test.md#synthetics-request-param) for more information.
87+
88+
89+
90+
## Add steps [synthetics-create-step]
91+
92+
A journey consists of one or more *steps*. Steps are actions that should be completed in a specific order. Steps are displayed individually in the Synthetics UI along with screenshots for convenient debugging and error tracking.
93+
94+
A basic two-step journey would look like this:
95+
96+
```js
97+
journey('Journey name', ({ page, browser, client, params, request }) => {
98+
step('Step 1 name', () => {
99+
// Do something here
100+
});
101+
step('Step 2 name', () => {
102+
// Do something else here
103+
});
104+
});
105+
```
106+
107+
Steps can be as simple or complex as you need them to be. For example, a basic first step might load a web page:
108+
109+
```js
110+
step('Load the demo page', () => {
111+
await page.goto('https://elastic.github.io/synthetics-demo/'); <1>
112+
});
113+
```
114+
115+
1. Go to the [`page.goto` reference](https://playwright.dev/docs/api/class-page#page-goto) for more information.
116+
117+
118+
119+
### Arguments [synthetics-step-ref]
120+
121+
| | |
122+
| --- | --- |
123+
| **`name`** (*string*) | A user-defined string to describe the journey. |
124+
| **`callback`** (*function*) | A function where you simulate user workflows using Synthetics and [Playwright](../../../solutions/observability/apps/write-synthetic-test.md#synthetics-playwright) syntax. |
125+
126+
::::{note}
127+
128+
If you want to generate code by interacting with a web page directly, you can use the **Synthetics Recorder**.
129+
130+
The recorder launches a [Chromium browser](https://www.chromium.org/Home/) that will listen to each interaction you have with the web page and record them internally using Playwright. When you’re done interacting with the browser, the recorder converts the recorded actions into JavaScript code that you can use with Elastic Synthetics or {{heartbeat}}.
131+
132+
For more details on getting started with the Synthetics Recorder, refer to [Use the Synthetics Recorder](../../../solutions/observability/apps/use-synthetics-recorder.md).
133+
134+
::::
135+
136+
137+
138+
### Playwright syntax [synthetics-playwright]
139+
140+
Inside the callback for each step, you’ll likely use a lot of Playwright syntax. Use Playwright to simulate and validate user workflows including:
141+
142+
* Interacting with the [browser](https://playwright.dev/docs/api/class-browser) or the current [page](https://playwright.dev/docs/api/class-page) (like in the example above).
143+
* Finding elements on a web page using [locators](https://playwright.dev/docs/api/class-locator).
144+
* Simulating [mouse](https://playwright.dev/docs/api/class-mouse), [touch](https://playwright.dev/docs/api/class-touchscreen), or [keyboard](https://playwright.dev/docs/api/class-keyboard) events.
145+
* Making assertions using [`@playwright/test`'s `expect` function](https://playwright.dev/docs/test-assertions). Read more in [Make assertions](../../../solutions/observability/apps/write-synthetic-test.md#synthetics-make-assertions).
146+
147+
Visit the [Playwright documentation](https://playwright.dev/docs) for information.
148+
149+
::::{note}
150+
Do not attempt to run in headful mode (using `headless:false`) when running through Elastic’s global managed testing infrastructure or Private Locations as this is not supported.
151+
152+
::::
153+
154+
155+
However, not all Playwright functionality should be used with Elastic Synthetics. In some cases, there are alternatives to Playwright functionality built into the Elastic Synthetics library. These alternatives are designed to work better for synthetic monitoring. Do *not* use Playwright syntax to:
156+
157+
* **Make API requests.** Use Elastic Synthetic’s `request` parameter instead. Read more in [Make API requests](../../../solutions/observability/apps/write-synthetic-test.md#synthetics-request-param).
158+
159+
There is also some Playwright functionality that is not supported out-of-the-box in Elastic Synthetics including:
160+
161+
* [Videos](https://playwright.dev/docs/api/class-video)
162+
* The [`toHaveScreenshot`](https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-have-screenshot-1) and [`toMatchSnapshot`](https://playwright.dev/docs/api/class-snapshotassertions) assertions
163+
164+
::::{note}
165+
Captures done programmatically via [`screenshot`](https://playwright.dev/docs/api/class-page#page-screenshot) or [`video`](https://playwright.dev/docs/api/class-page#page-video) are not stored and are not shown in the Synthetics application. Providing a `path` will likely make the monitor fail due to missing permissions to write local files.
166+
167+
::::
168+
169+
170+
171+
## Make assertions [synthetics-make-assertions]
172+
173+
A more complex `step` might wait for a page element to be selected and then make sure that it matches an expected value.
174+
175+
Elastic Synthetics uses `@playwright/test`'s `expect` function to make assertions and supports most [Playwright assertions](https://playwright.dev/docs/test-assertions). Elastic Synthetics does *not* support [`toHaveScreenshot`](https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-have-screenshot-1) or any [Snapshot Assertions](https://playwright.dev/docs/api/class-snapshotassertions).
176+
177+
For example, on a page using the following HTML:
178+
179+
```html
180+
<header class="header">
181+
<h1>todos</h1>
182+
<input class="new-todo"
183+
autofocus autocomplete="off"
184+
placeholder="What needs to be done?">
185+
</header>
186+
```
187+
188+
You can verify that the `input` element with class `new-todo` has the expected `placeholder` value (the hint text for `input` elements) with the following test:
189+
190+
```js
191+
step('Assert placeholder text', async () => {
192+
const input = await page.locator('input.new-todo'); <1>
193+
expect(await input.getAttribute('placeholder')).toBe(
194+
'What needs to be done?'
195+
); <2>
196+
});
197+
```
198+
199+
1. Find the `input` element with class `new-todo`.
200+
2. Use the assertion library provided by the Synthetics agent to check that the value of the `placeholder` attribute matches a specific string.
201+
202+
203+
204+
## Make API requests [synthetics-request-param]
205+
206+
You can use the `request` parameter to make API requests independently of browser interactions. For example, you could retrieve a token from an HTTP endpoint and use it in a subsequent webpage request.
207+
208+
```js
209+
step('make an API request', async () => {
210+
const response = await request.get(params.url);
211+
// Do something with the response
212+
})
213+
```
214+
215+
The Elastic Synthetics `request` parameter is similar to [other request objects that are exposed by Playwright](https://playwright.dev/docs/api/class-apirequestcontext) with a few key differences:
216+
217+
* The Elastic Synthetics `request` parameter comes built into the library so it doesn’t have to be imported separately, which reduces the amount of code needed and allows you to make API requests in [inline journeys](../../../solutions/observability/apps/create-monitors-in-synthetics-app.md#synthetics-get-started-ui-add-a-browser-monitor).
218+
* The top level `request` object exposed by Elastic Synthetics has its own isolated cookie storage unlike Playwright’s `context.request` and `page.request`, which share cookie storage with the corresponding [`BrowserContext`](https://playwright.dev/docs/api/class-browsercontext).
219+
* If you want to control the creation of the `request` object, you can do so by passing options via [`--playwright-options`](../../../solutions/observability/apps/use-synthetics-cli.md#elastic-synthetics-command) or in the [`synthetics.config.ts` file](../../../solutions/observability/apps/configure-synthetics-projects.md).
220+
221+
For a full example that shows how to use the `request` object, refer to the [Elastic Synthetics demo repository](https://github.com/elastic/synthetics-demo/blob/main/advanced-examples/journeys/api-requests.journey.ts).
222+
223+
::::{note}
224+
The `request` parameter is not intended to be used for writing pure API tests. Instead, it is a way to support writing plain HTTP requests in service of a browser-based test.
225+
::::
226+
227+
228+
229+
## Set up and remove a global state [before-after]
230+
231+
If there are any actions that should be done before or after journeys, you can use `before`, `beforeAll`, `after`, or `afterAll`.
232+
233+
To set up global state or a server that will be used for a **single** `journey`, for example, use a `before` hook. To perform this setup once before **all** journeys, use a `beforeAll` hook.
234+
235+
```js
236+
before(({ params }) => {
237+
// Actions to take
238+
});
239+
240+
beforeAll(({ params }) => {
241+
// Actions to take
242+
});
243+
```
244+
245+
You can clean up global state or close a server used for a **single** `journey` using an `after` hook. To perform this cleanup once after all journeys, use an `afterAll` hook.
246+
247+
```js
248+
after(({ params }) => {
249+
// Actions to take
250+
});
251+
252+
afterAll(({ params }) => {
253+
// Actions to take
254+
});
255+
```
256+
257+
258+
## Import NPM packages [synthetics-import-packages]
259+
260+
You can import and use other NPM packages inside journey code. Refer to the example below using the external NPM package `is-positive`:
261+
262+
```js
263+
import { journey, step, monitor, expect } from '@elastic/synthetics';
264+
import isPositive from 'is-positive';
265+
266+
journey('bundle test', ({ page, params }) => {
267+
step('check if positive', () => {
268+
expect(isPositive(4)).toBe(true);
269+
});
270+
});
271+
```
272+
273+
When you [create a monitor](../../../solutions/observability/apps/create-monitors-with-project-monitors.md) from a journey that uses external NPM packages, those packages will be bundled along with the journey code when the `push` command is invoked.
274+
275+
However there are some limitations when using external packages:
276+
277+
* Bundled journeys after compression should not be more than 800 Kilobytes.
278+
* Native node modules will not work as expected due to platform inconsistency.
279+
280+
281+
## Sample synthetic test [synthetics-sample-test]
282+
283+
A complete example of a basic synthetic test might look like this:
284+
285+
```js
286+
import { journey, step, expect } from '@elastic/synthetics';
287+
288+
journey('Ensure placeholder is correct', ({ page }) => {
289+
step('Load the demo page', async () => {
290+
await page.goto('https://elastic.github.io/synthetics-demo/');
291+
});
292+
step('Assert placeholder text', async () => {
293+
const placeholderValue = await page.getAttribute(
294+
'input.new-todo',
295+
'placeholder'
296+
);
297+
expect(placeholderValue).toBe('What needs to be done?');
298+
});
299+
});
300+
```
301+
302+
You can find more complex examples in the [Elastic Synthetics demo repository](https://github.com/elastic/synthetics-demo/blob/main/advanced-examples/journeys/api-requests.journey.ts).
303+
304+
305+
## Test locally [synthetics-test-locally]
306+
307+
As you write journeys, you can run them locally to verify they work as expected. Then, you can create monitors to run your journeys at a regular interval.
308+
309+
To test all the journeys in a Synthetics project, navigate into the directory containing the Synthetics project and run the journeys in there. By default, the `@elastic/synthetics` runner will only run files matching the filename `*.journey.(ts|js)*`.
310+
311+
```sh
312+
# Run tests on the current directory. The dot `.` indicates
313+
# that it should run all tests in the current directory.
314+
npx @elastic/synthetics .
315+
```
316+
317+
318+
### Test an inline monitor [synthetics-test-inline]
319+
320+
To test an inline monitor’s journey locally, pipe the inline journey into the `npx @elastic/synthetics` command.
321+
322+
Assume, for example, that your inline monitor includes the following code:
323+
324+
```js
325+
step('load homepage', async () => {
326+
await page.goto('https://www.elastic.co');
327+
});
328+
step('hover over products menu', async () => {
329+
await page.hover('css=[data-nav-item=products]');
330+
});
331+
```
332+
333+
To run that journey locally, you can save that code to a file and pipe the file’s contents into `@elastic-synthetics`:
334+
335+
```sh
336+
cat path/to/sample.js | npx @elastic/synthetics --inline
337+
```
338+
339+
And you’ll get a response like the following:
340+
341+
```sh
342+
Journey: inline
343+
✓ Step: 'load homepage' succeeded (1831 ms)
344+
✓ Step: 'hover over products menu' succeeded (97 ms)
345+
346+
2 passed (2511 ms)

0 commit comments

Comments
 (0)