diff --git a/docs/testing/testing.md b/docs/testing/testing.md index d34f9e9b64e..d87e61315a1 100644 --- a/docs/testing/testing.md +++ b/docs/testing/testing.md @@ -28,7 +28,7 @@ We have a steadily growing coverage of unit tests. You can run them locally via ```shell $ pnpm test:unit -$ pnpm -r test:unit +$ pnpm -r test:unit ``` You can also specify which package to run the test on, such as: `pnpm --filter @ownclouders/web-pkg test:unit`. @@ -235,3 +235,10 @@ All tests which are related to: - Admin Actions - Groups + +**The tests might show flakiness or fail due to the following reasons:** + +- Slower network connection +- Features enabled/disabled +- Running latest tests against an older version of oCIS/Web +- Large file uploads may take longer time diff --git a/tests/e2e/cucumber/features/app-provider/lock.feature b/tests/e2e/cucumber/features/app-provider/lock.feature index d787fe8d5d7..f95772794df 100644 --- a/tests/e2e/cucumber/features/app-provider/lock.feature +++ b/tests/e2e/cucumber/features/app-provider/lock.feature @@ -16,8 +16,8 @@ Feature: lock | resource | type | content | | test.odt | OpenDocument | some content | And "Alice" shares the following resource using API - | resource | recipient | type | role | - | test.odt | Brian | user | Can edit | + | resource | recipient | type | role | resourceType | + | test.odt | Brian | user | Can edit | file | And "Brian" logs in And "Brian" navigates to the shared with me page When "Brian" opens the following file in Collabora diff --git a/tests/e2e/cucumber/features/app-provider/secureView.feature b/tests/e2e/cucumber/features/app-provider/secureView.feature index 6411e9c30e0..2f33f001b36 100644 --- a/tests/e2e/cucumber/features/app-provider/secureView.feature +++ b/tests/e2e/cucumber/features/app-provider/secureView.feature @@ -12,6 +12,7 @@ Feature: Secure view | Alice | | Brian | And "Alice" logs in + And "Brian" logs in And "Alice" opens the "files" app @@ -29,14 +30,13 @@ Feature: Secure view | secureDocument.odt | OpenDocument | very important document | And "Alice" shares the following resources using the sidebar panel - | resource | recipient | type | role | - | secureDocument.odt | Brian | user | Can view (secure) | - | shared folder | Brian | user | Can view (secure) | + | resource | recipient | type | role | resourceType | + | secureDocument.odt | Brian | user | Can view (secure) | file | + | shared folder | Brian | user | Can view (secure) | folder | And "Alice" logs out - And "Brian" logs in - And "Brian" navigates to the shared with me page - When "Brian" opens the following file in Collabora + When "Brian" navigates to the shared with me page + And "Brian" opens the following file in Collabora | resource | | secureDocument.odt | @@ -75,14 +75,12 @@ Feature: Secure view | resource | type | content | | secureDocument.odt | OpenDocument | very important document | And "Alice" shares the following resources using the sidebar panel - | resource | recipient | type | role | - | secureDocument.odt | Brian | user | Can view (secure) | - | shared folder | Brian | user | Can view (secure) | + | resource | recipient | type | role | resourceType | + | secureDocument.odt | Brian | user | Can view (secure) | file | + | shared folder | Brian | user | Can view (secure) | fodler | And "Alice" logs out - And "Brian" logs in - And "Brian" navigates to the shared with me page - + When "Brian" navigates to the shared with me page # .odt file Then "Brian" should see following actions for file "secureDocument.odt" | action | diff --git a/tests/e2e/cucumber/features/file-action/copyMove.feature b/tests/e2e/cucumber/features/file-action/copyMove.feature index 9edc5bad56b..84b4bfd8f6e 100644 --- a/tests/e2e/cucumber/features/file-action/copyMove.feature +++ b/tests/e2e/cucumber/features/file-action/copyMove.feature @@ -8,6 +8,7 @@ Feature: Copy Given "Admin" creates following user using API | id | | Alice | + And "Alice" logs in And "Alice" creates the following folders in personal space using API | name | | PARENTCopy1 | @@ -37,7 +38,6 @@ Feature: Copy | PARENT/fileToCopy3.txt | some content | | PARENT/fileToCopy4.txt | some content | | PARENT/fileToCopy5.txt | some content | - And "Alice" logs in When "Alice" duplicates the following resource using sidebar-panel | resource | @@ -190,6 +190,7 @@ Feature: Copy Given "Admin" creates following user using API | id | | Alice | + And "Alice" logs in And "Alice" creates the following folders in personal space using API | name | | sub | @@ -202,7 +203,6 @@ Feature: Copy | folder1/example1.txt | folder1 location | | sub/folder1/example1.txt | sub/folder1 location | | sub1/folder1/example1.txt | sub1/folder1 location | - And "Alice" logs in # copy and move file When "Alice" copies the following resource using sidebar-panel diff --git a/tests/e2e/cucumber/features/file-action/download.feature b/tests/e2e/cucumber/features/file-action/download.feature index aacbcbb3158..059f7765090 100644 --- a/tests/e2e/cucumber/features/file-action/download.feature +++ b/tests/e2e/cucumber/features/file-action/download.feature @@ -12,6 +12,8 @@ Feature: Download Scenario: download resources + Given "Alice" logs in + And "Brian" logs in And "Alice" creates the following folders in personal space using API | name | | folderPublic | @@ -23,13 +25,12 @@ Feature: Download | localFile | to | | filesForUpload/testavatar.jpg | testavatar.jpg | And "Alice" shares the following resource using API - | resource | recipient | type | role | - | folderPublic | Brian | user | Can edit | - | emptyFolder | Brian | user | Can edit | - | testavatar.jpg | Brian | user | Can edit | + | resource | recipient | type | role | resourceType | + | folderPublic | Brian | user | Can edit | folder | + | emptyFolder | Brian | user | Can edit | folder | + | testavatar.jpg | Brian | user | Can edit | file | - When "Alice" logs in - And "Alice" downloads the following resources using the batch action + When "Alice" downloads the following resources using the batch action | resource | type | | folderPublic | folder | | emptyFolder | folder | @@ -43,8 +44,7 @@ Feature: Download And "Alice" closes the file viewer And "Alice" logs out - And "Brian" logs in - And "Brian" navigates to the shared with me page + When "Brian" navigates to the shared with me page And "Brian" downloads the following resources using the batch action | resource | type | | folderPublic | folder | diff --git a/tests/e2e/cucumber/features/file-action/fileViewer.feature b/tests/e2e/cucumber/features/file-action/fileViewer.feature new file mode 100644 index 00000000000..3bc639a1818 --- /dev/null +++ b/tests/e2e/cucumber/features/file-action/fileViewer.feature @@ -0,0 +1,66 @@ +@predefined-users +Feature: Different file viewers + + + Scenario: file viewers + Given "Admin" creates following user using API + | id | + | Alice | + And "Alice" logs in + When "Alice" creates the following resources + | resource | type | content | + | lorem.txt | txtFile | some text | + | lorem.md | mdFile | readme | + And "Alice" edits the following resources + | resource | content | + | lorem.txt | new content edited | + | lorem.md | new readme content edited | + And "Alice" uploads the following resource + | resource | + | simple.pdf | + | sampleGif.gif | + | testimage.mp3 | + | sampleOgg.ogg | + | sampleWebm.webm | + | test_video.mp4 | + | testavatar.jpeg | + | testavatar.png | + Then "Alice" should see thumbnail and preview for file "sampleGif.gif" + And "Alice" should see thumbnail and preview for file "testavatar.jpeg" + And "Alice" should see thumbnail and preview for file "testavatar.png" + When "Alice" opens a file "testavatar.png" in the media-viewer using the sidebar panel + Then "Alice" is in a media-viewer + When "Alice" closes the file viewer + And "Alice" opens the following file in mediaviewer + | resource | + | testavatar.jpeg | + Then "Alice" is in a media-viewer + When "Alice" navigates to the next media resource + And "Alice" navigates to the previous media resource + And "Alice" closes the file viewer + And "Alice" opens the following file in mediaviewer + | resource | + | sampleGif.gif | + Then "Alice" is in a media-viewer + When "Alice" closes the file viewer + And "Alice" opens the following file in mediaviewer + | resource | + | testimage.mp3 | + Then "Alice" is in a media-viewer + When "Alice" closes the file viewer + And "Alice" opens the following file in mediaviewer + | resource | + | sampleOgg.ogg | + Then "Alice" is in a media-viewer + When "Alice" closes the file viewer + And "Alice" opens the following file in mediaviewer + | resource | + | sampleWebm.webm | + Then "Alice" is in a media-viewer + When "Alice" closes the file viewer + And "Alice" opens the following file in mediaviewer + | resource | + | test_video.mp4 | + Then "Alice" is in a media-viewer + And "Alice" closes the file viewer + And "Alice" logs out \ No newline at end of file diff --git a/tests/e2e/cucumber/features/file-action/groupActions.feature b/tests/e2e/cucumber/features/file-action/groupActions.feature index 79fa52907d4..7ca32fb439b 100644 --- a/tests/e2e/cucumber/features/file-action/groupActions.feature +++ b/tests/e2e/cucumber/features/file-action/groupActions.feature @@ -36,25 +36,25 @@ Feature: Group actions | folder5 | | parentFolder/SubFolder | And "Alice" shares the following resource using API - | resource | recipient | type | role | - | folder1 | Brian | user | Can edit without versions | - | folder2 | Brian | user | Can edit without versions | - | folder3 | Brian | user | Can edit without versions | - | folder4 | Brian | user | Can edit without versions | - | folder5 | Brian | user | Can edit without versions | - | parentFolder | Brian | user | Can edit without versions | + | resource | recipient | type | role | resourceType | + | folder1 | Brian | user | Can edit without versions | folder | + | folder2 | Brian | user | Can edit without versions | folder | + | folder3 | Brian | user | Can edit without versions | folder | + | folder4 | Brian | user | Can edit without versions | folder | + | folder5 | Brian | user | Can edit without versions | folder | + | parentFolder | Brian | user | Can edit without versions | folder | And "Alice" logs in # multiple share And "Alice" shares the following resources using the sidebar panel - | resource | recipient | type | role | - | sharedFolder | Brian | user | Can edit without versions | - | sharedFolder | Carol | user | Can edit without versions | - | sharedFolder | David | user | Can edit without versions | - | sharedFolder | Edith | user | Can edit without versions | - | sharedFolder | sales | group | Can edit without versions | - | sharedFolder | finance | group | Can edit without versions | - | sharedFolder | security | group | Can edit without versions | + | resource | recipient | type | role | resourceType | + | sharedFolder | Brian | user | Can edit without versions | folder | + | sharedFolder | Carol | user | Can edit without versions | folder | + | sharedFolder | David | user | Can edit without versions | folder | + | sharedFolder | Edith | user | Can edit without versions | folder | + | sharedFolder | sales | group | Can edit without versions | folder | + | sharedFolder | finance | group | Can edit without versions | folder | + | sharedFolder | security | group | Can edit without versions | folder | And "Brian" navigates to the shared with me page diff --git a/tests/e2e/cucumber/features/file-action/rename.feature b/tests/e2e/cucumber/features/file-action/rename.feature index 328336b49f5..d825884bdd2 100644 --- a/tests/e2e/cucumber/features/file-action/rename.feature +++ b/tests/e2e/cucumber/features/file-action/rename.feature @@ -9,6 +9,8 @@ Feature: rename | id | | Alice | | Brian | + And "Alice" logs in + And "Brian" logs in And "Alice" creates the following folders in personal space using API | name | | folder | @@ -16,15 +18,14 @@ Feature: rename | pathToFile | content | | folder/example.txt | example text | And "Alice" shares the following resource using API - | resource | recipient | type | role | - | folder | Brian | user | Can edit | + | resource | resourceType | recipient | type | role | + | folder | folder | Brian | user | Can edit | And "Alice" creates a public link of following resource using API | resource | role | password | | folder | Can edit | %public% | - And "Brian" logs in + And "Brian" navigates to the shared with me page And "Brian" opens folder "folder" - # rename in the shares with me page When "Brian" renames the following resource | resource | as | @@ -39,7 +40,6 @@ Feature: rename | renamedByBrian.txt | renamedByAnonymous.txt | # rename in the shares with other page - And "Alice" logs in And "Alice" navigates to the shared with others page And "Alice" opens folder "folder" When "Alice" renames the following resource diff --git a/tests/e2e/cucumber/features/navigation/personalSpacePagination.feature b/tests/e2e/cucumber/features/navigation/personalSpacePagination.feature index d5b1b2a251a..4534026acd7 100644 --- a/tests/e2e/cucumber/features/navigation/personalSpacePagination.feature +++ b/tests/e2e/cucumber/features/navigation/personalSpacePagination.feature @@ -5,21 +5,23 @@ Feature: check files pagination in personal space So that I do not have to scroll deep down - Scenario: pagination - Given "Admin" creates following user using API - | id | - | Alice | - And "Alice" creates 55 folders in personal space using API - And "Alice" creates 55 files in personal space using API - And "Alice" creates the following files into personal space using API - | pathToFile | content | - | .hidden-testFile.txt | This is a hidden file. | - And "Alice" logs in - When "Alice" navigates to page "2" of the personal space files view - Then "Alice" should see the text "111 items with 1 kB in total (56 files including 1 hidden, 55 folders)" at the footer of the page - And "Alice" should see 10 resources in the personal space files view - When "Alice" enables the option to display the hidden file - Then "Alice" should see 11 resources in the personal space files view - When "Alice" changes the items per page to "500" - Then "Alice" should not see the pagination in the personal space files view - And "Alice" logs out + Scenario: pagination + Given "Admin" creates following user using API + | id | + | Alice | + And "Alice" logs in + And "Alice" creates 15 folders in personal space using API + And "Alice" creates 10 files in personal space using API + And "Alice" creates the following files into personal space using API + | pathToFile | content | + | .hidden-testFile.txt | This is a hidden file. | + When "Alice" opens the "files" app + And "Alice" changes the items per page to "20" + Then "Alice" should see the text "26 items with 223 B in total (11 files including 1 hidden, 15 folders)" at the footer of the page + When "Alice" navigates to page "2" of the personal space files view + Then "Alice" should see 5 resources in the personal space files view + When "Alice" enables the option to display the hidden file + Then "Alice" should see 6 resources in the personal space files view + When "Alice" changes the items per page to "500" + Then "Alice" should not see the pagination in the personal space files view + And "Alice" logs out diff --git a/tests/e2e/cucumber/features/navigation/shortcut.feature b/tests/e2e/cucumber/features/navigation/shortcut.feature index 33a3318b0b3..42c7b394af6 100644 --- a/tests/e2e/cucumber/features/navigation/shortcut.feature +++ b/tests/e2e/cucumber/features/navigation/shortcut.feature @@ -8,7 +8,9 @@ Feature: Users can create shortcuts for resources and sites | Brian | Scenario: shortcut - When "Alice" creates the following folders in personal space using API + Given "Alice" logs in + And "Brian" logs in + And "Alice" creates the following folders in personal space using API | name | | docs | And "Alice" creates the following files into personal space using API @@ -18,14 +20,13 @@ Feature: Users can create shortcuts for resources and sites | localFile | to | | filesForUpload/testavatar.jpg | testavatar.jpg | And "Alice" shares the following resource using API - | resource | recipient | type | role | - | testavatar.jpg | Brian | user | Can view | + | resource | recipient | type | role | resourceType | + | testavatar.jpg | Brian | user | Can view | file | And "Alice" creates a public link of following resource using API | resource | password | | docs/notice.txt | %public% | - And "Alice" logs in And "Alice" renames the most recently created public link of resource "docs/notice.txt" to "myPublicLink" - And "Alice" opens the "files" app + When "Alice" opens the "files" app # create a shortcut to file folder website And "Alice" creates a shortcut for the following resources @@ -46,8 +47,7 @@ Feature: Users can create shortcuts for resources and sites And "Alice" logs out # create a shortcut to the shared file - When "Brian" logs in - And "Brian" creates a shortcut for the following resources + When "Brian" creates a shortcut for the following resources | resource | name | type | | testavatar.jpg | logo | file | And "Brian" opens a shortcut "logo.url" diff --git a/tests/e2e/cucumber/features/search/fullTextSearch.feature b/tests/e2e/cucumber/features/search/fullTextSearch.feature index 215c87b5e17..308dcdc57d3 100644 --- a/tests/e2e/cucumber/features/search/fullTextSearch.feature +++ b/tests/e2e/cucumber/features/search/fullTextSearch.feature @@ -18,8 +18,8 @@ Feature: Search | resource | tags | | fileToShare.txt | alice tag | And "Alice" shares the following resource using API - | resource | recipient | type | role | - | fileToShare.txt | Brian | user | Can edit | + | resource | recipient | type | role | resourceType | + | fileToShare.txt | Brian | user | Can edit | file | And "Brian" creates the following folder in personal space using API | name | | testFolder | diff --git a/tests/e2e/cucumber/features/search/search.feature b/tests/e2e/cucumber/features/search/search.feature index 2493d531b69..4d70d8a430a 100644 --- a/tests/e2e/cucumber/features/search/search.feature +++ b/tests/e2e/cucumber/features/search/search.feature @@ -9,21 +9,20 @@ Feature: Search | id | | Alice | | Brian | - | Carol | + And "Brian" logs in + And "Alice" logs in And "Brian" creates the following folder in personal space using API | name | | new_share_from_brian | And "Brian" uploads the following local file into personal space using API | localFile | to | | filesForUpload/new-lorem-big.txt | new-lorem-big.txt | - And "Brian" logs in And "Brian" shares the following resource using the sidebar panel | resource | recipient | type | role | resourceType | | new_share_from_brian | Alice | user | Can view | folder | | new-lorem-big.txt | Alice | user | Can view | file | And "Brian" logs out - When "Alice" logs in And "Alice" creates the following resources | resource | type | | folder | folder | @@ -123,6 +122,7 @@ Feature: Search Given "Admin" creates following users using API | id | | Alice | + And "Alice" logs in And "Alice" creates the following folders in personal space using API | name | | mainFolder/subFolder | @@ -131,7 +131,6 @@ Feature: Search | exampleInsideThePersonalSpace.txt | I'm in the personal Space | | mainFolder/exampleInsideTheMainFolder.txt | I'm in the main folder | | mainFolder/subFolder/exampleInsideTheSubFolder.txt | I'm in the sub folder | - And "Alice" logs in When "Alice" opens folder "mainFolder" And "Alice" searches "example" using the global search and the "all files" filter Then following resources should be displayed in the search list for user "Alice" @@ -155,6 +154,7 @@ Feature: Search Given "Admin" creates following users using API | id | | Alice | + And "Alice" logs in And "Alice" creates the following folders in personal space using API | name | | mediaTest | @@ -167,7 +167,6 @@ Feature: Search | mediaTest.pdf | I'm a PDF | | mediaTest.mp3 | I'm a Audio | | mediaTest.zip | I'm a Archive | - And "Alice" logs in And "Alice" searches "mediaTest" using the global search and the "all files" filter and presses enter And "Alice" enables the option to search title only And "Alice" selects mediaType "Document" from the search result filter chip @@ -210,6 +209,7 @@ Feature: Search Given "Admin" creates following users using API | id | | Alice | + And "Alice" logs in And "Alice" creates the following folders in personal space using API | name | | mainFolder | @@ -218,7 +218,6 @@ Feature: Search | mainFolder/mediaTest.pdf | created 29 days ago | -29 days | | mainFolder/mediaTest.txt | created 5 days ago | -5 days | | mainFolder/mediaTest.md | created today | | - And "Alice" logs in When "Alice" opens folder "mainFolder" And "Alice" searches "mediaTest" using the global search and the "current folder" filter and presses enter And "Alice" enables the option to search title only diff --git a/tests/e2e/cucumber/features/shares/denyShareAccess.feature b/tests/e2e/cucumber/features/shares/denyShareAccess.feature index e60d2e3bcc4..315f7e22d41 100644 --- a/tests/e2e/cucumber/features/shares/denyShareAccess.feature +++ b/tests/e2e/cucumber/features/shares/denyShareAccess.feature @@ -7,6 +7,7 @@ Feature: deny share access | Alice | | Brian | When "Alice" logs in + And "Brian" logs in And "Alice" creates the following folder in personal space using API | name | | folder_to_shared | @@ -18,11 +19,10 @@ Feature: deny share access | folder_to_shared | Brian | user | Can view | folder | And "Alice" opens folder "folder_to_shared" # deny access - When "Alice" shares the following resource using the sidebar panel + And "Alice" shares the following resource using the sidebar panel | resource | recipient | type | role | resourceType | | folder_to_deny | Brian | user | Cannot access | folder | - And "Brian" logs in - And "Brian" opens the "files" app + When "Brian" opens the "files" app And "Brian" navigates to the shared with me page And "Brian" opens folder "folder_to_shared" Then following resources should not be displayed in the files list for user "Brian" diff --git a/tests/e2e/cucumber/features/shares/internalLink.feature b/tests/e2e/cucumber/features/shares/internalLink.feature index 8ec15990ba7..42027c6aa61 100644 --- a/tests/e2e/cucumber/features/shares/internalLink.feature +++ b/tests/e2e/cucumber/features/shares/internalLink.feature @@ -7,25 +7,25 @@ Feature: internal link share | id | | Alice | | Brian | + And "Alice" logs in + And "Brian" logs in And "Alice" creates the following folder in personal space using API | name | | myfolder | And "Alice" shares the following resource using API - | resource | recipient | type | role | - | myfolder | Brian | user | Can edit | + | resource | recipient | type | role | resourceType | + | myfolder | Brian | user | Can edit | folder | And "Alice" creates a public link of following resource using API | resource | role | | myfolder | Invited people | - And "Brian" opens the public link "Unnamed link" - And "Brian" logs in from the internal link + When "Brian" opens the public link "Unnamed link" And "Brian" navigates to the shared with me page And "Brian" uploads the following resource | resource | to | | simple.pdf | myfolder | - And "Alice" logs in And "Alice" updates following sharee role - | resource | recipient | type | role | - | myfolder | Brian | user | Can view | + | resource | recipient | type | role | resourceType | + | myfolder | Brian | user | Can view | folder | And "Alice" logs out Then "Brian" should not be able to edit folder "myfolder" And "Brian" logs out diff --git a/tests/e2e/cucumber/features/shares/link.feature b/tests/e2e/cucumber/features/shares/link.feature index 5736e237a61..f2badc6bcb8 100644 --- a/tests/e2e/cucumber/features/shares/link.feature +++ b/tests/e2e/cucumber/features/shares/link.feature @@ -1,4 +1,3 @@ -@predefined-users Feature: link Background: @@ -6,11 +5,12 @@ Feature: link | id | | Alice | - + @predefined-users Scenario: public link Given "Admin" creates following user using API | id | | Brian | + And "Alice" logs in And "Alice" creates the following folders in personal space using API | name | | folderPublic | @@ -19,8 +19,7 @@ Feature: link | pathToFile | content | | folderPublic/lorem.txt | lorem ipsum | - When "Alice" logs in - And "Alice" creates a public link of following resource using the sidebar panel + When "Alice" creates a public link of following resource using the sidebar panel | resource | role | password | | folderPublic | Secret File Drop | %public% | And "Alice" renames the most recently created public link of resource "folderPublic" to "myPublicLink" @@ -40,7 +39,6 @@ Feature: link | resource | | simple.pdf | - When "Alice" opens folder "folderPublic" Then following resources should be displayed in the files list for user "Alice" | resource | @@ -94,13 +92,14 @@ Feature: link And "Anonymous" should not be able to open the old link "myPublicLink" And "Alice" logs out - + @predefined-users Scenario: public link for folder and file (by authenticated user) Given "Admin" creates following user using API | id | | Brian | | Carol | And "Alice" logs in + And "Brian" logs in And "Alice" creates the following folders in personal space using API | name | | folderPublic | @@ -114,10 +113,10 @@ Feature: link | filesForUpload/testavatar.jpg | testavatar.jpg | | filesForUpload/test_video.mp4 | test_video.mp4 | And "Alice" shares the following resource using API - | resource | recipient | type | role | - | folderPublic | Brian | user | Can edit | - | simple.pdf | Brian | user | Can edit | - | testavatar.jpg | Brian | user | Can edit | + | resource | recipient | type | role | resourceType | + | folderPublic | Brian | user | Can edit | folder | + | simple.pdf | Brian | user | Can edit | file | + | testavatar.jpg | Brian | user | Can edit | file | And "Alice" opens the "files" app And "Alice" creates a public link of following resource using the sidebar panel @@ -147,7 +146,6 @@ Feature: link And "Alice" logs out # authenticated user with access to resources. should be redirected to shares with me page - And "Brian" logs in When "Brian" opens the public link "folderLink" And "Brian" unlocks the public link with password "%public%" And "Brian" downloads the following public link resources using the sidebar panel @@ -288,7 +286,7 @@ Feature: link And "Anonymous" unlocks the public link with password "%copied_password%" And "Alice" logs out - + @predefined-users Scenario: edit password of the public link When "Alice" logs in And "Alice" creates the following folders in personal space using API @@ -312,7 +310,7 @@ Feature: link | lorem.txt | file | And "Alice" logs out - + @predefined-users Scenario: link indication When "Alice" logs in And "Alice" creates the following folders in personal space using API diff --git a/tests/e2e/cucumber/features/shares/share.feature b/tests/e2e/cucumber/features/shares/share.feature index 33ba02268e7..19d4b5580f0 100644 --- a/tests/e2e/cucumber/features/shares/share.feature +++ b/tests/e2e/cucumber/features/shares/share.feature @@ -8,14 +8,13 @@ Feature: share @predefined-users Scenario: folder - # disabling auto accepting to check accepting share - Given "Brian" disables auto-accepting using API + Given "Alice" logs in + And "Brian" logs in And "Alice" creates the following folder in personal space using API | name | | folder_to_shared | | folder_to_shared_2 | | shared_folder | - And "Alice" logs in And "Alice" uploads the following resource | resource | to | | lorem.txt | folder_to_shared | @@ -26,7 +25,6 @@ Feature: share | shared_folder | Brian | user | Can edit without versions | folder | | folder_to_shared_2 | Brian | user | Can edit without versions | folder | - And "Brian" logs in And "Brian" navigates to the shared with me page And "Brian" opens folder "folder_to_shared" # user should have access to unsynced shares @@ -34,18 +32,19 @@ Feature: share | resource | | lorem.txt | When "Brian" navigates to the shared with me page - And "Brian" enables the sync for the following shares + And "Brian" disables the sync for the following shares | name | | folder_to_shared | | folder_to_shared_2 | - Then "Brian" should not see a sync status for the folder "shared_folder" + Then "Brian" should not see a sync status for the folder "folder_to_shared" + And "Brian" should not see a sync status for the folder "folder_to_shared_2" When "Brian" enables the sync for the following share using the context menu - | name | - | shared_folder | - And "Brian" disables the sync for the following share using the context menu - | name | - | shared_folder | - And "Brian" renames the following resource + | name | + | folder_to_shared | + | folder_to_shared_2 | + Then "Brian" should see a sync status for the folder "folder_to_shared" + And "Brian" should see a sync status for the folder "folder_to_shared_2" + When "Brian" renames the following resource | resource | as | | folder_to_shared/lorem.txt | lorem_new.txt | And "Brian" uploads the following resource @@ -79,6 +78,7 @@ Feature: share @predefined-users Scenario: file Given "Alice" logs in + And "Brian" logs in And "Alice" creates the following resources | resource | type | content | | shareToBrian.txt | txtFile | some text | @@ -91,56 +91,8 @@ Feature: share And "Alice" uploads the following resource | resource | | simple.pdf | - | sampleGif.gif | - | testimage.mp3 | - | sampleOgg.ogg | - | sampleWebm.webm | - | test_video.mp4 | - | testavatar.jpeg | - | testavatar.png | - Then "Alice" should see thumbnail and preview for file "sampleGif.gif" - And "Alice" should see thumbnail and preview for file "testavatar.jpeg" - And "Alice" should see thumbnail and preview for file "testavatar.png" - And "Alice" should see preview for file "shareToBrian.txt" - When "Alice" opens a file "testavatar.png" in the media-viewer using the sidebar panel - Then "Alice" is in a media-viewer - When "Alice" closes the file viewer - And "Alice" opens the following file in mediaviewer - | resource | | testavatar.jpeg | - Then "Alice" is in a media-viewer - When "Alice" navigates to the next media resource - And "Alice" navigates to the previous media resource - And "Alice" closes the file viewer - And "Alice" opens the following file in mediaviewer - | resource | - | sampleGif.gif | - Then "Alice" is in a media-viewer - When "Alice" closes the file viewer - And "Alice" opens the following file in mediaviewer - | resource | - | testimage.mp3 | - Then "Alice" is in a media-viewer - When "Alice" closes the file viewer - And "Alice" opens the following file in mediaviewer - | resource | - | sampleOgg.ogg | - Then "Alice" is in a media-viewer - When "Alice" closes the file viewer - And "Alice" opens the following file in mediaviewer - | resource | - | sampleWebm.webm | - Then "Alice" is in a media-viewer - When "Alice" closes the file viewer - And "Alice" opens the following file in mediaviewer - | resource | - | test_video.mp4 | - Then "Alice" is in a media-viewer - When "Alice" downloads the following resource using the preview topbar - | resource | type | - | test_video.mp4 | file | - And "Alice" closes the file viewer - And "Alice" shares the following resource using the sidebar panel + When "Alice" shares the following resource using the sidebar panel | resource | recipient | type | role | resourceType | | shareToBrian.txt | Brian | user | Can edit without versions | file | | shareToBrian.md | Brian | user | Can edit without versions | file | @@ -152,10 +104,9 @@ Feature: share | resource | | testavatar.jpeg | Then "Alice" is in a media-viewer - When "Alice" closes the file viewer + And "Alice" closes the file viewer - When "Brian" logs in - And "Brian" navigates to the shared with me page + When "Brian" navigates to the shared with me page And "Brian" disables the sync for the following share | name | | sharedFile.txt | @@ -235,31 +186,31 @@ Feature: share Given "Admin" creates following users using API | id | | Carol | + And "Alice" logs in + And "Brian" logs in And "Alice" creates the following folder in personal space using API | name | | test-folder | And "Alice" creates the following files into personal space using API | pathToFile | content | | testfile.txt | example text | - And "Alice" logs in - And "Alice" shares the following resource using the sidebar panel - | resource | recipient | type | role | - | testfile.txt | Brian | user | Can view | - | test-folder | Brian | user | Can view | - And "Alice" logs out + When "Alice" shares the following resource using the sidebar panel + | resource | recipient | type | role | resourceType | + | testfile.txt | Brian | user | Can view | file | + | test-folder | Brian | user | Can view | folder | + Then "Alice" logs out + When "Carol" logs in And "Carol" creates the following folder in personal space using API | name | | test-folder | And "Carol" creates the following files into personal space using API | pathToFile | content | | testfile.txt | example text | - And "Carol" logs in And "Carol" shares the following resource using the sidebar panel - | resource | recipient | type | role | - | testfile.txt | Brian | user | Can view | - | test-folder | Brian | user | Can view | + | resource | recipient | type | role | resourceType | + | testfile.txt | Brian | user | Can view | file | + | test-folder | Brian | user | Can view | folder | And "Carol" logs out - When "Brian" logs in And "Brian" navigates to the shared with me page Then following resources should be displayed in the Shares for user "Brian" | resource | @@ -272,9 +223,8 @@ Feature: share @predefined-users Scenario: check file with same name but different paths are displayed correctly in shared with others page - Given "Admin" creates following users using API - | id | - | Carol | + Given "Alice" logs in + And "Brian" logs in And "Alice" creates the following folder in personal space using API | name | | test-folder | @@ -283,25 +233,25 @@ Feature: share | testfile.txt | example text | | test-folder/testfile.txt | some text | And "Alice" shares the following resource using API - | resource | recipient | type | role | - | testfile.txt | Brian | user | Can edit without versions | - | test-folder/testfile.txt | Brian | user | Can edit without versions | - And "Alice" logs in - And "Alice" navigates to the shared with others page + | resource | recipient | type | role | resourceType | + | testfile.txt | Brian | user | Can edit without versions | file | + | test-folder/testfile.txt | Brian | user | Can edit without versions | file | + And "Brian" logs out + When "Alice" navigates to the shared with others page Then following resources should be displayed in the files list for user "Alice" | resource | | testfile.txt | | test-folder/testfile.txt | And "Alice" logs out - @predefined-users + Scenario: share indication When "Alice" creates the following folders in personal space using API | name | | shareFolder/subFolder | And "Alice" shares the following resource using API - | resource | recipient | type | role | - | shareFolder | Brian | user | Can edit without versions | + | resource | recipient | type | role | resourceType | + | shareFolder | Brian | user | Can edit without versions | folder | And "Alice" logs in Then "Alice" should see user-direct indicator on the folder "shareFolder" When "Alice" opens folder "shareFolder" diff --git a/tests/e2e/cucumber/features/smoke/activity.feature b/tests/e2e/cucumber/features/smoke/activity.feature index 6b4129571fe..f6f5a65a4e4 100644 --- a/tests/e2e/cucumber/features/smoke/activity.feature +++ b/tests/e2e/cucumber/features/smoke/activity.feature @@ -26,8 +26,8 @@ Feature: Users can see all activities of the resources and spaces | localFile | to | | filesForUpload/textfile.txt | sharedFolder/textfile.txt | And "Alice" shares the following resource using API - | resource | recipient | type | role | - | sharedFolder | Brian | user | Can edit without versions | + | resource | recipient | type | role | resourceType | + | sharedFolder | Brian | user | Can edit without versions | folder | And "Alice" creates a public link of following resource using API | resource | role | password | | sharedFolder | Can edit without versions | %public% | diff --git a/tests/e2e/cucumber/features/smoke/sse.feature b/tests/e2e/cucumber/features/smoke/sse.feature index 771b96a6b8f..28863100c68 100644 --- a/tests/e2e/cucumber/features/smoke/sse.feature +++ b/tests/e2e/cucumber/features/smoke/sse.feature @@ -73,8 +73,8 @@ Feature: server sent events # share-created When "Alice" shares the following resource using the sidebar panel - | resource | recipient | type | role | - | space-folder | Carol | user | Can view | + | resource | recipient | type | role | resourceType | + | space-folder | Carol | user | Can view | folder | Then "Alice" should get "share-created" SSE event And "Brian" should get "share-created" SSE event And "Brian" closes the sidebar @@ -138,8 +138,8 @@ Feature: server sent events # share-created When "Alice" shares the following resource using the sidebar panel - | resource | recipient | type | role | - | sharedFolder | Brian | user | Can view | + | resource | recipient | type | role | resourceType | + | sharedFolder | Brian | user | Can view | folder | Then "Alice" should get "share-created" SSE event And "Brian" should get "share-created" SSE event And "Brian" should not be able to edit folder "sharedFolder" diff --git a/tests/e2e/cucumber/features/smoke/tags.feature b/tests/e2e/cucumber/features/smoke/tags.feature index a249229e68d..b9c167d0f00 100644 --- a/tests/e2e/cucumber/features/smoke/tags.feature +++ b/tests/e2e/cucumber/features/smoke/tags.feature @@ -1,4 +1,3 @@ -@predefined-users Feature: Users can use web to organize tags Background: @@ -7,11 +6,12 @@ Feature: Users can use web to organize tags | Alice | | Brian | + @predefined-users Scenario: Tag management + Given "Alice" logs in When "Alice" creates the following files into personal space using API | pathToFile | content | | lorem.txt | lorem ipsum | - And "Alice" logs in And "Alice" adds the following tags for the following resources using the sidebar panel | resource | tags | | lorem.txt | tag 1, tag 2 | @@ -30,6 +30,14 @@ Feature: Users can use web to organize tags Then the following resources should contain the following tags in the details panel for user "Alice" | resource | tags | | lorem.txt | tag 2 | + And "Alice" logs out + + + Scenario: Long tag name + Given "Alice" logs in + And "Alice" creates the following files into personal space using API + | pathToFile | content | + | lorem.txt | lorem ipsum | When "Alice" tries to add the following tag for the following resources using the sidebar panel | resource | tags | | lorem.txt | Loremipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore | @@ -40,12 +48,13 @@ Feature: Users can use web to organize tags And "Alice" logs out + @predefined-users Scenario: Tag search + Given "Alice" logs in When "Alice" creates the following files into personal space using API | pathToFile | content | | lorem.txt | lorem ipsum | | textfile.txt | test file | - And "Alice" logs in And "Alice" adds the following tags for the following resources using the sidebar panel | resource | tags | | lorem.txt | tag1, tag2 | @@ -58,15 +67,16 @@ Feature: Users can use web to organize tags | textfile.txt | And "Alice" logs out - - Scenario: Tag sharing - Given "Alice" creates the following folders in personal space using API + @predefined-users + Scenario: Tags in shared resources + Given "Alice" logs in + And "Brian" logs in + And "Alice" creates the following folders in personal space using API | name | | folder_to_shared | And "Alice" creates the following files into personal space using API | pathToFile | content | | folder_to_shared/lorem.txt | lorem ipsum | - And "Alice" logs in And "Alice" adds the following tags for the following resources using the sidebar panel | resource | tags | | folder_to_shared/lorem.txt | tag 1, tag 2 | @@ -75,7 +85,6 @@ Feature: Users can use web to organize tags | folder_to_shared | Brian | user | Can edit without versions | folder | And "Alice" logs out - And "Brian" logs in And "Brian" navigates to the shared with me page Then the following resources should contain the following tags in the files list for user "Brian" | resource | tags | diff --git a/tests/e2e/cucumber/features/smoke/trashbinDelete.feature b/tests/e2e/cucumber/features/smoke/trashbinDelete.feature index 27492c54358..5a1a1742998 100644 --- a/tests/e2e/cucumber/features/smoke/trashbinDelete.feature +++ b/tests/e2e/cucumber/features/smoke/trashbinDelete.feature @@ -44,21 +44,21 @@ Feature: Trashbin delete Scenario: delete and restore a file inside a received shared folder - Given "Alice" creates the following folders in personal space using API + Given "Brian" logs in + And "Alice" creates the following folders in personal space using API | name | | folderToShare | - | empty-folder | + | empty-folder | And "Alice" creates the following files into personal space using API | pathToFile | content | | folderToShare/lorem.txt | lorem ipsum | | sample.txt | sample | - And "Alice" shares the following resource using the sidebar panel + And "Alice" shares the following resource using API | resource | recipient | type | role | resourceType | | folderToShare | Brian | user | Can edit without versions | folder | - And "Brian" logs in - And "Brian" navigates to the shared with me page + When "Brian" navigates to the shared with me page And "Brian" opens folder "folderToShare" - When "Brian" deletes the following resources using the sidebar panel + And "Brian" deletes the following resources using the sidebar panel | resource | | lorem.txt | And "Brian" navigates to the trashbin diff --git a/tests/e2e/cucumber/features/smoke/upload.feature b/tests/e2e/cucumber/features/smoke/upload.feature index 4a6fb765669..770283fc259 100644 --- a/tests/e2e/cucumber/features/smoke/upload.feature +++ b/tests/e2e/cucumber/features/smoke/upload.feature @@ -10,13 +10,8 @@ Feature: Upload And "Alice" logs in And "Alice" opens the "files" app - + @predefined-users Scenario: Upload files in personal space - Given "Admin" logs in - And "Admin" opens the "admin-settings" app - And "Admin" navigates to the users management page - And "Admin" changes the quota of the user "Alice" to "0.00008" using the sidebar panel - And "Admin" logs out Given "Alice" creates the following resources | resource | type | content | | new-lorem-big.txt | txtFile | new lorem big file | @@ -40,9 +35,6 @@ Feature: Upload | resource | | simple.pdf | | testavatar.jpg | - And "Alice" tries to upload the following resource - | resource | error | - | lorem-big.txt | Insufficient quota | And "Alice" downloads the following resources using the sidebar panel | resource | type | | PARENT | folder | @@ -84,3 +76,18 @@ Feature: Upload | resource | type | | FOLDER | folder | And "Alice" logs out + + + Scenario: Upload large file when insufficient quota + Given "Admin" logs in + And "Admin" opens the "admin-settings" app + And "Admin" navigates to the users management page + And "Admin" changes the quota of the user "Alice" to "0.00001" using the sidebar panel + And "Admin" logs out + And "Alice" uploads the following resource + | resource | + | simple.pdf | + When "Alice" tries to upload the following resource + | resource | error | + | lorem-big.txt | Insufficient quota | + And "Alice" logs out diff --git a/tests/e2e/cucumber/features/user-settings/languageChange.feature b/tests/e2e/cucumber/features/user-settings/languageChange.feature index ea66023b424..e57ac413e5f 100644 --- a/tests/e2e/cucumber/features/user-settings/languageChange.feature +++ b/tests/e2e/cucumber/features/user-settings/languageChange.feature @@ -10,35 +10,36 @@ Feature: language settings | Brian | Scenario: system language change - And "Brian" creates the following folder in personal space using API + Given "Alice" logs in + And "Brian" logs in + And "Alice" creates the following folder in personal space using API | name | | check_message | - And "Brian" shares the following resource using API - | resource | recipient | type | role | - | check_message | Alice | user | Can edit | - And "Alice" logs in - And "Alice" opens the user menu - And "Alice" changes the language to "Deutsch - German" - Then "Alice" should see the following account page title "Mein Konto" - When "Alice" logs out - And "Alice" logs in - Then "Alice" should see the following notifications - | message | - | %user_brian_displayName% hat check_message mit Ihnen geteilt | + And "Alice" shares the following resource using API + | resource | recipient | type | role | resourceType | + | check_message | Brian | user | Can edit | folder | And "Alice" logs out + When "Brian" opens the user menu + And "Brian" changes the language to "Deutsch - German" + Then "Brian" should see the following account page title "Mein Konto" + And "Brian" should see the following notifications + | message | + | %user_alice_displayName% hat check_message mit Ihnen geteilt | + And "Brian" logs out Scenario: anonymous user language change - When "Alice" creates the following folder in personal space using API + Given "Alice" logs in + And "Alice" creates the following folder in personal space using API | name | | folderPublic | And "Alice" uploads the following local file into personal space using API | localFile | to | | filesForUpload/lorem.txt | lorem.txt | - And "Alice" creates a public link of following resource using API | resource | password | | folderPublic | %public% | + And "Alice" logs out When "Anonymous" opens the public link "Unnamed link" And "Anonymous" unlocks the public link with password "%public%" And "Anonymous" opens the user menu diff --git a/tests/e2e/cucumber/steps/api.ts b/tests/e2e/cucumber/steps/api.ts index 85741b104ca..684b0e3a0d1 100644 --- a/tests/e2e/cucumber/steps/api.ts +++ b/tests/e2e/cucumber/steps/api.ts @@ -1,10 +1,10 @@ import { Given, DataTable } from '@cucumber/cucumber' import { World } from '../environment' import { api } from '../../support' +import { ResourceType } from '../../support/api/share/share' import fs from 'fs' import { Space } from '../../support/types' import { config } from '../../config' -import { setAccessAndRefreshToken, getUserIdFromToken } from '../../support/api/token' Given( '{string} creates following user(s) using API', @@ -13,14 +13,7 @@ Given( for (const info of stepTable.hashes()) { const user = this.usersEnvironment.getUser({ key: info.id }) // do not try to create users when using predefined users - if (config.predefinedUsers) { - await setAccessAndRefreshToken(user) - this.usersEnvironment.storeCreatedUser(info.id.toLowerCase(), { - ...user, - uuid: getUserIdFromToken(user) - }) - this.usersEnvironment.saveUserState(info.id, {}) - } else { + if (!config.predefinedUsers) { await api.provision.createUser({ user, admin }) } } @@ -102,7 +95,8 @@ Given( path: info.resource, shareType: info.type, shareWith: info.recipient, - role: info.role + role: info.role, + resourceType: info.resourceType as ResourceType }) } } @@ -113,8 +107,6 @@ Given( async function (this: World, stepUser: string): Promise { const user = this.usersEnvironment.getUser({ key: stepUser }) await api.settings.configureAutoAcceptShare({ user, state: false }) - // save initial auto-accept config - this.usersEnvironment.saveUserState(stepUser, { autoAcceptShare: true }) } ) diff --git a/tests/e2e/cucumber/steps/ui/accountMenu.ts b/tests/e2e/cucumber/steps/ui/accountMenu.ts index b1c15c3db3a..42f87e5107b 100644 --- a/tests/e2e/cucumber/steps/ui/accountMenu.ts +++ b/tests/e2e/cucumber/steps/ui/accountMenu.ts @@ -56,8 +56,6 @@ When( const accountObject = new objects.account.Account({ page }) const isAnonymousUser = stepUser === 'Anonymous' await accountObject.changeLanguage(language, isAnonymousUser) - // save initial language - this.usersEnvironment.saveUserState(stepUser, { language: 'en' }) } ) diff --git a/tests/e2e/cucumber/steps/ui/links.ts b/tests/e2e/cucumber/steps/ui/links.ts index 43732deeca3..af344efe7ad 100644 --- a/tests/e2e/cucumber/steps/ui/links.ts +++ b/tests/e2e/cucumber/steps/ui/links.ts @@ -2,7 +2,7 @@ import { DataTable, Then, When } from '@cucumber/cucumber' import { expect } from '@playwright/test' import { World } from '../../environment' import { objects } from '../../../support' -import { securePassword } from '../../../support/store' +import { substitute } from '../../../support/utils/substitute' When( '{string} creates a public link of following resource using the sidebar panel', @@ -14,7 +14,7 @@ When( await linkObject.create({ resource: info.resource, role: info.role, - password: info.password === '%public%' ? securePassword : info.password, + password: substitute(info.password), name: 'Unnamed link' }) } @@ -26,7 +26,7 @@ When( async function (this: World, stepUser: string, password: string): Promise { const { page } = this.actorsEnvironment.getActor({ key: stepUser }) const spaceObject = new objects.applicationFiles.Spaces({ page }) - password = password === '%public%' ? securePassword : password + password = substitute(password) await spaceObject.createPublicLink({ password }) } ) diff --git a/tests/e2e/cucumber/steps/ui/public.ts b/tests/e2e/cucumber/steps/ui/public.ts index 1fd44dbbc15..34b1d6c1912 100644 --- a/tests/e2e/cucumber/steps/ui/public.ts +++ b/tests/e2e/cucumber/steps/ui/public.ts @@ -4,7 +4,7 @@ import { World } from '../../environment' import { objects } from '../../../support' import { processDelete, processDownload } from './resources' import { editor } from '../../../support/objects/app-files/utils' -import { securePassword } from '../../../support/store' +import { substitute } from '../../../support/utils/substitute' When( '{string} opens the public link {string}', @@ -28,7 +28,7 @@ When( if (password === '%copied_password%') { password = await page.evaluate('navigator.clipboard.readText()') } else { - password = password === '%public%' ? securePassword : password + password = substitute(password) } await pageObject.authenticate({ password }) } @@ -213,7 +213,7 @@ When( async function (this: World, stepUser: string, password: string): Promise { const { page } = this.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.applicationFiles.page.Public({ page }) - password = password === '%public%' ? securePassword : password + password = substitute(password) await pageObject.authenticate({ password, passwordProtectedFolder: true }) } ) @@ -224,7 +224,7 @@ When( const { page } = this.actorsEnvironment.getActor({ key: stepUser }) const pageObject = new objects.applicationFiles.page.Public({ page }) const linkObject = new objects.applicationFiles.Link({ page }) - password = password === '%public%' ? securePassword : password + password = substitute(password) await pageObject.authenticate({ password, passwordProtectedFolder: true, diff --git a/tests/e2e/cucumber/steps/ui/session.ts b/tests/e2e/cucumber/steps/ui/session.ts index f1153c2322b..dc2db5c5cb5 100644 --- a/tests/e2e/cucumber/steps/ui/session.ts +++ b/tests/e2e/cucumber/steps/ui/session.ts @@ -2,8 +2,10 @@ import { Given, When, Then } from '@cucumber/cucumber' import { PickleTag } from '@cucumber/messages' import { World } from '../../environment' import { config } from '../../../config' -import { objects } from '../../../support' +import { objects, api } from '../../../support' import { listenSSE } from '../../../support/environment/sse' +import { UsersEnvironment } from '../../../support/environment' +import { User } from '../../../support/types' async function createNewSession(world: World, stepUser: string) { const { page } = await world.actorsEnvironment.createActor({ @@ -13,14 +15,29 @@ async function createNewSession(world: World, stepUser: string) { return new objects.runtime.Session({ page }) } +async function initUserStates(userKey: string, user: User, usersEnvironment: UsersEnvironment) { + const userInfo = await api.graph.getMeInfo(user) + usersEnvironment.storeCreatedUser(userKey, { + ...user, + uuid: userInfo.id, + email: userInfo.mail + }) + usersEnvironment.saveUserState(userKey, { + language: userInfo.preferredLanguage, + autoAcceptShare: await api.settings.getAutoAcceptSharesValue(user) + }) +} + async function LogInUser(this: World, stepUser: string): Promise { const sessionObject = await createNewSession(this, stepUser) const { page } = this.actorsEnvironment.getActor({ key: stepUser }) - const user = - stepUser === 'Admin' - ? this.usersEnvironment.getUser({ key: stepUser }) - : this.usersEnvironment.getCreatedUser({ key: stepUser }) + let user = null + if (stepUser === 'Admin' || config.predefinedUsers) { + user = this.usersEnvironment.getUser({ key: stepUser }) + } else { + user = this.usersEnvironment.getCreatedUser({ key: stepUser }) + } await page.goto(config.baseUrl) await sessionObject.login(user) @@ -38,6 +55,14 @@ async function LogInUser(this: World, stepUser: string): Promise { } await page.locator('#web-content').waitFor() + + // initialize user states: uuid, language, auto-sync + if (config.predefinedUsers) { + await initUserStates(stepUser, user, this.usersEnvironment) + // test should run with English language + await api.settings.changeLanguage({ user, language: 'en' }) + await page.reload({ waitUntil: 'load' }) + } } Given('{string} has logged in', LogInUser) diff --git a/tests/e2e/cucumber/steps/ui/shares.ts b/tests/e2e/cucumber/steps/ui/shares.ts index c3f61dee032..0d030a5c79f 100644 --- a/tests/e2e/cucumber/steps/ui/shares.ts +++ b/tests/e2e/cucumber/steps/ui/shares.ts @@ -8,6 +8,7 @@ import { } from '../../../support/objects/app-files/share/collaborator' import { ActionViaType } from '../../../support/objects/app-files/share/actions' import { substitute } from '../../../support/utils' +import { getDynamicRoleIdByName, ResourceType } from '../../../support/api/share/share' const parseShareTable = function ( stepTable: DataTable, @@ -41,6 +42,7 @@ When( async function (this: World, stepUser: string, actionType: string, stepTable: DataTable) { const { page } = this.actorsEnvironment.getActor({ key: stepUser }) const shareObject = new objects.applicationFiles.Share({ page }) + const sharer = this.usersEnvironment.getUser({ key: stepUser }) const shareInfo = parseShareTable(stepTable, this.usersEnvironment) let via: ActionViaType @@ -58,10 +60,16 @@ When( throw new Error(`Unknown action type: ${actionType}`) } - for (const resource of Object.keys(shareInfo)) { + for (const [resource, shareObj] of Object.entries(shareInfo)) { + const roleId = await getDynamicRoleIdByName( + sharer, + shareObj[0].role, + shareObj[0].resourceType as ResourceType + ) + shareObj.forEach((item) => (item.role = roleId)) await shareObject.create({ resource, - recipients: shareInfo[resource], + recipients: shareObj, via }) } @@ -86,11 +94,18 @@ When( const { page } = this.actorsEnvironment.getActor({ key: stepUser }) const shareObject = new objects.applicationFiles.Share({ page }) const shareInfo = parseShareTable(stepTable, this.usersEnvironment) + const sharer = this.usersEnvironment.getUser({ key: stepUser }) - for (const resource of Object.keys(shareInfo)) { + for (const [resource, shareObj] of Object.entries(shareInfo)) { + const roleId = await getDynamicRoleIdByName( + sharer, + shareObj[0].role, + shareObj[0].resourceType as ResourceType + ) + shareObj.forEach((item) => (item.role = roleId)) await shareObject.changeShareeRole({ resource, - recipients: shareInfo[resource] + recipients: shareObj }) } } diff --git a/tests/e2e/cucumber/steps/ui/spaces.ts b/tests/e2e/cucumber/steps/ui/spaces.ts index 46e3ed0a077..90cb1db7d07 100644 --- a/tests/e2e/cucumber/steps/ui/spaces.ts +++ b/tests/e2e/cucumber/steps/ui/spaces.ts @@ -4,6 +4,7 @@ import { World } from '../../environment' import { objects } from '../../../support' import { Space } from '../../../support/types' import { substitute } from '../../../support/utils' +import { getDynamicRoleIdByName, ResourceType } from '../../../support/api/share/share' When( '{string} navigates to the personal space page', @@ -88,14 +89,17 @@ When( async function (this: World, stepUser: string, stepTable: DataTable): Promise { const { page } = this.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationFiles.Spaces({ page }) + const sharer = this.usersEnvironment.getUser({ key: stepUser }) + for (const { user, role, kind } of stepTable.hashes()) { const collaborator = kind === 'user' ? this.usersEnvironment.getUser({ key: user }) : this.usersEnvironment.getGroup({ key: user }) + const roleId = await getDynamicRoleIdByName(sharer, role, 'space' as ResourceType) const collaboratorWithRole = { collaborator, - role + role: roleId } await spacesObject.addMembers({ users: [collaboratorWithRole] }) } @@ -147,10 +151,13 @@ When( async function (this: World, stepUser: string, stepTable: DataTable): Promise { const { page } = this.actorsEnvironment.getActor({ key: stepUser }) const spacesObject = new objects.applicationFiles.Spaces({ page }) + const sharer = this.usersEnvironment.getUser({ key: stepUser }) + for (const { user, role } of stepTable.hashes()) { + const roleId = await getDynamicRoleIdByName(sharer, role, 'space' as ResourceType) const member = { collaborator: this.usersEnvironment.getUser({ key: user }), - role + role: roleId } await spacesObject.changeRoles({ users: [member] }) } diff --git a/tests/e2e/support/api/graph/index.ts b/tests/e2e/support/api/graph/index.ts index 9729c209405..0bbd043cb85 100644 --- a/tests/e2e/support/api/graph/index.ts +++ b/tests/e2e/support/api/graph/index.ts @@ -7,6 +7,7 @@ export { addUserToGroup, assignRole, getUserId, - getGroups + getGroups, + getMeInfo } from './userManagement' export { createSpace, disableSpace, deleteSpace, getSpaceIdBySpaceName } from './spaces' diff --git a/tests/e2e/support/api/graph/userManagement.ts b/tests/e2e/support/api/graph/userManagement.ts index 94a810432ac..02836da8794 100644 --- a/tests/e2e/support/api/graph/userManagement.ts +++ b/tests/e2e/support/api/graph/userManagement.ts @@ -45,6 +45,18 @@ export const createUser = async ({ user, admin }: { user: User; admin: User }): return user } +export const getMeInfo = async (user: User): Promise => { + const response = await request({ + method: 'GET', + path: join('graph', 'v1.0', 'me'), + user + }) + checkResponseStatus(response, `Failed get user: ${user.id}`) + + const resBody = (await response.json()) as User + return resBody +} + export const deleteUser = async ({ user, admin }: { user: User; admin: User }): Promise => { const response = await request({ method: 'DELETE', diff --git a/tests/e2e/support/api/share/share.ts b/tests/e2e/support/api/share/share.ts index 619ec3996b2..fa226b65aaa 100644 --- a/tests/e2e/support/api/share/share.ts +++ b/tests/e2e/support/api/share/share.ts @@ -1,10 +1,21 @@ import join from 'join-path' +import { expect } from '@playwright/test' import { checkResponseStatus, request } from '../http' import { User } from '../../types' import { getSpaceIdBySpaceName } from '../graph' import { getIdOfFileInsideSpace } from '../davSpaces' import { LinksEnvironment, UsersEnvironment } from '../../environment' -import { securePassword } from '../../store' +import { substitute } from '../../utils/substitute' +import { config } from '../../../config' + +export type ResourceType = 'file' | 'folder' | 'space' +interface Role { + id: string + displayName: string + rolePermissions: { + condition: string[] + }[] +} export const shareTypes: Readonly<{ user: string @@ -29,6 +40,7 @@ export const shareRoles: Readonly<{ 'Can view': string 'Secret File Drop': string 'Cannot access': string + 'Can view (secure)': string }> = { 'Invited people': 'internal', 'Can upload': 'contributor', @@ -37,7 +49,8 @@ export const shareRoles: Readonly<{ 'Can edit without versions': 'editor', 'Can view': 'viewer', 'Secret File Drop': 'uploader', - 'Cannot access': 'denied' + 'Cannot access': 'denied', + 'Can view (secure)': 'secure viewer' } as const export const linkShareRoles: Readonly<{ @@ -56,33 +69,28 @@ export const linkShareRoles: Readonly<{ 'Secret File Drop': 'createOnly' } as const +const defaultRoles = { + viewer: { id: 'b1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5', displayName: 'Can view' }, + 'space viewer': { id: 'a8d5fe5e-96e3-418d-825b-534dbdf22b99', displayName: 'Can view (space)' }, + editor: { id: 'fb6c3e19-e378-47e5-b277-9732f9de6e21', displayName: 'Can edit without versions' }, + 'file editor': { + id: '2d00ce52-1fc2-4dbc-8b95-a73b73395f5a', + displayName: 'Can edit without versions (file)' + }, + 'space editor': { id: '58c63c02-1d89-4572-916a-870abc5a1b7d', displayName: 'Can edit (space)' }, + manager: { id: '312c0871-5ef7-4b3a-85b6-0e4074c64049', displayName: 'Can Manage' }, + uploader: { id: '1c996275-f1c9-4e71-abdf-a42f6495e960', displayName: 'Can upload' }, + 'secure viewer': { id: 'aa97fe03-7980-45ac-9e50-b325749fd7e6', displayName: 'Can view (secure)' }, + denied: { id: '63e64e19-8d43-42ec-a738-2b6af2610efa', displayName: 'Cannot access' } +} + const getPermissionsRoleIdByName = (permissionsRole: string): string => { - switch (permissionsRole) { - case 'viewer': - return 'b1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5' - case 'space viewer': - return 'a8d5fe5e-96e3-418d-825b-534dbdf22b99' - case 'editor': - return 'fb6c3e19-e378-47e5-b277-9732f9de6e21' - case 'space editor': - return '58c63c02-1d89-4572-916a-870abc5a1b7d' - case 'file editor': - return '2d00ce52-1fc2-4dbc-8b95-a73b73395f5a' - case 'co owner': - return '3a4ba8e9-6a0d-4235-9140-0e7a34007abe' - case 'uploader': - return '1c996275-f1c9-4e71-abdf-a42f6495e960' - case 'manager': - return '312c0871-5ef7-4b3a-85b6-0e4074c64049' - case 'secure viewer': - return 'aa97fe03-7980-45ac-9e50-b325749fd7e6' - default: - throw new Error(`Role ${permissionsRole} not found`) + if (!(permissionsRole in defaultRoles)) { + throw new Error(`Role '${permissionsRole}' not found`) } + return defaultRoles[permissionsRole].id } -type ResourceType = 'file' | 'folder' | 'space' - const getRoleId = (role: string, resourceType: ResourceType): string => { let roleId: string const shareRole: string = shareRoles[role as keyof typeof shareRoles] @@ -109,18 +117,132 @@ const getRecipientId = (shareType: string, shareWith: string): string => { return recipientId } +const dynamicRoles = {} +const requiredDynamicRoles = [ + 'Can view', + 'Can edit', + 'Can edit (file)', + 'Can edit without versions', + 'Can edit without versions (file)' +] + +export const getDynamicRoleIdByName = async ( + user: User, + roleName: string, + resourceType: ResourceType +): Promise => { + if (!config.predefinedUsers) { + return getRoleId(roleName, resourceType) + } + + if (resourceType === 'file' && ['Can edit', 'Can edit without versions'].includes(roleName)) { + roleName = `${roleName} (file)` + } else if (resourceType === 'space' && !['Can manage'].includes(roleName)) { + roleName = `${roleName} (space)` + } + + let roleId: string = '' + if (Object.keys(dynamicRoles).length) { + roleId = dynamicRoles[roleName] + } else { + const roles = await getDynamicShareRoles(user) + if (roleName in roles) { + roleId = roles[roleName] + } + } + + if (!roleId) { + throw new Error(`Role '${roleName}' not found`) + } + + return roleId +} + +export const getDynamicShareRoles = async (user: User): Promise => { + if (Object.keys(dynamicRoles).length) { + return dynamicRoles + } + + // NOTE: sometimes roles can be in different language + // fetch roles until all required roles are present + await expect + .poll( + async () => { + const roles = await getShareRoles(user) + const unknownRoles = Object.values(roles).filter((role) => !(role in requiredDynamicRoles)) + const dRoles = Object.values(defaultRoles) + for (const rId of Object.values(unknownRoles)) { + const role = dRoles.find((r) => r.id === rId) + if (role) { + dynamicRoles[role.displayName] = role.id + } + } + return Object.keys(dynamicRoles) + }, + // poll for half of the async timeout + { timeout: (config.timeout / 2) * 1000 } + ) + .toEqual(expect.arrayContaining(requiredDynamicRoles)) + + return dynamicRoles +} + +const getShareRoles = async (user: User): Promise => { + const response = await request({ + method: 'GET', + path: join('graph', 'v1beta1', 'roleManagement', 'permissions', 'roleDefinitions'), + user + }) + const roles = await response.json() + + for (const role of roles as Role[]) { + switch (role.displayName) { + case 'Can view': + if (role.rolePermissions[0].condition.includes('@Resource.Root')) { + dynamicRoles[`${role.displayName} (space)`] = role.id + } else { + dynamicRoles[role.displayName] = role.id + } + break + case 'Can edit without versions': + if (role.rolePermissions[0].condition.includes('@Resource.Root')) { + dynamicRoles[`${role.displayName} (space)`] = role.id + } else if (role.rolePermissions[0].condition.includes('@Resource.File')) { + dynamicRoles[`${role.displayName} (file)`] = role.id + } else { + dynamicRoles[role.displayName] = role.id + } + break + case 'Can edit': + if (role.rolePermissions[0].condition.includes('@Resource.Root')) { + dynamicRoles[`${role.displayName} (space)`] = role.id + } else if (role.rolePermissions[0].condition.includes('@Resource.File')) { + dynamicRoles[`${role.displayName} (file)`] = role.id + } else { + dynamicRoles[role.displayName] = role.id + } + break + default: + dynamicRoles[role.displayName] = role.id + } + } + return dynamicRoles +} + export const createShare = async ({ user, path, shareType, - shareWith, - role + role, + resourceType, + shareWith }: { user: User path: string shareType: string - shareWith?: string role: string + resourceType: ResourceType + shareWith?: string }): Promise => { const driveId: string = await getSpaceIdBySpaceName({ user, @@ -135,13 +257,12 @@ export const createShare = async ({ }) const recipientId: string = getRecipientId(shareType, shareWith) - let resourceType: ResourceType - if (path.includes('.')) { - resourceType = 'file' + let roleId: string + if (config.predefinedUsers) { + roleId = await getDynamicRoleIdByName(user, role, resourceType) } else { - resourceType = 'folder' + roleId = getRoleId(role, resourceType) } - const roleId: string = getRoleId(role, resourceType) const response = await request({ method: 'POST', @@ -235,7 +356,7 @@ export const createLinkShare = async ({ }) const roleType: string = linkShareRoles[role as keyof typeof linkShareRoles] - password = password === '%public%' ? securePassword : password + password = substitute(password) const response = await request({ method: 'POST', path: join('graph', 'v1beta1', 'drives', driveId, 'items', itemId, 'createLink'), @@ -248,6 +369,10 @@ export const createLinkShare = async ({ }) const responseData = (await response.json()) as { link: { webUrl: string } } + if (!responseData.link) { + throw new Error('Failed to create link share. \n' + JSON.stringify(responseData)) + } + const webUrl = responseData.link.webUrl const linksEnvironment: LinksEnvironment = new LinksEnvironment() linksEnvironment.createLink({ @@ -278,7 +403,7 @@ export const createSpaceLinkShare = async ({ }) const roleType: string = linkShareRoles[role as keyof typeof linkShareRoles] - password = password === '%public%' ? securePassword : password + password = substitute(password) const response = await request({ method: 'POST', diff --git a/tests/e2e/support/api/token/utils.ts b/tests/e2e/support/api/token/utils.ts index 282783d3aee..85af3331604 100644 --- a/tests/e2e/support/api/token/utils.ts +++ b/tests/e2e/support/api/token/utils.ts @@ -170,11 +170,3 @@ export const refreshAccessToken = async (user: User): Promise => { } }) } - -export const getUserIdFromToken = (user: User): string => { - const tokenEnvironment = TokenEnvironmentFactory() - const accessToken = tokenEnvironment.getToken({ user }).accessToken - const parsed = JSON.parse(atob(accessToken.split('.')[1])) - const userId = parsed['lg.i'].id.split('=')[1] - return userId -} diff --git a/tests/e2e/support/api/userSettings/index.ts b/tests/e2e/support/api/userSettings/index.ts index 1470c7aab3e..eca32dd0aef 100644 --- a/tests/e2e/support/api/userSettings/index.ts +++ b/tests/e2e/support/api/userSettings/index.ts @@ -1 +1 @@ -export { configureAutoAcceptShare, changeLanguage } from './settings' +export * from './settings' diff --git a/tests/e2e/support/api/userSettings/settings.ts b/tests/e2e/support/api/userSettings/settings.ts index 568d7e6a4c0..ec7a843fdd2 100644 --- a/tests/e2e/support/api/userSettings/settings.ts +++ b/tests/e2e/support/api/userSettings/settings.ts @@ -3,6 +3,75 @@ import join from 'join-path' import { checkResponseStatus, request } from '../http' import { User } from '../../types' +interface BundleSetting { + id: string + name: string + description: string + boolValue?: { + default?: boolean + label?: string + } +} + +interface Setting { + value: { + identifier: { + extension: string + bundle: string + setting: string + } + value: SettingValue + } +} + +interface SettingValue { + id: string + bundleId: string + settingId: string + accountUuid: string + resource: { + type: string + } + boolValue?: boolean + stringValue?: string + listValue?: { + values: { stringValue: string }[] + } + collectionValue?: { + values: { key: string; boolValue: boolean }[] + } +} + +const settings = { + 'auto-accept-shares': 'ec3ed4a3-3946-4efc-8f9f-76d38b12d3a9', + language: 'aa8cfbe5-95d4-4f7e-a032-c3c01f5f062f' +} + +const getSettingId = (setting: string): string => { + return settings[setting as keyof typeof settings] +} + +const getProfileBundleSettings = async (user: User) => { + const response = await request({ + method: 'POST', + path: join('api', 'v0', 'settings', 'bundle-get'), + body: JSON.stringify({ bundleId: '2a506de7-99bd-4f0d-994e-c38e72c28fd9' }), + user + }) + checkResponseStatus(response, 'Failed get profile bundle') + const { bundle } = (await response.json()) as { bundle: { settings: BundleSetting[] } } + return bundle.settings +} + +const getSingleProfileSetting = async (user: User, setting: string): Promise => { + const settings = await getProfileBundleSettings(user) + const settingObj = settings.find((s) => s.name === setting) + if (!settingObj) { + throw new Error(`Setting '${setting}' not found`) + } + return settingObj +} + export const configureAutoAcceptShare = async ({ user, state @@ -14,7 +83,7 @@ export const configureAutoAcceptShare = async ({ value: { accountUuid: 'me', bundleId: '2a506de7-99bd-4f0d-994e-c38e72c28fd9', - settingId: 'ec3ed4a3-3946-4efc-8f9f-76d38b12d3a9', + settingId: getSettingId('auto-accept-shares'), resource: { type: 'TYPE_USER' }, @@ -24,8 +93,8 @@ export const configureAutoAcceptShare = async ({ const response = await request({ method: 'POST', path: join('api', 'v0', 'settings', 'values-save'), - body: body, - user: user + body, + user }) checkResponseStatus(response, 'Failed while disabling auto-accept share') } @@ -41,7 +110,45 @@ export const changeLanguage = async ({ method: 'PATCH', path: join('graph', 'v1.0', 'me'), body: JSON.stringify({ preferredLanguage: language }), - user: user + user }) checkResponseStatus(response, 'Failed change language: ' + language) } + +export const getSettingValue = async ({ + user, + setting +}: { + user: User + setting: string +}): Promise => { + const body = JSON.stringify({ + accountUuid: 'me', + settingId: getSettingId(setting) + }) + + const response = await request({ + method: 'POST', + path: join('api', 'v0', 'settings', 'values-get-by-unique-identifiers'), + body, + user + }) + + if (response.status === 404) { + return null + } + + checkResponseStatus(response, 'Failed get setting: ' + setting) + const settingValue = (await response.json()) as Setting + return settingValue.value.value +} + +export const getAutoAcceptSharesValue = async (user: User): Promise => { + const settingValue = await getSettingValue({ user, setting: 'auto-accept-shares' }) + if (settingValue === null) { + // return default value + const defaultSetting = await getSingleProfileSetting(user, 'auto-accept-shares') + return defaultSetting.boolValue.default + } + return settingValue.boolValue +} diff --git a/tests/e2e/support/environment/sse.ts b/tests/e2e/support/environment/sse.ts index 380f1b05011..92e7754ce39 100644 --- a/tests/e2e/support/environment/sse.ts +++ b/tests/e2e/support/environment/sse.ts @@ -26,12 +26,13 @@ export const listenSSE = (baseUrl: string, user: User): Promise => { if (message.event === 'FatalError') { throw new Error(message.data) } - if (!Object.hasOwn(sseEventStore, user.id)) { - sseEventStore[user.id] = [] + const userKey = user.id.toLowerCase() + if (!Object.hasOwn(sseEventStore, userKey)) { + sseEventStore[userKey] = [] } // push event to the array // TODO: also store message.data if necessary - sseEventStore[user.id.toLowerCase()].push(message.event) + sseEventStore[userKey].push(message.event) }, onclose() { console.error('Closing SSE...') @@ -45,7 +46,7 @@ export const listenSSE = (baseUrl: string, user: User): Promise => { export const getSSEEvents = (user: User): Array => { // recent events should be evaluated first - return sseEventStore[user.id].reverse() + return sseEventStore[user.id.toLowerCase()].reverse() } export const closeSSEConnections = () => { diff --git a/tests/e2e/support/environment/userManagement.ts b/tests/e2e/support/environment/userManagement.ts index 64b4688b7fe..869e1bb1124 100644 --- a/tests/e2e/support/environment/userManagement.ts +++ b/tests/e2e/support/environment/userManagement.ts @@ -35,11 +35,12 @@ export class UsersEnvironment { } storeCreatedUser(key: string, user: User): User { + const userKey = key.toLowerCase() const store = config.federatedServer ? federatedUserStore : createdUserStore - if (store.has(key)) { - throw new Error(`user '${key}' already exists`) + if (store.has(userKey)) { + throw new Error(`user '${userKey}' already exists`) } - store.set(key, user) + store.set(userKey, user) return user } @@ -59,8 +60,12 @@ export class UsersEnvironment { throw new Error(`user '${userKey}' not found`) } createdUserStore.delete(userKey) - createdUserStore.set(user.id, user) - + // add to new key if the username is changed + if (userKey !== user.id) { + createdUserStore.set(user.id, user) + } else { + createdUserStore.set(userKey, user) + } return user } diff --git a/tests/e2e/support/objects/app-files/resource/actions.ts b/tests/e2e/support/objects/app-files/resource/actions.ts index e80307cdb85..776164f9fb1 100644 --- a/tests/e2e/support/objects/app-files/resource/actions.ts +++ b/tests/e2e/support/objects/app-files/resource/actions.ts @@ -17,7 +17,7 @@ import { editor, sidebar } from '../utils' import { environment, utils } from '../../../../support' import { config } from '../../../../config' import { File, Space } from '../../../types' -import { securePassword } from '../../../store' +import { substitute } from '../../../utils/substitute' const topbarFilenameSelector = '#app-top-bar-resource .oc-resource-name' const downloadFileButtonSingleShareView = '.oc-files-actions-download-file-trigger' @@ -62,7 +62,7 @@ const passwordProtectedFolderPasswordInput = '#input-folder-password' const resourceUploadButton = '#upload-menu-btn' const fileUploadInput = '#files-file-upload-input' const folderUploadInput = '#files-folder-upload-input' -const uploadInfoCloseButton = '#close-upload-bar-btn' +const uploadInfoCloseButton = '//button[contains(@id, "close-upload")]' const uploadErrorCloseButton = '.oc-notification-message-danger button[aria-label="Close"]' const filesBatchAction = '.files-app-bar-actions .oc-files-actions-%s-trigger' const pasteButton = '.paste-files-btn' @@ -118,13 +118,11 @@ const footerTextSelector = '//*[@data-testid="files-list-footer-info"]' const filesTableRowSelector = 'tbody tr' const itemsPerPageDropDownSelector = '.vs__actions' const filesPaginationNavSelector = '.files-pagination' -const uploadInfoSuccessLabelSelector = '.upload-bar-label.upload-bar-success' -const uploadInfoTitle = '.upload-bar-title' -const uploadInfoLabelSelector = '.upload-bar-label' -const pauseResumeUploadButton = '#pause-upload-bar-btn' -const pauseUploadButton = '#pause-upload-bar-btn[aria-label="Pause upload"]' -const resumeUploadButton = '#pause-upload-bar-btn[aria-label="Resume upload"]' -const cancelUploadButton = '#cancel-upload-bar-btn' +const uploadInfoLabel = '//*[text()="%s"]' +const pauseResumeUploadButton = '//button[contains(@id, "pause-upload")]' +const pauseUploadButton = '//button[contains(@id, "pause-upload") and @aria-label="Pause upload"]' +const resumeUploadButton = '//button[contains(@id, "pause-upload") and @aria-label="Resume upload"]' +const cancelUploadButton = '//button[contains(@id, "cancel-upload")]' const filesContextMenuAction = 'div[id^="context-menu-drop"] button.oc-files-actions-%s-trigger' const highlightedFileRowSelector = '#files-space-table tr.oc-table-highlighted' const emptyTrashbinButtonSelector = '.oc-files-actions-empty-trash-bin-trigger' @@ -154,10 +152,26 @@ export const clickResource = async ({ for (const name of paths) { // if resource name consists of single or double quotes, add an escape character const folder = name.replace(/'/g, "\\'").replace(/"/g, '\\"') - const resource = page.locator(util.format(resourceNameSelector, folder)) + const itemId = await resource.locator(fileRow).getAttribute('data-item-id') await Promise.all([ - page.waitForResponse((resp) => resp.request().method() === 'PROPFIND'), + page.waitForResponse((resp) => { + // check with resource name or file-id + if ( + ([207, 200].includes(resp.status()) && + ['PROPFIND', 'GET'].includes(resp.request().method()) && + resp.url().endsWith(encodeURIComponent(name))) || + resp.url().endsWith(itemId) || + resp.url().endsWith(encodeURIComponent(itemId)) + ) { + return true + } + // pass for PROPFIND request wiht 207 status code + // required for ocm shares where resource name and file-id are not determinable + if (resp.request().method() === 'PROPFIND' && resp.status() === 207) { + return true + } + }), resource.click() ]) } @@ -325,14 +339,29 @@ export const createPasswordProtectedFolder = async ({ resource: string password: string }): Promise => { - password = password === '%public%' ? securePassword : password + password = substitute(password) await page.locator(passwordProtectedFolderButton).click() await page.locator(passwordProtectedFolderNameInput).fill(resource) await page.locator(passwordProtectedFolderPasswordInput).fill(password) await Promise.all([ - page.waitForResponse((resp) => resp.status() === 201 && resp.request().method() === 'MKCOL'), - page.waitForResponse((resp) => resp.status() === 200 && resp.request().method() === 'POST'), - page.waitForResponse((resp) => resp.status() === 207 && resp.request().method() === 'PROPFIND'), + page.waitForResponse( + (resp) => + resp.url().endsWith(`/${encodeURIComponent(resource)}`) && + resp.status() === 201 && + resp.request().method() === 'MKCOL' + ), + page.waitForResponse( + (resp) => + resp.url().endsWith('/createLink') && + resp.status() === 200 && + resp.request().method() === 'POST' + ), + page.waitForResponse( + (resp) => + resp.url().endsWith(`${encodeURIComponent(resource)}.psec`) && + resp.status() === 207 && + resp.request().method() === 'PROPFIND' + ), page.locator(util.format(actionConfirmationButton, 'Create')).click() ]) } @@ -600,9 +629,7 @@ export const uploadLargeNumberOfResources = async (args: uploadResourceArgs): Pr const { page, resources } = args await performUpload(args) await page.locator(uploadInfoCloseButton).waitFor() - await expect(page.locator(uploadInfoSuccessLabelSelector)).toHaveText( - `${resources.length} items uploaded` - ) + await page.locator(util.format(uploadInfoLabel, `${resources.length} items uploaded`)).waitFor() } export const uploadResource = async (args: uploadResourceArgs): Promise => { @@ -633,6 +660,7 @@ export const dropUploadFiles = async (args: uploadResourceArgs): Promise = await page.locator(addNewResourceButton).waitFor() await utils.dragDropFiles(page, resources, filesView) + await page.locator(util.format(uploadInfoLabel, 'Upload complete')).waitFor() await page.locator(uploadInfoCloseButton).click() await Promise.all( resources.map((file) => @@ -680,14 +708,14 @@ export const resumeResourceUpload = async (page: Page): Promise => { await pauseResumeUpload(page) await page.locator(pauseUploadButton).waitFor() - await page.locator(uploadInfoSuccessLabelSelector).waitFor() + await page.locator(util.format(uploadInfoLabel, 'Upload complete')).waitFor() await page.locator(uploadInfoCloseButton).click() } export const cancelResourceUpload = async (page: Page): Promise => { await page.locator(cancelUploadButton).click() - await expect(page.locator(uploadInfoTitle)).toHaveText('Upload cancelled') - await expect(page.locator(uploadInfoLabelSelector)).toHaveText('0 items uploaded') + await page.locator(util.format(uploadInfoLabel, 'Upload cancelled')).waitFor() + await page.locator(util.format(uploadInfoLabel, '0 items uploaded')).waitFor() } /**/ @@ -1513,12 +1541,11 @@ export const searchResourceGlobalSearch = async ( await Promise.all([ page.waitForResponse((resp) => resp.status() === 207 && resp.request().method() === 'REPORT'), - page.locator(globalSearchInput).fill(keyword) + page.locator(globalSearchInput).fill(keyword), + expect(page.locator(globalSearchOptions)).toBeVisible(), + expect(page.locator(loadingSpinner)).not.toBeVisible() ]) - await expect(page.locator(globalSearchOptions)).toBeVisible() - await expect(page.locator(loadingSpinner)).not.toBeVisible() - if (pressEnter) { await page.keyboard.press('Enter') } diff --git a/tests/e2e/support/objects/app-files/share/collaborator.ts b/tests/e2e/support/objects/app-files/share/collaborator.ts index e625aae1cbe..0ec079169d9 100644 --- a/tests/e2e/support/objects/app-files/share/collaborator.ts +++ b/tests/e2e/support/objects/app-files/share/collaborator.ts @@ -65,8 +65,8 @@ export default class Collaborator { private static readonly sendInvitationButton = '#new-collaborators-form-create-button' public static readonly collaboratorRoleDropdownButton = '%s//button[contains(@class,"files-recipient-role-select-btn")]' - private static readonly collaboratorRoleItemSelector = - '%s//span[contains(@class,"roles-select-role-item")]/span[text()="%s"]' + private static readonly collaboratorRoleItemSelector = '%s//button[contains(@id, "%s")]' + private static readonly collaboratorRoleButton = '//button[contains(@id, "%s")]' public static readonly collaboratorEditDropdownButton = '%s//button[contains(@class,"collaborator-edit-dropdown-options-btn")]' private static readonly collaboratorUserSelector = '//*[@data-testid="collaborator-user-item-%s"]' @@ -100,7 +100,7 @@ export default class Collaborator { await collaboratorInputLocator.click() await Promise.all([ page.waitForResponse((resp) => resp.url().includes('users') && resp.status() === 200), - collaboratorInputLocator.fill(collaborator.id) + collaboratorInputLocator.fill(collaborator.displayName) ]) await collaboratorInputLocator.focus() await page.locator('.vs--open').waitFor() @@ -134,7 +134,7 @@ export default class Collaborator { const collaboratorNames = [] for (const collaborator of collaborators) { await Collaborator.addCollaborator({ page, collaborator }) - collaboratorNames.push(collaborator.collaborator.id) + collaboratorNames.push(collaborator.collaborator.displayName) } await Collaborator.setCollaboratorRole(page, role, resourceType) await Collaborator.sendInvitation(page, collaboratorNames) @@ -149,11 +149,11 @@ export default class Collaborator { ): Promise { if (!dropdownSelector) { dropdownSelector = Collaborator.newCollaboratorRoleDropdown - itemSelector = util.format(Collaborator.collaboratorRoleItemSelector, '') + itemSelector = Collaborator.collaboratorRoleButton } - await page.click(dropdownSelector) + await page.locator(dropdownSelector).click() - return await page.click(util.format(itemSelector, role)) + await page.locator(util.format(itemSelector, role)).click() } static async changeCollaboratorRole(args: CollaboratorArgs): Promise { diff --git a/tests/e2e/support/objects/app-files/utils/editor.ts b/tests/e2e/support/objects/app-files/utils/editor.ts index e68997331b7..7d53ac82daf 100644 --- a/tests/e2e/support/objects/app-files/utils/editor.ts +++ b/tests/e2e/support/objects/app-files/utils/editor.ts @@ -6,16 +6,17 @@ const texEditor = '#text-editor' const pdfViewer = '#pdf-viewer' const imageViewer = '.stage' -export const close = (page: Page): Promise => { - return Promise.all([ +export const close = async (page: Page): Promise => { + await Promise.all([ page.waitForURL(/.*\/files\/(spaces|shares|link|search)\/.*/), page.locator(closeTextEditorOrViewerButton).click() ]) } -export const save = async (page: Page): Promise => { - return await Promise.all([ +export const save = async (page: Page): Promise => { + await Promise.all([ page.waitForResponse((res) => res.request().method() === 'PUT' && res.status() === 204), + page.waitForResponse((res) => res.request().method() === 'PROPFIND' && res.status() === 207), page.locator(saveTextEditorOrViewerButton).click() ]) } diff --git a/tests/e2e/support/objects/runtime/session.ts b/tests/e2e/support/objects/runtime/session.ts index efc990dd094..62522962e6f 100644 --- a/tests/e2e/support/objects/runtime/session.ts +++ b/tests/e2e/support/objects/runtime/session.ts @@ -1,6 +1,7 @@ import { Page } from '@playwright/test' import { User } from '../../types' import { config } from '../../../config' +import { TokenEnvironmentFactory } from '../../environment' export class Session { #page: Page @@ -17,8 +18,8 @@ export class Session { } async idpSignIn(username: string, password: string): Promise { - await this.#page.locator('#oc-login-username').fill(username) - await this.#page.locator('#oc-login-password').fill(password) + await this.#page.locator('//input[@type="text" or @placeholder="Username"]').fill(username) + await this.#page.locator('//input[@type="password" or @placeholder="Password"]').fill(password) await this.#page.locator('button[type="submit"]').click() } @@ -31,7 +32,7 @@ export class Session { async login(user: User): Promise { const { id, password } = user - await Promise.all([ + const [response] = await Promise.all([ this.#page.waitForResponse( (resp) => resp.url().endsWith('/token') && @@ -40,6 +41,19 @@ export class Session { ), this.signIn(id, password) ]) + + if (config.predefinedUsers) { + const tokenRes = await response.json() + const tokenEnvironment = TokenEnvironmentFactory() + tokenEnvironment.setToken({ + user: { ...user }, + token: { + userId: user.id, + accessToken: tokenRes.access_token, + refreshToken: tokenRes.refresh_token + } + }) + } } async logout(): Promise { diff --git a/tests/e2e/support/store/link.ts b/tests/e2e/support/store/link.ts index 355cbc63080..e6cf0b8c8ab 100644 --- a/tests/e2e/support/store/link.ts +++ b/tests/e2e/support/store/link.ts @@ -9,4 +9,3 @@ export const roleDisplayText: Record = { 'Can edit': 'Anyone with the link can edit', 'Secret File Drop': 'Secret File drop' } -export const securePassword = 'Pwd:12345' diff --git a/tests/e2e/support/types.ts b/tests/e2e/support/types.ts index c656be096a4..ec7127181fb 100644 --- a/tests/e2e/support/types.ts +++ b/tests/e2e/support/types.ts @@ -30,7 +30,9 @@ export interface User { displayName: string password: string email: string + mail?: string role?: string + preferredLanguage?: string } export interface File { diff --git a/tests/e2e/support/utils/substitute.ts b/tests/e2e/support/utils/substitute.ts index 00f4a196797..731f9a9b5ed 100644 --- a/tests/e2e/support/utils/substitute.ts +++ b/tests/e2e/support/utils/substitute.ts @@ -1,10 +1,10 @@ import { UsersEnvironment } from '../../support/environment' import { User } from '../../support/types' -export const getValue = (pattern): string => { +const getValue = (pattern): string => { switch (pattern) { case '%public%': - return 'Pwd:12345' + return 'Pwd:12345567' default: pattern = pattern.replace(/%/g, '') const [type, userKey, property] = pattern.split('_') @@ -28,9 +28,12 @@ export const getValue = (pattern): string => { } export const substitute = (text: string): string => { + if (!text) { + return text + } + const regex = /%[A-Za-z0-9_-]+%/g const matches = text.match(regex) - console.log(matches) if (matches) { for (const match of matches) { const value = getValue(match)