Selenium Testing Rails with a Focus on File Uploads

I’m just posting this because all the instructions I found for doing file upload testing with selenium involved very few instructions that didn’t cover everything you needed, nor gave actual working examples. In the following I will give some examples of selenium testing a Rails application, with specific instructions on doing file upload testing, because getting that to work was the most tedious part.

Selenium Testing Rails

Our method for using selenium to test a Rails application starts with the Selenium gem.

Step 1: Install the selenium gem.


sudo gem install selenium

Step 2: Hack the jar in the gem.

There are two ways of doing this. Use this modified jar file if you’re using Selenium-1.0.1.

selenium-server.jar.txt

Copy the file to $GEM_HOME/Selenium-1.0.1/lib/selenium/openqa/selenium-server.jar.txt

Or

Add this line to the top of the doType function in core/scripts/selenium-api.js in the jar file:


netscape.security.PrivilegeManager.enablePrivilege("UniversalFileRead");

And merge the contents of this jar file into the existing .jar.txt file

capsapi_classes.zip

While you’re at it…

Go ahead and edit core/RemoteRunner.html and add this line with all the other script lines:


<script language="JavaScript"type="text/javascript"src="lib/cssQuery/cssQuery-p.js"></script>

There’s a bug in this version of selenium which is missing this line which breaks the ability to use css locators in your commands. That sucks, because css locators are a helluva lot easier to use than xpath locators.

Step 3: Install this selenium rake task into your lib/tasks directory in your rails project. Name it whatever you want. I called it selenium_test.rake. Install the selenium_manager.rb file in lib/. These two files are in the attached zip. selenium_rails.zip

Note that I run mongrel in the test environment on port 8080 for selenium, so hopefully you aren’t already using that port.

Step 4: Create a custom profile for firefox.

Create the directory test/selenium/firefox_profile and create a file named user.js with these contents

1
2
3
4
user_pref("capability.principal.codebase.p0.granted", "UniversalFileRead");
user_pref("capability.principal.codebase.p0.id", "http://localhost:8080");
user_pref("capability.principal.codebase.p0.subjectName", "");
user_pref("signed.applets.codebase_principal_support", true);

The lines above will tell firefox to allow file read access for unsigned javascript from “http://localhost:8080”

Step 5: Write your selenium tests.

I’m just going to give one example test file, and the helper code we use for our selenium tests. I’ll leave it to you to edit it down to something usable for your project.

I’ve changed the names of various models to protect the identity of this project.

Mainly you’re just going to want the setup and teardown functions, which I put into the helper because I hate duplicating code in all the tests.

Put your selenium tests into test/selenium. Below is an example test from that directory.

attachments_test.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
require File.dirname(__FILE__) + '/helper'

class AttachmentsTest < Test::Unit::TestCase
  should 'be able to go to attachments list page from zebra view page' do
    as_zebra_herder do
      set_speed '500'
      browse_to_zebra :fluffy
      click_list_attachments

      click_add_attachment
      see_add_attachment_form

      submit_attachment test_file('small')
      see_attachment test_file('small')
    end
  end
end

helper.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
require File.dirname(__FILE__) + '/../test_helper'
require 'test/unit'
require 'selenium'

