lib/async/webdriver/element.rb in async-webdriver-0.1.2 vs lib/async/webdriver/element.rb in async-webdriver-0.2.0

- old
+ new

@@ -1,20 +1,273 @@ -require "selenium-webdriver" -module Async - module Webdriver - class Element - def initialize(json:, connection:) - @id = json.fetch "ELEMENT" - @element_connection = ConnectionPath.new "element/#{@id}", connection: connection - end +# frozen_string_literal: true - def tag_name - @element_connection.call method: "get", path: "name" - value - end +# Released under the MIT License. +# Copyright, 2023, by Samuel Williams. - def text - @element_connection.call method: "get", path: "text" - end - end - end +require_relative 'request_helper' + +require_relative 'scope' + +module Async + module WebDriver + # An element represents a DOM element. This class is used to interact with the DOM. + # + # ``` ruby + # element = session.find_element(:css, "main#content") + # element.click + # ``` + class Element + # Attributes associated with an element. + class Attributes + include Enumerable + + def initialize(element) + @element = element + @keys = nil + end + + # Get the value of an attribute. + # @parameter name [String] The name of the attribute. + # @returns [Object] The value of the attribute with the given name. + def [](name) + @element.attribute(name) + end + + # Set the value of an attribute. + # @parameter name [String] The name of the attribute. + # @parameter value [Object] The value of the attribute. + def []=(name, value) + @element.set_attribute(name, value) + end + + # Get the names of all attributes. + def keys + @element.execute("return this.getAttributeNames()") + end + + # Check if an attribute exists. + # @parameter name [String] The name of the attribute. + # @returns [Boolean] True if the attribute exists. + def key?(name) + @element.execute("return this.hasAttribute(...arguments)", name) + end + + # Iterate over all attributes. + # @yields {|name, value| ...} The name and value of each attribute. + # @parameter name [String] The name of the attribute. + # @parameter value [Object] The value of the attribute. + def each(&block) + return to_enum unless block_given? + + keys.each do |key| + yield key, self[key] + end + end + end + + # Initialize the element. + # @parameter session [Session] The session the element belongs to. + # @parameter id [String] The element identifier. + def initialize(session, id) + @session = session + @delegate = session.delegate + @id = id + + @attributes = nil + @properties = nil + end + + # @returns [Hash] The JSON representation of the element. + def as_json + {ELEMENT_KEY => @id} + end + + # @returns [String] The JSON representation of the element. + def to_json(...) + as_json.to_json(...) + end + + # @attribute [Session] The session the element belongs to. + attr :session + + # @attribute [Protocol::HTTP::Middleware] The underlying HTTP client (or wrapper). + attr :delegate + + # @attribute [String] The element identifier. + attr :id + + # The path used for making requests to the web driver bridge. + # @parameter path [String | Nil] The path to append to the request path. + # @returns [String] The path used for making requests to the web driver bridge. + def request_path(path = nil) + if path + "/session/#{@session.id}/element/#{@id}/#{path}" + else + "/session/#{@session}/element/#{@id}" + end + end + + include RequestHelper + + # The current scope to use for making subsequent requests. + # @returns [Element] The element. + def current_scope + self + end + + include Scope::Alerts + include Scope::Cookies + include Scope::Elements + include Scope::Fields + include Scope::Printing + include Scope::ScreenCapture + + # Execute a script in the context of the element. `this` will be the element. + # @parameter script [String] The script to execute. + # @parameter arguments [Array] The arguments to pass to the script. + def execute(script, *arguments) + @session.execute("return (function(){#{script}}).call(...arguments)", self, *arguments) + end + + # Execute a script in the context of the element. `this` will be the element. + # @parameter script [String] The script to execute. + # @parameter arguments [Array] The arguments to pass to the script. + def execute_async(script, *arguments) + @session.execute_async("return (function(){#{script}}).call(...arguments)", self, *arguments) + end + + # Get the value of an attribute. + # + # Given an attribute name, e.g. `href`, this method will return the value of the attribute, as if you had executed the following JavaScript: + # + # ```js + # element.getAttribute("href") + # ``` + # + # @parameter name [String] The name of the attribute. + # @returns [Object] The value of the attribute. + def attribute(name) + get("attribute/#{name}") + end + + # Set the value of an attribute. + # @parameter name [String] The name of the attribute. + # @parameter value [Object] The value of the attribute. + def set_attribute(name, value) + execute("this.setAttribute(...arguments)", name, value) + end + + # Get attributes associated with the element. + # @returns [Attributes] The attributes associated with the element. + def attributes + @attributes ||= Attributes.new(self) + end + + # Get the value of a property. + # + # Given a property name, e.g. `offsetWidth`, this method will return the value of the property, as if you had executed the following JavaScript: + # + # ```js + # element.offsetWidth + # ``` + # + # @parameter name [String] The name of the property. + # @returns [Object] The value of the property. + def property(name) + get("property/#{name}") + end + + # Get the value of a CSS property. + # + # Given a CSS property name, e.g. `width`, this method will return the value of the property, as if you had executed the following JavaScript: + # + # ```js + # window.getComputedStyle(element).width + # ``` + # + # @parameter name [String] The name of the CSS property. + # @returns [String] The value of the CSS property. + def css(name) + get("css/#{name}") + end + + # Get the text content of the element. + # + # This method will return the text content of the element, as if you had executed the following JavaScript: + # + # ```js + # element.textContent + # ``` + # + # @returns [String] The text content of the element. + def text + get("text") + end + + # Get the element's tag name. + # + # This method will return the tag name of the element, as if you had executed the following JavaScript: + # + # ```js + # element.tagName + # ``` + # + # @returns [String] The tag name of the element. + def tag_name + get("name") + end + + # A struct representing the size of an element. + Rectangle = Struct.new(:x, :y, :width, :height) + + # Get the element's bounding rectangle. + # @returns [Rectangle] The element's bounding rectangle. + def rectangle + get("rect").tap do |reply| + Rectangle.new(reply["x"], reply["y"], reply["width"], reply["height"]) + end + end + + # Whether the element is selected OR checked. + # @returns [Boolean] True if the element is selected OR checked. + def selected? + get("selected") + end + + alias checked? selected? + + # Whether the element is enabled. + # @returns [Boolean] True if the element is enabled. + def enabled? + get("enabled") + end + + # Whether the element is displayed. + # @returns [Boolean] True if the element is displayed. + def displayed? + get("displayed") + end + + # Click the element. + def click + post("click") + end + + # Clear the element. + def clear + post("clear") + end + + # Send keys to the element. Simulates a user typing keys while the element is focused. + def send_keys(text) + post("value", {text: text}) + end + + FRAME_TAGS = ["frame", "iframe"].freeze + + # Whether the element is a frame. + def frame? + FRAME_TAGS.include?(self.tag_name) + end + end + end end