...28import org.openqa.selenium.WebElement;29import org.openqa.selenium.events.local.GuavaEventBus;30import org.openqa.selenium.grid.data.Session;31import org.openqa.selenium.grid.node.local.LocalNode;32import org.openqa.selenium.grid.node.locators.ById;33import org.openqa.selenium.grid.security.Secret;34import org.openqa.selenium.grid.testing.TestSessionFactory;35import org.openqa.selenium.remote.ErrorFilter;36import org.openqa.selenium.grid.web.Values;37import org.openqa.selenium.json.Json;38import org.openqa.selenium.json.TypeToken;39import org.openqa.selenium.remote.Dialect;40import org.openqa.selenium.remote.http.Contents;41import org.openqa.selenium.remote.http.HttpHandler;42import org.openqa.selenium.remote.http.HttpRequest;43import org.openqa.selenium.remote.http.HttpResponse;44import org.openqa.selenium.remote.http.UrlTemplate;45import org.openqa.selenium.remote.locators.CustomLocator;46import org.openqa.selenium.remote.tracing.DefaultTestTracer;47import org.openqa.selenium.remote.tracing.Tracer;48import java.net.URI;49import java.time.Instant;50import java.util.List;51import java.util.Map;52import java.util.UUID;53import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;54import static java.util.Collections.emptyList;55import static java.util.Collections.emptySet;56import static java.util.Collections.singleton;57import static java.util.Collections.singletonList;58import static java.util.Collections.singletonMap;59import static org.assertj.core.api.Assertions.assertThat;60import static org.assertj.core.api.Assertions.assertThatExceptionOfType;61import static org.mockito.ArgumentMatchers.argThat;62import static org.mockito.Mockito.when;63import static org.openqa.selenium.json.Json.MAP_TYPE;64import static org.openqa.selenium.remote.http.HttpMethod.POST;65import com.google.common.collect.ImmutableMap;66public class CustomLocatorHandlerTest {67 private final Secret registrationSecret = new Secret("cheese");68 private LocalNode.Builder nodeBuilder;69 private URI nodeUri;70 @Before71 public void partiallyBuildNode() {72 Tracer tracer = DefaultTestTracer.createTracer();73 nodeUri = URI.create("http://localhost:1234");74 nodeBuilder = LocalNode.builder(75 tracer,76 new GuavaEventBus(),77 nodeUri,78 URI.create("http://localhost:4567"),79 registrationSecret);80 }81 @Test82 public void shouldRequireInputToHaveAUsingParameter() {83 Node node = nodeBuilder.build();84 HttpHandler handler = new CustomLocatorHandler(node, registrationSecret, emptySet());85 HttpResponse res = handler.execute(86 new HttpRequest(POST, "/session/1234/element")87 .setContent(Contents.asJson(singletonMap("value", "1234"))));88 assertThat(res.getStatus()).isEqualTo(HTTP_BAD_REQUEST);89 assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy(() -> Values.get(res, MAP_TYPE));90 }91 @Test92 public void shouldRequireInputToHaveAValueParameter() {93 Node node = nodeBuilder.build();94 HttpHandler handler = new CustomLocatorHandler(node, registrationSecret, emptySet());95 HttpResponse res = handler.execute(96 new HttpRequest(POST, "/session/1234/element")97 .setContent(Contents.asJson(singletonMap("using", "magic"))));98 assertThat(res.getStatus()).isEqualTo(HTTP_BAD_REQUEST);99 assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy(() -> Values.get(res, MAP_TYPE));100 }101 @Test102 public void shouldRejectRequestWithAnUnknownLocatorMechanism() {103 Node node = nodeBuilder.build();104 HttpHandler handler = new CustomLocatorHandler(node, registrationSecret, emptySet());105 HttpResponse res = handler.execute(106 new HttpRequest(POST, "/session/1234/element")107 .setContent(Contents.asJson(ImmutableMap.of(108 "using", "cheese",109 "value", "tasty"))));110 assertThat(res.getStatus()).isEqualTo(HTTP_BAD_REQUEST);111 assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy(() -> Values.get(res, MAP_TYPE));112 }113 @Test114 public void shouldCallTheGivenLocatorForALocator() {115 Capabilities caps = new ImmutableCapabilities("browserName", "cheesefox");116 Node node = nodeBuilder.add(117 caps,118 new TestSessionFactory((id, c) -> new Session(id, nodeUri, caps, c, Instant.now())))119 .build();120 HttpHandler handler = new CustomLocatorHandler(121 node,122 registrationSecret,123 singleton(new CustomLocator() {124 @Override125 public String getLocatorName() {126 return "cheese";127 }128 @Override129 public By createBy(Object usingParameter) {130 return new By() {131 @Override132 public List<WebElement> findElements(SearchContext context) {133 return emptyList();134 }135 };136 }137 }));138 HttpResponse res = handler.with(new ErrorFilter()).execute(139 new HttpRequest(POST, "/session/1234/element")140 .setContent(Contents.asJson(ImmutableMap.of(141 "using", "cheese",142 "value", "tasty"))));143 assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(() -> Values.get(res, WebElement.class));144 }145 @Test146 public void shouldBeAbleToUseNodeAsWebDriver() {147 String elementId = UUID.randomUUID().toString();148 Node node = Mockito.mock(Node.class);149 when(node.executeWebDriverCommand(argThat(matchesUri("/session/{sessionId}/elements"))))150 .thenReturn(151 new HttpResponse()152 .addHeader("Content-Type", Json.JSON_UTF_8)153 .setContent(Contents.asJson(singletonMap(154 "value", singletonList(singletonMap(Dialect.W3C.getEncodedElementKey(), elementId))))));155 HttpHandler handler = new CustomLocatorHandler(156 node,157 registrationSecret,158 singleton(new CustomLocator() {159 @Override160 public String getLocatorName() {161 return "cheese";162 }163 @Override164 public By createBy(Object usingParameter) {165 return By.id("brie");166 }167 }));168 HttpResponse res = handler.execute(169 new HttpRequest(POST, "/session/1234/elements")170 .setContent(Contents.asJson(ImmutableMap.of(171 "using", "cheese",172 "value", "tasty"))));173 List<Map<String, Object>> elements = Values.get(res, new TypeToken<List<Map<String, Object>>>(){}.getType());174 assertThat(elements).hasSize(1);175 Object seenId = elements.get(0).get(Dialect.W3C.getEncodedElementKey());176 assertThat(seenId).isEqualTo(elementId);177 }178 @Test179 public void shouldBeAbleToRootASearchWithinAnElement() {180 String elementId = UUID.randomUUID().toString();181 Node node = Mockito.mock(Node.class);182 when(node.executeWebDriverCommand(argThat(matchesUri("/session/{sessionId}/element/{elementId}/element"))))183 .thenReturn(184 new HttpResponse()185 .addHeader("Content-Type", Json.JSON_UTF_8)186 .setContent(Contents.asJson(singletonMap(187 "value", singletonList(singletonMap(Dialect.W3C.getEncodedElementKey(), elementId))))));188 HttpHandler handler = new CustomLocatorHandler(189 node,190 registrationSecret,191 singleton(new CustomLocator() {192 @Override193 public String getLocatorName() {194 return "cheese";195 }196 @Override197 public By createBy(Object usingParameter) {198 return By.id("brie");199 }200 }));201 HttpResponse res = handler.execute(202 new HttpRequest(POST, "/session/1234/element/234345/elements")203 .setContent(Contents.asJson(ImmutableMap.of(204 "using", "cheese",205 "value", "tasty"))));206 List<Map<String, Object>> elements = Values.get(res, new TypeToken<List<Map<String, Object>>>(){}.getType());207 assertThat(elements).hasSize(1);208 Object seenId = elements.get(0).get(Dialect.W3C.getEncodedElementKey());209 assertThat(seenId).isEqualTo(elementId);210 }211 @Test212 public void shouldNotFindLocatorStrategyForId() {213 Capabilities caps = new ImmutableCapabilities("browserName", "cheesefox");214 Node node = nodeBuilder.add(215 caps,216 new TestSessionFactory((id, c) -> new Session(id, nodeUri, caps, c, Instant.now())))217 .build();218 HttpHandler handler = new CustomLocatorHandler(node, registrationSecret, emptySet());219 HttpResponse res = handler.with(new ErrorFilter()).execute(220 new HttpRequest(POST, "/session/1234/element")221 .setContent(Contents.asJson(ImmutableMap.of(222 "using", "id",223 "value", "tasty"))));224 assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy(() -> Values.get(res, WebElement.class));225 }226 @Test227 public void shouldFallbackToUseById() {228 String elementId = UUID.randomUUID().toString();229 Node node = Mockito.mock(Node.class);230 when(node.executeWebDriverCommand(argThat(matchesUri("/session/{sessionId}/elements"))))231 .thenReturn(232 new HttpResponse()233 .addHeader("Content-Type", Json.JSON_UTF_8)234 .setContent(Contents.asJson(singletonMap(235 "value", singletonList(singletonMap(Dialect.W3C.getEncodedElementKey(), elementId))))));236 HttpHandler handler = new CustomLocatorHandler(237 node,238 registrationSecret,239 singleton(new ById()));240 HttpResponse res = handler.execute(241 new HttpRequest(POST, "/session/1234/elements")242 .setContent(Contents.asJson(ImmutableMap.of(243 "using", "id",244 "value", "tasty"))));245 List<Map<String, Object>> elements = Values.get(res, new TypeToken<List<Map<String, Object>>>(){}.getType());246 assertThat(elements).hasSize(1);247 Object seenId = elements.get(0).get(Dialect.W3C.getEncodedElementKey());248 assertThat(seenId).isEqualTo(elementId);249 }250 private ArgumentMatcher<HttpRequest> matchesUri(String template) {251 UrlTemplate ut = new UrlTemplate(template);252 return req -> ut.match(req.getUri()) != null;253 }...