module Browsed class Client attr_accessor :configuration attr_accessor :manager, :maximum_processes attr_accessor :driver, :browser, :environment attr_accessor :session attr_accessor :device, :proxy attr_accessor :user_agent attr_accessor :resolution include Capybara::DSL def initialize(configuration: ::Browsed.configuration, driver: :poltergeist, browser: :phantomjs, device: :desktop, proxy: nil, user_agent: nil, resolution: nil, environment: :production, driver_options: {}, maximum_processes: nil) self.configuration = configuration self.driver = driver || self.configuration.driver self.browser = browser || self.configuration.browser self.environment = environment || self.configuration.environment self.device = device self.proxy = proxy self.manager = Browsed::Manager.new(browser: self.browser) self.maximum_processes = maximum_processes || self.configuration.maximum_processes set_user_agent(user_agent) set_resolution(resolution) setup_capybara(driver_options: driver_options) end include ::Browsed::Poltergeist include ::Browsed::Firefox include ::Browsed::Chrome def setup_capybara(driver_options: {}, retries: 3) if can_start_new_process? register_driver!(driver_options) Capybara.default_driver = self.driver Capybara.javascript_driver = self.driver Capybara.default_max_wait_time = driver_options.fetch(:wait_time, 30) #seconds self.session = Capybara::Session.new(self.driver) else raise Browsed::TooManyProcessesError, "Too many PhantomJS processes running, reached maximum allowed number of #{self.maximum_processes}" end end def can_start_new_process? self.maximum_processes.nil? || self.manager.can_start_more_processes?(max_count: self.maximum_processes) end def display_screenshot!(path) Launchy.open path if development? end # Resize the window separately and not based on initialization def resize!(res = nil) res ||= self.resolution if res && res.size.eql?(2) && !self.driver.eql?(:poltergeist) # Resolution for Poltergeist is set in the driver self.session.current_window.resize_to(res.first, res.last) # [width, height] end end def reset_session! self.session.reset_session! end def terminate_session! self.session.reset_session! self.session.driver.quit self.session = nil end private def register_driver!(driver_options = {}) if poltergeist? register_poltergeist_driver(driver_options: driver_options) elsif selenium? if firefox_browser? register_firefox_driver(driver_options: driver_options) elsif chrome_browser? self.driver = :selenium_chrome register_chrome_driver(driver_options: driver_options) end end end def poltergeist? self.driver.to_sym.eql?(:poltergeist) end def selenium? self.driver.to_sym.eql?(:selenium) end def firefox_browser? self.browser.to_sym.eql?(:firefox) end def chrome_browser? self.browser.to_sym.eql?(:chrome) end def development? in_environment?(:development) end def in_environment?(env) self.environment.eql?(env) end # User Agents def set_user_agent(user_agent) if !user_agent.to_s.empty? self.user_agent = user_agent elsif user_agent&.to_sym&.eql?(:randomize) || poltergeist? case self.device when :iphone self.user_agent = Agents.random_user_agent(:phones, :iphone) when :android_phone self.user_agent = Agents.random_user_agent(:phones, :android) when :ipad self.user_agent = Agents.random_user_agent(:tablets, :ipad) when :android_tablet self.user_agent = Agents.random_user_agent(:tablets, :android) else self.user_agent = Agents.random_user_agent(self.device) end end end def runs_ios? Agents.runs_ios?(self.user_agent) end def is_iphone? Agents.is_iphone?(self.user_agent) end def is_ipad? Agents.is_ipad?(self.user_agent) end # Resolution def set_resolution(res) if res && res.is_a?(Array) self.resolution = res elsif res && res.is_a?(Symbol) && res.eql?(:randomize) self.resolution = randomize_resolution end end def randomize_resolution runs_ios? ? randomize_ios_resolution : Browsed::Constants::RESOLUTIONS.fetch(self.device, :desktop).sample end def randomize_ios_resolution resolution_device = case self.device when :iphone, :android_phone :phone when :ipad, :android_tablet :tablet else self.device end random_key = Browsed::Constants::RESOLUTIONS.fetch(resolution_device, :desktop).keys.sample resolution = Browsed::Constants::RESOLUTIONS.fetch(resolution_device, :desktop)[random_key] end def wait_for_ajax Timeout.timeout(Capybara.default_max_wait_time) do loop until finished_all_ajax_requests? end end def finished_all_ajax_requests? evald = self.session.evaluate_script('jQuery.active') evald.nil? || evald.zero? end def log(message) puts "[Browsed::Client] - #{Time.now}: #{message}" if self.configuration.verbose? end end end