DramaFinder 2.0.0¶
DramaFinder 2.0 makes lookups strict and actions synchronous with the server:
- label lookups match the whole label;
- a label is the visible label: an
aria-labelis the accessible name, and the query builder finds it and the placeholder; - every action waits for the Vaadin round-trip it triggers;
- a typed query builder gives every element the same lookup grammar;
- the Spring Boot base class ships in the library.
1.x test suites compile unchanged, unless a test class declares its own
navigate, assertAt, assertPath or assertQueryParameter, or calls the removed
AccessibleNameLocator (see below). The getByLabel factories of components
without a visible label are deprecated. Lookups that pass part of a label, an
aria-label or a placeholder to getByLabel fail at runtime, as does a test that
asserts a state while a request is still in flight.
Read the migration guide
before upgrading. The jar also ships it, at META-INF/dramafinder/MIGRATION-2.0.md.
Breaking changes¶
Label lookups match exactly by default (#207)¶
Every factory that finds a component by its label, text, header, title or name now
matches the whole value, case-sensitively (whitespace-normalized), and applies
no .first(). TextFieldElement.getByLabel(page, "Name") no longer resolves to a
"Nickname" field, and two components sharing a label raise a Playwright
strict-mode error instead of resolving to the first one in the DOM.
Partial matching is now explicit: getByLabelContaining(Page|Locator, String) next
to every getByLabel, and ButtonElement.getByTextContaining(Page|Locator, String).
Both are case-insensitive substring matches, still without .first().
52 public factory methods change. P = 1.x matched part of the label, C = 1.x matched a differently-cased label, D = 1.x picked the first of several matches.
Field getByLabel — P C D (replacement getByLabelContaining for part of a
label; an aria-label or placeholder is no longer a label, see the next section),
on Page and Locator unless noted:
BigDecimalFieldElement,ComboBoxElement,DatePickerElement,DateTimePickerElement,EmailFieldElement,IntegerFieldElement,MultiSelectComboBoxElement,NumberFieldElement,SelectElement,SliderElement,TextAreaElement,TextFieldElement,TimePickerElement—getByLabel(Page, String)andgetByLabel(Locator, String)(26 methods)PasswordFieldElement.getByLabel(Page, String)SelectElement.getByLabelnow matches the visible label only, and has no placeholder fallback. The 1.x lookup matched the value button's name, which includes the selected value, so it broke after a selection.
Other getByLabel (replacement getByLabelContaining):
- P C D:
BreadcrumbsElement.getByLabel(Page, String),CheckboxElement.getByLabel(Page, String),CheckboxGroupElement.getByLabel(Page|Locator, String),CustomFieldElement.getByLabel(Page|Locator, String)(also behindCustomFieldElement.getField(Class, String)),ListBoxElement.getByLabel(Page, String),RadioButtonGroupElement.getByLabel(Page, String),SideNavigationElement.getByLabel(Page, String),SwitchElement.getByLabel(Page, String) - P C:
ButtonElement.getByLabel(Page, String),MenuBarElement.getByLabel(Page, String),MenuElement.getByLabel(Page, String),PopoverElement.getByLabel(Page, String)
Button text — P C (replacement getByTextContaining):
ButtonElement.getByText(Page, String), ButtonElement.getByText(Locator, String).
The name is the aria-label, or else the button's own text. An open tooltip is no
longer part of it; in 1.x the lookup stopped matching after a hover.
Headers, titles, summaries, button text (no …Containing variant):
- P C:
DialogElement.getByHeaderText(Page, String),ConfirmDialogElement.getByHeaderText(Page, String),LoginOverlayElement.getByTitle(Page, String),CardElement.getByTitle(Page|Locator, String),DetailsElement.getBySummaryText(Page, String)(now the summary's accessible name) - P C D:
UploadElement.getByButtonText(Page, String)
Avatar name — D only: AvatarElement.getByName(Page|Locator, String).
The next section narrows what a label is; the list above names the 2.0 methods whichever way a 1.x test used them.
These keep their 1.x matching: BadgeElement.getByText,
NotificationElement.getByText, TooltipElement.getByText and
VirtualListElement.getItemByText (documented "contains" helpers). So do the item
lookups inside containers, which were already exact since 1.2.0.
A label is the visible label (#214)¶
getByLabel matches the text a component shows in its own label slot, and nothing
else. An aria-label (or aria-labelledby text) is the component's accessible
name; a placeholder is neither. In 1.x getByLabel matched all three, while the
query builder's withLabel matched only the visible label, so the two disagreed.
Now getByLabel is the same lookup as withLabel, and a test checks they stay in
step.
The 1.x getByLabel relied on |
Use in 2.0 |
|---|---|
| the whole visible label | getByLabel, unchanged |
| part of it, or another case | getByLabelContaining |
an aria-label, or unknown (closest to 1.x) |
find(page, X.class).withAccessibleNameContaining(..) |
| the placeholder | find(page, X.class).withPlaceholder(..) |
- 21 elements with a visible label match it only: the text, number, date and
time fields, combo boxes, select, slider, checkbox, switch, checkbox and radio
groups, custom field, side nav and side-nav item. Each has
getByLabelandgetByLabelContainingwith aPageand aLocatoroverload; Password field, Switch, Radio group, Side nav, Checkbox and Side-nav item gain the missing ones. - 7 elements without a visible label keep a
getByLabelthat matches their accessible name, now deprecated for removal in 3.0: Breadcrumbs, ListBox, MenuBar, Menu, MenuItem and Popover (usefind(..).withAccessibleName(..)), and Button (usegetByText).MenuElementcan now be used withfind. withAccessibleNameContainingis on every query, andwithPlaceholdernow also on Select (while no value is selected) and DateTimePicker (its date placeholder).CustomFieldElement.getField(Class, String)finds a wrapped field by its whole accessible name.AccessibleNameLocatoris removed. Custom elements use the query builder.ViewOutlinesuggestsgetByLabelonly for a visible label, andwithAccessibleName/withPlaceholderotherwise.
Actions wait for the Vaadin server round-trip (#209)¶
Every element action that can reach the server now returns only once Vaadin Flow
has no request in flight. This covers click, setValue, clear,
check/uncheck, selectItem, key presses, hoverTarget, focus/blur,
upload, setProperty and the rest. A getter called right after an action reads
the state the server produced. In 1.x only setValue and a few date, slider and
checkbox-group methods waited.
The property setters wait too: setMinLength, setMaxLength, setPattern,
setPlaceholder and setAllowedCharPattern, setStep/setMin/setMax on number
fields, the progress bar's setters, CheckboxElement.setIndeterminate and
RadioButtonGroupElement.selectByValue. So does ContextMenuElement.openOn. A unit
test fails when an action is added without the wait.
A test that asserts the in-flight state (a loading indicator, a
disabled-while-saving button) now sees the final state. Wrap that action in
AutoWait.withoutWaiting(() -> ...), or pass -Ddramafinder.autoWait=false to
restore the 1.x timing for a whole run. An explicit waitForVaadinIdle() always
waits; call it after actions on a raw Playwright Locator.
The full suite ran about 7 % longer: an idle check is a single quick evaluation in the page.
New protected methods on AbstractBasePlaywrightIT (#211)¶
AbstractBasePlaywrightIT gains navigate(Class<?>), navigate(Class<?>, Object),
assertAt(Class<?>), assertPath(String) and assertQueryParameter(String, String)
(see "Navigation by route class"). A test class that already declares a method with
one of these names and parameter types now overrides it. If the return type differs,
or the method is less visible than protected, the class no longer compiles. Rename
the test's own method, or drop it in favour of the inherited one.
New features¶
Typed query builder (#206)¶
One lookup grammar for every element, alongside the factories:
import static org.vaadin.addons.dramafinder.element.query.ElementQueries.find;
TextFieldElement name = find(page, TextFieldElement.class).withLabel("Name").single();
dialog.find(ButtonElement.class).withText("Delete").single().click();
find(page, ButtonElement.class).withLabel("Save"); // does not compile
- Each element offers only the filters that fit it, checked at compile time:
withLabel/withLabelContainingon labelled components,withText/withTextContainingon buttons, tabs, badges and similar,withPlaceholderon input fields, the select and the date-time picker. - Every element has
withId,withTestId,withAttribute,withClassName,withAccessibleName,withAccessibleNameContainingand arefine(...)escape hatch. - Terminals:
single()(strict),first(),get(int),all(),count(),exists(),getLocator(). Queries are immutable and stay lazy. - Text filters read the component's own text. Slotted icons, tooltips and shadow-rendered numbers are left out, so a badge with an icon matches its text.
withAccessibleNamenever matches through a nested component: a dialog is not found by the name of a field it holds.- Custom components join by implementing
Findable<Q>with a query type.
getById and getByTestId on every element (#210)¶
The id and test-id factories now exist on 52 elements, each with a Page and a
Locator overload: getById(page, "save") and getByTestId(page, "save-button").
In 1.x getById existed only on AppLayout, FormLayout, Grid, TreeGrid and Tabs.
- The test id is the
data-testidattribute that Flow'sComponent.setTestId(...)sets. - Matches are exact, limited to the element's own tag, and fail on duplicates.
- They are fallbacks for components without a user-visible name.
- The five 1.x
getByIdmethods now also check the tag, and accept ids that are not valid CSS identifiers. - Not offered on Notification, Tooltip, Menu, ContextMenu and MenuItem, whose wrapped tag is not where Flow puts the id.
Navigation by route class (#211)¶
AbstractBasePlaywrightIT navigates by view class instead of a hand-built URL, and
asserts where the browser is, retrying until it gets there:
navigate(ProfileView.class); // nav/profile
navigate(ArticleView.class, 42); // HasUrlParameter
navigate(UserEditView.class, new RouteParameters("userId", "42")); // user/:userId/edit
assertAt(ProfileView.class); // e.g. after a login redirect or a guard
assertPath("nav/profile");
assertQueryParameter("tab", "2"); // null asserts the parameter is absent
- The path comes from
@Route, or Flow's naming convention when it has no value. It is prefixed by the@RoutePrefixof the route's layout and its@ParentLayoutchain, honouringabsolute. So a renamed route breaks at thenavigatecall, not silently. navigatewaits for Vaadin to finish loading.assertAtaccepts any value for a template's parameters or aHasUrlParameter.RouteNavigationoffers the same with an explicitPageand base URL, for tests that do not extendAbstractBasePlaywrightIT.com.vaadin:flow-serveris aprovideddependency: only tests that navigate by class need it.AbstractBasePlaywrightITstill loads without it.
Spring Boot base class in the library (#212)¶
org.vaadin.addons.dramafinder.spring.SpringPlaywrightIT replaces the class that
each Spring Boot project copied from the skill's template:
public class OrderViewIT extends SpringPlaywrightIT { // no annotation needed
@Override
public String getView() {
return "orders";
}
}
- It carries
@SpringBootTest(webEnvironment = RANDOM_PORT). Re-annotate the test to change properties or the web environment (DEFINED_PORTworks too). getUrl()comes from the running server: the local port, the servlet context path, andhttpswhenserver.ssl.enabledis set.- Spring stays optional. DramaFinder declares
spring-boot-testasprovided, so it never adds Spring to a project's classpath. Projects without Spring keep extendingAbstractBasePlaywrightIT. A Spring Boot project already hasspring-boot-starter-test. - A project's own
SpringPlaywrightITcopy keeps working. Delete it and switch the imports toorg.vaadin.addons.dramafinder.spring.SpringPlaywrightIT, then drop the now-redundant@SpringBootTest(webEnvironment = RANDOM_PORT)from the tests.
Improvements¶
ViewOutlinesuggestsgetByTestIdfor a component without a label, before falling back togetById. It no longer annotates ambiguous lookups as substring matches, because labels now match in full (#207, #210).ButtonQuery/TextQuerygainwithAccessibleNameContaining(String)(#207).
Fixes¶
Found while making lookups exact (#207):
SelectElement.getByLabelstopped finding the select once a value was picked.DetailsElement.getBySummaryTextmissed summaries whose text sits in a<span>.ButtonElement.getByTextandButtonQuery.withAccessibleNamestopped matching a button while its tooltip was open.
SliderElement.setValue throws IllegalStateException on a disabled or read-only
slider (#166). In 1.x it wrote the value from a script, which the disabled input
did not block: the browser showed the new value while the server ignored it, and
assertValue passed on a slider no user could move.
Agent skills and documentation¶
- Migration guide
MIGRATION-2.0.md, bundled in the jar (manifest entryDramafinder-Migration-2-0), linked from the README ("Upgrading from 1.x"),llms.txtandSTART-HERE.md. vaadin-playwright-testskill bumped to 0.13.0 (from 0.6.0). It now asks for whole labels, tells a label from an accessible name, uses navigation by route class, and explains the query builder. Its bundledapi-reference.mdgains a "Typed queries" section,AutoWaitandRouteNavigationunder "Test setup", and afindquery column plus "+ id, test id" in the element index.vaadin-playwright-screenshotskill bumped to 0.2.1.- The
vaadin-playwright-testskill now uses the library'sSpringPlaywrightITfrom 2.0 on, and generates its template copy only for projects pinned to 1.x (#212). AGENTS.mddocuments the exact-matching rules, the label rule, the typed query pattern, thewaitAfterAction()rule for new actions and the id factories.GLOSSARY.mddefines label, accessible name, placeholder and item.- The obsolete Vaadin Directory publish workflow is removed (#197).
Full changelog: https://github.com/parttio/dramafinder/compare/dramafinder-1.2.4...dramafinder-2.0.0