code: // 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.remote;
  
  import static org.openqa.selenium.remote.DriverCommand.FIND_CHILD_ELEMENT;
  import static org.openqa.selenium.remote.DriverCommand.FIND_CHILD_ELEMENTS;
  
  import com.google.common.collect.ImmutableMap;
  import java.io.File;
  import java.io.IOException;
  import java.util.Arrays;
  import java.util.List;
  import java.util.Map;
  import java.util.stream.Collectors;
  import org.openqa.selenium.Beta;
  import org.openqa.selenium.By;
  import org.openqa.selenium.Dimension;
  import org.openqa.selenium.JavascriptException;
  import org.openqa.selenium.OutputType;
  import org.openqa.selenium.Point;
  import org.openqa.selenium.Rectangle;
  import org.openqa.selenium.SearchContext;
  import org.openqa.selenium.TakesScreenshot;
  import org.openqa.selenium.WebDriver;
  import org.openqa.selenium.WebDriverException;
  import org.openqa.selenium.WebElement;
  import org.openqa.selenium.WrapsDriver;
  import org.openqa.selenium.WrapsElement;
  import org.openqa.selenium.interactions.Coordinates;
  import org.openqa.selenium.interactions.Locatable;
  import org.openqa.selenium.io.Zip;
  
  public class RemoteWebElement implements WebElement, Locatable, TakesScreenshot, WrapsDriver {
  
    private String foundBy;
    protected String id;
    protected RemoteWebDriver parent;
    protected FileDetector fileDetector;
  
    protected void setFoundBy(SearchContext foundFrom, String locator, String term) {
      this.foundBy = String.format("[%s] -> %s: %s", foundFrom, locator, term);
    }
  
    public void setParent(RemoteWebDriver parent) {
      this.parent = parent;
    }
  
    public String getId() {
      return id;
    }
  
    public void setId(String id) {
      this.id = id;
    }
  
    public void setFileDetector(FileDetector detector) {
      fileDetector = detector;
    }
  
    @Override
    public void click() {
      execute(DriverCommand.CLICK_ELEMENT(id));
    }
  
    @Override
    public void submit() {
      try {
        execute(DriverCommand.SUBMIT_ELEMENT(id));
      } catch (JavascriptException ex) {
        String message = "To submit an element, it must be nested inside a form element";
        throw new UnsupportedOperationException(message);
      }
    }
  
    @Override
    public void sendKeys(CharSequence... keysToSend) {
      if (keysToSend == null || keysToSend.length == 0) {
        throw new IllegalArgumentException("Keys to send should be a not null CharSequence");
      }
      for (CharSequence cs : keysToSend) {
        if (cs == null) {
          throw new IllegalArgumentException("Keys to send should be a not null CharSequence");
        }
      }
  
      String allKeysToSend = String.join("", keysToSend);
  
      List<File> files =
          Arrays.stream(allKeysToSend.split("\n"))
              .map(fileDetector::getLocalFile)
              .collect(Collectors.toList());
      if (!files.isEmpty() && !files.contains(null)) {
        allKeysToSend = files.stream().map(this::upload).collect(Collectors.joining("\n"));
      }
  
      execute(DriverCommand.SEND_KEYS_TO_ELEMENT(id, new CharSequence[] {allKeysToSend}));
    }
  
    private String upload(File localFile) {
      if (!localFile.isFile()) {
        throw new WebDriverException("You may only upload files: " + localFile);
      }
  
      try {
        String zip = Zip.zip(localFile);
        Response response = execute(DriverCommand.UPLOAD_FILE(zip));
        return (String) response.getValue();
      } catch (IOException e) {
        throw new WebDriverException("Cannot upload " + localFile, e);
      }
    }
  
    @Override
    public void clear() {
      execute(DriverCommand.CLEAR_ELEMENT(id));
    }
  
    @Override
    public String getTagName() {
      return (String) execute(DriverCommand.GET_ELEMENT_TAG_NAME(id)).getValue();
    }
  
    @Override
    public String getDomProperty(String name) {
      return stringValueOf(execute(DriverCommand.GET_ELEMENT_DOM_PROPERTY(id, name)).getValue());
    }
  
    @Override
    public String getDomAttribute(String name) {
      return stringValueOf(execute(DriverCommand.GET_ELEMENT_DOM_ATTRIBUTE(id, name)).getValue());
    }
  
    @Override
    public String getAttribute(String name) {
      return stringValueOf(execute(DriverCommand.GET_ELEMENT_ATTRIBUTE(id, name)).getValue());
    }
  
    @Override
    public String getAriaRole() {
      return stringValueOf(execute(DriverCommand.GET_ELEMENT_ARIA_ROLE(id)).getValue());
    }
  
    @Override
    public String getAccessibleName() {
      return stringValueOf(execute(DriverCommand.GET_ELEMENT_ACCESSIBLE_NAME(id)).getValue());
    }
  
    private static String stringValueOf(Object o) {
      if (o == null) {
        return null;
      }
      return String.valueOf(o);
    }
  
    @Override
    public boolean isSelected() {
      Object value = execute(DriverCommand.IS_ELEMENT_SELECTED(id)).getValue();
      try {
        return (Boolean) value;
      } catch (ClassCastException ex) {
        throw new WebDriverException("Returned value cannot be converted to Boolean: " + value, ex);
      }
    }
  
    @Override
    public boolean isEnabled() {
      Object value = execute(DriverCommand.IS_ELEMENT_ENABLED(id)).getValue();
      try {
        return (Boolean) value;
      } catch (ClassCastException ex) {
        throw new WebDriverException("Returned value cannot be converted to Boolean: " + value, ex);
      }
    }
  
    @Override
    public String getText() {
      Response response = execute(DriverCommand.GET_ELEMENT_TEXT(id));
      return (String) response.getValue();
    }
  
    @Override
    public String getCssValue(String propertyName) {
      Response response = execute(DriverCommand.GET_ELEMENT_VALUE_OF_CSS_PROPERTY(id, propertyName));
      return (String) response.getValue();
    }
  
    @Override
    public List<WebElement> findElements(By locator) {
      return parent.findElements(
          this,
          (using, value) -> FIND_CHILD_ELEMENTS(getId(), using, String.valueOf(value)),
          locator);
    }
  
    @Override
    public WebElement findElement(By locator) {
      return parent.findElement(
          this, (using, value) -> FIND_CHILD_ELEMENT(getId(), using, String.valueOf(value)), locator);
    }
  
    @Override
    public SearchContext getShadowRoot() {
      Response response = execute(DriverCommand.GET_ELEMENT_SHADOW_ROOT(getId()));
      return (SearchContext) response.getValue();
    }
  
    protected Response execute(CommandPayload payload) {
      try {
        return parent.execute(payload);
      } catch (WebDriverException ex) {
        ex.addInfo("Element", this.toString());
        throw ex;
      }
    }
  
    protected Response execute(String command, Map<String, ?> parameters) {
      try {
        return parent.execute(command, parameters);
      } catch (WebDriverException ex) {
        ex.addInfo("Element", this.toString());
        throw ex;
      }
    }
  
    @Override
    public boolean equals(Object obj) {
      if (!(obj instanceof WebElement)) {
        return false;
      }
  
      WebElement other = (WebElement) obj;
      while (other instanceof WrapsElement) {
        other = ((WrapsElement) other).getWrappedElement();
      }
  
      if (!(other instanceof RemoteWebElement)) {
        return false;
      }
  
      RemoteWebElement otherRemoteWebElement = (RemoteWebElement) other;
  
      return id.equals(otherRemoteWebElement.id);
    }
  
    /**
     * @return This element's hash code, which is a hash of its internal opaque ID.
     */
    @Override
    public int hashCode() {
      return id.hashCode();
    }
  
    /*
     * (non-Javadoc)
     *
     * @see org.openqa.selenium.internal.WrapsDriver#getWrappedDriver()
     */
    @Override
    public WebDriver getWrappedDriver() {
      return parent;
    }
  
    @Override
    public boolean isDisplayed() {
      Object value = execute(DriverCommand.IS_ELEMENT_DISPLAYED(id)).getValue();
      try {
        // See https://github.com/SeleniumHQ/selenium/issues/9266
        if (value == null) {
          return false;
        }
        return (Boolean) value;
      } catch (ClassCastException ex) {
        throw new WebDriverException("Returned value cannot be converted to Boolean: " + value, ex);
      }
    }
  
    @Override
    @SuppressWarnings({"unchecked"})
    public Point getLocation() {
      Response response = execute(DriverCommand.GET_ELEMENT_RECT(id));
      Map<String, Object> rawPoint = (Map<String, Object>) response.getValue();
      int x = ((Number) rawPoint.get("x")).intValue();
      int y = ((Number) rawPoint.get("y")).intValue();
      return new Point(x, y);
    }
  
    @Override
    @SuppressWarnings({"unchecked"})
    public Dimension getSize() {
      Response response = execute(DriverCommand.GET_ELEMENT_RECT(id));
      Map<String, Object> rawSize = (Map<String, Object>) response.getValue();
      int width = ((Number) rawSize.get("width")).intValue();
      int height = ((Number) rawSize.get("height")).intValue();
      return new Dimension(width, height);
    }
  
    @Override
    @SuppressWarnings({"unchecked"})
    public Rectangle getRect() {
      Response response = execute(DriverCommand.GET_ELEMENT_RECT(id));
      Map<String, Object> rawRect = (Map<String, Object>) response.getValue();
      int x = ((Number) rawRect.get("x")).intValue();
      int y = ((Number) rawRect.get("y")).intValue();
      int width = ((Number) rawRect.get("width")).intValue();
      int height = ((Number) rawRect.get("height")).intValue();
      return new Rectangle(x, y, height, width);
    }
  
    @Override
    public Coordinates getCoordinates() {
      return new Coordinates() {
  
        @Override
        public Point onScreen() {
          throw new UnsupportedOperationException("Not supported yet.");
        }
  
        @Override
        public Point inViewPort() {
          Response response =
              execute(DriverCommand.GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW(getId()));
  
          @SuppressWarnings("unchecked")
          Map<String, Number> mapped = (Map<String, Number>) response.getValue();
          return new Point(mapped.get("x").intValue(), mapped.get("y").intValue());
        }
  
        @Override
        public Point onPage() {
          return getLocation();
        }
  
        @Override
        public Object getAuxiliary() {
          return getId();
        }
      };
    }
  
    @Override
    @Beta
    public <X> X getScreenshotAs(OutputType<X> outputType) throws WebDriverException {
      Response response = execute(DriverCommand.ELEMENT_SCREENSHOT(id));
      Object result = response.getValue();
      if (result instanceof String) {
        String base64EncodedPng = (String) result;
        return outputType.convertFromBase64Png(base64EncodedPng);
      } else if (result instanceof byte[]) {
        return outputType.convertFromPngBytes((byte[]) result);
      } else {
        throw new RuntimeException(
            String.format(
                "Unexpected result for %s command: %s",
                DriverCommand.ELEMENT_SCREENSHOT,
                result == null ? "null" : result.getClass().getName() + " instance"));
      }
    }
  
    public String toString() {
      if (foundBy == null) {
        return String.format("[%s -> unknown locator]", super.toString());
      }
      return String.format("[%s]", foundBy);
    }
  
    public Map<String, Object> toJson() {
      return ImmutableMap.of(Dialect.W3C.getEncodedElementKey(), getId());
    }
  }
  