From 1b81d0cad183a399629a8f24c30dc76bf55931ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rene=CC=81?= Date: Wed, 19 Aug 2026 19:13:14 +0200 Subject: [PATCH 1/3] improved selector page --- app/components/ThemeToggle.vue | 5 +- app/components/content/DocFigure.vue | 69 ++++++++++++++++++++++ app/utils/highlight.ts | 2 +- app/utils/lang.ts | 1 + content/docs/concepts/selectors.md | 82 +++++++++++++++++++++++---- package.json | 8 +-- pnpm-workspace.yaml | 4 ++ public/images/color-toggle.png | Bin 0 -> 3305 bytes 8 files changed, 151 insertions(+), 20 deletions(-) create mode 100644 app/components/content/DocFigure.vue create mode 100644 pnpm-workspace.yaml create mode 100644 public/images/color-toggle.png diff --git a/app/components/ThemeToggle.vue b/app/components/ThemeToggle.vue index fd2f37d..ed25b65 100644 --- a/app/components/ThemeToggle.vue +++ b/app/components/ThemeToggle.vue @@ -55,9 +55,10 @@ const LABEL: Record = { diff --git a/app/components/content/DocFigure.vue b/app/components/content/DocFigure.vue new file mode 100644 index 0000000..8e24d39 --- /dev/null +++ b/app/components/content/DocFigure.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/app/utils/highlight.ts b/app/utils/highlight.ts index 61c7281..4127920 100644 --- a/app/utils/highlight.ts +++ b/app/utils/highlight.ts @@ -28,7 +28,7 @@ import { type Lang, ROBOT, ROBOT_REPL, THEME } from './lang' */ /** Languages the comparison and guides need, beyond Robot Framework. */ -const BUNDLED: BundledLanguage[] = ['python', 'typescript', 'javascript', 'bash', 'json', 'yaml', 'dockerfile'] +const BUNDLED: BundledLanguage[] = ['python', 'typescript', 'javascript', 'bash', 'json', 'yaml', 'dockerfile', 'html'] const robot = { ...(rfGrammar as unknown as LanguageRegistration), diff --git a/app/utils/lang.ts b/app/utils/lang.ts index b587db4..d0e0657 100644 --- a/app/utils/lang.ts +++ b/app/utils/lang.ts @@ -30,4 +30,5 @@ export const LANG_LABEL: Record = { json: 'JSON', yaml: 'YAML', dockerfile: 'Dockerfile', + html: 'HTML', } diff --git a/content/docs/concepts/selectors.md b/content/docs/concepts/selectors.md index 5ee4cb9..8629a57 100644 --- a/content/docs/concepts/selectors.md +++ b/content/docs/concepts/selectors.md @@ -52,10 +52,41 @@ rows: --- :: + +### Example: Our Landing Page + +On our landing page, we have some buttons and links. + +One of them is a colour theme toggle, which is a button with a visible label and aria-label. It is the only button with that aria-label, so it is a perfect candidate for a `role=` selector. However, it also has a `data-testid` attribute, which is a good candidate for a `data-testid=` selector. The visible text is "DARK", which is a good candidate for a `text=` selector. + +::doc-figure{src="/images/color-toggle.png" alt="A toggle button labelled DARK in the top-right corner"} +Color toggle at top right +:: + +See the following HTML DOM snippet for the button: + +```html [Color Theme Toggle] + +``` + ### 1. `role=` — how the user finds it ```robot-repl -Click role=button[name="Save"] +Click role=button[name="DARK-Mode colour theme. Activate to change."] +# ^ matches the aria-label as exact match + +Click role=button[name*="colour theme"] +# ^ matches the aria-label which contains (*=) the substring "colour theme" + +# other examples: Click role=link[name="Get started"] Fill Text role=textbox[name="Email"] admin@example.com ``` @@ -79,10 +110,28 @@ because the element has no proper role or no accessible name, **you have found an accessibility bug**. A screen-reader user cannot identify that control either. That is worth an issue, not a workaround. +When accessible names are long and complex, `role=` selectors can be brittle, if the name changes in a redesign. +In that case, a matching by substring or regex is a good compromise, e.g. `role=button[name="colour theme"]` matches the aria-label as a substring. + +the following operators are available for matching the name: + +| Operator | Meaning | Example | +| -------- | ------- | ------- | +| `=` | exact match | `role=button[name="DARK-Mode colour theme. Activate to change."]` | +| `*=` | contains substring | `role=button[name*="Activ"]` | +| `^=` | starts with string | `role=button[name^="DARK"]` | +| `$=` | ends with string | `role=button[name$="Activate to change."]` | +| `~=` | contains one whole word | `role=button[name~="Activate"]` | +| `|=` | contains hyphenated word | `role=button[name|="DARK"]` | + +Regex is also supported when the expected text is surrounded by slashes, +e.g. `role=button[name=/^(DARK|LIGHT|CONTRAST|AUTO)-Mode colour theme/]` matches the aria-label as a regex, case-sensitively. +Regex flags can be added after the closing slash, e.g. `i` for case-insensitive matching. + ### 2. `data-testid=` — the one attribute that belongs to us ```robot-repl -Click [data-testid="checkout-submit"] +Click data-testid=theme-toggle ``` Every other attribute on the page belongs to someone else. Classes belong to the @@ -101,9 +150,11 @@ just make it deliberately rather than by default. ### 3. `text=` — what is written on it ```robot-repl -Click text=Sign in -Click "Sign in" -Click text=/^Sign in$/i +Click text=DARK # contains match +Click "DARK" # exact match + +# if the aria-label would be the text: +Click text=/^(DARK|LIGHT|CONTRAST|AUTO)-Mode colour theme/i # regex match ``` Text selectors use a user-facing property, like `role=`, which is why they rank @@ -139,6 +190,9 @@ escaped as `\#submit-button`, or written as `id=submit-button`. ### 5. `css=` — acceptable, not preferable ```robot-repl +Click button.toggle:has-text("DARK") #button with the class "toggle" that contains the text "DARK" + +# other examples: Click css=button.primary Click .checkout > button ``` @@ -152,11 +206,18 @@ exactly the things a redesign changes. A class name is a styling decision, typic CSS is the implicit default: a selector that is not obviously something else is treated as CSS. +::doc-note{kind="aside"} +CSS is way more powerful than many realise. It can select by attribute, by position, by relationship, and even by text content. See [CSS Basics and Advanced](#css-basics-and-advanced) for a full reference. +:: + ### 6. `xpath=` — the last resort ```robot-repl -Click xpath=//button[@type="submit"] -Click //div[@class="row"]//button +Click xpath=//button[contains(@class, "toggle") and contains(text(), 'DARK')] +# ^ the literal same as the CSS above, but in XPath + +Click xpath=//header//button[contains(text(),'DARK')] # text contains, preceding whitespace ignored +Click //header//button[text()=' DARK'] # exact match, whitespace matters! ``` XPath is CSS's powerful, unpleasant relative. It is more verbose for the same @@ -164,15 +225,16 @@ result, many web developers do not read it fluently, it is not web-native, and it invites selecting by document position rather than function — which is the most brittle thing you can possibly do. -It is genuinely more powerful, and occasionally something is unselectable +It is partially more powerful, and occasionally something is unselectable without it. Use it then, and only then. It is the last resort, not a -general-purpose tool. +general-purpose tool. One of the very rare occasions where it is appropriate +to use XPath is when you need to navigate relative to a reliably identified element And if you are about to paste something like this out of your browser's devtools: ```robot-repl -Click /html/body/div[3]/div/div[2]/button +Click //body/div[1]/div/header/span/button ``` **DON'T!** That selector describes where the button sits today, not what it is. diff --git a/package.json b/package.json index 2f6f9e6..2797a71 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "robotframework-browser-org", "private": true, "type": "module", - "packageManager": "pnpm@10.30.3", + "packageManager": "pnpm@11.22.0+sha512.1ff870c4c6133dfd88fb2afc46dd13d47f09c9794b438c6fdb47ca98caf3bc16381ee0be93a091b8e3824cf01f889f46d7d9e20910fb0be1ab0fb5baa80dd621", "scripts": { "dev": "nuxt dev", "build": "pnpm libdoc && nuxt build", @@ -43,11 +43,5 @@ "sanitize-html": "^2.17.6", "vitest": "^4.1.10", "yaml": "^2.9.0" - }, - "pnpm": { - "onlyBuiltDependencies": [ - "esbuild", - "better-sqlite3" - ] } } diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..c4ac1e3 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + better-sqlite3: true + esbuild: true + sharp: true diff --git a/public/images/color-toggle.png b/public/images/color-toggle.png new file mode 100644 index 0000000000000000000000000000000000000000..aea757d023fb90dbff867ff09dc7ec97cd09a1dc GIT binary patch literal 3305 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 literal 0 HcmV?d00001 From c5983f1e148895ba521ef4eff6cca34bd20a6d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= <41592183+Snooz82@users.noreply.github.com> Date: Wed, 19 Aug 2026 19:19:45 +0200 Subject: [PATCH 2/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- content/docs/concepts/selectors.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/docs/concepts/selectors.md b/content/docs/concepts/selectors.md index 8629a57..0df8920 100644 --- a/content/docs/concepts/selectors.md +++ b/content/docs/concepts/selectors.md @@ -110,10 +110,10 @@ because the element has no proper role or no accessible name, **you have found an accessibility bug**. A screen-reader user cannot identify that control either. That is worth an issue, not a workaround. -When accessible names are long and complex, `role=` selectors can be brittle, if the name changes in a redesign. -In that case, a matching by substring or regex is a good compromise, e.g. `role=button[name="colour theme"]` matches the aria-label as a substring. +When accessible names are long and complex, `role=` selectors can be brittle if the name changes in a redesign. +In that case, matching by substring or regex is a good compromise, e.g. `role=button[name*="colour theme"]` matches the aria-label by substring. -the following operators are available for matching the name: +The following operators are available for matching the name: | Operator | Meaning | Example | | -------- | ------- | ------- | From b1caca51c936334010546ecd5d7a9fee6f6df4e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rene=CC=81?= Date: Wed, 19 Aug 2026 20:01:00 +0200 Subject: [PATCH 3/3] fixed broken table --- content/docs/concepts/selectors.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/content/docs/concepts/selectors.md b/content/docs/concepts/selectors.md index 0df8920..a13291f 100644 --- a/content/docs/concepts/selectors.md +++ b/content/docs/concepts/selectors.md @@ -122,7 +122,7 @@ The following operators are available for matching the name: | `^=` | starts with string | `role=button[name^="DARK"]` | | `$=` | ends with string | `role=button[name$="Activate to change."]` | | `~=` | contains one whole word | `role=button[name~="Activate"]` | -| `|=` | contains hyphenated word | `role=button[name|="DARK"]` | +| `\|=` | contains hyphenated word | `role=button[name\|="DARK"]` | Regex is also supported when the expected text is surrounded by slashes, e.g. `role=button[name=/^(DARK|LIGHT|CONTRAST|AUTO)-Mode colour theme/]` matches the aria-label as a regex, case-sensitively. @@ -464,6 +464,14 @@ you something false. One useful distinction to xpath: CSS can select **following** siblings with `+` and `~`, but it has no simple equivalent of XPath's `..` for selecting a parent directly. +### Attribute selection with comparison operators + +CSS supports a few comparison operators for attributes similar to the ones used in `role=` selectors. + +So the same operators are available: `=`, `*=`, `^=`, `$=`, `~=`, and `|=`. +[See role table above](#_1-role-how-the-user-finds-it) for explanation. +However RegEx selection is not possible. + ### Filtering inside a CSS selector Playwright adds pseudo-classes to CSS that stay inside one step, rather than