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
63.81k stars 3.46k forks source link

[Feature] Component testing scoped slots & function as child #18758

Open sand4rt opened 1 year ago

sand4rt commented 1 year ago

The ability to deal with scoped slots:

Vue API

Docs: https://vuejs.org/guide/components/slots.html#scoped-slots

// ScopedSlot.vue
<template>
  <div><slot name="slotName" v-bind="{ prop: 'scoped-prop' }" /></div>
</template>
// ScopedSlot.test.ts
import ScopedSlot from './components/ScopedSlot.vue';

test('scoped slot function', async ({ mount }) => {
  const component = await mount(ScopedSlot, {
    slots: {
      slotName: params => `<p>${params.prop}</p>`
    }
  });
  await expect(component.getByRole('paragraph')).toContainText('scoped-prop');
});

Not sure yet what the .tsx API could look like. Probably:

// ScopedSlot.test.tsx
import ScopedSlot from './components/ScopedSlot.vue';

test('scoped slot', async ({ mount }) => {
  const component = await mount(<ScopedSlot>{params => <p>{params.prop}</p>}</ScopedSlot>);
  await expect(component.getByRole('paragraph')).toContainText('scoped-prop');
});

Svelte API

Docs: https://svelte.dev/docs#template-syntax-slot-slot-key-value

// ScopedSlot.svelte
<div><slot name="slotName" prop="scoped-prop" /></div>
// ScopedSlot.test.ts
import ScopedSlot from './components/ScopedSlot.svelte';

test('scoped slot function', async ({ mount }) => {
  const component = await mount(ScopedSlot, {
    slots: {
      slotName: params => `<p>${params.prop}</p>`
    }
  });
  await expect(component.getByRole('paragraph')).toContainText('scoped-prop');
});

React & Solid API

// ScopedSlot.tsx
export function ScopedSlot({ children }) {
  return <div>{children({ prop: 'scoped-prop' })}</div>
}
// ScopedSlot.test.tsx
import ScopedSlot from './components/ScopedSlot';

test('scoped slot', async ({ mount }) => {
  const component = await mount(<ScopedSlot>{params => <p>{params.prop}</p>}</ScopedSlot>);
  await expect(component.getByRole('paragraph')).toContainText('scoped-prop');
});
pavelfeldman commented 1 year ago

I wonder if we can wait and suggest using testing wrappers for components with slots for now. There is a limit of what we can support, we just need to define where it is. Let's collect upvotes for this one.

bhvngt commented 1 year ago

I was trying to use svelte-htm with the latest playwright as it neatly allows slots and other usage. I took svelte-component-test-recipes as a base and converted one of the test to its playwright equivalent. Here's the code

import { test, expect } from '@playwright/experimental-ct-svelte'
import DefaultProps from '$lib/props/DefaultProps.svelte';
import html from 'svelte-htm';

test("doesn't pass prop", async ({mount}) => {
  const cmp = html`<${DefaultProps}></${DefaultProps}>`
  const component = await mount(DefaultProps);
  await expect(component).toContainText('The answer is a mystery');
});

I am getting following error

ReferenceError: DefaultProps is not defined

   7 | // describe('Test DefaultProps component', async () => {
   8 |  test("doesn't pass prop", async ({mount}) => {
>  9 |      const cmp = html`<${DefaultProps}></${DefaultProps}>`
     |                          ^
  10 |      const component = await mount(DefaultProps);
  11 |      await expect(component).toContainText('The answer is a mystery');
  12 |  });

If I comment out line:9, the tests run fine.

I am not sure how to make the output of svelte-htm work with playwright. If that is possible than we would have solved slot and other issues quiet elegantly.

WesleyYue commented 1 year ago

I wonder if we can wait and suggest using testing wrappers for components with slots for now. There is a limit of what we can support, we just need to define where it is. Let's collect upvotes for this one.

@pavelfeldman Can you provide an example of how to use a wrapper to solve this for the react example?

sand4rt commented 1 year ago

@WesleyYue believe the idea is to create a wrapper for Form.tsx instead of mounting it directly in the test:

// Form.tsx
type FormProps = {
  children(props: { value: string }): JSX.Element;
}

export function Form(props: FormProps) {
  return <form>{props.children({ value: 'hello' })}</form>;
}
// FormWrapperForTest.tsx
import { Form } from './Form';

export function FormWrapperForTest() {
  return <Form>{props => <input value={props.value} />}</Form>
}
// Form.test.tsx
import { test, expect } from '@playwright/experimental-ct-react';
import { FormWrapperForTest } from './FormWrapperForTest';

test('works', async ({ mount }) => {
  const component = await mount(<FormWrapperForTest />);
  await expect(component.getByRole('textbox')).toHaveValue('hello');
});
WesleyYue commented 1 year ago

Ah, I see. So function as child prop only breaks if you are trying to do it in the test.tsx directly, but not if it's a transitive dependency deeper down in the component tree

pavelfeldman commented 1 year ago

Why was this issue closed?

Thank you for your involvement. This issue was closed due to limited engagement (upvotes/activity), lack of recent activity, and insufficient actionability. To maintain a manageable database, we prioritize issues based on these factors.

If you disagree with this closure, please open a new issue and reference this one. More support or clarity on its necessity may prompt a review. Your understanding and cooperation are appreciated.