Could we have the ability to set the id attribute of a component?
Use Case
I use selenium to automate testing of my apps.
The selectors for components like buttons, links and text boxes can become very cumbersome. They are also brittle and often break if the component moves on the form (e.g. Into a different container).
Options
I can work around this now with some js, but it would be good not to have to do that.
This could be an optional attribute on a component. If I set it, it’s up to me to maintain the uniqueness.
Alternatively, it could just be set on all components using a uuid or similar.
I always wondered, given that IDs already exist in the (computed) web page, if I was going to assign an ID, how the heck would I avoid name collisions? Wouldn’t I need a way to discover all the existing IDs?
Or is there a way to generate a guaranteed unique ID, that won’t conflict with anything currently present, or likely to be added later?
I wonder if you could mix in the python builtin id( ) function to avoid collisions, in skulpt its just an integer counter that gets incremented from the start of the app on every creation of a new object.
I do my own thing for repeating panels. You can’t rely on anything else always giving the same result for the same component (that I’ve found thus far)
Hello I’ve been linked here from another thread, how exactly does this solution work?
What exactly is the property function and what are its arguments?
And if I do this in the startup module, but then display other modules inside it at runtime, will those modules inherit this adjustment, or do I need to duplicate it across multiple modules?
That property function is the equivalent of defining getter and setter methods on the class using the @property decorator, etc.:
Those modules inherit this adjustment. When you import the same Python module multiple times, it doesn’t import it again, it uses what’s already been imported.
Hi all, I came across this and some other associated discussions recently as I was looking to do some test automation myself using Playwright. Quite a few web UI test automation frameworks support looking for alternative element attributes such as data-testid which are separated from the id attribute which should always be unique across the page etc. I wanted to avoid using the id attribute for this reason, and because other web frameworks don’t allow you to control the id.
In Playwrights case there is a specific locator method called page.getByTestId() (see Locators | Playwright) ) which by default looks for the attribute mentioned above, or what ever you have set it to in other config.
I used a solution similar to the above, but I extended Component with a new data_test_id read-only property on startup of my main form. Then in various other places I just set that property.
Here is an example extracts from my code (easy to swap out the attribute name):
from anvil.js import get_dom_node
class FormMain(FormMainTemplate):
def __init__(self, **properties):
# Paste the following block into your solution starting form...
setattr(
Component,
'data_test_id',
property(
fset=lambda comp, val: get_dom_node(comp).setAttribute('data-testid', val)
)
)
self.init_components(**properties)
, then in one of your other pages/components you can simply do something like:
class Something(SomethingTemplate):
def __init__(self, **properties):
self.init_components(**properties)
# Set the test id value where ever you need to
self.your_component.data_test_id = 'Something to search by'
Thought I would post this just in case there are other people who want to use an alternative attribute for their testing.
Playwright sounds like they have the right approach! An attribute is much less disruptive than requiring an ID.
As a general point of style, “monkey-patching” like some of the examples in this thread (ie adding extra properties and things to system classes to do what you want) is something the Python community discourages. It’s brittle, because it could break (or cause mysterious breakages in your code) when we update the inner workings of the Component class. It’s also harder for readers of your code to understand (in this case, they’ll look at the property, go “wait, that’s not built in on all Anvil components, is it?”, try it in their own code, get frustrated, and eventually search over your whole code-base to find the form whose __init__ method installed the monkey patch!).
Instead, the Pythonic way to do it is to use a function. For example, you could put this in a module called TestHooks:
from anvil.js import get_dom_node
def set_test_id(component, test_id):
get_dom_node(component).setAttribute("data-testid", test_id)
Then in your forms, it’s no more lines of code than your version:
from TestHooks import set_test_id
# ...
class Something(SomethingTemplate):
def __init__(self, **properties):
# ...
set_test_id(self.your_component, "Something to search by")
Just as simple, but much easier for someone else to read – and much less fragile when we update the Component class!