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