Search code examples
vue.jsnuxt.jsplaywrightnuxt3.js

How can I use my own composables inside a playwright test?


I replaced pinia with a custom composable "useSettings". In my playwright tests, I was able to access Pinia like this:

window.__NUXT__?.pinia.myStore

Now when I try to use my composable inside my tests, I am getting the error "useSettings is not defined".

How can I import it? 🤔

The use case is simple:

In order to decide if my app should show a cookie banner, I have a variable "xIpContinent". This used to reside inside a pinia store, and I could access it via playwright.

Now this variable is provided by a composable, which I am not able to use inside my test - and I don't know how to access it.

Update after the comments:

All right, I am with you on the whole framework agnostic testing. However, in my use case requires some additional information.

Depending on the client IP, a cookie banner is shown (or not). In order to test this behaviour, I need to know whether to expect a cookie banner or not. Only my app knows the client IP.

Also, every subsequent test is impacted by the cookie banner which is pretty invasive - as long as it is shown, other buttons cannot be clicked.

Dealing with this without knowing what to expect will definitely increase the complexity of my E2E tests 🤔


Solution

  • Don't. Playwright is designed to be framework agnostic and not to test implementation details. You want to test user-visible behavior. The idea is to manipulate the app as your users would, by clicking and typing into the UI and asserting observable text changes, and the presence or absence of elements on the screen. Your users will not examine window variables or open the browser console, so neither should your tests. Your users don't care about variables, composables, stores, CSS classes, XPaths, or JavaScript libraries, and neither should your tests.

    Unit tests, not end to end tests, are aware of the UI library being used and the parameters and rendering values returned by components. Unit testing is also aware of state management solutions and will likely mock stores and network calls.

    But even in unit tests, local variables, composables and hooks are private to the component and are inaccessible to other components, as well as your tests, and are therefore still implementation details. Don't test them directly--manipulate the component as a user would (either a programmer client, a component passing props, injecting a service, or a human user of the interface, not the test user).

    This is no different from testing a vanilla JS service function or utility. You pass in parameters and assert on the return value. All of the local variables that help produce that output are unknown. We don't care how the function (or component, in a UI unit test) does its job, we care only that it does its job correctly. Linters and static analysis tools check variable naming conventions and internal function complexity, not unit tests.

    But unit tests are another story--in E2E tests, composables are completely irrelevant and should not be tested, because it makes your tests fragile, tests the wrong layer of abstraction, runs much more slowly than unit tests, and doesn't give any confidence that the application is working for the user. The more "black box" you can write your E2E tests, the less time you'll have to spend fixing broken tests, dealing with flakiness and missing bugs that your users can see.

    If you write your tests following Playwright best practices, you should be able to swap state management solutions, or even UI frameworks, without having to change any tests. In practice, it's not always possible, but the extent to which you can embrace that philosophy, the better. If you'd followed these principles from the start, you'd never have had to ask the question here! (But it's never too late to stop testing implementation details, now that you've experienced the problems caused by doing so firsthand).

    By the way, don't let coverage concerns make you want to test implementation details. You can measure coverage transitively without knowing the implementation details--clicking a button triggers a callback and state changes, giving you coverage.