> OctoPerf load testing - Playwright Spec Action documentation page. Playwright Spec actions execute real web browser interactions with your web-application.

Source: https://api.octoperf.com/doc/design/edit-virtual-user/action-types/playwright-actions/playwright-spec-action/

# Playwright Spec action

This action contains one or several [Playwright tests](https://playwright.dev/docs/writing-tests), it can only be used from inside a [Playwright virtual user](https://api.octoperf.com/doc/design/create-virtual-user/import-playwright-node-project/).

## Script

When you create a new Playwright Spec action it comes with a default script on [our demo application](https://petstore.octoperf.com):

![playwright-action](https://api.octoperf.com/doc/design/edit-virtual-user/action-types/playwright-actions/img/playwright-action.png)

!!! Info
    More details about the creation process of this script [can be found on our blog](https://blog.octoperf.com/playwright-test-generator/).

For a more complete overview of all the scripting possibilities, check [Playwright official documentation](https://playwright.dev/docs/writing-tests).

OctoPerf provides code auto-completion when using CTRL+Space from inside the script:

![code-autocompletion](https://api.octoperf.com/doc/design/edit-virtual-user/action-types/playwright-actions/img/code-autocompletion.png)

## Variables

### Read

Variables can be used inside a PlayWright action using the following syntax: `process.env.variableName` where **variableName** is the name of your variable. Here is a real life example:

```typescript
  await page.locator('input[name="username"]').click();
  await page.locator('input[name="username"]').fill(process.env.login);
  await page.locator('input[name="username"]').press('Tab');
  await page.locator('input[name="password"]').fill(process.env.password);
```

In this case, both values will be replaced with the variables upon runtime:

![variable-debug](https://api.octoperf.com/doc/design/edit-virtual-user/action-types/playwright-actions/img/variable.png)

!!! Info
    In order to use variables inside text for instance in the title of your test, you can use the following:
    ```
    test(`test with ${process.env.login}`, async ({page}) => {
    ```
    Note that the simple quotes around the text have been replaced with backquotes, as [instructed by typescript documentation](https://www.typescriptlang.org/docs/handbook/basic-types.html#string).

### Write

While inside a PlayWright action, you can also write variables to be saved for later using `setVariableValue("name", "value")` simply replace **name** and **value** with the variable. The same is true for properties using: `setPropertyValue("name", "value")`

A typical use case would be to use the following code to extract the value of an input inside the page and then write it to a variable for later use:

```typescript
  const sourcepageLocator = page.locator('input[name="_sourcePage"]').nth(0);
  const sourcepage = await sourcepageLocator.getAttribute('value');
  setVariableValue('_sourcePage',sourcepage);
```

!!! Info
    You can change the **nth(0)** to another value to get a different occurrence of this input, in case there are several. For more details, check [PlayWright documentation](https://playwright.dev/docs/api/class-locator#locator-nth).

## Files

It is possible to use files that were placed in the [project files menu](https://api.octoperf.com/doc/design/edit-virtual-user/configuration/files/), using the path *../resources/*.

Here's an example of how you would use them:

```typescript
await page.locator('input[type="file"]').nth(0).setInputFiles('../resources/myfile.txt');
```

## Core Web Vitals

During a load test, OctoPerf measures the [Core Web Vitals](https://web.dev/articles/vitals) of every page your script loads. There is nothing to add to the script: the measurement is always active.

| Metric | Measures | Unit |
|--------|----------|------|
| `LCP` (Largest Contentful Paint) | when the main content of the page is displayed | milliseconds |
| `FCP` (First Contentful Paint) | when the first content of the page is displayed | milliseconds |
| `TTFB` (Time To First Byte) | when the first byte of the page is received | milliseconds |
| `INP` (Interaction to Next Paint) | how fast the page reacted to the clicks and key presses made on it | milliseconds |
| `CLS` (Cumulative Layout Shift) | how much the content moves while the page loads | score, no unit |

Each measure belongs to the action that loaded the page: a `page.goto()`, or a click on a link that opens another page. An assertion or a hook never carries Web Vitals.

The Web Vitals show in the report as [custom metrics](https://api.octoperf.com/doc/analysis/edit-bench-report/metrics-selection/#custom-metrics), filtered by action like the response times.

An [SLA profile](https://api.octoperf.com/doc/design/edit-virtual-user/configuration/sla-profiles/#custom-metrics) judges them on Google's bands: a Web Vital that needs improvement raises a warning, a poor one a critical alarm. The Playwright sample Virtual User comes with a **Core Web Vitals** SLA profile attached.

!!! info
    - `LCP` and `INP` are measured by Chromium-based browsers; other browsers may report only part of the metrics.
    - `INP` only exists when the page receives interactions: a page that is only displayed has no `INP`.
    - The pages of a browser context created by the script itself, with `browser.newContext()`, are not measured.
    - A validation of the Virtual User does not report Web Vitals: run a test to get them.

## Debug

In order to debug a Playwright virtual user we are first going to create two script actions as described in the tabs below:

!!! Example "Petstore login"

    === "Pass"
    
        ``` Typescript
        import {test, expect} from '@playwright/test';
        
        test('test', async ({page}) => {
            await page.goto('https://petstore.octoperf.com/');
            await page.getByRole('link', {name: 'Enter the Store'}).click();
            await page.getByRole('link', {name: 'Sign In'}).click();
            await page.locator('input[name="username"]').click();
            await page.locator('input[name="username"]').fill('user1');
            await page.locator('input[name="password"]').click();
            await page.locator('input[name="password"]').fill('pass');
            await page.getByRole('button', {name: 'Login'}).click();
            await page.locator('#SidebarContent').getByRole('link').first().click();
            await page.getByRole('link', {name: 'FI-SW-01'}).click();
            await page.getByRole('link', {name: 'EST-1'}).click();
            await page.getByRole('link', {name: 'Add to Cart'}).click();
        });
        ```
    
    === "Fail"
    
        ``` Typescript
        import {test, expect} from '@playwright/test';
        
        test('test fail', async ({page}) => {
            await page.goto('https://petstore.octoperf.com/');
            await page.getByRole('link', {name: 'Enter the Store'}).click();
            await page.getByRole('link', {name: 'Sign In'}).click();
            await page.locator('#stripes--1962445802').click();
            await page.locator('#stripes--1962445802').fill('user1');
            await page.locator('input[name="password"]').click();
            await page.locator('input[name="password"]').fill('pass');
        });
        ```


Now when you run a [validation](https://api.octoperf.com/doc/design/edit-virtual-user/validation/) you can check the [Debug panel](#debug) to see the details:

![debug](https://api.octoperf.com/doc/design/edit-virtual-user/action-types/playwright-actions/img/debug.png)

!!! warning
    If you are getting certificates errors like `ERR_CERT_AUTHORITY_INVALID` make sure to add the proper configuration in order to [ignore https errors in your script](https://playwright.dev/docs/api/class-testoptions#test-options-ignore-https-errors).

### Trace

A nice way to see the details of a run is to use the [Playwright Trace viewer](https://playwright.dev/docs/trace-viewer). In OctoPerf you use the **trace** button or when you click on the icon next to your playwright action name in the debug menu. This opens the trace viewer in a new tab:

![trace-viewer](https://api.octoperf.com/doc/design/edit-virtual-user/action-types/playwright-actions/img/trace-viewer.png)

!!! Note
    Each trace is also available along with the [logs and files](https://api.octoperf.com/doc/design/edit-virtual-user/validation/#logs) of the validation.


### Aggregators

With no aggregation, results like response times will be reported for every hostname of your playwright script. That means any URL with a dynamic parameter won't be reported individually.

Aggregators allow you to bypass that and group requests based on a regex expression. That way you can have the averaged values for a group of similar requests.

To experiment with aggregators, it is best to use the debug tab after a validation, the difference is visible using the **Raw/Aggregated** buttons in the bottom section:

=== "Raw resources"

    The Raw resources displays all network call to help you write aggregators.
    ![raw](https://api.octoperf.com/doc/design/edit-virtual-user/action-types/playwright-actions/img/resources-raw.png)


=== "Aggregated resources"

    The Aggregated resources displays aggregated network calls. By default simply using hostnames.
    ![aggregated host](https://api.octoperf.com/doc/design/edit-virtual-user/action-types/playwright-actions/img/resources-aggregated-host.png)


You can use aggregators to get precise results. For example, with the PetStore:

![aggregated](https://api.octoperf.com/doc/design/edit-virtual-user/action-types/playwright-actions/img/resources-aggregated.png)

| Regular Expression | Template    |
|--------------------|-------------|
|           `petstore\.octoperf\.com/$`        | `Home`        |
|           `petstore\.octoperf\.com/actions/Account\.action`        | `Account`     |
|           `petstore\.octoperf\.com/actions/Catalog\.action`        | `Catalog`     |
|           `petstore\.octoperf\.com/actions/Order\.action`        | `Order`       |
|           `petstore\.octoperf\.com/actions/Cart\.action`        | `Cart`        |
|           `petstore\.octoperf\.com/(.+)`        | `Asset $1$` |

Aggregators are executed in sequence. So make sure the first one does not match all resources (Here using the `$` at the end of the regexp). 

!!! warning
    There is a hard limit on the number of network resources that a Playwright action can generate. Use aggregators to get detailed metrics for your website or application only (not for external resources like CDN calls, analytics tags, etc.).
