4465: add trailing /? to regexes
[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   # "Remove selected" selection option should not be available when current user cannot write to the project
449   test "remove selected action is not available when current user cannot write to project" do
450     my_project = api_fixture('groups')['anonymously_accessible_project']
451     visit page_with_token 'active', "/projects/#{my_project['uuid']}"
452
453     click_button 'Selection'
454     within('.selection-action-container') do
455       assert_selector 'li', text: 'Create new collection with selected collections'
456       assert_selector 'li', text: 'Compare selected'
457       assert_selector 'li', text: 'Copy selected'
458       assert_selector 'li', text: 'Move selected'
459       assert_no_selector 'li', text: 'Remove selected'
460     end
461   end
462
463   [
464     ['active', true],
465     ['project_viewer', false],
466   ].each do |user, expect_collection_in_aproject|
467     test "combine selected collections into new collection #{user} #{expect_collection_in_aproject}" do
468       my_project = api_fixture('groups')['aproject']
469       my_collection = api_fixture('collections')['collection_to_move_around_in_aproject']
470
471       visit page_with_token user, '/'
472       find("#projects-menu").click
473       find(".dropdown-menu a", text: my_project['name']).click
474       assert page.has_text?(my_collection['name']), 'Collection not found in project'
475
476       within('tr', text: my_collection['name']) do
477         find('input[type=checkbox]').click
478       end
479
480       click_button 'Selection'
481       within('.selection-action-container') do
482         click_link 'Create new collection with selected collections'
483       end
484
485       # now in the new collection page
486       if expect_collection_in_aproject
487         assert page.has_text?("Created new collection in the project #{my_project['name']}"),
488                               'Not found flash message that new collection is created in aproject'
489       else
490         assert page.has_text?("Created new collection in your Home project"),
491                               'Not found flash message that new collection is created in Home project'
492       end
493       assert page.has_text?('Content hash'), 'Not found content hash in collection page'
494     end
495   end
496
497   [
498     ["jobs", "/jobs"],
499     ["pipelines", "/pipeline_instances"],
500     ["collections", "/collections"]
501   ].each do |target,path|
502     test "Test dashboard button all #{target}" do
503       visit page_with_token 'active', '/'
504       click_link "All #{target}"
505       assert_equal path, current_path
506     end
507   end
508
509   def scroll_setup(project_name,
510                    total_nbr_items,
511                    item_list_parameter,
512                    sorted = false,
513                    sort_parameters = nil)
514     project_uuid = api_fixture('groups')[project_name]['uuid']
515     visit page_with_token 'user1_with_load', '/projects/' + project_uuid
516
517     assert(page.has_text?("#{item_list_parameter.humanize} (#{total_nbr_items})"), "Number of #{item_list_parameter.humanize} did not match the input amount")
518
519     click_link item_list_parameter.humanize
520     wait_for_ajax
521
522     if sorted
523       find("th[data-sort-order='#{sort_parameters.gsub(/\s/,'')}']").click
524       wait_for_ajax
525     end
526   end
527
528   def scroll_items_check(nbr_items,
529                          fixture_prefix,
530                          item_list_parameter,
531                          item_selector,
532                          sorted = false)
533     items = []
534     for i in 1..nbr_items
535       items << "#{fixture_prefix}#{i}"
536     end
537
538     verify_items = items.dup
539     unexpected_items = []
540     item_count = 0
541     within(".arv-project-#{item_list_parameter}") do
542       page.execute_script "window.scrollBy(0,999000)"
543       begin
544         wait_for_ajax
545       rescue
546       end
547
548       # Visit all rows. If not all expected items are found, retry
549       found_items = page.all(item_selector)
550       item_count = found_items.count
551
552       previous = nil
553       (0..item_count-1).each do |i|
554         # Found row text using the fixture string e.g. "Show Collection_#{n} "
555         item_name = found_items[i].text.split[1]
556         if !items.include? item_name
557           unexpected_items << item_name
558         else
559           verify_items.delete item_name
560         end
561         if sorted
562           # check sort order
563           assert_operator( previous.downcase, :<=, item_name.downcase) if previous
564           previous = item_name
565         end
566       end
567
568       assert_equal true, unexpected_items.empty?, "Found unexpected #{item_list_parameter.humanize} #{unexpected_items.inspect}"
569       assert_equal nbr_items, item_count, "Found different number of #{item_list_parameter.humanize}"
570       assert_equal true, verify_items.empty?, "Did not find all the #{item_list_parameter.humanize}"
571     end
572   end
573
574   [
575     ['project_with_10_collections', 10],
576     ['project_with_201_collections', 201], # two pages of data
577   ].each do |project_name, nbr_items|
578     test "scroll collections tab for #{project_name} with #{nbr_items} objects" do
579       item_list_parameter = "Data_collections"
580       scroll_setup project_name,
581                    nbr_items,
582                    item_list_parameter
583       scroll_items_check nbr_items,
584                          "Collection_",
585                          item_list_parameter,
586                          'tr[data-kind="arvados#collection"]'
587     end
588   end
589
590   [
591     ['project_with_10_collections', 10],
592     ['project_with_201_collections', 201], # two pages of data
593   ].each do |project_name, nbr_items|
594     test "scroll collections tab for #{project_name} with #{nbr_items} objects with ascending sort (case insensitive)" do
595       item_list_parameter = "Data_collections"
596       scroll_setup project_name,
597                    nbr_items,
598                    item_list_parameter,
599                    true,
600                    "collections.name"
601       scroll_items_check nbr_items,
602                          "Collection_",
603                          item_list_parameter,
604                          'tr[data-kind="arvados#collection"]',
605                          true
606     end
607   end
608
609   [
610     ['project_with_10_pipelines', 10, 0],
611     ['project_with_2_pipelines_and_60_jobs', 2, 60],
612     ['project_with_25_pipelines', 25, 0],
613   ].each do |project_name, num_pipelines, num_jobs|
614     test "scroll pipeline instances tab for #{project_name} with #{num_pipelines} pipelines and #{num_jobs} jobs" do
615       item_list_parameter = "Jobs_and_pipelines"
616       scroll_setup project_name,
617                    num_pipelines + num_jobs,
618                    item_list_parameter
619       # check the general scrolling and the pipelines
620       scroll_items_check num_pipelines,
621                          "pipeline_",
622                          item_list_parameter,
623                          'tr[data-kind="arvados#pipelineInstance"]'
624       # Check job count separately
625       jobs_found = page.all('tr[data-kind="arvados#job"]')
626       found_job_count = jobs_found.count
627       assert_equal num_jobs, found_job_count, 'Did not find expected number of jobs'
628     end
629   end
630
631   # Move button accessibility
632   [
633     ['admin', true],
634     ['active', true],  # project owner
635     ['project_viewer', false],
636     ].each do |user, can_move|
637     test "#{user} can move subproject under another user's Home #{can_move}" do
638       project = api_fixture('groups')['aproject']
639       collection = api_fixture('collections')['collection_to_move_around_in_aproject']
640
641       # verify the project move button
642       visit page_with_token user, "/projects/#{project['uuid']}"
643       if can_move
644         assert page.has_link? 'Move project...'
645       else
646         assert page.has_no_link? 'Move project...'
647       end
648     end
649   end
650
651   test "error while loading tab" do
652     original_arvados_v1_base = Rails.configuration.arvados_v1_base
653
654     visit page_with_token 'active', '/projects/' + api_fixture('groups')['aproject']['uuid']
655
656     # Point to a bad api server url to generate error
657     Rails.configuration.arvados_v1_base = "https://[100::f]:1/"
658     click_link 'Other objects'
659     within '#Other_objects' do
660       # Error
661       assert_selector('a', text: 'Reload tab')
662
663       # Now point back to the orig api server and reload tab
664       Rails.configuration.arvados_v1_base = original_arvados_v1_base
665       click_link 'Reload tab'
666       assert_no_selector('a', text: 'Reload tab')
667       assert_selector('button', text: 'Selection')
668       within '.selection-action-container' do
669         assert_selector 'tr[data-kind="arvados#trait"]'
670       end
671     end
672   end
673
674 end