Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
2bf799a
feat: prototype get* locator API (Button, TextField, Grid)
mstahv May 15, 2026
9f4ed52
feat: annotation processor generates locators for all @Tests testers
mstahv May 15, 2026
a40e157
chore: register generated-sources/annotations as a source root
mstahv May 15, 2026
c91a47f
feat: generate one locator per @Tests target, pinning shared type vars
mstahv May 15, 2026
c590597
chore: apply spotless formatting
vaadin-bot May 15, 2026
3c475d0
fix: don't skip tester-declared click overrides in generated locators
mstahv May 19, 2026
a888b71
refactor: rename locator entry methods from get* to find*
mstahv May 19, 2026
e69913f
feat: expand Locator filter API to match ComponentQuery
mstahv May 19, 2026
8abcd73
feat: split commercial locators out of GeneratedLocators
mstahv May 19, 2026
30355c0
feat: configurable entry-point FQN for end-user processor builds
mstahv May 19, 2026
cde055c
chore: apply spotless formatting
vaadin-bot May 19, 2026
13b6ad2
fix: emit locators in the tester's package, not the target's
mstahv May 19, 2026
5384e77
chore: apply spotless formatting
vaadin-bot May 19, 2026
8a0c81f
chore: make locator-processor an internal module
mcollovati May 19, 2026
7cee70d
docs: mark LocatorProcessor as internal, refresh stale Javadoc
mcollovati May 19, 2026
ed67d50
chore(processor): scope @SuppressWarnings to the constructor that nee…
mcollovati May 19, 2026
62a149b
fix: Locator.with(UnaryOperator) now honors the operator's return value
mstahv May 19, 2026
16b64d5
chore: apply spotless formatting
vaadin-bot May 19, 2026
0f42763
chore: fix publishing prevention
mcollovati May 19, 2026
202e89f
fix: throw IllegalStateException on null returns from user callbacks
mstahv May 19, 2026
f0c0835
docs: document IllegalStateException contracts on callback APIs
mstahv May 19, 2026
c931597
chore: add empty .mvn/ to anchor maven.multiModuleProjectDirectory
mstahv May 19, 2026
b4e0e2a
chore: apply spotless formatting
mstahv May 19, 2026
0035f83
Update junit6/src/test/java/com/vaadin/browserless/LocatorApiTest.java
mstahv May 19, 2026
20dca34
format
mstahv May 19, 2026
201a550
test: pin aggregator structural invariants via reflection
mcollovati May 19, 2026
0f6a202
feat(processor): generate Javadoc on Locator delegate methods
mcollovati May 19, 2026
680140c
feat(processor): generate Javadoc on GeneratedLocators interfaces
mcollovati May 19, 2026
1d20d52
fix(processor): delegate inherited tester methods on generated locators
mcollovati May 19, 2026
c4bafea
fix: reject zero or negative Locator.atIndex(int)
mcollovati May 19, 2026
b7f99b9
docs: document Locator.inside(Locator) eager parent resolution
mcollovati May 19, 2026
78e28b7
feat(processor): add class-level Javadoc on generated locators
mcollovati May 19, 2026
7fc8dc9
fix(processor): fail the build on entry-method name collisions
mcollovati May 19, 2026
e616ffb
docs: spell out auto-invalidate and the escape hatch on Locator
mcollovati May 19, 2026
b96bc81
test: cover Locator.exists, components, and inside(Component)
mcollovati May 19, 2026
b8e72c0
feat(processor): generate use(Component) seed factories for locators
mcollovati May 20, 2026
20f6dae
format
mcollovati May 20, 2026
0d26e8a
fix(processor): drop @Tests-less tester fallback
mcollovati May 20, 2026
dbabc1a
fix: Locator.invalidate() now also rewinds the atIndex pick
mcollovati May 20, 2026
88cf554
fix(processor): reject default-package testers with a clear error
mcollovati May 20, 2026
3ae0ee5
fix(processor): catch use(X) erasure clash when find arities differed
mcollovati May 20, 2026
d6716e8
feat: lazily resolve the parent passed to Locator.inside(Locator)
mcollovati May 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added .mvn/.gitkeep
Empty file.
16 changes: 16 additions & 0 deletions junit6/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,22 @@
<goals>
<goal>testCompile</goal>
</goals>
<configuration>
<!-- End-user-style configuration: run the locator
processor on junit6's own test sources, with
a project-specific entry-point FQN so we don't
collide with shared's GeneratedLocators. -->
<annotationProcessorPaths>
<path>
<groupId>com.vaadin</groupId>
<artifactId>browserless-test-locator-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Alocator.entrypoint.fqn=com.example.locator.AppLocators</arg>
</compilerArgs>
</configuration>
</execution>
</executions>
</plugin>
Expand Down
79 changes: 79 additions & 0 deletions junit6/src/test/java/com/example/locator/LocatorDemoView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright 2000-2026 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.example.locator;

import java.util.List;

import com.vaadin.flow.component.Composite;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.Route;

@Route("locator-demo")
public class LocatorDemoView extends VerticalLayout {

public record Person(String name, int age) {
}

public LocatorDemoView() {
TextField name = new TextField("Name");
name.setId("name");

Span echo = new Span("");
echo.setId("echo");

Button save = new Button("Save",
e -> echo.setText("Saved: " + name.getValue()));
save.setId("save");

Button clear = new Button("Clear", e -> name.setValue(""));

Grid<Person> people = new Grid<>(Person.class);
people.setItems(List.of(new Person("Alice", 30), new Person("Bob", 25),
new Person("Carol", 40)));
people.addItemClickListener(
event -> echo.setText("Clicked: " + event.getItem().name()));

PersonForm personForm = new PersonForm(echo);
personForm.setId("person-form");

add(name, save, clear, echo, people, personForm);
}

/**
* Demo composite mirroring an app-defined widget — exercises the
* custom-locator extension point.
*/
public static class PersonForm extends Composite<VerticalLayout> {

public final TextField nameField = new TextField("Name");
public final TextField emailField = new TextField("Email");
public final Button submit;

public PersonForm(Span echo) {
nameField.setId("pf-name");
emailField.setId("pf-email");
submit = new Button("Submit",
e -> echo.setText("Submitted: " + nameField.getValue()
+ " <" + emailField.getValue() + ">"));
submit.setId("pf-submit");
getContent().add(nameField, emailField, submit);
}
}
}
50 changes: 50 additions & 0 deletions junit6/src/test/java/com/example/locator/PersonFormLocator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2000-2026 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.example.locator;

