Merge branch '3781-browser-friendly-servers' refs #3781
[arvados.git] / apps / workbench / test / integration / projects_test.rb
1 require 'integration_helper'
2 require 'selenium-webdriver'
3 require 'headless'
4
5 class ProjectsTest < ActionDispatch::IntegrationTest
6   setup do
7     headless = Headless.new
8     headless.start
9     Capybara.current_driver = :selenium
10
11     # project tests need bigger page size to be able to see all the buttons
12     Capybara.current_session.driver.browser.manage.window.resize_to(1152, 768)
13   end
14
15   test 'Check collection count for A Project in the tab pane titles' do
16     project_uuid = api_fixture('groups')['aproject']['uuid']
17     visit page_with_token 'active', '/projects/' + project_uuid
18     wait_for_ajax
19     collection_count = page.all("[data-pk*='collection']").count
20     assert_selector '#Data_collections-tab span', text: "(#{collection_count})"
21   end
22
23   test 'Find a project and edit its description' do
24     visit page_with_token 'active', '/'
25     find("#projects-menu").click
26     find(".dropdown-menu a", text: "A Project").click
27     within('.container-fluid', text: api_fixture('groups')['aproject']['name']) do
28       find('span', text: api_fixture('groups')['aproject']['name']).click
29       within('.arv-description-as-subtitle') do
30         find('.fa-pencil').click
31         find('.editable-input textarea').set('I just edited this.')
32         find('.editable-submit').click
33       end
34       wait_for_ajax
35     end
36     visit current_path
37     assert(find?('.container-fluid', text: 'I just edited this.'),
38            "Description update did not survive page refresh")
39   end
40
41   test 'Find a project and edit description to textile description' do
42     visit page_with_token 'active', '/'
43     find("#projects-menu").click
44     find(".dropdown-menu a", text: "A Project").click
45     within('.container-fluid', text: api_fixture('groups')['aproject']['name']) do
46       find('span', text: api_fixture('groups')['aproject']['name']).click
47       within('.arv-description-as-subtitle') do
48         find('.fa-pencil').click
49         find('.editable-input textarea').set('<p>*Textile description for A project* - "take me home":/ </p><p>And a new paragraph in description.</p>')
50         find('.editable-submit').click
51       end
52       wait_for_ajax
53     end
54
55     # visit project page
56     visit current_path
57     assert_no_text '*Textile description for A project*'
58     assert(find?('.container-fluid', text: 'Textile description for A project'),
59            "Description update did not survive page refresh")
60     assert(find?('.container-fluid', text: 'And a new paragraph in description'),
61            "Description did not contain the expected new paragraph")
62     assert(page.has_link?("take me home"), "link not found in description")
63
64     click_link 'take me home'
65
66     # now in dashboard
67     assert(page.has_text?('Active pipelines'), 'Active pipelines - not found on dashboard')
68   end
69
70   test 'Find a project and edit description to html description' do
71     visit page_with_token 'active', '/'
72     find("#projects-menu").click
73     find(".dropdown-menu a", text: "A Project").click
74     within('.container-fluid', text: api_fixture('groups')['aproject']['name']) do
75       find('span', text: api_fixture('groups')['aproject']['name']).click
76       within('.arv-description-as-subtitle') do
77         find('.fa-pencil').click
78         find('.editable-input textarea').set('<br>Textile description for A project</br> - <a href="/">take me home</a>')
79         find('.editable-submit').click
80       end
81       wait_for_ajax
82     end
83     visit current_path
84     assert(find?('.container-fluid', text: 'Textile description for A project'),
85            "Description update did not survive page refresh")
86     assert(!find?('.container-fluid', text: '<br>Textile description for A project</br>'),
87            "Textile description is displayed with uninterpreted formatting characters")
88     assert(page.has_link?("take me home"),"link not found in description")
89     click_link 'take me home'
90     assert page.has_text?('Active pipelines')
91   end
92
93   test 'Find a project and edit description to textile description with link to object' do
94     visit page_with_token 'active', '/'
95     find("#projects-menu").click
96     find(".dropdown-menu a", text: "A Project").click
97     within('.container-fluid', text: api_fixture('groups')['aproject']['name']) do
98       find('span', text: api_fixture('groups')['aproject']['name']).click
99       within('.arv-description-as-subtitle') do
100         find('.fa-pencil').click
101         find('.editable-input textarea').set('*Textile description for A project* - "go to sub-project":' + api_fixture('groups')['asubproject']['uuid'] + "'")
102         find('.editable-submit').click
103       end
104       wait_for_ajax
105     end
106     visit current_path
107     assert(find?('.container-fluid', text: 'Textile description for A project'),
108            "Description update did not survive page refresh")
109     assert(!find?('.container-fluid', text: '*Textile description for A project*'),
110            "Textile description is displayed with uninterpreted formatting characters")
111     assert(page.has_link?("go to sub-project"), "link not found in description")
112     click_link 'go to sub-project'
113     assert(page.has_text?(api_fixture('groups')['asubproject']['name']), 'sub-project name not found after clicking link')
114   end
115
116   test 'Add a new name, then edit it, without creating a duplicate' do
117     project_uuid = api_fixture('groups')['aproject']['uuid']
118     specimen_uuid = api_fixture('traits')['owned_by_aproject_with_no_name']['uuid']
119     visit page_with_token 'active', '/projects/' + project_uuid
120     click_link 'Other objects'
121     within '.selection-action-container' do
122       # Wait for the tab to load:
123       assert_selector 'tr[data-kind="arvados#trait"]'
124       within first('tr', text: 'Trait') do
125         find(".fa-pencil").click
126         find('.editable-input input').set('Now I have a name.')
127         find('.glyphicon-ok').click
128         assert_selector '.editable', text: 'Now I have a name.'
129         find(".fa-pencil").click
130         find('.editable-input input').set('Now I have a new name.')
131         find('.glyphicon-ok').click
132       end
133       wait_for_ajax
134       assert_selector '.editable', text: 'Now I have a new name.'
135     end
136     visit current_path
137     click_link 'Other objects'
138     within '.selection-action-container' do
139       find '.editable', text: 'Now I have a new name.'
140       page.assert_no_selector '.editable', text: 'Now I have a name.'
141     end
142   end
143
144   test 'Create a project and move it into a different project' do
145     visit page_with_token 'active', '/projects'
146     find("#projects-menu").click
147     find(".dropdown-menu a", text: "Home").click
148     find('.btn', text: "Add a subproject").click
149
150     # within('.editable', text: 'New project') do
151     within('h2') do
152       find('.fa-pencil').click
153       find('.editable-input input').set('Project 1234')
154       find('.glyphicon-ok').click
155     end
156     wait_for_ajax
157
158     visit '/projects'
159     find("#projects-menu").click
160     find(".dropdown-menu a", text: "Home").click
161     find('.btn', text: "Add a subproject").click
162     within('h2') do
163       find('.fa-pencil').click
164       find('.editable-input input').set('Project 5678')
165       find('.glyphicon-ok').click
166     end
167     wait_for_ajax
168
169     click_link 'Move project...'
170     find('.selectable', text: 'Project 1234').click
171     find('.modal-footer a,button', text: 'Move').click
172     wait_for_ajax
173
174     # Wait for the page to refresh and show the new parent in Sharing panel
175     click_link 'Sharing'
176     assert(page.has_link?("Project 1234"),
177            "Project 5678 should now be inside project 1234")
178   end
179
180   def show_project_using(auth_key, proj_key='aproject')
181     project_uuid = api_fixture('groups')[proj_key]['uuid']
182     visit(page_with_token(auth_key, "/projects/#{project_uuid}"))
183     assert(page.has_text?("A Project"), "not on expected project page")
184   end
185
186   def share_rows
187     find('#project_sharing').all('tr')
188   end
189
190   def add_share_and_check(share_type, name, obj=nil)
191     assert(page.has_no_text?(name), "project is already shared with #{name}")
192     start_share_count = share_rows.size
193     click_on("Share with #{share_type}")
194     within(".modal-container") do
195       # Order is important here: we should find something that appears in the
196       # modal before we make any assertions about what's not in the modal.
197       # Otherwise, the not-included assertions might falsely pass because
198       # the modal hasn't loaded yet.
199       find(".selectable", text: name).click
200       assert(has_no_selector?(".modal-dialog-preview-pane"),
201              "preview pane available in sharing dialog")
202       if share_type == 'users' and obj and obj['email']
203         assert(page.has_text?(obj['email']), "Did not find user's email")
204       end
205       assert_raises(Capybara::ElementNotFound,
206                     "Projects pulldown available from sharing dialog") do
207         click_on "All projects"
208       end
209       click_on "Add"
210     end
211     using_wait_time(Capybara.default_wait_time * 3) do
212       assert(page.has_link?(name),
213              "new share was not added to sharing table")
214       assert_equal(start_share_count + 1, share_rows.size,
215                    "new share did not add row to sharing table")
216     end
217   end
218
219   def modify_share_and_check(name)
220     start_rows = share_rows
221     link_row = start_rows.select { |row| row.has_text?(name) }
222     assert_equal(1, link_row.size, "row with new permission not found")
223     within(link_row.first) do
224       click_on("Read")
225       select("Write", from: "share_change_level")
226       click_on("editable-submit")
227       assert(has_link?("Write"),
228              "failed to change access level on new share")
229       click_on "Revoke"
230       page.driver.browser.switch_to.alert.accept
231     end
232     wait_for_ajax
233     using_wait_time(Capybara.default_wait_time * 3) do
234       assert(page.has_no_text?(name),
235              "new share row still exists after being revoked")
236       assert_equal(start_rows.size - 1, share_rows.size,
237                    "revoking share did not remove row from sharing table")
238     end
239   end
240
241   test "project viewer can't see project sharing tab" do
242     show_project_using("project_viewer")
243     assert(page.has_no_link?("Sharing"),
244            "read-only project user sees sharing tab")
245   end
246
247   test "project owner can manage sharing for another user" do
248     add_user = api_fixture('users')['future_project_user']
249     new_name = ["first_name", "last_name"].map { |k| add_user[k] }.join(" ")
250
251     show_project_using("active")
252     click_on "Sharing"
253     add_share_and_check("users", new_name, add_user)
254     modify_share_and_check(new_name)
255   end
256
257   test "project owner can manage sharing for another group" do
258     new_name = api_fixture('groups')['future_project_viewing_group']['name']
259
260     show_project_using("active")
261     click_on "Sharing"
262     add_share_and_check("groups", new_name)
263     modify_share_and_check(new_name)
264   end
265
266   test "'share with group' listing does not offer projects" do
267     show_project_using("active")
268     click_on "Sharing"
269     click_on "Share with groups"
270     good_uuid = api_fixture("groups")["private"]["uuid"]
271     assert(page.has_selector?(".selectable[data-object-uuid=\"#{good_uuid}\"]"),
272            "'share with groups' listing missing owned user group")
273     bad_uuid = api_fixture("groups")["asubproject"]["uuid"]
274     assert(page.has_no_selector?(".selectable[data-object-uuid=\"#{bad_uuid}\"]"),
275            "'share with groups' listing includes project")
276   end
277
278   [
279     ['Move',api_fixture('collections')['collection_to_move_around_in_aproject'],
280       api_fixture('groups')['aproject'],api_fixture('groups')['asubproject']],
281     ['Remove',api_fixture('collections')['collection_to_move_around_in_aproject'],
282       api_fixture('groups')['aproject']],
283     ['Copy',api_fixture('collections')['collection_to_move_around_in_aproject'],
284       api_fixture('groups')['aproject'],api_fixture('groups')['asubproject']],
285     ['Remove',api_fixture('collections')['collection_in_aproject_with_same_name_as_in_home_project'],
286       api_fixture('groups')['aproject'],nil,true],
287   ].each do |action, my_collection, src, dest=nil, expect_name_change=nil|
288     test "selection #{action} -> #{expect_name_change.inspect} for project" do
289       perform_selection_action src, dest, my_collection, action
290
291       case action
292       when 'Copy'
293         assert page.has_text?(my_collection['name']), 'Collection not found in src project after copy'
294         visit page_with_token 'active', '/'
295         find("#projects-menu").click
296         find(".dropdown-menu a", text: dest['name']).click
297         assert page.has_text?(my_collection['name']), 'Collection not found in dest project after copy'
298
299       when 'Move'
300         assert page.has_no_text?(my_collection['name']), 'Collection still found in src project after move'
301         visit page_with_token 'active', '/'
302         find("#projects-menu").click
303         find(".dropdown-menu a", text: dest['name']).click
304         assert page.has_text?(my_collection['name']), 'Collection not found in dest project after move'
305
306       when 'Remove'
307         assert page.has_no_text?(my_collection['name']), 'Collection still found in src project after remove'
308         visit page_with_token 'active', '/'
309         find("#projects-menu").click
310         find(".dropdown-menu a", text: "Home").click
311         assert page.has_text?(my_collection['name']), 'Collection not found in home project after remove'
312         if expect_name_change
313           assert page.has_text?(my_collection['name']+' removed from ' + src['name']),
314             'Collection with update name is not found in home project after remove'
315         end
316       end
317     end
318   end
319
320   def perform_selection_action src, dest, item, action
321     visit page_with_token 'active', '/'
322     find("#projects-menu").click
323     find(".dropdown-menu a", text: src['name']).click
324     assert page.has_text?(item['name']), 'Collection not found in src project'
325
326     within('tr', text: item['name']) do
327       find('input[type=checkbox]').click
328     end
329
330     click_button 'Selection...'
331
332     within('.selection-action-container') do
333       assert page.has_text?("Compare selected"), "Compare selected link text not found"
334       assert page.has_link?("Copy selected"), "Copy selected link not found"
335       assert page.has_link?("Move selected"), "Move selected link not found"
336       assert page.has_link?("Remove selected"), "Remove selected link not found"
337
338       click_link "#{action} selected"
339     end
340
341     # select the destination project if a Copy or Move action is being performed
342     if action == 'Copy' || action == 'Move'
343       within(".modal-container") do
344         find('.selectable', text: dest['name']).click
345         find('.modal-footer a,button', text: action).click
346         wait_for_ajax
347       end
348     end
349   end
350
351   # Test copy action state. It should not be available when a subproject is selected.
352   test "copy action is disabled when a subproject is selected" do
353     my_project = api_fixture('groups')['aproject']
354     my_collection = api_fixture('collections')['collection_to_move_around_in_aproject']
355     my_subproject = api_fixture('groups')['asubproject']
356
357     # verify that selection options are disabled on the project until an item is selected
358     visit page_with_token 'active', '/'
359     find("#projects-menu").click
360     find(".dropdown-menu a", text: my_project['name']).click
361
362     click_button 'Selection...'
363     within('.selection-action-container') do
364       page.assert_selector 'li.disabled', text: 'Create new collection with selected collections'
365       page.assert_selector 'li.disabled', text: 'Compare selected'
366       page.assert_selector 'li.disabled', text: 'Copy selected'
367       page.assert_selector 'li.disabled', text: 'Move selected'
368       page.assert_selector 'li.disabled', text: 'Remove selected'
369     end
370
371     # select collection and verify links are enabled
372     visit page_with_token 'active', '/'
373     find("#projects-menu").click
374     find(".dropdown-menu a", text: my_project['name']).click
375     assert page.has_text?(my_collection['name']), 'Collection not found in project'
376
377     within('tr', text: my_collection['name']) do
378       find('input[type=checkbox]').click
379     end
380
381     click_button 'Selection...'
382     within('.selection-action-container') do
383       page.assert_no_selector 'li.disabled', text: 'Create new collection with selected collections'
384       page.assert_selector 'li', text: 'Create new collection with selected collections'
385       page.assert_selector 'li.disabled', text: 'Compare selected'
386       page.assert_no_selector 'li.disabled', text: 'Copy selected'
387       page.assert_selector 'li', text: 'Copy selected'
388       page.assert_no_selector 'li.disabled', text: 'Move selected'
389       page.assert_selector 'li', text: 'Move selected'
390       page.assert_no_selector 'li.disabled', text: 'Remove selected'
391       page.assert_selector 'li', text: 'Remove selected'
392     end
393
394     # select subproject and verify that copy action is disabled
395     visit page_with_token 'active', '/'
396     find("#projects-menu").click
397     find(".dropdown-menu a", text: my_project['name']).click
398
399     click_link 'Subprojects'
400     assert page.has_text?(my_subproject['name']), 'Subproject not found in project'
401
402     within('tr', text: my_subproject['name']) do
403       find('input[type=checkbox]').click
404     end
405
406     click_button 'Selection...'
407     within('.selection-action-container') do
408       page.assert_selector 'li.disabled', text: 'Create new collection with selected collections'
409       page.assert_selector 'li.disabled', text: 'Compare selected'
410       page.assert_selector 'li.disabled', text: 'Copy selected'
411       page.assert_no_selector 'li.disabled', text: 'Move selected'
412       page.assert_selector 'li', text: 'Move selected'
413       page.assert_no_selector 'li.disabled', text: 'Remove selected'
414       page.assert_selector 'li', text: 'Remove selected'
415     end
416
417     # select subproject and a collection and verify that copy action is still disabled
418     visit page_with_token 'active', '/'
419     find("#projects-menu").click
420     find(".dropdown-menu a", text: my_project['name']).click
421
422     click_link 'Subprojects'
423     assert page.has_text?(my_subproject['name']), 'Subproject not found in project'
424
425     within('tr', text: my_subproject['name']) do
426       find('input[type=checkbox]').click
427     end
428
429     click_link 'Data collections'
430     assert page.has_text?(my_collection['name']), 'Collection not found in project'
431
432     within('tr', text: my_collection['name']) do
433       find('input[type=checkbox]').click
434     end
435
436     click_button 'Selection...'
437     within('.selection-action-container') do
438       page.assert_selector 'li.disabled', text: 'Create new collection with selected collections'
439       page.assert_selector 'li.disabled', text: 'Compare selected'
440       page.assert_selector 'li.disabled', text: 'Copy selected'
441       page.assert_no_selector 'li.disabled', text: 'Move selected'
442       page.assert_selector 'li', text: 'Move selected'
443       page.assert_no_selector 'li.disabled', text: 'Remove selected'
444       page.assert_selector 'li', text: 'Remove selected'
445     end
446   end
447
448   [
449     ['active', true],
450     ['project_viewer', false],
451   ].each do |user, expect_collection_in_aproject|
452     test "combine selected collections into new collection #{user} #{expect_collection_in_aproject}" do
453       my_project = api_fixture('groups')['aproject']
454       my_collection = api_fixture('collections')['collection_to_move_around_in_aproject']
455
456       visit page_with_token user, '/'
457       find("#projects-menu").click
458       find(".dropdown-menu a", text: my_project['name']).click
459       assert page.has_text?(my_collection['name']), 'Collection not found in project'
460
461       within('tr', text: my_collection['name']) do
462         find('input[type=checkbox]').click
463       end
464
465       click_button 'Selection...'
466       within('.selection-action-container') do
467         click_link 'Create new collection with selected collections'
468       end
469
470       # now in the new collection page
471       if expect_collection_in_aproject
472         assert page.has_text?("Created new collection in the project #{my_project['name']}"),
473                               'Not found flash message that new collection is created in aproject'
474       else
475         assert page.has_text?("Created new collection in your Home project"),
476                               'Not found flash message that new collection is created in Home project'
477       end
478       assert page.has_text?('Content hash'), 'Not found content hash in collection page'
479     end
480   end
481
482   [
483     ["jobs", "/jobs"],
484     ["pipelines", "/pipeline_instances"],
485     ["collections", "/collections"]
486   ].each do |target,path|
487     test "Test dashboard button all #{target}" do
488       visit page_with_token 'active', '/'
489       click_link "All #{target}"
490       assert_equal path, current_path
491     end
492   end
493
494   def scroll_setup(project_name,
495                    total_nbr_items,
496                    item_list_parameter,
497                    sorted = false,
498                    sort_parameters = nil)
499     project_uuid = api_fixture('groups')[project_name]['uuid']
500     visit page_with_token 'user1_with_load', '/projects/' + project_uuid
501
502     assert(page.has_text?("#{item_list_parameter.humanize} (#{total_nbr_items})"), "Number of #{item_list_parameter.humanize} did not match the input amount")
503
504     click_link item_list_parameter.humanize
505     wait_for_ajax
506
507     if sorted
508       find("th[data-sort-order='#{sort_parameters.gsub(/\s/,'')}']").click
509       wait_for_ajax
510     end
511   end
512
513   def scroll_items_check(nbr_items,
514                          fixture_prefix,
515                          item_list_parameter,
516                          item_selector,
517                          sorted = false)
518     items = []
519     for i in 1..nbr_items
520       items << "#{fixture_prefix}#{i}"
521     end
522
523     verify_items = items.dup
524     unexpected_items = []
525     item_count = 0
526     within(".arv-project-#{item_list_parameter}") do
527       page.execute_script "window.scrollBy(0,999000)"
528       begin
529         wait_for_ajax
530       rescue
531       end
532
533       # Visit all rows. If not all expected items are found, retry
534       found_items = page.all(item_selector)
535       item_count = found_items.count
536
537       previous = nil
538       (0..item_count-1).each do |i|
539         # Found row text using the fixture string e.g. "Show Collection_#{n} "
540         item_name = found_items[i].text.split[1]
541         if !items.include? item_name
542           unexpected_items << item_name
543         else
544           verify_items.delete item_name
545         end
546         if sorted
547           # check sort order
548           assert_operator( previous.downcase, :<=, item_name.downcase) if previous
549           previous = item_name
550         end
551       end
552
553       assert_equal true, unexpected_items.empty?, "Found unexpected #{item_list_parameter.humanize} #{unexpected_items.inspect}"
554       assert_equal nbr_items, item_count, "Found different number of #{item_list_parameter.humanize}"
555       assert_equal true, verify_items.empty?, "Did not find all the #{item_list_parameter.humanize}"
556     end
557   end
558
559   [
560     ['project_with_10_collections', 10],
561     ['project_with_201_collections', 201], # two pages of data
562   ].each do |project_name, nbr_items|
563     test "scroll collections tab for #{project_name} with #{nbr_items} objects" do
564       item_list_parameter = "Data_collections"
565       scroll_setup project_name,
566                    nbr_items,
567                    item_list_parameter
568       scroll_items_check nbr_items,
569                          "Collection_",
570                          item_list_parameter,
571                          'tr[data-kind="arvados#collection"]'
572     end
573   end
574
575   [
576     ['project_with_10_collections', 10],
577     ['project_with_201_collections', 201], # two pages of data
578   ].each do |project_name, nbr_items|
579     test "scroll collections tab for #{project_name} with #{nbr_items} objects with ascending sort (case insensitive)" do
580       item_list_parameter = "Data_collections"
581       scroll_setup project_name,
582                    nbr_items,
583                    item_list_parameter,
584                    true,
585                    "collections.name"
586       scroll_items_check nbr_items,
587                          "Collection_",
588                          item_list_parameter,
589                          'tr[data-kind="arvados#collection"]',
590                          true
591     end
592   end
593
594   [
595     ['project_with_10_pipelines', 10, 0],
596     ['project_with_2_pipelines_and_60_jobs', 2, 60],
597     ['project_with_25_pipelines', 25, 0],
598   ].each do |project_name, num_pipelines, num_jobs|
599     test "scroll pipeline instances tab for #{project_name} with #{num_pipelines} pipelines and #{num_jobs} jobs" do
600       item_list_parameter = "Jobs_and_pipelines"
601       scroll_setup project_name,
602                    num_pipelines + num_jobs,
603                    item_list_parameter
604       # check the general scrolling and the pipelines
605       scroll_items_check num_pipelines,
606                          "pipeline_",
607                          item_list_parameter,
608                          'tr[data-kind="arvados#pipelineInstance"]'
609       # Check job count separately
610       jobs_found = page.all('tr[data-kind="arvados#job"]')
611       found_job_count = jobs_found.count
612       assert_equal num_jobs, found_job_count, 'Did not find expected number of jobs'
613     end
614   end
615
616   # Move button accessibility
617   [
618     ['admin', true],
619     ['active', true],  # project owner
620     ['project_viewer', false],
621     ].each do |user, can_move|
622     test "#{user} can move subproject under another user's Home #{can_move}" do
623       project = api_fixture('groups')['aproject']
624       collection = api_fixture('collections')['collection_to_move_around_in_aproject']
625
626       # verify the project move button
627       visit page_with_token user, "/projects/#{project['uuid']}"
628       if can_move
629         assert page.has_link? 'Move project...'
630       else
631         assert page.has_no_link? 'Move project...'
632       end
633     end
634   end
635
636   test "error while loading tab" do
637     original_arvados_v1_base = Rails.configuration.arvados_v1_base
638
639     visit page_with_token 'active', '/projects/' + api_fixture('groups')['aproject']['uuid']
640
641     # Point to a bad api server url to generate error
642     Rails.configuration.arvados_v1_base = "https://[100::f]:1/"
643     click_link 'Other objects'
644     within '#Other_objects' do
645       # Error
646       assert_selector('a', text: 'Reload tab')
647
648       # Now point back to the orig api server and reload tab
649       Rails.configuration.arvados_v1_base = original_arvados_v1_base
650       click_link 'Reload tab'
651       assert_no_selector('a', text: 'Reload tab')
652       assert_selector('button', text: 'Selection...')
653       within '.selection-action-container' do
654         assert_selector 'tr[data-kind="arvados#trait"]'
655       end
656     end
657   end
658
659 end