Why does Playwright `toThrowError` still fail the test even when asserting for the correct error?

In Playwright tests, when checking if a function throws an error, using expect(...).rejects.toThrowError() can still fail even though the correct error message is thrown.

This usually happens when the function isn’t asynchronous or when the error is thrown synchronously before Playwright’s expect can handle it as a rejected promise.

To fix this, you should wrap the function call inside an async function, for example:

await expect(async () => globalState.setComponent = {...}).rejects.toThrowError("Two components cannot have the same id");

This ensures the error is caught by Jest’s asynchronous matcher correctly.

I’ve dealt with this a couple of times in my own testing setup, especially when trying to understand why a playwright throw error assertion fails even when the message is correct. The root issue in most cases is timing the error gets thrown before the async matcher can catch it.

Same thing happens in pytest when you want the full parameterized test output the default command never shows the expanded values. That’s why I started using pytest_collection_finish inside conftest.py:

def pytest_collection_finish(session):
    for item in session.items:
        print(item.nodeid)

It runs after collection is finished, so you always get full IDs with params. Playwright has a similar “async vs sync” mismatch problem — the tools expect async, but the error fires synchronously, so the test fails even though it’s technically correct.

Yeah, I ran into the same playwright throw error confusion. The thing that finally clicked for me was realizing that .rejects.toThrowError() only works if the error comes from a rejected promise not from a normal immediate throw.

I confirmed it by dropping a console.log right before the call — the test never even got to the assertion. The error was happening too early.

For sync code, this works way better:

expect(() => globalState.setComponent = {...})
  .toThrowError("Two components cannot have the same id");

No await, no .rejects just a standard synchronous assertion. Once I separated async from sync cases, Playwright stopped failing the test for no reason.

Yep, that’s the mistake most people make with the whole playwright throw error situation mixing sync and async logic. Jest is strict about it:

  • If the function returns a promise, you must use: await expect(...).rejects.toThrowError()
  • If the function throws immediately, you must use: expect(...).toThrowError()

In one of my helper tests, the function sometimes returned a promise and sometimes threw immediately that’s why the assertion kept failing even with the right message. Once I separated them into two cases, everything behaved exactly as expected.