DeploySentinel / Recorder

A browser extension that generates Cypress, Playwright and Puppeteer test scripts from your interactions 🖱 ⌨
https://deploysentinel.com/recorder
Apache License 2.0
444 stars 36 forks source link

React selectors #28

Open pdufour opened 2 years ago

pdufour commented 2 years ago

I've noticed if you are using React things are typically a lot easier to select using the new(ish) React selectors playwright supports. https://playwright.dev/docs/selectors#react-selectors. This is because a lot of times you would not have assigned unique classes / ids to your dom elements unless you set up your app with this concept from the start.

Would you accept a PR to add support for using React selectors instead of classes etc? Maybe have an option in the UI to toggle between the two? And any idea how I could get started with those changes?

Thanks.

MikeShi42 commented 2 years ago

Hi @pdufour! Sorry I totally missed this issue in my GH notifications. Absolutely would love contributions. Here's two approaches I can think of (one being the pragmatic one, the other being quite ambitious).

Hacky Solution

The hackiest and likely easiest way is to create a new ScriptType defined as possibly "PlaywrightReact" or something. ScriptType is what we define when we show the dropdown options in the UI for what library a user wants to generate code for, this way a user can choose between "Playwright" or "Playwright for React" or something like that in the library dropdown to specify what kind of selectors they want.

Next you'll need to actually generate the selector that can be used, I'm not sure if this is easy to do so as you'll need to convert HTMLElement -> component name (perhaps there is a React API to do so?). Though if you can do that, you'll just need to add a new property returned from genSelectors, maybe call it reactComponentNameSelector or something depending on what the logic is. The property should be nullable if a suitable selector is not found.

Once a new script type is defined and the selector is returned from genSelectors, you'll want to update getBestSelectorForAction to actually use your selector. You can see the function basically takes some action data (the type of action like is it a click, hover, type, etc.), and possibly some data about the target of the action (tagname) and then returns a single selector value, going down a specific chain of options returned from genSelectors.

Once this is done, you'll want to update the script generator logic to actually output code for your new ScriptType, you likely only need to add a 1-line case statement to the genCode function here to make sure your new PlaywrightReact script type uses the PlaywrightScriptBuilder as well (since it doesn't seem like any changes to the builder need to happen, just the selector logic you've done above).

At this point, you'll only need to update the UI to actually show the option to the user, which should only involve adding a new option to this component, but I'd double check when manually testing just in case we haven't migrated any selectors over to this centralized selector component.

Nicer solution?

A nicer solution is likely allowing the user to choose (at action-time or post code-gen) what kind of selector they'd like to use for a given action. This would likely require a pretty big change to the genCode function as well as the UI in general to support that. It's where we'd likely need to go long term, but I think the hacky solution at least allows this new PlaywrightReact logic to look like the rest of our existing patterns, so a future migration would be simple and not incur a lot of new tech debt.

If you want to take on a nicer solution, the above functions are a good starting point to look at (I'd imagine it's either modifying codeGen to take in additional options for user overrides on a per-action basis, or baking the user override selector option into the action objects themselves). The only other things I'd look at is probably our CodeGen React component which will need to be updated to support user interaction.


Sorry I know the above is probably a mouthful, but hopefully can serve as some insight into the inner mechanisms of the extension :)