import com.example.locator.LocatorDemoView.PersonForm;

import com.vaadin.browserless.locator.Locator;
import com.vaadin.flow.component.button.ButtonLocator;
import com.vaadin.flow.component.textfield.TextFieldLocator;

/**
* App-side locator for a composite. Demonstrates two reuse patterns:
*
* <ul>
* <li>Subclass {@link Locator} with the recursive self-type so filter steps
* stay chainable.</li>
* <li>Compose built-in locators (TextField, Button) and scope them with
* {@code inside(this)} so sub-queries only see descendants of the resolved
* composite.</li>
* </ul>
*/
public class PersonFormLocator extends Locator<PersonForm, PersonFormLocator> {

public PersonFormLocator() {
super(PersonForm.class);
}

public PersonFormLocator fillIn(String name, String email) {
new TextFieldLocator().withId("pf-name").inside(this).setValue(name);
new TextFieldLocator().withId("pf-email").inside(this).setValue(email);
return this;
}

public void submit() {
new ButtonLocator().withId("pf-submit").inside(this).click();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ void extendingBaseClass_runTest_routesAreDiscovered() {
allViews.add(com.example.multiuser.SharedCounterView.class);
allViews.add(com.example.multiuser.ExternalNavigationView.class);
allViews.add(com.example.multiuser.SimpleView.class);
allViews.add(com.example.locator.LocatorDemoView.class);
Assertions.assertEquals(allViews.size(), routes.size());
Assertions.assertTrue(routes.containsAll(allViews));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright 2000-2026 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.browserless;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
* Reflection assertions pinning structural invariants of the locator
* processor's output:
* <ul>
* <li>{@code GeneratedLocators} is core-only — no commercial entries leak in,
* so loading it does not require commercial Vaadin classes on the classpath.
* <li>{@code GeneratedCommercialLocators} carries the commercial entries.
* <li>{@code CommercialLocators} unions both via interface inheritance.
* <li>The end-user-style aggregator emitted by junit6's test-compile
* ({@code com.example.locator.AppLocators}) is scoped to this module's own
* {@code @Tests}-annotated testers and does not regenerate shared.jar's.
* </ul>
*/
class GeneratedAggregatorsTest {

@Test
void defaultAggregatorDoesNotContainCommercialEntries() throws Exception {
Class<?> agg = Class
.forName("com.vaadin.browserless.locator.GeneratedLocators");
Set<String> methods = methodNames(agg.getDeclaredMethods());
assertTrue(methods.contains("findButton"),
"core aggregator should expose findButton, was: " + methods);
assertFalse(methods.contains("findChart"),
"core aggregator must not expose findChart: " + methods);
}

@Test
void commercialAggregatorContainsChartAndNotCoreEntries() throws Exception {
Class<?> agg = Class.forName(
"com.vaadin.browserless.locator.GeneratedCommercialLocators");
Set<String> methods = methodNames(agg.getDeclaredMethods());
assertTrue(methods.contains("findChart"),
"commercial aggregator should expose findChart, was: "
+ methods);
assertFalse(methods.contains("findButton"),
"commercial aggregator should not duplicate core entries: "
+ methods);
}

@Test
void commercialLocatorsMixinUnionsCoreAndCommercial() throws Exception {
Class<?> mixin = Class
.forName("com.vaadin.browserless.locator.CommercialLocators");
// getMethods() walks inherited interfaces; getDeclaredMethods()
// would only show what's declared on CommercialLocators itself.
Set<String> methods = methodNames(mixin.getMethods());
assertTrue(methods.contains("findButton"),
"CommercialLocators should surface core findButton, was: "
+ methods);
assertTrue(methods.contains("findChart"),
"CommercialLocators should surface commercial findChart, was: "
+ methods);
}

@Test
void downstreamAggregatorEmittedAtConfiguredFqn() throws Exception {
// junit6's test-compile wires the processor with
// -Alocator.entrypoint.fqn=com.example.locator.AppLocators
// and the processor only scans junit6's own @Tests-annotated test
// sources, not shared's testers (which are pre-compiled in
// shared.jar). This pins both behaviours.
Class<?> downstream = Class.forName("com.example.locator.AppLocators");
assertTrue(downstream.isInterface(),
"downstream aggregator should be an interface");

Set<String> methods = methodNames(downstream.getDeclaredMethods());
assertTrue(methods.contains("findTestComponent"),
"downstream aggregator should include local TestComponent, was: "
+ methods);
assertTrue(methods.contains("findTestComponentForConcreteTester"),
"downstream aggregator should include local TestComponentForConcreteTester, was: "
+ methods);
assertFalse(methods.contains("findButton"),
"downstream aggregator should not regenerate framework entries: "
+ methods);
assertFalse(methods.contains("findChart"),
"downstream aggregator should not regenerate framework entries: "
+ methods);
}

private static Set<String> methodNames(Method[] methods) {
return Arrays.stream(methods).map(Method::getName)
.collect(Collectors.toSet());
}
}
Loading
Loading