mirror of
https://github.com/microsoft/playwright.git
synced 2025-01-05 19:04:43 +03:00
feat: Support React forwards refs and memo (#23262)
This PR fixes the react selector behavior to support components that are wrapped by the memo or forwardRef React builtin functions. Previously these components couldn't be selected. This PR fixes that behavior, enabling selecting those components. Current behavior: ``` const Foo = memo(() => <div id="foo_component" />); Foo.displayName = "Foo"; ... playwright.$("_react=Foo") -> undefined ``` Fixed behavior: ``` const Foo = memo(() => <div id="foo_component" />); Foo.displayName = "Foo"; ... playwright.$("_react=Foo") -> <div id ="foo_component" /> ```
This commit is contained in:
parent
38c89df330
commit
7e6e5f0706
@ -43,13 +43,23 @@ type ReactVNode = {
|
||||
_renderedChildren?: any[],
|
||||
};
|
||||
|
||||
function getFunctionComponentName(component: any) {
|
||||
return component.displayName || component.name || 'Anonymous';
|
||||
}
|
||||
|
||||
function getComponentName(reactElement: ReactVNode): string {
|
||||
// React 16+
|
||||
// @see https://github.com/baruchvlz/resq/blob/5c15a5e04d3f7174087248f5a158c3d6dcc1ec72/src/utils.js#L16
|
||||
if (typeof reactElement.type === 'function')
|
||||
return reactElement.type.displayName || reactElement.type.name || 'Anonymous';
|
||||
if (typeof reactElement.type === 'string')
|
||||
return reactElement.type;
|
||||
if (reactElement.type) {
|
||||
switch (typeof reactElement.type) {
|
||||
case 'function':
|
||||
return getFunctionComponentName(reactElement.type);
|
||||
case 'string':
|
||||
return reactElement.type;
|
||||
case 'object': // support memo and forwardRef
|
||||
return reactElement.type.displayName || (reactElement.type.render ? getFunctionComponentName(reactElement.type.render) : '');
|
||||
}
|
||||
}
|
||||
|
||||
// React 15
|
||||
// @see https://github.com/facebook/react/blob/2edf449803378b5c58168727d4f123de3ba5d37f/packages/react-devtools-shared/src/backend/legacy/renderer.js#L59
|
||||
|
@ -38,7 +38,7 @@ function ColorButton (props) {
|
||||
return e('button', {className: props.color, disabled: !props.enabled}, 'button ' + props.nested.index);
|
||||
}
|
||||
|
||||
function ButtonGrid() {
|
||||
const ButtonGrid = React.memo(function() {
|
||||
const buttons = [];
|
||||
for (let i = 0; i < 9; ++i) {
|
||||
buttons.push(e(ColorButton, {
|
||||
@ -51,7 +51,9 @@ function ButtonGrid() {
|
||||
}, null));
|
||||
};
|
||||
return e(React.Fragment, null, ...buttons);
|
||||
}
|
||||
});
|
||||
|
||||
ButtonGrid.displayName = "ButtonGrid";
|
||||
|
||||
class BookItem extends React.Component {
|
||||
render() {
|
||||
|
@ -38,7 +38,7 @@ function ColorButton (props) {
|
||||
return e('button', {className: props.color, disabled: !props.enabled}, 'button ' + props.nested.index);
|
||||
}
|
||||
|
||||
function ButtonGrid() {
|
||||
const ButtonGrid = React.memo(function() {
|
||||
const buttons = [];
|
||||
for (let i = 0; i < 9; ++i) {
|
||||
buttons.push(e(ColorButton, {
|
||||
@ -51,7 +51,9 @@ function ButtonGrid() {
|
||||
}, null));
|
||||
};
|
||||
return e(React.Fragment, null, ...buttons);
|
||||
}
|
||||
});
|
||||
|
||||
ButtonGrid.displayName = "ButtonGrid";
|
||||
|
||||
class BookItem extends React.Component {
|
||||
render() {
|
||||
|
@ -124,6 +124,11 @@ for (const [name, url] of Object.entries(reacts)) {
|
||||
await expect(page.locator(`_react=BookItem`)).toHaveCount(6);
|
||||
});
|
||||
|
||||
it('should work with react memo', async ({ page }) => {
|
||||
it.skip(name === 'react15' || name === 'react16', 'Class components dont support memo');
|
||||
await expect(page.locator(`_react=ButtonGrid`)).toHaveCount(9);
|
||||
});
|
||||
|
||||
it('should work with multiroot react', async ({ page }) => {
|
||||
await it.step('mount second root', async () => {
|
||||
await expect(page.locator(`_react=BookItem`)).toHaveCount(3);
|
||||
|
Loading…
Reference in New Issue
Block a user