Repository: pnpm/pnpm
Pull request #9253: test: fix silently failing tests in `default-reporter` package
Author: login: gluxon
Created: 2025-03-09T19:36:36Z | Merged: 2025-03-09T23:47:58Z

This PR should be easier to review by looking at each commit individually.

I've isolated each type of refactor into its own commit, making it easier to scroll through the changes to make sure refactors of each type were done correctly.

---

## Problem

The tests in the `cli/default-reporter` package are silently failing. **They always pass, even if the `expect(...).toBe(...)` statements fail.**

One easy way to reproduce this is by setting `jest.retryTimes(0)` here:

https://github.com/pnpm/pnpm/blob/58d859793f8238a6e78aaae57e0727508ad7a29a/__utils__/jest-config/setupFilesAfterEnv.js#L3

And adding `test.only` here:

https://github.com/pnpm/pnpm/blob/58d859793f8238a6e78aaae57e0727508ad7a29a/cli/default-reporter/test/reportingLifecycleScripts.ts#L29

If you run:

```console
❯ cd cli/default-reporter
❯ pnpm run test -- test/reportingLifecycleScripts
```

The Jest test will pass, despite the `JestAssertionError` error:

<img width="842" alt="Screenshot 2025-03-09 at 3 12 04 PM" src="https://github.com/user-attachments/assets/6e0f72be-ea4f-4042-ab0a-59d1a0531612" />

## Explanation

**Why is Jest passing the test when the `expect` statement clearly fails?**

The `JestAssertionError` errors are thrown after each test completes.

Since the `expect` statements run inside of a RxJS `subscribe` hook. RxJS is capturing the error and rethrowing it later. You can see the error above is being thrown from `node_modules/rxjs/dist/cjs/internal/util/reportUnhandledError.js`

```
/Users/gluxon/Developer/pnpm2/node_modules/.pnpm/rxjs@7.8.1/node_modules/rxjs/dist/cjs/internal/util/reportUnhandledError.js:13
            throw err;
            ^
```

**Why isn't `expect.assertions(1)` working?**

Many of the tests in `cli/default-reporter` have an `expect.assertions(1)` line to protect against `expect(...)` assertions running after the test completes.

https://github.com/pnpm/pnpm/blob/58d859793f8238a6e78aaae57e0727508ad7a29a/cli/default-reporter/test/reportingLifecycleScripts.ts#L114

I actually don't know the full story here. My educated guess for what's happening is that:

1. The `expect(...)` statement does run before the test completes. I verified this in a debugger.
2. The `expect(...)` call is recorded by Jest, and the counter increments to `1`.
3. The test completes.
4. Later the `JestAssertionError` gets thrown.

This might be a Jest bug, or a situation `expect.assertions()` isn't designed to handle. Jest most likely assumes the `JestAssertionError` error will be thrown from the context of the test and whatever code path handles `expect.assertions()` doesn't need to make sure the `expect` assertion actually passes as a result.

## Problem Extended

There's an even more confusing scenario that the problem above causes.

If you add `test.only` to the first 2 tests in `cli/default-reporter/test/reportingLifecycleScripts.ts`, you'll notice that the 2nd test fails. However, **the failing assertion is actually from the 1st test.** 👻

<img width="842" alt="Screenshot 2025-03-09 at 3 24 58 PM" src="https://github.com/user-attachments/assets/4adfa9ed-090f-4740-aa38-ac04f79536c9" />

This is because the `JestAssertionError` from test 1 is rethrown by RxJS during the execution of test 2.

## Retries

Everything works on CI because test retries have been hiding this problem. Failures from previous tests are causing the next test to fail, but retries just propagate the problem further down until everything passes.

https://github.com/pnpm/pnpm/blob/58d859793f8238a6e78aaae57e0727508ad7a29a/__utils__/jest-config/setupFilesAfterEnv.js#L3

## Changes

I believe the right fix here is to avoid calling `expect(...)` inside an RxJS subscriber. Let's instead consume the stream's output, and then call `expect(...)` on the result.

This PR removes the `output$.pipe(...).subscribe(...)` test setup and favor of consuming the stream through `firstValueFrom`. This results in easier to read and less buggy code.


Diff:
diff --git a/cli/default-reporter/test/filterLogHook.ts b/cli/default-reporter/test/filterLogHook.ts
index 9e33ddf957e..97b7ebf8974 100644
--- a/cli/default-reporter/test/filterLogHook.ts
+++ b/cli/default-reporter/test/filterLogHook.ts
@@ -1,8 +1,9 @@
 import { type Log } from '@pnpm/core-loggers'
 import { toOutput$ } from '@pnpm/default-reporter'
 import { logger, createStreamParser } from '@pnpm/logger'
+import { firstValueFrom } from 'rxjs'
 
