Class: Gless::Session
- Inherits:
-
Object
- Object
- Gless::Session
- Includes:
- RSpec::Matchers
- Defined in:
- lib/gless/session.rb
Overview
Provides an abstraction layer between the individual pages of an website and the high-level application layer, so that the application layer doesn’t have to know about what page it’s on or similar.
For details, see the README.
Instance Attribute Summary collapse
-
#acceptable_pages ⇒ Object
A list of page classes of pages that it’s OK for us to be on.
-
#current_page ⇒ Object
readonly
The page class for the page the session thinks we’re currently on.
Class Method Summary collapse
-
.add_page_class(klass) ⇒ Object
This exists only to be called by
inherited
on Gless::BasePage; see documentation there.
Instance Method Summary collapse
-
#change_pages(click_destination) { ... } ⇒ Boolean, String
Does the heavy lifting of moving between pages when an element has a new page destination.
-
#check_acceptable_pages(newpage) ⇒ Array<Gless::BasePage>
Does the heavy lifting, such as it is, for
acceptable_pages=
. -
#clear_cache(page_class = nil) ⇒ Object
Clears the cached elements.
-
#enter(pklas, always = true) ⇒ Object
This function is used to go to an intitial entry point for a website.
-
#get_config(*args) ⇒ Object
Just passes through to the Gless::EnvConfig component’s
get
method. -
#get_config_default(*args) ⇒ Object
Just passes through to the Gless::EnvConfig component’s
get_default
method. -
#handle_alert(wait_for_alert = true, expected_text = nil) ⇒ Object
Deals with popup alerts in the browser (i.e. the javascript alert() function).
-
#initialize(browser, config, logger, application) ⇒ Session
constructor
Sets up the session object.
-
#log ⇒ Object
Just a shortcut to get to the Gless::Logger object.
-
#long_wait(message, opts = {}) ⇒ Boolean
Wait for long-term AJAX-style processing, i.e.
-
#method_missing(m, *args, &block) ⇒ Object
Anything that we don’t otherwise recognize is passed on to the current underlying page object (i.e. descendant of Gless::BasePage).
Constructor Details
#initialize(browser, config, logger, application) ⇒ Session
Sets up the session object. As the core abstraction layer that sits in the middle of everything, this requires a number of arguments. :)
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/gless/session.rb', line 59 def initialize( browser, config, logger, application ) @logger = logger log.debug "Session: Initializing with #{browser.inspect}" @browser = browser @application = application @pages = Hash.new @timeout = config.get_default( 600, :global, :browser, :timeout ) @acceptable_pages = nil @config = config @@page_classes.each do |sc| @pages[sc] = sc.new( @browser, self, @application ) end log.debug "Session: Final pages table: #{@pages.keys.map { |x| x.name }}" return self end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(m, *args, &block) ⇒ Object
Anything that we don’t otherwise recognize is passed on to the current underlying page object (i.e. descendant of Gless::BasePage).
This gets complicated because of the state checking: we test extensively that we’re on the page that we think we should be on before passing things on to the page object.
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/gless/session.rb', line 104 def method_missing(m, *args, &block) # Do some logging. if m.inspect =~ /(password|login)/i or args.inspect =~ /(password|login)/i log.debug "Session: Doing something with passwords, redacted." else log.debug "Session: method_missing for #{m} with arguments #{args.inspect}" end log.debug "Session: check if we've changed pages: #{@browser.title}, #{@browser.url}, #{@previous_url}, #{@current_page}, #{@acceptable_pages}" # Changed URL means we've changed pages, probably by surprise # since desired page changes happen in Gless::WrapWatir#click if @browser.url == @previous_url log.debug "Session: doesn't look like we've moved." else # See if we're on one of the acceptable pages. We do no # significant waiting because Gless::WrapWatir#click should # have handeled that. good_page=false new_page=nil if @acceptable_pages.nil? # If we haven't gone anywhere yet, anything is good good_page = true new_page = @pages[@current_page] else @acceptable_pages.each do |page| log.debug "Session: Checking our current url, #{@browser.url}, for a match in #{page.name}: #{@pages[page].match_url(@browser.url)}" if @pages[page].match_url(@browser.url) clear_cache good_page = true @current_page = page new_page = @pages[page] log.debug "Session: we seem to be on #{page.name} at #{@browser.url}" end end end good_page.should be_truthy, "Current URL is #{@browser.url}, which doesn't match any of the acceptable pages: #{@acceptable_pages}" url=@browser.url log.debug "Session: refreshed browser URL: #{url}" new_page.match_url(url).should be_truthy log.info "Session: We are currently on page #{new_page.class.name}, as we should be" @previous_url = url end # End of page checking code. cpage = @pages[@current_page] if m.inspect =~ /(password|login)/i or args.inspect =~ /(password|login)/i log.debug "Session: dispatching method #{m} with args [redacted; password maybe] to #{cpage}" else log.debug "Session: dispatching method #{m} with args #{args.inspect} to #{cpage}" end retval = cpage.send(m, *args, &block) log.debug "Session: method returned #{retval}" retval end |
Instance Attribute Details
#acceptable_pages ⇒ Object
A list of page classes of pages that it’s OK for us to be on. Usually just one, but some site workflows might have more than one thing that can happen when you click a button or whatever.
When you assign a value here, a fair bit of processing is done. Most of the actual work is in check_acceptable_pages
The user can give us a class, a symbol, or a list of those; no matter what, we return a list. That list is of possible pages that, if we turn out to be on one of them, that’s OK, and if not we freak out.
33 34 35 |
# File 'lib/gless/session.rb', line 33 def acceptable_pages @acceptable_pages end |
#current_page ⇒ Object (readonly)
The page class for the page the session thinks we’re currently on.
16 17 18 |
# File 'lib/gless/session.rb', line 16 def current_page @current_page end |
Class Method Details
.add_page_class(klass) ⇒ Object
This exists only to be called by inherited
on Gless::BasePage; see documentation there.
44 45 46 47 |
# File 'lib/gless/session.rb', line 44 def self.add_page_class( klass ) @@page_classes ||= [] @@page_classes << klass end |
Instance Method Details
#change_pages(click_destination) { ... } ⇒ Boolean, String
Does the heavy lifting of moving between pages when an element has a new page destination. Mostly used by Gless::WrapWatir
Note that this attempts to click on the button (or do whatever else the passed block does) many times in an attempt to get to the right page. If multiple attempts are a problem, you should circumvent this method; WrapWatir#click_once exists for this purpose.
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 |
# File 'lib/gless/session.rb', line 375 def change_pages click_destination self.acceptable_pages = click_destination log.debug "Session: change_pages: checking to see if we have changed pages: #{@browser.title}, #{@current_page}, #{@acceptable_pages}" good_page = false = nil new_page = nil exceptions = {} # See if we're on one of the acceptable pages; wait until we # are for "timeout" seconds. start_time = Time.now.to_i while true self.log.debug "Session: change_pages: yielding to passed block." begin yield if block_given? rescue Watir::Exception::UnknownObjectException => e ||= "Caught UnknownObjectExepction in the block we were passed; are the validators for #{@acceptable_pages} correct? Are you sure that's the right list of pages? Here's the exception: #{e.inspect}" end self.log.debug "Session: change_pages: done yielding to passed block." # We're *definitely* staying on the same page; don't do any # more work to check where we are if @acceptable_pages.member?( @current_page ) and @acceptable_pages.length == 1 good_page = true new_page = @current_page break else new_page = nil if @acceptable_pages.nil? # If we haven't gone anywhere yet, anything is good log.debug "Session: change_pages: no acceptable pages, so accepting the current page." good_page = true new_page = @pages[@current_page] break end url=@browser.url log.debug "Session: change_pages: refreshed browser URL: #{url}" @acceptable_pages.each do |page| log.debug "Session: change_pages: Checking our current url, #{url}, for a match in #{page.name}: #{@pages[page].match_url(url)}" begin if @pages[page].match_url(url) and @pages[page].arrived? == true clear_cache good_page = true @current_page = page new_page = @pages[page] log.debug "Session: change_pages: we seem to be on #{page.name} at #{url}" break end rescue StandardError => e # Catching exceptions from "arrived?"; in this case we don't # care until later exceptions[page.name] = e.inspect end end if good_page == true break else sleep 1 end end if (Time.now.to_i - start_time) > @timeout break end end if good_page log.info "Session: change_pages: We have successfully moved to page #{new_page.name}" @previous_url = url else # Timed out. ||= "Session: change_pages: attempt to change pages to #{click_destination} timed out after #{@timeout} seconds, more or less. If the clicked element exists, are the validators for #{@acceptable_pages} correct? Here are the exceptions from each page we tried: #{YAML.dump(exceptions)}" end return good_page, end |
#check_acceptable_pages(newpage) ⇒ Array<Gless::BasePage>
Does the heavy lifting, such as it is, for acceptable_pages=
343 344 345 346 347 348 349 350 351 352 353 |
# File 'lib/gless/session.rb', line 343 def check_acceptable_pages newpage if newpage.kind_of? Class return [ newpage ] elsif newpage.kind_of? Symbol return [ @pages.keys.find { |x| x.name =~ /(^|::)#{newpage.to_s}$/ } ] elsif newpage.kind_of? Array return newpage.map { |p| check_acceptable_pages p } else raise "You set the acceptable_pages to #{newpage.class.name}; unhandled" end end |
#clear_cache(page_class = nil) ⇒ Object
Clears the cached elements. Used before each page change.
332 333 334 |
# File 'lib/gless/session.rb', line 332 def clear_cache page_class = nil @pages[page_class || current_page].cached_elements = Hash.new end |
#enter(pklas, always = true) ⇒ Object
This function is used to go to an intitial entry point for a website. The page in question must have had set_entry_url run in its class definition, to define how to do this. This setup exists because explaining to the session that we really should be on that page is a bit tricky.
176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/gless/session.rb', line 176 def enter(pklas, always = true) log.info "Session: Entering the site directly using the entry point for the #{pklas.name} page class" if always || pklas != @current_page @current_page = pklas @pages[pklas].enter # Needs to run through our custom acceptable_pages= method self.acceptable_pages = pklas else log.debug "Session: Already on page" end end |
#get_config(*args) ⇒ Object
Just passes through to the Gless::EnvConfig component’s get
method.
82 83 84 |
# File 'lib/gless/session.rb', line 82 def get_config(*args) @config.get(*args) end |
#get_config_default(*args) ⇒ Object
Just passes through to the Gless::EnvConfig component’s get_default
method.
88 89 90 |
# File 'lib/gless/session.rb', line 88 def get_config_default(*args) @config.get_default(*args) end |
#handle_alert(wait_for_alert = true, expected_text = nil) ⇒ Object
Deals with popup alerts in the browser (i.e. the javascript alert() function). Always clicks “ok” or equivalent.
Note that we’re using @browser because things can be a bit wonky during an alert; we don’t want to run session’s “are we on the right page?” tests, or even talk to the page object.
is present, failing if the request times out, before processing it; otherwise, handle any alerts if there are any currently present.
pop-up alert is checked against this parameter; if it differs, an exception will be raised.
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/gless/session.rb', line 301 def handle_alert wait_for_alert = true, expected_text = nil @browser.alert.wait_until_present if wait_for_alert if @browser.alert.exists? begin if expected_text current_text = @browser.alert.text if (expected_text.kind_of? Regexp) ? expected_text !~ current_text : expected_text != current_text msg = "The actual alert text differs from what was expected. current_text: #{current_text}; expected_text: #{expected_text}" @logger.error msg raise msg end end @browser.alert.ok rescue Selenium::WebDriver::Error::NoAlertPresentError => e msg = "Alert no longer exists; likely closed by user: #{e.}" if wait_for_alert @logger.warn msg raise else @logger.info msg end end end end |
#log ⇒ Object
Just a shortcut to get to the Gless::Logger object.
93 94 95 |
# File 'lib/gless/session.rb', line 93 def log @logger end |
#long_wait(message, opts = {}) ⇒ Boolean
Wait for long-term AJAX-style processing, i.e. watch the page for extended amounts of time until particular events have occured.
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/gless/session.rb', line 218 def long_wait , opts = {} # Merge in the defaults opts = { :numtimes => 120, :interval => 30, :any_elements => nil, :all_elements => nil }.merge(opts) begin opts[:numtimes].times do |count| # Run a code block if given; might do other checks, or # click things we need to finish, or whatever if block_given? self.log.debug "Session: long_wait: yielding to passed block." blockout = yield if blockout == true return true end end # If any of these are present, we're done. if opts[:any_elements] opts[:any_elements].each do |elem| self.log.debug "Session: long_wait: in any_elements, looking for #{elem}" if elem.present? self.log.debug "Session: long_wait: completed due to the presence of #{elem}" return true end end end # If all of these are present, we're done. if opts[:all_elements] all_elems=true opts[:all_elements].each do |elem| self.log.debug "Session: long_wait: in all_elements, looking for #{elem}" if ! elem.present? all_elems=false end end if all_elems == true self.log.debug "Session: long_wait: completed due to the presence of all off #{opts[:all_elements]}" return true end end # We're still here, let the user know self.log.info if (((count + 1) % 20) == 0) && (self.get_config :global, :debug) self.log.debug "Session: long_wait: We've waited a multiple of 20 times, so giving you a debugger; 'c' to continue." debugger end sleep opts[:interval] end rescue Exception => e self.log.warn "Session: long_wait: Had an exception #{e}" if self.get_config :global, :debug self.log.debug "Session: long_wait: Had an exception in debug mode: #{e.inspect}" self.log.debug "Session: long_wait: Had an exception in debug mode: #{e.}" self.log.debug "Session: long_wait: Had an exception in debug mode: #{e.backtrace.join("\n")}" self.log.debug "Session: long_wait: Had an exception, and you're in debug mode, so giving you a debugger. Use 'continue' to proceed." debugger end self.log.debug "Session: long_wait: Retrying after exception." retry end return false end |