microsoft / playwright

Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.
https://playwright.dev
Apache License 2.0
65.8k stars 3.58k forks source link

[Feature]: ability to pass components to `HooksConfig` #30453

Closed sand4rt closed 3 months ago

sand4rt commented 5 months ago

🚀 Feature Request

The ability to pass components to HooksConfig, probably by using the resolveImportRef to resolve the hooksConfig;

Example

beforeMount(async ({ app, hooksConfig }) => {
  for (const [name, component] of Object.entries(hooksConfig?.components || {}))
    app.component(name, component);
});
import { test, expect } from '@playwright/experimental-ct-vue';
import DefaultSlot from '@/components/DefaultSlot.vue';
import Button from '@/components/Button.vue';

test('render a component as slot', async ({ mount }) => {
  const component = await mount(DefaultSlot, {
    slots: {
      default: '<Button title="Submit" />',
    },
    hooksConfig: {
      components: { Button }
    }
  });
  await expect(component).toContainText('Submit');
});

Motivation

It simplifies testing slots and other use cases such as providing plugins

pavelfeldman commented 5 months ago

Could you help me understand why we need to register component in beforeMount for this use case to work? I was hoping it would work out of the box.

sand4rt commented 5 months ago

Vue is unable to locate the <Button /> component when it's passed to the slot as a string.

So this test will fail because there's no reference from the <Button /> string to the actual component:

-- DefaultSlot.vue <-- NOTE: Button component not registered here
<template>
  <main>
    <slot />
  </main>
</template>
test('render a component as slot', async ({ mount }) => {
  const component = await mount(DefaultSlot, {
    slots: {
      default: '<Button title="Submit" />', <-- NOTE: There is no reference
    },
  });
  await expect(component).toContainText('Submit');
})

Vue test utils follows a similar approach: https://test-utils.vuejs.org/api/#global-components. Their mount.global is kind of similar to Playwright's mount.hooksConfig: https://test-utils.vuejs.org/api/#global

pavelfeldman commented 5 months ago

Ah, I missed the string quotes around \<Button>, it makes sense to me now. In terms of the proposed shape of the API, I see hooksConfig as a user object w/o schema. In this case it seems like Vue would benefit from framework-specific components property that would point to the component registry. Does it make sense?

sand4rt commented 5 months ago

I see hooksConfig as a user object w/o schema

I actually use hooksConfig specifically for registering global things like mixins, components, plugins and directives. It's been a while, but hooksConfig was introduced two years ago for this reason right?: https://github.com/microsoft/playwright/issues/14416

In this case it seems like Vue would benefit from framework-specific components property that would point to the component registry. Does it make sense?

It is a common scenario, but i don't think it needs a specific API as it saves just a few lines of code. This would also be the first API that's different from the rest and i kind of like the flexibility of the hooksConfig.

It's a trade-off between a generic hooksConfig API or framework-specific APIs IMO. I recently sent something about this on Discord. With the Angular adapter we are also confronted with the question: 'domain specific or generic'

sand4rt commented 5 months ago

Another option is to put the boilerplate code in playwright-create:

// /playwright/index.ts
beforeMount(async ({ app, hooksConfig }) => {
  for (const [name, component] of Object.entries(hooksConfig?.components || {}))
    app.component(name, component);
});
pavelfeldman commented 5 months ago

Ah, so you want the user to control both sides of the story, so hooksConfig's schema is left to the user to define? That's fine with me.