diff --git a/packages/playwright-ct-vue2/registerSource.mjs b/packages/playwright-ct-vue2/registerSource.mjs
index f4ef0ee68c..c27981d8e5 100644
--- a/packages/playwright-ct-vue2/registerSource.mjs
+++ b/packages/playwright-ct-vue2/registerSource.mjs
@@ -42,6 +42,20 @@ function createChild(child, h) {
return typeof child === 'string' ? child : createWrapper(child, h);
}
+/**
+ * Exists to support fallthrough attributes:
+ * https://vuejs.org/guide/components/attrs.html#fallthrough-attributes
+ * @param {any} Component
+ * @param {string} key
+ * @return {boolean}
+ */
+function componentHasKeyInProps(Component, key) {
+ if (Array.isArray(Component.props))
+ return Component.props.includes(key);
+
+ return Object.entries(Component.props).flat().includes(key);
+}
+
/**
* @param {Component} component
* @param {import('vue').CreateElement} h
@@ -96,7 +110,7 @@ function createComponent(component, h) {
const event = key.substring('v-on:'.length);
nodeData.on[event] = value;
} else {
- if (isVueComponent)
+ if (isVueComponent && componentHasKeyInProps(componentFunc, key))
nodeData.props[key] = value;
else
nodeData.attrs[key] = value;
diff --git a/tests/components/ct-react-vite/index.html b/tests/components/ct-react-vite/index.html
index 38f3861103..cf3a13303c 100644
--- a/tests/components/ct-react-vite/index.html
+++ b/tests/components/ct-react-vite/index.html
@@ -2,7 +2,7 @@
-
+
Vite App
diff --git a/tests/components/ct-react-vite/src/App.tsx b/tests/components/ct-react-vite/src/App.tsx
index 1a5c71390f..8a1bf0f964 100644
--- a/tests/components/ct-react-vite/src/App.tsx
+++ b/tests/components/ct-react-vite/src/App.tsx
@@ -1,5 +1,5 @@
import { useState } from 'react'
-import logo from './components/logo.svg'
+import logo from './assets/logo.svg'
import './App.css'
function App() {
diff --git a/tests/components/ct-react-vite/src/components/Button.tsx b/tests/components/ct-react-vite/src/components/Button.tsx
index 78b0a7791f..2b44784ac4 100644
--- a/tests/components/ct-react-vite/src/components/Button.tsx
+++ b/tests/components/ct-react-vite/src/components/Button.tsx
@@ -1,7 +1,13 @@
+import { ButtonHTMLAttributes } from "react";
+
type ButtonProps = {
title: string;
onClick?(props: string): void;
-}
-export default function Button(props: ButtonProps) {
- return
+ className?: string;
+} & ButtonHTMLAttributes;
+
+export default function Button({ onClick, title, ...attributes }: ButtonProps) {
+ return
}
diff --git a/tests/components/ct-react-vite/src/tests.spec.tsx b/tests/components/ct-react-vite/src/tests.spec.tsx
index 38b525ada1..987dcf5ed1 100644
--- a/tests/components/ct-react-vite/src/tests.spec.tsx
+++ b/tests/components/ct-react-vite/src/tests.spec.tsx
@@ -13,6 +13,11 @@ test('render props', async ({ mount }) => {
await expect(component).toContainText('Submit');
});
+test('render attributes', async ({ mount }) => {
+ const component = await mount()
+ await expect(component).toHaveClass('primary');
+});
+
test('renderer updates props without remounting', async ({ mount }) => {
const component = await mount()
await expect(component.locator('#props')).toContainText('9001')
diff --git a/tests/components/ct-react/src/components/Button.tsx b/tests/components/ct-react/src/components/Button.tsx
index 78b0a7791f..2b44784ac4 100644
--- a/tests/components/ct-react/src/components/Button.tsx
+++ b/tests/components/ct-react/src/components/Button.tsx
@@ -1,7 +1,13 @@
+import { ButtonHTMLAttributes } from "react";
+
type ButtonProps = {
title: string;
onClick?(props: string): void;
-}
-export default function Button(props: ButtonProps) {
- return
+ className?: string;
+} & ButtonHTMLAttributes;
+
+export default function Button({ onClick, title, ...attributes }: ButtonProps) {
+ return
}
diff --git a/tests/components/ct-react/src/tests.spec.tsx b/tests/components/ct-react/src/tests.spec.tsx
index 5a79c245c8..049581ac62 100644
--- a/tests/components/ct-react/src/tests.spec.tsx
+++ b/tests/components/ct-react/src/tests.spec.tsx
@@ -16,6 +16,11 @@ test('render props', async ({ mount }) => {
await expect(component).toContainText('Submit');
});
+test('render attributes', async ({ mount }) => {
+ const component = await mount()
+ await expect(component).toHaveClass('primary');
+})
+
test('renderer updates props without remounting', async ({ mount }) => {
const component = await mount()
await expect(component.locator('#props')).toContainText('9001')
diff --git a/tests/components/ct-solid/src/components/Button.tsx b/tests/components/ct-solid/src/components/Button.tsx
index 78b0a7791f..07ed57e61d 100644
--- a/tests/components/ct-solid/src/components/Button.tsx
+++ b/tests/components/ct-solid/src/components/Button.tsx
@@ -1,7 +1,13 @@
+import type { JSX } from "solid-js";
+
type ButtonProps = {
title: string;
onClick?(props: string): void;
-}
-export default function Button(props: ButtonProps) {
- return
+ className?: string;
+} & JSX.ButtonHTMLAttributes;
+
+export default function Button({ onClick, title, ...attributes }: ButtonProps) {
+ return
}
diff --git a/tests/components/ct-solid/src/tests.spec.tsx b/tests/components/ct-solid/src/tests.spec.tsx
index 00804374f3..3542e3ef7b 100644
--- a/tests/components/ct-solid/src/tests.spec.tsx
+++ b/tests/components/ct-solid/src/tests.spec.tsx
@@ -12,6 +12,11 @@ test('render props', async ({ mount }) => {
await expect(component).toContainText('Submit');
});
+test('render attributes', async ({ mount }) => {
+ const component = await mount()
+ await expect(component).toHaveClass('primary');
+})
+
test('execute callback when the button is clicked', async ({ mount }) => {
const messages: string[] = [];
const component = await mount(
diff --git a/tests/components/ct-vue-vite/src/notation-jsx.spec.tsx b/tests/components/ct-vue-vite/src/notation-jsx.spec.tsx
index 7e7b20a2cc..82e84e6275 100644
--- a/tests/components/ct-vue-vite/src/notation-jsx.spec.tsx
+++ b/tests/components/ct-vue-vite/src/notation-jsx.spec.tsx
@@ -13,6 +13,11 @@ test('render props', async ({ mount }) => {
await expect(component).toContainText('Submit')
})
+test('render attributes', async ({ mount }) => {
+ const component = await mount()
+ await expect(component).toHaveClass('primary');
+});
+
test('renderer updates props without remounting', async ({ mount }) => {
const component = await mount()
await expect(component.locator('#props')).toContainText('9001')
diff --git a/tests/components/ct-vue2-cli/src/notation-jsx.spec.tsx b/tests/components/ct-vue2-cli/src/notation-jsx.spec.tsx
index cdd3125bd8..797d08f44c 100644
--- a/tests/components/ct-vue2-cli/src/notation-jsx.spec.tsx
+++ b/tests/components/ct-vue2-cli/src/notation-jsx.spec.tsx
@@ -12,6 +12,11 @@ test('render props', async ({ mount }) => {
await expect(component).toContainText('Submit')
})
+test('render attributes', async ({ mount }) => {
+ const component = await mount()
+ await expect(component).toHaveClass('primary');
+});
+
test('renderer updates props without remounting', async ({ mount }) => {
const component = await mount()
await expect(component.locator('#props')).toContainText('9001')