-test('logger with filterLog hook', (done) => {
+test('logger with filterLog hook', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -49,16 +50,6 @@ test('logger with filterLog hook', (done) => {
 
   expect.assertions(1)
 
-  const subscription = output$.subscribe({
-    complete: () => done(),
-    error: done,
-    next: (msg) => {
-      expect(msg).toEqual(expect.stringContaining('bbb'))
-    },
-  })
-
-  setTimeout(() => {
-    done()
-    subscription.unsubscribe()
-  }, 10)
+  const msg = await firstValueFrom(output$)
+  expect(msg).toEqual(expect.stringContaining('bbb'))
 })
diff --git a/cli/default-reporter/test/index.ts b/cli/default-reporter/test/index.ts
index b72b3f7e6aa..bbd665a1df5 100644
--- a/cli/default-reporter/test/index.ts
+++ b/cli/default-reporter/test/index.ts
@@ -20,6 +20,7 @@ import {
 import { map, skip, take } from 'rxjs/operators'
 import chalk from 'chalk'
 import normalizeNewline from 'normalize-newline'
+import { firstValueFrom } from 'rxjs'
 import repeat from 'ramda/src/repeat'
 import { formatWarn } from '../src/reporterForClient/utils/formatWarn'
 
@@ -35,7 +36,7 @@ const h1 = chalk.cyanBright
 
 const EOL = '\n'
 
-test('prints summary (of current package only)', (done) => {
+test('prints summary (of current package only)', async () => {
   const prefix = '/home/jane/project'
   const output$ = toOutput$({
     context: {
@@ -198,11 +199,8 @@ test('prints summary (of current package only)', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(skip(2), take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`packages/foo                             |   ${chalk.green('+5')}   ${chalk.red('-1')} ${ADD + SUB}${EOL}` +
+  const output = await firstValueFrom(output$.pipe(skip(2), take(1), map(normalizeNewline)))
+  expect(output).toBe(`packages/foo                             |   ${chalk.green('+5')}   ${chalk.red('-1')} ${ADD + SUB}${EOL}` +
         `${formatWarn(`${DEPRECATED} bar@2.0.0: This package was deprecated because bla bla bla`)}${EOL}${EOL}` +
         `\
 ${h1('dependencies:')}
@@ -226,11 +224,9 @@ ${ADD} qar ${versionColor('2.0.0')}
 ${h1('node_modules:')}
 ${ADD} is-linked2 ${chalk.grey(`<- ${path.relative(prefix, '/src/is-linked2')}`)}
 `)
-    },
-  })
 })
 
-test('prints summary without the filtered out entries', (done) => {
+test('prints summary without the filtered out entries', async () => {
   const prefix = '/home/jane/project'
   const output$ = toOutput$({
     context: {
@@ -291,19 +287,17 @@ test('prints summary without the filtered out entries', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(EOL + `\
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(EOL + `\
 ${h1('dependencies:')}
 ${ADD} foo ${versionColor('1.0.0')} ${versionColor('(2.0.0 is available)')}
+
+${h1('devDependencies:')}
+${ADD} qar ${versionColor('2.0.0')}
 `)
-    },
-  })
 })
 
-test('does not print deprecation message when log level is set to error', (done) => {
+test('does not print deprecation message when log level is set to error', async () => {
   const prefix = '/home/jane/project'
   const output$ = toOutput$({
     context: {
@@ -354,16 +348,11 @@ test('does not print deprecation message when log level is set to error', (done)
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(formatError('ERR_PNPM_SOME_CODE', 'some error'))
-    },
-  })
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(formatError('ERR_PNPM_SOME_CODE', 'some error'))
 })
 
-test('prints summary for global installation', (done) => {
+test('prints summary for global installation', async () => {
   const prefix = '/home/jane/.nvs/node/10.0.0/x64/pnpm-global/1'
   const output$ = toOutput$({
     context: {
@@ -413,20 +402,15 @@ test('prints summary for global installation', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(EOL + `\
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(EOL + `\
 ${h1(`${prefix}:`)}
 ${ADD} bar ${versionColor('2.0.0')}
 ${ADD} foo ${versionColor('1.0.0')} ${versionColor('(2.0.0 is available)')}
 `)
-    },
-  })
 })
 
-test('prints added peer dependency', (done) => {
+test('prints added peer dependency', async () => {
   const prefix = '/home/jane/.nvs/node/10.0.0/x64/pnpm-global/1'
   const output$ = toOutput$({
     context: {
@@ -457,22 +441,17 @@ test('prints added peer dependency', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(EOL + `\
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(EOL + `\
 ${h1('peerDependencies:')}
 ${ADD} is-negative ${versionColor('^1.0.0')}
 
 ${h1('devDependencies:')}
 ${ADD} is-negative ${versionColor('^1.0.0')}
 `)
-    },
-  })
 })
 
-test('prints summary correctly when the same package is specified both in optional and prod dependencies', (done) => {
+test('prints summary correctly when the same package is specified both in optional and prod dependencies', async () => {
   const prefix = '/home/jane/.nvs/node/10.0.0/x64/pnpm-global/1'
   const output$ = toOutput$({
     context: {
@@ -524,19 +503,14 @@ test('prints summary correctly when the same package is specified both in option
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(EOL + `\
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(EOL + `\
 ${h1('dependencies:')}
 ${ADD} bar ${versionColor('2.0.0')}
 `)
-    },
-  })
 })
 
-test('in the installation summary report which dependency types are skipped', (done) => {
+test('in the installation summary report which dependency types are skipped', async () => {
   const prefix = '/home/jane/.nvs/node/10.0.0/x64/pnpm-global/1'
   const output$ = toOutput$({
     context: {
@@ -594,11 +568,8 @@ test('in the installation summary report which dependency types are skipped', (d
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(EOL + `\
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(EOL + `\
 ${h1('dependencies:')}
 ${ADD} bar ${versionColor('2.0.0')}
 
@@ -606,11 +577,9 @@ ${h1('optionalDependencies:')} skipped
 
 ${h1('devDependencies:')} skipped
 `)
-    },
-  })
 })
 
-test('prints summary when some packages fail', (done) => {
+test('prints summary when some packages fail', async () => {
   const output$ = toOutput$({
     context: { argv: ['run'], config: { recursive: true } as Config },
     streamParser: createStreamParser(),
@@ -618,32 +587,6 @@ test('prints summary when some packages fail', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(EOL + `Summary: ${chalk.red('6 fails')}, 7 passes
-
-/a:
-${formatError('ERROR', 'a failed')}
-
-/b:
-${formatError('ERROR', 'b failed')}
-
-/c:
-${formatError('ERROR', 'c failed')}
-
-/d:
-${formatError('ERROR', 'd failed')}
-
-/e:
-${formatError('ERROR', 'e failed')}
-
-/f:
-${formatError('ERROR', 'f failed')}`)
-    },
-  })
-
   const err = Object.assign(new PnpmError('RECURSIVE_FAIL', '...'), {
     failures: [
       {
@@ -675,9 +618,34 @@ ${formatError('ERROR', 'f failed')}`)
   })
 
   logger.error(err, err)
+
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`\
+${formatError('ERR_PNPM_RECURSIVE_FAIL', '')}
+
+
+Summary: ${chalk.red('6 fails')}, 7 passes
+
+/a:
+${formatError('ERROR', 'a failed')}
+
+/b:
+${formatError('ERROR', 'b failed')}
+
+/c:
+${formatError('ERROR', 'c failed')}
+
+/d:
+${formatError('ERROR', 'd failed')}
+
+/e:
+${formatError('ERROR', 'e failed')}
+
+/f:
+${formatError('ERROR', 'f failed')}`)
 })
 
-test('prints info', (done) => {
+test('prints info', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     streamParser: createStreamParser(),
@@ -687,16 +655,11 @@ test('prints info', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe('info message')
-    },
-  })
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe('info message')
 })
 
-test('prints added/removed stats during installation', (done) => {
+test('prints added/removed stats during installation', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     streamParser: createStreamParser(),
@@ -708,18 +671,12 @@ test('prints added/removed stats during installation', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`Packages: ${chalk.green('+5')} ${chalk.red('-1')}
-${ADD + ADD + ADD + ADD + ADD + SUB}`
-      )
-    },
-  })
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`Packages: ${chalk.green('+5')} ${chalk.red('-1')}
+${ADD + ADD + ADD + ADD + ADD + SUB}`)
 })
 
-test('prints added/removed stats during installation when 0 removed', (done) => {
+test('prints added/removed stats during installation when 0 removed', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     streamParser: createStreamParser(),
@@ -731,18 +688,12 @@ test('prints added/removed stats during installation when 0 removed', (done) =>
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`Packages: ${chalk.green('+2')}
-${ADD + ADD}`
-      )
-    },
-  })
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`Packages: ${chalk.green('+2')}
+${ADD + ADD}`)
 })
 
-test('prints only the added stats if nothing was removed', (done) => {
+test('prints only the added stats if nothing was removed', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     streamParser: createStreamParser(),
@@ -754,17 +705,12 @@ test('prints only the added stats if nothing was removed', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`Packages: ${chalk.green('+1')}
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`Packages: ${chalk.green('+1')}
 ${ADD}`)
-    },
-  })
 })
 
-test('prints only the removed stats if nothing was added', (done) => {
+test('prints only the removed stats if nothing was added', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     streamParser: createStreamParser(),
@@ -776,17 +722,12 @@ test('prints only the removed stats if nothing was added', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`Packages: ${chalk.red('-1')}
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`Packages: ${chalk.red('-1')}
 ${SUB}`)
-    },
-  })
 })
 
-test('prints only the added stats if nothing was removed and a lot added', (done) => {
+test('prints only the added stats if nothing was removed and a lot added', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     reportingOptions: { outputMaxWidth: 20 },
@@ -799,17 +740,12 @@ test('prints only the added stats if nothing was removed and a lot added', (done
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`Packages: ${chalk.green('+100')}
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`Packages: ${chalk.green('+100')}
 ${repeat(ADD, 20).join('')}`)
-    },
-  })
 })
 
-test('prints only the removed stats if nothing was added and a lot removed', (done) => {
+test('prints only the removed stats if nothing was added and a lot removed', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     reportingOptions: { outputMaxWidth: 20 },
@@ -822,17 +758,12 @@ test('prints only the removed stats if nothing was added and a lot removed', (do
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`Packages: ${chalk.red('-100')}
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`Packages: ${chalk.red('-100')}
 ${repeat(SUB, 20).join('')}`)
-    },
-  })
 })
 
-test('prints at least one remove sign when removed !== 0', (done) => {
+test('prints at least one remove sign when removed !== 0', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     reportingOptions: { outputMaxWidth: 20 },
@@ -845,18 +776,12 @@ test('prints at least one remove sign when removed !== 0', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`Packages: ${chalk.green('+100')} ${chalk.red('-1')}
-${repeat(ADD, 19).join('') + SUB}`
-      )
-    },
-  })
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`Packages: ${chalk.green('+100')} ${chalk.red('-1')}
+${repeat(ADD, 19).join('') + SUB}`)
 })
 
-test('prints at least one add sign when added !== 0', (done) => {
+test('prints at least one add sign when added !== 0', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     reportingOptions: { outputMaxWidth: 20 },
@@ -869,17 +794,12 @@ test('prints at least one add sign when added !== 0', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`Packages: ${chalk.green('+1')} ${chalk.red('-100')}
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`Packages: ${chalk.green('+1')} ${chalk.red('-100')}
 ${ADD + repeat(SUB, 19).join('')}`)
-    },
-  })
 })
 
-test('prints just removed during uninstallation', (done) => {
+test('prints just removed during uninstallation', async () => {
   const output$ = toOutput$({
     context: { argv: ['remove'] },
     streamParser: createStreamParser(),
@@ -890,17 +810,12 @@ test('prints just removed during uninstallation', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`Packages: ${chalk.red('-4')}
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`Packages: ${chalk.red('-4')}
 ${SUB + SUB + SUB + SUB}`)
-    },
-  })
 })
 
-test('prints added/removed stats and warnings during recursive installation', (done) => {
+test('prints added/removed stats and warnings during recursive installation', async () => {
   const rootPrefix = '/home/jane/repo'
   const output$ = toOutput$({
     context: {
@@ -945,12 +860,9 @@ test('prints added/removed stats and warnings during recursive installation', (d
 
   expect.assertions(1)
 
-  output$.pipe(skip(8), take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      // cspell:disable
-      expect(output).toBe(`\
+  const output = await firstValueFrom(output$.pipe(skip(8), take(1), map(normalizeNewline)))
+  // cspell:disable
+  expect(output).toBe(`\
 pkg-5                                    | ${formatWarn('Some issue')}
 .                                        | ${formatWarn('Some other issue')}
 .                                        |   ${chalk.red('-1')} ${SUB}
@@ -960,12 +872,10 @@ dir/pkg-2                                |   ${chalk.green('+2')} ${ADD}
 .../pkg-3                                |   ${chalk.green('+1')} ${ADD}
 ...ooooooooooooooooooooooooooooong-pkg-4 |   ${chalk.red('-1')} ${SUB}
 .                                        | ${formatWarn(`${DEPRECATED} foo@1.0.0`)}`)
-      // cspell:enable
-    },
-  })
+  // cspell:enable
 })
 
-test('recursive installation: prints only the added stats if nothing was removed and a lot added', (done) => {
+test('recursive installation: prints only the added stats if nothing was removed and a lot added', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['recursive'],
@@ -980,16 +890,11 @@ test('recursive installation: prints only the added stats if nothing was removed
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`pkg-1                                    | ${chalk.green('+190')} ${repeat(ADD, 12).join('')}`)
-    },
-  })
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`pkg-1                                    | ${chalk.green('+190')} ${repeat(ADD, 12).join('')}`)
 })
 
-test('recursive installation: prints only the removed stats if nothing was added and a lot removed', (done) => {
+test('recursive installation: prints only the removed stats if nothing was added and a lot removed', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['recursive'],
@@ -1004,16 +909,11 @@ test('recursive installation: prints only the removed stats if nothing was added
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`pkg-1                                    | ${chalk.red('-190')} ${repeat(SUB, 12).join('')}`)
-    },
-  })
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`pkg-1                                    | ${chalk.red('-190')} ${repeat(SUB, 12).join('')}`)
 })
 
-test('recursive installation: prints at least one remove sign when removed !== 0', (done) => {
+test('recursive installation: prints at least one remove sign when removed !== 0', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['recursive'],
@@ -1028,16 +928,11 @@ test('recursive installation: prints at least one remove sign when removed !== 0
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`pkg-1                                    | ${chalk.green('+100')}   ${chalk.red('-1')} ${repeat(ADD, 8).join('') + SUB}`)
-    },
-  })
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`pkg-1                                    | ${chalk.green('+100')}   ${chalk.red('-1')} ${repeat(ADD, 8).join('') + SUB}`)
 })
 
-test('recursive installation: prints at least one add sign when added !== 0', (done) => {
+test('recursive installation: prints at least one add sign when added !== 0', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['recursive'],
@@ -1052,16 +947,11 @@ test('recursive installation: prints at least one add sign when added !== 0', (d
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`pkg-1                                    |   ${chalk.green('+1')} ${chalk.red('-100')} ${ADD + repeat(SUB, 8).join('')}`)
-    },
-  })
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`pkg-1                                    |   ${chalk.green('+1')} ${chalk.red('-100')} ${ADD + repeat(SUB, 8).join('')}`)
 })
 
-test('recursive uninstall: prints removed packages number', (done) => {
+test('recursive uninstall: prints removed packages number', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['remove'],
@@ -1075,16 +965,11 @@ test('recursive uninstall: prints removed packages number', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`pkg-1                                    |   ${chalk.red('-1')} ${SUB}`)
-    },
-  })
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`pkg-1                                    |   ${chalk.red('-1')} ${SUB}`)
 })
 
-test('install: print hook message', (done) => {
+test('install: print hook message', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -1102,16 +987,11 @@ test('install: print hook message', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`${chalk.magentaBright('readPackage')}: foo`)
-    },
-  })
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`${chalk.magentaBright('readPackage')}: foo`)
 })
 
-test('recursive: print hook message', (done) => {
+test('recursive: print hook message', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['recursive'],
@@ -1129,16 +1009,11 @@ test('recursive: print hook message', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`pkg-1                                    | ${chalk.magentaBright('readPackage')}: foo`)
-    },
-  })
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`pkg-1                                    | ${chalk.magentaBright('readPackage')}: foo`)
 })
 
-test('prints skipped optional dependency info message', (done) => {
+test('prints skipped optional dependency info message', async () => {
   const prefix = process.cwd()
   const output$ = toOutput$({
     context: {
@@ -1163,16 +1038,11 @@ test('prints skipped optional dependency info message', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`info: ${pkgId} is an optional dependency and failed compatibility check. Excluding it from installation.`)
-    },
-  })
+  const output = await firstValueFrom(output$)
+  expect(output).toBe(`info: ${pkgId} is an optional dependency and failed compatibility check. Excluding it from installation.`)
 })
 
-test('logLevel=default', (done) => {
+test('logLevel=default', async () => {
   const prefix = process.cwd()
   const output$ = toOutput$({
     context: {
@@ -1189,18 +1059,13 @@ test('logLevel=default', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(skip(2), take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`Info message
+  const output = await firstValueFrom(output$.pipe(skip(2), take(1)))
+  expect(output).toBe(`Info message
 ${formatWarn('Some issue')}
-${formatError('ERROR', 'some error')}`)
-    },
-  })
+${formatError('ERR_PNPM_SOME_CODE', 'some error')}`)
 })
 
-test('logLevel=warn', (done) => {
+test('logLevel=warn', async () => {
   const prefix = process.cwd()
   const output$ = toOutput$({
     context: {
@@ -1220,17 +1085,12 @@ test('logLevel=warn', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(skip(1), take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`${formatWarn('Some issue')}
+  const output = await firstValueFrom(output$.pipe(skip(1), take(1)))
+  expect(output).toBe(`${formatWarn('Some issue')}
 ${formatError('ERR_PNPM_SOME_CODE', 'some error')}`)
-    },
-  })
 })
 
-test('logLevel=error', (done) => {
+test('logLevel=error', async () => {
   const prefix = process.cwd()
   const output$ = toOutput$({
     context: {
@@ -1250,16 +1110,11 @@ test('logLevel=error', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(formatError('ERR_PNPM_SOME_CODE', 'some error'))
-    },
-  })
+  const output = await firstValueFrom(output$)
+  expect(output).toBe(formatError('ERR_PNPM_SOME_CODE', 'some error'))
 })
 
-test('warnings are collapsed', (done) => {
+test('warnings are collapsed', async () => {
   const prefix = process.cwd()
   const output$ = toOutput$({
     context: {
@@ -1282,21 +1137,16 @@ test('warnings are collapsed', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(skip(6), take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`${formatWarn('Some issue 1')}
+  const output = await firstValueFrom(output$.pipe(skip(6), take(1)))
+  expect(output).toBe(`${formatWarn('Some issue 1')}
 ${formatWarn('Some issue 2')}
 ${formatWarn('Some issue 3')}
 ${formatWarn('Some issue 4')}
 ${formatWarn('Some issue 5')}
 ${formatWarn('2 other warnings')}`)
-    },
-  })
 })
 
-test('warnings are not collapsed when append-only is true', (done) => {
+test('warnings are not collapsed when append-only is true', async () => {
   const prefix = process.cwd()
   const output$ = toOutput$({
     context: {
@@ -1320,11 +1170,6 @@ test('warnings are not collapsed when append-only is true', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(skip(6), take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(formatWarn('Some issue 7'))
-    },
-  })
+  const output = await firstValueFrom(output$.pipe(skip(6), take(1)))
+  expect(output).toBe(formatWarn('Some issue 7'))
 })
diff --git a/cli/default-reporter/test/reportingContext.ts b/cli/default-reporter/test/reportingContext.ts
index 157dc5868a0..7597427fc87 100644
--- a/cli/default-reporter/test/reportingContext.ts
+++ b/cli/default-reporter/test/reportingContext.ts
@@ -1,11 +1,14 @@
+import { setTimeout } from 'node:timers/promises'
 import { contextLogger, packageImportMethodLogger } from '@pnpm/core-loggers'
 import { toOutput$ } from '@pnpm/default-reporter'
 import {
   createStreamParser,
 } from '@pnpm/logger'
-import { take } from 'rxjs/operators'
+import { firstValueFrom } from 'rxjs'
 
-test('print context and import method info', (done) => {
+const NO_OUTPUT = Symbol('test should not log anything')
+
+test('print context and import method info', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -24,19 +27,14 @@ test('print context and import method info', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`\
+  const output = await firstValueFrom(output$)
+  expect(output).toBe(`\
 Packages are hard linked from the content-addressable store to the virtual store.
   Content-addressable store is at: ~/.pnpm-store/v3
   Virtual store is at:             node_modules/.pnpm`)
-    },
-  })
 })
 
-test('do not print info if not fresh install', (done) => {
+test('do not print info if not fresh install', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -53,21 +51,15 @@ test('do not print info if not fresh install', (done) => {
     method: 'hardlink',
   })
 
-  const subscription = output$.subscribe({
-    complete: () => done(),
-    error: done,
-    next: (msg) => {
-      expect(msg).toBeFalsy()
-    },
-  })
+  const output = await Promise.race([
+    firstValueFrom(output$),
+    setTimeout(10).then(() => NO_OUTPUT),
+  ])
 
-  setTimeout(() => {
-    done()
-    subscription.unsubscribe()
-  }, 10)
+  expect(output).toEqual(NO_OUTPUT)
 })
 
-test('do not print info if dlx is the executed command', (done) => {
+test('do not print info if dlx is the executed command', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['dlx'],
@@ -84,16 +76,10 @@ test('do not print info if dlx is the executed command', (done) => {
     method: 'hardlink',
   })
 
-  const subscription = output$.subscribe({
-    complete: () => done(),
-    error: done,
-    next: (msg) => {
-      expect(msg).toBeFalsy()
-    },
-  })
+  const output = await Promise.race([
+    firstValueFrom(output$),
+    setTimeout(10).then(() => NO_OUTPUT),
+  ])
 
-  setTimeout(() => {
-    done()
-    subscription.unsubscribe()
-  }, 10)
+  expect(output).toEqual(NO_OUTPUT)
 })
diff --git a/cli/default-reporter/test/reportingDeprecations.ts b/cli/default-reporter/test/reportingDeprecations.ts
index 2dc96d9cf0a..ba0ea1104cb 100644
--- a/cli/default-reporter/test/reportingDeprecations.ts
+++ b/cli/default-reporter/test/reportingDeprecations.ts
@@ -5,12 +5,13 @@ import {
 } from '@pnpm/core-loggers'
 import { toOutput$ } from '@pnpm/default-reporter'
 import { createStreamParser } from '@pnpm/logger'
+import { firstValueFrom } from 'rxjs'
 import { map, take } from 'rxjs/operators'
 import chalk from 'chalk'
 import normalizeNewline from 'normalize-newline'
 import { formatWarn } from '../src/reporterForClient/utils/formatWarn'
 
-test('prints summary of deprecated subdependencies', (done) => {
+test('prints summary of deprecated subdependencies', async () => {
   const prefix = '/home/jane/project'
   const output$ = toOutput$({
     context: {
@@ -43,11 +44,6 @@ test('prints summary of deprecated subdependencies', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`${formatWarn(`${chalk.red('2 deprecated subdependencies found:')} bar@2.0.0, qar@3.0.0`)}`)
-    },
-  })
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`${formatWarn(`${chalk.red('2 deprecated subdependencies found:')} bar@2.0.0, qar@3.0.0`)}`)
 })
diff --git a/cli/default-reporter/test/reportingErrors.ts b/cli/default-reporter/test/reportingErrors.ts
index 1bd83f37070..b4b7549d10a 100644
--- a/cli/default-reporter/test/reportingErrors.ts
+++ b/cli/default-reporter/test/reportingErrors.ts
@@ -5,6 +5,7 @@ import {
   createStreamParser,
   logger,
 } from '@pnpm/logger'
+import { firstValueFrom } from 'rxjs'
 import { map, take } from 'rxjs/operators'
 import chalk from 'chalk'
 import loadJsonFile from 'load-json-file'
@@ -22,7 +23,7 @@ const formatError = (code: string, message: string) => {
 }
 const ERROR_PAD = ''
 
-test('prints generic error', (done) => {
+test('prints generic error', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     streamParser: createStreamParser(),
@@ -33,17 +34,13 @@ test('prints generic error', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`${formatError('ERROR', 'some error')}
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`${formatError('ERROR', 'some error')}
+${ERROR_PAD}
 ${ERROR_PAD}${(new StackTracey(err.stack).asTable() as string).split('\n').join(`\n${ERROR_PAD}`)}`)
-    },
-  })
 })
 
-test('prints generic error when recursive install fails', (done) => {
+test('prints generic error when recursive install fails', async () => {
   const output$ = toOutput$({
     context: { argv: ['recursive'] },
     streamParser: createStreamParser(),
@@ -55,19 +52,14 @@ test('prints generic error when recursive install fails', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`/home/src/:
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`/home/src/:
 ${formatError('ERROR', 'some error')}
 ${ERROR_PAD}
 ${ERROR_PAD}${(new StackTracey(err.stack).asTable() as string).split('\n').join(`\n${ERROR_PAD}`)}`)
-    },
-  })
 })
 
-test('prints no matching version error when many dist-tags exist', (done) => {
+test('prints no matching version error when many dist-tags exist', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     streamParser: createStreamParser(),
@@ -75,11 +67,13 @@ test('prints no matching version error when many dist-tags exist', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`${formatError('ERR_PNPM_NO_MATCHING_VERSION', 'No matching version found for pnpm@1000.0.0')}
+  const err = Object.assign(new PnpmError('NO_MATCHING_VERSION', 'No matching version found for pnpm@1000.0.0'), {
+    packageMeta: loadJsonFile.sync(path.join(__dirname, 'pnpm-meta.json')),
+  })
+  logger.error(err, err)
+
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`${formatError('ERR_PNPM_NO_MATCHING_VERSION', 'No matching version found for pnpm@1000.0.0')}
 ${ERROR_PAD}
 ${ERROR_PAD}The latest release of pnpm is "2.4.0".
 ${ERROR_PAD}
@@ -89,16 +83,9 @@ ${ERROR_PAD}  * next: 2.4.0
 ${ERROR_PAD}  * latest-1: 1.43.1
 ${ERROR_PAD}
 ${ERROR_PAD}If you need the full list of all 281 published versions run "$ pnpm view pnpm versions".`)
-    },
-  })
-
-  const err = Object.assign(new PnpmError('NO_MATCHING_VERSION', 'No matching version found for pnpm@1000.0.0'), {
-    packageMeta: loadJsonFile.sync(path.join(__dirname, 'pnpm-meta.json')),
-  })
-  logger.error(err, err)
 })
 
-test('prints no matching version error when only the latest dist-tag exists', (done) => {
+test('prints no matching version error when only the latest dist-tag exists', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     streamParser: createStreamParser(),
@@ -106,25 +93,20 @@ test('prints no matching version error when only the latest dist-tag exists', (d
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`${formatError('ERR_PNPM_NO_MATCHING_VERSION', 'No matching version found for is-positive@1000.0.0')}
-${ERROR_PAD}
-${ERROR_PAD}The latest release of is-positive is "3.1.0".
-${ERROR_PAD}
-${ERROR_PAD}If you need the full list of all 4 published versions run "$ pnpm view is-positive versions".`)
-    },
-  })
-
   const err = Object.assign(new PnpmError('NO_MATCHING_VERSION', 'No matching version found for is-positive@1000.0.0'), {
     packageMeta: loadJsonFile.sync(path.join(__dirname, 'is-positive-meta.json')),
   })
   logger.error(err, err)
+
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`${formatError('ERR_PNPM_NO_MATCHING_VERSION', 'No matching version found for is-positive@1000.0.0')}
+${ERROR_PAD}
+${ERROR_PAD}The latest release of is-positive is "3.1.0".
+${ERROR_PAD}
+${ERROR_PAD}If you need the full list of all 4 published versions run "$ pnpm view is-positive versions".`)
 })
 
-test('prints suggestions when an internet-connection related error happens', (done) => {
+test('prints suggestions when an internet-connection related error happens', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     streamParser: createStreamParser(),
@@ -132,11 +114,25 @@ test('prints suggestions when an internet-connection related error happens', (do
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`/project-dir:
+  const err = Object.assign(
+    new PnpmError('BAD_TARBALL_SIZE', 'Actual size (99) of tarball (https://foo) did not match the one specified in \'Content-Length\' header (100)'),
+    {
+      prefix: '/project-dir',
+      pkgsStack: [
+        {
+          id: 'registry.npmjs.org/foo/1.0.0',
+          name: 'foo',
+          version: '1.0.0',
+        },
+      ],
+      expectedSize: 100,
+      receivedSize: 99,
+    }
+  )
+  logger.error(err, err)
+
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`/project-dir:
 ${formatError('ERR_PNPM_BAD_TARBALL_SIZE', 'Actual size (99) of tarball (https://foo) did not match the one specified in \'Content-Length\' header (100)')}
 ${ERROR_PAD}
 ${ERROR_PAD}This error happened while installing the dependencies of foo@1.0.0
@@ -155,28 +151,9 @@ ${ERROR_PAD}    delete the config once the internet connection is good again: \`
 ${ERROR_PAD}
 ${ERROR_PAD}NOTE: You may also override configs via flags.
 ${ERROR_PAD}For instance, \`pnpm install --fetch-retries 5 --network-concurrency 1\``)
-    },
-  })
-
-  const err = Object.assign(
-    new PnpmError('BAD_TARBALL_SIZE', 'Actual size (99) of tarball (https://foo) did not match the one specified in \'Content-Length\' header (100)'),
-    {
-      prefix: '/project-dir',
-      pkgsStack: [
-        {
-          id: 'registry.npmjs.org/foo/1.0.0',
-          name: 'foo',
-          version: '1.0.0',
-        },
-      ],
-      expectedSize: 100,
-      receivedSize: 99,
-    }
-  )
-  logger.error(err, err)
 })
 
-test('prints test error', (done) => {
+test('prints test error', async () => {
   const output$ = toOutput$({
     context: { argv: ['run', 'test'] },
     streamParser: createStreamParser(),
@@ -184,22 +161,17 @@ test('prints test error', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`${formatError('ELIFECYCLE', 'Test failed. See above for more details.')}`)
-    },
-  })
-
   const err = Object.assign(new Error('Tests failed'), {
     code: 'ELIFECYCLE',
     stage: 'test',
   })
   logger.error(err, err)
+
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`${formatError('ELIFECYCLE', 'Test failed. See above for more details.')}`)
 })
 
-test('prints command error with exit code', (done) => {
+test('prints command error with exit code', async () => {
   const output$ = toOutput$({
     context: { argv: ['run', 'lint'] },
     streamParser: createStreamParser(),
@@ -207,22 +179,17 @@ test('prints command error with exit code', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`${formatError('ELIFECYCLE', 'Command failed with exit code 100.')}`)
-    },
-  })
-
   const err: Exception = new Error('Command failed')
   err['errno'] = 100
   err['stage'] = 'lint'
   err['code'] = 'ELIFECYCLE'
   logger.error(err, err)
+
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`${formatError('ELIFECYCLE', 'Command failed with exit code 100.')}`)
 })
 
-test('prints command error without exit code', (done) => {
+test('prints command error without exit code', async () => {
   const output$ = toOutput$({
     context: { argv: ['run', 'lint'] },
     streamParser: createStreamParser(),
@@ -230,21 +197,16 @@ test('prints command error without exit code', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`${formatError('ELIFECYCLE', 'Command failed.')}`)
-    },
-  })
-
   const err: Exception = new Error('Command failed')
   err['stage'] = 'lint'
   err['code'] = 'ELIFECYCLE'
   logger.error(err, err)
+
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`${formatError('ELIFECYCLE', 'Command failed.')}`)
 })
 
-test('prints unsupported pnpm version error', (done) => {
+test('prints unsupported pnpm version error', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     streamParser: createStreamParser(),
@@ -252,11 +214,15 @@ test('prints unsupported pnpm version error', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`${formatError('ERR_PNPM_UNSUPPORTED_ENGINE', 'Unsupported environment (bad pnpm and/or Node.js version)')}
+  const err = Object.assign(new PnpmError('UNSUPPORTED_ENGINE', 'Unsupported pnpm version'), {
+    packageId: '/home/zoltan/project',
+    wanted: { pnpm: '2' },
+    current: { pnpm: '3.0.0', node: '10.0.0' },
+  })
+  logger.error(err, err)
+
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`${formatError('ERR_PNPM_UNSUPPORTED_ENGINE', 'Unsupported environment (bad pnpm and/or Node.js version)')}
 ${ERROR_PAD}
 ${ERROR_PAD}Your pnpm version is incompatible with "/home/zoltan/project".
 ${ERROR_PAD}
@@ -268,18 +234,9 @@ ${ERROR_PAD}To fix this issue, install the required pnpm version globally.
 ${ERROR_PAD}
 ${ERROR_PAD}To install the latest version of pnpm, run "pnpm i -g pnpm".
 ${ERROR_PAD}To check your pnpm version, run "pnpm -v".`)
-    },
-  })
-
-  const err = Object.assign(new PnpmError('UNSUPPORTED_ENGINE', 'Unsupported pnpm version'), {
-    packageId: '/home/zoltan/project',
-    wanted: { pnpm: '2' },
-    current: { pnpm: '3.0.0', node: '10.0.0' },
-  })
-  logger.error(err, err)
 })
 
-test('prints unsupported Node version error', (done) => {
+test('prints unsupported Node version error', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     streamParser: createStreamParser(),
@@ -287,11 +244,15 @@ test('prints unsupported Node version error', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`${formatError('ERR_PNPM_UNSUPPORTED_ENGINE', 'Unsupported environment (bad pnpm and/or Node.js version)')}
+  const err = Object.assign(new PnpmError('UNSUPPORTED_ENGINE', 'Unsupported pnpm version'), {
+    packageId: '/home/zoltan/project',
+    wanted: { node: '>=12' },
+    current: { pnpm: '3.0.0', node: '10.0.0' },
+  })
+  logger.error(err, err)
+
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`${formatError('ERR_PNPM_UNSUPPORTED_ENGINE', 'Unsupported environment (bad pnpm and/or Node.js version)')}
 ${ERROR_PAD}
 ${ERROR_PAD}Your Node version is incompatible with "/home/zoltan/project".
 ${ERROR_PAD}
@@ -300,18 +261,9 @@ ${ERROR_PAD}Got: 10.0.0
 ${ERROR_PAD}
 ${ERROR_PAD}This is happening because the package's manifest has an engines.node field specified.
 ${ERROR_PAD}To fix this issue, install the required Node version.`)
-    },
-  })
-
-  const err = Object.assign(new PnpmError('UNSUPPORTED_ENGINE', 'Unsupported pnpm version'), {
-    packageId: '/home/zoltan/project',
-    wanted: { node: '>=12' },
-    current: { pnpm: '3.0.0', node: '10.0.0' },
-  })
-  logger.error(err, err)
 })
 
-test('prints unsupported pnpm and Node versions error', (done) => {
+test('prints unsupported pnpm and Node versions error', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     streamParser: createStreamParser(),
@@ -319,11 +271,15 @@ test('prints unsupported pnpm and Node versions error', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`${formatError('ERR_PNPM_UNSUPPORTED_ENGINE', 'Unsupported environment (bad pnpm and/or Node.js version)')}
+  const err = Object.assign(new PnpmError('UNSUPPORTED_ENGINE', 'Unsupported pnpm version'), {
+    packageId: '/home/zoltan/project',
+    wanted: { pnpm: '2', node: '>=12' },
+    current: { pnpm: '3.0.0', node: '10.0.0' },
+  })
+  logger.error(err, err)
+
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`${formatError('ERR_PNPM_UNSUPPORTED_ENGINE', 'Unsupported environment (bad pnpm and/or Node.js version)')}
 ${ERROR_PAD}
 ${ERROR_PAD}Your pnpm version is incompatible with "/home/zoltan/project".
 ${ERROR_PAD}
@@ -342,18 +298,9 @@ ${ERROR_PAD}Got: 10.0.0
 ${ERROR_PAD}
 ${ERROR_PAD}This is happening because the package's manifest has an engines.node field specified.
 ${ERROR_PAD}To fix this issue, install the required Node version.`)
-    },
-  })
-
-  const err = Object.assign(new PnpmError('UNSUPPORTED_ENGINE', 'Unsupported pnpm version'), {
-    packageId: '/home/zoltan/project',
-    wanted: { pnpm: '2', node: '>=12' },
-    current: { pnpm: '3.0.0', node: '10.0.0' },
-  })
-  logger.error(err, err)
 })
 
-test('prints error even if the error object not passed in through the message object', (done) => {
+test('prints error even if the error object not passed in through the message object', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     streamParser: createStreamParser(),
@@ -364,16 +311,11 @@ test('prints error even if the error object not passed in through the message ob
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(formatError('ERR_PNPM_SOME_ERROR', 'some error'))
-    },
-  })
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(formatError('ERR_PNPM_SOME_ERROR', 'some error'))
 })
 
-test('prints error without packages stacktrace when pkgsStack is empty but do print the project directory path', (done) => {
+test('prints error without packages stacktrace when pkgsStack is empty but do print the project directory path', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     streamParser: createStreamParser(),
@@ -386,19 +328,14 @@ test('prints error without packages stacktrace when pkgsStack is empty but do pr
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`/project-dir:
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`/project-dir:
 ${formatError('ERR_PNPM_SOME_ERROR', 'some error')}
 ${ERROR_PAD}
 ${ERROR_PAD}This error happened while installing a direct dependency of /project-dir`)
-    },
-  })
 })
 
-test('prints error with packages stacktrace - depth 1 and hint', (done) => {
+test('prints error with packages stacktrace - depth 1 and hint', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     streamParser: createStreamParser(),
@@ -416,19 +353,15 @@ test('prints error with packages stacktrace - depth 1 and hint', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`${formatError('ERR_PNPM_SOME_ERROR', 'some error')}
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`${formatError('ERR_PNPM_SOME_ERROR', 'some error')}
 ${ERROR_PAD}
 ${ERROR_PAD}This error happened while installing the dependencies of foo@1.0.0
+${ERROR_PAD}
 ${ERROR_PAD}hint`)
-    },
-  })
 })
 
-test('prints error with packages stacktrace - depth 2', (done) => {
+test('prints error with packages stacktrace - depth 2', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     streamParser: createStreamParser(),
@@ -452,20 +385,15 @@ test('prints error with packages stacktrace - depth 2', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`/project-dir:
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`/project-dir:
 ${formatError('ERR_PNPM_SOME_ERROR', 'some error')}
 ${ERROR_PAD}
 ${ERROR_PAD}This error happened while installing the dependencies of foo@1.0.0
 ${ERROR_PAD} at bar@1.0.0`)
-    },
-  })
 })
 
-test('prints error and hint', (done) => {
+test('prints error and hint', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     streamParser: createStreamParser(),
@@ -476,18 +404,13 @@ test('prints error and hint', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(formatErrorCode('ERR_PNPM_SOME_ERROR') + ' ' + `${chalk.red('some error')}
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(formatErrorCode('ERR_PNPM_SOME_ERROR') + ' ' + `${chalk.red('some error')}
 
 some hint`)
-    },
-  })
 })
 
-test('prints authorization error with auth settings', (done) => {
+test('prints authorization error with auth settings', async () => {
   const rawConfig = {
     '//foo.bar:_auth': '9876543219',
     '//foo.bar:_authToken': '9876543219',
@@ -509,11 +432,8 @@ test('prints authorization error with auth settings', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`${formatError('ERR_PNPM_FETCH_401', 'some error')}
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`${formatError('ERR_PNPM_FETCH_401', 'some error')}
 ${ERROR_PAD}
 ${ERROR_PAD}some hint
 ${ERROR_PAD}
@@ -527,11 +447,9 @@ ${ERROR_PAD}_auth=0123[hidden]
 ${ERROR_PAD}_authToken=0123[hidden]
 ${ERROR_PAD}_password=[hidden]
 ${ERROR_PAD}username=nagy.gabor`)
-    },
-  })
 })
 
-test('prints authorization error without auth settings, where there are none', (done) => {
+test('prints authorization error without auth settings, where there are none', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'], config: { rawConfig: {} } as any }, // eslint-disable-line
     streamParser: createStreamParser(),
@@ -542,17 +460,12 @@ test('prints authorization error without auth settings, where there are none', (
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`${formatError('ERR_PNPM_FETCH_401', 'some error')}
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe(`${formatError('ERR_PNPM_FETCH_401', 'some error')}
 ${ERROR_PAD}
 ${ERROR_PAD}some hint
 ${ERROR_PAD}
 ${ERROR_PAD}No authorization settings were found in the configs.
 ${ERROR_PAD}Try to log in to the registry by running "pnpm login"
 ${ERROR_PAD}or add the auth tokens manually to the ~/.npmrc file.`)
-    },
-  })
 })
diff --git a/cli/default-reporter/test/reportingExecutionTime.ts b/cli/default-reporter/test/reportingExecutionTime.ts
index a2fb01b9efe..2756267e572 100644
--- a/cli/default-reporter/test/reportingExecutionTime.ts
+++ b/cli/default-reporter/test/reportingExecutionTime.ts
@@ -1,10 +1,13 @@
+import { setTimeout } from 'node:timers/promises'
 import { executionTimeLogger } from '@pnpm/core-loggers'
 import { packageManager } from '@pnpm/cli-meta'
 import { toOutput$ } from '@pnpm/default-reporter'
 import { createStreamParser } from '@pnpm/logger'
-import { take } from 'rxjs/operators'
+import { firstValueFrom } from 'rxjs'
 
-test('does not print execution time for help command', (done) => {
+const NO_OUTPUT = Symbol('test should not log anything')
+
+test('does not print execution time for help command', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['help'],
@@ -17,21 +20,15 @@ test('does not print execution time for help command', (done) => {
     endedAt: 1665279413671,
   })
 
-  const subscription = output$.subscribe({
-    complete: () => done(),
-    error: done,
-    next: () => {
-      done('should not log anything')
-    },
-  })
+  const output = await Promise.race([
+    firstValueFrom(output$),
+    setTimeout(10).then(() => NO_OUTPUT),
+  ])
 
-  setTimeout(() => {
-    done()
-    subscription.unsubscribe()
-  }, 10)
+  expect(output).toEqual(NO_OUTPUT)
 })
 
-test('prints execution time for install command', (done) => {
+test('prints execution time for install command', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -46,11 +43,6 @@ test('prints execution time for install command', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`Done in 10.8s using ${packageManager.name} v${packageManager.version}`)
-    },
-  })
+  const output = await firstValueFrom(output$)
+  expect(output).toBe(`Done in 10.8s using ${packageManager.name} v${packageManager.version}`)
 })
diff --git a/cli/default-reporter/test/reportingLifecycleScripts.ts b/cli/default-reporter/test/reportingLifecycleScripts.ts
index c41f4bc6f1d..9d6de1a7efe 100644
--- a/cli/default-reporter/test/reportingLifecycleScripts.ts
+++ b/cli/default-reporter/test/reportingLifecycleScripts.ts
@@ -26,7 +26,7 @@ function replaceTimeWith1Sec (text: string) {
     .replace(/failed in [a-z0-9μ]+/g, 'failed in 1s')
 }
 
-test('groups lifecycle output', (done) => {
+test('groups lifecycle output', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     reportingOptions: { outputMaxWidth: 79 },
@@ -113,13 +113,10 @@ test('groups lifecycle output', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(skip(9), take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: (output: string) => {
-      expect(replaceTimeWith1Sec(output)).toBe(`\
+  const output = await firstValueFrom(output$.pipe(skip(9), take(1), map(normalizeNewline)))
+  expect(replaceTimeWith1Sec(output)).toBe(`\
 packages/foo ${PREINSTALL}$ node foo
-${OUTPUT_INDENTATION} foo 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
+${OUTPUT_INDENTATION} foo 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 2…
 ${STATUS_INDENTATION} ${STATUS_RUNNING}
 packages/foo ${POSTINSTALL}$ node foo
 ${OUTPUT_INDENTATION} foo I
@@ -131,8 +128,6 @@ ${OUTPUT_INDENTATION} bar I
 ${STATUS_INDENTATION} ${STATUS_RUNNING}
 packages/qar ${INSTALL}$ node qar
 ${STATUS_INDENTATION} ${STATUS_DONE}`)
-    },
-  })
 })
 
 test('groups lifecycle output when append-only is used', async () => {
@@ -363,7 +358,7 @@ test('groups lifecycle output when append-only and aggregate-output are used', a
   ])
 })
 
-test('groups lifecycle output when streamLifecycleOutput is used', (done) => {
+test('groups lifecycle output when streamLifecycleOutput is used', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     reportingOptions: {
@@ -460,11 +455,8 @@ test('groups lifecycle output when streamLifecycleOutput is used', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(skip(11), take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: (output: string) => {
-      expect(output).toBe(`\
+  const output = await firstValueFrom(output$.pipe(skip(11), take(1), map(normalizeNewline)))
+  expect(output).toBe(`\
 ${chalk.cyan('packages/foo')} ${PREINSTALL}$ node foo
 ${chalk.cyan('packages/foo')} ${PREINSTALL}: foo 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
 ${chalk.cyan('packages/foo')} ${PREINSTALL}: Failed
@@ -477,8 +469,6 @@ ${chalk.cyan('packages/foo')} ${POSTINSTALL}: foo III
 ${chalk.blue('packages/qar')} ${INSTALL}$ node qar
 ${chalk.blue('packages/qar')} ${INSTALL}: Done
 ${chalk.cyan('packages/foo')} ${POSTINSTALL}: Done`)
-    },
-  })
 })
 
 test('groups lifecycle output when append-only and aggregate-output are used with mixed stages', async () => {
@@ -663,7 +653,7 @@ test('groups lifecycle output when append-only and reporter-hide-prefix are used
   ])
 })
 
-test('collapse lifecycle output when it has too many lines', (done) => {
+test('collapse lifecycle output when it has too many lines', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     reportingOptions: { outputMaxWidth: 79 },
@@ -696,11 +686,8 @@ test('collapse lifecycle output when it has too many lines', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(skip(101), take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: (output: string) => {
-      expect(replaceTimeWith1Sec(output)).toBe(`\
+  const output = await firstValueFrom(output$.pipe(skip(101), take(1), map(normalizeNewline)))
+  expect(replaceTimeWith1Sec(output)).toBe(`\
 packages/foo ${POSTINSTALL}$ node foo
 [90 lines collapsed]
 ${OUTPUT_INDENTATION} foo 90
@@ -714,11 +701,9 @@ ${OUTPUT_INDENTATION} foo 97
 ${OUTPUT_INDENTATION} foo 98
 ${OUTPUT_INDENTATION} foo 99
 ${STATUS_INDENTATION} ${STATUS_DONE}`)
-    },
-  })
 })
 
-test('collapses lifecycle output of packages from node_modules', (done) => {
+test('collapses lifecycle output of packages from node_modules', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     reportingOptions: { outputMaxWidth: 79 },
@@ -810,20 +795,15 @@ test('collapses lifecycle output of packages from node_modules', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(skip(5), take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: (output: string) => {
-      expect(replaceTimeWith1Sec(output)).toBe(`\
+  const output = await firstValueFrom(output$.pipe(skip(5), take(1), map(normalizeNewline)))
+  expect(replaceTimeWith1Sec(output)).toBe(`\
 ${chalk.gray('node_modules/.registry.npmjs.org/foo/1.0.0/node_modules/')}foo: Running preinstall script...
 ${chalk.gray('node_modules/.registry.npmjs.org/foo/1.0.0/node_modules/')}foo: Running postinstall script, done in 1s
 ${chalk.gray('node_modules/.registry.npmjs.org/bar/1.0.0/node_modules/')}bar: Running postinstall script...
 ${chalk.gray('node_modules/.registry.npmjs.org/qar/1.0.0/node_modules/')}qar: Running install script, done in 1s`)
-    },
-  })
 })
 
-test('collapses lifecycle output from preparation of a git-hosted dependency', (done) => {
+test('collapses lifecycle output from preparation of a git-hosted dependency', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     reportingOptions: { outputMaxWidth: 79 },
@@ -885,18 +865,13 @@ test('collapses lifecycle output from preparation of a git-hosted dependency', (
 
   expect.assertions(1)
 
-  output$.pipe(skip(2), take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: (output: unknown) => {
-      expect(replaceTimeWith1Sec(output as string)).toBe(`\
-${chalk.gray('tmp')}_tmp_01234: Running preinstall script...
-${chalk.gray('tmp')}_tmp_01234: Running postinstall script, done in 1s`)
-    },
-  })
+  const output = await firstValueFrom(output$.pipe(skip(2), take(1), map(normalizeNewline)))
+  expect(replaceTimeWith1Sec(output)).toBe(`\
+${chalk.gray('tmp/')}_tmp_01243 [registry.npmjs.org/foo/1.0.0]: Running preinstall script...
+${chalk.gray('tmp/')}_tmp_01243 [registry.npmjs.org/foo/1.0.0]: Running postinstall script, done in 1s`)
 })
 
-test('output of failed optional dependency is not shown', (done) => {
+test('output of failed optional dependency is not shown', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     reportingOptions: { outputMaxWidth: 79 },
@@ -929,16 +904,11 @@ test('output of failed optional dependency is not shown', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(skip(1), take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: (output: string) => {
-      expect(replaceTimeWith1Sec(output)).toBe(`${chalk.gray('node_modules/.registry.npmjs.org/foo/1.0.0/node_modules/')}foo: Running install script, failed in 1s (skipped as optional)`)
-    },
-  })
+  const output = await firstValueFrom(output$.pipe(skip(1), take(1), map(normalizeNewline)))
+  expect(replaceTimeWith1Sec(output)).toBe(`${chalk.gray('node_modules/.registry.npmjs.org/foo/1.0.0/node_modules/')}foo: Running install script, failed in 1s (skipped as optional)`)
 })
 
-test('output of failed non-optional dependency is printed', (done) => {
+test('output of failed non-optional dependency is printed', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     reportingOptions: { outputMaxWidth: 79 },
@@ -971,20 +941,15 @@ test('output of failed non-optional dependency is printed', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(skip(1), take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: (output: string) => {
-      expect(replaceTimeWith1Sec(output)).toBe(`\
+  const output = await firstValueFrom(output$.pipe(skip(1), take(1), map(normalizeNewline)))
+  expect(replaceTimeWith1Sec(output)).toBe(`\
 ${chalk.gray('node_modules/.registry.npmjs.org/foo/1.0.0/node_modules/')}foo: Running install script, failed in 1s
 .../foo/1.0.0/node_modules/foo ${INSTALL}$ node foo
 ${OUTPUT_INDENTATION} foo 0 1 2 3 4 5 6 7 8 9
 ${STATUS_INDENTATION} ${failedAt(wd)}`)
-    },
-  })
 })
 
-test('do not fail if the debug log has no output', (done) => {
+test('do not fail if the debug log has no output', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     reportingOptions: { outputMaxWidth: 79 },
@@ -1017,21 +982,16 @@ test('do not fail if the debug log has no output', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(skip(1), take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: (output: string) => {
-      expect(replaceTimeWith1Sec(output)).toBe(`\
+  const output = await firstValueFrom(output$.pipe(skip(1), take(1), map(normalizeNewline)))
+  expect(replaceTimeWith1Sec(output)).toBe(`\
 ${chalk.gray('node_modules/.registry.npmjs.org/foo/1.0.0/node_modules/')}foo: Running install script, failed in 1s
 .../foo/1.0.0/node_modules/foo ${INSTALL}$ node foo
 ${OUTPUT_INDENTATION} 
 ${STATUS_INDENTATION} ${failedAt(wd)}`)
-    },
-  })
 })
 
 // Many libs use stderr for logging, so showing all stderr adds not much value
-test['skip']('prints lifecycle progress', (done) => {
+test['skip']('prints lifecycle progress', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     streamParser: createStreamParser(),
@@ -1077,17 +1037,12 @@ test['skip']('prints lifecycle progress', (done) => {
   const childOutputColor = chalk.grey
   const childOutputError = chalk.red
 
-  output$.pipe(skip(3), take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`\
+  const output = await firstValueFrom(output$.pipe(skip(3), take(1), map(normalizeNewline)))
+  expect(output).toBe(`\
 Running ${POSTINSTALL} for ${hlPkgId('registry.npmjs.org/foo/1.0.0')}: ${childOutputColor('foo I')}
 Running ${POSTINSTALL} for ${hlPkgId('registry.npmjs.org/foo/1.0.0')}! ${childOutputError('foo II')}
 Running ${POSTINSTALL} for ${hlPkgId('registry.npmjs.org/foo/1.0.0')}: ${childOutputColor('foo III')}
 Running ${POSTINSTALL} for ${hlPkgId('registry.npmjs.org/bar/1.0.0')}: ${childOutputColor('bar I')}`)
-    },
-  })
 })
 
 function failedAt (wd: string) {
diff --git a/cli/default-reporter/test/reportingPeerDependencyIssues.ts b/cli/default-reporter/test/reportingPeerDependencyIssues.ts
index 89f016b544e..060e377a034 100644
--- a/cli/default-reporter/test/reportingPeerDependencyIssues.ts
+++ b/cli/default-reporter/test/reportingPeerDependencyIssues.ts
@@ -4,9 +4,9 @@ import {
   createStreamParser,
   logger,
 } from '@pnpm/logger'
-import { take } from 'rxjs/operators'
+import { firstValueFrom } from 'rxjs'
 
-test('print peer dependency issues warning', (done) => {
+test('print peer dependency issues warning', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -42,16 +42,11 @@ test('print peer dependency issues warning', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toContain('.')
-    },
-  })
+  const output = await firstValueFrom(output$)
+  expect(output).toContain('.')
 })
 
-test('print peer dependency issues error', (done) => {
+test('print peer dependency issues error', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     streamParser: createStreamParser(),
@@ -89,11 +84,6 @@ test('print peer dependency issues error', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toContain('.')
-    },
-  })
+  const output = await firstValueFrom(output$)
+  expect(output).toContain('.')
 })
diff --git a/cli/default-reporter/test/reportingProgress.ts b/cli/default-reporter/test/reportingProgress.ts
index 9a9c9cc1267..90c0c12eabf 100644
--- a/cli/default-reporter/test/reportingProgress.ts
+++ b/cli/default-reporter/test/reportingProgress.ts
@@ -10,7 +10,8 @@ import {
   createStreamParser,
   logger,
 } from '@pnpm/logger'
-import { map, skip, take } from 'rxjs/operators'
+import { firstValueFrom } from 'rxjs'
+import { map, skip, take, toArray } from 'rxjs/operators'
 import chalk from 'chalk'
 import normalizeNewline from 'normalize-newline'
 import { formatWarn } from '../src/reporterForClient/utils/formatWarn'
@@ -20,7 +21,7 @@ const hlPkgId = chalk['whiteBright']
 
 const EOL = '\n'
 
-test('prints progress beginning', (done) => {
+test('prints progress beginning', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -41,16 +42,11 @@ test('prints progress beginning', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}`)
-    },
-  })
+  const output = await firstValueFrom(output$)
+  expect(output).toBe(`Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}`)
 })
 
-test('prints progress without added packages stats', (done) => {
+test('prints progress without added packages stats', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -74,16 +70,11 @@ test('prints progress without added packages stats', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}`)
-    },
-  })
+  const output = await firstValueFrom(output$)
+  expect(output).toBe(`Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}`)
 })
 
-test('prints all progress stats', (done) => {
+test('prints all progress stats', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -120,16 +111,11 @@ test('prints all progress stats', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(skip(3), take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`Progress: resolved ${hlValue('1')}, reused ${hlValue('1')}, downloaded ${hlValue('1')}, added ${hlValue('1')}`)
-    },
-  })
+  const output = await firstValueFrom(output$.pipe(skip(3), take(1)))
+  expect(output).toBe(`Progress: resolved ${hlValue('1')}, reused ${hlValue('1')}, downloaded ${hlValue('1')}, added ${hlValue('1')}`)
 })
 
-test('prints progress beginning of node_modules from not cwd', (done) => {
+test('prints progress beginning of node_modules from not cwd', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -150,16 +136,11 @@ test('prints progress beginning of node_modules from not cwd', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`foo                                      | Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}`)
-    },
-  })
+  const output = await firstValueFrom(output$)
+  expect(output).toBe(`foo                                      | Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}`)
 })
 
-test('prints progress beginning of node_modules from not cwd, when progress prefix is hidden', (done) => {
+test('prints progress beginning of node_modules from not cwd, when progress prefix is hidden', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -183,16 +164,11 @@ test('prints progress beginning of node_modules from not cwd, when progress pref
 
   expect.assertions(1)
 
-  output$.pipe(take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}`)
-    },
-  })
+  const output = await firstValueFrom(output$)
+  expect(output).toBe(`Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}`)
 })
 
-test('prints progress beginning when appendOnly is true', (done) => {
+test('prints progress beginning when appendOnly is true', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -216,16 +192,11 @@ test('prints progress beginning when appendOnly is true', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}`)
-    },
-  })
+  const output = await firstValueFrom(output$)
+  expect(output).toBe(`Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}`)
 })
 
-test('prints progress beginning during recursive install', (done) => {
+test('prints progress beginning during recursive install', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -249,16 +220,11 @@ test('prints progress beginning during recursive install', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}`)
-    },
-  })
+  const output = await firstValueFrom(output$)
+  expect(output).toBe(`Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}`)
 })
 
-test('prints progress on first download', (done) => {
+test('prints progress on first download', async () => {
   expect.assertions(1)
 
   const output$ = toOutput$({
@@ -270,14 +236,6 @@ test('prints progress on first download', (done) => {
     streamParser: createStreamParser(),
   })
 
-  output$.pipe(skip(1), take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(`Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('1')}, added ${hlValue('0')}`)
-    },
-  })
-
   const packageId = 'registry.npmjs.org/foo/1.0.0'
 
   stageLogger.debug({
@@ -295,9 +253,12 @@ test('prints progress on first download', (done) => {
     requester: '/src/project',
     status: 'fetched',
   })
+
+  const output = await firstValueFrom(output$.pipe(skip(1), take(1)))
+  expect(output).toBe(`Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('1')}, added ${hlValue('0')}`)
 })
 
-test('moves fixed line to the end', (done) => {
+test('moves fixed line to the end', async () => {
   expect.assertions(1)
   const prefix = '/src/project'
   const output$ = toOutput$({
@@ -309,15 +270,6 @@ test('moves fixed line to the end', (done) => {
     streamParser: createStreamParser(),
   })
 
-  output$.pipe(skip(3), take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(formatWarn('foo') + EOL +
-        `Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('1')}, added ${hlValue('0')}, done`)
-    },
-  })
-
   const packageId = 'registry.npmjs.org/foo/1.0.0'
 
   stageLogger.debug({
@@ -345,9 +297,13 @@ test('moves fixed line to the end', (done) => {
     prefix,
     stage: 'importing_done',
   })
+
+  const output = await firstValueFrom(output$.pipe(skip(3), take(1), map(normalizeNewline)))
+  expect(output).toBe(formatWarn('foo') + EOL +
+    `Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('1')}, added ${hlValue('0')}, done`)
 })
 
-test('prints "Already up to date"', (done) => {
+test('prints "Already up to date"', async () => {
   const output$ = toOutput$({
     context: { argv: ['install'] },
     streamParser: createStreamParser(),
@@ -360,16 +316,11 @@ test('prints "Already up to date"', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1), map(normalizeNewline)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe('Already up to date')
-    },
-  })
+  const output = await firstValueFrom(output$.pipe(take(1), map(normalizeNewline)))
+  expect(output).toBe('Already up to date')
 })
 
-test('prints progress of big files download', (done) => {
+test('prints progress of big files download', async () => {
   expect.assertions(6)
 
   const output$ = toOutput$({
@@ -385,50 +336,6 @@ test('prints progress of big files download', (done) => {
   const pkgId2 = 'registry.npmjs.org/bar/2.0.0'
   const pkgId3 = 'registry.npmjs.org/qar/3.0.0'
 
-  output$.pipe(
-    take(9),
-    map(normalizeNewline),
-    map((output, index) => {
-      switch (index) {
-      case 0:
-        expect(output).toBe(`Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}`)
-        return
-      case 1:
-        expect(output).toBe(`\
-Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}
-Downloading ${hlPkgId(pkgId1)}: ${hlValue('0.00 B')}/${hlValue('10.49 MB')}`)
-        return
-      case 2:
-        expect(output).toBe(`\
-Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}
-Downloading ${hlPkgId(pkgId1)}: ${hlValue('5.77 MB')}/${hlValue('10.49 MB')}`)
-        return
-      case 4:
-        expect(output).toBe(`\
-Progress: resolved ${hlValue('2')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}
-Downloading ${hlPkgId(pkgId1)}: ${hlValue('7.34 MB')}/${hlValue('10.49 MB')}`)
-        return
-      case 7:
-        expect(output).toBe(`\
-Progress: resolved ${hlValue('3')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}
-Downloading ${hlPkgId(pkgId1)}: ${hlValue('7.34 MB')}/${hlValue('10.49 MB')}
-Downloading ${hlPkgId(pkgId3)}: ${hlValue('19.92 MB')}/${hlValue('20.97 MB')}`)
-        return
-      case 8:
-        expect(output).toBe(`\
-Downloading ${hlPkgId(pkgId1)}: ${hlValue('10.49 MB')}/${hlValue('10.49 MB')}, done
-Progress: resolved ${hlValue('3')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}
-Downloading ${hlPkgId(pkgId3)}: ${hlValue('19.92 MB')}/${hlValue('20.97 MB')}`)
-        return // eslint-disable-line
-      }
-    })
-  )
-    .subscribe({
-      complete: () => done(),
-      error: done,
-      next: () => undefined,
-    })
-
   stageLogger.debug({
     prefix: '/src/project',
     stage: 'resolution_started',
@@ -496,4 +403,41 @@ Downloading ${hlPkgId(pkgId3)}: ${hlValue('19.92 MB')}/${hlValue('20.97 MB')}`)
     packageId: pkgId1,
     status: 'in_progress',
   })
