mirror of
https://github.com/TracksApp/tracks.git
synced 2025-12-18 00:00:12 +01:00
Upgrade Selenium on Rails to r140
This commit is contained in:
parent
156862200b
commit
40074c71ad
117 changed files with 16789 additions and 8867 deletions
332
vendor/plugins/selenium-on-rails/README
vendored
332
vendor/plugins/selenium-on-rails/README
vendored
|
|
@ -1,332 +0,0 @@
|
|||
|
||||
Installation for rails versions before Rails 2.1
|
||||
|
||||
Rails has been changed in ways that break the original versions of Selenium on Rails. If you're using versions before Rails 2.1 you need to use this release. There are no plans to update this release with new changes or bug fixes unless there is sufficient demand, so if you can update then do so.
|
||||
|
||||
1. Install Selenium on Rails: script/plugin install http://svn.openqa.org/svn/selenium-on-rails/tags/pre-rails-2-1/selenium-on-rails
|
||||
2. If you‘re on Windows, gem install win32-open3
|
||||
3. If the RedCloth gem is available the Selenese test cases can use it for better markup.
|
||||
4. Run the Rakefile in the plugin‘s directory to run the tests in order to see that everything works. (If RedCloth isn‘t installed a few tests will fail since they assume RedCloth is installed.)
|
||||
5. Create a test case: script/generate selenium login
|
||||
6. Start the server: script/server -e test
|
||||
7. Point your browser to localhost:3000/selenium
|
||||
8. If everything works as expected you should see the Selenium test runner. The north east frame contains all your test cases (just one for now), and the north frame contains your test case.
|
||||
|
||||
Formats
|
||||
|
||||
The test cases can be written in a number of formats. Which one you choose is a matter of taste. You can generate your test files by running script/generate selenium or by creating them manually in your /test/selenium directory.
|
||||
RSelenese, .rsel
|
||||
|
||||
RSelenese enable you to write your tests in Ruby. This is my favorite format.
|
||||
|
||||
setup :fixtures => :all
|
||||
open '/'
|
||||
assert_title 'Home'
|
||||
('a'..'z').each {|c| open :controller => 'user', :action => 'create', :name => c }
|
||||
|
||||
See SeleniumOnRails::TestBuilder for available commands. This is also available in the SeleniumIDE, using the format here. IMPORTANT NOTE: RSelenese generates the HTML tables for Selenium behind the scenes when the page is loaded - ONCE. That means code like this:
|
||||
|
||||
(1..10).each do |index|
|
||||
do something
|
||||
end
|
||||
|
||||
Will only be executed when the test is loaded, not when the test is run. This is a common error and leads to tests that work the first time and fail the second time.
|
||||
Selenese, .sel
|
||||
|
||||
Selenese is the dumbest format (in a good way). You just write your commands delimited by | characters.
|
||||
|
||||
|open|/selenium/setup|
|
||||
|open|/|
|
||||
|goBack|
|
||||
|
||||
If you don‘t want to write Selenese tests by hand you can use SeleniumIDE which has support for Selenese.
|
||||
|
||||
SeleniumIDE makes it super easy to record test and edit them.
|
||||
HTML/RHTML
|
||||
|
||||
You can write your tests in HTML/RHTML but that‘s mostly useful if you have existing tests you want to reuse.
|
||||
Partial test cases
|
||||
|
||||
If you have some common actions you want to do in several test cases you can put them in a separate partial test case and include them in your other test cases. This is highly recommended, just as small functions would be recommended in structured programming.
|
||||
|
||||
A partial test case is just like a normal test case besides that its filename has to start with _:
|
||||
|
||||
#_login.rsel
|
||||
open '/login'
|
||||
type 'name', name
|
||||
type 'password', password
|
||||
click 'submit', :wait=>true
|
||||
|
||||
To include a partial test case in a RSelenese test case:
|
||||
|
||||
include_partial 'login', :name => 'Jane Doe', :password => 'Jane Doe'.reverse
|
||||
|
||||
in a Selenese test case:
|
||||
|
||||
|includePartial|login|name=John Doe|password=eoD nhoJ|
|
||||
|
||||
and in a RHTML test case:
|
||||
|
||||
<%= render :partial => 'login', :locals => {:name = 'Joe Schmo', :password => 'Joe Schmo'.reverse} %>
|
||||
|
||||
Configuration
|
||||
|
||||
There are a number of settings available. You make them by renaming config.yml.example to config.yml and make your changes in that file.
|
||||
Environments
|
||||
|
||||
Per default this plugin is only available in test environment. You can change this by setting environments, such as:
|
||||
|
||||
#config.yml
|
||||
environments:
|
||||
- test
|
||||
- development
|
||||
|
||||
Selenium Core path
|
||||
|
||||
If you don‘t want to use the bundled Selenium Core version you can set selenium_path to the directory where Selenium Core is stored.
|
||||
|
||||
#config.yml
|
||||
selenium_path: 'c:\selenium'
|
||||
|
||||
test:acceptance
|
||||
|
||||
You can run all your Selenium tests as a Rake task. If you're using a continuous builder this is a great way to integrate selenium into your build process.
|
||||
|
||||
First, if you‘re on Windows, you have to make sure win32-open3 is installed. Then you have to configure which browsers you want to run, like this:
|
||||
|
||||
#config.yml
|
||||
browsers:
|
||||
firefox: 'c:\Program Files\Mozilla Firefox\firefox.exe'
|
||||
ie: 'c:\Program Files\Internet Explorer\iexplore.exe'
|
||||
|
||||
Now you‘re all set. First start a server:
|
||||
|
||||
script/server -e test
|
||||
|
||||
Then run the tests:
|
||||
|
||||
rake test:acceptance
|
||||
|
||||
Now it should work, otherwise let me know!
|
||||
Store results
|
||||
|
||||
If you want to store the results from a test:acceptance you just need to set in which directory they should be stored:
|
||||
|
||||
#config.yml
|
||||
result_dir: 'c:\result'
|
||||
|
||||
So when you run rake test:acceptance the tables with the results will be stored as .html files in that directory.
|
||||
|
||||
This can be useful especially for continous integration.
|
||||
|
||||
|
||||
= Selenium on Rails
|
||||
|
||||
== Overview
|
||||
|
||||
Selenium on Rails provides an easy way to test Rails application with
|
||||
SeleniumCore[http://www.openqa.org/selenium-core/].
|
||||
|
||||
This plugin does four things:
|
||||
1. The Selenium Core files don't have to pollute <tt>/public</tt>.
|
||||
2. No need to create suite files, they are generated on the fly -- one suite per directory in <tt>/test/selenium</tt> (suites can be nested).
|
||||
3. Instead of writing the test cases in HTML you can use a number of better formats (see <tt>Formats</tt>).
|
||||
4. Loading of fixtures and wiping of session (<tt>/selenium/setup</tt>).
|
||||
|
||||
== Installation
|
||||
|
||||
== Installation for Rails 2.1
|
||||
|
||||
1. Install Selenium on Rails: script/plugin install http://svn.openqa.org/svn/selenium-on-rails/current/selenium-on-rails
|
||||
2. If you‘re on Windows, gem install win32-open3
|
||||
3. If the RedCloth gem is available the Selenese test cases can use it for better markup.
|
||||
4. Run the Rakefile in the plugin‘s directory to run the tests in order to see that everything works. (If RedCloth isn‘t installed a few tests will fail since they assume RedCloth is installed.)
|
||||
5. Create a test case: script/generate selenium login
|
||||
6. Start the server: script/server -e test
|
||||
7. Point your browser to localhost:3000/selenium
|
||||
8. If everything works as expected you should see the Selenium test runner. The north east frame contains all your test cases (just one for now), and the north frame contains your test case.
|
||||
|
||||
== Installation for rails versions before Rails 2.1
|
||||
|
||||
Rails has been changed in ways that break the original versions of Selenium on Rails. If you're using versions before Rails 2.1 you need to use this release. There are no plans to update this release with new changes or bug fixes unless there is sufficient demand, so if you can update then do so.
|
||||
|
||||
1. Install Selenium on Rails: script/plugin install http://svn.openqa.org/svn/selenium-on-rails/tags/pre-rails-2-1/selenium-on-rails
|
||||
2. If you‘re on Windows, gem install win32-open3
|
||||
3. If the RedCloth gem is available the Selenese test cases can use it for better markup.
|
||||
4. Run the Rakefile in the plugin‘s directory to run the tests in order to see that everything works. (If RedCloth isn‘t installed a few tests will fail since they assume RedCloth is installed.)
|
||||
5. Create a test case: script/generate selenium login
|
||||
6. Start the server: script/server -e test
|
||||
7. Point your browser to localhost:3000/selenium
|
||||
8. If everything works as expected you should see the Selenium test runner. The north east frame contains all your test cases (just one for now), and the north frame contains your test case.
|
||||
|
||||
|
||||
== Formats
|
||||
|
||||
The test cases can be written in a number of formats. Which one you choose is a
|
||||
matter of taste. You can generate your test files by running
|
||||
<tt>script/generate selenium</tt> or by creating them manually in your
|
||||
<tt>/test/selenium</tt> directory.
|
||||
|
||||
=== Selenese, .sel
|
||||
|
||||
Selenese is the dumbest format (in a good way). You just write your commands
|
||||
delimited by | characters.
|
||||
|
||||
|open|/selenium/setup|
|
||||
|open|/|
|
||||
|goBack|
|
||||
|
||||
If you don't want to write Selenese tests by hand you can use
|
||||
SeleniumIDE[http://www.openqa.org/selenium-ide/] which has
|
||||
support[http://wiki.openqa.org/display/SIDE/SeleniumOnRails] for Selenese.
|
||||
|
||||
SeleniumIDE makes it super easy to record test and edit them.
|
||||
|
||||
=== RSelenese, .rsel
|
||||
|
||||
RSelenese enable you to write your tests in Ruby.
|
||||
|
||||
setup :fixtures => :all
|
||||
open '/'
|
||||
assert_title 'Home'
|
||||
('a'..'z').each {|c| open :controller => 'user', :action => 'create', :name => c }
|
||||
|
||||
See SeleniumOnRails::TestBuilder for available commands.
|
||||
|
||||
=== HTML/RHTML
|
||||
|
||||
You can write your tests in HTML/RHTML but that's mostly useful if you have
|
||||
existing tests you want to reuse.
|
||||
|
||||
=== Partial test cases
|
||||
|
||||
If you have some common actions you want to do in several test cases you can put
|
||||
them in a separate partial test case and include them in your other test cases.
|
||||
|
||||
A partial test case is just like a normal test case besides that its filename
|
||||
has to start with _:
|
||||
|
||||
#_login.rsel
|
||||
open '/login'
|
||||
type 'name', name
|
||||
type 'password', password
|
||||
click 'submit', :wait=>true
|
||||
|
||||
To include a partial test case you write like this in a Selenese test case:
|
||||
|
||||
|includePartial|login|name=John Doe|password=eoD nhoJ|
|
||||
|
||||
in a RSelenese test case:
|
||||
|
||||
include_partial 'login', :name => 'Jane Doe', :password => 'Jane Doe'.reverse
|
||||
|
||||
and in a RHTML test case:
|
||||
|
||||
<%= render :partial => 'login', :locals => {:name = 'Joe Schmo', :password => 'Joe Schmo'.reverse} %>
|
||||
|
||||
== Configuration
|
||||
|
||||
There are a number of settings available. You make them by copying <tt>config.yml.example</tt> to <tt>config/selenium.yml</tt> in your application and make your changes in that file.
|
||||
|
||||
=== Environments
|
||||
|
||||
Per default this plugin is only available in test environment. You can change
|
||||
this by setting <tt>environments</tt>, such as:
|
||||
|
||||
#config.yml
|
||||
environments:
|
||||
- test
|
||||
- development
|
||||
|
||||
=== Selenium Core path
|
||||
|
||||
If you don't want to use the bundled Selenium Core version you can set
|
||||
<tt>selenium_path</tt> to the directory where Selenium Core is stored.
|
||||
|
||||
#config.yml
|
||||
selenium_path: 'c:\selenium'
|
||||
|
||||
== <tt>test:acceptance</tt>
|
||||
|
||||
You can run all your Selenium tests as a Rake task.
|
||||
|
||||
First, if you're on Windows, you have to make sure win32-open3 is installed.
|
||||
Then you have to configure which browsers you want to run, like this:
|
||||
|
||||
#config.yml
|
||||
browsers:
|
||||
firefox: 'c:\Program Files\Mozilla Firefox\firefox.exe'
|
||||
ie: 'c:\Program Files\Internet Explorer\iexplore.exe'
|
||||
|
||||
Now you're all set. First start a server:
|
||||
|
||||
script/server -e test
|
||||
|
||||
Then run the tests:
|
||||
|
||||
rake test:acceptance
|
||||
|
||||
Now it should work, otherwise let me know!
|
||||
|
||||
=== Store results
|
||||
|
||||
If you want to store the results from a <tt>test:acceptance</tt> you just need
|
||||
to set in which directory they should be stored:
|
||||
|
||||
#config.yml
|
||||
result_dir: 'c:\result'
|
||||
|
||||
So when you run <tt>rake test:acceptance</tt> the tables with the results will
|
||||
be stored as <tt>.html</tt> files in that directory.
|
||||
|
||||
This can be useful especially for continous integration.
|
||||
|
||||
=== user_extension.js
|
||||
|
||||
Selenium has support for <tt>user_extension.js</tt> which is a way to extend the
|
||||
functionality of Selenium Core. Selenium on Rails now provides the means for you
|
||||
to extend it's functionality to match.
|
||||
|
||||
To get you started, we've included the example files
|
||||
<tt>lib/test_builder_user_accessors.rb.example</tt> and
|
||||
<tt>lib/test_builder_user_actions.rb.example</tt> that replicate the sample
|
||||
extensions in Selenium Core's <tt>user-extensions.js.sample</tt>
|
||||
|
||||
To get these examples running, simply remove the .example and .sample extensions
|
||||
from the files and restart your server.
|
||||
|
||||
== Todo
|
||||
|
||||
=== Standalone mode
|
||||
|
||||
More work is needed on <tt>test:acceptance</tt> on Windows to be able to start
|
||||
the server when needed.
|
||||
|
||||
=== More setup/teardown support?
|
||||
|
||||
Currently there is only support to load fixtures and to wipe the session in
|
||||
<tt>/selenium/setup</tt>. Is there a need for more kinds of setups or teardowns?
|
||||
|
||||
=== More documentation
|
||||
|
||||
|
||||
== Not todo
|
||||
|
||||
=== Editor
|
||||
|
||||
Creating an editor for the test cases is currently considered out of scope for
|
||||
this plugin. SeleniumIDE[http://www.openqa.org/selenium-ide/] does such a good
|
||||
job and has support[http://wiki.openqa.org/display/SIDE/SeleniumOnRails] for
|
||||
the Selenese format.
|
||||
|
||||
== Credits
|
||||
|
||||
* Jon Tirsen, http://jutopia.tirsen.com -- initial inspiration[http://wiki.rubyonrails.com/rails/pages/SeleniumIntegration]
|
||||
* Eric Kidd, http://www.randomhacks.net -- contribution of RSelenese
|
||||
* Jonas Bengston -- original creator
|
||||
* Marcos Tapajos -- Several useful features
|
||||
* Ryan Bates, http://railscasts.com -- Fixes for Rails 2.1
|
||||
|
||||
== Information
|
||||
|
||||
For more information, check out the website[http://www.openqa.org/selenium-on-rails/].
|
||||
202
vendor/plugins/selenium-on-rails/README.md
vendored
Normal file
202
vendor/plugins/selenium-on-rails/README.md
vendored
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
Welcome to the Selenium on Rails README. Exciting isn't it?
|
||||
|
||||
# Selenium on Rails #
|
||||
|
||||
## Overview ##
|
||||
|
||||
Selenium on Rails provides an easy way to test Rails application with
|
||||
SeleniumCore[http://www.openqa.org/selenium-core/].
|
||||
|
||||
This plugin does four things:
|
||||
|
||||
1. The Selenium Core files don't have to pollute <tt>/public</tt>.
|
||||
2. No need to create suite files, they are generated on the fly -- one suite per directory in <tt>/test/selenium</tt> (suites can be nested).
|
||||
3. Instead of writing the test cases in HTML you can use a number of better formats (see <tt>Formats</tt>).
|
||||
4. Loading of fixtures and wiping of session (<tt>/selenium/setup</tt>).
|
||||
|
||||
## Installation ##
|
||||
|
||||
Rails periodically changes the way that it renders pages, which unfortunately breaks backwards versions of Selenium on Rails. Therefore there are different
|
||||
installation locations depending on your version of Rails:
|
||||
|
||||
*Rails 2.2 and up:*
|
||||
|
||||
http://svn.openqa.org/svn/selenium-on-rails/stable/selenium-on-rails
|
||||
|
||||
|
||||
*Rails 2.1:*
|
||||
|
||||
http://svn.openqa.org/svn/selenium-on-rails/tags/rails_2_1/selenium-on-rails
|
||||
|
||||
|
||||
*Before Rails 2.1:*
|
||||
|
||||
http://svn.openqa.org/svn/selenium-on-rails/tags/pre-rails-2-1/selenium-on-rails
|
||||
|
||||
|
||||
The latest release is always kept on GitHub at
|
||||
|
||||
git clone git://github.com/paytonrules/selenium-on-rails.git
|
||||
|
||||
|
||||
To install:
|
||||
|
||||
1. Install Selenium on Rails: script/plugin install <URL>
|
||||
2. If you‘re on Windows, gem install win32-open3
|
||||
3. If the RedCloth gem is available the Selenese test cases can use it for better markup.
|
||||
4. Run the Rakefile in the plugin‘s directory to run the tests in order to see that everything works. (If RedCloth isn‘t installed a few tests will fail since they assume RedCloth is installed.)
|
||||
5. Create a test case: script/generate selenium <TestName>
|
||||
6. Start the server: script/server -e test
|
||||
7. Point your browser to localhost:3000/selenium
|
||||
8. If everything works as expected you should see the Selenium test runner. The north east frame contains all your test cases (just one for now), and the north frame contains your test case.
|
||||
|
||||
## Formats ##
|
||||
|
||||
The test cases can be written in a number of formats. Which one you choose is a matter of taste. You can generate your test files by running script/generate selenium or by creating them manually in your /test/selenium directory.
|
||||
|
||||
## RSelenese, .rsel ##
|
||||
|
||||
RSelenese lets you write your tests in Ruby. This is my favorite format.
|
||||
|
||||
setup :fixtures => :all
|
||||
open '/'
|
||||
assert_title 'Home'
|
||||
('a'..'z').each {|c| open :controller => 'user', :action => 'create', :name => c }
|
||||
|
||||
See SeleniumOnRails::TestBuilder for available commands. *IMPORTANT NOTE:* RSelenese generates the HTML tables for Selenium behind the scenes when the page is loaded - ONCE. That means code like this:
|
||||
|
||||
(1..10).each do |index|
|
||||
do something
|
||||
end
|
||||
|
||||
Will only be executed when the test is loaded, not when the test is run. This is a common error and leads to tests that work the first time and fail the second time.
|
||||
|
||||
## Selenese, .sel ##
|
||||
|
||||
Selenese is the dumbest format (in a good way). You just write your commands delimited by | characters.
|
||||
|
||||
|open|/selenium/setup|
|
||||
|open|/|
|
||||
|goBack|
|
||||
|
||||
If you don‘t want to write Selenese tests by hand you can use SeleniumIDE which has support for Selenese.
|
||||
|
||||
## HTML/RHTML ##
|
||||
|
||||
You can write your tests in HTML/RHTML but that‘s mostly useful if you have existing tests you want to reuse.
|
||||
|
||||
## Partial test cases ##
|
||||
|
||||
If you have some common actions you want to do in several test cases you can put them in a separate partial test case and include them in your other test cases. This is highly recommended, just as small functions would be recommended in structured programming.
|
||||
|
||||
A partial test case is just like a normal test case besides that its filename has to start with _:
|
||||
|
||||
#_login.rsel
|
||||
open '/login'
|
||||
type 'name', name
|
||||
type 'password', password
|
||||
click 'submit', :wait=>true
|
||||
|
||||
To include a partial test case in a RSelenese test case:
|
||||
|
||||
include_partial 'login', :name => 'Jane Doe', :password => 'Jane Doe'.reverse
|
||||
|
||||
in a Selenese test case:
|
||||
|
||||
|includePartial|login|name=John Doe|password=eoD nhoJ|
|
||||
|
||||
and in a RHTML test case:
|
||||
|
||||
<%= render :partial => 'login', :locals => {:name = 'Joe Schmo', :password => 'Joe Schmo'.reverse} %>
|
||||
|
||||
## Configuration ##
|
||||
|
||||
There are a number of settings available. You make them by renaming selenium.yml.example to selenium.yml and placing it in your rails app's config
|
||||
file. Make your changes in that file.
|
||||
|
||||
## Environments ##
|
||||
|
||||
Per default this plugin is only available in test environment. You can change this by setting environments, such as:
|
||||
|
||||
#selenium.yml
|
||||
environments:
|
||||
- test
|
||||
- development
|
||||
|
||||
## Selenium Core path ##
|
||||
|
||||
If you don‘t want to use the bundled Selenium Core version you can set selenium_path to the directory where Selenium Core is stored.
|
||||
|
||||
#config.yml
|
||||
selenium_path: 'c:\selenium'
|
||||
|
||||
## Rake Task ##
|
||||
|
||||
You can run all your Selenium tests as a Rake task. If you're using a continuous builder this is a great way to integrate selenium into your build process. First, if you‘re on Windows, you have to make sure win32-open3 is installed. Then you have to configure which browsers you want to run, like this:
|
||||
|
||||
|
||||
#config.yml
|
||||
browsers:
|
||||
firefox: 'c:\Program Files\Mozilla Firefox\firefox.exe'
|
||||
ie: 'c:\Program Files\Internet Explorer\iexplore.exe'
|
||||
|
||||
Now you‘re all set. First start a server:
|
||||
|
||||
script/server -e test
|
||||
|
||||
Then run the tests:
|
||||
|
||||
rake test:acceptance
|
||||
|
||||
Now it should work, otherwise let me know!
|
||||
|
||||
## Store results ##
|
||||
|
||||
If you want to store the results from a test:acceptance you just need to set in which directory they should be stored:
|
||||
|
||||
#config.yml
|
||||
result_dir: 'c:\result'
|
||||
|
||||
So when you run rake test:acceptance the tables with the results will be stored as .html files in that directory.
|
||||
|
||||
## user_extension.js ##
|
||||
|
||||
Selenium has support for user_extension.js which is a way to extend the functionality of Selenium Core. Selenium on Rails now provides the means for you to extend it's functionality to match.
|
||||
|
||||
To get you started, we've included the example files lib/test\_builder\_user\_accessors.rb.example and lib/test\_builder\_user\_actions.rb.example that replicate the sample extensions in Selenium Core's user-extensions.js.sample.
|
||||
|
||||
To get these examples running, simply remove the .example and .sample extensions
|
||||
from the files and restart your server.
|
||||
|
||||
## Todo ##
|
||||
|
||||
* Standalone mode
|
||||
More work is needed on test:acceptance< on Windows to be able to start the server when needed.
|
||||
|
||||
* Documentation update
|
||||
|
||||
|
||||
## Not todo ##
|
||||
|
||||
* Editor
|
||||
Creating an editor for the test cases is currently considered out of scope for this plugin. SeleniumIDE[http://www.openqa.org/selenium-ide/] does such a good job and has support[http://wiki.openqa.org/display/SIDE/SeleniumOnRails] for both the Selenese and RSelenese formats.
|
||||
|
||||
## Credits ##
|
||||
|
||||
* Jonas Bengston -- original creator
|
||||
* Eric Smith, http://blog.8thlight.com/eric -- Current Maintainer
|
||||
* Jon Tirsen, http://jutopia.tirsen.com -- initial inspiration[http://wiki.rubyonrails.com/rails/pages/SeleniumIntegration]
|
||||
* Eric Kidd, http://www.randomhacks.net -- contribution of RSelenese
|
||||
* Marcos Tapajós http://www.improveit.com.br/en/company/tapajos -- Several useful features, current committer
|
||||
* Ryan Bates, http://railscasts.com -- Fixes for Rails 2.1
|
||||
* Nando Vieira, http://simplesideias.com.br
|
||||
* Gordon McCreight, a neat script that lists any unsupported methods
|
||||
|
||||
## Contributing ##
|
||||
|
||||
Contributing is simple. Fork this repo, make your changes, then issue a pull request. *IMPORTANT* I will not take forks that do not have associated unit tests. There must be tests, and they must pass, so I can bring the changes in.
|
||||
|
||||
|
||||
## Information ##
|
||||
|
||||
For more information, check out the [website](http://seleniumhq.org/projects/on-rails/).
|
||||
11
vendor/plugins/selenium-on-rails/Rakefile
vendored
11
vendor/plugins/selenium-on-rails/Rakefile
vendored
|
|
@ -12,6 +12,16 @@ Rake::TestTask.new(:test) do |t|
|
|||
t.verbose = true
|
||||
end
|
||||
|
||||
desc 'Test the Selenium on Rails plugin, and run the _authortest.rb files, too'
|
||||
Rake::TestTask.new(:alltests) do |t|
|
||||
t.libs << 'lib'
|
||||
# note: Both pattern and test_files are used, so the list of test files is
|
||||
# the union of the two.
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.test_files = FileList['test/**/*_authortest.rb']
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
desc 'Generate documentation for the Selenium on Rails plugin.'
|
||||
task :rdoc do
|
||||
rm_rf 'doc'
|
||||
|
|
@ -22,6 +32,7 @@ begin
|
|||
require 'rcov/rcovtask'
|
||||
Rcov::RcovTask.new do |t|
|
||||
t.test_files = FileList['test/*_test.rb']
|
||||
t.rcov_opts = ['-x /site_ruby/ -x .*gems.* --rails']
|
||||
end
|
||||
rescue LoadError #if rcov isn't available, ignore
|
||||
end
|
||||
|
|
|
|||
|
|
@ -68,280 +68,229 @@
|
|||
|
||||
<div id="contextContent">
|
||||
|
||||
<div id="description">
|
||||
<h1>Selenium on Rails</h1>
|
||||
<h2>Overview</h2>
|
||||
<p>
|
||||
Selenium on Rails provides an easy way to test Rails application with <a
|
||||
href="http://www.openqa.org/selenium-core/">SeleniumCore</a>.
|
||||
</p>
|
||||
<p>
|
||||
This plugin does four things:
|
||||
</p>
|
||||
<ol>
|
||||
<li>The Selenium Core files don‘t have to pollute <tt>/public</tt>.
|
||||
<div id="description">
|
||||
<p>Welcome to the Selenium on Rails README. Exciting isn’t it?</p>
|
||||
|
||||
</li>
|
||||
<li>No need to create suite files, they are generated on the fly — one
|
||||
suite per directory in <tt>/test/selenium</tt> (suites can be nested).
|
||||
<h1 id="selenium_on_rails">Selenium on Rails</h1>
|
||||
|
||||
</li>
|
||||
<li>Instead of writing the test cases in HTML you can use a number of better
|
||||
formats (see <tt>Formats</tt>).
|
||||
<h2 id="overview">Overview</h2>
|
||||
|
||||
</li>
|
||||
<li>Loading of fixtures and wiping of session (<tt>/selenium/setup</tt>).
|
||||
<p>Selenium on Rails provides an easy way to test Rails application with
|
||||
SeleniumCore[http://www.openqa.org/selenium-core/].</p>
|
||||
|
||||
</li>
|
||||
</ol>
|
||||
<h2>Installation</h2>
|
||||
<ol>
|
||||
<li>Install Selenium on Rails: <tt>script/plugin install <a
|
||||
href="http://svn.openqa.org/svn/selenium-on-rails/selenium-on-rails">svn.openqa.org/svn/selenium-on-rails/selenium-on-rails</a></tt>
|
||||
<p>This plugin does four things:</p>
|
||||
|
||||
</li>
|
||||
<li>If you‘re on Windows, <tt>gem install win32-open3</tt>
|
||||
<ol>
|
||||
<li>The Selenium Core files don’t have to pollute <tt>/public</tt>.</li>
|
||||
<li>No need to create suite files, they are generated on the fly — one suite per directory in <tt>/test/selenium</tt> (suites can be nested).</li>
|
||||
<li>Instead of writing the test cases in HTML you can use a number of better formats (see <tt>Formats</tt>).</li>
|
||||
<li>Loading of fixtures and wiping of session (<tt>/selenium/setup</tt>).</li>
|
||||
</ol>
|
||||
|
||||
</li>
|
||||
<li><em>If the RedCloth gem is available the Selenese test cases can use it for
|
||||
better markup.</em>
|
||||
<h2 id="installation">Installation</h2>
|
||||
|
||||
</li>
|
||||
<li>Run the Rakefile in the plugin‘s directory to run the tests in order
|
||||
to see that everything works. (If RedCloth isn‘t installed a few
|
||||
tests will fail since they assume RedCloth is installed.)
|
||||
<p>Rails periodically changes the way that it renders pages, which unfortunately breaks backwards versions of Selenium on Rails. Therefore there are different
|
||||
installation locations depending on your version of Rails:</p>
|
||||
|
||||
</li>
|
||||
<li>Create a test case: <tt>script/generate selenium login</tt>
|
||||
<p><em>Rails 2.2 and up:</em></p>
|
||||
|
||||
</li>
|
||||
<li>Start the server: <tt>script/server -e test</tt>
|
||||
<pre><code>http://svn.openqa.org/svn/selenium-on-rails/stable/selenium-on-rails
|
||||
</code></pre>
|
||||
|
||||
</li>
|
||||
<li>Point your browser to <tt><a
|
||||
href="http://localhost:3000/selenium">localhost:3000/selenium</a></tt>
|
||||
<p><em>Rails 2.1:</em></p>
|
||||
|
||||
</li>
|
||||
<li>If everything works as expected you should see the Selenium test runner.
|
||||
The north east frame contains all your test cases (just one for now), and
|
||||
the north frame contains your test case.
|
||||
<pre><code>http://svn.openqa.org/svn/selenium-on-rails/tags/rails_2_1/selenium-on-rails
|
||||
</code></pre>
|
||||
|
||||
</li>
|
||||
</ol>
|
||||
<h2>Formats</h2>
|
||||
<p>
|
||||
The test cases can be written in a number of formats. Which one you choose
|
||||
is a matter of taste. You can generate your test files by running
|
||||
<tt>script/generate selenium</tt> or by creating them manually in your
|
||||
<tt>/test/selenium</tt> directory.
|
||||
</p>
|
||||
<h3>Selenese, .sel</h3>
|
||||
<p>
|
||||
Selenese is the dumbest format (in a good way). You just write your
|
||||
commands delimited by | characters.
|
||||
</p>
|
||||
<pre>
|
||||
|open|/selenium/setup|
|
||||
|open|/|
|
||||
|goBack|
|
||||
</pre>
|
||||
<p>
|
||||
If you don‘t want to write Selenese tests by hand you can use <a
|
||||
href="http://www.openqa.org/selenium-ide/">SeleniumIDE</a> which has <a
|
||||
href="http://wiki.openqa.org/display/SIDE/SeleniumOnRails">support</a> for
|
||||
Selenese.
|
||||
</p>
|
||||
<p>
|
||||
SeleniumIDE makes it super easy to record test and edit them.
|
||||
</p>
|
||||
<h3>RSelenese, .rsel</h3>
|
||||
<p>
|
||||
RSelenese enable you to write your tests in Ruby.
|
||||
</p>
|
||||
<pre>
|
||||
setup :fixtures => :all
|
||||
<p><em>Before Rails 2.1:</em></p>
|
||||
|
||||
<pre><code>http://svn.openqa.org/svn/selenium-on-rails/tags/pre-rails-2-1/selenium-on-rails
|
||||
</code></pre>
|
||||
|
||||
<p>The latest release is always kept on GitHub at </p>
|
||||
|
||||
<pre><code>git clone git://github.com/paytonrules/selenium-on-rails.git
|
||||
</code></pre>
|
||||
|
||||
<p>To install:</p>
|
||||
|
||||
<ol>
|
||||
<li>Install Selenium on Rails: script/plugin install <URL></li>
|
||||
<li>If you‘re on Windows, gem install win32-open3</li>
|
||||
<li>If the RedCloth gem is available the Selenese test cases can use it for better markup.</li>
|
||||
<li>Run the Rakefile in the plugin‘s directory to run the tests in order to see that everything works. (If RedCloth isn‘t installed a few tests will fail since they assume RedCloth is installed.)</li>
|
||||
<li>Create a test case: script/generate selenium <TestName></li>
|
||||
<li>Start the server: script/server -e test</li>
|
||||
<li>Point your browser to localhost:3000/selenium</li>
|
||||
<li>If everything works as expected you should see the Selenium test runner. The north east frame contains all your test cases (just one for now), and the north frame contains your test case.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="formats">Formats</h2>
|
||||
|
||||
<p>The test cases can be written in a number of formats. Which one you choose is a matter of taste. You can generate your test files by running script/generate selenium or by creating them manually in your /test/selenium directory.</p>
|
||||
|
||||
<h2 id="rselenese_rsel">RSelenese, .rsel</h2>
|
||||
|
||||
<p>RSelenese lets you write your tests in Ruby. This is my favorite format.</p>
|
||||
|
||||
<pre><code>setup :fixtures => :all
|
||||
open '/'
|
||||
assert_title 'Home'
|
||||
('a'..'z').each {|c| open :controller => 'user', :action => 'create', :name => c }
|
||||
</pre>
|
||||
<p>
|
||||
See <a
|
||||
href="../classes/SeleniumOnRails/TestBuilder.html">SeleniumOnRails::TestBuilder</a>
|
||||
for available commands.
|
||||
</p>
|
||||
<h3>HTML/RHTML</h3>
|
||||
<p>
|
||||
You can write your tests in HTML/RHTML but that‘s mostly useful if
|
||||
you have existing tests you want to reuse.
|
||||
</p>
|
||||
<h3>Partial test cases</h3>
|
||||
<p>
|
||||
If you have some common actions you want to do in several test cases you
|
||||
can put them in a separate partial test case and include them in your other
|
||||
test cases.
|
||||
</p>
|
||||
<p>
|
||||
A partial test case is just like a normal test case besides that its
|
||||
filename has to start with _:
|
||||
</p>
|
||||
<pre>
|
||||
#_login.rsel
|
||||
</code></pre>
|
||||
|
||||
<p>See SeleniumOnRails::TestBuilder for available commands. <em>IMPORTANT NOTE:</em> RSelenese generates the HTML tables for Selenium behind the scenes when the page is loaded - ONCE. That means code like this:</p>
|
||||
|
||||
<pre><code>(1..10).each do |index|
|
||||
do something
|
||||
end
|
||||
</code></pre>
|
||||
|
||||
<p>Will only be executed when the test is loaded, not when the test is run. This is a common error and leads to tests that work the first time and fail the second time.</p>
|
||||
|
||||
<h2 id="selenese_sel">Selenese, .sel</h2>
|
||||
|
||||
<p>Selenese is the dumbest format (in a good way). You just write your commands delimited by | characters.</p>
|
||||
|
||||
<pre><code>|open|/selenium/setup|
|
||||
|open|/|
|
||||
|goBack|
|
||||
</code></pre>
|
||||
|
||||
<p>If you don‘t want to write Selenese tests by hand you can use SeleniumIDE which has support for Selenese.</p>
|
||||
|
||||
<h2 id="html_rhtml">HTML/RHTML</h2>
|
||||
|
||||
<p>You can write your tests in HTML/RHTML but that‘s mostly useful if you have existing tests you want to reuse.</p>
|
||||
|
||||
<h2 id="partial_test_cases">Partial test cases</h2>
|
||||
|
||||
<p>If you have some common actions you want to do in several test cases you can put them in a separate partial test case and include them in your other test cases. This is highly recommended, just as small functions would be recommended in structured programming.</p>
|
||||
|
||||
<p>A partial test case is just like a normal test case besides that its filename has to start with _:</p>
|
||||
|
||||
<pre><code>#_login.rsel
|
||||
open '/login'
|
||||
type 'name', name
|
||||
type 'password', password
|
||||
click 'submit', :wait=>true
|
||||
</pre>
|
||||
<p>
|
||||
To include a partial test case you write like this in a Selenese test case:
|
||||
</p>
|
||||
<pre>
|
||||
|includePartial|login|name=John Doe|password=eoD nhoJ|
|
||||
</pre>
|
||||
<p>
|
||||
in a RSelenese test case:
|
||||
</p>
|
||||
<pre>
|
||||
include_partial 'login', :name => 'Jane Doe', :password => 'Jane Doe'.reverse
|
||||
</pre>
|
||||
<p>
|
||||
and in a RHTML test case:
|
||||
</p>
|
||||
<pre>
|
||||
<%= render :partial => 'login', :locals => {:name = 'Joe Schmo', :password => 'Joe Schmo'.reverse} %>
|
||||
</pre>
|
||||
<h2>Configuration</h2>
|
||||
<p>
|
||||
There are a number of settings available. You make them by renaming
|
||||
<tt>config.yml.example</tt> to <tt>config.yml</tt> and make your changes in
|
||||
that file.
|
||||
</p>
|
||||
<h3>Environments</h3>
|
||||
<p>
|
||||
Per default this plugin is only available in test environment. You can
|
||||
change this by setting <tt>environments</tt>, such as:
|
||||
</p>
|
||||
<pre>
|
||||
#config.yml
|
||||
</code></pre>
|
||||
|
||||
<p>To include a partial test case in a RSelenese test case:</p>
|
||||
|
||||
<pre><code>include_partial 'login', :name => 'Jane Doe', :password => 'Jane Doe'.reverse
|
||||
</code></pre>
|
||||
|
||||
<p>in a Selenese test case:</p>
|
||||
|
||||
<pre><code>|includePartial|login|name=John Doe|password=eoD nhoJ|
|
||||
</code></pre>
|
||||
|
||||
<p>and in a RHTML test case:</p>
|
||||
|
||||
<pre><code><%= render :partial => 'login', :locals => {:name = 'Joe Schmo', :password => 'Joe Schmo'.reverse} %>
|
||||
</code></pre>
|
||||
|
||||
<h2 id="configuration">Configuration</h2>
|
||||
|
||||
<p>There are a number of settings available. You make them by renaming selenium.yml.example to selenium.yml and placing it in your rails app’s config
|
||||
file. Make your changes in that file.</p>
|
||||
|
||||
<h2 id="environments">Environments</h2>
|
||||
|
||||
<p>Per default this plugin is only available in test environment. You can change this by setting environments, such as:</p>
|
||||
|
||||
<pre><code>#selenium.yml
|
||||
environments:
|
||||
- test
|
||||
- development
|
||||
</pre>
|
||||
<h3>Selenium Core path</h3>
|
||||
<p>
|
||||
If you don‘t want to use the bundled Selenium Core version you can
|
||||
set <tt>selenium_path</tt> to the directory where Selenium Core is stored.
|
||||
</p>
|
||||
<pre>
|
||||
#config.yml
|
||||
</code></pre>
|
||||
|
||||
<h2 id="selenium_core_path">Selenium Core path</h2>
|
||||
|
||||
<p>If you don‘t want to use the bundled Selenium Core version you can set selenium_path to the directory where Selenium Core is stored.</p>
|
||||
|
||||
<pre><code>#config.yml
|
||||
selenium_path: 'c:\selenium'
|
||||
</pre>
|
||||
<h2><tt>test:acceptance</tt></h2>
|
||||
<p>
|
||||
You can run all your Selenium tests as a Rake task.
|
||||
</p>
|
||||
<p>
|
||||
First, if you‘re on Windows, you have to make sure win32-open3 is
|
||||
installed. Then you have to configure which browsers you want to run, like
|
||||
this:
|
||||
</p>
|
||||
<pre>
|
||||
#config.yml
|
||||
</code></pre>
|
||||
|
||||
<h2 id="rake_task">Rake Task</h2>
|
||||
|
||||
<p>You can run all your Selenium tests as a Rake task. If you’re using a continuous builder this is a great way to integrate selenium into your build process. First, if you‘re on Windows, you have to make sure win32-open3 is installed. Then you have to configure which browsers you want to run, like this:</p>
|
||||
|
||||
<pre><code>#config.yml
|
||||
browsers:
|
||||
firefox: 'c:\Program Files\Mozilla Firefox\firefox.exe'
|
||||
ie: 'c:\Program Files\Internet Explorer\iexplore.exe'
|
||||
</pre>
|
||||
<p>
|
||||
Now you‘re all set. First start a server:
|
||||
</p>
|
||||
<pre>
|
||||
script/server -e test
|
||||
</pre>
|
||||
<p>
|
||||
Then run the tests:
|
||||
</p>
|
||||
<pre>
|
||||
rake test:acceptance
|
||||
</pre>
|
||||
<p>
|
||||
Now it should work, otherwise let me know!
|
||||
</p>
|
||||
<h3>Store results</h3>
|
||||
<p>
|
||||
If you want to store the results from a <tt>test:acceptance</tt> you just
|
||||
need to set in which directory they should be stored:
|
||||
</p>
|
||||
<pre>
|
||||
#config.yml
|
||||
</code></pre>
|
||||
|
||||
<p>Now you‘re all set. First start a server:</p>
|
||||
|
||||
<pre><code>script/server -e test
|
||||
</code></pre>
|
||||
|
||||
<p>Then run the tests:</p>
|
||||
|
||||
<pre><code>rake test:acceptance
|
||||
</code></pre>
|
||||
|
||||
<p>Now it should work, otherwise let me know!</p>
|
||||
|
||||
<h2 id="store_results">Store results</h2>
|
||||
|
||||
<p>If you want to store the results from a test:acceptance you just need to set in which directory they should be stored:</p>
|
||||
|
||||
<pre><code>#config.yml
|
||||
result_dir: 'c:\result'
|
||||
</pre>
|
||||
<p>
|
||||
So when you run <tt>rake test:acceptance</tt> the tables with the results
|
||||
will be stored as <tt>.html</tt> files in that directory.
|
||||
</p>
|
||||
<p>
|
||||
This can be useful especially for continous integration.
|
||||
</p>
|
||||
<h3>user_extension.js</h3>
|
||||
<p>
|
||||
Selenium has support for <tt>user_extension.js</tt> which is a way to
|
||||
extend the functionality of Selenium Core. Selenium on Rails now provides
|
||||
the means for you to extend it‘s functionality to match.
|
||||
</p>
|
||||
<p>
|
||||
To get you started, we‘ve included the example files
|
||||
<tt>lib/test_builder_user_accessors.rb.example</tt> and
|
||||
<tt>lib/test_builder_user_actions.rb.example</tt> that replicate the sample
|
||||
extensions in Selenium Core‘s <tt>user-extensions.js.sample</tt>
|
||||
</p>
|
||||
<p>
|
||||
To get these examples running, simply remove the .example and .sample
|
||||
extensions from the files and restart your server.
|
||||
</p>
|
||||
<h2>Todo</h2>
|
||||
<h3>Standalone mode</h3>
|
||||
<p>
|
||||
More work is needed on <tt>test:acceptance</tt> on Windows to be able to
|
||||
start the server when needed.
|
||||
</p>
|
||||
<h3>More setup/teardown support?</h3>
|
||||
<p>
|
||||
Currently there is only support to load fixtures and to wipe the session in
|
||||
<tt>/selenium/setup</tt>. Is there a need for more kinds of setups or
|
||||
teardowns?
|
||||
</p>
|
||||
<h3>More documentation</h3>
|
||||
<h2>Not todo</h2>
|
||||
<h3>Editor</h3>
|
||||
<p>
|
||||
Creating an editor for the test cases is currently considered out of scope
|
||||
for this plugin. <a
|
||||
href="http://www.openqa.org/selenium-ide/">SeleniumIDE</a> does such a good
|
||||
job and has <a
|
||||
href="http://wiki.openqa.org/display/SIDE/SeleniumOnRails">support</a> for
|
||||
the Selenese format.
|
||||
</p>
|
||||
<h2>Credits</h2>
|
||||
<ul>
|
||||
<li>Jon Tirsen, <a href="http://jutopia.tirsen.com">jutopia.tirsen.com</a>
|
||||
— initial <a
|
||||
href="http://wiki.rubyonrails.com/rails/pages/SeleniumIntegration">inspiration</a>
|
||||
</code></pre>
|
||||
|
||||
</li>
|
||||
<li>Eric Kidd, <a href="http://www.randomhacks.net">www.randomhacks.net</a>
|
||||
— contribution of RSelenese
|
||||
<p>So when you run rake test:acceptance the tables with the results will be stored as .html files in that directory.</p>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
<h2>Information</h2>
|
||||
<p>
|
||||
For more information, check out the <a
|
||||
href="http://www.openqa.org/selenium-on-rails/">website</a>.
|
||||
</p>
|
||||
<h2 id="user_extensionjs">user_extension.js</h2>
|
||||
|
||||
</div>
|
||||
<p>Selenium has support for user_extension.js which is a way to extend the functionality of Selenium Core. Selenium on Rails now provides the means for you to extend it’s functionality to match.</p>
|
||||
|
||||
<p>To get you started, we’ve included the example files lib/test_builder_user_accessors.rb.example and lib/test_builder_user_actions.rb.example that replicate the sample extensions in Selenium Core’s user-extensions.js.sample.</p>
|
||||
|
||||
<p>To get these examples running, simply remove the .example and .sample extensions
|
||||
from the files and restart your server.</p>
|
||||
|
||||
<h2 id="todo">Todo</h2>
|
||||
|
||||
<ul>
|
||||
<li><p>Standalone mode
|
||||
More work is needed on test:acceptance< on Windows to be able to start the server when needed.</p></li>
|
||||
<li><p>Documentation update</p></li>
|
||||
</ul>
|
||||
|
||||
<h2 id="not_todo">Not todo</h2>
|
||||
|
||||
<ul>
|
||||
<li>Editor
|
||||
Creating an editor for the test cases is currently considered out of scope for this plugin. SeleniumIDE[http://www.openqa.org/selenium-ide/] does such a good job and has support[http://wiki.openqa.org/display/SIDE/SeleniumOnRails] for both the Selenese and RSelenese formats.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="credits">Credits</h2>
|
||||
|
||||
<ul>
|
||||
<li>Jonas Bengston — original creator</li>
|
||||
<li>Eric Smith, http://blog.8thlight.com/eric — Current Maintainer</li>
|
||||
<li>Jon Tirsen, http://jutopia.tirsen.com — initial inspiration[http://wiki.rubyonrails.com/rails/pages/SeleniumIntegration]</li>
|
||||
<li>Eric Kidd, http://www.randomhacks.net — contribution of RSelenese</li>
|
||||
<li>Marcos Tapajós http://www.improveit.com.br/en/company/tapajos — Several useful features, current committer</li>
|
||||
<li>Ryan Bates, http://railscasts.com — Fixes for Rails 2.1</li>
|
||||
<li>Nando Vieira, http://simplesideias.com.br</li>
|
||||
<li>Gordon McCreight, a neat script that lists any unsupported methods</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="contributing_">Contributing ##</h2>
|
||||
|
||||
<p>Contributing is simple. Fork this repo, make your changes, then issue a pull request. <em>IMPORTANT</em> I will not take forks that do not have associated unit tests. There must be tests, and they must pass, so I can bring the changes in.</p>
|
||||
|
||||
<h2 id="information">Information</h2>
|
||||
|
||||
<p>For more information, check out the <a href="http://seleniumhq.org/projects/on-rails/">website</a>.</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
|
|
|||
2
vendor/plugins/selenium-on-rails/init.rb
vendored
2
vendor/plugins/selenium-on-rails/init.rb
vendored
|
|
@ -1,5 +1,5 @@
|
|||
require 'selenium_on_rails_config'
|
||||
envs = SeleniumOnRailsConfig.new.get :environments
|
||||
envs = SeleniumOnRailsConfig.get :environments
|
||||
|
||||
if envs.include? RAILS_ENV
|
||||
#initialize the plugin
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ class SeleniumController < ActionController::Base
|
|||
include SeleniumOnRails::Renderer
|
||||
|
||||
def initialize
|
||||
@config = SeleniumOnRailsConfig.new
|
||||
@result_dir = SeleniumOnRailsConfig.get(:result_dir)
|
||||
end
|
||||
|
||||
def setup
|
||||
unless params.has_key? :keep_session
|
||||
reset_session
|
||||
reset_session # IS THIS WORKING! NO THINK SO
|
||||
@session_wiped = true
|
||||
end
|
||||
@cleared_tables = clear_tables params[:clear_tables].to_s
|
||||
|
|
@ -65,9 +65,9 @@ class SeleniumController < ActionController::Base
|
|||
end
|
||||
|
||||
def record_table
|
||||
return nil unless result_dir = @config.get(:result_dir)
|
||||
return nil unless @result_dir
|
||||
|
||||
cur_result_dir = File.join(result_dir, (params[:logFile] || "default").sub(/\.yml$/, ''))
|
||||
cur_result_dir = File.join(@result_dir, (params[:logFile] || "default").sub(/\.yml$/, ''))
|
||||
FileUtils.mkdir_p(cur_result_dir)
|
||||
File.open("#{cur_result_dir}/index.html", "wb") do |f|
|
||||
f.write <<EOS
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ class SwitchEnvironmentController < ActionController::Base
|
|||
readme_path = File.expand_path File.join(File.dirname(__FILE__), '..', 'README')
|
||||
render :status => 500, :locals => {:readme_path => readme_path }, :inline => <<END
|
||||
<p>
|
||||
Selenium on Rails is only activated for <%= SeleniumOnRailsConfig.new.get(:environments).join ', ' %>
|
||||
Selenium on Rails is only activated for <%= SeleniumOnRailsConfig.get(:environments).join ', ' %>
|
||||
environment<%= SeleniumOnRailsConfig.get(:environments).size > 1 ? 's' : '' %> (you're running
|
||||
<%= RAILS_ENV %>).
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ require 'net/http'
|
|||
require 'tempfile'
|
||||
|
||||
|
||||
def c(var, default = nil) SeleniumOnRailsConfig.new.get var, default end
|
||||
def c_b(var, default = nil) SeleniumOnRailsConfig.new.get(var, default) { yield } end
|
||||
def c(var, default = nil) SeleniumOnRailsConfig.get var, default end
|
||||
def c_b(var, default = nil) SeleniumOnRailsConfig.get(var, default) { yield } end
|
||||
|
||||
BROWSERS = c :browsers, {}
|
||||
REUSE_EXISTING_SERVER = c :reuse_existing_server, true
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ require 'selenium_on_rails_config'
|
|||
|
||||
module SeleniumOnRails
|
||||
module Paths
|
||||
attr_accessor :config
|
||||
|
||||
def selenium_path
|
||||
@@selenium_path ||= find_selenium_path
|
||||
|
|
@ -10,6 +9,7 @@ module SeleniumOnRails
|
|||
end
|
||||
|
||||
def selenium_tests_path
|
||||
return SeleniumOnRailsConfig.get("selenium_tests_path") if SeleniumOnRailsConfig.get("selenium_tests_path")
|
||||
File.expand_path(File.join(RAILS_ROOT, 'test/selenium'))
|
||||
end
|
||||
|
||||
|
|
@ -25,6 +25,7 @@ module SeleniumOnRails
|
|||
end
|
||||
|
||||
def fixtures_path
|
||||
return SeleniumOnRailsConfig.get("fixtures_path") if SeleniumOnRailsConfig.get("fixtures_path")
|
||||
File.expand_path File.join(RAILS_ROOT, 'test/fixtures')
|
||||
end
|
||||
|
||||
|
|
@ -42,7 +43,7 @@ module SeleniumOnRails
|
|||
private ###############################################
|
||||
|
||||
def find_selenium_path
|
||||
sel_dirs = @config.get :selenium_path do
|
||||
sel_dirs = SeleniumOnRailsConfig.get :selenium_path do
|
||||
File.expand_path(File.dirname(__FILE__) + '/../../selenium-core')
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ class SeleniumOnRails::RSelenese < SeleniumOnRails::TestBuilder
|
|||
end
|
||||
ActionView::Template.register_template_handler 'rsel', SeleniumOnRails::RSelenese
|
||||
|
||||
class SeleniumOnRails::RSelenese < SeleniumOnRails::TestBuilder
|
||||
class SeleniumOnRails::RSelenese
|
||||
attr_accessor :view
|
||||
|
||||
def initialize view
|
||||
|
|
@ -18,19 +18,27 @@ class SeleniumOnRails::RSelenese < SeleniumOnRails::TestBuilder
|
|||
@view = view
|
||||
end
|
||||
|
||||
def render template, local_assigns
|
||||
def render template, local_assigns = {}
|
||||
title = (@view.assigns['page_title'] or local_assigns['page_title'])
|
||||
table(title) do
|
||||
test = self #to enable test.command
|
||||
|
||||
assign_locals_code = ''
|
||||
local_assigns.each_key {|key| assign_locals_code << "#{key} = local_assigns[#{key.inspect}];"}
|
||||
|
||||
eval assign_locals_code + "\n" + template.source
|
||||
evaluator = Evaluator.new(@view)
|
||||
evaluator.run_script title, assign_locals_code_from(local_assigns) + "\n" + template.source, local_assigns
|
||||
end
|
||||
|
||||
def assign_locals_code_from(local_assigns)
|
||||
return local_assigns.keys.collect {|key| "#{key} = local_assigns[#{key.inspect}];"}.join
|
||||
end
|
||||
|
||||
def self.call(template)
|
||||
"#{name}.new(self).render(template, local_assigns)"
|
||||
end
|
||||
|
||||
class Evaluator < SeleniumOnRails::TestBuilder
|
||||
def run_script(title, script, local_assigns)
|
||||
table(title) do
|
||||
test = self #to enable test.command
|
||||
eval script
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -503,9 +503,8 @@ private
|
|||
def self.generate_and_wait_actions
|
||||
public_instance_methods.each do |method|
|
||||
define_method method + '_and_wait' do |*args|
|
||||
make_command_waiting do
|
||||
send method, *args
|
||||
end
|
||||
methods_array = method.split("_")
|
||||
send 'command_and_wait', methods_array.first.downcase + methods_array[1..-1].collect{|part| part.camelize}.join, *args
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,26 +1,30 @@
|
|||
require 'yaml'
|
||||
require 'erb'
|
||||
|
||||
class SeleniumOnRailsConfig
|
||||
attr_accessor :configs
|
||||
|
||||
def initialize
|
||||
@defaults = {:environments => ['test']}
|
||||
initialize_configs
|
||||
end
|
||||
|
||||
def get var, default = nil
|
||||
value = @configs[var.to_s]
|
||||
value ||= @defaults[var]
|
||||
@@defaults = {:environments => ['test']}
|
||||
def self.get var, default = nil
|
||||
value = configs[var.to_s]
|
||||
value ||= @@defaults[var]
|
||||
value ||= default
|
||||
value ||= yield if block_given?
|
||||
value
|
||||
end
|
||||
|
||||
def initialize_configs
|
||||
@configs = {}
|
||||
files = [File.expand_path(File.dirname(__FILE__) + '/../config.yml')]
|
||||
files << File.join(RAILS_ROOT, 'config', 'selenium.yml')
|
||||
files.each { |file| @configs = YAML.load_file(file) if File.exist?(file) }
|
||||
private
|
||||
def self.configs
|
||||
@@configs ||= nil
|
||||
unless @@configs
|
||||
files = [File.join(RAILS_ROOT, 'config', 'selenium.yml'), File.expand_path(File.dirname(__FILE__) + '/../config.yml')]
|
||||
files.each do |file|
|
||||
if File.exist?(file)
|
||||
@@configs = YAML.load(ERB.new(IO.read(file)).result)
|
||||
break
|
||||
end
|
||||
end
|
||||
@@configs ||= {}
|
||||
end
|
||||
@@configs
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -3,6 +3,5 @@
|
|||
<meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
|
||||
</head>
|
||||
<body>
|
||||
<h3>selenium-rc initial page</h3>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -19,12 +19,14 @@ Copyright 2004 ThoughtWorks, Inc
|
|||
<head>
|
||||
<meta content="text/html; charset=ISO-8859-1"
|
||||
http-equiv="content-type">
|
||||
<title>Selenium Functional Test Runner</title>
|
||||
<title>Selenium Remote Control</title>
|
||||
<link rel="stylesheet" type="text/css" href="selenium.css" />
|
||||
<script language="JavaScript" type="text/javascript" src="jsunit/app/jsUnitCore.js"></script>
|
||||
<script type="text/javascript" src="scripts/xmlextras.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="lib/prototype.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="lib/cssQuery/cssQuery-p.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="lib/snapsie.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/htmlutils.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/ui-element.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/selenium-browserdetect.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/selenium-browserbot.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/find_matching_child.js"></script>
|
||||
|
|
@ -34,13 +36,14 @@ http-equiv="content-type">
|
|||
<script language="JavaScript" type="text/javascript" src="scripts/selenium-remoterunner.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/selenium-logging.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/selenium-version.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="xpath/misc.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="xpath/util.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="xpath/xmltoken.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="xpath/dom.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="xpath/xpath.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/user-extensions.js"></script>
|
||||
<script language="JavaScript" type="text/javascript">
|
||||
function openDomViewer() {
|
||||
var autFrame = document.getElementById('myiframe');
|
||||
var autFrame = document.getElementById('selenium_myiframe');
|
||||
var autFrameDocument = getIframeDocument(autFrame);
|
||||
var domViewer = window.open(getDocumentBase(document) + 'domviewer/domviewer.html');
|
||||
domViewer.rootDocument = autFrameDocument;
|
||||
|
|
@ -58,29 +61,25 @@ http-equiv="content-type">
|
|||
|
||||
<body onLoad="setTimeout(function(){runSeleniumTest();},1000)" onUnload="cleanUp()">
|
||||
|
||||
<table border="1" style="height: 100%;">
|
||||
<table border="1" style="height: 100%; width: 100%;">
|
||||
<tr>
|
||||
<td width="50%" height="30%">
|
||||
<td width="50%">
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="selenium-logo.png">
|
||||
</td>
|
||||
<td>
|
||||
<h1><a href="http://selenium.thoughtworks.com" >Selenium</a> Functional Testing for Web Apps</h1>
|
||||
Open Source From <a href="http://www.thoughtworks.com">ThoughtWorks, Inc</a> and Friends
|
||||
<td class="remoterunner">
|
||||
<h4><a href="http://selenium.openqa.org">Selenium</a> Functional Testing for Web Apps</h4>
|
||||
Open Source From <a href="http://selenium.openqa.org/thoughtworks-and-friends.html">ThoughtWorks and Friends</a>
|
||||
<form action="">
|
||||
<br/>Slow Mode:<INPUT TYPE="CHECKBOX" NAME="FASTMODE" VALUE="YES" onmouseup="slowClicked()">
|
||||
|
||||
<br/>
|
||||
<iframe id="seleniumLoggingFrame" name="seleniumLoggingFrame" src="Blank.html" style="border: 0; height: 0; width: 0; "></iframe>
|
||||
<fieldset>
|
||||
<legend>Tools</legend>
|
||||
|
||||
<button type="button" id="domViewer1" onclick="openDomViewer();">
|
||||
View DOM
|
||||
</button>
|
||||
<button type="button" onclick="LOG.show();">
|
||||
Show Log
|
||||
</button>
|
||||
<label><INPUT TYPE="CHECKBOX" NAME="FASTMODE" VALUE="YES" onmouseup="slowClicked()"> Slow Mode</label>
|
||||
</fieldset>
|
||||
|
||||
</form>
|
||||
|
|
@ -92,14 +91,16 @@ http-equiv="content-type">
|
|||
<label id="context" name="context"></label>
|
||||
</form>
|
||||
</td>
|
||||
<td width="50%" height="30%">
|
||||
<b>Last Four Test Commands:</b><br/>
|
||||
<div id="commandList"></div>
|
||||
<td width="50%" class="remoterunner">
|
||||
<h4>Command History:</h4>
|
||||
<form name="commands">
|
||||
<textarea style="overflow:auto; height:8em; width:100%" wrap="off" id="commandList"></textarea>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" height="70%">
|
||||
<iframe name="myiframe" id="myiframe" src="" height="100%" width="100%"></iframe>
|
||||
<td colspan="2" height="100%">
|
||||
<iframe name="selenium_myiframe" id="selenium_myiframe" src="Blank.html" height="100%" width="100%"></iframe>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -3,12 +3,43 @@
|
|||
<head>
|
||||
<title>Selenium Log Console</title>
|
||||
<link id="cssLink" rel="stylesheet" href="selenium.css" />
|
||||
|
||||
</head>
|
||||
<body id="logging-console">
|
||||
|
||||
<script src="scripts/htmlutils.js"></script>
|
||||
<script language="JavaScript">
|
||||
|
||||
var disabled = true;
|
||||
|
||||
function logOnLoad() {
|
||||
var urlConfig = new URLConfiguration();
|
||||
urlConfig.queryString = window.location.search.substr(1);
|
||||
var startingThreshold = urlConfig._getQueryParameter("startingThreshold");
|
||||
setThresholdLevel(startingThreshold);
|
||||
var buttons = document.getElementsByTagName("input");
|
||||
for (var i = 0; i < buttons.length; i++) {
|
||||
addChangeListener(buttons[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function enableButtons() {
|
||||
var buttons = document.getElementsByTagName("input");
|
||||
for (var i = 0; i < buttons.length; i++) {
|
||||
buttons[i].disabled = false;
|
||||
disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function callBack() {}
|
||||
|
||||
function changeHandler() {
|
||||
callBack(getThresholdLevel());
|
||||
}
|
||||
|
||||
function addChangeListener(element) {
|
||||
if (window.addEventListener && !window.opera)
|
||||
element.addEventListener("click", changeHandler, true);
|
||||
else if (window.attachEvent)
|
||||
element.attachEvent("onclick", changeHandler);
|
||||
}
|
||||
|
||||
var logLevels = {
|
||||
debug: 0,
|
||||
info: 1,
|
||||
|
|
@ -53,16 +84,20 @@ function append(message, logLevel) {
|
|||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body id="logging-console" onload="logOnLoad();">
|
||||
|
||||
|
||||
|
||||
<div id="banner">
|
||||
<form id="logLevelChooser">
|
||||
<input id="level-error" type="radio" name="level"
|
||||
<input id="level-error" type="radio" name="level" disabled='true'
|
||||
value="error" /><label for="level-error">Error</label>
|
||||
<input id="level-warn" type="radio" name="level"
|
||||
<input id="level-warn" type="radio" name="level" disabled='true'
|
||||
value="warn" /><label for="level-warn">Warn</label>
|
||||
<input id="level-info" type="radio" name="level"
|
||||
<input id="level-info" type="radio" name="level" disabled='true'
|
||||
value="info" /><label for="level-info">Info</label>
|
||||
<input id="level-debug" type="radio" name="level" checked="yes"
|
||||
<input id="level-debug" type="radio" name="level" checked="yes" disabled='true'
|
||||
value="debug" /><label for="level-debug">Debug</label>
|
||||
</form>
|
||||
<h1>Selenium Log Console</h1>
|
||||
|
|
|
|||
|
|
@ -20,12 +20,19 @@ Copyright 2004 ThoughtWorks, Inc
|
|||
http-equiv="content-type">
|
||||
<title>Select a Test Suite</title>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/selenium-browserdetect.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/xmlextras.js"></script>
|
||||
<script>
|
||||
|
||||
function load() {
|
||||
if (browserVersion.isHTA) {
|
||||
document.getElementById("save-div").style.display = "inline";
|
||||
}
|
||||
if (/thisIsSeleniumServer/.test(window.location.search)) {
|
||||
document.getElementById("slowResources-div").style.display = "inline";
|
||||
if (browserVersion.isHTA || browserVersion.isChrome) {
|
||||
document.getElementById("test").value = "http://localhost:4444/selenium-server/tests/TestSuite.html";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function autoCheck() {
|
||||
|
|
@ -38,6 +45,15 @@ Copyright 2004 ThoughtWorks, Inc
|
|||
}
|
||||
}
|
||||
|
||||
function slowCheck() {
|
||||
var slowResourcesCheckbox = document.getElementById("slowResources");
|
||||
var slowResources = slowResourcesCheckbox.checked ? true : false;
|
||||
var xhr = XmlHttp.create();
|
||||
var driverUrl = "http://localhost:4444/selenium-server/driver/?cmd=slowResources&1=" + slowResources;
|
||||
xhr.open("GET", driverUrl, true);
|
||||
xhr.send(null);
|
||||
}
|
||||
|
||||
function saveCheck() {
|
||||
var results = document.getElementById("results");
|
||||
var check = document.getElementById("save").checked;
|
||||
|
|
@ -51,7 +67,7 @@ Copyright 2004 ThoughtWorks, Inc
|
|||
}
|
||||
|
||||
function go() {
|
||||
if (!browserVersion.isHTA) return true;
|
||||
if (!browserVersion.isHTA && !browserVersion.isChrome) return true;
|
||||
var inputs = document.getElementsByTagName("input");
|
||||
var queryString = "";
|
||||
for (var i = 0; i < inputs.length; i++) {
|
||||
|
|
@ -94,6 +110,12 @@ Copyright 2004 ThoughtWorks, Inc
|
|||
|
||||
<p>
|
||||
|
||||
<div id="slowResources-div" style="display: none">
|
||||
<p>
|
||||
<input id="slowResources" type="checkbox" name="slowResources" onclick="slowCheck();" /> <label for="slowResources">Slow down web server</label>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<input id="auto" type="checkbox" name="auto" onclick="autoCheck();"/> <label for="auto">Run
|
||||
automatically</label>
|
||||
|
|
|
|||
|
|
@ -26,13 +26,13 @@ to work-around a bug in IE on Win2K whereby the HTA application doesn't function
|
|||
|
||||
<title>Selenium Functional Test Runner</title>
|
||||
<link rel="stylesheet" type="text/css" href="selenium.css"/>
|
||||
<script type="text/javascript" src="scripts/narcissus-defs.js"></script>
|
||||
<script type="text/javascript" src="scripts/narcissus-parse.js"></script>
|
||||
<script type="text/javascript" src="scripts/narcissus-exec.js"></script>
|
||||
<script type="text/javascript" src="scripts/xmlextras.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="lib/prototype.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/htmlutils.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="lib/scriptaculous/scriptaculous.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="lib/cssQuery/cssQuery-p.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="lib/snapsie.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/htmlutils.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/ui-element.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="lib/scriptaculous/scriptaculous.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/selenium-browserdetect.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/selenium-browserbot.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/find_matching_child.js"></script>
|
||||
|
|
@ -42,13 +42,15 @@ to work-around a bug in IE on Win2K whereby the HTA application doesn't function
|
|||
<script language="JavaScript" type="text/javascript" src="scripts/selenium-testrunner.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/selenium-logging.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/selenium-version.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="xpath/misc.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="xpath/util.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="xpath/xmltoken.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="xpath/dom.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="xpath/xpath.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="xpath/javascript-xpath-0.1.11.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/user-extensions.js"></script>
|
||||
<script language="JavaScript" type="text/javascript">
|
||||
function openDomViewer() {
|
||||
var autFrame = document.getElementById('myiframe');
|
||||
var autFrame = document.getElementById('selenium_myiframe');
|
||||
var autFrameDocument = new SeleniumFrame(autFrame).getDocument();
|
||||
this.rootDocument = autFrameDocument;
|
||||
var domViewer = window.open(getDocumentBase(document) + 'domviewer/domviewer.html');
|
||||
|
|
@ -68,14 +70,14 @@ to work-around a bug in IE on Win2K whereby the HTA application doesn't function
|
|||
<iframe name="testSuiteFrame" id="testSuiteFrame" src="./TestPrompt.html" application="yes"></iframe>
|
||||
</td>
|
||||
<td width="50%" height="30%">
|
||||
<iframe name="testFrame" id="testFrame" application="yes"></iframe>
|
||||
<iframe name="testFrame" id="testFrame" application="yes" src="Blank.html"></iframe>
|
||||
</td>
|
||||
|
||||
<td width="25%">
|
||||
<table class="layout">
|
||||
<tr class="selenium">
|
||||
<th width="25%" height="1" class="header">
|
||||
<h1><a href="http://selenium.thoughtworks.com" title="The Selenium Project">Selenium</a> TestRunner
|
||||
<h1><a href="http://selenium.openqa.org" title="The Selenium Project">Selenium</a> TestRunner
|
||||
</h1>
|
||||
</th>
|
||||
</tr>
|
||||
|
|
@ -86,16 +88,16 @@ to work-around a bug in IE on Win2K whereby the HTA application doesn't function
|
|||
|
||||
<div id="imageButtonPanel">
|
||||
<button type="button" id="runSuite" onClick="htmlTestRunner.startTestSuite();"
|
||||
title="Run All tests">
|
||||
title="Run All tests" accesskey="a">
|
||||
</button>
|
||||
<button type="button" id="runSeleniumTest" onClick="htmlTestRunner.runSingleTest();"
|
||||
title="Run the Selected test">
|
||||
title="Run the Selected test" accesskey="r">
|
||||
</button>
|
||||
<button type="button" id="pauseTest" disabled="disabled"
|
||||
title="Pause/Continue" class="cssPauseTest">
|
||||
title="Pause/Continue" accesskey="p" class="cssPauseTest">
|
||||
</button>
|
||||
<button type="button" id="stepTest" disabled="disabled"
|
||||
title="Step">
|
||||
title="Step" accesskey="s">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -164,7 +166,7 @@ to work-around a bug in IE on Win2K whereby the HTA application doesn't function
|
|||
|
||||
<tr>
|
||||
<td colspan="3" height="70%">
|
||||
<iframe name="myiframe" id="myiframe" src="TestRunner-splash.html"></iframe>
|
||||
<iframe name="selenium_myiframe" id="selenium_myiframe" src="TestRunner-splash.html"></iframe>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
|
|
|||
|
|
@ -26,13 +26,13 @@ to work-around a bug in IE on Win2K whereby the HTA application doesn't function
|
|||
|
||||
<title>Selenium Functional Test Runner</title>
|
||||
<link rel="stylesheet" type="text/css" href="selenium.css"/>
|
||||
<script type="text/javascript" src="scripts/narcissus-defs.js"></script>
|
||||
<script type="text/javascript" src="scripts/narcissus-parse.js"></script>
|
||||
<script type="text/javascript" src="scripts/narcissus-exec.js"></script>
|
||||
<script type="text/javascript" src="scripts/xmlextras.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="lib/prototype.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/htmlutils.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="lib/scriptaculous/scriptaculous.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="lib/cssQuery/cssQuery-p.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="lib/snapsie.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/htmlutils.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/ui-element.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="lib/scriptaculous/scriptaculous.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/selenium-browserdetect.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/selenium-browserbot.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/find_matching_child.js"></script>
|
||||
|
|
@ -42,13 +42,15 @@ to work-around a bug in IE on Win2K whereby the HTA application doesn't function
|
|||
<script language="JavaScript" type="text/javascript" src="scripts/selenium-testrunner.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/selenium-logging.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/selenium-version.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="xpath/misc.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="xpath/util.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="xpath/xmltoken.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="xpath/dom.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="xpath/xpath.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="xpath/javascript-xpath-0.1.11.js"></script>
|
||||
<script language="JavaScript" type="text/javascript" src="scripts/user-extensions.js"></script>
|
||||
<script language="JavaScript" type="text/javascript">
|
||||
function openDomViewer() {
|
||||
var autFrame = document.getElementById('myiframe');
|
||||
var autFrame = document.getElementById('selenium_myiframe');
|
||||
var autFrameDocument = new SeleniumFrame(autFrame).getDocument();
|
||||
this.rootDocument = autFrameDocument;
|
||||
var domViewer = window.open(getDocumentBase(document) + 'domviewer/domviewer.html');
|
||||
|
|
@ -68,14 +70,14 @@ to work-around a bug in IE on Win2K whereby the HTA application doesn't function
|
|||
<iframe name="testSuiteFrame" id="testSuiteFrame" src="./TestPrompt.html" application="yes"></iframe>
|
||||
</td>
|
||||
<td width="50%" height="30%">
|
||||
<iframe name="testFrame" id="testFrame" application="yes"></iframe>
|
||||
<iframe name="testFrame" id="testFrame" application="yes" src="Blank.html"></iframe>
|
||||
</td>
|
||||
|
||||
<td width="25%">
|
||||
<table class="layout">
|
||||
<tr class="selenium">
|
||||
<th width="25%" height="1" class="header">
|
||||
<h1><a href="http://selenium.thoughtworks.com" title="The Selenium Project">Selenium</a> TestRunner
|
||||
<h1><a href="http://selenium.openqa.org" title="The Selenium Project">Selenium</a> TestRunner
|
||||
</h1>
|
||||
</th>
|
||||
</tr>
|
||||
|
|
@ -86,16 +88,16 @@ to work-around a bug in IE on Win2K whereby the HTA application doesn't function
|
|||
|
||||
<div id="imageButtonPanel">
|
||||
<button type="button" id="runSuite" onClick="htmlTestRunner.startTestSuite();"
|
||||
title="Run All tests">
|
||||
title="Run All tests" accesskey="a">
|
||||
</button>
|
||||
<button type="button" id="runSeleniumTest" onClick="htmlTestRunner.runSingleTest();"
|
||||
title="Run the Selected test">
|
||||
title="Run the Selected test" accesskey="r">
|
||||
</button>
|
||||
<button type="button" id="pauseTest" disabled="disabled"
|
||||
title="Pause/Continue" class="cssPauseTest">
|
||||
title="Pause/Continue" accesskey="p" class="cssPauseTest">
|
||||
</button>
|
||||
<button type="button" id="stepTest" disabled="disabled"
|
||||
title="Step">
|
||||
title="Step" accesskey="s">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -164,7 +166,7 @@ to work-around a bug in IE on Win2K whereby the HTA application doesn't function
|
|||
|
||||
<tr>
|
||||
<td colspan="3" height="70%">
|
||||
<iframe name="myiframe" id="myiframe" src="TestRunner-splash.html"></iframe>
|
||||
<iframe name="selenium_myiframe" id="selenium_myiframe" src="TestRunner-splash.html"></iframe>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
|
|
|||
|
|
@ -18,14 +18,14 @@ We support the following strategies for locating elements:
|
|||
|
||||
<ul>
|
||||
<li><strong>identifier</strong>=<em>id</em>:
|
||||
Select the element with the specified @id attribute. If no match is
|
||||
found, select the first element whose @name attribute is <em>id</em>.
|
||||
Select the element with the specified @id attribute. If no match is
|
||||
found, select the first element whose @name attribute is <em>id</em>.
|
||||
(This is normally the default; see below.)</li>
|
||||
<li><strong>id</strong>=<em>id</em>:
|
||||
Select the element with the specified @id attribute.</li>
|
||||
Select the element with the specified @id attribute.</li>
|
||||
|
||||
<li><strong>name</strong>=<em>name</em>:
|
||||
Select the first element with the specified @name attribute.
|
||||
Select the first element with the specified @name attribute.
|
||||
<ul class="first last simple">
|
||||
<li>username</li>
|
||||
<li>name=username</li>
|
||||
|
|
@ -52,12 +52,12 @@ Model using JavaScript. Note that you must not return a value in this string; s
|
|||
<li><strong>xpath</strong>=<em>xpathExpression</em>:
|
||||
Locate an element using an XPath expression.
|
||||
<ul class="first last simple">
|
||||
<li>xpath=//img[@alt='The image alt text']</li>
|
||||
<li>xpath=//table[@id='table1']//tr[4]/td[2]</li>
|
||||
<li>xpath=//a[contains(@href,'#id1')]</li>
|
||||
<li>xpath=//a[contains(@href,'#id1')]/@class</li>
|
||||
<li>xpath=(//table[@class='stylee'])//th[text()='theHeaderText']/../td</li>
|
||||
<li>xpath=//input[@name='name2' and @value='yes']</li>
|
||||
<li>xpath=//img[@alt='The image alt text']</li>
|
||||
<li>xpath=//table[@id='table1']//tr[4]/td[2]</li>
|
||||
<li>xpath=//a[contains(@href,'#id1')]</li>
|
||||
<li>xpath=//a[contains(@href,'#id1')]/@class</li>
|
||||
<li>xpath=(//table[@class='stylee'])//th[text()='theHeaderText']/../td</li>
|
||||
<li>xpath=//input[@name='name2' and @value='yes']</li>
|
||||
<li>xpath=//*[text()="right"]</li>
|
||||
|
||||
</ul>
|
||||
|
|
@ -79,6 +79,16 @@ Select the element using css selectors. Please refer to <a href="http://www.w3.o
|
|||
</ul>
|
||||
<p>Currently the css selector locator supports all css1, css2 and css3 selectors except namespace in css3, some pseudo classes(:nth-of-type, :nth-last-of-type, :first-of-type, :last-of-type, :only-of-type, :visited, :hover, :active, :focus, :indeterminate) and pseudo elements(::first-line, ::first-letter, ::selection, ::before, ::after). </p>
|
||||
</li>
|
||||
|
||||
<li><strong>ui</strong>=<em>uiSpecifierString</em>:
|
||||
Locate an element by resolving the UI specifier string to another locator, and evaluating it. See the <a href="http://svn.openqa.org/fisheye/browse/~raw,r=trunk/selenium/trunk/src/main/resources/core/scripts/ui-doc.html">Selenium UI-Element Reference</a> for more details.
|
||||
<ul class="first last simple">
|
||||
<li>ui=loginPages::loginButton()</li>
|
||||
<li>ui=settingsPages::toggle(label=Hide Email)</li>
|
||||
<li>ui=forumPages::postBody(index=2)//a[2]</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
|
|
@ -123,6 +133,8 @@ string.</li>
|
|||
<li><strong>regexp:</strong><em>regexp</em>:
|
||||
Match a string using a regular-expression. The full power of JavaScript
|
||||
regular-expressions is available.</li>
|
||||
<li><strong>regexpi:</strong><em>regexpi</em>:
|
||||
Match a string using a case-insensitive regular-expression.</li>
|
||||
<li><strong>exact:</strong><em>string</em>:
|
||||
|
||||
Match a string exactly, verbatim, without any of that fancy wildcard
|
||||
|
|
@ -131,6 +143,14 @@ stuff.</li>
|
|||
<p>
|
||||
If no pattern prefix is specified, Selenium assumes that it's a "glob"
|
||||
pattern.
|
||||
</p>
|
||||
<p>
|
||||
For commands that return multiple values (such as verifySelectOptions),
|
||||
the string being matched is a comma-separated list of the return values,
|
||||
where both commas and backslashes in the values are backslash-escaped.
|
||||
When providing a pattern, the optional matching syntax (i.e. glob,
|
||||
regexp, etc.) is specified once, as usual, at the beginning of the
|
||||
pattern.
|
||||
</p></top>
|
||||
|
||||
<function name="click">
|
||||
|
|
@ -153,6 +173,14 @@ waitForPageToLoad.</comment>
|
|||
|
||||
</function>
|
||||
|
||||
<function name="contextMenu">
|
||||
|
||||
<param name="locator">an element locator</param>
|
||||
|
||||
<comment>Simulates opening the context menu for the specified element (as might happen if the user "right-clicked" on the element).</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="clickAt">
|
||||
|
||||
<param name="locator">an element locator</param>
|
||||
|
|
@ -177,6 +205,16 @@ waitForPageToLoad.</comment>
|
|||
|
||||
</function>
|
||||
|
||||
<function name="contextMenuAt">
|
||||
|
||||
<param name="locator">an element locator</param>
|
||||
|
||||
<param name="coordString">specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator.</param>
|
||||
|
||||
<comment>Simulates opening the context menu for the specified element (as might happen if the user "right-clicked" on the element).</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="fireEvent">
|
||||
|
||||
<param name="locator">an <a href="#locators">element locator</a></param>
|
||||
|
|
@ -188,6 +226,14 @@ handler.</comment>
|
|||
|
||||
</function>
|
||||
|
||||
<function name="focus">
|
||||
|
||||
<param name="locator">an <a href="#locators">element locator</a></param>
|
||||
|
||||
<comment>Move the focus to the specified element; for example, if the element is an input field, move the cursor to that field.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="keyPress">
|
||||
|
||||
<param name="locator">an <a href="#locators">element locator</a></param>
|
||||
|
|
@ -286,7 +332,16 @@ handler.</comment>
|
|||
|
||||
<param name="locator">an <a href="#locators">element locator</a></param>
|
||||
|
||||
<comment>Simulates a user pressing the mouse button (without releasing it yet) on
|
||||
<comment>Simulates a user pressing the left mouse button (without releasing it yet) on
|
||||
the specified element.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="mouseDownRight">
|
||||
|
||||
<param name="locator">an <a href="#locators">element locator</a></param>
|
||||
|
||||
<comment>Simulates a user pressing the right mouse button (without releasing it yet) on
|
||||
the specified element.</comment>
|
||||
|
||||
</function>
|
||||
|
|
@ -297,8 +352,19 @@ the specified element.</comment>
|
|||
|
||||
<param name="coordString">specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator.</param>
|
||||
|
||||
<comment>Simulates a user pressing the mouse button (without releasing it yet) on
|
||||
the specified element.</comment>
|
||||
<comment>Simulates a user pressing the left mouse button (without releasing it yet) at
|
||||
the specified location.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="mouseDownRightAt">
|
||||
|
||||
<param name="locator">an <a href="#locators">element locator</a></param>
|
||||
|
||||
<param name="coordString">specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator.</param>
|
||||
|
||||
<comment>Simulates a user pressing the right mouse button (without releasing it yet) at
|
||||
the specified location.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
|
|
@ -306,8 +372,17 @@ the specified element.</comment>
|
|||
|
||||
<param name="locator">an <a href="#locators">element locator</a></param>
|
||||
|
||||
<comment>Simulates a user pressing the mouse button (without releasing it yet) on
|
||||
the specified element.</comment>
|
||||
<comment>Simulates the event that occurs when the user releases the mouse button (i.e., stops
|
||||
holding the button down) on the specified element.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="mouseUpRight">
|
||||
|
||||
<param name="locator">an <a href="#locators">element locator</a></param>
|
||||
|
||||
<comment>Simulates the event that occurs when the user releases the right mouse button (i.e., stops
|
||||
holding the button down) on the specified element.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
|
|
@ -317,8 +392,19 @@ the specified element.</comment>
|
|||
|
||||
<param name="coordString">specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator.</param>
|
||||
|
||||
<comment>Simulates a user pressing the mouse button (without releasing it yet) on
|
||||
the specified element.</comment>
|
||||
<comment>Simulates the event that occurs when the user releases the mouse button (i.e., stops
|
||||
holding the button down) at the specified location.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="mouseUpRightAt">
|
||||
|
||||
<param name="locator">an <a href="#locators">element locator</a></param>
|
||||
|
||||
<param name="coordString">specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator.</param>
|
||||
|
||||
<comment>Simulates the event that occurs when the user releases the right mouse button (i.e., stops
|
||||
holding the button down) at the specified location.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
|
|
@ -386,6 +472,8 @@ the delay is 0 milliseconds.</comment>
|
|||
|
||||
<function name="getSpeed">
|
||||
|
||||
<return type="string">the execution speed in milliseconds.</return>
|
||||
|
||||
<comment>Get execution speed (i.e., get the millisecond length of the delay following each selenium operation). By default, there is no such delay, i.e.,
|
||||
the delay is 0 milliseconds.
|
||||
|
||||
|
|
@ -538,21 +626,43 @@ an empty (blank) url, like this: openWindow("", "myFunnyWindow").</p></comment>
|
|||
|
||||
<param name="windowID">the JavaScript window ID of the window to select</param>
|
||||
|
||||
<comment>Selects a popup window; once a popup window has been selected, all
|
||||
<comment>Selects a popup window using a window locator; once a popup window has been selected, all
|
||||
commands go to that window. To select the main window again, use null
|
||||
as the target.
|
||||
|
||||
<p>Selenium has several strategies for finding the window object referred to by the "windowID" parameter.</p>
|
||||
<p>
|
||||
|
||||
<p>1.) if windowID is null, then it is assumed the user is referring to the original window instantiated by the browser).</p>
|
||||
Window locators provide different ways of specifying the window object:
|
||||
by title, by internal JavaScript "name," or by JavaScript variable.
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>title</strong>=<em>My Special Window</em>:
|
||||
Finds the window using the text that appears in the title bar. Be careful;
|
||||
two windows can share the same title. If that happens, this locator will
|
||||
just pick one.
|
||||
</li>
|
||||
<li><strong>name</strong>=<em>myWindow</em>:
|
||||
Finds the window using its internal JavaScript "name" property. This is the second
|
||||
parameter "windowName" passed to the JavaScript method window.open(url, windowName, windowFeatures, replaceFlag)
|
||||
(which Selenium intercepts).
|
||||
</li>
|
||||
<li><strong>var</strong>=<em>variableName</em>:
|
||||
Some pop-up windows are unnamed (anonymous), but are associated with a JavaScript variable name in the current
|
||||
application window, e.g. "window.foo = window.open(url);". In those cases, you can open the window using
|
||||
"var=foo".
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
If no window locator prefix is provided, we'll try to guess what you mean like this:</p>
|
||||
<p>1.) if windowID is null, (or the string "null") then it is assumed the user is referring to the original window instantiated by the browser).</p>
|
||||
<p>2.) if the value of the "windowID" parameter is a JavaScript variable name in the current application window, then it is assumed
|
||||
that this variable contains the return value from a call to the JavaScript window.open() method.</p>
|
||||
<p>3.) Otherwise, selenium looks in a hash it maintains that maps string names to window objects. Each of these string
|
||||
names matches the second parameter "windowName" past to the JavaScript method window.open(url, windowName, windowFeatures, replaceFlag)
|
||||
(which selenium intercepts).</p>
|
||||
<p>3.) Otherwise, selenium looks in a hash it maintains that maps string names to window "names".</p>
|
||||
<p>4.) If <em>that</em> fails, we'll try looping over all of the known windows to try to find the appropriate "title".
|
||||
Since "title" is not necessarily unique, this may have unexpected behavior.</p>
|
||||
|
||||
<p>If you're having trouble figuring out what is the name of a window that you want to manipulate, look at the selenium log messages
|
||||
which identify the names of windows created via window.open (and therefore intercepted by selenium). You will see messages
|
||||
<p>If you're having trouble figuring out the name of a window that you want to manipulate, look at the Selenium log messages
|
||||
which identify the names of windows created via window.open (and therefore intercepted by Selenium). You will see messages
|
||||
like the following for each window as it is opened:</p>
|
||||
|
||||
<p><code>debug: window.open call intercepted; window ID (which you can use with selectWindow()) is "myNewWindow"</code></p>
|
||||
|
|
@ -570,29 +680,14 @@ an empty (blank) url, like this: openWindow("", "myFunnyWindow").</p></comment>
|
|||
<comment>Selects a frame within the current window. (You may invoke this command
|
||||
multiple times to select nested frames.) To select the parent frame, use
|
||||
"relative=parent" as a locator; to select the top frame, use "relative=top".
|
||||
You can also select a frame by its 0-based index number; select the first frame with
|
||||
"index=0", or the third frame with "index=2".
|
||||
|
||||
<p>You may also use a DOM expression to identify the frame you want directly,
|
||||
like this: <code>dom=frames["main"].frames["subframe"]</code></p></comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="getLogMessages">
|
||||
|
||||
<return type="string">all log messages seen since the last call to this API</return>
|
||||
|
||||
<comment>Return the contents of the log.
|
||||
|
||||
<p>This is a placeholder intended to make the code generator make this API
|
||||
available to clients. The selenium server will intercept this call, however,
|
||||
and return its recordkeeping of log messages since the last call to this API.
|
||||
Thus this code in JavaScript will never be called.</p>
|
||||
|
||||
<p>The reason I opted for a servercentric solution is to be able to support
|
||||
multiple frames served from different domains, which would break a
|
||||
centralized JavaScript logging mechanism under some conditions.</p></comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="getWhetherThisFrameMatchFrameExpression">
|
||||
|
||||
<return type="boolean">true if the new frame is this code's window</return>
|
||||
|
|
@ -631,7 +726,7 @@ The selected window will return true, while all others will return false.</p></c
|
|||
|
||||
<function name="waitForPopUp">
|
||||
|
||||
<param name="windowID">the JavaScript window ID of the window that will appear</param>
|
||||
<param name="windowID">the JavaScript window "name" of the window that will appear (not the text of the title bar)</param>
|
||||
|
||||
<param name="timeout">a timeout in milliseconds, after which the action will return with an error</param>
|
||||
|
||||
|
|
@ -641,10 +736,40 @@ The selected window will return true, while all others will return false.</p></c
|
|||
|
||||
<function name="chooseCancelOnNextConfirmation">
|
||||
|
||||
<comment>By default, Selenium's overridden window.confirm() function will
|
||||
return true, as if the user had manually clicked OK. After running
|
||||
<comment><p>
|
||||
By default, Selenium's overridden window.confirm() function will
|
||||
return true, as if the user had manually clicked OK; after running
|
||||
this command, the next call to confirm() will return false, as if
|
||||
the user had clicked Cancel.</comment>
|
||||
the user had clicked Cancel. Selenium will then resume using the
|
||||
default behavior for future confirmations, automatically returning
|
||||
true (OK) unless/until you explicitly call this command for each
|
||||
confirmation.
|
||||
</p>
|
||||
<p>
|
||||
Take note - every time a confirmation comes up, you must
|
||||
consume it with a corresponding getConfirmation, or else
|
||||
the next selenium operation will fail.
|
||||
</p></comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="chooseOkOnNextConfirmation">
|
||||
|
||||
<comment><p>
|
||||
Undo the effect of calling chooseCancelOnNextConfirmation. Note
|
||||
that Selenium's overridden window.confirm() function will normally automatically
|
||||
return true, as if the user had manually clicked OK, so you shouldn't
|
||||
need to use this command unless for some reason you need to change
|
||||
your mind prior to the next confirmation. After any confirmation, Selenium will resume using the
|
||||
default behavior for future confirmations, automatically returning
|
||||
true (OK) unless/until you explicitly call chooseCancelOnNextConfirmation for each
|
||||
confirmation.
|
||||
</p>
|
||||
<p>
|
||||
Take note - every time a confirmation comes up, you must
|
||||
consume it with a corresponding getConfirmation, or else
|
||||
the next selenium operation will fail.
|
||||
</p></comment>
|
||||
|
||||
</function>
|
||||
|
||||
|
|
@ -719,13 +844,13 @@ This function never throws an exception
|
|||
<comment>Retrieves the message of a JavaScript alert generated during the previous action, or fail if there were no alerts.
|
||||
|
||||
<p>Getting an alert has the same effect as manually clicking OK. If an
|
||||
alert is generated but you do not get/verify it, the next Selenium action
|
||||
alert is generated but you do not consume it with getAlert, the next Selenium action
|
||||
will fail.</p>
|
||||
|
||||
<p>NOTE: under Selenium, JavaScript alerts will NOT pop up a visible alert
|
||||
<p>Under Selenium, JavaScript alerts will NOT pop up a visible alert
|
||||
dialog.</p>
|
||||
|
||||
<p>NOTE: Selenium does NOT support JavaScript alerts that are generated in a
|
||||
<p>Selenium does NOT support JavaScript alerts that are generated in a
|
||||
page's onload() event handler. In this case a visible dialog WILL be
|
||||
generated and Selenium will hang until someone manually clicks OK.</p></comment>
|
||||
|
||||
|
|
@ -741,8 +866,11 @@ the previous action.
|
|||
<p>
|
||||
By default, the confirm function will return true, having the same effect
|
||||
as manually clicking OK. This can be changed by prior execution of the
|
||||
chooseCancelOnNextConfirmation command. If an confirmation is generated
|
||||
but you do not get/verify it, the next Selenium action will fail.
|
||||
chooseCancelOnNextConfirmation command.
|
||||
</p>
|
||||
<p>
|
||||
If an confirmation is generated but you do not consume it with getConfirmation,
|
||||
the next Selenium action will fail.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
|
@ -846,13 +974,12 @@ text shown to the user.</comment>
|
|||
have multiple lines, but only the result of the last line will be returned.
|
||||
|
||||
<p>Note that, by default, the snippet will run in the context of the "selenium"
|
||||
object itself, so <code>this</code> will refer to the Selenium object, and <code>window</code> will
|
||||
refer to the top-level runner test window, not the window of your application.</p>
|
||||
object itself, so <code>this</code> will refer to the Selenium object. Use <code>window</code> to
|
||||
refer to the window of your application, e.g. <code>window.document.getElementById('foo')</code></p>
|
||||
|
||||
<p>If you need a reference to the window of your application, you can refer
|
||||
to <code>this.browserbot.getCurrentWindow()</code> and if you need to use
|
||||
<p>If you need to use
|
||||
a locator to refer to a single element in your application page, you can
|
||||
use <code>this.browserbot.findElement("foo")</code> where "foo" is your locator.</p></comment>
|
||||
use <code>this.browserbot.findElement("id=foo")</code> where "id=foo" is your locator.</p></comment>
|
||||
|
||||
</function>
|
||||
|
||||
|
|
@ -981,9 +1108,11 @@ tableLocator.row.column, where row and column start at 0.</comment>
|
|||
|
||||
<return type="string">the value of the specified attribute</return>
|
||||
|
||||
<param name="attributeLocator">an element locator followed by an</param>
|
||||
<param name="attributeLocator">an element locator followed by an @ sign and then the name of the attribute, e.g. "foo@bar"</param>
|
||||
|
||||
<comment>Gets the value of an element attribute.</comment>
|
||||
<comment>Gets the value of an element attribute. The value of the attribute may
|
||||
differ across browsers (this is the case for the "style" attribute, for
|
||||
example).</comment>
|
||||
|
||||
</function>
|
||||
|
||||
|
|
@ -1126,17 +1255,13 @@ just send one "mousemove" at the start location and then one final one at the en
|
|||
|
||||
<function name="windowFocus">
|
||||
|
||||
<param name="windowName">name of the window to be given focus</param>
|
||||
|
||||
<comment>Gives focus to a window</comment>
|
||||
<comment>Gives focus to the currently selected window</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="windowMaximize">
|
||||
|
||||
<param name="windowName">name of the window to be enlarged</param>
|
||||
|
||||
<comment>Resize window to take up the entire screen</comment>
|
||||
<comment>Resize currently selected window to take up the entire screen</comment>
|
||||
|
||||
</function>
|
||||
|
||||
|
|
@ -1197,13 +1322,13 @@ will be ignored.</comment>
|
|||
|
||||
<function name="isOrdered">
|
||||
|
||||
<return type="boolean">true if two elements are ordered and have same parent, false otherwise</return>
|
||||
<return type="boolean">true if element1 is the previous sibling of element2, false otherwise</return>
|
||||
|
||||
<param name="locator1">an <a href="#locators">element locator</a> pointing to the first element</param>
|
||||
|
||||
<param name="locator2">an <a href="#locators">element locator</a> pointing to the second element</param>
|
||||
|
||||
<comment>Check if these two elements have same parent and are ordered. Two same elements will
|
||||
<comment>Check if these two elements have same parent and are ordered siblings in the DOM. Two same elements will
|
||||
not be considered ordered.</comment>
|
||||
|
||||
</function>
|
||||
|
|
@ -1262,23 +1387,6 @@ This method will fail if the specified element isn't an input element or textare
|
|||
|
||||
</function>
|
||||
|
||||
<function name="setContext">
|
||||
|
||||
<param name="context">the message to be sent to the browser</param>
|
||||
|
||||
<param name="logLevelThreshold">one of "debug", "info", "warn", "error", sets the threshold for browser-side logging</param>
|
||||
|
||||
<comment>Writes a message to the status bar and adds a note to the browser-side
|
||||
log.
|
||||
|
||||
<p>If logLevelThreshold is specified, set the threshold for logging
|
||||
to that level (debug, info, warn, error).</p>
|
||||
|
||||
<p>(Note that the browser-side logs will <i>not</i> be sent back to the
|
||||
server, and are invisible to the Client Driver.)</p></comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="getExpression">
|
||||
|
||||
<return type="string">the value passed in</return>
|
||||
|
|
@ -1292,6 +1400,58 @@ It is used to generate commands like assertExpression and waitForExpression.</p>
|
|||
|
||||
</function>
|
||||
|
||||
<function name="getXpathCount">
|
||||
|
||||
<return type="number">the number of nodes that match the specified xpath</return>
|
||||
|
||||
<param name="xpath">the xpath expression to evaluate. do NOT wrap this expression in a 'count()' function; we will do that for you.</param>
|
||||
|
||||
<comment>Returns the number of nodes that match the specified xpath, eg. "//table" would give
|
||||
the number of tables.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="assignId">
|
||||
|
||||
<param name="locator">an <a href="#locators">element locator</a> pointing to an element</param>
|
||||
|
||||
<param name="identifier">a string to be used as the ID of the specified element</param>
|
||||
|
||||
<comment>Temporarily sets the "id" attribute of the specified element, so you can locate it in the future
|
||||
using its ID rather than a slow/complicated XPath. This ID will disappear once the page is
|
||||
reloaded.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="allowNativeXpath">
|
||||
|
||||
<param name="allow">boolean, true means we'll prefer to use native XPath; false means we'll only use JS XPath</param>
|
||||
|
||||
<comment>Specifies whether Selenium should use the native in-browser implementation
|
||||
of XPath (if any native version is available); if you pass "false" to
|
||||
this function, we will always use our pure-JavaScript xpath library.
|
||||
Using the pure-JS xpath library can improve the consistency of xpath
|
||||
element locators between different browser vendors, but the pure-JS
|
||||
version is much slower than the native implementations.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="ignoreAttributesWithoutValue">
|
||||
|
||||
<param name="ignore">boolean, true means we'll ignore attributes without value at the expense of xpath "correctness"; false means we'll sacrifice speed for correctness.</param>
|
||||
|
||||
<comment>Specifies whether Selenium will ignore xpath attributes that have no
|
||||
value, i.e. are the empty string, when using the non-native xpath
|
||||
evaluation engine. You'd want to do this for performance reasons in IE.
|
||||
However, this could break certain xpaths, for example an xpath that looks
|
||||
for an attribute whose value is NOT the empty string.
|
||||
|
||||
The hope is that such xpaths are relatively rare, but the user should
|
||||
have the option of using them. Note that this only influences xpath
|
||||
evaluation when using the ajaxslt engine (i.e. not "javascript-xpath").</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="waitForCondition">
|
||||
|
||||
<param name="script">the JavaScript snippet to run</param>
|
||||
|
|
@ -1336,6 +1496,21 @@ wait immediately after a Selenium command that caused a page-load.</p></comment>
|
|||
|
||||
</function>
|
||||
|
||||
<function name="waitForFrameToLoad">
|
||||
|
||||
<param name="frameAddress">FrameAddress from the server side</param>
|
||||
|
||||
<param name="timeout">a timeout in milliseconds, after which this command will return with an error</param>
|
||||
|
||||
<comment>Waits for a new frame to load.
|
||||
|
||||
<p>Selenium constantly keeps track of new pages and frames loading,
|
||||
and sets a "newPageLoaded" flag when it first notices a page load.</p>
|
||||
|
||||
See waitForPageToLoad for more information.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="getCookie">
|
||||
|
||||
<return type="string">all cookies of the current page under test</return>
|
||||
|
|
@ -1344,11 +1519,31 @@ wait immediately after a Selenium command that caused a page-load.</p></comment>
|
|||
|
||||
</function>
|
||||
|
||||
<function name="getCookieByName">
|
||||
|
||||
<return type="string">the value of the cookie</return>
|
||||
|
||||
<param name="name">the name of the cookie</param>
|
||||
|
||||
<comment>Returns the value of the cookie with the specified name, or throws an error if the cookie is not present.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="isCookiePresent">
|
||||
|
||||
<return type="boolean">true if a cookie with the specified name is present, or false otherwise.</return>
|
||||
|
||||
<param name="name">the name of the cookie</param>
|
||||
|
||||
<comment>Returns true if a cookie with the specified name is present, or false otherwise.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="createCookie">
|
||||
|
||||
<param name="nameValuePair">name and value of the cookie in a format "name=value"</param>
|
||||
|
||||
<param name="optionsString">options for the cookie. Currently supported options include 'path' and 'max_age'. the optionsString's format is "path=/path/, max_age=60". The order of options are irrelevant, the unit of the value of 'max_age' is second.</param>
|
||||
<param name="optionsString">options for the cookie. Currently supported options include 'path', 'max_age' and 'domain'. the optionsString's format is "path=/path/, max_age=60, domain=.foo.com". The order of options are irrelevant, the unit of the value of 'max_age' is second. Note that specifying a domain that isn't a subset of the current domain will usually fail.</param>
|
||||
|
||||
<comment>Create a new cookie whose path and domain are same with those of current page
|
||||
under test, unless you specified a path for this cookie explicitly.</comment>
|
||||
|
|
@ -1359,9 +1554,142 @@ under test, unless you specified a path for this cookie explicitly.</comment>
|
|||
|
||||
<param name="name">the name of the cookie to be deleted</param>
|
||||
|
||||
<param name="path">the path property of the cookie to be deleted</param>
|
||||
<param name="optionsString">options for the cookie. Currently supported options include 'path', 'domain' and 'recurse.' The optionsString's format is "path=/path/, domain=.foo.com, recurse=true". The order of options are irrelevant. Note that specifying a domain that isn't a subset of the current domain will usually fail.</param>
|
||||
|
||||
<comment>Delete a named cookie with specified path.</comment>
|
||||
<comment>Delete a named cookie with specified path and domain. Be careful; to delete a cookie, you
|
||||
need to delete it using the exact same path and domain that were used to create the cookie.
|
||||
If the path is wrong, or the domain is wrong, the cookie simply won't be deleted. Also
|
||||
note that specifying a domain that isn't a subset of the current domain will usually fail.
|
||||
|
||||
Since there's no way to discover at runtime the original path and domain of a given cookie,
|
||||
we've added an option called 'recurse' to try all sub-domains of the current domain with
|
||||
all paths that are a subset of the current path. Beware; this option can be slow. In
|
||||
big-O notation, it operates in O(n*m) time, where n is the number of dots in the domain
|
||||
name and m is the number of slashes in the path.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="deleteAllVisibleCookies">
|
||||
|
||||
<comment>Calls deleteCookie with recurse=true on all cookies visible to the current page.
|
||||
As noted on the documentation for deleteCookie, recurse=true can be much slower
|
||||
than simply deleting the cookies using a known domain/path.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="setBrowserLogLevel">
|
||||
|
||||
<param name="logLevel">one of the following: "debug", "info", "warn", "error" or "off"</param>
|
||||
|
||||
<comment>Sets the threshold for browser-side logging messages; log messages beneath this threshold will be discarded.
|
||||
Valid logLevel strings are: "debug", "info", "warn", "error" or "off".
|
||||
To see the browser logs, you need to
|
||||
either show the log window in GUI mode, or enable browser-side logging in Selenium RC.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="runScript">
|
||||
|
||||
<param name="script">the JavaScript snippet to run</param>
|
||||
|
||||
<comment>Creates a new "script" tag in the body of the current test window, and
|
||||
adds the specified text into the body of the command. Scripts run in
|
||||
this way can often be debugged more easily than scripts executed using
|
||||
Selenium's "getEval" command. Beware that JS exceptions thrown in these script
|
||||
tags aren't managed by Selenium, so you should probably wrap your script
|
||||
in try/catch blocks if there is any chance that the script will throw
|
||||
an exception.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="addLocationStrategy">
|
||||
|
||||
<param name="strategyName">the name of the strategy to define; this should use only letters [a-zA-Z] with no spaces or other punctuation.</param>
|
||||
|
||||
<param name="functionDefinition">a string defining the body of a function in JavaScript. For example: <code>return inDocument.getElementById(locator);</code></param>
|
||||
|
||||
<comment>Defines a new function for Selenium to locate elements on the page.
|
||||
For example,
|
||||
if you define the strategy "foo", and someone runs click("foo=blah"), we'll
|
||||
run your function, passing you the string "blah", and click on the element
|
||||
that your function
|
||||
returns, or throw an "Element not found" error if your function returns null.
|
||||
|
||||
We'll pass three arguments to your function:
|
||||
<ul>
|
||||
<li>locator: the string the user passed in</li>
|
||||
<li>inWindow: the currently selected window</li>
|
||||
<li>inDocument: the currently selected document</li>
|
||||
</ul>
|
||||
The function must return null if the element can't be found.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="captureEntirePageScreenshot">
|
||||
|
||||
<param name="filename">the path to the file to persist the screenshot as. No filename extension will be appended by default. Directories will not be created if they do not exist, and an exception will be thrown, possibly by native code.</param>
|
||||
|
||||
<param name="kwargs">a kwargs string that modifies the way the screenshot is captured. Example: "background=#CCFFDD" . Currently valid options: <dl> <dt>background</dt> <dd>the background CSS for the HTML document. This may be useful to set for capturing screenshots of less-than-ideal layouts, for example where absolute positioning causes the calculation of the canvas dimension to fail and a black background is exposed (possibly obscuring black text).</dd> </dl></param>
|
||||
|
||||
<comment>Saves the entire contents of the current window canvas to a PNG file.
|
||||
Contrast this with the captureScreenshot command, which captures the
|
||||
contents of the OS viewport (i.e. whatever is currently being displayed
|
||||
on the monitor), and is implemented in the RC only. Currently this only
|
||||
works in Firefox when running in chrome mode, and in IE non-HTA using
|
||||
the EXPERIMENTAL "Snapsie" utility. The Firefox implementation is mostly
|
||||
borrowed from the Screengrab! Firefox extension. Please see
|
||||
http://www.screengrab.org and http://snapsie.sourceforge.net/ for
|
||||
details.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="rollup">
|
||||
|
||||
<param name="rollupName">the name of the rollup command</param>
|
||||
|
||||
<param name="kwargs">keyword arguments string that influences how the rollup expands into commands</param>
|
||||
|
||||
<comment>Executes a command rollup, which is a series of commands with a unique
|
||||
name, and optionally arguments that control the generation of the set of
|
||||
commands. If any one of the rolled-up commands fails, the rollup is
|
||||
considered to have failed. Rollups may also contain nested rollups.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="addScript">
|
||||
|
||||
<param name="scriptContent">the Javascript content of the script to add</param>
|
||||
|
||||
<param name="scriptTagId">(optional) the id of the new script tag. If specified, and an element with this id already exists, this operation will fail.</param>
|
||||
|
||||
<comment>Loads script content into a new script tag in the Selenium document. This
|
||||
differs from the runScript command in that runScript adds the script tag
|
||||
to the document of the AUT, not the Selenium document. The following
|
||||
entities in the script content are replaced by the characters they
|
||||
represent:
|
||||
|
||||
<
|
||||
>
|
||||
&
|
||||
|
||||
The corresponding remove command is removeScript.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="removeScript">
|
||||
|
||||
<param name="scriptTagId">the id of the script element to remove.</param>
|
||||
|
||||
<comment>Removes a script tag from the Selenium document identified by the given
|
||||
id. Does nothing if the referenced tag doesn't exist.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="useXpathLibrary">
|
||||
|
||||
<param name="libraryName">name of the desired library Only the following three can be chosen: ajaxslt - Google's library javascript - Cybozu Labs' faster library default - The default library. Currently the default library is ajaxslt. If libraryName isn't one of these three, then no change will be made.</param>
|
||||
|
||||
<comment>Allows choice of one of the available libraries.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
|
|
|
|||
|
|
@ -18,14 +18,14 @@ We support the following strategies for locating elements:
|
|||
|
||||
<ul>
|
||||
<li><strong>identifier</strong>=<em>id</em>:
|
||||
Select the element with the specified @id attribute. If no match is
|
||||
found, select the first element whose @name attribute is <em>id</em>.
|
||||
Select the element with the specified @id attribute. If no match is
|
||||
found, select the first element whose @name attribute is <em>id</em>.
|
||||
(This is normally the default; see below.)</li>
|
||||
<li><strong>id</strong>=<em>id</em>:
|
||||
Select the element with the specified @id attribute.</li>
|
||||
Select the element with the specified @id attribute.</li>
|
||||
|
||||
<li><strong>name</strong>=<em>name</em>:
|
||||
Select the first element with the specified @name attribute.
|
||||
Select the first element with the specified @name attribute.
|
||||
<ul class="first last simple">
|
||||
<li>username</li>
|
||||
<li>name=username</li>
|
||||
|
|
@ -52,12 +52,12 @@ Model using JavaScript. Note that you must not return a value in this string; s
|
|||
<li><strong>xpath</strong>=<em>xpathExpression</em>:
|
||||
Locate an element using an XPath expression.
|
||||
<ul class="first last simple">
|
||||
<li>xpath=//img[@alt='The image alt text']</li>
|
||||
<li>xpath=//table[@id='table1']//tr[4]/td[2]</li>
|
||||
<li>xpath=//a[contains(@href,'#id1')]</li>
|
||||
<li>xpath=//a[contains(@href,'#id1')]/@class</li>
|
||||
<li>xpath=(//table[@class='stylee'])//th[text()='theHeaderText']/../td</li>
|
||||
<li>xpath=//input[@name='name2' and @value='yes']</li>
|
||||
<li>xpath=//img[@alt='The image alt text']</li>
|
||||
<li>xpath=//table[@id='table1']//tr[4]/td[2]</li>
|
||||
<li>xpath=//a[contains(@href,'#id1')]</li>
|
||||
<li>xpath=//a[contains(@href,'#id1')]/@class</li>
|
||||
<li>xpath=(//table[@class='stylee'])//th[text()='theHeaderText']/../td</li>
|
||||
<li>xpath=//input[@name='name2' and @value='yes']</li>
|
||||
<li>xpath=//*[text()="right"]</li>
|
||||
|
||||
</ul>
|
||||
|
|
@ -79,6 +79,16 @@ Select the element using css selectors. Please refer to <a href="http://www.w3.o
|
|||
</ul>
|
||||
<p>Currently the css selector locator supports all css1, css2 and css3 selectors except namespace in css3, some pseudo classes(:nth-of-type, :nth-last-of-type, :first-of-type, :last-of-type, :only-of-type, :visited, :hover, :active, :focus, :indeterminate) and pseudo elements(::first-line, ::first-letter, ::selection, ::before, ::after). </p>
|
||||
</li>
|
||||
|
||||
<li><strong>ui</strong>=<em>uiSpecifierString</em>:
|
||||
Locate an element by resolving the UI specifier string to another locator, and evaluating it. See the <a href="http://svn.openqa.org/fisheye/browse/~raw,r=trunk/selenium/trunk/src/main/resources/core/scripts/ui-doc.html">Selenium UI-Element Reference</a> for more details.
|
||||
<ul class="first last simple">
|
||||
<li>ui=loginPages::loginButton()</li>
|
||||
<li>ui=settingsPages::toggle(label=Hide Email)</li>
|
||||
<li>ui=forumPages::postBody(index=2)//a[2]</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
|
|
@ -123,6 +133,8 @@ string.</li>
|
|||
<li><strong>regexp:</strong><em>regexp</em>:
|
||||
Match a string using a regular-expression. The full power of JavaScript
|
||||
regular-expressions is available.</li>
|
||||
<li><strong>regexpi:</strong><em>regexpi</em>:
|
||||
Match a string using a case-insensitive regular-expression.</li>
|
||||
<li><strong>exact:</strong><em>string</em>:
|
||||
|
||||
Match a string exactly, verbatim, without any of that fancy wildcard
|
||||
|
|
@ -131,6 +143,14 @@ stuff.</li>
|
|||
<p>
|
||||
If no pattern prefix is specified, Selenium assumes that it's a "glob"
|
||||
pattern.
|
||||
</p>
|
||||
<p>
|
||||
For commands that return multiple values (such as verifySelectOptions),
|
||||
the string being matched is a comma-separated list of the return values,
|
||||
where both commas and backslashes in the values are backslash-escaped.
|
||||
When providing a pattern, the optional matching syntax (i.e. glob,
|
||||
regexp, etc.) is specified once, as usual, at the beginning of the
|
||||
pattern.
|
||||
</p></top>
|
||||
|
||||
<function name="click">
|
||||
|
|
@ -153,6 +173,14 @@ waitForPageToLoad.</comment>
|
|||
|
||||
</function>
|
||||
|
||||
<function name="contextMenu">
|
||||
|
||||
<param name="locator">an element locator</param>
|
||||
|
||||
<comment>Simulates opening the context menu for the specified element (as might happen if the user "right-clicked" on the element).</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="clickAt">
|
||||
|
||||
<param name="locator">an element locator</param>
|
||||
|
|
@ -177,6 +205,16 @@ waitForPageToLoad.</comment>
|
|||
|
||||
</function>
|
||||
|
||||
<function name="contextMenuAt">
|
||||
|
||||
<param name="locator">an element locator</param>
|
||||
|
||||
<param name="coordString">specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator.</param>
|
||||
|
||||
<comment>Simulates opening the context menu for the specified element (as might happen if the user "right-clicked" on the element).</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="fireEvent">
|
||||
|
||||
<param name="locator">an <a href="#locators">element locator</a></param>
|
||||
|
|
@ -188,6 +226,14 @@ handler.</comment>
|
|||
|
||||
</function>
|
||||
|
||||
<function name="focus">
|
||||
|
||||
<param name="locator">an <a href="#locators">element locator</a></param>
|
||||
|
||||
<comment>Move the focus to the specified element; for example, if the element is an input field, move the cursor to that field.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="keyPress">
|
||||
|
||||
<param name="locator">an <a href="#locators">element locator</a></param>
|
||||
|
|
@ -286,7 +332,16 @@ handler.</comment>
|
|||
|
||||
<param name="locator">an <a href="#locators">element locator</a></param>
|
||||
|
||||
<comment>Simulates a user pressing the mouse button (without releasing it yet) on
|
||||
<comment>Simulates a user pressing the left mouse button (without releasing it yet) on
|
||||
the specified element.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="mouseDownRight">
|
||||
|
||||
<param name="locator">an <a href="#locators">element locator</a></param>
|
||||
|
||||
<comment>Simulates a user pressing the right mouse button (without releasing it yet) on
|
||||
the specified element.</comment>
|
||||
|
||||
</function>
|
||||
|
|
@ -297,8 +352,19 @@ the specified element.</comment>
|
|||
|
||||
<param name="coordString">specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator.</param>
|
||||
|
||||
<comment>Simulates a user pressing the mouse button (without releasing it yet) on
|
||||
the specified element.</comment>
|
||||
<comment>Simulates a user pressing the left mouse button (without releasing it yet) at
|
||||
the specified location.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="mouseDownRightAt">
|
||||
|
||||
<param name="locator">an <a href="#locators">element locator</a></param>
|
||||
|
||||
<param name="coordString">specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator.</param>
|
||||
|
||||
<comment>Simulates a user pressing the right mouse button (without releasing it yet) at
|
||||
the specified location.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
|
|
@ -306,8 +372,17 @@ the specified element.</comment>
|
|||
|
||||
<param name="locator">an <a href="#locators">element locator</a></param>
|
||||
|
||||
<comment>Simulates a user pressing the mouse button (without releasing it yet) on
|
||||
the specified element.</comment>
|
||||
<comment>Simulates the event that occurs when the user releases the mouse button (i.e., stops
|
||||
holding the button down) on the specified element.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="mouseUpRight">
|
||||
|
||||
<param name="locator">an <a href="#locators">element locator</a></param>
|
||||
|
||||
<comment>Simulates the event that occurs when the user releases the right mouse button (i.e., stops
|
||||
holding the button down) on the specified element.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
|
|
@ -317,8 +392,19 @@ the specified element.</comment>
|
|||
|
||||
<param name="coordString">specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator.</param>
|
||||
|
||||
<comment>Simulates a user pressing the mouse button (without releasing it yet) on
|
||||
the specified element.</comment>
|
||||
<comment>Simulates the event that occurs when the user releases the mouse button (i.e., stops
|
||||
holding the button down) at the specified location.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="mouseUpRightAt">
|
||||
|
||||
<param name="locator">an <a href="#locators">element locator</a></param>
|
||||
|
||||
<param name="coordString">specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator.</param>
|
||||
|
||||
<comment>Simulates the event that occurs when the user releases the right mouse button (i.e., stops
|
||||
holding the button down) at the specified location.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
|
|
@ -386,6 +472,8 @@ the delay is 0 milliseconds.</comment>
|
|||
|
||||
<function name="getSpeed">
|
||||
|
||||
<return type="string">the execution speed in milliseconds.</return>
|
||||
|
||||
<comment>Get execution speed (i.e., get the millisecond length of the delay following each selenium operation). By default, there is no such delay, i.e.,
|
||||
the delay is 0 milliseconds.
|
||||
|
||||
|
|
@ -538,21 +626,43 @@ an empty (blank) url, like this: openWindow("", "myFunnyWindow").</p></comment>
|
|||
|
||||
<param name="windowID">the JavaScript window ID of the window to select</param>
|
||||
|
||||
<comment>Selects a popup window; once a popup window has been selected, all
|
||||
<comment>Selects a popup window using a window locator; once a popup window has been selected, all
|
||||
commands go to that window. To select the main window again, use null
|
||||
as the target.
|
||||
|
||||
<p>Selenium has several strategies for finding the window object referred to by the "windowID" parameter.</p>
|
||||
<p>
|
||||
|
||||
<p>1.) if windowID is null, then it is assumed the user is referring to the original window instantiated by the browser).</p>
|
||||
Window locators provide different ways of specifying the window object:
|
||||
by title, by internal JavaScript "name," or by JavaScript variable.
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>title</strong>=<em>My Special Window</em>:
|
||||
Finds the window using the text that appears in the title bar. Be careful;
|
||||
two windows can share the same title. If that happens, this locator will
|
||||
just pick one.
|
||||
</li>
|
||||
<li><strong>name</strong>=<em>myWindow</em>:
|
||||
Finds the window using its internal JavaScript "name" property. This is the second
|
||||
parameter "windowName" passed to the JavaScript method window.open(url, windowName, windowFeatures, replaceFlag)
|
||||
(which Selenium intercepts).
|
||||
</li>
|
||||
<li><strong>var</strong>=<em>variableName</em>:
|
||||
Some pop-up windows are unnamed (anonymous), but are associated with a JavaScript variable name in the current
|
||||
application window, e.g. "window.foo = window.open(url);". In those cases, you can open the window using
|
||||
"var=foo".
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
If no window locator prefix is provided, we'll try to guess what you mean like this:</p>
|
||||
<p>1.) if windowID is null, (or the string "null") then it is assumed the user is referring to the original window instantiated by the browser).</p>
|
||||
<p>2.) if the value of the "windowID" parameter is a JavaScript variable name in the current application window, then it is assumed
|
||||
that this variable contains the return value from a call to the JavaScript window.open() method.</p>
|
||||
<p>3.) Otherwise, selenium looks in a hash it maintains that maps string names to window objects. Each of these string
|
||||
names matches the second parameter "windowName" past to the JavaScript method window.open(url, windowName, windowFeatures, replaceFlag)
|
||||
(which selenium intercepts).</p>
|
||||
<p>3.) Otherwise, selenium looks in a hash it maintains that maps string names to window "names".</p>
|
||||
<p>4.) If <em>that</em> fails, we'll try looping over all of the known windows to try to find the appropriate "title".
|
||||
Since "title" is not necessarily unique, this may have unexpected behavior.</p>
|
||||
|
||||
<p>If you're having trouble figuring out what is the name of a window that you want to manipulate, look at the selenium log messages
|
||||
which identify the names of windows created via window.open (and therefore intercepted by selenium). You will see messages
|
||||
<p>If you're having trouble figuring out the name of a window that you want to manipulate, look at the Selenium log messages
|
||||
which identify the names of windows created via window.open (and therefore intercepted by Selenium). You will see messages
|
||||
like the following for each window as it is opened:</p>
|
||||
|
||||
<p><code>debug: window.open call intercepted; window ID (which you can use with selectWindow()) is "myNewWindow"</code></p>
|
||||
|
|
@ -570,29 +680,14 @@ an empty (blank) url, like this: openWindow("", "myFunnyWindow").</p></comment>
|
|||
<comment>Selects a frame within the current window. (You may invoke this command
|
||||
multiple times to select nested frames.) To select the parent frame, use
|
||||
"relative=parent" as a locator; to select the top frame, use "relative=top".
|
||||
You can also select a frame by its 0-based index number; select the first frame with
|
||||
"index=0", or the third frame with "index=2".
|
||||
|
||||
<p>You may also use a DOM expression to identify the frame you want directly,
|
||||
like this: <code>dom=frames["main"].frames["subframe"]</code></p></comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="getLogMessages">
|
||||
|
||||
<return type="string">all log messages seen since the last call to this API</return>
|
||||
|
||||
<comment>Return the contents of the log.
|
||||
|
||||
<p>This is a placeholder intended to make the code generator make this API
|
||||
available to clients. The selenium server will intercept this call, however,
|
||||
and return its recordkeeping of log messages since the last call to this API.
|
||||
Thus this code in JavaScript will never be called.</p>
|
||||
|
||||
<p>The reason I opted for a servercentric solution is to be able to support
|
||||
multiple frames served from different domains, which would break a
|
||||
centralized JavaScript logging mechanism under some conditions.</p></comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="getWhetherThisFrameMatchFrameExpression">
|
||||
|
||||
<return type="boolean">true if the new frame is this code's window</return>
|
||||
|
|
@ -631,7 +726,7 @@ The selected window will return true, while all others will return false.</p></c
|
|||
|
||||
<function name="waitForPopUp">
|
||||
|
||||
<param name="windowID">the JavaScript window ID of the window that will appear</param>
|
||||
<param name="windowID">the JavaScript window "name" of the window that will appear (not the text of the title bar)</param>
|
||||
|
||||
<param name="timeout">a timeout in milliseconds, after which the action will return with an error</param>
|
||||
|
||||
|
|
@ -641,10 +736,40 @@ The selected window will return true, while all others will return false.</p></c
|
|||
|
||||
<function name="chooseCancelOnNextConfirmation">
|
||||
|
||||
<comment>By default, Selenium's overridden window.confirm() function will
|
||||
return true, as if the user had manually clicked OK. After running
|
||||
<comment><p>
|
||||
By default, Selenium's overridden window.confirm() function will
|
||||
return true, as if the user had manually clicked OK; after running
|
||||
this command, the next call to confirm() will return false, as if
|
||||
the user had clicked Cancel.</comment>
|
||||
the user had clicked Cancel. Selenium will then resume using the
|
||||
default behavior for future confirmations, automatically returning
|
||||
true (OK) unless/until you explicitly call this command for each
|
||||
confirmation.
|
||||
</p>
|
||||
<p>
|
||||
Take note - every time a confirmation comes up, you must
|
||||
consume it with a corresponding getConfirmation, or else
|
||||
the next selenium operation will fail.
|
||||
</p></comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="chooseOkOnNextConfirmation">
|
||||
|
||||
<comment><p>
|
||||
Undo the effect of calling chooseCancelOnNextConfirmation. Note
|
||||
that Selenium's overridden window.confirm() function will normally automatically
|
||||
return true, as if the user had manually clicked OK, so you shouldn't
|
||||
need to use this command unless for some reason you need to change
|
||||
your mind prior to the next confirmation. After any confirmation, Selenium will resume using the
|
||||
default behavior for future confirmations, automatically returning
|
||||
true (OK) unless/until you explicitly call chooseCancelOnNextConfirmation for each
|
||||
confirmation.
|
||||
</p>
|
||||
<p>
|
||||
Take note - every time a confirmation comes up, you must
|
||||
consume it with a corresponding getConfirmation, or else
|
||||
the next selenium operation will fail.
|
||||
</p></comment>
|
||||
|
||||
</function>
|
||||
|
||||
|
|
@ -719,13 +844,13 @@ This function never throws an exception
|
|||
<comment>Retrieves the message of a JavaScript alert generated during the previous action, or fail if there were no alerts.
|
||||
|
||||
<p>Getting an alert has the same effect as manually clicking OK. If an
|
||||
alert is generated but you do not get/verify it, the next Selenium action
|
||||
alert is generated but you do not consume it with getAlert, the next Selenium action
|
||||
will fail.</p>
|
||||
|
||||
<p>NOTE: under Selenium, JavaScript alerts will NOT pop up a visible alert
|
||||
<p>Under Selenium, JavaScript alerts will NOT pop up a visible alert
|
||||
dialog.</p>
|
||||
|
||||
<p>NOTE: Selenium does NOT support JavaScript alerts that are generated in a
|
||||
<p>Selenium does NOT support JavaScript alerts that are generated in a
|
||||
page's onload() event handler. In this case a visible dialog WILL be
|
||||
generated and Selenium will hang until someone manually clicks OK.</p></comment>
|
||||
|
||||
|
|
@ -741,8 +866,11 @@ the previous action.
|
|||
<p>
|
||||
By default, the confirm function will return true, having the same effect
|
||||
as manually clicking OK. This can be changed by prior execution of the
|
||||
chooseCancelOnNextConfirmation command. If an confirmation is generated
|
||||
but you do not get/verify it, the next Selenium action will fail.
|
||||
chooseCancelOnNextConfirmation command.
|
||||
</p>
|
||||
<p>
|
||||
If an confirmation is generated but you do not consume it with getConfirmation,
|
||||
the next Selenium action will fail.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
|
@ -846,13 +974,12 @@ text shown to the user.</comment>
|
|||
have multiple lines, but only the result of the last line will be returned.
|
||||
|
||||
<p>Note that, by default, the snippet will run in the context of the "selenium"
|
||||
object itself, so <code>this</code> will refer to the Selenium object, and <code>window</code> will
|
||||
refer to the top-level runner test window, not the window of your application.</p>
|
||||
object itself, so <code>this</code> will refer to the Selenium object. Use <code>window</code> to
|
||||
refer to the window of your application, e.g. <code>window.document.getElementById('foo')</code></p>
|
||||
|
||||
<p>If you need a reference to the window of your application, you can refer
|
||||
to <code>this.browserbot.getCurrentWindow()</code> and if you need to use
|
||||
<p>If you need to use
|
||||
a locator to refer to a single element in your application page, you can
|
||||
use <code>this.browserbot.findElement("foo")</code> where "foo" is your locator.</p></comment>
|
||||
use <code>this.browserbot.findElement("id=foo")</code> where "id=foo" is your locator.</p></comment>
|
||||
|
||||
</function>
|
||||
|
||||
|
|
@ -981,9 +1108,11 @@ tableLocator.row.column, where row and column start at 0.</comment>
|
|||
|
||||
<return type="string">the value of the specified attribute</return>
|
||||
|
||||
<param name="attributeLocator">an element locator followed by an</param>
|
||||
<param name="attributeLocator">an element locator followed by an @ sign and then the name of the attribute, e.g. "foo@bar"</param>
|
||||
|
||||
<comment>Gets the value of an element attribute.</comment>
|
||||
<comment>Gets the value of an element attribute. The value of the attribute may
|
||||
differ across browsers (this is the case for the "style" attribute, for
|
||||
example).</comment>
|
||||
|
||||
</function>
|
||||
|
||||
|
|
@ -1126,17 +1255,13 @@ just send one "mousemove" at the start location and then one final one at the en
|
|||
|
||||
<function name="windowFocus">
|
||||
|
||||
<param name="windowName">name of the window to be given focus</param>
|
||||
|
||||
<comment>Gives focus to a window</comment>
|
||||
<comment>Gives focus to the currently selected window</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="windowMaximize">
|
||||
|
||||
<param name="windowName">name of the window to be enlarged</param>
|
||||
|
||||
<comment>Resize window to take up the entire screen</comment>
|
||||
<comment>Resize currently selected window to take up the entire screen</comment>
|
||||
|
||||
</function>
|
||||
|
||||
|
|
@ -1197,13 +1322,13 @@ will be ignored.</comment>
|
|||
|
||||
<function name="isOrdered">
|
||||
|
||||
<return type="boolean">true if two elements are ordered and have same parent, false otherwise</return>
|
||||
<return type="boolean">true if element1 is the previous sibling of element2, false otherwise</return>
|
||||
|
||||
<param name="locator1">an <a href="#locators">element locator</a> pointing to the first element</param>
|
||||
|
||||
<param name="locator2">an <a href="#locators">element locator</a> pointing to the second element</param>
|
||||
|
||||
<comment>Check if these two elements have same parent and are ordered. Two same elements will
|
||||
<comment>Check if these two elements have same parent and are ordered siblings in the DOM. Two same elements will
|
||||
not be considered ordered.</comment>
|
||||
|
||||
</function>
|
||||
|
|
@ -1262,23 +1387,6 @@ This method will fail if the specified element isn't an input element or textare
|
|||
|
||||
</function>
|
||||
|
||||
<function name="setContext">
|
||||
|
||||
<param name="context">the message to be sent to the browser</param>
|
||||
|
||||
<param name="logLevelThreshold">one of "debug", "info", "warn", "error", sets the threshold for browser-side logging</param>
|
||||
|
||||
<comment>Writes a message to the status bar and adds a note to the browser-side
|
||||
log.
|
||||
|
||||
<p>If logLevelThreshold is specified, set the threshold for logging
|
||||
to that level (debug, info, warn, error).</p>
|
||||
|
||||
<p>(Note that the browser-side logs will <i>not</i> be sent back to the
|
||||
server, and are invisible to the Client Driver.)</p></comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="getExpression">
|
||||
|
||||
<return type="string">the value passed in</return>
|
||||
|
|
@ -1292,6 +1400,58 @@ It is used to generate commands like assertExpression and waitForExpression.</p>
|
|||
|
||||
</function>
|
||||
|
||||
<function name="getXpathCount">
|
||||
|
||||
<return type="number">the number of nodes that match the specified xpath</return>
|
||||
|
||||
<param name="xpath">the xpath expression to evaluate. do NOT wrap this expression in a 'count()' function; we will do that for you.</param>
|
||||
|
||||
<comment>Returns the number of nodes that match the specified xpath, eg. "//table" would give
|
||||
the number of tables.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="assignId">
|
||||
|
||||
<param name="locator">an <a href="#locators">element locator</a> pointing to an element</param>
|
||||
|
||||
<param name="identifier">a string to be used as the ID of the specified element</param>
|
||||
|
||||
<comment>Temporarily sets the "id" attribute of the specified element, so you can locate it in the future
|
||||
using its ID rather than a slow/complicated XPath. This ID will disappear once the page is
|
||||
reloaded.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="allowNativeXpath">
|
||||
|
||||
<param name="allow">boolean, true means we'll prefer to use native XPath; false means we'll only use JS XPath</param>
|
||||
|
||||
<comment>Specifies whether Selenium should use the native in-browser implementation
|
||||
of XPath (if any native version is available); if you pass "false" to
|
||||
this function, we will always use our pure-JavaScript xpath library.
|
||||
Using the pure-JS xpath library can improve the consistency of xpath
|
||||
element locators between different browser vendors, but the pure-JS
|
||||
version is much slower than the native implementations.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="ignoreAttributesWithoutValue">
|
||||
|
||||
<param name="ignore">boolean, true means we'll ignore attributes without value at the expense of xpath "correctness"; false means we'll sacrifice speed for correctness.</param>
|
||||
|
||||
<comment>Specifies whether Selenium will ignore xpath attributes that have no
|
||||
value, i.e. are the empty string, when using the non-native xpath
|
||||
evaluation engine. You'd want to do this for performance reasons in IE.
|
||||
However, this could break certain xpaths, for example an xpath that looks
|
||||
for an attribute whose value is NOT the empty string.
|
||||
|
||||
The hope is that such xpaths are relatively rare, but the user should
|
||||
have the option of using them. Note that this only influences xpath
|
||||
evaluation when using the ajaxslt engine (i.e. not "javascript-xpath").</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="waitForCondition">
|
||||
|
||||
<param name="script">the JavaScript snippet to run</param>
|
||||
|
|
@ -1336,6 +1496,21 @@ wait immediately after a Selenium command that caused a page-load.</p></comment>
|
|||
|
||||
</function>
|
||||
|
||||
<function name="waitForFrameToLoad">
|
||||
|
||||
<param name="frameAddress">FrameAddress from the server side</param>
|
||||
|
||||
<param name="timeout">a timeout in milliseconds, after which this command will return with an error</param>
|
||||
|
||||
<comment>Waits for a new frame to load.
|
||||
|
||||
<p>Selenium constantly keeps track of new pages and frames loading,
|
||||
and sets a "newPageLoaded" flag when it first notices a page load.</p>
|
||||
|
||||
See waitForPageToLoad for more information.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="getCookie">
|
||||
|
||||
<return type="string">all cookies of the current page under test</return>
|
||||
|
|
@ -1344,11 +1519,31 @@ wait immediately after a Selenium command that caused a page-load.</p></comment>
|
|||
|
||||
</function>
|
||||
|
||||
<function name="getCookieByName">
|
||||
|
||||
<return type="string">the value of the cookie</return>
|
||||
|
||||
<param name="name">the name of the cookie</param>
|
||||
|
||||
<comment>Returns the value of the cookie with the specified name, or throws an error if the cookie is not present.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="isCookiePresent">
|
||||
|
||||
<return type="boolean">true if a cookie with the specified name is present, or false otherwise.</return>
|
||||
|
||||
<param name="name">the name of the cookie</param>
|
||||
|
||||
<comment>Returns true if a cookie with the specified name is present, or false otherwise.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="createCookie">
|
||||
|
||||
<param name="nameValuePair">name and value of the cookie in a format "name=value"</param>
|
||||
|
||||
<param name="optionsString">options for the cookie. Currently supported options include 'path' and 'max_age'. the optionsString's format is "path=/path/, max_age=60". The order of options are irrelevant, the unit of the value of 'max_age' is second.</param>
|
||||
<param name="optionsString">options for the cookie. Currently supported options include 'path', 'max_age' and 'domain'. the optionsString's format is "path=/path/, max_age=60, domain=.foo.com". The order of options are irrelevant, the unit of the value of 'max_age' is second. Note that specifying a domain that isn't a subset of the current domain will usually fail.</param>
|
||||
|
||||
<comment>Create a new cookie whose path and domain are same with those of current page
|
||||
under test, unless you specified a path for this cookie explicitly.</comment>
|
||||
|
|
@ -1359,9 +1554,246 @@ under test, unless you specified a path for this cookie explicitly.</comment>
|
|||
|
||||
<param name="name">the name of the cookie to be deleted</param>
|
||||
|
||||
<param name="path">the path property of the cookie to be deleted</param>
|
||||
<param name="optionsString">options for the cookie. Currently supported options include 'path', 'domain' and 'recurse.' The optionsString's format is "path=/path/, domain=.foo.com, recurse=true". The order of options are irrelevant. Note that specifying a domain that isn't a subset of the current domain will usually fail.</param>
|
||||
|
||||
<comment>Delete a named cookie with specified path.</comment>
|
||||
<comment>Delete a named cookie with specified path and domain. Be careful; to delete a cookie, you
|
||||
need to delete it using the exact same path and domain that were used to create the cookie.
|
||||
If the path is wrong, or the domain is wrong, the cookie simply won't be deleted. Also
|
||||
note that specifying a domain that isn't a subset of the current domain will usually fail.
|
||||
|
||||
Since there's no way to discover at runtime the original path and domain of a given cookie,
|
||||
we've added an option called 'recurse' to try all sub-domains of the current domain with
|
||||
all paths that are a subset of the current path. Beware; this option can be slow. In
|
||||
big-O notation, it operates in O(n*m) time, where n is the number of dots in the domain
|
||||
name and m is the number of slashes in the path.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="deleteAllVisibleCookies">
|
||||
|
||||
<comment>Calls deleteCookie with recurse=true on all cookies visible to the current page.
|
||||
As noted on the documentation for deleteCookie, recurse=true can be much slower
|
||||
than simply deleting the cookies using a known domain/path.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="setBrowserLogLevel">
|
||||
|
||||
<param name="logLevel">one of the following: "debug", "info", "warn", "error" or "off"</param>
|
||||
|
||||
<comment>Sets the threshold for browser-side logging messages; log messages beneath this threshold will be discarded.
|
||||
Valid logLevel strings are: "debug", "info", "warn", "error" or "off".
|
||||
To see the browser logs, you need to
|
||||
either show the log window in GUI mode, or enable browser-side logging in Selenium RC.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="runScript">
|
||||
|
||||
<param name="script">the JavaScript snippet to run</param>
|
||||
|
||||
<comment>Creates a new "script" tag in the body of the current test window, and
|
||||
adds the specified text into the body of the command. Scripts run in
|
||||
this way can often be debugged more easily than scripts executed using
|
||||
Selenium's "getEval" command. Beware that JS exceptions thrown in these script
|
||||
tags aren't managed by Selenium, so you should probably wrap your script
|
||||
in try/catch blocks if there is any chance that the script will throw
|
||||
an exception.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="addLocationStrategy">
|
||||
|
||||
<param name="strategyName">the name of the strategy to define; this should use only letters [a-zA-Z] with no spaces or other punctuation.</param>
|
||||
|
||||
<param name="functionDefinition">a string defining the body of a function in JavaScript. For example: <code>return inDocument.getElementById(locator);</code></param>
|
||||
|
||||
<comment>Defines a new function for Selenium to locate elements on the page.
|
||||
For example,
|
||||
if you define the strategy "foo", and someone runs click("foo=blah"), we'll
|
||||
run your function, passing you the string "blah", and click on the element
|
||||
that your function
|
||||
returns, or throw an "Element not found" error if your function returns null.
|
||||
|
||||
We'll pass three arguments to your function:
|
||||
<ul>
|
||||
<li>locator: the string the user passed in</li>
|
||||
<li>inWindow: the currently selected window</li>
|
||||
<li>inDocument: the currently selected document</li>
|
||||
</ul>
|
||||
The function must return null if the element can't be found.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="captureEntirePageScreenshot">
|
||||
|
||||
<param name="filename">the path to the file to persist the screenshot as. No filename extension will be appended by default. Directories will not be created if they do not exist, and an exception will be thrown, possibly by native code.</param>
|
||||
|
||||
<param name="kwargs">a kwargs string that modifies the way the screenshot is captured. Example: "background=#CCFFDD" . Currently valid options: <dl> <dt>background</dt> <dd>the background CSS for the HTML document. This may be useful to set for capturing screenshots of less-than-ideal layouts, for example where absolute positioning causes the calculation of the canvas dimension to fail and a black background is exposed (possibly obscuring black text).</dd> </dl></param>
|
||||
|
||||
<comment>Saves the entire contents of the current window canvas to a PNG file.
|
||||
Contrast this with the captureScreenshot command, which captures the
|
||||
contents of the OS viewport (i.e. whatever is currently being displayed
|
||||
on the monitor), and is implemented in the RC only. Currently this only
|
||||
works in Firefox when running in chrome mode, and in IE non-HTA using
|
||||
the EXPERIMENTAL "Snapsie" utility. The Firefox implementation is mostly
|
||||
borrowed from the Screengrab! Firefox extension. Please see
|
||||
http://www.screengrab.org and http://snapsie.sourceforge.net/ for
|
||||
details.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="rollup">
|
||||
|
||||
<param name="rollupName">the name of the rollup command</param>
|
||||
|
||||
<param name="kwargs">keyword arguments string that influences how the rollup expands into commands</param>
|
||||
|
||||
<comment>Executes a command rollup, which is a series of commands with a unique
|
||||
name, and optionally arguments that control the generation of the set of
|
||||
commands. If any one of the rolled-up commands fails, the rollup is
|
||||
considered to have failed. Rollups may also contain nested rollups.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="addScript">
|
||||
|
||||
<param name="scriptContent">the Javascript content of the script to add</param>
|
||||
|
||||
<param name="scriptTagId">(optional) the id of the new script tag. If specified, and an element with this id already exists, this operation will fail.</param>
|
||||
|
||||
<comment>Loads script content into a new script tag in the Selenium document. This
|
||||
differs from the runScript command in that runScript adds the script tag
|
||||
to the document of the AUT, not the Selenium document. The following
|
||||
entities in the script content are replaced by the characters they
|
||||
represent:
|
||||
|
||||
<
|
||||
>
|
||||
&
|
||||
|
||||
The corresponding remove command is removeScript.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="removeScript">
|
||||
|
||||
<param name="scriptTagId">the id of the script element to remove.</param>
|
||||
|
||||
<comment>Removes a script tag from the Selenium document identified by the given
|
||||
id. Does nothing if the referenced tag doesn't exist.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="useXpathLibrary">
|
||||
|
||||
<param name="libraryName">name of the desired library Only the following three can be chosen: ajaxslt - Google's library javascript - Cybozu Labs' faster library default - The default library. Currently the default library is ajaxslt. If libraryName isn't one of these three, then no change will be made.</param>
|
||||
|
||||
<comment>Allows choice of one of the available libraries.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="setContext">
|
||||
|
||||
<param name="context">the message to be sent to the browser</param>
|
||||
|
||||
<comment>Writes a message to the status bar and adds a note to the browser-side
|
||||
log.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="attachFile">
|
||||
|
||||
<param name="fieldLocator">an <a href="#locators">element locator</a></param>
|
||||
|
||||
<param name="fileLocator">a URL pointing to the specified file. Before the file can be set in the input field (fieldLocator), Selenium RC may need to transfer the file to the local machine before attaching the file in a web page form. This is common in selenium grid configurations where the RC server driving the browser is not the same machine that started the test. Supported Browsers: Firefox ("*chrome") only.</param>
|
||||
|
||||
<comment>Sets a file input (upload) field to the file listed in fileLocator</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="captureScreenshot">
|
||||
|
||||
<param name="filename">the absolute path to the file to be written, e.g. "c:\blah\screenshot.png"</param>
|
||||
|
||||
<comment>Captures a PNG screenshot to the specified file.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="captureScreenshotToString">
|
||||
|
||||
<return type="string">The base 64 encoded string of the screen shot (PNG file)</return>
|
||||
|
||||
<comment>Capture a PNG screenshot. It then returns the file as a base 64 encoded string.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="captureEntirePageScreenshotToString">
|
||||
|
||||
<return type="string">The base 64 encoded string of the page screenshot (PNG file)</return>
|
||||
|
||||
<param name="kwargs">A kwargs string that modifies the way the screenshot is captured. Example: "background=#CCFFDD". This may be useful to set for capturing screenshots of less-than-ideal layouts, for example where absolute positioning causes the calculation of the canvas dimension to fail and a black background is exposed (possibly obscuring black text).</param>
|
||||
|
||||
<comment>Downloads a screenshot of the browser current window canvas to a
|
||||
based 64 encoded PNG file. The <em>entire</em> windows canvas is captured,
|
||||
including parts rendered outside of the current view port.
|
||||
|
||||
Currently this only works in Mozilla and when running in chrome mode.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="shutDownSeleniumServer">
|
||||
|
||||
<comment>Kills the running Selenium Server and all browser sessions. After you run this command, you will no longer be able to send
|
||||
commands to the server; you can't remotely start the server once it has been stopped. Normally
|
||||
you should prefer to run the "stop" command, which terminates the current browser session, rather than
|
||||
shutting down the entire server.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="retrieveLastRemoteControlLogs">
|
||||
|
||||
<return type="string">The last N log messages as a multi-line string.</return>
|
||||
|
||||
<comment>Retrieve the last messages logged on a specific remote control. Useful for error reports, especially
|
||||
when running multiple remote controls in a distributed environment. The maximum number of log messages
|
||||
that can be retrieve is configured on remote control startup.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="keyDownNative">
|
||||
|
||||
<param name="keycode">an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes!</param>
|
||||
|
||||
<comment>Simulates a user pressing a key (without releasing it yet) by sending a native operating system keystroke.
|
||||
This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing
|
||||
a key on the keyboard. It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and
|
||||
metaKeyDown commands, and does not target any particular HTML element. To send a keystroke to a particular
|
||||
element, focus on the element first before running this command.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="keyUpNative">
|
||||
|
||||
<param name="keycode">an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes!</param>
|
||||
|
||||
<comment>Simulates a user releasing a key by sending a native operating system keystroke.
|
||||
This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing
|
||||
a key on the keyboard. It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and
|
||||
metaKeyDown commands, and does not target any particular HTML element. To send a keystroke to a particular
|
||||
element, focus on the element first before running this command.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="keyPressNative">
|
||||
|
||||
<param name="keycode">an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes!</param>
|
||||
|
||||
<comment>Simulates a user pressing and releasing a key by sending a native operating system keystroke.
|
||||
This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing
|
||||
a key on the keyboard. It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and
|
||||
metaKeyDown commands, and does not target any particular HTML element. To send a keystroke to a particular
|
||||
element, focus on the element first before running this command.</comment>
|
||||
|
||||
</function>
|
||||
|
||||
|
|
|
|||
|
|
@ -1339,8 +1339,8 @@ Selector.prototype = {
|
|||
},
|
||||
|
||||
compileMatcher: function() {
|
||||
this.match = new Function('element', 'if (!element.tagName) return false; \
|
||||
return ' + this.buildMatchExpression());
|
||||
this.match = new Function('element', 'if (!element.tagName) return false; \n' +
|
||||
'return ' + this.buildMatchExpression());
|
||||
},
|
||||
|
||||
findElements: function(scope) {
|
||||
|
|
|
|||
91
vendor/plugins/selenium-on-rails/selenium-core/lib/snapsie.js
vendored
Normal file
91
vendor/plugins/selenium-on-rails/selenium-core/lib/snapsie.js
vendored
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* This file wraps the Snapsie ActiveX object, exposing a single saveSnapshot()
|
||||
* method on a the object.
|
||||
*
|
||||
* See http://snapsie.sourceforge.net/
|
||||
*/
|
||||
|
||||
function Snapsie() {
|
||||
// private methods
|
||||
|
||||
function isQuirksMode(inDocument) {
|
||||
return (inDocument.compatMode == 'BackCompat');
|
||||
}
|
||||
|
||||
function getDrawableElement(inDocument) {
|
||||
if (isQuirksMode(inDocument)) {
|
||||
var body = inDocument.getElementsByTagName('body')[0];
|
||||
return body;
|
||||
}
|
||||
else {
|
||||
// standards mode
|
||||
return inDocument.documentElement;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the canonical Windows path for a given path. This means
|
||||
* basically replacing any forwards slashes with backslashes.
|
||||
*
|
||||
* @param path the path whose canonical form to return
|
||||
*/
|
||||
function getCanonicalPath(path) {
|
||||
path = path.replace(/\//g, '\\');
|
||||
path = path.replace(/\\\\/g, '\\');
|
||||
return path;
|
||||
}
|
||||
|
||||
// public methods
|
||||
|
||||
/**
|
||||
* Saves a screenshot of the current document to a file. If frameId is
|
||||
* specified, a screenshot of just the frame is captured instead.
|
||||
*
|
||||
* @param outputFile the file to which to save the screenshot
|
||||
* @param frameId the frame to capture; omit to capture entire document
|
||||
*/
|
||||
this.saveSnapshot = function(outputFile, frameId) {
|
||||
var drawableElement = getDrawableElement(document);
|
||||
var drawableInfo = {
|
||||
overflow : drawableElement.style.overflow
|
||||
, scrollLeft: drawableElement.scrollLeft
|
||||
, scrollTop : drawableElement.scrollTop
|
||||
};
|
||||
drawableElement.style.overflow = 'hidden';
|
||||
|
||||
var capturableDocument;
|
||||
var frameBCR = { left: 0, top: 0 };
|
||||
if (!frameId) {
|
||||
capturableDocument = document;
|
||||
}
|
||||
else {
|
||||
var frame = document.getElementById(frameId);
|
||||
capturableDocument = frame.document;
|
||||
|
||||
// scroll as much of the frame into view as possible
|
||||
frameBCR = frame.getBoundingClientRect();
|
||||
window.scroll(frameBCR.left, frameBCR.top);
|
||||
frameBCR = frame.getBoundingClientRect();
|
||||
}
|
||||
|
||||
var nativeObj = new ActiveXObject('Snapsie.CoSnapsie');
|
||||
nativeObj.saveSnapshot(
|
||||
getCanonicalPath(outputFile),
|
||||
frameId,
|
||||
drawableElement.scrollWidth,
|
||||
drawableElement.scrollHeight,
|
||||
drawableElement.clientWidth,
|
||||
drawableElement.clientHeight,
|
||||
drawableElement.clientLeft,
|
||||
drawableElement.clientTop,
|
||||
frameBCR.left,
|
||||
frameBCR.top
|
||||
);
|
||||
|
||||
// revert
|
||||
|
||||
drawableElement.style.overflow = drawableInfo.overflow;
|
||||
drawableElement.scrollLeft = drawableInfo.scrollLeft;
|
||||
drawableElement.scrollTop = drawableInfo.scrollTop;
|
||||
}
|
||||
};
|
||||
|
|
@ -31,7 +31,7 @@ function objectExtend(destination, source) {
|
|||
return destination;
|
||||
}
|
||||
|
||||
function $() {
|
||||
function sel$() {
|
||||
var results = [], element;
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
element = arguments[i];
|
||||
|
|
@ -42,7 +42,7 @@ function $() {
|
|||
return results.length < 2 ? results[0] : results;
|
||||
}
|
||||
|
||||
function $A(iterable) {
|
||||
function sel$A(iterable) {
|
||||
if (!iterable) return [];
|
||||
if (iterable.toArray) {
|
||||
return iterable.toArray();
|
||||
|
|
@ -55,9 +55,9 @@ function $A(iterable) {
|
|||
}
|
||||
|
||||
function fnBind() {
|
||||
var args = $A(arguments), __method = args.shift(), object = args.shift();
|
||||
var args = sel$A(arguments), __method = args.shift(), object = args.shift();
|
||||
var retval = function() {
|
||||
return __method.apply(object, args.concat($A(arguments)));
|
||||
return __method.apply(object, args.concat(sel$A(arguments)));
|
||||
}
|
||||
retval.__method = __method;
|
||||
return retval;
|
||||
|
|
@ -121,6 +121,46 @@ String.prototype.startsWith = function(str) {
|
|||
return this.indexOf(str) == 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a string literal that would appear in an XPath, puts it in quotes and
|
||||
* returns it. Special consideration is given to literals who themselves
|
||||
* contain quotes. It's possible for a concat() expression to be returned.
|
||||
*/
|
||||
String.prototype.quoteForXPath = function()
|
||||
{
|
||||
if (/\'/.test(this)) {
|
||||
if (/\"/.test(this)) {
|
||||
// concat scenario
|
||||
var pieces = [];
|
||||
var a = "'", b = '"', c;
|
||||
for (var i = 0, j = 0; i < this.length;) {
|
||||
if (this.charAt(i) == a) {
|
||||
// encountered a quote that cannot be contained in current
|
||||
// quote, so need to flip-flop quoting scheme
|
||||
if (j < i) {
|
||||
pieces.push(a + this.substring(j, i) + a);
|
||||
j = i;
|
||||
}
|
||||
c = a;
|
||||
a = b;
|
||||
b = c;
|
||||
}
|
||||
else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
pieces.push(a + this.substring(j) + a);
|
||||
return 'concat(' + pieces.join(', ') + ')';
|
||||
}
|
||||
else {
|
||||
// quote with doubles
|
||||
return '"' + this + '"';
|
||||
}
|
||||
}
|
||||
// quote with singles
|
||||
return "'" + this + "'";
|
||||
};
|
||||
|
||||
// Returns the text in this element
|
||||
function getText(element) {
|
||||
var text = "";
|
||||
|
|
@ -171,7 +211,7 @@ function getTextContent(element, preformatted) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Convert all newlines to \m
|
||||
* Convert all newlines to \n
|
||||
*/
|
||||
function normalizeNewlines(text)
|
||||
{
|
||||
|
|
@ -246,7 +286,7 @@ function getInputValue(inputElement) {
|
|||
/* Fire an event in a browser-compatible manner */
|
||||
function triggerEvent(element, eventType, canBubble, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown) {
|
||||
canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
|
||||
if (element.fireEvent) {
|
||||
if (element.fireEvent && element.ownerDocument && element.ownerDocument.createEventObject) { // IE
|
||||
var evt = createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown);
|
||||
element.fireEvent('on' + eventType, evt);
|
||||
}
|
||||
|
|
@ -299,7 +339,7 @@ function createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, me
|
|||
function triggerKeyEvent(element, eventType, keySequence, canBubble, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown) {
|
||||
var keycode = getKeyCodeFromKeySequence(keySequence);
|
||||
canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
|
||||
if (element.fireEvent) {
|
||||
if (element.fireEvent && element.ownerDocument && element.ownerDocument.createEventObject) { // IE
|
||||
var keyEvent = createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown);
|
||||
keyEvent.keyCode = keycode;
|
||||
element.fireEvent('on' + eventType, keyEvent);
|
||||
|
|
@ -327,7 +367,7 @@ function triggerKeyEvent(element, eventType, keySequence, canBubble, controlKeyD
|
|||
}
|
||||
|
||||
function removeLoadListener(element, command) {
|
||||
LOG.info('Removing loadListenter for ' + element + ', ' + command);
|
||||
LOG.debug('Removing loadListenter for ' + element + ', ' + command);
|
||||
if (window.removeEventListener)
|
||||
element.removeEventListener("load", command, true);
|
||||
else if (window.detachEvent)
|
||||
|
|
@ -335,7 +375,7 @@ function removeLoadListener(element, command) {
|
|||
}
|
||||
|
||||
function addLoadListener(element, command) {
|
||||
LOG.info('Adding loadListenter for ' + element + ', ' + command);
|
||||
LOG.debug('Adding loadListenter for ' + element + ', ' + command);
|
||||
var augmentedCommand = function() {
|
||||
command.call(this, element);
|
||||
}
|
||||
|
|
@ -373,6 +413,25 @@ function getTagName(element) {
|
|||
return tagName;
|
||||
}
|
||||
|
||||
function selArrayToString(a) {
|
||||
if (isArray(a)) {
|
||||
// DGF copying the array, because the array-like object may be a non-modifiable nodelist
|
||||
var retval = [];
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
var item = a[i];
|
||||
var replaced = new String(item).replace(/([,\\])/g, '\\$1');
|
||||
retval[i] = replaced;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
return new String(a);
|
||||
}
|
||||
|
||||
|
||||
function isArray(x) {
|
||||
return ((typeof x) == "object") && (x["length"] != null);
|
||||
}
|
||||
|
||||
function absolutify(url, baseUrl) {
|
||||
/** returns a relative url in its absolute form, given by baseUrl.
|
||||
*
|
||||
|
|
@ -499,9 +558,30 @@ function reassembleLocation(loc) {
|
|||
}
|
||||
|
||||
function canonicalize(url) {
|
||||
if(url == "about:blank")
|
||||
{
|
||||
return url;
|
||||
}
|
||||
var tempLink = window.document.createElement("link");
|
||||
tempLink.href = url; // this will canonicalize the href
|
||||
tempLink.href = url; // this will canonicalize the href on most browsers
|
||||
var loc = parseUrl(tempLink.href)
|
||||
if (!/\/\.\.\//.test(loc.pathname)) {
|
||||
return tempLink.href;
|
||||
}
|
||||
// didn't work... let's try it the hard way
|
||||
var originalParts = loc.pathname.split("/");
|
||||
var newParts = [];
|
||||
newParts.push(originalParts.shift());
|
||||
for (var i = 0; i < originalParts.length; i++) {
|
||||
var part = originalParts[i];
|
||||
if (".." == part) {
|
||||
newParts.pop();
|
||||
continue;
|
||||
}
|
||||
newParts.push(part);
|
||||
}
|
||||
loc.pathname = newParts.join("/");
|
||||
return reassembleLocation(loc);
|
||||
}
|
||||
|
||||
function extractExceptionMessage(ex) {
|
||||
|
|
@ -591,6 +671,20 @@ PatternMatcher.strategies = {
|
|||
};
|
||||
},
|
||||
|
||||
regexpi: function(regexpString) {
|
||||
this.regexp = new RegExp(regexpString, "i");
|
||||
this.matches = function(actual) {
|
||||
return this.regexp.test(actual);
|
||||
};
|
||||
},
|
||||
|
||||
regexi: function(regexpString) {
|
||||
this.regexp = new RegExp(regexpString, "i");
|
||||
this.matches = function(actual) {
|
||||
return this.regexp.test(actual);
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* "globContains" (aka "wildmat") patterns, e.g. "glob:one,two,*",
|
||||
* but don't require a perfect match; instead succeed if actual
|
||||
|
|
@ -638,16 +732,17 @@ PatternMatcher.regexpFromGlob = function(glob) {
|
|||
return "^" + PatternMatcher.convertGlobMetaCharsToRegexpMetaChars(glob) + "$";
|
||||
};
|
||||
|
||||
var Assert = {
|
||||
if (!this["Assert"]) Assert = {};
|
||||
|
||||
fail: function(message) {
|
||||
|
||||
Assert.fail = function(message) {
|
||||
throw new AssertionFailedError(message);
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
* Assert.equals(comment?, expected, actual)
|
||||
*/
|
||||
equals: function() {
|
||||
Assert.equals = function() {
|
||||
var args = new AssertionArguments(arguments);
|
||||
if (args.expected === args.actual) {
|
||||
return;
|
||||
|
|
@ -655,12 +750,14 @@ var Assert = {
|
|||
Assert.fail(args.comment +
|
||||
"Expected '" + args.expected +
|
||||
"' but was '" + args.actual + "'");
|
||||
},
|
||||
};
|
||||
|
||||
Assert.assertEquals = Assert.equals;
|
||||
|
||||
/*
|
||||
* Assert.matches(comment?, pattern, actual)
|
||||
*/
|
||||
matches: function() {
|
||||
Assert.matches = function() {
|
||||
var args = new AssertionArguments(arguments);
|
||||
if (PatternMatcher.matches(args.expected, args.actual)) {
|
||||
return;
|
||||
|
|
@ -668,12 +765,12 @@ var Assert = {
|
|||
Assert.fail(args.comment +
|
||||
"Actual value '" + args.actual +
|
||||
"' did not match '" + args.expected + "'");
|
||||
},
|
||||
}
|
||||
|
||||
/*
|
||||
* Assert.notMtches(comment?, pattern, actual)
|
||||
*/
|
||||
notMatches: function() {
|
||||
Assert.notMatches = function() {
|
||||
var args = new AssertionArguments(arguments);
|
||||
if (!PatternMatcher.matches(args.expected, args.actual)) {
|
||||
return;
|
||||
|
|
@ -681,9 +778,8 @@ var Assert = {
|
|||
Assert.fail(args.comment +
|
||||
"Actual value '" + args.actual +
|
||||
"' did match '" + args.expected + "'");
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Preprocess the arguments to allow for an optional comment.
|
||||
function AssertionArguments(args) {
|
||||
|
|
@ -707,6 +803,17 @@ function AssertionFailedError(message) {
|
|||
|
||||
function SeleniumError(message) {
|
||||
var error = new Error(message);
|
||||
if (typeof(arguments.caller) != 'undefined') { // IE, not ECMA
|
||||
var result = '';
|
||||
for (var a = arguments.caller; a != null; a = a.caller) {
|
||||
result += '> ' + a.callee.toString() + '\n';
|
||||
if (a.caller == a) {
|
||||
result += '*';
|
||||
break;
|
||||
}
|
||||
}
|
||||
error.stack = result;
|
||||
}
|
||||
error.isSeleniumError = true;
|
||||
return error;
|
||||
}
|
||||
|
|
@ -749,6 +856,11 @@ function openSeparateApplicationWindow(url, suppressMozillaWarning) {
|
|||
window.moveTo(window.screenX, 0);
|
||||
|
||||
var appWindow = window.open(url + '?start=true', 'main');
|
||||
if (appWindow == null) {
|
||||
var errorMessage = "Couldn't open app window; is the pop-up blocker enabled?"
|
||||
LOG.error(errorMessage);
|
||||
throw new Error("Couldn't open app window; is the pop-up blocker enabled?");
|
||||
}
|
||||
try {
|
||||
var windowHeight = 500;
|
||||
if (window.outerHeight) {
|
||||
|
|
@ -840,3 +952,665 @@ function safeScrollIntoView(element) {
|
|||
// TODO: work out how to scroll browsers that don't support
|
||||
// scrollIntoView (like Konqueror)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute time represented as an offset of the current time.
|
||||
* Throws a SeleniumException if timeout is invalid.
|
||||
*
|
||||
* @param timeout the number of milliseconds from "now" whose absolute time
|
||||
* to return
|
||||
*/
|
||||
function getTimeoutTime(timeout) {
|
||||
var now = new Date().getTime();
|
||||
var timeoutLength = parseInt(timeout);
|
||||
|
||||
if (isNaN(timeoutLength)) {
|
||||
throw new SeleniumError("Timeout is not a number: '" + timeout + "'");
|
||||
}
|
||||
|
||||
return now + timeoutLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true iff the current environment is the IDE.
|
||||
*/
|
||||
function is_IDE()
|
||||
{
|
||||
return (typeof(SeleniumIDE) != 'undefined');
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message if the Logger exists, and does nothing if it doesn't exist.
|
||||
*
|
||||
* @param level the level to log at
|
||||
* @param msg the message to log
|
||||
*/
|
||||
function safe_log(level, msg)
|
||||
{
|
||||
try {
|
||||
LOG[level](msg);
|
||||
}
|
||||
catch (e) {
|
||||
// couldn't log!
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a warning message to the user appropriate to the context under
|
||||
* which the issue is encountered. This is primarily used to avoid popping up
|
||||
* alert dialogs that might pause an automated test suite.
|
||||
*
|
||||
* @param msg the warning message to display
|
||||
*/
|
||||
function safe_alert(msg)
|
||||
{
|
||||
if (is_IDE()) {
|
||||
alert(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true iff the given element represents a link with a javascript
|
||||
* href attribute, and does not have an onclick attribute defined.
|
||||
*
|
||||
* @param element the element to test
|
||||
*/
|
||||
function hasJavascriptHref(element) {
|
||||
if (getTagName(element) != 'a') {
|
||||
return false;
|
||||
}
|
||||
if (element.onclick) {
|
||||
return false;
|
||||
}
|
||||
if (! element.href) {
|
||||
return false;
|
||||
}
|
||||
if (! /\s*javascript:/i.test(element.href)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given element, or its nearest ancestor, that satisfies
|
||||
* hasJavascriptHref(). Returns null if none is found.
|
||||
*
|
||||
* @param element the element whose ancestors to test
|
||||
*/
|
||||
function getAncestorOrSelfWithJavascriptHref(element) {
|
||||
if (hasJavascriptHref(element)) {
|
||||
return element;
|
||||
}
|
||||
if (element.parentNode == null) {
|
||||
return null;
|
||||
}
|
||||
return getAncestorOrSelfWithJavascriptHref(element.parentNode);
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
// Locator evaluation support
|
||||
|
||||
/**
|
||||
* Parses a Selenium locator, returning its type and the unprefixed locator
|
||||
* string as an object.
|
||||
*
|
||||
* @param locator the locator to parse
|
||||
*/
|
||||
function parse_locator(locator)
|
||||
{
|
||||
var result = locator.match(/^([A-Za-z]+)=(.+)/);
|
||||
if (result) {
|
||||
return { type: result[1].toLowerCase(), string: result[2] };
|
||||
}
|
||||
return { type: 'implicit', string: locator };
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates an xpath on a document, and returns a list containing nodes in the
|
||||
* resulting nodeset. The browserbot xpath methods are now backed by this
|
||||
* function. A context node may optionally be provided, and the xpath will be
|
||||
* evaluated from that context.
|
||||
*
|
||||
* @param xpath the xpath to evaluate
|
||||
* @param inDocument the document in which to evaluate the xpath.
|
||||
* @param opts (optional) An object containing various flags that can
|
||||
* modify how the xpath is evaluated. Here's a listing of
|
||||
* the meaningful keys:
|
||||
*
|
||||
* contextNode:
|
||||
* the context node from which to evaluate the xpath. If
|
||||
* unspecified, the context will be the root document
|
||||
* element.
|
||||
*
|
||||
* namespaceResolver:
|
||||
* the namespace resolver function. Defaults to null.
|
||||
*
|
||||
* xpathLibrary:
|
||||
* the javascript library to use for XPath. "ajaxslt" is
|
||||
* the default. "javascript-xpath" is newer and faster,
|
||||
* but needs more testing.
|
||||
*
|
||||
* allowNativeXpath:
|
||||
* whether to allow native evaluate(). Defaults to true.
|
||||
*
|
||||
* ignoreAttributesWithoutValue:
|
||||
* whether it's ok to ignore attributes without value
|
||||
* when evaluating the xpath. This can greatly improve
|
||||
* performance in IE; however, if your xpaths depend on
|
||||
* such attributes, you can't ignore them! Defaults to
|
||||
* true.
|
||||
*
|
||||
* returnOnFirstMatch:
|
||||
* whether to optimize the XPath evaluation to only
|
||||
* return the first match. The match, if any, will still
|
||||
* be returned in a list. Defaults to false.
|
||||
*/
|
||||
function eval_xpath(xpath, inDocument, opts)
|
||||
{
|
||||
if (!opts) {
|
||||
var opts = {};
|
||||
}
|
||||
var contextNode = opts.contextNode
|
||||
? opts.contextNode : inDocument;
|
||||
var namespaceResolver = opts.namespaceResolver
|
||||
? opts.namespaceResolver : null;
|
||||
var xpathLibrary = opts.xpathLibrary
|
||||
? opts.xpathLibrary : null;
|
||||
var allowNativeXpath = (opts.allowNativeXpath != undefined)
|
||||
? opts.allowNativeXpath : true;
|
||||
var ignoreAttributesWithoutValue = (opts.ignoreAttributesWithoutValue != undefined)
|
||||
? opts.ignoreAttributesWithoutValue : true;
|
||||
var returnOnFirstMatch = (opts.returnOnFirstMatch != undefined)
|
||||
? opts.returnOnFirstMatch : false;
|
||||
|
||||
// Trim any trailing "/": not valid xpath, and remains from attribute
|
||||
// locator.
|
||||
if (xpath.charAt(xpath.length - 1) == '/') {
|
||||
xpath = xpath.slice(0, -1);
|
||||
}
|
||||
// HUGE hack - remove namespace from xpath for IE
|
||||
if (browserVersion && browserVersion.isIE) {
|
||||
xpath = xpath.replace(/x:/g, '')
|
||||
}
|
||||
|
||||
var nativeXpathAvailable = inDocument.evaluate;
|
||||
var useNativeXpath = allowNativeXpath && nativeXpathAvailable;
|
||||
var useDocumentEvaluate = useNativeXpath;
|
||||
|
||||
// When using the new and faster javascript-xpath library,
|
||||
// we'll use the TestRunner's document object, not the App-Under-Test's document.
|
||||
// The new library only modifies the TestRunner document with the new
|
||||
// functionality.
|
||||
if (xpathLibrary == 'javascript-xpath' && !useNativeXpath) {
|
||||
documentForXpath = document;
|
||||
useDocumentEvaluate = true;
|
||||
} else {
|
||||
documentForXpath = inDocument;
|
||||
}
|
||||
var results = [];
|
||||
|
||||
// this is either native xpath or javascript-xpath via TestRunner.evaluate
|
||||
if (useDocumentEvaluate) {
|
||||
try {
|
||||
// Regarding use of the second argument to document.evaluate():
|
||||
// http://groups.google.com/group/comp.lang.javascript/browse_thread/thread/a59ce20639c74ba1/a9d9f53e88e5ebb5
|
||||
var xpathResult = documentForXpath
|
||||
.evaluate((contextNode == inDocument ? xpath : '.' + xpath),
|
||||
contextNode, namespaceResolver, 0, null);
|
||||
}
|
||||
catch (e) {
|
||||
throw new SeleniumError("Invalid xpath: " + extractExceptionMessage(e));
|
||||
}
|
||||
finally{
|
||||
if (xpathResult == null) {
|
||||
// If the result is null, we should still throw an Error.
|
||||
throw new SeleniumError("Invalid xpath: " + xpath);
|
||||
}
|
||||
}
|
||||
var result = xpathResult.iterateNext();
|
||||
while (result) {
|
||||
results.push(result);
|
||||
result = xpathResult.iterateNext();
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
// If not, fall back to slower JavaScript implementation
|
||||
// DGF set xpathdebug = true (using getEval, if you like) to turn on JS XPath debugging
|
||||
//xpathdebug = true;
|
||||
var context;
|
||||
if (contextNode == inDocument) {
|
||||
context = new ExprContext(inDocument);
|
||||
}
|
||||
else {
|
||||
// provide false values to get the default constructor values
|
||||
context = new ExprContext(contextNode, false, false,
|
||||
contextNode.parentNode);
|
||||
}
|
||||
context.setCaseInsensitive(true);
|
||||
context.setIgnoreAttributesWithoutValue(ignoreAttributesWithoutValue);
|
||||
context.setReturnOnFirstMatch(returnOnFirstMatch);
|
||||
var xpathObj;
|
||||
try {
|
||||
xpathObj = xpathParse(xpath);
|
||||
}
|
||||
catch (e) {
|
||||
throw new SeleniumError("Invalid xpath: " + extractExceptionMessage(e));
|
||||
}
|
||||
var xpathResult = xpathObj.evaluate(context);
|
||||
if (xpathResult && xpathResult.value) {
|
||||
for (var i = 0; i < xpathResult.value.length; ++i) {
|
||||
results.push(xpathResult.value[i]);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full resultset of a CSS selector evaluation.
|
||||
*/
|
||||
function eval_css(locator, inDocument)
|
||||
{
|
||||
return cssQuery(locator, inDocument);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function duplicates part of BrowserBot.findElement() to open up locator
|
||||
* evaluation on arbitrary documents. It returns a plain old array of located
|
||||
* elements found by using a Selenium locator.
|
||||
*
|
||||
* Multiple results may be generated for xpath and CSS locators. Even though a
|
||||
* list could potentially be generated for other locator types, such as link,
|
||||
* we don't try for them, because they aren't very expressive location
|
||||
* strategies; if you want a list, use xpath or CSS. Furthermore, strategies
|
||||
* for these locators have been optimized to only return the first result. For
|
||||
* these types of locators, performance is more important than ideal behavior.
|
||||
*
|
||||
* @param locator a locator string
|
||||
* @param inDocument the document in which to apply the locator
|
||||
* @param opt_contextNode the context within which to evaluate the locator
|
||||
*
|
||||
* @return a list of result elements
|
||||
*/
|
||||
function eval_locator(locator, inDocument, opt_contextNode)
|
||||
{
|
||||
locator = parse_locator(locator);
|
||||
|
||||
var pageBot;
|
||||
if (typeof(selenium) != 'undefined' && selenium != undefined) {
|
||||
if (typeof(editor) == 'undefined' || editor.state == 'playing') {
|
||||
safe_log('info', 'Trying [' + locator.type + ']: '
|
||||
+ locator.string);
|
||||
}
|
||||
pageBot = selenium.browserbot;
|
||||
}
|
||||
else {
|
||||
if (!UI_GLOBAL.mozillaBrowserBot) {
|
||||
// create a browser bot to evaluate the locator. Hand it the IDE
|
||||
// window as a dummy window, and cache it for future use.
|
||||
UI_GLOBAL.mozillaBrowserBot = new MozillaBrowserBot(window)
|
||||
}
|
||||
pageBot = UI_GLOBAL.mozillaBrowserBot;
|
||||
}
|
||||
|
||||
var results = [];
|
||||
|
||||
if (locator.type == 'xpath' || (locator.string.charAt(0) == '/' &&
|
||||
locator.type == 'implicit')) {
|
||||
results = eval_xpath(locator.string, inDocument,
|
||||
{ contextNode: opt_contextNode });
|
||||
}
|
||||
else if (locator.type == 'css') {
|
||||
results = eval_css(locator.string, inDocument);
|
||||
}
|
||||
else {
|
||||
var element = pageBot
|
||||
.findElementBy(locator.type, locator.string, inDocument);
|
||||
if (element != null) {
|
||||
results.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
// UI-Element
|
||||
|
||||
/**
|
||||
* Escapes the special regular expression characters in a string intended to be
|
||||
* used as a regular expression.
|
||||
*
|
||||
* Based on: http://simonwillison.net/2006/Jan/20/escape/
|
||||
*/
|
||||
RegExp.escape = (function() {
|
||||
var specials = [
|
||||
'/', '.', '*', '+', '?', '|', '^', '$',
|
||||
'(', ')', '[', ']', '{', '}', '\\'
|
||||
];
|
||||
|
||||
var sRE = new RegExp(
|
||||
'(\\' + specials.join('|\\') + ')', 'g'
|
||||
);
|
||||
|
||||
return function(text) {
|
||||
return text.replace(sRE, '\\$1');
|
||||
}
|
||||
})();
|
||||
|
||||
/**
|
||||
* Returns true if two arrays are identical, and false otherwise.
|
||||
*
|
||||
* @param a1 the first array, may only contain simple values (strings or
|
||||
* numbers)
|
||||
* @param a2 the second array, same restricts on data as for a1
|
||||
* @return true if the arrays are equivalent, false otherwise.
|
||||
*/
|
||||
function are_equal(a1, a2)
|
||||
{
|
||||
if (typeof(a1) != typeof(a2))
|
||||
return false;
|
||||
|
||||
switch(typeof(a1)) {
|
||||
case 'object':
|
||||
// arrays
|
||||
if (a1.length) {
|
||||
if (a1.length != a2.length)
|
||||
return false;
|
||||
for (var i = 0; i < a1.length; ++i) {
|
||||
if (!are_equal(a1[i], a2[i]))
|
||||
return false
|
||||
}
|
||||
}
|
||||
// associative arrays
|
||||
else {
|
||||
var keys = {};
|
||||
for (var key in a1) {
|
||||
keys[key] = true;
|
||||
}
|
||||
for (var key in a2) {
|
||||
keys[key] = true;
|
||||
}
|
||||
for (var key in keys) {
|
||||
if (!are_equal(a1[key], a2[key]))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
return a1 == a2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a clone of an object and return it. This is a deep copy of everything
|
||||
* but functions, whose references are copied. You shouldn't expect a deep copy
|
||||
* of functions anyway.
|
||||
*
|
||||
* @param orig the original object to copy
|
||||
* @return a deep copy of the original object. Any functions attached,
|
||||
* however, will have their references copied only.
|
||||
*/
|
||||
function clone(orig) {
|
||||
var copy;
|
||||
switch(typeof(orig)) {
|
||||
case 'object':
|
||||
copy = (orig.length) ? [] : {};
|
||||
for (var attr in orig) {
|
||||
copy[attr] = clone(orig[attr]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
copy = orig;
|
||||
break;
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emulates php's print_r() functionality. Returns a nicely formatted string
|
||||
* representation of an object. Very useful for debugging.
|
||||
*
|
||||
* @param object the object to dump
|
||||
* @param maxDepth the maximum depth to recurse into the object. Ellipses will
|
||||
* be shown for objects whose depth exceeds the maximum.
|
||||
* @param indent the string to use for indenting progressively deeper levels
|
||||
* of the dump.
|
||||
* @return a string representing a dump of the object
|
||||
*/
|
||||
function print_r(object, maxDepth, indent)
|
||||
{
|
||||
var parentIndent, attr, str = "";
|
||||
if (arguments.length == 1) {
|
||||
var maxDepth = Number.MAX_VALUE;
|
||||
} else {
|
||||
maxDepth--;
|
||||
}
|
||||
if (arguments.length < 3) {
|
||||
parentIndent = ''
|
||||
var indent = ' ';
|
||||
} else {
|
||||
parentIndent = indent;
|
||||
indent += ' ';
|
||||
}
|
||||
|
||||
switch(typeof(object)) {
|
||||
case 'object':
|
||||
if (object.length != undefined) {
|
||||
if (object.length == 0) {
|
||||
str += "Array ()\r\n";
|
||||
}
|
||||
else {
|
||||
str += "Array (\r\n";
|
||||
for (var i = 0; i < object.length; ++i) {
|
||||
str += indent + '[' + i + '] => ';
|
||||
if (maxDepth == 0)
|
||||
str += "...\r\n";
|
||||
else
|
||||
str += print_r(object[i], maxDepth, indent);
|
||||
}
|
||||
str += parentIndent + ")\r\n";
|
||||
}
|
||||
}
|
||||
else {
|
||||
str += "Object (\r\n";
|
||||
for (attr in object) {
|
||||
str += indent + "[" + attr + "] => ";
|
||||
if (maxDepth == 0)
|
||||
str += "...\r\n";
|
||||
else
|
||||
str += print_r(object[attr], maxDepth, indent);
|
||||
}
|
||||
str += parentIndent + ")\r\n";
|
||||
}
|
||||
break;
|
||||
case 'boolean':
|
||||
str += (object ? 'true' : 'false') + "\r\n";
|
||||
break;
|
||||
case 'function':
|
||||
str += "Function\r\n";
|
||||
break;
|
||||
default:
|
||||
str += object + "\r\n";
|
||||
break;
|
||||
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array containing all properties of an object. Perl-style.
|
||||
*
|
||||
* @param object the object whose keys to return
|
||||
* @return array of object keys, as strings
|
||||
*/
|
||||
function keys(object)
|
||||
{
|
||||
var keys = [];
|
||||
for (var k in object) {
|
||||
keys.push(k);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emulates python's range() built-in. Returns an array of integers, counting
|
||||
* up (or down) from start to end. Note that the range returned is up to, but
|
||||
* NOT INCLUDING, end.
|
||||
*.
|
||||
* @param start integer from which to start counting. If the end parameter is
|
||||
* not provided, this value is considered the end and start will
|
||||
* be zero.
|
||||
* @param end integer to which to count. If omitted, the function will count
|
||||
* up from zero to the value of the start parameter. Note that
|
||||
* the array returned will count up to but will not include this
|
||||
* value.
|
||||
* @return an array of consecutive integers.
|
||||
*/
|
||||
function range(start, end)
|
||||
{
|
||||
if (arguments.length == 1) {
|
||||
var end = start;
|
||||
start = 0;
|
||||
}
|
||||
|
||||
var r = [];
|
||||
if (start < end) {
|
||||
while (start != end)
|
||||
r.push(start++);
|
||||
}
|
||||
else {
|
||||
while (start != end)
|
||||
r.push(start--);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a python-style keyword arguments string and returns the pairs in a
|
||||
* new object.
|
||||
*
|
||||
* @param kwargs a string representing a set of keyword arguments. It should
|
||||
* look like <tt>keyword1=value1, keyword2=value2, ...</tt>
|
||||
* @return an object mapping strings to strings
|
||||
*/
|
||||
function parse_kwargs(kwargs)
|
||||
{
|
||||
var args = new Object();
|
||||
var pairs = kwargs.split(/,/);
|
||||
for (var i = 0; i < pairs.length;) {
|
||||
if (i > 0 && pairs[i].indexOf('=') == -1) {
|
||||
// the value string contained a comma. Glue the parts back together.
|
||||
pairs[i-1] += ',' + pairs.splice(i, 1)[0];
|
||||
}
|
||||
else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < pairs.length; ++i) {
|
||||
var splits = pairs[i].split(/=/);
|
||||
if (splits.length == 1) {
|
||||
continue;
|
||||
}
|
||||
var key = splits.shift();
|
||||
var value = splits.join('=');
|
||||
args[key.trim()] = value.trim();
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a python-style keyword arguments string from an object.
|
||||
*
|
||||
* @param args an associative array mapping strings to strings
|
||||
* @param sortedKeys (optional) a list of keys of the args parameter that
|
||||
* specifies the order in which the arguments will appear in
|
||||
* the returned kwargs string
|
||||
*
|
||||
* @return a kwarg string representation of args
|
||||
*/
|
||||
function to_kwargs(args, sortedKeys)
|
||||
{
|
||||
var s = '';
|
||||
if (!sortedKeys) {
|
||||
var sortedKeys = keys(args).sort();
|
||||
}
|
||||
for (var i = 0; i < sortedKeys.length; ++i) {
|
||||
var k = sortedKeys[i];
|
||||
if (args[k] != undefined) {
|
||||
if (s) {
|
||||
s += ', ';
|
||||
}
|
||||
s += k + '=' + args[k];
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a node is an ancestor node of a target node, and false
|
||||
* otherwise.
|
||||
*
|
||||
* @param node the node being compared to the target node
|
||||
* @param target the target node
|
||||
* @return true if node is an ancestor node of target, false otherwise.
|
||||
*/
|
||||
function is_ancestor(node, target)
|
||||
{
|
||||
while (target.parentNode) {
|
||||
target = target.parentNode;
|
||||
if (node == target)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
// parseUri 1.2.1
|
||||
// MIT License
|
||||
|
||||
/*
|
||||
Copyright (c) 2007 Steven Levithan <stevenlevithan.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
*/
|
||||
|
||||
function parseUri (str) {
|
||||
var o = parseUri.options,
|
||||
m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
|
||||
uri = {},
|
||||
i = 14;
|
||||
|
||||
while (i--) uri[o.key[i]] = m[i] || "";
|
||||
|
||||
uri[o.q.name] = {};
|
||||
uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
|
||||
if ($1) uri[o.q.name][$1] = $2;
|
||||
});
|
||||
|
||||
return uri;
|
||||
};
|
||||
|
||||
parseUri.options = {
|
||||
strictMode: false,
|
||||
key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
|
||||
q: {
|
||||
name: "queryKey",
|
||||
parser: /(?:^|&)([^&=]*)=?([^&]*)/g
|
||||
},
|
||||
parser: {
|
||||
strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
|
||||
loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
{
|
||||
|
||||
__SELENIUM_JS__
|
||||
|
||||
// Some background on the code below: broadly speaking, where we are relative to other windows
|
||||
// when running in proxy injection mode depends on whether we are in a frame set file or not.
|
||||
//
|
||||
|
|
@ -18,18 +17,15 @@
|
|||
// assigned a regular frame for selenium?)
|
||||
//
|
||||
BrowserBot.prototype.getContentWindow = function() {
|
||||
if (window["seleniumInSameWindow"] != null) return window;
|
||||
return window.parent;
|
||||
return window;
|
||||
};
|
||||
|
||||
BrowserBot.prototype.getTargetWindow = function(windowName) {
|
||||
if (window["seleniumInSameWindow"] != null) return window;
|
||||
return window.parent;
|
||||
return window;
|
||||
};
|
||||
|
||||
BrowserBot.prototype.getCurrentWindow = function() {
|
||||
if (window["seleniumInSameWindow"] != null) return window;
|
||||
return window.parent;
|
||||
return window;
|
||||
};
|
||||
|
||||
LOG.openLogWindow = function(message, className) {
|
||||
|
|
@ -42,10 +38,6 @@ BrowserBot.prototype.relayToRC = function(name) {
|
|||
sendToRC(s,"state=true");
|
||||
}
|
||||
|
||||
BrowserBot.prototype.relayBotToRC = function(s) {
|
||||
this.relayToRC("selenium." + s);
|
||||
}
|
||||
|
||||
function selenium_frameRunTest(oldOnLoadRoutine) {
|
||||
if (oldOnLoadRoutine) {
|
||||
eval(oldOnLoadRoutine);
|
||||
|
|
@ -54,13 +46,13 @@ function selenium_frameRunTest(oldOnLoadRoutine) {
|
|||
}
|
||||
|
||||
function seleniumOnLoad() {
|
||||
injectedSessionId = @SESSION_ID@;
|
||||
injectedSessionId = "@SESSION_ID@";
|
||||
window["selenium_has_been_loaded_into_this_window"] = true;
|
||||
runSeleniumTest();
|
||||
}
|
||||
|
||||
function seleniumOnUnload() {
|
||||
sendToRC("OK"); // just in case some poor PI server thread is waiting for a response
|
||||
sendToRC("Current window or frame is closed!", "closing=true");
|
||||
}
|
||||
|
||||
if (window.addEventListener) {
|
||||
|
|
@ -74,6 +66,7 @@ else {
|
|||
throw "causing a JavaScript error to tell the world that I did not arrange to be run on load";
|
||||
}
|
||||
|
||||
injectedSessionId = @SESSION_ID@;
|
||||
injectedSessionId = "@SESSION_ID@";
|
||||
proxyInjectionMode = true;
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
<script language="JavaScript">
|
||||
// Ideally I would avoid polluting the namespace by enclosing this snippet with
|
||||
// curly braces, but I want to make it easy to look at what URL I used for anyone
|
||||
// who is interested in looking into http://jira.openqa.org/browse/SRC-101:
|
||||
var _sel_url_ = "http://" + location.host + "/selenium-server/core/scripts/injection.html";
|
||||
document.write('<iframe name="selenium" width=0 height=0 id="selenium" src="' + _sel_url_ + '"></iframe>');
|
||||
</script>
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
/*
|
||||
|
||||
This is an experiment in using the Narcissus JavaScript engine
|
||||
to allow Selenium scripts to be written in plain JavaScript.
|
||||
|
||||
The 'jsparse' function will compile each high level block into a Selenium table script.
|
||||
|
||||
|
||||
TODO:
|
||||
1) Test! (More browsers, more sample scripts)
|
||||
2) Stepping and walking lower levels of the parse tree
|
||||
3) Calling Selenium commands directly from JavaScript
|
||||
4) Do we want comments to appear in the TestRunner?
|
||||
5) Fix context so variables don't have to be global
|
||||
For now, variables defined with "var" won't be found
|
||||
if used later on in a script.
|
||||
6) Fix formatting
|
||||
*/
|
||||
|
||||
|
||||
function jsparse() {
|
||||
var script = document.getElementById('sejs')
|
||||
var fname = 'javascript script';
|
||||
parse_result = parse(script.text, fname, 0);
|
||||
|
||||
var x2 = new ExecutionContext(GLOBAL_CODE);
|
||||
ExecutionContext.current = x2;
|
||||
|
||||
|
||||
var new_test_source = '';
|
||||
var new_line = '';
|
||||
|
||||
for (i=0;i<parse_result.$length;i++){
|
||||
var the_start = parse_result[i].start;
|
||||
var the_end;
|
||||
if ( i == (parse_result.$length-1)) {
|
||||
the_end = parse_result.tokenizer.source.length;
|
||||
} else {
|
||||
the_end = parse_result[i+1].start;
|
||||
}
|
||||
|
||||
var script_fragment = parse_result.tokenizer.source.slice(the_start,the_end)
|
||||
|
||||
new_line = '<tr><td style="display:none;" class="js">getEval</td>' +
|
||||
'<td style="display:none;">currentTest.doNextCommand()</td>' +
|
||||
'<td style="white-space: pre;">' + script_fragment + '</td>' +
|
||||
'<td></td></tr>\n';
|
||||
new_test_source += new_line;
|
||||
//eval(script_fragment);
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
execute(parse_result,x2)
|
||||
|
||||
// Create HTML Table
|
||||
body = document.body
|
||||
body.innerHTML += "<table class='selenium' id='se-js-table'>"+
|
||||
"<tbody>" +
|
||||
"<tr><td>// " + document.title + "</td></tr>" +
|
||||
new_test_source +
|
||||
"</tbody" +
|
||||
"</table>";
|
||||
|
||||
//body.innerHTML = "<pre>" + parse_result + "</pre>"
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,175 +0,0 @@
|
|||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (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.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is the Narcissus JavaScript engine.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Brendan Eich <brendan@mozilla.org>.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2004
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/*
|
||||
* Narcissus - JS implemented in JS.
|
||||
*
|
||||
* Well-known constants and lookup tables. Many consts are generated from the
|
||||
* tokens table via eval to minimize redundancy, so consumers must be compiled
|
||||
* separately to take advantage of the simple switch-case constant propagation
|
||||
* done by SpiderMonkey.
|
||||
*/
|
||||
|
||||
// jrh
|
||||
//module('JS.Defs');
|
||||
|
||||
GLOBAL = this;
|
||||
|
||||
var tokens = [
|
||||
// End of source.
|
||||
"END",
|
||||
|
||||
// Operators and punctuators. Some pair-wise order matters, e.g. (+, -)
|
||||
// and (UNARY_PLUS, UNARY_MINUS).
|
||||
"\n", ";",
|
||||
",",
|
||||
"=",
|
||||
"?", ":", "CONDITIONAL",
|
||||
"||",
|
||||
"&&",
|
||||
"|",
|
||||
"^",
|
||||
"&",
|
||||
"==", "!=", "===", "!==",
|
||||
"<", "<=", ">=", ">",
|
||||
"<<", ">>", ">>>",
|
||||
"+", "-",
|
||||
"*", "/", "%",
|
||||
"!", "~", "UNARY_PLUS", "UNARY_MINUS",
|
||||
"++", "--",
|
||||
".",
|
||||
"[", "]",
|
||||
"{", "}",
|
||||
"(", ")",
|
||||
|
||||
// Nonterminal tree node type codes.
|
||||
"SCRIPT", "BLOCK", "LABEL", "FOR_IN", "CALL", "NEW_WITH_ARGS", "INDEX",
|
||||
"ARRAY_INIT", "OBJECT_INIT", "PROPERTY_INIT", "GETTER", "SETTER",
|
||||
"GROUP", "LIST",
|
||||
|
||||
// Terminals.
|
||||
"IDENTIFIER", "NUMBER", "STRING", "REGEXP",
|
||||
|
||||
// Keywords.
|
||||
"break",
|
||||
"case", "catch", "const", "continue",
|
||||
"debugger", "default", "delete", "do",
|
||||
"else", "enum",
|
||||
"false", "finally", "for", "function",
|
||||
"if", "in", "instanceof",
|
||||
"new", "null",
|
||||
"return",
|
||||
"switch",
|
||||
"this", "throw", "true", "try", "typeof",
|
||||
"var", "void",
|
||||
"while", "with",
|
||||
// Extensions
|
||||
"require", "bless", "mixin", "import"
|
||||
];
|
||||
|
||||
// Operator and punctuator mapping from token to tree node type name.
|
||||
// NB: superstring tokens (e.g., ++) must come before their substring token
|
||||
// counterparts (+ in the example), so that the opRegExp regular expression
|
||||
// synthesized from this list makes the longest possible match.
|
||||
var opTypeNames = {
|
||||
'\n': "NEWLINE",
|
||||
';': "SEMICOLON",
|
||||
',': "COMMA",
|
||||
'?': "HOOK",
|
||||
':': "COLON",
|
||||
'||': "OR",
|
||||
'&&': "AND",
|
||||
'|': "BITWISE_OR",
|
||||
'^': "BITWISE_XOR",
|
||||
'&': "BITWISE_AND",
|
||||
'===': "STRICT_EQ",
|
||||
'==': "EQ",
|
||||
'=': "ASSIGN",
|
||||
'!==': "STRICT_NE",
|
||||
'!=': "NE",
|
||||
'<<': "LSH",
|
||||
'<=': "LE",
|
||||
'<': "LT",
|
||||
'>>>': "URSH",
|
||||
'>>': "RSH",
|
||||
'>=': "GE",
|
||||
'>': "GT",
|
||||
'++': "INCREMENT",
|
||||
'--': "DECREMENT",
|
||||
'+': "PLUS",
|
||||
'-': "MINUS",
|
||||
'*': "MUL",
|
||||
'/': "DIV",
|
||||
'%': "MOD",
|
||||
'!': "NOT",
|
||||
'~': "BITWISE_NOT",
|
||||
'.': "DOT",
|
||||
'[': "LEFT_BRACKET",
|
||||
']': "RIGHT_BRACKET",
|
||||
'{': "LEFT_CURLY",
|
||||
'}': "RIGHT_CURLY",
|
||||
'(': "LEFT_PAREN",
|
||||
')': "RIGHT_PAREN"
|
||||
};
|
||||
|
||||
// Hash of keyword identifier to tokens index. NB: we must null __proto__ to
|
||||
// avoid toString, etc. namespace pollution.
|
||||
var keywords = {__proto__: null};
|
||||
|
||||
// Define const END, etc., based on the token names. Also map name to index.
|
||||
var consts = " ";
|
||||
for (var i = 0, j = tokens.length; i < j; i++) {
|
||||
if (i > 0)
|
||||
consts += "; ";
|
||||
var t = tokens[i];
|
||||
if (/^[a-z]/.test(t)) {
|
||||
consts += t.toUpperCase();
|
||||
keywords[t] = i;
|
||||
} else {
|
||||
consts += (/^\W/.test(t) ? opTypeNames[t] : t);
|
||||
}
|
||||
consts += " = " + i;
|
||||
tokens[t] = i;
|
||||
}
|
||||
eval(consts + ";");
|
||||
|
||||
// Map assignment operators to their indexes in the tokens array.
|
||||
var assignOps = ['|', '^', '&', '<<', '>>', '>>>', '+', '-', '*', '/', '%'];
|
||||
|
||||
for (i = 0, j = assignOps.length; i < j; i++) {
|
||||
t = assignOps[i];
|
||||
assignOps[t] = tokens[t];
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
|
||||
This is an experiment in creating a "selenese" parser that drastically
|
||||
cuts down on the line noise associated with writing tests in HTML.
|
||||
|
||||
The 'parse' function will accept the follow sample commands.
|
||||
|
||||
test-cases:
|
||||
//comment
|
||||
command "param"
|
||||
command "param" // comment
|
||||
command "param" "param2"
|
||||
command "param" "param2" // this is a comment
|
||||
|
||||
TODO:
|
||||
1) Deal with multiline parameters
|
||||
2) Escape quotes properly
|
||||
3) Determine whether this should/will become the "preferred" syntax
|
||||
for delivered Selenium self-test scripts
|
||||
*/
|
||||
|
||||
|
||||
function separse(doc) {
|
||||
// Get object
|
||||
script = doc.getElementById('testcase')
|
||||
// Split into lines
|
||||
lines = script.text.split('\n');
|
||||
|
||||
|
||||
var command_pattern = / *(\w+) *"([^"]*)" *(?:"([^"]*)"){0,1}(?: *(\/\/ *.+))*/i;
|
||||
var comment_pattern = /^ *(\/\/ *.+)/
|
||||
|
||||
// Regex each line into selenium command and convert into table row.
|
||||
// eg. "<command> <quote> <quote> <comment>"
|
||||
var new_test_source = '';
|
||||
var new_line = '';
|
||||
for (var x=0; x < lines.length; x++) {
|
||||
result = lines[x].match(command_pattern);
|
||||
if (result != null) {
|
||||
new_line = "<tr><td>" + (result[1] || ' ') + "</td>" +
|
||||
"<td>" + (result[2] || ' ') + "</td>" +
|
||||
"<td>" + (result[3] || ' ') + "</td>" +
|
||||
"<td>" + (result[4] || ' ') + "</td></tr>\n";
|
||||
new_test_source += new_line;
|
||||
}
|
||||
result = lines[x].match(comment_pattern);
|
||||
if (result != null) {
|
||||
new_line = '<tr><td rowspan="1" colspan="4">' +
|
||||
(result[1] || ' ') +
|
||||
'</td></tr>';
|
||||
new_test_source += new_line;
|
||||
}
|
||||
}
|
||||
|
||||
// Create HTML Table
|
||||
body = doc.body
|
||||
body.innerHTML += "<table class='selenium' id='testtable'>"+
|
||||
new_test_source +
|
||||
"</table>";
|
||||
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -24,6 +24,14 @@
|
|||
var BrowserVersion = function() {
|
||||
this.name = navigator.appName;
|
||||
|
||||
if (navigator.userAgent.indexOf('Mac OS X') != -1) {
|
||||
this.isOSX = true;
|
||||
}
|
||||
|
||||
if (navigator.userAgent.indexOf('Windows NT 6') != -1) {
|
||||
this.isVista = true;
|
||||
}
|
||||
|
||||
if (window.opera != null) {
|
||||
this.browser = BrowserVersion.OPERA;
|
||||
this.isOpera = true;
|
||||
|
|
@ -85,9 +93,12 @@ var BrowserVersion = function() {
|
|||
self.isHTA = false;
|
||||
}
|
||||
}
|
||||
if (navigator.appVersion.match(/MSIE 6.0/)) {
|
||||
this.isIE6 = true;
|
||||
}
|
||||
if ("0" == navigator.appMinorVersion) {
|
||||
this.preSV1 = true;
|
||||
if (navigator.appVersion.match(/MSIE 6.0/)) {
|
||||
if (this.isIE6) {
|
||||
this.appearsToBeBrokenInitialIE6 = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ objectExtend(CommandHandlerFactory.prototype, {
|
|||
// is true when the value returned by the accessor matches the specified value.
|
||||
return function(target, value) {
|
||||
var accessorResult = accessBlock(target);
|
||||
accessorResult = selArrayToString(accessorResult);
|
||||
if (PatternMatcher.matches(value, accessorResult)) {
|
||||
return new PredicateResult(true, "Actual value '" + accessorResult + "' did match '" + value + "'");
|
||||
} else {
|
||||
|
|
@ -150,6 +151,7 @@ objectExtend(CommandHandlerFactory.prototype, {
|
|||
// is true when the value returned by the accessor matches the specified value.
|
||||
return function(value) {
|
||||
var accessorResult = accessBlock();
|
||||
accessorResult = selArrayToString(accessorResult);
|
||||
if (PatternMatcher.matches(value, accessorResult)) {
|
||||
return new PredicateResult(true, "Actual value '" + accessorResult + "' did match '" + value + "'");
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ TestLoop.prototype = {
|
|||
this.continueTestWhenConditionIsTrue();
|
||||
} catch (e) {
|
||||
if (!this._handleCommandError(e)) {
|
||||
this._testComplete();
|
||||
this.testComplete();
|
||||
} else {
|
||||
this.continueTest();
|
||||
}
|
||||
|
|
@ -119,10 +119,8 @@ TestLoop.prototype = {
|
|||
_handleCommandError : function(e) {
|
||||
if (!e.isSeleniumError) {
|
||||
LOG.exception(e);
|
||||
var msg = "Selenium failure. Please report to selenium-dev@openqa.org, with error details from the log window.";
|
||||
if (e.message) {
|
||||
msg += " The error message is: " + e.message;
|
||||
}
|
||||
var msg = "Command execution failure. Please search the forum at http://clearspace.openqa.org for error details from the log window.";
|
||||
msg += " The error message is: " + extractExceptionMessage(e);
|
||||
return this.commandError(msg);
|
||||
} else {
|
||||
LOG.error(e.message);
|
||||
|
|
|
|||
|
|
@ -19,11 +19,24 @@ var Logger = function() {
|
|||
}
|
||||
Logger.prototype = {
|
||||
|
||||
logLevels: {
|
||||
debug: 0,
|
||||
info: 1,
|
||||
warn: 2,
|
||||
error: 3,
|
||||
off: 999
|
||||
},
|
||||
|
||||
pendingMessages: new Array(),
|
||||
|
||||
threshold: "info",
|
||||
|
||||
setLogLevelThreshold: function(logLevel) {
|
||||
this.pendingLogLevelThreshold = logLevel;
|
||||
this.show();
|
||||
this.threshold = logLevel;
|
||||
var logWindow = this.getLogWindow()
|
||||
if (logWindow && logWindow.setThresholdLevel) {
|
||||
logWindow.setThresholdLevel(logLevel);
|
||||
}
|
||||
// NOTE: log messages will be discarded until the log window is
|
||||
// fully loaded.
|
||||
},
|
||||
|
|
@ -32,23 +45,12 @@ Logger.prototype = {
|
|||
if (this.logWindow && this.logWindow.closed) {
|
||||
this.logWindow = null;
|
||||
}
|
||||
if (this.logWindow && this.pendingLogLevelThreshold && this.logWindow.setThresholdLevel) {
|
||||
this.logWindow.setThresholdLevel(this.pendingLogLevelThreshold);
|
||||
|
||||
// can't just directly log because that action would loop back
|
||||
// to this code infinitely
|
||||
var pendingMessage = new LogMessage("info", "Log level programmatically set to " + this.pendingLogLevelThreshold + " (presumably by driven-mode test code)");
|
||||
this.pendingMessages.push(pendingMessage);
|
||||
|
||||
this.pendingLogLevelThreshold = null; // let's only go this way one time
|
||||
}
|
||||
|
||||
return this.logWindow;
|
||||
},
|
||||
|
||||
openLogWindow: function() {
|
||||
this.logWindow = window.open(
|
||||
getDocumentBase(document) + "SeleniumLog.html", "SeleniumLog",
|
||||
getDocumentBase(document) + "SeleniumLog.html?startingThreshold="+this.threshold, "SeleniumLog",
|
||||
"width=600,height=1000,bottom=0,right=0,status,scrollbars,resizable"
|
||||
);
|
||||
this.logWindow.moveTo(window.screenX + 1210, window.screenY + window.outerHeight - 1400);
|
||||
|
|
@ -66,33 +68,39 @@ Logger.prototype = {
|
|||
if (! this.getLogWindow()) {
|
||||
this.openLogWindow();
|
||||
}
|
||||
setTimeout(function(){LOG.info("Log window displayed");}, 500);
|
||||
setTimeout(function(){LOG.error("Log window displayed. Logging events will now be recorded to this window.");}, 500);
|
||||
},
|
||||
|
||||
logHook: function(className, message) {
|
||||
logHook: function(logLevel, message) {
|
||||
},
|
||||
|
||||
log: function(className, message) {
|
||||
log: function(logLevel, message) {
|
||||
if (this.logLevels[logLevel] < this.logLevels[this.threshold]) {
|
||||
return;
|
||||
}
|
||||
this.logHook(logLevel, message);
|
||||
var logWindow = this.getLogWindow();
|
||||
this.logHook(className, message);
|
||||
if (logWindow) {
|
||||
if (logWindow.append) {
|
||||
if (logWindow.disabled) {
|
||||
logWindow.callBack = fnBind(this.setLogLevelThreshold, this);
|
||||
logWindow.enableButtons();
|
||||
}
|
||||
if (this.pendingMessages.length > 0) {
|
||||
logWindow.append("info: Appending missed logging messages", "info");
|
||||
logWindow.append("info("+(new Date().getTime())+"): Appending missed logging messages", "info");
|
||||
while (this.pendingMessages.length > 0) {
|
||||
var msg = this.pendingMessages.shift();
|
||||
logWindow.append(msg.type + ": " + msg.msg, msg.type);
|
||||
logWindow.append(msg.type + "("+msg.timestamp+"): " + msg.msg, msg.type);
|
||||
}
|
||||
logWindow.append("info: Done appending missed logging messages", "info");
|
||||
logWindow.append("info("+(new Date().getTime())+"): Done appending missed logging messages", "info");
|
||||
}
|
||||
logWindow.append(className + ": " + message, className);
|
||||
logWindow.append(logLevel + "("+(new Date().getTime())+"): " + message, logLevel);
|
||||
}
|
||||
} else {
|
||||
// uncomment this to turn on background logging
|
||||
/* these logging messages are never flushed, which creates
|
||||
an enormous array of strings that never stops growing. Only
|
||||
turn this on if you need it for debugging! */
|
||||
//this.pendingMessages.push(new LogMessage(className, message));
|
||||
// TODO these logging messages are never flushed, which creates
|
||||
// an enormous array of strings that never stops growing.
|
||||
// there should at least be a way to clear the messages!
|
||||
this.pendingMessages.push(new LogMessage(logLevel, message));
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -136,4 +144,5 @@ var LOG = new Logger();
|
|||
var LogMessage = function(type, msg) {
|
||||
this.type = type;
|
||||
this.msg = msg;
|
||||
this.timestamp = (new Date().getTime());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,16 +22,23 @@ workingColor = "#DEE7EC";
|
|||
doneColor = "#FFFFCC";
|
||||
|
||||
var injectedSessionId;
|
||||
var cmd1 = document.createElement("div");
|
||||
var cmd2 = document.createElement("div");
|
||||
var cmd3 = document.createElement("div");
|
||||
var cmd4 = document.createElement("div");
|
||||
|
||||
var postResult = "START";
|
||||
var debugMode = false;
|
||||
var relayToRC = null;
|
||||
var proxyInjectionMode = false;
|
||||
var uniqueId = 'sel_' + Math.round(100000 * Math.random());
|
||||
var seleniumSequenceNumber = 0;
|
||||
var cmd8 = "";
|
||||
var cmd7 = "";
|
||||
var cmd6 = "";
|
||||
var cmd5 = "";
|
||||
var cmd4 = "";
|
||||
var cmd3 = "";
|
||||
var cmd2 = "";
|
||||
var cmd1 = "";
|
||||
var lastCmd = "";
|
||||
var lastCmdTime = new Date();
|
||||
|
||||
var RemoteRunnerOptions = classCreate();
|
||||
objectExtend(RemoteRunnerOptions.prototype, URLConfiguration.prototype);
|
||||
|
|
@ -51,8 +58,12 @@ objectExtend(RemoteRunnerOptions.prototype, {
|
|||
return this._getQueryParameter("driverUrl");
|
||||
},
|
||||
|
||||
// requires per-session extension Javascript as soon as this Selenium
|
||||
// instance becomes aware of the session identifier
|
||||
getSessionId: function() {
|
||||
return this._getQueryParameter("sessionId");
|
||||
var sessionId = this._getQueryParameter("sessionId");
|
||||
requireExtensionJs(sessionId);
|
||||
return sessionId;
|
||||
},
|
||||
|
||||
_acquireQueryString: function () {
|
||||
|
|
@ -62,7 +73,7 @@ objectExtend(RemoteRunnerOptions.prototype, {
|
|||
if (args.length < 2) return null;
|
||||
this.queryString = args[1];
|
||||
} else if (proxyInjectionMode) {
|
||||
this.queryString = selenium.browserbot.getCurrentWindow().location.search.substr(1);
|
||||
this.queryString = window.location.search.substr(1);
|
||||
} else {
|
||||
this.queryString = top.location.search.substr(1);
|
||||
}
|
||||
|
|
@ -77,8 +88,8 @@ function runSeleniumTest() {
|
|||
|
||||
if (runOptions.isMultiWindowMode()) {
|
||||
testAppWindow = openSeparateApplicationWindow('Blank.html', true);
|
||||
} else if ($('myiframe') != null) {
|
||||
var myiframe = $('myiframe');
|
||||
} else if (sel$('selenium_myiframe') != null) {
|
||||
var myiframe = sel$('selenium_myiframe');
|
||||
if (myiframe) {
|
||||
testAppWindow = myiframe.contentWindow;
|
||||
}
|
||||
|
|
@ -95,7 +106,7 @@ function runSeleniumTest() {
|
|||
debugMode = runOptions.isDebugMode();
|
||||
}
|
||||
if (proxyInjectionMode) {
|
||||
LOG.log = logToRc;
|
||||
LOG.logHook = logToRc;
|
||||
selenium.browserbot._modifyWindow(testAppWindow);
|
||||
}
|
||||
else if (debugMode) {
|
||||
|
|
@ -108,13 +119,6 @@ function runSeleniumTest() {
|
|||
|
||||
currentTest = new RemoteRunner(commandFactory);
|
||||
|
||||
if (document.getElementById("commandList") != null) {
|
||||
document.getElementById("commandList").appendChild(cmd4);
|
||||
document.getElementById("commandList").appendChild(cmd3);
|
||||
document.getElementById("commandList").appendChild(cmd2);
|
||||
document.getElementById("commandList").appendChild(cmd1);
|
||||
}
|
||||
|
||||
var doContinue = runOptions.getContinue();
|
||||
if (doContinue != null) postResult = "OK";
|
||||
|
||||
|
|
@ -130,21 +134,18 @@ function buildDriverUrl() {
|
|||
var slashPairOffset = s.indexOf("//") + "//".length
|
||||
var pathSlashOffset = s.substring(slashPairOffset).indexOf("/")
|
||||
return s.substring(0, slashPairOffset + pathSlashOffset) + "/selenium-server/driver/";
|
||||
//return "http://localhost" + uniqueId + "/selenium-server/driver/";
|
||||
}
|
||||
|
||||
function logToRc(logLevel, message) {
|
||||
if (debugMode) {
|
||||
if (logLevel == null) {
|
||||
logLevel = "debug";
|
||||
}
|
||||
if (debugMode) {
|
||||
sendToRC("logLevel=" + logLevel + ":" + message.replace(/[\n\r\015]/g, " ") + "\n", "logging=true");
|
||||
sendToRCAndForget("logLevel=" + logLevel + ":" + message.replace(/[\n\r\015]/g, " ") + "\n", "logging=true");
|
||||
}
|
||||
}
|
||||
|
||||
function isArray(x) {
|
||||
return ((typeof x) == "object") && (x["length"] != null);
|
||||
}
|
||||
|
||||
function serializeString(name, s) {
|
||||
return name + "=unescape(\"" + escape(s) + "\");";
|
||||
}
|
||||
|
|
@ -204,33 +205,44 @@ objectExtend(RemoteRunner.prototype, {
|
|||
|
||||
commandStarted : function(command) {
|
||||
this.commandNode = document.createElement("div");
|
||||
var innerHTML = command.command + '(';
|
||||
var cmdText = command.command + '(';
|
||||
if (command.target != null && command.target != "") {
|
||||
innerHTML += command.target;
|
||||
cmdText += command.target;
|
||||
if (command.value != null && command.value != "") {
|
||||
innerHTML += ', ' + command.value;
|
||||
cmdText += ', ' + command.value;
|
||||
}
|
||||
}
|
||||
innerHTML += ")";
|
||||
if (innerHTML.length >40) {
|
||||
innerHTML = innerHTML.substring(0,40);
|
||||
innerHTML += "...";
|
||||
if (cmdText.length > 70) {
|
||||
cmdText = cmdText.substring(0, 70) + "...\n";
|
||||
} else {
|
||||
cmdText += ")\n";
|
||||
}
|
||||
this.commandNode.innerHTML = innerHTML;
|
||||
this.commandNode.style.backgroundColor = workingColor;
|
||||
if (document.getElementById("commandList") != null) {
|
||||
document.getElementById("commandList").removeChild(cmd1);
|
||||
document.getElementById("commandList").removeChild(cmd2);
|
||||
document.getElementById("commandList").removeChild(cmd3);
|
||||
document.getElementById("commandList").removeChild(cmd4);
|
||||
|
||||
if (cmdText == lastCmd) {
|
||||
var rightNow = new Date();
|
||||
var msSinceStart = rightNow.getTime() - lastCmdTime.getTime();
|
||||
var sinceStart = msSinceStart + "ms";
|
||||
if (msSinceStart > 1000) {
|
||||
sinceStart = Math.round(msSinceStart / 1000) + "s";
|
||||
}
|
||||
cmd1 = "Same command (" + sinceStart + "): " + lastCmd;
|
||||
} else {
|
||||
lastCmdTime = new Date();
|
||||
cmd8 = cmd7;
|
||||
cmd7 = cmd6;
|
||||
cmd6 = cmd5;
|
||||
cmd5 = cmd4;
|
||||
cmd4 = cmd3;
|
||||
cmd3 = cmd2;
|
||||
cmd2 = cmd1;
|
||||
cmd1 = this.commandNode;
|
||||
document.getElementById("commandList").appendChild(cmd4);
|
||||
document.getElementById("commandList").appendChild(cmd3);
|
||||
document.getElementById("commandList").appendChild(cmd2);
|
||||
document.getElementById("commandList").appendChild(cmd1);
|
||||
cmd1 = cmdText;
|
||||
}
|
||||
lastCmd = cmdText;
|
||||
|
||||
if (! proxyInjectionMode) {
|
||||
var commandList = document.commands.commandList;
|
||||
commandList.value = cmd8 + cmd7 + cmd6 + cmd5 + cmd4 + cmd3 + cmd2 + cmd1;
|
||||
commandList.scrollTop = commandList.scrollHeight;
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -250,7 +262,9 @@ objectExtend(RemoteRunner.prototype, {
|
|||
if (result.result == null) {
|
||||
postResult = "OK";
|
||||
} else {
|
||||
postResult = "OK," + result.result;
|
||||
var actualResult = result.result;
|
||||
actualResult = selArrayToString(actualResult);
|
||||
postResult = "OK," + actualResult;
|
||||
}
|
||||
this.commandNode.style.backgroundColor = doneColor;
|
||||
}
|
||||
|
|
@ -259,7 +273,7 @@ objectExtend(RemoteRunner.prototype, {
|
|||
commandError : function(message) {
|
||||
postResult = "ERROR: " + message;
|
||||
this.commandNode.style.backgroundColor = errorColor;
|
||||
this.commandNode.title = message;
|
||||
this.commandNode.titcle = message;
|
||||
},
|
||||
|
||||
testComplete : function() {
|
||||
|
|
@ -270,16 +284,26 @@ objectExtend(RemoteRunner.prototype, {
|
|||
},
|
||||
|
||||
_HandleHttpResponse : function() {
|
||||
// When request is completed
|
||||
if (this.xmlHttpForCommandsAndResults.readyState == 4) {
|
||||
// OK
|
||||
if (this.xmlHttpForCommandsAndResults.status == 200) {
|
||||
if (this.xmlHttpForCommandsAndResults.responseText=="") {
|
||||
LOG.error("saw blank string xmlHttpForCommandsAndResults.responseText");
|
||||
return;
|
||||
}
|
||||
var command = this._extractCommand(this.xmlHttpForCommandsAndResults);
|
||||
if (command.command == 'retryLast') {
|
||||
setTimeout(fnBind(function() {
|
||||
sendToRC("RETRY", "retry=true", fnBind(this._HandleHttpResponse, this), this.xmlHttpForCommandsAndResults, true);
|
||||
}, this), 1000);
|
||||
} else {
|
||||
this.currentCommand = command;
|
||||
this.continueTestAtCurrentCommand();
|
||||
} else {
|
||||
}
|
||||
}
|
||||
// Not OK
|
||||
else {
|
||||
var s = 'xmlHttp returned: ' + this.xmlHttpForCommandsAndResults.status + ": " + this.xmlHttpForCommandsAndResults.statusText;
|
||||
LOG.error(s);
|
||||
this.currentCommand = null;
|
||||
|
|
@ -290,7 +314,15 @@ objectExtend(RemoteRunner.prototype, {
|
|||
},
|
||||
|
||||
_extractCommand : function(xmlHttp) {
|
||||
var command;
|
||||
var command, text, json;
|
||||
text = command = xmlHttp.responseText;
|
||||
if (/^json=/.test(text)) {
|
||||
eval(text);
|
||||
if (json.rest) {
|
||||
eval(json.rest);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
try {
|
||||
var re = new RegExp("^(.*?)\n((.|[\r\n])*)");
|
||||
if (re.exec(xmlHttp.responseText)) {
|
||||
|
|
@ -364,21 +396,68 @@ function sendToRC(dataToBePosted, urlParms, callback, xmlHttpObject, async) {
|
|||
if (urlParms) {
|
||||
url += urlParms;
|
||||
}
|
||||
url += "&localFrameAddress=" + (proxyInjectionMode ? makeAddressToAUTFrame() : "top");
|
||||
url += getSeleniumWindowNameURLparameters();
|
||||
url += "&uniqueId=" + uniqueId;
|
||||
url = addUrlParams(url);
|
||||
url += "&sequenceNumber=" + seleniumSequenceNumber++;
|
||||
|
||||
if (callback == null) {
|
||||
callback = function() {
|
||||
};
|
||||
}
|
||||
url += buildDriverParams() + preventBrowserCaching();
|
||||
var postedData = "postedData=" + encodeURIComponent(dataToBePosted);
|
||||
|
||||
//xmlHttpObject.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
xmlHttpObject.open("POST", url, async);
|
||||
xmlHttpObject.onreadystatechange = callback;
|
||||
xmlHttpObject.send(dataToBePosted);
|
||||
if (callback) xmlHttpObject.onreadystatechange = callback;
|
||||
xmlHttpObject.send(postedData);
|
||||
return null;
|
||||
}
|
||||
|
||||
function addUrlParams(url) {
|
||||
return url + "&localFrameAddress=" + (proxyInjectionMode ? makeAddressToAUTFrame() : "top")
|
||||
+ getSeleniumWindowNameURLparameters()
|
||||
+ "&uniqueId=" + uniqueId
|
||||
+ buildDriverParams() + preventBrowserCaching()
|
||||
}
|
||||
|
||||
function sendToRCAndForget(dataToBePosted, urlParams) {
|
||||
var url;
|
||||
if (!(browserVersion.isChrome || browserVersion.isHTA)) {
|
||||
// DGF we're behind a proxy, so we can send our logging message to literally any host, to avoid 2-connection limit
|
||||
var protocol = "http:";
|
||||
if (window.location.protocol == "https:") {
|
||||
// DGF if we're in HTTPS, use another HTTPS url to avoid security warning
|
||||
protocol = "https:";
|
||||
}
|
||||
// we don't choose a super large random value, but rather 1 - 16, because this matches with the pre-computed
|
||||
// tunnels waiting on the Selenium Server side. This gives us higher throughput than the two-connection-per-host
|
||||
// limitation, but doesn't require we generate an extremely large ammount of fake SSL certs either.
|
||||
url = protocol + "//" + Math.floor(Math.random()* 16 + 1) + ".selenium.doesnotexist/selenium-server/driver/?" + urlParams;
|
||||
} else {
|
||||
url = buildDriverUrl() + "?" + urlParams;
|
||||
}
|
||||
url = addUrlParams(url);
|
||||
|
||||
var method = "GET";
|
||||
if (method == "POST") {
|
||||
// DGF submit a request using an iframe; we can't see the response, but we don't need to
|
||||
// TODO not using this mechanism because it screws up back-button
|
||||
var loggingForm = document.createElement("form");
|
||||
loggingForm.method = "POST";
|
||||
loggingForm.action = url;
|
||||
loggingForm.target = "seleniumLoggingFrame";
|
||||
var postedDataInput = document.createElement("input");
|
||||
postedDataInput.type = "hidden";
|
||||
postedDataInput.name = "postedData";
|
||||
postedDataInput.value = dataToBePosted;
|
||||
loggingForm.appendChild(postedDataInput);
|
||||
document.body.appendChild(loggingForm);
|
||||
loggingForm.submit();
|
||||
document.body.removeChild(loggingForm);
|
||||
} else {
|
||||
var postedData = "&postedData=" + encodeURIComponent(dataToBePosted);
|
||||
var scriptTag = document.createElement("script");
|
||||
scriptTag.src = url + postedData;
|
||||
document.body.appendChild(scriptTag);
|
||||
document.body.removeChild(scriptTag);
|
||||
}
|
||||
}
|
||||
|
||||
function buildDriverParams() {
|
||||
var params = "";
|
||||
|
||||
|
|
@ -420,11 +499,13 @@ function getSeleniumWindowNameURLparameters() {
|
|||
return s;
|
||||
}
|
||||
if (w["seleniumWindowName"] == null) {
|
||||
s += 'generatedSeleniumWindowName_' + Math.round(100000 * Math.random());
|
||||
if (w.name) {
|
||||
w["seleniumWindowName"] = w.name;
|
||||
} else {
|
||||
w["seleniumWindowName"] = 'generatedSeleniumWindowName_' + Math.round(100000 * Math.random());
|
||||
}
|
||||
}
|
||||
else {
|
||||
s += w["seleniumWindowName"];
|
||||
}
|
||||
var windowOpener = w.opener;
|
||||
for (key in windowOpener) {
|
||||
var val = null;
|
||||
|
|
@ -464,3 +545,151 @@ function makeAddressToAUTFrame(w, frameNavigationalJSexpression)
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Selenium.prototype.doSetContext = function(context) {
|
||||
/**
|
||||
* Writes a message to the status bar and adds a note to the browser-side
|
||||
* log.
|
||||
*
|
||||
* @param context
|
||||
* the message to be sent to the browser
|
||||
*/
|
||||
//set the current test title
|
||||
var ctx = document.getElementById("context");
|
||||
if (ctx != null) {
|
||||
ctx.innerHTML = context;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a script tag referencing a specially-named user extensions "file". The
|
||||
* resource handler for this special file (which won't actually exist) will use
|
||||
* the session ID embedded in its name to retrieve per-session specified user
|
||||
* extension javascript.
|
||||
*
|
||||
* @param sessionId
|
||||
*/
|
||||
function requireExtensionJs(sessionId) {
|
||||
var src = 'scripts/user-extensions.js[' + sessionId + ']';
|
||||
if (document.getElementById(src) == null) {
|
||||
var scriptTag = document.createElement('script');
|
||||
scriptTag.language = 'JavaScript';
|
||||
scriptTag.type = 'text/javascript';
|
||||
scriptTag.src = src;
|
||||
scriptTag.id = src;
|
||||
var headTag = document.getElementsByTagName('head')[0];
|
||||
headTag.appendChild(scriptTag);
|
||||
}
|
||||
}
|
||||
|
||||
Selenium.prototype.doAttachFile = function(fieldLocator,fileLocator) {
|
||||
/**
|
||||
* Sets a file input (upload) field to the file listed in fileLocator
|
||||
*
|
||||
* @param fieldLocator an <a href="#locators">element locator</a>
|
||||
* @param fileLocator a URL pointing to the specified file. Before the file
|
||||
* can be set in the input field (fieldLocator), Selenium RC may need to transfer the file
|
||||
* to the local machine before attaching the file in a web page form. This is common in selenium
|
||||
* grid configurations where the RC server driving the browser is not the same
|
||||
* machine that started the test.
|
||||
*
|
||||
* Supported Browsers: Firefox ("*chrome") only.
|
||||
*
|
||||
*/
|
||||
// This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
|
||||
};
|
||||
|
||||
Selenium.prototype.doCaptureScreenshot = function(filename) {
|
||||
/**
|
||||
* Captures a PNG screenshot to the specified file.
|
||||
*
|
||||
* @param filename the absolute path to the file to be written, e.g. "c:\blah\screenshot.png"
|
||||
*/
|
||||
// This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
|
||||
};
|
||||
|
||||
Selenium.prototype.doCaptureScreenshotToString = function() {
|
||||
/**
|
||||
* Capture a PNG screenshot. It then returns the file as a base 64 encoded string.
|
||||
*
|
||||
* @return string The base 64 encoded string of the screen shot (PNG file)
|
||||
*/
|
||||
// This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
|
||||
};
|
||||
|
||||
Selenium.prototype.doCaptureEntirePageScreenshotToString = function(kwargs) {
|
||||
/**
|
||||
* Downloads a screenshot of the browser current window canvas to a
|
||||
* based 64 encoded PNG file. The <em>entire</em> windows canvas is captured,
|
||||
* including parts rendered outside of the current view port.
|
||||
*
|
||||
* Currently this only works in Mozilla and when running in chrome mode.
|
||||
*
|
||||
* @param kwargs A kwargs string that modifies the way the screenshot is captured. Example: "background=#CCFFDD". This may be useful to set for capturing screenshots of less-than-ideal layouts, for example where absolute positioning causes the calculation of the canvas dimension to fail and a black background is exposed (possibly obscuring black text).
|
||||
*
|
||||
* @return string The base 64 encoded string of the page screenshot (PNG file)
|
||||
*/
|
||||
// This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
|
||||
};
|
||||
|
||||
Selenium.prototype.doShutDownSeleniumServer = function(keycode) {
|
||||
/**
|
||||
* Kills the running Selenium Server and all browser sessions. After you run this command, you will no longer be able to send
|
||||
* commands to the server; you can't remotely start the server once it has been stopped. Normally
|
||||
* you should prefer to run the "stop" command, which terminates the current browser session, rather than
|
||||
* shutting down the entire server.
|
||||
*
|
||||
*/
|
||||
// This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
|
||||
};
|
||||
|
||||
Selenium.prototype.doRetrieveLastRemoteControlLogs = function() {
|
||||
/**
|
||||
* Retrieve the last messages logged on a specific remote control. Useful for error reports, especially
|
||||
* when running multiple remote controls in a distributed environment. The maximum number of log messages
|
||||
* that can be retrieve is configured on remote control startup.
|
||||
*
|
||||
* @return string The last N log messages as a multi-line string.
|
||||
*/
|
||||
// This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
|
||||
};
|
||||
|
||||
Selenium.prototype.doKeyDownNative = function(keycode) {
|
||||
/**
|
||||
* Simulates a user pressing a key (without releasing it yet) by sending a native operating system keystroke.
|
||||
* This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing
|
||||
* a key on the keyboard. It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and
|
||||
* metaKeyDown commands, and does not target any particular HTML element. To send a keystroke to a particular
|
||||
* element, focus on the element first before running this command.
|
||||
*
|
||||
* @param keycode an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes!
|
||||
*/
|
||||
// This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
|
||||
};
|
||||
|
||||
Selenium.prototype.doKeyUpNative = function(keycode) {
|
||||
/**
|
||||
* Simulates a user releasing a key by sending a native operating system keystroke.
|
||||
* This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing
|
||||
* a key on the keyboard. It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and
|
||||
* metaKeyDown commands, and does not target any particular HTML element. To send a keystroke to a particular
|
||||
* element, focus on the element first before running this command.
|
||||
*
|
||||
* @param keycode an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes!
|
||||
*/
|
||||
// This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
|
||||
};
|
||||
|
||||
Selenium.prototype.doKeyPressNative = function(keycode) {
|
||||
/**
|
||||
* Simulates a user pressing and releasing a key by sending a native operating system keystroke.
|
||||
* This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing
|
||||
* a key on the keyboard. It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and
|
||||
* metaKeyDown commands, and does not target any particular HTML element. To send a keystroke to a particular
|
||||
* element, focus on the element first before running this command.
|
||||
*
|
||||
* @param keycode an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes!
|
||||
*/
|
||||
// This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,10 @@ objectExtend(HtmlTestRunner.prototype, {
|
|||
},
|
||||
|
||||
loadSuiteFrame: function() {
|
||||
var logLevel = this.controlPanel.getDefaultLogLevel();
|
||||
if (logLevel) {
|
||||
LOG.setLogLevelThreshold(logLevel);
|
||||
}
|
||||
if (selenium == null) {
|
||||
var appWindow = this._getApplicationWindow();
|
||||
try { appWindow.location; }
|
||||
|
|
@ -76,7 +80,7 @@ objectExtend(HtmlTestRunner.prototype, {
|
|||
if (this.controlPanel.isMultiWindowMode()) {
|
||||
return this._getSeparateApplicationWindow();
|
||||
}
|
||||
return $('myiframe').contentWindow;
|
||||
return sel$('selenium_myiframe').contentWindow;
|
||||
},
|
||||
|
||||
_getSeparateApplicationWindow: function () {
|
||||
|
|
@ -87,6 +91,7 @@ objectExtend(HtmlTestRunner.prototype, {
|
|||
},
|
||||
|
||||
_onloadTestSuite:function () {
|
||||
suiteFrame = new HtmlTestSuiteFrame(getSuiteFrame());
|
||||
if (! this.getTestSuite().isAvailable()) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -97,7 +102,12 @@ objectExtend(HtmlTestRunner.prototype, {
|
|||
addLoadListener(this._getApplicationWindow(), fnBind(this._startSingleTest, this));
|
||||
this._getApplicationWindow().src = this.controlPanel.getAutoUrl();
|
||||
} else {
|
||||
this.getTestSuite().getSuiteRows()[0].loadTestCase();
|
||||
var testCaseLoaded = fnBind(function(){this.testCaseLoaded=true;},this);
|
||||
var testNumber = 0;
|
||||
if (this.controlPanel.getTestNumber() != null){
|
||||
var testNumber = this.controlPanel.getTestNumber() - 1;
|
||||
}
|
||||
this.getTestSuite().getSuiteRows()[testNumber].loadTestCase(testCaseLoaded);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -134,6 +144,8 @@ objectExtend(HtmlTestRunner.prototype, {
|
|||
//todo: move testFailed and storedVars to TestCase
|
||||
this.testFailed = false;
|
||||
storedVars = new Object();
|
||||
storedVars.nbsp = String.fromCharCode(160);
|
||||
storedVars.space = ' ';
|
||||
this.currentTest = new HtmlRunnerTestLoop(testFrame.getCurrentTestCase(), this.metrics, this.commandFactory);
|
||||
currentTest = this.currentTest;
|
||||
this.currentTest.start();
|
||||
|
|
@ -157,6 +169,10 @@ objectExtend(SeleniumFrame.prototype, {
|
|||
addLoadListener(this.frame, fnBind(this._handleLoad, this));
|
||||
},
|
||||
|
||||
getWindow : function() {
|
||||
return this.frame.contentWindow;
|
||||
},
|
||||
|
||||
getDocument : function() {
|
||||
return this.frame.contentWindow.document;
|
||||
},
|
||||
|
|
@ -166,7 +182,6 @@ objectExtend(SeleniumFrame.prototype, {
|
|||
this._onLoad();
|
||||
if (this.loadCallback) {
|
||||
this.loadCallback();
|
||||
this.loadCallback = null;
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -187,10 +202,12 @@ objectExtend(SeleniumFrame.prototype, {
|
|||
// this works in every browser (except Firefox in chrome mode)
|
||||
var styleSheetPath = window.location.pathname.replace(/[^\/\\]+$/, "selenium-test.css");
|
||||
if (browserVersion.isIE && window.location.protocol == "file:") {
|
||||
styleSheetPath = "file://" + styleSheetPath;
|
||||
styleSheetPath = "file:///" + styleSheetPath;
|
||||
}
|
||||
styleLink.href = styleSheetPath;
|
||||
}
|
||||
// DGF You're only going to see this log message if you set defaultLogLevel=debug
|
||||
LOG.debug("styleLink.href="+styleLink.href);
|
||||
head.appendChild(styleLink);
|
||||
},
|
||||
|
||||
|
|
@ -205,7 +222,8 @@ objectExtend(SeleniumFrame.prototype, {
|
|||
var isChrome = browserVersion.isChrome || false;
|
||||
var isHTA = browserVersion.isHTA || false;
|
||||
// DGF TODO multiWindow
|
||||
location += "?thisIsChrome=" + isChrome + "&thisIsHTA=" + isHTA;
|
||||
location += (location.indexOf("?") == -1 ? "?" : "&");
|
||||
location += "thisIsChrome=" + isChrome + "&thisIsHTA=" + isHTA;
|
||||
if (browserVersion.isSafari) {
|
||||
// safari doesn't reload the page when the location equals to current location.
|
||||
// hence, set the location to blank so that the page will reload automatically.
|
||||
|
|
@ -246,7 +264,7 @@ objectExtend(HtmlTestFrame.prototype, SeleniumFrame.prototype);
|
|||
objectExtend(HtmlTestFrame.prototype, {
|
||||
|
||||
_onLoad: function() {
|
||||
this.currentTestCase = new HtmlTestCase(this.getDocument(), htmlTestRunner.getTestSuite().getCurrentRow());
|
||||
this.currentTestCase = new HtmlTestCase(this.getWindow(), htmlTestRunner.getTestSuite().getCurrentRow());
|
||||
},
|
||||
|
||||
getCurrentTestCase: function() {
|
||||
|
|
@ -265,19 +283,19 @@ var suiteFrame;
|
|||
var testFrame;
|
||||
|
||||
function getSuiteFrame() {
|
||||
var f = $('testSuiteFrame');
|
||||
var f = sel$('testSuiteFrame');
|
||||
if (f == null) {
|
||||
f = top;
|
||||
// proxyInjection mode does not set myiframe
|
||||
// proxyInjection mode does not set selenium_myiframe
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
function getTestFrame() {
|
||||
var f = $('testFrame');
|
||||
var f = sel$('testFrame');
|
||||
if (f == null) {
|
||||
f = top;
|
||||
// proxyInjection mode does not set myiframe
|
||||
// proxyInjection mode does not set selenium_myiframe
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
|
@ -290,9 +308,9 @@ objectExtend(HtmlTestRunnerControlPanel.prototype, {
|
|||
|
||||
this.runInterval = 0;
|
||||
|
||||
this.highlightOption = $('highlightOption');
|
||||
this.pauseButton = $('pauseTest');
|
||||
this.stepButton = $('stepTest');
|
||||
this.highlightOption = sel$('highlightOption');
|
||||
this.pauseButton = sel$('pauseTest');
|
||||
this.stepButton = sel$('stepTest');
|
||||
|
||||
this.highlightOption.onclick = fnBindAsEventListener((function() {
|
||||
this.setHighlightOption();
|
||||
|
|
@ -342,7 +360,7 @@ objectExtend(HtmlTestRunnerControlPanel.prototype, {
|
|||
},
|
||||
|
||||
reset: function() {
|
||||
// this.runInterval = this.speedController.value;
|
||||
this.runInterval = this.speedController.value;
|
||||
this._switchContinueButtonToPause();
|
||||
},
|
||||
|
||||
|
|
@ -352,7 +370,7 @@ objectExtend(HtmlTestRunnerControlPanel.prototype, {
|
|||
},
|
||||
|
||||
_switchPauseButtonToContinue: function() {
|
||||
$('stepTest').disabled = false;
|
||||
sel$('stepTest').disabled = false;
|
||||
this.pauseButton.className = "cssContinueTest";
|
||||
this.pauseButton.onclick = fnBindAsEventListener(this.continueCurrentTest, this);
|
||||
},
|
||||
|
|
@ -378,6 +396,10 @@ objectExtend(HtmlTestRunnerControlPanel.prototype, {
|
|||
return this._getQueryParameter("test");
|
||||
},
|
||||
|
||||
getTestNumber: function() {
|
||||
return this._getQueryParameter("testNumber");
|
||||
},
|
||||
|
||||
getSingleTestName: function() {
|
||||
return this._getQueryParameter("singletest");
|
||||
},
|
||||
|
|
@ -386,6 +408,10 @@ objectExtend(HtmlTestRunnerControlPanel.prototype, {
|
|||
return this._getQueryParameter("autoURL");
|
||||
},
|
||||
|
||||
getDefaultLogLevel: function() {
|
||||
return this._getQueryParameter("defaultLogLevel");
|
||||
},
|
||||
|
||||
getResultsUrl: function() {
|
||||
return this._getQueryParameter("resultsUrl");
|
||||
},
|
||||
|
|
@ -574,7 +600,9 @@ objectExtend(HtmlTestSuite.prototype, {
|
|||
initialize: function(suiteDocument) {
|
||||
this.suiteDocument = suiteDocument;
|
||||
this.suiteRows = this._collectSuiteRows();
|
||||
this.titleRow = new TitleRow(this.getTestTable().rows[0]);
|
||||
var testTable = this.getTestTable();
|
||||
if (!testTable) return;
|
||||
this.titleRow = new TitleRow(testTable.rows[0]);
|
||||
this.reset();
|
||||
},
|
||||
|
||||
|
|
@ -593,7 +621,7 @@ objectExtend(HtmlTestSuite.prototype, {
|
|||
},
|
||||
|
||||
getTestTable: function() {
|
||||
var tables = $A(this.suiteDocument.getElementsByTagName("table"));
|
||||
var tables = sel$A(this.suiteDocument.getElementsByTagName("table"));
|
||||
return tables[0];
|
||||
},
|
||||
|
||||
|
|
@ -603,15 +631,16 @@ objectExtend(HtmlTestSuite.prototype, {
|
|||
|
||||
_collectSuiteRows: function () {
|
||||
var result = [];
|
||||
var tables = $A(this.suiteDocument.getElementsByTagName("table"));
|
||||
var tables = sel$A(this.suiteDocument.getElementsByTagName("table"));
|
||||
var testTable = tables[0];
|
||||
if (!testTable) return;
|
||||
for (rowNum = 1; rowNum < testTable.rows.length; rowNum++) {
|
||||
var rowElement = testTable.rows[rowNum];
|
||||
result.push(new HtmlTestSuiteRow(rowElement, testFrame, this));
|
||||
}
|
||||
|
||||
// process the unsuited rows as well
|
||||
for (var tableNum = 1; tableNum < $A(this.suiteDocument.getElementsByTagName("table")).length; tableNum++) {
|
||||
for (var tableNum = 1; tableNum < sel$A(this.suiteDocument.getElementsByTagName("table")).length; tableNum++) {
|
||||
testTable = tables[tableNum];
|
||||
for (rowNum = 1; rowNum < testTable.rows.length; rowNum++) {
|
||||
var rowElement = testTable.rows[rowNum];
|
||||
|
|
@ -652,7 +681,7 @@ objectExtend(HtmlTestSuite.prototype, {
|
|||
|
||||
_onTestSuiteComplete: function() {
|
||||
this.markDone();
|
||||
new TestResult(this.failed, this.getTestTable()).post();
|
||||
new SeleniumTestResult(this.failed, this.getTestTable()).post();
|
||||
},
|
||||
|
||||
updateSuiteWithResultOfPreviousTest: function() {
|
||||
|
|
@ -676,8 +705,8 @@ objectExtend(HtmlTestSuite.prototype, {
|
|||
|
||||
});
|
||||
|
||||
var TestResult = classCreate();
|
||||
objectExtend(TestResult.prototype, {
|
||||
var SeleniumTestResult = classCreate();
|
||||
objectExtend(SeleniumTestResult.prototype, {
|
||||
|
||||
// Post the results to a servlet, CGI-script, etc. The URL of the
|
||||
// results-handler defaults to "/postResults", but an alternative location
|
||||
|
|
@ -713,7 +742,7 @@ objectExtend(TestResult.prototype, {
|
|||
|
||||
form.id = "resultsForm";
|
||||
form.method = "post";
|
||||
form.target = "myiframe";
|
||||
form.target = "selenium_myiframe";
|
||||
|
||||
var resultsUrl = this.controlPanel.getResultsUrl();
|
||||
if (!resultsUrl) {
|
||||
|
|
@ -766,11 +795,22 @@ objectExtend(TestResult.prototype, {
|
|||
}
|
||||
}
|
||||
|
||||
form.createHiddenField("numTestTotal", rowNum);
|
||||
form.createHiddenField("numTestTotal", rowNum-1);
|
||||
|
||||
// Add HTML for the suite itself
|
||||
form.createHiddenField("suite", this.suiteTable.parentNode.innerHTML);
|
||||
|
||||
var logMessages = [];
|
||||
while (LOG.pendingMessages.length > 0) {
|
||||
var msg = LOG.pendingMessages.shift();
|
||||
logMessages.push(msg.type);
|
||||
logMessages.push(": ");
|
||||
logMessages.push(msg.msg);
|
||||
logMessages.push('\n');
|
||||
}
|
||||
var logOutput = logMessages.join("");
|
||||
form.createHiddenField("log", logOutput);
|
||||
|
||||
if (this.controlPanel.shouldSaveResultsToFile()) {
|
||||
this._saveToFile(resultsUrl, form);
|
||||
} else {
|
||||
|
|
@ -788,22 +828,50 @@ objectExtend(TestResult.prototype, {
|
|||
for (var i = 0; i < form.elements.length; i++) {
|
||||
inputs[form.elements[i].name] = form.elements[i].value;
|
||||
}
|
||||
|
||||
var objFSO = new ActiveXObject("Scripting.FileSystemObject")
|
||||
|
||||
// DGF get CSS
|
||||
var styles = "";
|
||||
try {
|
||||
var styleSheetPath = window.location.pathname.replace(/[^\/\\]+$/, "selenium-test.css");
|
||||
if (window.location.protocol == "file:") {
|
||||
var stylesFile = objFSO.OpenTextFile(styleSheetPath, 1);
|
||||
styles = stylesFile.ReadAll();
|
||||
} else {
|
||||
var xhr = XmlHttp.create();
|
||||
xhr.open("GET", styleSheetPath, false);
|
||||
xhr.send("");
|
||||
styles = xhr.responseText;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
var scriptFile = objFSO.CreateTextFile(fileName);
|
||||
scriptFile.WriteLine("<html><body>\n<h1>Test suite results </h1>" +
|
||||
|
||||
|
||||
scriptFile.WriteLine("<html><head><title>Test suite results</title><style>");
|
||||
scriptFile.WriteLine(styles);
|
||||
scriptFile.WriteLine("</style>");
|
||||
scriptFile.WriteLine("<body>\n<h1>Test suite results</h1>" +
|
||||
"\n\n<table>\n<tr>\n<td>result:</td>\n<td>" + inputs["result"] + "</td>\n" +
|
||||
"</tr>\n<tr>\n<td>totalTime:</td>\n<td>" + inputs["totalTime"] + "</td>\n</tr>\n" +
|
||||
"<tr>\n<td>numTestTotal:</td>\n<td>" + inputs["numTestTotal"] + "</td>\n</tr>\n" +
|
||||
"<tr>\n<td>numTestPasses:</td>\n<td>" + inputs["numTestPasses"] + "</td>\n</tr>\n" +
|
||||
"<tr>\n<td>numTestFailures:</td>\n<td>" + inputs["numTestFailures"] + "</td>\n</tr>\n" +
|
||||
"<tr>\n<td>numCommandPasses:</td>\n<td>" + inputs["numCommandPasses"] + "</td>\n</tr>\n" +
|
||||
"<tr>\n<td>numCommandFailures:</td>\n<td>" + inputs["numCommandFailures"] + "</td>\n</tr>\n" +
|
||||
"<tr>\n<td>numCommandErrors:</td>\n<td>" + inputs["numCommandErrors"] + "</td>\n</tr>\n" +
|
||||
"<tr>\n<td>" + inputs["suite"] + "</td>\n<td> </td>\n</tr>");
|
||||
"<tr>\n<td>" + inputs["suite"] + "</td>\n<td> </td>\n</tr></table><table>");
|
||||
var testNum = inputs["numTestTotal"];
|
||||
for (var rowNum = 1; rowNum < testNum; rowNum++) {
|
||||
|
||||
for (var rowNum = 1; rowNum <= testNum; rowNum++) {
|
||||
scriptFile.WriteLine("<tr>\n<td>" + inputs["testTable." + rowNum] + "</td>\n<td> </td>\n</tr>");
|
||||
}
|
||||
scriptFile.WriteLine("</table></body></html>");
|
||||
scriptFile.WriteLine("</table><pre>");
|
||||
var log = inputs["log"];
|
||||
log=log.replace(/&/gm,"&").replace(/</gm,"<").replace(/>/gm,">").replace(/"/gm,""").replace(/'/gm,"'");
|
||||
scriptFile.WriteLine(log);
|
||||
scriptFile.WriteLine("</pre></body></html>");
|
||||
scriptFile.Close();
|
||||
}
|
||||
});
|
||||
|
|
@ -812,14 +880,22 @@ objectExtend(TestResult.prototype, {
|
|||
var HtmlTestCase = classCreate();
|
||||
objectExtend(HtmlTestCase.prototype, {
|
||||
|
||||
initialize: function(testDocument, htmlTestSuiteRow) {
|
||||
if (testDocument == null) {
|
||||
throw "testDocument should not be null";
|
||||
initialize: function(testWindow, htmlTestSuiteRow) {
|
||||
if (testWindow == null) {
|
||||
throw "testWindow should not be null";
|
||||
}
|
||||
if (htmlTestSuiteRow == null) {
|
||||
throw "htmlTestSuiteRow should not be null";
|
||||
}
|
||||
this.testDocument = testDocument;
|
||||
this.testWindow = testWindow;
|
||||
this.testDocument = testWindow.document;
|
||||
this.pathname = "'unknown'";
|
||||
try {
|
||||
if (this.testWindow.location) {
|
||||
this.pathname = this.testWindow.location.pathname;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
this.htmlTestSuiteRow = htmlTestSuiteRow;
|
||||
this.headerRow = new TitleRow(this.testDocument.getElementsByTagName("tr")[0]);
|
||||
this.commandRows = this._collectCommandRows();
|
||||
|
|
@ -829,11 +905,11 @@ objectExtend(HtmlTestCase.prototype, {
|
|||
|
||||
_collectCommandRows: function () {
|
||||
var commandRows = [];
|
||||
var tables = $A(this.testDocument.getElementsByTagName("table"));
|
||||
var tables = sel$A(this.testDocument.getElementsByTagName("table"));
|
||||
var self = this;
|
||||
for (var i = 0; i < tables.length; i++) {
|
||||
var table = tables[i];
|
||||
var tableRows = $A(table.rows);
|
||||
var tableRows = sel$A(table.rows);
|
||||
for (var j = 0; j < tableRows.length; j++) {
|
||||
var candidateRow = tableRows[j];
|
||||
if (self.isCommandRow(candidateRow)) {
|
||||
|
|
@ -959,11 +1035,11 @@ objectExtend(Metrics.prototype, {
|
|||
},
|
||||
|
||||
printMetrics: function() {
|
||||
setText($('commandPasses'), this.numCommandPasses);
|
||||
setText($('commandFailures'), this.numCommandFailures);
|
||||
setText($('commandErrors'), this.numCommandErrors);
|
||||
setText($('testRuns'), this.numTestPasses + this.numTestFailures);
|
||||
setText($('testFailures'), this.numTestFailures);
|
||||
setText(sel$('commandPasses'), this.numCommandPasses);
|
||||
setText(sel$('commandFailures'), this.numCommandFailures);
|
||||
setText(sel$('commandErrors'), this.numCommandErrors);
|
||||
setText(sel$('testRuns'), this.numTestPasses + this.numTestFailures);
|
||||
setText(sel$('testFailures'), this.numTestFailures);
|
||||
|
||||
this.currentTime = new Date().getTime();
|
||||
|
||||
|
|
@ -973,7 +1049,7 @@ objectExtend(Metrics.prototype, {
|
|||
var minutes = Math.floor(totalSecs / 60);
|
||||
var seconds = totalSecs % 60;
|
||||
|
||||
setText($('elapsedTime'), this._pad(minutes) + ":" + this._pad(seconds));
|
||||
setText(sel$('elapsedTime'), this._pad(minutes) + ":" + this._pad(seconds));
|
||||
},
|
||||
|
||||
// Puts a leading 0 on num if it is less than 10
|
||||
|
|
@ -1020,9 +1096,7 @@ objectExtend(HtmlRunnerTestLoop.prototype, {
|
|||
this.metrics = metrics;
|
||||
|
||||
this.htmlTestCase = htmlTestCase;
|
||||
|
||||
se = selenium;
|
||||
global.se = selenium;
|
||||
LOG.info("Starting test " + htmlTestCase.pathname);
|
||||
|
||||
this.currentRow = null;
|
||||
this.currentRowIndex = 0;
|
||||
|
|
@ -1034,17 +1108,6 @@ objectExtend(HtmlRunnerTestLoop.prototype, {
|
|||
this.expectedFailureType = null;
|
||||
|
||||
this.htmlTestCase.reset();
|
||||
|
||||
this.sejsElement = this.htmlTestCase.testDocument.getElementById('sejs');
|
||||
if (this.sejsElement) {
|
||||
var fname = 'Selenium JavaScript';
|
||||
parse_result = parse(this.sejsElement.innerHTML, fname, 0);
|
||||
|
||||
var x2 = new ExecutionContext(GLOBAL_CODE);
|
||||
ExecutionContext.current = x2;
|
||||
|
||||
execute(parse_result, x2)
|
||||
}
|
||||
},
|
||||
|
||||
_advanceToNextRow: function() {
|
||||
|
|
@ -1069,7 +1132,7 @@ objectExtend(HtmlRunnerTestLoop.prototype, {
|
|||
},
|
||||
|
||||
commandStarted : function() {
|
||||
$('pauseTest').disabled = false;
|
||||
sel$('pauseTest').disabled = false;
|
||||
this.currentRow.select();
|
||||
this.metrics.printMetrics();
|
||||
},
|
||||
|
|
@ -1141,8 +1204,8 @@ objectExtend(HtmlRunnerTestLoop.prototype, {
|
|||
},
|
||||
|
||||
testComplete : function() {
|
||||
$('pauseTest').disabled = true;
|
||||
$('stepTest').disabled = true;
|
||||
sel$('pauseTest').disabled = true;
|
||||
sel$('stepTest').disabled = true;
|
||||
if (htmlTestRunner.testFailed) {
|
||||
this.htmlTestCase.markFailed();
|
||||
this.metrics.numTestFailures += 1;
|
||||
|
|
@ -1231,6 +1294,24 @@ Selenium.prototype.doEcho = function(message) {
|
|||
currentTest.currentRow.setMessage(message);
|
||||
}
|
||||
|
||||
/*
|
||||
* doSetSpeed and getSpeed are already defined in selenium-api.js,
|
||||
* so we're defining these functions in a tricky way so that doc.js doesn't
|
||||
* try to read API doc from the function definitions here.
|
||||
*/
|
||||
Selenium.prototype._doSetSpeed = function(value) {
|
||||
var milliseconds = parseInt(value);
|
||||
if (milliseconds < 0) milliseconds = 0;
|
||||
htmlTestRunner.controlPanel.speedController.setValue(milliseconds);
|
||||
htmlTestRunner.controlPanel.setRunInterval(milliseconds);
|
||||
}
|
||||
Selenium.prototype.doSetSpeed = Selenium.prototype._doSetSpeed;
|
||||
|
||||
Selenium.prototype._getSpeed = function() {
|
||||
return htmlTestRunner.controlPanel.runInterval;
|
||||
}
|
||||
Selenium.prototype.getSpeed = Selenium.prototype._getSpeed;
|
||||
|
||||
Selenium.prototype.assertSelected = function(selectLocator, optionLocator) {
|
||||
/**
|
||||
* Verifies that the selected option of a drop-down satisfies the optionSpecifier. <i>Note that this command is deprecated; you should use assertSelectedLabel, assertSelectedValue, assertSelectedIndex, or assertSelectedId instead.</i>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
Selenium.version = "0.8.2";
|
||||
Selenium.revision = "1727";
|
||||
Selenium.version = "1.0-beta-2";
|
||||
Selenium.revision = "2330";
|
||||
|
||||
window.top.document.title += " v" + Selenium.version + " [" + Selenium.revision + "]";
|
||||
|
||||
|
|
|
|||
803
vendor/plugins/selenium-on-rails/selenium-core/scripts/ui-doc.html
vendored
Normal file
803
vendor/plugins/selenium-on-rails/selenium-core/scripts/ui-doc.html
vendored
Normal file
|
|
@ -0,0 +1,803 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Selenium UI-Element Reference</title>
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin-left: 5%;
|
||||
margin-right: 5%;
|
||||
}
|
||||
dt {
|
||||
font-weight: bolder;
|
||||
}
|
||||
dd {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
pre {
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
background-color: #eaeaea;
|
||||
}
|
||||
code {
|
||||
padding: 2px;
|
||||
background-color: #dfd;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border: solid 1px black;
|
||||
}
|
||||
th, td {
|
||||
border: solid 1px grey;
|
||||
padding: 5px;
|
||||
}
|
||||
.highlight {
|
||||
background-color: #eea;
|
||||
}
|
||||
.deprecated {
|
||||
font-style: italic;
|
||||
color: #c99;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<h1>Selenium UI-Element Reference</h1>
|
||||
|
||||
<h2>Introduction</h2>
|
||||
|
||||
<p>UI-Element is a Selenium feature that makes it possible to define a mapping between semantically meaningful names of elements on webpages, and the elements themselves. The mapping is defined using <a href="http://en.wikipedia.org/wiki/JSON">JavaScript Object Notation</a>, and may be shared both by the IDE and tests run via Selenium RC. It also offers a single point of update should the user interface of the application under test change.</p>
|
||||
|
||||
<h2>Terminology</h2>
|
||||
|
||||
<dl>
|
||||
<dt>Page</dt>
|
||||
<dd>A unique URL, and the contents available by accessing that URL. A page typically consists of several interactive page elements. A page may also be considered a DOM document object, complete with URL information.</dd>
|
||||
<dt>Page element</dt>
|
||||
<dd>An element on the actual webpage. Generally speaking, an element is anything the user might interact with, or anything that contains meaningful content. More specifically, an element is realized as a <a href="http://en.wikipedia.org/wiki/Document_Object_Model">Document Object Model (DOM)</a> node and its contents. So when we refer to a page element, we mean both of the following, at the same time:
|
||||
<ul>
|
||||
<li>something on the page</li>
|
||||
<li>its DOM representation, including its relationship with other page elements</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt>Pageset</dt>
|
||||
<dd>A set of pages that share some set of common page elements. For example, I might be able to log into my application from several different pages. If certain page elements on each of those pages appear similarly (i.e. their DOM representations are identical), those pages can be grouped into a pageset with respect to these page elements. There is no restriction on how many pagesets a given page can be a member of. Similarly, a UI element belong to multiple pagesets. A pageset is commonly represented by a <a href="http://en.wikipedia.org/wiki/Regular_expression">regular expression</a> which matches the URL's that uniquely identify pages; however, there are cases when the page content must be considered to determine pageset membership. A pageset also has a name.</dd>
|
||||
<dt>UI element</dt>
|
||||
<dd>A mapping between a meaningful name for a page element, and the means to locate that page element's DOM node. The page element is located via a locator. UI elements belong to pagesets.</dd>
|
||||
<dt>UI argument</dt>
|
||||
<dd>An optional piece of logic that determines how the locator is generated by a UI element. Typically used when similar page elements appear multiple times on the same page, and you want to address them all with a single UI element. For example, if a page presents 20 clickable search results, the index of the search result might be a UI argument.</dd>
|
||||
<dt>UI map</dt>
|
||||
<dd>A collection of pagesets, which in turn contain UI elements. The UI map is the medium for translating between UI specifier strings, page elements, and UI elements.</dd>
|
||||
<dt>UI specifier string</dt>
|
||||
<dd>A bit of text containing a pageset name, a UI element name, and optionally arguments that modify the way a locator is constructed by the UI element. UI specifier strings are intended to be the human-readable identifier for page elements.</dd>
|
||||
<dt>Rollup rule</dt>
|
||||
<dd>Logic that describes how one or more Selenium commands can be grouped into a single command, and how that single command may be expanded into its component Selenium commands. The single command is referred to simply as a "rollup".</dd>
|
||||
<dt>Command matcher</dt>
|
||||
<dd>Typically folded into a rollup rule, it matches one or more Selenium commands and optionally sets values for rollup arguments based on the matched commands. A rollup rule usually has one or more command matchers.</dd>
|
||||
<dt>Rollup argument</dt>
|
||||
<dd>An optional piece of logic that modifies the command expansion of a rollup.</dd>
|
||||
</dl>
|
||||
|
||||
<h2>The Basics</h2>
|
||||
|
||||
<h3>Getting Motivated</h3>
|
||||
|
||||
<p>Question: Why use UI-Element? Answer: So your testcases can look like this (boilerplate code omitted):</p>
|
||||
|
||||
<pre>
|
||||
<tr>
|
||||
<td>open</td>
|
||||
<td>/</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>clickAndWait</td>
|
||||
<td><span class="highlight">ui=allPages::section(section=topics)</span></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>clickAndWait</td>
|
||||
<td><span class="highlight">ui=topicListingPages::topic(topic=Process)</span></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>clickAndWait</td>
|
||||
<td><span class="highlight">ui=subtopicListingPages::subtopic(subtopic=Creativity)</span></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>click</td>
|
||||
<td><span class="highlight">ui=subtopicArticleListingPages::article(index=2)</span></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</pre>
|
||||
|
||||
<h3>Including the Right Files</h3>
|
||||
|
||||
<p>UI-Element is now fully integrated with Selenium. The only additional file that needs to be specified is your map definitions file. In the IDE, add it to the comma-delimited <em>Selenium Core extensions</em> field of the IDE options. A sample definition file created for the website <a href="http://alistapart.com">alistapart.com</a> is included in the distribution and is available here:</p>
|
||||
|
||||
<pre><a href="chrome://selenium-ide/content/selenium/scripts/ui-map-sample.js">chrome://selenium-ide/content/selenium/scripts/ui-map-sample.js</a></pre>
|
||||
|
||||
<p>You might want to experiment with the sample map to get a feel for UI-Element. For the Selenium RC, you have two options. The map file may be included in the <code>user-extensions.js</code> file specified at startup with the <code>-userExtensions</code> switch. Or, you may load it dynamically with the variant of the <code>setUserExtensionJs</code> command in your driver language, before the browser is started.</p>
|
||||
|
||||
<h3>Map Definitions File Syntax</h3>
|
||||
|
||||
<p>This is the general format of a map file:</p>
|
||||
|
||||
<pre>
|
||||
var map = new UIMap();
|
||||
|
||||
map.addPageset({
|
||||
name: 'aPageset'
|
||||
, ...
|
||||
});
|
||||
map.addElement('aPageset', { ... });
|
||||
map.addElement('aPageset', { ... });
|
||||
...
|
||||
|
||||
map.addPageset({
|
||||
name: 'anotherPageset'
|
||||
, ...
|
||||
});
|
||||
...
|
||||
</pre>
|
||||
|
||||
<p>The map object is initialized by creating a new <code>UIMap</code> object. Next, a pageset is defined. Then one or more UI elements are defined for that pageset. More pagesets are defined, each with corresponding UI elements. That's it!</p>
|
||||
|
||||
<h3>Pageset Shorthand</h3>
|
||||
|
||||
<p>The method signature of <code>addPageset()</code> is <em>(pagesetShorthand)</em>. <em>pagesetShorthand</em> is a JSON description of the pageset. Here's a minimal example:</p>
|
||||
|
||||
<pre>
|
||||
map.addPageset({
|
||||
name: 'allPages'
|
||||
, description: 'contains elements common to all pages'
|
||||
, pathRegexp: '.*'
|
||||
});
|
||||
</pre>
|
||||
|
||||
<p>Here's a table containing information about the attributes of the Pageset object. The conditionally required or unrequired items are for IDE recording support only.</p>
|
||||
|
||||
<table>
|
||||
<tr><th>Name</th>
|
||||
<th>Required?</th>
|
||||
<th>Description</th>
|
||||
<th>Example</th>
|
||||
</tr>
|
||||
<tr><td>name</td>
|
||||
<td>Yes</td>
|
||||
<td>(String) the name of the pageset. This should be unique within the map.</td>
|
||||
<td><pre>name: 'shopPages'</pre></td>
|
||||
</tr>
|
||||
<tr><td>description</td>
|
||||
<td>Yes</td>
|
||||
<td>(String) a description of the pageset. Ideally, this will give the reader an idea of what types of UI elements the pageset will have.</td>
|
||||
<td><pre>description: 'all pages displaying product'</pre></td>
|
||||
</tr>
|
||||
<tr><td>pathPrefix</td>
|
||||
<td>No</td>
|
||||
<td>(String) the path of the URL of all included pages in this pageset will contain this prefix. For example, if all pages are of the form http://www.example.com/<span class="highlight">gallery/</span>light-show/, the page prefix might be <code>gallery/</code> .</td>
|
||||
<td><pre>pathPrefix: 'gallery/'</pre></td>
|
||||
</tr>
|
||||
<tr><td>paths<br />pathRegexp</td>
|
||||
<td>Conditional</td>
|
||||
<td>(Array | String) either a list of path strings, or a string that represents a regular expression. One or the other should be defined, but not both. If an array, it enumerates pages that are included in the pageset. If a regular expression, any pages whose URL paths match the expression are considered part of the pageset. In either case, the part of the URL being matched (called the <em>path</em>) is the part following the domain, less any training slash, and not including the CGI parameters. For example:
|
||||
<ul>
|
||||
<li>http://www.example.com/<span class="highlight">articles/index.php</span></li>
|
||||
<li>http://www.example.com/<span class="highlight">articles/2579</span>?lang=en_US</li>
|
||||
<li>http://www.example.com/<span class="highlight">articles/selenium</span>/</li>
|
||||
</ul>
|
||||
The entire path must match (however, <code>pathPrefix</code> is taken into account if specified). If specified as a regular expression, the two regular expression characters <code>^</code> and <code>$</code> marking the start and end of the matched string are included implicitly, and should not be specified in this string. Please notice too that backslashes must be backslash-escaped in javascript strings.</td>
|
||||
<td><pre>paths: [
|
||||
'gotoHome.do'
|
||||
, 'gotoAbout.do'
|
||||
, 'gotoFaq.do'
|
||||
]</pre>
|
||||
<pre>pathRegexp: 'goto(Home|About|Faq)\\.do'</pre>
|
||||
</tr>
|
||||
<tr><td>paramRegexps</td>
|
||||
<td>No</td>
|
||||
<td>(Object) a mapping from URL parameter names to regular expression strings which must match their values. If specified, the set of pages potentially included in this pageset will be further filtered by URL parameter values. There is no filtering by parameter value by default.</td>
|
||||
<td><pre>paramRegexps: {
|
||||
dept: '^[abcd]$'
|
||||
, team: 'marketing'
|
||||
}</pre></td>
|
||||
</tr>
|
||||
<tr><td>pageContent</td>
|
||||
<td>Conditional</td>
|
||||
<td><p>(Function) a function that tests whether a page, represented by its document object, is contained in the pageset, and returns true if and only if this is the case. If specified, the set of pages potentially included in this pageset will be further filtered by content, after URL and URL parameter filtering.</p>
|
||||
<p>Since the URL is available from the document object (<code>document.location.href</code>), you may encode the logic used for the <code>paths</code> and <code>pathRegexp</code> attributes all into the definition of <code>pageContent</code>. Thus, you may choose to omit the former if and only if using <code>pageContent</code>. Of course, you may continue to use them for clarity.</td>
|
||||
<td><pre>pageContent: function(doc) {
|
||||
var id = 'address-tab';
|
||||
return doc.getElementById(id) != null;
|
||||
}</pre></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3><a name="ui-element-shorthand">UI-Element Shorthand</a></h3>
|
||||
|
||||
<p>The method signature of <code>addElement()</code> is <em>(pagesetName, uiElementShorthand)</em>. <em>pagesetName</em> is the name of the pageset the UI element is being added to. <em>uiElementShorthand</em> is a complete JSON description of the UI element object in shorthand notation.</p>
|
||||
|
||||
<p>In its simplest form, a UI element object looks like this:</p>
|
||||
|
||||
<pre>
|
||||
map.addElement('allPages', {
|
||||
name: 'about_link'
|
||||
, description: 'link to the about page'
|
||||
, locator: "//a[contains(@href, 'about.php')]"
|
||||
});
|
||||
</pre>
|
||||
|
||||
<p>Here's a table containing information about the attributes of the UI element object. The asterisk (<code>*</code>) means any string:</p>
|
||||
|
||||
<table>
|
||||
<tr><th>Name</th>
|
||||
<th>Required?</th>
|
||||
<th>Description</th>
|
||||
<th>Example</th>
|
||||
</tr>
|
||||
<tr><td>name</td>
|
||||
<td>Yes</td>
|
||||
<td>(String) the name of the UI element</td>
|
||||
<td><pre>name: 'article'</pre></td>
|
||||
</tr>
|
||||
<tr><td>description</td>
|
||||
<td>Yes</td>
|
||||
<td>(String) a description of the UI element. This is the main documentation for this UI element, so the more detailed, the better.</td>
|
||||
<td><pre>description: 'front or issue page link to article'</pre></td>
|
||||
</tr>
|
||||
<tr><td>args</td>
|
||||
<td>No</td>
|
||||
<td>(Array) a list of arguments that modify the <code>getLocator()</code> method. If unspecified, it will be treated as an empty list.</td>
|
||||
<td><pre>[
|
||||
{ name: 'index'
|
||||
, description: 'the index of the author, by article'
|
||||
, defaultValues: range(1, 5) }
|
||||
]</pre><em>See section below elaborating on attributes of argument objects.</em></td>
|
||||
</tr>
|
||||
<tr><td>locator<br />getLocator()<br /><span class="deprecated">xpath</span<br /><span class="deprecated">getXPath()</span></td>
|
||||
<td>Yes</td>
|
||||
<td><p>(String | Function) either a fixed locator string, or a function that returns a locator string given a set of arguments. One or the other should be defined, but not both. Under the sheets, the <code>locator</code> attribute eventually gets transcripted as a <code>getLocator()</code> function.</p><p><span class="deprecated">As of ui0.7, <code>xpath</code> and <code>getXPath()</code> have been deprecated. They are still supported for backward compatibility.</span></p></td>
|
||||
<td><pre>locator: 'submit'</pre>
|
||||
<pre>getLocator: function(args) {
|
||||
return 'css=div.item:nth-child(' + args.index + ')'
|
||||
+ ' > h5 > a';
|
||||
}</pre>
|
||||
<pre>getLocator: function(args) {
|
||||
var label = args.label;
|
||||
var id = this._idMap[label];
|
||||
return '//input[@id=' + id.quoteForXPath() + ']';
|
||||
}</pre></td>
|
||||
<tr><td>genericLocator<br />getGenericLocator</td>
|
||||
<td>No</td>
|
||||
<td><p>(String | Function) either a fixed locator string, or a function that returns a locator string. If a function, it should take no arguments.</p><p>You may experience some slowdown when recording on pages where individual UI elements have many default locators (due to many permutations of default values over multiple arguments). This is because each default locator is potentially evaluated and matched against the interacted page element. This becomes especially problematic if several UI elements have this characteristic.</p><p>By specifying a generic locator, you give the matching engine a chance to skip over UI elements that definitely don't match. The default locators for skipped elements will not be evaluated unless the generic locator matches the interacted page element..</p></td>
|
||||
<td><pre>genericLocator: "//table[@class='ctrl']"
|
||||
+ "/descendant::input"</pre>
|
||||
<pre>getGenericLocator: function() {
|
||||
return this._xpathPrefix + '/descendant::a';
|
||||
}</pre>
|
||||
</tr>
|
||||
<tr><td>getOffsetLocator</td>
|
||||
<td>No</td>
|
||||
<td><p>(Function) a function that returns an offset locator. The locator is offset from the element identified by a UI specifier string. The function should take this element, and the interacted page element, as arguments, and have the method signature <code>getOffsetLocator(locatedElement, pageElement)</code>. If an offset locator can't be found, a value that evaluates to <code>false</code> must be returned.</p><p>A convenient default function <code>UIElement.defaultOffsetLocatorStrategy</code> is provided so you don't have to define your own. It uses several typical strategies also employed by the IDE recording when recording normally. See the Advanced Topics section below for more information on offset locators.</p></td>
|
||||
<td><pre>getOffsetLocator: <span class="highlight">UIElement.defaultOffsetLocatorStrategy</span></pre>
|
||||
<pre>getOffsetLocator:
|
||||
function(locatedElement, pageElement) {
|
||||
if (pageElement.parentNode == locatedElement) {
|
||||
return '/child::' + pageElement.nodeName;
|
||||
}
|
||||
return null;
|
||||
}</pre></td>
|
||||
<tr><td>testcase*</td>
|
||||
<td>No</td>
|
||||
<td>(Object) a testcase for testing the implementation of the <code>getLocator()</code> method. As many testcases as desired may be defined for each UI element. They must all start with the string "testcase".</td>
|
||||
<td><pre>testcase1: {
|
||||
xhtml: '<div class="item"><h5>'
|
||||
+ '<a expected-result="1" /></h5></div>'
|
||||
}</pre><em>See section below elaborating on testcases.</em></td>
|
||||
</tr>
|
||||
<tr><td>_*</td>
|
||||
<td>No</td>
|
||||
<td>(Any data type) a "local variable" declared for the UI element. This variable will be available both within the <code>getLocator()</code> method of the UI element, and any <code>getDefaultValues()</code> methods of the arguments via the <code>this</code> keyword. They must all start with an underscore "_".</td>
|
||||
<td><pre>_labelMap: {
|
||||
'Name': 'user'
|
||||
, 'Email': 'em'
|
||||
, 'Phone': 'tel'
|
||||
}</pre></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>UI-Argument Shorthand</h3>
|
||||
|
||||
<p>UI arguments are defined as part of UI elements, and help determine how an XPath is generated. A list of arguments may be defined within the UI element JSON shorthand. Here's an example of how that might look:</p>
|
||||
|
||||
<pre>
|
||||
map.addElement('searchPages', {
|
||||
name: 'result'
|
||||
, description: 'link to a result page'
|
||||
, args: [
|
||||
{
|
||||
name: 'index'
|
||||
, description: 'the index of the search result'
|
||||
, defaultValues: range(1, 21)
|
||||
}
|
||||
, {
|
||||
name: 'type'
|
||||
, description: 'the type of result page'
|
||||
, defaultValues: [ 'summary', 'detail' ]
|
||||
}
|
||||
]
|
||||
, getLocator: function(args) {
|
||||
var index = args['index'];
|
||||
var type = args['type'];
|
||||
return "//div[@class='result'][" + index + "]"
|
||||
+ "/descendant::a[@class='" + type + "']";
|
||||
}
|
||||
});
|
||||
</pre>
|
||||
|
||||
<p>In the above example, two arguments are defined, <code>index</code> and <code>type</code>. Metadata is provided to describe them, and default values are also specified. FInally, the <code>getLocator()</code> method is defined. The behavior of the method depends on the values of the arguments that are passed in.</p>
|
||||
|
||||
<p>Default values come into play when recording tests using the Selenium IDE. When you interact with a page element in recording mode, the IDE uses all the locator strategies at its disposal to deduce an appropriate locator string for that element. UI-Element introduces a new <code>ui</code> locator strategy. When applying this strategy using a particular UI element, Selenium generates a list of XPaths to try by permuting the arguments of that UI element over all default values. Here, the default values <em>{ 1, 2, 3, 4 .. 20 }</em> are given for the <code>index</code> argument using the special <code>range()</code> function, and the values <em>{ summary, detail }</em> are given for the <code>type</code> argument in standard javascript array notation. If you don't intend to use the IDE, go ahead and set the default values to the empty array <code>[]</code>.</p>
|
||||
|
||||
<p>Here's a table containing information about the attributes of the UI argument object.</p>
|
||||
|
||||
<table>
|
||||
<tr><th>Name</th>
|
||||
<th>Required?</th>
|
||||
<th>Description</th>
|
||||
<th>Example</th>
|
||||
</tr>
|
||||
<tr><td>name</td>
|
||||
<td>Yes</td>
|
||||
<td>(String) the name of the argument. This will be the name of the property of the object passed into the parent UI element's <code>getLocator()</code> method containing the argument value.</td>
|
||||
<td><pre>name: 'index'</pre></td>
|
||||
</tr>
|
||||
<tr><td>description</td>
|
||||
<td>Yes</td>
|
||||
<td>(String) a description for the argument.</td>
|
||||
<td><pre>description: 'the index of the article'</pre></td>
|
||||
</tr>
|
||||
<tr><td>defaultValues<br/>getDefaultValues()</td>
|
||||
<td>Yes</td>
|
||||
<td><p>(Array | Function) either an array of string or numerical values, or a function that returns an array of string or numerical values. One or the other should be defined, but not both. Under the sheets, the <code>defaultValues</code> attribute eventually gets transcripted as a <code>getDefaultValues()</code> function.</p><p>The method signature of the function is <code>getDefaultValues(inDocument)</code>. <code>inDocument</code> is the current document object of the page at time of recording. In cases where the default values are known a priori, <code>inDocument</code> need not be used. If the default values of all arguments of a UI element are known a priori, the list of default locators for the element may be precalculated, resulting in better performance. If <code>inDocument</code> is used, in cases where the current document is inspected for valid values, the element's default locators are calculated once for every recordable event.</p></td>
|
||||
<td><pre>defaultValues: [ 'alpha', 'beta', 'unlimited' ]</pre>
|
||||
<pre>getDefaultValues: function() {
|
||||
return keys(this._idMap);
|
||||
}</pre>
|
||||
<pre>getDefaultValues: function(inDocument) {
|
||||
var defaultValues = [];
|
||||
var links = inDocument
|
||||
.getElementsByTagName('a');
|
||||
for (var i = 0; i < links.length; ++i) {
|
||||
var link = links[i];
|
||||
if (link.className == 'category') {
|
||||
defaultValues.push(link.innerHTML);
|
||||
}
|
||||
}
|
||||
return defaultValues;
|
||||
}</pre></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>About <code>this</code></h3>
|
||||
|
||||
<p>You may have noticed usage of the <code>this</code> keyword in the examples above, specifically in the <code>getLocator()</code> and <code>getDefaultValues()</code> methods. Well, what exactly is <code>this</code>?</p>
|
||||
|
||||
<p>The answer is: it depends. The object referred to by <code>this</code> changes depending on the context in which it is being evaluated. At the time of object creation using <code>addPageset()</code> or <code>addElement()</code>, it refers to the <code>window</code> object of the Selenium IDE, which isn't useful at all. However, subsequently any time <code>getLocator()</code> is called, its <code>this</code> references the UI element object it's attached too. Thus, using <code>this</code>, any "local variables" defined for the UI element may be accessed. Similarly, when <code>getDefaultValues()</code> is called, its <code>this</code> references the UI argument object it's attached too. But ... what "local variables" are accessible by the from <code>getDefaultValues()</code>?</p>
|
||||
|
||||
<p>There's a little magic here. If you defined your local variables as prescribed in the above <a href="#ui-element-shorthand">UI-Element Shorthand</a> section, starting with an underscore, those variable are automatically made available to the UI argument via its <code>this</code> keyword. Note that this isn't standard javascript behavior. It's implemented this way to clear out clutter in the method definitions and to avoid the use of global variables for lists and maps used within the methods. It is sometimes useful, for example, to define an object that maps human-friendly argument values to DOM element <code>id</code>'s. In such a case, <code>getDefaultValues()</code> can be made to simply return the <code>keys()</code> (or property names) of the map, while the <code>getLocator()</code> method uses the map to retrieve the associated <code>id</code> to involve in the locator.</p>
|
||||
|
||||
<p>Also note that <code>this</code> only behaves this way in the two mentioned methods, <code>getLocator()</code> and <code>getDefaultValues()</code>; in other words you can't reference the UI element's local variables using <code>this</code> outside of methods.</p>
|
||||
|
||||
<p>If you're interested, here's some <a href="http://www.digital-web.com/articles/scope_in_javascript/">additional reading on javascript scope</a>.</p>
|
||||
|
||||
<h2>Advanced Topics</h2>
|
||||
|
||||
<h3>Testcases</h3>
|
||||
|
||||
<p>You can write testcases for your UI element implementations that are run every time the Selenium IDE is started. Any testcases that fail are reported on. The dual purpose of writing testcases is to both validate the <code>getLocator()</code> method against a representation of the real page under test, and to give a visual example of what the DOM context of a page element is expected to be.</p>
|
||||
|
||||
<p>A testcase is an object with a required <code>xhtml</code> property (String), and a required <code>args</code> property (Object). An example is due:</p>
|
||||
|
||||
<pre>
|
||||
testcase1: {
|
||||
args: { line: 2, column: 3 }
|
||||
, xhtml: '<table id="scorecard">'
|
||||
+ '<tr class="line" />'
|
||||
+ '<tr class="line"><td /><td /><td expected-result="1" /></tr>'
|
||||
+ '</table>'
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>The <code>args</code> property specifies the object to be passed into the UI element's <code>getLocator()</code> method to generate the test locator, which is then applied to the <code>xhtml</code>. If evaluating the locator on the XHTML document returns a DOM node with the <code>expected-result</code> attribute, the testcase is considered to have passed.</p>
|
||||
|
||||
<p>The <code>xhtml</code> property must represent a complete XML document, sans <code><html></code> tags, which are automatically added. The reason this is necessary is that the text is being converted into an XPath evaluable DOM tree via Mozilla's native XML parser. Unfortunately, there is no way to generate a simple HTML document, only XML documents. This means that the content of the <code>xhtml</code> must be well-formed. <span class="highlight">Tags should also be specified in lowercase.</span></p>
|
||||
|
||||
<h3>Fuzzy Matching</h3>
|
||||
|
||||
<p>Here's a real-world example of where fuzzy matching is important:</p>
|
||||
|
||||
<pre>
|
||||
<table>
|
||||
<tr onclick="showDetails(0)">
|
||||
<td>Brahms</td>
|
||||
<td>Viola Quintet</td>
|
||||
</tr>
|
||||
<tr onclick="showDetails(1)">
|
||||
<td>Saegusa</td>
|
||||
<td>Cello 88</td>
|
||||
</tr>
|
||||
</table>
|
||||
</pre>
|
||||
|
||||
<p>Imagine I'm recording in the IDE. Let's say I click on "Cello 88". The IDE would create locator for this action like <code>//table/tr[2]/td[2]</code>. Does that mean that my <code>getLocator()</code> method should return the same XPath?</p>
|
||||
|
||||
<p>Clearly not. Clicking on either of the table cells for the second row has the same result. I would like my UI element generated XPath to be <code>//table/tr[2]</code> . However, when recording, the page element that was identified as being acted upon was the table cell, which doesn't match my UI element XPath, so the <code>ui</code> locator strategy will fail to auto-populate. What to do?</p>
|
||||
|
||||
<p>Fuzzy matching to the rescue! Fuzzy matching is realized as a fuzzy matcher function that returns true if a target DOM element is considered to be equivalent to a reference DOM element. The reference DOM element would be the element specified by the UI element's generated XPath. Currently, the fuzzy matcher considers it a match if:
|
||||
|
||||
<ul>
|
||||
<li>the elements are the same element,</li>
|
||||
<li>the reference element is an anchor (<code><a></code>) element, and the target element is a descendant of it; or</li>
|
||||
<li>the reference element has an <code>onclick</code> attribute, and the target element is a descendant of it.</li>
|
||||
</ul>
|
||||
|
||||
<p>This logic may or may not be sufficient for you. The good news is, it's very easy to modify. Look for the definition of <code>BrowserBot.prototype.locateElementByUIElement.is_fuzzy_match</code> in <code>ui-element.js</code> .</p>
|
||||
|
||||
<h3>Offset Locators</h3>
|
||||
|
||||
<p>Offset locators are locators that are appended to UI specifier strings to form composite locators. They can be automatically deduced by the IDE recorder for UI elements that have specified a <code>getOffsetLocator</code> function. This feature may be useful if your pages contain too many elements to write UI elements for. In this case, offset locators allow you to define UI elements that "anchor" other elements. Given the following markup:</p>
|
||||
|
||||
<pre><form name="contact_info">
|
||||
<input type="text" name="foo" />
|
||||
<textarea name="bar"></textarea>
|
||||
<input type="submit" value="baz" />
|
||||
</form></pre>
|
||||
|
||||
<p>Assume that a UI element has been defined for the form element. Then the following locators containing offset locators and "anchored" off this element would be recorded using the default offset locator function (<code>UIElement.defaultOffsetLocatorStrategy</code>):</p>
|
||||
|
||||
<pre>ui=contactPages::contact_form()<span class="highlight">->//input[@name='foo']</span>
|
||||
ui=contactPages::contact_form()<span class="highlight">->//textarea[@name='bar']</span>
|
||||
ui=contactPages::contact_form()<span class="highlight">->//input[@value='baz']</span></pre>
|
||||
|
||||
<p>The character sequence <code>-></code> serves to delimit the offset locator from the main locator. For this reason, the sequence should not appear in the main locator, or else ambiguity will result.</p>
|
||||
|
||||
<p>When recording with the IDE, no preference is given to matching plain vanilla UI specifier strings over ones that have offset locators. In other words, if a page element could be specified both by a UI specifier string for one UI element, and by one augmented by an offset locator for a different UI element, there is no guarantee that one or the other locator will be recorded.</p>
|
||||
|
||||
<p>Currently, <span class="highlight">only XPath is supported as an offset locator type</span>, as it is the only locator for which a context node can be specified at evaluation time. Other locator strategies may be supported in the future.</p>
|
||||
|
||||
<h3>Rollup Rules</h3>
|
||||
|
||||
<p>Question: Why use rollup rules? Answer: Remember the testcase from the "Getting Motivated" section above? With rollups, that testcase can be condensed into this:</p>
|
||||
|
||||
<pre>
|
||||
<tr>
|
||||
<td>open</td>
|
||||
<td>/</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>rollup</td>
|
||||
<td><span class="highlight">navigate_to_subtopic_article</span></td>
|
||||
<td><span class="highlight">index=2, subtopic=Creativity</span></td>
|
||||
</tr>
|
||||
</pre>
|
||||
|
||||
<p>It's inevitable that certain sequences of Selenium commands will appear in testcases over and over again. When this happens, you might wish to group several fine-grained commands into a single coarser, more semantically meaningful action. In doing so, you would abstract out the execution details for the action, such that if they were to change at some point, you would have a single point of update. In UI-Element, such actions are given their own command, called <code>rollup</code>. In a sense, rollups are a natural extension of the <code>ui</code> locator.</p>
|
||||
|
||||
<p>UI-Element is designed with the belief that the IDE can be a useful tool for writing testcases, and need not be shunned for lack of functionality. A corollary belief is that you should be able to drive your RC test in the language of your choice. The execution of the <code>rollup</code> command by the Selenium testrunner produces the component commands, which are executed in a new context, like a function call. The logic of the rollup "expansion" is written in javascript. Corresponding logic for inferring a rollup from a list of commands is also written in javascript. Thus, the logic can be incorporated into any of the family of Selenium products as a user extension. Most notably, the IDE is made viable as a testcase creation tool that understands both how rollups expand to commands, and also how rollup rules can be "applied" to commands to reduce them to rollups.</p>
|
||||
|
||||
<p>Rollup rule definitions appear in this general format:</p>
|
||||
|
||||
<pre>
|
||||
var manager = new RollupManager();
|
||||
|
||||
manager.addRollupRule({ ... });
|
||||
manager.addRollupRule({ ... });
|
||||
...
|
||||
</pre>
|
||||
|
||||
<p>In a relatively simple form, a rollup rule looks like this:</p>
|
||||
|
||||
<pre>
|
||||
manager.addRollupRule({
|
||||
name: 'do_search'
|
||||
, description: 'performs a search'
|
||||
, args: [
|
||||
name: 'term'
|
||||
, description: 'the search term'
|
||||
]
|
||||
, commandMatchers: [
|
||||
{
|
||||
command: 'type'
|
||||
, target: 'ui=searchPages::search_box\\(.+'
|
||||
, updateArgs: function(command, args) {
|
||||
var uiSpecifier = new UISpecifier(command.target);
|
||||
args.term = uiSpecifier.args.term;
|
||||
return args;
|
||||
}
|
||||
}
|
||||
, {
|
||||
command: 'click.+'
|
||||
, target: 'ui=searchPages::search_go\\(.+'
|
||||
}
|
||||
]
|
||||
, getExpandedCommands: function(args) {
|
||||
var commands = [];
|
||||
var uiSpecifier = new UISpecifier(
|
||||
'searchPages'
|
||||
, 'search_box'
|
||||
, { term: args.term });
|
||||
commands.push({
|
||||
command: 'type'
|
||||
, target: 'ui=' + uiSpecifier.toString()
|
||||
});
|
||||
commands.push({
|
||||
command: 'clickAndWait'
|
||||
, target: 'ui=searchPages::search_go()'
|
||||
});
|
||||
return commands;
|
||||
}
|
||||
});
|
||||
</pre>
|
||||
|
||||
<p>In the above example, a rollup rule is defined for performing a search. The rule can be "applied" to two consecutive commands that match the <code>commandMatchers</code>. The rollup takes one argument, <code>term</code>, and expands back to the original two commands. One thing to note is that the second command matcher will match all commands starting with <code>click</code>. The rollup will expand that command to a <code>clickAndWait</code>.</p>
|
||||
|
||||
<p>Here's a table containing information about the attributes of the rollup rule object.</p>
|
||||
|
||||
<table>
|
||||
<tr><th>Name</th>
|
||||
<th>Required?</th>
|
||||
<th>Description</th>
|
||||
<th>Example</th>
|
||||
</tr>
|
||||
<tr><td>name</td>
|
||||
<td>Yes</td>
|
||||
<td>(String) the name of the rollup rule. This will be the target of the resulting <code>rollup</code> command.</td>
|
||||
<td><pre>name: 'do_login'</pre></td>
|
||||
</tr>
|
||||
<tr><td>description</td>
|
||||
<td>Yes</td>
|
||||
<td>(String) a description for the rollup rule.</td>
|
||||
<td><pre>description: 'logs into the application'</pre></td>
|
||||
</tr>
|
||||
<tr><td>alternateCommand</td>
|
||||
<td>No</td>
|
||||
<td>(String) specifies an alternate usage of rollup rules to replace commands. This string is used to replace the command name of the first matched command.</td>
|
||||
<td><pre>alternateCommand: 'clickAndWait'</pre></td>
|
||||
</tr>
|
||||
<tr><td>pre</td>
|
||||
<td>No</td>
|
||||
<td>(String) a detailed summary of the preconditions that must be satisfied for the rollup to execute successfully. This metadata is easily viewable in the IDE when the rollup command is selected.</td>
|
||||
<td><pre>pre: 'the page contains login widgets'</pre></td>
|
||||
</tr>
|
||||
<tr><td>post</td>
|
||||
<td>No</td>
|
||||
<td>(String) a detailed summary of the postconditions that will exist after the rollup has been executed.</td>
|
||||
<td><pre>post: 'the user is logged in, or is \
|
||||
directed to a login error page'</pre></td>
|
||||
</tr>
|
||||
<tr><td>args</td>
|
||||
<td>Conditional</td>
|
||||
<td><p>(Array) a list of arguments that are used to modify the rollup expansion. These are similar to UI arguments, with the exception that <code>exampleValues</code> are provided, instead of <code>defaultValues</code>. Here, example values are used for reference purposes only; they are displayed in the rollup pane in the IDE.</p><p>This attribute may be omitted if no <code>updateArgs()</code> functions are defined for any command matchers.</td>
|
||||
<td><pre>args: {
|
||||
name: 'user'
|
||||
, description: 'the username to login as'
|
||||
, exampleValues: [
|
||||
'John Doe'
|
||||
, 'Jane Doe'
|
||||
]
|
||||
}</pre></td>
|
||||
</tr>
|
||||
<tr><td>commandMatchers<br />getRollup()</td>
|
||||
<td>Yes</td>
|
||||
<td><p>(Array | Function) a list of command matcher definitions, or a function that, given a list of commands, returns either 1) a rollup command if the rule is considered to match the commands (starting at the first command); or 2) false. If a function, it should have the method signature <code>getRollup(commands)</code>, and the returned rollup command (if any) must have the <code>replacementIndexes</code> attribute, which is a list of array indexes indicating which commands in the <code>commands</code> parameter are to be replaced.</p>
|
||||
<p>If you don't intend on using the IDE with your rollups, go ahead and set this to the empty array <code>[]</code>.</p></td>
|
||||
<td><pre>commandMatchers: [
|
||||
{
|
||||
command: 'type'
|
||||
, target: 'ui=loginPages::user\\(.+'
|
||||
, value: '.+'
|
||||
, minMatches: 1
|
||||
, maxMatches: 1
|
||||
, updateArgs:
|
||||
function(command, args) {
|
||||
args.user = command.value;
|
||||
return args;
|
||||
}
|
||||
}
|
||||
]</pre>
|
||||
<pre>// this is a simplistic example, roughy
|
||||
// equivalent to the commandMatchers
|
||||
// example above. The <span class="highlight">to_kwargs()</span> function
|
||||
// is used to turn an arguments object into
|
||||
// a keyword-arguments string.
|
||||
getRollup: function(commands) {
|
||||
var command = commands[0];
|
||||
var re = /^ui=loginPages::user\(.+/;
|
||||
if (command.command == 'type' &&
|
||||
re.test(command.target) &&
|
||||
command.value) {
|
||||
var args = { user: command.value };
|
||||
return {
|
||||
command: 'rollup'
|
||||
, target: this.name
|
||||
, value: to_kwargs(args)
|
||||
, replacementIndexes: [ 0 ]
|
||||
};
|
||||
}
|
||||
return false;
|
||||
}</pre><em>See section below elaborating on command matcher objects.</em></td>
|
||||
</tr>
|
||||
<tr><td>expandedCommands<br />getExpandedCommands()</td>
|
||||
<td>Yes</td>
|
||||
<td><p>(Array | Function) a list of commands the rollup command expands into, or a function that, given an argument object mapping argument names to values, returns a list of expanded commands. If a function, it should have the method signature <code>getExpandedCommands(args)</code>.</p><p>Each command in the list of expanded commands should contain a <code>command</code> attribute, which is the name of the command, and optionally <code>target</code> and <code>value</code> attributes, depending on the type of command.</p><p>It is expected that providing a fixed list of expanded commands will be of limited use, as rollups will typically contain commands that have arguments, requiring more sophisticated logic to expand.</td>
|
||||
<td><pre>expandedCommands: [
|
||||
{
|
||||
command: 'check'
|
||||
, target: 'ui=termsPages::agree\\(.+'
|
||||
}
|
||||
, {
|
||||
command: 'clickAndWait'
|
||||
, target: 'ui=termsPages::submit\\(.+'
|
||||
}
|
||||
]</pre>
|
||||
<pre>getExpandedCommands: function(args) {
|
||||
var commands = [];
|
||||
commands.push({
|
||||
command: 'type'
|
||||
, target: 'ui=loginPages::user()'
|
||||
, value: args.user
|
||||
});
|
||||
commands.push({
|
||||
command: 'type'
|
||||
, target: 'ui=loginPages::pass()'
|
||||
, value: args.pass
|
||||
});
|
||||
commands.push({
|
||||
command: 'clickAndWait'
|
||||
, target: 'ui=loginPages::submit()'
|
||||
});
|
||||
commands.push({
|
||||
command: 'verifyLocation'
|
||||
, target: 'regexp:.+/home'
|
||||
});
|
||||
return commands;
|
||||
}</pre>
|
||||
<pre>// if using alternateCommand
|
||||
expandedCommands: []
|
||||
</pre></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p>The user should be able to freely record commands in the IDE, which can be collapsed into rollups at any point by applying the defined rollup rules. Healthy usage of the <code>ui</code> locator makes commands easy to match using command matcher definitions. Command matchers simplify the specification of a command match. In basic usage, for a rollup rule, you might specify 3 command matchers: <em>M1</em>, <em>M2</em>, and <em>M3</em>, that you intend to match 3 corresponding commands, <em>C1</em>, <em>C2</em>, and <em>C3</em>. In more complex usage, a single command matcher might match more than one command. For example, <em>M1</em> matches <em>C1</em> and <em>C2</em>, <em>M2</em> matches <em>C3</em>, and <em>M3</em> matches <em>C4</em>, <em>C5</em>, and <em>C6</em>. In the latter case, you would want to track the matches by updating argument values in the command matchers' <code>updateArgs()</code> methods.</p>
|
||||
|
||||
<p>Here are the required and optional fields for command matcher objects:</p>
|
||||
|
||||
<table>
|
||||
<tr><th>Name</th>
|
||||
<th>Required?</th>
|
||||
<th>Description</th>
|
||||
<th>Example</th>
|
||||
</tr>
|
||||
<tr><td>command</td>
|
||||
<td>Yes</td>
|
||||
<td>(String) a simplified regular expression string that matches the command name of a command. The special regexp characters <code>^</code> and <code>$</code> are automatically included at the beginning and end of the string, and therefore should not be explicitly provided.</td>
|
||||
<td><pre>command: 'click.+'</pre>
|
||||
<pre>command: 'rollup'</pre></td>
|
||||
</tr>
|
||||
<tr><td>target</td>
|
||||
<td>Yes</td>
|
||||
<td>(String) a simplified regular expression string that matches the target of a command. Same rules as for the <code>command</code> attribute.</td>
|
||||
<td><pre>target: 'btnG'</pre>
|
||||
<pre>// special regexp characters must be
|
||||
// escaped in regexp strings. Backslashes
|
||||
// always need to be escaped in javascript
|
||||
// strings.
|
||||
target: 'ui=loginPages::user\\(.+'
|
||||
</pre></td>
|
||||
</tr>
|
||||
<tr><td>value</td>
|
||||
<td>No</td>
|
||||
<td>(String) a simplified regular expression string that matches the value of a command. Same rules as for the <code>command</code> attribute.</td>
|
||||
<td><pre>value: '\\d+'</pre>
|
||||
<pre>value: (?:foo|bar)</pre></td>
|
||||
</tr>
|
||||
<tr><td>minMatches</td>
|
||||
<td>No</td>
|
||||
<td>(Number) the minimum number of times this command matcher must match consecutive commands for the rollup rule to match a set of commands. If unspecified, the default is 1. If <code>maxMatches</code> is also specified, <code>minMatches</code> must be less than or equal to it.</td>
|
||||
<td><pre>minMatches: 2</pre></td>
|
||||
</tr>
|
||||
<tr><td>maxMatches</td>
|
||||
<td>No</td>
|
||||
<td>(Number) the maximum number of times this command matcher is allowed to match consecutive commands for the rollup rule. If unspecified, the default is 1.</td>
|
||||
<td><pre>maxMatches: 2</pre></td>
|
||||
</tr>
|
||||
<tr><td>updateArgs()</td>
|
||||
<td>No</td>
|
||||
<td><p>(Function) updates an arguments object when a match has been found, and returns the updated arguments object. This method is used to keep track of the way in which one or more commands were matched. When a rollup rule is successfully applied, any argument name-value pairs are stored as the rollup command's value. At time of expanding the rollup command, the command's value is converted back to an arguments object, which is passed to the rollup rule's <code>getExpandedCommands()</code> method.</p><p>This method must have the following method signature: <code>updateArgs(command, args)</code>, where <code>command</code> is the command object that was just matched, and <code>args</code> is the arguments object for the current trial application of the parent rollup rule.</td>
|
||||
<td><pre>// reused from above
|
||||
updateArgs: function(command, args) {
|
||||
args.user = command.value;
|
||||
return args;
|
||||
}</pre>
|
||||
<pre>// for multiple matches
|
||||
updateArgs: function(command, args) {
|
||||
if (!args.clickCount) {
|
||||
args.clickCount = 0;
|
||||
}
|
||||
++args.clickCount;
|
||||
return args;
|
||||
}</pre>
|
||||
<pre>// another example from above (modified).
|
||||
// If you need to parse a UI specifier,
|
||||
// instantiate a new <span class="highlight">UISpecifier</span> object with the
|
||||
// locator. To do it by the book, you should
|
||||
// first strip off the "ui=" prefix. If you just
|
||||
// want to inspect the UI specifier's args, you
|
||||
// can safely skip this step.
|
||||
updateArgs: function(command, args) {
|
||||
var s = command.target.replace(/^ui=/, '');
|
||||
var uiSpecifier = new UISpecifier(s);
|
||||
args.term = uiSpecifier.args.term;
|
||||
return args;
|
||||
}</pre>
|
||||
<pre>// example from sample map file. If you're
|
||||
// matching a rollup command that has arguments,
|
||||
// you'll want to parse them. The easiest way
|
||||
// to do this is with the <span class="highlight">parse_kwargs()</span>
|
||||
// function, which has its roots in python.
|
||||
updateArgs: function(command, args) {
|
||||
var args1 = parse_kwargs(command.value);
|
||||
args.subtopic = args1.subtopic;
|
||||
return args;
|
||||
}</pre></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p>Too much mumbo jumbo?</p>
|
||||
|
||||
<p>To see rollup rules in action in the IDE, use the included sample map with UI-Element (see instructions above in the "Including the Right Files" section), and grab the listing of 4 commands from the "Getting Motivated" section, above. Under the <em>Source</em> tab of the IDE, paste the commands in between the <code><tbody></code> and <code></tbody></code> tags. Now switch back to the <em>Table</em> tab, and click the new <span class="highlight">purple spiral button</span>; this is the "Apply rollup rules" button. If done correctly, you should be prompted when rollup rule matches are found. Go ahead - go to the <a href="http://alistapart.com">alistapart.com</a> site and try executing the rollups!</p>
|
||||
|
||||
<h2>Release Notes</h2>
|
||||
|
||||
<h3>Core-1.0</h3>
|
||||
|
||||
<ul>
|
||||
<li>UI-Element is now completely integrated into Selenium Core.</li>
|
||||
<li>Added offset locators. Modified the delimiter to be <code>-></code>.</li>
|
||||
<li><code>getDefaultValues()</code> can dynamically construct a list of values and assume that a <code>document</code> object is being passed in.</li>
|
||||
<li>Arguments in UI specifier strings are presented in the same order as they are defined in the mapping file (no longer alphabetically).</li>
|
||||
<li>Allow generic locators to be specified, potentially improving recording performance when there are many UI arguments.</li>
|
||||
<li>Updated documentation.</li>
|
||||
<li>Many other fixes.</li>
|
||||
</ul>
|
||||
|
||||
<h3>ui0.7</h3>
|
||||
|
||||
<ul>
|
||||
<li>Changed extensions id and homepage to avoid conflicting with standard Selenium IDE distribution.</li>
|
||||
<li>Added rollup button.</li>
|
||||
<li>Added UI-Element and Rollup panes, with beautiful colors and formatting.</li>
|
||||
<li>Updated referenced version of Selenium Core to 0.8.3, and RC to 0.9.2 .</li>
|
||||
<li>Added <code>quoteForXPath()</code> to <code>String</code> prototype.</li>
|
||||
<li>Made XPath uppercasing much more robust. It is now backed by the ajaxslt library. Uppercasing is no longer required by UI-Element. It is used to demonstrate how XPath transformations may be used to improve XPath evaluation performance when using the javascript engine. See <a href="http://groups.google.com/group/google-ajax-discuss/browse_thread/thread/f7a7a2014a6415d4">this thread on XPath performance</a>.
|
||||
<li>Added new <code>rollup</code> Selenium command and associated functionality with the RollupManager object.</li>
|
||||
<li>Deprecated <code>getXPath()</code> and <code>xpath</code> in favor of <code>getLocator()</code> and <code>locator</code> in the UI-Element shorthand. Testcases now work with locator types other than XPath (implicit, CSS, etc.).</li>
|
||||
<li>UI element testcases are now truly XHTML. All content is considered inner HTML of the html element, which is automatically generated. This supports the use of alternate locator types described in the above bullet.</li>
|
||||
<li>Global variables introduced by UI-Element are now properties of the variable <code>GLOBAL</code>. You can now name your map <code>uiMap</code> if you wish.</li>
|
||||
<li>Updated the sample map, including demonstration of implicit and CSS locators, Rollup Rules, and usage of <code>quoteForXPath()</code>.</li>
|
||||
<li>Improved auto-population of target dropdown with UI element locators and rollup names. The population logic is as follows: when a command is loaded from a file or inserted without having been recorded, all UI element locators are shown. After a command recorded or executed, only UI element locators for pagesets that match the page at time of recording or execution will be shown.</li>
|
||||
<li>Made UI element testcase args mandatory. This reduces the startup time for the IDE, and improves testcase readability.</li>
|
||||
<li>Updated documentation. Added this release notes section.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Final Thoughts</h2>
|
||||
|
||||
<p>Catch UI-Element news in the <a href="http://ttwhy.org/home/blog/category/selenium/">Selenium category of my blog</a>. You can also find me on the <a href="http://clearspace.openqa.org/people/gyrm">OpenQA Forums</a>.</p>
|
||||
|
||||
<p><em>- Haw-Bin Chai</em></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
1537
vendor/plugins/selenium-on-rails/selenium-core/scripts/ui-element.js
vendored
Normal file
1537
vendor/plugins/selenium-on-rails/selenium-core/scripts/ui-element.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
979
vendor/plugins/selenium-on-rails/selenium-core/scripts/ui-map-sample.js
vendored
Normal file
979
vendor/plugins/selenium-on-rails/selenium-core/scripts/ui-map-sample.js
vendored
Normal file
|
|
@ -0,0 +1,979 @@
|
|||
// sample UI element mapping definition. This is for http://alistapart.com/,
|
||||
// a particularly well structured site on web design principles.
|
||||
|
||||
|
||||
|
||||
// in general, the map should capture structural aspects of the system, instead
|
||||
// of "content". In other words, interactive elements / assertible elements
|
||||
// that can be counted on to always exist should be defined here. Content -
|
||||
// for example text or a link that appears in a blog entry - is always liable
|
||||
// to change, and will not be fun to represent in this way. You probably don't
|
||||
// want to be testing specific content anyway.
|
||||
|
||||
// create the UI mapping object. THIS IS THE MOST IMPORTANT PART - DON'T FORGET
|
||||
// TO DO THIS! In order for it to come into play, a user extension must
|
||||
// construct the map in this way.
|
||||
var myMap = new UIMap();
|
||||
|
||||
|
||||
|
||||
|
||||
// any values which may appear multiple times can be defined as variables here.
|
||||
// For example, here we're enumerating a list of top level topics that will be
|
||||
// used as default argument values for several UI elements. Check out how
|
||||
// this variable is referenced further down.
|
||||
var topics = [
|
||||
'Code',
|
||||
'Content',
|
||||
'Culture',
|
||||
'Design',
|
||||
'Process',
|
||||
'User Science'
|
||||
];
|
||||
|
||||
// map subtopics to their parent topics
|
||||
var subtopics = {
|
||||
'Browsers': 'Code'
|
||||
, 'CSS': 'Code'
|
||||
, 'Flash': 'Code'
|
||||
, 'HTML and XHTML': 'Code'
|
||||
, 'Scripting': 'Code'
|
||||
, 'Server Side': 'Code'
|
||||
, 'XML': 'Code'
|
||||
, 'Brand Arts': 'Content'
|
||||
, 'Community': 'Content'
|
||||
, 'Writing': 'Content'
|
||||
, 'Industry': 'Culture'
|
||||
, 'Politics and Money': 'Culture'
|
||||
, 'State of the Web': 'Culture'
|
||||
, 'Graphic Design': 'Design'
|
||||
, 'User Interface Design': 'Design'
|
||||
, 'Typography': 'Design'
|
||||
, 'Layout': 'Design'
|
||||
, 'Business': 'Process'
|
||||
, 'Creativity': 'Process'
|
||||
, 'Project Management and Workflow': 'Process'
|
||||
, 'Accessibility': 'User Science'
|
||||
, 'Information Architecture': 'User Science'
|
||||
, 'Usability': 'User Science'
|
||||
};
|
||||
|
||||
|
||||
|
||||
// define UI elements common for all pages. This regular expression does the
|
||||
// trick. '^' is automatically prepended, and '$' is automatically postpended.
|
||||
// Please note that because the regular expression is being represented as a
|
||||
// string, all backslashes must be escaped with an additional backslash. Also
|
||||
// note that the URL being matched will always have any trailing forward slash
|
||||
// stripped.
|
||||
myMap.addPageset({
|
||||
name: 'allPages'
|
||||
, description: 'all alistapart.com pages'
|
||||
, pathRegexp: '.*'
|
||||
});
|
||||
myMap.addElement('allPages', {
|
||||
name: 'masthead'
|
||||
// the description should be short and to the point, usually no longer than
|
||||
// a single line
|
||||
, description: 'top level image link to site homepage'
|
||||
// make sure the function returns the XPath ... it's easy to leave out the
|
||||
// "return" statement by accident!
|
||||
, locator: "xpath=//*[@id='masthead']/a/img"
|
||||
, testcase1: {
|
||||
xhtml: '<h1 id="masthead"><a><img expected-result="1" /></a></h1>'
|
||||
}
|
||||
});
|
||||
myMap.addElement('allPages', {
|
||||
// be VERY CAREFUL to include commas in the correct place. Missing commas
|
||||
// and extra commas can cause lots of headaches when debugging map
|
||||
// definition files!!!
|
||||
name: 'current_issue'
|
||||
, description: 'top level link to issue currently being browsed'
|
||||
, locator: "//div[@id='ish']/a"
|
||||
, testcase1: {
|
||||
xhtml: '<div id="ish"><a expected-result="1"></a></div>'
|
||||
}
|
||||
});
|
||||
myMap.addElement('allPages', {
|
||||
name: 'section'
|
||||
, description: 'top level link to articles section'
|
||||
, args: [
|
||||
{
|
||||
name: 'section'
|
||||
, description: 'the name of the section'
|
||||
, defaultValues: [
|
||||
'articles'
|
||||
, 'topics'
|
||||
, 'about'
|
||||
, 'contact'
|
||||
, 'contribute'
|
||||
, 'feed'
|
||||
]
|
||||
}
|
||||
]
|
||||
// getXPath has been deprecated by getLocator, but verify backward
|
||||
// compatability here
|
||||
, getXPath: function(args) {
|
||||
return "//li[@id=" + args.section.quoteForXPath() + "]/a";
|
||||
}
|
||||
, testcase1: {
|
||||
args: { section: 'feed' }
|
||||
, xhtml: '<ul><li id="feed"><a expected-result="1" /></li></ul>'
|
||||
}
|
||||
});
|
||||
myMap.addElement('allPages', {
|
||||
name: 'search_box'
|
||||
, description: 'site search input field'
|
||||
// xpath has been deprecated by locator, but verify backward compatability
|
||||
, xpath: "//input[@id='search']"
|
||||
, testcase1: {
|
||||
xhtml: '<input id="search" expected-result="1" />'
|
||||
}
|
||||
});
|
||||
myMap.addElement('allPages', {
|
||||
name: 'search_discussions'
|
||||
, description: 'site search include discussions checkbox'
|
||||
, locator: 'incdisc'
|
||||
, testcase1: {
|
||||
xhtml: '<input id="incdisc" expected-result="1" />'
|
||||
}
|
||||
});
|
||||
myMap.addElement('allPages', {
|
||||
name: 'search_submit'
|
||||
, description: 'site search submission button'
|
||||
, locator: 'submit'
|
||||
, testcase1: {
|
||||
xhtml: '<input id="submit" expected-result="1" />'
|
||||
}
|
||||
});
|
||||
myMap.addElement('allPages', {
|
||||
name: 'topics'
|
||||
, description: 'sidebar links to topic categories'
|
||||
, args: [
|
||||
{
|
||||
name: 'topic'
|
||||
, description: 'the name of the topic'
|
||||
, defaultValues: topics
|
||||
}
|
||||
]
|
||||
, getLocator: function(args) {
|
||||
return "//div[@id='topiclist']/ul/li" +
|
||||
"/a[text()=" + args.topic.quoteForXPath() + "]";
|
||||
}
|
||||
, testcase1: {
|
||||
args: { topic: 'foo' }
|
||||
, xhtml: '<div id="topiclist"><ul><li>'
|
||||
+ '<a expected-result="1">foo</a>'
|
||||
+ '</li></ul></div>'
|
||||
}
|
||||
});
|
||||
myMap.addElement('allPages', {
|
||||
name: 'copyright'
|
||||
, description: 'footer link to copyright page'
|
||||
, getLocator: function(args) { return "//span[@class='copyright']/a"; }
|
||||
, testcase1: {
|
||||
xhtml: '<span class="copyright"><a expected-result="1" /></span>'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
// define UI elements for the homepage, i.e. "http://alistapart.com/", and
|
||||
// magazine issue pages, i.e. "http://alistapart.com/issues/234".
|
||||
myMap.addPageset({
|
||||
name: 'issuePages'
|
||||
, description: 'pages including magazine issues'
|
||||
, pathRegexp: '(issues/.+)?'
|
||||
});
|
||||
myMap.addElement('issuePages', {
|
||||
name: 'article'
|
||||
, description: 'front or issue page link to article'
|
||||
, args: [
|
||||
{
|
||||
name: 'index'
|
||||
, description: 'the index of the article'
|
||||
// an array of default values for the argument. A default
|
||||
// value is one that is passed to the getXPath() method of
|
||||
// the container UIElement object when trying to build an
|
||||
// element locator.
|
||||
//
|
||||
// range() may be used to count easily. Remember though that
|
||||
// the ending value does not include the right extreme; for
|
||||
// example range(1, 5) counts from 1 to 4 only.
|
||||
, defaultValues: range(1, 5)
|
||||
}
|
||||
]
|
||||
, getLocator: function(args) {
|
||||
return "//div[@class='item'][" + args.index + "]/h4/a";
|
||||
}
|
||||
});
|
||||
myMap.addElement('issuePages', {
|
||||
name: 'author'
|
||||
, description: 'article author link'
|
||||
, args: [
|
||||
{
|
||||
name: 'index'
|
||||
, description: 'the index of the author, by article'
|
||||
, defaultValues: range(1, 5)
|
||||
}
|
||||
]
|
||||
, getLocator: function(args) {
|
||||
return "//div[@class='item'][" + args.index + "]/h5/a";
|
||||
}
|
||||
});
|
||||
myMap.addElement('issuePages', {
|
||||
name: 'store'
|
||||
, description: 'alistapart.com store link'
|
||||
, locator: "//ul[@id='banners']/li/a[@title='ALA Store']/img"
|
||||
});
|
||||
myMap.addElement('issuePages', {
|
||||
name: 'special_article'
|
||||
, description: "editor's choice article link"
|
||||
, locator: "//div[@id='choice']/h4/a"
|
||||
});
|
||||
myMap.addElement('issuePages', {
|
||||
name: 'special_author'
|
||||
, description: "author link of editor's choice article"
|
||||
, locator: "//div[@id='choice']/h5/a"
|
||||
});
|
||||
|
||||
|
||||
|
||||
// define UI elements for the articles page, i.e.
|
||||
// "http://alistapart.com/articles"
|
||||
myMap.addPageset({
|
||||
name: 'articleListPages'
|
||||
, description: 'page with article listings'
|
||||
, paths: [ 'articles' ]
|
||||
});
|
||||
myMap.addElement('articleListPages', {
|
||||
name: 'issue'
|
||||
, description: 'link to issue'
|
||||
, args: [
|
||||
{
|
||||
name: 'index'
|
||||
, description: 'the index of the issue on the page'
|
||||
, defaultValues: range(1, 10)
|
||||
}
|
||||
]
|
||||
, getLocator: function(args) {
|
||||
return "//h2[@class='ishinfo'][" + args.index + ']/a';
|
||||
}
|
||||
, genericLocator: "//h2[@class='ishinfo']/a"
|
||||
});
|
||||
myMap.addElement('articleListPages', {
|
||||
name: 'article'
|
||||
, description: 'link to article, by issue and article number'
|
||||
, args: [
|
||||
{
|
||||
name: 'issue_index'
|
||||
, description: "the index of the article's issue on the page; "
|
||||
+ 'typically five per page'
|
||||
, defaultValues: range(1, 6)
|
||||
}
|
||||
, {
|
||||
name: 'article_index'
|
||||
, description: 'the index of the article within the issue; '
|
||||
+ 'typically two per issue'
|
||||
, defaultValues: range(1, 5)
|
||||
}
|
||||
]
|
||||
, getLocator: function(args) {
|
||||
var xpath = "//h2[@class='ishinfo'][" + (args.issue_index || 1) + ']'
|
||||
+ "/following-sibling::div[@class='item']"
|
||||
+ '[' + (args.article_index || 1) + "]/h3[@class='title']/a";
|
||||
return xpath;
|
||||
}
|
||||
, genericLocator: "//h2[@class='ishinfo']"
|
||||
+ "/following-sibling::div[@class='item']/h3[@class='title']/a"
|
||||
});
|
||||
myMap.addElement('articleListPages', {
|
||||
name: 'author'
|
||||
, description: 'article author link, by issue and article'
|
||||
, args: [
|
||||
{
|
||||
name: 'issue_index'
|
||||
, description: "the index of the article's issue on the page; \
|
||||
typically five per page"
|
||||
, defaultValues: range(1, 6)
|
||||
}
|
||||
, {
|
||||
name: 'article_index'
|
||||
, description: "the index of the article within the issue; \
|
||||
typically two articles per issue"
|
||||
, defaultValues: range(1, 3)
|
||||
}
|
||||
]
|
||||
// this XPath uses the "following-sibling" axis. The div elements for
|
||||
// the articles in an issue are not children, but siblings of the h2
|
||||
// element identifying the article.
|
||||
, getLocator: function(args) {
|
||||
var xpath = "//h2[@class='ishinfo'][" + (args.issue_index || 1) + ']'
|
||||
+ "/following-sibling::div[@class='item']"
|
||||
+ '[' + (args.article_index || 1) + "]/h4[@class='byline']/a";
|
||||
return xpath;
|
||||
}
|
||||
, genericLocator: "//h2[@class='ishinfo']"
|
||||
+ "/following-sibling::div[@class='item']/h4[@class='byline']/a"
|
||||
});
|
||||
myMap.addElement('articleListPages', {
|
||||
name: 'next_page'
|
||||
, description: 'link to next page of articles (older)'
|
||||
, locator: "//a[contains(text(),'Next page')]"
|
||||
});
|
||||
myMap.addElement('articleListPages', {
|
||||
name: 'previous_page'
|
||||
, description: 'link to previous page of articles (newer)'
|
||||
, locator: "//a[contains(text(),'Previous page')]"
|
||||
});
|
||||
|
||||
|
||||
|
||||
// define UI elements for specific article pages, i.e.
|
||||
// "http://alistapart.com/articles/culturalprobe"
|
||||
myMap.addPageset({
|
||||
name: 'articlePages'
|
||||
, description: 'pages for actual articles'
|
||||
, pathRegexp: 'articles/.+'
|
||||
});
|
||||
myMap.addElement('articlePages', {
|
||||
name: 'title'
|
||||
, description: 'article title loop-link'
|
||||
, locator: "//div[@id='content']/h1[@class='title']/a"
|
||||
});
|
||||
myMap.addElement('articlePages', {
|
||||
name: 'author'
|
||||
, description: 'article author link'
|
||||
, locator: "//div[@id='content']/h3[@class='byline']/a"
|
||||
});
|
||||
myMap.addElement('articlePages', {
|
||||
name: 'article_topics'
|
||||
, description: 'links to topics under which article is published, before \
|
||||
article content'
|
||||
, args: [
|
||||
{
|
||||
name: 'topic'
|
||||
, description: 'the name of the topic'
|
||||
, defaultValues: keys(subtopics)
|
||||
}
|
||||
]
|
||||
, getLocator: function(args) {
|
||||
return "//ul[@id='metastuff']/li/a"
|
||||
+ "[@title=" + args.topic.quoteForXPath() + "]";
|
||||
}
|
||||
});
|
||||
myMap.addElement('articlePages', {
|
||||
name: 'discuss'
|
||||
, description: 'link to article discussion area, before article content'
|
||||
, locator: "//ul[@id='metastuff']/li[@class='discuss']/p/a"
|
||||
});
|
||||
myMap.addElement('articlePages', {
|
||||
name: 'related_topics'
|
||||
, description: 'links to topics under which article is published, after \
|
||||
article content'
|
||||
, args: [
|
||||
{
|
||||
name: 'topic'
|
||||
, description: 'the name of the topic'
|
||||
, defaultValues: keys(subtopics)
|
||||
}
|
||||
]
|
||||
, getLocator: function(args) {
|
||||
return "//div[@id='learnmore']/p/a"
|
||||
+ "[@title=" + args.topic.quoteForXPath() + "]";
|
||||
}
|
||||
});
|
||||
myMap.addElement('articlePages', {
|
||||
name: 'join_discussion'
|
||||
, description: 'link to article discussion area, after article content'
|
||||
, locator: "//div[@class='discuss']/p/a"
|
||||
});
|
||||
|
||||
|
||||
|
||||
myMap.addPageset({
|
||||
name: 'topicListingPages'
|
||||
, description: 'top level listing of topics'
|
||||
, paths: [ 'topics' ]
|
||||
});
|
||||
myMap.addElement('topicListingPages', {
|
||||
name: 'topic'
|
||||
, description: 'link to topic category'
|
||||
, args: [
|
||||
{
|
||||
name: 'topic'
|
||||
, description: 'the name of the topic'
|
||||
, defaultValues: topics
|
||||
}
|
||||
]
|
||||
, getLocator: function(args) {
|
||||
return "//div[@id='content']/h2/a"
|
||||
+ "[text()=" + args.topic.quoteForXPath() + "]";
|
||||
}
|
||||
});
|
||||
myMap.addElement('topicListingPages', {
|
||||
name: 'subtopic'
|
||||
, description: 'link to subtopic category'
|
||||
, args: [
|
||||
{
|
||||
name: 'subtopic'
|
||||
, description: 'the name of the subtopic'
|
||||
, defaultValues: keys(subtopics)
|
||||
}
|
||||
]
|
||||
, getLocator: function(args) {
|
||||
return "//div[@id='content']" +
|
||||
"/descendant::a[text()=" + args.subtopic.quoteForXPath() + "]";
|
||||
}
|
||||
});
|
||||
|
||||
// the following few subtopic page UI elements are very similar. Define UI
|
||||
// elements for the code page, which is a subpage under topics, i.e.
|
||||
// "http://alistapart.com/topics/code/"
|
||||
myMap.addPageset({
|
||||
name: 'subtopicListingPages'
|
||||
, description: 'pages listing subtopics'
|
||||
, pathPrefix: 'topics/'
|
||||
, paths: [
|
||||
'code'
|
||||
, 'content'
|
||||
, 'culture'
|
||||
, 'design'
|
||||
, 'process'
|
||||
, 'userscience'
|
||||
]
|
||||
});
|
||||
myMap.addElement('subtopicListingPages', {
|
||||
name: 'subtopic'
|
||||
, description: 'link to a subtopic category'
|
||||
, args: [
|
||||
{
|
||||
name: 'subtopic'
|
||||
, description: 'the name of the subtopic'
|
||||
, defaultValues: keys(subtopics)
|
||||
}
|
||||
]
|
||||
, getLocator: function(args) {
|
||||
return "//div[@id='content']/h2" +
|
||||
"/a[text()=" + args.subtopic.quoteForXPath() + "]";
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
// subtopic articles page
|
||||
myMap.addPageset({
|
||||
name: 'subtopicArticleListingPages'
|
||||
, description: 'pages listing the articles for a given subtopic'
|
||||
, pathRegexp: 'topics/[^/]+/.+'
|
||||
});
|
||||
myMap.addElement('subtopicArticleListingPages', {
|
||||
name: 'article'
|
||||
, description: 'link to a subtopic article'
|
||||
, args: [
|
||||
{
|
||||
name: 'index'
|
||||
, description: 'the index of the article'
|
||||
, defaultValues: range(1, 51) // the range seems unlimited ...
|
||||
}
|
||||
]
|
||||
, getLocator: function(args) {
|
||||
return "//div[@id='content']/div[@class='item']"
|
||||
+ "[" + args.index + "]/h3/a";
|
||||
}
|
||||
, testcase1: {
|
||||
args: { index: 2 }
|
||||
, xhtml: '<div id="content"><div class="item" /><div class="item">'
|
||||
+ '<h3><a expected-result="1" /></h3></div></div>'
|
||||
}
|
||||
});
|
||||
myMap.addElement('subtopicArticleListingPages', {
|
||||
name: 'author'
|
||||
, description: "link to a subtopic article author's page"
|
||||
, args: [
|
||||
{
|
||||
name: 'article_index'
|
||||
, description: 'the index of the authored article'
|
||||
, defaultValues: range(1, 51)
|
||||
}
|
||||
, {
|
||||
name: 'author_index'
|
||||
, description: 'the index of the author when there are multiple'
|
||||
, defaultValues: range(1, 4)
|
||||
}
|
||||
]
|
||||
, getLocator: function(args) {
|
||||
return "//div[@id='content']/div[@class='item'][" +
|
||||
args.article_index + "]/h4/a[" +
|
||||
(args.author_index ? args.author_index : '1') + ']';
|
||||
}
|
||||
});
|
||||
myMap.addElement('subtopicArticleListingPages', {
|
||||
name: 'issue'
|
||||
, description: 'link to issue a subtopic article appears in'
|
||||
, args: [
|
||||
{
|
||||
name: 'index'
|
||||
, description: 'the index of the subtopic article'
|
||||
, defaultValues: range(1, 51)
|
||||
}
|
||||
]
|
||||
, getLocator: function(args) {
|
||||
return "//div[@id='content']/div[@class='item']"
|
||||
+ "[" + args.index + "]/h5/a";
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
myMap.addPageset({
|
||||
name: 'aboutPages'
|
||||
, description: 'the website about page'
|
||||
, paths: [ 'about' ]
|
||||
});
|
||||
myMap.addElement('aboutPages', {
|
||||
name: 'crew'
|
||||
, description: 'link to site crew member bio or personal website'
|
||||
, args: [
|
||||
{
|
||||
name: 'role'
|
||||
, description: 'the role of the crew member'
|
||||
, defaultValues: [
|
||||
'ALA Crew'
|
||||
, 'Support'
|
||||
, 'Emeritus'
|
||||
]
|
||||
}
|
||||
, {
|
||||
name: 'role_index'
|
||||
, description: 'the index of the member within the role'
|
||||
, defaultValues: range(1, 20)
|
||||
}
|
||||
, {
|
||||
name: 'member_index'
|
||||
, description: 'the index of the member within the role title'
|
||||
, defaultValues: range(1, 5)
|
||||
}
|
||||
]
|
||||
, getLocator: function(args) {
|
||||
// the first role is kind of funky, and requires a conditional to
|
||||
// build the XPath correctly. Its header looks like this:
|
||||
//
|
||||
// <h3>
|
||||
// <span class="caps">ALA 4</span>.0 <span class="caps">CREW</span>
|
||||
// </h3>
|
||||
//
|
||||
// This kind of complexity is a little daunting, but you can see
|
||||
// how the format can handle it relatively easily and concisely.
|
||||
if (args.role == 'ALA Crew') {
|
||||
var selector = "descendant::text()='CREW'";
|
||||
}
|
||||
else {
|
||||
var selector = "text()=" + args.role.quoteForXPath();
|
||||
}
|
||||
var xpath =
|
||||
"//div[@id='secondary']/h3[" + selector + ']' +
|
||||
"/following-sibling::dl/dt[" + (args.role_index || 1) + ']' +
|
||||
'/a[' + (args.member_index || '1') + ']';
|
||||
return xpath;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
myMap.addPageset({
|
||||
name: 'searchResultsPages'
|
||||
, description: 'pages listing search results'
|
||||
, paths: [ 'search' ]
|
||||
});
|
||||
myMap.addElement('searchResultsPages', {
|
||||
name: 'result_link'
|
||||
, description: 'search result link'
|
||||
, args: [
|
||||
{
|
||||
name: 'index'
|
||||
, description: 'the index of the search result'
|
||||
, defaultValues: range(1, 11)
|
||||
}
|
||||
]
|
||||
, getLocator: function(args) {
|
||||
return "//div[@id='content']/ul[" + args.index + ']/li/h3/a';
|
||||
}
|
||||
});
|
||||
myMap.addElement('searchResultsPages', {
|
||||
name: 'more_results_link'
|
||||
, description: 'next or previous results link at top or bottom of page'
|
||||
, args: [
|
||||
{
|
||||
name: 'direction'
|
||||
, description: 'next or previous results page'
|
||||
// demonstrate a method which acquires default values from the
|
||||
// document object. Such default values may contain EITHER commas
|
||||
// OR equals signs, but NOT BOTH.
|
||||
, getDefaultValues: function(inDocument) {
|
||||
var defaultValues = [];
|
||||
var divs = inDocument.getElementsByTagName('div');
|
||||
for (var i = 0; i < divs.length; ++i) {
|
||||
if (divs[i].className == 'pages') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
var links = divs[i].getElementsByTagName('a');
|
||||
for (i = 0; i < links.length; ++i) {
|
||||
defaultValues.push(links[i].innerHTML
|
||||
.replace(/^\xab\s*/, "")
|
||||
.replace(/\s*\bb$/, "")
|
||||
.replace(/\s*\d+$/, ""));
|
||||
}
|
||||
return defaultValues;
|
||||
}
|
||||
}
|
||||
, {
|
||||
name: 'position'
|
||||
, description: 'position of the link'
|
||||
, defaultValues: ['top', 'bottom']
|
||||
}
|
||||
]
|
||||
, getLocator: function(args) {
|
||||
return "//div[@id='content']/div[@class='pages']["
|
||||
+ (args.position == 'top' ? '1' : '2') + ']'
|
||||
+ "/a[contains(text(), "
|
||||
+ (args.direction ? args.direction.quoteForXPath() : undefined)
|
||||
+ ")]";
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
myMap.addPageset({
|
||||
name: 'commentsPages'
|
||||
, description: 'pages listing comments made to an article'
|
||||
, pathRegexp: 'comments/.+'
|
||||
});
|
||||
myMap.addElement('commentsPages', {
|
||||
name: 'article_link'
|
||||
, description: 'link back to the original article'
|
||||
, locator: "//div[@id='content']/h1[@class='title']/a"
|
||||
});
|
||||
myMap.addElement('commentsPages', {
|
||||
name: 'comment_link'
|
||||
, description: 'same-page link to comment'
|
||||
, args: [
|
||||
{
|
||||
name: 'index'
|
||||
, description: 'the index of the comment'
|
||||
, defaultValues: range(1, 11)
|
||||
}
|
||||
]
|
||||
, getLocator: function(args) {
|
||||
return "//div[@class='content']/div[contains(@class, 'comment')]" +
|
||||
'[' + args.index + ']/h4/a[2]';
|
||||
}
|
||||
});
|
||||
myMap.addElement('commentsPages', {
|
||||
name: 'paging_link'
|
||||
, description: 'links to more pages of comments'
|
||||
, args: [
|
||||
{
|
||||
name: 'dest'
|
||||
, description: 'the destination page'
|
||||
, defaultValues: ['next', 'prev'].concat(range(1, 16))
|
||||
}
|
||||
, {
|
||||
name: 'position'
|
||||
, description: 'position of the link'
|
||||
, defaultValues: ['top', 'bottom']
|
||||
}
|
||||
]
|
||||
, getLocator: function(args) {
|
||||
var dest = args.dest;
|
||||
var xpath = "//div[@id='content']/div[@class='pages']" +
|
||||
'[' + (args.position == 'top' ? '1' : '2') + ']/p';
|
||||
if (dest == 'next' || dest == 'prev') {
|
||||
xpath += "/a[contains(text(), " + dest.quoteForXPath() + ")]";
|
||||
}
|
||||
else {
|
||||
xpath += "/a[text()=" + dest.quoteForXPath() + "]";
|
||||
}
|
||||
return xpath;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
myMap.addPageset({
|
||||
name: 'authorPages'
|
||||
, description: 'personal pages for each author'
|
||||
, pathRegexp: 'authors/[a-z]/.+'
|
||||
});
|
||||
myMap.addElement('authorPages', {
|
||||
name: 'article'
|
||||
, description: "link to article written by this author.\n"
|
||||
+ 'This description has a line break.'
|
||||
, args: [
|
||||
{
|
||||
name: 'index'
|
||||
, description: 'index of the article on the page'
|
||||
, defaultValues: range(1, 11)
|
||||
}
|
||||
]
|
||||
, getLocator: function(args) {
|
||||
var index = args.index;
|
||||
// try out the CSS locator!
|
||||
//return "//h4[@class='title'][" + index + "]/a";
|
||||
return 'css=h4.title:nth-child(' + index + ') > a';
|
||||
}
|
||||
, testcase1: {
|
||||
args: { index: '2' }
|
||||
, xhtml: '<h4 class="title" /><h4 class="title">'
|
||||
+ '<a expected-result="1" /></h4>'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
// test the offset locator. Something like the following can be recorded:
|
||||
// ui=qaPages::content()//a[contains(text(),'May I quote from your articles?')]
|
||||
myMap.addPageset({
|
||||
name: 'qaPages'
|
||||
, description: 'question and answer pages'
|
||||
, pathRegexp: 'qa'
|
||||
});
|
||||
myMap.addElement('qaPages', {
|
||||
name: 'content'
|
||||
, description: 'the content pane containing the q&a entries'
|
||||
, locator: "//div[@id='content' and "
|
||||
+ "child::h1[text()='Questions and Answers']]"
|
||||
, getOffsetLocator: UIElement.defaultOffsetLocatorStrategy
|
||||
});
|
||||
myMap.addElement('qaPages', {
|
||||
name: 'last_updated'
|
||||
, description: 'displays the last update date'
|
||||
// demonstrate calling getLocator() for another UI element within a
|
||||
// getLocator(). The former must have already been added to the map. And
|
||||
// obviously, you can't randomly combine different locator types!
|
||||
, locator: myMap.getUIElement('qaPages', 'content').getLocator() + '/p/em'
|
||||
});
|
||||
|
||||
|
||||
|
||||
//******************************************************************************
|
||||
|
||||
var myRollupManager = new RollupManager();
|
||||
|
||||
// though the description element is required, its content is free form. You
|
||||
// might want to create a documentation policy as given below, where the pre-
|
||||
// and post-conditions of the rollup are spelled out.
|
||||
//
|
||||
// To take advantage of a "heredoc" like syntax for longer descriptions,
|
||||
// add a backslash to the end of the current line and continue the string on
|
||||
// the next line.
|
||||
myRollupManager.addRollupRule({
|
||||
name: 'navigate_to_subtopic_article_listing'
|
||||
, description: 'drill down to the listing of articles for a given subtopic \
|
||||
from the section menu, then the topic itself.'
|
||||
, pre: 'current page contains the section menu (most pages should)'
|
||||
, post: 'navigated to the page listing all articles for a given subtopic'
|
||||
, args: [
|
||||
{
|
||||
name: 'subtopic'
|
||||
, description: 'the subtopic whose article listing to navigate to'
|
||||
, exampleValues: keys(subtopics)
|
||||
}
|
||||
]
|
||||
, commandMatchers: [
|
||||
{
|
||||
command: 'clickAndWait'
|
||||
, target: 'ui=allPages::section\\(section=topics\\)'
|
||||
// must escape parentheses in the the above target, since the
|
||||
// string is being used as a regular expression. Again, backslashes
|
||||
// in strings must be escaped too.
|
||||
}
|
||||
, {
|
||||
command: 'clickAndWait'
|
||||
, target: 'ui=topicListingPages::topic\\(.+'
|
||||
}
|
||||
, {
|
||||
command: 'clickAndWait'
|
||||
, target: 'ui=subtopicListingPages::subtopic\\(.+'
|
||||
, updateArgs: function(command, args) {
|
||||
// don't bother stripping the "ui=" prefix from the locator
|
||||
// here; we're just using UISpecifier to parse the args out
|
||||
var uiSpecifier = new UISpecifier(command.target);
|
||||
args.subtopic = uiSpecifier.args.subtopic;
|
||||
return args;
|
||||
}
|
||||
}
|
||||
]
|
||||
, getExpandedCommands: function(args) {
|
||||
var commands = [];
|
||||
var topic = subtopics[args.subtopic];
|
||||
var subtopic = args.subtopic;
|
||||
commands.push({
|
||||
command: 'clickAndWait'
|
||||
, target: 'ui=allPages::section(section=topics)'
|
||||
});
|
||||
commands.push({
|
||||
command: 'clickAndWait'
|
||||
, target: 'ui=topicListingPages::topic(topic=' + topic + ')'
|
||||
});
|
||||
commands.push({
|
||||
command: 'clickAndWait'
|
||||
, target: 'ui=subtopicListingPages::subtopic(subtopic=' + subtopic
|
||||
+ ')'
|
||||
});
|
||||
commands.push({
|
||||
command: 'verifyLocation'
|
||||
, target: 'regexp:.+/topics/.+/.+'
|
||||
});
|
||||
return commands;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
myRollupManager.addRollupRule({
|
||||
name: 'replace_click_with_clickAndWait'
|
||||
, description: 'replaces commands where a click was detected with \
|
||||
clickAndWait instead'
|
||||
, alternateCommand: 'clickAndWait'
|
||||
, commandMatchers: [
|
||||
{
|
||||
command: 'click'
|
||||
, target: 'ui=subtopicArticleListingPages::article\\(.+'
|
||||
}
|
||||
]
|
||||
, expandedCommands: []
|
||||
});
|
||||
|
||||
|
||||
|
||||
myRollupManager.addRollupRule({
|
||||
name: 'navigate_to_subtopic_article'
|
||||
, description: 'navigate to an article listed under a subtopic.'
|
||||
, pre: 'current page contains the section menu (most pages should)'
|
||||
, post: 'navigated to an article page'
|
||||
, args: [
|
||||
{
|
||||
name: 'subtopic'
|
||||
, description: 'the subtopic whose article listing to navigate to'
|
||||
, exampleValues: keys(subtopics)
|
||||
}
|
||||
, {
|
||||
name: 'index'
|
||||
, description: 'the index of the article in the listing'
|
||||
, exampleValues: range(1, 11)
|
||||
}
|
||||
]
|
||||
, commandMatchers: [
|
||||
{
|
||||
command: 'rollup'
|
||||
, target: 'navigate_to_subtopic_article_listing'
|
||||
, value: 'subtopic\\s*=.+'
|
||||
, updateArgs: function(command, args) {
|
||||
var args1 = parse_kwargs(command.value);
|
||||
args.subtopic = args1.subtopic;
|
||||
return args;
|
||||
}
|
||||
}
|
||||
, {
|
||||
command: 'clickAndWait'
|
||||
, target: 'ui=subtopicArticleListingPages::article\\(.+'
|
||||
, updateArgs: function(command, args) {
|
||||
var uiSpecifier = new UISpecifier(command.target);
|
||||
args.index = uiSpecifier.args.index;
|
||||
return args;
|
||||
}
|
||||
}
|
||||
]
|
||||
/*
|
||||
// this is pretty much equivalent to the commandMatchers immediately above.
|
||||
// Seems more verbose and less expressive, doesn't it? But sometimes you
|
||||
// might prefer the flexibility of a function.
|
||||
, getRollup: function(commands) {
|
||||
if (commands.length >= 2) {
|
||||
command1 = commands[0];
|
||||
command2 = commands[1];
|
||||
var args1 = parse_kwargs(command1.value);
|
||||
try {
|
||||
var uiSpecifier = new UISpecifier(command2.target
|
||||
.replace(/^ui=/, ''));
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
if (command1.command == 'rollup' &&
|
||||
command1.target == 'navigate_to_subtopic_article_listing' &&
|
||||
args1.subtopic &&
|
||||
command2.command == 'clickAndWait' &&
|
||||
uiSpecifier.pagesetName == 'subtopicArticleListingPages' &&
|
||||
uiSpecifier.elementName == 'article') {
|
||||
var args = {
|
||||
subtopic: args1.subtopic
|
||||
, index: uiSpecifier.args.index
|
||||
};
|
||||
return {
|
||||
command: 'rollup'
|
||||
, target: this.name
|
||||
, value: to_kwargs(args)
|
||||
, replacementIndexes: [ 0, 1 ]
|
||||
};
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
, getExpandedCommands: function(args) {
|
||||
var commands = [];
|
||||
commands.push({
|
||||
command: 'rollup'
|
||||
, target: 'navigate_to_subtopic_article_listing'
|
||||
, value: to_kwargs({ subtopic: args.subtopic })
|
||||
});
|
||||
var uiSpecifier = new UISpecifier(
|
||||
'subtopicArticleListingPages'
|
||||
, 'article'
|
||||
, { index: args.index });
|
||||
commands.push({
|
||||
command: 'clickAndWait'
|
||||
, target: 'ui=' + uiSpecifier.toString()
|
||||
});
|
||||
commands.push({
|
||||
command: 'verifyLocation'
|
||||
, target: 'regexp:.+/articles/.+'
|
||||
});
|
||||
return commands;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 8.4 KiB |
|
|
@ -48,7 +48,11 @@ iframe {
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
background: white;
|
||||
overflow: auto;
|
||||
/* HBC - this particular property seems to be causing an issue in
|
||||
conjunction with the native Draw() method in Snapsie. I don't really
|
||||
see a visual difference from commenting this out, so I'm going ahead
|
||||
with it.
|
||||
overflow: auto; */
|
||||
}
|
||||
|
||||
/*---( Style )---*/
|
||||
|
|
@ -67,6 +71,19 @@ body, html {
|
|||
font-size: 90%;
|
||||
}
|
||||
|
||||
.remoterunner {
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
.remoterunner fieldset {
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
.remoterunner button, .remoterunner label {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
|
||||
#controlPanel {
|
||||
padding: 0.5ex;
|
||||
background: #eee;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,10 @@
|
|||
// Copyright 2005 Google Inc.
|
||||
// All Rights Reserved
|
||||
//
|
||||
// Author: Steffen Meschkat <mesch@google.com>
|
||||
//
|
||||
// An XML parse and a minimal DOM implementation that just supportes
|
||||
// the subset of the W3C DOM that is used in the XSLT implementation.
|
||||
//
|
||||
// References:
|
||||
//
|
||||
// [DOM] W3C DOM Level 3 Core Specification
|
||||
// <http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/>.
|
||||
//
|
||||
//
|
||||
// Author: Steffen Meschkat <mesch@google.com>
|
||||
|
||||
// NOTE: The split() method in IE omits empty result strings. This is
|
||||
// utterly annoying. So we don't use it here.
|
||||
|
|
@ -20,23 +14,25 @@
|
|||
// the API level. I.e. no entity references are passed through the
|
||||
// API. See "Entities and the DOM core", p.12, DOM 2 Core
|
||||
// Spec. However, different browsers actually pass very different
|
||||
// values at the API.
|
||||
//
|
||||
// values at the API. See <http://mesch.nyc/test-xml-quote>.
|
||||
function xmlResolveEntities(s) {
|
||||
|
||||
var parts = stringSplit(s, '&');
|
||||
|
||||
var ret = parts[0];
|
||||
for (var i = 1; i < parts.length; ++i) {
|
||||
var rp = stringSplit(parts[i], ';');
|
||||
if (rp.length == 1) {
|
||||
var rp = parts[i].indexOf(';');
|
||||
if (rp == -1) {
|
||||
// no entity reference: just a & but no ;
|
||||
ret += parts[i];
|
||||
continue;
|
||||
}
|
||||
|
||||
var entityName = parts[i].substring(0, rp);
|
||||
var remainderText = parts[i].substring(rp + 1);
|
||||
|
||||
var ch;
|
||||
switch (rp[0]) {
|
||||
switch (entityName) {
|
||||
case 'lt':
|
||||
ch = '<';
|
||||
break;
|
||||
|
|
@ -60,29 +56,48 @@ function xmlResolveEntities(s) {
|
|||
// the entity text through non-W3C DOM properties and read it
|
||||
// through the W3C DOM. W3C DOM access is specified to resolve
|
||||
// entities.
|
||||
var span = window.document.createElement('span');
|
||||
span.innerHTML = '&' + rp[0] + '; ';
|
||||
var span = domCreateElement(window.document, 'span');
|
||||
span.innerHTML = '&' + entityName + '; ';
|
||||
ch = span.childNodes[0].nodeValue.charAt(0);
|
||||
}
|
||||
ret += ch + rp[1];
|
||||
ret += ch + remainderText;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
var XML10_TAGNAME_REGEXP = new RegExp('^(' + XML10_NAME + ')');
|
||||
var XML10_ATTRIBUTE_REGEXP = new RegExp(XML10_ATTRIBUTE, 'g');
|
||||
|
||||
var XML11_TAGNAME_REGEXP = new RegExp('^(' + XML11_NAME + ')');
|
||||
var XML11_ATTRIBUTE_REGEXP = new RegExp(XML11_ATTRIBUTE, 'g');
|
||||
|
||||
// Parses the given XML string with our custom, JavaScript XML parser. Written
|
||||
// by Steffen Meschkat (mesch@google.com).
|
||||
function xmlParse(xml) {
|
||||
Timer.start('xmlparse');
|
||||
var regex_empty = /\/$/;
|
||||
|
||||
// See also <http://www.w3.org/TR/REC-xml/#sec-common-syn> for
|
||||
// allowed chars in a tag and attribute name. TODO(mesch): the
|
||||
// following is still not completely correct.
|
||||
|
||||
var regex_tagname = /^([\w:-]*)/;
|
||||
var regex_attribute = /([\w:-]+)\s?=\s?('([^\']*)'|"([^\"]*)")/g;
|
||||
var regex_tagname;
|
||||
var regex_attribute;
|
||||
if (xml.match(/^<\?xml/)) {
|
||||
// When an XML document begins with an XML declaration
|
||||
// VersionInfo must appear.
|
||||
if (xml.search(new RegExp(XML10_VERSION_INFO)) == 5) {
|
||||
regex_tagname = XML10_TAGNAME_REGEXP;
|
||||
regex_attribute = XML10_ATTRIBUTE_REGEXP;
|
||||
} else if (xml.search(new RegExp(XML11_VERSION_INFO)) == 5) {
|
||||
regex_tagname = XML11_TAGNAME_REGEXP;
|
||||
regex_attribute = XML11_ATTRIBUTE_REGEXP;
|
||||
} else {
|
||||
// VersionInfo is missing, or unknown version number.
|
||||
// TODO : Fallback to XML 1.0 or XML 1.1, or just return null?
|
||||
alert('VersionInfo is missing, or unknown version number.');
|
||||
}
|
||||
} else {
|
||||
// When an XML declaration is missing it's an XML 1.0 document.
|
||||
regex_tagname = XML10_TAGNAME_REGEXP;
|
||||
regex_attribute = XML10_ATTRIBUTE_REGEXP;
|
||||
}
|
||||
|
||||
var xmldoc = new XDocument();
|
||||
var root = xmldoc;
|
||||
|
|
@ -100,13 +115,67 @@ function xmlParse(xml) {
|
|||
var parent = root;
|
||||
stack.push(parent);
|
||||
|
||||
// The token that delimits a section that contains markup as
|
||||
// content: CDATA or comments.
|
||||
var slurp = '';
|
||||
|
||||
var x = stringSplit(xml, '<');
|
||||
for (var i = 1; i < x.length; ++i) {
|
||||
var xx = stringSplit(x[i], '>');
|
||||
var tag = xx[0];
|
||||
var text = xmlResolveEntities(xx[1] || '');
|
||||
|
||||
if (tag.charAt(0) == '/') {
|
||||
if (slurp) {
|
||||
// In a "slurp" section (CDATA or comment): only check for the
|
||||
// end of the section, otherwise append the whole text.
|
||||
var end = x[i].indexOf(slurp);
|
||||
if (end != -1) {
|
||||
var data = x[i].substring(0, end);
|
||||
parent.nodeValue += '<' + data;
|
||||
stack.pop();
|
||||
parent = stack[stack.length-1];
|
||||
text = x[i].substring(end + slurp.length);
|
||||
slurp = '';
|
||||
} else {
|
||||
parent.nodeValue += '<' + x[i];
|
||||
text = null;
|
||||
}
|
||||
|
||||
} else if (tag.indexOf('![CDATA[') == 0) {
|
||||
var start = '![CDATA['.length;
|
||||
var end = x[i].indexOf(']]>');
|
||||
if (end != -1) {
|
||||
var data = x[i].substring(start, end);
|
||||
var node = domCreateCDATASection(xmldoc, data);
|
||||
domAppendChild(parent, node);
|
||||
} else {
|
||||
var data = x[i].substring(start);
|
||||
text = null;
|
||||
var node = domCreateCDATASection(xmldoc, data);
|
||||
domAppendChild(parent, node);
|
||||
parent = node;
|
||||
stack.push(node);
|
||||
slurp = ']]>';
|
||||
}
|
||||
|
||||
} else if (tag.indexOf('!--') == 0) {
|
||||
var start = '!--'.length;
|
||||
var end = x[i].indexOf('-->');
|
||||
if (end != -1) {
|
||||
var data = x[i].substring(start, end);
|
||||
var node = domCreateComment(xmldoc, data);
|
||||
domAppendChild(parent, node);
|
||||
} else {
|
||||
var data = x[i].substring(start);
|
||||
text = null;
|
||||
var node = domCreateComment(xmldoc, data);
|
||||
domAppendChild(parent, node);
|
||||
parent = node;
|
||||
stack.push(node);
|
||||
slurp = '-->';
|
||||
}
|
||||
|
||||
} else if (tag.charAt(0) == '/') {
|
||||
stack.pop();
|
||||
parent = stack[stack.length-1];
|
||||
|
||||
|
|
@ -117,43 +186,87 @@ function xmlParse(xml) {
|
|||
} else {
|
||||
var empty = tag.match(regex_empty);
|
||||
var tagname = regex_tagname.exec(tag)[1];
|
||||
var node = xmldoc.createElement(tagname);
|
||||
var node = domCreateElement(xmldoc, tagname);
|
||||
|
||||
var att;
|
||||
while (att = regex_attribute.exec(tag)) {
|
||||
var val = xmlResolveEntities(att[3] || att[4] || '');
|
||||
node.setAttribute(att[1], val);
|
||||
var val = xmlResolveEntities(att[5] || att[7] || '');
|
||||
domSetAttribute(node, att[1], val);
|
||||
}
|
||||
|
||||
if (empty) {
|
||||
parent.appendChild(node);
|
||||
} else {
|
||||
parent.appendChild(node);
|
||||
domAppendChild(parent, node);
|
||||
if (!empty) {
|
||||
parent = node;
|
||||
stack.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
if (text && parent != root) {
|
||||
parent.appendChild(xmldoc.createTextNode(text));
|
||||
domAppendChild(parent, domCreateTextNode(xmldoc, text));
|
||||
}
|
||||
}
|
||||
|
||||
Timer.end('xmlparse');
|
||||
return root;
|
||||
}
|
||||
|
||||
// Based on <http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/
|
||||
// core.html#ID-1950641247>
|
||||
var DOM_ELEMENT_NODE = 1;
|
||||
var DOM_ATTRIBUTE_NODE = 2;
|
||||
var DOM_TEXT_NODE = 3;
|
||||
var DOM_CDATA_SECTION_NODE = 4;
|
||||
var DOM_ENTITY_REFERENCE_NODE = 5;
|
||||
var DOM_ENTITY_NODE = 6;
|
||||
var DOM_PROCESSING_INSTRUCTION_NODE = 7;
|
||||
var DOM_COMMENT_NODE = 8;
|
||||
var DOM_DOCUMENT_NODE = 9;
|
||||
var DOM_DOCUMENT_TYPE_NODE = 10;
|
||||
var DOM_DOCUMENT_FRAGMENT_NODE = 11;
|
||||
var DOM_NOTATION_NODE = 12;
|
||||
|
||||
// Traverses the element nodes in the DOM section underneath the given
|
||||
// node and invokes the given callbacks as methods on every element
|
||||
// node encountered. Function opt_pre is invoked before a node's
|
||||
// children are traversed; opt_post is invoked after they are
|
||||
// traversed. Traversal will not be continued if a callback function
|
||||
// returns boolean false. NOTE(mesch): copied from
|
||||
// <//google3/maps/webmaps/javascript/dom.js>.
|
||||
function domTraverseElements(node, opt_pre, opt_post) {
|
||||
var ret;
|
||||
if (opt_pre) {
|
||||
ret = opt_pre.call(null, node);
|
||||
if (typeof ret == 'boolean' && !ret) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (var c = node.firstChild; c; c = c.nextSibling) {
|
||||
if (c.nodeType == DOM_ELEMENT_NODE) {
|
||||
ret = arguments.callee.call(this, c, opt_pre, opt_post);
|
||||
if (typeof ret == 'boolean' && !ret) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (opt_post) {
|
||||
ret = opt_post.call(null, node);
|
||||
if (typeof ret == 'boolean' && !ret) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Our W3C DOM Node implementation. Note we call it XNode because we
|
||||
// can't define the identifier Node. We do this mostly for Opera,
|
||||
// where we can't reuse the HTML DOM for parsing our own XML, and for
|
||||
// Safari, where it is too expensive to have the template processor
|
||||
// operate on native DOM nodes.
|
||||
function XNode(type, name, value, owner) {
|
||||
function XNode(type, name, opt_value, opt_owner) {
|
||||
this.attributes = [];
|
||||
this.childNodes = [];
|
||||
|
||||
XNode.init.call(this, type, name, value, owner);
|
||||
XNode.init.call(this, type, name, opt_value, opt_owner);
|
||||
}
|
||||
|
||||
// Don't call as method, use apply() or call().
|
||||
|
|
@ -274,6 +387,7 @@ XNode.prototype.replaceChild = function(newNode, oldNode) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
XNode.prototype.insertBefore = function(newNode, oldNode) {
|
||||
if (oldNode == newNode) {
|
||||
return;
|
||||
|
|
@ -312,6 +426,7 @@ XNode.prototype.insertBefore = function(newNode, oldNode) {
|
|||
this.childNodes = newChildren;
|
||||
}
|
||||
|
||||
|
||||
XNode.prototype.removeChild = function(node) {
|
||||
var newChildren = [];
|
||||
for (var i = 0; i < this.childNodes.length; ++i) {
|
||||
|
|
@ -349,7 +464,7 @@ XNode.prototype.setAttribute = function(name, value) {
|
|||
return;
|
||||
}
|
||||
}
|
||||
this.attributes.push(new XNode(DOM_ATTRIBUTE_NODE, name, value));
|
||||
this.attributes.push(XNode.create(DOM_ATTRIBUTE_NODE, name, value, this));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -362,6 +477,7 @@ XNode.prototype.getAttribute = function(name) {
|
|||
return null;
|
||||
}
|
||||
|
||||
|
||||
XNode.prototype.removeAttribute = function(name) {
|
||||
var a = [];
|
||||
for (var i = 0; i < this.attributes.length; ++i) {
|
||||
|
|
@ -373,8 +489,42 @@ XNode.prototype.removeAttribute = function(name) {
|
|||
}
|
||||
|
||||
|
||||
XNode.prototype.getElementsByTagName = function(name) {
|
||||
var ret = [];
|
||||
var self = this;
|
||||
if ("*" == name) {
|
||||
domTraverseElements(this, function(node) {
|
||||
if (self == node) return;
|
||||
ret.push(node);
|
||||
}, null);
|
||||
} else {
|
||||
domTraverseElements(this, function(node) {
|
||||
if (self == node) return;
|
||||
if (node.nodeName == name) {
|
||||
ret.push(node);
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
XNode.prototype.getElementById = function(id) {
|
||||
var ret = null;
|
||||
domTraverseElements(this, function(node) {
|
||||
if (node.getAttribute('id') == id) {
|
||||
ret = node;
|
||||
return false;
|
||||
}
|
||||
}, null);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
function XDocument() {
|
||||
XNode.call(this, DOM_DOCUMENT_NODE, '#document', null, this);
|
||||
// NOTE(mesch): Acocording to the DOM Spec, ownerDocument of a
|
||||
// document node is null.
|
||||
XNode.call(this, DOM_DOCUMENT_NODE, '#document', null, null);
|
||||
this.documentElement = null;
|
||||
}
|
||||
|
||||
|
|
@ -411,18 +561,6 @@ XDocument.prototype.createComment = function(data) {
|
|||
return XNode.create(DOM_COMMENT_NODE, '#comment', data, this);
|
||||
}
|
||||
|
||||
XNode.prototype.getElementsByTagName = function(name, list) {
|
||||
if (!list) {
|
||||
list = [];
|
||||
}
|
||||
|
||||
if (this.nodeName == name) {
|
||||
list.push(this);
|
||||
}
|
||||
|
||||
for (var i = 0; i < this.childNodes.length; ++i) {
|
||||
this.childNodes[i].getElementsByTagName(name, list);
|
||||
}
|
||||
|
||||
return list;
|
||||
XDocument.prototype.createCDATASection = function(data) {
|
||||
return XNode.create(DOM_CDATA_SECTION_NODE, '#cdata-section', data, this);
|
||||
}
|
||||
|
|
|
|||
2816
vendor/plugins/selenium-on-rails/selenium-core/xpath/javascript-xpath-0.1.11.js
vendored
Normal file
2816
vendor/plugins/selenium-on-rails/selenium-core/xpath/javascript-xpath-0.1.11.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,255 +0,0 @@
|
|||
// Copyright 2005 Google Inc.
|
||||
// All Rights Reserved
|
||||
//
|
||||
// Miscellania that support the ajaxslt implementation.
|
||||
//
|
||||
// Author: Steffen Meschkat <mesch@google.com>
|
||||
//
|
||||
|
||||
function el(i) {
|
||||
return document.getElementById(i);
|
||||
}
|
||||
|
||||
function px(x) {
|
||||
return x + 'px';
|
||||
}
|
||||
|
||||
// Split a string s at all occurrences of character c. This is like
|
||||
// the split() method of the string object, but IE omits empty
|
||||
// strings, which violates the invariant (s.split(x).join(x) == s).
|
||||
function stringSplit(s, c) {
|
||||
var a = s.indexOf(c);
|
||||
if (a == -1) {
|
||||
return [ s ];
|
||||
}
|
||||
|
||||
var parts = [];
|
||||
parts.push(s.substr(0,a));
|
||||
while (a != -1) {
|
||||
var a1 = s.indexOf(c, a + 1);
|
||||
if (a1 != -1) {
|
||||
parts.push(s.substr(a + 1, a1 - a - 1));
|
||||
} else {
|
||||
parts.push(s.substr(a + 1));
|
||||
}
|
||||
a = a1;
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
// Returns the text value if a node; for nodes without children this
|
||||
// is the nodeValue, for nodes with children this is the concatenation
|
||||
// of the value of all children.
|
||||
function xmlValue(node) {
|
||||
if (!node) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var ret = '';
|
||||
if (node.nodeType == DOM_TEXT_NODE ||
|
||||
node.nodeType == DOM_CDATA_SECTION_NODE ||
|
||||
node.nodeType == DOM_ATTRIBUTE_NODE) {
|
||||
ret += node.nodeValue;
|
||||
|
||||
} else if (node.nodeType == DOM_ELEMENT_NODE ||
|
||||
node.nodeType == DOM_DOCUMENT_NODE ||
|
||||
node.nodeType == DOM_DOCUMENT_FRAGMENT_NODE) {
|
||||
for (var i = 0; i < node.childNodes.length; ++i) {
|
||||
ret += arguments.callee(node.childNodes[i]);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Returns the representation of a node as XML text.
|
||||
function xmlText(node) {
|
||||
var ret = '';
|
||||
if (node.nodeType == DOM_TEXT_NODE) {
|
||||
ret += xmlEscapeText(node.nodeValue);
|
||||
|
||||
} else if (node.nodeType == DOM_ELEMENT_NODE) {
|
||||
ret += '<' + node.nodeName;
|
||||
for (var i = 0; i < node.attributes.length; ++i) {
|
||||
var a = node.attributes[i];
|
||||
if (a && a.nodeName && a.nodeValue) {
|
||||
ret += ' ' + a.nodeName;
|
||||
ret += '="' + xmlEscapeAttr(a.nodeValue) + '"';
|
||||
}
|
||||
}
|
||||
|
||||
if (node.childNodes.length == 0) {
|
||||
ret += '/>';
|
||||
|
||||
} else {
|
||||
ret += '>';
|
||||
for (var i = 0; i < node.childNodes.length; ++i) {
|
||||
ret += arguments.callee(node.childNodes[i]);
|
||||
}
|
||||
ret += '</' + node.nodeName + '>';
|
||||
}
|
||||
|
||||
} else if (node.nodeType == DOM_DOCUMENT_NODE ||
|
||||
node.nodeType == DOM_DOCUMENT_FRAGMENT_NODE) {
|
||||
for (var i = 0; i < node.childNodes.length; ++i) {
|
||||
ret += arguments.callee(node.childNodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Applies the given function to each element of the array.
|
||||
function mapExec(array, func) {
|
||||
for (var i = 0; i < array.length; ++i) {
|
||||
func(array[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns an array that contains the return value of the given
|
||||
// function applied to every element of the input array.
|
||||
function mapExpr(array, func) {
|
||||
var ret = [];
|
||||
for (var i = 0; i < array.length; ++i) {
|
||||
ret.push(func(array[i]));
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
// Reverses the given array in place.
|
||||
function reverseInplace(array) {
|
||||
for (var i = 0; i < array.length / 2; ++i) {
|
||||
var h = array[i];
|
||||
var ii = array.length - i - 1;
|
||||
array[i] = array[ii];
|
||||
array[ii] = h;
|
||||
}
|
||||
}
|
||||
|
||||
// Shallow-copies an array.
|
||||
function copyArray(dst, src) {
|
||||
for (var i = 0; i < src.length; ++i) {
|
||||
dst.push(src[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function assert(b) {
|
||||
if (!b) {
|
||||
throw 'assertion failed';
|
||||
}
|
||||
}
|
||||
|
||||
// Based on
|
||||
// <http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247>
|
||||
var DOM_ELEMENT_NODE = 1;
|
||||
var DOM_ATTRIBUTE_NODE = 2;
|
||||
var DOM_TEXT_NODE = 3;
|
||||
var DOM_CDATA_SECTION_NODE = 4;
|
||||
var DOM_ENTITY_REFERENCE_NODE = 5;
|
||||
var DOM_ENTITY_NODE = 6;
|
||||
var DOM_PROCESSING_INSTRUCTION_NODE = 7;
|
||||
var DOM_COMMENT_NODE = 8;
|
||||
var DOM_DOCUMENT_NODE = 9;
|
||||
var DOM_DOCUMENT_TYPE_NODE = 10;
|
||||
var DOM_DOCUMENT_FRAGMENT_NODE = 11;
|
||||
var DOM_NOTATION_NODE = 12;
|
||||
|
||||
|
||||
var xpathdebug = false; // trace xpath parsing
|
||||
var xsltdebug = false; // trace xslt processing
|
||||
|
||||
|
||||
// Escape XML special markup chracters: tag delimiter < > and entity
|
||||
// reference start delimiter &. The escaped string can be used in XML
|
||||
// text portions (i.e. between tags).
|
||||
function xmlEscapeText(s) {
|
||||
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
// Escape XML special markup characters: tag delimiter < > entity
|
||||
// reference start delimiter & and quotes ". The escaped string can be
|
||||
// used in double quoted XML attribute value portions (i.e. in
|
||||
// attributes within start tags).
|
||||
function xmlEscapeAttr(s) {
|
||||
return xmlEscapeText(s).replace(/\"/g, '"');
|
||||
}
|
||||
|
||||
// Escape markup in XML text, but don't touch entity references. The
|
||||
// escaped string can be used as XML text (i.e. between tags).
|
||||
function xmlEscapeTags(s) {
|
||||
return s.replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
// An implementation of the debug log.
|
||||
|
||||
var logging__ = false;
|
||||
|
||||
function Log() {};
|
||||
|
||||
Log.lines = [];
|
||||
|
||||
Log.write = function(s) {
|
||||
if (logging__) {
|
||||
this.lines.push(xmlEscapeText(s));
|
||||
this.show();
|
||||
}
|
||||
};
|
||||
|
||||
// Writes the given XML with every tag on a new line.
|
||||
Log.writeXML = function(xml) {
|
||||
if (logging__) {
|
||||
var s0 = xml.replace(/</g, '\n<');
|
||||
var s1 = xmlEscapeText(s0);
|
||||
var s2 = s1.replace(/\s*\n(\s|\n)*/g, '<br/>');
|
||||
this.lines.push(s2);
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
|
||||
// Writes without any escaping
|
||||
Log.writeRaw = function(s) {
|
||||
if (logging__) {
|
||||
this.lines.push(s);
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
|
||||
Log.clear = function() {
|
||||
if (logging__) {
|
||||
var l = this.div();
|
||||
l.innerHTML = '';
|
||||
this.lines = [];
|
||||
}
|
||||
}
|
||||
|
||||
Log.show = function() {
|
||||
var l = this.div();
|
||||
l.innerHTML += this.lines.join('<br/>') + '<br/>';
|
||||
this.lines = [];
|
||||
l.scrollTop = l.scrollHeight;
|
||||
}
|
||||
|
||||
Log.div = function() {
|
||||
var l = document.getElementById('log');
|
||||
if (!l) {
|
||||
l = document.createElement('div');
|
||||
l.id = 'log';
|
||||
l.style.position = 'absolute';
|
||||
l.style.right = '5px';
|
||||
l.style.top = '5px';
|
||||
l.style.width = '250px';
|
||||
l.style.height = '150px';
|
||||
l.style.overflow = 'auto';
|
||||
l.style.backgroundColor = '#f0f0f0';
|
||||
l.style.border = '1px solid gray';
|
||||
l.style.fontSize = '10px';
|
||||
l.style.padding = '5px';
|
||||
document.body.appendChild(l);
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
|
||||
function Timer() {}
|
||||
Timer.start = function() {}
|
||||
Timer.end = function() {}
|
||||
549
vendor/plugins/selenium-on-rails/selenium-core/xpath/util.js
vendored
Normal file
549
vendor/plugins/selenium-on-rails/selenium-core/xpath/util.js
vendored
Normal file
|
|
@ -0,0 +1,549 @@
|
|||
// Copyright 2005 Google
|
||||
//
|
||||
// Author: Steffen Meschkat <mesch@google.com>
|
||||
//
|
||||
// Miscellaneous utility and placeholder functions.
|
||||
|
||||
// Dummy implmentation for the logging functions. Replace by something
|
||||
// useful when you want to debug.
|
||||
function xpathLog(msg) {};
|
||||
function xsltLog(msg) {};
|
||||
function xsltLogXml(msg) {};
|
||||
|
||||
var ajaxsltIsIE6 = navigator.appVersion.match(/MSIE 6.0/);
|
||||
|
||||
// Throws an exception if false.
|
||||
function assert(b) {
|
||||
if (!b) {
|
||||
throw "Assertion failed";
|
||||
}
|
||||
}
|
||||
|
||||
// Splits a string s at all occurrences of character c. This is like
|
||||
// the split() method of the string object, but IE omits empty
|
||||
// strings, which violates the invariant (s.split(x).join(x) == s).
|
||||
function stringSplit(s, c) {
|
||||
var a = s.indexOf(c);
|
||||
if (a == -1) {
|
||||
return [ s ];
|
||||
}
|
||||
var parts = [];
|
||||
parts.push(s.substr(0,a));
|
||||
while (a != -1) {
|
||||
var a1 = s.indexOf(c, a + 1);
|
||||
if (a1 != -1) {
|
||||
parts.push(s.substr(a + 1, a1 - a - 1));
|
||||
} else {
|
||||
parts.push(s.substr(a + 1));
|
||||
}
|
||||
a = a1;
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
// The following function does what document.importNode(node, true)
|
||||
// would do for us here; however that method is broken in Safari/1.3,
|
||||
// so we have to emulate it.
|
||||
function xmlImportNode(doc, node) {
|
||||
if (node.nodeType == DOM_TEXT_NODE) {
|
||||
return domCreateTextNode(doc, node.nodeValue);
|
||||
|
||||
} else if (node.nodeType == DOM_CDATA_SECTION_NODE) {
|
||||
return domCreateCDATASection(doc, node.nodeValue);
|
||||
|
||||
} else if (node.nodeType == DOM_ELEMENT_NODE) {
|
||||
var newNode = domCreateElement(doc, node.nodeName);
|
||||
for (var i = 0; i < node.attributes.length; ++i) {
|
||||
var an = node.attributes[i];
|
||||
var name = an.nodeName;
|
||||
var value = an.nodeValue;
|
||||
domSetAttribute(newNode, name, value);
|
||||
}
|
||||
|
||||
for (var c = node.firstChild; c; c = c.nextSibling) {
|
||||
var cn = arguments.callee(doc, c);
|
||||
domAppendChild(newNode, cn);
|
||||
}
|
||||
|
||||
return newNode;
|
||||
|
||||
} else {
|
||||
return domCreateComment(doc, node.nodeName);
|
||||
}
|
||||
}
|
||||
|
||||
// A set data structure. It can also be used as a map (i.e. the keys
|
||||
// can have values other than 1), but we don't call it map because it
|
||||
// would be ambiguous in this context. Also, the map is iterable, so
|
||||
// we can use it to replace for-in loops over core javascript Objects.
|
||||
// For-in iteration breaks when Object.prototype is modified, which
|
||||
// some clients of the maps API do.
|
||||
//
|
||||
// NOTE(mesch): The set keys by the string value of its element, NOT
|
||||
// by the typed value. In particular, objects can't be used as keys.
|
||||
//
|
||||
// @constructor
|
||||
function Set() {
|
||||
this.keys = [];
|
||||
}
|
||||
|
||||
Set.prototype.size = function() {
|
||||
return this.keys.length;
|
||||
}
|
||||
|
||||
// Adds the entry to the set, ignoring if it is present.
|
||||
Set.prototype.add = function(key, opt_value) {
|
||||
var value = opt_value || 1;
|
||||
if (!this.contains(key)) {
|
||||
this[':' + key] = value;
|
||||
this.keys.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the entry in the set, adding if it is not yet present.
|
||||
Set.prototype.set = function(key, opt_value) {
|
||||
var value = opt_value || 1;
|
||||
if (!this.contains(key)) {
|
||||
this[':' + key] = value;
|
||||
this.keys.push(key);
|
||||
} else {
|
||||
this[':' + key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Increments the key's value by 1. This works around the fact that
|
||||
// numbers are always passed by value, never by reference, so that we
|
||||
// can't increment the value returned by get(), or the iterator
|
||||
// argument. Sets the key's value to 1 if it doesn't exist yet.
|
||||
Set.prototype.inc = function(key) {
|
||||
if (!this.contains(key)) {
|
||||
this[':' + key] = 1;
|
||||
this.keys.push(key);
|
||||
} else {
|
||||
this[':' + key]++;
|
||||
}
|
||||
}
|
||||
|
||||
Set.prototype.get = function(key) {
|
||||
if (this.contains(key)) {
|
||||
return this[':' + key];
|
||||
} else {
|
||||
var undefined;
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Removes the entry from the set.
|
||||
Set.prototype.remove = function(key) {
|
||||
if (this.contains(key)) {
|
||||
delete this[':' + key];
|
||||
removeFromArray(this.keys, key, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Tests if an entry is in the set.
|
||||
Set.prototype.contains = function(entry) {
|
||||
return typeof this[':' + entry] != 'undefined';
|
||||
}
|
||||
|
||||
// Gets a list of values in the set.
|
||||
Set.prototype.items = function() {
|
||||
var list = [];
|
||||
for (var i = 0; i < this.keys.length; ++i) {
|
||||
var k = this.keys[i];
|
||||
var v = this[':' + k];
|
||||
list.push(v);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
// Invokes function f for every key value pair in the set as a method
|
||||
// of the set.
|
||||
Set.prototype.map = function(f) {
|
||||
for (var i = 0; i < this.keys.length; ++i) {
|
||||
var k = this.keys[i];
|
||||
f.call(this, k, this[':' + k]);
|
||||
}
|
||||
}
|
||||
|
||||
Set.prototype.clear = function() {
|
||||
for (var i = 0; i < this.keys.length; ++i) {
|
||||
delete this[':' + this.keys[i]];
|
||||
}
|
||||
this.keys.length = 0;
|
||||
}
|
||||
|
||||
|
||||
// Applies the given function to each element of the array, preserving
|
||||
// this, and passing the index.
|
||||
function mapExec(array, func) {
|
||||
for (var i = 0; i < array.length; ++i) {
|
||||
func.call(this, array[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns an array that contains the return value of the given
|
||||
// function applied to every element of the input array.
|
||||
function mapExpr(array, func) {
|
||||
var ret = [];
|
||||
for (var i = 0; i < array.length; ++i) {
|
||||
ret.push(func(array[i]));
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
// Reverses the given array in place.
|
||||
function reverseInplace(array) {
|
||||
for (var i = 0; i < array.length / 2; ++i) {
|
||||
var h = array[i];
|
||||
var ii = array.length - i - 1;
|
||||
array[i] = array[ii];
|
||||
array[ii] = h;
|
||||
}
|
||||
}
|
||||
|
||||
// Removes value from array. Returns the number of instances of value
|
||||
// that were removed from array.
|
||||
function removeFromArray(array, value, opt_notype) {
|
||||
var shift = 0;
|
||||
for (var i = 0; i < array.length; ++i) {
|
||||
if (array[i] === value || (opt_notype && array[i] == value)) {
|
||||
array.splice(i--, 1);
|
||||
shift++;
|
||||
}
|
||||
}
|
||||
return shift;
|
||||
}
|
||||
|
||||
// Shallow-copies an array to the end of another array
|
||||
// Basically Array.concat, but works with other non-array collections
|
||||
function copyArray(dst, src) {
|
||||
if (!src) return;
|
||||
var dstLength = dst.length;
|
||||
for (var i = src.length - 1; i >= 0; --i) {
|
||||
dst[i+dstLength] = src[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an optimization for copying attribute lists in IE. IE includes many
|
||||
* extraneous properties in its DOM attribute lists, which take require
|
||||
* significant extra processing when evaluating attribute steps. With this
|
||||
* function, we ignore any such attributes that has an empty string value.
|
||||
*/
|
||||
function copyArrayIgnoringAttributesWithoutValue(dst, src)
|
||||
{
|
||||
if (!src) return;
|
||||
for (var i = src.length - 1; i >= 0; --i) {
|
||||
// this test will pass so long as the attribute has a non-empty string
|
||||
// value, even if that value is "false", "0", "undefined", etc.
|
||||
if (src[i].nodeValue) {
|
||||
dst.push(src[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the text value of a node; for nodes without children this
|
||||
// is the nodeValue, for nodes with children this is the concatenation
|
||||
// of the value of all children. Browser-specific optimizations are used by
|
||||
// default; they can be disabled by passing "true" in as the second parameter.
|
||||
function xmlValue(node, disallowBrowserSpecificOptimization) {
|
||||
if (!node) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var ret = '';
|
||||
if (node.nodeType == DOM_TEXT_NODE ||
|
||||
node.nodeType == DOM_CDATA_SECTION_NODE) {
|
||||
ret += node.nodeValue;
|
||||
|
||||
} else if (node.nodeType == DOM_ATTRIBUTE_NODE) {
|
||||
if (ajaxsltIsIE6) {
|
||||
ret += xmlValueIE6Hack(node);
|
||||
} else {
|
||||
ret += node.nodeValue;
|
||||
}
|
||||
} else if (node.nodeType == DOM_ELEMENT_NODE ||
|
||||
node.nodeType == DOM_DOCUMENT_NODE ||
|
||||
node.nodeType == DOM_DOCUMENT_FRAGMENT_NODE) {
|
||||
if (!disallowBrowserSpecificOptimization) {
|
||||
// IE, Safari, Opera, and friends
|
||||
var innerText = node.innerText;
|
||||
if (innerText != undefined) {
|
||||
return innerText;
|
||||
}
|
||||
// Firefox
|
||||
var textContent = node.textContent;
|
||||
if (textContent != undefined) {
|
||||
return textContent;
|
||||
}
|
||||
}
|
||||
// pobrecito!
|
||||
var len = node.childNodes.length;
|
||||
for (var i = 0; i < len; ++i) {
|
||||
ret += arguments.callee(node.childNodes[i]);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function xmlValueIE6Hack(node) {
|
||||
// Issue 19, IE6 mangles href attribute when it's a javascript: url
|
||||
var nodeName = node.nodeName;
|
||||
var nodeValue = node.nodeValue;
|
||||
if (nodeName.length != 4) return nodeValue;
|
||||
if (!/^href$/i.test(nodeName)) return nodeValue;
|
||||
if (!/^javascript:/.test(nodeValue)) return nodeValue;
|
||||
return unescape(nodeValue);
|
||||
}
|
||||
|
||||
// Returns the representation of a node as XML text.
|
||||
function xmlText(node, opt_cdata) {
|
||||
var buf = [];
|
||||
xmlTextR(node, buf, opt_cdata);
|
||||
return buf.join('');
|
||||
}
|
||||
|
||||
function xmlTextR(node, buf, cdata) {
|
||||
if (node.nodeType == DOM_TEXT_NODE) {
|
||||
buf.push(xmlEscapeText(node.nodeValue));
|
||||
|
||||
} else if (node.nodeType == DOM_CDATA_SECTION_NODE) {
|
||||
if (cdata) {
|
||||
buf.push(node.nodeValue);
|
||||
} else {
|
||||
buf.push('<![CDATA[' + node.nodeValue + ']]>');
|
||||
}
|
||||
|
||||
} else if (node.nodeType == DOM_COMMENT_NODE) {
|
||||
buf.push('<!--' + node.nodeValue + '-->');
|
||||
|
||||
} else if (node.nodeType == DOM_ELEMENT_NODE) {
|
||||
buf.push('<' + xmlFullNodeName(node));
|
||||
for (var i = 0; i < node.attributes.length; ++i) {
|
||||
var a = node.attributes[i];
|
||||
if (a && a.nodeName && a.nodeValue) {
|
||||
buf.push(' ' + xmlFullNodeName(a) + '="' +
|
||||
xmlEscapeAttr(a.nodeValue) + '"');
|
||||
}
|
||||
}
|
||||
|
||||
if (node.childNodes.length == 0) {
|
||||
buf.push('/>');
|
||||
} else {
|
||||
buf.push('>');
|
||||
for (var i = 0; i < node.childNodes.length; ++i) {
|
||||
arguments.callee(node.childNodes[i], buf, cdata);
|
||||
}
|
||||
buf.push('</' + xmlFullNodeName(node) + '>');
|
||||
}
|
||||
|
||||
} else if (node.nodeType == DOM_DOCUMENT_NODE ||
|
||||
node.nodeType == DOM_DOCUMENT_FRAGMENT_NODE) {
|
||||
for (var i = 0; i < node.childNodes.length; ++i) {
|
||||
arguments.callee(node.childNodes[i], buf, cdata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function xmlFullNodeName(n) {
|
||||
if (n.prefix && n.nodeName.indexOf(n.prefix + ':') != 0) {
|
||||
return n.prefix + ':' + n.nodeName;
|
||||
} else {
|
||||
return n.nodeName;
|
||||
}
|
||||
}
|
||||
|
||||
// Escape XML special markup chracters: tag delimiter < > and entity
|
||||
// reference start delimiter &. The escaped string can be used in XML
|
||||
// text portions (i.e. between tags).
|
||||
function xmlEscapeText(s) {
|
||||
return ('' + s).replace(/&/g, '&').replace(/</g, '<').
|
||||
replace(/>/g, '>');
|
||||
}
|
||||
|
||||
// Escape XML special markup characters: tag delimiter < > entity
|
||||
// reference start delimiter & and quotes ". The escaped string can be
|
||||
// used in double quoted XML attribute value portions (i.e. in
|
||||
// attributes within start tags).
|
||||
function xmlEscapeAttr(s) {
|
||||
return xmlEscapeText(s).replace(/\"/g, '"');
|
||||
}
|
||||
|
||||
// Escape markup in XML text, but don't touch entity references. The
|
||||
// escaped string can be used as XML text (i.e. between tags).
|
||||
function xmlEscapeTags(s) {
|
||||
return s.replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper function to access the owner document uniformly for document
|
||||
* and other nodes: for the document node, the owner document is the
|
||||
* node itself, for all others it's the ownerDocument property.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Document}
|
||||
*/
|
||||
function xmlOwnerDocument(node) {
|
||||
if (node.nodeType == DOM_DOCUMENT_NODE) {
|
||||
return node;
|
||||
} else {
|
||||
return node.ownerDocument;
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper around DOM methods so we can condense their invocations.
|
||||
function domGetAttribute(node, name) {
|
||||
return node.getAttribute(name);
|
||||
}
|
||||
|
||||
function domSetAttribute(node, name, value) {
|
||||
return node.setAttribute(name, value);
|
||||
}
|
||||
|
||||
function domRemoveAttribute(node, name) {
|
||||
return node.removeAttribute(name);
|
||||
}
|
||||
|
||||
function domAppendChild(node, child) {
|
||||
return node.appendChild(child);
|
||||
}
|
||||
|
||||
function domRemoveChild(node, child) {
|
||||
return node.removeChild(child);
|
||||
}
|
||||
|
||||
function domReplaceChild(node, newChild, oldChild) {
|
||||
return node.replaceChild(newChild, oldChild);
|
||||
}
|
||||
|
||||
function domInsertBefore(node, newChild, oldChild) {
|
||||
return node.insertBefore(newChild, oldChild);
|
||||
}
|
||||
|
||||
function domRemoveNode(node) {
|
||||
return domRemoveChild(node.parentNode, node);
|
||||
}
|
||||
|
||||
function domCreateTextNode(doc, text) {
|
||||
return doc.createTextNode(text);
|
||||
}
|
||||
|
||||
function domCreateElement(doc, name) {
|
||||
return doc.createElement(name);
|
||||
}
|
||||
|
||||
function domCreateAttribute(doc, name) {
|
||||
return doc.createAttribute(name);
|
||||
}
|
||||
|
||||
function domCreateCDATASection(doc, data) {
|
||||
return doc.createCDATASection(data);
|
||||
}
|
||||
|
||||
function domCreateComment(doc, text) {
|
||||
return doc.createComment(text);
|
||||
}
|
||||
|
||||
function domCreateDocumentFragment(doc) {
|
||||
return doc.createDocumentFragment();
|
||||
}
|
||||
|
||||
function domGetElementById(doc, id) {
|
||||
return doc.getElementById(id);
|
||||
}
|
||||
|
||||
// Same for window methods.
|
||||
function windowSetInterval(win, fun, time) {
|
||||
return win.setInterval(fun, time);
|
||||
}
|
||||
|
||||
function windowClearInterval(win, id) {
|
||||
return win.clearInterval(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape the special regular expression characters when the regular expression
|
||||
* is specified as a string.
|
||||
*
|
||||
* Based on: http://simonwillison.net/2006/Jan/20/escape/
|
||||
*/
|
||||
RegExp.escape = (function() {
|
||||
var specials = [
|
||||
'/', '.', '*', '+', '?', '|', '^', '$',
|
||||
'(', ')', '[', ']', '{', '}', '\\'
|
||||
];
|
||||
|
||||
var sRE = new RegExp(
|
||||
'(\\' + specials.join('|\\') + ')', 'g'
|
||||
);
|
||||
|
||||
return function(text) {
|
||||
return text.replace(sRE, '\\$1');
|
||||
}
|
||||
})();
|
||||
|
||||
/**
|
||||
* Determines whether a predicate expression contains a "positional selector".
|
||||
* A positional selector filters nodes from the nodelist input based on their
|
||||
* position within that list. When such selectors are encountered, the
|
||||
* evaluation of the predicate cannot be depth-first, because the positional
|
||||
* selector may be based on the result of evaluating predicates that precede
|
||||
* it.
|
||||
*/
|
||||
function predicateExprHasPositionalSelector(expr, isRecursiveCall) {
|
||||
if (!expr) {
|
||||
return false;
|
||||
}
|
||||
if (!isRecursiveCall && exprReturnsNumberValue(expr)) {
|
||||
// this is a "proximity position"-based predicate
|
||||
return true;
|
||||
}
|
||||
if (expr instanceof FunctionCallExpr) {
|
||||
var value = expr.name.value;
|
||||
return (value == 'last' || value == 'position');
|
||||
}
|
||||
if (expr instanceof BinaryExpr) {
|
||||
return (
|
||||
predicateExprHasPositionalSelector(expr.expr1, true) ||
|
||||
predicateExprHasPositionalSelector(expr.expr2, true));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function exprReturnsNumberValue(expr) {
|
||||
if (expr instanceof FunctionCallExpr) {
|
||||
var isMember = {
|
||||
last: true
|
||||
, position: true
|
||||
, count: true
|
||||
, 'string-length': true
|
||||
, number: true
|
||||
, sum: true
|
||||
, floor: true
|
||||
, ceiling: true
|
||||
, round: true
|
||||
};
|
||||
return isMember[expr.name.value];
|
||||
}
|
||||
else if (expr instanceof UnaryMinusExpr) {
|
||||
return true;
|
||||
}
|
||||
else if (expr instanceof BinaryExpr) {
|
||||
var isMember = {
|
||||
'+': true
|
||||
, '-': true
|
||||
, '*': true
|
||||
, mod: true
|
||||
, div: true
|
||||
};
|
||||
return isMember[expr.op.value];
|
||||
}
|
||||
else if (expr instanceof NumberExpr) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
149
vendor/plugins/selenium-on-rails/selenium-core/xpath/xmltoken.js
vendored
Normal file
149
vendor/plugins/selenium-on-rails/selenium-core/xpath/xmltoken.js
vendored
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
// Copyright 2006 Google Inc.
|
||||
// All Rights Reserved
|
||||
//
|
||||
// Defines regular expression patterns to extract XML tokens from string.
|
||||
// See <http://www.w3.org/TR/REC-xml/#sec-common-syn>,
|
||||
// <http://www.w3.org/TR/xml11/#sec-common-syn> and
|
||||
// <http://www.w3.org/TR/REC-xml-names/#NT-NCName> for the specifications.
|
||||
//
|
||||
// Author: Junji Takagi <jtakagi@google.com>
|
||||
|
||||
// Detect whether RegExp supports Unicode characters or not.
|
||||
|
||||
var REGEXP_UNICODE = function() {
|
||||
var tests = [' ', '\u0120', -1, // Konquerer 3.4.0 fails here.
|
||||
'!', '\u0120', -1,
|
||||
'\u0120', '\u0120', 0,
|
||||
'\u0121', '\u0120', -1,
|
||||
'\u0121', '\u0120|\u0121', 0,
|
||||
'\u0122', '\u0120|\u0121', -1,
|
||||
'\u0120', '[\u0120]', 0, // Safari 2.0.3 fails here.
|
||||
'\u0121', '[\u0120]', -1,
|
||||
'\u0121', '[\u0120\u0121]', 0, // Safari 2.0.3 fails here.
|
||||
'\u0122', '[\u0120\u0121]', -1,
|
||||
'\u0121', '[\u0120-\u0121]', 0, // Safari 2.0.3 fails here.
|
||||
'\u0122', '[\u0120-\u0121]', -1];
|
||||
for (var i = 0; i < tests.length; i += 3) {
|
||||
if (tests[i].search(new RegExp(tests[i + 1])) != tests[i + 2]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}();
|
||||
|
||||
// Common tokens in XML 1.0 and XML 1.1.
|
||||
|
||||
var XML_S = '[ \t\r\n]+';
|
||||
var XML_EQ = '(' + XML_S + ')?=(' + XML_S + ')?';
|
||||
var XML_CHAR_REF = '&#[0-9]+;|&#x[0-9a-fA-F]+;';
|
||||
|
||||
// XML 1.0 tokens.
|
||||
|
||||
var XML10_VERSION_INFO = XML_S + 'version' + XML_EQ + '("1\\.0"|' + "'1\\.0')";
|
||||
var XML10_BASE_CHAR = (REGEXP_UNICODE) ?
|
||||
'\u0041-\u005a\u0061-\u007a\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff' +
|
||||
'\u0100-\u0131\u0134-\u013e\u0141-\u0148\u014a-\u017e\u0180-\u01c3' +
|
||||
'\u01cd-\u01f0\u01f4-\u01f5\u01fa-\u0217\u0250-\u02a8\u02bb-\u02c1\u0386' +
|
||||
'\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03ce\u03d0-\u03d6\u03da\u03dc' +
|
||||
'\u03de\u03e0\u03e2-\u03f3\u0401-\u040c\u040e-\u044f\u0451-\u045c' +
|
||||
'\u045e-\u0481\u0490-\u04c4\u04c7-\u04c8\u04cb-\u04cc\u04d0-\u04eb' +
|
||||
'\u04ee-\u04f5\u04f8-\u04f9\u0531-\u0556\u0559\u0561-\u0586\u05d0-\u05ea' +
|
||||
'\u05f0-\u05f2\u0621-\u063a\u0641-\u064a\u0671-\u06b7\u06ba-\u06be' +
|
||||
'\u06c0-\u06ce\u06d0-\u06d3\u06d5\u06e5-\u06e6\u0905-\u0939\u093d' +
|
||||
'\u0958-\u0961\u0985-\u098c\u098f-\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2' +
|
||||
'\u09b6-\u09b9\u09dc-\u09dd\u09df-\u09e1\u09f0-\u09f1\u0a05-\u0a0a' +
|
||||
'\u0a0f-\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32-\u0a33\u0a35-\u0a36' +
|
||||
'\u0a38-\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8b\u0a8d' +
|
||||
'\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2-\u0ab3\u0ab5-\u0ab9' +
|
||||
'\u0abd\u0ae0\u0b05-\u0b0c\u0b0f-\u0b10\u0b13-\u0b28\u0b2a-\u0b30' +
|
||||
'\u0b32-\u0b33\u0b36-\u0b39\u0b3d\u0b5c-\u0b5d\u0b5f-\u0b61\u0b85-\u0b8a' +
|
||||
'\u0b8e-\u0b90\u0b92-\u0b95\u0b99-\u0b9a\u0b9c\u0b9e-\u0b9f\u0ba3-\u0ba4' +
|
||||
'\u0ba8-\u0baa\u0bae-\u0bb5\u0bb7-\u0bb9\u0c05-\u0c0c\u0c0e-\u0c10' +
|
||||
'\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c60-\u0c61\u0c85-\u0c8c' +
|
||||
'\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cde\u0ce0-\u0ce1' +
|
||||
'\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d28\u0d2a-\u0d39\u0d60-\u0d61' +
|
||||
'\u0e01-\u0e2e\u0e30\u0e32-\u0e33\u0e40-\u0e45\u0e81-\u0e82\u0e84' +
|
||||
'\u0e87-\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5' +
|
||||
'\u0ea7\u0eaa-\u0eab\u0ead-\u0eae\u0eb0\u0eb2-\u0eb3\u0ebd\u0ec0-\u0ec4' +
|
||||
'\u0f40-\u0f47\u0f49-\u0f69\u10a0-\u10c5\u10d0-\u10f6\u1100\u1102-\u1103' +
|
||||
'\u1105-\u1107\u1109\u110b-\u110c\u110e-\u1112\u113c\u113e\u1140\u114c' +
|
||||
'\u114e\u1150\u1154-\u1155\u1159\u115f-\u1161\u1163\u1165\u1167\u1169' +
|
||||
'\u116d-\u116e\u1172-\u1173\u1175\u119e\u11a8\u11ab\u11ae-\u11af' +
|
||||
'\u11b7-\u11b8\u11ba\u11bc-\u11c2\u11eb\u11f0\u11f9\u1e00-\u1e9b' +
|
||||
'\u1ea0-\u1ef9\u1f00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d' +
|
||||
'\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc' +
|
||||
'\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec' +
|
||||
'\u1ff2-\u1ff4\u1ff6-\u1ffc\u2126\u212a-\u212b\u212e\u2180-\u2182' +
|
||||
'\u3041-\u3094\u30a1-\u30fa\u3105-\u312c\uac00-\ud7a3' :
|
||||
'A-Za-z';
|
||||
var XML10_IDEOGRAPHIC = (REGEXP_UNICODE) ?
|
||||
'\u4e00-\u9fa5\u3007\u3021-\u3029' :
|
||||
'';
|
||||
var XML10_COMBINING_CHAR = (REGEXP_UNICODE) ?
|
||||
'\u0300-\u0345\u0360-\u0361\u0483-\u0486\u0591-\u05a1\u05a3-\u05b9' +
|
||||
'\u05bb-\u05bd\u05bf\u05c1-\u05c2\u05c4\u064b-\u0652\u0670\u06d6-\u06dc' +
|
||||
'\u06dd-\u06df\u06e0-\u06e4\u06e7-\u06e8\u06ea-\u06ed\u0901-\u0903\u093c' +
|
||||
'\u093e-\u094c\u094d\u0951-\u0954\u0962-\u0963\u0981-\u0983\u09bc\u09be' +
|
||||
'\u09bf\u09c0-\u09c4\u09c7-\u09c8\u09cb-\u09cd\u09d7\u09e2-\u09e3\u0a02' +
|
||||
'\u0a3c\u0a3e\u0a3f\u0a40-\u0a42\u0a47-\u0a48\u0a4b-\u0a4d\u0a70-\u0a71' +
|
||||
'\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0b01-\u0b03' +
|
||||
'\u0b3c\u0b3e-\u0b43\u0b47-\u0b48\u0b4b-\u0b4d\u0b56-\u0b57\u0b82-\u0b83' +
|
||||
'\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0c01-\u0c03\u0c3e-\u0c44' +
|
||||
'\u0c46-\u0c48\u0c4a-\u0c4d\u0c55-\u0c56\u0c82-\u0c83\u0cbe-\u0cc4' +
|
||||
'\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5-\u0cd6\u0d02-\u0d03\u0d3e-\u0d43' +
|
||||
'\u0d46-\u0d48\u0d4a-\u0d4d\u0d57\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1' +
|
||||
'\u0eb4-\u0eb9\u0ebb-\u0ebc\u0ec8-\u0ecd\u0f18-\u0f19\u0f35\u0f37\u0f39' +
|
||||
'\u0f3e\u0f3f\u0f71-\u0f84\u0f86-\u0f8b\u0f90-\u0f95\u0f97\u0f99-\u0fad' +
|
||||
'\u0fb1-\u0fb7\u0fb9\u20d0-\u20dc\u20e1\u302a-\u302f\u3099\u309a' :
|
||||
'';
|
||||
var XML10_DIGIT = (REGEXP_UNICODE) ?
|
||||
'\u0030-\u0039\u0660-\u0669\u06f0-\u06f9\u0966-\u096f\u09e6-\u09ef' +
|
||||
'\u0a66-\u0a6f\u0ae6-\u0aef\u0b66-\u0b6f\u0be7-\u0bef\u0c66-\u0c6f' +
|
||||
'\u0ce6-\u0cef\u0d66-\u0d6f\u0e50-\u0e59\u0ed0-\u0ed9\u0f20-\u0f29' :
|
||||
'0-9';
|
||||
var XML10_EXTENDER = (REGEXP_UNICODE) ?
|
||||
'\u00b7\u02d0\u02d1\u0387\u0640\u0e46\u0ec6\u3005\u3031-\u3035' +
|
||||
'\u309d-\u309e\u30fc-\u30fe' :
|
||||
'';
|
||||
var XML10_LETTER = XML10_BASE_CHAR + XML10_IDEOGRAPHIC;
|
||||
var XML10_NAME_CHAR = XML10_LETTER + XML10_DIGIT + '\\._:' +
|
||||
XML10_COMBINING_CHAR + XML10_EXTENDER + '-';
|
||||
var XML10_NAME = '[' + XML10_LETTER + '_:][' + XML10_NAME_CHAR + ']*';
|
||||
|
||||
var XML10_ENTITY_REF = '&' + XML10_NAME + ';';
|
||||
var XML10_REFERENCE = XML10_ENTITY_REF + '|' + XML_CHAR_REF;
|
||||
var XML10_ATT_VALUE = '"(([^<&"]|' + XML10_REFERENCE + ')*)"|' +
|
||||
"'(([^<&']|" + XML10_REFERENCE + ")*)'";
|
||||
var XML10_ATTRIBUTE =
|
||||
'(' + XML10_NAME + ')' + XML_EQ + '(' + XML10_ATT_VALUE + ')';
|
||||
|
||||
// XML 1.1 tokens.
|
||||
// TODO(jtakagi): NameStartChar also includes \u10000-\ueffff.
|
||||
// ECMAScript Language Specifiction defines UnicodeEscapeSequence as
|
||||
// "\u HexDigit HexDigit HexDigit HexDigit" and we may need to use
|
||||
// surrogate pairs, but any browser doesn't support surrogate paris in
|
||||
// character classes of regular expression, so avoid including them for now.
|
||||
|
||||
var XML11_VERSION_INFO = XML_S + 'version' + XML_EQ + '("1\\.1"|' + "'1\\.1')";
|
||||
var XML11_NAME_START_CHAR = (REGEXP_UNICODE) ?
|
||||
':A-Z_a-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff\u0370-\u037d' +
|
||||
'\u037f-\u1fff\u200c-\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff' +
|
||||
'\uf900-\ufdcf\ufdf0-\ufffd' :
|
||||
':A-Z_a-z';
|
||||
var XML11_NAME_CHAR = XML11_NAME_START_CHAR +
|
||||
((REGEXP_UNICODE) ? '\\.0-9\u00b7\u0300-\u036f\u203f-\u2040-' : '\\.0-9-');
|
||||
var XML11_NAME = '[' + XML11_NAME_START_CHAR + '][' + XML11_NAME_CHAR + ']*';
|
||||
|
||||
var XML11_ENTITY_REF = '&' + XML11_NAME + ';';
|
||||
var XML11_REFERENCE = XML11_ENTITY_REF + '|' + XML_CHAR_REF;
|
||||
var XML11_ATT_VALUE = '"(([^<&"]|' + XML11_REFERENCE + ')*)"|' +
|
||||
"'(([^<&']|" + XML11_REFERENCE + ")*)'";
|
||||
var XML11_ATTRIBUTE =
|
||||
'(' + XML11_NAME + ')' + XML_EQ + '(' + XML11_ATT_VALUE + ')';
|
||||
|
||||
// XML Namespace tokens.
|
||||
// Used in XML parser and XPath parser.
|
||||
|
||||
var XML_NC_NAME_CHAR = XML10_LETTER + XML10_DIGIT + '\\._' +
|
||||
XML10_COMBINING_CHAR + XML10_EXTENDER + '-';
|
||||
var XML_NC_NAME = '[' + XML10_LETTER + '_][' + XML_NC_NAME_CHAR + ']*';
|
||||
|
|
@ -31,9 +31,6 @@
|
|||
// that are used internally follow after them.
|
||||
//
|
||||
//
|
||||
// TODO(mesch): add jsdoc comments. Use more coherent naming.
|
||||
//
|
||||
//
|
||||
// Author: Steffen Meschkat <mesch@google.com>
|
||||
|
||||
|
||||
|
|
@ -44,16 +41,12 @@
|
|||
// expression context.
|
||||
|
||||
function xpathParse(expr) {
|
||||
if (xpathdebug) {
|
||||
Log.write('XPath parse ' + expr);
|
||||
}
|
||||
xpathLog('parse ' + expr);
|
||||
xpathParseInit();
|
||||
|
||||
var cached = xpathCacheLookup(expr);
|
||||
if (cached) {
|
||||
if (xpathdebug) {
|
||||
Log.write(' ... cached');
|
||||
}
|
||||
xpathLog(' ... cached');
|
||||
return cached;
|
||||
}
|
||||
|
||||
|
|
@ -66,25 +59,18 @@ function xpathParse(expr) {
|
|||
if (expr.match(/^(\$|@)?\w+$/i)) {
|
||||
var ret = makeSimpleExpr(expr);
|
||||
xpathParseCache[expr] = ret;
|
||||
if (xpathdebug) {
|
||||
Log.write(' ... simple');
|
||||
}
|
||||
xpathLog(' ... simple');
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (expr.match(/^\w+(\/\w+)*$/i)) {
|
||||
var ret = makeSimpleExpr2(expr);
|
||||
xpathParseCache[expr] = ret;
|
||||
if (xpathdebug) {
|
||||
Log.write(' ... simple 2');
|
||||
}
|
||||
xpathLog(' ... simple 2');
|
||||
return ret;
|
||||
}
|
||||
|
||||
var cachekey = expr; // expr is modified during parse
|
||||
if (xpathdebug) {
|
||||
Timer.start('XPath parse', cachekey);
|
||||
}
|
||||
|
||||
var stack = [];
|
||||
var ahead = null;
|
||||
|
|
@ -143,9 +129,7 @@ function xpathParse(expr) {
|
|||
|
||||
if (rule) {
|
||||
expr = expr.substr(match.length);
|
||||
if (xpathdebug) {
|
||||
Log.write('token: ' + match + ' -- ' + rule.label);
|
||||
}
|
||||
xpathLog('token: ' + match + ' -- ' + rule.label);
|
||||
ahead = {
|
||||
tag: rule,
|
||||
match: match,
|
||||
|
|
@ -154,24 +138,19 @@ function xpathParse(expr) {
|
|||
};
|
||||
|
||||
} else {
|
||||
if (xpathdebug) {
|
||||
Log.write('DONE');
|
||||
}
|
||||
xpathLog('DONE');
|
||||
done = true;
|
||||
}
|
||||
|
||||
while (xpathReduce(stack, ahead)) {
|
||||
reduce_count++;
|
||||
if (xpathdebug) {
|
||||
Log.write('stack: ' + stackToString(stack));
|
||||
}
|
||||
xpathLog('stack: ' + stackToString(stack));
|
||||
}
|
||||
}
|
||||
|
||||
if (xpathdebug) {
|
||||
Log.write(stackToString(stack));
|
||||
}
|
||||
xpathLog('stack: ' + stackToString(stack));
|
||||
|
||||
// DGF any valid XPath should "reduce" to a single Expr token
|
||||
if (stack.length != 1) {
|
||||
throw 'XPath parse error ' + cachekey + ':\n' + stackToString(stack);
|
||||
}
|
||||
|
|
@ -179,14 +158,8 @@ function xpathParse(expr) {
|
|||
var result = stack[0].expr;
|
||||
xpathParseCache[cachekey] = result;
|
||||
|
||||
if (xpathdebug) {
|
||||
Timer.end('XPath parse', cachekey);
|
||||
}
|
||||
|
||||
if (xpathdebug) {
|
||||
Log.write('XPath parse: ' + parse_count + ' / ' +
|
||||
xpathLog('XPath parse: ' + parse_count + ' / ' +
|
||||
lexer_count + ' / ' + reduce_count);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -197,6 +170,32 @@ function xpathCacheLookup(expr) {
|
|||
return xpathParseCache[expr];
|
||||
}
|
||||
|
||||
/*DGF xpathReduce is where the magic happens in this parser.
|
||||
Skim down to the bottom of this file to find the table of
|
||||
grammatical rules and precedence numbers, "The productions of the grammar".
|
||||
|
||||
The idea here
|
||||
is that we want to take a stack of tokens and apply
|
||||
grammatical rules to them, "reducing" them to higher-level
|
||||
tokens. Ultimately, any valid XPath should reduce to exactly one
|
||||
"Expr" token.
|
||||
|
||||
Reduce too early or too late and you'll have two tokens that can't reduce
|
||||
to single Expr. For example, you may hastily reduce a qname that
|
||||
should name a function, incorrectly treating it as a tag name.
|
||||
Or you may reduce too late, accidentally reducing the last part of the
|
||||
XPath into a top-level "Expr" that won't reduce with earlier parts of
|
||||
the XPath.
|
||||
|
||||
A "cand" is a grammatical rule candidate, with a given precedence
|
||||
number. "ahead" is the upcoming token, which also has a precedence
|
||||
number. If the token has a higher precedence number than
|
||||
the rule candidate, we'll "shift" the token onto the token stack,
|
||||
instead of immediately applying the rule candidate.
|
||||
|
||||
Some tokens have left associativity, in which case we shift when they
|
||||
have LOWER precedence than the candidate.
|
||||
*/
|
||||
function xpathReduce(stack, ahead) {
|
||||
var cand = null;
|
||||
|
||||
|
|
@ -228,14 +227,13 @@ function xpathReduce(stack, ahead) {
|
|||
stack.pop();
|
||||
}
|
||||
|
||||
if (xpathdebug) {
|
||||
Log.write('reduce ' + cand.tag.label + ' ' + cand.prec +
|
||||
xpathLog('reduce ' + cand.tag.label + ' ' + cand.prec +
|
||||
' ahead ' + (ahead ? ahead.tag.label + ' ' + ahead.prec +
|
||||
(ahead.tag.left ? ' left' : '')
|
||||
: ' none '));
|
||||
}
|
||||
|
||||
var matchexpr = mapExpr(cand.match, function(m) { return m.expr; });
|
||||
xpathLog('going to apply ' + cand.rule[3].toString());
|
||||
cand.expr = cand.rule[3].apply(null, matchexpr);
|
||||
|
||||
stack.push(cand);
|
||||
|
|
@ -243,12 +241,10 @@ function xpathReduce(stack, ahead) {
|
|||
|
||||
} else {
|
||||
if (ahead) {
|
||||
if (xpathdebug) {
|
||||
Log.write('shift ' + ahead.tag.label + ' ' + ahead.prec +
|
||||
xpathLog('shift ' + ahead.tag.label + ' ' + ahead.prec +
|
||||
(ahead.tag.left ? ' left' : '') +
|
||||
' over ' + (cand ? cand.tag.label + ' ' +
|
||||
cand.prec : ' none'));
|
||||
}
|
||||
stack.push(ahead);
|
||||
}
|
||||
ret = false;
|
||||
|
|
@ -398,29 +394,68 @@ function stackToString(stack) {
|
|||
//
|
||||
// getVariable(name) -- what the name says.
|
||||
//
|
||||
// setNode(node, position) -- sets the context to the new node and
|
||||
// its corresponding position. Needed to implement scoping rules for
|
||||
// variables in XPath. (A variable is visible to all subsequent
|
||||
// siblings, not only to its children.)
|
||||
// setNode(position) -- sets the context to the node at the given
|
||||
// position. Needed to implement scoping rules for variables in
|
||||
// XPath. (A variable is visible to all subsequent siblings, not
|
||||
// only to its children.)
|
||||
//
|
||||
// set/isCaseInsensitive -- specifies whether node name tests should
|
||||
// be case sensitive. If you're executing xpaths against a regular
|
||||
// HTML DOM, you probably don't want case-sensitivity, because
|
||||
// browsers tend to disagree about whether elements & attributes
|
||||
// should be upper/lower case. If you're running xpaths in an
|
||||
// XSLT instance, you probably DO want case sensitivity, as per the
|
||||
// XSL spec.
|
||||
|
||||
function ExprContext(node, position, nodelist, parent) {
|
||||
function ExprContext(node, opt_position, opt_nodelist, opt_parent,
|
||||
opt_caseInsensitive, opt_ignoreAttributesWithoutValue,
|
||||
opt_returnOnFirstMatch)
|
||||
{
|
||||
this.node = node;
|
||||
this.position = position || 0;
|
||||
this.nodelist = nodelist || [ node ];
|
||||
this.position = opt_position || 0;
|
||||
this.nodelist = opt_nodelist || [ node ];
|
||||
this.variables = {};
|
||||
this.parent = parent || null;
|
||||
this.root = parent ? parent.root : node.ownerDocument;
|
||||
this.parent = opt_parent || null;
|
||||
this.caseInsensitive = opt_caseInsensitive || false;
|
||||
this.ignoreAttributesWithoutValue = opt_ignoreAttributesWithoutValue || false;
|
||||
this.returnOnFirstMatch = opt_returnOnFirstMatch || false;
|
||||
if (opt_parent) {
|
||||
this.root = opt_parent.root;
|
||||
} else if (this.node.nodeType == DOM_DOCUMENT_NODE) {
|
||||
// NOTE(mesch): DOM Spec stipulates that the ownerDocument of a
|
||||
// document is null. Our root, however is the document that we are
|
||||
// processing, so the initial context is created from its document
|
||||
// node, which case we must handle here explcitly.
|
||||
this.root = node;
|
||||
} else {
|
||||
this.root = node.ownerDocument;
|
||||
}
|
||||
}
|
||||
|
||||
ExprContext.prototype.clone = function(node, position, nodelist) {
|
||||
return new
|
||||
ExprContext(node || this.node,
|
||||
typeof position != 'undefined' ? position : this.position,
|
||||
nodelist || this.nodelist, this);
|
||||
ExprContext.prototype.clone = function(opt_node, opt_position, opt_nodelist) {
|
||||
return new ExprContext(
|
||||
opt_node || this.node,
|
||||
typeof opt_position != 'undefined' ? opt_position : this.position,
|
||||
opt_nodelist || this.nodelist, this, this.caseInsensitive,
|
||||
this.ignoreAttributesWithoutValue, this.returnOnFirstMatch);
|
||||
};
|
||||
|
||||
ExprContext.prototype.setVariable = function(name, value) {
|
||||
if (value instanceof StringValue || value instanceof BooleanValue ||
|
||||
value instanceof NumberValue || value instanceof NodeSetValue) {
|
||||
this.variables[name] = value;
|
||||
return;
|
||||
}
|
||||
if ('true' === value) {
|
||||
this.variables[name] = new BooleanValue(true);
|
||||
} else if ('false' === value) {
|
||||
this.variables[name] = new BooleanValue(false);
|
||||
} else if (TOK_NUMBER.re.test(value)) {
|
||||
this.variables[name] = new NumberValue(value);
|
||||
} else {
|
||||
// DGF What if it's null?
|
||||
this.variables[name] = new StringValue(value);
|
||||
}
|
||||
};
|
||||
|
||||
ExprContext.prototype.getVariable = function(name) {
|
||||
|
|
@ -433,13 +468,40 @@ ExprContext.prototype.getVariable = function(name) {
|
|||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ExprContext.prototype.setNode = function(node, position) {
|
||||
this.node = node;
|
||||
ExprContext.prototype.setNode = function(position) {
|
||||
this.node = this.nodelist[position];
|
||||
this.position = position;
|
||||
}
|
||||
};
|
||||
|
||||
ExprContext.prototype.contextSize = function() {
|
||||
return this.nodelist.length;
|
||||
};
|
||||
|
||||
ExprContext.prototype.isCaseInsensitive = function() {
|
||||
return this.caseInsensitive;
|
||||
};
|
||||
|
||||
ExprContext.prototype.setCaseInsensitive = function(caseInsensitive) {
|
||||
return this.caseInsensitive = caseInsensitive;
|
||||
};
|
||||
|
||||
ExprContext.prototype.isIgnoreAttributesWithoutValue = function() {
|
||||
return this.ignoreAttributesWithoutValue;
|
||||
};
|
||||
|
||||
ExprContext.prototype.setIgnoreAttributesWithoutValue = function(ignore) {
|
||||
return this.ignoreAttributesWithoutValue = ignore;
|
||||
};
|
||||
|
||||
ExprContext.prototype.isReturnOnFirstMatch = function() {
|
||||
return this.returnOnFirstMatch;
|
||||
};
|
||||
|
||||
ExprContext.prototype.setReturnOnFirstMatch = function(returnOnFirstMatch) {
|
||||
return this.returnOnFirstMatch = returnOnFirstMatch;
|
||||
};
|
||||
|
||||
// XPath expression values. They are what XPath expressions evaluate
|
||||
// to. Strangely, the different value types are not specified in the
|
||||
|
|
@ -492,7 +554,7 @@ StringValue.prototype.numberValue = function() {
|
|||
}
|
||||
|
||||
StringValue.prototype.nodeSetValue = function() {
|
||||
throw this + ' ' + Error().stack;
|
||||
throw this;
|
||||
}
|
||||
|
||||
function BooleanValue(value) {
|
||||
|
|
@ -513,7 +575,7 @@ BooleanValue.prototype.numberValue = function() {
|
|||
}
|
||||
|
||||
BooleanValue.prototype.nodeSetValue = function() {
|
||||
throw this + ' ' + Error().stack;
|
||||
throw this;
|
||||
}
|
||||
|
||||
function NumberValue(value) {
|
||||
|
|
@ -534,7 +596,7 @@ NumberValue.prototype.numberValue = function() {
|
|||
}
|
||||
|
||||
NumberValue.prototype.nodeSetValue = function() {
|
||||
throw this + ' ' + Error().stack;
|
||||
throw this;
|
||||
}
|
||||
|
||||
function NodeSetValue(value) {
|
||||
|
|
@ -593,17 +655,49 @@ function LocationExpr() {
|
|||
}
|
||||
|
||||
LocationExpr.prototype.appendStep = function(s) {
|
||||
var combinedStep = this._combineSteps(this.steps[this.steps.length-1], s);
|
||||
if (combinedStep) {
|
||||
this.steps[this.steps.length-1] = combinedStep;
|
||||
} else {
|
||||
this.steps.push(s);
|
||||
}
|
||||
}
|
||||
|
||||
LocationExpr.prototype.prependStep = function(s) {
|
||||
var steps0 = this.steps;
|
||||
this.steps = [ s ];
|
||||
for (var i = 0; i < steps0.length; ++i) {
|
||||
this.steps.push(steps0[i]);
|
||||
var combinedStep = this._combineSteps(s, this.steps[0]);
|
||||
if (combinedStep) {
|
||||
this.steps[0] = combinedStep;
|
||||
} else {
|
||||
this.steps.unshift(s);
|
||||
}
|
||||
};
|
||||
|
||||
// DGF try to combine two steps into one step (perf enhancement)
|
||||
LocationExpr.prototype._combineSteps = function(prevStep, nextStep) {
|
||||
if (!prevStep) return null;
|
||||
if (!nextStep) return null;
|
||||
var hasPredicates = (prevStep.predicates && prevStep.predicates.length > 0);
|
||||
if (prevStep.nodetest instanceof NodeTestAny && !hasPredicates) {
|
||||
// maybe suitable to be combined
|
||||
if (prevStep.axis == xpathAxis.DESCENDANT_OR_SELF) {
|
||||
if (nextStep.axis == xpathAxis.CHILD) {
|
||||
// HBC - commenting out, because this is not a valid reduction
|
||||
//nextStep.axis = xpathAxis.DESCENDANT;
|
||||
//return nextStep;
|
||||
} else if (nextStep.axis == xpathAxis.SELF) {
|
||||
nextStep.axis = xpathAxis.DESCENDANT_OR_SELF;
|
||||
return nextStep;
|
||||
}
|
||||
} else if (prevStep.axis == xpathAxis.DESCENDANT) {
|
||||
if (nextStep.axis == xpathAxis.SELF) {
|
||||
nextStep.axis = xpathAxis.DESCENDANT;
|
||||
return nextStep;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
LocationExpr.prototype.evaluate = function(ctx) {
|
||||
var start;
|
||||
if (this.absolute) {
|
||||
|
|
@ -621,8 +715,44 @@ LocationExpr.prototype.evaluate = function(ctx) {
|
|||
function xPathStep(nodes, steps, step, input, ctx) {
|
||||
var s = steps[step];
|
||||
var ctx2 = ctx.clone(input);
|
||||
var nodelist = s.evaluate(ctx2).nodeSetValue();
|
||||
|
||||
if (ctx.returnOnFirstMatch && !s.hasPositionalPredicate) {
|
||||
var nodelist = s.evaluate(ctx2).nodeSetValue();
|
||||
// the predicates were not processed in the last evaluate(), so that we can
|
||||
// process them here with the returnOnFirstMatch optimization. We do a
|
||||
// depth-first grab at any nodes that pass the predicate tests. There is no
|
||||
// way to optimize when predicates contain positional selectors, including
|
||||
// indexes or uses of the last() or position() functions, because they
|
||||
// typically require the entire nodelist for context. Process without
|
||||
// optimization if we encounter such selectors.
|
||||
var nLength = nodelist.length;
|
||||
var pLength = s.predicate.length;
|
||||
nodelistLoop:
|
||||
for (var i = 0; i < nLength; ++i) {
|
||||
var n = nodelist[i];
|
||||
for (var j = 0; j < pLength; ++j) {
|
||||
if (!s.predicate[j].evaluate(ctx.clone(n, i, nodelist)).booleanValue()) {
|
||||
continue nodelistLoop;
|
||||
}
|
||||
}
|
||||
// n survived the predicate tests!
|
||||
if (step == steps.length - 1) {
|
||||
nodes.push(n);
|
||||
}
|
||||
else {
|
||||
xPathStep(nodes, steps, step + 1, n, ctx);
|
||||
}
|
||||
if (nodes.length > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// set returnOnFirstMatch to false for the cloned ExprContext, because
|
||||
// behavior in StepExpr.prototype.evaluate is driven off its value. Note
|
||||
// that the original context may still have true for this value.
|
||||
ctx2.returnOnFirstMatch = false;
|
||||
var nodelist = s.evaluate(ctx2).nodeSetValue();
|
||||
for (var i = 0; i < nodelist.length; ++i) {
|
||||
if (step == steps.length - 1) {
|
||||
nodes.push(nodelist[i]);
|
||||
|
|
@ -630,21 +760,37 @@ function xPathStep(nodes, steps, step, input, ctx) {
|
|||
xPathStep(nodes, steps, step + 1, nodelist[i], ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function StepExpr(axis, nodetest, predicate) {
|
||||
function StepExpr(axis, nodetest, opt_predicate) {
|
||||
this.axis = axis;
|
||||
this.nodetest = nodetest;
|
||||
this.predicate = predicate || [];
|
||||
this.predicate = opt_predicate || [];
|
||||
this.hasPositionalPredicate = false;
|
||||
for (var i = 0; i < this.predicate.length; ++i) {
|
||||
if (predicateExprHasPositionalSelector(this.predicate[i].expr)) {
|
||||
this.hasPositionalPredicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StepExpr.prototype.appendPredicate = function(p) {
|
||||
this.predicate.push(p);
|
||||
if (!this.hasPositionalPredicate) {
|
||||
this.hasPositionalPredicate = predicateExprHasPositionalSelector(p.expr);
|
||||
}
|
||||
}
|
||||
|
||||
StepExpr.prototype.evaluate = function(ctx) {
|
||||
var input = ctx.node;
|
||||
var nodelist = [];
|
||||
var skipNodeTest = false;
|
||||
|
||||
if (this.nodetest instanceof NodeTestAny) {
|
||||
skipNodeTest = true;
|
||||
}
|
||||
|
||||
// NOTE(mesch): When this was a switch() statement, it didn't work
|
||||
// in Safari/2.0. Not sure why though; it resulted in the JavaScript
|
||||
|
|
@ -652,30 +798,71 @@ StepExpr.prototype.evaluate = function(ctx) {
|
|||
|
||||
if (this.axis == xpathAxis.ANCESTOR_OR_SELF) {
|
||||
nodelist.push(input);
|
||||
for (var n = input.parentNode; n; n = input.parentNode) {
|
||||
for (var n = input.parentNode; n; n = n.parentNode) {
|
||||
nodelist.push(n);
|
||||
}
|
||||
|
||||
} else if (this.axis == xpathAxis.ANCESTOR) {
|
||||
for (var n = input.parentNode; n; n = input.parentNode) {
|
||||
for (var n = input.parentNode; n; n = n.parentNode) {
|
||||
nodelist.push(n);
|
||||
}
|
||||
|
||||
} else if (this.axis == xpathAxis.ATTRIBUTE) {
|
||||
if (this.nodetest.name != undefined) {
|
||||
// single-attribute step
|
||||
if (input.attributes) {
|
||||
if (input.attributes instanceof Array) {
|
||||
// probably evaluating on document created by xmlParse()
|
||||
copyArray(nodelist, input.attributes);
|
||||
}
|
||||
else {
|
||||
if (this.nodetest.name == 'style') {
|
||||
var value = input.getAttribute('style');
|
||||
if (value && typeof(value) != 'string') {
|
||||
// this is the case where indexing into the attributes array
|
||||
// doesn't give us the attribute node in IE - we create our own
|
||||
// node instead
|
||||
nodelist.push(XNode.create(DOM_ATTRIBUTE_NODE, 'style',
|
||||
value.cssText, document));
|
||||
}
|
||||
else {
|
||||
nodelist.push(input.attributes[this.nodetest.name]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
nodelist.push(input.attributes[this.nodetest.name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// all-attributes step
|
||||
if (ctx.ignoreAttributesWithoutValue) {
|
||||
copyArrayIgnoringAttributesWithoutValue(nodelist, input.attributes);
|
||||
}
|
||||
else {
|
||||
copyArray(nodelist, input.attributes);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (this.axis == xpathAxis.CHILD) {
|
||||
copyArray(nodelist, input.childNodes);
|
||||
|
||||
} else if (this.axis == xpathAxis.DESCENDANT_OR_SELF) {
|
||||
if (this.nodetest.evaluate(ctx).booleanValue()) {
|
||||
nodelist.push(input);
|
||||
xpathCollectDescendants(nodelist, input);
|
||||
}
|
||||
var tagName = xpathExtractTagNameFromNodeTest(this.nodetest);
|
||||
xpathCollectDescendants(nodelist, input, tagName);
|
||||
if (tagName) skipNodeTest = true;
|
||||
|
||||
} else if (this.axis == xpathAxis.DESCENDANT) {
|
||||
xpathCollectDescendants(nodelist, input);
|
||||
var tagName = xpathExtractTagNameFromNodeTest(this.nodetest);
|
||||
xpathCollectDescendants(nodelist, input, tagName);
|
||||
if (tagName) skipNodeTest = true;
|
||||
|
||||
} else if (this.axis == xpathAxis.FOLLOWING) {
|
||||
for (var n = input.parentNode; n; n = n.parentNode) {
|
||||
for (var n = input; n; n = n.parentNode) {
|
||||
for (var nn = n.nextSibling; nn; nn = nn.nextSibling) {
|
||||
nodelist.push(nn);
|
||||
xpathCollectDescendants(nodelist, nn);
|
||||
|
|
@ -683,7 +870,7 @@ StepExpr.prototype.evaluate = function(ctx) {
|
|||
}
|
||||
|
||||
} else if (this.axis == xpathAxis.FOLLOWING_SIBLING) {
|
||||
for (var n = input.nextSibling; n; n = input.nextSibling) {
|
||||
for (var n = input.nextSibling; n; n = n.nextSibling) {
|
||||
nodelist.push(n);
|
||||
}
|
||||
|
||||
|
|
@ -696,7 +883,7 @@ StepExpr.prototype.evaluate = function(ctx) {
|
|||
}
|
||||
|
||||
} else if (this.axis == xpathAxis.PRECEDING) {
|
||||
for (var n = input.parentNode; n; n = n.parentNode) {
|
||||
for (var n = input; n; n = n.parentNode) {
|
||||
for (var nn = n.previousSibling; nn; nn = nn.previousSibling) {
|
||||
nodelist.push(nn);
|
||||
xpathCollectDescendantsReverse(nodelist, nn);
|
||||
|
|
@ -704,7 +891,7 @@ StepExpr.prototype.evaluate = function(ctx) {
|
|||
}
|
||||
|
||||
} else if (this.axis == xpathAxis.PRECEDING_SIBLING) {
|
||||
for (var n = input.previousSibling; n; n = input.previousSibling) {
|
||||
for (var n = input.previousSibling; n; n = n.previousSibling) {
|
||||
nodelist.push(n);
|
||||
}
|
||||
|
||||
|
|
@ -715,6 +902,7 @@ StepExpr.prototype.evaluate = function(ctx) {
|
|||
throw 'ERROR -- NO SUCH AXIS: ' + this.axis;
|
||||
}
|
||||
|
||||
if (!skipNodeTest) {
|
||||
// process node test
|
||||
var nodelist0 = nodelist;
|
||||
nodelist = [];
|
||||
|
|
@ -724,8 +912,10 @@ StepExpr.prototype.evaluate = function(ctx) {
|
|||
nodelist.push(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// process predicates
|
||||
if (!ctx.returnOnFirstMatch) {
|
||||
for (var i = 0; i < this.predicate.length; ++i) {
|
||||
var nodelist0 = nodelist;
|
||||
nodelist = [];
|
||||
|
|
@ -736,6 +926,7 @@ StepExpr.prototype.evaluate = function(ctx) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new NodeSetValue(nodelist);
|
||||
};
|
||||
|
|
@ -748,10 +939,12 @@ NodeTestAny.prototype.evaluate = function(ctx) {
|
|||
return this.value;
|
||||
};
|
||||
|
||||
function NodeTestElement() {}
|
||||
function NodeTestElementOrAttribute() {}
|
||||
|
||||
NodeTestElement.prototype.evaluate = function(ctx) {
|
||||
return new BooleanValue(ctx.node.nodeType == DOM_ELEMENT_NODE);
|
||||
NodeTestElementOrAttribute.prototype.evaluate = function(ctx) {
|
||||
return new BooleanValue(
|
||||
ctx.node.nodeType == DOM_ELEMENT_NODE ||
|
||||
ctx.node.nodeType == DOM_ATTRIBUTE_NODE);
|
||||
}
|
||||
|
||||
function NodeTestText() {}
|
||||
|
|
@ -788,12 +981,17 @@ NodeTestNC.prototype.evaluate = function(ctx) {
|
|||
|
||||
function NodeTestName(name) {
|
||||
this.name = name;
|
||||
this.re = new RegExp('^' + name + '$', "i");
|
||||
}
|
||||
|
||||
NodeTestName.prototype.evaluate = function(ctx) {
|
||||
var n = ctx.node;
|
||||
// NOTE (Patrick Lightbody): this change allows node selection to be case-insensitive
|
||||
return new BooleanValue(n.nodeName.toUpperCase() == this.name.toUpperCase());
|
||||
if (ctx.caseInsensitive) {
|
||||
if (n.nodeName.length != this.name.length) return new BooleanValue(false);
|
||||
return new BooleanValue(this.re.test(n.nodeName));
|
||||
} else {
|
||||
return new BooleanValue(n.nodeName == this.name);
|
||||
}
|
||||
}
|
||||
|
||||
function PredicateExpr(expr) {
|
||||
|
|
@ -827,7 +1025,7 @@ FunctionCallExpr.prototype.evaluate = function(ctx) {
|
|||
if (f) {
|
||||
return f.call(this, ctx);
|
||||
} else {
|
||||
Log.write('XPath NO SUCH FUNCTION ' + fn);
|
||||
xpathLog('XPath NO SUCH FUNCTION ' + fn);
|
||||
return new BooleanValue(false);
|
||||
}
|
||||
};
|
||||
|
|
@ -836,7 +1034,7 @@ FunctionCallExpr.prototype.xpathfunctions = {
|
|||
'last': function(ctx) {
|
||||
assert(this.args.length == 0);
|
||||
// NOTE(mesch): XPath position starts at 1.
|
||||
return new NumberValue(ctx.nodelist.length);
|
||||
return new NumberValue(ctx.contextSize());
|
||||
},
|
||||
|
||||
'position': function(ctx) {
|
||||
|
|
@ -853,21 +1051,22 @@ FunctionCallExpr.prototype.xpathfunctions = {
|
|||
|
||||
'id': function(ctx) {
|
||||
assert(this.args.length == 1);
|
||||
var e = this.args.evaluate(ctx);
|
||||
var e = this.args[0].evaluate(ctx);
|
||||
var ret = [];
|
||||
var ids;
|
||||
if (e.type == 'node-set') {
|
||||
ids = [];
|
||||
for (var i = 0; i < e.length; ++i) {
|
||||
var v = xmlValue(e[i]).split(/\s+/);
|
||||
var en = e.nodeSetValue();
|
||||
for (var i = 0; i < en.length; ++i) {
|
||||
var v = xmlValue(en[i]).split(/\s+/);
|
||||
for (var ii = 0; ii < v.length; ++ii) {
|
||||
ids.push(v[ii]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ids = e.split(/\s+/);
|
||||
ids = e.stringValue().split(/\s+/);
|
||||
}
|
||||
var d = ctx.node.ownerDocument;
|
||||
var d = ctx.root;
|
||||
for (var i = 0; i < ids.length; ++i) {
|
||||
var n = d.getElementById(ids[i]);
|
||||
if (n) {
|
||||
|
|
@ -925,6 +1124,14 @@ FunctionCallExpr.prototype.xpathfunctions = {
|
|||
return new BooleanValue(s0.indexOf(s1) == 0);
|
||||
},
|
||||
|
||||
'ends-with': function(ctx) {
|
||||
assert(this.args.length == 2);
|
||||
var s0 = this.args[0].evaluate(ctx).stringValue();
|
||||
var s1 = this.args[1].evaluate(ctx).stringValue();
|
||||
var re = new RegExp(RegExp.escape(s1) + '$');
|
||||
return new BooleanValue(re.test(s0));
|
||||
},
|
||||
|
||||
'contains': function(ctx) {
|
||||
assert(this.args.length == 2);
|
||||
var s0 = this.args[0].evaluate(ctx).stringValue();
|
||||
|
|
@ -1014,6 +1221,26 @@ FunctionCallExpr.prototype.xpathfunctions = {
|
|||
return new StringValue(s0);
|
||||
},
|
||||
|
||||
'matches': function(ctx) {
|
||||
assert(this.args.length >= 2);
|
||||
var s0 = this.args[0].evaluate(ctx).stringValue();
|
||||
var s1 = this.args[1].evaluate(ctx).stringValue();
|
||||
if (this.args.length > 2) {
|
||||
var s2 = this.args[2].evaluate(ctx).stringValue();
|
||||
if (/[^mi]/.test(s2)) {
|
||||
throw 'Invalid regular expression syntax: ' + s2;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
var re = new RegExp(s1, s2);
|
||||
}
|
||||
catch (e) {
|
||||
throw 'Invalid matches argument: ' + s1;
|
||||
}
|
||||
return new BooleanValue(re.test(s0));
|
||||
},
|
||||
|
||||
'boolean': function(ctx) {
|
||||
assert(this.args.length == 1);
|
||||
return new BooleanValue(this.args[0].evaluate(ctx).booleanValue());
|
||||
|
|
@ -1125,15 +1352,6 @@ FunctionCallExpr.prototype.xpathfunctions = {
|
|||
}
|
||||
},
|
||||
|
||||
'ext-sprintf': function(ctx) {
|
||||
assert(this.args.length >= 1);
|
||||
var args = [];
|
||||
for (var i = 0; i < this.args.length; ++i) {
|
||||
args.push(this.args[i].evaluate(ctx).stringValue());
|
||||
}
|
||||
return new StringValue(sprintf.apply(null, args));
|
||||
},
|
||||
|
||||
// ext-cardinal() evaluates its single argument as a number, and
|
||||
// returns the current node that many times. It can be used in the
|
||||
// select attribute to iterate over an integer range.
|
||||
|
|
@ -1159,16 +1377,19 @@ UnionExpr.prototype.evaluate = function(ctx) {
|
|||
var nodes2 = this.expr2.evaluate(ctx).nodeSetValue();
|
||||
var I1 = nodes1.length;
|
||||
for (var i2 = 0; i2 < nodes2.length; ++i2) {
|
||||
var n = nodes2[i2];
|
||||
var inBoth = false;
|
||||
for (var i1 = 0; i1 < I1; ++i1) {
|
||||
if (nodes1[i1] == nodes2[i2]) {
|
||||
// break inner loop and continue outer loop, labels confuse
|
||||
// the js compiler, so we don't use them here.
|
||||
i1 = I1;
|
||||
if (nodes1[i1] == n) {
|
||||
inBoth = true;
|
||||
i1 = I1; // break inner loop
|
||||
}
|
||||
}
|
||||
nodes1.push(nodes2[i2]);
|
||||
if (!inBoth) {
|
||||
nodes1.push(n);
|
||||
}
|
||||
return new NodeSetValue(nodes2);
|
||||
}
|
||||
return new NodeSetValue(nodes1);
|
||||
};
|
||||
|
||||
function PathExpr(filter, rel) {
|
||||
|
|
@ -1177,8 +1398,25 @@ function PathExpr(filter, rel) {
|
|||
}
|
||||
|
||||
PathExpr.prototype.evaluate = function(ctx) {
|
||||
// the filter expression should be evaluated in its entirety with no
|
||||
// optimization, as we can't backtrack to it after having moved on to
|
||||
// evaluating the relative location path
|
||||
var flag = ctx.returnOnFirstMatch;
|
||||
ctx.setReturnOnFirstMatch(false);
|
||||
var nodes = this.filter.evaluate(ctx).nodeSetValue();
|
||||
ctx.setReturnOnFirstMatch(flag);
|
||||
|
||||
var nodes1 = [];
|
||||
if (ctx.returnOnFirstMatch) {
|
||||
for (var i = 0; i < nodes.length; ++i) {
|
||||
nodes1 = this.rel.evaluate(ctx.clone(nodes[i], i, nodes)).nodeSetValue();
|
||||
if (nodes1.length > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new NodeSetValue(nodes1);
|
||||
}
|
||||
else {
|
||||
for (var i = 0; i < nodes.length; ++i) {
|
||||
var nodes0 = this.rel.evaluate(ctx.clone(nodes[i], i, nodes)).nodeSetValue();
|
||||
for (var ii = 0; ii < nodes0.length; ++ii) {
|
||||
|
|
@ -1186,6 +1424,7 @@ PathExpr.prototype.evaluate = function(ctx) {
|
|||
}
|
||||
}
|
||||
return new NodeSetValue(nodes1);
|
||||
}
|
||||
};
|
||||
|
||||
function FilterExpr(expr, predicate) {
|
||||
|
|
@ -1462,6 +1701,7 @@ function makeLocationExpr6(rel, slash, step) {
|
|||
|
||||
function makeLocationExpr7(rel, dslash, step) {
|
||||
rel.appendStep(makeAbbrevStep(dslash.value));
|
||||
rel.appendStep(step);
|
||||
return rel;
|
||||
}
|
||||
|
||||
|
|
@ -1504,7 +1744,7 @@ function makeAbbrevStep(abbrev) {
|
|||
}
|
||||
|
||||
function makeNodeTestExpr1(asterisk) {
|
||||
return new NodeTestElement;
|
||||
return new NodeTestElementOrAttribute;
|
||||
}
|
||||
|
||||
function makeNodeTestExpr2(ncname, colon, asterisk) {
|
||||
|
|
@ -1528,14 +1768,14 @@ function makeNodeTestExpr4(typeo, parenc) {
|
|||
return new NodeTestComment;
|
||||
|
||||
case 'processing-instruction':
|
||||
return new NodeTestPI;
|
||||
return new NodeTestPI('');
|
||||
}
|
||||
}
|
||||
|
||||
function makeNodeTestExpr5(typeo, target, parenc) {
|
||||
var type = typeo.replace(/\s*\($/, '');
|
||||
if (type != 'processing-instruction') {
|
||||
throw type + ' ' + Error().stack;
|
||||
throw type;
|
||||
}
|
||||
return new NodeTestPI(target.value);
|
||||
}
|
||||
|
|
@ -1631,9 +1871,9 @@ function makeSimpleExpr(expr) {
|
|||
}
|
||||
|
||||
function makeSimpleExpr2(expr) {
|
||||
var steps = expr.split('/');
|
||||
var steps = stringSplit(expr, '/');
|
||||
var c = new LocationExpr();
|
||||
for (var i in steps) {
|
||||
for (var i = 0; i < steps.length; ++i) {
|
||||
var a = new NodeTestName(steps[i]);
|
||||
var b = new StepExpr('child', a);
|
||||
c.appendStep(b);
|
||||
|
|
@ -1716,7 +1956,7 @@ var TOK_BRACKO = { label: "[", prec: 32, re: new RegExp("^\\[") };
|
|||
var TOK_BRACKC = { label: "]", re: new RegExp("^\\]") };
|
||||
var TOK_DOLLAR = { label: "$", re: new RegExp("^\\$") };
|
||||
|
||||
var TOK_NCNAME = { label: "[ncname]", re: new RegExp('^[a-z][-\\w]*','i') };
|
||||
var TOK_NCNAME = { label: "[ncname]", re: new RegExp('^' + XML_NC_NAME) };
|
||||
|
||||
var TOK_ASTERISK = { label: "*", prec: 15, re: new RegExp("^\\*"), left: true };
|
||||
var TOK_LITERALQ = { label: "[litq]", prec: 20, re: new RegExp("^'[^\\']*'") };
|
||||
|
|
@ -1733,7 +1973,7 @@ var TOK_NUMBER = {
|
|||
|
||||
var TOK_QNAME = {
|
||||
label: "[qname]",
|
||||
re: new RegExp('^([a-z][-\\w]*:)?[a-z][-\\w]*','i')
|
||||
re: new RegExp('^(' + XML_NC_NAME + ':)?' + XML_NC_NAME)
|
||||
};
|
||||
|
||||
var TOK_NODEO = {
|
||||
|
|
@ -1856,6 +2096,9 @@ var ASSOC_LEFT = true;
|
|||
// instead. TODO: It shouldn't be necessary to explicitly assign
|
||||
// precedences to rules.
|
||||
|
||||
// DGF As it stands, these precedences are purely empirical; we're
|
||||
// not sure they can be made to be consistent at all.
|
||||
|
||||
var xpathGrammarRules =
|
||||
[
|
||||
[ XPathLocationPath, [ XPathRelativeLocationPath ], 18,
|
||||
|
|
@ -1918,7 +2161,7 @@ var xpathGrammarRules =
|
|||
passExpr ],
|
||||
[ XPathPrimaryExpr, [ XPathNumber ], 30,
|
||||
passExpr ],
|
||||
[ XPathPrimaryExpr, [ XPathFunctionCall ], 30,
|
||||
[ XPathPrimaryExpr, [ XPathFunctionCall ], 31,
|
||||
passExpr ],
|
||||
|
||||
[ XPathFunctionCall, [ TOK_QNAME, TOK_PARENO, TOK_PARENC ], -1,
|
||||
|
|
@ -1940,13 +2183,13 @@ var xpathGrammarRules =
|
|||
[ XPathPathExpr, [ XPathFilterExpr ], 19,
|
||||
passExpr ],
|
||||
[ XPathPathExpr,
|
||||
[ XPathFilterExpr, TOK_SLASH, XPathRelativeLocationPath ], 20,
|
||||
[ XPathFilterExpr, TOK_SLASH, XPathRelativeLocationPath ], 19,
|
||||
makePathExpr1 ],
|
||||
[ XPathPathExpr,
|
||||
[ XPathFilterExpr, TOK_DSLASH, XPathRelativeLocationPath ], 20,
|
||||
[ XPathFilterExpr, TOK_DSLASH, XPathRelativeLocationPath ], 19,
|
||||
makePathExpr2 ],
|
||||
|
||||
[ XPathFilterExpr, [ XPathPrimaryExpr, XPathPredicate, Q_MM ], 20,
|
||||
[ XPathFilterExpr, [ XPathPrimaryExpr, XPathPredicate, Q_MM ], 31,
|
||||
makeFilterExpr ],
|
||||
|
||||
[ XPathExpr, [ XPathPrimaryExpr ], 16,
|
||||
|
|
@ -2036,7 +2279,7 @@ function xpathParseInit() {
|
|||
xpathTokenRules[i].key = k++;
|
||||
}
|
||||
|
||||
Log.write('XPath parse INIT: ' + k + ' rules');
|
||||
xpathLog('XPath parse INIT: ' + k + ' rules');
|
||||
|
||||
// Another slight optimization: sort the rules into bins according
|
||||
// to the last element (observing quantifiers), so we can restrict
|
||||
|
|
@ -2074,7 +2317,7 @@ function xpathParseInit() {
|
|||
}
|
||||
}
|
||||
|
||||
Log.write('XPath parse INIT: ' + xpathRules.length + ' rule bins');
|
||||
xpathLog('XPath parse INIT: ' + xpathRules.length + ' rule bins');
|
||||
|
||||
var sum = 0;
|
||||
mapExec(xpathRules, function(i) {
|
||||
|
|
@ -2083,22 +2326,39 @@ function xpathParseInit() {
|
|||
}
|
||||
});
|
||||
|
||||
Log.write('XPath parse INIT: ' + (sum / xpathRules.length) + ' average bin size');
|
||||
xpathLog('XPath parse INIT: ' + (sum / xpathRules.length) +
|
||||
' average bin size');
|
||||
}
|
||||
|
||||
// Local utility functions that are used by the lexer or parser.
|
||||
|
||||
function xpathCollectDescendants(nodelist, node) {
|
||||
function xpathCollectDescendants(nodelist, node, opt_tagName) {
|
||||
if (opt_tagName && node.getElementsByTagName) {
|
||||
copyArray(nodelist, node.getElementsByTagName(opt_tagName));
|
||||
return;
|
||||
}
|
||||
for (var n = node.firstChild; n; n = n.nextSibling) {
|
||||
nodelist.push(n);
|
||||
arguments.callee(nodelist, n);
|
||||
xpathCollectDescendants(nodelist, n);
|
||||
}
|
||||
}
|
||||
|
||||
// DGF extract a tag name suitable for getElementsByTagName
|
||||
function xpathExtractTagNameFromNodeTest(nodetest) {
|
||||
if (nodetest instanceof NodeTestName) {
|
||||
return nodetest.name;
|
||||
} else if (/* nodetest instanceof NodeTestAny || */ nodetest instanceof NodeTestElementOrAttribute) {
|
||||
// HBC - commented out the NodeTestAny in the above condition; it causes
|
||||
// non-element nodes to be excluded! The XPath spec says "node()" must
|
||||
// match all node types.
|
||||
return "*";
|
||||
}
|
||||
}
|
||||
|
||||
function xpathCollectDescendantsReverse(nodelist, node) {
|
||||
for (var n = node.lastChild; n; n = n.previousSibling) {
|
||||
nodelist.push(n);
|
||||
arguments.callee(nodelist, n);
|
||||
xpathCollectDescendantsReverse(nodelist, n);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2120,7 +2380,7 @@ function xpathSort(input, sort) {
|
|||
|
||||
var sortlist = [];
|
||||
|
||||
for (var i = 0; i < input.nodelist.length; ++i) {
|
||||
for (var i = 0; i < input.contextSize(); ++i) {
|
||||
var node = input.nodelist[i];
|
||||
var sortitem = { node: node, key: [] };
|
||||
var context = input.clone(node, 0, [ node ]);
|
||||
|
|
@ -2153,7 +2413,7 @@ function xpathSort(input, sort) {
|
|||
nodes.push(sortlist[i].node);
|
||||
}
|
||||
input.nodelist = nodes;
|
||||
input.setNode(nodes[0], 0);
|
||||
input.setNode(0);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -2164,7 +2424,6 @@ function xpathSort(input, sort) {
|
|||
// NOTE: In browsers which do not follow the spec, this breaks only in
|
||||
// the case that numbers should be sorted as strings, which is very
|
||||
// uncommon.
|
||||
|
||||
function xpathSortByKey(v1, v2) {
|
||||
// NOTE: Sort key vectors of different length never occur in
|
||||
// xsltSort.
|
||||
|
|
@ -2180,3 +2439,12 @@ function xpathSortByKey(v1, v2) {
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Parses and then evaluates the given XPath expression in the given
|
||||
// input context. Notice that parsed xpath expressions are cached.
|
||||
function xpathEval(select, context) {
|
||||
var expr = xpathParse(select);
|
||||
var ret = expr.evaluate(context);
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# Please read as our directions have changed:
|
||||
# Move this file to your rails apps config directory and rename it to selenium.yml in order to configure the plugin
|
||||
|
||||
#
|
||||
|
|
@ -31,3 +32,6 @@ browsers:
|
|||
#multi_window: false
|
||||
|
||||
#result_dir: 'c:\result' # the directory where the results will be stored after a test:acceptance run
|
||||
|
||||
#fixtures_path: <%= "#{RAILS_ROOT}/spec/fixtures" %>
|
||||
#selenium_tests_path: <%= "#{RAILS_ROOT}/spec/selenium" %>
|
||||
37
vendor/plugins/selenium-on-rails/test/fixtures/config.yml
vendored
Normal file
37
vendor/plugins/selenium-on-rails/test/fixtures/config.yml
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# Please read as our directions have changed:
|
||||
# Move this file to your rails apps config directory and rename it to selenium.yml in order to configure the plugin
|
||||
|
||||
#
|
||||
# General settings
|
||||
#
|
||||
|
||||
environments:
|
||||
- test
|
||||
# - development # Uncomment this line to enable in development environment. N.B. your development database will likely be altered/destroyed/abducted
|
||||
|
||||
#selenium_path: 'c:\selenium' #path to selenium installation. only needed if you for some reason don't want to use the bundled version of selenium core
|
||||
|
||||
#
|
||||
# rake test:acceptance settings
|
||||
#
|
||||
|
||||
browsers:
|
||||
# Windows
|
||||
# firefox: 'c:\Program Files\Mozilla Firefox\firefox.exe'
|
||||
# ie: 'c:\Program Files\Internet Explorer\iexplore.exe'
|
||||
|
||||
# Mac OS X
|
||||
firefox: '/Applications/Firefox.app/Contents/MacOS/firefox-bin'
|
||||
safari: '/Applications/Safari.app/Contents/MacOS/Safari'
|
||||
|
||||
#host: 'localhost'
|
||||
#port_start: 3000
|
||||
#port_end: 3005
|
||||
#base_url_path: '/'
|
||||
#max_browser_duration: 120
|
||||
#multi_window: false
|
||||
|
||||
#result_dir: 'c:\result' # the directory where the results will be stored after a test:acceptance run
|
||||
|
||||
#fixtures_path: <%= "#{RAILS_ROOT}/spec/fixtures" %>
|
||||
#selenium_tests_path: <%= "#{RAILS_ROOT}/spec/selenium" %>
|
||||
27
vendor/plugins/selenium-on-rails/test/fixtures/selenium.yml
vendored
Normal file
27
vendor/plugins/selenium-on-rails/test/fixtures/selenium.yml
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Move this file to your rails apps config directory and rename it to selenium.yml in order to configure the plugin
|
||||
|
||||
#
|
||||
# General settings
|
||||
#
|
||||
|
||||
environments:
|
||||
- test_cache
|
||||
# - development # Uncomment this line to enable in development environment. N.B. your development database will likely be altered/destroyed/abducted
|
||||
|
||||
#selenium_path: 'c:\selenium' #path to selenium installation. only needed if you for some reason don't want to use the bundled version of selenium core
|
||||
|
||||
#
|
||||
# rake test:acceptance settings
|
||||
#
|
||||
|
||||
browsers:
|
||||
firefox: 'script/openfirefox'
|
||||
# safari: '/Applications/Safari.app/Contents/MacOS/Safari'
|
||||
|
||||
port_start: 4000
|
||||
port_end: 4001
|
||||
#base_url_path: '/'
|
||||
#max_browser_duration: 120
|
||||
#multi_window: false
|
||||
|
||||
#result_dir: 'c:\result' # the directory where the results will be stored after a test:acceptance run
|
||||
72
vendor/plugins/selenium-on-rails/test/paths_test.rb
vendored
Normal file
72
vendor/plugins/selenium-on-rails/test/paths_test.rb
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
require File.dirname(__FILE__) + '/test_helper'
|
||||
require 'mocha'
|
||||
RAILS_ROOT = File.expand_path(File.dirname(__FILE__) + "/")
|
||||
|
||||
class SeleniumOnRails::PathsTest < Test::Unit::TestCase
|
||||
|
||||
include SeleniumOnRails::Paths
|
||||
|
||||
def test_selenium_tests_path_when_config_has_not_selenium_tests_path
|
||||
SeleniumOnRailsConfig.expects(:get).with("selenium_tests_path").returns(nil)
|
||||
assert_equal "#{RAILS_ROOT}/test/selenium", selenium_tests_path
|
||||
end
|
||||
|
||||
def test_selenium_tests_path_when_config_has_selenium_tests_path
|
||||
SeleniumOnRailsConfig.expects(:get).with("selenium_tests_path").returns("path").at_least_once
|
||||
assert_equal "path", selenium_tests_path
|
||||
end
|
||||
|
||||
def test_fixtures_path_when_config_has_not_fixtures_path
|
||||
SeleniumOnRailsConfig.expects(:get).with("fixtures_path").returns(nil)
|
||||
assert_equal "#{RAILS_ROOT}/test/fixtures", fixtures_path
|
||||
end
|
||||
|
||||
def test_fixtures_path_when_config_has_fixtures_path
|
||||
SeleniumOnRailsConfig.expects(:get).with("fixtures_path").returns("path").at_least_once
|
||||
assert_equal "path", fixtures_path
|
||||
end
|
||||
|
||||
def test_view_path
|
||||
assert_equal File.expand_path("#{RAILS_ROOT}/../lib/views/my_view"), view_path('my_view')
|
||||
end
|
||||
|
||||
def test_layout_path
|
||||
assert_equal "layout.rhtml", layout_path
|
||||
end
|
||||
|
||||
def test_skip_file_when_file_contain_CVS
|
||||
assert skip_file?("file/with/CVS/in/the/middle/of/path")
|
||||
end
|
||||
|
||||
def test_skip_file_when_file_contain_dot
|
||||
assert skip_file?("file/with/./(dot)/in/the/middle/of/path")
|
||||
end
|
||||
|
||||
def test_skip_file_when_file_contain_underline
|
||||
assert skip_file?("file/with/_underline/in/the/middle/of/path")
|
||||
end
|
||||
|
||||
def test_skip_file_when_file_contain_accent_mark
|
||||
assert skip_file?("file/with/mark~/in/the/middle/of/path")
|
||||
end
|
||||
|
||||
def test_skip_file_when_file_does_not_have_any_reason_to_skip
|
||||
assert !skip_file?("my/valid/file")
|
||||
end
|
||||
|
||||
def test_selenium_path
|
||||
assert_equal File.expand_path("#{RAILS_ROOT}/../selenium-core") + "/", selenium_path
|
||||
end
|
||||
|
||||
def test_selenium_path_when_selenium_core_installation_is_not_found
|
||||
selenium_core_path = File.expand_path(File.dirname(__FILE__) + "/../selenium-core")
|
||||
|
||||
File.expects(:exist?).with("#{selenium_core_path}/core/TestRunner.html").returns(false)
|
||||
File.expects(:exist?).with("#{selenium_core_path}/selenium/TestRunner.html").returns(false)
|
||||
File.expects(:exist?).with("#{selenium_core_path}/javascript/TestRunner.html").returns(false)
|
||||
File.expects(:exist?).with("#{selenium_core_path}/TestRunner.html").returns(false)
|
||||
@@selenium_path = nil
|
||||
assert_raise(RuntimeError) { selenium_path }
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -2,7 +2,9 @@ require File.dirname(__FILE__) + '/test_helper'
|
|||
|
||||
class RendererTest < Test::Unit::TestCase
|
||||
def setup
|
||||
SeleniumOnRails::PartialsSupport.send(:include, SeleniumOnRails::PathsTestHelper)
|
||||
@controller = SeleniumController.new
|
||||
@controller.extend(SeleniumOnRails::PathsTestHelper)
|
||||
ActionController::Routing::Routes.draw
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
|
|
@ -20,8 +22,6 @@ END
|
|||
end
|
||||
|
||||
def test_html
|
||||
get :test_file, :testname => 'html.html'
|
||||
assert_headers
|
||||
expected =<<END
|
||||
<html><head><title>test layout</title></head><body>
|
||||
<p>Testing plain HTML</p>
|
||||
|
|
@ -32,9 +32,17 @@ END
|
|||
<p>and it works...</p>
|
||||
</body></html>
|
||||
END
|
||||
File.open(test_path_for('html.html'), 'w+') { |index_file| index_file << expected }
|
||||
get :test_file, :testname => 'html.html'
|
||||
assert_headers
|
||||
|
||||
assert_text_equal expected, @response.body
|
||||
end
|
||||
|
||||
def test_path_for(name)
|
||||
"#{File.expand_path(File.dirname(__FILE__) + "/../test_data")}/#{name}"
|
||||
end
|
||||
|
||||
def test_rhtml
|
||||
get :test_file, :testname => 'rhtml.rhtml'
|
||||
assert_headers
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ class RSeleneseTest < Test::Unit::TestCase
|
|||
create_rsel_file_from(input, "html.rsel")
|
||||
|
||||
@view = TestView.new
|
||||
@view.extend(SeleniumOnRails::PathsTestHelper)
|
||||
@sel = SeleniumOnRails::RSelenese.new(@view)
|
||||
|
||||
@sel.render ActionView::Template.new(test_path_for("html.rsel")), {'page_title' => page_title}
|
||||
end
|
||||
|
||||
|
|
@ -51,6 +51,21 @@ END
|
|||
assert_rselenese expected_html, 'Selenese Commands', input
|
||||
end
|
||||
|
||||
def test_render_rselenese_without_locals
|
||||
expected_html = <<END
|
||||
<table>
|
||||
<tr><th colspan="3"></th></tr>
|
||||
</table>
|
||||
END
|
||||
create_rsel_file_from('', "html.rsel")
|
||||
|
||||
@view = TestView.new
|
||||
@view.extend(SeleniumOnRails::PathsTestHelper)
|
||||
@sel = SeleniumOnRails::RSelenese.new(@view)
|
||||
|
||||
assert_text_equal expected_html, @sel.render(ActionView::Template.new(test_path_for("html.rsel")))
|
||||
end
|
||||
|
||||
def test_element_locators
|
||||
assert_generates_command %w{click aCheckbox}, :click, 'aCheckbox'
|
||||
assert_generates_command %w{click document.foo}, :click, 'document.foo'
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
require File.dirname(__FILE__) + '/test_helper'
|
||||
require File.dirname(__FILE__) + '/test_helper'
|
||||
|
||||
class SeleneseTest < Test::Unit::TestCase
|
||||
|
||||
def setup
|
||||
@view = TestView.new
|
||||
@view.extend(SeleniumOnRails::PathsTestHelper)
|
||||
@sel = SeleniumOnRails::Selenese.new(@view)
|
||||
end
|
||||
|
||||
|
|
@ -57,7 +58,6 @@ END
|
|||
</table>
|
||||
END
|
||||
input = <<END
|
||||
|
||||
Comment *1*
|
||||
|
||||
Comment 2
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ class SeleniumControllerTest < Test::Unit::TestCase
|
|||
|
||||
def setup
|
||||
@controller = SeleniumController.new
|
||||
@controller.extend(SeleniumOnRails::PathsTestHelper)
|
||||
ActionController::Routing::Routes.draw
|
||||
SeleniumController.any_instance.stubs(:layout_path).returns(false)
|
||||
@request = ActionController::TestRequest.new
|
||||
|
|
@ -26,7 +27,7 @@ EOS
|
|||
end
|
||||
|
||||
def test_record_with_result
|
||||
@controller.config.configs["result_dir"] = @result_dir
|
||||
@controller.instance_variable_set(:@result_dir, @result_dir)
|
||||
|
||||
post :record, :suite => @suite, "testTable.1" => "<table></table>", "testTable.2" => "<table></table>"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,43 @@
|
|||
require File.dirname(__FILE__) + '/test_helper'
|
||||
require 'mocha'
|
||||
|
||||
class SeleniumOnRailsConfigTest < Test::Unit::TestCase
|
||||
|
||||
def test_config_file_in_config_directory
|
||||
File.stubs(:expand_path).returns("temporary")
|
||||
File.expects(:exist?).with("temporary").returns(false)
|
||||
File.expects(:exist?).with(File.join(RAILS_ROOT, 'config', 'selenium.yml')).returns(true)
|
||||
YAML.expects(:load_file).with(File.join(RAILS_ROOT, 'config', 'selenium.yml')).returns({:fake => "hash"})
|
||||
|
||||
assert_equal({:fake => "hash"}, SeleniumOnRailsConfig.new.configs)
|
||||
end
|
||||
|
||||
def test_setting_config_manually
|
||||
config = SeleniumOnRailsConfig.new
|
||||
config.configs["test"] = "result"
|
||||
|
||||
assert_equal("result", config.configs["test"])
|
||||
class SeleniumOnRailsConfig
|
||||
def self.reset_config
|
||||
@@configs = nil
|
||||
end
|
||||
end
|
||||
|
||||
class SeleniumOnRailsConfigTest < Test::Unit::TestCase
|
||||
|
||||
def setup
|
||||
SeleniumOnRailsConfig.reset_config
|
||||
@selenium_file = File.join(RAILS_ROOT, 'config', 'selenium.yml')
|
||||
@config_file = File.expand_path(File.dirname(__FILE__) + '/../config.yml')
|
||||
@selenium_content = File.read(File.dirname(__FILE__) + '/fixtures/selenium.yml')
|
||||
@config_content = File.read(File.dirname(__FILE__) + '/fixtures/config.yml')
|
||||
end
|
||||
|
||||
def test_get_selenium_yaml
|
||||
File.expects(:exist?).with(@selenium_file).returns(true)
|
||||
IO.expects(:read).with(@selenium_file).returns(@selenium_content)
|
||||
IO.expects(:read).with(@config_file).never
|
||||
IO.expects(:exist?).with(@config_file).never
|
||||
|
||||
assert_equal ["test_cache"], SeleniumOnRailsConfig.get(:environments)
|
||||
assert_equal({"firefox"=>"script/openfirefox"}, SeleniumOnRailsConfig.get(:browsers))
|
||||
end
|
||||
|
||||
def test_get_when_config_yml_exists_but_selenium_yaml_not
|
||||
File.expects(:exist?).with(@selenium_file).returns(false)
|
||||
File.expects(:exist?).with(@config_file).returns(true)
|
||||
IO.expects(:read).with(@config_file).returns(@config_content)
|
||||
IO.expects(:read).with(@selenium_file).never
|
||||
|
||||
assert_equal ["test"], SeleniumOnRailsConfig.get(:environments)
|
||||
expected_config = {"safari"=>"/Applications/Safari.app/Contents/MacOS/Safari",
|
||||
"firefox"=>"/Applications/Firefox.app/Contents/MacOS/firefox-bin"}
|
||||
|
||||
assert_equal(expected_config, SeleniumOnRailsConfig.get(:browsers))
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -3,6 +3,7 @@ require File.dirname(__FILE__) + '/test_helper'
|
|||
class SeleniumSupportTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@controller = SeleniumController.new
|
||||
@controller.extend(SeleniumOnRails::PathsTestHelper)
|
||||
ActionController::Routing::Routes.draw
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ RAILS_ROOT = File.expand_path(File.dirname(__FILE__) + "/")
|
|||
class SetupTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@controller = SeleniumController.new
|
||||
@controller.extend(SeleniumOnRails::PathsTestHelper)
|
||||
SeleniumController.any_instance.stubs(:clear_tables).returns([])
|
||||
SeleniumController.any_instance.stubs(:layout_path).returns(false)
|
||||
ActionController::Routing::Routes.draw
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ require File.dirname(__FILE__) + '/test_helper'
|
|||
class SuiteRendererTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@controller = SeleniumController.new
|
||||
@controller.extend(SeleniumOnRails::PathsTestHelper)
|
||||
ActionController::Routing::Routes.draw
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
|
|
|
|||
|
|
@ -10,11 +10,8 @@ class SwitchEnvironmentControllerTest < Test::Unit::TestCase
|
|||
end
|
||||
|
||||
def test_index
|
||||
SeleniumOnRailsConfig.expects(:new).returns(@config)
|
||||
@config.expects(:get).with(:environments).returns("hello dolly")
|
||||
|
||||
SeleniumOnRailsConfig.expects(:get).with(:environments).returns("hello dolly")
|
||||
get :index
|
||||
|
||||
assert @response.body.include?('hello dolly')
|
||||
end
|
||||
end
|
||||
51
vendor/plugins/selenium-on-rails/test/test_builder_functions_authortest.rb
vendored
Normal file
51
vendor/plugins/selenium-on-rails/test/test_builder_functions_authortest.rb
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#----------------------------------------------------------------------------
|
||||
# This is a *_authortest.rb file, which means it will only run if you run:
|
||||
# rake alltests
|
||||
# It is not run as part of the standard test suite, as it's of limited
|
||||
# value unless you're actually developing Selenium On Rails.
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
# The test_builder_actions.rb and test_builder_accessors.rb files do not
|
||||
# necessarily contain all the functions which are available in Selenium.
|
||||
# Here we use the iedoc.xml file to find functions which might need to be
|
||||
# added to the files. Ultimately it would be great not to need to do this
|
||||
# process manually, however, this is a temporary step toward improving
|
||||
# function parity.
|
||||
|
||||
require File.dirname(__FILE__) + '/test_helper'
|
||||
|
||||
class TestTheTestBuilderFunctions < Test::Unit::TestCase
|
||||
|
||||
def test_functions_in_iedoc_are_supported
|
||||
|
||||
base_path = File.dirname(__FILE__) + '/../'
|
||||
|
||||
iedoc_file = File.read base_path + "selenium-core/iedoc.xml"
|
||||
test_builder_actions_file = File.read base_path + "lib/selenium_on_rails/test_builder_actions.rb"
|
||||
test_builder_accessors_file = File.read base_path + "lib/selenium_on_rails/test_builder_accessors.rb"
|
||||
|
||||
# Don't include any deprecated functions
|
||||
deprecated_functions = %W{dragdrop}
|
||||
|
||||
iedoc_functions = iedoc_file.scan(/function *name *= *["']([a-zA-Z]+)["']/)\
|
||||
.sort.collect{|x| x[0]} - deprecated_functions
|
||||
|
||||
for function_name in iedoc_functions
|
||||
|
||||
function_name.gsub!(/[A-Z]/) { |s| "_" + s.downcase }
|
||||
|
||||
test_description = "The function listed in the iedoc.xml file, " +
|
||||
"#{function_name}, exists in the test_builder files"
|
||||
|
||||
if test_builder_actions_file.match(/def *#{function_name}/) ||
|
||||
test_builder_accessors_file.match(/(?:def *|tt>)#{function_name}/)
|
||||
assert true, test_description
|
||||
else
|
||||
assert false, test_description
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -6,6 +6,7 @@ require 'rubygems'
|
|||
gem 'activesupport'
|
||||
require 'active_support'
|
||||
|
||||
gem 'actionpack'
|
||||
require 'action_view/template_handler'
|
||||
require 'action_view/template_handlers/builder'
|
||||
require 'action_view/template_handlers/erb'
|
||||
|
|
@ -31,11 +32,6 @@ def setup_controller_test(controller)
|
|||
@response = ActionController::TestResponse.new
|
||||
end
|
||||
|
||||
module SeleniumOnRails::Paths
|
||||
def selenium_tests_path
|
||||
File.expand_path(File.dirname(__FILE__) + '/../test_data')
|
||||
end
|
||||
end
|
||||
|
||||
class SeleniumController
|
||||
attr_accessor :layout_override
|
||||
|
|
@ -76,6 +72,12 @@ class Test::Unit::TestCase
|
|||
|
||||
end
|
||||
|
||||
module SeleniumOnRails::PathsTestHelper
|
||||
def selenium_tests_path
|
||||
File.expand_path(File.dirname(__FILE__) + '/../test_data')
|
||||
end
|
||||
end
|
||||
|
||||
class TestView < ActionView::Base
|
||||
include SeleniumOnRails::PartialsSupport
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
<p>Testing plain HTML</p>
|
||||
<table>
|
||||
<tr><th colspan="3">Test HTML</th></tr>
|
||||
<tr><td>open</td><td>/selenium/setup</td><td> </td></tr>
|
||||
</table>
|
||||
<p>and it works...</p>
|
||||
|
|
@ -4,5 +4,5 @@ test.setup :fixtures => :all
|
|||
setup :fixtures => [:foo, 'bar']
|
||||
setup :clear_tables => [:foo, :bar], :fixtures => :all
|
||||
assert_absolute_location :controller => 'selenium', :action => 'setup' #urls must be tested with a controller
|
||||
assert_title view.controller.controller_name #make sure we can access the view easily
|
||||
assert_title @view.controller.controller_name #make sure we can access the view easily
|
||||
include_partial 'partial', :source => 'RSelenese'
|
||||
Loading…
Add table
Add a link
Reference in a new issue