# Solving E2E Testing Challenges with Checkly’s PWT Cleanup

> Learn how Checkly’s Playwright garbage collector helps manage test artifacts and prevent data clutter during automated E2E testing.

Source: https://www.checklyhq.com/blog/solving-e2e-testing-challenges-checkly-pwt-garbage-collector/

---

[Blog](https://www.checklyhq.com/blog/) / [Playwright](https://www.checklyhq.com/blog/tag/playwright/)

# The Advent of Monitoring, Day 4: Solving E2E Testing Challenges With Checkly's PWT Garbage Collector

[Javier Pérez Anaya](https://www.checklyhq.com/blog/author/javier-perez-anaya/)

December 11, 2023 · Updated May 26, 2025

_This is the fourth part of our 12-day Advent of Monitoring series. In this series, Checkly's engineers will share practical monitoring tips from their own experience._

One of the biggest challenges in conducting end-to-end (E2E) testing is managing the artifacts created during the process. These artifacts are necessary for asserting specific functionalities.

For instance, we might want to verify a resource creation ability, but we do not want to:

- Assert the deletion of the created entities afterwards, since this could compromise the isolation and single responsibility of the checks. For example, a check that creates and then deletes a resource might fail during the cleanup process. This could result in a false positive.
- Keep the entities around. Imagine having monitors that execute this check every 10 minutes. That would mean six entities are created per hour, or 144 leftovers per day. These would keep piling up and require a specific external cron job to keep the room tidy.

In Checkly, it was our decision, as frontenders, that we wanted to keep not only the testing flow but also the maintenance flow as contained within Checkly as possible.

The conception of the method I will show you is an elaboration on top of the low-level API calls approach that we implemented a while back and Nico splendidly showcased [in a past blog](https://www.checklyhq.com/blog/how-low-level-api-calls-stabilize-your-end-to-end-tests/).

From this point onwards in the article, there will be some heavy TS nerdiness involved, so behold!

** cracks knuckles * —* let’s go!

## 1. Let’s declare an API module factory

This is a factory method that we will use for returning a wrapped API module that will include two extra methods: `addToCleanupQueue` and `cleanup`.

The first one is a method that we will invoke during checks to add IDs to an internal pile of pending-to-remove artifact IDs. The second method will be responsible for invoking the `cleanupMethod` declared module call sequentially with the IDs, present in the cleanup pile.

```
export type ChecklyApiModule any>> = Methods & {
addToCleanupQueue: (id: string | number) => void
cleanup: () => Promise
}

function defineChecklyApiModule any>> ({
methods,
cleanupMethod,
}: {
methods: Methods
cleanupMethod: keyof Methods
}): ChecklyApiModule {
const cleanupIds: Set = new Set()

const module = {
...methods,
addToCleanupQueue: (id: string | number) => {
cleanupIds.add(id)
},
cleanup: async () => {
const ids = [...cleanupIds]

if (!ids.length) return

console.log('Cleaning up IDs:', ids.join(', '))

for (const id of [...cleanupIds]) {
await methods[cleanupMethod](id)
}
},
}

return module
}
```

## 2. Let’s declare the API fixture

For showcasing purposes, I will only list a single module here. Note that the fixture would be configurable to pass any http client you might prefer (axios, fetch, or what have you):

```
export type HttpClient = {
[key in 'get' | 'delete' | 'patch' | 'post' | 'put']: (
url: string,
config?: {
// PWT request doesn’t tell apart methods with or without data.
data?: any,
params?: Record
}
) => Promise
}

function createChecklyApiService ({ httpClient }: { httpClient: HttpClient }) {
const moduleMap = {
checks: defineChecklyApiModule({
methods: {
fetchAll: () => {
return httpClient.get('/v1/checks', { params: { limit: 100 } })
},
create: (data) => {
return httpClient.post('/v1/checks', { data })
},
remove: (checkId: string) => {
return httpClient.delete(`/v1/checks/${checkId}`)
},
},
cleanupMethod: 'remove',
}),
}

const modules = Object.values(moduleMap)

async function cleanup () {
for (const module of modules) {
try {
await module.cleanup()
} catch (e) {
console.error(e)
}
}
}

return {
...moduleMap,
cleanup,
}
}
```

As you might have noticed, there are two interesting parts here:

1. The declaration of API modules gets full TS-type inference, so you cannot go wrong there

2. There is an API fixture global method for iterating the declared modules and invoking `cleanup` on every one of them.

## 3. Let’s use the API fixture

Reading through the PWT docs, you would notice that creating a fixture involves invoking `use` to tell Playwright to inject it into our tests and then having the ability to do some additional calls for whatever housekeeping would be needed. This is the cornerstone of our implementation since we leverage that to invoke our API fixture cleanup:

```
export const test = base.extend ({
// other fixturs
api: async ({ request, auth }, use) => {
/**
* here you would build your http client by using axios,
* wrapping request, fetch, or even mocking it if you needed so.
*/
const api = createChecklyApiService({ httpClient })

await use(api)
await api.cleanup()
},
```

## 4. Let’s use it in our checks

```
import { test } from './wrappedChecklyTest'

test('should create a check', async ({ page, api }) => {
// perform our check logic by navigating through the UI

const createdId = /** retrieve the created ID via page.waitForResponse */

// this is where the magic happens
api.checks.addToCleanupQueue(createdId)
})
```

As you can see, all the bloat that would be required to perform garbage collection at the end of each check is reduced to a clean and tidy oneliner to just add the created entity ID to the API module cleanup pile.

The fixture will take responsibility for invoking the clean-up after the check execution is finished.

[Javier Pérez Anaya](https://www.checklyhq.com/blog/author/javier-perez-anaya/)

Frontend Engineer

In This Article

- 1. Let’s declare an API module factory
- 2. Let’s declare the API fixture
- 3. Let’s use the API fixture
- 4. Let’s use it in our checks

Monitor with AI

Let your agents create & deploy monitors with deep understanding of your application

[Start for free](https://app.checklyhq.com/signup)

Share on social

Try Checkly

## Catch it before your users do

Run Playwright and API checks against production every minute, from locations around the world. Your checks live in your repo and deploy with your code.

[Start for free](https://app.checklyhq.com/signup) [Book a demo](https://www.checklyhq.com/demo-request/)

Free plan. No credit card.

# scaffold a project with example checks

$ npm create checkly@latest

# run them from our global locations

$ npx checkly test

# turn them into production monitors

$ npx checkly deploy

## Related Articles

[The Advent of Monitoring, Day 6: How We Use Checkly to Monitor Checkly: A Backender’s Perspective December 18, 2023](https://www.checklyhq.com/blog/how-we-use-checkly-to-monitor-checkly/)[The Advent of Monitoring, Day 5: Dealing With Third-Party Dependencies Causing False Positives for Synthetics December 12, 2023](https://www.checklyhq.com/blog/dealing-with-third-party-dependencies-causing-false/)[The Advent of Monitoring, Day 3: Easy Monitoring for Self-Hosted Projects with Checkly December 10, 2023](https://www.checklyhq.com/blog/setting-up-multi-step-api-checks/)[The Advent of Monitoring, Day 2: Debugging Dashboard Outages with Checkly's API Checks December 9, 2023](https://www.checklyhq.com/blog/debugging-dashboard-outages-with-checklys-api-checks/)[The Advent of Monitoring, Day 1: What Are Synthetics and Why They Are Needed December 8, 2023](https://www.checklyhq.com/blog/the-advent-of-monitoring-day-1-what-are-synthetics/)