+
+  const output = await firstValueFrom(output$.pipe(take(9), map(normalizeNewline), toArray()))
+
+  output.forEach((output, index) => {
+    switch (index) {
+    case 0:
+      expect(output).toBe(`Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}`)
+      return
+    case 1:
+      expect(output).toBe(`\
+Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}
+Downloading ${hlPkgId(pkgId1)}: ${hlValue('0.00 B')}/${hlValue('10.49 MB')}`)
+      return
+    case 2:
+      expect(output).toBe(`\
+Progress: resolved ${hlValue('1')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}
+Downloading ${hlPkgId(pkgId1)}: ${hlValue('5.77 MB')}/${hlValue('10.49 MB')}`)
+      return
+    case 4:
+      expect(output).toBe(`\
+Progress: resolved ${hlValue('2')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}
+Downloading ${hlPkgId(pkgId1)}: ${hlValue('7.34 MB')}/${hlValue('10.49 MB')}`)
+      return
+    case 7:
+      expect(output).toBe(`\
+Progress: resolved ${hlValue('3')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}
+Downloading ${hlPkgId(pkgId1)}: ${hlValue('7.34 MB')}/${hlValue('10.49 MB')}
+Downloading ${hlPkgId(pkgId3)}: ${hlValue('19.92 MB')}/${hlValue('20.97 MB')}`)
+      return
+    case 8:
+      expect(output).toBe(`\
+Downloading ${hlPkgId(pkgId1)}: ${hlValue('10.49 MB')}/${hlValue('10.49 MB')}, done
+Progress: resolved ${hlValue('3')}, reused ${hlValue('0')}, downloaded ${hlValue('0')}, added ${hlValue('0')}
+Downloading ${hlPkgId(pkgId3)}: ${hlValue('19.92 MB')}/${hlValue('20.97 MB')}`)
+      return // eslint-disable-line
+    }
+  })
 })