class Test::Unit::TestCase
  load_all_fixtures
  include SeleniumHelper

  # make parent happy
  def default_test
    assert true
  end

  #
  # BROWSE helpers
  #

  def browse_to_home
    open('/')
  end

  def browse_to_zebra(zebra)
    browse_to_home
    click_view_zebra zebra
  end

  def browse_to_cantaloupe_list
    browse_to_home
    clickAndWait('link=List All Cantaloupes')
  end

  #
  # CLICK helpers
  #

  def click_cancel
    click 'css=.cancel_link a'
    sleep 1
  end

  def click_list_attachments
    clickAndWait 'link=List Attachments'
  end

  def click_view_zebra(zebra)
    zebra = zebra_by_fixture_or_name(zebra)
    clickAndWait "css=.zebras .zebra#zebra_#{zebra.id} .zebra_link a"
  end

  def click_add_beaver
    clickAndWait('css=.add_beaver_link a')
  end

  def click_add_cantaloupe
    click 'link=Add a Cantaloupe'
    sleep 1
  end

  def click_complete_beaver(description)
    beaver = get_beaver(description)
    click("beaver_tail_checkbox_#{beaver.id}")
    sleep 1
  end

  def click_attach_chips_to_beaver(description)
    beaver = get_beaver(description)
    click("attach_chips_link_#{beaver.id}")
    sleep 1
  end

  def click_add_attachment
    click 'link=Add Attachment'
    sleep 1
  end

  def click_edit_cantaloupe(name)
    click("css=#cantaloupe_#{get_cantaloupe(name).id} .edit_cantaloupe_link a")
    sleep 1
  end

  def click_delete_cantaloupe(name, opts)
    cantaloupe = get_cantaloupe(name)
    unless opts[:ok]
      choose_cancel_on_next_confirmation
    end
    click("css=#cantaloupe_#{cantaloupe.id} .delete_cantaloupe_link a")
    sleep 1
  end

  #
  # SEE helpers
  #
  def see_attachment(file)
    file_name = File.basename(file)
    attachment = get_attachment(file_name)
    assert_equal file_name, get_text("css=.attachments #attachment_#{attachment.id} .attachment_filename"), 'did not see attachment'
    assert_equal 'Download', get_text("css=.attachments #attachment_#{attachment.id} .attachment_link a"), 'did not see link to view attachment'
  end

  def see_delete_confirmation
    assert_match /Are you sure/i, get_confirmation
  end

  def see_cantaloupe_list
   assert_equal true, is_element_present('css=.cantaloupes')
  end

  def see_zebra_beaver(description)
    beaver = get_beaver(description)
    assert_equal description, get_text("css=#beaver_#{beaver.id} .beaver_description"), 'did not see beaver description'
    assert_equal true, is_element_present("css=#beaver_#{beaver.id} input[type=checkbox]"), 'did not see beaver tail'
    assert_equal beaver.tail, is_element_present("css=#beaver_#{beaver.id} input[type=checkbox][checked=checked]"), 'did not see beaver tail checked value'
  end

  def see_add_beaver_form
    assert_equal true, is_element_present('css=form#add_beaver_form')
    assert_equal true, is_element_present('css=input#beaver_description[type=text]'), 'Did not see beaver description field'
    assert_equal true, is_element_present('css=input[type=submit]'), 'Did not see a submit button'
  end

  def see_add_attachment_form
    assert_equal true, is_element_present('css=form#add_attachment_form input#attachment_uploaded_data[type=file]'), 'did not see file input field'
    assert_equal true, is_element_present('css=form#add_attachment_form input[type=submit]'), 'did not see submit button'
  end

  def see_edit_cantaloupe_form(name)
    cantaloupe = get_cantaloupe(name)
    assert_equal true, is_element_present('css=form#edit_cantaloupe_form'), 'did not see edit form'
    assert_equal true, is_element_present("css=input#cantaloupe_name[type=text][value=#{name}]"), 'did not see cantaloupe name field'
    assert_equal true, is_element_present('css=input[type=submit]'), 'did not see submit button'
  end

  def see_cantaloupe(name)
    cantaloupe = get_cantaloupe(name)
    assert_equal name, get_text("css=#cantaloupe_#{cantaloupe.id} .cantaloupe_name")
  end

  def see_no_cantaloupe(name)
    assert_nil Cantaloupe.find_by_name(name)
  end

  #
  # ADD helpers
  #
  def add_beaver_to_zebra(zebra, beaver)
    browse_to_zebra zebra

    click_add_beaver
    see_add_beaver_form
    submit_new_beaver beaver
    see_zebra_beaver beaver
  end

  def add_cantaloupe(name)
    browse_to_cantaloupe_list
    click_add_cantaloupe
    submit_new_cantaloupe name
    see_cantaloupe name
  end

  #
  # SUBMIT helpers
  #
  def submit_attachment(file)
    type 'attachment_uploaded_data', file.to_s
    sleep 1
    click 'commit'
    sleep 5
    Attachment.clear_active_connections!
    Attachment.clear_reloadable_connections!
  end

  def submit_new_beaver(description)
    type 'beaver_description', description
    clickAndWait 'commit'
    Beaver.clear_active_connections!
    Beaver.clear_reloadable_connections!
  end

  def submit_new_cantaloupe(name)
    type 'cantaloupe_name', name
    click 'commit'
    sleep 1
    Cantaloupe.clear_active_connections!
    Cantaloupe.clear_reloadable_connections!
  end

  #
  # AS helpers
  #
  def as_zebra_herder
    raise 'must call with block' if !block_given?
    yield
  end

  #
  # FILE helpers
  #
  def test_file(file)
    File.expand_path(File.join(File.dirname(__FILE__), '..', 'fixtures', "selenium.#{file}.test_file"))
  end

  #
  # SETUP/TEARDOWN
  #

  def setup
    @selenium = Selenium::SeleniumDriver.new("localhost", 4444,"*firefox", "http://localhost:8080", 15000);
    @selenium.start
  end

  def teardown
    @selenium.stop
  end

  private
  def get_attachment(file)
    puts "Attachment file: #{file}"
    attachment = Attachment.find_by_filename(file)
    assert_not_nil attachment
    attachment
  end

  def get_beaver(description)
    beaver = Beaver.find_by_description(description)
    assert_not_nil beaver
    beaver
  end

  def get_cantaloupe(name)
    cantaloupe = Cantaloupe.find_by_name(name)
    assert_not_nil cantaloupe
    cantaloupe
  end

  def clickAndWait(locator)
    click locator
    wait_for_page_to_load('5000')
  end

  def zebra_by_fixture_or_name(zebra)
    zebras(zebra)
  rescue
    Zebra.find_by_name(zebra)
  end
end
Filed in: System Testing


Leave a Reply