From cacc0208e34da7d4761fdddb0e54a0a8460fbf84 Mon Sep 17 00:00:00 2001 From: Simon Stewart Date: Fri, 2 Nov 2018 14:37:36 +0000 Subject: [PATCH] Rework how we obtain command handlers Ideally, each time we request a handler it is created anew. The `CommandHandlerFactory` makes this a simpler task. --- .../openqa/selenium/grid/router/Router.java | 4 +- .../selenium/grid/web/CombinedRoute.java | 57 +++++ .../selenium/grid/web/PredicatedRoute.java | 63 +++++ .../org/openqa/selenium/grid/web/Route.java | 106 +++++++++ .../org/openqa/selenium/grid/web/Routes.java | 80 +++++++ .../selenium/grid/web/SpecificRoute.java | 94 ++++++++ .../openqa/selenium/grid/web/RoutesTest.java | 225 ++++++++++++++++++ .../webdriver/common/actions/mouse_button.py | 17 ++ py/setup.py | 17 +- .../common/select_element_handling_tests.py | 2 +- py/test/selenium/webdriver/ie/__init__.py | 17 ++ .../unit/selenium/webdriver/ie/__init__.py | 17 ++ rb/lib/selenium/webdriver/atoms.rb | 17 ++ .../common/interactions/interactions.rb | 2 +- 14 files changed, 714 insertions(+), 4 deletions(-) create mode 100644 java/server/src/org/openqa/selenium/grid/web/CombinedRoute.java create mode 100644 java/server/src/org/openqa/selenium/grid/web/PredicatedRoute.java create mode 100644 java/server/src/org/openqa/selenium/grid/web/Route.java create mode 100644 java/server/src/org/openqa/selenium/grid/web/Routes.java create mode 100644 java/server/src/org/openqa/selenium/grid/web/SpecificRoute.java create mode 100644 java/server/test/org/openqa/selenium/grid/web/RoutesTest.java diff --git a/java/server/src/org/openqa/selenium/grid/router/Router.java b/java/server/src/org/openqa/selenium/grid/router/Router.java index acf527e5897e8..d3a0d2d11b30d 100644 --- a/java/server/src/org/openqa/selenium/grid/router/Router.java +++ b/java/server/src/org/openqa/selenium/grid/router/Router.java @@ -23,6 +23,7 @@ import org.openqa.selenium.grid.sessionmap.SessionMap; import org.openqa.selenium.grid.web.CommandHandler; import org.openqa.selenium.grid.web.CompoundHandler; +import org.openqa.selenium.grid.web.Routes; import org.openqa.selenium.injector.Injector; import org.openqa.selenium.remote.http.HttpRequest; import org.openqa.selenium.remote.http.HttpResponse; @@ -35,7 +36,8 @@ */ public class Router implements Predicate, CommandHandler { - private final CompoundHandler handler; + private final Injector injector; + private final Routes handlerFactory; public Router(SessionMap sessions, Distributor distributor) { HandleSession activeSession = new HandleSession(sessions); diff --git a/java/server/src/org/openqa/selenium/grid/web/CombinedRoute.java b/java/server/src/org/openqa/selenium/grid/web/CombinedRoute.java new file mode 100644 index 0000000000000..1c3dd19c4e0db --- /dev/null +++ b/java/server/src/org/openqa/selenium/grid/web/CombinedRoute.java @@ -0,0 +1,57 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you 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 org.openqa.selenium.grid.web; + +import com.google.common.collect.ImmutableList; + +import org.openqa.selenium.injector.Injector; +import org.openqa.selenium.injector.UnableToInstaniateInstanceException; +import org.openqa.selenium.remote.http.HttpRequest; + +import java.util.List; +import java.util.Optional; + +public class CombinedRoute extends Route { + + private final List factories; + + CombinedRoute(List factories) { + this.factories = ImmutableList.copyOf(factories); + } + + @Override + protected void validate() { + // No-op + } + + @Override + protected CommandHandler newHandler(Injector injector, HttpRequest request) { + for (Routes factory : factories) { + try { + Optional handler = factory.match(injector, request); + if (handler.isPresent()) { + return handler.get(); + } + } catch (IllegalArgumentException | UnableToInstaniateInstanceException e) { + // ignore and carry on + } + } + return getFallback(injector); + } + +} diff --git a/java/server/src/org/openqa/selenium/grid/web/PredicatedRoute.java b/java/server/src/org/openqa/selenium/grid/web/PredicatedRoute.java new file mode 100644 index 0000000000000..f55161a6a8158 --- /dev/null +++ b/java/server/src/org/openqa/selenium/grid/web/PredicatedRoute.java @@ -0,0 +1,63 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you 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 org.openqa.selenium.grid.web; + +import org.openqa.selenium.injector.Injector; +import org.openqa.selenium.remote.http.HttpRequest; + +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Predicate; + +public class PredicatedRoute extends Route { + + private Function handlerFunc; + private final Predicate predicate; + + PredicatedRoute(Predicate predicate) { + this.predicate = Objects.requireNonNull(predicate); + } + + public PredicatedRoute using(Class handlerClass) { + Objects.requireNonNull(handlerClass); + handlerFunc = (inj) -> inj.newInstance(handlerClass); + return this; + } + + public PredicatedRoute using(CommandHandler handlerInstance) { + Objects.requireNonNull(handlerInstance); + handlerFunc = (inj) -> handlerInstance; + return this; + } + + @Override + protected void validate() { + if (handlerFunc == null) { + throw new IllegalStateException("Handler for route is required"); + } + } + + @Override + protected CommandHandler newHandler(Injector injector, HttpRequest request) { + if (!predicate.test(request)) { + return getFallback(injector); + } + + return handlerFunc.apply(injector); + } +} diff --git a/java/server/src/org/openqa/selenium/grid/web/Route.java b/java/server/src/org/openqa/selenium/grid/web/Route.java new file mode 100644 index 0000000000000..6333e081d62b6 --- /dev/null +++ b/java/server/src/org/openqa/selenium/grid/web/Route.java @@ -0,0 +1,106 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you 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 org.openqa.selenium.grid.web; + +import org.openqa.selenium.injector.Injector; +import org.openqa.selenium.remote.http.HttpRequest; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.function.BiFunction; + +public abstract class Route { + + private final List> decorators = new ArrayList<>(); + private Class fallback; + + protected Route() { + // no-op + } + + protected abstract void validate(); + + protected abstract CommandHandler newHandler(Injector injector, HttpRequest request); + + /** + * The given {@code decorator} must take a {@link CommandHandler} in its longest constructor. + */ + public T decorateWith(Class decorator) { + Objects.requireNonNull(decorator); + + // Find the longest constructor, which is what the injector uses + Constructor constructor = Arrays.stream(decorator.getDeclaredConstructors()) + .max(Comparator.comparing(Constructor::getParameterCount)) + .orElse(null); + + if (constructor == null) { + throw new IllegalArgumentException("Unable to find a constructor for " + decorator); + } + + Boolean hasHandlerArg = Arrays.stream(constructor.getParameterTypes()) + .map(CommandHandler.class::isAssignableFrom) + .reduce(Boolean::logicalOr) + .orElse(false); + + if (!hasHandlerArg) { + throw new IllegalArgumentException( + "Decorator must take a CommandHandler as a constructor arg in its longest " + + "constructor. " + decorator); + } + + decorators.add(decorator); + + //noinspection unchecked + return (T) this; + } + + public T fallbackTo(Class fallback) { + this.fallback = Objects.requireNonNull(fallback); + //noinspection unchecked + return (T) this; + } + + public Routes build() { + validate(); + + BiFunction func = (inj, req) -> { + CommandHandler handler = newHandler(inj, req); + if (handler == null) { + return getFallback(inj); + } + + Injector injector = inj; + for (Class decorator : decorators) { + injector = Injector.builder().parent(injector).register(handler).build(); + handler = injector.newInstance(decorator); + } + + return handler; + }; + + return new Routes(func); + } + + protected CommandHandler getFallback(Injector injector) { + return fallback == null ? null : injector.newInstance(fallback); + } +} diff --git a/java/server/src/org/openqa/selenium/grid/web/Routes.java b/java/server/src/org/openqa/selenium/grid/web/Routes.java new file mode 100644 index 0000000000000..ea2648bf52aad --- /dev/null +++ b/java/server/src/org/openqa/selenium/grid/web/Routes.java @@ -0,0 +1,80 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you 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 org.openqa.selenium.grid.web; + +import static org.openqa.selenium.remote.http.HttpMethod.DELETE; +import static org.openqa.selenium.remote.http.HttpMethod.GET; +import static org.openqa.selenium.remote.http.HttpMethod.POST; + +import com.google.common.collect.ImmutableList; + +import org.openqa.selenium.injector.Injector; +import org.openqa.selenium.remote.http.HttpRequest; + +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public class Routes { + + private final BiFunction handlerFunc; + + Routes(BiFunction handlerFunc) { + this.handlerFunc = Objects.requireNonNull(handlerFunc); + } + + public static PredicatedRoute matching(Predicate predicate) { + return new PredicatedRoute(predicate); + } + + public static SpecificRoute delete(String template) { + return new SpecificRoute(DELETE, template); + } + + public static SpecificRoute get(String template) { + return new SpecificRoute(GET, template); + } + + public static SpecificRoute post(String template) { + return new SpecificRoute(POST, template); + } + + public static CombinedRoute combine(Route atLeastOne, Route... optionalOthers) { + return combine( + atLeastOne.build(), + Arrays.stream(optionalOthers).map(Route::build).toArray(Routes[]::new)); + } + + public static CombinedRoute combine( + Routes atLeastOne, + Routes... optionalOthers) { + ImmutableList queue = + Stream.concat(Stream.of(atLeastOne), Arrays.stream(optionalOthers)) + .collect(ImmutableList.toImmutableList()); + + return new CombinedRoute(queue.reverse()); + } + + public Optional match(Injector injector, HttpRequest request) { + return Optional.of(handlerFunc.apply(injector, request)); + } + +} diff --git a/java/server/src/org/openqa/selenium/grid/web/SpecificRoute.java b/java/server/src/org/openqa/selenium/grid/web/SpecificRoute.java new file mode 100644 index 0000000000000..5d3ec374b2158 --- /dev/null +++ b/java/server/src/org/openqa/selenium/grid/web/SpecificRoute.java @@ -0,0 +1,94 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you 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 org.openqa.selenium.grid.web; + +import org.openqa.selenium.injector.Injector; +import org.openqa.selenium.remote.http.HttpMethod; +import org.openqa.selenium.remote.http.HttpRequest; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +public class SpecificRoute extends Route { + + private final Map> mappers = new HashMap<>(); + private final HttpMethod method; + private final UrlTemplate template; + private Function handlerFunc; + + SpecificRoute(HttpMethod method, String template) { + this.method = Objects.requireNonNull(method); + this.template = new UrlTemplate(Objects.requireNonNull(template)); + } + + public SpecificRoute map(String parameterName, Function mapper) { + Objects.requireNonNull(parameterName); + Objects.requireNonNull(mapper); + + mappers.put(parameterName, mapper); + + return this; + } + + public SpecificRoute using(Class handlerClass) { + Objects.requireNonNull(handlerClass); + handlerFunc = (inj) -> inj.newInstance(handlerClass); + return this; + } + + public SpecificRoute using(CommandHandler handlerInstance) { + Objects.requireNonNull(handlerInstance); + handlerFunc = (inj) -> handlerInstance; + return this; + } + + + @Override + protected void validate() { + if (handlerFunc == null) { + throw new IllegalStateException("Handler for route is required"); + } + } + + @Override + protected CommandHandler newHandler(Injector injector, HttpRequest request) { + if (request.getMethod() != method) { + return getFallback(injector); + } + + UrlTemplate.Match match = template.match(request.getUri()); + if (match == null) { + return getFallback(injector); + } + + if (!match.getParameters().isEmpty()) { + Injector.Builder builder = Injector.builder().parent(injector); + mappers.forEach((name, mapper) -> { + String value = match.getParameters().get(name); + if (value != null) { + builder.register(mapper.apply(value)); + } + }); + injector = builder.build(); + } + + return handlerFunc.apply(injector); + } +} diff --git a/java/server/test/org/openqa/selenium/grid/web/RoutesTest.java b/java/server/test/org/openqa/selenium/grid/web/RoutesTest.java new file mode 100644 index 0000000000000..c1255348f50f5 --- /dev/null +++ b/java/server/test/org/openqa/selenium/grid/web/RoutesTest.java @@ -0,0 +1,225 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you 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 org.openqa.selenium.grid.web; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.openqa.selenium.grid.web.Routes.combine; +import static org.openqa.selenium.grid.web.Routes.get; +import static org.openqa.selenium.grid.web.Routes.post; +import static org.openqa.selenium.remote.http.HttpMethod.GET; + +import org.junit.Test; +import org.openqa.selenium.injector.Injector; +import org.openqa.selenium.remote.SessionId; +import org.openqa.selenium.remote.http.HttpRequest; +import org.openqa.selenium.remote.http.HttpResponse; + +import java.io.IOException; +import java.util.UUID; +import java.util.function.Function; + +public class RoutesTest { + + @Test + public void canCreateAHandlerWithNoDependencies() { + Routes factory = get("/hello").using(SimpleHandler.class).build(); + + Injector injector = Injector.builder().build(); + CommandHandler handler = factory.match(injector, new HttpRequest(GET, "/hello")).get(); + + assertThat(handler).isInstanceOf(SimpleHandler.class); + } + + private static class SimpleHandler implements CommandHandler { + @Override + public void execute(HttpRequest req, HttpResponse resp) { + resp.setContent("Hello, World!".getBytes(UTF_8)); + } + } + + @Test + public void canCreateAHandlerWithADependencyInTheInjector() { + Routes factory = get("/hello").using(DependencyHandler.class).build(); + + SessionId id = new SessionId(UUID.randomUUID()); + Injector injector = Injector.builder().register(id).build(); + CommandHandler handler = factory.match(injector, new HttpRequest(GET, "/hello")).get(); + + assertThat(handler).isInstanceOf(DependencyHandler.class); + assertThat(((DependencyHandler) handler).id).isEqualTo(id); + } + + private static class DependencyHandler implements CommandHandler { + + private final SessionId id; + + public DependencyHandler(SessionId id) { + this.id = id; + } + + @Override + public void execute(HttpRequest req, HttpResponse resp) { + resp.setContent(id.toString().getBytes(UTF_8)); + } + } + + @Test + public void canCreateAHandlerWithAStringDependencyThatIsAUrlTemplateParameter() { + Routes factory = get("/hello/{cheese}") + .using(StringDepHandler.class) + .map("cheese", Function.identity()) + .build(); + + Injector injector = Injector.builder().build(); + CommandHandler handler = factory.match(injector, new HttpRequest(GET, "/hello/cheddar")) + .get(); + + assertThat(handler).isInstanceOf(StringDepHandler.class); + assertThat(((StringDepHandler) handler).cheese).isEqualTo("cheddar"); + } + + private static class StringDepHandler implements CommandHandler { + + private final String cheese; + + public StringDepHandler(String cheese) { + this.cheese = cheese; + } + + @Override + public void execute(HttpRequest req, HttpResponse resp) { + resp.setContent(("I love cheese. Especially " + cheese).getBytes(UTF_8)); + } + } + + @Test + public void shouldAllowAMappingFunctionToBeSetForEachParameter() { + Routes factory = get("/hello/{sessionId}") + .using(DependencyHandler.class) + .map("sessionId", SessionId::new) + .build(); + + SessionId id = new SessionId(UUID.randomUUID()); + Injector injector = Injector.builder().build(); + CommandHandler handler = factory.match(injector, new HttpRequest(GET, "/hello/" + id)) + .get(); + + assertThat(handler).isInstanceOf(DependencyHandler.class); + assertThat(((DependencyHandler) handler).id).isEqualTo(id); + } + + @Test + public void canDecorateSpecificHandlers() throws IOException { + Routes factory = get("/foo") + .using(SimpleHandler.class) + .decorateWith(ResponseDecorator.class) + .build(); + + HttpRequest request = new HttpRequest(GET, "/foo"); + CommandHandler handler = factory.match(Injector.builder().build(), request).get(); + HttpResponse response = new HttpResponse(); + + handler.execute(request, response); + + assertThat(response.getContentString()).isEqualTo("Hello, World!"); + assertThat(response.getHeader("Cheese")).isEqualTo("Camembert"); + } + + private static class ResponseDecorator implements CommandHandler { + + private final CommandHandler delegate; + + public ResponseDecorator(CommandHandler delegate) { + this.delegate = delegate; + } + + @Override + public void execute(HttpRequest req, HttpResponse resp) throws IOException { + resp.setHeader("Cheese", "Camembert"); + + delegate.execute(req, resp); + } + } + + @Test + public void canDecorateAllHandlers() throws IOException { + Routes factory = get("/session/{sessionId}") + .using(SimpleHandler.class) + .map("sessionId", SessionId::new) + .decorateWith(ResponseDecorator.class) + .build(); + + SessionId id = new SessionId(UUID.randomUUID()); + HttpRequest request = new HttpRequest(GET, "/session/" + id); + CommandHandler handler = factory.match(Injector.builder().build(), request).get(); + HttpResponse response = new HttpResponse(); + + handler.execute(request, response); + + assertThat(response.getContentString()).isEqualTo("Hello, World!"); + assertThat(response.getHeader("Cheese")).isEqualTo("Camembert"); + } + + @Test + public void shouldAllowFallbackHandlerToBeSpecified() { + Routes factory = post("/something") + .using(SimpleHandler.class) + .fallbackTo(SimpleHandler.class) + .build(); + + CommandHandler handler = factory.match( + Injector.builder().build(), + new HttpRequest(GET, "/status")) + .get(); + + assertThat(handler).isInstanceOf(SimpleHandler.class); + } + + @Test + public void whenCombiningMultipleRoutesUseTheLastOneAddedThatMatchesRequest() { + Routes first = get("/session/{sessionId}").using(SimpleHandler.class).build(); + Routes second = get("/session/{sessionId}").using(DependencyHandler.class).build(); + + Routes combined = combine(first, second).build(); + + SessionId id = new SessionId(UUID.randomUUID()); + CommandHandler handler = combined.match( + Injector.builder().register(id).build(), + new HttpRequest(GET, "/session/" + id)) + .get(); + + assertThat(handler).isInstanceOf(DependencyHandler.class); + } + + @Test + public void decoratorsMustTakeACommandHandlerAsAnArgument() { + Route route = get("/foo").using(DependencyHandler.class); + + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> route.decorateWith(SimpleHandler.class)); + } + + @Test + public void routesRequireAnImplementation() { + Route route = get("/peas"); + + assertThatExceptionOfType(IllegalStateException.class).isThrownBy(route::build); + } +} diff --git a/py/selenium/webdriver/common/actions/mouse_button.py b/py/selenium/webdriver/common/actions/mouse_button.py index 7261fd8218622..ce7e9672f02fe 100644 --- a/py/selenium/webdriver/common/actions/mouse_button.py +++ b/py/selenium/webdriver/common/actions/mouse_button.py @@ -1,3 +1,20 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + class MouseButton(object): LEFT = 0 diff --git a/py/setup.py b/py/setup.py index 5acb28f1f0cde..818393cd6cc20 100755 --- a/py/setup.py +++ b/py/setup.py @@ -1,4 +1,19 @@ -#!/usr/bin/env python +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. # Licensed to the Software Freedom Conservancy (SFC) under one # or more contributor license agreements. See the NOTICE file diff --git a/py/test/selenium/webdriver/common/select_element_handling_tests.py b/py/test/selenium/webdriver/common/select_element_handling_tests.py index 6484569a6bc68..c6bfc7dc13e73 100644 --- a/py/test/selenium/webdriver/common/select_element_handling_tests.py +++ b/py/test/selenium/webdriver/common/select_element_handling_tests.py @@ -3,7 +3,7 @@ # distributed with this work for additional information # regarding copyright ownership. The SFC licenses this file # to you under the Apache License, Version 2.0 (the -# "License") you may not use this file except in compliance +# "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 diff --git a/py/test/selenium/webdriver/ie/__init__.py b/py/test/selenium/webdriver/ie/__init__.py index e69de29bb2d1d..556d95e81d402 100644 --- a/py/test/selenium/webdriver/ie/__init__.py +++ b/py/test/selenium/webdriver/ie/__init__.py @@ -0,0 +1,17 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + diff --git a/py/test/unit/selenium/webdriver/ie/__init__.py b/py/test/unit/selenium/webdriver/ie/__init__.py index e69de29bb2d1d..556d95e81d402 100644 --- a/py/test/unit/selenium/webdriver/ie/__init__.py +++ b/py/test/unit/selenium/webdriver/ie/__init__.py @@ -0,0 +1,17 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + diff --git a/rb/lib/selenium/webdriver/atoms.rb b/rb/lib/selenium/webdriver/atoms.rb index 909659da33643..9d5b5c7af6990 100644 --- a/rb/lib/selenium/webdriver/atoms.rb +++ b/rb/lib/selenium/webdriver/atoms.rb @@ -1,3 +1,20 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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. + module Selenium module WebDriver module Atoms diff --git a/rb/lib/selenium/webdriver/common/interactions/interactions.rb b/rb/lib/selenium/webdriver/common/interactions/interactions.rb index a859338610b48..1d941629c0067 100644 --- a/rb/lib/selenium/webdriver/common/interactions/interactions.rb +++ b/rb/lib/selenium/webdriver/common/interactions/interactions.rb @@ -13,7 +13,7 @@ # "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 +# under the License. module Selenium module WebDriver