# frozen_string_literal: true require 'em/pure_ruby' require 'faye/websocket' require 'openssl' require 'rest-client' require 'securerandom' require 'selenium/webdriver' require 'selenium/devtools' require 'socksify' require 'timeout' require 'watir' module PWN module Plugins # This plugin rocks. Chrome, Firefox, headless, REST Client, # all from the comfort of one plugin. Proxy support (e.g. Burp # Suite Professional) is completely available for all browsers # except for limited functionality within IE (IE has interesting # protections in place to prevent this). This plugin also supports # taking screenshots :) module TransparentBrowser @@logger = PWN::Plugins::PWNLogger.create # Supported Method Parameters:: # verify_devtools_browser( # browser_obj: 'required - browser_obj returned from #open method', # supported: 'optional - array of supported browser types (defaults to [:chrome, :headless_chrome, :firefox, :headless_firefox, :headless])' # ) private_class_method def self.verify_devtools_browser(opts = {}) browser_obj = opts[:browser_obj] supported = opts[:supported] ||= %i[chrome headless_chrome firefox headless_firefox headless] browser_type = browser_obj[:type] raise "ERROR: browser_type must be #{supported}" unless supported.include?(browser_type) rescue StandardError => e raise e end # Supported Method Parameters:: # browser_obj1 = PWN::Plugins::TransparentBrowser.open( # browser_type: 'optional - :firefox|:chrome|:headless|:rest|:websocket (defaults to :chrome)', # proxy: 'optional - scheme://proxy_host:port || tor (defaults to nil)', # with_devtools: 'optional - boolean (defaults to true)', # url: 'optional - URL to navigate to after opening browser (Defaults to about:about#RANDID)' # ) public_class_method def self.open(opts = {}) browser_type = opts[:browser_type] ||= :chrome proxy = opts[:proxy].to_s unless opts[:proxy].nil? browser_obj = {} browser_obj[:type] = browser_type tor_obj = nil if opts[:proxy] == 'tor' tor_obj = PWN::Plugins::Tor.start proxy = "socks5://#{tor_obj[:ip]}:#{tor_obj[:port]}" browser_obj[:tor_obj] = tor_obj end devtools_supported = %i[chrome headless_chrome firefox headless_firefox headless] with_devtools = opts[:with_devtools] ||= false with_devtools = true if devtools_supported.include?(browser_type) && with_devtools url = opts[:url] ||= "about:about##{SecureRandom.hex(8)}" # Let's crank up the default timeout from 30 seconds to 15 min for slow sites Watir.default_timeout = 900 args = [] args.push('--start-maximized') args.push('--disable-notifications') unless browser_type == :rest logger = Selenium::WebDriver.logger logger.level = :error end case browser_type when :firefox this_profile = Selenium::WebDriver::Firefox::Profile.new # Increase Web Assembly Verbosity this_profile['javascript.options.wasm_verbose'] = true # Downloads reside in ~/Downloads this_profile['browser.download.folderList'] = 1 this_profile['browser.helperApps.neverAsk.saveToDisk'] = 'application/pdf' # disable Firefox's built-in PDF viewer this_profile['pdfjs.disabled'] = true # disable Adobe Acrobat PDF preview plugin this_profile['plugin.scan.plid.all'] = false this_profile['plugin.scan.Acrobat'] = '99.0' # ensure localhost proxy capabilities are enabled this_profile['network.proxy.no_proxies_on'] = '' # allow scripts to run a bit longer # this_profile['dom.max_chrome_script_run_time'] = 180 # this_profile['dom.max_script_run_time'] = 180 # disable browser cache this_profile['browser.cache.disk.enable'] = false this_profile['browser.cache.disk_cache_ssl.enable'] = false this_profile['browser.cache.memory.enable'] = false this_profile['browser.cache.offline.enable'] = false this_profile['devtools.cache.disabled'] = true this_profile['dom.caches.enabled'] = false # caps = Selenium::WebDriver::Remote::Capabilities.firefox # caps[:acceptInsecureCerts] = true if proxy this_profile['network.proxy.type'] = 1 this_profile['network.proxy.allow_hijacking_localhost'] = true if tor_obj this_profile['network.proxy.socks_version'] = 5 this_profile['network.proxy.socks'] = tor_obj[:ip] this_profile['network.proxy.socks_port'] = tor_obj[:port] else this_profile['network.proxy.ftp'] = URI(proxy).host this_profile['network.proxy.ftp_port'] = URI(proxy).port this_profile['network.proxy.http'] = URI(proxy).host this_profile['network.proxy.http_port'] = URI(proxy).port this_profile['network.proxy.ssl'] = URI(proxy).host this_profile['network.proxy.ssl_port'] = URI(proxy).port end end args.push('--devtools') if with_devtools options = Selenium::WebDriver::Firefox::Options.new( args: args, accept_insecure_certs: true ) options.web_socket_url = true options.profile = this_profile driver = Selenium::WebDriver.for(:firefox, options: options) browser_obj[:browser] = Watir::Browser.new(driver) when :chrome this_profile = Selenium::WebDriver::Chrome::Profile.new this_profile['download.prompt_for_download'] = false this_profile['download.default_directory'] = '~/Downloads' if proxy args.push("--host-resolver-rules='MAP * 0.0.0.0 , EXCLUDE #{tor_obj[:ip]}'") if tor_obj args.push("--proxy-server=#{proxy}") end if with_devtools args.push('--auto-open-devtools-for-tabs') args.push('--disable-hang-monitor') end options = Selenium::WebDriver::Chrome::Options.new( args: args, accept_insecure_certs: true ) options.web_socket_url = true options.profile = this_profile driver = Selenium::WebDriver.for(:chrome, options: options) browser_obj[:browser] = Watir::Browser.new(driver) when :headless, :headless_firefox this_profile = Selenium::WebDriver::Firefox::Profile.new # Increase Web Assembly Verbosity this_profile['javascript.options.wasm_verbose'] = true # Downloads reside in ~/Downloads this_profile['browser.download.folderList'] = 1 this_profile['browser.helperApps.neverAsk.saveToDisk'] = 'application/pdf' # disable Firefox's built-in PDF viewer this_profile['pdfjs.disabled'] = true # disable Adobe Acrobat PDF preview plugin this_profile['plugin.scan.plid.all'] = false this_profile['plugin.scan.Acrobat'] = '99.0' # ensure localhost proxy capabilities are enabled this_profile['network.proxy.no_proxies_on'] = '' # allow scripts to run a bit longer # this_profile['dom.max_chrome_script_run_time'] = 180 # this_profile['dom.max_script_run_time'] = 180 # disable browser cache this_profile['browser.cache.disk.enable'] = false this_profile['browser.cache.disk_cache_ssl.enable'] = false this_profile['browser.cache.memory.enable'] = false this_profile['browser.cache.offline.enable'] = false this_profile['devtools.cache.disabled'] = true this_profile['dom.caches.enabled'] = false if proxy this_profile['network.proxy.type'] = 1 this_profile['network.proxy.allow_hijacking_localhost'] = true if tor_obj this_profile['network.proxy.socks_version'] = 5 this_profile['network.proxy.socks'] = tor_obj[:ip] this_profile['network.proxy.socks_port'] = tor_obj[:port] else this_profile['network.proxy.ftp'] = URI(proxy).host this_profile['network.proxy.ftp_port'] = URI(proxy).port this_profile['network.proxy.http'] = URI(proxy).host this_profile['network.proxy.http_port'] = URI(proxy).port this_profile['network.proxy.ssl'] = URI(proxy).host this_profile['network.proxy.ssl_port'] = URI(proxy).port end end args.push('--headless') options = Selenium::WebDriver::Firefox::Options.new( args: args, accept_insecure_certs: true ) options.web_socket_url = true options.profile = this_profile driver = Selenium::WebDriver.for(:firefox, options: options) browser_obj[:browser] = Watir::Browser.new(driver) when :headless_chrome this_profile = Selenium::WebDriver::Chrome::Profile.new this_profile['download.prompt_for_download'] = false this_profile['download.default_directory'] = '~/Downloads' args.push('--headless') if proxy args.push("--host-resolver-rules='MAP * 0.0.0.0 , EXCLUDE #{tor_obj[:ip]}'") if tor_obj args.push("--proxy-server=#{proxy}") end options = Selenium::WebDriver::Chrome::Options.new( args: args, accept_insecure_certs: true ) options.web_socket_url = true options.profile = this_profile driver = Selenium::WebDriver.for(:chrome, options: options) browser_obj[:browser] = Watir::Browser.new(driver) when :rest browser_obj[:browser] = RestClient if proxy if tor_obj TCPSocket.socks_server = tor_obj[:ip] TCPSocket.socks_port = tor_obj[:port] else browser_obj[:browser].proxy = proxy end end when :websocket if proxy if tor_obj TCPSocket.socks_server = tor_obj[:ip] TCPSocket.socks_port = tor_obj[:port] end proxy_opts = { origin: proxy } tls_opts = { verify_peer: false } browser_obj[:browser] = Faye::WebSocket::Client.new( '', [], { tls: tls_opts, proxy: proxy_opts } ) else browser_obj[:browser] = Faye::WebSocket::Client.new('') end else puts 'Error: browser_type only supports :firefox, :chrome, :headless, :headless_chrome, :headless_firefox, :rest, :websocket' return nil end if devtools_supported.include?(browser_type) rand_tab = SecureRandom.hex(8) browser_obj[:browser].goto("about:about##{rand_tab}") browser_obj[:browser].execute_script("document.title = '#{rand_tab}'") if with_devtools driver = browser_obj[:browser].driver browser_obj[:devtools] = driver.devtools browser_obj[:devtools].send_cmd('DOM.enable') browser_obj[:devtools].send_cmd('Log.enable') browser_obj[:devtools].send_cmd('Network.enable') browser_obj[:devtools].send_cmd('Page.enable') browser_obj[:devtools].send_cmd('Runtime.enable') browser_obj[:devtools].send_cmd('Security.enable') chrome_browser_types = %i[chrome headless_chrome] if chrome_browser_types.include?(browser_type) browser_obj[:devtools].send_cmd('Debugger.enable') browser_obj[:devtools].send_cmd('DOMStorage.enable') browser_obj[:devtools].send_cmd('DOMSnapshot.enable') end browser_obj[:bidi] = driver.bidi browser_obj[:browser].send_keys(:escape) end end browser_obj rescue StandardError => e raise e end # Supported Method Parameters:: # browser_obj = PWN::Plugins::TransparentBrowser.dump_links( # browser_obj: browser_obj1 # ) public_class_method def self.dump_links(opts = {}) browser_obj = opts[:browser_obj] links = browser_obj[:browser].links dump_links_arr = [] links.each do |link| link_hash = {} link_hash[:text] = link.text link_hash[:href] = link.href link_hash[:id] = link.id link_hash[:name] = link.name link_hash[:class_name] = link.class_name link_hash[:html] = link.html link_hash[:target] = link.target dump_links_arr.push(link_hash) yield link if block_given? end dump_links_arr rescue StandardError => e raise e end # Supported Method Parameters:: # browser_obj = PWN::Plugins::TransparentBrowser.find_elements_by_text( # browser_obj: browser_obj1, # text: 'required - text to search for in the DOM' # ) public_class_method def self.find_elements_by_text(opts = {}) browser_obj = opts[:browser_obj] text = opts[:text].to_s elements = browser_obj[:browser].elements elements_found_arr = [] elements.each do |element| begin if element.text == text || element.value == text element_hash = {} element_hash[:tag_name] = element.tag_name element_hash[:html] = element.html elements_found_arr.push(element_hash) yield element if block_given? end rescue NoMethodError next end end elements_found_arr rescue StandardError => e puts e.backtrace raise e end # Supported Method Parameters:: # PWN::Plugins::TransparentBrowser.type_as_human( # string: 'required - string to type as human', # rand_sleep_float: 'optional - float timing in between keypress (defaults to 0.09)' # ) public_class_method def self.type_as_human(opts = {}) string = opts[:string].to_s rand_sleep_float = if opts[:rand_sleep_float] opts[:rand_sleep_float].to_f else 0.09 end string.each_char do |char| yield char sleep Random.rand(rand_sleep_float) end rescue StandardError => e raise e end # Supported Method Parameters:: # console_resp = PWN::Plugins::TransparentBrowser.console( # browser_obj: browser_obj1, # js: 'required - JavaScript expression to evaluate' # ) public_class_method def self.console(opts = {}) browser_obj = opts[:browser_obj] verify_devtools_browser(browser_obj: browser_obj) js = opts[:js] ||= "alert('ACK from => #{self}')" browser = browser_obj[:browser] case js when 'debugger', 'debugger;', 'debugger()', 'debugger();' Timeout.timeout(1) { console_resp = browser.execute_script('debugger') } when 'clear', 'clear;', 'clear()', 'clear();' console_resp = browser.execute_script('console.clear()') else console_resp = browser.execute_script("console.log(#{js})") end console_resp rescue Timeout::Error, Timeout::ExitException console_resp rescue StandardError => e raise e end # Supported Method Parameters:: # tabs = PWN::Plugins::TransparentBrowser.list_tabs( # browser_obj: 'required - browser_obj returned from #open method)' # ) public_class_method def self.list_tabs(opts = {}) browser_obj = opts[:browser_obj] verify_devtools_browser(browser_obj: browser_obj) browser = browser_obj[:browser] browser.windows.map do |tab| state = :inactive state = :active if browser.title == tab.title && browser.url == tab.url { title: tab.title, url: tab.url, state: state } end rescue StandardError => e raise e end # Supported Method Parameters:: # tab = PWN::Plugins::TransparentBrowser.jmp_tab( # browser_obj: 'required - browser_obj returned from #open method)', # keyword: 'required - keyword in title or url used to switch tabs' # ) public_class_method def self.jmp_tab(opts = {}) browser_obj = opts[:browser_obj] verify_devtools_browser(browser_obj: browser_obj) keyword = opts[:keyword] raise 'ERROR: keyword parameter is required' if keyword.nil? browser = browser_obj[:browser] all_tabs = browser.windows tab_sel = all_tabs.select { |tab| tab.use if tab.title.include?(keyword) || tab.url.include?(keyword) } { title: tab_sel.last.title, url: tab_sel.last.url, state: :active } if tab_sel.any? rescue StandardError => e raise e end # Supported Method Parameters:: # tab = PWN::Plugins::TransparentBrowser.new_tab( # browser_obj: 'required - browser_obj returned from #open method)', # url: 'optional - URL to navigate to after opening new tab (Defaults to nil)' # ) public_class_method def self.new_tab(opts = {}) browser_obj = opts[:browser_obj] verify_devtools_browser(browser_obj: browser_obj) url = opts[:url] firefox_types = %i[firefox headless_firefox] browser = browser_obj[:browser] browser_type = browser_obj[:type] devtools = browser_obj[:devtools] browser.execute_script('window.open()') jmp_tab(browser_obj: browser_obj, keyword: 'about:blank') rand_tab = SecureRandom.hex(8) if url.nil? browser.goto("about:about##{rand_tab}") browser.execute_script("document.title = '#{rand_tab}'") end # Open the DevTools for Firefox, Chrome opens them automatically browser.send_keys(:f12) if firefox_types.include?(browser_type) # Open Console drawer if DevTools are open browser.send_keys(:escape) unless devtools.nil? browser.goto(url) unless url.nil? { title: browser.title, url: browser.url, state: :active } rescue StandardError => e raise e end # Supported Method Parameters:: # tab = PWN::Plugins::TransparentBrowser.close_tab( # browser_obj: 'required - browser_obj returned from #open method)', # keyword: 'required - keyword in title or url used to close tabs' # ) public_class_method def self.close_tab(opts = {}) browser_obj = opts[:browser_obj] verify_devtools_browser(browser_obj: browser_obj) keyword = opts[:keyword] raise 'ERROR: keyword parameter is required' if keyword.nil? browser = browser_obj[:browser] # Switch to an inactive tab before closing the active tab if it's currently active tab_list = list_tabs(browser_obj: browser_obj) active_tab = tab_list.find { |tab| tab[:state] == :active } if active_tab[:url].include?(keyword) inactive_tabs = tab_list.reject { |tab| tab[:url] == browser.url } if inactive_tabs.any? tab_to_activate = inactive_tabs.last[:url] jmp_tab(browser_obj: browser_obj, keyword: tab_to_activate) end end all_tabs = browser.windows tabs_to_close = all_tabs.select { |tab| tab.title.include?(keyword) || tab.url.include?(keyword) } tabs_closed = tabs_to_close.map do |tab| { title: tab.title, url: tab.url, state: :closed } end tabs_to_close.each(&:close) tabs_closed rescue StandardError => e raise e end # Supported Method Parameters:: # PWN::Plugins::TransparentBrowser.debugger( # browser_obj: 'required - browser_obj returned from #open method)', # action: 'optional - action to take :pause|:resume (Defaults to :pause)', # url: 'optional - URL to navigate to after pausing debugger (Defaults to nil)' # ) public_class_method def self.debugger(opts = {}) browser_obj = opts[:browser_obj] supported = %i[chrome headless_chrome] verify_devtools_browser(browser_obj: browser_obj, supported: supported) browser = browser_obj[:browser] action = opts[:action] ||= :pause url = opts[:url] devtools = browser_obj[:devtools] case action.to_s.downcase.to_sym when :pause console(browser_obj: browser_obj, js: 'debugger') # devtools.send_cmd('Debugger.enable') # devtools.send_cmd( # 'Debugger.setInstrumentationBreakpoint', # instrumentation: 'beforeScriptExecution' # ) # devtools.send_cmd( # 'EventBreakpoints.setInstrumentationBreakpoint', # eventName: 'load' # ) # devtools.send_cmd( # 'Debugger.setPauseOnExceptions', # state: 'all' # ) begin Timeout.timeout(1) do browser.refresh if url.nil? browser.goto(url) unless url.nil? end rescue Timeout::Error url end when :resume devtools.send_cmd('Debugger.resume') else raise 'ERROR: action parameter must be :pause or :resume' end rescue StandardError => e raise e end # Supported Method Parameters:: # PWN::Plugins::TransparentBrowser.step_into( # browser_obj: 'required - browser_obj returned from #open method)' # ) public_class_method def self.step_into(opts = {}) browser_obj = opts[:browser_obj] supported = %i[chrome headless_chrome] verify_devtools_browser(browser_obj: browser_obj, supported: supported) devtools = browser_obj[:devtools] devtools.send_cmd('Debugger.stepInto') rescue StandardError => e raise e end # Supported Method Parameters:: # PWN::Plugins::TransparentBrowser.step_out( # browser_obj: 'required - browser_obj returned from #open method)' # ) public_class_method def self.step_out(opts = {}) browser_obj = opts[:browser_obj] supported = %i[chrome headless_chrome] verify_devtools_browser(browser_obj: browser_obj, supported: supported) devtools = browser_obj[:devtools] devtools.send_cmd('Debugger.stepOut') rescue StandardError => e raise e end # Supported Method Parameters:: # PWN::Plugins::TransparentBrowser.step_over( # browser_obj: 'required - browser_obj returned from #open method)' # ) public_class_method def self.step_over(opts = {}) browser_obj = opts[:browser_obj] supported = %i[chrome headless_chrome] verify_devtools_browser(browser_obj: browser_obj, supported: supported) devtools = browser_obj[:devtools] devtools.send_cmd('Debugger.stepOver') rescue StandardError => e raise e end # Supported Method Parameters:: # PWN::Plugins::TransparentBrowser.toggle_devtools( # browser_obj: 'required - browser_obj returned from #open method)' # ) public_class_method def self.toggle_devtools(opts = {}) browser_obj = opts[:browser_obj] verify_devtools_browser(browser_obj: browser_obj) browser = browser_obj[:browser] browser.send_keys(:f12) rescue StandardError => e raise e end # Supported Method Parameters:: # PWN::Plugins::TransparentBrowser.jmp_devtools_panel( # browser_obj: 'required - browser_obj returned from #open method)', # panel: 'optional - panel to switch to :elements|:inspector|:console|:debugger|:sources|:network # ) public_class_method def self.jmp_devtools_panel(opts = {}) browser_obj = opts[:browser_obj] verify_devtools_browser(browser_obj: browser_obj) panel = opts[:panel] ||= :elements browser = browser_obj[:browser] browser_type = browser_obj[:type] firefox_types = %i[firefox headless_firefox] chrome_types = %i[chrome headless_chrome] hotkey = [] case PWN::Plugins::DetectOS.type when :linux, :openbsd, :windows hotkey = %i[control shift] when :macos hotkey = %i[command option] end case panel when :elements, :inspector hotkey.push('c') if firefox_types.include?(browser_type) when :console hotkey.push('j') if chrome_types.include?(browser_type) hotkey.push('k') if firefox_types.include?(browser_type) when :debugger, :sources hotkey.push('z') if firefox_types.include?(browser_type) when :network hotkey.push('e') if firefox_types.include?(browser_type) else raise 'ERROR: panel parameter must be :elements|:inspector|:console|:debugger|:sources|:network' end # Have to call twice for Chrome, otherwise devtools stays closed browser.send_keys(hotkey) browser.send_keys(hotkey) if chrome_types.include?(browser_type) rescue StandardError => e raise e end # Supported Method Parameters:: # browser_obj1 = PWN::Plugins::TransparentBrowser.close( # browser_obj: 'required - browser_obj returned from #open method)' # ) public_class_method def self.close(opts = {}) browser_obj = opts[:browser_obj] return nil unless browser_obj.is_a?(Hash) browser = browser_obj[:browser] tor_obj = browser_obj[:tor_obj] PWN::Plugins::Tor.stop(tor_obj: browser_obj[:tor_obj]) if tor_obj # Close the browser unless browser.nil? (thus the &) browser&.close unless browser.to_s == 'RestClient' nil rescue StandardError => e raise e end # Author(s):: 0day Inc. public_class_method def self.authors "AUTHOR(S): 0day Inc. " end # Display Usage for this Module public_class_method def self.help puts "USAGE: browser_obj1 = #{self}.open( browser_type: 'optional - :firefox|:chrome|:headless|:rest|:websocket (defaults to :chrome)', proxy: 'optional scheme://proxy_host:port || tor (defaults to nil)', with_devtools: 'optional - boolean (defaults to true)' ) browser = browser_obj1[:browser] puts browser.public_methods ******************************************************** * DevTools Interaction * All DevTools Commands can be found here: * https://chromedevtools.github.io/devtools-protocol/ * Examples devtools = browser.driver.devtools puts devtools.public_methods puts devtools.instance_variables puts devtools.instance_variable_get('@session_id') websocket = devtools.instance_variable_get('@ws') puts websocket.public_methods puts websocket.instance_variables puts websocket.instance_variable_get('@messages') * Tracing devtools.send_cmd('Tracing.start') devtools.send_cmd('Tracing.requestMemoryDump') devtools.send_cmd('Tracing.end') puts devtools.instance_variable_get('@messages') * Network devtools.send_cmd('Network.enable') last_ws_resp = devtools.instance_variable_get('@messages').last if devtools.instance_variable_get('@messages').last['method'] == 'Network.webSocketFrameReceived' puts last_ws_resp devtools.send_cmd('Network.disable') * Debugging DOM and Sending JavaScript to Console devtools.send_cmd('Runtime.enable') devtools.send_cmd('Console.enable') devtools.send_cmd('DOM.enable') devtools.send_cmd('Page.enable') devtools.send_cmd('Log.enable') devtools.send_cmd('Debugger.enable') devtools.send_cmd('Debugger.pause') step = 1 next_step = 60 loop do devtools.send_cmd('Console.clearMessages') devtools.send_cmd('Log.clear') console_events = [] browser.driver.on_log_event(:console) { |event| console_events.push(event) } devtools.send_cmd('Debugger.stepInto') puts \"Step: \#{step}\" this_document = devtools.send_cmd('DOM.getDocument') puts \"This #document:\\n\#{this_document}\\n\\n\\n\" console_cmd = { expression: 'for(var pop_var in window) { if (window.hasOwnProperty(pop_var) && window[pop_var] != null) console.log(pop_var + \" = \" + window[pop_var]); }' } puts devtools.send_cmd('Runtime.evaluate', **console_cmd) print '-' * 180 print \"\\n\" console_events.each do |event| puts event.args end puts \"Console Response Length: \#{console_events.length}\" console_events_digest = OpenSSL::Digest::SHA256.hexdigest( console_events.inspect ) puts \"Console Events Array SHA256 Digest: \#{console_events_digest}\" print '-' * 180 puts \"\\n\\n\\n\" print \"Next Step in \" next_step.downto(1) {|n| print \"\#{n} \"; sleep 1 } puts 'READY!' step += 1 end devtools.send_cmd('Debugger.disable') devtools.send_cmd('Log.disable') devtools.send_cmd('Page.disable') devtools.send_cmd('DOM.disable') devtools.send_cmd('Console.disable') devtools.send_cmd('Runtime.disable') * End of DevTools Examples ******************************************************** browser_obj1 = #{self}.dump_links( browser_obj: 'required - browser_obj returned from #open method)' ) browser_obj1 = #{self}.find_elements_by_text( browser_obj: 'required - browser_obj returned from #open method)', text: 'required - text to search for in the DOM' ) #{self}.type_as_human( string: 'required - string to type as human', rand_sleep_float: 'optional - float timing in between keypress (defaults to 0.09)' ) {|char| browser_obj1.text_field(name: \"search\").send_keys(char) } console_resp = #{self}.console( browser_obj: 'required - browser_obj returned from #open method)', js: 'required - JavaScript expression to evaluate' ) tabs = #{self}.list_tabs( browser_obj: 'required - browser_obj returned from #open method)' ) tab = #{self}.jmp_tab( browser_obj: 'required - browser_obj returned from #open method)', keyword: 'required - keyword in title or url used to switch tabs' ) tab = #{self}.new_tab( browser_obj: 'required - browser_obj returned from #open method)', url: 'optional - URL to navigate to after opening new tab (Defaults to nil)' ) tab = #{self}.close_tab( browser_obj: 'required - browser_obj returned from #open method)', keyword: 'required - keyword in title or url used to close tabs' ) #{self}.debugger( browser_obj: 'required - browser_obj returned from #open method)', action: 'optional - action to take :pause|:resume (Defaults to :pause)', url: 'optional - URL to navigate to after pausing debugger (Defaults to nil)' ) #{self}.step_into( browser_obj: 'required - browser_obj returned from #open method)' ) #{self}.step_out( browser_obj: 'required - browser_obj returned from #open method)' ) #{self}.step_over( browser_obj: 'required - browser_obj returned from #open method)' ) #{self}.toggle_devtools( browser_obj: 'required - browser_obj returned from #open method)' ) #{self}.jmp_devtools_panel( browser_obj: 'required - browser_obj returned from #open method)', panel: 'optional - panel to switch to :elements|:inspector|:console|:debugger|:sources|:network' ) browser_obj1 = #{self}.close( browser_obj: 'required - browser_obj returned from #open method)' ) #{self}.authors " end end end end