diff --git a/cli/default-reporter/test/reportingRequestRetry.ts b/cli/default-reporter/test/reportingRequestRetry.ts
index 74fe1a84f12..8a94a3c0cf5 100644
--- a/cli/default-reporter/test/reportingRequestRetry.ts
+++ b/cli/default-reporter/test/reportingRequestRetry.ts
@@ -3,10 +3,10 @@ import { toOutput$ } from '@pnpm/default-reporter'
 import {
   createStreamParser,
 } from '@pnpm/logger'
-import { take } from 'rxjs/operators'
+import { firstValueFrom } from 'rxjs'
 import { formatWarn } from '../src/reporterForClient/utils/formatWarn'
 
-test('print warning about request retry', (done) => {
+test('print warning about request retry', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -25,11 +25,6 @@ test('print warning about request retry', (done) => {
 
   expect.assertions(1)
 
-  output$.pipe(take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe(formatWarn('GET https://foo.bar/qar error (undefined). Will retry in 12.5 seconds. 4 retries left.'))
-    },
-  })
+  const output = await firstValueFrom(output$)
+  expect(output).toBe(formatWarn('GET https://foo.bar/qar error (undefined). Will retry in 12.5 seconds. 4 retries left.'))
 })
diff --git a/cli/default-reporter/test/reportingScope.ts b/cli/default-reporter/test/reportingScope.ts
index 783c410af06..6f5e2994941 100644
--- a/cli/default-reporter/test/reportingScope.ts
+++ b/cli/default-reporter/test/reportingScope.ts
@@ -1,10 +1,13 @@
+import { setTimeout } from 'node:timers/promises'
 import { type Config } from '@pnpm/config'
 import { scopeLogger } from '@pnpm/core-loggers'
 import { toOutput$ } from '@pnpm/default-reporter'
 import { createStreamParser } from '@pnpm/logger'
-import { take } from 'rxjs/operators'
+import { firstValueFrom } from 'rxjs'
 
-test('does not print scope of non-recursive install in a workspace', (done) => {
+const NO_OUTPUT = Symbol('test should not log anything')
+
+test('does not print scope of non-recursive install in a workspace', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -17,21 +20,15 @@ test('does not print scope of non-recursive install in a workspace', (done) => {
     workspacePrefix: '/home/src',
   })
 
-  const subscription = output$.subscribe({
-    complete: () => done(),
-    error: done,
-    next: () => {
-      done('should not log anything')
-    },
-  })
+  const output = await Promise.race([
+    firstValueFrom(output$),
+    setTimeout(10).then(() => NO_OUTPUT),
+  ])
 
-  setTimeout(() => {
-    done()
-    subscription.unsubscribe()
-  }, 10)
+  expect(output).toEqual(NO_OUTPUT)
 })
 
-test('prints scope of recursive install in a workspace when not all packages are selected', (done) => {
+test('prints scope of recursive install in a workspace when not all packages are selected', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -48,16 +45,11 @@ test('prints scope of recursive install in a workspace when not all packages are
 
   expect.assertions(1)
 
-  output$.pipe(take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe('Scope: 2 of 10 workspace projects')
-    },
-  })
+  const output = await firstValueFrom(output$)
+  expect(output).toBe('Scope: 2 of 10 workspace projects')
 })
 
-test('prints scope of recursive install in a workspace when all packages are selected', (done) => {
+test('prints scope of recursive install in a workspace when all packages are selected', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -74,16 +66,11 @@ test('prints scope of recursive install in a workspace when all packages are sel
 
   expect.assertions(1)
 
-  output$.pipe(take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe('Scope: all 10 workspace projects')
-    },
-  })
+  const output = await firstValueFrom(output$)
+  expect(output).toBe('Scope: all 10 workspace projects')
 })
 
-test('prints scope of recursive install not in a workspace when not all packages are selected', (done) => {
+test('prints scope of recursive install not in a workspace when not all packages are selected', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -99,16 +86,11 @@ test('prints scope of recursive install not in a workspace when not all packages
 
   expect.assertions(1)
 
-  output$.pipe(take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe('Scope: 2 of 10 projects')
-    },
-  })
+  const output = await firstValueFrom(output$)
+  expect(output).toBe('Scope: 2 of 10 projects')
 })
 
-test('prints scope of recursive install not in a workspace when all packages are selected', (done) => {
+test('prints scope of recursive install not in a workspace when all packages are selected', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -124,11 +106,6 @@ test('prints scope of recursive install not in a workspace when all packages are
 
   expect.assertions(1)
 
-  output$.pipe(take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(output).toBe('Scope: all 10 projects')
-    },
-  })
+  const output = await firstValueFrom(output$)
+  expect(output).toBe('Scope: all 10 projects')
 })
diff --git a/cli/default-reporter/test/reportingUpdateCheck.ts b/cli/default-reporter/test/reportingUpdateCheck.ts
index b7266438709..9750538b315 100644
--- a/cli/default-reporter/test/reportingUpdateCheck.ts
+++ b/cli/default-reporter/test/reportingUpdateCheck.ts
@@ -1,11 +1,14 @@
+import { setTimeout } from 'node:timers/promises'
 import { type Config } from '@pnpm/config'
 import { updateCheckLogger } from '@pnpm/core-loggers'
 import { toOutput$ } from '@pnpm/default-reporter'
 import { createStreamParser } from '@pnpm/logger'
-import { take } from 'rxjs/operators'
+import { firstValueFrom } from 'rxjs'
 import { stripVTControlCharacters as stripAnsi } from 'util'
 
-test('does not print update if latest is less than current', (done) => {
+const NO_OUTPUT = Symbol('test should not log anything')
+
+test('does not print update if latest is less than current', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -19,21 +22,15 @@ test('does not print update if latest is less than current', (done) => {
     latestVersion: '9.0.0',
   })
 
-  const subscription = output$.subscribe({
-    complete: () => done(),
-    error: done,
-    next: () => {
-      done('should not log anything')
-    },
-  })
+  const output = await Promise.race([
+    firstValueFrom(output$),
+    setTimeout(10).then(() => NO_OUTPUT),
+  ])
 
-  setTimeout(() => {
-    done()
-    subscription.unsubscribe()
-  }, 10)
+  expect(output).toEqual(NO_OUTPUT)
 })
 
-test('print update notification if the latest version is greater than the current', (done) => {
+test('print update notification if the latest version is greater than the current', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -50,16 +47,11 @@ test('print update notification if the latest version is greater than the curren
 
   expect.assertions(1)
 
-  output$.pipe(take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(stripAnsi(output)).toMatchSnapshot()
-    },
-  })
+  const output = await firstValueFrom(output$)
+  expect(stripAnsi(output)).toMatchSnapshot()
 })
 
-test('print update notification for Corepack if the latest version is greater than the current', (done) => {
+test('print update notification for Corepack if the latest version is greater than the current', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -78,16 +70,11 @@ test('print update notification for Corepack if the latest version is greater th
 
   expect.assertions(1)
 
-  output$.pipe(take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(stripAnsi(output)).toMatchSnapshot()
-    },
-  })
+  const output = await firstValueFrom(output$)
+  expect(stripAnsi(output)).toMatchSnapshot()
 })
 
-test('print update notification that suggests to use the standalone scripts for the upgrade', (done) => {
+test('print update notification that suggests to use the standalone scripts for the upgrade', async () => {
   const output$ = toOutput$({
     context: {
       argv: ['install'],
@@ -109,11 +96,6 @@ test('print update notification that suggests to use the standalone scripts for
 
   expect.assertions(1)
 
-  output$.pipe(take(1)).subscribe({
-    complete: () => done(),
-    error: done,
-    next: output => {
-      expect(stripAnsi(output)).toMatchSnapshot()
-    },
-  })
+  const output = await firstValueFrom(output$)
+  expect(stripAnsi(output)).toMatchSnapshot()
 })


Reviews:
user: login: gluxon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-09T19:42:47Z
user: login: zkochan
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-09T23:47:32Z

Review comments:
diff_hunk: @@ -1055,7 +1062,7 @@ test('logLevel=default', async () => {
   const output = await firstValueFrom(output$.pipe(skip(2), take(1)))
   expect(output).toBe(`Info message
 ${formatWarn('Some issue')}
-${formatError('ERROR', 'some error')}`)
+${formatError('ERR_PNPM_SOME_CODE', 'some error')}`)
path: cli/default-reporter/test/index.ts
user: login: gluxon
body: A bunch of tests were actually failing, but fortunately they were very easy to update. Note how this test throws `SOME_CODE` above, but we weren't checking for it correctly when calling `expect(...)`.

https://github.com/pnpm/pnpm/blob/55dc29428e24eae16ccd291c6a8011f5bafd79af/cli/default-reporter/test/index.ts#L346
created_at: 2025-03-09T19:41:30Z
author_association: MEMBER
diff_hunk: @@ -116,7 +116,7 @@ test('groups lifecycle output', async () => {
   const output = await firstValueFrom(output$.pipe(skip(9), take(1), map(normalizeNewline)))
   expect(replaceTimeWith1Sec(output)).toBe(`\
 packages/foo ${PREINSTALL}$ node foo
-${OUTPUT_INDENTATION} foo 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
+${OUTPUT_INDENTATION} foo 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 2…
path: cli/default-reporter/test/reportingLifecycleScripts.ts
user: login: gluxon
body: It looks like we add an ellipsis at 100 characters for lifecycle scripts, which caused this test to fail until the fix here.
created_at: 2025-03-09T19:42:38Z
author_association: MEMBER

PR comments:
user: login: gluxon
created_at: 2025-03-09T19:38:47Z
body: It looks like this issue has been observed by others (@zirkelc) in the past. @zirkelc I know it's been ~1.5 years, but I have the answer to your question if you're still curious. 😅

- https://github.com/pnpm/pnpm/pull/7086#issuecomment-1727200287
author_association: MEMBER
user: login: zkochan
created_at: 2025-03-09T23:47:53Z
body: Thanks, this is a great improvement!
author_association: MEMBER
user: login: gluxon
created_at: 2025-03-10T00:00:48Z
body: I'm really glad it's appreciated. It was pretty satisfying when I realized why these tests were flaking.
author_association: MEMBER
user: login: zirkelc
created_at: 2025-03-10T04:17:53Z
body: > It looks like this issue has been observed by others (@zirkelc) in the past. @zirkelc I know it's been ~1.5 years, but I have the answer to your question if you're still curious. 😅
> 
> * [feat(reporter): add `--reporter-hide-prefix` option #7086 (comment)](https://github.com/pnpm/pnpm/pull/7086#issuecomment-1727200287)

Yeah, better late than never! 😄 
author_association: CONTRIBUTOR