Are you having trouble setting up component tests with Playwright and Quasar? There’s an app extension for Quasar using Cypress, but what about Quasar using Playwright? We’ll start with a simple example that works end to end, then fix the issues that come with component testing in Playwright.
End to End Testing
For this example, I’ve created a simple example component using a Pinia store. Whenever you click the increment button, the count will increment by 1 and be rendered in the Vue component. Note that I am using a Quasar CLI project that is set up to use Tailwind.
<template>
<div class="flex flex-col items-center">
<div
data-testid="count"
class="text-3xl"
>
{{ exampleStore.count }}
</div>
<q-btn
class="font-bold text-2xl"
icon="mdi-heart"
data-testid="increment"
@click="exampleStore.count++"
>
Increment
</q-btn>
</div>
</template>
<script setup lang="ts">
import { useExampleStore } from '../stores/exampleStore';
const exampleStore = useExampleStore();
</script>
HellowWorld.vueimport { defineStore } from 'pinia';
export const useExampleStore = defineStore('example', () => {
const count = 0;
return {
count,
};
});
exampleStore.tsRender this component on the index page and you should get something like this. Make sure you have Quasar set up as a Vue plugin or using the Quasar CLI (which I highly recommend). Note that my Quasar setup is set to dark mode by default.
Next we’ll run a basic end to end test using Playwright. Run this command to install Playwright and configure it in your project. Note that you can run the create command inside your current Quasar project without mucking things up, it just creates a config file and a tests folder.
yarn add -D playwright
yarn create playwright
BashAdd this to file to the tests
folder as index.spec.ts
.
import { test, expect } from '@playwright/test';
test('count should increment when button is pressed', async ({ page }) => {
await page.goto('http://localhost:9000');
await expect(page.getByTestId('count')).toHaveText('0');
await page.getByTestId('increment').click();
await expect(page.getByTestId('count')).toHaveText('1');
});
index.spec.tsTo run this end to end test, use this command to open the Playwright UI. From here, you’ll be able to see a complete timeline of how the test was run. The Playwright UI has a much better UX than the Cypress web UI.
yarn run playwright test --ui
BashHit the play button on the left, and you should see the test pass alongside a thorough timeline of events.
This is one way of testing your Quasar app, but what if you want to test the component in isolation, the way you would with Cypress’s component testing?
Component Testing
Playwright offers experimental component testing, giving you similar component tests to Cypress with the power of the Playwright UI. Setting it up is as simple as running this command in your project folder:
yarn create playwright --ct
BashLet’s write an example component test in the same folder as HellowWorld.vue
import { test, expect } from '@playwright/experimental-ct-vue';
import HelloWorld from './HelloWorld.vue';
test.use({ viewport: { width: 500, height: 500 } });
test('should increment count when button is clicked', async ({ mount }) => {
const component = await mount(HelloWorld);
const count = component.getByTestId('count');
await expect(count).toContainText('0');
await component.getByTestId('increment').click();
await expect(count).toContainText('1');
});
HelloWorld.test.tsTo run our component tests in the UI, we use this command.
yarn run ct-test --ui
# or alternatively
yarn playwright test -c playwright-ct.config.ts
BashIf you have issues with Playwright picking up the correct test files, set a glob pattern inside of playwright-ct.config.ts
using the testMatch
attribute until you get the desired results.
If we try running our component tests as is, our component won’t render.
What gives? Well, in our end to end tests, Quasar is serving the traffic and sends us our components already processed through its plugins. But when we run our component tests with Playwright, it’s starting its own Vite server then serving us the mounted components individually. This Vite server doesn’t have our configured plugins.
This is the same way Cypress works. If you were using Cypress, this is when you could install the Cypress Quasar App Extension to get component testing out of the box.
The Fix
When you ran the create --ct
command earlier, it created a new folder called playwright
which contains index.html
and index.ts
.
index.ts
is equivalent to the components.ts
support file in Cypress — it is code that is executed before your components are mounted that gives you a chance to import necessary styles, plugins, etc.
We’re going to use this file to install the Quasar plugin using the Vite plugin method.
Inside of index.ts
put in the following code.
// Import styles, initialize component theme here.
import 'quasar/src/css/index.sass'; // Or 'quasar/dist/quasar.prod.css' if no CSS preprocessor is installed
// Change this if you have a different entrypoint for the main scss.
import '../src/css/app.scss'; // Or 'src/css/app.css' if no CSS preprocessor is installed
import '../src/css/tailwind.css'; // if you're using tailwind like me
// ICON SETS
// If you use multiple or different icon-sets then the default, be sure to import them here.
import 'quasar/icon-set/mdi-v7';
import '@quasar/extras/mdi-v7/mdi-v7.css';
import { beforeMount } from '@playwright/experimental-ct-vue/hooks';
import { Quasar, Dialog, Notify } from 'quasar';
import { createPinia } from 'pinia';
beforeMount(async ({ app }) => {
app.use(createPinia());
app.use(Quasar, {
plugins: {
Dialog,
Notify,
},
});
});
playwright/index.tsIn the beforeMount
hook provided by Playwright, you have access to the Vue 3 App state similar to Quasar boot files. From here we can mount whatever plugins we need, including Pinia, Quasar, and any Quasar plugins our application uses. This isn’t well documented in the Playwright docs and I had to go digging in the source code for it.
Make these changes to playwright-ct.config.ts
import { defineConfig, devices } from '@playwright/experimental-ct-vue';
import vue from '@vitejs/plugin-vue';
import { quasar, transformAssetUrls } from '@quasar/vite-plugin';
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
...
use: {
...
ctViteConfig: {
plugins: [
vue({ template: { transformAssetUrls } }),
AutoImport({
imports: [
'vue',
'vue-router',
'@vueuse/head',
'pinia',
'quasar',
{
'@/store': ['useStore'],
},
],
dts: 'src/auto-imports.d.ts',
eslintrc: {
enabled: true,
},
}),
Components({
dirs: ['src/components'],
extensions: ['vue'],
}),
quasar(),
],
},
},
...
});
playwright-ct.config.tsPlaywright’s ctViteConfig
lets us override parts of the Vite config that it uses to serve our components. The AutoImport
and Components
plugins here aren’t strictly necessary, but the Typescript checker will throw a fit if they aren’t there. If your project can’t find those imports, make sure you have unplugin-vue-components
and unplugin-auto-import
installed in your package.json
. Make sure you have the transform
AssetUrls imported correctly, per the Quasar documentation.
If you did everything correctly, your Quasar components should now render correctly in your Playwright component tests!
If they still aren’t rendering correctly, try uncommenting the App.use...
block inside of index.ts
, running the tests, the uncommenting it again. The component testing support is still experimental and somewhat buggy. If all else fails, restarting your system should do the trick.
If you get the error TypeError: buildInfo.deps[component] is not iterable
, commit your changes, reset your branch to the last known good commit and then reset with your changes. I got this error and my tests would still work in CI, but not locally. Playwright seems to have issues caching certain parts of the Vite build, and like the above issue, the best thing you can do it compel Playwright to “reset” whatever cache its using for the build.